/* ═══════════════════════════════════════════════════════════════════
   TREASURYFLOW — DASHBOARD PORTAL · PAGE STYLE LAYER
   backend/app/static/portal.css
   ───────────────────────────────────────────────────────────────────
   Created by dashboard-migration increment I1 (token bridge + CSS
   extraction). See .agents/initiatives/dashboard-migration-plan-2026-05.md
   §I1 + §5.

   WHAT THIS FILE IS
   The consolidation of the 8 inline <style> blocks that used to live in
   portal.html (formerly ~lines 33, 2523, 2906, 3064, 3249, 16567,
   16653 #byoe-styles, 17024 #cell-rail-styles). They are concatenated here
   VERBATIM, in document order, so the cascade is byte-for-byte preserved —
   I1 is pure plumbing with no intended visual change.

   LOAD ORDER (portal.html <head>):
     1. /static/vendor/design-tokens.css   — the v4 token :root + reset
     2. /static/vendor/components.css      — the v4 .tf-* component library
     3. /static/portal.css   ← THIS FILE   — the portal page layer; loads
                                             LAST → wins the cascade, exactly
                                             as the inline blocks did before.

   I1 ADDED EXACTLY TWO THINGS (everything else is verbatim-extracted):
     · The v4 TOKEN NAME-BRIDGE :root below.
     · The GLOBAL-LEAK NEUTRALIZER block.
   Both are deleted/folded away in increment I7.

   DO NOT hand-add component styling here during I2–I7 — new components are
   authored in website/src/components.css (the canonical library). This file
   is the portal-specific page/composition layer only.
   ═══════════════════════════════════════════════════════════════════ */

/* ═══════════════════════════════════════════════════════════════════
   PORTAL-LOCAL PAGE TOKENS  (dashboard-migration I8.1 / I8.2)
   ───────────────────────────────────────────────────────────────────
   The I1 v4-token NAME-BRIDGE :root that used to sit here was DELETED in
   increment I8.2 (dashboard-migration-plan §11). The bridge redefined ~43
   legacy token names (--teal, --text-3, --border, …) in terms of v4
   tokens so the portal's then-19.9k lines kept resolving while the v4
   migration was additive. I8.1 re-tokenized portal.css's rules and I8.2
   re-tokenized portal.html's ~1,005 inline `style="…var(--…)…"` references
   onto v4 token names directly — so nothing referenced the bridge any
   more and it was removed. The portal now runs on ONE token vocabulary:
   the canonical v4 design system in /static/vendor/design-tokens.css.

   What remains here is the genuine portal page-layer: two tokens that
   have no single equivalent in the v4 design system. portal.css is, per
   this file's header, "the portal-specific page/composition layer", and a
   page layer legitimately owns page-specific tokens.

   · --gradient-primary — the teal→navy hero gradient. v4 carries exactly
     one gradient token (--ai-gradient, the violet AI sub-brand); the
     portal's brand gradient has no v4 home, so it lives here as a literal.
   · --ease-spring — the portal's tap/overshoot easing. v4's --ease-spring
     is cubic-bezier(0.34, 1.4, 0.64, 1) — a GENTLER 1.4 overshoot. The
     portal shipped (pre-I1, and live today) with a 1.56 overshoot. I8 is a
     zero-visual-change teardown, so the portal keeps its 1.56 literal here
     rather than inheriting v4's 1.4. (Adopting v4's --ease-spring is a
     deliberate motion-design change for a future increment, not I8.)
   ─────────────────────────────────────────────────────────────────── */
:root {
  --gradient-primary: linear-gradient(135deg, #0F766E 0%, #1E3A8A 100%);
  --ease-spring:      cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* ═══════════════════════════════════════════════════════════════════
   GLOBAL-LEAK NEUTRALIZER  (dashboard-migration I1)
   ───────────────────────────────────────────────────────────────────
   design-tokens.css ships a system-wide reset (its ~lines 438-503): bare
   `html`, `body`, `a`, `::selection`, `:focus-visible`, `.num` rules.
   Linked before this file, those element rules would LEAK new styling onto
   the portal — e.g. body{font-size:15px;line-height:1.6;font-feature-
   settings:...}, a{color:teal;text-decoration:none}. I1 must be invisible,
   so this block re-asserts the portal's pre-I1 element defaults. portal.css
   loads LAST, so these win. Pure additive — no portal rule is removed.

   Transitional: as the portal body migrates onto v4 in I2–I7, these
   neutralizers are revisited and removed where the v4 default is wanted.
   ─────────────────────────────────────────────────────────────────── */
html {
  /* v4 html{} sets scroll-behavior:smooth — the portal had instant scroll. */
  scroll-behavior: auto;
}
body {
  /* v4 body{} sets font-size:15px / line-height:1.6 / font-feature-settings.
     The pre-I1 portal body declared none of these → user-agent defaults.
     Re-assert UA defaults so global text metrics do not shift. (The portal
     CSS uses 0 rem units, so this is belt-and-braces, not load-bearing.) */
  font-size: medium;
  line-height: normal;
  font-feature-settings: normal;
}
/* The portal has no bare `a {}` rule — its 14 unclassed <a> tags render
   with the user-agent link style. design-tokens.css's
   `a{color:var(--text-link);text-decoration:none}` would restyle them.
   `revert` rolls these properties back to the user-agent stylesheet,
   ignoring all author rules, so the unclassed anchors render exactly as
   pre-I1. Classed anchors (.nav-brand, .auth-demo-link, …) carry a class
   and are untouched by this rule. */
a:not([class]) {
  color: revert;
  text-decoration: revert;
  transition: revert;
}
/* The portal has no ::selection rule. Revert design-tokens.css's teal
   selection highlight to the user-agent default. */
::selection {
  background-color: revert;
  color: revert;
}
/* design-tokens.css resets `img, svg, video, canvas { max-width:100%;
   display:block }`. The portal has 156 inline <svg> icons and NO author
   rule setting their `display`, so pre-I1 they were `display:inline` (the
   UA default) — many sit inline beside text (sidebar items, buttons, KPI
   labels). Forcing them to `display:block` drops each onto its own line /
   shifts baseline alignment → a portal-wide reflow.
   Re-assert the pre-I1 layout with EXPLICIT values (not `revert` — `revert`
   on `<svg>` `display` is browser-dependent and did not reliably override
   the v4 reset in testing). `display:inline` + `max-width:none` is exactly
   the UA default the portal relied on. The portal's own `.<class> svg
   { width/height }` rules are class-scoped (higher specificity) and never
   set `display`, so they still size the icons on top of this. */
img, svg, video, canvas {
  display: inline;
  max-width: none;
}
/* design-tokens.css resets `button, input, select, textarea { font-family:
   inherit; font-size: inherit }`. The portal has NO bare form-control reset,
   so pre-I1 every <button>/<input>/<select>/<textarea> used the user-agent
   CONTROL font (the platform's compact UI font, NOT Inter) and the UA's
   shrunk control font-size. `font-family: inherit` switches controls to
   Inter; Inter is wider than the compact control font, so multi-button rows
   that fit pre-I1 now overflow and wrap — a portal-wide reflow that is most
   visible at mobile widths (e.g. the AI-briefing "13-week forecast" / "Export
   to Excel" button pair wrapping at 402px).
   `revert` rolls font-family + font-size on form controls back to the UA
   stylesheet, restoring the pre-I1 metrics. Controls with their own explicit
   .class font rules (most portal buttons) are higher-specificity and keep
   their styling — this only catches controls that relied on the UA default. */
button, input, select, textarea {
  font-family: revert;
  font-size: revert;
}

/* ═══════════════════════════════════════════════════════════════════
   ▼▼▼  VERBATIM-EXTRACTED PORTAL STYLE BLOCKS  ▼▼▼
   The 8 former inline <style> blocks of portal.html, concatenated in their
   original document order. Not one rule below is modified — this is a
   mechanical extraction. The cascade among these blocks is identical to
   when they were inline in portal.html.
   ═══════════════════════════════════════════════════════════════════ */

/* ─── former inline <style> block 1 — body of the portal.html head
       <style> AFTER its :root (the :root is replaced by the bridge above).
       Was portal.html ~L95-2285. ──────────────────────────────────── */
    * { margin:0; padding:0; box-sizing:border-box; }
    html { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; overflow-x: hidden; }
    body {
      font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      background: var(--surface-page);
      color: var(--ink-900);
      min-height: 100vh;
      /* Horizontal-overflow clipping lives on <html> (above), NOT here.
         `overflow-x: hidden` on <body> forces body's computed overflow-y to
         `auto`, turning <body> into a scroll container that never actually
         scrolls — which silently breaks `position: sticky` on .tf-sidebar:
         the sidebar scrolled away with the content instead of staying put,
         making tab-switching hard (founder, 2026-06-03; empirically verified
         that moving the clip to <html> restores stickiness). Do NOT re-add
         overflow-x to <body>. */
    }

    /* Glow orbs removed — light theme uses clean white surface */
    body::before, body::after { display: none; }

    /* ── Nav (top header bar) ──
       v3.6: Switched header to LIGHT surface to match sidebar + body.
       Was rgba(6,9,24,0.75) near-black with dark slate text tokens =
       unreadable. Now solid white with dark text. */
    .tf-app__topbar {
      position: sticky; top: 0; z-index: 100;
      padding: 0 32px;
      height: 56px;
      display: flex; align-items: center; justify-content: space-between;
      background: #FFFFFF;
      border-bottom: 1px solid var(--border-subtle);
    }
    .nav-brand { display: flex; align-items: center; gap: 10px; text-decoration: none; }
    .nav-brand span { font-weight: 700; font-size: 17px; color: var(--ink-900); letter-spacing: -0.3px; }
    .nav-right { display: flex; align-items: center; gap: 6px; }
    .nav-user { font-size: 13px; color: var(--ink-500); }
    .nav-icon-btn {
      width: 34px; height: 34px; box-sizing: border-box;
      border-radius: var(--radius-md); border: 1px solid var(--border-card);
      background: rgba(148,163,184,0.04); color: var(--ink-500); cursor: pointer;
      display: flex; align-items: center; justify-content: center;
      transition: all 0.2s var(--ease-out);
    }
    .nav-icon-btn:hover {
      background: rgba(15,118,110,0.08); color: var(--accent);
      border-color: rgba(15,118,110,0.25);
      transform: translateY(-1px);
    }
    /* Primary action button — elevated peer to .nav-icon-btn for the Add Bank
       conversion moment. Same height (34px) so the band reads as one line, but
       teal-tinted fill + accent border + tightened type signals "this is the
       move" without screaming. References: Mercury "Add account", Stripe
       "+ New", Linear "+ Issue" — premium, dense, restrained. */
    .nav-add-bank-btn {
      display: inline-flex; align-items: center; gap: 7px;
      height: 34px; padding: 0 14px 0 12px; box-sizing: border-box;
      border-radius: var(--radius-md);
      border: 1px solid rgba(15,118,110,0.45);
      background: linear-gradient(180deg, rgba(15,118,110,0.10), rgba(15,118,110,0.06));
      color: #0B5E58;
      font-family: inherit; font-size: 12.5px; font-weight: 600; letter-spacing: -0.1px;
      cursor: pointer; white-space: nowrap;
      box-shadow: 0 1px 2px rgba(15,118,110,0.08), inset 0 1px 0 rgba(255,255,255,0.4);
      transition: background 0.18s var(--ease-out), border-color 0.18s var(--ease-out),
                  box-shadow 0.18s var(--ease-out), transform 0.12s var(--ease-out),
                  color 0.18s var(--ease-out);
    }
    .nav-add-bank-btn svg { width: 15px; height: 15px; flex-shrink: 0; opacity: 0.92; }
    .nav-add-bank-btn:hover {
      background: linear-gradient(180deg, rgba(15,118,110,0.18), rgba(15,118,110,0.12));
      border-color: rgba(15,118,110,0.7);
      color: #084842;
      box-shadow: 0 4px 14px rgba(15,118,110,0.18), inset 0 1px 0 rgba(255,255,255,0.5);
      transform: translateY(-1px);
    }
    .nav-add-bank-btn:active {
      transform: translateY(0) scale(0.97);
      box-shadow: inset 0 1px 2px rgba(15,118,110,0.20);
    }
    .nav-add-bank-btn:focus-visible {
      outline: 2px solid var(--accent); outline-offset: 2px;
    }
    .nav-cmd-btn {
      display: flex; align-items: center; gap: 5px;
      height: 34px; padding: 0 12px; box-sizing: border-box;
      border-radius: var(--radius-md); border: 1px solid var(--border-card);
      background: rgba(148,163,184,0.04); color: var(--ink-500); cursor: pointer;
      font-size: 12px; font-weight: 600;
      transition: all 0.2s var(--ease-out);
    }
    .nav-cmd-btn:hover {
      background: rgba(15,118,110,0.08); color: var(--accent);
      border-color: rgba(15,118,110,0.25);
    }
    /* Matt R12 (2026-05-11): Customize Dashboard top-nav pill. Visible on
       every tab — Matt couldn't find the per-tile dots OR the previous
       collections-only button. This is the obvious affordance, always
       reachable. Same visual weight as Command Palette button. */
    .nav-customize-btn {
      display: inline-flex; align-items: center; gap: 6px;
      height: 34px; padding: 0 12px; box-sizing: border-box;
      border-radius: var(--radius-md);
      border: 1px solid var(--border-card);
      background: rgba(148,163,184,0.04); color: var(--ink-500);
      font-family: inherit; font-size: 12px; font-weight: 600;
      cursor: pointer; white-space: nowrap;
      transition: all 0.2s var(--ease-out);
    }
    .nav-customize-btn svg { flex-shrink: 0; opacity: 0.85; }
    .nav-customize-btn:hover {
      background: rgba(15,118,110,0.08); color: var(--accent);
      border-color: rgba(15,118,110,0.25);
    }
    .nav-customize-btn:active { transform: scale(0.97); }
    .nav-customize-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
    /* On phones (≤640px) the nav bar already loses tab text — drop the
       Customize label too. Keep the icon as a tap target. */
    @media (max-width: 640px) {
      .nav-customize-btn span { display: none; }
      .nav-customize-btn { padding: 0 8px; }
    }
    /* On very small phones (≤480px) hide entirely — Account Settings
       in the avatar dropdown has a Customize Dashboard item too. */
    @media (max-width: 480px) {
      .nav-customize-btn { display: none; }
    }
    /* ── Avatar Dropdown ── */
    .avatar-wrap { position: relative; }
    .avatar-dropdown {
      position: absolute; top: calc(100% + 10px); right: 0; z-index: 800;
      background: #FFFFFF; border: 1px solid rgba(15,23,42,0.08);
      border-radius: var(--radius-lg);
      box-shadow: 0 4px 12px rgba(15,23,42,0.06), 0 16px 48px rgba(15,23,42,0.12);
      min-width: 200px; overflow: hidden;
      opacity: 0; visibility: hidden; transform: translateY(6px) scale(0.97);
      transition: opacity 0.18s ease, transform 0.18s ease, visibility 0.18s;
    }
    .avatar-dropdown.open { opacity: 1; visibility: visible; transform: translateY(0) scale(1); }
    .avatar-dropdown__header {
      padding: 14px 16px 10px;
      border-bottom: 1px solid var(--border-default);
    }
    .avatar-dropdown__name { font-weight: 700; font-size: 13px; color: var(--ink-900); }
    .avatar-dropdown__email { font-size: 11px; color: var(--ink-500); margin-top: 2px; }
    .avatar-dropdown__item {
      display: flex; align-items: center; gap: 10px;
      padding: 10px 16px; font-size: 13px; color: var(--ink-500);
      cursor: pointer; transition: background 0.15s; border: none;
      background: none; width: 100%; text-align: left; font-family: inherit;
    }
    .avatar-dropdown__item:hover { background: rgba(15,23,42,0.04); color: var(--ink-900); }
    .avatar-dropdown__item--danger { color: #DC2626; }
    .avatar-dropdown__item--danger:hover { background: rgba(220,38,38,0.06); color: var(--cash-down); }
    .avatar-dropdown__item svg { width: 15px; height: 15px; flex-shrink: 0; }

    .nav-avatar {
      width: 34px; height: 34px; box-sizing: border-box; border-radius: 50%;
      background: var(--accent); color: white;
      display: flex; align-items: center; justify-content: center;
      font-size: 12px; font-weight: 800; letter-spacing: .02em;
      flex-shrink: 0; transition: transform 0.2s;
    }
    .nav-avatar:hover { transform: scale(1.08); }
    /* Minimal hero bar — replaces the large static banner */
    .hero-bar {
      display: flex; align-items: center; justify-content: space-between;
      padding: 8px 0 4px; gap: 16px; flex-wrap: wrap;
    }
    .hero-bar__greeting {
      /* Sans, bold — the established UI voice (the Move-5 serif greeting was
         reverted 2026-06-05 per founder: didn't like the serif on the greeting). */
      font-size: var(--text-lg);
      font-weight: var(--weight-bold);
      letter-spacing: var(--tracking-tight);
      color: var(--text-primary);
    }
    .hero-bar__sub { font-size: 13px; color: var(--ink-500); margin-top: 2px; }
    /* 2026-06-01 (founder): the firm greeting's clickable attention count. A
       quiet link (dotted underline) that resolves to the brand accent on
       hover/focus — an affordance, not a shouting button. */
    .hero-sub__attn-link {
      cursor: pointer; color: inherit;
      border-bottom: 1px dashed var(--border-default);
      transition: color 0.15s var(--ease-out), border-color 0.15s var(--ease-out);
    }
    .hero-sub__attn-link:hover, .hero-sub__attn-link:focus-visible {
      color: var(--accent); border-bottom-color: var(--accent); outline: none;
    }
    /* Last-synced freshness indicator */
    .sync-freshness {
      display: flex; align-items: center; gap: 6px;
      font-size: 11px; color: var(--ink-500); padding: 2px 0 10px;
    }
    .sync-dot {
      width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0;
      background: var(--ink-500);
    }
    .sync-dot--fresh { background: var(--cash-up); animation: sync-pulse 3s ease-in-out infinite; }
    .sync-dot--stale { background: #f59e0b; }
    .sync-dot--old { background: var(--cash-down); }
    @keyframes sync-pulse { 0%,100% { opacity:1; } 50% { opacity:0.4; } }

    /* ── Buttons ── */
    .btn {
      display: inline-flex; align-items: center; justify-content: center; gap: 8px;
      padding: 10px 22px; border: none; border-radius: var(--radius-pill);
      font-family: inherit; font-size: 14px; font-weight: 600;
      cursor: pointer; transition: all 0.2s var(--ease-out); text-decoration: none;
      white-space: nowrap;
    }
    .btn-primary {
      /* Brand: primary actions are SOLID teal (v4 one-accent / Excel-spirit
         restraint). The teal->navy --gradient-primary fade was reverted 2026-06-05
         per founder (inconsistent vs the solid teal CTAs). One brand button. */
      background: var(--accent); color: var(--accent-contrast, #fff);
      box-shadow: 0 4px 20px rgba(15,118,110,0.25);
    }
    .btn-primary:hover {
      transform: translateY(-2px);
      box-shadow: 0 8px 32px rgba(15,118,110,0.35);
    }
    .btn-primary:active { transform: translateY(0) scale(0.97); }
    .btn-primary:disabled, .btn-ghost:disabled {
      opacity: 0.45; cursor: not-allowed; pointer-events: none;
    }
    .btn-ghost {
      background: transparent; color: var(--ink-500);
      border: 1px solid var(--border-card);
    }
    .btn-ghost:hover {
      border-color: var(--accent); color: var(--ink-900);
      background: rgba(15,118,110,0.05);
    }
    .btn-ghost:active { transform: scale(0.97); }
    .btn-download {
      background: linear-gradient(135deg, #22c55e, #16a34a);
      color: white; font-size: 16px; padding: 16px 36px;
      box-shadow: 0 4px 20px rgba(34,197,94,0.25);
    }
    .btn-download:hover { transform: translateY(-2px); box-shadow: 0 8px 30px rgba(34,197,94,0.35); }
    .btn-export-nav {
      background: linear-gradient(135deg, #22c55e, #16a34a); color: white;
      padding: 8px 18px; font-size: 13px; font-weight: 700;
      border-radius: var(--radius-pill);
      border: none; cursor: pointer; transition: all 0.2s var(--ease-out);
      display: inline-flex; align-items: center; gap: 6px;
      box-shadow: 0 3px 12px rgba(34,197,94,0.25);
    }
    .btn-export-nav:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(34,197,94,0.35); }
    @media (max-width: 600px) { .export-label { display: none; } .btn-export-nav { padding: 8px 10px; } }
    .btn-sso {
      background: var(--surface-page); color: var(--ink-900);
      border: 1px solid var(--border-default); padding: 14px 24px;
    }
    .btn-sso:hover { border-color: var(--accent); }

    /* ── Focus & Active States ── */
    .btn:focus-visible, .tab-btn:focus-visible, .tf-nav-item:focus-visible,
    .tf-bottom-nav__item:focus-visible, .nav-icon-btn:focus-visible,
    .ai-send:focus-visible, .onramp-card__btn:focus-visible {
      outline: 2px solid var(--accent); outline-offset: 2px;
    }
    .tab-btn:active { transform: scale(0.97); }
    .tf-bottom-nav__item:active { transform: scale(0.92); }
    .nav-icon-btn:active { transform: scale(0.9); }
    .btn-download:active { transform: translateY(0) scale(0.97); }
    .btn-export-nav:active { transform: translateY(0) scale(0.97); }

    /* ── Auth Screen ── */
    .auth-overlay {
      position: fixed; inset: 0; z-index: 200;
      display: flex; align-items: center; justify-content: center;
      background: var(--surface-page);
      background-image:
        linear-gradient(rgba(148,163,184,0.025) 1px, transparent 1px),
        linear-gradient(90deg, rgba(148,163,184,0.025) 1px, transparent 1px);
      background-size: 60px 60px;
    }
    #liquidity-canvas, #liquidity-canvas-2 {
      display: none;
    }
    #liquidity-bloom, #liquidity-bloom-2 { display: none; }
    #liquidity-vignette, #liquidity-vignette-2 { display: none; }
    #liquidity-vignette, #liquidity-vignette-2 {
      position: absolute; inset: 0; z-index: 1; pointer-events: none;
      background: radial-gradient(ellipse at 50% 50%, transparent 38%, rgba(255,255,255,0.6) 100%);
    }
    @keyframes card-glow {
      0%, 100% { box-shadow: 0 0 0 1px rgba(15,118,110,0.12), 0 40px 80px rgba(0,0,0,0.7); }
      50%       { box-shadow: 0 0 0 1px rgba(15,118,110,0.30), 0 0 60px rgba(15,118,110,0.06), 0 40px 80px rgba(0,0,0,0.7); }
    }
    @keyframes card-enter {
      from { opacity: 0; transform: translateY(22px); }
      to   { opacity: 1; transform: translateY(0); }
    }
    @keyframes logo-pulse {
      0%, 100% { box-shadow: 0 0 32px rgba(15,118,110,0.10); }
      50%       { box-shadow: 0 0 52px rgba(15,118,110,0.22); }
    }
    @keyframes bloom-breathe {
      0%, 100% { opacity: 0.6; transform: translate(-50%, -50%) scale(1); }
      50%       { opacity: 1;   transform: translate(-50%, -50%) scale(1.12); }
    }
    .auth-card {
      position: relative; z-index: 2;
      text-align: center; max-width: 400px; width: 100%;
      padding: 52px 40px 36px;
      background: var(--bg-card);
      border: 1px solid var(--border-default);
      border-radius: var(--radius-lg);
      box-shadow: var(--shadow-lg);
      animation: card-enter 0.7s cubic-bezier(0.16,1,0.3,1) both;
    }
    #liquidity-bloom, #liquidity-bloom-2 {
      position: absolute; z-index: 2; pointer-events: none;
      width: 680px; height: 680px; border-radius: 50%;
      background: radial-gradient(circle, rgba(15,118,110,0.05) 0%, rgba(15,118,110,0.03) 40%, transparent 65%);
      top: 50%; left: 50%;
      transform: translate(-50%, -50%);
      animation: bloom-breathe 6s ease-in-out infinite;
    }
    .auth-logo-wrap {
      display: inline-flex; align-items: center; justify-content: center;
      width: 64px; height: 64px; border-radius: var(--radius-base);
      background: var(--accent);
      margin: 0 auto 22px;
    }
    .auth-wordmark {
      font-size: 26px; font-weight: 800; letter-spacing: -0.5px;
      color: var(--text-primary);
      margin-bottom: 6px;
    }
    .auth-tagline {
      font-size: 13px; color: var(--ink-500); margin: 0 0 26px;
      letter-spacing: 0.2px; line-height: 1.5;
    }
    .auth-sep {
      width: 36px; height: 1px;
      background: linear-gradient(90deg, transparent, rgba(15,118,110,0.5), transparent);
      margin: 0 auto 26px;
    }
    /* CDO 2026-05-08 — auth screen visual hierarchy fix.
       Previous: Google btn was white-on-white (subtle), demo link was teal-tinted
       (visually stronger). First-time visitors read demo as the primary action and
       skipped signup, eroding conversion. Now: Google is solid noir → reads as
       primary CTA; demo is dashed-ghost → reads as a secondary "explore first"
       option. Mercury / Stripe pattern. */
    .auth-google-btn {
      width: 100%; font-size: 15px; font-weight: 600; padding: 14px 20px;
      background: #0B1020;
      border: 1px solid #0B1020;
      border-radius: var(--radius-base); color: #FFFFFF;
      transition: background 0.2s, border-color 0.2s, transform 0.2s, box-shadow 0.2s;
      display: inline-flex; align-items: center; justify-content: center; gap: 10px;
      cursor: pointer; font-family: inherit;
      box-shadow: 0 1px 3px rgba(15,23,42,0.12);
    }
    .auth-google-btn:hover {
      background: #1F2937;
      border-color: #1F2937;
      transform: translateY(-1px);
      box-shadow: 0 6px 18px rgba(15,23,42,0.20);
    }
    .auth-google-btn:active {
      transform: translateY(0) scale(0.98);
    }
    /* Demo shortcut — tertiary. Quieter than the primary/secondary auth buttons
       (transparent bg, secondary text), but uses the same shape/padding/font as
       sibling .auth-provider-btn so the stack reads as one button family. */
    .auth-demo-link {
      display: inline-flex; align-items: center; justify-content: center; gap: 10px;
      margin-top: 12px; padding: 13px 18px;
      width: 100%; font-size: 14.5px; font-weight: 600;
      color: var(--text-secondary, #475569); text-decoration: none;
      border: 1px solid var(--border-card, #E2E8F0); border-radius: var(--radius-base);
      background: transparent;
      white-space: nowrap;
      transition: background 0.18s, border-color 0.18s, color 0.18s, transform 0.18s, box-shadow 0.18s;
    }
    .auth-demo-link:hover {
      background: rgba(15,118,110,0.03);
      border-color: rgba(15,118,110,0.45);
      color: var(--text-primary, #0B1020);
      transform: translateY(-1px);
      box-shadow: 0 4px 12px rgba(15,23,42,0.08);
    }
    .auth-demo-link:active { transform: translateY(0) scale(0.99); }
    .auth-demo-link:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

    /* ── Trial-signup screen (rebuilt 2026-05-16) ─────────────────────────
       The front door. Uses the v4 Ledger tokens (--teal #0F766E, noir
       #0B1020). Three sign-in options; reads as a trial start, not a login. */
    .auth-card--signup {
      max-width: 412px;
      padding: 44px 38px 30px;
      text-align: center;
    }
    .auth-headline {
      font-size: 23px; font-weight: 800; letter-spacing: -0.5px;
      color: var(--text-primary); margin: 18px 0 8px; line-height: 1.25;
    }
    .auth-subhead {
      font-size: 13.5px; color: var(--ink-500); margin: 0 0 4px;
      line-height: 1.6; letter-spacing: 0.1px;
    }
    .auth-subhead strong { color: var(--text-secondary, #475569); font-weight: 700; }
    .auth-plan-badge {
      display: inline-flex; align-items: center; gap: 6px;
      margin-top: 14px; padding: 5px 14px;
      font-size: 11.5px; font-weight: 700; letter-spacing: 0.2px;
      color: var(--accent); background: rgba(15,118,110,0.07);
      border: 1px solid rgba(15,118,110,0.22); border-radius: var(--radius-pill);
    }
    .auth-options {
      display: flex; flex-direction: column; gap: 9px;
      margin-bottom: 4px;
    }
    .auth-provider-btn {
      width: 100%; font-size: 14.5px; font-weight: 600;
      padding: 13px 18px; border-radius: var(--radius-base);
      display: inline-flex; align-items: center; justify-content: center; gap: 10px;
      cursor: pointer; font-family: inherit;
      transition: background 0.18s, border-color 0.18s, transform 0.18s, box-shadow 0.18s;
    }
    .auth-provider-btn:disabled { opacity: 0.55; pointer-events: none; }
    /* Primary = solid noir (Google + the email-form submit). Mercury/Stripe pattern. */
    .auth-provider-btn--primary {
      background: #0B1020; border: 1px solid #0B1020; color: #FFFFFF;
      box-shadow: 0 1px 3px rgba(15,23,42,0.12);
    }
    .auth-provider-btn--primary:hover {
      background: #1F2937; border-color: #1F2937;
      transform: translateY(-1px); box-shadow: 0 6px 18px rgba(15,23,42,0.20);
    }
    .auth-provider-btn--primary:active { transform: translateY(0) scale(0.99); }
    /* Secondary = white card w/ border (the email-signup opener) */
    .auth-provider-btn--secondary {
      background: #FFFFFF; border: 1px solid var(--border-card, #E2E8F0);
      color: var(--text-primary, #0B1020);
      box-shadow: 0 1px 2px rgba(15,23,42,0.04);
    }
    .auth-provider-btn--secondary:hover {
      border-color: rgba(15,118,110,0.45);
      background: rgba(15,118,110,0.03);
      transform: translateY(-1px); box-shadow: 0 4px 12px rgba(15,23,42,0.08);
    }
    .auth-provider-btn--secondary:active { transform: translateY(0) scale(0.99); }
    .auth-provider-btn:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

    /* Inline email-signup form */
    .auth-email-form {
      display: flex; flex-direction: column; gap: 7px; text-align: left;
      animation: card-enter 0.3s cubic-bezier(0.16,1,0.3,1) both;
    }
    .auth-field-label {
      font-size: 11.5px; font-weight: 700; color: var(--text-secondary, #475569);
      letter-spacing: 0.3px; margin: 6px 0 1px;
    }
    .auth-field {
      width: 100%; font-family: inherit; font-size: 14px;
      padding: 11px 13px; border-radius: var(--radius-base);
      border: 1px solid var(--border-card, #E2E8F0); background: #FFFFFF;
      color: var(--text-primary, #0B1020);
      transition: border-color 0.15s, box-shadow 0.15s;
    }
    .auth-field::placeholder { color: #94A3B8; }
    .auth-field:focus {
      outline: none; border-color: var(--accent);
      box-shadow: 0 0 0 3px rgba(15,118,110,0.10);
    }
    .auth-field.auth-field--error { border-color: var(--cash-down, #DC2626); }
    .auth-email-back {
      background: none; border: none; cursor: pointer; font-family: inherit;
      font-size: 12px; font-weight: 600; color: var(--ink-500);
      padding: 8px 0 2px; align-self: center;
    }
    .auth-email-back:hover { color: var(--accent); }

    /* ── Email-signup success panel ── */
    .auth-email-success {
      display: flex; flex-direction: column; text-align: center;
      animation: card-enter 0.3s cubic-bezier(0.16,1,0.3,1) both;
    }
    .auth-success-check {
      width: 44px; height: 44px; border-radius: 50%;
      display: flex; align-items: center; justify-content: center;
      margin: 0 auto 12px; color: #fff;
      background: var(--accent);
    }
    .auth-success-title {
      font-size: 18px; font-weight: 700; color: var(--text-primary, #0B1020);
      margin: 0 0 6px; letter-spacing: -0.2px;
    }
    .auth-success-sub {
      font-size: 12.5px; line-height: 1.55; color: var(--text-secondary, #475569);
      margin: 0 0 16px;
    }
    .auth-key-label {
      font-size: 11px; font-weight: 700; color: var(--text-secondary, #475569);
      letter-spacing: 0.4px; text-transform: uppercase; text-align: left; margin-bottom: 5px;
    }
    .auth-key-row {
      display: flex; align-items: stretch; gap: 7px; margin-bottom: 8px;
    }
    .auth-key-value {
      flex: 1; min-width: 0; font-family: 'SF Mono', Menlo, Consolas, monospace;
      font-size: 12px; padding: 10px 12px; border-radius: var(--radius-base);
      border: 1px solid var(--border-card, #E2E8F0); background: #F8FAFC;
      color: var(--text-primary, #0B1020); text-align: left;
      overflow-x: auto; white-space: nowrap; word-break: break-all;
    }
    .auth-key-copy {
      flex-shrink: 0; font-family: inherit; font-size: 12px; font-weight: 600;
      padding: 0 14px; border-radius: var(--radius-base); cursor: pointer;
      border: 1px solid var(--accent); background: rgba(15,118,110,0.06);
      color: var(--accent); transition: background 0.15s;
    }
    .auth-key-copy:hover { background: rgba(15,118,110,0.12); }
    .auth-key-copy.auth-key-copy--done {
      background: var(--accent); color: #fff; border-color: var(--accent);
    }
    .auth-key-note {
      font-size: 11px; line-height: 1.5; color: var(--ink-500);
      margin: 0 0 14px; text-align: left;
    }

    /* Footer + returning-user note */
    .auth-footer { padding-top: 16px; margin-top: 18px; border-top: 1px solid rgba(148,163,184,0.12); }
    .auth-returning {
      font-size: 11.5px; color: var(--ink-500); margin: 0 0 12px; line-height: 1.5;
    }
    .auth-footer-links { text-align: center; }
    .auth-footer-links a {
      font-size: 11px; color: var(--ink-500); text-decoration: none;
    }
    .auth-footer-links a:hover { color: var(--accent); }
    .auth-footer-dot { color: var(--border-default); margin: 0 8px; }
    /* In the rebuilt layout the demo link sits below the email form — tighten its top gap. */
    .auth-card--signup .auth-demo-link { margin-top: 14px; }
    @media (max-width: 460px) {
      .auth-card--signup { padding: 36px 24px 26px; }
      .auth-headline { font-size: 21px; }
    }

    /* ── Offline banner ── */
    .offline-banner {
      position: fixed; top: 0; left: 0; right: 0; z-index: 99999;
      padding: 10px 16px; text-align: center; font-size: 13px; font-weight: 600;
      display: flex; align-items: center; justify-content: center; gap: 8px;
      animation: slideDown 0.3s var(--ease-out);
    }
    .offline-banner--warn { background: var(--warn); color: #1a1a1a; }
    .offline-banner--ok { background: var(--cash-up); color: #fff; }
    .offline-banner__dismiss {
      background: none; border: none; cursor: pointer; font-size: 18px; line-height: 1;
      margin-left: 12px; opacity: 0.7; color: inherit;
    }
    .offline-banner__dismiss:hover { opacity: 1; }
    @keyframes slideDown { from { transform: translateY(-100%); } to { transform: translateY(0); } }
    /* ── Scenario save/compare ── */
    .saved-scenarios-panel { margin-top: 16px; }
    .saved-scenario-card {
      background: var(--bg-card); border: 1px solid var(--border-default); border-radius: 10px;
      padding: 14px 16px; margin-bottom: 8px; display: flex; align-items: center; justify-content: space-between; gap: 12px;
    }
    .saved-scenario-card__info { flex: 1; min-width: 0; }
    .saved-scenario-card__name { font-size: 13px; font-weight: 600; color: var(--ink-900); margin-bottom: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .saved-scenario-card__meta { font-size: 11px; color: var(--ink-500); }
    .saved-scenario-card__actions { display: flex; gap: 6px; flex-shrink: 0; }
    .saved-scenario-card__check { display: flex; align-items: center; flex-shrink: 0; cursor: pointer; }
    .saved-scenario-card__check input { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; margin: 0; }
    .saved-scenario-card__check input:disabled { cursor: not-allowed; opacity: 0.4; }
    .saved-scenario-card.is-selected { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
    .scenario-compare-table { width: 100%; border-collapse: collapse; margin-top: 12px; }
    .scenario-compare-table th, .scenario-compare-table td { padding: 8px 12px; text-align: left; font-size: 13px; border-bottom: 1px solid var(--border-default); }
    .scenario-compare-table th { color: var(--ink-500); font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: 0.4px; }
    .auth-trust {
      display: flex; align-items: center; justify-content: center; gap: 5px;
      font-size: 11px; color: var(--ink-500); margin: 14px 0 22px;
      letter-spacing: 0.3px;
    }
    .auth-trust svg { opacity: 0.5; flex-shrink: 0; }

    /* ── Layout ── */
    .container { max-width: 1200px; margin: 0 auto; padding: 28px 28px; position: relative; z-index: 1; min-width: 0; }
    /* Mobile flex-overflow guard — without min-width:0 a flex child sizes to its
       content's intrinsic min-width, which on portal pages with long-line cards
       blows past the 402px viewport. */
    .tf-app__main > .container { min-width: 0; max-width: 100%; }
    /* When sidebar is visible, container fills the full app-main width */
    @media (min-width: 1024px) {
      .container { max-width: 100%; margin: 0; padding: 28px 36px; }
    }
    /* Hide dashboard-only chrome when viewing sub-tabs (Forecast, Reports, etc.)
       #treasury-cockpit + #multi-entity-view (the Cash Position hero / consolidated
       "All companies" home) and #demo-ai-briefing-card (the ?demo=true briefing)
       are dashboard-only chrome too — they live in .container as siblings of the
       tab panels, so without this they persist on every sub-tab, pushing the
       newly-activated panel below the fold and making sidebar nav look dead
       (the cockpit was simply omitted when it was introduced). */
    .container.tab-sub #briefing-card,
    .container.tab-sub #demo-ai-briefing-card,
    .container.tab-sub #hero-banner,
    .container.tab-sub #treasury-cockpit,
    .container.tab-sub #multi-entity-view,
    .container.tab-sub #sync-freshness-row,
    .container.tab-sub #trial-banner,
    .container.tab-sub #alerts-banner,
    .container.tab-sub #insight-cards,
    .container.tab-sub #kpi-grid { display: none !important; }
    /* Custom scrollbar to match dark theme */
    ::-webkit-scrollbar { width: 6px; height: 6px; }
    ::-webkit-scrollbar-track { background: transparent; }
    ::-webkit-scrollbar-thumb { background: rgba(148,163,184,0.15); border-radius: 3px; }
    ::-webkit-scrollbar-thumb:hover { background: rgba(148,163,184,0.25); }

    /* ── Section overline — matches landing page pill label ── */
    .section-overline {
      display: inline-block; font-size: 11px; font-weight: 700;
      text-transform: uppercase; letter-spacing: 1.8px;
      color: var(--accent); margin-bottom: 8px;
      padding: 4px 14px; border: 1px solid rgba(15,118,110,0.2);
      border-radius: var(--radius-pill);
      background: rgba(15,118,110,0.05);
    }

    /* ── Hero Banner ── */
    .hero-banner {
      text-align: center; padding: 48px 28px;
      /* v4 single-accent wash — second stop was a residual v3-blue
         rgba(0,113,227); collapsed to teal so the glow stays inside the
         one-accent rule (same call as card-glow / #liquidity-bloom). */
      background: linear-gradient(160deg, rgba(15,118,110,0.06) 0%, rgba(15,118,110,0.025) 100%);
      backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
      border: 1px solid rgba(15,118,110,0.15); border-radius: var(--radius-xl);
      margin-bottom: 32px; position: relative; overflow: hidden;
    }
    .hero-banner::before {
      content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px;
      background: linear-gradient(90deg, transparent, rgba(15,118,110,0.3), transparent);
    }
    .hero-banner h1 { font-size: 30px; font-weight: 800; letter-spacing: -1px; margin-bottom: 8px; }
    .hero-banner p { color: var(--ink-500); font-size: 15px; margin-bottom: 24px; }
    .hero-banner .download-hint {
      font-size: 13px; color: var(--ink-500); margin-top: 12px;
    }

    /* ── KPI Grid ── */
    .kpi-grid {
      display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      gap: 16px; margin-bottom: 32px;
    }
    /* Matt R6a (2026-04-30) — main dashboard KPI strip locked to 3 columns
       per his deck. Use ID selector so this beats the inline auto-fit
       fallback on the element. The class-level media queries below at
       768/480 still apply for narrow screens (cascading by ID specificity
       inside the same media context). */
    #kpi-grid { grid-template-columns: repeat(3, 1fr); }
    @media (max-width: 768px) {
      #kpi-grid { grid-template-columns: repeat(2, 1fr); gap: 12px; }
    }
    @media (max-width: 480px) {
      #kpi-grid { grid-template-columns: 1fr; gap: 10px; }
    }
    /* ── Legacy `.kpi-card*` rule family — DELETED in dashboard-migration
       I9.6. The v3-era generic KPI-tile component (`.kpi-card`,
       `.kpi-card__label/__value/__sub`, the `--danger/--warning/--healthy`
       state modifiers, `--text/--compact`, the `kpi-danger-pulse`
       keyframe) was superseded by the v4 `.tf-kpi` (components.css + the
       I3 portal.css block). I3/I4/I8.4/I9.2/I9.4 migrated every consumer
       to `.tf-kpi`; I9.6 migrated the last one — the company-mode Cash-tab
       runway tile's JS state toggles — to `.tf-kpi--danger/--warning/
       --healthy` + `.tf-kpi__value--text`. A full grep (markup `class=`,
       JS `classList`, JS template strings) confirmed zero remaining
       `.kpi-card*` consumer before deletion. The `.kpi-shimmer` skeleton
       below is a SEPARATE class (still live — 13 uses) and is kept. */
    /* Shimmer skeleton — replaces "--" while data loads */
    @keyframes kpi-shimmer { 0%,100% { opacity: 0.35; } 50% { opacity: 0.7; } }
    .kpi-shimmer[data-loaded="false"] {
      color: transparent !important;
      background: linear-gradient(90deg, var(--border-default) 25%, rgba(148,163,184,0.2) 50%, var(--border-default) 75%);
      background-size: 200% 100%; border-radius: 6px; animation: kpi-shimmer 1.4s ease-in-out infinite;
      min-width: 80px; display: inline-block;
    }

    /* ── Forecast chart animations ── */
    .fc-path-main { stroke-dasharray: 1500; stroke-dashoffset: 1500; animation: fcDraw 1.4s ease forwards; }
    @keyframes fcDraw { to { stroke-dashoffset: 0; } }
    .fc-dot { opacity: 0; animation: fcFade 0.3s ease forwards; }
    @keyframes fcFade { to { opacity: 1; } }

    /* ── Section panels & their header slots ─────────────────────────
       The legacy `.data-section` base rule family — DELETED in
       dashboard-migration I9.6. The v3-era generic section-panel
       component (`.data-section` + `::before/::after/:hover`) was
       superseded by the v4 `.tf-card` (components.css + the I3 portal.css
       block). I3–I8.4/I9.2/I9.4 migrated every panel to `.tf-card`;
       I9.5 migrated the AI tab (the last bare `.data-section`). A full
       grep confirmed zero remaining `.data-section` consumer. The
       `.tf-card__*` header/title/subtitle/actions slot rules below are
       the v4 vocabulary (renamed from `.data-section__*` in I9.5). */
    /* ── Card header / title / subtitle / actions slots ──────────────
       dashboard-migration I9.5: renamed from the legacy `.data-section__*`
       vocabulary to the v4 `.tf-card__*` vocabulary. The portal's
       `.tf-card__header` here (0,1,0, loaded after components.css)
       OVERRIDES the components.css `.tf-card__header` — which carries a
       hairline `border-bottom` divider; the portal's card headers have no
       under-title divider, so this portal rule restores the no-divider
       look (this is exactly why I8.4 could not delete the legacy
       `data-section__*` layer — the v4 `tf-card__header` is a different
       design; the portal re-authors it). `.tf-card__subtitle` /
       `.tf-card__actions` are portal-local slots — v4 components.css has
       no `__subtitle` / `__actions`, only `__header` / `__title` /
       `__footer`. */
    .tf-card__header {
      display: flex; align-items: center; justify-content: space-between;
      margin-bottom: 16px; gap: 12px; flex-wrap: wrap;
      /* portal card headers have no under-title divider — override the
         components.css `.tf-card__header` border-bottom + its padding. */
      padding-bottom: 0; border-bottom: none;
    }
    .tf-card__header > div:first-child { min-width: 0; flex: 1 1 200px; }
    .tf-card__title { font-size: var(--text-md); font-weight: var(--weight-bold); letter-spacing: var(--tracking-tight); }
    .tf-card__subtitle { font-size: var(--text-xs); color: var(--text-secondary); margin-top: var(--space-1); line-height: var(--lh-snug); }
    .tf-card__actions { display: flex; gap: 8px; flex-wrap: wrap; }
    /* Mobile: stack header rows so subtitle gets full width before action buttons */
    @media (max-width: 640px) {
      .tf-card__header { flex-direction: column; align-items: stretch; }
      /* In column direction the `flex: 1 1 200px` on the title block (set for
         the desktop row layout) reads its 200px basis as HEIGHT — ballooning
         the header to ~200px with the title pinned to the top and a dead gap
         below. Reset to content height so the title/subtitle and the action
         buttons sit flush with the normal 12px gap between them. */
      .tf-card__header > div:first-child { flex: 0 0 auto; }
      .tf-card__actions { justify-content: flex-start; }
    }

    /* ── Visual Weight Tiers — primary / secondary / tertiary cards ──
       dashboard-migration I9.5: renamed from `.data-section--*` to the v4
       `.tf-card--*` vocabulary. The card SURFACE (white / sunken bg) is
       composed by the I3 `.tf-card` block + the `.tf-card.tf-card--primary`
       / `--tertiary` rules near it; THESE rules carry only what the
       surface rules do not — the per-tier title-size tuning + the
       primary-tier hover accent. */
    .tf-card--primary {
      border-color: rgba(15,118,110,0.2);
    }
    .tf-card--primary:hover { border-color: rgba(15,118,110,0.35); }
    .tf-card--secondary { /* inherits base */ }
    .tf-card--tertiary {
      border-color: var(--border-default);
    }
    .tf-card--tertiary .tf-card__title { /* Inherits standard card title styling */ }
    .tf-card--tertiary:hover { border-color: var(--border-strong); }

    /* Firm-dashboard KPI strip — pin every tile to ONE height. MOVE 3
       (2026-06-04) fixed the layout: the strip is now a FIXED 4-col grid with
       the Total-cash hero spanning 2 columns (clean 4+4, no orphan — see the
       inline <style> in portal.html). A CSS grid still stretches tiles only
       WITHIN a row, so a row whose tallest tile has a 2-line `.tf-kpi__label`
       and/or 2-line `.tf-kpi__verdict` would leave its row-mates short;
       min-height at the worst case keeps every tile (the wide hero included)
       level with its row.
       164px = tallest possible tile at desktop padding:
         66 chrome (48 padding-block + 2 border + 16 row-gap) + 28 two-line
         label + 33 value (--num-md x 1.1) + 34 two-line verdict (13px x
         --lh-snug) = 161; 163.3 measured with Inter -> 164.
       Label caps at 2 lines, verdict is `-webkit-line-clamp:2` — a true
       ceiling; re-measure only if KPI type tokens change. `margin-bottom:
       auto` on the value banks the slack so verdicts still bottom-align. */
    .bk-kpi-tile { min-height: 164px; display: flex; flex-direction: column; }
    .bk-kpi-tile .tf-kpi__label { min-height: 32px; align-items: flex-start; }
    .bk-kpi-tile .tf-kpi__value { margin-bottom: auto; }

    /* ════════════════════════════════════════════════════════════════════
       TEAM & ACCESS panel (per-staff RBAC, 2026-05-24) — owner-only.
       v4 Ledger tokens only. Mirrors the roster table rhythm + the tag-editor
       popover. No new color / radius / easing / font. Staff scope = teal
       presence, never a new hue.
       ════════════════════════════════════════════════════════════════════ */
    .bk-team-table-wrap { overflow-x: auto; }
    .bk-team-table { width: 100%; border-collapse: collapse; font-size: 13px; }
    .bk-team-table thead th {
      text-align: left; color: var(--ink-500); font-weight: 500; font-size: 11px;
      text-transform: uppercase; letter-spacing: 0.5px; padding: 8px 10px;
      border-bottom: 1px solid var(--border-default); white-space: nowrap;
    }
    .bk-team-table thead th:last-child { text-align: right; }
    .bk-team-row { border-bottom: 1px solid var(--ink-150); }
    .bk-team-row td { padding: 11px 10px; vertical-align: middle; }
    .bk-team-row td:last-child { text-align: right; white-space: nowrap; }
    .bk-team-row--owner { background: var(--accent-softer); }
    .bk-team-row--revoked { opacity: 0.55; }
    .bk-team-cell-who { display: flex; align-items: center; gap: 10px; }
    .bk-team-avatar {
      width: 30px; height: 30px; border-radius: 50%; background: var(--accent-soft);
      color: var(--accent-strong); font-weight: 800; font-size: 12px;
      display: inline-flex; align-items: center; justify-content: center;
      flex-shrink: 0; letter-spacing: -0.3px; border: 1px solid rgba(15,118,110,0.18);
    }
    .bk-team-who { display: inline-flex; flex-direction: column; min-width: 0; }
    .bk-team-who__name { font-weight: 700; letter-spacing: -0.1px; color: var(--ink-900); }
    .bk-team-youtag { font-weight: 500; color: var(--ink-400); font-size: 12px; }
    .bk-team-who__email { font-size: 11px; color: var(--ink-500); margin-top: 1px; }
    .bk-team-pill {
      display: inline-block; font-size: 10px; font-weight: 800; padding: 3px 9px;
      border-radius: 11px; text-transform: uppercase; letter-spacing: 0.7px;
    }
    .bk-team-pill--owner { background: var(--accent-soft); color: var(--accent-strong); border: 1px solid rgba(15,118,110,0.28); }
    .bk-team-pill--staff { background: var(--ink-50); color: var(--ink-500); border: 1px solid var(--ink-200); }
    .bk-team-pill--invited { background: var(--warn-soft); color: var(--warn-text); border: 1px solid var(--warn-border); }
    .bk-team-access {
      display: inline-flex; align-items: center; gap: 5px; cursor: pointer;
      font-weight: 600; color: var(--accent); border: none; background: none;
      border-radius: var(--radius-sm); padding: 4px 8px; font-size: 13px;
      font-family: inherit; transition: background 0.12s var(--ease-out);
    }
    .bk-team-access:hover { background: rgba(15,118,110,0.08); }
    .bk-team-access__chev { opacity: 0.6; font-size: 15px; line-height: 1; }
    .bk-team-access--all { color: var(--ink-900); font-weight: 600; cursor: default; padding-left: 0; }
    .bk-team-access--all:hover { background: transparent; }
    .bk-team-access--none { color: var(--ink-500); }
    .bk-team-meta { font-size: 12px; color: var(--ink-500); font-variant-numeric: tabular-nums; }
    .bk-team-actions { white-space: nowrap; }
    .bk-team-ghost {
      padding: 4px 10px; font-size: 11px; background: transparent;
      border: 1px solid var(--border-default); color: var(--ink-500);
      border-radius: var(--radius-sm); cursor: pointer; font-family: inherit;
      transition: all 0.15s var(--ease-out);
    }
    .bk-team-ghost:hover { border-color: var(--accent); color: var(--accent); }
    .bk-team-ghost--accent { color: var(--accent); border-color: rgba(15,118,110,0.4); }
    .bk-team-danger {
      background: transparent; color: var(--cash-down-text); border: none;
      padding: 4px 8px; font-size: 11px; cursor: pointer; font-family: inherit;
    }
    .bk-team-danger:hover { text-decoration: underline; }
    /* Empty-state (owner, 0 staff) */
    .bk-team-empty { padding: 30px 16px 8px; text-align: center; }
    .bk-team-empty__icon {
      width: 52px; height: 52px; border-radius: 14px; margin: 0 auto 14px;
      background: linear-gradient(135deg,rgba(15,118,110,0.08),rgba(15,118,110,0.03));
      display: inline-flex; align-items: center; justify-content: center; color: #0F766E;
    }
    .bk-team-empty__title { font-weight: 800; color: var(--ink-900); font-size: 17px; letter-spacing: -0.3px; margin-bottom: 6px; }
    .bk-team-empty__body { color: var(--ink-500); max-width: 420px; margin: 0 auto; font-size: 13.5px; line-height: 1.55; }

    /* Assignment popover (reuses the tag-editor scaffold; these are its inner
       list-item + footer styles). */
    .bk-team-pop-title {
      font-size: 11px; font-weight: 800; color: var(--ink-500); letter-spacing: 0.5px;
      text-transform: uppercase; margin-bottom: 8px;
    }
    .bk-team-pop-search {
      width: 100%; padding: 7px 10px; border: 1px solid var(--border-default);
      border-radius: var(--radius-sm); font-size: 12.5px; font-family: inherit;
      margin-bottom: 6px; outline: none; box-sizing: border-box;
    }
    .bk-team-pop-item {
      display: flex; align-items: center; gap: 9px; padding: 8px 9px;
      border-radius: var(--radius-sm); cursor: pointer;
    }
    .bk-team-pop-item:hover { background: rgba(15,118,110,0.04); }
    .bk-team-pop-item.sel { background: rgba(15,118,110,0.10); }
    .bk-team-pop-item input { accent-color: var(--accent); width: 15px; height: 15px; flex-shrink: 0; }
    .bk-team-pop-name { font-weight: 600; color: var(--ink-900); flex: 1; min-width: 0; }
    .bk-team-pop-item.sel .bk-team-pop-name { color: var(--accent-strong); }
    .bk-team-pop-cash { font-size: 11px; color: var(--ink-500); font-variant-numeric: tabular-nums; }
    .bk-team-pop-foot {
      display: flex; align-items: center; justify-content: space-between;
      border-top: 1px solid var(--ink-150); margin-top: 8px; padding-top: 10px; gap: 10px;
    }
    .bk-team-pop-count { font-size: 12px; color: var(--ink-500); font-weight: 600; }
    .bk-team-pop-count b { color: var(--ink-900); }
    .bk-team-pop-link { background: none; border: none; color: var(--accent); font-weight: 600; font-size: 12px; cursor: pointer; font-family: inherit; }

    /* Mobile ≤640px: table → stacked cards; tap targets ≥44px. */
    @media (max-width: 640px) {
      .bk-team-table thead { display: none; }
      .bk-team-table, .bk-team-table tbody, .bk-team-row, .bk-team-row td { display: block; width: 100%; }
      .bk-team-row {
        border: 1px solid var(--ink-150); border-radius: var(--radius-md);
        margin-bottom: 10px; padding: 12px 12px 8px;
      }
      .bk-team-row--owner { background: var(--accent-softer); }
      .bk-team-row td { padding: 4px 0; text-align: left !important; }
      .bk-team-row td:last-child { text-align: left; white-space: normal; }
      /* In card mode the per-cell "—" placeholders (Last active everywhere in
         v1, and the owner row's Actions) read as floating dashes — hide them.
         The access chip + role pill carry the meaning; dashes add noise. */
      .bk-team-meta { display: none; }
      .bk-team-row--owner .bk-team-actions { display: none; }
      .bk-team-actions { display: flex; gap: 8px; margin-top: 6px; }
      .bk-team-access, .bk-team-ghost, .bk-team-danger {
        min-height: 44px; display: inline-flex; align-items: center;
      }
      .bk-team-empty { padding: 24px 8px 6px; }
      /* Assignment popover → bottom sheet */
      .bk-team-popover {
        position: fixed !important; left: 0 !important; right: 0 !important;
        bottom: 0 !important; top: auto !important; width: 100% !important;
        max-width: 100% !important; max-height: 72vh !important;
        border-radius: var(--radius-lg) var(--radius-lg) 0 0 !important;
        box-shadow: 0 -8px 40px rgba(15,23,42,0.22) !important;
      }
      .bk-team-popover .bk-team-pop-item { min-height: 44px; }
      .bk-team-popover .bk-team-pop-foot {
        position: sticky; bottom: 0; background: #fff; margin: 8px -14px 0;
        padding: 12px 14px; border-top: 1px solid var(--ink-150);
      }
    }

    /* The legacy `.data-section--drawer` modifier + the `.bk-table` rule
       family — DELETED in dashboard-migration I9.6. They styled the
       bookkeeper client-drawer interior (the 2026-05-13 CDO D-19 flat-
       section design); I9.3 rewrote that drawer onto the v4
       `.bk-drawer-section` flat-group composition + the v4 `.tf-table`,
       so `data-section--drawer` / `bk-table*` have zero consumer. */


    table { width: 100%; border-collapse: collapse; }
    thead tr { background: rgba(148,163,184,0.03); }
    th {
      text-align: left; padding: 12px 16px;
      font-size: 10px; font-weight: 700; color: var(--ink-500);
      text-transform: uppercase; letter-spacing: 1px;
      border-bottom: 1px solid var(--border-card);
      position: sticky; top: 0; background: var(--bg-card);
      white-space: nowrap;
    }
    td {
      padding: 12px 16px; font-size: 13px;
      border-bottom: 1px solid rgba(148,163,184,0.04);
      transition: background 0.15s var(--ease-out);
      vertical-align: middle;
    }
    tbody tr:nth-child(even) td { background: rgba(148,163,184,0.015); }
    tr:hover td { background: rgba(15,118,110,0.04) !important; }
    tr:last-child td { border-bottom: none; }
    .text-green { color: var(--cash-up); }
    .text-red { color: var(--cash-down); }
    .text-amber { color: var(--warn); }
    .text-muted { color: var(--ink-500); }

    /* ── Custom Info Tooltip ── */
    .tf-tip {
      display: inline-flex; align-items: center; justify-content: center;
      width: 15px; height: 15px; border-radius: 50%;
      background: rgba(148,163,184,0.1); border: 1px solid rgba(148,163,184,0.2);
      color: var(--ink-500); font-size: 9px; font-weight: 800; font-style: normal;
      cursor: pointer; flex-shrink: 0; vertical-align: middle;
      transition: background 0.15s, border-color 0.15s, color 0.15s;
      user-select: none; line-height: 1; margin-left: 3px;
    }
    .tf-tip:hover, .tf-tip.active {
      background: rgba(15,118,110,0.15); border-color: rgba(15,118,110,0.4); color: var(--accent);
    }
    .tf-tooltip-box {
      position: fixed; z-index: 9999;
      background: rgba(12,15,36,0.98); border: 1px solid rgba(15,118,110,0.2);
      border-radius: 10px; padding: 12px 16px;
      font-size: 13px; color: rgba(255,255,255,0.92); max-width: 280px; line-height: 1.55;
      box-shadow: 0 12px 40px rgba(0,0,0,0.6), 0 0 0 1px rgba(15,118,110,0.05);
      pointer-events: none; opacity: 0; transform: translateY(5px) scale(0.97);
      transition: opacity 0.15s ease, transform 0.15s ease;
    }
    .tf-tooltip-box.visible {
      opacity: 1; transform: translateY(0) scale(1); pointer-events: auto;
    }
    /* Firm roster (firm shell, NOT drilled into a client): the header "Add bank"
       has no client to attach to — a firm's banks are connected per client via
       "Add a client". Hide it here; it re-shows when acting-as-client. */
    body.firm-shell-mode:not(.act-as-client-mode) #btn-add-bank { display: none !important; }

    /* ── Tabs ── */
    .tab-bar {
      display: flex; gap: 4px; padding: 4px;
      background: var(--bg-card);
      backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
      border-radius: var(--radius-lg);
      border: 1px solid var(--border-card); margin-bottom: 24px;
      overflow-x: auto;
    }
    .tab-btn {
      flex: 1; padding: 10px 10px;
      background: none; border: none; color: var(--ink-500);
      font-family: inherit; font-size: 13px; font-weight: 600;
      cursor: pointer; border-radius: var(--radius-md);
      transition: all 0.2s var(--ease-out);
      white-space: nowrap;
    }
    .tab-btn:hover { color: var(--ink-500); background: rgba(148,163,184,0.07); }
    .tab-btn.active {
      background: rgba(15,118,110,0.12); color: var(--accent);
      box-shadow: inset 0 0 0 1px rgba(15,118,110,0.2);
    }
    .tab-panel { display: none; animation: fadeUp 0.3s ease; }
    .tab-panel.active { display: block; }
    @keyframes fadeUp { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }

    /* Dashboard section toggle chips */
    .dash-toggle-chip {
      display: inline-flex; align-items: center; gap: 6px;
      padding: 5px 12px; border-radius: var(--radius-pill); cursor: pointer;
      font-size: 12px; font-weight: 600; color: var(--ink-500);
      border: 1px solid var(--border-card); background: rgba(148,163,184,0.04);
      transition: all 0.2s var(--ease-out); user-select: none;
    }
    .dash-toggle-chip:hover { background: rgba(148,163,184,0.09); border-color: var(--accent); }
    .dash-toggle-chip input[type=checkbox] { width: 13px; height: 13px; accent-color: var(--accent); cursor: pointer; }
    .dash-toggle-chip:has(input:checked) { border-color: rgba(15,118,110,0.35); background: rgba(15,118,110,0.08); color: var(--accent); }

    .empty-state {
      text-align: center; padding: 48px 24px; color: var(--ink-500);
      font-size: 14px; line-height: 1.6;
      background: rgba(148,163,184,0.03); border-radius: var(--radius-lg);
      border: 1px dashed rgba(148,163,184,0.12);
    }
    .empty-state-icon { font-size: 32px; margin-bottom: 8px; }
    .onramp-card {
      display: flex; flex-direction: column; align-items: center;
      padding: 52px 36px; text-align: center;
      /* v4 single-accent wash — v3-blue second stop collapsed to teal. */
      background: linear-gradient(160deg, rgba(15,118,110,0.05) 0%, rgba(15,118,110,0.02) 100%);
      backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
      border: 1px dashed rgba(15,118,110,0.2); border-radius: var(--radius-xl);
      animation: fadeUp 0.35s ease;
    }
    .onramp-card__icon {
      width: 60px; height: 60px; border-radius: var(--radius-lg);
      background: rgba(15,118,110,0.1); border: 1px solid rgba(15,118,110,0.2);
      display: flex; align-items: center; justify-content: center;
      margin-bottom: 20px; color: var(--accent);
    }
    .onramp-card__title { font-size: 18px; font-weight: 700; letter-spacing: -0.3px; color: var(--ink-900); margin-bottom: 8px; }
    .onramp-card__desc { font-size: 14px; color: var(--ink-500); max-width: 360px; line-height: 1.65; margin-bottom: 24px; }
    .onramp-card__btn {
      padding: 11px 26px; border-radius: var(--radius-pill); border: none;
      background: var(--accent); color: #fff; font-size: 14px; font-weight: 700;
      font-family: inherit; cursor: pointer;
      transition: transform 0.2s var(--ease-out), box-shadow 0.2s;
      box-shadow: 0 4px 16px rgba(15,118,110,0.25);
    }
    .onramp-card__btn:hover { transform: translateY(-2px); box-shadow: 0 8px 24px rgba(15,118,110,0.35); }
    .onramp-card__btn--ghost {
      background: transparent; border: 1px solid var(--border-card);
      color: var(--ink-500); margin-left: 10px;
      box-shadow: none;
    }
    .onramp-card__btn--ghost:hover { border-color: var(--accent); color: var(--accent); box-shadow: none; transform: none; }

    /* ── Loading ── */
    .spinner {
      width: 24px; height: 24px;
      border: 3px solid var(--border-default);
      border-top-color: var(--accent);
      border-radius: 50%; animation: spin 0.6s linear infinite;
    }
    @keyframes spin { to { transform: rotate(360deg); } }
    @keyframes aiPulse { 0%, 80%, 100% { opacity: 0.3; transform: scale(0.8); } 40% { opacity: 1; transform: scale(1); } }
    .loading-state {
      display: flex; flex-direction: column; align-items: center;
      padding: 48px; gap: 16px;
    }
    .loading-state p { color: var(--ink-500); font-size: 14px; }

    /* ── Footer ── */
    .portal-footer {
      text-align: center;
      padding: 32px 28px 40px;          /* more top air + real bottom breathing room as last child of .container */
      font-size: 12px; color: var(--ink-500);
      border-top: 1px solid var(--border-subtle); margin-top: 64px;  /* clears the last tab panel with bank-grade separation */
      letter-spacing: 0.2px;
    }
    /* Trust-pills row sits ABOVE the © legal line inside the footer (2026-06-01,
       CDO) — a whisper hairline (same --border-subtle token as the footer's top
       rule, not a heavier divider) demotes the legal caption beneath the pills,
       mirroring the email _email_shell footer. */
    .portal-footer .tf-dashboard-trust-band { padding-bottom: 14px; border-bottom: 1px solid var(--border-subtle); }
    .portal-footer a { color: var(--accent); text-decoration: none; }
    .portal-footer a:hover { text-decoration: underline; }

    /* ── Onboarding Banner ── */
    .onboarding-banner {
      text-align: center; padding: 48px 24px;
      /* v4 single-accent wash — v3-blue second stop collapsed to teal. */
      background: linear-gradient(160deg, rgba(15,118,110,0.07), rgba(15,118,110,0.03));
      backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
      border: 1px dashed rgba(15,118,110,0.25); border-radius: var(--radius-xl);
      margin-bottom: 32px;
    }
    .onboarding-banner h2 { font-size: 22px; font-weight: 800; letter-spacing: -0.5px; margin: 16px 0 8px; }
    .onboarding-banner p { color: var(--ink-500); font-size: 14px; max-width: 480px; margin: 0 auto 20px; line-height: 1.5; }

    /* ── AI Chat ── */
    .ai-section { display: flex; flex-direction: column; min-height: 400px; }
    .ai-suggested { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 16px; }
    .ai-pill {
      background: rgba(15,118,110,0.07); border: 1px solid rgba(15,118,110,0.18);
      color: var(--accent); padding: 8px 16px; border-radius: var(--radius-pill);
      font-size: 13px; font-weight: 500; cursor: pointer; font-family: inherit;
      transition: all 0.2s var(--ease-out);
    }
    .ai-pill:hover {
      background: rgba(15,118,110,0.14); border-color: rgba(15,118,110,0.4);
      transform: translateY(-1px);
    }
    .ai-messages { flex: 1; overflow-y: auto; margin-bottom: 16px; max-height: 400px; }
    .ai-msg {
      padding: 14px 18px; border-radius: var(--radius-lg); margin-bottom: 10px;
      font-size: 14px; line-height: 1.6;
    }
    .ai-msg.user {
      /* v3-blue residual → teal. The full-tab AI chat (.ai-section) is an
         all-teal surface (.ai-pill, .ai-send, focus rings all teal); the
         user bubble was the lone v3-blue holdout. Swept to the host
         surface's accent — NOT to the violet --ai-* family, which is the
         inline Ask-AI *drawer*'s palette, a separate component. */
      background: rgba(15,118,110,0.07); border: 1px solid rgba(15,118,110,0.14);
      margin-left: 40px; border-radius: var(--radius-lg) var(--radius-lg) 4px var(--radius-lg);
    }
    .ai-msg.assistant {
      background: var(--bg-card); border: 1px solid var(--border-card);
      margin-right: 40px; border-radius: var(--radius-lg) var(--radius-lg) var(--radius-lg) 4px;
    }
    .ai-input-bar { display: flex; gap: 8px; }
    .ai-input, .input {
      flex: 1; padding: 10px 14px; background: var(--surface-page);
      border: 1px solid var(--border-card); border-radius: var(--radius-md);
      color: var(--ink-900); font-family: inherit; font-size: 14px; outline: none;
      transition: border-color 0.2s var(--ease-out), box-shadow 0.2s var(--ease-out);
    }
    .ai-input:focus, .input:focus {
      border-color: rgba(15,118,110,0.5);
      box-shadow: 0 0 0 3px rgba(15,118,110,0.08);
      outline: none;
    }
    .input::placeholder { color: var(--ink-500); }
    .ai-send {
      background: var(--ai-gradient); border: none; border-radius: var(--radius-md);
      padding: 12px 16px; cursor: pointer; display: flex; align-items: center;
      transition: transform 0.2s var(--ease-spring), box-shadow 0.2s;
      box-shadow: 0 2px 10px rgba(15,118,110,0.2);
    }
    .ai-send:hover { transform: translateY(-1px); box-shadow: 0 4px 16px rgba(15,118,110,0.3); }
    .ai-send:active { transform: scale(0.95); }
    .ai-send:disabled { opacity: 0.4; cursor: not-allowed; box-shadow: none; }
    .ai-send:disabled:hover { transform: none; box-shadow: none; }
    .ai-send svg { stroke: white; }

    /* ── Scenario Planning ── */
    .scenario-presets { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px; }
    .dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; margin-right: 6px; }
    .dot-red { background: var(--cash-down); }
    .dot-green { background: var(--cash-up); }
    .dot-amber { background: var(--warn); }
    .dot-blue { background: var(--accent-secondary); }
    .scenario-sliders { display: flex; flex-direction: column; gap: 16px; }
    .slider-group label { display: flex; justify-content: space-between; font-size: 13px; color: var(--ink-500); margin-bottom: 6px; }
    .slider-val { color: var(--accent); font-weight: 600; }
    .slider-group input[type="range"] {
      width: 100%; -webkit-appearance: none; appearance: none;
      height: 6px; background: var(--surface-page); border-radius: 3px; outline: none;
    }
    .slider-group input[type="range"]::-webkit-slider-thumb {
      -webkit-appearance: none; width: 18px; height: 18px;
      background: var(--accent); border-radius: 50%; cursor: pointer;
    }
    .scenario-impact-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 12px; }
    .impact-card {
      background: var(--bg-card);
      backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
      border: 1px solid var(--border-card); border-radius: var(--radius-lg);
      padding: 18px; text-align: center;
      transition: border-color 0.2s, transform 0.25s var(--ease-spring);
    }
    .impact-card:hover { border-color: var(--accent); transform: translateY(-2px); }
    .impact-card__label { font-size: 11px; color: var(--ink-500); text-transform: uppercase; margin-bottom: 4px; }
    .impact-card__value { font-size: 20px; font-weight: 700; }

    /* ── Settings ── */
    .settings-grid { display: flex; flex-direction: column; gap: 1px; }
    .settings-row { display: flex; justify-content: space-between; align-items: center; padding: 12px 4px; border-bottom: 1px solid var(--border-default); border-radius: 4px; transition: background 0.15s; }
    .settings-row:hover { background: rgba(148,163,184,0.04); }
    .settings-key { font-size: 13px; color: var(--ink-500); }
    .settings-val { font-size: 13px; color: var(--ink-900); font-weight: 500; }
    .bank-item {
      display: flex; justify-content: space-between; align-items: center;
      padding: 14px 18px; background: var(--bg-card);
      backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
      border: 1px solid var(--border-card); border-radius: var(--radius-lg);
      margin-bottom: 10px;
      transition: border-color 0.25s var(--ease-out), transform 0.25s var(--ease-spring), box-shadow 0.25s;
    }
    .bank-item:hover {
      border-color: var(--accent);
      transform: translateY(-2px);
      box-shadow: var(--shadow-focus);
    }
    .bank-item__name { font-weight: 600; font-size: 14px; }
    .bank-item__type { font-size: 12px; color: var(--ink-500); }
    /* Move-bank (#6): on phones the row-action buttons (Edit / Sync / Move +
       Reconcile / Remove) ship at ~30px — below the 44px tap-target floor.
       Scoped to .bank-item so nothing else is affected (fixes the pre-existing
       Edit/Sync gap too, not just the new Move button). */
    @media (max-width: 480px) {
      .bank-item button { min-height: 44px; }
    }

    /* ── Toast ── */
    /* z-index must beat demo-sticky-footer (9998) and tf-checkout-banner (10000) — toasts were silently hidden behind those bottom bars */
    .tf-toast-rack { position: fixed; bottom: 24px; right: 24px; z-index: 10001; display: flex; flex-direction: column; gap: 8px; pointer-events: none; }
    .tf-toast-rack .tf-toast { pointer-events: auto; }
    /* Raise the rack above the sticky bottom bars when they're visible */
    body:has(#demo-sticky-footer:not(.hidden)) .tf-toast-rack,
    body:has(.tf-checkout-banner:not(.hidden)) .tf-toast-rack { bottom: 88px; }
    .tf-toast {
      padding: 13px 22px; border-radius: var(--radius-pill); font-size: 13px; font-weight: 600;
      color: var(--info-contrast);
      backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
      min-width: 280px; box-shadow: var(--shadow-md);
      animation: toastIn 0.3s var(--ease-spring);
      transition: opacity 0.3s ease, transform 0.3s ease;
    }
    .tf-toast.toast-out { opacity: 0; transform: translateX(20px); }
    .tf-toast.success { background: rgba(34,197,94,0.9); }
    .tf-toast.error { background: rgba(239,68,68,0.9); }
    /* info severity → v4 --info token (deep dusty blue, white text 6.12:1 AA).
       Was a near-opaque retired-v3 blue rgba(0,113,227,0.9); v4 has a real
       info-severity hue now (design-tokens.css §4) distinct from accent/warn/
       danger — the toast keeps its backdrop-blur glass, so a solid fill is
       cleaner than the legacy 0.9 frost. */
    .tf-toast.info { background: var(--info); }
    @keyframes toastIn { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }

    /* ── Post-checkout celebration banner ── */
    @keyframes tfBannerDrop {
      from { opacity: 0; transform: translateX(-50%) translateY(-20px); }
      to   { opacity: 1; transform: translateX(-50%) translateY(0); }
    }
    .tf-checkout-banner {
      animation: tfBannerDrop 0.38s cubic-bezier(0.34,1.56,0.64,1) both;
    }
    @media (prefers-reduced-motion: reduce) {
      .tf-checkout-banner { animation: none !important; }
    }

    /* ── Tablet breakpoint ── */
    @media (max-width: 768px) {
      .kpi-grid { grid-template-columns: repeat(2, 1fr); gap: 12px; }
      .briefing-metrics { grid-template-columns: repeat(2, 1fr); gap: 12px; }
      .nav-links { gap: 6px; }
      .nav-links > a, .nav-links > button { font-size: 12px; padding: 5px 8px; }
      .tf-card__title { font-size: 15px; }
      .books-pnl-grid { grid-template-columns: repeat(2, 1fr); }
      /* Reports sub-nav: allow scroll */
      .reports-subnav { overflow-x: auto !important; -webkit-overflow-scrolling: touch; }
    }

    /* ── Mobile ── */
    @media (max-width: 640px) {
      /* Nav: compact, no overflow */
      .tf-app__topbar { padding: 0 12px; height: 48px; }
      .nav-right { gap: 4px; }
      .nav-icon-btn { width: 40px; height: 40px; }
      .nav-add-bank-btn { height: 40px; padding: 0 11px 0 10px; font-size: 12px; gap: 6px; }
      .nav-add-bank-btn svg { width: 14px; height: 14px; }
      .nav-cmd-btn { display: none; } /* cmd palette via ⌘K or sidebar, save space */
      /* Hide the cash-health pulse on mobile — with the grade letter suppressed
         it was an orphaned dot crowding the brand mark. The grade still lives in
         the Cash-Health KPI tile + Settings. !important beats the inline
         display:flex that loadHealthPulse() sets on the element. */
      .health-pulse { display: none !important; }
      /* Icon-only primary action + drop the Customize pill (also reachable from
         the avatar menu) so the topbar fits and the avatar isn't clipped off the
         right edge on phones. */
      .nav-add-bank-btn { padding: 0; width: 30px; justify-content: center; }
      .nav-add-bank-btn span { display: none; }
      .nav-customize-btn { display: none; }
      /* Match the 30px visible-chip height (icon + add-bank) so the topbar
         reads as one row of equal chips on phones too. */
      .nav-avatar { width: 30px; height: 30px; font-size: 11px; flex-shrink: 0; }

      /* Container: tighter padding, room for bottom nav */
      .container { padding: 12px 14px 80px; }

      /* Briefing card: compact single-column metrics */
      .briefing-card { padding: 16px 14px; border-radius: var(--radius-lg); }
      .briefing-card::before { display: none; } /* skip decorative shimmer line */
      .briefing-header { margin-bottom: 12px; }
      .briefing-header__title { font-size: 14px; }
      .briefing-header__date { font-size: 11px; }
      .briefing-metrics { grid-template-columns: 1fr; gap: 10px; }
      .briefing-metric__value { font-size: 20px; }
      .briefing-actions { display: none; } /* too cramped on mobile */
      .briefing-ai-row { margin-top: 10px; padding-top: 10px; }
      .briefing-ai-input { font-size: 12px; padding: 8px 14px; }

      /* Hero bar */
      .hero-bar { padding: 6px 0 2px; }
      .hero-bar__greeting { font-size: var(--text-md); }
      .hero-bar__sub { font-size: 12px; }

      /* Sync freshness */
      .sync-freshness { font-size: 10px; padding: 2px 0 8px; }

      /* Trial banner: stack vertically on small screens */
      #trial-banner { flex-direction: column !important; gap: 10px !important; padding: 14px 16px !important; border-radius: var(--radius-lg) !important; }
      #trial-banner a { width: 100%; text-align: center; }

      /* Alert banner */
      #alerts-banner > div { border-radius: var(--radius-md) !important; padding: 12px 14px !important; font-size: 12px; }

      /* KPI grid: 2 columns */
      .kpi-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }

      /* Card titles: tighter */
      .tf-card__title { font-size: var(--text-sm); }
      .tf-card__subtitle { font-size: var(--text-xs); }

      /* Tables: let overflow-x:auto wrapper handle scrolling, not data-section */
      table { font-size: 12px; min-width: 480px; } /* force scroll instead of squish */
      th, td { padding: 8px 8px; white-space: nowrap; }

      /* Vendor drill-down header: wrap stats */
      .vendor-drill-stats { flex-shrink: 1 !important; }

      /* Hero banner (new users) */
      .hero-banner { padding: 28px 16px; border-radius: var(--radius-lg); }
      .hero-banner h1 { font-size: 20px; }

      /* Buttons */
      .btn { padding: 9px 18px; font-size: 13px; }
      .btn-download { width: 100%; padding: 12px; font-size: 14px; }

      /* AI chat */
      .ai-msg { padding: 10px 14px; font-size: 13px; }
      .ai-msg.user { margin-left: 12px; border-radius: 14px 14px 3px 14px; }
      .ai-msg.assistant { margin-right: 12px; border-radius: 14px 14px 14px 3px; }
      .ai-input-bar { gap: 6px; }
      .ai-input { font-size: 13px; padding: 9px 12px; }
      .ai-send { padding: 9px 12px; }

      /* Scenarios */
      .scenario-impact-grid { grid-template-columns: 1fr; }
      .scenario-presets { gap: 6px; }
      .ai-pill { font-size: 12px; padding: 7px 12px; }

      /* Reports sub-nav: scroll horizontally */
      .reports-subnav { overflow-x: auto !important; -webkit-overflow-scrolling: touch; width: 100% !important; flex-wrap: nowrap !important; }
      .reports-subnav button { flex-shrink: 0 !important; font-size: 12px !important; padding: 6px 14px !important; }

      /* Command palette */
      .cmd-palette-modal { width: 95vw; max-width: 95vw; border-radius: var(--radius-lg); }
      .cmd-palette-input { font-size: 15px; padding: 14px 16px; }

      /* FABs: AI hidden on mobile (bottom nav provides access); feedback stays visible */
      .ai-fab { display: none !important; }
      .ai-fab-label { display: none !important; }

      /* Insight cards */
      .insight-cards { gap: 8px; flex-direction: column; }
      .insight-card { padding: 12px; min-width: unset; border-radius: var(--radius-md); }

      /* Onboarding — v4 OB-1: the responsive overrides live in their own
         @media block AFTER the .ob-* base rules (search "OB-1 RESPONSIVE")
         so they win the cascade. The base rules are defined later in this
         file than this @media block, so an override placed HERE would lose. */

      /* PnL / Books cards */
      .books-pnl-grid { grid-template-columns: repeat(2, 1fr); gap: 8px; }

      /* Modals */
      #group-modal-overlay > div,
      #outflow-modal-overlay > div {
        width: 95vw !important; padding: 20px 16px !important;
        border-radius: 16px !important;
      }

      /* Feedback modal */
      .feedback-modal { padding: 24px 18px; width: 95vw; }
      .fb-categories { flex-direction: column; gap: 6px; }
      .fb-cat { padding: 10px; }

      /* Tab bar (hidden, bottom nav replaces it) */
      .tab-bar { display: none !important; }
      #dashboard-customize-btn { display: none !important; }
    }

    /* ── Small phones (≤480px) — tighter briefing ── */
    @media (max-width: 480px) {
      .briefing-metrics { grid-template-columns: 1fr !important; gap: 8px !important; }
      .briefing-metric__value { font-size: 20px !important; }
      .briefing-card { padding: 16px 12px; }
      .briefing-actions { display: none !important; }
      .briefing-ai-input { font-size: 12px; }
      .hero-bar__greeting { font-size: var(--text-md); }
      .hero-bar__sub { font-size: 11px; }
      .tf-app__topbar { padding: 0 10px !important; }
      /* Tap targets: 40px (was 28px — below the 44px floor). Prod audit #36. */
      .nav-icon-btn { width: 40px !important; height: 40px !important; }
      .nav-add-bank-btn { height: 40px !important; padding: 0 11px 0 10px !important; font-size: 12px !important; gap: 5px !important; }
      .nav-add-bank-btn svg { width: 14px !important; height: 14px !important; }
      .nav-avatar { width: 40px !important; height: 40px !important; font-size: 12px !important; }

      /* Day row stats: tighter gap on small screens (fixed-width columns wrap as a grid) */
      .day-row-stats { gap: 8px !important; }

      /* Invite form: stack inputs */
      .invite-input { min-width: 0 !important; width: 100% !important; }
    }

    /* ── Extra small (≤380px) — single column KPIs ── */
    @media (max-width: 380px) {
      .kpi-grid { grid-template-columns: 1fr; }
      .books-pnl-grid { grid-template-columns: 1fr; }
      .briefing-card { padding: 14px 10px; }
      .container { padding: 10px 10px 80px; }
      .hero-bar__greeting { font-size: var(--text-base); }
      .tf-kpi__value { font-size: 20px; }
      .tf-bottom-nav__label { font-size: 9px; }

      /* Cash flow summary: single column */
      .cashflow-summary { flex-direction: column !important; gap: 8px !important; }
      .cashflow-summary > div { min-width: unset !important; }
    }

    /* ── Left Sidebar Layout (desktop ≥1024px) ──
       v3.6: Switched sidebar to LIGHT surface to match the rest of the (light)
       dashboard. Previous build had dark-navy bg with light-mode text tokens
       (--text-3 #5A6878 dark slate on rgba(6,9,24,0.82) near-black) =
       unreadable. Now a clean white sidebar with native dark-on-light text. */
    .tf-app {
      display: flex; min-height: 100vh; position: relative; z-index: 1;
    }
    .tf-sidebar {
      display: none; /* shown on desktop below */
      flex-direction: column;
      width: 228px; flex-shrink: 0;
      background: #FFFFFF;
      border-right: 1px solid var(--border-subtle);
      position: sticky; top: 0; height: 100vh; overflow-y: auto;
      padding: 20px 14px;
      box-shadow: 1px 0 0 rgba(15,23,42,0.04);
    }
    .app-sidebar__brand {
      display: flex; align-items: center; gap: 10px;
      text-decoration: none; padding: 6px 10px; margin-bottom: 24px;
      color: #0B1020; /* explicit, not inherited from <a> default blue */
      overflow: hidden; max-width: 100%;
    }
    .app-sidebar__brand span {
      font-weight: 700; font-size: 16px; color: #0B1020; letter-spacing: -0.3px;
      white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; flex: 1;
    }
    .app-sidebar__section-label {
      font-size: 10px; font-weight: 700; color: #64748B; text-transform: uppercase;
      letter-spacing: 1.5px; padding: 0 12px; margin-bottom: 6px; margin-top: 24px;
    }
    .app-sidebar__section-label:first-of-type { margin-top: 0; }
    .tf-nav-item {
      display: flex; align-items: center; gap: 10px;
      padding: 9px 12px; border-radius: var(--radius-md); cursor: pointer;
      color: #334155; /* slate-700 — strong contrast on white (12.6:1) */
      font-size: 13px; font-weight: 500;
      transition: all 0.2s var(--ease-out); border: none; background: none;
      width: 100%; text-align: left; font-family: inherit;
      text-decoration: none;
    }
    .tf-nav-item:hover {
      background: rgba(15,23,42,0.04);
      color: #0B1020; /* darker on hover */
    }
    .tf-nav-item.active {
      background: rgba(15,118,110,0.10);
      color: #0F766E; /* teal-700 — passes AA on the light tint (~5.1:1) */
      font-weight: 600;
      box-shadow: inset 0 0 0 1px rgba(15,118,110,0.20);
    }
    .tf-nav-item svg { width: 17px; height: 17px; flex-shrink: 0; }
    .app-sidebar__spacer { flex: 1; }
    .app-sidebar__bottom { padding-top: 16px; border-top: 1px solid var(--border-subtle); }
    .tf-app__main { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow-x: hidden; }
    /* Hide tab bar on desktop when sidebar is visible */
    @media (min-width: 1024px) {
      .tf-sidebar { display: flex; }
      .tab-bar { display: none !important; }
      /* Matt R10 fix (2026-05-07): the Customize button MUST appear on desktop —
         previously this rule hid it for every viewport ≥1024px, which meant
         power users (Matt Picciano specifically) couldn't find the customize
         affordance even after refresh+logout. The button is gated by JS to
         only show on the dashboard tab, which is the right control surface. */
      .tf-app__main .tf-app__topbar { border-bottom: 1px solid var(--border-subtle); }
      .tf-app__topbar { padding: 0 28px; height: 56px; }
      .nav-brand { display: none; } /* brand is in sidebar */
    }
    @media (max-width: 1023px) {
      .tf-sidebar { display: none !important; }
    }

    /* ── Acting-As chip (2026-05-13) ──
       When a fractional CFO has entered Acting-As-Client mode the sidebar
       active-state highlight gets re-set to whatever sub-tab they're on,
       which loses the breadcrumb context of "which client am I inside?".
       This chip sits in the Clients section header and surfaces both the
       client identity AND a one-click exit. Ramp's "Viewing as <entity>"
       pattern, but inline in the sidebar (not a top banner) to keep the
       hero/cash-position area uncluttered. */
    .snav-acting-chip {
      display: none; /* shown via JS only when _actAsClient is set */
      align-items: center; gap: 6px;
      margin: 4px 8px 8px;
      padding: 6px 10px;
      background: rgba(34,197,94,0.08);
      border: 1px solid rgba(34,197,94,0.22);
      border-radius: 999px;
      font-size: 11px; font-weight: 600;
      color: #166534; /* green-800, AA on the light tint */
      cursor: pointer;
      transition: background 0.18s var(--ease-out), border-color 0.18s var(--ease-out);
      width: calc(100% - 16px); text-align: left;
      font-family: inherit;
      line-height: 1.2;
    }
    .snav-acting-chip:hover {
      background: rgba(34,197,94,0.14);
      border-color: rgba(34,197,94,0.36);
    }
    .snav-acting-chip__dot {
      width: 7px; height: 7px; border-radius: 50%;
      background: #22c55e; flex-shrink: 0;
      box-shadow: 0 0 0 2px rgba(34,197,94,0.22);
    }
    .snav-acting-chip__label {
      flex: 1; min-width: 0; white-space: nowrap;
      overflow: hidden; text-overflow: ellipsis;
      color: #166534; font-weight: 600; letter-spacing: -0.1px;
    }
    .snav-acting-chip__label-prefix {
      color: #475569; font-weight: 500; margin-right: 2px;
    }
    .snav-acting-chip__close {
      flex-shrink: 0; opacity: 0.55; font-size: 12px;
      line-height: 1; padding-left: 2px;
    }
    .snav-acting-chip:hover .snav-acting-chip__close { opacity: 0.95; }
    body.act-as-client-mode .snav-acting-chip { display: flex; }

    /* ── Staff-scope chip (per-staff RBAC, 2026-05-24) ──
       Clone of .snav-acting-chip but PERMANENT (not a button, no hover/close):
       a firm STAFF member's fixed scope cue, "Your clients · N". Teal (--accent
       #0F766E) rather than the act-as-client green so it reads as identity, not
       a dismissible mode. Shown only via body.firm-staff-mode. */
    .snav-scope-chip {
      display: none; /* shown only via body.firm-staff-mode */
      align-items: center; gap: 6px;
      margin: 4px 8px 8px;
      padding: 6px 10px;
      background: var(--accent-soft);
      border: 1px solid rgba(15,118,110,0.22);
      border-radius: 999px;
      font-size: 11px; font-weight: 700;
      color: var(--accent-strong);
      width: calc(100% - 16px); text-align: left;
      font-family: inherit;
      line-height: 1.2;
      letter-spacing: -0.1px;
    }
    .snav-scope-chip__dot {
      width: 7px; height: 7px; border-radius: 50%;
      background: var(--accent); flex-shrink: 0;
      box-shadow: 0 0 0 2px rgba(15,118,110,0.22);
    }
    .snav-scope-chip__label {
      flex: 1; min-width: 0; white-space: nowrap;
      overflow: hidden; text-overflow: ellipsis;
      color: var(--accent-strong); font-weight: 700;
    }
    body.firm-staff-mode .snav-scope-chip { display: flex; }

    /* ── Collapsible sidebar (2026-05-13) ──
       At ≤1280px (13" MacBook + below — Mercury/Ramp/Linear pattern) the
       228px sidebar squeezes the main content. Toggle collapses to a 44px
       icon-only rail with hover tooltips. State persists in localStorage
       under `tf_sidebar_collapsed`. Smooth 200ms transition on width.
       Section labels + nav-item text + acting-chip label all hide; the
       acting-chip itself shrinks to a centered green dot so the breadcrumb
       signal survives the collapse. */
    .tf-sidebar {
      transition: width 0.2s var(--ease-out);
    }
    .app-sidebar__toggle {
      display: none; /* shown only at ≤1280px via media query */
      position: fixed; top: 12px; left: 216px;
      width: 22px; height: 22px; border-radius: 50%;
      background: #FFFFFF;
      border: 1px solid var(--border-subtle);
      box-shadow: 0 1px 2px rgba(15,23,42,0.06);
      cursor: pointer; padding: 0;
      align-items: center; justify-content: center;
      color: #475569; z-index: 50;
      transition: background 0.18s, color 0.18s, left 0.2s var(--ease-out), top 0.2s var(--ease-out);
    }
    /* When the sidebar is collapsed, the 44px rail has no horizontal room to
       float the toggle beside the centered logo — at left:32px (the old
       value) the 22px toggle overlapped the 26px brand-mark AND poked ~10px
       off the rail's right edge, rendering as a clipped sliver at the
       viewport's top-left (the bug the founder hit on the Firm Dashboard).
       Fix: center the toggle on the 44px rail (left:11px) and drop it into
       the gap BELOW the brand-mark (top:54px — logo bottom ~52px, first nav
       item top ~78px, so the toggle sits clear of both) so the rail reads
       as a clean vertical stack: logo, expand chevron, nav. The toggle is a
       child of .tf-sidebar so this selector hits at any viewport; the
       @media(min-width:1281px) block below resets it. */
    .tf-sidebar.sidebar--collapsed .app-sidebar__toggle { left: 11px; top: 54px; }
    .app-sidebar__toggle:hover {
      background: rgba(15,118,110,0.08);
      color: #0F766E;
      border-color: rgba(15,118,110,0.30);
    }
    .app-sidebar__toggle svg { width: 12px; height: 12px; stroke-width: 2.5; }

    /* Collapsed state — applied via .sidebar--collapsed class (JS toggles) */
    @media (max-width: 1280px) {
      .app-sidebar__toggle { display: inline-flex; }
      .tf-sidebar.sidebar--collapsed { width: 44px; padding: 20px 0; }
      .tf-sidebar.sidebar--collapsed .app-sidebar__brand span,
      .tf-sidebar.sidebar--collapsed .app-sidebar__section-label,
      .tf-sidebar.sidebar--collapsed .tf-nav-item > span:not(.snav-collapsed-keep),
      .tf-sidebar.sidebar--collapsed .snav-acting-chip__label,
      .tf-sidebar.sidebar--collapsed .snav-acting-chip__close,
      .tf-sidebar.sidebar--collapsed .snav-scope-chip__label,
      .tf-sidebar.sidebar--collapsed .snav-collapsed-hide,
      .tf-sidebar.sidebar--collapsed .snav-trust-badge {
        display: none !important;
      }
      .tf-sidebar.sidebar--collapsed .app-sidebar__brand {
        justify-content: center; padding: 6px 0; margin-bottom: 20px;
      }
      .tf-sidebar.sidebar--collapsed .tf-nav-item {
        /* Collapse text: hide everything that isn't the leading icon by
           making the button a square that only fits the 17px icon. */
        justify-content: center; padding: 9px 0;
        font-size: 0; /* drops any direct-child text node */
        gap: 0;
      }
      .tf-sidebar.sidebar--collapsed .tf-nav-item svg,
      .tf-sidebar.sidebar--collapsed .tf-nav-item i[data-lucide] {
        font-size: 0; margin: 0 auto;
      }
      .tf-sidebar.sidebar--collapsed .snav-acting-chip,
      .tf-sidebar.sidebar--collapsed .snav-scope-chip {
        padding: 4px 0; margin: 4px auto 8px;
        width: 28px; height: 28px; justify-content: center;
        border-radius: 50%;
      }
      .tf-sidebar.sidebar--collapsed .snav-acting-chip__dot {
        width: 10px; height: 10px;
        box-shadow: 0 0 0 3px rgba(34,197,94,0.22);
      }
      .tf-sidebar.sidebar--collapsed .snav-scope-chip__dot {
        width: 10px; height: 10px;
        box-shadow: 0 0 0 3px rgba(15,118,110,0.22);
      }
      /* ── Collapsed-rail HOPPER (#snav-acting-chip is now a .cosw switcher) ──
         The chip-circle rule above sizes the outer element; these strip the
         INNER .cosw__trigger chrome so only the per-client accent dot shows,
         and pop the menu out to the right of the 44px rail (it can't fit a
         left/right:0 menu in a 44px column). The dot color is inline (per
         client), so the collapsed halo is a neutral token, not the green
         "acting" halo. */
      .tf-sidebar.sidebar--collapsed .snav-acting-chip.cosw { padding: 0; }
      .tf-sidebar.sidebar--collapsed .snav-acting-chip.cosw .cosw__trigger {
        width: 28px; height: 28px; padding: 0; gap: 0;
        border: 0; background: none; justify-content: center; border-radius: 50%;
      }
      .tf-sidebar.sidebar--collapsed .snav-acting-chip.cosw .cosw__name,
      .tf-sidebar.sidebar--collapsed .snav-acting-chip.cosw .cosw__chev {
        display: none !important;
      }
      .tf-sidebar.sidebar--collapsed .snav-acting-chip.cosw .cosw__dot {
        width: 10px; height: 10px;
        box-shadow: 0 0 0 3px color-mix(in srgb, var(--ink-500, #64748b) 22%, transparent);
      }
      .tf-sidebar.sidebar--collapsed .snav-acting-chip.cosw .cosw__menu {
        left: calc(100% + 8px); right: auto; top: 0;
        width: 220px; min-width: 200px;
      }
      .tf-sidebar.sidebar--collapsed .snav-scope-chip:hover::after {
        content: attr(aria-label);
        position: absolute; left: calc(100% + 8px); top: 50%;
        transform: translateY(-50%);
        background: #0B1020; color: #FFFFFF;
        padding: 5px 9px; border-radius: 6px;
        font-size: 12px; font-weight: 500;
        white-space: nowrap; z-index: 1000;
      }
      .tf-sidebar.sidebar--collapsed .snav-scope-chip { position: relative; }
      /* Tooltip on hover — pure CSS, no JS */
      .tf-sidebar.sidebar--collapsed .tf-nav-item {
        position: relative;
      }
      .tf-sidebar.sidebar--collapsed .tf-nav-item[aria-label]:hover::after {
        content: attr(aria-label);
        position: absolute; left: calc(100% + 8px); top: 50%;
        transform: translateY(-50%);
        background: #0B1020; color: #FFFFFF;
        padding: 5px 9px; border-radius: 6px;
        font-size: 12px; font-weight: 500; line-height: 1.2;
        white-space: nowrap; z-index: 1000;
        box-shadow: 0 4px 12px rgba(15,23,42,0.18);
        pointer-events: none;
      }
      .tf-sidebar.sidebar--collapsed .tf-nav-item[aria-label]:hover::before {
        /* Tooltip arrow */
        content: ''; position: absolute; left: calc(100% + 3px); top: 50%;
        transform: translateY(-50%);
        border: 4px solid transparent;
        border-right-color: #0B1020;
        z-index: 1000; pointer-events: none;
      }
      .tf-sidebar.sidebar--collapsed .snav-acting-chip:hover::after {
        /* The chip is now the in-client HOPPER ("Inside: <client>" + a hop
           menu), not a plain "Acting:" breadcrumb — the collapsed tooltip
           matches the expanded trigger label. */
        content: 'Inside: ' attr(data-client-name);
        position: absolute; left: calc(100% + 8px); top: 50%;
        transform: translateY(-50%);
        background: #0B1020; color: #FFFFFF;
        padding: 5px 9px; border-radius: 6px;
        font-size: 12px; font-weight: 500;
        white-space: nowrap; z-index: 1000;
      }
      .tf-sidebar.sidebar--collapsed .app-sidebar__toggle svg { transform: rotate(180deg); }
    }
    /* Desktop ≥1281px: sidebar always expanded, ignore localStorage. The
       .sidebar--collapsed class may still be on the element (persisted in
       localStorage), but we override every collapse-state rule back to its
       default so the saved preference doesn't leak across viewport sizes. */
    @media (min-width: 1281px) {
      .tf-sidebar.sidebar--collapsed { width: 228px; padding: 20px 14px; }
      .tf-sidebar.sidebar--collapsed .app-sidebar__brand { justify-content: flex-start; padding: 6px 10px; margin-bottom: 24px; }
      .tf-sidebar.sidebar--collapsed .app-sidebar__brand span,
      .tf-sidebar.sidebar--collapsed .app-sidebar__section-label { display: block; }
      .tf-sidebar.sidebar--collapsed .tf-nav-item > span,
      .tf-sidebar.sidebar--collapsed .snav-acting-chip__label,
      .tf-sidebar.sidebar--collapsed .snav-acting-chip__close,
      .tf-sidebar.sidebar--collapsed .snav-scope-chip__label,
      .tf-sidebar.sidebar--collapsed .snav-collapsed-hide,
      .tf-sidebar.sidebar--collapsed .snav-trust-badge { display: inline; }
      .tf-sidebar.sidebar--collapsed .snav-trust-badge { display: block; }
      .tf-sidebar.sidebar--collapsed .tf-nav-item {
        font-size: 13px; gap: 10px; padding: 9px 12px; justify-content: flex-start;
      }
      .tf-sidebar.sidebar--collapsed .snav-acting-chip,
      .tf-sidebar.sidebar--collapsed .snav-scope-chip {
        width: calc(100% - 16px); height: auto;
        padding: 6px 10px; margin: 4px 8px 8px;
        border-radius: 999px; justify-content: flex-start;
      }
      .tf-sidebar.sidebar--collapsed .snav-acting-chip__dot,
      .tf-sidebar.sidebar--collapsed .snav-scope-chip__dot {
        width: 7px; height: 7px;
      }
    }

    /* ── Mobile Bottom Navigation Bar ──
       v3.6: Light surface to match the rest of the dashboard. */
    .tf-bottom-nav {
      display: none; /* shown only on mobile via media query */
      position: fixed; bottom: 0; left: 0; right: 0; z-index: 900;
      background: #FFFFFF; border-top: 1px solid var(--border-subtle);
      box-shadow: 0 -1px 0 rgba(15,23,42,0.04);
      padding: 0 0 env(safe-area-inset-bottom, 0px);
      height: calc(60px + env(safe-area-inset-bottom, 0px));
    }
    .mobile-bottom-nav__items {
      display: flex; height: 100%; align-items: stretch;
      padding: 0 8px; gap: 4px;
    }
    .tf-bottom-nav__item {
      flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
      gap: 3px; cursor: pointer; background: none; border: none;
      color: #475569; /* readable on white */
      font-family: inherit; transition: color 0.18s;
      padding: 4px 2px; -webkit-tap-highlight-color: transparent;
    }
    .tf-bottom-nav__item.active { color: #0F766E; }
    .tf-bottom-nav__item svg { width: 18px; height: 18px; flex-shrink: 0; }
    .tf-bottom-nav__label {
      font-size: 9px; font-weight: 600; letter-spacing: -0.02em;
      white-space: nowrap; text-align: center; display: block;
    }
    @media (max-width: 640px) {
      .tf-bottom-nav { display: flex; }
      /* Push page content above the bottom nav */
      .tf-app__main { padding-bottom: calc(60px + env(safe-area-inset-bottom, 0px)); }
      /* Keep toasts above the bottom nav */
      .tf-toast-rack { bottom: calc(68px + env(safe-area-inset-bottom, 0px)); right: 12px; left: 12px; }
      .tf-toast { min-width: unset; }
    }

    /* ── Skeleton Shimmer Loading ── */
    .skeleton {
      background: linear-gradient(90deg, rgba(148,163,184,0.05) 25%, rgba(148,163,184,0.12) 50%, rgba(148,163,184,0.05) 75%);
      background-size: 200% 100%;
      animation: shimmer 1.5s ease-in-out infinite;
      border-radius: 6px;
    }
    .skeleton-line { height: 14px; margin-bottom: 8px; }
    .skeleton-line:last-child { width: 60%; }
    .skeleton-kpi { height: 32px; width: 80px; margin-top: 8px; }
    .skeleton-block { height: 120px; margin-bottom: 12px; }
    @keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } }

    /* ── Command Palette (⌘K) ── */
    .cmd-palette-overlay {
      position: fixed; inset: 0; z-index: 9000;
      background: rgba(15,23,42,0.40);
      backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
      display: flex; align-items: flex-start; justify-content: center;
      padding-top: 18vh;
      animation: cmdFadeIn 0.15s ease;
    }
    .cmd-palette-modal {
      background: #FFFFFF;
      border: 1px solid rgba(15,23,42,0.08);
      border-radius: var(--radius-xl); width: 580px; max-width: 90vw;
      box-shadow: 0 4px 12px rgba(15,23,42,0.08), 0 24px 80px rgba(15,23,42,0.16);
      overflow: hidden;
    }
    .cmd-palette-input {
      width: 100%; padding: 18px 20px; border: none;
      background: transparent; color: #0B1020;
      font-size: 16px; font-family: inherit; outline: none;
      border-bottom: 1px solid rgba(15,23,42,0.08);
    }
    .cmd-palette-input::placeholder { color: #5A6878; }
    .cmd-palette-results {
      max-height: 320px; overflow-y: auto;
      padding: 8px;
    }
    .cmd-result {
      padding: 12px 16px; border-radius: 8px;
      display: flex; align-items: center; gap: 12px;
      cursor: pointer; transition: background 0.1s;
      font-size: 14px; color: #475569;
    }
    .cmd-result:hover, .cmd-result.selected {
      background: rgba(15,118,110,0.08); color: #0B1020;
    }
    /* Client drawer (founder 2026-06-05 #6): the breadcrumb header is sticky
       (top:0, z-index:2); the global .tf-table sticky thead (top:0,
       z-index:--z-sticky=20) rendered OVER it on scroll, overlapping the
       "Firm › Client Dashboard" breadcrumb. Inside the drawer body, drop the
       sticky thead so column headers scroll with the content and never cover
       the header. (Scoped — full-page tables keep their sticky headers.) */
    #bk-drawer-body .tf-table thead th { position: static; top: auto; z-index: auto; }
    .cmd-result__icon { font-size: 16px; width: 24px; text-align: center; }
    .cmd-result__label { flex: 1; }
    .cmd-result__hint { font-size: 11px; color: #5A6878; }
    .cmd-palette-footer {
      padding: 10px 16px; border-top: 1px solid rgba(15,23,42,0.08);
      font-size: 11px; color: #5A6878; display: flex; gap: 16px;
    }
    .cmd-palette-footer kbd {
      background: rgba(15,23,42,0.04); padding: 2px 6px;
      border-radius: 4px; border: 1px solid rgba(15,23,42,0.08);
      font-family: inherit; font-size: 10px; color: #475569;
    }
    @keyframes cmdFadeIn { from { opacity: 0; } to { opacity: 1; } }

    /* ──────────────────────────────────────────────────────────────────
       Light Modal System (v3.6 — 2026-05-08)
       Reference: Stripe Dashboard, Mercury, Linear modals
       Canonical light surface for ALL modals/dropdowns/popovers in the
       portal. Replaces 11 vestigial dark surfaces from the pre-cream
       theme. Demo-grade quality bar for Matt + Rishabh.
       ────────────────────────────────────────────────────────────────── */
    .modal-overlay-light {
      position: fixed; inset: 0; z-index: 500;
      background: rgba(15, 23, 42, 0.40);
      backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
      display: none;
      align-items: center; justify-content: center;
    }
    .modal-card-light {
      background: #FFFFFF;
      border: 1px solid rgba(15, 23, 42, 0.08);
      border-radius: 12px;
      box-shadow: 0 4px 12px rgba(15,23,42,0.08), 0 24px 80px rgba(15,23,42,0.16);
      padding: 28px;
      width: 460px; max-width: 90vw; max-height: 80vh;
      overflow-y: auto;
      color: #0B1020;
    }
    .modal-card-light h2,
    .modal-card-light .modal-title {
      color: #0B1020; font-weight: 700; font-size: 18px; letter-spacing: -0.2px;
      margin: 0;
    }
    .modal-card-light .modal-desc,
    .modal-card-light p { color: #475569; font-size: 13px; line-height: 1.6; }
    .modal-card-light label {
      color: #475569; font-size: 11px; font-weight: 700;
      text-transform: uppercase; letter-spacing: 0.4px;
    }
    .modal-card-light input,
    .modal-card-light select,
    .modal-card-light textarea {
      background: #F6F8FB; color: #0B1020;
      border: 1px solid rgba(15, 23, 42, 0.10);
      border-radius: 8px; padding: 11px 14px; font-size: 14px;
      font-family: inherit;
    }
    .modal-card-light input:focus,
    .modal-card-light select:focus,
    .modal-card-light textarea:focus {
      border-color: #0F766E;
      outline: 2px solid rgba(15,118,110,0.20); outline-offset: 1px;
    }
    .modal-card-light .modal-actions {
      display: flex; gap: 10px; justify-content: flex-end; margin-top: 18px;
    }
    .modal-card-light .btn-modal-cancel {
      padding: 10px 20px; border-radius: 8px;
      border: 1px solid rgba(15,23,42,0.10); background: transparent;
      color: #475569; font-size: 13px; font-weight: 600; cursor: pointer;
      font-family: inherit;
    }
    .modal-card-light .btn-modal-cancel:hover {
      background: rgba(15,23,42,0.04); color: #0B1020;
    }
    .modal-card-light .btn-modal-save {
      padding: 10px 20px; border-radius: 8px; border: none;
      background: #0F766E; color: #FFFFFF;
      font-size: 13px; font-weight: 700; cursor: pointer;
      font-family: inherit;
    }
    .modal-card-light .btn-modal-save:hover {
      background: #134E4A; box-shadow: 0 4px 12px rgba(15,118,110,0.25);
    }

    /* ── Feedback FAB + Modal ── */
    .tf-fab {
      position: fixed; bottom: 24px; right: 24px; z-index: 800;
      width: 48px; height: 48px; border-radius: 50%;
      /* Brand-teal chat bubble — was --gradient-primary (teal->navy #1E3A8A),
         whose navy far-stop read as off-brand on TF's own chrome (founder
         flagged). Teal->deep-teal keeps it on-brand; icon stays white. */
      background: linear-gradient(135deg, #0F766E 0%, #0B5E58 100%); border: none; color: white;
      font-size: 18px; cursor: pointer;
      box-shadow: 0 4px 20px rgba(15,118,110,0.3), 0 0 0 1px rgba(15,118,110,0.15);
      transition: all 0.25s var(--ease-spring);
      display: flex; align-items: center; justify-content: center;
      backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
    }
    .tf-fab:hover {
      transform: scale(1.08) translateY(-2px);
      box-shadow: 0 8px 28px rgba(15,118,110,0.4), 0 0 0 1px rgba(15,118,110,0.25);
    }
    /* .ai-fab — the round legacy AI button. Kept display:none since the
       2026-05-17 dogfood (the #ask-ai-fab pill replaced it). Re-pointed
       off its orphan indigo-500 (rgba(99,102,241,…)) onto the governed
       v4 AI sub-brand family so a future un-hide is on-palette and no
       fifth uncoordinated violet survives in the file. */
    .ai-fab {
      position: fixed; bottom: 84px; right: 24px; z-index: 800;
      width: 48px; height: 48px; border-radius: 50%;
      background: var(--ai-accent); border: 1px solid var(--ai-border); color: var(--ai-contrast);
      cursor: pointer; box-shadow: 0 4px 20px var(--ai-glow);
      transition: all 0.25s var(--ease-spring);
      display: flex; align-items: center; justify-content: center;
      backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
    }
    .ai-fab:hover {
      transform: scale(1.08) translateY(-2px);
      box-shadow: 0 8px 28px var(--ai-glow);
      background: var(--ai-accent);
    }
    .ai-fab-label {
      position: fixed; bottom: 99px; right: 82px; z-index: 800;
      background: #0B1020; border: 1px solid rgba(255,255,255,0.08);
      border-radius: var(--radius-sm); padding: 5px 12px; font-size: 11px; font-weight: 600;
      color: #FFFFFF; white-space: nowrap; pointer-events: none;
      opacity: 0; transition: opacity 0.2s var(--ease-out);
      box-shadow: 0 4px 12px rgba(15,23,42,0.18);
    }
    .ai-fab:hover + .ai-fab-label { opacity: 1; }
    .feedback-overlay {
      position: fixed; inset: 0; z-index: 8000;
      background: rgba(15,23,42,0.40);
      backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
      display: flex; align-items: center; justify-content: center;
      animation: cmdFadeIn 0.2s ease;
    }
    .feedback-modal {
      background: #FFFFFF;
      border: 1px solid rgba(15,23,42,0.08);
      border-radius: var(--radius-xl); width: 480px; max-width: 90vw; padding: 36px;
      box-shadow: 0 4px 12px rgba(15,23,42,0.08), 0 24px 80px rgba(15,23,42,0.16);
      color: #0B1020;
    }
    .feedback-modal h2 { color: #0B1020; }
    .feedback-modal h2 { font-size: 20px; font-weight: 800; letter-spacing: -0.5px; margin-bottom: 4px; }
    .feedback-modal .fb-subtitle { font-size: 13px; color: var(--ink-500); margin-bottom: 20px; }
    .fb-categories {
      display: flex; gap: 8px; margin-bottom: 16px;
    }
    .fb-cat {
      flex: 1; padding: 14px 12px; border-radius: var(--radius-lg);
      border: 1px solid var(--border-card); background: var(--bg-card);
      backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
      text-align: center; cursor: pointer; transition: all 0.2s var(--ease-out);
      font-family: inherit; color: var(--ink-500);
    }
    .fb-cat:hover { border-color: rgba(15,118,110,0.3); }
    .fb-cat.active { border-color: var(--accent); background: rgba(15,118,110,0.08); color: var(--accent); }
    .fb-cat__icon { font-size: 22px; display: block; margin-bottom: 6px; }
    .fb-cat__label { font-size: 11px; font-weight: 600; }
    .fb-textarea {
      width: 100%; height: 100px; resize: vertical;
      background: var(--surface-page); border: 1px solid var(--border-default);
      border-radius: 10px; padding: 14px; color: var(--ink-900);
      font-family: inherit; font-size: 14px; outline: none;
      transition: border-color 0.2s;
    }
    .fb-textarea:focus { border-color: var(--accent); }
    .fb-textarea::placeholder { color: var(--ink-500); }
    .fb-char-count { text-align: right; font-size: 11px; color: var(--ink-500); margin-top: 4px; }
    .fb-success {
      text-align: center; padding: 32px 16px;
      animation: feedbackSuccess 0.4s ease;
    }
    .fb-success__icon { font-size: 48px; margin-bottom: 12px; }
    .fb-success__title { font-size: 18px; font-weight: 700; margin-bottom: 4px; }
    .fb-success__msg { color: var(--ink-500); font-size: 13px; }
    @keyframes feedbackSuccess { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } }

    /* ── Health Pulse (nav) ── */
    .health-pulse {
      display: flex; align-items: center; gap: 6px;
      height: 34px; padding: 0 12px 0 10px; box-sizing: border-box;
      border-radius: var(--radius-pill);
      background: rgba(148,163,184,0.05); border: 1px solid var(--border-card);
      font-size: 12px; font-weight: 700; cursor: pointer;
      transition: all 0.2s var(--ease-out);
    }
    .health-pulse:hover {
      border-color: rgba(15,118,110,0.25); background: rgba(15,118,110,0.06);
    }
    .health-dot {
      width: 8px; height: 8px; border-radius: 50%;
      animation: pulse 2s ease-in-out infinite;
    }
    @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
    .health-dot--a { background: var(--cash-up); }
    .health-dot--b { background: #86efac; }
    .health-dot--c { background: var(--warn); }
    .health-dot--d { background: #fb923c; }
    .health-dot--f { background: var(--cash-down); }

    /* ── Morning Briefing Card ── */
    .briefing-card {
      padding: 28px 32px;
      background: linear-gradient(160deg, rgba(15,118,110,0.06) 0%, var(--bg-card) 55%);
      backdrop-filter: blur(16px); -webkit-backdrop-filter: blur(16px);
      border: 1px solid rgba(15,118,110,0.18);
      border-radius: var(--radius-xl); margin-bottom: 24px;
      box-shadow: 0 0 0 0 rgba(15,118,110,0), var(--shadow-sm);
      animation: fadeUp 0.4s ease;
      position: relative; overflow: hidden;
    }
    .briefing-card::before {
      content: ''; position: absolute; top: 0; left: 0; right: 0; height: 1px;
      background: linear-gradient(90deg, transparent, rgba(15,118,110,0.3), transparent);
    }
    .briefing-header {
      display: flex; align-items: center; gap: 12px; margin-bottom: 16px;
    }
    .briefing-header__icon { font-size: 24px; }
    .briefing-header__title { font-size: 16px; font-weight: 700; }
    .briefing-header__date { font-size: 12px; color: var(--ink-500); }
    .briefing-metrics {
      display: grid; grid-template-columns: repeat(3, 1fr); gap: 16px;
      margin-bottom: 16px;
    }
    .briefing-metric__label {
      font-size: 11px; color: var(--ink-500); text-transform: uppercase; font-weight: 700;
      letter-spacing: 0.6px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    }
    .briefing-metric__value { font-size: 24px; font-weight: 800; margin-top: 4px; letter-spacing: -0.5px; }
    .briefing-metric__sub { font-size: 11px; color: var(--ink-500); margin-top: 2px; }
    .briefing-insights {
      display: flex; flex-direction: column; gap: 6px;
      padding-top: 12px; border-top: 1px solid var(--border-default);
    }
    .briefing-insight {
      font-size: 13px; color: var(--ink-500); line-height: 1.5;
      padding: 6px 0; overflow-wrap: anywhere; word-break: break-word;
    }
    .briefing-actions {
      display: flex; gap: 8px; margin-top: 16px; flex-wrap: wrap;
    }
    .briefing-action-btn {
      padding: 7px 16px; border-radius: var(--radius-pill);
      border: 1px solid var(--border-card); background: transparent;
      color: var(--ink-500); cursor: pointer; font-size: 12px; font-weight: 600;
      font-family: inherit; transition: all 0.2s var(--ease-out); white-space: nowrap;
    }
    .briefing-action-btn:hover {
      border-color: var(--accent); color: var(--accent);
      background: rgba(15,118,110,0.07);
      transform: translateY(-1px);
    }
    .briefing-ai-row {
      display: flex; align-items: center; gap: 8px;
      margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border-default);
    }
    .briefing-ai-input {
      flex: 1; padding: 10px 16px; border-radius: var(--radius-pill);
      border: 1px solid rgba(148,163,184,0.1); background: rgba(255,255,255,0.04);
      color: var(--ink-900); font-size: 13px; font-family: inherit;
      outline: none; transition: border-color 0.2s var(--ease-out), box-shadow 0.2s;
    }
    .briefing-ai-input:focus {
      border-color: rgba(15,118,110,0.4);
      box-shadow: 0 0 0 3px rgba(15,118,110,0.06);
    }
    .briefing-ai-input::placeholder { color: var(--ink-500); }
    .briefing-ai-send {
      width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0;
      border: none; background: var(--ai-gradient); color: #fff;
      cursor: pointer; display: flex; align-items: center; justify-content: center;
      transition: transform 0.2s var(--ease-spring), box-shadow 0.2s;
      box-shadow: 0 2px 10px rgba(15,118,110,0.2);
    }
    .briefing-ai-send:hover {
      transform: scale(1.08);
      box-shadow: 0 4px 16px rgba(15,118,110,0.3);
    }

    /* ── Smart Insight Cards ── */
    .insight-cards {
      display: flex; gap: 12px; margin-bottom: 24px;
      overflow-x: auto; scroll-snap-type: x mandatory;
    }
    .insight-card {
      flex: 1; min-width: 200px;
      padding: 16px; border-radius: var(--radius-lg);
      background: var(--bg-card);
      backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
      border: 1px solid var(--border-card);
      box-shadow: var(--shadow-sm);
      scroll-snap-align: start;
      transition: transform 0.3s var(--ease-spring), box-shadow 0.3s var(--ease-out), border-color 0.2s;
    }
    .insight-card:hover {
      border-color: var(--accent);
      box-shadow: var(--shadow-focus);
      transform: translateY(-3px);
    }
    .insight-card__icon { font-size: 18px; margin-bottom: 8px; }
    .insight-card__text { font-size: 13px; color: var(--ink-500); line-height: 1.5; }
    .insight-card__dismiss {
      background: none; border: none; color: var(--ink-500);
      font-size: 11px; cursor: pointer; margin-top: 8px;
      font-family: inherit; transition: color 0.2s;
    }
    .insight-card__dismiss:hover { color: var(--ink-500); }

    /* ── Onboarding Wizard ── */
    /* ═══════════════════════════════════════════════════════════════
       ONBOARDING — v4 "Ledger" first-run flow  (dashboard-migration OB-1)
       ───────────────────────────────────────────────────────────────
       The portal-specific page/composition layer for the first-run
       onboarding flow injected into #onboarding-wizard. The visual
       weight is carried by the v4 .tf-* component library
       (.tf-stepper, .tf-bank-logo, .tf-permission-list, .tf-card,
       .tf-btn, .tf-badge, .tf-skeleton, .tf-progress, .tf-banner) —
       these `ob-` rules are layout/scaffolding ONLY, the same role
       page-onboarding.css plays for the marketing reference.

       Built on v4 tokens (--space-*, --text-*, --ink-*, --accent*,
       --radius-*) — no raw hex, no legacy bridge token. Five screens:
       Welcome → Connect → Syncing → First cash view → Next.
       ═══════════════════════════════════════════════════════════════ */
    .onboarding-wizard {
      animation: fadeUp 0.5s var(--ease-out);
    }
    @media (prefers-reduced-motion: reduce) {
      .onboarding-wizard { animation: none; }
    }

    /* The screen frame — a single centered column the injected markup
       fills. One frame per server step (0 = Welcome+Connect, 1 = Sync). */
    .ob-screen {
      max-width: 720px;
      margin: 0 auto var(--space-7);
      text-align: center;
    }

    /* Top bar — brand lockup + the step stepper. */
    .ob-topbar {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: var(--space-5);
      padding-bottom: var(--space-6);
      margin-bottom: var(--space-7);
      border-bottom: var(--border-width) solid var(--border-subtle);
      flex-wrap: wrap;
    }
    .ob-brand {
      display: inline-flex;
      align-items: center;
      gap: var(--space-3);
      font-weight: var(--weight-bold);
      letter-spacing: var(--tracking-tight);
      color: var(--ink-900);
      font-size: var(--text-md);
    }
    .ob-topbar__progress {
      display: inline-flex;
      align-items: center;
      gap: var(--space-4);
      font-size: var(--text-xs);
      font-weight: var(--weight-semibold);
      color: var(--text-quiet);
      text-transform: uppercase;
      letter-spacing: var(--tracking-wide);
    }

    /* Headline / sub copy. */
    .ob-eyebrow { display: block; margin-bottom: var(--space-4); }
    .ob-headline {
      font-size: var(--text-xl);
      font-weight: var(--weight-bold);
      letter-spacing: var(--tracking-tight);
      color: var(--ink-900);
      margin: 0 0 var(--space-3);
      line-height: var(--lh-snug);
    }
    .ob-sub {
      font-size: var(--text-md);
      color: var(--text-secondary);
      line-height: var(--lh-body, 1.6);
      max-width: 520px;
      margin: 0 auto var(--space-6);
    }

    /* The blurred payoff preview (Welcome) — motivation made visible. */
    .ob-preview {
      max-width: 380px;
      margin: 0 auto var(--space-6);
      padding: var(--space-6);
      border: var(--border-width) solid var(--accent-border);
      border-radius: var(--radius-lg);
      background: var(--accent-softer);
    }
    .ob-preview__cap {
      display: block;
      font-size: var(--text-xs);
      font-weight: var(--weight-semibold);
      text-transform: uppercase;
      letter-spacing: var(--tracking-wide);
      color: var(--accent-strong);
      margin-bottom: var(--space-3);
    }
    .ob-preview__ghost {
      font-family: var(--font-mono);
      font-variant-numeric: tabular-nums lining-nums;
      font-size: var(--num-md);
      font-weight: var(--weight-semibold);
      color: var(--accent-strong);
      filter: blur(9px);
      user-select: none;
    }
    .ob-preview__lockrow {
      display: flex;
      align-items: center;
      justify-content: center;
      gap: var(--space-3);
      margin-top: var(--space-4);
      font-size: var(--text-sm);
      color: var(--text-quiet);
    }
    .ob-preview__lockrow svg { width: 14px; height: 14px; flex-shrink: 0; }

    /* Action stack. */
    .ob-actions {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: var(--space-4);
    }
    .ob-reassure {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      justify-content: center;
      gap: var(--space-3) var(--space-5);
    }
    .ob-reassure__item {
      display: inline-flex;
      align-items: center;
      gap: var(--space-2);
      font-size: var(--text-xs);
      color: var(--text-secondary);
    }
    .ob-reassure__item svg { width: 13px; height: 13px; color: var(--cash-up-text); }
    .ob-skip {
      font-size: var(--text-sm);
      color: var(--text-quiet);
      margin: var(--space-2) 0 0;
    }
    .ob-skip a, .ob-plaid a { color: var(--accent); font-weight: var(--weight-medium); }

    /* The connect card (Connect-a-bank screen). */
    .ob-connect-card { text-align: left; }
    .ob-plaid {
      display: flex;
      align-items: flex-start;
      gap: var(--space-3);
      margin-top: var(--space-5);
      padding: var(--space-4) var(--space-5);
      border-radius: var(--radius-md);
      background: var(--surface-sunken);
      font-size: var(--text-sm);
      color: var(--text-secondary);
      line-height: var(--lh-snug, 1.4);
    }
    .ob-plaid__mark {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      flex-shrink: 0;
      width: 26px; height: 26px;
      border-radius: var(--radius-sm);
      background: var(--ink-900);
      color: #FFFFFF;
      font-weight: var(--weight-bold);
      font-size: var(--text-sm);
    }

    /* The bank picker grid. */
    .ob-banks {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: var(--space-3);
      margin-top: var(--space-4);
    }
    .ob-bank {
      display: flex;
      flex-direction: column;
      align-items: center;
      gap: var(--space-3);
      padding: var(--space-5) var(--space-3);
      border: var(--border-width) solid var(--border-default);
      border-radius: var(--radius-md);
      background: var(--surface-raised);
      cursor: pointer;
      text-align: center;
      transition: border-color var(--duration-fast) var(--ease-standard),
                  background-color var(--duration-fast) var(--ease-standard),
                  transform var(--duration-fast) var(--ease-standard);
    }
    .ob-bank:hover {
      border-color: var(--accent);
      background: var(--accent-softer);
      transform: translateY(-2px);
    }
    .ob-bank:focus-visible {
      outline: 2px solid var(--accent);
      outline-offset: 2px;
    }
    @media (prefers-reduced-motion: reduce) {
      .ob-bank { transition: none; }
      .ob-bank:hover { transform: none; }
    }
    .ob-bank__name {
      font-size: var(--text-xs);
      font-weight: var(--weight-semibold);
      color: var(--text-primary);
      line-height: var(--lh-snug, 1.3);
    }

    /* Syncing screen — skeleton-of-the-dashboard + a real checklist. */
    .ob-sync {
      display: grid;
      grid-template-columns: 0.85fr 1fr;
      gap: var(--space-6);
      text-align: left;
    }
    .ob-sync__skeleton {
      padding: var(--space-5);
      border: var(--border-width) solid var(--border-subtle);
      border-radius: var(--radius-md);
      background: var(--surface-sunken);
    }
    .ob-sync__skel-kpi { margin-bottom: var(--space-5); }
    .ob-sync__skel-rows {
      display: flex;
      flex-direction: column;
      gap: var(--space-4);
    }
    .ob-sync__skel-row {
      display: flex;
      align-items: center;
      gap: var(--space-3);
    }
    .ob-sync__steps {
      padding: var(--space-5);
      border: var(--border-width) solid var(--border-default);
      border-radius: var(--radius-md);
      background: var(--surface-raised);
    }
    .ob-sync__heading {
      display: flex;
      align-items: center;
      gap: var(--space-3);
      font-size: var(--text-sm);
      font-weight: var(--weight-semibold);
      color: var(--text-primary);
      margin-bottom: var(--space-5);
    }

    /* The itemized progress checklist. */
    .ob-checklist {
      display: flex;
      flex-direction: column;
      gap: var(--space-4);
    }
    .ob-check {
      display: flex;
      align-items: center;
      gap: var(--space-3);
      font-size: var(--text-sm);
    }
    .ob-check__icon {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      flex-shrink: 0;
      width: 20px; height: 20px;
      border-radius: var(--radius-pill);
    }
    .ob-check__icon svg { width: 12px; height: 12px; }
    .ob-check--done .ob-check__icon {
      background: var(--cash-up-soft);
      color: var(--cash-up-text);
    }
    .ob-check--active .ob-check__icon {
      background: var(--accent-soft);
      color: var(--accent-strong);
    }
    .ob-check--pending .ob-check__icon {
      border: var(--border-width) solid var(--border-default);
    }
    .ob-check__label { flex: 1; color: var(--text-primary); }
    .ob-check--pending .ob-check__label { color: var(--text-quiet); }
    .ob-check__meta {
      flex-shrink: 0;
      font-size: var(--text-xs);
      color: var(--text-quiet);
      font-variant-numeric: tabular-nums;
    }
    .ob-check--done .ob-check__meta { color: var(--cash-up-text); }

    /* The small spinner used in the active checklist row. */
    .ob-spin-sm {
      display: inline-block;
      width: 12px; height: 12px;
      border: 2px solid var(--accent-border);
      border-top-color: var(--accent);
      border-radius: var(--radius-pill);
      animation: spin 0.8s linear infinite;
    }
    @media (prefers-reduced-motion: reduce) {
      .ob-spin-sm { animation-duration: 2.4s; }
    }

    /* ── First-cash-view (OB-2) — the crafted aha peak ──────────────────
       The just-connected user sees their OWN consolidated cash number for
       the first time. The figure is the hero: a large tabular-num mono
       number that counts up. Everything else is quiet around it. */
    .ob-firstcash { max-width: 560px; }
    /* The "consolidating…" hold state — shown until real figures land.
       No number is rendered here (a placeholder would read as a real
       balance); a calm reconciling line holds the screen honestly. */
    .ob-firstcash__hold {
      display: flex;
      align-items: center;
      justify-content: center;
      gap: var(--space-3);
      margin: var(--space-7) auto var(--space-4);
      padding: var(--space-6);
      border: var(--border-width) solid var(--border-subtle);
      border-radius: var(--radius-md);
      background: var(--surface-sunken);
      font-size: var(--text-sm);
      color: var(--text-secondary);
    }
    .ob-firstcash__cap {
      font-size: var(--text-sm);
      color: var(--text-quiet);
      margin: var(--space-6) 0 var(--space-2);
    }
    /* The hero number. tabular-nums (from .tf-num) keeps digit columns
       fixed so the count-up climbs without horizontal jitter. */
    .ob-firstcash__figure {
      font-size: var(--num-lg);
      font-weight: var(--weight-bold);
      letter-spacing: var(--tracking-tight);
      color: var(--ink-900);
      line-height: 1.05;
      margin-bottom: var(--space-2);
    }
    .ob-firstcash__trend {
      font-size: var(--text-sm);
      font-weight: var(--weight-semibold);
      margin: 0 0 var(--space-6);
    }
    .ob-firstcash__trend--up { color: var(--cash-up-text); }
    .ob-firstcash__trend--down { color: var(--cash-down-text); }
    /* The AI briefing card sits below the number — supporting, not competing. */
    .ob-firstcash__brief { text-align: left; }
    .ob-firstcash__brief-head {
      display: flex;
      align-items: center;
      gap: var(--space-3);
      font-size: var(--text-xs);
      font-weight: var(--weight-semibold);
      text-transform: uppercase;
      letter-spacing: var(--tracking-wide);
      color: var(--accent-strong);
      margin-bottom: var(--space-3);
    }
    .ob-firstcash__brief-head svg { width: 16px; height: 16px; flex-shrink: 0; }
    .ob-firstcash__narrative {
      font-size: var(--text-md);
      line-height: var(--lh-body, 1.6);
      color: var(--text-secondary);
      margin: 0;
    }
    .ob-firstcash__secondary {
      display: flex;
      flex-wrap: wrap;
      align-items: center;
      justify-content: center;
      gap: var(--space-3) var(--space-5);
    }

    /* ── OB-3 NEXT STEP — the habit handoff ─────────────────────────────
       The onboarding arc ends pointed at the HABIT, not at a dead "go to
       dashboard" link. Three durable habits as cards (daily check-in, 7am
       digest, 13-week forecast); the first is the recommended next click.
       Setup -> Aha is done — this starts Habit. (Reference screen 5.) */
    .ob-firstcash__next {
      margin-top: var(--space-7);
      text-align: left;
    }
    .ob-firstcash__next-head {
      font-size: var(--text-md);
      font-weight: var(--weight-semibold);
      color: var(--ink-900);
      letter-spacing: var(--tracking-tight);
      margin: 0 0 var(--space-2);
      text-align: center;
    }
    .ob-firstcash__next-sub {
      font-size: var(--text-sm);
      color: var(--text-secondary);
      margin: 0 0 var(--space-5);
      text-align: center;
    }
    .ob-habits {
      display: grid;
      grid-template-columns: repeat(3, 1fr);
      gap: var(--space-4);
    }
    /* Each habit is a tappable card. The whole card is the click target;
       the link row is a visual affordance, not a separate hit area. */
    .ob-habit {
      display: flex;
      flex-direction: column;
      gap: var(--space-3);
      padding: var(--space-5);
      border: var(--border-width) solid var(--border-subtle);
      border-radius: var(--radius-md);
      background: var(--surface-raised);
      text-align: left;
      cursor: pointer;
      transition: border-color 0.18s var(--ease-out),
                  box-shadow 0.18s var(--ease-out),
                  transform 0.18s var(--ease-out);
    }
    .ob-habit:hover {
      border-color: var(--accent);
      box-shadow: var(--shadow-sm);
      transform: translateY(-2px);
    }
    .ob-habit:focus-visible {
      outline: 2px solid var(--accent);
      outline-offset: 2px;
    }
    /* The recommended habit — the daily check-in. A faint-teal wash so
       it reads as the next click without shouting. */
    .ob-habit--primary {
      border-color: var(--accent);
      background: var(--accent-softer);
    }
    .ob-habit__icon {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      width: 36px;
      height: 36px;
      border-radius: var(--radius-sm);
      background: var(--accent-soft);
      color: var(--accent-strong);
    }
    .ob-habit--primary .ob-habit__icon {
      background: var(--accent);
      color: var(--accent-contrast);
    }
    .ob-habit__icon svg { width: 18px; height: 18px; }
    .ob-habit__title {
      font-size: var(--text-sm);
      font-weight: var(--weight-semibold);
      color: var(--ink-900);
    }
    .ob-habit__text {
      font-size: var(--text-xs);
      line-height: var(--lh-body, 1.6);
      color: var(--text-secondary);
    }
    .ob-habit__link {
      display: inline-flex;
      align-items: center;
      gap: var(--space-2);
      margin-top: auto;
      font-size: var(--text-xs);
      font-weight: var(--weight-semibold);
      color: var(--accent-strong);
    }
    .ob-habit__link svg { width: 13px; height: 13px; }

    /* ── OB-1 / OB-2 RESPONSIVE — onboarding at phone widths ────────────
       Placed AFTER the .ob-* base rules above so these overrides win the
       cascade (the base .ob-banks etc. are equal-specificity and would
       otherwise win by source order). Onboarding is heavily phone-used —
       verified at 375 / 393 / 402 / 430 px. */
    @media (max-width: 640px) {
      .ob-screen { margin-bottom: var(--space-6); }
      .ob-topbar {
        padding-bottom: var(--space-5);
        margin-bottom: var(--space-6);
      }
      .ob-headline { font-size: var(--text-lg); }
      .ob-sub { font-size: var(--text-sm); }
      .ob-banks { grid-template-columns: repeat(2, 1fr); }
      .ob-sync { grid-template-columns: 1fr; gap: var(--space-5); }
      .ob-sync__skeleton { order: 2; }
      .ob-sync__steps { order: 1; }
      /* First-cash-view: keep the hero number from overflowing narrow
         viewports — the count-up figure is the largest glyph on screen. */
      .ob-firstcash__figure { font-size: var(--num-md); }
      .ob-firstcash__secondary { flex-direction: column; gap: var(--space-2); }
      /* OB-3 Next-step: habit cards stack one-up at phone widths so each
         card keeps a comfortable tap target and readable text width. */
      .ob-habits { grid-template-columns: 1fr; gap: var(--space-3); }
    }

    /* ── Smart Books Styles ── */
    .books-pnl-grid {
      display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
      gap: 12px; margin-bottom: 24px;
    }
    /* The legacy `.pnl-card` base + `.pnl-card__label` / `.pnl-card__value`
       + the `.pnl-card--<x> .pnl-card__value` colour rules — DELETED in
       dashboard-migration I9.6. I8.5 migrated the P&L summary cards to the
       v4 `.tf-kpi` (card → `.tf-kpi`, label → `.tf-kpi__label`, value →
       `.tf-kpi__value`); those legacy classes have zero consumer. The
       per-tier value COLOUR is now the v4-rescoped `.pnl-card--<x>
       .tf-kpi__value` rules (§"Re-assert the P&L semantic value colours"
       below). KEPT: `.pnl-card__change` — the delta line still carries
       this bespoke class (no v4 twin); `.pnl-card--revenue/--expense/
       --profit/--loss` are KEPT modifier classes (live on the P&L cards
       / set by `loadPnL`). */
    .pnl-card__change { font-size: 11px; margin-top: 4px; font-weight: 600; }

    .cat-pill {
      display: inline-flex; align-items: center; gap: 4px;
      padding: 3px 10px; border-radius: 12px; font-size: 11px; font-weight: 600;
      cursor: pointer; transition: all 0.15s;
    }
    .cat-pill:hover { opacity: 0.85; transform: scale(1.02); }
    .cat-pill--revenue { background: rgba(34,197,94,0.12); color: var(--cash-up); }
    .cat-pill--cogs { background: rgba(245,158,11,0.12); color: var(--warn); }
    .cat-pill--opex { background: rgba(148,163,184,0.12); color: var(--ink-500); }
    .cat-pill--other { background: var(--chart-6-soft); color: var(--chart-6); }
    .cat-pill--uncategorized { background: rgba(239,68,68,0.12); color: var(--cash-down); }
    .cat-source { font-size: 10px; opacity: 0.6; }

    .books-disclaimer {
      font-size: 11px; color: var(--ink-500); text-align: center;
      padding: 12px; margin-top: 16px;
      border-top: 1px solid var(--border-default);
    }

    .books-header-row {
      display: flex; align-items: center; justify-content: space-between;
      flex-wrap: wrap; gap: 12px;
    }
    .books-actions { display: flex; gap: 8px; }
    .btn-sm {
      padding: 8px 16px; font-size: 12px; border-radius: var(--radius-pill);
    }
    .btn-green {
      background: linear-gradient(135deg, #22c55e, #16a34a); color: white;
      border: none; cursor: pointer; font-family: inherit; font-weight: 600;
      transition: all 0.2s var(--ease-out);
      box-shadow: 0 3px 12px rgba(34,197,94,0.2);
    }
    .btn-green:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(34,197,94,0.3); }

    .onramp-banner {
      text-align: center; padding: 40px 32px;
      /* teal + cash-up green wash (both v4-valid for a success/onramp
         surface). Third stop was a residual v3-blue rgba(0,113,227) —
         collapsed onto teal. */
      background: linear-gradient(135deg, rgba(15,118,110,0.05), rgba(34,197,94,0.04), rgba(15,118,110,0.025));
      border: 1px solid rgba(15,118,110,0.2); border-radius: 20px;
      margin-bottom: 24px;
      position: relative; overflow: hidden;
    }
    .onramp-banner::before {
      content: ''; position: absolute; top: -1px; left: -1px; right: -1px; bottom: -1px;
      border-radius: 20px; padding: 1px;
      background: linear-gradient(135deg, rgba(15,118,110,0.3), transparent, rgba(34,197,94,0.3));
      -webkit-mask: linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
      -webkit-mask-composite: xor; mask-composite: exclude;
      pointer-events: none;
    }
    .onramp-banner h3 { font-size: 20px; font-weight: 800; margin-bottom: 8px; }
    .onramp-banner p { color: var(--ink-500); font-size: 14px; max-width: 480px; margin: 0 auto 20px; line-height: 1.6; }

    .category-select {
      background: var(--surface-page); border: 1px solid var(--border-card);
      color: var(--ink-900); border-radius: var(--radius-sm); padding: 5px 10px;
      font-size: 11px; font-family: inherit; cursor: pointer;
      transition: border-color 0.2s var(--ease-out), box-shadow 0.2s;
    }
    .category-select:focus {
      border-color: rgba(15,118,110,0.5); outline: none;
      box-shadow: 0 0 0 3px rgba(15,118,110,0.08);
    }

    .hidden { display: none !important; }

    /* ── Account Breakdown toggle ── */
    #account-breakdown-section .tf-card__header { cursor: default; }
    #account-breakdown-toggle-btn { transition: all 0.2s; }
    #account-breakdown-toggle-btn:hover { border-color: var(--accent); color: var(--accent); }

    /* ── Transfer toggle label ── */
    #exclude-transfers-toggle:checked + span, label:has(#exclude-transfers-toggle:checked) { color: var(--accent); }

    /* ── Group modal account picker ── */
    #group-account-picker label:hover { background: rgba(15,118,110,0.06); border-color: rgba(15,118,110,0.3); }
    #group-account-picker label:has(input:checked) { background: rgba(15,118,110,0.08); border-color: var(--accent); color: var(--ink-900); }

    /* ── Reporting groups list badges ── */
    .group-tag {
      font-size:10px; background:rgba(15,118,110,0.12); color:var(--accent);
      padding:2px 7px; border-radius:10px; font-weight:600; white-space:nowrap;
    }

    /* ── Status dot for bank items ── */
    .status-dot { display:inline-block; width:7px; height:7px; border-radius:50%; }
    .status-dot--active { background: var(--cash-up); }

    /* ──────────────────────────────────────────────────────────────────────
       PER-CLIENT CONTEXT-SWITCH MODE (founder offsite 2026-05-12 / T-16h)
       Activated by adding body.act-as-client-mode and the per-client
       --client-* tokens. The visual treatment:
       1. A small accent stripe at the top of the main content area —
          subtle, but persistently signals "you are inside one client."
       2. A faint diagonal tint wash over the main content area background.
       3. The sticky banner uses the same accent color via inherited vars.
       4. The pulse + slide-in animations make the transition FELT.
       Removed on exitActAsClient() so the global firm mode returns clean.
       ────────────────────────────────────────────────────────────────────── */
    @keyframes client-pulse {
      0%, 100% { transform: scale(1); opacity: 1; }
      50% { transform: scale(1.55); opacity: 0.45; }
    }
    @keyframes act-as-client-banner-in {
      from { opacity: 0; transform: translateY(-10px); }
      to { opacity: 1; transform: translateY(0); }
    }
    @keyframes act-as-client-stripe-in {
      from { transform: scaleX(0); }
      to { transform: scaleX(1); }
    }
    body.act-as-client-mode {
      /* a subtle diagonal wash over the main content area in the client's
         accent — strong enough to be felt, subtle enough not to muddy data */
      background-image:
        linear-gradient(135deg,
          var(--client-tint-wash, rgba(15,118,110,0.045)) 0%,
          transparent 60%);
      background-attachment: fixed;
      background-size: cover;
    }
    body.act-as-client-mode #app-main {
      position: relative;
    }
    body.act-as-client-mode #app-main::before {
      content: '';
      position: sticky;
      top: 0;
      display: block;
      height: 3px;
      margin: 0 -24px;
      background: linear-gradient(
        90deg,
        var(--client-accent, #0F766E) 0%,
        var(--client-accent-end, #0EA5E9) 100%
      );
      transform-origin: left;
      animation: act-as-client-stripe-in 0.45s ease-out both;
      z-index: 70;
      box-shadow: 0 1px 0 rgba(15,118,110,0.12);
    }
    body.act-as-client-mode #act-as-client-banner[style*="flex"] {
      animation: act-as-client-banner-in 0.40s ease-out both;
    }
    /* Sidebar firm name → client name swap while in context */
    body.act-as-client-mode .firm-name-firm-default { display: none; }
    .firm-name-client-active { display: none; }
    body.act-as-client-mode .firm-name-client-active { display: inline; }
    /* Tab title prefix while in context */
    .scoped-client-prefix { display: none; color: var(--client-accent, #0F766E); font-weight: 700; }
    body.act-as-client-mode .scoped-client-prefix { display: inline; }

    /* 2026-05-13 — kill the duplicate context-establishing UI in act-as-client mode.
       Matt feedback 2026-04-21 (and founder dogfood 2026-05-12): "too much noise vs. useful."
       In firm mode the morning briefing card + personal greeting are useful. In client mode
       the sticky banner already shows the at-a-glance KPI strip + client name, and the
       hero subtitle already says "Inside <client> · scoped data only" — adding a personal
       greeting + a separate Cash Briefing widget is visual clutter that competes with the
       actual work surface (alerts + tabs). Hide both in client mode. */
    body.act-as-client-mode #briefing-card { display: none !important; }
    body.act-as-client-mode #hero-banner { display: none !important; }

    /* 2026-05-13 — kill the duplicate AI FABs.
       Founder dogfood: "is this ask ai button on top of another button?"
       Yes — three FABs were stacked at bottom-right:
         1. .tf-fab  (gray chat icon, bottom:24 right:24)
         2. .ai-fab        (round purple robot icon, bottom:84 right:24) → switches to AI tab
         3. #ask-ai-fab    (gradient "💬 Ask AI" pill, bottom:24 right:20) → opens AI drawer
       The pill (#ask-ai-fab) is the better experience (drawer overlays without losing
       context, vs ai-fab which takes you away to a full tab). The round purple ai-fab
       is redundant with the pill. The feedback-fab collides with the pill at bottom:24.
       Fix: hide the round ai-fab + label. Move feedback-fab up so the pill stands alone. */
    .ai-fab, .ai-fab-label { display: none !important; }
    .tf-fab { bottom: 84px !important; }

    /* 2026-05-17 dogfood — mobile: the bottom-anchored overlays were colliding
       with the 60px mobile bottom nav (z 900). The 2026-05-13 desktop FAB
       de-stacking fix above never accounted for the nav, which only appears on
       mobile. Push every bottom-fixed overlay above the nav so all six nav
       items stay tappable:
         · #ask-ai-fab pill        — was bottom:24, covered QBO + More icons
         · .tf-fab bubble    — sits above the pill
         · #demo-sticky-footer CTA — was bottom:0, swallowed the whole nav
       This block sits after the .tf-fab override above so it wins; ID +
       !important beats the JS inline styles on the pill and the demo footer. */
    @media (max-width: 640px) {
      #ask-ai-fab { bottom: calc(84px + env(safe-area-inset-bottom, 0px)) !important; }
      .tf-fab { bottom: calc(144px + env(safe-area-inset-bottom, 0px)) !important; }
      #demo-sticky-footer { bottom: calc(60px + env(safe-area-inset-bottom, 0px)) !important; }
    }

/* ─── Firm-triage "Why?" affordance (explain-client, CDO spec 2026-05-23) ───
       A single quiet inline trailing link on the triage row narrative line
       ([data-role="row-narrative"]). It reads as the continuation of the AI's
       sentence — NOT a button/chip/icon. 12px / weight-600 / --ink-500 with a
       1px AI-gradient underline that saturates on hover. The row narrative div
       is built with inline styles in _firmTriageRowHtml(); these class rules
       layer the link styling on top without touching the row's inline layout.

       Underline: a 1px linear-gradient drawn as a bottom background-image so it
       can saturate on hover (rest = muted blend over the link's ink colour;
       hover = full --ai-gradient). text-decoration is intentionally OFF so the
       gradient line is the only underline. */
    a.tf-why-link {
      color: var(--ink-500);
      font-size: 12px;
      font-weight: 600;
      text-decoration: none;
      cursor: pointer;
      white-space: nowrap;             /* "Why?" never splits across lines */
      padding-bottom: 1px;
      background-image: var(--ai-gradient);   /* token IS a full linear-gradient() */
      background-repeat: no-repeat;
      background-position: 0 100%;
      background-size: 100% 1px;       /* 1px underline hugging the baseline */
      opacity: 0.85;                   /* quiet at rest */
      transition: opacity 0.15s var(--ease-out),
                  background-size 0.15s var(--ease-out),
                  color 0.15s var(--ease-out);
    }
    a.tf-why-link:hover,
    a.tf-why-link:focus-visible {
      opacity: 1;                      /* gradient saturates on hover */
      color: var(--ai-accent);
      background-size: 100% 2px;       /* underline thickens a hair on hover */
    }
    a.tf-why-link:focus-visible {
      outline: 2px solid var(--ai-glow);
      outline-offset: 2px;
      border-radius: 2px;
    }
    @media (prefers-reduced-motion: reduce) {
      a.tf-why-link { transition: none; }
    }

    /* Mobile (≤ 402px reference width): the narrative line + chips row get
       crowded, so the separator is hidden and the "Why?" link drops onto its
       OWN full-width row with a ≥44px tap target (Apple HIG) — it never
       collides with the runway/cash/sync chips below it. The row narrative div
       is display:block (inline-styled), so forcing the link to display:block
       with a top margin pushes it to a new line; min-height + line-height give
       the 44px tap row. */
    @media (max-width: 402px) {
      [data-role="row-narrative"] .tf-why-sep { display: none; }
      [data-role="row-narrative"] a.tf-why-link {
        display: block;
        margin-top: 6px;
        min-height: 44px;
        line-height: 44px;
        width: max-content;            /* tap target hugs the word, row is full-width */
      }
    }

/* ─── former inline <style> block 2 — #bank-trust-modal keyframes
       + media query. Was portal.html ~L2524-2530. ───────────────── */
    @keyframes bankTrustModalIn {
      0% { opacity: 0; transform: translateY(4px); }
      100% { opacity: 1; transform: translateY(0); }
    }
    @media (max-width: 540px) {
      #bank-trust-modal-grid { grid-template-columns: 1fr !important; }
    }

/* ─── former inline <style> block 3 — .claude-sb-banner.
       Was portal.html ~L2907-2966. ───────────────────────────────── */
        /* Banner — soft amber, 1px ledger-line bottom, never alarming red.
           Inherits font-family from body. */
        .claude-sb-banner {
          background: #FFF8E1;
          border: 1px solid #F2D38A;
          border-radius: 12px;
          margin: 0 0 16px 0;
          padding: 0;
          color: #1F1F1F;
          font-size: 13px;
          line-height: 1.5;
          letter-spacing: -0.005em;
          box-shadow: 0 1px 0 rgba(15,23,42,0.02);
        }
        .claude-sb-banner__inner {
          display: flex;
          align-items: center;
          gap: 12px;
          padding: 10px 14px;
        }
        .claude-sb-banner__pill {
          flex-shrink: 0;
          display: inline-block;
          background: #B45309;
          color: #FFFFFF;
          font-size: 10px;
          font-weight: 700;
          letter-spacing: 0.08em;
          text-transform: uppercase;
          padding: 3px 8px;
          border-radius: 4px;
          line-height: 1;
        }
        .claude-sb-banner__copy { flex: 1; min-width: 0; }
        .claude-sb-banner__copy a {
          color: #0A6EBD;
          font-weight: 600;
          text-decoration: none;
          margin-left: 4px;
          white-space: nowrap;
        }
        .claude-sb-banner__copy a:hover { text-decoration: underline; }
        .claude-sb-banner__close {
          flex-shrink: 0;
          background: transparent;
          border: 0;
          color: #6B4F1A;
          font-size: 18px;
          line-height: 1;
          width: 28px;
          height: 28px;
          border-radius: 6px;
          cursor: pointer;
          padding: 0;
        }
        .claude-sb-banner__close:hover { background: rgba(180,83,9,0.08); }
        @media (max-width: 640px) {
          .claude-sb-banner__inner { flex-wrap: wrap; padding: 8px 12px; }
          .claude-sb-banner__copy { font-size: 12.5px; flex-basis: 100%; }
        }

/* ─── former inline <style> block 4 — .treasury-cockpit.
       Was portal.html ~L3065-3211. ───────────────────────────────── */
        /* Treasury Cockpit — Square / Linear visual register.
           Tabular-nums everywhere so 7-digit numbers don't wobble. */
        .treasury-cockpit {
          background: #FFFFFF;
          border: 1px solid rgba(15,23,42,0.08);
          border-radius: 14px;
          padding: 28px 28px 22px;
          margin: 0 0 20px;
          box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04), 0 8px 24px rgba(15, 23, 42, 0.06);
          /* Faint ledger-paper texture via CSS gradient — no image asset
             so we don't add a network round-trip. */
          background-image:
            linear-gradient(transparent 0 31px, rgba(15,23,42,0.025) 32px),
            radial-gradient(at top right, rgba(15,118,110,0.05), transparent 40%);
          background-size: 100% 32px, auto;
          position: relative;
        }
        .treasury-cockpit__head {
          display: flex;
          justify-content: space-between;
          align-items: center;
          margin-bottom: 14px;
        }
        .treasury-cockpit__label {
          font-size: 11.5px;
          font-weight: 600;
          color: #5A6878;
          letter-spacing: 0.08em;
          text-transform: uppercase;
        }
        .treasury-cockpit__cmd-trigger {
          display: inline-flex;
          align-items: center;
          gap: 7px;
          background: #F6F8FB;
          border: 1px solid rgba(15,23,42,0.08);
          border-radius: 8px;
          color: #475569;
          font-size: 12px;
          font-weight: 500;
          padding: 6px 10px 6px 9px;
          cursor: pointer;
          transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
          font-family: inherit;
        }
        .treasury-cockpit__cmd-trigger:hover {
          background: #FFFFFF;
          border-color: rgba(15,118,110,0.32);
          color: #0F766E;
        }
        .treasury-cockpit__kbd {
          background: #FFFFFF;
          border: 1px solid rgba(15,23,42,0.10);
          border-radius: 4px;
          padding: 1px 5px;
          font-size: 10.5px;
          color: #475569;
          font-family: 'JetBrains Mono', ui-monospace, "SF Mono", Menlo, monospace;
        }
        .treasury-cockpit__primary {
          display: grid;
          grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
          align-items: end;
          gap: 24px;
          margin-bottom: 20px;
        }
        .treasury-cockpit__amount {
          font-family: 'JetBrains Mono', ui-monospace, "SF Mono", Menlo, monospace;
          font-variant-numeric: tabular-nums;
          font-feature-settings: "tnum" 1;
          font-size: 56px;
          font-weight: 700;
          line-height: 1;
          letter-spacing: -0.035em;
          color: #0B1020;
        }
        .treasury-cockpit__spark {
          width: 100%;
          height: 64px;
          align-self: end;
        }
        .treasury-cockpit__tiles {
          display: grid;
          grid-template-columns: repeat(5, minmax(0, 1fr));
          gap: 12px;
          margin-bottom: 14px;
        }
        .cockpit-tile {
          background: #FFFFFF;
          border: 1px solid rgba(15,23,42,0.07);
          border-radius: 10px;
          padding: 14px 14px 12px;
          transition: transform 160ms ease, box-shadow 160ms ease, border-color 160ms ease;
        }
        .cockpit-tile:hover {
          transform: translateY(-1px);
          box-shadow: 0 4px 12px rgba(15, 23, 42, 0.08);
          border-color: rgba(15,118,110,0.22);
        }
        .cockpit-tile__label {
          font-size: 10.5px;
          font-weight: 600;
          color: #5A6878;
          letter-spacing: 0.06em;
          text-transform: uppercase;
          margin-bottom: 6px;
        }
        .cockpit-tile__value {
          font-family: 'JetBrains Mono', ui-monospace, "SF Mono", Menlo, monospace;
          font-variant-numeric: tabular-nums;
          font-feature-settings: "tnum" 1;
          font-size: 20px;
          font-weight: 700;
          color: #0B1020;
          letter-spacing: -0.02em;
          line-height: 1.1;
        }
        .cockpit-tile__sub {
          font-size: 11px;
          color: #5A6878;
          margin-top: 4px;
        }
        .treasury-cockpit__foot {
          display: flex;
          align-items: center;
          gap: 8px;
          font-size: 11.5px;
          color: #5A6878;
          padding-top: 14px;
          border-top: 1px dashed rgba(15,23,42,0.08);
        }
        .cockpit-foot__dot {
          display: inline-block;
          width: 6px;
          height: 6px;
          border-radius: 50%;
          background: #16A34A;
          box-shadow: 0 0 0 3px rgba(22, 163, 74, 0.16);
        }

        /* ── Covenant strip (2026-05-23) ──────────────────────────────
           The slim covenant verdict, inserted between the primary row and the
           5-tile grid. CDO spec (Option A). Status drives color via the single
           data-covenant-status switch (clear|approaching|projected_breach|
           breached); JS maps the backend enum `ok` -> `clear`. Tokens only —
           no raw hex (the standalone-mockup :root fallback is intentionally
           NOT lifted; production tokens come from vendor/design-tokens.css).
           Teal-and-neutral when clear (a TRUST state, not a green direction). */
        .cockpit-covenant {
          display: flex;
          align-items: center;
          gap: 14px;
          flex-wrap: wrap;
          padding: 12px 16px;
          margin-bottom: 14px;
          border-radius: var(--radius-md, 8px);
          border: 1px solid var(--accent-border, #B6E3DC);
          background: var(--accent-soft, #ECFDF5);
          font-size: 12.5px;
          color: var(--ink-900, #0B1020);
          letter-spacing: -0.1px;
          animation: cov-in 200ms var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1));
        }
        @keyframes cov-in {
          from { opacity: 0; transform: translateY(3px); }
          to   { opacity: 1; transform: none; }
        }
        .cov-status { display: inline-flex; align-items: center; gap: 8px; flex-shrink: 0; }
        .cov-dot {
          width: 7px; height: 7px; border-radius: 50%;
          background: var(--accent, #0F766E);
          box-shadow: 0 0 0 3px rgba(15, 118, 110, 0.16);
          flex-shrink: 0;
        }
        .cov-word {
          font-weight: 700; font-size: 13px; letter-spacing: -0.15px;
          color: var(--accent-strong, #0A4D47);
        }
        .cov-fact { display: inline-flex; align-items: baseline; gap: 6px; min-width: 0; }
        .cov-label {
          font-size: 10.5px; font-weight: 600; text-transform: uppercase;
          letter-spacing: 0.06em; color: var(--ink-400, #8C8C85);
        }
        .cov-val {
          font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1;
          font-size: 13px; font-weight: 700; color: var(--ink-900, #0B1020);
          letter-spacing: -0.02em;
        }
        .cov-headroom {
          font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1;
          font-weight: 700; font-size: 13px; color: var(--accent-strong, #0A4D47);
        }
        .cov-week {
          font-size: 10.5px; font-weight: 600; color: var(--ink-400, #8C8C85);
        }
        .cov-sep { color: var(--ink-300, #BFBFB9); }
        .cov-edit {
          margin-left: auto; background: none; border: 0;
          color: var(--ink-500, #5F5F58); font-size: 11px; font-weight: 600;
          cursor: pointer; font-family: inherit; padding: 4px 6px;
          border-radius: 6px; display: inline-flex; align-items: center; gap: 5px;
          transition: color 120ms var(--ease-out, ease), background 120ms var(--ease-out, ease);
        }
        .cov-edit:hover { color: var(--accent, #0F766E); background: rgba(15, 118, 110, 0.06); }

        /* ── status modifiers ── */
        .cockpit-covenant[data-covenant-status="approaching"],
        .cockpit-covenant[data-covenant-status="projected_breach"] {
          border-color: var(--warn-border, #F3D98B);
          background: var(--warn-soft, #FEF3C7);
        }
        .cockpit-covenant[data-covenant-status="approaching"] .cov-word,
        .cockpit-covenant[data-covenant-status="projected_breach"] .cov-word,
        .cockpit-covenant[data-covenant-status="approaching"] .cov-headroom,
        .cockpit-covenant[data-covenant-status="projected_breach"] .cov-headroom {
          color: var(--warn-text, #B45309);
        }
        .cockpit-covenant[data-covenant-status="approaching"] .cov-dot,
        .cockpit-covenant[data-covenant-status="projected_breach"] .cov-dot {
          background: var(--warn, #D97706);
          box-shadow: 0 0 0 3px rgba(217, 119, 6, 0.18);
        }
        /* projected adds a wider ring to escalate WITHOUT a color jump (a
           projected breach has not happened — amber + a named week, not red). */
        .cockpit-covenant[data-covenant-status="projected_breach"] .cov-dot {
          box-shadow: 0 0 0 4px rgba(217, 119, 6, 0.22);
        }
        .cockpit-covenant[data-covenant-status="breached"] {
          border-color: var(--cash-down-border, #F3B0B0);
          background: var(--cash-down-soft, #FEE6E6);
        }
        .cockpit-covenant[data-covenant-status="breached"] .cov-word,
        .cockpit-covenant[data-covenant-status="breached"] .cov-headroom {
          color: var(--cash-down-text, #B91C1C);
        }
        .cockpit-covenant[data-covenant-status="breached"] .cov-dot {
          background: var(--cash-down, #DC2626);
          box-shadow: 0 0 0 4px rgba(220, 38, 38, 0.20);
        }

        /* ══════════════════════════════════════════════════════════════
           Bank-truth delta (2026-06-07) — the moat line directly under the
           cockpit hero: live bank balance (Plaid) vs the QuickBooks book
           balance (lags ~2d), with the gap named. A GL-only tool cannot show
           this line — it has only one of the two numbers. Renders ONLY when
           the backend returns has_data (a connected QBO + an active bank), so
           a no-QBO login (Matt) never sees this node — it stays display:none.
           Same slim-strip register as .cockpit-covenant; tokens first with the
           cockpit's own hex fallbacks (portal.css does not import :root tokens
           at build time). */
        .cockpit-bank-truth {
          display: flex;
          align-items: center;
          gap: 12px;
          flex-wrap: wrap;
          padding: 10px 16px;
          margin: 0 0 14px;
          border-radius: var(--radius-md, 8px);
          border: 1px solid var(--border-subtle, rgba(15,23,42,0.08));
          background: var(--surface-2, #F8FAFC);
          font-size: 12.5px;
          color: var(--ink-900, #0B1020);
          letter-spacing: -0.1px;
          animation: cov-in 200ms var(--ease-out, cubic-bezier(0.16, 1, 0.3, 1));
        }
        .bt-pair { display: inline-flex; align-items: baseline; gap: 6px; min-width: 0; }
        .bt-label {
          font-size: 10.5px; font-weight: 600; text-transform: uppercase;
          letter-spacing: 0.06em; color: var(--ink-400, #8C8C85);
        }
        .bt-val {
          font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1;
          font-size: 13px; font-weight: 700; color: var(--ink-900, #0B1020);
          letter-spacing: -0.02em;
        }
        /* The book figure is a secondary truth (lags the bank) — slightly
           recessed so the eye reads live-bank first, then the gap. */
        .bt-val--book { color: var(--ink-500, #5F5F58); font-weight: 600; }
        .bt-sep { color: var(--ink-300, #BFBFB9); }
        /* The named gap — the payoff. Pushed to the right so the two balances
           read as the premise and the explanation as the conclusion. */
        .bt-gap {
          margin-left: auto;
          display: inline-flex; align-items: center; gap: 7px;
          font-size: 12px; font-weight: 600;
          color: var(--accent-strong, #0A4D47);
        }
        .bt-dot {
          width: 7px; height: 7px; border-radius: 50%;
          background: var(--accent, #0F766E);
          box-shadow: 0 0 0 3px rgba(15, 118, 110, 0.16);
          flex-shrink: 0;
        }
        .bt-note {
          font-size: 12px; font-weight: 600; color: var(--ink-500, #5F5F58);
        }
        /* Direction modifiers — bank_ahead (deposits incoming) stays teal/accent
           (the default, positive cash story). qbo_ahead (payments cleared ahead
           of the books) is amber — money already left the bank that the books
           haven't caught. aligned is muted (no story to tell). NO red: a timing
           gap is never an error, it's the ~2-day book lag the bank doesn't have. */
        .cockpit-bank-truth[data-bank-truth-direction="qbo_ahead"] .bt-gap {
          color: var(--warn-text, #B45309);
        }
        .cockpit-bank-truth[data-bank-truth-direction="qbo_ahead"] .bt-dot {
          background: var(--warn, #D97706);
          box-shadow: 0 0 0 3px rgba(217, 119, 6, 0.18);
        }

        /* ══════════════════════════════════════════════════════════════
           Multi-entity consolidated "All companies" home + empty-company
           (2026-06-04). Renders ONLY at >=2 active owned companies (or a
           focused company with zero banks). Single-company logins + Matt
           never see this node — it stays display:none. Mercury/Ramp
           register: one consolidated balance up top, a card per company
           you read WITHOUT switching. Same white card + 14px radius +
           JetBrains-Mono tabular numerals + single teal accent as
           .treasury-cockpit so it reads as the same product. Tokens
           first; the hex fallbacks mirror the cockpit's own literals
           (portal.css does not import the :root tokens at build time). */
        .me-home {
          background: #FFFFFF;
          border: 1px solid rgba(15,23,42,0.08);
          border-radius: 14px;
          padding: 28px 28px 22px;
          margin: 0 0 20px;
          box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04), 0 8px 24px rgba(15, 23, 42, 0.06);
          background-image:
            linear-gradient(transparent 0 31px, rgba(15,23,42,0.025) 32px),
            radial-gradient(at top right, rgba(15,118,110,0.05), transparent 40%);
          background-size: 100% 32px, auto;
          position: relative;
        }
        .me-home__label {
          font-size: 11.5px; font-weight: 600; color: #5A6878;
          letter-spacing: 0.08em; text-transform: uppercase; margin-bottom: 10px;
        }
        .me-home__hero {
          font-family: 'JetBrains Mono', ui-monospace, "SF Mono", Menlo, monospace;
          font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1;
          font-size: 56px; font-weight: 700; line-height: 1;
          letter-spacing: -0.035em; color: #0B1020;
        }
        .me-home__sub {
          font-size: 12.5px; color: #5A6878; margin-top: 8px; font-weight: 500;
        }
        .me-home__grid {
          display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
          gap: 12px; margin-top: 22px;
        }
        /* Per-company card — a clickable focus tile. Hover-lift mirrors
           .cockpit-tile so the two grids feel like one system. */
        .me-card {
          display: flex; align-items: center; gap: 12px; width: 100%;
          text-align: left; background: #FFFFFF; font: inherit;
          border: 1px solid rgba(15,23,42,0.07); border-radius: 10px;
          padding: 16px; cursor: pointer; color: #0B1020;
          transition: transform 160ms ease, box-shadow 160ms ease, border-color 160ms ease;
        }
        .me-card:hover {
          transform: translateY(-1px);
          box-shadow: 0 4px 12px rgba(15, 23, 42, 0.08);
          border-color: rgba(15,118,110,0.22);
        }
        .me-card:focus-visible {
          outline: 2px solid #0F766E; outline-offset: 2px;
        }
        .me-card__dot { width: 11px; height: 11px; border-radius: 50%; flex: 0 0 auto; }
        .me-card__body { flex: 1 1 auto; min-width: 0; }
        .me-card__name {
          font-size: 14px; font-weight: 700; color: #0B1020; letter-spacing: -0.1px;
          white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
        }
        .me-card__cash {
          font-family: 'JetBrains Mono', ui-monospace, "SF Mono", Menlo, monospace;
          font-variant-numeric: tabular-nums; font-feature-settings: "tnum" 1;
          font-size: 19px; font-weight: 700; color: #0B1020; letter-spacing: -0.02em;
          margin-top: 3px;
        }
        .me-card__default-tag {
          display: inline-block; font-size: 9.5px; font-weight: 700;
          text-transform: uppercase; letter-spacing: 0.06em; color: #0F766E;
          background: rgba(15,118,110,0.09); border-radius: 5px;
          padding: 1px 5px; margin-left: 6px; vertical-align: 1px;
        }
        .me-card__chev { flex: 0 0 auto; opacity: 0.4; color: #5A6878; }
        /* Empty company (no bank) — dashed border + the connect prompt.
           NEVER shows "$0": a CFO must not confuse "no bank" with "$0 cash". */
        .me-card--empty {
          border: 1px dashed rgba(15,23,42,0.16); background: #FAFBFC;
        }
        .me-card--empty:hover { border-color: rgba(15,118,110,0.34); }
        .me-card__prompt {
          font-size: 12.5px; font-weight: 600; color: #0F766E; margin-top: 3px;
        }
        /* Skeleton shimmer for the hero on company switch — never the prior
           value, never "—". Reuses the page `shimmer` keyframe. */
        .me-skel {
          display: inline-block; border-radius: 8px; vertical-align: middle;
          background: linear-gradient(90deg, #EEF1F5 25%, #E2E7EE 37%, #EEF1F5 63%);
          background-size: 200% 100%; animation: shimmer 1.5s ease-in-out infinite;
        }
        .me-skel--hero { width: 260px; max-width: 70%; height: 52px; }
        /* ── Focused-company context bar (#5, 2026-06-04) ────────────────────
           Previously the bar + its "All companies" back button were unstyled
           browser chrome (the bar had no rule at all; the back button was a bare
           link). Restyled to the on-brand .btn.btn-ghost / tf-btn family: a
           bordered card holding a ghost back-button (chevron + label), the
           company name, and a tabular cash meta. Tokens with hex fallbacks so it
           renders correctly even if a token is missing. */
        .cosw-ctx {
          display: flex; align-items: center; gap: 10px;
          padding: 10px 14px; margin: 0 0 16px;
          border: 1px solid var(--ink-150, #E8ECF1); border-radius: 12px;
          background: var(--surface-card, #fff); font-size: 13px;
        }
        .cosw-ctx__name {
          font-weight: 700; color: var(--ink-900, #0B1020); letter-spacing: -0.1px;
        }
        .cosw-ctx__meta {
          margin-left: auto; font-variant-numeric: tabular-nums;
          font-weight: 700; color: var(--ink-900, #0B1020);
        }
        .cosw-ctx__back {
          order: -1; display: inline-flex; align-items: center; gap: 6px;
          padding: 6px 11px; margin: 0;
          border: 1px solid var(--ink-200, #D9DEE6); border-radius: 8px;
          background: var(--ink-50, #F7F9FB); color: var(--ink-700, #334155);
          font: inherit; font-size: 12px; font-weight: 600; cursor: pointer;
          transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
        }
        .cosw-ctx__back:hover {
          background: rgba(15,118,110,0.07);
          border-color: rgba(15,118,110,0.30); color: var(--accent, #0F766E);
        }
        .cosw-ctx__back:focus-visible {
          outline: 2px solid var(--accent, #0F766E); outline-offset: 2px;
        }
        /* Touch targets — on a coarse pointer (touch), every switcher menu row
           (the company switcher, the firm "Jump to client…" launcher, AND the
           in-client hopper all share .cosw__item) gets a >=44px hit area per
           the WCAG 2.5.5 / Apple HIG floor. The base .cosw__item styles are
           injected from portal.html; this is the touch-only override. */
        @media (pointer: coarse) {
          .cosw__item { min-height: 44px; }
        }
        @media (max-width: 780px) {
          .me-home { padding: 22px 18px 18px; }
          .me-home__hero { font-size: 40px; }
          .me-home__grid { grid-template-columns: 1fr; }
          .me-card { min-height: 56px; }
        }

        /* ── empty state (no floor set) — a calm onramp, NOT a nag ── */
        .cockpit-covenant--empty {
          border: 1px dashed var(--ink-200, #D9D9D5);
          background: var(--ink-50, #F7F7F5);
          color: var(--ink-500, #5F5F58);
          font-size: 12.5px; font-weight: 500; animation: none;
        }
        .cockpit-covenant--empty b { color: var(--ink-900, #0B1020); font-weight: 700; }
        .cov-onramp-cta {
          margin-left: auto; background: none; border: 0;
          color: var(--accent, #0F766E); font-size: 12.5px; font-weight: 700;
          cursor: pointer; font-family: inherit; padding: 4px 6px;
          border-radius: 6px; white-space: nowrap;
        }
        .cov-onramp-cta:hover { text-decoration: underline; text-underline-offset: 2px; }

        /* ≤480px: stack to a mini-card. The colored headroom number stays
           prominent (≥16px); the edit button becomes a full-width 44px tap
           target. All type ≥11px (older-buyer contrast floor). */
        @media (max-width: 480px) {
          .cockpit-covenant { font-size: 12px; gap: 8px 10px; }
          .cov-headroom { font-size: 16px; flex-basis: 100%; }
          .cov-sep { display: none; }
          .cov-edit, .cov-onramp-cta {
            margin-left: 0; width: 100%; justify-content: center; min-height: 44px;
          }
        }

        /* ── Frontier cockpit additions (2026-05-22) ──────────────────
           Provenance line under the greeting, the hero verdict line, and the
           drill affordance on the net-cash amount. Same Square/Linear register
           as the cockpit: one teal accent, calm, tabular numerals. */

        /* Provenance line — the trust proof under the greeting. Sits in the
           hero-bar; reads as a verified-receipt: check mark + plain sentence. */
        .cockpit-provenance {
          display: inline-flex;
          align-items: center;
          gap: 7px;
          margin-top: 6px;
          font-size: 12.5px;
          font-weight: 500;
          color: #475569;
          letter-spacing: -0.1px;
          line-height: 1.35;
        }
        .cockpit-provenance__check {
          display: inline-flex;
          align-items: center;
          justify-content: center;
          color: var(--accent, #0F766E);
          flex-shrink: 0;
        }
        .cockpit-provenance b,
        .cockpit-provenance strong { color: #0B1020; font-weight: 700; }
        .cockpit-provenance .cockpit-provenance__sep { color: #C4CCD6; padding: 0 1px; }
        /* When a sync is stale the JS adds this modifier — the check turns amber
           and the copy says "needs a refresh" so trust isn't over-claimed. */
        .cockpit-provenance.is-stale .cockpit-provenance__check { color: #B45309; }

        /* Left column of the primary row — stacks the amount + verdict. */
        .treasury-cockpit__primary-col {
          min-width: 0;
          display: flex;
          flex-direction: column;
          gap: 8px;
          align-self: center;
        }

        /* The amount is now a button (drill affordance). Reset button chrome;
           the chevron fades in on hover/focus so the resting state is the clean
           number, and the affordance reveals on intent. */
        .treasury-cockpit__amount-btn {
          display: inline-flex;
          align-items: center;
          gap: 10px;
          background: none;
          border: none;
          padding: 0;
          margin: 0;
          font-family: inherit;
          cursor: pointer;
          text-align: left;
          border-radius: 8px;
          transition: opacity 140ms ease;
        }
        .treasury-cockpit__amount-btn:focus-visible {
          outline: 2px solid var(--accent, #0F766E);
          outline-offset: 4px;
        }
        .treasury-cockpit__amount-chev {
          display: inline-flex;
          align-items: center;
          justify-content: center;
          color: var(--accent, #0F766E);
          opacity: 0;
          transform: translateX(-4px);
          transition: opacity 160ms ease, transform 160ms ease;
        }
        .treasury-cockpit__amount-btn:hover .treasury-cockpit__amount-chev,
        .treasury-cockpit__amount-btn:focus-visible .treasury-cockpit__amount-chev {
          opacity: 1;
          transform: translateX(0);
        }
        .treasury-cockpit__amount-btn:hover .treasury-cockpit__amount {
          color: var(--accent, #0F766E);
        }

        /* Verdict line — the cash answer in one sentence. The delta token is a
           colored pill; the runway clause is emphasized. */
        .treasury-cockpit__verdict {
          font-size: 14px;
          font-weight: 500;
          color: #475569;
          letter-spacing: -0.1px;
          line-height: 1.4;
          display: flex;
          align-items: center;
          gap: 8px;
          flex-wrap: wrap;
          min-height: 20px; /* reserve space — no layout shift when it lands */
        }
        .treasury-cockpit__lowpoint {
          margin-top: 6px;
          font-size: 12px;
          font-weight: 600;
          color: var(--ink-500);
          letter-spacing: -0.1px;
          line-height: 1.4;
          min-height: 16px; /* reserve space — no layout shift when it lands */
        }
        .treasury-cockpit__verdict .verdict-delta {
          display: inline-flex;
          align-items: center;
          gap: 4px;
          font-weight: 700;
          font-size: 12.5px;
          padding: 2px 9px;
          border-radius: 9999px;
          letter-spacing: 0;
          font-variant-numeric: tabular-nums;
        }
        .treasury-cockpit__verdict .verdict-delta--up {
          color: var(--cash-up, #15803D);
          background: rgba(22, 163, 74, 0.10);
        }
        .treasury-cockpit__verdict .verdict-delta--down {
          color: var(--cash-down, #B91C1C);
          background: rgba(220, 38, 38, 0.09);
        }
        .treasury-cockpit__verdict .verdict-delta--flat {
          color: #5A6878;
          background: rgba(148, 163, 184, 0.14);
        }
        .treasury-cockpit__verdict strong { color: #0B1020; font-weight: 700; }

        /* ── COVERAGE + TRIAGE verdict line — 2026-06-08 ─────────────────────
           The money-sensitive first-value centerpiece (research killed the word
           "safe"). A two-part block: a bold COVERAGE/TRIAGE headline answering
           "am I covered, through when, with how much spare", and a quieter sub
           clause naming the single sharpest risk or the buffer. The container
           uses flex-wrap row for the legacy delta pills; this line is a column,
           so it overrides flex-direction to stack the two parts cleanly. */
        .treasury-cockpit__verdict .cockpit-verdict-line {
          display: flex;
          flex-direction: column;
          gap: 2px;
          width: 100%;
        }
        .treasury-cockpit__verdict .cockpit-verdict-line__primary {
          font-size: 15px;
          font-weight: 700;
          color: var(--ink-900, #0B1020);
          letter-spacing: -0.2px;
          line-height: 1.35;
        }
        .treasury-cockpit__verdict .cockpit-verdict-line__sub {
          font-size: 13px;
          font-weight: 500;
          color: var(--ink-500, #5A6878);
          letter-spacing: -0.1px;
          line-height: 1.4;
        }
        /* State 2 (warning) — the headline carries the cash-down accent and the
           whole block becomes a drill affordance into the alerts. NEVER a loud
           red fill: a CFO reads the tone, not a siren. */
        .treasury-cockpit__verdict .cockpit-verdict-line--warn {
          cursor: pointer;
          border-radius: 8px;
          margin: -4px -8px;
          padding: 4px 8px;
          transition: background 120ms ease;
        }
        .treasury-cockpit__verdict .cockpit-verdict-line--warn:hover,
        .treasury-cockpit__verdict .cockpit-verdict-line--warn:focus-visible {
          background: var(--cash-down-soft, #FEF2F2);
          outline: none;
        }
        .treasury-cockpit__verdict .cockpit-verdict-line--warn .cockpit-verdict-line__primary {
          color: var(--cash-down, #B91C1C);
        }
        /* First-paint neutral state — calm, never a blank row or a wrong number. */
        .treasury-cockpit__verdict .verdict-loading {
          font-size: 13.5px;
          font-weight: 500;
          color: var(--ink-400, #94A3B8);
          letter-spacing: -0.1px;
        }

        /* ── Post-connect sync state (State B) — 2026-06-04 ──────────────────
           Shown to a just-connected user whose balance is in but whose
           transactions are still importing. Honest, finite, calm — the same teal
           accent + tabular register as the cockpit, never an error tone. The
           banner replaces the verdict line; the placeholder fills the big panels
           in place of a "Sync Bank Now" onramp (wrong copy for a syncing user). */
        .cockpit-syncing-banner {
          display: inline-flex; align-items: center; gap: 8px;
          font-size: 13px; font-weight: 600; color: var(--ink-700, #334155);
          letter-spacing: -0.1px; line-height: 1.4;
        }
        .cockpit-syncing-banner .ob-spin-sm { flex: 0 0 auto; }
        /* The importing placeholder rendered into Money In & Out / Forecast / AR
           during State B. A bordered calm card, NOT an empty/"$0" panel. */
        .cockpit-sync-placeholder {
          display: flex; align-items: center; gap: 12px;
          padding: 20px 22px; margin: 0;
          border: 1px solid var(--ink-150, #E8ECF1); border-radius: 12px;
          background: var(--ink-50, #F7F9FB);
        }
        .cockpit-sync-placeholder .ob-spin-sm { flex: 0 0 auto; }
        .cockpit-sync-placeholder__title {
          font-size: 13.5px; font-weight: 700; color: var(--ink-900, #0B1020);
          letter-spacing: -0.1px;
        }
        .cockpit-sync-placeholder__desc {
          font-size: 12.5px; font-weight: 500; color: var(--ink-500, #5A6878);
          margin-top: 2px; line-height: 1.4;
        }

        @media (max-width: 780px) {
          .treasury-cockpit__primary { grid-template-columns: 1fr; gap: 14px; }
          .treasury-cockpit__primary-col { align-self: start; }
          .treasury-cockpit__amount { font-size: 44px; }
          .treasury-cockpit__tiles { grid-template-columns: repeat(2, minmax(0,1fr)); }
          /* On touch there is no hover — show the chevron at rest so the drill
             affordance is discoverable. */
          .treasury-cockpit__amount-chev { opacity: 0.5; transform: none; }
        }
        @media (max-width: 420px) {
          .treasury-cockpit { padding: 22px 18px 18px; }
          .treasury-cockpit__amount { font-size: 36px; }
          .treasury-cockpit__verdict { font-size: 13px; }
          .treasury-cockpit__lowpoint { font-size: 11px; }
          .treasury-cockpit__tiles { grid-template-columns: 1fr; }
          .cockpit-provenance { font-size: 12px; }
        }

/* ─── former inline <style> block 5 — .cmd-bar command palette.
       Was portal.html ~L3250-3410. ───────────────────────────────── */
        /* Cash Command Bar — Linear / Raycast register. Full-cover scrim
           with a 520px panel; 92vw on mobile. */
        .cmd-bar { position: fixed; inset: 0; z-index: 12000; }
        .cmd-bar[hidden] { display: none; }
        .cmd-bar__scrim {
          position: absolute; inset: 0;
          background: rgba(11,16,32,0.42);
          backdrop-filter: blur(4px);
          -webkit-backdrop-filter: blur(4px);
        }
        .cmd-bar__panel {
          position: relative;
          max-width: 560px;
          width: 92vw;
          margin: 12vh auto 0;
          background: #FFFFFF;
          border-radius: 14px;
          box-shadow: 0 12px 24px rgba(15,23,42,0.12), 0 32px 96px rgba(15,23,42,0.20);
          overflow: hidden;
          font-family: inherit;
          animation: cmdBarIn 140ms ease-out;
        }
        @keyframes cmdBarIn {
          from { opacity: 0; transform: translateY(-8px) scale(0.985); }
          to { opacity: 1; transform: translateY(0) scale(1); }
        }
        .cmd-bar__search {
          display: flex;
          align-items: center;
          gap: 10px;
          padding: 14px 16px;
          border-bottom: 1px solid rgba(15,23,42,0.08);
          color: #5A6878;
        }
        .cmd-bar__search input {
          flex: 1;
          border: 0;
          outline: 0;
          font-size: 15px;
          color: #0B1020;
          background: transparent;
          font-family: inherit;
          letter-spacing: -0.01em;
        }
        .cmd-bar__search input::placeholder { color: #94A3B8; }
        .cmd-bar__esc {
          font-size: 10.5px;
          font-family: 'JetBrains Mono', ui-monospace, "SF Mono", Menlo, monospace;
          color: #475569;
          background: #F6F8FB;
          border: 1px solid rgba(15,23,42,0.10);
          border-radius: 4px;
          padding: 1px 6px;
        }
        .cmd-bar__list {
          max-height: 56vh;
          overflow-y: auto;
          padding: 6px 0 8px;
        }
        .cmd-bar__empty {
          padding: 24px 16px;
          text-align: center;
          color: #5A6878;
          font-size: 13px;
        }
        .cmd-bar__group {
          padding: 10px 16px 4px;
          font-size: 10.5px;
          letter-spacing: 0.08em;
          font-weight: 600;
          color: #5A6878;
          text-transform: uppercase;
        }
        .cmd-bar__item {
          display: flex;
          align-items: center;
          gap: 10px;
          padding: 9px 16px;
          cursor: pointer;
          font-size: 13.5px;
          color: #0B1020;
          letter-spacing: -0.005em;
          line-height: 1.3;
        }
        .cmd-bar__item:hover,
        .cmd-bar__item[aria-selected="true"] {
          background: #F6F8FB;
        }
        .cmd-bar__item-label { flex: 1; font-weight: 500; }
        .cmd-bar__item-desc { color: #5A6878; font-size: 11.5px; font-weight: 400; margin-top: 1px; }
        .cmd-bar__ai-badge {
          flex-shrink: 0;
          background: rgba(15, 118, 110, 0.10);
          color: #0F766E;
          border: 1px solid rgba(15,118,110,0.22);
          font-size: 9.5px;
          letter-spacing: 0.08em;
          text-transform: uppercase;
          font-weight: 700;
          border-radius: 4px;
          padding: 2px 6px;
          margin-left: 4px;
        }
        .cmd-bar__result {
          padding: 14px 16px;
          border-top: 1px solid rgba(15,23,42,0.08);
          font-size: 12.5px;
          color: #1F2937;
          background: #FAFBFC;
          max-height: 30vh;
          overflow-y: auto;
        }
        .cmd-bar__result pre {
          font-family: 'JetBrains Mono', ui-monospace, "SF Mono", Menlo, monospace;
          font-size: 11.5px;
          line-height: 1.5;
          color: #0B1020;
          background: transparent;
          padding: 0;
          margin: 0;
          white-space: pre-wrap;
          word-break: break-word;
        }
        .cmd-bar__result-status {
          display: inline-flex;
          align-items: center;
          gap: 6px;
          font-size: 11px;
          letter-spacing: 0.06em;
          font-weight: 700;
          text-transform: uppercase;
          color: #166534;
          margin-bottom: 8px;
        }
        .cmd-bar__result-status[data-state="error"] { color: #991B1B; }
        .cmd-bar__foot {
          display: flex;
          justify-content: space-between;
          align-items: center;
          padding: 9px 16px;
          border-top: 1px solid rgba(15,23,42,0.08);
          font-size: 11px;
          color: #5A6878;
          background: #FBFCFE;
          gap: 12px;
          flex-wrap: wrap;
        }
        .cmd-bar__foot kbd {
          font-family: 'JetBrains Mono', ui-monospace, "SF Mono", Menlo, monospace;
          font-size: 10px;
          color: #475569;
          background: #FFFFFF;
          border: 1px solid rgba(15,23,42,0.10);
          border-radius: 4px;
          padding: 0 4px;
          margin: 0 2px;
        }
        @media (max-width: 520px) {
          .cmd-bar__panel { width: 92vw; margin-top: 8vh; }
          .cmd-bar__list { max-height: 60vh; }
        }

/* ─── former inline <style> block 6 — .fb-grid Forecast Builder
       grid. Was portal.html ~L16568-16650. ──────────────────────── */
    /* Forecast Builder grid styles (scoped) */
    .fb-grid th, .fb-grid td { font-family: inherit; }
    .fb-grid__sticky-col {
      position: sticky; left: 0; background: #fff;
      border-right: 1px solid var(--border-default, #e2e8f0); z-index: 4;
    }
    .fb-grid__cell {
      border-bottom: 1px solid rgba(15,23,42,0.06);
      border-right: 1px solid rgba(15,23,42,0.04);
      padding: 0; min-width: 92px; height: 32px;
    }
    .fb-grid__cell--past { background: #F6F8FB; }
    .fb-grid__cell--current { background: #ECFDF5; }
    .fb-grid__cell input {
      width: 100%; height: 32px; border: 0; padding: 0 10px;
      font: inherit; font-variant-numeric: tabular-nums;
      text-align: right; background: transparent; color: var(--ink-900, #0f172a);
      outline: none;
    }
    .fb-grid__cell input:focus {
      box-shadow: inset 0 0 0 2px #0F766E;
      background: #fff;
    }
    /* Saved-flash micro-animation: triggered when a cell write succeeds */
    @keyframes fbCellSavedFlash {
      0%   { background: rgba(16,185,129,0.18); }
      100% { background: transparent; }
    }
    .fb-grid__cell--saved-flash {
      animation: fbCellSavedFlash 700ms ease-out;
    }
    .fb-grid__cell--actual {
      font-weight: 600; color: #166534; padding: 0 10px;
      text-align: right; font-variant-numeric: tabular-nums;
      font-size: 11px;
    }
    .fb-grid__cell--variance {
      padding: 0 10px; text-align: right; font-variant-numeric: tabular-nums;
      font-size: 11px;
    }
    .fb-grid__cell--variance.pos { color: #166534; }
    .fb-grid__cell--variance.neg { color: #991B1B; }
    .fb-grid__cell--label {
      padding: 8px 14px; font-weight: 600;
      border-right: 1px solid var(--border-default, #e2e8f0);
      background: #fff; min-width: 200px;
    }
    .fb-grid__cell--series {
      padding: 6px 12px; color: var(--ink-500, #94a3b8);
      font-size: 11px; text-transform: uppercase; letter-spacing: 0.05em;
      border-right: 1px solid rgba(15,23,42,0.04);
      background: #fafafa;
    }
    .fb-grid__cell--section {
      padding: 6px 14px; font-weight: 700; font-size: 11px;
      letter-spacing: 0.08em; text-transform: uppercase;
      color: var(--ink-500, #64748b); background: #FBF8F1;
      border-bottom: 1px solid var(--border-default, #e2e8f0);
    }
    .fb-grid__week-header {
      padding: 8px 10px; font-size: 11px; font-weight: 600;
      color: var(--ink-900, #0f172a); border-bottom: 1px solid var(--border-default, #e2e8f0);
      border-right: 1px solid rgba(15,23,42,0.04);
      white-space: nowrap; min-width: 92px;
      background: #fafafa;
    }
    .fb-grid__week-header--current {
      background: #ECFDF5; color: #0F766E;
    }
    .fb-brand-tab {
      padding: 10px 16px; font-size: 13px; font-weight: 600;
      color: var(--ink-500, #64748b); background: transparent;
      border: 0; border-bottom: 2px solid transparent;
      cursor: pointer; transition: color 0.15s, border-color 0.15s;
      display: inline-flex; align-items: center; gap: 8px;
    }
    .fb-brand-tab:hover { color: var(--ink-900, #0f172a); }
    .fb-brand-tab--active {
      color: var(--ink-900, #0f172a); border-bottom-color: #0F766E;
    }
    .fb-brand-tab__dot {
      width: 9px; height: 9px; border-radius: 3px;
    }

/* ─── former inline <style id="byoe-styles"> block 7 — the BYOE
       upload + manual-mapping wizard. Was portal.html ~L16654-17021.
       v4 "Ledger" migration (dashboard-migration I5, plan §I5): I1
       extracted the block from inline; I5 routes its raw hex / rgba
       literals through the v4 token system. Slate neutrals → v4
       surface tokens, teal washes → --accent-soft/-softer, semantic
       greens/ambers/reds → the v4 cash / warn grades, the AI-assist
       violet → the governed --ai-* tokens. Pure-effect literals (the white
       progress-bar shimmer, decorative box-shadow alphas) are kept —
       they are not palette colours. No markup, no JS, no class touched. */
    /* ─── BYOE Upload v2 — CDO 2026-05-08 — v4-tokenized I5 ─── */
    .byoe-overlay{position:fixed;inset:0;background:var(--surface-overlay);backdrop-filter:blur(3px);z-index:9999;align-items:center;justify-content:center;display:flex;padding:24px;}
    .byoe-shell{background:var(--surface-raised);border-radius:14px;width:min(760px,100%);max-height:92vh;overflow:auto;box-shadow:var(--shadow-modal);display:flex;flex-direction:column;}
    .byoe-header{display:flex;justify-content:space-between;align-items:flex-start;gap:16px;padding:22px 24px 14px;border-bottom:1px solid var(--border-subtle);}
    .byoe-title{font-size:18px;font-weight:700;letter-spacing:-0.3px;color:var(--ink-900);}
    .byoe-sub{font-size:13px;color:var(--ink-500);margin-top:4px;line-height:1.55;max-width:60ch;}
    .byoe-close{background:none;border:0;color:var(--ink-500);cursor:pointer;width:44px;height:44px;border-radius:10px;display:inline-flex;align-items:center;justify-content:center;transition:background 140ms,color 140ms;flex-shrink:0;}
    .byoe-close:hover{background:var(--surface-sunken);color:var(--ink-900);}
    .byoe-close:focus-visible,.byoe-sheet__head:focus-visible,.byoe-warn__btn:focus-visible,.byoe-verdict-btn:focus-visible,.byoe-btn:focus-visible,.byoe-reuse__cta:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
    .byoe-stage{padding:20px 24px 24px;}

    /* Dropzone */
    .byoe-dropzone{display:flex;flex-direction:column;align-items:center;justify-content:center;border:1.5px dashed var(--border-default);border-radius:12px;padding:42px 24px;text-align:center;background:var(--surface-sunken);cursor:pointer;transition:border-color 160ms,background 160ms,transform 160ms;color:var(--ink-500);}
    .byoe-dropzone:hover{border-color:var(--accent);background:var(--accent-soft);}
    .byoe-dropzone.is-dragging{border-color:var(--accent);background:var(--accent-soft);border-style:solid;transform:scale(1.005);}
    .byoe-dropzone__icon{width:56px;height:56px;border-radius:14px;background:var(--surface-raised);border:1px solid var(--border-default);display:flex;align-items:center;justify-content:center;color:var(--accent);margin-bottom:14px;box-shadow:var(--shadow-sm);}
    .byoe-dropzone__title{font-weight:600;font-size:14.5px;color:var(--ink-900);}
    .byoe-dropzone__meta{font-size:12.5px;color:var(--ink-500);margin-top:4px;}
    .byoe-trustline{display:flex;align-items:center;gap:6px;font-size:11.5px;color:var(--ink-500);margin-top:14px;justify-content:center;line-height:1.4;}
    .byoe-trustline svg{color:var(--accent);flex-shrink:0;}

    /* Parse stage — streaming milestones */
    .byoe-parse-head{display:flex;align-items:center;gap:14px;margin-bottom:14px;}
    .byoe-parse-spinner{width:36px;height:36px;border-radius:10px;background:var(--accent-soft);color:var(--accent);display:flex;align-items:center;justify-content:center;animation:byoe-spin 1100ms linear infinite;}
    @keyframes byoe-spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}
    .byoe-parse-title{font-size:15px;font-weight:700;color:var(--ink-900);letter-spacing:-0.2px;}
    .byoe-parse-file{font-size:12px;color:var(--ink-500);font-variant-numeric:tabular-nums;margin-top:2px;}
    .byoe-progress{height:4px;background:var(--accent-soft);border-radius:9999px;overflow:hidden;margin-bottom:16px;}
    .byoe-progress__bar{height:100%;background:linear-gradient(90deg,var(--accent),var(--cash-up));border-radius:9999px;width:6%;transition:width 280ms ease-out;position:relative;}
    .byoe-progress__bar::after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,0.55) 50%,transparent 100%);animation:byoe-shimmer 1300ms linear infinite;}
    @keyframes byoe-shimmer{from{transform:translateX(-100%)}to{transform:translateX(100%)}}
    .byoe-milestones{list-style:none;padding:0;margin:0;display:flex;flex-direction:column;gap:7px;}
    .byoe-milestone{display:flex;align-items:center;gap:10px;font-size:12.5px;color:var(--ink-500);font-variant-numeric:tabular-nums;padding:5px 0;opacity:0;transform:translateY(4px);animation:byoe-rise 320ms ease-out forwards;}
    .byoe-milestone__dot{width:7px;height:7px;border-radius:50%;background:var(--accent);flex-shrink:0;box-shadow:0 0 0 3px var(--accent-soft);}
    .byoe-milestone__time{color:var(--ink-500);font-size:11px;min-width:42px;}
    .byoe-milestone--done .byoe-milestone__dot{background:var(--cash-up);box-shadow:0 0 0 3px var(--cash-up-soft);}
    .byoe-milestone--pending .byoe-milestone__dot{background:var(--ink-500);box-shadow:none;animation:byoe-pulse 900ms ease-in-out infinite;}
    @keyframes byoe-pulse{0%,100%{opacity:0.4}50%{opacity:1}}
    @keyframes byoe-rise{to{opacity:1;transform:translateY(0)}}

    /* Review — header summary */
    .byoe-review-head{display:flex;flex-wrap:wrap;align-items:center;gap:14px;justify-content:space-between;padding:16px;background:var(--accent-softer);border:1px solid var(--border-subtle);border-radius:12px;margin-bottom:14px;}
    .byoe-review-head__left{display:flex;align-items:center;gap:14px;min-width:0;flex:1;}
    .byoe-confidence{position:relative;width:60px;height:60px;flex-shrink:0;}
    .byoe-confidence svg{width:100%;height:100%;}
    .byoe-confidence__track{stroke:var(--border-default);}
    .byoe-confidence__fill{stroke-linecap:round;transition:stroke-dashoffset 800ms cubic-bezier(.2,.8,.2,1);}
    .byoe-confidence__text{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;font-variant-numeric:tabular-nums;}
    .byoe-confidence__num{font-size:18px;font-weight:700;color:var(--ink-900);letter-spacing:-0.3px;line-height:1;}
    .byoe-confidence__pct{font-size:9px;color:var(--ink-500);font-weight:600;letter-spacing:0.4px;}
    .byoe-review-head__title{font-size:15.5px;font-weight:700;color:var(--ink-900);letter-spacing:-0.2px;line-height:1.25;word-break:break-word;}
    .byoe-review-head__stats{font-size:12.5px;color:var(--ink-500);margin-top:3px;font-variant-numeric:tabular-nums;}
    .byoe-confidence-band{font-size:10.5px;font-weight:700;letter-spacing:0.45px;text-transform:uppercase;padding:3px 8px;border-radius:9999px;white-space:nowrap;flex-shrink:0;}
    /* Confidence-band pills carry small uppercase TEXT — route each hue to its
       v4 text-grade token (AA on white + on its own soft fill), not the
       icon-grade hue. --amber (#D97706, 3.19:1) fails AA as text; --warn-text
       (#B45309, 4.75:1) is the text grade. Likewise --red → --cash-down-text
       so all three bands use the same text-grade tier as --band--high.
       (Cross-surface consistency audit 2026-05-18, Finding 2.) */
    .byoe-band--high{background:var(--cash-up-soft);color:var(--cash-up-text);}
    .byoe-band--med{background:var(--warn-soft);color:var(--warn-text);}
    .byoe-band--low{background:var(--cash-down-soft);color:var(--cash-down-text);}

    /* Re-upload magic banner */
    .byoe-reuse{display:flex;align-items:center;gap:14px;padding:14px 16px;background:var(--accent-softer);border:1px solid var(--accent-border);border-radius:12px;margin-bottom:12px;animation:byoe-recognize 520ms cubic-bezier(.2,.8,.2,1);}
    @keyframes byoe-recognize{from{opacity:0;transform:translateY(-4px) scale(0.985)}to{opacity:1;transform:none}}
    .byoe-reuse__icon{width:38px;height:38px;border-radius:10px;background:var(--surface-raised);border:1px solid var(--accent-border);color:var(--accent);display:flex;align-items:center;justify-content:center;flex-shrink:0;box-shadow:var(--shadow-sm);}
    .byoe-reuse__body{flex:1;min-width:0;}
    .byoe-reuse__title{font-size:13.5px;font-weight:700;color:var(--ink-900);letter-spacing:-0.15px;display:flex;align-items:center;gap:6px;}
    .byoe-reuse__sub{font-size:12px;color:var(--ink-500);margin-top:2px;line-height:1.5;}
    .byoe-reuse__cta{padding:8px 14px;font-size:12.5px;font-weight:700;background:var(--accent);color:var(--surface-raised);border:0;border-radius:8px;cursor:pointer;flex-shrink:0;transition:background 140ms,transform 140ms;}
    .byoe-reuse__cta:hover{background:var(--accent-active);transform:translateY(-1px);}

    /* Warnings rail */
    .byoe-warn-rail{display:flex;flex-direction:column;gap:8px;margin-bottom:14px;}
    .byoe-warn{display:flex;gap:12px;padding:14px;border-radius:10px;border:1px solid;background:var(--surface-raised);animation:byoe-rise 280ms ease-out;}
    .byoe-warn--high{border-color:var(--warn-border);background:var(--warn-soft);}
    .byoe-warn--high.is-reviewed{border-color:var(--cash-up-border);background:var(--cash-up-soft);}
    .byoe-warn--medium{border-color:var(--accent-border);background:var(--accent-softer);}
    .byoe-warn--info{border-color:var(--border-subtle);background:var(--surface-sunken);}
    .byoe-warn--blocking{border-color:var(--cash-down-border);background:var(--cash-down-soft);}
    .byoe-warn__icon{width:30px;height:30px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0;}
    .byoe-warn--high .byoe-warn__icon{background:var(--warn-soft);color:var(--warn);}
    .byoe-warn--high.is-reviewed .byoe-warn__icon{background:var(--cash-up-soft);color:var(--cash-up-text);}
    .byoe-warn--medium .byoe-warn__icon{background:var(--accent-soft);color:var(--accent);}
    .byoe-warn--info .byoe-warn__icon{background:var(--surface-sunken);color:var(--ink-500);}
    .byoe-warn--blocking .byoe-warn__icon{background:var(--cash-down-soft);color:var(--cash-down);}
    .byoe-warn__body{flex:1;min-width:0;}
    .byoe-warn__title{font-size:13px;font-weight:700;color:var(--ink-900);letter-spacing:-0.1px;line-height:1.35;}
    .byoe-warn__msg{font-size:12.5px;color:var(--ink-500);margin-top:3px;line-height:1.55;}
    .byoe-warn__actions{display:flex;gap:8px;align-items:center;margin-top:10px;flex-wrap:wrap;}
    .byoe-warn__btn{padding:6px 12px;font-size:12px;font-weight:600;border-radius:8px;border:1px solid var(--border-default);background:var(--surface-raised);color:var(--ink-900);cursor:pointer;transition:all 140ms;}
    .byoe-warn__btn:hover{background:var(--surface-sunken);border-color:var(--ink-500);}
    .byoe-warn__btn--primary{background:var(--accent);color:var(--surface-raised);border-color:var(--accent);}
    .byoe-warn__btn--primary:hover{background:var(--accent-active);border-color:var(--accent-active);}
    .byoe-warn__chip-reviewed{display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:700;color:var(--cash-up-text);letter-spacing:0.35px;text-transform:uppercase;}
    .byoe-warn__chip-reviewed svg{flex-shrink:0;}

    /* Coerced cells drilldown */
    .byoe-coerce{margin-top:10px;padding:10px;background:var(--surface-raised);border:1px solid var(--border-subtle);border-radius:8px;}
    .byoe-coerce__head{font-size:11px;font-weight:700;color:var(--ink-500);letter-spacing:0.5px;text-transform:uppercase;margin-bottom:8px;}
    .byoe-coerce__row{display:flex;align-items:center;gap:10px;padding:8px 0;border-bottom:1px solid var(--border-subtle);font-size:12.5px;font-variant-numeric:tabular-nums;}
    .byoe-coerce__row:last-child{border-bottom:0;}
    .byoe-coerce__loc{color:var(--ink-500);min-width:0;flex:1;}
    .byoe-coerce__loc strong{color:var(--ink-900);}
    .byoe-coerce__from{font-family:'JetBrains Mono',ui-monospace,SF Mono,Menlo,monospace;font-size:11.5px;background:var(--warn-soft);color:var(--warn-text);padding:2px 6px;border-radius:5px;}
    .byoe-coerce__arrow{color:var(--ink-500);flex-shrink:0;}
    .byoe-coerce__to{font-family:'JetBrains Mono',ui-monospace,SF Mono,Menlo,monospace;font-size:11.5px;background:var(--accent-soft);color:var(--accent);padding:2px 6px;border-radius:5px;font-weight:700;}
    .byoe-coerce__verdict{display:flex;gap:4px;flex-shrink:0;margin-left:auto;}
    .byoe-verdict-btn{padding:4px 9px;font-size:11px;font-weight:600;border:1px solid var(--border-default);background:var(--surface-raised);color:var(--ink-500);border-radius:6px;cursor:pointer;transition:all 120ms;}
    .byoe-verdict-btn:hover{background:var(--surface-sunken);}
    .byoe-verdict-btn.is-active-right{background:var(--cash-up-soft);border-color:var(--cash-up-border);color:var(--cash-up-text);}
    /* --cash-down-text (6.36:1) mirrors the --cash-up-text grade on the
       is-active-right sibling — the right/wrong pair uses one text tier.
       (Consistency audit 2026-05-18, Finding 2.) */
    .byoe-verdict-btn.is-active-wrong{background:var(--cash-down-soft);border-color:var(--cash-down-border);color:var(--cash-down-text);}

    /* Sheet cards grid */
    .byoe-sheets{display:grid;grid-template-columns:1fr;gap:12px;margin-bottom:14px;}
    @media(min-width:680px){.byoe-sheets{grid-template-columns:1fr 1fr;}}
    .byoe-sheet{border:1px solid var(--border-default);border-radius:10px;background:var(--surface-raised);overflow:hidden;transition:border-color 160ms,box-shadow 160ms;}
    .byoe-sheet:hover{border-color:var(--accent-border);box-shadow:var(--shadow-sm);}
    .byoe-sheet__head{display:flex;align-items:center;gap:12px;padding:14px;cursor:pointer;min-height:44px;width:100%;background:transparent;border:0;border-radius:10px 10px 0 0;font-family:inherit;text-align:left;color:inherit;}
    .byoe-sheet__gauge{width:38px;height:38px;flex-shrink:0;position:relative;}
    .byoe-sheet__gauge svg{width:100%;height:100%;}
    .byoe-sheet__gauge-text{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:var(--ink-900);font-variant-numeric:tabular-nums;}
    .byoe-sheet__name{font-size:14px;font-weight:700;color:var(--ink-900);letter-spacing:-0.15px;line-height:1.3;}
    .byoe-sheet__meta{font-size:11.5px;color:var(--ink-500);margin-top:2px;font-variant-numeric:tabular-nums;line-height:1.4;}
    .byoe-sheet__expand{margin-left:auto;color:var(--ink-500);transition:transform 200ms;flex-shrink:0;}
    .byoe-sheet.is-open .byoe-sheet__expand{transform:rotate(180deg);}
    .byoe-sheet__body{display:none;border-top:1px solid var(--border-subtle);padding:12px 14px 14px;background:var(--surface-sunken);}
    .byoe-sheet.is-open .byoe-sheet__body{display:block;}
    .byoe-sheet__chips{display:flex;flex-wrap:wrap;gap:6px;padding:0 14px 12px;}
    .byoe-chip{display:inline-flex;align-items:center;gap:4px;padding:4px 10px;border-radius:9999px;font-size:11px;font-weight:600;line-height:1.45;max-width:100%;white-space:normal;word-break:break-word;}
    .byoe-chip--match{background:var(--accent-soft);color:var(--accent);border:1px solid var(--accent-border);}
    /* Chip text → amber text-grade (--warn-text, 4.75:1) not the icon-grade
       --amber (3.19:1, fails AA as text). Matches .byoe-coerce__from /
       .byoe-wizard__chip--outflow. (Consistency audit 2026-05-18, Finding 2.) */
    .byoe-chip--warn{background:var(--warn-soft);color:var(--warn-text);border:1px solid var(--warn-border);}
    .byoe-chip--info{background:var(--surface-sunken);color:var(--ink-500);border:1px solid var(--border-subtle);}
    .byoe-chip--unknown{background:var(--surface-sunken);color:var(--ink-500);border:1px dashed var(--border-default);}
    .byoe-li{display:flex;align-items:center;gap:8px;padding:6px 0;font-size:12px;border-bottom:1px solid var(--border-subtle);}
    .byoe-li:last-child{border-bottom:0;}
    .byoe-li__kind{font-size:9.5px;font-weight:700;letter-spacing:0.5px;text-transform:uppercase;padding:2px 6px;border-radius:5px;flex-shrink:0;}
    .byoe-li__kind--in{background:var(--cash-up-soft);color:var(--cash-up-text);}
    .byoe-li__kind--out{background:var(--cash-down-soft);color:var(--cash-down);}
    .byoe-li__kind--neutral{background:var(--surface-sunken);color:var(--ink-500);}
    .byoe-li__label{flex:1;color:var(--ink-900);min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
    .byoe-li__count{color:var(--ink-500);font-variant-numeric:tabular-nums;font-size:11px;}

    /* Apply gate */
    .byoe-applybar{position:sticky;bottom:0;background:linear-gradient(180deg,rgba(255,255,255,0) 0%,var(--surface-raised) 28%);padding:14px 0 4px;display:flex;align-items:center;gap:12px;flex-wrap:wrap;border-top:1px solid var(--border-subtle);margin-top:6px;}
    .byoe-applybar__status{font-size:12.5px;color:var(--ink-500);flex:1;line-height:1.45;min-width:160px;}
    .byoe-applybar__status strong{color:var(--ink-900);}
    .byoe-applybar__status--ready{color:var(--cash-up-text);}
    .byoe-btn{padding:10px 18px;font-size:13px;font-weight:700;border-radius:9px;border:1px solid;cursor:pointer;transition:all 160ms;font-family:inherit;letter-spacing:-0.1px;}
    .byoe-btn--primary{background:var(--accent);color:var(--surface-raised);border-color:var(--accent);}
    .byoe-btn--primary:hover{background:var(--accent-active);border-color:var(--accent-active);transform:translateY(-1px);box-shadow:var(--shadow-raised);}
    .byoe-btn--primary:disabled{background:var(--surface-disabled);border-color:var(--border-strong);color:var(--text-disabled);cursor:not-allowed;transform:none;box-shadow:none;}
    .byoe-btn--ghost{background:var(--surface-raised);color:var(--ink-900);border-color:var(--border-default);}
    .byoe-btn--ghost:hover{background:var(--surface-sunken);border-color:var(--ink-500);}
    .byoe-applybar__skip{background:none;border:0;color:var(--ink-500);font-size:12px;cursor:pointer;text-decoration:underline;text-underline-offset:2px;padding:6px;}
    .byoe-applybar__skip:hover{color:var(--ink-500);}

    /* Errors */
    .byoe-err{display:flex;flex-direction:column;align-items:center;text-align:center;padding:18px 12px;}
    .byoe-err__art{width:88px;height:88px;border-radius:18px;background:var(--surface-raised);border:1px solid var(--border-default);display:flex;align-items:center;justify-content:center;margin-bottom:18px;position:relative;}
    .byoe-err__art--macro{background:var(--warn-soft);border-color:var(--warn-border);}
    .byoe-err__art--password{background:var(--cash-down-soft);border-color:var(--cash-down-border);}
    .byoe-err__art--corrupt{background:var(--surface-sunken);}
    .byoe-err__art--empty{background:var(--accent-soft);border-color:var(--accent-border);}
    .byoe-err__art svg{width:38px;height:38px;}
    .byoe-err__art--macro svg{color:var(--warn);}
    .byoe-err__art--password svg{color:var(--cash-down);}
    .byoe-err__art--corrupt svg{color:var(--ink-500);}
    .byoe-err__art--empty svg{color:var(--accent);}
    .byoe-err__title{font-size:17px;font-weight:700;color:var(--ink-900);letter-spacing:-0.3px;margin-bottom:6px;}
    .byoe-err__body{font-size:13px;color:var(--ink-500);line-height:1.55;max-width:48ch;margin-bottom:6px;}
    .byoe-err__path{font-size:12px;color:var(--ink-900);background:var(--surface-sunken);border:1px solid var(--border-subtle);padding:8px 12px;border-radius:8px;margin:14px 0 16px;display:inline-flex;align-items:center;gap:6px;font-family:'JetBrains Mono',ui-monospace,SF Mono,Menlo,monospace;}
    .byoe-err__path svg{color:var(--ink-500);flex-shrink:0;}
    .byoe-err__actions{display:flex;gap:10px;flex-wrap:wrap;justify-content:center;margin-top:6px;}

    /* Done state */
    .byoe-done{text-align:center;padding:18px 12px 8px;}
    .byoe-done__check{width:64px;height:64px;border-radius:50%;background:var(--cash-up-soft);color:var(--cash-up-text);display:inline-flex;align-items:center;justify-content:center;margin-bottom:14px;animation:byoe-pop 460ms cubic-bezier(.2,1.6,.4,1);}
    @keyframes byoe-pop{from{transform:scale(0.4);opacity:0}to{transform:scale(1);opacity:1}}
    .byoe-done__title{font-size:18px;font-weight:700;color:var(--ink-900);letter-spacing:-0.3px;}
    .byoe-done__sub{font-size:13px;color:var(--ink-500);margin-top:6px;line-height:1.55;max-width:48ch;margin-left:auto;margin-right:auto;}
    .byoe-done__stats{display:flex;justify-content:center;gap:24px;margin:18px 0 8px;flex-wrap:wrap;}
    .byoe-done__stat{text-align:center;}
    .byoe-done__stat-num{font-size:22px;font-weight:700;color:var(--ink-900);font-variant-numeric:tabular-nums;letter-spacing:-0.4px;line-height:1;}
    .byoe-done__stat-label{font-size:11px;color:var(--ink-500);font-weight:600;letter-spacing:0.4px;text-transform:uppercase;margin-top:5px;}

    @media (max-width:640px){
      .byoe-shell{max-height:100vh;border-radius:0;width:100%;}
      .byoe-stage{padding:18px;}
      .byoe-header{padding:18px 18px 12px;}
      .byoe-review-head{flex-direction:column;align-items:flex-start;}
      .byoe-applybar{position:static;margin-top:14px;}
    }
    @media(prefers-reduced-motion:reduce){
      .byoe-progress__bar::after,.byoe-parse-spinner{animation:none;}
      .byoe-milestone{animation:none;opacity:1;transform:none;}
    }

    /* ═══════════════════════════════════════════════════════════════
       Cinematic fill animation — Sprint 1 #3 (Engineering 2026-05-09)
       Renders parsed sheets × line items × first 13 weeks as a mini
       grid that fills row-by-row over ~3s after Apply succeeds. Pure
       CSS keyframes + minimal JS. No new color tokens. Snaps to filled
       state on prefers-reduced-motion or mobile <640px.
       ═══════════════════════════════════════════════════════════════ */
    .byoe-cine{margin-top:14px;border:1px solid var(--border-default);border-radius:12px;background:var(--surface-raised);overflow:hidden;}
    .byoe-cine.is-complete{animation:byoe-cine-frame-pulse 720ms cubic-bezier(.2,.8,.2,1);}
    @keyframes byoe-cine-frame-pulse{
      0%{box-shadow:0 0 0 0 rgba(15,118,110,0);}
      40%{box-shadow:0 0 0 6px rgba(15,118,110,0.10);}
      100%{box-shadow:0 0 0 0 rgba(15,118,110,0);}
    }
    .byoe-cine__head{display:flex;align-items:center;gap:10px;padding:12px 14px;border-bottom:1px solid var(--border-subtle);background:var(--accent-softer);}
    .byoe-cine__head-title{font-size:13px;font-weight:700;color:var(--ink-900);letter-spacing:-0.15px;}
    .byoe-cine__head-meta{font-size:11.5px;color:var(--ink-500);margin-left:auto;font-variant-numeric:tabular-nums;}
    .byoe-cine__sheet{border-bottom:1px solid var(--border-subtle);}
    .byoe-cine__sheet:last-child{border-bottom:0;}
    .byoe-cine__sheet-name{font-size:11px;font-weight:700;color:var(--ink-500);letter-spacing:0.45px;text-transform:uppercase;padding:10px 14px 6px;}
    .byoe-cine__grid{display:block;padding:0 14px 12px;overflow-x:auto;}
    .byoe-cine__row{display:flex;align-items:center;gap:4px;min-height:26px;padding:2px 0;border-bottom:1px dashed var(--border-subtle);}
    .byoe-cine__row:last-child{border-bottom:0;}
    .byoe-cine__label{flex:0 0 140px;font-size:12px;color:var(--ink-900);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;padding-right:8px;}
    .byoe-cine__label-kind{display:inline-block;width:14px;font-size:9.5px;font-weight:700;letter-spacing:0.35px;color:var(--ink-500);margin-right:4px;}
    .byoe-cine__label-kind--in{color:var(--cash-up-text);}
    .byoe-cine__label-kind--out{color:var(--cash-down);}
    .byoe-cine__cells{display:flex;gap:3px;flex-wrap:nowrap;}
    .byoe-cine__cell{position:relative;width:46px;height:22px;border-radius:4px;background:var(--surface-sunken);border:1px solid var(--border-subtle);font-size:10.5px;font-weight:600;color:var(--ink-900);font-variant-numeric:tabular-nums;display:flex;align-items:center;justify-content:center;letter-spacing:-0.15px;flex-shrink:0;overflow:hidden;}
    .byoe-cine__cell-num{opacity:0;transition:opacity 180ms ease-out;}
    .byoe-cine__cell.is-filled .byoe-cine__cell-num{opacity:1;}
    .byoe-cine__cell.is-filled{background:var(--surface-raised);border-color:var(--border-default);animation:byoe-cine-pulse 480ms ease-out;}
    @keyframes byoe-cine-pulse{
      0%{background:rgba(15,118,110,0.18);box-shadow:0 0 0 0 rgba(15,118,110,0.16);}
      55%{background:rgba(15,118,110,0.04);box-shadow:0 0 0 3px rgba(15,118,110,0.08);}
      100%{background:var(--surface-raised);box-shadow:0 0 0 0 rgba(15,118,110,0);}
    }
    .byoe-cine__cell-dot{position:absolute;top:3px;right:3px;width:5px;height:5px;border-radius:50%;background:var(--accent);opacity:0;transition:opacity 220ms ease-out;}
    .byoe-cine__cell.is-filled .byoe-cine__cell-dot{opacity:0.85;}
    .byoe-cine__cell--zero{color:var(--ink-500);font-weight:500;}
    .byoe-cine__cta{display:flex;justify-content:center;align-items:center;gap:10px;padding:14px;border-top:1px solid var(--border-subtle);background:var(--surface-sunken);opacity:0;transform:translateY(4px);transition:opacity 360ms ease-out,transform 360ms ease-out;}
    .byoe-cine__cta.is-visible{opacity:1;transform:translateY(0);}
    .byoe-cine__cta-btn{padding:10px 18px;font-size:13px;font-weight:700;border-radius:9px;border:1px solid var(--accent);background:var(--accent);color:var(--surface-raised);cursor:pointer;transition:background 160ms,transform 160ms,box-shadow 160ms;font-family:inherit;letter-spacing:-0.1px;display:inline-flex;align-items:center;gap:6px;}
    .byoe-cine__cta-btn:hover{background:var(--accent-active);border-color:var(--accent-active);transform:translateY(-1px);box-shadow:var(--shadow-raised);}
    .byoe-cine__cta-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
    .byoe-cine__cta-skip{background:none;border:0;color:var(--ink-500);font-size:12px;cursor:pointer;text-decoration:underline;text-underline-offset:2px;padding:6px;}
    .byoe-cine__cta-skip:hover{color:var(--ink-500);}
    @media (max-width:640px){
      /* Mobile: snap directly to filled state, no animation cinematics. */
      .byoe-cine__cell{width:40px;}
      .byoe-cine__label{flex:0 0 110px;}
      .byoe-cine.is-snap .byoe-cine__cell{background:var(--surface-raised);border-color:var(--border-default);animation:none;}
      .byoe-cine.is-snap .byoe-cine__cell-num{opacity:1;}
      .byoe-cine.is-snap .byoe-cine__cell-dot{opacity:0.85;}
      .byoe-cine.is-snap .byoe-cine__cta{opacity:1;transform:none;transition:none;}
    }
    @media (prefers-reduced-motion:reduce){
      /* Reduced-motion: no animation, fill state instant via .is-snap. */
      .byoe-cine.is-snap .byoe-cine__cell{background:var(--surface-raised);border-color:var(--border-default);animation:none;}
      .byoe-cine.is-snap .byoe-cine__cell-num{opacity:1;}
      .byoe-cine.is-snap .byoe-cine__cell-dot{opacity:0.85;}
      .byoe-cine.is-snap .byoe-cine__cta{opacity:1;transform:none;transition:none;}
      .byoe-cine.is-complete{animation:none;}
    }

    /* ─── AI-assist opt-in banner (CPO 3-tier 2026-05-08) ───
       Routed to the governed v4 AI sub-brand family (design-tokens.css §4c).
       Before this sweep the banner reached for raw violet-500 washes
       (rgba(139,92,246,…)) and an un-governed local violet alias —
       uncoordinated with the rest of the AI surface. Now: the violet washes
       flow through --ai-soft / --ai-border, the solid icon hue through
       --ai-accent. The teal half of each gradient keeps its
       --accent-soft/-softer token (a deliberate two-tone AI-meets-ledger
       banner); the white icon surface keeps --surface-raised. One violet
       source for the AI surface (the account-role chip is a separate,
       deliberately categorical violet — see design-tokens.css §4c). */
    .byoe-ai-banner{display:flex;align-items:center;gap:14px;padding:14px 16px;border-radius:12px;background:linear-gradient(135deg,var(--ai-soft) 0%,var(--accent-softer) 100%);border:1px solid var(--ai-border);margin-bottom:14px;flex-wrap:wrap;}
    .byoe-ai-banner--prominent{background:linear-gradient(135deg,var(--ai-soft) 0%,var(--accent-soft) 100%);border-color:var(--ai-border);}
    .byoe-ai-banner__icon{width:36px;height:36px;border-radius:10px;background:var(--surface-raised);border:1px solid var(--ai-border);color:var(--ai-accent);display:flex;align-items:center;justify-content:center;flex-shrink:0;box-shadow:var(--shadow-sm);}
    .byoe-ai-banner__body{flex:1;min-width:160px;}
    .byoe-ai-banner__title{font-size:13.5px;font-weight:600;color:var(--ink-900);line-height:1.4;}
    .byoe-ai-banner__privacy{font-size:11px;color:var(--ink-500);margin-top:4px;line-height:1.45;}
    .byoe-ai-banner__actions{display:flex;gap:8px;align-items:center;flex-shrink:0;flex-wrap:wrap;}
    .byoe-ai-badge-banner{display:flex;align-items:center;gap:10px;padding:9px 12px;border-radius:8px;background:var(--ai-soft);border:1px solid var(--ai-border);color:var(--ink-900);font-size:12px;line-height:1.5;margin-bottom:12px;}
    .byoe-ai-badge-banner .byoe-ai-banner__icon{width:24px;height:24px;border-radius:6px;color:var(--ai-accent);}

    /* ═══════════════════════════════════════════════════════════════
       BYOE Manual Mapping Wizard (Stage 6) — CDO 2026-05-08
       v4 "Ledger" migration (dashboard-migration I5, plan §I5):
       I1 extracted this from the former inline <style id="byoe-styles">;
       I5 routes its raw hex / rgba literals through the v4 token system.
       Class names + the legacy bridge tokens (--teal, --text-1/2/3,
       --border, --border-subtle, --amber) are UNCHANGED — they are
       already v4 bridges. The literal slate-neutral fills now resolve to
       v4 surface tokens; the teal washes to --accent-soft/-softer; the
       inflow/outflow/transfer semantics to the v4 cash / warn /
       accent-secondary grades. No markup, no JS, no class touched.
       ═══════════════════════════════════════════════════════════════ */
    .byoe-wizard-modal{position:fixed;inset:0;background:var(--surface-overlay);backdrop-filter:blur(2px);z-index:10000;display:flex;align-items:center;justify-content:center;padding:24px;animation:byoeFadeIn 180ms cubic-bezier(0.16,1,0.3,1);}
    .byoe-wizard-modal[hidden]{display:none;}
    @keyframes byoeFadeIn{from{opacity:0;}to{opacity:1;}}
    .byoe-wizard{background:var(--surface-raised);border-radius:16px;width:min(760px,100%);max-height:calc(100vh - 48px);display:flex;flex-direction:column;box-shadow:var(--shadow-modal);overflow:hidden;}
    .byoe-wizard__header{display:flex;align-items:flex-start;justify-content:space-between;gap:16px;padding:20px 24px 18px;border-bottom:1px solid var(--border-subtle);}
    .byoe-wizard__title{font-size:17px;font-weight:700;letter-spacing:-0.2px;color:var(--ink-900);}
    .byoe-wizard__subtitle{font-size:12px;color:var(--ink-500);margin-top:3px;}
    .byoe-wizard__header-right{display:flex;align-items:center;gap:14px;flex-shrink:0;}
    .byoe-wizard__steps{display:flex;align-items:center;gap:6px;font-variant-numeric:tabular-nums;}
    .byoe-wizard__step{display:flex;align-items:center;gap:6px;padding:5px 10px;border-radius:6px;background:var(--surface-sunken);font-size:11px;font-weight:600;color:var(--ink-500);transition:background 180ms cubic-bezier(0.16,1,0.3,1),color 180ms cubic-bezier(0.16,1,0.3,1);}
    .byoe-wizard__step--active{background:var(--accent-soft);color:var(--accent);}
    .byoe-wizard__step--done{background:var(--accent-softer);color:var(--accent);}
    .byoe-wizard__step-num{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;border-radius:50%;background:var(--border-strong);font-size:10px;font-weight:700;}
    .byoe-wizard__step--active .byoe-wizard__step-num,.byoe-wizard__step--done .byoe-wizard__step-num{background:var(--accent);color:var(--surface-raised);}
    .byoe-wizard__step-sep{width:14px;height:1px;background:var(--border-default);}
    .byoe-wizard__close{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border:1px solid var(--border-default);border-radius:8px;background:transparent;color:var(--ink-500);cursor:pointer;transition:all 180ms cubic-bezier(0.16,1,0.3,1);}
    .byoe-wizard__close:hover{background:var(--surface-sunken);color:var(--ink-900);}
    .byoe-wizard__sheettabs{display:flex;gap:2px;padding:8px 24px 0;background:var(--surface-sunken);border-bottom:1px solid var(--border-subtle);overflow-x:auto;}
    .byoe-wizard__sheettab{flex-shrink:0;padding:8px 14px;font-size:12px;font-weight:600;color:var(--ink-500);border-bottom:2px solid transparent;cursor:pointer;background:none;border-left:none;border-right:none;border-top:none;}
    .byoe-wizard__sheettab--active{color:var(--accent);border-bottom-color:var(--accent);}
    .byoe-wizard__body{flex:1;overflow-y:auto;padding:22px 24px;}
    .byoe-wizard__h2{font-size:15px;font-weight:700;letter-spacing:-0.1px;color:var(--ink-900);margin:0 0 6px;}
    .byoe-wizard__lede{font-size:13px;line-height:1.55;color:var(--ink-500);margin:0 0 16px;}
    .byoe-wizard__lede code{font-family:'SF Mono',Menlo,Consolas,monospace;font-size:12px;background:var(--surface-sunken);padding:1px 5px;border-radius:4px;color:var(--ink-900);}
    .byoe-wizard__preview-shell{border:1px solid var(--border-default);border-radius:10px;overflow:hidden;background:var(--surface-raised);}
    .byoe-wizard__preview{max-height:360px;overflow:auto;font-variant-numeric:tabular-nums;}
    .byoe-wizard__preview-empty{padding:36px 24px;text-align:center;font-size:13px;color:var(--ink-500);}
    .byoe-wizard__preview table{border-collapse:collapse;width:max-content;min-width:100%;font-size:12px;}
    .byoe-wizard__preview th,.byoe-wizard__preview td{padding:6px 10px;border-right:1px solid var(--border-subtle);border-bottom:1px solid var(--border-subtle);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:140px;}
    .byoe-wizard__preview thead th{position:sticky;top:0;z-index:3;background:var(--surface-sunken);font-weight:600;color:var(--ink-500);font-size:11px;letter-spacing:0.4px;text-transform:uppercase;}
    .byoe-wizard__preview .byoe-wizard__rownum{position:sticky;left:0;z-index:2;background:var(--surface-sunken);font-weight:600;color:var(--ink-500);font-size:11px;text-align:right;min-width:36px;border-right:1px solid var(--border-default);}
    .byoe-wizard__preview thead .byoe-wizard__rownum{z-index:4;}
    .byoe-wizard__preview td{color:var(--ink-900);}
    .byoe-wizard__preview td.is-empty{color:var(--ink-500);text-align:center;}
    .byoe-wizard__preview tr.byoe-wizard__row{cursor:pointer;transition:background 120ms cubic-bezier(0.16,1,0.3,1);}
    .byoe-wizard__preview tr.byoe-wizard__row:hover td{background:var(--accent-softer);}
    .byoe-wizard__preview tr.byoe-wizard__row--suggested td{background:var(--warn-soft);}
    .byoe-wizard__preview tr.byoe-wizard__row--selected td{background:var(--accent-soft);box-shadow:inset 2px 0 0 var(--accent);}
    .byoe-wizard__preview tr.byoe-wizard__row--selected .byoe-wizard__rownum{background:var(--accent);color:var(--surface-raised);}
    .byoe-wizard__hint{margin-top:12px;display:flex;align-items:flex-start;gap:8px;background:var(--accent-softer);border-left:3px solid var(--accent);border-radius:6px;padding:9px 12px;font-size:12px;color:var(--ink-900);line-height:1.5;}
    .byoe-wizard__hint strong{color:var(--accent);}
    .byoe-wizard__range{display:flex;flex-direction:column;gap:18px;}
    .byoe-wizard__range-summary{background:var(--accent-softer);border:1px solid var(--accent-border);border-radius:10px;padding:16px 18px;display:flex;align-items:baseline;gap:12px;flex-wrap:wrap;}
    .byoe-wizard__range-summary-num{font-size:32px;font-weight:800;letter-spacing:-1px;color:var(--accent);font-variant-numeric:tabular-nums;line-height:1;}
    .byoe-wizard__range-summary-label{font-size:13px;color:var(--ink-500);line-height:1.5;}
    .byoe-wizard__range-summary-label strong{color:var(--ink-900);font-weight:600;}
    .byoe-wizard__range-controls{display:grid;grid-template-columns:1fr 1fr;gap:12px;}
    .byoe-wizard__field{display:flex;flex-direction:column;gap:6px;}
    .byoe-wizard__field-label{font-size:11px;font-weight:700;color:var(--ink-500);text-transform:uppercase;letter-spacing:0.6px;}
    .byoe-wizard__select{padding:9px 12px;border:1px solid var(--border-default);border-radius:8px;background:var(--surface-raised);font-size:13px;color:var(--ink-900);cursor:pointer;}
    .byoe-wizard__select:focus{outline:2px solid var(--accent);outline-offset:1px;border-color:var(--accent);}
    .byoe-wizard__pillgroup{display:inline-flex;background:var(--surface-sunken);border-radius:8px;padding:3px;gap:2px;}
    .byoe-wizard__pill{padding:7px 18px;border:none;border-radius:6px;background:transparent;font-size:13px;font-weight:600;color:var(--ink-500);cursor:pointer;transition:all 180ms cubic-bezier(0.16,1,0.3,1);}
    .byoe-wizard__pill--active{background:var(--surface-raised);color:var(--accent);box-shadow:var(--shadow-raised);}
    .byoe-wizard__preview--lineitems table{width:100%;}
    .byoe-wizard__lineitem-cb{appearance:none;-webkit-appearance:none;width:16px;height:16px;border:1.5px solid var(--border-default);border-radius:4px;cursor:pointer;display:inline-block;vertical-align:middle;position:relative;background:var(--surface-raised);}
    .byoe-wizard__lineitem-cb:checked{background:var(--accent);border-color:var(--accent);}
    .byoe-wizard__lineitem-cb:checked::after{content:'';position:absolute;left:4px;top:1px;width:4px;height:8px;border:solid var(--surface-raised);border-width:0 2px 2px 0;transform:rotate(45deg);}
    .byoe-wizard__kind-select{padding:4px 22px 4px 8px;border:1px solid var(--border-default);border-radius:6px;background:var(--surface-raised);font-size:12px;font-weight:600;color:var(--ink-900);cursor:pointer;appearance:none;-webkit-appearance:none;}
    .byoe-wizard__kind-select[data-kind="inflow"]{color:var(--cash-up-text);border-color:var(--cash-up-border);}
    .byoe-wizard__kind-select[data-kind="outflow"]{color:var(--warn-text);border-color:var(--warn-border);}
    .byoe-wizard__kind-select[data-kind="transfer"]{color:var(--accent-secondary-hover);border-color:var(--accent-secondary-border);}
    .byoe-wizard__kind-select[data-kind="skip"]{color:var(--ink-500);border-color:var(--border-default);}
    .byoe-wizard__legend{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px;}
    .byoe-wizard__chip{display:inline-flex;align-items:center;padding:3px 10px;border-radius:9999px;font-size:11px;font-weight:600;border:1px solid;}
    .byoe-wizard__chip--inflow{color:var(--cash-up-text);background:var(--cash-up-soft);border-color:var(--cash-up-border);}
    .byoe-wizard__chip--outflow{color:var(--warn-text);background:var(--warn-soft);border-color:var(--warn-border);}
    .byoe-wizard__chip--transfer{color:var(--accent-secondary-hover);background:var(--accent-secondary-soft);border-color:var(--accent-secondary-border);}
    .byoe-wizard__chip--skip{color:var(--ink-500);background:var(--surface-sunken);border-color:var(--border-default);}
    .byoe-wizard__footer{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:14px 24px;border-top:1px solid var(--border-subtle);background:var(--surface-sunken);flex-wrap:wrap;}
    .byoe-wizard__escape{background:none;border:none;font-size:12px;color:var(--ink-500);cursor:pointer;text-decoration:underline;text-underline-offset:3px;text-decoration-color:var(--border-strong);padding:4px;}
    .byoe-wizard__escape:hover{color:var(--ink-500);}
    .byoe-wizard__footer-right{display:flex;gap:8px;align-items:center;}
    .byoe-wizard__btn{padding:9px 18px;border-radius:8px;border:1px solid;font-size:13px;font-weight:600;cursor:pointer;transition:all 180ms cubic-bezier(0.16,1,0.3,1);}
    .byoe-wizard__btn--primary{background:var(--accent);color:var(--surface-raised);border-color:var(--accent);}
    .byoe-wizard__btn--primary:hover{filter:brightness(1.05);}
    .byoe-wizard__btn--primary[disabled]{opacity:0.5;cursor:not-allowed;}
    .byoe-wizard__btn--ghost{background:transparent;color:var(--ink-500);border-color:var(--border-default);}
    .byoe-wizard__btn--ghost:hover{background:var(--surface-sunken);color:var(--ink-900);}
    @media(max-width:640px){
      .byoe-wizard-modal{padding:0;}
      .byoe-wizard{width:100%;height:100vh;max-height:100vh;border-radius:0;}
      .byoe-wizard__header{padding:14px 16px 12px;flex-wrap:wrap;}
      .byoe-wizard__steps{gap:4px;}
      .byoe-wizard__step-label{display:none;}
      .byoe-wizard__step{padding:5px 8px;}
      .byoe-wizard__body{padding:16px;}
      .byoe-wizard__preview{max-height:280px;}
      .byoe-wizard__preview th,.byoe-wizard__preview td{padding:5px 8px;max-width:100px;font-size:11px;}
      .byoe-wizard__range-controls{grid-template-columns:1fr;}
      .byoe-wizard__footer{padding:12px 16px;}
      .byoe-wizard__btn{flex:1;}
      .byoe-wizard__escape{width:100%;text-align:center;order:2;}
      .byoe-wizard__footer-right{width:100%;order:1;}
    }

/* ─── former inline <style id="cell-rail-styles"> block 8 — the
       cell-provenance side rail. Was portal.html ~L17025-17116. ──── */
    /* ═══════════════════════════════════════════════════════════════
       Cell-provenance side rail — Sprint 1 #2 (Engineering 2026-05-09)
       "Click any cell, see its DNA." 360px right-rail on desktop,
       bottom-sheet on mobile <640px.
       v4 "Ledger" migration (dashboard-migration I5, plan §I5): I1
       extracted this from inline; I5 routes its raw hex / rgba literals
       through the v4 token system. Legacy bridge tokens (--teal,
       --text-1/2/3, --border, --border-subtle, --red, --green, --amber)
       are unchanged — already v4 bridges. The teal value-card wash →
       --accent-soft/-softer; the amber override state → --warn-soft;
       the danger surfaces → --cash-down-*; white → --surface-raised,
       warm-grey wells → --surface-sunken. The rail's bespoke DIRECTIONAL
       drop-shadows (-12px 0 … / 0 -12px …) keep their rgba — no v4
       shadow token carries that offset. CSS-only; no markup / JS / class.
       Trigger: cell info-button (revealed on hover/focus) + Cmd/Ctrl+I.
       Override flow: Type → Save → Confirm card (auditable three-layer).
       ═══════════════════════════════════════════════════════════════ */
    .cell-rail{position:fixed;top:0;right:0;height:100vh;width:360px;background:var(--surface-raised);border-left:1px solid var(--border-default);box-shadow:-12px 0 32px rgba(15,23,42,0.10);z-index:9500;display:flex;flex-direction:column;transform:translateX(100%);transition:transform 240ms cubic-bezier(0.2,0.8,0.2,1);font-family:inherit;}
    .cell-rail.is-open{transform:translateX(0);}
    .cell-rail[hidden]{display:none;}
    .cell-rail__head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding:18px 18px 12px;border-bottom:1px solid var(--border-subtle);}
    .cell-rail__head-meta{flex:1;min-width:0;}
    .cell-rail__eyebrow{font-size:10.5px;font-weight:700;color:var(--ink-500);letter-spacing:0.6px;text-transform:uppercase;margin-bottom:4px;}
    .cell-rail__title{font-size:15px;font-weight:700;color:var(--ink-900);letter-spacing:-0.2px;line-height:1.3;}
    .cell-rail__subtitle{font-size:12px;color:var(--ink-500);margin-top:2px;font-variant-numeric:tabular-nums;}
    .cell-rail__close{flex:0 0 auto;width:32px;height:32px;border-radius:8px;border:1px solid transparent;background:transparent;color:var(--ink-500);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-family:inherit;}
    .cell-rail__close:hover{background:var(--surface-hover);color:var(--ink-900);}
    .cell-rail__close:focus-visible{outline:2px solid var(--accent);outline-offset:1px;}
    .cell-rail__body{flex:1;overflow-y:auto;padding:14px 18px 18px;}
    .cell-rail__section{margin-bottom:18px;}
    .cell-rail__section:last-child{margin-bottom:0;}
    .cell-rail__section-label{font-size:10.5px;font-weight:700;color:var(--ink-500);letter-spacing:0.55px;text-transform:uppercase;margin-bottom:8px;}
    .cell-rail__value-card{padding:14px 14px 12px;border-radius:12px;background:var(--accent-softer);border:1px solid var(--accent-border);}
    .cell-rail__value-card.is-override{background:var(--warn-soft);border-color:var(--warn-border);}
    .cell-rail__value{font-size:24px;font-weight:700;color:var(--ink-900);font-variant-numeric:tabular-nums;letter-spacing:-0.4px;}
    .cell-rail__value-source{margin-top:4px;font-size:11.5px;color:var(--ink-500);display:flex;align-items:center;gap:6px;}
    .cell-rail__value-source-dot{width:7px;height:7px;border-radius:50%;background:var(--accent);}
    .cell-rail__value-card.is-override .cell-rail__value-source-dot{background:var(--warn);}
    .cell-rail__assumption{padding:10px 12px;border-radius:9px;background:var(--surface-sunken);border:1px solid var(--border-subtle);}
    .cell-rail__assumption-label{font-size:12px;color:var(--ink-900);font-weight:600;}
    .cell-rail__assumption-value{font-size:12px;color:var(--ink-500);margin-top:2px;font-variant-numeric:tabular-nums;}
    .cell-rail__txns{border:1px solid var(--border-subtle);border-radius:9px;overflow:hidden;}
    .cell-rail__txn-row{display:flex;align-items:center;gap:8px;padding:9px 12px;border-bottom:1px solid var(--border-subtle);font-size:12px;}
    .cell-rail__txn-row:last-child{border-bottom:0;}
    .cell-rail__txn-meta{flex:1;min-width:0;}
    .cell-rail__txn-merchant{color:var(--ink-900);font-weight:500;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
    .cell-rail__txn-date{color:var(--ink-500);font-size:11px;font-variant-numeric:tabular-nums;}
    .cell-rail__txn-amt{color:var(--ink-900);font-variant-numeric:tabular-nums;font-weight:600;flex:0 0 auto;}
    .cell-rail__txns-empty{padding:14px 12px;font-size:12px;color:var(--ink-500);text-align:center;font-style:italic;border:1px dashed var(--border-subtle);border-radius:9px;}
    .cell-rail__txns-more{display:block;text-align:center;font-size:11.5px;color:var(--accent);text-decoration:none;padding:8px 12px;background:var(--accent-softer);font-weight:600;}
    .cell-rail__txns-more:hover{text-decoration:underline;}
    .cell-rail__history{display:flex;flex-direction:column;gap:6px;}
    .cell-rail__history-row{display:flex;align-items:center;gap:8px;font-size:11.5px;color:var(--ink-500);padding:6px 0;border-bottom:1px dashed var(--border-subtle);}
    .cell-rail__history-row:last-child{border-bottom:0;}
    .cell-rail__history-row.is-inactive{opacity:0.55;}
    .cell-rail__history-arrow{flex:0 0 auto;color:var(--ink-500);}
    .cell-rail__history-vals{flex:1;min-width:0;font-variant-numeric:tabular-nums;}
    .cell-rail__history-time{flex:0 0 auto;color:var(--ink-500);font-size:11px;}
    .cell-rail__history-empty{font-size:12px;color:var(--ink-500);font-style:italic;}
    .cell-rail__override{border-top:1px solid var(--border-subtle);padding:14px 18px;background:var(--surface-sunken);}
    .cell-rail__override-form{display:flex;flex-direction:column;gap:10px;}
    .cell-rail__override-row{display:flex;gap:8px;align-items:center;}
    .cell-rail__override-input{flex:1;padding:9px 12px;border:1px solid var(--border-default);border-radius:8px;font-size:13px;font-variant-numeric:tabular-nums;font-family:inherit;color:var(--ink-900);}
    .cell-rail__override-input:focus{outline:2px solid var(--accent);outline-offset:0;border-color:var(--accent);}
    .cell-rail__override-why{padding:8px 12px;border:1px solid var(--border-default);border-radius:8px;font-size:12px;font-family:inherit;color:var(--ink-900);resize:vertical;min-height:36px;max-height:96px;}
    .cell-rail__override-why:focus{outline:2px solid var(--accent);outline-offset:0;border-color:var(--accent);}
    .cell-rail__override-actions{display:flex;gap:8px;align-items:center;}
    .cell-rail__btn{padding:8px 14px;font-size:12.5px;font-weight:700;border-radius:8px;border:1px solid transparent;cursor:pointer;font-family:inherit;letter-spacing:-0.05px;display:inline-flex;align-items:center;gap:6px;}
    .cell-rail__btn--primary{background:var(--accent);color:var(--surface-raised);border-color:var(--accent);}
    .cell-rail__btn--primary:hover{background:var(--accent-active);border-color:var(--accent-active);}
    .cell-rail__btn--primary:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
    .cell-rail__btn--secondary{background:var(--surface-raised);color:var(--ink-900);border-color:var(--border-default);}
    .cell-rail__btn--secondary:hover{background:var(--surface-hover);}
    .cell-rail__btn--ghost{background:transparent;color:var(--ink-500);border-color:transparent;text-decoration:underline;text-underline-offset:2px;}
    .cell-rail__btn--ghost:hover{color:var(--ink-500);}
    .cell-rail__btn--danger{background:var(--surface-raised);color:var(--cash-down);border-color:var(--cash-down-border);}
    .cell-rail__btn--danger:hover{background:var(--cash-down-soft);border-color:var(--cash-down);}
    .cell-rail__btn:disabled{opacity:0.5;cursor:not-allowed;}
    .cell-rail__confirm{padding:12px 14px;border-radius:10px;background:var(--accent-softer);border:1px solid var(--accent-border);font-size:12px;color:var(--ink-900);}
    .cell-rail__confirm-actions{display:flex;gap:8px;margin-top:10px;}
    .cell-rail__error{padding:10px 12px;border-radius:9px;background:var(--cash-down-soft);border:1px solid var(--cash-down-border);color:var(--cash-down);font-size:12px;line-height:1.45;}
    .cell-rail__loader{padding:36px 12px;text-align:center;color:var(--ink-500);font-size:12px;}
    .cell-rail-backdrop{position:fixed;inset:0;background:var(--surface-overlay);backdrop-filter:blur(1px);z-index:9499;opacity:0;pointer-events:none;transition:opacity 200ms ease-out;}
    .cell-rail-backdrop.is-visible{opacity:1;pointer-events:auto;}
    /* Cell info-button (injected per-cell on hover/focus) — used in the
       cinematic-fill mini grid + the live forecast grid. */
    .cell-info-btn{position:absolute;top:2px;right:2px;width:14px;height:14px;border-radius:50%;border:0;background:transparent;color:var(--ink-500);cursor:pointer;display:inline-flex;align-items:center;justify-content:center;font-size:9.5px;font-weight:800;font-family:inherit;line-height:1;padding:0;opacity:0;transition:opacity 120ms ease-out,background 120ms ease-out;}
    .cell-info-btn:hover,.cell-info-btn:focus-visible{opacity:1;background:var(--accent);color:var(--surface-raised);outline:none;}
    .byoe-cine__cell:hover .cell-info-btn,.byoe-cine__cell:focus-within .cell-info-btn{opacity:0.65;}
    /* Override badge on cells the user has typed — small dot indicator */
    .cell-override-badge{position:absolute;bottom:2px;left:2px;width:6px;height:6px;border-radius:50%;background:var(--warn);box-shadow:0 0 0 1px var(--surface-raised);}
    @media (max-width:640px){
      /* Mobile: bottom-sheet, full-width, slide up from bottom. */
      .cell-rail{top:auto;right:0;left:0;bottom:0;width:100%;height:auto;max-height:80vh;border-left:0;border-top:1px solid var(--border-default);box-shadow:0 -12px 32px rgba(15,23,42,0.18);transform:translateY(100%);border-top-left-radius:16px;border-top-right-radius:16px;}
      .cell-rail.is-open{transform:translateY(0);}
    }
    @media (prefers-reduced-motion:reduce){
      .cell-rail{transition:none;}
      .cell-rail-backdrop{transition:none;}
    }

/* ═══════════════════════════════════════════════════════════════════
   I2 · v4 "LEDGER" APP-SHELL COMPOSITION  (dashboard-migration I2 + I8.3)
   ───────────────────────────────────────────────────────────────────
   Composes the dashboard CHROME — the app shell, the left sidebar, and
   the top bar — on the v4 design system. Plan:
   dashboard-migration-plan-2026-05.md §I2 + §11 (I8.3 teardown).

   HISTORY — WHY THIS BLOCK IS A SEPARATE COMPOSITION LAYER.
   I2 migrated the chrome ADDITIVELY: every shell element carried BOTH a
   legacy class and its v4 twin (`.app-sidebar tf-sidebar`,
   `.app-main tf-app__main`, `.nav tf-app__topbar`,
   `.app-sidebar__item tf-nav-item`, `.app-layout tf-app`), and this
   block won via COMBINED `.legacy.tf-x` selectors (0,2,0). I8.3 tore
   that legacy layer out: the legacy classes are gone from the markup,
   the legacy app-shell rules (~L1455-1714) were RENAMED onto the v4
   class (`.app-sidebar {}` → `.tf-sidebar {}`, etc.), and the combined
   selectors here collapsed to standalone `.tf-*`.

   THE CASCADE NOW. Load order is design-tokens → components →
   portal.css. Three sources can declare a shell rule for the same
   element: (a) components.css `.tf-*` base, (b) the renamed-legacy
   app-shell rules earlier in THIS file (~L1455+), (c) this block. All
   three are single-class (0,1,0) for the same `.tf-*` token, so the
   winner is purely SOURCE ORDER — and this block is last, so it wins.
   That is the same end result the I2 combined selectors produced; I8.3
   simply removed the redundant legacy class so the win is by order, not
   by an inflated 0,2,0. Every value flows through a v4 token — no raw
   hex (plan §I1 token discipline).

   WHAT IS DELIBERATELY LEFT ALONE.
   · The collapse system (`.tf-sidebar.sidebar--collapsed …`, ~L1610-
     1714) is JS-owned (`sidebar--collapsed` toggled by
     toggleSidebarCollapsed()). It is 0,2,0 — it beats this block's
     0,1,0 `.tf-sidebar` rules when collapsed — so the v4 look here is
     the EXPANDED state; collapse behaviour is unchanged. (I8.3 renamed
     the collapse selectors `.app-sidebar.sidebar--collapsed` →
     `.tf-sidebar.sidebar--collapsed`; `sidebar--collapsed` stays the
     JS-owned state class, so the feature is byte-identical.)
   · Mobile (≤1023px): the renamed-legacy `.tf-sidebar{display:none
     !important}` + the `.tf-bottom-nav` bottom bar stay the mobile
     pattern; the v4 off-canvas `.tf-sidebar` drawer (I7 block below)
     governs the open-drawer state. So this block's shell change is
     DESKTOP-ONLY (≥1024px).
   · `_applyBookkeeperShellMode()` already swaps the firm/company nav
     GROUPS via inline `style.display`. It also stamps
     `#tf-app[data-mode]`. This block needs no mode-specific shell rule —
     in the v4 reference the sidebar + topbar chrome is identical in both
     modes; only the nav groups + tab content differ.
   ─────────────────────────────────────────────────────────────────── */

/* ── ALL-WIDTH LEAK NEUTRALIZER ──────────────────────────────────────
   The `.tf-*` shell classes make the components.css `.tf-*` component
   rules apply — and SOME of those are bare rules with no media query, so
   they leak onto MOBILE (≤1023px), where I2 intends NO change at all
   (the v4 shell is desktop-only; mobile keeps `.tf-bottom-nav`). Two
   such leaks were measured (computed-style probe at 402px) and are
   neutralized here, at all widths, so mobile is byte-identical to pre-I2:

   1. `.tf-app { background: var(--surface-sunken) }` (components.css,
      base rule) would tint the whole `.tf-app` canvas warm-grey on
      mobile. I2 does not adopt the v4 sunken canvas at all (see the
      desktop rule below) — re-assert `transparent` globally.
   2. `.tf-app__topbar { gap: var(--space-5) }` (components.css, base
      rule) leaks a 16px flex `gap` onto the mobile `.tf-app__topbar`
      (the renamed-legacy `.tf-app__topbar` declares no `gap`). Re-assert
      `gap: normal` so the mobile topbar's internal spacing is unchanged.

   This is the same structural pattern as the I1 GLOBAL-LEAK NEUTRALIZER
   block above. (Pre-I8.3 these were `.app-layout.tf-app` / `.nav.tf-app
   __topbar` 0,2,0 combined selectors; I8.3 collapsed them to standalone
   `.tf-app` / `.tf-app__topbar` — they still win on source order.) ── */
.tf-app { background: transparent; }
.tf-app__topbar { gap: normal; }

/* ── The shell. At ≤1023px the shell is UNCHANGED from pre-I2 — the
   legacy `.tf-app{display:flex}` keeps driving mobile (sidebar
   `display:none`, `.tf-app__main{flex:1}` fills; `.tf-bottom-nav` is the
   mobile pattern). So the entire v4 shell composition is scoped inside
   `@media (min-width:1024px)`: the v4 grid must NOT apply at mobile
   widths — a 2-column grid with the sidebar `display:none` would leave
   `.tf-app__main` in the `auto` track (shrink-to-content) instead of filling
   the viewport. Desktop ≥1024px is where the v4 shell renders. ── */
@media (min-width: 1024px) {
  /* The shell grid — `.tf-app` IS the flex parent of sidebar+main;
     this rule makes it the v4 two-column grid (sidebar auto, main 1fr).
     This `.tf-app` rule (0,1,0) and the renamed-legacy `.tf-app`
     (0,1,0, ~L1460) `display:flex` are specificity-equal — this block
     is LATER, so `display:grid` wins on source order. With the sidebar
     held at 228px (`auto` track) and main `1fr`, the grid is
     geometrically identical to the legacy flex layout — no reflow.

     THE v4 SUNKEN APP CANVAS — ADOPTED IN I3. I2 held this `transparent`
     (body white showing through) because the portal's demo banner +
     briefing card have semi-transparent backgrounds that would tint over
     a sunken canvas — a content-area delta I2's mandate forbade. I3
     migrates the Cash Position content (content paint IS in scope) and
     gives the demo banner + briefing card explicit opaque white surfaces
     (the OPAQUE-SURFACE block below) — so the v4 `--surface-sunken`
     canvas is now adopted. The v4 read view: a warm-grey sunken canvas
     with white `.tf-card` / `.tf-kpi` panels floating on it. DESKTOP
     ONLY — this rule is inside the ≥1024px block; at mobile the
     all-width leak-neutralizer keeps `transparent` (legacy white). */
  .tf-app {
    display: grid;
    grid-template-columns: auto 1fr;
    min-height: 100vh;
    background: var(--surface-sunken);  /* v4 sunken canvas — adopted I3 */
    position: relative;
    z-index: 1;
  }
  /* The left sidebar — v4 SURFACE TREATMENT. Wins over the renamed-
     legacy `.tf-sidebar` base rule (0,1,0, ~L1463) on source order
     (this block is later). The legacy rule's `display:none` for
     ≤1023px is preserved — this rule is inside the ≥1024px block, so
     mobile keeps the sidebar hidden.

     WIDTH IS THE v4 --sidebar-w (248px) — ADOPTED IN I3.
     I2 deliberately held the sidebar at the portal's 228px so the content
     column was pixel-stable (a 248px sidebar narrows the `1fr` content
     track 20px, reflowing wrapping text on every tab). I3 migrates the
     Cash Position CONTENT — content reflow is in scope now — so the v4
     geometry is adopted: `--sidebar-w` (248px).

     `:not(.sidebar--collapsed)` SCOPES this rule to the EXPANDED rail.
     The collapse system (`.tf-sidebar.sidebar--collapsed`, ~L1610-1714 —
     JS-toggled) is 0,2,0; this `:not()` rule is also 0,2,0 (one class +
     the `:not()`'s inner class). The `:not()` makes this rule simply NOT
     MATCH when collapsed, so the collapse rules (`width:44px`, …) drive
     the rail unopposed — collapse behaviour is byte-identical to pre-I2.
     (Pre-I8.3 this selector was `.app-sidebar.tf-sidebar:not(…)`, 0,3,0;
     I8.3 collapsed the combined `.app-sidebar.tf-sidebar` half away —
     0,3,0 → 0,2,0 — and the mutual-exclusion via `:not()` is unchanged.) */
  .tf-sidebar:not(.sidebar--collapsed) {
    display: flex;
    flex-direction: column;
    width: var(--sidebar-w);            /* v4 248px — adopted in I3 */
    flex-shrink: 0;
    background: var(--surface-page);    /* v4 white canvas */
    border-right: 1px solid var(--border-default);  /* v4 hairline */
    box-shadow: none;                   /* v4 sidebar is border-defined */
    position: sticky;
    top: 0;
    height: 100vh;
    overflow-y: auto;
    padding: var(--space-5) var(--space-4);  /* 16px / 12px — v4 rhythm */
  }
  /* The content column of the grid. Geometry only — NO background, for
     the same reason as `.tf-app` above: the content tabs nested
     inside `.tf-app__main` include transparent-background chrome, so a
     canvas tint would bleed into the content area. The components.css
     `.tf-app__main` base rule sets no background either, and the
     renamed-legacy `.tf-app__main` rule (~L1510) is likewise
     background-less — so the content stays on the body white. */
  .tf-app__main {
    display: flex;
    flex-direction: column;
    min-width: 0;
    overflow-x: hidden;
  }

  /* ── Sidebar brand lockup — v4 spacing. `.app-sidebar__brand` is a
     KEPT legacy class (a JS-queried hook — `querySelectorAll(
     '.app-sidebar__brand, .nav-brand')`; it has no v4 twin, so I8.3
     left it in place). Scoped under the `.tf-sidebar` parent. ── */
  .tf-sidebar .app-sidebar__brand {
    margin-bottom: var(--space-5);
    padding: var(--space-3) var(--space-4);
    border-radius: var(--radius-sm);
  }

  /* ── Sidebar section labels — v4 overline treatment. ── */
  .tf-sidebar .app-sidebar__section-label {
    font-size: var(--text-xs);
    font-weight: var(--weight-semibold);
    letter-spacing: var(--tracking-wide);
    color: var(--text-quiet);
    padding: var(--space-5) var(--space-3) var(--space-2);
    margin: 0;
  }
  .tf-sidebar .app-sidebar__section-label:first-of-type { padding-top: 0; }

  /* ── Nav items — the v4 `.tf-nav-item`. This rule (0,1,0) wins over
     both the renamed-legacy `.tf-nav-item` base (0,1,0, ~L1488) and the
     components.css `.tf-nav-item` (0,1,0) on SOURCE ORDER — this block
     is last. v4 item: 38px tall, 13px medium, transparent 2px left-rule,
     quiet text at rest. ── */
  .tf-nav-item {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    height: 38px;
    padding: 0 var(--space-3);
    border: none;
    border-left: 2px solid transparent;
    border-radius: var(--radius-sm);
    background: none;
    width: 100%;
    text-align: left;
    text-decoration: none;
    font-family: inherit;
    font-size: var(--text-sm);
    font-weight: var(--weight-medium);
    color: var(--text-secondary);
    cursor: pointer;
    transition: background-color var(--duration-fast, .15s) var(--ease-standard, ease),
                color var(--duration-fast, .15s) var(--ease-standard, ease);
  }
  .tf-nav-item svg,
  .tf-nav-item i[data-lucide] {
    width: 18px;
    height: 18px;
    flex-shrink: 0;
    color: var(--text-quiet);
    transition: color var(--duration-fast, .15s) ease;
  }
  .tf-nav-item:hover {
    background: var(--surface-hover);
    color: var(--text-primary);
  }
  .tf-nav-item:hover svg,
  .tf-nav-item:hover i[data-lucide] { color: var(--text-secondary); }
  .tf-nav-item:focus-visible {
    outline: 2px solid var(--focus-ring, var(--accent));
    outline-offset: -2px;
  }
  /* ── Active state — `active` is a JS-owned state class
     (`snavSetActive()` ~L4810 does `classList.remove('active')` then
     `.add('active')` on the current nav button). The v4 active look is
     MAPPED onto it here rather than migrating the JS to a `tf-` state
     class (plan §4 step 3 — the CSS-map is the low-risk path for
     chrome). This `.tf-nav-item.active` rule (0,2,0) and the renamed-
     legacy active rule `.tf-nav-item.active` (0,2,0, ~L1501) are
     specificity-equal — this block is later, so it wins on source
     order. The v4 active: teal-soft wash, teal text, teal left-rule. ── */
  .tf-nav-item.active {
    background: var(--accent-soft);
    color: var(--accent-strong);
    font-weight: var(--weight-semibold);
    border-left-color: var(--accent);
    border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
    box-shadow: none;                   /* drop the legacy inset ring */
  }
  .tf-nav-item.active svg,
  .tf-nav-item.active i[data-lucide] { color: var(--accent); }

  /* ── Top bar — v4 SURFACE + v4 GEOMETRY. This `.tf-app__topbar` rule
     (0,1,0) wins over the renamed-legacy `.tf-app__topbar` base (0,1,0)
     on source order. Scoped to ≥1024px so the renamed-legacy mobile
     `.tf-app__topbar` rules (48px, ~L1295/L1427) are untouched.

     HEIGHT + PADDING ARE THE v4 TOKENS — ADOPTED IN I3: `--topbar-h`
     (60px) + `--dash-pad` (24px). ── */
  .tf-app__topbar {
    position: sticky;
    top: 0;
    z-index: 100;
    display: flex;
    align-items: center;
    height: var(--topbar-h);            /* v4 60px — adopted in I3 */
    padding: 0 var(--dash-pad);         /* v4 24px — adopted in I3 */
    background: var(--surface-page);    /* v4 white canvas */
    border-bottom: 1px solid var(--border-default);  /* v4 hairline */
    justify-content: space-between;     /* preserve the brand↔actions split */
    /* NO `gap` — the global neutralizer's `gap: normal` applies at all
       widths. The portal topbar's children (.nav-brand — hidden on
       desktop — and .nav-right) manage their own spacing; the v4
       topbar `gap` is unneeded here and would shift desktop spacing. */
  }
}
/* ── end I2 v4 app-shell composition ─────────────────────────────── */


/* ═══════════════════════════════════════════════════════════════════
   I3 · v4 "LEDGER" CASH-POSITION COMPOSITION  (dashboard-migration I3 + I8.4)
   ───────────────────────────────────────────────────────────────────
   Composes the Cash Position tab (`#tab-collections`) CONTENT — the KPI
   strip, the section panels, the AR table — on the v4 design system.
   Plan: dashboard-migration-plan-2026-05.md §I3 + §11 (I8.4 teardown).

   HISTORY — THE MIGRATION, AND HOW THE LEGACY LAYER WENT AWAY.
   I3 migrated the Cash Position content ADDITIVELY: every migrated
   element carried a legacy class AND its v4 twin (`.kpi-card tf-kpi`,
   `.data-section tf-card`), and this block won via COMBINED `.legacy.tf-x`
   selectors (0,2,0). I8.4 collapsed those to standalone `.tf-kpi` /
   `.tf-card` / `.tf-segmented` and dropped the legacy class from the
   migrated markup — but KEPT the `.kpi-card {}` / `.data-section {}`
   legacy base rules, because the `bookkeeper` firm-mode tab still ran on
   them. I9 (the bookkeeper-tab migration) closed that out: I9.2/I9.3
   migrated the firm tab + its client drawer to v4, I9.4 stripped the
   firm tab's legacy twins, I9.5 migrated the `data-section__*` /
   `data-section--*` element/modifier vocabulary + the AI tab, and I9.6
   migrated the company-mode runway tile's JS state classes to
   `.tf-kpi--danger/--warning/--healthy` + `.tf-kpi__value--text` and
   then DELETED the now-zero-consumer legacy `.kpi-card* {}` /
   `.data-section* {}` / `.pnl-card` base rule families. The portal now
   runs on ONE class vocabulary.

   THE CASCADE NOW. The `.tf-kpi` / `.tf-card` rules here (0,1,0) and the
   components.css `.tf-kpi` / `.tf-card` base (0,1,0) are specificity-
   equal — this block is later, so it wins on SOURCE ORDER. Every value
   flows through a v4 token — no raw hex (plan §I1 discipline).

   STILL HERE (not legacy — kept by design).
   · `.kpi-shimmer` — the loading skeleton, a distinct class (not part of
     the `.kpi-card*` family); JS-owned state, mapped to v4 below.
   · The Cash Position JS-rendered content that is NOT table markup —
     `renderCollections`'s day-cards, `renderAccountBreakdown`'s account
     rows. Those are inline-styled <div> renderers, not <table>s and not
     class-hooked. The v4 surface they sit ON (the white `.tf-card`
     panel) is what changes.
   ═══════════════════════════════════════════════════════════════════ */

/* ── OPAQUE-SURFACE FIX — the sunken-canvas precondition ─────────────
   The I2 block deferred the v4 sunken app canvas because two Cash
   Position elements have SEMI-TRANSPARENT backgrounds: the demo banner
   (`#demo-mode-banner`, inline rgba-gradient) and the briefing card
   (`.briefing-card`, a gradient ending at `--bg-card`). Over the white
   `body` they read teal-tinted-on-white; over the v4 `--surface-sunken`
   canvas the warm-grey would bleed through and shift their tint. I3
   adopts the sunken canvas (see the I2-block edits), so those two
   elements need an explicit OPAQUE white base UNDER their decorative
   layer. `background-color` (not the `background` shorthand) sets the
   base without disturbing the element's `background-image` gradient —
   the gradient now composites over white exactly as it did pre-I3.
   Desktop only — the sunken canvas is ≥1024px. ── */
@media (min-width: 1024px) {
  #demo-mode-banner,
  .briefing-card {
    background-color: var(--surface-raised);  /* opaque white base */
  }
}

/* ── Demo "View as" persona switcher (mounted in #demo-mode-banner) ────
   A compact role-switcher chip + dropdown so a prospect can flip the firm
   demo between Owner / Staff / Client live. Demo-only chrome; tokens only
   (no raw hex — the no-raw-hex CI guard covers this file). */
.demo-viewas { position: relative; display: inline-flex; }
.demo-viewas__trigger {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--surface-raised);
  border: var(--border-width) solid color-mix(in srgb, var(--accent) 32%, transparent);
  border-radius: var(--radius-sm);
  padding: 6px 10px; cursor: pointer; font: inherit;
  color: var(--ink-900); white-space: nowrap;
  transition: border-color 0.15s, box-shadow 0.15s, transform 0.15s;
}
.demo-viewas__trigger:hover {
  border-color: color-mix(in srgb, var(--accent) 55%, transparent);
  box-shadow: 0 2px 10px color-mix(in srgb, var(--accent) 18%, transparent);
}
.demo-viewas__eyebrow {
  font-size: 10px; font-weight: 700; letter-spacing: 0.4px; text-transform: uppercase;
  color: var(--ink-500);
}
.demo-viewas__who { font-size: 13px; font-weight: 700; color: var(--ink-900); }
.demo-viewas__tag {
  font-size: 11px; font-weight: 600; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-radius: var(--radius-pill, 999px); padding: 2px 8px;
}
.demo-viewas__chev { color: var(--ink-500); flex-shrink: 0; }
.demo-viewas__menu {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 60;
  min-width: 280px; max-width: 320px;
  background: var(--surface-raised);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-md);
  box-shadow: 0 12px 32px color-mix(in srgb, var(--ink-900) 16%, transparent);
  padding: 6px;
}
.demo-viewas__menu[hidden] { display: none; }
.demo-viewas__menu-label {
  font-size: 10.5px; font-weight: 700; letter-spacing: 0.4px; text-transform: uppercase;
  color: var(--ink-500); padding: 6px 8px 4px;
}
.demo-viewas__opt {
  display: flex; flex-direction: column; gap: 3px; width: 100%; text-align: left;
  background: transparent; border: 0; border-radius: var(--radius-sm);
  padding: 8px 10px; cursor: pointer; font: inherit; transition: background 0.12s;
}
.demo-viewas__opt:hover { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.demo-viewas__opt.is-active { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.demo-viewas__opt-head { display: flex; align-items: center; gap: 8px; }
.demo-viewas__opt-name { font-size: 13px; font-weight: 700; color: var(--ink-900); }
.demo-viewas__opt-tag {
  font-size: 10.5px; font-weight: 600; color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  border-radius: var(--radius-pill, 999px); padding: 1px 7px;
}
.demo-viewas__opt-desc { font-size: 11.5px; color: var(--ink-500); line-height: 1.4; }

/* ── KPI STRIP — the v4 `.tf-kpi` tile ──────────────────────────────
   `.tf-kpi` (0,1,0) wins over the components.css `.tf-kpi` base (0,1,0)
   on source order — this block is later. A migrated card no longer
   carries `.kpi-card`, so the legacy `.kpi-card` base rule (~L782) does
   not match it. The v4 tile: a white border-defined card, flat (no lift
   / no glow — the v4 read view is quiet), v4 padding rhythm. */
.tf-kpi {
  background: var(--surface-raised);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-md);
  padding: var(--space-6);
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  box-shadow: none;                  /* v4 cards are border-defined */
  transition: border-color var(--duration-fast, .15s) var(--ease-standard, ease);
}
/* The v4 tile is flat — no decorative pseudo-elements. The legacy
   `.kpi-card::before/::after` (shimmer line + hover glow) no longer
   reach a migrated card (it carries no `.kpi-card`); these explicit
   `display:none` rules keep the tile flat even so. */
.tf-kpi::before { display: none; }
.tf-kpi::after  { display: none; }
/* The v4 tile does not lift on hover — quiet read view. */
.tf-kpi:hover {
  transform: none;
  box-shadow: none;
  border-color: var(--border-strong);
}
/* Label — v4 uppercase micro-overline. */
.tf-kpi__label {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--text-quiet);
  display: flex;
  align-items: center;
  gap: var(--space-2);
}
/* Value — v4 mono tabular figure. The legacy overflow guard (ellipsis +
   nowrap + min-width:0 — the 2026-05-13 fix for $11.5M-class numbers
   blowing the tile) is PRESERVED: a v4 mono number is just as wide. */
.tf-kpi__value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums lining-nums;
  font-size: var(--num-md);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.02em;
  color: var(--text-primary);
  line-height: 1.1;
  margin-top: 0;
  max-width: 100%;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* The JS-owned non-numeric KPI value modifier — `tf-kpi__value--text`,
   classList-toggled for "Cash Positive"-style strings — keep it smaller
   so the word does not wrap. Used by the bookkeeper client-drawer runway
   tile (I9.3) and, since dashboard-migration I9.6, the company-mode
   Cash-tab runway tile (`renderRunway` migrated off the legacy
   `kpi-card__value--text` twin — its last consumer). The mono
   `.tf-kpi__value` (30px tabular) is for pure numbers; a labelled value
   ("142 days", "Cash Positive") in 30px mono overruns the tile — the
   `--text` variant (20px, wrap-allowed) is the correct treatment. */
.tf-kpi__value--text {
  font-size: var(--text-lg);
  letter-spacing: -0.01em;
  white-space: normal;
}
/* Verdict / sub-line — v4 secondary text. The legacy `.kpi-card__sub`
   2-line clamp is preserved (the sub-line carries delta + runway). */
.tf-kpi__verdict {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  margin-top: 0;
  line-height: var(--lh-snug);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
/* HERO tile — the one teal number on the dashboard (Total Cash
   Balance). v4 `.tf-kpi--hero`: faint-teal fill, teal hairline, the
   value in accent teal at the large dashboard-hero size. */
.tf-kpi--hero {
  border-color: var(--accent-border);
  background: var(--accent-softer);
}
.tf-kpi--hero:hover { border-color: var(--accent); }
.tf-kpi--hero .tf-kpi__value {
  font-size: var(--num-lg);
  color: var(--accent-strong);
}
.tf-kpi--hero .tf-kpi__label { color: var(--accent-strong); }
/* The hero value at --num-lg (44px) is large — on a narrow tile a long
   number could still clip. The ellipsis guard above already covers it;
   this ≤1024px rule drops the hero value to --num-md so mobile is sane. */
@media (max-width: 1024px) {
  .tf-kpi--hero .tf-kpi__value { font-size: var(--num-md); }
}
/* The v4-native KPI state modifiers — danger / warning / healthy.
   `tf-kpi--danger`/`--warning` arrived in I9.3 (the bookkeeper client-
   drawer runway tile); dashboard-migration I9.6 adds `--healthy` and
   migrates the company-mode Cash-tab runway tile (`renderRunway`,
   portal.html ~L6769) to toggle these v4 modifiers — it was the LAST
   consumer of the legacy `kpi-card--danger/--warning/--healthy` state
   classes, so migrating it lets the whole legacy `.kpi-card* {}` rule
   family be deleted. Tinted border + soft fill, legible on the v4 white
   tile; `--healthy` is just the up-grade border (no fill — a healthy
   runway is the quiet default, it does not need a tint). */
.tf-kpi--danger  { border-color: var(--cash-down); background: var(--cash-down-soft); }
.tf-kpi--warning { border-color: var(--warn-border, var(--cash-flat)); background: var(--warn-soft, var(--cash-flat-soft)); }
.tf-kpi--healthy { border-color: var(--cash-up); }

/* ── SECTION PANELS — the v4 `.tf-card` ─────────────────────────────
   `.tf-card` (0,1,0) wins over the components.css `.tf-card` base
   (0,1,0) on source order. A migrated panel no longer carries
   `.data-section`, so the legacy `.data-section` base rule (~L868) does
   not match it. The v4 `.tf-card--primary/--secondary/--tertiary` weight
   MODIFIERS (renamed from `data-section--*` in I9.5) tune the title size;
   this rule normalizes the panel SURFACE to v4. The v4 panel: white,
   border-defined, flat, v4 padding. */
.tf-card {
  background: var(--surface-raised);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-lg);
  padding: var(--space-7);
  box-shadow: none;                  /* v4 panels are border-defined */
  transition: border-color var(--duration-fast, .15s) var(--ease-standard, ease);
}
.tf-card::before { display: none; }  /* drop legacy shimmer line */
.tf-card::after  { display: none; }  /* drop legacy hover glow */
.tf-card:hover {
  transform: none;
  box-shadow: none;
  border-color: var(--border-strong);
}
/* The --primary weight tier carried a faint-teal gradient wash; on the v4
   sunken canvas the primary panel reads as plain white (the v4 read view
   does not tint panels). The --tertiary tier had a sunken bg — keep it
   visually recessive with the v4 sunken token so the hierarchy the portal
   designed survives. (I9.5 renamed the modifier `data-section--* ` →
   `tf-card--*`; these compound rules pair the surface with the tier.) */
.tf-card.tf-card--primary  { background: var(--surface-raised); }
.tf-card.tf-card--tertiary { background: var(--surface-raised) !important; border-color: var(--border-default); }

/* ── BOOKKEEPER CLIENT-DRAWER FLAT SECTION — `.bk-drawer-section` ────
   dashboard-migration I9.3. The bookkeeper client drill-in drawer
   (`openBkClientDrawer`) renders 4 interior content groups — Bank
   accounts / 13-week forecast / Recent transactions / Top vendors —
   INSIDE the drawer card. The pre-I9 markup used the legacy
   `.data-section--drawer` modifier (a deliberately FLAT section: no
   border, no card background, no padding — cards-inside-a-card looked
   cluttered, per the 2026-05-13 CDO D-19 fix). v4 has no "flat section
   inside a drawer" primitive — a `.tf-card` is the opposite (a
   border-defined panel). So this is a portal-local v4 composition, the
   same kind of page-layer rule the I3 `.tf-card` block above is.
   A flat group: no chrome of its own, a v4 title, spacing between
   groups, and an internal top hairline once a `.tf-table` follows. */
.bk-drawer-section { margin-bottom: var(--space-6); }
.bk-drawer-section__title {
  font-size: var(--text-md);
  font-weight: var(--weight-semibold);
  color: var(--text-primary);
  letter-spacing: var(--tracking-snug);
}
.bk-drawer-section__subtitle {
  font-size: var(--text-sm);
  color: var(--text-secondary);
  margin-top: 2px;
  line-height: var(--lh-snug);
}
.bk-drawer-section__head { margin-bottom: var(--space-3); }

/* ── AR-AGING TABLE — the v4 `.tf-table` ────────────────────────────
   `loadAR()` renders the AR table into `#ar-content` with `.tf-table`
   on the `<table>` and `.tf-table-wrap` on the wrapper. The
   components.css `.tf-table` rules style it; the only portal-side
   hazard is the portal's generic `table { width:100% }` (~L1035) and
   the mobile `table { min-width:480px }` (~L1402) — both compatible
   with `.tf-table` (which also sets width:100%). The `.tf-table-wrap`
   from components.css provides the v4 bordered, rounded, scroll-capped
   container. No portal-side override needed — components.css handles
   the table fully once the classes are on the markup. This anchor
   comment documents that intentional no-op so a future reader does not
   "fix" a missing rule that is correctly absent. */

/* ── end I3 v4 cash-position composition ────────────────────────── */


/* ═══════════════════════════════════════════════════════════════════
   I4 · v4 "LEDGER" FORECAST COMPOSITION  (dashboard-migration I4 + I8.4)
   ───────────────────────────────────────────────────────────────────
   Composes the Forecast tab (`#tab-forecast`) and Forecast Builder tab
   (`#tab-forecast-builder`) CONTENT on the v4 design system. Plan:
   dashboard-migration-plan-2026-05.md §I4 + §11 (I8.4 teardown).

   HISTORY — THE CASCADE.
   I4 migrated the forecast content ADDITIVELY: every migrated element
   carried a legacy class AND its v4 twin (`.forecast-toggle tf-segmented`,
   `.forecast-toggle__btn tf-segmented__option`, `.data-section tf-card`),
   and this block won via COMBINED `.legacy.tf-x` selectors (0,2,0). I8.4
   collapsed those to standalone `.tf-segmented` / `.tf-segmented__option`
   and dropped the legacy class from the markup — every forecast-toggle
   element was migrated, so `.forecast-toggle` is fully gone from the
   portal. The `.tf-segmented` rule here (0,1,0) wins over the
   components.css `.tf-segmented` base (0,1,0) on source order.

   WHAT IS DELIBERATELY LEFT ALONE.
   · The `.tf-card` panel surface is ALREADY composed by the I3 block
     above (a global, un-scoped rule). The forecast tabs' `.tf-card`
     panels inherit it for free — this block adds NOTHING for those
     panels. Documented so a reader does not "fix" a correctly-absent
     rule.
   · The Forecast Builder grid (`#fb-grid` / `.fb-grid`) keeps its own
     `.fb-grid*` styling (portal.css ~L2820, former inline block 6).
     The grid is the most JS-coupled surface in the file — fbRenderGrid
     injects `fb-grid__week-header` / `fb-grid__cell*` cells and the
     auto-save flash toggles `fb-grid__cell--saved-flash`. Per plan §4
     those JS-owned classes are NOT renamed; the v4 `.tf-card` panel
     simply HOSTS the existing grid. Grid-cell v4 restyle, if wanted,
     is a deliberate later increment with its own smoke coverage.
   · The payroll-detection banner (`#payroll-detect-banner`) and the
     `.fb-grid-hint` callout are hand-styled alert/hint cards with
     intentional colored left-borders — not generic panels. Like the
     I3 demo banner, they are left as-is (nothing to add a class
     ALONGSIDE — they are inline-styled, not class-hooked).
   · The BYOE template-upload dialog + manual-mapping wizard
     (`.byoe-*`, `#byoe-styles`) and the cell-provenance side rail
     (`#cell-rail-styles`) are explicitly I5 scope (plan §I5) — not
     touched by I4.
   ═══════════════════════════════════════════════════════════════════ */

/* ── FORECAST VIEW TOGGLE — the v4 `.tf-segmented` ──────────────────
   `.tf-segmented` (0,1,0) wins over the components.css `.tf-segmented`
   base (0,1,0) on source order. The v4 segmented control: a sunken pill
   track, hairline border, v4 radius. */
.tf-segmented {
  display: inline-flex;
  padding: 3px;
  gap: 2px;
  background: var(--surface-sunken);
  border: var(--border-width) solid var(--border-subtle);
  border-radius: var(--radius-sm);
  margin-bottom: 14px;          /* preserve the legacy bottom rhythm */
}
/* The option button. `.tf-segmented__option` (0,1,0) wins over the
   components.css base on source order. */
.tf-segmented__option {
  padding: 0 var(--space-5);
  height: 28px;
  display: inline-flex;
  align-items: center;
  font-size: var(--text-sm);
  font-weight: var(--weight-medium);
  color: var(--text-secondary);
  background: transparent;
  border: none;
  border-radius: var(--radius-xs);
  cursor: pointer;
  font-family: inherit;
  transition: background-color var(--duration-fast) var(--ease-standard),
              color var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-fast) var(--ease-standard);
}
.tf-segmented__option:hover { color: var(--text-primary); }
/* THE STATE MAP — `active` is a JS-owned state class (`setForecastView`,
   `loadForecast` toggle it), NOT the v4 `.is-active`. Per plan §4 step 3
   (low-risk CSS-map path) the JS-owned `.active` is kept and the v4
   active look is mapped onto it here via `.tf-segmented__option.active`
   (0,2,0). */
.tf-segmented__option.active {
  background: var(--surface-page);
  color: var(--text-primary);
  font-weight: var(--weight-semibold);
  box-shadow: var(--shadow-raised);
}
.tf-segmented__option:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
}

/* ── FORECAST CHART CARD — the v4 `.tf-card` ────────────────────────
   `renderForecastChart()` injects the chart container as a bare
   `.tf-card forecast-chart-card` `<div>` (the hardcoded inline
   bg/border/radius were dropped — the v4 token-based card owns the
   surface). The bare `.tf-card` is styled by components.css `.tf-card`
   (0,1,0) — there is NO competing legacy single-class rule here (the
   `<div>` carries no other class), so components.css applies cleanly.
   `forecast-chart-card` is the I4-specific hook: the chart needs the
   v4 card padding but a touch less than the default `--space-6` so the
   13-bar SVG and the legend breathe inside the panel. */
.forecast-chart-card {
  padding: var(--space-5) var(--space-6);
}

/* ── 13-WEEK FORECAST TABLE — the v4 `.tf-table` ────────────────────
   `renderForecastChart()` renders the 13-week table into
   `#forecast-content` with `.tf-table` on the `<table>` and
   `.tf-table-wrap` on the wrapper — exactly the I3 AR-table pattern.
   The components.css `.tf-table` rules style it fully; the only
   portal-side hazard is the portal's generic `table { width:100% }`
   (~L1035) and the mobile `table { min-width:480px }` (~L1402) — both
   compatible with `.tf-table` (which also sets width:100%). The
   numeric columns (Inflow / Outflow / Net / Balance) carry
   `.tf-th--num` / `.tf-td--num` — components.css right-aligns them
   with tabular figures.

   THE COLOUR-CLASS CASCADE FIX. The Inflow / Outflow / Net cells keep
   their legacy `.text-green` / `.text-red` colour classes ALONGSIDE
   `.tf-td--num` (additive — the classes are not removed). But
   `components.css .tf-table .tf-td--num` (0,2,0) sets
   `color: var(--text-primary)` and would BEAT the legacy single-class
   `.text-green` / `.text-red` (0,1,0) — the green/red figure colour
   the pre-I4 forecast table relied on would be lost (a regression).
   So this block re-asserts the semantic colour on the COMBINED
   selectors below (0,3,0) — they win over the v4 `.tf-td--num`. The
   colours flow through the v4 cash-up / cash-down tokens so the
   forecast table's positive/negative semantics now match the v4
   palette exactly (the legacy `--green` / `--red` are themselves v4
   token bridges after I1). */
.tf-table .tf-td--num.text-green { color: var(--cash-up-text); }
.tf-table .tf-td--num.text-red   { color: var(--cash-down-text); }

/* ── end I4 v4 forecast composition ─────────────────────────────── */


/* ═══════════════════════════════════════════════════════════════════
   I5 · v4 "LEDGER" SECONDARY-TAB COMPOSITION  (dashboard-migration I5)
   ───────────────────────────────────────────────────────────────────
   Migrates the five remaining secondary tabs onto the v4 design system:
   Reports (`#tab-reports`, 7 sub-panels), What-If (`#tab-scenarios`),
   Close (`#tab-close`), QuickBooks (`#tab-quickbooks`), Xero
   (`#tab-xero`). Plan: dashboard-migration-plan-2026-05.md §I5.

   THE CASCADE — same mechanism as the I2/I3/I4 blocks above. I5
   migrated the secondary tabs ADDITIVELY: every migrated element carried
   a legacy class AND its v4 twin, and this block / the inherited I3
   rules won via COMBINED selectors. I8.4 then tore out the
   `.data-section` / `.kpi-card` legacy layer globally; I8.5 tore out the
   `.pnl-card` (base) + `.reports-subnav-btn` + `.input` / `.ai-input` /
   `.category-select` legacy layer. The P&L-specific rules are now scoped
   via the kept `#books-pnl-grid` container and the reports-button rules
   via the kept `.reports-subnav` track (see the inline notes below).
   Every value flows through a v4 token — no raw hex (plan §I1).

   WHAT IS DELIBERATELY LEFT ALONE — INHERITED FOR FREE.
   · The `.tf-card` panel surface is composed by the I3 block above (a
     global, un-scoped rule). Every `.tf-card` panel on the Reports /
     What-If / Close / QBO / Xero tabs — the 7 Reports sub-panels, the 3
     What-If panels, the 2 Close panels, the 5 QBO sections, the 6 Xero
     sections — inherits it with ZERO new CSS. Documented so a reader
     does not "fix" a correctly-absent rule.
   · The `.tf-kpi` tile is composed by the I3 block (global). The QBO
     3-tile AR/AP/Net row (`#qbo-kpi-row`) and the Xero 3-tile row
     (`#xero-kpi-row`) carry `.tf-kpi` — identical to the Cash Position
     KPIs — so they inherit the v4 tile, the `--hero`/state maps, and the
     overflow guard for free. Only the P&L summary cards need a NEW rule
     (below) because they are `.pnl-card`, NOT `.kpi-card`.
   · The Reports summary cards `#accuracy-summary-card` /
     `#fees-summary-card` are hand-styled coloured alert cards (inline
     green / red gradient + coloured hairline) — like the I3 demo banner
     and the I4 payroll-detect banner, they are intentional coloured
     callouts, not generic panels. Nothing to add a `tf-` class
     ALONGSIDE (they are inline-styled, not class-hooked) — left as-is.
   · The What-If preset pills (`.scenario-presets .ai-pill`) and the AI
     suggested-question pills keep their `.ai-pill` class — the JS reads
     `.scenario-presets .ai-pill` (applyPreset highlight, ~L7212/7219).
     `.ai-pill` is NOT renamed (plan §4). The pill restyle is not in I5
     scope; the tab's v4 win is the `.tf-card` panel they sit in.
   · The Close progress bar, the checklist rows, the month/year
     `<select>`s, and the QBO/Xero connect/sync/disconnect buttons are
     inline-styled or `.btn`-classed elements — left as-is; the v4
     surface they sit ON (the `.tf-card` panel) is what changes.
   ═══════════════════════════════════════════════════════════════════ */

/* ── REPORTS SUB-NAV — the v4 `.tf-segmented` ───────────────────────
   The Reports inner sub-nav (the `.reports-subnav` track + 7 buttons) is
   a v4 segmented control. UNLIKE the I4 forecast toggle, the track and
   buttons carry INLINE `style="..."` AND `switchReport()` writes
   `btn.style.background` / `btn.style.color` per click. Inline styles
   beat any CSS rule, so the per-button background / active-colour stay
   JS-owned — fine, the inline values are v4 token-grade. These rules
   supply ONLY what the inline styles do NOT pin:

   1. `.reports-subnav` is a KEPT legacy class — §11 keeps it as the
      track hook because it carries reports-specific mobile-scroll rules
      (`overflow-x:auto !important`, ~L1289/L1376) with no v4 twin. The
      track element is `class="reports-subnav tf-segmented"`; this
      `.reports-subnav.tf-segmented` rule adds the v4 hairline border the
      inline `style` omits (the zero-risk visual win — the track gains a
      defined edge).
   2. The components.css base `.tf-segmented__option` (0,1,0) sets
      `height: 28px` — the reports buttons have an inline `padding:7px
      18px` but NO height, so 28px would newly pin the box. I8.5 tore the
      legacy `.reports-subnav-btn` class off the buttons; the `height:
      auto` re-assertion is therefore SCOPED via the kept track —
      `.reports-subnav .tf-segmented__option` (0,2,0) — NOT a bare
      `.tf-segmented__option` (which would also reset the FORECAST
      toggle's 28px). The descendant selector matches reports buttons
      ONLY; the forecast toggle keeps its 28px.
   3. The components.css `.tf-segmented__option` `transition` is scoped
      to background/colour/box-shadow; the legacy inline `transition:all`
      stays (inline wins) — no conflict, documented. */
.reports-subnav.tf-segmented {
  border: var(--border-width) solid var(--border-subtle);
  display: flex;
  gap: 2px;
  padding: 4px;
  background: var(--surface-sunken);
  border-radius: var(--radius-sm);
  margin-bottom: 20px;
  width: fit-content;
}
.reports-subnav .tf-segmented__option {
  height: auto;            /* inline padding:7px 18px drives height — pre-I5 geometry */
  padding: 7px 18px;
  border-radius: var(--radius-xs);
  border: none;
  cursor: pointer;
  font-size: var(--text-sm);
  font-weight: var(--weight-semibold);
  background: transparent;
  color: var(--text-secondary);
  transition: all 0.15s;
  font-family: inherit;
}
.reports-subnav .tf-segmented__option:hover {
  color: var(--text-primary);
}
.reports-subnav .tf-segmented__option.active {
  background: var(--surface-raised);
  color: var(--text-primary);
  box-shadow: var(--shadow-raised);
}
.reports-subnav .tf-segmented__option:focus-visible {
  outline: 2px solid var(--focus-ring);
  outline-offset: 1px;
}

/* ── P&L SUMMARY CARDS — the v4 `.tf-kpi` tile ──────────────────────
   The 5 Profit & Loss summary cards (Revenue / Direct Costs / Profit
   After Costs / OpEx / Net Income) carry `.tf-kpi`. The I3 global
   `.tf-kpi` rule (0,1,0) reaches them, but the P&L strip needs a
   `display:block`-centred treatment that differs from the v4 flex tile.
   I8.5 tore out the legacy `.pnl-card` base class; the P&L-specific
   rules are therefore SCOPED via the kept `#books-pnl-grid` container
   (`.books-pnl-grid .tf-kpi`, 0,2,0) — NOT a bare `.tf-kpi` (which would
   restyle every Cash-Position KPI tile). The descendant selector beats
   the I3 global `.tf-kpi` (0,1,0) and re-states the v4 tile — flat,
   border-defined, no glass blur, no hover lift.

   The P&L cards are `text-align:center` BY DESIGN (a 5-up summary
   strip) — preserved; this is a SURFACE migration, not a layout change.
   The v4 `.tf-kpi` is `display:flex;flex-direction:column`; this rule
   re-asserts `display:block` so the centred strip is not broken —
   `flex-direction`/`gap` leaking from the 0,1,0 `.tf-kpi` rule are inert
   on a `display:block` box. */
.books-pnl-grid .tf-kpi {
  background: var(--surface-raised);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-md);
  padding: var(--space-6);
  display: block;                    /* keep the centred 5-up strip */
  text-align: center;
  -webkit-backdrop-filter: none;     /* drop the legacy glass blur */
  backdrop-filter: none;
  box-shadow: none;                  /* v4 tiles are border-defined */
  transition: border-color var(--duration-fast, .15s) var(--ease-standard, ease);
}
/* The v4 read view is quiet — no hover lift / glow. */
.books-pnl-grid .tf-kpi:hover {
  transform: none;
  box-shadow: none;
  border-color: var(--border-strong);
}
/* Label — v4 uppercase micro-overline, scoped via `#books-pnl-grid`
   (0,2,0) so it beats the I3 global `.tf-kpi__label` (0,1,0). */
.books-pnl-grid .tf-kpi__label {
  font-size: var(--text-xs);
  font-weight: var(--weight-semibold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-wide);
  color: var(--text-quiet);
  /* NOT flex — the P&L label is a plain centred line; keep it so. */
  display: block;
}
/* Value — v4 mono tabular figure, scoped via `#books-pnl-grid` (0,2,0).
   THE COLOUR-CLASS CASCADE: the value colour is owned by the
   `.pnl-card--revenue/--expense/--profit/--loss` MODIFIER rules below
   (also 0,2,0; they follow this rule, so they win on source order — the
   green/red/teal P&L semantics survive over this rule's `--text-primary`
   default). */
.books-pnl-grid .tf-kpi__value {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums lining-nums;
  font-size: var(--num-sm);
  font-weight: var(--weight-semibold);
  letter-spacing: -0.02em;
  color: var(--text-primary);
  line-height: 1.15;
  margin-top: var(--space-2);
}
/* Re-assert the P&L semantic value colours. `.pnl-card--<x>` is a KEPT
   MODIFIER class (I8.5 tore out only the `.pnl-card` base). These
   `.pnl-card--<x> .tf-kpi__value` selectors (0,2,0) follow the
   `.books-pnl-grid .tf-kpi__value` rule above (also 0,2,0) — so they win
   on source order. Colours route to the v4 TEXT-GRADE cash tokens —
   `--cash-up-text` (#15803D), `--cash-down-text` (#B91C1C),
   `--accent-strong` (#0A4D47) — NOT the base `--cash-up` / `--cash-down`
   / `--accent`: the text-grade tokens carry the 4.6:1+ on-soft / 4.8:1+
   on-white ratios a P&L figure needs to be legible as text. */
.pnl-card--revenue .tf-kpi__value { color: var(--cash-up-text); }
.pnl-card--expense .tf-kpi__value { color: var(--cash-down-text); }
.pnl-card--profit  .tf-kpi__value { color: var(--accent-strong); }
.pnl-card--loss    .tf-kpi__value { color: var(--cash-down-text); }
/* The `#pnl-gross-card` / `#pnl-net-card` value colour is set by JS
   (loadPnL rewrites the card className to `tf-kpi pnl-card--profit|
   --loss` by sign — see portal.html ~L11238; I8.5 dropped the torn-out
   `pnl-card` base from that write, keeping `tf-kpi` + the modifier) —
   the modifier rules above already cover both, no extra rule needed. */
/* Mobile guard. The P&L grid stays a 2-up grid at ≤640px
   (`.books-pnl-grid{grid-template-columns:repeat(2,1fr)}`) — UNLIKE the
   Cash Position KPI strip, which stacks 1-up. So the P&L cards are
   tightened for the narrow 2-up cell: `padding:12px`, value
   `font-size:18px`. These `.books-pnl-grid .tf-kpi` rules (0,2,0)
   follow the desktop `.books-pnl-grid .tf-kpi` rules above (also 0,2,0)
   AND sit inside the `@media`, so they win on source order inside
   ≤640px — without them the v4 `--space-6` (24px) padding + `--num-sm`
   (20px) would leave a cramped 2-up cell. (I3's `.tf-kpi` takes v4
   `--space-6` on mobile because that strip is 1-up — different context,
   hence the different call here.) */
@media (max-width: 640px) {
  .books-pnl-grid .tf-kpi { padding: 12px; }
  .books-pnl-grid .tf-kpi__value { font-size: 18px; }
}

/* ── end I5 v4 secondary-tab composition ────────────────────────── */


/* ═══════════════════════════════════════════════════════════════════
   I6 · v4 "LEDGER" SETTINGS + BANK-CONNECT COMPOSITION  (dashboard-migration I6)
   ───────────────────────────────────────────────────────────────────
   Migrates the Settings tab (`#tab-settings` — Account, Connected Banks,
   Reporting Groups, Scheduled Outflows, Billing, Cash Health Score,
   Invite Accountant, Referral, Morning Cash Digest) and the bank-connect
   surface (the pre-Plaid trust modal `#bank-trust-modal`) onto the v4
   design system. Plan: dashboard-migration-plan-2026-05.md §I6, parity
   inventory §13 + §14.1.

   BANKING-GRADE — PLAID JS UNTOUCHED. The three `Plaid.create()` call
   sites (portal.html ~L4536 OAuth-callback resume, ~L6882
   `_launchPlaidConnect`, ~L6999 `relinkBank`) and ALL connect / reconnect
   / Plaid-token JavaScript are BYTE-IDENTICAL to pre-I6. I6 migrated
   ONLY the surrounding markup + CSS. The trust modal's JS
   (`showBankTrustModal`, ~L6814) reads only the ids `bank-trust-modal` /
   `bank-trust-modal-close` / `bank-trust-modal-continue` and
   `modal.querySelectorAll('details')` — all kept; the `<details>` /
   `<summary>` tags are preserved. Every trust claim in the can/cannot
   card is verbatim-preserved.

   THE CASCADE — same mechanism as the I2-I5 blocks above. The migrated
   markup carries BOTH classes on every migrated element (additive, plan
   §3/§4). Load order is design-tokens → components → portal.css, so on
   EQUAL specificity portal.css wins; the combined-selector rules below
   re-state v4 values at (0,2,0) so they deterministically beat BOTH the
   legacy portal.css rules AND the components.css base. Every value flows
   through a v4 token — no raw hex (plan §I1 discipline).

   WHAT IS DELIBERATELY LEFT ALONE — INHERITED FOR FREE / NOT A FIT.
   · The 8 Settings panels carry `.tf-card` — the SAME class the I3 / I4
     / I5 panels use (I8.4 tore the legacy `.data-section` layer out
     globally). The v4 card surface is composed by the global `.tf-card`
     rule in the I3 block above — all 8 inherit it with ZERO new CSS,
     documented so a reader does not "fix" a correctly-absent rule.
   · The pre-Plaid trust modal's can/cannot grid is restyled to the v4
     `.tf-permission-list` component (promoted into components.css this
     increment). The component is self-sufficient — it needs no portal.css
     rule. The conflicting inline grid/card/list styles on that markup
     were dropped (inline `style` is not a class / id / handler / JS, so
     dropping it does not violate the additive rule — the classes, ids,
     handlers and the trust copy are all kept) so the component renders.
   · The Morning Cash Digest toggle is a NATIVE `<input type=checkbox>`
     (inline 20x20 + `accent-color`). The v4 `.tf-checkbox` hides the
     native input and renders a `.tf-checkbox__box` span — this control
     has no such span — so it is left as the native control. The v4
     surface it sits ON (`.tf-card`) is the win.
   · The sub-overlay tier rows (`#sub-tier-solo` / `-pro` / `-cfo`,
     `.sub-tier-row`) are JS-state-owned: `renderSubOverlayTiers()`
     (portal.html ~L3333) writes `el.style.borderColor` / `.background` /
     `.boxShadow` per click. Inline styles beat any CSS rule, so a `tf-`
     class would be inert AND could fight the JS — left as-is, exactly
     as I5 left the What-If preset pills. (§14.2 is a parity row — leaving
     it untouched LOSES nothing.)
   · The reporting-group colour swatches (`.color-swatch`) are likewise
     JS-state-owned (`selectGroupColor` / `openGroupModal` write
     `btn.style.border` / `.transform`) — untouched.
   · The Edit Bank modal inputs (`#edit-bank-name-input`,
     `#edit-bank-role-select`) are inline-styled with NO class hook — an
     additive class migration has nothing to add a class ALONGSIDE that
     would render; the inline `style` fully pins them. Left as-is.
   ═══════════════════════════════════════════════════════════════════ */

/* ── SETTINGS + MODAL FORM INPUTS — the v4 `.tf-input` / `.tf-select` ──
   The Invite-Accountant inputs, the group-name input, and the
   Scheduled-Outflow modal inputs carry the v4 `tf-input` / `tf-select`.
   I6 added these alongside the legacy `.input` / `.ai-input` /
   `.category-select`; I8.5 tore those legacy classes off the MIGRATED
   inputs (the Cash-tab search + AI-tab input keep their legacy class —
   they were never migrated — see the kept legacy `.ai-input, .input` /
   `.category-select` base rules ~L1165 / ~L2693). These `.tf-input` /
   `.tf-select` rules (0,1,0) win over the components.css base on source
   order — this block is later.

   The migrated inputs all carry an inline `style="width:..."` (and the
   outflow selects an inline `padding` / `font-size`). Those inline
   values beat any CSS rule, so these rules supply ONLY the v4 SURFACE
   the inline styles do NOT pin — border, radius, background, focus ring,
   font-family — and DELIBERATELY do not set `padding` / `font-size` /
   `height`, so each input keeps its existing geometry byte-identical to
   pre-I6. This is a surface migration, not a resize. */
.tf-input {
  background: var(--surface-page);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-xs);
  color: var(--text-primary);
  font-family: var(--font-sans);
  transition: border-color var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-fast) var(--ease-standard);
}
.tf-input::placeholder { color: var(--text-quiet); }
.tf-input:hover { border-color: var(--border-strong); }
.tf-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--shadow-focus);
}
/* Select — v4 field surface + the native chevron affordance. The
   outflow selects have an inline `padding:10px 12px`; the chevron's
   trailing room is added via `padding-right` (a separate longhand the
   inline `padding` shorthand resets, so re-state it here) so the
   chevron never overlaps the value. */
.tf-select {
  appearance: none;
  -webkit-appearance: none;
  background-color: var(--surface-page);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12' fill='none'%3E%3Cpath d='M3 4.5L6 7.5L9 4.5' stroke='%235F5F58' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-3) center;
  padding-right: var(--space-8);
  border: var(--border-width) solid var(--border-default);
  border-radius: var(--radius-xs);
  color: var(--text-primary);
  font-family: var(--font-sans);
  transition: border-color var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-fast) var(--ease-standard);
}
.tf-select:hover { border-color: var(--border-strong); }
.tf-select:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: var(--shadow-focus);
}

/* ── end I6 v4 settings + bank-connect composition ──────────────── */

/* ═══════════════════════════════════════════════════════════════════
   I7 · v4 "LEDGER" — MOBILE NAV / FAB / MODALS COMPOSITION  (I7 + I8.6)
   ───────────────────────────────────────────────────────────────────
   Composes the last chrome — the mobile bottom nav, the floating action
   buttons, the toast surface — on the v4 design system, and adds a
   mobile off-canvas sidebar drawer. Plan:
   dashboard-migration-plan-2026-05.md §I7 + §11 (I8.6 teardown).

   HISTORY — THE CASCADE.
   I7 migrated this chrome ADDITIVELY: every element carried a legacy
   class AND its v4 twin (`.mobile-bottom-nav tf-bottom-nav`,
   `.mobile-bottom-nav__item tf-bottom-nav__item`, `.feedback-fab tf-fab`,
   `.toast tf-toast`, `.toast-rack tf-toast-rack`), and this block won via
   COMBINED `.legacy.tf-x` selectors. I8.6 — the FINAL I8 increment —
   tore the legacy layer out: the legacy mobile-nav / FAB / toast rules
   earlier in this file were RENAMED onto the v4 class
   (`.mobile-bottom-nav {}` → `.tf-bottom-nav {}`, `.toast {}` →
   `.tf-toast {}`, etc.), the combined selectors here collapsed to
   standalone `.tf-*`, and the legacy classes were dropped from the
   markup. Every element ends up matching the identical rule set, at
   identical specificity, in identical source order — computed style is
   byte-identical. Every value flows through a v4 token (plan §I1).
   (`mobile-bottom-nav__items` — the inner flex container — and the
   retired `ai-fab` are KEPT classes: no combined selector / no v4 twin
   rule; they are out of §11's I8.6 scope.)

   THE v4 COMPONENT LIBRARY (components.css) defines `.tf-toast` (a
   dark FLEX panel with `__icon`/`__body` children) and `.tf-toast-rack`.
   The portal's toast() builds a flat-text pill — no such children — so
   the renamed-legacy `.tf-toast` pill rule + this block's `.tf-toast`
   rules (both later in source than components.css) win and keep the
   proven pill shape. components.css's `display:flex` still applies (no
   portal `.tf-toast` rule sets `display`) — harmless on a single-text
   pill, exactly as pre-I8.6.

   Z-INDEX NOTE. The portal's legacy mobile z-stack is inflated far
   above the v4 --z-* tokens (bottom-nav z-index:900, toast-rack:10001,
   demo-sticky-footer:9998). The v4 --z-overlay/--z-modal tokens
   (200/210) sit BELOW 900 — so the mobile drawer + scrim use explicit
   portal-scoped z-index values chosen to beat the bottom nav (901/902).
   A portal-composition concern, documented rather than token-forced.
   ─────────────────────────────────────────────────────────────────── */

/* ── 1 · MOBILE SIDEBAR DRAWER (≤1023px) ─────────────────────────────
   The renamed-legacy CSS (~L1525) hides `.tf-sidebar` entirely at
   ≤1023px and `.tf-bottom-nav` is the mobile nav. I7 ADDS an
   off-canvas drawer: the SAME `.tf-sidebar` element slides in from the
   left on a `.tf-nav-drawer-toggle` tap (JS `toggleSidebarDrawer()`
   toggles the `is-open` class — the v4 components.css off-canvas
   convention). The bottom nav is untouched; this only governs the
   ≤1023px drawer state. Desktop ≥1024px: the drawer rules do not apply.
   (I8.3 note: pre-I8.3 these drawer selectors were combined
   `.app-sidebar.tf-sidebar`; I8.3 tore the legacy `.app-sidebar` half
   out — they are now standalone `.tf-sidebar` and win on source order.)
   ── */
@media (max-width: 1023px) {
  /* The drawer panel. The renamed-legacy `.tf-sidebar{display:none
     !important}` at ~L1525 must be OVERRIDDEN so the off-canvas panel
     can exist. Both rules are `.tf-sidebar` (0,1,0) and both `!important`
     — this block is LATER, so `display:flex !important` wins on source
     order. The panel is positioned off-screen (translateX -100%) and
     slides in when JS adds `is-open`. */
  .tf-sidebar {
    display: flex !important;
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: min(86vw, var(--sidebar-w));
    z-index: 902;                 /* above the legacy bottom nav (900) */
    transform: translateX(-100%);
    transition: transform var(--duration-slow) var(--ease-out);
    overflow-y: auto;
    box-shadow: var(--shadow-modal);
    -webkit-overflow-scrolling: touch;
  }
  .tf-sidebar.is-open { transform: translateX(0); }
  /* The collapse toggle is a desktop-≤1280px affordance; it has no role
     inside the mobile drawer (the drawer is full-width-ish, not an icon
     rail) — hide it so it does not overlap the drawer's nav items. */
  .tf-sidebar .app-sidebar__toggle { display: none; }

  /* ── COLLAPSE-MODE NEUTRALIZER ───────────────────────────────────────
     `_initSidebarCollapsedState()` default-collapses the sidebar at
     ≤1280px and persists `sidebar--collapsed` to localStorage. The
     mobile drawer opens that SAME `.tf-sidebar` element — so without
     this block the drawer would render the 44px ICON-RAIL form with the
     nav-item LABELS hidden (the renamed-legacy collapse rules at
     ~L1619-1687 apply at ≤1280px, which includes the ≤1023px drawer
     range). A 44px-wide drawer of unlabelled icons is unusable. The
     collapse state has NO meaning in an off-canvas drawer — it is a
     desktop icon-rail feature. These overrides are specificity 0,3,0
     (`.tf-sidebar` + `.is-open` + `.sidebar--collapsed`) so they beat
     the collapse rules (0,2,0) — restoring the FULL expanded drawer
     regardless of the persisted collapse preference. The collapse system
     is untouched at desktop widths. ── */
  .tf-sidebar.is-open.sidebar--collapsed {
    width: min(86vw, var(--sidebar-w));
    padding: 20px 14px;
  }
  .tf-sidebar.is-open.sidebar--collapsed .app-sidebar__brand span,
  .tf-sidebar.is-open.sidebar--collapsed .app-sidebar__section-label,
  .tf-sidebar.is-open.sidebar--collapsed .tf-nav-item > span,
  .tf-sidebar.is-open.sidebar--collapsed .snav-acting-chip__label {
    display: inline !important;
  }
  /* Role-gated section labels must survive the drawer-open reveal above.
     That rule re-shows EVERY .app-sidebar__section-label with
     display:inline !important so the collapsed rail expands when the mobile
     drawer opens — but _applyBookkeeperShellMode() hides the role-irrelevant
     sections via inline display:none (company mode has no Clients/Firm items;
     firm mode has no Cash/Accounting items). Without this guard those labels
     reappear as empty, orphaned headers on mobile. data-mode on .tf-app is the
     canonical firm/company signal; these selectors out-specify the reveal. */
  .tf-app[data-mode="company"] .tf-sidebar #snav-firm-tools-label,
  .tf-app[data-mode="company"] .tf-sidebar .snav-firm-admin-label,
  .tf-app[data-mode="firm"] .tf-sidebar #snav-cash-label,
  .tf-app[data-mode="firm"] .tf-sidebar #snav-accounting-label {
    display: none !important;
  }
  .tf-sidebar.is-open.sidebar--collapsed .snav-trust-badge {
    display: block !important;
  }
  .tf-sidebar.is-open.sidebar--collapsed .app-sidebar__brand {
    justify-content: flex-start;
    padding: 6px 10px;
    margin-bottom: 24px;
  }
  .tf-sidebar.is-open.sidebar--collapsed .tf-nav-item {
    justify-content: flex-start;
    padding: 9px 12px;
    font-size: 13px;          /* restore label text the collapse set to 0 */
    gap: 11px;
  }
  .tf-sidebar.is-open.sidebar--collapsed .tf-nav-item svg,
  .tf-sidebar.is-open.sidebar--collapsed .tf-nav-item i[data-lucide] {
    font-size: 13px;
    margin: 0;
  }
  /* The collapse-mode hover tooltip is pointless in an expanded drawer
     (the labels are visible) — suppress it so a touch-hold does not
     pop a redundant tooltip over the on-screen label. */
  .tf-sidebar.is-open.sidebar--collapsed .tf-nav-item[aria-label]:hover::after,
  .tf-sidebar.is-open.sidebar--collapsed .tf-nav-item[aria-label]:hover::before {
    display: none;
  }
  /* The drawer scrim — a tap-to-dismiss backdrop behind the panel. */
  .tf-sidebar-scrim {
    position: fixed;
    inset: 0;
    z-index: 901;                 /* above bottom nav (900), below panel (902) */
    background: var(--surface-overlay);
    -webkit-backdrop-filter: blur(2px);
    backdrop-filter: blur(2px);
    border: 0;
  }
  .tf-sidebar-scrim[hidden] { display: none; }
  /* The hamburger trigger — shown only at ≤1023px, sits left of the
     brand mark in the top bar. */
  .tf-nav-drawer-toggle {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 38px;
    height: 38px;
    margin-right: var(--space-2);
    padding: 0;
    background: transparent;
    border: var(--border-width) solid transparent;
    border-radius: var(--radius-sm);
    color: var(--text-secondary);
    cursor: pointer;
    flex-shrink: 0;
    -webkit-tap-highlight-color: transparent;
    transition: background-color var(--duration-fast) var(--ease-standard),
                color var(--duration-fast) var(--ease-standard);
  }
  .tf-nav-drawer-toggle:hover { background: var(--surface-hover); color: var(--text-primary); }
  .tf-nav-drawer-toggle:active { transform: scale(0.94); }
  .tf-nav-drawer-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
}
/* Desktop: the hamburger never shows — the full sidebar is always
   present at ≥1024px. The drawer scrim is likewise desktop-irrelevant. */
@media (min-width: 1024px) {
  .tf-nav-drawer-toggle { display: none; }
  .tf-sidebar-scrim { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .tf-sidebar { transition: none; }
}

/* ── 2 · MOBILE BOTTOM NAV → v4 "Ledger" surface ─────────────────────
   The bottom nav keeps its geometry (height, fixed position, safe-area
   inset — all proven by the 2026-05-17 mobile dogfood; that geometry is
   in the renamed-legacy `.tf-bottom-nav*` rules earlier in this file)
   and these rules carry the v4 surface treatment. `.tf-bottom-nav` /
   `.tf-bottom-nav__item` here (0,1,0) win over the renamed-legacy
   `.tf-bottom-nav*` rules (0,1,0) on source order — this block is
   later. Every colour flows through a v4 token. ── */
.tf-bottom-nav {
  background: var(--surface-page);
  border-top: var(--border-width) solid var(--border-default);
}
.tf-bottom-nav__item {
  color: var(--text-secondary);
  transition: color var(--duration-fast) var(--ease-standard);
}
/* Active state — `active` is a JS-owned state class (`mnavSetActive()`
   toggles it). Per plan §4 step 3 the v4 look is MAPPED onto it. This
   `.tf-bottom-nav__item.active` rule (0,2,0) and the renamed-legacy
   `.tf-bottom-nav__item.active` rule (0,2,0) are specificity-equal —
   this block is later, so it wins on source order. */
.tf-bottom-nav__item.active { color: var(--accent); }
.tf-bottom-nav__item:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -3px;
  border-radius: var(--radius-xs);
}
.tf-bottom-nav__label {
  font-weight: var(--weight-semibold);
}

/* ── 3 · FLOATING ACTION BUTTONS → v4 "Ledger" ───────────────────────
   The feedback FAB is the only visible FAB. The round `.ai-fab` is a
   KEPT class (§11 left it out of I8.6 scope) and is permanently
   `display:none !important` — the 2026-05-17 dogfood retired it; the
   `#ask-ai-fab` gradient pill is the live AI affordance. The feedback
   FAB's positioning rules (`bottom`/`right`, the mobile de-stack from
   PR #134) live in the renamed-legacy `.tf-fab` rules earlier in this
   file — UNTOUCHED. This `.tf-fab` rule adds only the v4 transition.
   `.tf-fab` also matches the `.ai-fab` element (it carries `tf-fab`),
   but `ai-fab`'s `display:none !important` makes every `.tf-fab`
   declaration inert on it — exactly as pre-I8.6. ── */
.tf-fab {
  transition: background-color var(--duration-fast) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-fast) var(--ease-standard);
}
.tf-fab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── 4 · TOAST SURFACE → v4 "Ledger" ─────────────────────────────────
   The portal's toast() builds `class="tf-toast <type>"` — a flat-text
   pill (I8.6 dropped the torn-out `toast` base from that className
   write). components.css's `.tf-toast` is a dark FLEX panel with
   `__icon`/`__body` children that the portal's child-less pill does not
   have; the renamed-legacy `.tf-toast` pill rule + these `.tf-toast`
   rules (both later in source than components.css) win and keep the
   proven pill geometry. The `toast-out` leaving class stays the
   JS-owned animation toggle. The semantic backgrounds
   (success/error/info) keep the translucent fills — they read correctly
   over any surface and the `backdrop-filter` blur is part of the look. ── */
.tf-toast-rack {
  /* rack geometry is fully owned by the renamed-legacy `.tf-toast-rack`
     rule (fixed, bottom-right, the mobile-dogfood-tuned bottom offsets)
     — nothing to re-state; this `display:flex` is a harmless no-op
     (the legacy rule already sets it). */
  display: flex;
}
.tf-toast {
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-popover);
  font-weight: var(--weight-semibold);
}
.tf-toast.success { background: rgba(22, 163, 74, 0.92); }
.tf-toast.error   { background: rgba(220, 38, 38, 0.92); }
.tf-toast.info    { background: var(--surface-ink); }

/* ── end I7 v4 mobile-nav / FAB / modals composition ─────────────── */

/* Firm roster per-row actions kebab (wires off-board / resend / cancel).
   Prod-audit 2026-06-02 P0 — removing/re-inviting a client had no UI. */
.bk-row-kebab {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--ink-400);
  font-size: 18px;
  line-height: 1;
  padding: 2px 7px;
  border-radius: 6px;
  vertical-align: middle;
  transition: background .12s ease, color .12s ease;
}
.bk-row-kebab:hover,
.bk-row-kebab:focus-visible {
  background: var(--ink-50);
  color: var(--ink-700);
  outline: none;
}

/* Manage-companies modal rows (prod audit 2026-06-02 #11 — rename/archive UI). */
.comgr__list { margin-top: 14px; display: flex; flex-direction: column; gap: 4px; max-height: 52vh; overflow-y: auto; }
.comgr__row { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border: 1px solid var(--border-default, #e5e7eb); border-radius: 10px; }
.comgr__name { font-weight: 600; font-size: 13.5px; color: var(--ink-900, #0f172a); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; flex: 0 1 auto; }
.comgr__tag { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; color: var(--ink-500, #64748b); background: var(--ink-50, #f1f5f9); border: 1px solid var(--border-default, #e5e7eb); border-radius: 6px; padding: 1px 5px; vertical-align: middle; }
.comgr__sub { font-size: 12px; color: var(--ink-500, #64748b); margin-left: auto; white-space: nowrap; font-variant-numeric: tabular-nums; }
.comgr__acts { display: flex; gap: 6px; flex: 0 0 auto; }
.comgr__btn { background: #fff; border: 1px solid var(--border-default, #e5e7eb); border-radius: 8px; padding: 5px 11px; font: inherit; font-size: 12.5px; font-weight: 600; color: var(--ink-700, #334155); cursor: pointer; transition: background .12s ease, border-color .12s ease; }
.comgr__btn:hover:not([disabled]) { background: var(--ink-50, #f1f5f9); border-color: var(--border-strong, #cbd5e1); }
.comgr__btn[disabled] { opacity: 0.45; cursor: not-allowed; }
.comgr__btn--danger { color: #b91c1c; border-color: #fca5a5; }

/* Firm client roster → stacked cards on phones (prod audit #13). Scoped to
   #bk-client-table so the shared .tf-table--drill (by-bank, vendors) is
   unaffected. Mirrors the proven .bk-team-table card pattern; the triage
   numbers (Cash/Net/Runway) carry an inline label so they're unambiguous
   without horizontal scroll. NOTE: verify on a real phone before relying on
   pixel polish — additive (≤640px only), cannot affect desktop.
   MOVE 2 (2026-06-04): updated for the rebuilt row — a leading severity-dot
   cell (data-label="") + a monogram-anchored Client cell. On a phone the dot
   floats top-left of the card; the Client cell (monogram + name + email) is the
   card header; the desktop 56px row height is reset so empty/short cells don't
   pad out the stacked card. */
@media (max-width: 640px) {
  #bk-client-table .tf-table--drill { min-width: 0; }
  #bk-client-table .tf-table--drill thead { display: none; }
  #bk-client-table .tf-table--drill,
  #bk-client-table .tf-table--drill tbody,
  #bk-client-table .tf-table--drill tr,
  #bk-client-table .tf-table--drill td { display: block; width: 100%; }
  /* Reset the desktop fixed row height — cards size to content on mobile. */
  #bk-client-table .tf-table--roster tbody td { height: auto; }
  #bk-client-table .tf-table--drill tr {
    position: relative;
    border: 1px solid var(--ink-150);
    border-radius: 12px;
    margin-bottom: 10px;
    padding: 12px 14px;
  }
  #bk-client-table .tf-table--drill td {
    padding: 5px 0;
    border: 0;
    text-align: left !important;
  }
  /* Leading STATUS cell renders as the first block of the card (above the
     client name) — natural reading order, no overlap with the monogram. The
     pill is self-labeling, so it needs no ::before. Healthy rows show just the
     quiet grey dot. */
  #bk-client-table .tf-table--roster td.bk-roster-status-cell {
    width: auto;
    padding: 0 0 4px;
    text-align: left !important;
  }
  #bk-client-table .tf-table--drill td[data-label="Client"] {
    padding-top: 0;
    padding-right: 40px; /* leave room for the absolutely-placed action cell */
  }
  /* NOTE: the desktop ellipsis trick (max-width:0; width:42%) is reset for
     mobile in the portal.html inline <style> — it must live there because that
     block loads AFTER portal.css and would otherwise override a reset placed
     here. Keep the reset co-located with the rule it counters. */
  #bk-client-table .tf-table--drill td[data-label="Sync"]::before,
  #bk-client-table .tf-table--drill td[data-label="Cash"]::before,
  #bk-client-table .tf-table--drill td[data-label="Net 7d"]::before,
  #bk-client-table .tf-table--drill td[data-label="Runway"]::before {
    content: attr(data-label);
    display: inline-block;
    min-width: 78px;
    font-size: 11px;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.4px;
    color: var(--ink-500);
  }
  #bk-client-table .tf-table--drill td:last-child {
    position: absolute;
    top: 10px;
    right: 10px;
    width: auto;
    padding: 0;
  }
}
.comgr__btn--danger:hover:not([disabled]) { background: #fef2f2; border-color: #f87171; }
