/* ============================================================================
   Carpe Money — shared brand layer  (design-system foundation)
   ----------------------------------------------------------------------------
   Ported from the Carpe Money Design System handoff (claude.ai/design). Loaded
   LAST in each template's <head> (after the page's own inline <style>) so these
   tokens, the parchment field, base type, and the component classes win over the
   older inline rules without each template having to delete them first.

   Layers, top to bottom:
     1. Colour tokens        — base palette (light + dark) + semantic aliases
     2. Typography tokens     — one family (Plus Jakarta Sans), scale + tracking
     3. Spacing / shape       — spacing, radii, borders, 3-step shadows, motion
     4. Base field + lockup   — the aged-scroll parchment, brand wordmark, label
     5. Components (.cm-*)     — opt-in: card, button, input, badge, meter, …
     6. Legacy retrofit       — keeps NOT-yet-migrated screens looking right

   Palette is "Tyrian-forward": Tyrian purple is the brand; emerald is money/
   positive; laurel green is the heritage accent; on an aged-parchment field.
   Light parchment is the DEFAULT; dark espresso via :root[data-theme="dark"].
   ========================================================================== */

/* ── 1 · Colour — base palette ───────────────────────────────────────────── */
:root,
:root[data-theme="light"] {
  --bg:        #fbf6ea;   /* aged parchment            */
  --surface:   #fdfcf4;   /* card (warm ivory)         */
  --surface2:  #f6efde;   /* inputs / secondary fill   */
  --accent:    #6e1f5a;   /* Tyrian purple (brand)     */
  --accent2:   #581548;   /* darker Tyrian (hover)     */
  --glow:      #9c4783;   /* lighter Tyrian (focus)    */
  --good:      #2f7d52;   /* emerald (positive/money)  */
  --laurel:      #557a36; /* laurel green (heritage accent) */
  --laurel-deep: #3f5c28; /* deep laurel (text on tint)     */
  --text:      #382616;   /* ink                       */
  --muted:     #6f5a45;   /* secondary ink (Tyrian-tinted readability) */
  --danger:    #b23a2b;   /* Pompeian red              */
  --amber:     #a67c3c;   /* gold                      */
  --tex-ink:   #6f3a20;   /* parchment grain ink       */
  --frame:     rgba(166,124,60,0.50);   /* gold frame line */
  --border:    rgba(110,31,90,0.22);    /* Tyrian-tinted hairline */
  color-scheme: light;
}

:root[data-theme="dark"] {
  --bg:        #1c1410;   /* deep espresso parchment   */
  --surface:   #261a13;
  --surface2:  #32231a;
  --accent:    #c071a8;   /* lifted Tyrian             */
  --accent2:   #9c4783;
  --glow:      #d98fc2;
  --good:      #4faf78;   /* lifted emerald            */
  --laurel:      #9cbb6a; /* lifted laurel green       */
  --laurel-deep: #b9d089;
  --text:      #ecdcc4;   /* warm ivory ink            */
  --muted:     #bda286;
  --danger:    #df5a44;
  --amber:     #c89a4e;
  --tex-ink:   #d9a877;
  --frame:     rgba(196,150,74,0.42);
  --border:    rgba(192,113,168,0.26);
  color-scheme: dark;
}

/* ── 1 · Colour — semantic aliases (theme-agnostic role names) ────────────── */
:root {
  /* Surfaces & field */
  --surface-page:    var(--bg);
  --surface-card:    var(--surface);
  --surface-sunken:  var(--surface2);

  /* Text roles */
  --text-body:       var(--text);
  --text-muted:      var(--muted);
  --text-on-accent:  #f6eef2;        /* ivory on Tyrian fills */
  --text-heading:    var(--accent);
  --text-label:      var(--accent);

  /* Brand roles */
  --brand:           var(--accent);
  --brand-hover:     var(--accent2);
  --brand-focus:     var(--glow);
  --brand-laurel:    var(--laurel);
  --creed:           var(--danger);

  /* Money / status roles */
  --money-positive:  var(--good);
  --money-negative:  var(--danger);
  --status-warning:  var(--amber);
  --status-info:     var(--accent);

  /* Lines & focus */
  --border-hairline: var(--border);
  --border-strong:   color-mix(in srgb, var(--accent) 40%, var(--border));
  --ring-focus:      color-mix(in srgb, var(--glow) 65%, transparent);
  --frame-gold:      var(--frame);

  /* ── 2 · Typography ────────────────────────────────────────────────────── */
  --font-sans:    'Plus Jakarta Sans', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --font-display: 'Plus Jakarta Sans', system-ui, sans-serif;
  --font-mono:    'Plus Jakarta Sans', ui-monospace, 'SF Mono', Menlo, monospace;

  --weight-regular: 400;
  --weight-medium: 500;
  --weight-semibold: 600;
  --weight-bold: 700;
  --weight-black: 800;

  --text-2xs: 0.6875rem;   /* 11px */
  --text-xs:  0.75rem;     /* 12px */
  --text-sm:  0.875rem;    /* 14px */
  --text-base: 1rem;       /* 16px */
  --text-md:  1.125rem;    /* 18px */
  --text-lg:  1.375rem;    /* 22px */
  --text-xl:  1.75rem;     /* 28px */
  --text-2xl: 2.25rem;     /* 36px */
  --text-3xl: 3rem;        /* 48px */
  --text-4xl: 4rem;        /* 64px */

  --leading-tight: 1.1;
  --leading-snug: 1.3;
  --leading-normal: 1.55;

  --tracking-tight: -0.02em;
  --tracking-normal: 0;
  --tracking-label: 0.16em;
  --tracking-word: 0.14em;
  --tracking-creed: 0.3em;

  /* ── 3 · Spacing / radii / borders / shadows / motion / layout ─────────── */
  --space-0: 0;
  --space-1: 0.25rem;   /* 4px  */
  --space-2: 0.5rem;    /* 8px  */
  --space-3: 0.75rem;   /* 12px */
  --space-4: 1rem;      /* 16px */
  --space-5: 1.5rem;    /* 24px */
  --space-6: 2rem;      /* 32px */
  --space-7: 3rem;      /* 48px */
  --space-8: 4rem;      /* 64px */

  --radius-xs: 6px;
  --radius-sm: 10px;
  --radius-md: 14px;
  --radius-lg: 20px;
  --radius-pill: 999px;

  --border-width: 1px;
  --border-accent-width: 3px;
  --hairline: var(--border-width) solid var(--border-hairline);

  --shadow-card:  0 1px 2px color-mix(in srgb, var(--tex-ink) 18%, transparent),
                  0 6px 20px color-mix(in srgb, var(--accent) 12%, transparent);
  --shadow-raised: 0 2px 4px color-mix(in srgb, var(--tex-ink) 18%, transparent),
                   0 12px 32px color-mix(in srgb, var(--accent) 18%, transparent);
  --shadow-pop:   0 18px 48px color-mix(in srgb, var(--accent) 24%, transparent);
  --shadow-inset: inset 0 1px 0 color-mix(in srgb, #ffffff 40%, transparent);

  --ring-width: 3px;
  --ring: 0 0 0 var(--ring-width) var(--ring-focus);

  --ease-standard: cubic-bezier(0.4, 0, 0.2, 1);
  --ease-out:      cubic-bezier(0.16, 1, 0.3, 1);
  --ease-bounce:   cubic-bezier(0.34, 1.56, 0.64, 1);
  --duration-fast:  120ms;
  --duration-base:  200ms;
  --duration-slow:  320ms;
  --duration-bounce: 360ms;

  --app-max:   1180px;
  --phone-max: 420px;
  --tap-min:   44px;
}

/* ── 4 · Base field — the aged-scroll parchment ──────────────────────────────
   Four layers, back to front: aged-edge vignette · paper-fiber noise (inline
   SVG feTurbulence) · grain-ink mottling · base parchment colour. The field is
   calm; cards float above it with modern, springy elevation. */
body {
  margin: 0;
  background-color: var(--surface-page);
  font-family: var(--font-sans);
  color: var(--text-body);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}
/* Parchment field on a real position:fixed layer — NOT background-attachment:fixed,
   which iOS/mobile mispositions to the layout viewport and renders as a shadowy band
   down one edge. inset:0 sizes it to the visual viewport, behind all content, so the
   field stays calm as content scrolls. Edge vignette + right-side cloud intentionally
   omitted (no edge darkening). */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-color: var(--surface-page);
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.55' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.42 0 0 0 0 0.25 0 0 0 0 0.12 0 0 0 0.16 0'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23p)'/%3E%3C/svg%3E"),
    radial-gradient(ellipse 90% 70% at 18% 12%,  color-mix(in srgb, var(--tex-ink) 14%, transparent), transparent 70%),
    radial-gradient(ellipse 100% 80% at 50% 105%, color-mix(in srgb, var(--tex-ink) 15%, transparent), transparent 72%),
    radial-gradient(ellipse 70% 60% at 30% 70%,  color-mix(in srgb, var(--tex-ink) 9%, transparent),  transparent 66%);
  background-repeat: repeat, no-repeat, no-repeat, no-repeat;
  background-size: 240px 240px, cover, cover, cover;
}

