@layer pfa-app {
/* =====================================================================
   Personal Finance Analyser - liquid-glass surface layer  (glass.css)
   Loaded AFTER styles.css. Purely additive: it introduces NO new colour
   palette (every value derives from the existing config-driven tokens),
   removes nothing, and touches no print rule. It is a REPLACEABLE surface
   layer - strip this file and the app falls back to the solid styles.css
   surfaces unchanged.

   Design rules it enforces (from the frozen plan):
     - the effect lives in the SURFACES, never the content: a number is
       always plain, sharp and legible, sitting ON the glass, not made of it;
     - SOLID FALLBACK FIRST: the glass look is applied only where the browser
       supports backdrop-filter AND the person has not asked to reduce
       transparency; otherwise a solid panel is used;
     - the accent is the light source (specular top-edge highlight + focus
       glow), so the glass reads as tinted by the app's own colour;
     - holds in BOTH themes; contrast of text on glass is proven, not hoped;
     - never enters the print/report path (all rules are screen-scoped).
   ===================================================================== */

/* ---- stacking scale (one declared source, like the filter-facet registry
   pattern this app uses for state) ---------------------------------------
   Content lives at the browser default (auto / 0). Everything that floats
   ABOVE content is declared here, in ascending order, so a new overlay has
   an obvious slot and a content component can never accidentally out-stack a
   menu again (the Export-menu-behind-treemap bug). Any positioned content
   component - the treemap overlay included - must stay at or below --z-content. */
:root {
  --z-content: 0;
  --z-sticky-chrome: 30; /* the period bar / topbar stack */
  --z-nav: 40; /* the destination nav shell */
  --z-overlay-backdrop: 100;
  --z-menu: 110; /* export menu, pickers */
  --z-banner: 120; /* install / backup / first-run banners, to-top */
  --z-toast: 130; /* transient status, always on top */
}

/* ---- glass tokens (derived from the app's own colours) ----------------- */
:root {
  --glass-fill-answer: color-mix(in srgb, var(--panel) 58%, transparent);
  --glass-fill-chrome: color-mix(in srgb, var(--panel) 88%, transparent);
  --glass-fill-menu: color-mix(in srgb, var(--panel) 70%, transparent);
  --glass-fill-inner: color-mix(in srgb, var(--panel) 52%, transparent);
  --glass-edge: color-mix(in srgb, var(--fg) 10%, transparent);
  --glass-hi: color-mix(in srgb, #fff 55%, transparent);
  --glass-corner: color-mix(in srgb, var(--accent) 30%, transparent);
  --glass-drop: color-mix(in srgb, #0a2a52 40%, transparent);
  --orb: color-mix(in srgb, var(--accent) 15%, transparent);
}
[data-theme='dark'] {
  --glass-hi: color-mix(in srgb, #fff 16%, transparent);
  --glass-corner: color-mix(in srgb, var(--accent) 40%, transparent);
  --glass-drop: #000;
  --orb: color-mix(in srgb, var(--accent) 30%, transparent);
}
@media (prefers-color-scheme: dark) {
  [data-theme='auto'] {
    --glass-hi: color-mix(in srgb, #fff 16%, transparent);
    --glass-corner: color-mix(in srgb, var(--accent) 40%, transparent);
    --glass-drop: #000;
    --orb: color-mix(in srgb, var(--accent) 30%, transparent);
  }
}
/* Stacking floor for floating surfaces - unconditional, so it
   holds identically whether the glass enhancement is active or the reduced-
   transparency solid fallback is. A picker sits at --z-menu, banners
   above them, toasts above everything; every value is well clear of any
   content component's own stacking (the treemap overlay is isolated at
   z-index:0). position:relative is asserted only where a surface might not
   already establish one, so its z-index actually takes effect. */
.picker {
  position: absolute;
  z-index: var(--z-menu);
}
.offer,
.to-top {
  z-index: var(--z-banner);
}
.overlay {
  z-index: var(--z-overlay-backdrop);
}

/* ---- glass: the default wherever backdrop-filter exists; solid is the
   explicit case below (reduced transparency, more contrast, forced colours).
   Three strengths of one recipe. The Answer is the lightest because only
   the wash and its own orb sit behind it; the sticky chrome stays nearly
   opaque because saturated chart colour must not tint it as it scrolls
   underneath; menus and popovers are transient and sit between the two.
   A backdrop filter is never nested inside another. ---------------------- */
.glass-chrome {
  background: var(--bg);
}
.glass-menu {
  background: var(--panel);
}
@media screen {
  @supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .card[data-surface='lead'] {
      background:
        radial-gradient(120% 90% at 88% 0%, var(--orb), transparent 62%),
        var(--glass-fill-answer);
      -webkit-backdrop-filter: saturate(1.7) blur(24px);
      backdrop-filter: saturate(1.7) blur(24px);
      border: 1px solid var(--glass-edge);
      box-shadow:
        inset 0 1px 0 var(--glass-hi),
        inset 0 24px 44px -40px var(--glass-corner),
        0 22px 48px -30px var(--glass-drop);
    }
    .card[data-surface='lead']:has(.dh[data-tone='good']) {
      --orb: color-mix(in srgb, var(--good) 18%, transparent);
    }
    .card[data-surface='lead']:has(.dh[data-tone='watch']),
    .card[data-surface='lead']:has(.dh[data-tone='alert']) {
      --orb: color-mix(in srgb, var(--warn) 18%, transparent);
    }
    .glass-chrome {
      background: var(--glass-fill-chrome);
      -webkit-backdrop-filter: saturate(1.1) blur(28px);
      backdrop-filter: saturate(1.1) blur(28px);
    }
    @media (max-width: 1099px) {
      .glass-dock {
        background: color-mix(in srgb, var(--panel) 92%, transparent);
        -webkit-backdrop-filter: saturate(1.1) blur(28px);
        backdrop-filter: saturate(1.1) blur(28px);
        box-shadow: 0 -14px 32px -26px var(--glass-drop);
      }
    }
    .glass-menu {
      background: var(--glass-fill-menu);
      -webkit-backdrop-filter: saturate(1.3) blur(24px);
      backdrop-filter: saturate(1.3) blur(24px);
      border: 1px solid var(--glass-edge);
      box-shadow:
        inset 0 1px 0 var(--glass-hi),
        0 18px 40px -22px var(--glass-drop);
    }
  }
}
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more), (forced-colors: active) {
  .card[data-surface='lead'] {
    background:
      radial-gradient(120% 90% at 88% 0%, var(--orb), transparent 62%),
      var(--panel);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .glass-chrome,
  .glass-menu {
    background: var(--panel);
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  @media (max-width: 1099px) {
    .glass-dock {
      background: var(--panel);
      -webkit-backdrop-filter: none;
      backdrop-filter: none;
    }
  }
}

/* =====================================================================
   The shared CONTENT MODEL component: number -> tag -> dropdown.
   Every analysis view-model (available-now, committed-flexible,
   spend-breakdown, category-intentions, goals, position) emits this shape.
   The number is the largest, plainest thing; the tag is a few pronoun-free
   words; the fuller reasoning hides in a native <details> dropdown.
   These are CONTENT, so they are always solid, sharp and legible - never
   glassy. Reuses existing tokens and the .attn-dot tone convention.
   ===================================================================== */
.vm {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}
.vm-lead {
  display: flex;
  flex-direction: column;
  gap: 2px;
}
/* the number: the largest thing on the card, never behind a click, never
   translucent - it sits ON the glass, it is not made of it. */
/* Base lead-figure size for every ANALYSIS card (Activity, Position, Forecast).
   Tuned to 27px to sit in the same comfortable number-to-label-to-space
   proportion Overview's hero reads at, rather than dominating its card - a
   32px figure felt heavy and, with several cards on a screen (and two cards
   paired side by side at >=1000px), read as a wall of competing numbers. The
   ONE hero figure keeps .lg (42px) below; dense mode overrides via --vm-number.
   Layout is unaffected: no grid (.vm-pair / .dash-grid / .accounts-grid) keys
   off this figure's height, so side-by-side pairing is untouched. */
.vm-number {
  font-size: 27px;
  font-weight: 780;
  letter-spacing: -0.3px;
  line-height: 1.1;
  color: var(--fg);
  font-variant-numeric: tabular-nums;
}
/* FIGURE-SIZE STANDARD: 42px (.lg) is reserved for a screen's single HERO
   figure - and the only hero card in this app is Available-now (.card.hero on
   Overview). Every other card is a plain .card in a scrolling analysis set and
   uses the base 32px above, so a screen never shows several 42px figures each
   louder than the app's real hero. Do not add .lg to a non-hero card; if a new
   true hero is introduced, give its card .card.hero and use .lg there only. */
.vm-number.lg {
  font-size: 42px;
}
.vm-label {
  font-size: 13px;
  color: var(--dim);
}
/* the tag: a few words closing the number against a reference point. Flat
   pill, pronoun-free by construction (the view-models guarantee it). Tone
   maps to the SAME dot convention styles.css already uses. */
.vm-tag {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  align-self: flex-start;
  padding: 3px 10px;
  border-radius: var(--radius-pill);
  font-size: 12.5px;
  font-weight: 650;
  background: var(--panel-2);
  color: var(--fg);
  border: 1px solid var(--edge);
}
.vm-tag::before {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 50%;
  flex: none;
  background: var(--dim);
}
/* A wrapping ROW of tags. .vm is flex-direction:column, so .vm-tag children
   appended straight to it stack vertically; .vm-tags makes them a horizontal
   set of pills that wraps to a second line only when it runs out of width. */
.vm-tags {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}
/* Tone is carried by the DOT only; the tag TEXT stays plain --fg. This mirrors
   the app's own .ktag rule ("the colour is carried by the dot; the text always
   names the state"), so tone never relies on colour alone AND the text keeps
   the proven --fg-on-panel contrast in both themes. Tinting the text toward the
   light-only --good/--warn tokens failed dark-mode AA (3.7-3.9:1); this avoids
   that entirely while staying within the existing colour vocabulary. */
.vm-tag.tone-good::before {
  background: var(--good);
}
.vm-tag.tone-watch::before {
  background: var(--warn);
}
.vm-tag.tone-neutral::before {
  background: var(--dim);
}
/* dark mode: use the theme-aware green/warm for the DOT so it stays vivid on a
   dark surface (the light --good/--warn read muddy there). Dot only - not text. */
[data-theme='dark'] .vm-tag.tone-good::before {
  background: var(--down);
}
[data-theme='dark'] .vm-tag.tone-watch::before {
  background: var(--up);
}
@media (prefers-color-scheme: dark) {
  [data-theme='auto'] .vm-tag.tone-good::before {
    background: var(--down);
  }
  [data-theme='auto'] .vm-tag.tone-watch::before {
    background: var(--up);
  }
}

/* (The reduced-motion override that used to sit here died with the private
   chevron: .disclosure > summary::after carries its own, in premium.css.) */

/* the mandatory reconciling / supporting line (committed-vs-flexible, the
   goal guard verdict) - always attached beneath its figure, never a separate
   dismissible element. Quiet, but present. */
.vm-reconcile {
  margin-top: var(--s2);
  padding: var(--s2) var(--s3);
  border-radius: 10px;
  font-size: 12.5px;
  line-height: 1.45;
  background: color-mix(in srgb, var(--panel-2) 70%, transparent);
  border-left: 3px solid var(--dim);
}
.vm-reconcile.tone-watch {
  border-left-color: var(--warn);
  background: color-mix(in srgb, var(--warn) 7%, var(--panel-2));
}
.vm-reconcile.tone-good {
  border-left-color: var(--good);
}

/* =====================================================================
   Four-destination navigation shell (Overview / Activity / Forecast /
   Position). Desktop: a compact glass sidebar. Phone: a glass bottom bar.
   Built on the existing .ledger-switch / .ledger-tab primitives so it
   inherits their proven touch sizing and active states; glass.css only
   adds the four-item layout and the frosted surface.
   ===================================================================== */
.dest-nav {
  display: flex;
  gap: 4px;
}
.dest-tab {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  border: 0;
  background: transparent;
  color: var(--dim);
  font-weight: 650;
  font-size: 13.5px;
  padding: 9px 16px;
  border-radius: var(--radius-pill);
  cursor: pointer;
  min-height: var(--tap-min);
}
.dest-tab:hover {
  color: var(--fg);
}
.dest-tab.active {
  background: var(--panel);
  color: var(--fg);
  box-shadow: var(--shadow);
}
.dest-tab:focus-visible,
.ledger-tab:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
.dest-tab svg {
  width: 18px;
  height: 18px;
}

@media (min-width: 960px) {
  /* desktop: vertical glass sidebar */
  .dest-nav.dest-sidebar {
    flex-direction: column;
    gap: 3px;
    position: sticky;
    top: 0;
    align-self: flex-start;
    padding: 14px 10px;
    min-width: 200px;
    height: 100vh;
    border-right: 1px solid var(--glass-edge, var(--edge));
  }
  .dest-sidebar .dest-tab {
    justify-content: flex-start;
    width: 100%;
    padding: 11px 12px;
    border-radius: 11px;
  }
  .dest-sidebar .dest-tab.active {
    background: color-mix(in srgb, var(--accent) 12%, var(--panel-2));
    color: var(--accent-dark);
    box-shadow: none;
  }
}
@media (max-width: 959px) {
  /* phone/tablet: fixed glass bottom bar, thumb reach */
  .dest-nav.dest-bottom {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 40;
    display: flex;
    gap: 4px;
    padding: 6px calc(8px + env(safe-area-inset-right)) calc(6px + env(safe-area-inset-bottom))
      calc(8px + env(safe-area-inset-left));
    border-top: 1px solid var(--glass-edge, var(--edge));
  }
  .dest-bottom .dest-tab {
    flex: 1 1 0;
    flex-direction: column;
    gap: 3px;
    min-height: 48px;
    border-radius: 12px;
    font-size: 10.5px;
  }
  body.has-dest-nav {
    padding-bottom: calc(72px + env(safe-area-inset-bottom));
  }
}

/* =====================================================================
   Paired-panel layout: two symmetric, related cards side by side on wide
   screens; a lone card (no partner this period) takes the full row rather
   than sitting beside an empty space. Prose inside a card is width-capped
   (the REAL fix the research supported - reading measure, not card width),
   while numbers and tags are exempt so figures never wrap oddly.
   ===================================================================== */
@media (min-width: 1000px) {
  .vm-pair {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: var(--card-gap);
    align-items: start;
  }
  .vm-pair.solo {
    grid-template-columns: 1fr;
  }
  /* A genuine N-column row, not a hardcoded 2-up or 3-up grid - used where
     several peer figures (Position's cash on hand / owed on card / income
     stability) should read as equal columns regardless of how many exist.
     auto-fit means: one card stretches full width (.solo makes that
     explicit rather than relying on auto-fit's own behaviour), two split
     the row evenly, three sit evenly across it, and a future fourth wraps
     onto a second line rather than the rule silently breaking. Below this
     breakpoint every item stacks fully in normal document order - the same
     mobile fallback .vm-pair already uses, so nothing about small-screen
     behaviour changes. */
  .vm-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap: var(--card-gap);
    align-items: start;
  }
  .vm-row.solo {
    grid-template-columns: 1fr;
  }
}

.scenario-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(130px, auto) auto;
  align-items: center;
  gap: var(--s3);
}
.scenario-row .recurring-amt {
  text-align: right;
}

@media (max-width: 639px) {
  .scenario-row {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas:
      'name amount'
      'choices choices';
  }
  .scenario-row .recurring-name {
    grid-area: name;
    min-width: 0;
    word-break: normal;
  }
  .scenario-row .recurring-amt {
    grid-area: amount;
  }
  .scenario-row .seg {
    grid-area: choices;
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    width: 100%;
  }
}

/* Grouped segmented control (Keep / Cut half / Cut all) - three connected
   buttons in one bordered container, so they read as a single choice, in the
   same grouped-segmented language as the account slicer but tighter for an
   inline row. */
.seg {
  display: inline-flex;
  border: 1px solid var(--edge);
  border-radius: 10px;
  overflow: hidden;
  flex: none;
}
.seg-btn {
  border: 0;
  background: transparent;
  color: var(--dim);
  font-size: 12.5px;
  font-weight: 600;
  padding: 6px 10px;
  cursor: pointer;
  min-height: 32px;
  white-space: nowrap;
}
.seg-btn + .seg-btn {
  border-left: 1px solid var(--edge);
}
.seg-btn:hover {
  color: var(--fg);
  background: var(--panel-2);
}
.seg-btn.active {
  color: var(--fg);
  background: color-mix(in srgb, var(--accent) 16%, var(--panel-2));
}
.seg-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* A brief, calm arrival highlight for a specific transaction row reached via
   drillToTransaction (shared-helpers.js) - the clearest signal that "this is
   the one" versus merely landing somewhere in a filtered list. Fades using
   the same accent-tinted language .insight:hover already establishes
   elsewhere, so it reads as one consistent "this matters right now"
   treatment rather than a new visual idiom. */
@keyframes tx-focus-flash {
  from {
    background: color-mix(in srgb, var(--accent) 16%, transparent);
  }
  to {
    background: transparent;
  }
}
.tx-row.focus-row {
  animation: tx-focus-flash 2.2s ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .tx-row.focus-row {
    animation: none;
    background: color-mix(in srgb, var(--accent) 12%, transparent);
  }
}

/* print: the content model must print solid and dark-on-white; glass never
   reaches print because every glass rule above is @media screen scoped, but
   assert the vm component stays legible if a card is ever included. */
@media print {
  .vm-number,
  .vm-label,
  .vm-tag,
  .vm-reconcile {
    color: #10161f !important;
    background: none !important;
  }
  .vm-tag {
    border-color: #cdd5df !important;
  }
}

/* =====================================================================
   APPLE PASS - floating surfaces + gentle interaction (screen-only).
   Everything here is a REPLACEABLE enhancement: it upgrades menus,
   pop-ups and temporary controls to the same soft glass language the
   panels use, and gives buttons / tabs a quiet, physical response. It
   introduces no new palette (every value derives from existing tokens),
   never touches print, and is fully rolled back by the reduced-
   transparency / reduced-motion contracts at the foot of this block.
   The guiding rule: glass is strongest on navigation, menus and
   decisions; ordinary information panels stay calm; the money figures
   stay solid and sharp.
   ===================================================================== */
@media screen {
  @supports ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
          /* ---- Floating decision surfaces: the Export menu, every picker /
         confirmation window, the toast, the install prompt, the busy card,
         and the two floating controls. These genuinely sit ABOVE the app,
         so they carry the clearest glass character on the screen: real
         translucency, a soft top-edge light, and a broad, gentle shadow
         instead of a hard dark rectangle. */
      .menu,
      .picker,
      .pfa-sonner [data-sonner-toast],
      .busy-card,
      .report-close,
      .to-top {
        /* A floating DECISION surface must occlude the content behind it
           through its own fill, NEVER by relying on backdrop-filter. These
           surfaces are descendants of the sticky topbar, which itself has a
           backdrop-filter - and a nested backdrop-filter is silently
           defeated by an ancestor that already has one (there is no
           un-composited backdrop left for the child to sample). When that
           happens the old 72% fill let a person's actual balances bleed
           straight through the open Export menu on every tab. The fill is
           now near-opaque, so the surface reads as solid regardless of
           whether the blur renders; the blur stays only as an aesthetic
           enhancement WHERE it works, no longer load-bearing for occlusion.
           This is the same fix applied to the period bar (styles.css). Cards
           and panels are deliberately NOT changed - their translucency is
           the glass aesthetic and content behind them is MEANT to be softly
           present; a menu is a different category, nothing behind it should
           be readable while it is open. */
        background: color-mix(in srgb, var(--panel) 97%, transparent);
        -webkit-backdrop-filter: saturate(1.6) blur(22px);
        backdrop-filter: saturate(1.6) blur(22px);
        box-shadow:
          inset 0 0 0 1px color-mix(in srgb, var(--fg) 10%, var(--edge)),
          inset 0 1px 0 var(--glass-hi),
          inset 0 -1px 0 color-mix(in srgb, #000 10%, transparent),
          0 4px 16px -8px color-mix(in srgb, #000 26%, transparent),
          0 24px 60px -24px color-mix(in srgb, #000 34%, transparent);
      }

      /* The toast keeps its dark, high-contrast pill for legibility of the
         undo action, but softens its edge to match the family. Fill raised
         to near-opaque for the same reason as the menu family above: a
         transient status surface must read solidly regardless of whether
         its nested blur renders. */
      .pfa-sonner [data-sonner-toast] {
        background: color-mix(in srgb, var(--tip-bg) 97%, transparent);
        border-color: color-mix(in srgb, #fff 12%, transparent);
      }

      /* The dimmed page behind a modal is softened rather than blacked out,
         so context is never fully lost - the app stays recognisable beneath
         the active decision (Apple's "the layer below remains present"). */
      .overlay {
        background: color-mix(in srgb, var(--bg) 46%, transparent);
        -webkit-backdrop-filter: saturate(1.1) blur(8px);
        backdrop-filter: saturate(1.1) blur(8px);
      }

      .ledger-tab.active {
        background: color-mix(in srgb, var(--panel) 88%, transparent);
        box-shadow:
          inset 0 1px 0 var(--glass-hi),
          0 1px 6px -3px color-mix(in srgb, #000 26%, transparent);
      }
      .ledger-tab:hover:not(.active) {
        background: color-mix(in srgb, var(--fg) 6%, transparent);
      }

      /* ---- Buttons: a quiet, physical response. Hover brightens a soft
         upper-edge light; press settles the surface down a hair. No growth,
         no bounce, no colour jump - the neighbour content never shifts. */
      .btn {
        transition:
          background 0.16s ease,
          border-color 0.16s ease,
          box-shadow 0.18s ease,
          transform 0.08s ease,
          filter 0.16s ease;
      }
      .btn:hover {
        box-shadow:
          inset 0 1px 0 color-mix(in srgb, #fff 22%, transparent),
          0 2px 10px -6px color-mix(in srgb, #000 24%, transparent);
      }
      .btn:active {
        transform: translateY(1px);
        box-shadow: inset 0 1px 2px color-mix(in srgb, #000 18%, transparent);
      }
      /* The primary action keeps its accent fill but gains the same soft
         upper light, so it reads as a lit glass button rather than a flat
         coloured block. */
      .btn.primary:hover {
        filter: brightness(1.04);
        box-shadow:
          inset 0 1px 0 color-mix(in srgb, #fff 30%, transparent),
          0 3px 14px -6px color-mix(in srgb, var(--accent) 45%, transparent);
      }

      /* ---- Openable rows: insights, category rows, commitment rows and the
         account selector get a soft light on hover and a gentle arrow lift,
         with NO sideways slide, so long lists stay calm while still feeling
         responsive. */
      .insight {
        transition:
          background 0.16s ease,
          box-shadow 0.18s ease,
          border-color 0.16s ease;
      }
      .insight:hover {
        transform: none;
        border-color: color-mix(in srgb, var(--accent) 30%, var(--edge));
        background: color-mix(in srgb, var(--accent) 8%, var(--panel-2));
        box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 14%, transparent);
      }
      .insight:hover .insight-go {
        opacity: 1;
        color: var(--accent-text);
      }
    
  }
}

/* ---- ACCESSIBILITY CONTRACTS (mirror the base file) --------------------
   Reduced transparency: every floating surface returns to a fully solid
   panel, exactly like the base cards do, so nothing here weakens the
   opaque-surface promise. */
@media (prefers-reduced-transparency: reduce), (prefers-contrast: more), (forced-colors: active) {
  .menu,
  .picker,
  .pfa-sonner [data-sonner-toast],
  .install-banner,
  .busy-card,
  .report-close,
  .to-top {
    background: var(--panel) !important;
    -webkit-backdrop-filter: none !important;
    backdrop-filter: none !important;
  }
  .pfa-sonner [data-sonner-toast] {
    background: var(--tip-bg) !important;
  }
  .overlay {
    background: color-mix(in srgb, #000 42%, transparent) !important;
    -webkit-backdrop-filter: none !important;
    backdrop-filter: none !important;
  }
}

/* Reduced motion: keep every state CHANGE (colour, light, solidity) but
   remove the movement and easing, so a person who asked for stillness gets
   an instant, calm result with no sliding or settling. */
@media (prefers-reduced-motion: reduce) {
  .ledger-tab,
  .btn,
  .insight {
    transition: none !important;
  }
  .btn:active {
    transform: none !important;
  }
}

/* =====================================================================
   Privacy mode - RETIRED from this file.

   What used to live here was a hand-maintained list of figure-bearing
   classes blurred by :root[data-privacy='on'], plus two hardcoded bar
   overrides. It could only ever reach a figure that happened to have its
   own element AND happened to be on the list, so the Available-now
   headline, every amount written into a sentence, every hover title and
   every aria-label stayed fully legible with the figures "hidden" - and
   nothing failed loudly when a new figure surface was added.

   Privacy is now a product-level contract, not a stylesheet trick:
     - application/core/privacy.js holds the state and the exempt paths
       (print, "Copy summary", export);
     - application/core/money-format.js is the ONE formatter every screen
       and every analysis model calls, so a figure cannot be rendered
       without passing the gate;
     - shape-encoded figures declare themselves with markProportional()
       at construction, and charts return one shared placeholder;
     - the remaining presentation rules live in premium.css, which loads
       after this file.
   ===================================================================== */

/* =====================================================================
   Keep the support area (Data & settings) quiet on every screen. It is a
   .card.secondary and can land first in the DOM on some destinations,
   where it would otherwise inherit the strong "lead panel" glass and read
   as more important than the financial answer. This holds it to a calm,
   nearly-flat surface with no bright edge and no lifted glow, so the money
   content always leads even when the ordering puts settings first. Higher
   specificity than the lead rule (the id form) and later source order (the
   wrapper form) guarantee this wins. Screen-only; the reduced-transparency
   contract above still forces it fully solid when asked.
   ===================================================================== */
@media screen {
  #app.wrap > .card.secondary,
  .accounts-wrap > .card.secondary,
  .card.secondary {
    background: color-mix(in srgb, var(--panel-2) 55%, var(--panel));
    -webkit-backdrop-filter: saturate(1.1) blur(10px);
    backdrop-filter: saturate(1.1) blur(10px);
    border: 1px solid color-mix(in srgb, var(--fg) 7%, var(--edge));
    box-shadow: none;
  }
}

/* =====================================================================
   v19 surface hierarchy (loads last, so it settles ties over both files).
   Plan sections 1 and 4: one lead surface per screen carries the deepest
   float; ordinary supporting cards recede to a calmer, flatter surface so
   the screen reads as a hierarchy, not a stack of equally loud boxes. The
   glass fill and blur set above are untouched; only the elevation and edge
   are tuned here. Data & settings (.secondary) and the ambient status line
   are deliberately excluded so they never gain float. Screen-only; the
   reduced-transparency contract earlier in this file still forces solids.
   ===================================================================== */
@media screen {
  /* The one lead panel per destination: a slightly deeper, softer float and
     a faintly accent-tinted edge, so it sits forward through depth rather
     than brightness. */
  .card.hero,
  .card.lead {
    border-color: color-mix(in srgb, var(--accent) 20%, var(--glass-edge, var(--edge)));
    box-shadow:
      inset 0 1px 0 var(--glass-hi),
      inset 0 34px 60px -34px var(--glass-corner),
      0 26px 60px -30px color-mix(in srgb, #000 34%, transparent);
  }
  /* Ordinary supporting cards: quieter edge, shallow shadow, no corner glow,
     so they clearly sit behind the lead surface. */
  .card:not(.hero):not(.lead):not(.secondary) {
    border-color: color-mix(in srgb, var(--fg) 8%, var(--edge));
    box-shadow:
      inset 0 1px 0 color-mix(in srgb, #fff 6%, transparent),
      0 10px 34px -30px color-mix(in srgb, #000 30%, transparent);
  }
}

/* =====================================================================
   Dashboard grid utilities
   ===================================================================== */
@media (min-width: 1000px) {
  .dash-grid {
    display: grid;
    grid-template-columns: repeat(12, minmax(0, 1fr));
    gap: var(--card-gap);
    align-items: start;
  }

  .dash-grid > .card {
    grid-column: span 12;
  }

  .dash-grid > .span-6 {
    grid-column: span 6;
  }

  .dash-grid > .span-4 {
    grid-column: span 4;
  }

  .dash-grid > .span-8 {
    grid-column: span 8;
  }

  .dash-grid > .hero,
  .dash-grid > .card.hero {
    grid-column: span 12;
  }
}

/* =====================================================================
   Dense numeric presentation
   ===================================================================== */
body.dense {
  --vm-number-lg: 30px;
  --vm-number: 22px;
}

body.dense .vm-number.lg {
  font-size: var(--vm-number-lg);
  letter-spacing: -0.5px;
}

body.dense .vm-number {
  font-size: var(--vm-number);
  letter-spacing: -0.3px;
}

body.dense table.grid {
  font-size: 13px;
}

body.dense table.grid td,
body.dense table.grid th {
  padding: 7px 10px;
}

body.dense .num,
body.dense .vm-number,
body.dense .recurring-amt {
  font-variant-numeric: tabular-nums;
}

/* Programmatic drill destination. */
#acct-tx:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 10px;
}

/* ---------------------------------------------------------------------------
 * Modal layering, and the page behind one.
 *
 * The floating chrome was declared at --z-banner (120) and --z-toast (130),
 * both ABOVE --z-overlay-backdrop (100). Measured with a dialog open on the
 * Activity tab: elementFromPoint at the back-to-top button's centre returned
 * the button's own arrow - it rendered bright over the dimmed backdrop and was
 * still clickable through it. The dev pill sat on top of the backdrop too.
 *
 * A modal is modal. While one is up, the floating chrome gets out of the way
 * and the page behind stops scrolling under it.
 * ------------------------------------------------------------------------- */
body.modal-open .to-top,
body.modal-open #pfa-mock-switcher {
  display: none;
}
body.modal-open {
  overflow: hidden;
}
/* The busy overlay blocks interaction while work is in flight, so nothing may
   float above it either - it was at 80, under the export menu (110) and both
   banners (120), so a statement could be imported with the export menu open on
   top of the "working" curtain. It sits above those and below the toast, which
   is status and must stay readable. */
.busy {
  z-index: 125;
}
}