/* A detached scroll panel (when the field isn't on <body> — phone frames). */
.cm-parchment {
  background-color: var(--surface-page);
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='240' height='240'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.55' numOctaves='3' stitchTiles='stitch'/%3E%3CfeColorMatrix values='0 0 0 0 0.42 0 0 0 0 0.25 0 0 0 0 0.12 0 0 0 0.16 0'/%3E%3C/filter%3E%3Crect width='240' height='240' filter='url(%23p)'/%3E%3C/svg%3E"),
    radial-gradient(ellipse 90% 70% at 18% 12%,  color-mix(in srgb, var(--tex-ink) 14%, transparent), transparent 70%),
    radial-gradient(ellipse 100% 80% at 50% 105%, color-mix(in srgb, var(--tex-ink) 15%, transparent), transparent 72%),
    radial-gradient(ellipse 70% 60% at 30% 70%,  color-mix(in srgb, var(--tex-ink) 9%, transparent),  transparent 66%);
  background-repeat: repeat, no-repeat, no-repeat, no-repeat;
  background-size: 240px 240px, cover, cover, cover;
  color: var(--text-body);
}

/* ── 4 · Brand lockup — wordmark + creed ─────────────────────────────────── */
.brand-text { display: flex; flex-direction: column; line-height: 1; }
.brand-word {
  font-family: var(--font-display);
  font-weight: var(--weight-black);
  letter-spacing: var(--tracking-word);
  color: var(--brand);
  line-height: 1;
}
.brand-creed {
  font-size: var(--text-2xs);
  letter-spacing: var(--tracking-creed);
  text-transform: uppercase;
  color: var(--creed);
  margin-top: 4px;
}

/* Reusable uppercase Tyrian micro-label. */
.cm-label {
  font-size: var(--text-2xs);
  font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--text-label);
}

/* Tabular numerals — align money columns. */
.cm-tnum { font-variant-numeric: tabular-nums; }

/* ── 5 · Components (opt-in .cm-* classes) ───────────────────────────────── */

/* Icon host: a Lucide <i data-lucide> becomes an <svg> inheriting currentColor. */
.cm-icon { display: inline-flex; align-items: center; justify-content: center; flex: none; line-height: 0; }
.cm-icon svg, i[data-lucide] { stroke-width: 2; }

/* Card — the signature surface: ivory fill, hairline, 3px Tyrian top rule, soft
   purple lift. .quiet drops the top rule; .interactive springs up on hover. */
.cm-card {
  background: var(--surface-card);
  border: 1px solid var(--border-hairline);
  border-top: var(--border-accent-width) solid var(--brand);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-card);
  padding: var(--space-5);
}
.cm-card.quiet { border-top: 1px solid var(--border-hairline); }
.cm-card.interactive {
  cursor: pointer;
  transition: box-shadow var(--duration-base) var(--ease-standard),
              transform var(--duration-bounce) var(--ease-bounce);
}
.cm-card.interactive:hover { box-shadow: var(--shadow-raised); transform: translateY(-3px) scale(1.004); }

/* Card header — Tyrian title + optional trailing action. */
.cm-card-head { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--space-4); }
.cm-card-head h2 { margin: 0; font-size: var(--text-lg); font-weight: var(--weight-bold); color: var(--text-heading); }

/* Button — Tyrian primary, parchment-outline secondary, ghost, danger, success.
   Calm hover (darken), gentle press (down 1px). */
.cm-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2);
  height: 44px; min-width: 44px; padding: 0 18px;
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: var(--weight-bold);
  line-height: 1; letter-spacing: 0.01em;
  border-radius: var(--radius-sm); border: 1px solid transparent; cursor: pointer;
  white-space: nowrap; text-decoration: none;   /* so an <a class="cm-btn"> reads as a button */
  transition: background var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard);
}
.cm-btn.sm { height: 36px; padding: 0 14px; }
.cm-btn.xs { height: 28px; min-width: 0; padding: 0 10px; font-size: var(--text-xs); }
.cm-btn.lg { height: 52px; padding: 0 24px; font-size: var(--text-base); border-radius: var(--radius-md); }
.cm-btn.block { width: 100%; }
.cm-btn.primary   { background: var(--brand); color: var(--text-on-accent); }
.cm-btn.primary:hover   { background: var(--brand-hover); }
.cm-btn.secondary { background: var(--surface-card); color: var(--brand); border-color: var(--border-strong); }
.cm-btn.secondary:hover { border-color: var(--brand); }
.cm-btn.ghost     { background: transparent; color: var(--brand); }
.cm-btn.ghost:hover     { background: color-mix(in srgb, var(--brand) 10%, transparent); }
.cm-btn.danger    { background: var(--danger); color: #fff; }
.cm-btn.danger:hover    { background: color-mix(in srgb, var(--danger) 84%, #000); }
/* ghost + danger = a transparent button with danger-red text (not a solid red fill) —
   .danger alone would win the cascade and make a "ghost danger" button solid. */
.cm-btn.ghost.danger       { background: transparent; color: var(--danger); }
.cm-btn.ghost.danger:hover { background: color-mix(in srgb, var(--danger) 10%, transparent); }
.cm-btn.success   { background: var(--good); color: #fff; }
.cm-btn.success:hover   { background: color-mix(in srgb, var(--good) 86%, #000); }
.cm-btn:active:not(:disabled) { transform: translateY(1px); }
.cm-btn:disabled { opacity: 0.5; cursor: not-allowed; }
.cm-btn[hidden] { display: none; }   /* class display:inline-flex would otherwise beat the UA [hidden] rule */

/* Input — labelled field on the sunken parchment fill, Tyrian focus ring. */
.cm-field { display: block; }
.cm-field > .cm-label { display: block; margin-bottom: var(--space-2); }
.cm-inputwrap {
  display: flex; align-items: center; gap: var(--space-2);
  background: var(--surface-sunken);
  border: 1px solid var(--border-hairline);
  border-radius: var(--radius-sm);
  padding: 0 14px; height: 46px;
  transition: border-color var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-fast) var(--ease-standard);
}
.cm-inputwrap:focus-within { border-color: var(--brand); box-shadow: var(--ring); }
.cm-inputwrap.error { border-color: var(--danger); }
.cm-inputwrap > .cm-icon, .cm-inputwrap > i[data-lucide] { color: var(--muted); }
.cm-inputwrap > input {
  flex: 1; min-width: 0; height: 100%;
  border: none; outline: none; background: transparent;
  font-family: var(--font-sans); font-size: var(--text-sm); color: var(--text-body);
}
.cm-inputwrap > input::placeholder { color: var(--muted); }
.cm-hint { display: block; margin-top: var(--space-2); font-size: var(--text-xs); color: var(--muted); }
.cm-hint.error { color: var(--danger); }

/* Divider with a centred label ("or"). */
.cm-divider {
  display: flex; align-items: center; gap: 12px;
  color: var(--muted); font-size: var(--text-2xs);
  text-transform: uppercase; letter-spacing: 0.1em;
}
.cm-divider::before, .cm-divider::after { content: ''; flex: 1; height: 1px; background: var(--border-hairline); }
.cm-divider[hidden] { display: none; }

/* Badge — compact tinted status / money pill. */
.cm-badge {
  display: inline-flex; align-items: center; gap: var(--space-1);
  height: 22px; padding: 0 10px; border-radius: var(--radius-pill);
  font-size: var(--text-2xs); font-weight: var(--weight-semibold);
  letter-spacing: 0.02em; line-height: 1; white-space: nowrap;
}
.cm-badge.positive { background: color-mix(in srgb, var(--good) 14%, transparent);   color: var(--good); }
.cm-badge.negative { background: color-mix(in srgb, var(--danger) 14%, transparent); color: var(--danger); }
.cm-badge.warning  { background: color-mix(in srgb, var(--amber) 14%, transparent);  color: var(--amber); }
.cm-badge.brand    { background: color-mix(in srgb, var(--accent) 14%, transparent); color: var(--accent); }
.cm-badge.laurel   { background: color-mix(in srgb, var(--laurel) 14%, transparent); color: var(--laurel-deep); }
.cm-badge.neutral  { background: color-mix(in srgb, var(--muted) 14%, transparent);  color: var(--muted); }
.cm-badge.solid          { color: #fff; }
.cm-badge.solid.positive { background: var(--good); }
.cm-badge.solid.negative { background: var(--danger); }
.cm-badge.solid.warning  { background: var(--amber); }
.cm-badge.solid.brand    { background: var(--accent); }

/* Spend-vs-limit meter — emerald → gold (.near) → Pompeian red (.over). */
.cm-meter { height: 8px; border-radius: var(--radius-pill); background: var(--surface-sunken); overflow: hidden; }
.cm-meter > span {
  display: block; height: 100%; border-radius: var(--radius-pill); background: var(--good);
  transition: width var(--duration-bounce) var(--ease-bounce), background var(--duration-base) var(--ease-standard);
}
.cm-meter.near > span { background: var(--amber); }
.cm-meter.over > span { background: var(--danger); }

/* Logo coin tile — gold frame line + soft lift on parchment. */
.cm-coin { border-radius: var(--radius-md); box-shadow: var(--shadow-card); border: 1px solid var(--frame-gold); display: block; }

/* TrustNote — the security voice: modern, plain, emerald. Never decorated. */
.cm-trustnote {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--text-xs); line-height: 1.5; color: var(--text-muted);
}
.cm-trustnote > .cm-icon, .cm-trustnote > i[data-lucide] { color: var(--good); }
.cm-trustnote.panel {
  display: flex; align-items: flex-start; gap: var(--space-3); padding: var(--space-4);
  background: color-mix(in srgb, var(--good) 8%, var(--surface-card));
  border: 1px solid color-mix(in srgb, var(--good) 30%, transparent);
  border-radius: var(--radius-sm);
  font-size: var(--text-sm); line-height: 1.55; color: var(--text-body);
}

/* Text link in the brand voice. */
.cm-link { color: var(--brand); text-decoration: none; font-weight: var(--weight-semibold); }
.cm-link:hover { text-decoration: underline; }

/* Laurel host — the parametric SVG is injected by carpe-laurel.js. */
.cm-laurel { display: block; color: var(--brand-laurel); }
.cm-laurel svg { display: block; }

/* ============================================================================
   6 · Legacy retrofit — the small set of older inline class names still in use.
   index.html is fully migrated to .cm-*; these remaining hooks serve admin.html
   (.card) and a few shared inline bits in index.html (main h2, label,
   .tx-table th, .wiz-inst, .cat-section-title, header/nav rails, .acct-row hover).
   ========================================================================== */
main h2 { color: var(--accent); }

label, .cat-section-title, .wiz-inst, .tx-table th, .drawer-section > .lbl {
  color: var(--accent);
}

.card {
  border-top: 3px solid var(--accent);
  box-shadow: var(--shadow-card);
}

header, nav.tabs {
  border-bottom-color: color-mix(in srgb, var(--accent) 40%, var(--border));
}
.acct-row:hover, #pwa-install-btn:hover { border-color: var(--accent); }

/* ============================================================================
   7 · Phase-2 mobile Dashboard — "Ledger Lines" (Design 2).
   ADD-ONLY component classes for renderDashboardV2() + cm-viz.js. Token-based;
   the mobile-only LAYOUT rules are gated under @media (max-width:760px), (max-height:540px
   and max-width:1024px) [landscape phones] so the desktop dashboard is untouched. Faithful to the prototype's sizes/weights/
   spacing/states (explorations/mobile-home/shared.jsx + Design2.jsx).
   ========================================================================== */

/* Header eye / hide-balances toggle (mobile only — desktop has no eye control). */
.cm-iconbtn {
  width: 38px; height: 38px; flex: none;
  display: none;                       /* shown only on mobile (media query below) */
  align-items: center; justify-content: center;
  border-radius: var(--radius-sm);
  border: 1px solid var(--border-hairline);
  background: var(--surface-card); color: var(--accent);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard);
}
.cm-iconbtn:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.cm-iconbtn:active { transform: translateY(1px); }

/* Solid Tyrian icon/initials chip — bank marks + category glyphs read crisp. */
.cm-avatar {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  border-radius: var(--radius-sm);
  background: var(--brand); color: var(--text-on-accent);
  font-family: var(--font-sans); font-weight: var(--weight-black); letter-spacing: .01em;
  box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 22%, transparent),
              0 1px 2px color-mix(in srgb, var(--accent) 35%, transparent);
  border: 1px solid color-mix(in srgb, #000 12%, var(--brand));
}
.cm-avatar.round { border-radius: var(--radius-pill); }

/* Dashboard V2 host: only rendered into on mobile; section stack + padding. */
.cm-dashv2 { display: none; flex-direction: column; gap: 16px; padding: 4px 16px 20px; }
/* Loading skeleton (replaces raw "Loading…" text on the mobile dashboard host). */
.cm-skel { border-radius: 12px; position: relative; overflow: hidden;
  background: color-mix(in srgb, var(--text-body) 6%, transparent); }
.cm-skel::after { content: ''; position: absolute; inset: 0; transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, color-mix(in srgb, var(--text-body) 8%, transparent), transparent);
  animation: cm-shimmer 1.3s infinite; }
@keyframes cm-shimmer { 100% { transform: translateX(100%); } }
.cm-skel-hero { height: 220px; }
/* Shimmer-row skeleton — YouTube-style placeholder shown while a panel/list hydrates
   (replaces the raw "Loading…" text). Reuses .cm-skel for the box + shimmer. */
.cm-skel-list { display: flex; flex-direction: column; gap: 14px; padding: 6px 0; }
.cm-skel-row { display: flex; align-items: center; gap: 12px; }
.cm-skel-av { width: 40px; height: 40px; border-radius: 12px; flex: none; }
.cm-skel-lines { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.cm-skel-l1 { height: 12px; width: 62%; }
.cm-skel-l2 { height: 10px; width: 38%; }
@media (prefers-reduced-motion: reduce) { .cm-skel::after { animation: none; } }

/* Spending hero card. */
.cm-hero { position: relative; overflow: hidden; }
/* Watermark laurel: a faint flourish on the right of the hero's upper band — below the
   top-right "$ left · save rate" badge and above the chart, so it collides with neither. */
.cm-hero .cm-hero-laurel {
  position: absolute; right: 8px; top: 44px; pointer-events: none; opacity: .05;
}
.cm-hero-amt {
  font-size: 36px; font-weight: var(--weight-black); letter-spacing: -.035em;
  color: var(--text-body); line-height: 1; white-space: nowrap;
}
.cm-hero-of { font-size: var(--text-sm); color: var(--muted); font-weight: var(--weight-semibold); }
.cm-hero-axis { display: flex; justify-content: space-between; font-size: var(--text-xs); color: var(--muted); font-weight: var(--weight-semibold); }
/* Hero month-pivot nav — [‹] Month YYYY [›]; steps the hero to a past month. */
.cm-hero-nav { display: flex; align-items: center; justify-content: space-between; margin: 2px 0 2px; }
.cm-hero-nav-label { font-size: var(--text-sm); font-weight: var(--weight-bold); color: var(--text-body); letter-spacing: -.01em; }
.cm-hero-nav-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 44px; height: 44px; border-radius: var(--radius-pill);
  background: none; border: none; color: var(--accent); cursor: pointer;
  transition: background .14s ease; -webkit-tap-highlight-color: transparent;
}
/* Hover only on hover-capable devices — otherwise a mobile TAP leaves the fill "stuck" so the two
   arrows look asymmetric (one active). Keyboard focus gets a ring via :focus-visible, not a fill. */
@media (hover: hover) { .cm-hero-nav-btn:hover:not(:disabled) { background: color-mix(in srgb, var(--accent) 10%, transparent); } }
.cm-hero-nav-btn:focus { outline: none; }
.cm-hero-nav-btn:focus-visible { outline: 2px solid var(--ring-focus); outline-offset: 2px; }
.cm-hero-nav-btn:disabled { opacity: .3; cursor: default; color: var(--muted); }
/* While a month-pivot's >90d backfill runs, dim the hero + freeze its arrows instead of the
   full-screen boot splash (which an in-card control shouldn't trigger). */
.cm-hero-pivoting .cm-hero-chart { opacity: .4; transition: opacity .15s ease; }
.cm-hero-pivoting .cm-hero-nav-btn { pointer-events: none; }
.cm-hero-pacekey { display: inline-flex; align-items: center; gap: 5px; }
.cm-hero-pacekey > span { width: 14px; height: 0; border-top: 1.5px dashed var(--muted); }
/* Hero line-graph legend — one row of [swatch · name · total]. Four CSS vars
   trade text size against breathing room so it stays one line. Spending swatch =
   purple (--accent, → --danger over budget), income = green (--good), budget =
   a DASHED rule matching the chart's reference line. */
.cm-hero-legend {
  --leg-font: 12px;       /* legend text size */
  --leg-gap: 4px;         /* space between the three legend entries */
  --leg-swatch: 13px;     /* swatch width */
  --leg-swatch-gap: 5px;  /* space between a swatch and its label */
  display: flex; flex-wrap: wrap; align-items: center;
  justify-content: space-between; gap: 4px var(--leg-gap); margin-top: 8px;
}
.cm-hero-leg {
  display: inline-flex; align-items: center; gap: var(--leg-swatch-gap);
  font-size: var(--leg-font); color: var(--muted); font-weight: var(--weight-semibold);
  white-space: nowrap; margin: 0; width: auto;
}
.cm-hero-leg b { color: var(--text-body); font-weight: var(--weight-bold); margin-left: 2px; }
.cm-hero-sw { width: var(--leg-swatch); height: 3px; border-radius: var(--radius-pill); flex: none; }
.cm-hero-sw.spend       { background: var(--accent); }
.cm-hero-sw.spend.over  { background: var(--danger); }
.cm-hero-sw.income      { background: var(--good); }
/* Segmented control (e.g. the recurring editor's Starts with / Contains / Exactly match toggle).
   Full-width flex so 2–3 options share the row evenly and never overflow a narrow modal. */
.cm-segment {
  display: flex; padding: 3px; gap: 3px; border-radius: var(--radius-pill);
  background: var(--surface-sunken); border: 1px solid var(--border-hairline);
}
.cm-segment-opt {
  appearance: none; border: none; background: none; cursor: pointer; flex: 1;
  padding: 6px 10px; border-radius: var(--radius-pill); font: inherit; font-size: 13px;
  white-space: nowrap; text-align: center;
  font-weight: var(--weight-semibold); color: var(--muted); transition: background .14s ease, color .14s ease;
}
.cm-segment-opt.is-active { background: var(--surface-card); color: var(--accent); box-shadow: var(--shadow-card); }

/* Merchant autocomplete dropdown (recurring editor) — a floating listbox under the input. */
.cm-ac-menu {
  position: absolute; left: 0; right: 0; top: calc(100% + 3px); z-index: 30;
  background: var(--surface-card); border: 1px solid var(--border);
  border-radius: 10px; box-shadow: var(--shadow-card); max-height: 224px; overflow-y: auto;
}
.cm-ac-item {
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
  padding: 9px 12px; cursor: pointer; font-size: 14px; color: var(--text-body);
}
.cm-ac-item:hover, .cm-ac-item.is-active { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.cm-ac-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cm-ac-count { flex: none; font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* Recurring swatch — a solid fill block matching the chart's plain filled recurring band.
   Taller than the 3px line swatches (like the handoff's recurring block) so it reads as a fill,
   not a line; translucent to echo the chart's 0.24 fill without being illegibly faint. */
/* Recurring swatch — a gold block matching the chart's distinct gold commitment band (constant
   colour; it doesn't track the over-budget red). */
.cm-hero-sw.recurring,
.cm-hero-sw.recurring.over { height: 9px; border-radius: 2px; background: var(--amber); opacity: 0.85; }
/* Budget swatch = dashed rule (matches the chart's dashed budget reference line). */
.cm-hero-sw.budget {
  height: 0; background: none; border-radius: 0;
  border-top: 2px dashed var(--muted); opacity: 0.7;
}

/* Extra-income pill (income beyond estimate raised the budget). */
.cm-pill-good {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 11px; font-weight: var(--weight-bold); color: var(--good);
  background: color-mix(in srgb, var(--good) 14%, transparent);
  padding: 3px 9px; border-radius: var(--radius-pill); white-space: nowrap;
}

/* (StatTile / Money-in-out tiles removed — income + spending now live in the
   hero line-graph legend above.) */

/* Emphasis block (Alert rules / Subscriptions): conditional 3px top-rule +
   breached red-tint border + raised shadow. .breached deepens both. */
.cm-block {
  background: var(--surface-card);
  border: 1px solid var(--border-hairline);
  border-top: 3px solid var(--brand);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-card);
  padding: 16px 16px 8px;
}
.cm-block.raised { box-shadow: var(--shadow-raised); }
.cm-block.tone-good    { border-top-color: var(--good); }
.cm-block.tone-warn    { border-top-color: var(--amber); }
.cm-block.tone-bad     { border-top-color: var(--danger); }
.cm-block.breached     { border-color: color-mix(in srgb, var(--danger) 38%, var(--border-hairline)); }
.cm-block-head { display: flex; align-items: center; gap: 12px; }
.cm-block-head h2 { margin: 0; font-size: 17px; font-weight: var(--weight-bold); color: var(--accent); }
.cm-block-chip {
  width: 40px; height: 40px; flex: none; border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--text-on-accent);
  box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 26%, transparent);
}
.cm-block-counts { display: flex; gap: 14px; margin-top: 12px; padding-bottom: 2px; }
.cm-block-counts .ct { display: flex; align-items: center; gap: 6px; }
.cm-block-counts .dot { width: 8px; height: 8px; border-radius: 3px; flex: none; }
.cm-block-counts .n { font-size: var(--text-sm); font-weight: var(--weight-black); color: var(--text-body); }
.cm-block-counts .lbl { font-size: var(--text-xs); color: var(--muted); font-weight: var(--weight-semibold); }
.cm-block-rows { border-top: 1px solid var(--border-hairline); margin-top: 8px; }
.cm-block-action {
  width: 100%; margin: 4px 0 8px; height: 40px; border-radius: var(--radius-md);
  border: 1px solid var(--border-strong); background: transparent; color: var(--accent);
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: var(--weight-bold);
  cursor: pointer; display: inline-flex; align-items: center; justify-content: center; gap: 8px;
}
.cm-block-action:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); }

/* Collapsible dashboard cards (Alert rules + Recurring): the summary (head + counts)
   stays visible; the rule rows collapse. The title is a separate deep-link to its tab. */
details.cm-block > summary { list-style: none; cursor: pointer; }
details.cm-block > summary::-webkit-details-marker { display: none; }
details.cm-block[open] .cm-stat-chev { transform: rotate(180deg); }
.cm-block-link { cursor: pointer; display: inline-flex; align-items: center; gap: 2px; }
.cm-block-link:hover { text-decoration: underline; text-underline-offset: 2px; }
.cm-block-link i[data-lucide] { color: var(--accent); }

/* Prominent rule row inside the alert block (solid status chip + tall meter). */
.cm-rule-row { padding: 12px 0; border-bottom: 1px solid var(--border-hairline); }
.cm-rule-row:last-child { border-bottom: none; }
.cm-rule-top { display: flex; align-items: center; gap: 10px; }
.cm-rule-chip {
  width: 32px; height: 32px; flex: none; border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; justify-content: center;
  box-shadow: inset 0 1px 0 color-mix(in srgb, #fff 26%, transparent);
}
/* weight-medium (not semibold) so the amount — the actually-actionable figure — reads as the
   one clear "loudest" thing on the row, not tied with the name. */
.cm-rule-name { font-size: var(--text-sm); font-weight: var(--weight-medium); color: var(--text-body);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; flex: 1 1 auto; }
.cm-rule-scope { font-size: var(--text-xs); color: var(--muted); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cm-rule-amt { font-size: var(--text-sm); font-weight: var(--weight-bold); flex: none; white-space: nowrap; }
.cm-rule-amt .of { color: var(--muted); font-weight: var(--weight-medium); }
.cm-rule-note { font-size: var(--text-xs); font-weight: var(--weight-semibold); margin-top: 6px; }
/* Narrow phones (iPhone SE/mini class, ≤400px): the name+amount pair no longer fit at full size
   on one line without the name mid-word-truncating — step both down and shrink the chip so the
   name gets more room. */
@media (max-width: 400px) {
  .cm-rule-chip { width: 28px; height: 28px; }
  .cm-rule-name { font-size: 13px; }
  .cm-rule-amt { font-size: 13px; }
}

/* Subscription validation row (smaller chip). */
.cm-sub-row { padding: 12px 0; border-bottom: 1px solid var(--border-hairline); }
.cm-sub-row:last-child { border-bottom: none; }
.cm-sub-chip { width: 34px; height: 34px; }

/* Net-worth strip (demoted summary). */
.cm-nw { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.cm-nw-val { font-size: 22px; font-weight: var(--weight-black); letter-spacing: -.01em; color: var(--text-body); margin-top: 3px; }
.cm-nw-spark { width: 92px; flex: none; opacity: .9; }

/* Transaction + account rows (Tyrian-chip ledger lines). */
.cm-tx-row { display: flex; align-items: center; gap: var(--space-3); padding: var(--space-3) var(--space-2); border-bottom: 1px solid var(--border-hairline); }
.cm-tx-row:last-child { border-bottom: none; }
/* Tappable ledger lines (Activity) — open the txn detail/recategorize drawer.
   Subtle Tyrian-tinted hover/active so the row reads as a button, plus a clear
   keyboard-focus ring (rows get role="button" tabindex="0"). */
.cm-tx-row.tappable { cursor: pointer; border-radius: var(--radius-sm);
  transition: background var(--duration-fast) var(--ease-standard); }
.cm-tx-row.tappable:hover { background: color-mix(in srgb, var(--accent) 7%, transparent); }
.cm-tx-row.tappable:active { background: color-mix(in srgb, var(--accent) 12%, transparent); }
.cm-tx-row.tappable:focus-visible { outline: 2px solid var(--accent); outline-offset: -2px; }
.cm-tx-icon { width: 38px; height: 38px; }
/* Category icon inside a drill-down row's merchant cell — the same Tyrian .cm-avatar
   treatment as Activity (cm-tx-icon), sized down to sit inline in the compact table row. */
.cm-drill-cat { width: 26px; height: 26px; margin-right: 8px; vertical-align: middle; }
.cm-drill-cat svg { width: 15px; height: 15px; }
.cm-tx-name { font-size: var(--text-sm); font-weight: var(--weight-semibold); color: var(--text-body); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cm-tx-meta { font-size: var(--text-xs); color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cm-tx-amt { font-size: var(--text-sm); font-weight: var(--weight-bold); white-space: nowrap; flex: none; }
.cm-tx-amt.in { color: var(--good); }
.cm-tx-amt.out { color: var(--text-body); }
.cm-tx-amt.pending { color: var(--amber); }

.cm-acct-row {
  display: flex; align-items: center; gap: var(--space-4); padding: var(--space-4);
  background: var(--surface-card); border: 1px solid var(--border-hairline);
  border-radius: var(--radius-md); cursor: pointer; width: 100%; text-align: left;
  font: inherit; color: inherit;
  transition: border-color var(--duration-fast) var(--ease-standard);
}
.cm-acct-row:hover { border-color: var(--brand); }
.cm-acct-name { font-size: var(--text-sm); font-weight: var(--weight-bold); color: var(--text-body); }
.cm-acct-sub { font-size: var(--text-xs); color: var(--muted); }
.cm-acct-bal { font-size: var(--text-md); font-weight: var(--weight-bold); color: var(--text-body); }

.cm-secthead { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.cm-secthead h2 { margin: 0; font-size: 17px; font-weight: var(--weight-bold); color: var(--accent); }

/* ── DualLine hero chart — overlaid haloed end-dots + "budget" chip + area fills
   are inline-styled in cm-viz.js so they stay correct under the SVG's fluid
   stretch (preserveAspectRatio="none"). The wrapper is position:relative; this
   keeps any future overlay clipping consistent with the card. ───────────────── */
.cm-hero .cm-dualline { position: relative; }

/* W1: the hero day/month drilldown reuses #drill-backdrop but its header "View
   Transactions →" button replays the last Stats query (wrong dataset). When the
   backdrop is opened from the hero (data-source='hero'), hide that header button
   entirely — the modal body's own list is the correct content. */
#drill-backdrop[data-source='hero'] .modal-head button.btn { display: none; }

/* Month-ledger day-group sticky headers (monthDrilldown — newest day first). */
.tx-table .drill-dayhead td {
  position: sticky; top: 0; z-index: 1; background: var(--surface-card);
  font-size: 10px; font-weight: var(--weight-bold); letter-spacing: .06em;
  text-transform: uppercase; color: var(--muted);
  border-bottom: 1px solid var(--border-hairline); padding: 10px 6px 4px;
}

/* Spotlight scrub day-detail strip (centred; fills on scrub/pin; empty otherwise).
   Reserves a min-height so revealing the detail doesn't shift the chart/legend. */
.cm-hero-daystrip {
  min-height: 30px; display: flex; align-items: center;
  gap: 10px; flex-wrap: wrap; margin: 6px 0 2px;
}
.cm-hero-day-info { display: flex; align-items: baseline; gap: 10px 14px; flex-wrap: wrap; min-width: 0; }
.cm-hero-day-date { font-size: var(--text-sm); font-weight: var(--weight-bold); color: var(--text-body); }
.cm-hero-day-metric { font-size: var(--text-xs); color: var(--muted); }
.cm-hero-day-metric b { font-weight: var(--weight-bold); color: var(--text-body); }
.cm-hero-day-metric.out b { color: var(--text-body); }
.cm-hero-day-metric.in b { color: var(--good); }
.cm-hero-day-see { flex: none; font-size: 12px; gap: 5px; padding: 0 9px; height: 30px; min-height: 0; }

/* Hero header row 2 — "of $budget" + the View-transactions slot. Min-height is
   reserved so the button appearing/disappearing never reflows the chart down. */
.cm-hero-row2 {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; min-height: 30px; margin-top: 2px;
}
.cm-hero-row2-slot { flex: none; }
.cm-hero-badge { font-size: 12px; flex: none; margin-left: auto; }  /* stays right-aligned even when it wraps to its own line */

/* ── Spotlight scrub: marker line + riding dots + callout + pulse ─────────────── */
.cm-dl-scrub { user-select: none; -webkit-user-select: none; touch-action: none; }
/* Series dim transition while a finger is down (set via inline opacity). */
.cm-dl-series { transition: opacity .18s; }
/* Full-bleed push-and-drag surface. */
.cm-dl-surf {
  position: absolute; inset: 0; touch-action: none; cursor: grab; outline: none;
}
.cm-dl-surf:active { cursor: grabbing; }
.cm-dl-surf:focus-visible { box-shadow: inset 0 0 0 2px var(--ring-focus); border-radius: 8px; }
/* Riding dots (HTML overlay, % positioned → stay circular under the SVG stretch). */
.cm-dl-ride {
  position: absolute; transform: translate(-50%, -50%); border-radius: 50%;
  border: 2px solid var(--surface-card); pointer-events: none;
}
/* Haptic-style pulse ring fired on each day-cross (re-keyed by node replacement). */
.cm-ride-pulse {
  position: absolute; inset: 0; border-radius: 50%;
  animation: cmRidePulse 460ms cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
@keyframes cmRidePulse {
  0%   { transform: scale(1);   opacity: .7; }
  100% { transform: scale(2.6); opacity: 0; }
}
@media (prefers-reduced-motion: reduce) {
  .cm-ride-pulse { animation: none; opacity: 0; }
}
/* Value callout floating above the spending dot — that day's per-day spend. */
.cm-dl-callout {
  position: absolute; transform: translate(-50%, calc(-100% - 14px));
  background: var(--accent); color: var(--text-on-accent);
  padding: 5px 9px; border-radius: 8px; white-space: nowrap; text-align: center;
  line-height: 1.15; pointer-events: none;
  box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 32%, transparent);
}
.cm-dl-callout-amt { font-size: 14px; font-weight: var(--weight-black); letter-spacing: -.01em; }
.cm-dl-callout-sub { font-size: 9px; font-weight: var(--weight-semibold); opacity: .85; letter-spacing: .02em; }

/* ── Net-worth Spotlight scrub (Stats, MONTHLY) ───────────────────────────────
   The Stats analog of the hero scrub: a prominent hero-style card with one
   net-worth line scrubbed by MONTH. DISTINCT cm-nw-* ids/classes from the hero's
   cm-dl-* (so the two never collide). Reuses the hero's cmRidePulse keyframes +
   the slider focus ring + amt-btn styles. */
.cm-nw-hero { position: relative; overflow: hidden; }
.cm-nw-hero .cm-nw-laurel {
  position: absolute; right: -36px; top: -22px; pointer-events: none; opacity: .06;
}
.cm-nw-amt {
  font-size: 36px; font-weight: var(--weight-black); letter-spacing: -.035em;
  color: var(--text-body); line-height: 1; white-space: nowrap;
}
.cm-nw-badge { font-size: 12px; flex: none; }
.cm-nw-sub { color: var(--muted); }
.cm-nw-chart { position: relative; }
/* The scrub wrap inherits the same no-select / touch-action lock as the hero. */
.cm-nw-scrub { user-select: none; -webkit-user-select: none; touch-action: none; }
/* Series dim transition while a finger is down (set via inline opacity). */
.cm-nw-series { transition: opacity .18s; }
/* Full-bleed push-and-drag surface (mirror .cm-dl-surf). */
.cm-nw-surf {
  position: absolute; inset: 0; touch-action: none; cursor: grab; outline: none;
}
.cm-nw-surf:active { cursor: grabbing; }
.cm-nw-surf:focus-visible { box-shadow: inset 0 0 0 2px var(--ring-focus); border-radius: 8px; }
/* Home net-worth card acting as a link to the Accounts chart (renderNetWorthV2). */
.cm-nw-link { cursor: pointer; }
.cm-nw-link:focus-visible { outline: 2px solid var(--ring-focus); outline-offset: 2px; border-radius: var(--radius-md); }
/* Single riding dot (HTML overlay, % positioned → stays circular under stretch). */
.cm-nw-ride {
  position: absolute; transform: translate(-50%, -50%); border-radius: 50%;
  border: 2px solid var(--surface-card); pointer-events: none;
}
/* (reuses .cm-ride-pulse + @keyframes cmRidePulse from the hero scrub above.) */
/* Value callout floating above the dot — that month's net worth. */
.cm-nw-callout {
  position: absolute; transform: translate(-50%, calc(-100% - 14px));
  background: var(--accent); color: var(--text-on-accent);
  padding: 5px 9px; border-radius: 8px; white-space: nowrap; text-align: center;
  line-height: 1.15; pointer-events: none;
  box-shadow: 0 4px 14px color-mix(in srgb, var(--accent) 32%, transparent);
}
.cm-nw-callout-amt { font-size: 14px; font-weight: var(--weight-black); letter-spacing: -.01em; }
.cm-nw-callout-sub { font-size: 9px; font-weight: var(--weight-semibold); opacity: .85; letter-spacing: .02em; }
/* Callout extra rows — the Assets + Liabilities values under the net-worth headline. */
.cm-nw-callout-row {
  display: flex; align-items: baseline; justify-content: space-between; gap: 10px;
  font-size: 10px; font-weight: var(--weight-semibold); opacity: .92; margin-top: 2px;
}
.cm-nw-callout-row b { font-weight: var(--weight-bold); }
.cm-nw-callout-k { opacity: .8; }
/* Three-series legend swatches (token colors only). */
.cm-hero-sw.cm-nw-sw-net    { background: var(--accent); }
.cm-hero-sw.cm-nw-sw-net.over { background: var(--danger); }
.cm-hero-sw.cm-nw-sw-assets { background: var(--good); }
.cm-hero-sw.cm-nw-sw-liab   { background: var(--amber); }
/* A side riding dot (Assets/Liabilities) — thinner ring than the primary net-worth dot. */
.cm-nw-ride.side { border-width: 1.5px; }
/* Month-strip Assets/Liabilities value accents. */
.cm-nw-month-assets b { color: var(--good); }
.cm-nw-month-liab b   { color: var(--amber); }
/* Month strip (fills on scrub/pin; empty otherwise; min-height reserved so the
   chart/axis don't shift when it appears). */
.cm-nw-monthstrip {
  min-height: 30px; display: flex; align-items: center;
  gap: 10px; flex-wrap: wrap; margin: 6px 0 2px;
}
.cm-nw-month-info { display: flex; align-items: baseline; gap: 10px 14px; flex-wrap: wrap; min-width: 0; }
.cm-nw-month-date { font-size: var(--text-sm); font-weight: var(--weight-bold); color: var(--text-body); }
.cm-nw-month-val { font-size: var(--text-xs); color: var(--muted); }
.cm-nw-month-val b { font-weight: var(--weight-bold); color: var(--text-body); }
.cm-nw-month-delta { font-size: var(--text-xs); color: var(--muted); }
.cm-nw-month-delta b { font-weight: var(--weight-bold); }
.cm-nw-month-delta.up b { color: var(--good); }
.cm-nw-month-delta.down b { color: var(--danger); }
/* Row 2 — the "View transactions" slot (min-height reserved, no reflow). */
.cm-nw-row2 { display: flex; align-items: center; justify-content: center; min-height: 30px; margin-top: 2px; }
.cm-nw-row2-slot { flex: none; }
.cm-nw-month-see { flex: none; font-size: 12px; gap: 5px; padding: 0 9px; height: 30px; min-height: 0; }
/* The NW drilldown reuses #drill-backdrop; like the hero source, hide the header's
   "View Transactions →" replay button (it would replay the wrong Stats query). */
#drill-backdrop[data-source='networth'] .modal-head button.btn { display: none; }

/* Clickable hero total — drills into the full month-to-date ledger sheet. */
.cm-hero-amt-btn {
  display: inline-flex; align-items: center; gap: 2px;
  background: none; border: none; padding: 2px 4px; margin: -2px -4px;
  border-radius: 8px; cursor: pointer; color: inherit;
  transition: background .14s var(--ease-standard);
}
.cm-hero-amt-btn:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); }
.cm-hero-amt-btn:active { transform: translateY(1px); }
.cm-hero-amt-btn:focus-visible { outline: 2px solid var(--ring-focus); outline-offset: 2px; }
.cm-hero-amt-btn .cm-icon, .cm-hero-amt-btn svg { transition: transform .14s var(--ease-standard); }
.cm-hero-amt-btn:hover .cm-icon, .cm-hero-amt-btn:hover svg { transform: translateX(2px); }

/* ── Activity screen V2 (mobile Activity tab) ──────────────────────────────── */
.cm-activityv2 { display: none; flex-direction: column; gap: 14px; }
.cm-act-head { display: flex; align-items: center; justify-content: space-between; }
.cm-act-head h1, .cm-secthead h1 {
  margin: 0; font-size: 22px; font-weight: var(--weight-black);
  letter-spacing: -.01em; color: var(--text-body);
  /* Never break the title mid-word at narrow widths — keep it whole + let the filter-note (flex:1)
     absorb the shrink and ellipsize instead ("Activity", not "Activi/ty"). */
  white-space: nowrap; flex: 0 0 auto;
}
/* (.cm-act-search reuses .cm-iconbtn, shown via the mobile media-query rule.) */

/* Spent / Earned 2-card summary. */
.cm-act-summary { display: flex; gap: 10px; }
.cm-act-stat { flex: 1; min-width: 0; padding: 12px 14px; }
.cm-act-stat .cm-label { font-size: 10px; }
.cm-act-amt { font-size: 18px; font-weight: var(--weight-black); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Very narrow phones (≤360px): step the KPI value + label down so a 6-figure value doesn't clip to
   "$2…" and labels ("SAVE RATE") don't wrap mid-word in the 3-up summary. */
@media (max-width: 360px) {
  .cm-act-amt { font-size: 15px; }
  .cm-act-stat { padding: 11px 10px; }
  .cm-act-stat .cm-label { letter-spacing: 0; white-space: nowrap; }
}
.cm-nw-hidden { height: 80px; display: flex; align-items: center; justify-content: center;
  color: var(--muted); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; border-radius: 10px;
  background: repeating-linear-gradient(45deg, color-mix(in srgb, var(--text-body) 5%, transparent) 0 7px, transparent 7px 14px); }
/* On-track confirmation — a quiet on-theme green check that replaces the old "On track" pills. */
.cm-ok { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px;
  border-radius: 999px; flex: none; color: var(--good);
  background: color-mix(in srgb, var(--good) 15%, transparent); }

/* CompactIncome strip (relocated from the dashboard hero). */
.cm-compact-income { padding: 12px 14px; }
.cm-ci-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.cm-ci-val { font-size: var(--text-sm); font-weight: var(--weight-bold); color: var(--good); }
.cm-ci-val .of { color: var(--muted); font-weight: var(--weight-medium); }
.cm-ci-foot { display: flex; align-items: center; justify-content: space-between; gap: 6px; margin-top: 6px; }
.cm-ci-foot span { font-size: var(--text-xs); color: var(--muted); font-weight: var(--weight-semibold); }

/* Filter rail (pill segmented control; active = surface-card + shadow). */
.cm-act-rail {
  display: flex; gap: 6px; padding: 4px;
  background: var(--surface-sunken); border-radius: var(--radius-pill);
}
.cm-act-pill {
  flex: 1; min-width: 0; padding: 7px 4px; border: none; cursor: pointer;
  border-radius: var(--radius-pill); font: inherit;
  font-size: var(--text-xs); font-weight: var(--weight-bold); white-space: nowrap;
  background: transparent; color: var(--muted);
  transition: background var(--duration-fast) var(--ease-standard);
}
.cm-act-pill.on { background: var(--surface-card); color: var(--accent); box-shadow: var(--shadow-card); }

/* Date-grouped ledger. */
.cm-act-ledger { display: flex; flex-direction: column; gap: 16px; }
.cm-act-group { margin-bottom: 6px; }

/* Filter/search button — a prominent Tyrian-FILLED magnifying glass, standardized
   across the Stats + Activity headers so the filter affordance reads clearly. */
.cm-act-search {
  position: relative;
  background: var(--accent); border-color: var(--accent); color: var(--text-on-accent);
}
.cm-act-search:hover {
  background: color-mix(in srgb, var(--accent) 86%, #000);
  border-color: color-mix(in srgb, var(--accent) 86%, #000);
}
.cm-act-search.has-filters::after {
  content: ""; position: absolute; top: 5px; right: 5px;
  width: 9px; height: 9px; border-radius: 50%;
  background: var(--amber); border: 2px solid var(--accent);   /* gold "active filters" dot, pops on the Tyrian fill */
}

/* ── Wave-1 mobile screens (Profile / Accounts) — host stacks + section heads.
   Hosts are display:none by default; the mobile @media gate (below) flips them to
   flex and hides the desktop siblings. Composed from the existing .cm-* inventory;
   only the page-specific layout glue lives here. ───────────────────────────── */
.cm-profilev2, .cm-accountsv2, .cm-alertsv2, .cm-statsv2, .cm-categoriesv2, .cm-settingsv2 { display: none; flex-direction: column; gap: 14px; }
.cm-prof-head-row h1, .cm-acct-head h1 {
  margin: 0; font-size: 22px; font-weight: var(--weight-black);
  letter-spacing: -.01em; color: var(--text-body);
}

/* ── Wave-2 mobile screens (Alerts / Stats) ──────────────────────────────── */
/* Alerts: per-rule action strip under the meter (toggle + Edit/Delete). */
.cm-rule-actions { display: flex; align-items: center; gap: 6px; margin-top: 12px; }
.cm-rule-actions .switch { font-size: var(--text-xs); }

/* Stats: condensed stat row (tappable → drilldown), full-width like a ledger line. */
.cm-stat-row {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 11px 0; border-bottom: 1px solid var(--border-hairline);
  background: transparent; border-left: none; border-right: none; border-top: none;
  font: inherit; color: inherit; cursor: pointer;
}
.cm-stat-row:last-child { border-bottom: none; }
.cm-stat-row:active { background: color-mix(in srgb, var(--accent) 7%, transparent); }

/* Stats: collapsible section (native <details>; chevron rotates when open). */
.cm-stat-sect { padding: 0; overflow: hidden; }
.cm-stat-summary {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 14px 16px; cursor: pointer; list-style: none; user-select: none;
}
.cm-stat-summary::-webkit-details-marker { display: none; }
.cm-stat-secttitle { font-size: 16px; font-weight: var(--weight-bold); color: var(--accent);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cm-stat-chev { color: var(--muted); flex: none; transition: transform var(--duration-fast) var(--ease-standard); }
.cm-stat-sect[open] .cm-stat-chev { transform: rotate(180deg); }
.cm-stat-body { padding: 0 16px 14px; }
.cm-acct-head { display: flex; align-items: center; justify-content: space-between; }

/* Home accounts card (TASK3): the by-type totals chips live INSIDE the <summary>
   so they show on the COLLAPSED card (two types per line on mobile); the per-type
   subheading inside the body is just the group label (the total is the chip). */
.cm-home-acct-summary { flex-direction: column; align-items: stretch; gap: 12px; }
.cm-home-acct-toprow { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.cm-home-acct-chips {
  display: grid; grid-template-columns: 1fr 1fr; gap: 8px 14px;
  padding: 0;   /* now sits inside the already-padded <summary> */
}
.cm-home-acct-chip {
  display: flex; flex-direction: column; gap: 1px; min-width: 0;
  padding: 8px 10px; border-radius: var(--radius-sm);
  background: var(--surface-sunken);
}
.cm-home-acct-chip .k {
  font-size: var(--text-xs); color: var(--muted); font-weight: var(--weight-semibold);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cm-home-acct-chip .v { font-size: var(--text-sm); font-weight: var(--weight-bold); color: var(--text-body); }
.cm-home-acct-chip .v.neg { color: var(--amber); }
.cm-home-acct-grouph {
  display: flex; align-items: baseline; justify-content: space-between; gap: 8px;
  margin: 4px 0 2px; padding-top: 6px;
  font-size: var(--text-xs); font-weight: var(--weight-bold); letter-spacing: .02em;
  text-transform: uppercase; color: var(--muted);
}
.cm-home-acct-grouph:first-child { padding-top: 0; }
.cm-home-acct-grouph .cm-tnum { color: var(--text-body); text-transform: none; letter-spacing: 0; }
.cm-home-acct-grouph .cm-tnum.neg { color: var(--amber); }

/* Profile header card. */
.cm-prof-head { display: flex; align-items: center; gap: 12px; }
.cm-prof-name {
  font-size: var(--text-md); font-weight: var(--weight-bold); color: var(--text-body);
  overflow-wrap: anywhere; word-break: break-word;   /* let a long email wrap rather than ellipsize the primary identity */
}
/* Stacked rows (members / pending / accounts) inside a card. */
.cm-prof-rows { display: flex; flex-direction: column; gap: 10px; }

/* Accounts net-position card + group-by control. */
.cm-acct-net { position: relative; overflow: hidden; }
.cm-acct-net-val {
  font-size: var(--text-2xl); font-weight: var(--weight-black); letter-spacing: -.02em;
  line-height: 1.1; margin: 6px 0 12px; position: relative; color: var(--text-body);
}
.cm-acct-net-badges { display: flex; flex-wrap: wrap; gap: 8px; position: relative; }
.cm-acct-groupby { display: flex; align-items: center; gap: 10px; }
.cm-acct-groupby select { flex: 1; }
.cm-acct-group { display: flex; flex-direction: column; }
.cm-acct-grouptotal { font-size: var(--text-sm); font-weight: var(--weight-bold); color: var(--text-body); }
.cm-acct-grouptotal.neg { color: var(--amber); }
.cm-acct-excluded { opacity: .62; }

/* ── Wave-3 mobile screen (Categories) ───────────────────────────────────── */
/* Each category is a full-width card: label on top, stacked toggles, then (for
   custom categories) a Rename/Delete action group. Composed from .cm-card + .tgl. */
.cm-catv2 { padding: 14px 16px; }
.cm-catv2-head { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }
.cm-catv2-name { font-size: var(--text-md); font-weight: var(--weight-bold); color: var(--text-body); }
.cm-catv2-tags { display: inline-flex; flex-wrap: wrap; gap: 6px; }
/* Toggles STACKED (not horizontal): label on the left, switch on the right. */
.cm-catv2-toggles { display: flex; flex-direction: column; gap: 10px; margin-top: 12px; }
.cm-catv2-tgl { display: flex; align-items: center; justify-content: space-between; gap: 12px; width: 100%; }
.cm-catv2-tgl-lbl { flex: 1; min-width: 0; font-size: var(--text-sm); color: var(--text-body); }
/* A locked category's toggles are non-interactive — dim them to read as disabled. */
.cm-catv2-tgl input:disabled ~ .tgl-track,
.cm-catv2-tgl:has(input:disabled) { opacity: .72; cursor: default; }
.cm-catv2-actions { display: flex; gap: 8px; margin-top: 14px; }
.cm-catv2-actions .cm-btn { flex: 1; }
/* The auto-rule toggle row inside the collapsible Categorization-rules card. */
.cm-catv2-autorule { display: flex; align-items: center; gap: 12px;
  border: 1px solid var(--border); border-radius: var(--radius-md); padding: 11px 13px; }
/* Mobile categorization-rule rows: summary on top, action strip below. */
.cm-crulev2-top { display: flex; align-items: flex-start; gap: 8px; min-width: 0; }
.cm-crulev2-desc { font-size: var(--text-sm); color: var(--text-body); min-width: 0; line-height: 1.4; }

/* ── Suggested-recurring: compact trigger + reformatted modal rows ──────────── */
/* Slim one-line banner/button rendered into #sub-suggestions; opens the modal. */
.cm-suggest-trigger {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  width: 100%; margin-bottom: 16px; padding: 11px 14px; cursor: pointer;
  border: 1px solid color-mix(in srgb, var(--accent) 35%, var(--border-hairline));
  border-radius: var(--radius-md); font: inherit; text-align: left;
  background: color-mix(in srgb, var(--accent) 7%, var(--surface-card));
  color: var(--text-body);
  transition: background var(--duration-fast) var(--ease-standard);
}
.cm-suggest-trigger:hover { background: color-mix(in srgb, var(--accent) 12%, var(--surface-card)); }
.cm-suggest-trig-l { display: inline-flex; align-items: center; gap: 8px; min-width: 0;
  font-size: var(--text-sm); font-weight: var(--weight-semibold); }
.cm-suggest-trig-l i[data-lucide] { color: var(--accent); flex: none; }
.cm-suggest-trig-r { display: inline-flex; align-items: center; gap: 4px; flex: none;
  font-size: var(--text-xs); font-weight: var(--weight-bold); color: var(--accent); }

/* Modal rows: merchant gets the width; actions ~a third, comfortably tappable. */
.cm-suggest-row {
  display: flex; align-items: center; gap: 12px;
  padding: 12px 0; border-top: 1px solid var(--border-hairline);
  cursor: pointer; border-radius: 8px;
}
.cm-suggest-row:hover { background: color-mix(in srgb, var(--accent) 5%, transparent); }
.cm-suggest-row:first-child { border-top: none; }
.cm-suggest-chip {
  width: 36px; height: 36px; flex: none; border-radius: var(--radius-sm);
  display: inline-flex; align-items: center; justify-content: center;
  background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent);
}
.cm-suggest-main { flex: 1 1 auto; min-width: 0; }
.cm-suggest-name {
  font-weight: var(--weight-bold); font-size: 15px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Tonal "Add" — clearly actionable but not a solid block, so a list of them doesn't read as a wall. */
.cm-suggest-actions .cm-btn.cm-suggest-add {
  background: color-mix(in srgb, var(--accent) 12%, transparent); color: var(--accent); border: none;
}
.cm-suggest-actions .cm-btn.cm-suggest-add:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); }
.cm-suggest-meta { font-size: var(--text-xs); color: var(--muted); margin-top: 2px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.cm-suggest-actions { display: flex; align-items: center; gap: 6px; flex: 0 0 auto; }
.cm-suggest-actions .cm-btn.sm { min-height: 36px; }
/* Higher specificity than .cm-btn.ghost (which sets --brand) so the dismiss × stays a quiet
   grey and doesn't compete with the tonal Add. */
.cm-suggest-actions .cm-btn.cm-suggest-dismiss {
  width: 36px; padding: 0; font-size: 18px; line-height: 1; color: var(--muted);
}

/* Bank-link launch overlay — hidden by default (DESKTOP never shows it). The full
   fixed-overlay styling lives inside the mobile @media block below; here we only
   ensure the element is inert on desktop (mirrors .cm-bnav/.cm-sheet-scrim base). */
.cm-link-ov { display: none; }

/* ── Mobile gate: show the V2 dashboard + eye toggle, hide the desktop one ──
   A landscape phone (≈844×390) is WIDER than 760px but SHORT — also treat short
   viewports as mobile (OR with max-height) so rotating keeps the mobile UI. Keep
   this list in lockstep with the index.html shell media query + the JS matchMedia
   gates (ACTIVITY_MQ / DASHV2_MQ). */
@media (max-width: 760px), ((max-height: 540px) and (max-width: 1024px)) {
  #panel-dashboard.active .cm-dashv2 { display: flex; }
  /* When the V2 host is active, hide the desktop dashboard cards on mobile.
     !important so it also overrides inline display styles (e.g. the heading row). */
  #panel-dashboard.active.v2 > :not(.cm-dashv2) { display: none !important; }
  .cm-iconbtn { display: inline-flex; }

  /* Activity: show the V2 screen, hide the desktop filter bar + table. */
  #panel-transactions.active .cm-activityv2 { display: flex; }
  #panel-transactions.active.v2 > :not(.cm-activityv2) { display: none !important; }

  /* Profile (Wave 1): show the V2 screen, hide the desktop cards. */
  #panel-profile.active .cm-profilev2 { display: flex; }
  #panel-profile.active.v2 > :not(.cm-profilev2) { display: none !important; }

  /* Accounts (Wave 1): show the V2 screen, hide the desktop net-position + list. */
  #panel-accounts.active .cm-accountsv2 { display: flex; }
  #panel-accounts.active.v2 > :not(.cm-accountsv2) { display: none !important; }

  /* Alerts (Wave 2): show the V2 screen, hide the desktop head + wizard + list. */
  #panel-alerts.active .cm-alertsv2 { display: flex; }
  #panel-alerts.active.v2 > :not(.cm-alertsv2) { display: none !important; }

  /* Stats (Wave 2): show the V2 screen, hide the desktop range-bar + body. */
  #panel-stats.active .cm-statsv2 { display: flex; }
  #panel-stats.active.v2 > :not(.cm-statsv2) { display: none !important; }

  /* Categories (Wave 3): show the V2 screen, hide the desktop cards + rule editor. */
  #panel-categories.active .cm-categoriesv2 { display: flex; }
  #panel-categories.active.v2 > :not(.cm-categoriesv2) { display: none !important; }

  /* Settings (Wave 4): show the V2 screen, hide the desktop title/cards. The desktop
     cards are MOVED inside #settingsv2 on mobile, so they show as the collapsibles'
     bodies; the hide rule targets only the panel's *direct* children that aren't the
     host (the moved cards are now descendants of the host, not direct children). */
  #panel-settings.active .cm-settingsv2 { display: flex; }
  #panel-settings.active.v2 > :not(.cm-settingsv2) { display: none !important; }

  /* A desktop card moved into a collapsible section body sheds its own card chrome
     (border / radius / shadow / padding) so it sits flush — the surrounding <details>
     .cm-card already supplies the surface. The inner section padding still applies. */
  .cm-setv2-slot > .cm-card {
    border: none; border-radius: 0; box-shadow: none; background: transparent;
    padding: 0; margin: 0;
  }
  .cm-setv2-slot { padding-top: 10px; }   /* breathing room below the <summary> hairline */
  .cm-setv2-signout { margin-top: 4px; color: var(--danger); }
  .cm-setv2-signout i[data-lucide] { color: var(--danger); }

  /* The Security / Appearance / Monthly-budget / Notifications cards each lead with their own
     seal+heading row; inside a collapsible that title is already in the <summary>, so hide the
     redundant inner one (mobile-only; the desktop card is untouched). Linked-accounts leads with
     a .panel-head (title row + Connect button) — hide just the title row there so the button
     survives (a bare div:first-child rule would hide the whole panel-head, button included). */
  #setv2-slot-security > .cm-card > div:first-child,
  #setv2-slot-appearance > .cm-card > div:first-child,
  #setv2-slot-budget > .cm-card > div:first-child,
  #setv2-slot-notifications > .cm-card > div:first-child { display: none; }
  #setv2-slot-banks > .cm-card > .panel-head > .modal-head-row { display: none; }

  /* ── Bank-link launch overlay (Wave-4) ──────────────────────────────────────
     A branded, full-screen scrim shown the instant the user taps Connect-a-bank,
     covering the gap while the Plaid Link SDK downloads + initialises. It is
     DISMISSED the moment Plaid's own iframe opens (and on exit/error/success).
     This is a WRAPPER around the vendor flow only — it never touches tokens. The
     copy is the security voice (plain, emerald, no laurel inside the trust note;
     the laurel here is a decorative brand watermark only). Mobile-only by virtue
     of living inside this @media block; the JS also gates on isMobile(). */
  .cm-link-ov {
    position: fixed; inset: 0; z-index: 90;   /* above the bottom-nav (60) + sheets (75) */
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 18px; padding: 28px calc(28px + env(safe-area-inset-left, 0px)) calc(28px + env(safe-area-inset-bottom, 0px));
    text-align: center;
    background: color-mix(in srgb, var(--bg) 94%, transparent);
    -webkit-backdrop-filter: blur(10px) saturate(1.2); backdrop-filter: blur(10px) saturate(1.2);
    opacity: 0; pointer-events: none; transition: opacity var(--duration-base, .26s) var(--ease-standard, ease);
  }
  .cm-link-ov.open { opacity: 1; pointer-events: auto; }
  .cm-link-ov-laurel {     /* decorative brand watermark behind the spinner */
    position: absolute; top: 50%; left: 50%; transform: translate(-50%, -64%);
    color: var(--brand-laurel); opacity: .10; pointer-events: none;
  }
  .cm-link-ov-spin {
    position: relative; width: 46px; height: 46px; border-radius: 50%;
    border: 3px solid color-mix(in srgb, var(--accent) 22%, transparent);
    border-top-color: var(--accent); animation: cm-link-spin .9s linear infinite;
  }
  @keyframes cm-link-spin { to { transform: rotate(360deg); } }
  .cm-link-ov-title {
    position: relative; font-size: var(--text-md); font-weight: var(--weight-bold);
    color: var(--text-heading, var(--text-body)); max-width: 22ch;
  }
  .cm-link-ov-sub { position: relative; font-size: var(--text-sm); color: var(--text-muted); max-width: 28ch; }
  /* Trust note — the security voice; plain + emerald, never laurel-decorated. */
  .cm-link-ov .cm-trustnote { position: relative; justify-content: center; max-width: 30ch; }
  .cm-link-ov-cancel { position: relative; margin-top: 4px; }
  @media (prefers-reduced-motion: reduce) {
    .cm-link-ov-spin { animation-duration: 2.4s; }
    .cm-link-ov { transition: none; }
  }

  /* ── Drill-down → vertical drawer (bottom sheet) ───────────────────────────
     The shared drill-down (#drill-backdrop) — Stats by-category/merchant/account
     + top-purchases, the hero day/month ledgers, and the net-worth per-month /
     full-range drilldowns — becomes a bottom sheet on mobile: scrim behind, the
     sheet anchored to the viewport bottom, slides UP, rounded top corners, a
     Tyrian top rule, body scrolls inside. Scoped to #drill-backdrop only, so the
     OTHER .modal-backdrop sheets (rule-editor / crule-editor / stats-filter /
     activity-filter / recur-suggest) keep the existing full-screen mobile modal
     defined in templates/index.html. These rules OVERRIDE that full-screen block
     for #drill-backdrop specifically. */
  #drill-backdrop {
    padding: 0;
    align-items: flex-end;          /* anchor the sheet to the bottom */
    /* Above the bottom nav (z 60), the sheet-scrim (70) + the More sheet (75) — else the
       fixed bottom nav overlays the sheet and clips its last row. */
    z-index: 90;
    background: color-mix(in srgb, #1c1410 52%, transparent);   /* calm dark scrim */
  }
  #drill-backdrop .modal {
    max-width: none;
    min-height: 0;                  /* undo the full-screen min-height:100vh */
    max-height: 85vh;
    display: flex;
    flex-direction: column;
    border: none;
    border-top: 3px solid var(--accent);          /* Tyrian top rule */
    border-radius: 20px 20px 0 0;                 /* rounded TOP corners */
    box-shadow: 0 -16px 44px rgba(26, 16, 20, .28);
    transform: translateY(100%);                  /* parked below the viewport */
    transition: transform var(--duration-base, .26s) var(--ease-standard, ease);
    padding-bottom: env(safe-area-inset-bottom, 0px);
    overflow: hidden;               /* the body scrolls, not the sheet */
  }
  #drill-backdrop.show .modal { transform: translateY(0); }   /* slide up */
  /* The backdrop toggles display:none→flex, so the slide needs an explicit
     starting frame to animate from on open. @starting-style supplies it on the
     browsers that ship it (recent WebKit/Chromium = the mobile targets); older
     engines simply snap into place at the bottom — still a bottom sheet. */
  @starting-style {
    #drill-backdrop.show .modal { transform: translateY(100%); }
  }
  #drill-backdrop .modal-head {
    border-radius: 20px 20px 0 0;
    flex: 0 0 auto;                 /* pinned head stays put while the body scrolls */
  }
  #drill-backdrop .modal-body {
    flex: 1 1 auto;
    overflow-y: auto;
    -webkit-overflow-scrolling: touch;
  }
  @media (prefers-reduced-motion: reduce) {
    #drill-backdrop .modal { transition: none; }
  }
}
