
  :root{
    --page:#EEF0EF;
    --surface:#FFFFFF;
    --surface-2:#F1F3F1;
    --text-primary:#16181B;
    --text-secondary:#6B7280;
    --text-muted:#9CA3AF;
    --border:#E5E7EB;
    --border-strong:#C7CBD1;
    --teal:#0F8F6B;
    --teal-deep:#0B6E52;
    --teal-bg:#E1F5EE;
    --teal-rgb:15,143,107;
    --glass-bg:rgba(255,255,255,.28);
    --teal-text:#0B6E52;
    --danger:#E24B4A;
    --danger-bg:#FCEBEB;
    --danger-text:#A32D2D;
    --info-text:#2563EB;
    --status-active:#16A34A;
    --status-no-loan:#9CA3AF;
    --status-pending:#D9A404;
    --status-suspended:#DC2626;
    --status-blacklisted:#111111;
    --radius:12px;
  }
  [data-theme="dark"]{
    --page:#121417;
    --surface:#1C1F23;
    --surface-2:#262A2F;
    --text-primary:#F0F1F3;
    --text-secondary:#9CA3AF;
    --text-muted:#6B7280;
    --border:#33383E;
    --border-strong:#4A5058;
    --teal:#14A67D;
    --teal-deep:#0F8F6B;
    --teal-bg:#10332A;
    --teal-rgb:20,166,125;
    --glass-bg:rgba(28,31,35,.28);
    --teal-text:#34D399;
    --danger:#F87171;
    --danger-bg:#3B1615;
    --danger-text:#F87171;
    --info-text:#60A5FA;
    --status-active:#22C55E;
    --status-no-loan:#6B7280;
    --status-pending:#EAB308;
    --status-suspended:#EF4444;
    --status-blacklisted:#E5E7EB;
  }
  [data-theme="neon"]{
    --page:#070B14;
    --glass-bg:rgba(14,21,32,.28);
    --surface:#0E1520;
    --surface-2:#141F30;
    --text-primary:#E8F4FF;
    --text-secondary:#7C93B8;
    --text-muted:#4A5D7A;
    --border:#1E2C42;
    --border-strong:#2E4568;
    --teal:#00D9FF;
    --teal-deep:#00A8CC;
    --teal-bg:#062330;
    --teal-text:#4DEEFF;
    --teal-rgb:0,217,255;
    --glow2-rgb:0,255,157;
    --danger:#FF3B5C;
    --danger-bg:#2A0A16;
    --danger-text:#FF6B85;
    --info-text:#5B8DEF;
    --status-active:#00FF9D;
    --status-no-loan:#4A5D7A;
    --status-pending:#FFB800;
    --status-suspended:#FF3B5C;
    --status-blacklisted:#E8F4FF;
  }
  [data-theme="aurora"]{
    --glass-bg:rgba(16,21,20,.28);
    --page:#050409;
    --surface:#0D0C14;
    --surface-2:#16151F;
    --text-primary:#E8FFFC;
    --text-secondary:#7A9490;
    --text-muted:#445250;
    --border:#1A2826;
    --border-strong:#2A423E;
    --teal:#14DCC8;
    --teal-deep:#0FA894;
    --teal-bg:#0A2620;
    --teal-text:#2EFFE0;
    --teal-rgb:20,220,200;
    --glow2-rgb:46,255,180;
    --danger:#FF3B5C;
    --danger-bg:#2A0A16;
    --danger-text:#FF6B85;
    --info-text:#4FA8FF;
    --status-active:#14DCC8;
    --status-no-loan:#445250;
    --status-pending:#FFB800;
    --status-suspended:#FF3B5C;
    --status-blacklisted:#E8FFFC;
  }
  [data-theme="gold"]{
    --page:#0A0A0C;
    --surface:#141317;
    --surface-2:#1D1B1F;
    --text-primary:#F5F1E8;
    --text-secondary:#9C9690;
    --text-muted:#6B655F;
    --border:#2A2620;
    --border-strong:#3D3730;
    --teal:#D4AF37;
    --teal-deep:#B8860B;
    --teal-bg:#2A2210;
    --teal-rgb:212,175,55;
    --glass-bg:rgba(20,19,23,.28);
    --teal-text:#F5D061;
    --danger:#F87171;
    --danger-bg:#3B1615;
    --danger-text:#F87171;
    --info-text:#6FA8DC;
    --status-active:#22C55E;
    --status-no-loan:#6B655F;
    --status-pending:#EAB308;
    --status-suspended:#EF4444;
    --status-blacklisted:#F5F1E8;
    --glow2-rgb:245,208,97;
  }
  /* ===== Apple Liquid Glass theme (Standing Directives #1 & #3) =====
     Design-system-first, per the owner's explicit sequencing rule: this
     block defines the tokens; the component rules further below apply
     those tokens to the SHARED classes every screen already uses
     (.sheet, .glass-card, .btn-primary/.btn-secondary, header, .dc-card,
     .c360-card, inputs) rather than styling any screen individually --
     so every screen inherits the system automatically, with zero
     per-screen bespoke code. Cool, translucent, high-clarity palette --
     deliberately distinct from Gold's warm restrained glow, since this
     is meant to read as glass/light rather than metal/gold. */
  /* ===== Eclipse theme: cinematic black+gold, halo-ring motif =====
     Reference-inspired (owner-provided moodboard, 2026-07-19): deep
     near-black, a bold warm gold (bolder/brighter than the restrained
     "Gold" theme, which was deliberately muted), and a signature glowing
     "halo ring" motif behind portraits/hero moments -- the visual
     through-line of the reference image. Brothers+Cash identity is kept
     (own logo, own copy, own data) -- only the *treatment* is inspired
     by the reference, per Directive #1's "take inspiration, don't copy." */
  [data-theme="eclipse"]{
    --page:#0A0A0D;
    --surface:#131316;
    --surface-2:#1C1B1F;
    --text-primary:#F5F3EE;
    --text-secondary:#9C978C;
    --text-muted:#68635A;
    --border:rgba(244,192,91,.14);
    --border-strong:rgba(244,192,91,.28);
    --teal:#F4C05B;
    --teal-deep:#D89B3C;
    --teal-bg:rgba(244,192,91,.14);
    --teal-rgb:244,192,91;
    --teal-text:#F9D583;
    --glass-bg:rgba(19,19,22,.28);
    --danger:#E85C5C;
    --danger-bg:rgba(232,92,92,.14);
    --danger-text:#F08080;
    --info-text:#6FA8DC;
    --status-active:#4ADE80;
    --status-no-loan:#68635A;
    --status-pending:#F4C05B;
    --status-suspended:#E85C5C;
    --status-blacklisted:#F5F3EE;
    --glow2-rgb:249,213,131;
    --halo-color: 244,192,91;
  }
  /* ===== Prism theme (2026-08-10, direct owner requirement): the new,
     separate, toggleable theme -- glass overdue banners, a spaced-out
     loan list with the real logo visible through the gaps, and a
     swipeable status/risk chart above the banners. Base palette is the
     same as Dark, since every approved mockup was built against these
     exact colors. Component-specific overrides follow further below,
     scoped the same way Eclipse's own are. ===== */
  [data-theme="prism"]{
    --page:#121417;
    --surface:#1C1F23;
    --surface-2:#262A2F;
    --text-primary:#F0F1F3;
    --text-secondary:#9CA3AF;
    --text-muted:#6B7280;
    --border:#33383E;
    --border-strong:#4A5058;
    --teal:#14A67D;
    --teal-deep:#0F8F6B;
    --teal-bg:#10332A;
    --teal-rgb:20,166,125;
    --glass-bg:rgba(28,31,35,.28);
    --teal-text:#34D399;
    --danger:#F87171;
    --danger-bg:#3B1615;
    --danger-text:#F87171;
    --info-text:#60A5FA;
    --status-active:#22C55E;
    --status-no-loan:#6B7280;
    --status-pending:#EAB308;
    --status-suspended:#EF4444;
    --status-blacklisted:#E5E7EB;
  }
  /* ===== Prism Light theme (2026-08-10, direct owner requirement): the
     same design as Prism, on the light base palette instead of dark.
     Structural rules (swipe mechanics, layout, spacing) are shared with
     dark Prism via :is() below -- only the hardcoded translucent-color
     rules (banner glass, list row glass, ring card) get a light-specific
     counterpart, since those can't use CSS variables the same way. ===== */
  [data-theme="prism-light"]{
    --page:#EEF0EF;
    --surface:#FFFFFF;
    --surface-2:#F1F3F1;
    --text-primary:#16181B;
    --text-secondary:#6B7280;
    --text-muted:#9CA3AF;
    --border:#E5E7EB;
    --border-strong:#C7CBD1;
    --teal:#0F8F6B;
    --teal-deep:#0B6E52;
    --teal-bg:#E1F5EE;
    --teal-rgb:15,143,107;
    --glass-bg:rgba(255,255,255,.28);
    --teal-text:#0B6E52;
    --danger:#E24B4A;
    --danger-bg:#FCEBEB;
    --danger-text:#A32D2D;
    --info-text:#2563EB;
    --status-active:#16A34A;
    --status-no-loan:#9CA3AF;
    --status-pending:#D9A404;
    --status-suspended:#DC2626;
    --status-blacklisted:#111111;
  }
  [data-theme="glass"]{
    --page:#0B0D12;
    --surface:#12151C;
    --surface-2:#1A1E27;
    --text-primary:#F2F5F8;
    --text-secondary:#9AA5B4;
    --text-muted:#5E6878;
    --border:rgba(255,255,255,.10);
    --border-strong:rgba(255,255,255,.20);
    --teal:#5FB8FF;
    --teal-deep:#3B8FDB;
    --teal-bg:rgba(95,184,255,.14);
    --teal-rgb:95,184,255;
    --teal-text:#8FD0FF;
    --glass-bg:rgba(20,23,31,.28);
    --danger:#FF6B6B;
    --danger-bg:rgba(255,107,107,.14);
    --danger-text:#FF8A8A;
    --info-text:#4C7EFF;
    --status-active:#3DDC97;
    --status-no-loan:#5E6878;
    --status-pending:#FFC94D;
    --status-suspended:#FF6B6B;
    --status-blacklisted:#F2F5F8;
    --glow2-rgb:143,208,255;
    /* Liquid-Glass-specific tokens (new -- no other theme needs these,
       so they're only defined here rather than bloating :root for
       everyone). Component rules below reference them by name. */
    --glass-blur: 26px;
    --glass-blur-soft: 16px;
    --glass-saturate: 1.7;
    --glass-radius-lg: 20px;
    --glass-radius-md: 14px;
    --glass-spring: cubic-bezier(.34,1.4,.64,1);
    --glass-ease: cubic-bezier(.22,.61,.36,1);
    --glass-highlight: rgba(255,255,255,.14);
  }
  *{box-sizing:border-box;}

  /* Plain Mode (2026-07-21): a real, deliberate low-cost mode for
     lower-end devices -- not a cosmetic toggle. backdrop-filter blur is
     one of the most GPU-expensive operations that exists, and every
     glass surface in this app (header, cards, sheets, dialogs -- 23
     separate declarations) references these same few custom
     properties, so zeroing them here turns off the actual expensive
     work everywhere at once, instead of fighting specificity on 23
     individual rules. Motion durations are also collapsed to
     near-instant, since a slide animation over a now-unblurred
     background is cheap on its own -- the blur was always the real
     cost, but removing motion too gives the plainest, safest baseline
     for genuinely low-end hardware. */
  [data-motion="plain"]{
    --glass-blur: 0px;
    --glass-blur-soft: 0px;
    --glass-saturate: 1;
  }
  [data-motion="plain"] *{
    backdrop-filter: none !important;
    -webkit-backdrop-filter: none !important;
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
  }
  /* Respects the OS-level accessibility setting automatically, even if
     the person never finds or touches the in-app toggle -- the same
     real-world scenario as Plain Mode (return the cheapest possible
     render path), just triggered by a different signal. */
  @media (prefers-reduced-motion: reduce){
    *{
      transition-duration: .01ms !important;
      animation-duration: .01ms !important;
    }
  }
  /* REAL BUG (2026-08-27, direct owner report: turning Plain Mode ON made
     the ambient particles "go crazy"/flicker instead of calming down --
     inconsistent between his device and a friend's Android, smooth on
     one, frantic on the other). Root cause: compressing
     animation-duration to .01ms above is the right move for a one-shot
     transition (a sheet sliding open just finishes almost instantly),
     but wrong for anything with animation-iteration-count:infinite --
     an ambient particle float, a twinkle, a rotating halo. An .01ms
     duration doesn't stop an infinite loop, it replays the whole loop up
     to ~100,000 times a second, and which phase of that loop actually
     gets painted on a given repaint is not something the CSS spec pins
     down -- different browsers/GPUs land on different-looking chaos,
     which is exactly why this looked fine on one device and frantic on
     another rather than failing the same way everywhere.
     This file already had the correct fix for the OS-level
     prefers-reduced-motion signal on SOME of these elements --
     `animation:none` (removes animation-name entirely, so the .01ms
     duration above has nothing left to apply to and is harmless) -- see
     .glass-ambient-particle/.vip-glow/.sheet-hero-title/.eclipse-halo/
     .dc-hero-particle below. It was just never mirrored onto
     [data-motion="plain"], and two more infinite loops
     (.login-star-particle, .nl-header-star) had no override under
     EITHER signal. Every [data-motion="plain"] sibling added at each of
     those sites below closes this gap; this comment is the single place
     explaining why. */

  /* ===== Gold theme: restrained liquid-glass + gold glow (no neon-style
     flashing -- per the design docs, "premium" means refined/restrained) ===== */
  [data-theme="gold"] .btn-primary{
    background:linear-gradient(135deg,#F5D061,#D4AF37 55%,#B8860B);
    color:#171310;box-shadow:0 2px 10px -2px rgba(var(--teal-rgb),.45), inset 0 1px 0 rgba(255,255,255,.25);
  }
  [data-theme="gold"] .btn-primary:active{filter:brightness(.94);}
  [data-theme="gold"] header{box-shadow:0 1px 0 rgba(var(--teal-rgb),.12), 0 8px 24px -12px rgba(0,0,0,.6);}
  [data-theme="gold"] .brand-mark, [data-theme="gold"] .login-mark{
    box-shadow:0 0 0 1px rgba(var(--teal-rgb),.35), 0 2px 10px -2px rgba(var(--teal-rgb),.4);
  }
  [data-theme="gold"] .glass-card, [data-theme="gold"] .sheet{
    box-shadow:0 1px 0 rgba(var(--teal-rgb),.08), 0 12px 32px -16px rgba(0,0,0,.7);
  }
  /* Gold-theme .due-banner-header.overdue border-left override removed
     (2026-08-21, part of the continuous-accent fix -- see the
     .due-banner:has(...) comment near .due-banner). It set the exact
     same var(--danger-text) the base rule already resolved to (every
     theme, gold included, defines its own --danger-text), so it was a
     no-op even before this change; left in place it would have
     re-introduced a header-only accent for gold specifically now that
     the header's own border-left has been removed in favor of the
     container-level one. */
  /* ===== end gold theme ===== */

  /* ===== Apple Liquid Glass theme: component layer =====
     Every rule below targets a SHARED class already used across every
     screen (header, .sheet, .glass-card, .c360-card, .dc-card,
     .btn-primary/.btn-secondary, inputs, .loan-row/.opstats-row) --
     deliberately no screen-specific selectors. This is what makes the
     design system apply "everywhere at once": elevate the shared
     component, every consumer of it inherits the change automatically. */

  [data-theme="glass"] body{
    background:
      radial-gradient(120% 90% at 15% -10%, rgba(var(--teal-rgb),.10), transparent 55%),
      radial-gradient(100% 80% at 100% 100%, rgba(143,120,255,.07), transparent 60%),
      var(--page);
  }

  /* Header -- floating glass toolbar, heavier blur than the base theme,
     with a soft top highlight to read as a real glass edge catching light. */
  [data-theme="glass"] header{
    background:var(--glass-bg);
    backdrop-filter:blur(var(--glass-blur)) saturate(var(--glass-saturate));
    -webkit-backdrop-filter:blur(var(--glass-blur)) saturate(var(--glass-saturate));
    border:1px solid var(--border);
    box-shadow:
      inset 0 1px 0 var(--glass-highlight),
      0 8px 32px -12px rgba(0,0,0,.55),
      0 1px 0 rgba(var(--teal-rgb),.10);
  }
  [data-theme="glass"] header.header-elevated{
    backdrop-filter:blur(calc(var(--glass-blur) + 6px)) saturate(calc(var(--glass-saturate) + .2));
    -webkit-backdrop-filter:blur(calc(var(--glass-blur) + 6px)) saturate(calc(var(--glass-saturate) + .2));
  }
  [data-theme="glass"] .brand-mark, [data-theme="glass"] .login-mark{
    box-shadow:inset 0 1px 0 var(--glass-highlight), 0 0 0 1px var(--border), 0 4px 14px -4px rgba(var(--teal-rgb),.35);
  }

  /* Sheets/dialogs -- real glass, not solid. Every .sheet in the app
     (New Loan, Customer Profile, Analytics, Document Center, Settings,
     the viewer, list-sheet, etc.) is the SAME element/class, so this one
     rule reaches every one of them. */
  [data-theme="glass"] .sheet{
    background:
      radial-gradient(ellipse 600px 240px at 20% 0%, rgba(var(--teal-rgb),.10), transparent 70%),
      var(--glass-bg);
    backdrop-filter:blur(var(--glass-blur)) saturate(var(--glass-saturate));
    -webkit-backdrop-filter:blur(var(--glass-blur)) saturate(var(--glass-saturate));
    border-top:1px solid var(--border-strong);
    box-shadow:inset 0 1px 0 var(--glass-highlight), 0 -16px 48px -20px rgba(0,0,0,.6);
    border-radius:var(--glass-radius-lg) var(--glass-radius-lg) 0 0;
    transition:transform .55s var(--glass-spring), opacity .4s ease, filter .2s ease;
  }
  [data-theme="glass"] .sheet.sheet-layered{
    box-shadow:inset 0 1px 0 var(--glass-highlight), 0 -8px 32px -12px rgba(0,0,0,.5), 0 0 0 1px var(--border);
  }

  /* Cards -- .glass-card, .c360-card, .dc-card, .dc-customer-card all
     become genuine layered glass panels instead of flat tinted boxes. */
  [data-theme="glass"] .glass-card, [data-theme="glass"] .c360-card,
  [data-theme="glass"] .dc-card, [data-theme="glass"] .dc-customer-card{
    background:var(--glass-bg);
    backdrop-filter:blur(var(--glass-blur-soft)) saturate(var(--glass-saturate));
    -webkit-backdrop-filter:blur(var(--glass-blur-soft)) saturate(var(--glass-saturate));
    border:1px solid var(--border);
    box-shadow:inset 0 1px 0 var(--glass-highlight), 0 12px 32px -18px rgba(0,0,0,.55);
    border-radius:var(--glass-radius-md);
  }
  @media (hover:hover){
    [data-theme="glass"] .dc-card:hover{
      border-color:rgba(var(--teal-rgb),.45);
      box-shadow:inset 0 1px 0 var(--glass-highlight), 0 16px 40px -16px rgba(var(--teal-rgb),.28);
    }
  }

  /* Buttons -- glass sheen + spring compression instead of a flat gradient press. */
  [data-theme="glass"] .btn-primary{
    background:linear-gradient(180deg, rgba(255,255,255,.16), rgba(255,255,255,0) 40%), linear-gradient(180deg, var(--teal), var(--teal-deep));
    box-shadow:inset 0 1px 0 rgba(255,255,255,.35), 0 6px 18px -6px rgba(var(--teal-rgb),.55);
    transition:transform .18s var(--glass-spring), box-shadow .18s var(--glass-ease);
  }
  [data-theme="glass"] .btn-primary:active{transform:scale(.965);box-shadow:inset 0 1px 4px rgba(0,0,0,.25);}
  [data-theme="glass"] .btn-secondary{
    background:rgba(255,255,255,.05);
    backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px);
    border-color:var(--border);
  }
  @media (hover:hover){
    [data-theme="glass"] .btn-secondary:hover:not(:disabled){background:rgba(255,255,255,.09);border-color:var(--border-strong);}
  }

  /* Inputs / search -- subtle glow on focus, shared across every search
     bar and text field in the app (login, customer search, Document
     Center's search, Activity Center's search all use plain <input>). */
  [data-theme="glass"] input:focus, [data-theme="glass"] select:focus, [data-theme="glass"] textarea:focus{
    outline:none;
    border-color:rgba(var(--teal-rgb),.55);
    box-shadow:0 0 0 3px rgba(var(--teal-rgb),.18), inset 0 1px 0 rgba(255,255,255,.05);
    transition:box-shadow .22s var(--glass-ease), border-color .22s var(--glass-ease);
  }

  /* Toggles -- ios-toggle already exists app-wide (fund source, the new
     Collateral toggle); give the active track a soft glow to match. */
  [data-theme="glass"] .ios-toggle input:checked ~ .ios-toggle-track{
    box-shadow:0 0 10px -1px rgba(var(--teal-rgb),.6);
  }

  /* Tables / list rows -- .loan-row, .opstats-row, .link-row, .dc-doc-row
     all share the same hover-tint pattern already; add a touch of glow. */
  [data-theme="glass"] .loan-row:active, [data-theme="glass"] .opstats-row:active{background-color:rgba(255,255,255,.05);}
  @media (hover:hover){
    [data-theme="glass"] .loan-row:hover, [data-theme="glass"] .opstats-row:hover,
    [data-theme="glass"] .link-row:hover, [data-theme="glass"] .dc-doc-row:hover{background-color:rgba(255,255,255,.05);}
  }

  /* Ambient motion -- generalizes the Document Center hero's own
     .dc-hero-particle technique (Batch 62) into a reusable utility any
     glass surface can opt into, per Directive #3's explicit instruction
     to expand that idea rather than leave it isolated to one screen.
     Reduced-motion respected identically to the original. */
  .glass-ambient{position:relative;overflow:hidden;}
  .glass-ambient-particle{
    position:absolute;width:4px;height:4px;border-radius:50%;
    background:var(--teal);opacity:.4;
    box-shadow:0 0 12px 3px rgba(var(--teal-rgb),.5);
    animation:glass-float 11s ease-in-out infinite;pointer-events:none;
  }
  /* Header gets its own non-clipping ambient layer instead of reusing
     .glass-ambient directly -- the header's own dropdown menu
     (#logo-menu-dropdown) extends BELOW the header's visual box, so
     overflow:hidden on <header> itself would silently clip the menu.
     This inner absolutely-positioned layer clips only itself.
     REAL BUG (2026-07-21, re-fixed after a sandbox revert lost the
     first fix): position:absolute was only ever defined under
     [data-theme="glass"] -- every other theme, Eclipse included, left
     this as a plain, unstyled flex child sitting BEFORE the logo in
     the header's own flex layout, pushing the logo away from the left
     corner. Made universal. */
  .header-ambient-layer{
    position:absolute;inset:0;overflow:hidden;border-radius:inherit;
    pointer-events:none;z-index:0;
  }
  @keyframes glass-float{
    0%,100%{transform:translateY(0) scale(1);opacity:.25;}
    50%{transform:translateY(-16px) scale(1.35);opacity:.6;}
  }
  @media (prefers-reduced-motion: reduce){ [data-theme="glass"] .glass-ambient-particle{animation:none;} }
  /* Plain Mode twin of the reduce-motion rule above -- see the "REAL BUG
     (2026-08-27...)" note near [data-motion="plain"] *. */
  [data-motion="plain"] .glass-ambient-particle{animation:none;}

  /* Login-only "star" particles (2026-08-20, direct owner requirement --
     "APPROVED. IMPLEMENT..."): a brand-new, additive class, scoped only
     to spans placed inside #login-screen -- never applied anywhere
     else, in particular never inside #app-screen's header. Deliberately
     does NOT touch .glass-ambient-particle or @keyframes glass-float
     above; it only REFERENCES glass-float by name from a new selector,
     which does not alter that shared rule or anything else that already
     uses it (the authenticated app's #app-screen > header
     .header-ambient-layer particles). Per-instance size/opacity/color/
     animation-duration/animation-delay are set inline on each span so
     this one class can produce visually varied particles without a
     class per variant.
     CORRECTION (2026-08-27): the claim this comment used to make here --
     that reduced-motion/[data-motion="plain"] need no extra rule because
     the global `*` selector already covers it -- was wrong. Forcing
     animation-duration to .01ms on an infinite-looping animation doesn't
     stop it, it replays it up to ~100,000 times a second, which is
     exactly the "particles flicker like crazy" bug reported 2026-08-27.
     See the "REAL BUG" note near [data-motion="plain"] * for the full
     explanation; this class needed the same explicit animation:none
     override .glass-ambient-particle already had, under both signals. */
  .login-star-particle{
    position:absolute;border-radius:50%;
    background:#fff;
    animation:glass-float 11s ease-in-out infinite;
    pointer-events:none;
  }
  @media (prefers-reduced-motion: reduce){ .login-star-particle{animation:none;} }
  [data-motion="plain"] .login-star-particle{animation:none;}

  /* Celebratory burst -- a DIFFERENT character from the ambient float
     above: fires once, radiates outward from a point, then vanishes.
     Reserved for genuine reward moments (a payment successfully
     recorded), not idle decoration -- per Directive #3's "success
     messages feel rewarding" ask, without turning every toast into a
     confetti cannon. */
  [data-theme="glass"] .glass-burst-particle{
    position:absolute;top:50%;left:50%;width:5px;height:5px;border-radius:50%;
    background:var(--status-active);
    box-shadow:0 0 8px 2px rgba(var(--teal-rgb),.6);
    animation:glass-burst .85s cubic-bezier(.16,.84,.44,1) forwards;
    pointer-events:none;
  }
  @keyframes glass-burst{
    0%{transform:translate(-50%,-50%) translate(0,0) scale(1);opacity:1;}
    100%{transform:translate(-50%,-50%) translate(var(--bx,0),var(--by,-40px)) scale(.3);opacity:0;}
  }
  @media (prefers-reduced-motion: reduce){ [data-theme="glass"] .glass-burst-particle{animation:none;display:none;} }

  /* VIP breathing glow -- delight reserved for the one customer tier
     the app already treats as special everywhere else (gold crown badge,
     gold photo ring). Reuses that SAME gold, doesn't invent a second
     one -- consistent with the app's own "one gold" rule from Batch 50.
     Made universal (2026-07-21, re-fixed after a sandbox revert) --
     uses a fixed color, not a theme-scoped variable, so there was never
     a real reason this couldn't apply everywhere. */
  .vip-glow::before{
    content:"";position:absolute;inset:-8px;border-radius:50%;
    background:radial-gradient(circle, rgba(212,175,55,.35), transparent 70%);
    animation:vip-breathe 3.2s ease-in-out infinite;
    z-index:-1;pointer-events:none;
  }
  @keyframes vip-breathe{
    0%,100%{opacity:.45;transform:scale(1);}
    50%{opacity:.85;transform:scale(1.12);}
  }
  @media (prefers-reduced-motion: reduce){ .vip-glow::before{animation:none;opacity:.6;} }
  [data-motion="plain"] .vip-glow::before{animation:none;opacity:.6;}

  /* Sheet hero -- a real compact banner (not a bare title crammed into
     a wrapper), for Analytics/Settings. Owner feedback (2026-07-19): the
     first version was too subtle -- particles had no room and there was
     no background/line to give the strip visual weight, unlike Document
     Center's hero. This gives it real presence: padding, a radial wash,
     an accent line under the title (same DNA as Document Center's own
     .dc-hero/.dc-hero-line), AND a title shimmer sweep Document Center
     doesn't have -- so these screens feel like family, not a lesser copy. */
  /* Embossed PANEL, not embossed text (2026-08-29, direct owner
     correction: "when I say embossed the heading I did not mean the
     words... I meant the whole box up there... the box that it sits
     on... now you doubled embossed the words and they look fuzzy I
     don't like it... I do not like a square like analytics is, I like
     round like document center is"). Two earlier passes put the emboss
     on the TEXT (text-shadow, then a stronger text-shadow + a second
     accent line) -- both wrong target, and the stronger one visibly
     read as fuzzy letters. Reverted .sheet-hero-title back to a plain
     shimmer-only heading (no text-shadow) and moved the actual emboss
     onto .sheet-hero itself: a real raised-panel treatment (bright
     top/left inner edge, dark bottom/right inner edge, like a light
     source from the upper-left -- the same convention the old text
     attempts used, just applied to the box instead of the letters) plus
     a soft outer drop shadow so the whole panel visibly lifts off the
     page. border-radius bumped from --glass-radius-md (14px) to 20px --
     Document Center's box (.dc-hero, 16px + a solid border + opaque
     card background) already read as "round" to the owner; this one's
     glass/blur background is more transparent so the same-ish radius
     read as "square" by comparison -- rounding it further, and giving
     it the same explicit emboss shadow as .dc-hero below, makes the two
     match. */
  .sheet-hero{
    position:relative;overflow:hidden;border-radius:20px;
    padding:20px 18px 22px;margin-bottom:4px;min-height:76px;
    background:
      radial-gradient(130% 160% at 12% 0%, rgba(var(--teal-rgb),.20), transparent 62%),
      var(--glass-bg);
    backdrop-filter:blur(var(--glass-blur-soft)) saturate(var(--glass-saturate));
    -webkit-backdrop-filter:blur(var(--glass-blur-soft)) saturate(var(--glass-saturate));
    border:1px solid var(--border);
    box-shadow:
      inset 0 1.5px 0 rgba(255,255,255,.22),
      inset 1.5px 0 0 rgba(255,255,255,.08),
      inset 0 -1.5px 0 rgba(0,0,0,.30),
      inset -1.5px 0 0 rgba(0,0,0,.16),
      0 8px 20px -10px rgba(0,0,0,.4);
  }
  .sheet-hero-title{
    position:relative;margin:0 0 10px;font-size:1.2rem;font-weight:700;letter-spacing:-.01em;
    color:var(--text-primary);
    background-image:linear-gradient(100deg, var(--text-primary) 40%, #fff 50%, var(--text-primary) 60%);
    background-size:220% 100%;background-clip:text;-webkit-background-clip:text;
    color:transparent;-webkit-text-fill-color:transparent;
    animation:sheet-hero-shimmer 5s ease-in-out infinite;
  }
  /* REAL BUG (2026-08-27, direct owner report -- "the icons in the
     headings are black, no details at all" with Plain Mode off, correct
     with it on): the emoji glyph placed directly inside .sheet-hero-title
     ("💰 Finance Center", "ℹ️ Analytics", "⚙️ Settings") inherits this
     heading's own color:transparent / -webkit-text-fill-color:transparent
     -- the mechanism the shimmer gradient above needs so
     background-clip:text can show the moving gradient through the text.
     Several rendering engines don't special-case color/bitmap emoji glyphs
     inside that clip-through-transparent-fill technique, and paint them as
     a flat black silhouette instead of their native colors -- a
     well-documented cross-browser quirk of background-clip:text, not
     something specific to this app. Plain Mode "fixed" this by accident:
     its own override below (mirroring the reduced-motion one) resets
     -webkit-text-fill-color back to opaque for the WHOLE heading, which
     incidentally un-breaks the emoji too, but only as a side effect --
     the default (non-Plain) heading was always broken for the icon.
     Real fix: give the icon its own inline element and explicitly pull
     it back out of the transparent-fill inheritance, so it always renders
     with its native color regardless of Plain Mode. Wrap the leading
     emoji in each of those three headings with
     <span class="sheet-hero-icon">...</span> (done at each h2 below). */
  .sheet-hero-icon{ color:var(--text-primary); -webkit-text-fill-color:currentColor; }
  @keyframes sheet-hero-shimmer{
    0%{background-position:160% 0;}
    45%,100%{background-position:-60% 0;}
  }
  .sheet-hero-line{height:2px;width:56px;border-radius:2px;background:linear-gradient(90deg,var(--teal),transparent);}
  @media (prefers-reduced-motion: reduce){ .sheet-hero-title{animation:none;-webkit-text-fill-color:var(--text-primary);color:var(--text-primary);} }
  [data-motion="plain"] .sheet-hero-title{animation:none;-webkit-text-fill-color:var(--text-primary);color:var(--text-primary);}
  /* ===== end Apple Liquid Glass theme ===== */

  /* ===== Eclipse theme: component layer =====
     Owner-provided reference image, 2026-07-19: rebuild Customer
     Profile, Home, and New Loan with real creative reinterpretation of
     that moodboard's language -- halo rings behind portraits, a bold
     gold-on-black cinematic feel, stat-strips, a circular status ring.
     Every number used below comes from data these screens already
     fetch (Customer 360, the due report) -- nothing fabricated. */

  [data-theme="eclipse"] body{
    background:
      radial-gradient(90% 60% at 50% -10%, rgba(var(--halo-color),.08), transparent 60%),
      var(--page);
  }

  /* The halo ring -- the reference's single most distinctive motif: a
     glowing crescent behind a portrait. Reusable utility; used behind
     the Customer Profile photo and the New Loan hero icon.
     Structural positioning made universal (2026-07-21, re-fixed after a
     sandbox revert): this class is applied unconditionally to the
     customer photo wrapper on EVERY theme, not just Eclipse, so layout
     can't depend on which theme is active.
     The glow itself made universal too (2026-09-02, direct owner
     request -- "I like that [halo]... can we extend that to all the
     themes, not just exclusive to the eclipse"). Dropped the
     [data-theme="eclipse"] gate and swapped --halo-color (only ever
     defined inside the Eclipse theme block, so this rule genuinely
     could not have rendered anywhere else) for --teal-rgb, the same
     universal per-theme accent-color variable .eclipse-pill already
     uses for exactly this reason a few rounds back -- each theme now
     glows in its own accent color rather than Eclipse's gold everywhere.
     Applies to every #customer-photo-wrap, VIP included -- the VIP-
     specific gold override and backing plate live in the two rules
     below, layered on top of this one rather than replacing it, so a
     VIP customer gets everything a regular customer gets PLUS the
     extra gold treatment, not a swapped-out different effect. */
  .eclipse-halo{
    position:relative;display:inline-flex;align-items:center;justify-content:center;
  }
  .eclipse-halo::before{
    content:"";position:absolute;inset:-10px;border-radius:50%;
    background:conic-gradient(from -40deg, transparent 0deg, rgba(var(--teal-rgb),.9) 60deg, transparent 140deg, transparent 360deg);
    filter:blur(7px);
    animation:eclipse-rotate 14s linear infinite;
    z-index:-1;
  }
  @keyframes eclipse-rotate{ to{ transform:rotate(360deg); } }
  @media (prefers-reduced-motion: reduce){ .eclipse-halo::before{animation:none;} }
  [data-motion="plain"] .eclipse-halo::before{animation:none;}
  /* VIP treatment (2026-09-02, direct owner follow-up -- tried giving
     VIP customers their own separate breathing-glow effect instead of
     the halo; owner's verdict: "looks ugly... vip needs to look
     Exclusive. revert it back to the Gold Halo and even go further and
     give a background to the picture. so it looks exclusive"). Forces
     the ring itself to the app's one fixed VIP gold (#D4AF37/
     rgb(212,175,55), same value .vip-glow and every other VIP treatment
     in this file already uses -- the "one gold" rule from Batch 50, no
     second gold invented here) instead of the theme's own --teal-rgb
     accent, on every theme, not just Gold or Eclipse. Only `background`
     is redeclared -- position, inset, animation, filter, z-index all
     still come from the plain .eclipse-halo::before rule above, since
     this selector (two classes, beats .eclipse-halo's one) doesn't
     touch them.
     A second, wider gold "background plate" (::after, z-index:-2, a
     radial-gradient sitting further back than the ring) was added the
     same day per the owner's "give a background to the picture" ask,
     then reverted minutes later per direct follow-up: "The halo change
     that you did is overwhelming... now it looks like one big circle.
     I don't like it. The gold halo is what I wanted, not the new one
     that you did." So the plate is gone -- this gold ring alone is the
     whole VIP photo-halo treatment now. (The rest of that same message
     -- "the placeholder for the VIP should have a texture background...
     do not remove the crown" -- is a separate ask about the no-photo
     placeholder markup itself, handled where that markup is built, not
     here; see the comment there.) */
  .vip-glow.eclipse-halo::before{
    background:conic-gradient(from -40deg, transparent 0deg, rgba(212,175,55,.9) 60deg, transparent 140deg, transparent 360deg);
  }

  /* Stat strip -- three stacked-number columns with dividers, matching
     the reference's "Days / Habits / Focus Hrs" block exactly in
     structure, populated with real Customer 360 data instead. */
  /* Hidden by default for every other theme; only Eclipse turns it on.
     A base (non-themed) rule + a themed override -- not an inline style,
     so the theme system is what actually controls this, cleanly. */
  .eclipse-stat-strip{display:none;}
  [data-theme="eclipse"] .eclipse-stat-strip{
    display:flex;justify-content:space-around;align-items:center;
    background:var(--surface-2);border:1px solid var(--border);border-radius:14px;
    padding:14px 8px;margin:14px 0;
  }
  [data-theme="eclipse"] .eclipse-stat{text-align:center;flex:1;position:relative;}
  [data-theme="eclipse"] .eclipse-stat:not(:last-child)::after{
    content:"";position:absolute;right:0;top:10%;bottom:10%;width:1px;background:var(--border);
  }
  [data-theme="eclipse"] .eclipse-stat-value{font-size:1.3rem;font-weight:800;color:var(--teal-text);line-height:1.1;}
  [data-theme="eclipse"] .eclipse-stat-label{font-size:.68rem;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.04em;margin-top:2px;}

  /* Rating pill -- adds the reference's rounded, glowing pill FEEL
     without touching the badge's actual background/color (those are set
     inline per rating on purpose -- red/orange/green must stay visually
     distinct, a core rule of this app; Eclipse shouldn't override
     meaning for the sake of style). Only supplements: glow ring,
     tracking, and a soft entrance -- properties the inline style never
     sets, so nothing fights here. */
  /* Rating pill -- adds the reference's rounded, glowing pill FEEL
     without touching the badge's actual background/color (those are set
     inline per rating on purpose -- red/orange/green must stay visually
     distinct, a core rule of this app; no theme should override
     meaning for the sake of style). Only supplements: glow ring,
     tracking, and a soft entrance -- properties the inline style never
     sets, so nothing fights here. Made universal (2026-07-21, re-fixed
     after a sandbox revert) using --teal-rgb, which is defined for
     every theme -- the original used --halo-color, which only exists
     for Eclipse, so this genuinely couldn't have worked anywhere else. */
  .eclipse-pill{
    letter-spacing:.05em;
    box-shadow:0 0 0 1px rgba(255,255,255,.08), 0 2px 10px -3px rgba(var(--teal-rgb),.5);
    animation:eclipse-pill-in .4s cubic-bezier(.22,.61,.36,1) both;
  }
  @keyframes eclipse-pill-in{ from{opacity:0;transform:scale(.85);} to{opacity:1;transform:scale(1);} }
  @media (prefers-reduced-motion: reduce){ .eclipse-pill{animation:none;} }

  /* Collections ring -- Home screen. Four real segments (overdue /
     overdue-soon / due-today / upcoming), same shape as the reference's
     circular progress + colored-dot legend, built with a single conic-
     gradient (no SVG/canvas needed, no new dependency). */
  /* Hidden by default for every other theme -- same reasoning as
     .eclipse-stat-strip above: without this, the ring card would render
     as an unstyled, ugly block on Dark/Gold/Neon/Aurora/Glass.
     2026-08-10, direct owner requirement: replaced everywhere by the
     swipeable prism-chart-card below -- this old, non-swipeable ring
     no longer has a visibility override for any theme, including
     Eclipse, so it now stays hidden universally via the base rule
     above. Left in place rather than deleted, in case it's ever needed
     again, but it renders nowhere now. */
  .eclipse-ring-card{display:none;}
  [data-theme="eclipse"] .eclipse-ring{
    position:relative;width:88px;height:88px;border-radius:50%;flex-shrink:0;
    display:flex;align-items:center;justify-content:center;
  }
  [data-theme="eclipse"] .eclipse-ring-inner{
    position:absolute;inset:9px;border-radius:50%;background:var(--surface-2);
    display:flex;flex-direction:column;align-items:center;justify-content:center;
  }
  [data-theme="eclipse"] .eclipse-ring-pct{font-size:1.05rem;font-weight:800;color:var(--text-primary);}
  [data-theme="eclipse"] .eclipse-ring-sub{font-size:.55rem;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.03em;}
  [data-theme="eclipse"] .eclipse-legend{flex:1;display:flex;flex-direction:column;gap:6px;}
  [data-theme="eclipse"] .eclipse-legend-row{display:flex;align-items:center;justify-content:space-between;font-size:.78rem;}
  [data-theme="eclipse"] .eclipse-legend-dot{width:8px;height:8px;border-radius:50%;display:inline-block;margin-right:7px;}

  /* Cinematic hero -- New Loan screen. Same DNA as .sheet-hero (glass
     theme) and .dc-hero (Document Center), rebuilt with the reference's
     bolder, more dramatic gold glow and the halo motif behind a small
     icon mark instead of a photo. */
  .eclipse-loan-hero{display:none;}
  [data-theme="eclipse"] .eclipse-loan-hero{
    position:relative;overflow:hidden;border-radius:18px;padding:22px 20px;margin-bottom:18px;
    background:
      radial-gradient(120% 160% at 20% -20%, rgba(var(--halo-color),.22), transparent 60%),
      linear-gradient(160deg, var(--surface-2), var(--surface));
    border:1px solid var(--border);
    display:flex;align-items:center;gap:16px;
  }
  [data-theme="eclipse"] .eclipse-loan-hero-mark{
    width:52px;height:52px;border-radius:50%;flex-shrink:0;
    background:var(--surface);border:1px solid var(--border-strong);
    display:flex;align-items:center;justify-content:center;font-size:1.4rem;
  }
  [data-theme="eclipse"] .eclipse-loan-hero-title{font-size:1.05rem;font-weight:700;color:var(--text-primary);margin-bottom:3px;}
  [data-theme="eclipse"] .eclipse-loan-hero-sub{font-size:.78rem;color:var(--text-secondary);line-height:1.35;}
  /* ===== end Eclipse theme ===== */

  /* ===== Prism theme components (2026-08-10, direct owner requirement):
     glass banners, spaced list, visible logo watermark. Scoped entirely
     to [data-theme="prism"] -- every other theme, Eclipse included,
     left completely untouched, same isolation pattern as Glass/Eclipse
     already use for their own component rules. =====
     2026-08-29 follow-up, direct owner request: the customer list's
     "spaced cards with the page background showing through" look (once
     Prism-only) is now generalized to EVERY theme -- see the base
     .loan-list/.loan-row rules further down, which now carry exactly
     the background:transparent/gap/card-per-row treatment that used to
     live only here. The two rules below are left in place ONLY because
     they're genuinely Prism-specific (the app-wide watermark opacity
     bump, and the glass due-banner/due-row transparency) -- the
     list-spacing rules that used to sit here were removed as pure
     duplicates of the new universal base rule, not because Prism lost
     the look, just because every theme now shares one definition
     instead of Prism having its own copy. */
  :is([data-theme="prism"],[data-theme="prism-light"]) #app-screen::before{
    opacity:.24;
  }
  :is([data-theme="prism"],[data-theme="prism-light"]) .due-banner{
    background:var(--surface);
    border:1px solid var(--border);box-shadow:0 4px 16px -8px rgba(0,0,0,.18);
  }
  :is([data-theme="prism"],[data-theme="prism-light"]) .due-banner-header,
  :is([data-theme="prism"],[data-theme="prism-light"]) .due-banner-header.overdue{background:transparent;}
  :is([data-theme="prism"],[data-theme="prism-light"]) .due-row{background:transparent;}
  /* Swipeable status/risk chart -- universal across every theme
     (2026-08-10, direct owner requirement, replacing the old
     Eclipse-only non-swipeable ring above). Uses var(--surface)/
     var(--border)/var(--text-*) throughout so it automatically adapts
     to whichever theme is active, with zero hardcoded per-theme colors
     needed anywhere in this block. */
  #prism-chart-card{display:block;margin-bottom:8px;position:relative;z-index:1;}
  .prism-ring-track{display:flex;overflow-x:auto;scroll-snap-type:x mandatory;border-radius:16px;-webkit-overflow-scrolling:touch;scrollbar-width:none;}
  .prism-ring-track::-webkit-scrollbar{display:none;}
  .prism-ring-page{flex:0 0 100%;scroll-snap-align:start;}
  .prism-ring-card{
    display:flex;align-items:center;gap:16px;background:var(--surface);
    border:1px solid var(--border);border-radius:16px;padding:15px;
    box-shadow:0 4px 16px -8px rgba(0,0,0,.18);
    min-height:112px;box-sizing:border-box;
  }
  .prism-ring{position:relative;width:80px;height:80px;border-radius:50%;flex-shrink:0;display:flex;align-items:center;justify-content:center;}
  :is([data-theme="prism"],[data-theme="prism-light"]) .btn-collect{
    background:var(--surface-2);border-color:var(--border-strong);
  }
  .prism-ring-inner{
    position:absolute;inset:9px;border-radius:50%;background:var(--surface);
    display:flex;flex-direction:column;align-items:center;justify-content:center;
    box-shadow:inset 0 0 0 1px var(--border);
  }
  .prism-ring-pct{font-size:15px;font-weight:800;color:var(--text-primary);}
  .prism-ring-sub{font-size:8px;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.03em;}
  .prism-legend{flex:1;display:flex;flex-direction:column;gap:5px;min-width:0;}
  .prism-legend-row{display:flex;align-items:center;justify-content:space-between;font-size:11px;}
  .prism-legend-dot{width:7px;height:7px;border-radius:50%;display:inline-block;margin-right:6px;flex-shrink:0;}
  .prism-legend-label{color:var(--text-secondary);}
  .prism-legend-count{font-weight:700;color:var(--text-primary);}
  .prism-dots{display:flex;justify-content:center;gap:5px;margin-top:8px;}
  .prism-dot{width:5px;height:5px;border-radius:50%;background:var(--border-strong);transition:width .2s ease,background .2s ease;}
  .prism-dot.on{background:var(--text-secondary);width:14px;border-radius:3px;}

  /* ===== Neon theme: glow effects, gradients, sci-fi styling ===== */
  :is([data-theme="neon"],[data-theme="aurora"]) body{
    background:
      radial-gradient(ellipse 900px 500px at 15% -5%, rgba(var(--teal-rgb),.05), transparent 60%),
      radial-gradient(ellipse 700px 500px at 100% 10%, rgba(var(--glow2-rgb),.03), transparent 55%),
      var(--page);
  }
  :is([data-theme="neon"],[data-theme="aurora"]) .summary-strip, :is([data-theme="neon"],[data-theme="aurora"]) .summary-chip,
  :is([data-theme="neon"],[data-theme="aurora"]) .due-banner, :is([data-theme="neon"],[data-theme="aurora"]) .loan-row,
  :is([data-theme="neon"],[data-theme="aurora"]) .sheet, :is([data-theme="neon"],[data-theme="aurora"]) .receipt-block,
  :is([data-theme="neon"],[data-theme="aurora"]) .login-card{
    background:linear-gradient(155deg, var(--surface) 0%, var(--surface-2) 130%);
    border:1px solid rgba(var(--teal-rgb),.10);
    box-shadow:0 0 0 1px rgba(var(--teal-rgb),.03), 0 8px 24px -8px rgba(0,0,0,.6);
  }
  :is([data-theme="neon"],[data-theme="aurora"]) .btn-primary{
    background:linear-gradient(135deg, var(--teal) 0%, var(--teal-deep) 100%);
    box-shadow:0 0 8px rgba(var(--teal-rgb),.3), 0 0 1px rgba(var(--teal-rgb),.5);
    border:none;text-shadow:0 0 4px rgba(0,0,0,.3);
  }
  :is([data-theme="neon"],[data-theme="aurora"]) .btn-primary:active{box-shadow:0 0 4px rgba(var(--teal-rgb),.25);}
  :is([data-theme="neon"],[data-theme="aurora"]) .summary-chip .value, :is([data-theme="neon"],[data-theme="aurora"]) .num{
    text-shadow:0 0 4px rgba(var(--teal-rgb),.4);
  }
  :is([data-theme="neon"],[data-theme="aurora"]) .status-dot{
    box-shadow:0 0 4px currentColor;
  }
  :is([data-theme="neon"],[data-theme="aurora"]) .status-active, :is([data-theme="neon"],[data-theme="aurora"]) .status-current{box-shadow:0 0 4px var(--status-active);}
  :is([data-theme="neon"],[data-theme="aurora"]) .status-overdue, :is([data-theme="neon"],[data-theme="aurora"]) .status-suspended{box-shadow:0 0 4px var(--status-suspended);}
  :is([data-theme="neon"],[data-theme="aurora"]) .status-grace, :is([data-theme="neon"],[data-theme="aurora"]) .status-pending_review{box-shadow:0 0 4px var(--status-pending);}
  :is([data-theme="neon"],[data-theme="aurora"]) .brand-mark, :is([data-theme="neon"],[data-theme="aurora"]) .login-mark{
    box-shadow:0 0 8px rgba(var(--teal-rgb),.3);
  }
  :is([data-theme="neon"],[data-theme="aurora"]) .milestone.paid .milestone-marker{
    box-shadow:0 0 5px rgba(var(--teal-rgb),.4);
  }
  :is([data-theme="neon"],[data-theme="aurora"]) .milestone-end.paid .milestone-end-marker{
    box-shadow:0 0 0 2px var(--surface), 0 0 6px rgba(255,184,0,.4);
  }
  :is([data-theme="neon"],[data-theme="aurora"]) .fab{
    box-shadow:0 0 8px rgba(var(--teal-rgb),.3), 0 4px 16px rgba(0,0,0,.5);
  }
  :is([data-theme="neon"],[data-theme="aurora"]) .search-row input:focus,
  :is([data-theme="neon"],[data-theme="aurora"]) input:focus, :is([data-theme="neon"],[data-theme="aurora"]) select:focus{
    box-shadow:0 0 0 2px rgba(var(--teal-rgb),.15);
    border-color:var(--teal) !important;
  }
  :is([data-theme="neon"],[data-theme="aurora"]) .tag-chip{
    box-shadow:0 0 3px rgba(var(--teal-rgb),.2);
  }
  /* Neon/Aurora .due-banner-header.overdue/.today inset box-shadow accents
     removed (2026-08-21, same continuous-accent fix -- see the
     .due-banner:has(...) comment near .due-banner). These painted the
     glow accent on the header only, so it also stopped at the header on
     expand; the new container-level border-left (0,3,0) already
     outranks this theme's own `.due-banner{border:...}` rule (0,2,0),
     so Neon/Aurora gets the same full-height accent as every other
     theme with no special-casing needed here. */
  /* ===== end neon theme ===== */
  /* Login screen background -- made theme-INDEPENDENT (2026-08-21,
     direct owner requirement, superseding the previous Prism-Light-only
     contrast fix below in history). Original report: logging out while
     on Prism Light showed "essentially NO background -- plain white/
     blank." First attempt scoped a fix to just [data-theme="prism-light"]
     (raising .login-card's alpha + the watermark opacity for that theme
     only) -- direct owner feedback after seeing it live: still not good
     enough on that theme. Rather than keep chasing per-theme contrast
     values, the owner asked directly for the login screen to stop
     following the active theme's --page token at all: one fixed dark
     backdrop behind the logo/card for every theme, the same way this
     already looked fine on Dark/Glass/Prism before any of this.
     Scoped strictly to the logged-out state via body:not(.app-active) --
     specificity (0 IDs, 1 attribute-equivalent pseudo-class + 1 element)
     matches the three existing per-theme body{} rules above (glass,
     eclipse, neon/aurora) and this rule is the last one in source order,
     so it wins the cascade tie for login only. The in-app background
     (body.app-active, and those three theme rules) is completely
     untouched and stays fully theme-reactive -- this only ever fires
     before login. .login-card itself keeps its own per-theme background
     (still var(--glass-bg), still translucent) -- the owner's ask was
     about the background behind the card, not the card's own theme
     identity, and every theme's card already reads fine floating on a
     dark backdrop (that's exactly how Dark/Glass/Prism look today). */
  body:not(.app-active){
    background:
      radial-gradient(ellipse 900px 700px at 50% 0%, #1B1F24 0%, transparent 60%),
      #0A0A0C;
    /* Lock the login screen in place (2026-08-21, direct owner report:
       "it moves when I swipe up and down... sideways is ok it doesn't
       move but up and down it does"). The login screen has nothing that
       actually needs to scroll -- one centered card -- so any vertical
       movement is an unwanted mobile artifact, not real scrolling.
       Root cause: #login-screen below uses min-height:100vh, and
       mobile Safari/Chrome's 100vh is based on the LARGEST possible
       viewport (address bar hidden) -- when the address bar is
       actually showing, the real visible area is shorter than 100vh,
       so the page has a few extra px of scrollable overflow below the
       fold. A vertical swipe scrolls into that sliver (collapsing the
       browser chrome as it goes), which reads exactly as "the login
       moves." overflow:hidden + overscroll-behavior:none here removes
       any scroll capability outright, and the html:has() rule just
       below does the same one level up (iOS rubber-banding can still
       bounce the outer document even with overflow:hidden on body
       alone). Scoped to :not(.app-active) only -- the in-app screens
       (loan lists etc.) need and keep their normal scrolling; this
       never applies once showApp() adds the .app-active class. */
    overflow:hidden;
    overscroll-behavior:none;
  }
  html:has(body:not(.app-active)){
    overflow:hidden;
    overscroll-behavior:none;
  }
  html,body{margin:0;padding:0;overflow-x:hidden;max-width:100%;}
  body{
    background:var(--page);
    color:var(--text-primary);
    font-family:'Inter',sans-serif;
    -webkit-font-smoothing:antialiased;
    min-height:100vh;
    padding-bottom:env(safe-area-inset-bottom);
    position:relative;
  }
  body::before{
    content:"";
    position:fixed;
    inset:0;
    background-image:url('/logo-blue.png');
    background-repeat:no-repeat;
    background-position:center 40%;
    /* Fixed WIDTH, auto height -- NOT a fixed 280x280 box (2026-08-21,
       direct owner report: "the gold-bg is stretch not 1:1 like the
       blue one is"). logo-blue.png is a 1080x1080 perfect square, so a
       hardcoded 280px 280px box never visibly distorted it -- but
       logo-gold-full-bg.png (used here once [data-logo-style="gold"]
       is active, via the override immediately below) is 1024x1536, a
       2:3 portrait image, and forcing THAT into a square box squashed
       it horizontally. auto height lets each image's own native aspect
       ratio set the second dimension instead of forcing one for both:
       blue still renders at exactly 280x280 (its own ratio already is
       1:1, so auto resolves to 280 anyway -- zero visual change there),
       gold now renders at 280 wide x its own true proportional height,
       undistorted. Same fixed-width/auto-height convention already
       used by body.app-active::before below (min(70vw,380px) auto) --
       this just brings the login version in line with it. */
    background-size:280px auto;
    pointer-events:none;
    z-index:0;
  }
  /* Login watermark opacity (2026-08-20, direct owner requirement --
     "APPROVED. IMPLEMENT..."): split out into its own explicit
     selector instead of leaving it as the shared body::before value
     that body.app-active::before below happens to override via higher
     specificity. This makes the login-vs-app split visible in the
     rules themselves, not just implied by cascade order. Raised from
     the previous .05 (reported as barely visible) to .12 -- clearly
     visible without competing with the login card's own content.
     body.app-active::before immediately below is completely
     unchanged: same selector, same properties, same values as before
     this edit. */
  body:not(.app-active)::before{
    opacity:.12;
  }
  /* Login watermark gold swap (2026-08-21, direct owner report -- "I
     need both logos to change": the login screen's big background
     watermark stayed on the blue asset regardless of the gold/blue
     toggle). Root cause: body::before's base rule (above) is always
     logo-blue.png -- unconditional, no swap logic at all -- and the
     only existing gold override, html[data-logo-style="gold"] body.
     app-active::before further below, is scoped to .app-active (the
     in-app watermark) only. The login state (body:not(.app-active))
     never had a gold counterpart. Same asset the in-app version already
     uses (logo-gold-full-bg.png), just scoped to the login state
     instead. Opacity deliberately left at the existing .12 above, not
     copying app-active's raised .40 -- that raise was tuned for gold
     specifically at app-active's larger min(70vw,380px) box size (see
     the note below), which doesn't apply to the login watermark's
     smaller fixed 280px box; revisit only if gold reads too dim here
     once seen live. */
  html[data-logo-style="gold"] body:not(.app-active)::before{
    background-image:url('/logo-gold-full-bg.png');
  }
  /* Header Logo Style (2026-08-11/12, direct owner requirement):
     ONE watermark element, not two -- the earlier version used a
     separate #app-screen::before alongside this one, which rendered
     simultaneously once logged in (nothing ever hid the login
     watermark), producing two overlapping backgrounds. Scoped here by
     .app-active (toggled in showApp()/logout()) instead. Full-screen
     cover sizing was tried and reverted (2026-08-12) -- reported as
     too distracting and too blurry, since it stretched the source
     image well past its native resolution. Back to a constrained,
     centered box. Gold opacity stays raised (a separate, still-valid
     request) even though the sizing reverted.
     2026-08-19 (direct owner request): watermark was effectively
     invisible against the dense loan-list cards -- opacity raised
     .14->.30 / .24->.40 (box size left untouched, per the note above
     about size increases being reverted as distracting). */
  body.app-active::before{
    background-position:center;
    background-size:min(70vw,380px) auto;
    opacity:.30;
  }
  html[data-logo-style="gold"] body.app-active::before{
    background-image:url('/logo-gold-full-bg.png');
    opacity:.40;
  }
  header, main, #login-screen, .sheet{ position:relative; z-index:1; }
  h1,h2,h3{font-weight:500;margin:0;}
  .num{font-variant-numeric:tabular-nums;font-feature-settings:"tnum";}
  button{font-family:inherit;cursor:pointer;}
  input,select,textarea{font-family:inherit;}

  /* ---------- Login ---------- */
  #login-screen{
    min-height:100vh;
    min-height:100dvh; /* dynamic viewport height -- tracks the actual
      visible area as mobile browser chrome shows/hides, instead of
      100vh's fixed largest-possible-viewport number; part of the
      scroll-lock fix above. Plain 100vh stays first as a fallback for
      any browser that doesn't understand dvh -- it's simply ignored,
      not applied, on those, so nothing regresses there. */
    display:flex;align-items:center;justify-content:center;
    padding:24px;
  }
  /* Login card fill (2026-08-21, direct owner requirement, second pass):
     first pass took this to ~5% opacity ("if 100 was nothing I want it
     95"); direct follow-up: "make it instead of 95 make it 100 full
     transparent." Fill removed entirely -- no color-mix tint, no
     radial-gradient accent, background:transparent outright. The 1px
     border (unchanged, still var(--border)) and box-shadow below are
     now the ONLY thing that reads as the card -- exactly the "can still
     see the edge-rim" requirement from the first pass, still true here
     since neither was touched. backdrop-filter left in place (it's
     already a no-op on every non-Glass theme per the earlier
     investigation, and with zero fill behind it there's nothing left
     for it to tint even where it does apply). */
  .login-card{
    width:100%;max-width:360px;
    background:transparent;
    backdrop-filter:blur(var(--glass-blur)) saturate(var(--glass-saturate));
    -webkit-backdrop-filter:blur(var(--glass-blur)) saturate(var(--glass-saturate));
    border-radius:var(--radius);padding:32px 28px;
    border:1px solid var(--border);
    box-shadow:0 4px 20px -4px rgba(0,0,0,.08), 0 1px 3px rgba(0,0,0,.04);
  }
  .login-card h1, .login-card .login-sub{text-align:center;}
  .login-mark{
    width:40px;height:40px;border-radius:10px;background:var(--teal);
    display:flex;align-items:center;justify-content:center;margin:0 auto 16px;overflow:hidden;
    box-shadow:0 2px 8px -2px rgba(var(--teal-rgb),.5);
  }
  .login-mark svg{width:20px;height:20px;}
  .login-mark img{width:100%;height:100%;object-fit:cover;}
  .login-card h1{font-size:1.4rem;margin-bottom:4px;}
  .login-sub{color:var(--text-secondary);font-size:.88rem;margin-bottom:24px;}
  .field{margin-bottom:16px;}
  .field label{display:block;font-size:.8rem;font-weight:500;color:var(--text-secondary);margin-bottom:6px;}
  /* Login card text -- theme-independent, following directly from the
     card-transparency change above (2026-08-21). Once the card's own
     fill dropped to ~5% opacity, text sitting directly on that fill
     (the title, subtitle, field labels) started using each theme's own
     --text-primary/--text-secondary -- dark, on light themes like Prism
     Light -- against what's now a dark backdrop showing straight
     through. Result: near-invisible title text on light themes.
     Scoped to .login-card specifically, NOT a global var(--text-primary)
     change, so nothing outside the login screen is touched -- the
     .field input boxes are already unaffected by any of this (they keep
     their own opaque var(--surface) fill and var(--text-primary), so
     they stay exactly as readable as before, theme by theme; this only
     covers the card's passive text sitting straight on the fill). */
  .login-card h1{color:#F2F3F5;}
  .login-card .login-sub, .login-card .field label{color:#9BA3AE;}
  .field input{
    width:100%;padding:11px 13px;border:1px solid var(--border);border-radius:9px;
    font-size:1rem;background:var(--surface);color:var(--text-primary);
  }
  /* Raised/embossed field shadow, now app-wide (2026-08-29, direct owner
     request: "add some shadow to all those boxes so they look a lil 3d
     and nice... around every form field"). This exact 5-layer recipe
     already existed, but scoped ONLY to #loan-sheet -- a 2026-08-21
     owner request had explicitly limited it to New Loan ("strictly a
     visual CSS/UI refinement of the NEW LOAN form", so as not to
     silently redesign every field in the app without being asked). The
     owner has now explicitly asked for it everywhere, so it moved from
     the #loan-sheet-scoped rule (removed below, see that comment) to
     this base .field input/.field select rule -- same values, same
     look, just no longer fenced to one sheet. Kept as box-shadow only,
     zero change to border/background/padding, for the same reason as
     before: several selects across the app (Payment method, etc.) set
     border/background/padding via their own inline style="...", and
     inline always wins over a class for those properties -- but nothing
     sets box-shadow inline anywhere, so this reaches every input AND
     select in the app uniformly with zero markup edits. */
  .field input, .field select{
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,.6),
      inset 0 -1px 0 rgba(0,0,0,.05),
      0 -1px 0 rgba(255,255,255,.45),
      0 2px 5px -1px rgba(0,0,0,.10),
      0 1px 2px rgba(0,0,0,.05);
  }
  .field input:focus, .field select:focus{
    outline:none;border-color:var(--teal);
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,.6),
      inset 0 -1px 0 rgba(0,0,0,.05),
      0 -1px 0 rgba(255,255,255,.45),
      0 2px 5px -1px rgba(0,0,0,.10),
      0 1px 2px rgba(0,0,0,.05),
      0 0 0 3px var(--teal-bg);
  }
  /* Native <select> elements don't inherit text color from anywhere by
     default, so without this every dropdown falls back to the browser's
     default black text -- invisible against any dark theme. */
  select{color:var(--text-primary);}
  /* New Loan fields' raised bevel (2026-08-21) is now the app-wide
     default -- see .field input/.field select above. This used to be a
     duplicate #loan-sheet-scoped copy of that exact same box-shadow
     recipe (kept fenced to just this one sheet per an explicit
     owner request at the time: "strictly a visual CSS/UI refinement" of
     "the NEW LOAN form", so as not to silently redesign every field in
     the app). Removed as redundant now that the owner has asked for the
     same treatment everywhere (2026-08-29: "add some shadow to all
     those boxes... around every form field") -- the base rule above
     already covers #loan-sheet's fields identically, nothing lost. */
  .btn-primary{
    width:100%;padding:12px;border:none;border-radius:9px;
    background:linear-gradient(180deg, var(--teal) 0%, var(--teal-deep) 100%);
    color:#fff;font-size:.95rem;font-weight:500;margin-top:4px;
    box-shadow:0 1px 2px rgba(0,0,0,.1), inset 0 1px 0 rgba(255,255,255,.15);
    transition:transform .16s cubic-bezier(.22,.61,.36,1), box-shadow .16s cubic-bezier(.22,.61,.36,1), background .16s ease;
  }
  .btn-primary:active{background:var(--teal-deep);box-shadow:inset 0 1px 3px rgba(0,0,0,.2);transform:scale(.98);}
  @media (hover:hover){
    .btn-primary:hover:not(:disabled){box-shadow:0 3px 12px -3px rgba(var(--teal-rgb),.4), inset 0 1px 0 rgba(255,255,255,.15);}
  }
  .btn-primary:disabled{opacity:.55;}
  .error-msg{
    background:var(--danger-bg);border:1px solid #F3C2C1;color:var(--danger-text);
    padding:10px 12px;border-radius:9px;font-size:.85rem;margin-bottom:16px;display:none;
  }

  /* ---------- App shell ---------- */
  #app-screen{display:none;min-height:100vh;}
  header{
    position:fixed;top:calc(8px + env(safe-area-inset-top));left:10px;right:10px;z-index:25;
    border-radius:16px;
    background:var(--glass-bg);
    backdrop-filter:blur(14px) saturate(1.5);-webkit-backdrop-filter:blur(14px) saturate(1.5);
    border:1px solid var(--border);
    box-shadow:0 4px 20px -6px rgba(0,0,0,.15);
    padding:12px 16px;
    display:flex;align-items:center;justify-content:space-between;
    transition:backdrop-filter .32s cubic-bezier(.22,.61,.36,1), box-shadow .32s cubic-bezier(.22,.61,.36,1), border-color .32s ease;
  }
  /* Once the page has actually scrolled -- content now passing underneath
     the header, not just sitting flush against it -- the glass reads
     deeper: more blur, more saturation, a more pronounced shadow. At rest
     (scroll top) the header stays closer to flush with the page, the way
     iOS/macOS translucent bars only fully "activate" once there's
     something behind them to separate from. Toggled by a single class,
     eased with the same curve on the way in and out -- no bounce. */
  header.header-elevated{
    backdrop-filter:blur(22px) saturate(1.9);-webkit-backdrop-filter:blur(22px) saturate(1.9);
    box-shadow:0 10px 36px -10px rgba(0,0,0,.32);
    border-color:var(--border-strong);
  }
  header .title{display:flex;align-items:center;gap:10px;}
  .brand-plaque-wrap{position:relative;display:inline-flex;}
  .brand-plaque{
    margin:0;font-weight:700;font-size:1rem;letter-spacing:.015em;
    border:1px solid rgba(212,175,55,.32);
    border-radius:8px;
    padding:6px 16px;
    box-shadow:inset 0 1px 0 rgba(255,255,255,.05), 0 3px 8px rgba(0,0,0,.35);
    background:linear-gradient(180deg, #1C1F24, #0F1114);
  }
  .brand-plaque-text{
    background-image:linear-gradient(160deg, #F0DA96, #D4AF37 40%, #9A7A28 75%, #6B551B 100%);
    background-clip:text;
    -webkit-background-clip:text;
    color:transparent;
    -webkit-text-fill-color:transparent;
  }
  [data-logo-style="blue"] .brand-plaque{
    border-color:rgba(93,169,232,.32);
  }
  [data-logo-style="blue"] .brand-plaque-text{
    background-image:linear-gradient(160deg, #C7E2F8, #5DA9E8 40%, #2E6FA8 75%, #1E4C75 100%);
  }
  .brand-plaque-screw{
    position:absolute;width:5px;height:5px;border-radius:50%;
    background:radial-gradient(circle at 35% 30%, #E8CD7A, #9A7A28 55%, #6B551B 100%);
    box-shadow:0 .5px 1px rgba(0,0,0,.6);
  }
  [data-logo-style="blue"] .brand-plaque-screw{
    background:radial-gradient(circle at 35% 30%, #F0F3F5, #A8B0B5 55%, #6E767B 100%);
  }
  .brand-plaque-screw::after{
    content:"";position:absolute;top:50%;left:50%;width:3.2px;height:.6px;
    background:rgba(0,0,0,.55);transform:translate(-50%,-50%) rotate(35deg);
    border-radius:1px;
  }
  .brand-plaque-screw.tl{top:3px;left:3px;}
  .brand-plaque-screw.tr{top:3px;right:3px;}
  .brand-plaque-screw.bl{bottom:3px;left:3px;}
  .brand-plaque-screw.br{bottom:3px;right:3px;}
  .brand-mark{
    width:32px;height:32px;border-radius:8px;background:var(--teal);
    display:flex;align-items:center;justify-content:center;flex-shrink:0;overflow:hidden;
  }
  .brand-mark svg{width:17px;height:17px;}
  .brand-mark img{width:100%;height:100%;object-fit:cover;}
  header h1{font-size:1rem;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:240px;}
  .role-badge{
    font-size:.72rem;font-weight:500;background:var(--teal-bg);color:var(--teal-text);
    padding:3px 10px;border-radius:20px;
  }
  #profile-menu-dropdown{
    position:absolute;top:calc(100% + 8px);right:0;z-index:30;min-width:200px;
    background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
    box-shadow:0 8px 24px -4px rgba(0,0,0,.25);overflow:hidden;padding:6px;
  }
  /* Start-menu (logo dropdown) item typography -- rescoped from a bare
     .profile-menu-item to #logo-menu-dropdown .profile-menu-item as part
     of the 2026-08-20 "Premium Glass" visual redesign (direct owner
     requirement, visual-only). Confirmed via full-file search this class
     is not used by any other element, so the narrower scope changes
     nothing today -- it just keeps this redesign's footprint strictly
     contained to the Start menu per the owner's explicit scoping
     instruction, in case the class is ever reused elsewhere later. */
  #logo-menu-dropdown .profile-menu-item{
    padding:12px 14px;font-size:.9rem;border-radius:10px;cursor:pointer;color:var(--text-primary);
    position:relative;z-index:1;transition:background-color .12s ease;
  }
  #logo-menu-dropdown .profile-menu-item:active{background:var(--surface-2);}
  /* Desktop-only hover (media-gated the same way .btn-secondary:hover
     already is elsewhere in this file) -- avoids sticky/stuck hover
     backgrounds on touch devices. */
  @media (hover:hover){
    #logo-menu-dropdown .profile-menu-item:hover{background:var(--surface-2);}
  }
  /* Icon prominence without touching the DOM: every row's translated
     label is set via el.textContent (applyTranslations()), which would
     blow away any wrapper <span> put around the emoji. ::first-letter is
     a pseudo-element -- immune to textContent swaps -- and every T-dict
     string (en + es) starts with the emoji, so this keeps working across
     language switches with zero HTML/JS changes.
     ::first-letter also legitimately accepts background/padding/border-
     radius/box-shadow (CSS Pseudo-Elements spec), which is what turns
     the plain emoji into a small dimensional tile behind it -- the emoji
     itself still renders in its own native full-color glyph, so "colorful
     icons" comes for free from the system emoji font, not from CSS. */
  /* Two-tier emboss (2026-08-21, direct owner request: "can you emboss
     the start menu? make it into 2 tear... what i want enmbossed is the
     icons with the bg-square", plus "make the icons a tad bigger.. they
     are too small"). ::first-letter cannot host a nested pseudo-element,
     so both tiers are built from layered box-shadow on this single
     pseudo-element:
       Tier 1 (outer rim, raised) -- top highlight + bottom inner shadow +
       thin outer ring stroke + soft outer drop shadow, lifting the tile
       off the menu surface.
       Tier 2 (inner face, pressed) -- a second, tighter inset shadow pair
       with reversed light direction (dark top / light bottom) so the
       emoji reads as sitting in a shallow dished inner face one step down
       from the outer rim.
     Size bumped modestly from the original 1.28em/5px 6px/8px radius.
     First pass (1.48em/6px 7px/9px) came back "too big" (direct owner
     report, 2026-08-21) -- retuned here to the midpoint between the
     original size and that first pass: 1.38em/5.5px 6.5px/8.5px radius. */
  #logo-menu-dropdown .profile-menu-item::first-letter{
    font-size:1.38em;
    padding:5.5px 6.5px;
    margin-right:.21em;
    border-radius:8.5px;
    background:color-mix(in srgb, var(--text-secondary) 12%, transparent);
    box-shadow:
      /* tier 1 -- outer rim */
      inset 0 1px 0 rgba(255,255,255,.35),
      inset 0 -1px 0 rgba(0,0,0,.16),
      inset 0 0 0 1px rgba(255,255,255,.07),
      0 2px 6px -2px rgba(0,0,0,.24),
      /* tier 2 -- inner face */
      inset 0 2px 3px -1px rgba(0,0,0,.16),
      inset 0 -1px 0 1px rgba(255,255,255,.10);
  }
  /* Per-item accent tint -- id-scoped (every row already has a stable,
     unique id) so this needed zero HTML changes. Kept restrained: a
     translucent color-mix tint behind the glyph, not a solid block.
     Reuses existing theme tokens everywhere one fits (teal, status-
     active/pending, danger); Search/Partner Workflow (blue), Activity
     Center (indigo) and Document Center (orange) have no existing
     MoneyFlow token for those hues, so those four use a small literal
     accent color, same restrained low-alpha treatment as the rest. */
  #menu-commandcenter-btn::first-letter{background:color-mix(in srgb, var(--teal) 20%, transparent);}
  #menu-notifications-btn::first-letter{background:color-mix(in srgb, var(--status-pending) 24%, transparent);}
  #logo-menu-newloan::first-letter{background:color-mix(in srgb, var(--teal) 20%, transparent);}
  #logo-menu-pay::first-letter{background:color-mix(in srgb, var(--status-active) 20%, transparent);}
  #menu-analytics-btn::first-letter{background:color-mix(in srgb, var(--teal) 20%, transparent);}
  #menu-history-btn::first-letter{background:color-mix(in srgb, #8166FF 20%, transparent);}
  #menu-cashcapital-btn::first-letter{background:color-mix(in srgb, #D4AF37 22%, transparent);}
  #menu-partnerworkflow-btn::first-letter{background:color-mix(in srgb, #3B82F6 20%, transparent);}
  #menu-documents-btn::first-letter{background:color-mix(in srgb, #EA911C 20%, transparent);}
  #menu-settings-btn::first-letter{background:color-mix(in srgb, var(--text-secondary) 14%, transparent);}
  #menu-logout-btn::first-letter{background:color-mix(in srgb, var(--danger) 18%, transparent);}
  /* Divider "accent" (2026-08-21, same owner request: "the dividers
     accent them as well"). Layered on top of the existing inline
     border-top via box-shadow -- not replacing it -- as a thin groove:
     a soft dark line immediately above the inline border and a soft
     light line immediately below it, so each divider reads as a shallow
     recessed channel, echoing the icon tiles' dark-top/light-bottom
     emboss direction above. */
  #logo-menu-dropdown .pmi-divider{
    /* margin moved here from each divider's inline style (2026-08-21,
       Android compactness pass) -- purely mechanical, same 6px 4px value,
       so this line changes nothing on its own. Needed so the compact-mode
       media query below can override just this one value cleanly:
       inline styles always beat external stylesheet rules regardless of
       selector specificity, so a margin left inline could only have been
       overridden with !important. Moving it into the class avoids that. */
    margin:6px 4px;
    box-shadow:
      0 -1px 0 rgba(0,0,0,.14),
      0 1px 0 rgba(255,255,255,.08);
  }
  /* Horizontal position (2026-08-21, direct owner report: "the Start Menu
     currently opens too far toward the center of the screen... I want it
     positioned much closer to the LEFT side of the screen, visually
     anchored to the Brothers+Cash logo/start button"). Measured via
     direct DOM getBoundingClientRect() at 390px and 1440px viewport
     widths: left:0 (relative to .title, the logo button's own wrapper)
     already put the dropdown's left edge exactly flush with the logo
     button's left edge at every width tested -- there was no
     width-dependent drift. The visual gap the owner is seeing is
     .title's own inset from the header's rounded card: header has
     padding:12px 16px, so the logo (and, at left:0, the dropdown) sits
     16px in from the header's own left edge. Using left:-16px instead
     pulls the dropdown flush with the header card's own inner-left edge
     -- one full padding-width further left than before, while it still
     opens directly beneath the logo (the logo is the first thing in that
     padded area) so it reads as dropping down from the start/logo area,
     not the header's whole width. This is relative to the same existing
     anchor (.title, position:relative) at every viewport, so it's
     inherently responsive -- no desktop-only hardcoded value, nothing
     UA-sniffed. */
  #logo-menu-dropdown{
    position:absolute;top:calc(100% + 10px);left:-16px;z-index:30;min-width:258px;
    /* Frosted glass, not a transparent window (2026-08-20 refinement,
       direct owner requirement): var(--glass-bg) alone (~.28 alpha) let
       loan names/balances read straight through. color-mix() blends in
       enough of the theme's own opaque --surface to make the underlying
       page recede to soft shapes/color while staying visibly translucent
       and fully theme-reactive (no hardcoded literal background). */
    /* backdrop-filter deliberately removed here (2026-08-20, iOS PWA
       regression fix -- direct owner report: "hovers fine in Safari, but
       scrolls with the page in iPhone app/home-screen mode"). Root cause:
       #logo-menu-dropdown is a descendant of <header>, which already has
       its own position:fixed + backdrop-filter. Two nested elements both
       requesting backdrop-filter is a known trigger for WebKit's iOS
       standalone-mode bug where position:fixed silently stops tracking
       the viewport during scroll. This also matches what was already
       found while tuning the frosted-glass opacity: nested backdrop-
       filter barely blurred anything here anyway (samples got bounded to
       header's own small box), so removing it costs almost nothing
       visually and removes the likely trigger. The frosted look now
       comes entirely from the opaque color-mix() background below. */
    background:
      linear-gradient(180deg, rgba(255,255,255,.05), rgba(0,0,0,.03)),
      color-mix(in srgb, var(--glass-bg) 4%, var(--surface) 96%);
    border:1px solid var(--border);border-radius:calc(var(--radius) + 2px);
    /* Thin double-line rim (border + a same-radius ring one layer further
       out, same technique as .sheet.sheet-layered) + a faint upper-left
       highlight / lower-right shade (same technique as .brand-plaque) +
       a soft two-layer shadow instead of one flat shadow. */
    box-shadow:
      0 0 0 1px var(--border-strong),
      inset 1px 1px 0 rgba(255,255,255,.05),
      inset -1px -1px 0 rgba(0,0,0,.05),
      0 4px 14px -6px rgba(0,0,0,.18),
      0 16px 40px -14px rgba(0,0,0,.28);
    overflow:hidden;padding:0;
    transform-origin:top left;transform:translateY(-6px) scale(.96);opacity:0;
    transition:transform .18s cubic-bezier(.2,.8,.2,1), opacity .15s ease;pointer-events:none;
  }
  #logo-menu-dropdown.open{transform:translateY(0) scale(1);opacity:1;pointer-events:auto;}
  /* Viewport-safe height + internal scroll (2026-08-21, direct owner
     report: "on my Android phone, the Start Menu is too tall/spaced out.
     The bottom of the menu is cut off, so I cannot see the final menu
     items such as Sign Out"). Root cause: #logo-menu-dropdown was sized
     purely by its content -- no max-height, no scroll fallback -- so on
     any viewport short enough that the menu's natural height exceeds the
     space below the header, the bottom simply rendered off-screen with
     no way to reach it (the menu is position:absolute inside the fixed
     header, so it never scrolls with the page). iPhone happened to have
     enough visible height for this menu's current content; shorter
     Android viewports do not.
     Fix lives on this NEW inner wrapper (added around the existing menu
     item/divider rows, no other markup changed) rather than on
     #logo-menu-dropdown itself: that outer element owns the grain
     texture (::before) and the gold/blue "toe" cap (::after), both
     position:absolute + inset/bottom:0 against the outer box. Testing
     confirmed that making #logo-menu-dropdown itself the scroll
     container drags those two decorations along with the scrolled
     content instead of keeping them pinned to the visible card edge --
     the toe cap ends up floating mid-list once scrolled. Splitting scroll
     onto an inner wrapper keeps the outer shell (background/border/grain/
     toe cap) static and correct, and only the items inside scroll.
     `var(--header-height)` + the safe-area top inset is the same
     header-offset formula #startmenu-blur-scrim already uses, reused
     here rather than guessing a new anchor. 100dvh (dynamic viewport
     height) is used over 100vh for the same mobile-chrome reason as the
     login screen's viewport fix, with 100vh kept as a fallback for
     browsers without dvh support. When the menu's natural content height
     is shorter than this cap (e.g. a tall iPhone viewport), max-height
     simply never binds and nothing about the current look/spacing
     changes. */
  #logo-menu-dropdown .startmenu-scroll{
    overflow-x:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch;
    padding:8px;
    max-height:calc(100vh - 8px - var(--header-height, 76px) - 36px);
    max-height:calc(100dvh - 8px - env(safe-area-inset-top) - var(--header-height, 76px) - env(safe-area-inset-bottom) - 20px);
  }
  /* Compact mode for short viewports (2026-08-21, direct owner report:
     "on Android... the menu items have noticeably more vertical spacing...
     the total menu becomes so tall that it turns into a scrollable menu...
     I do NOT want that if we can reasonably avoid it"). Explicitly a
     viewport-HEIGHT media query, not a device/UA check, per direct owner
     instruction ("Do NOT blindly detect Android by user-agent... the
     actual problem is available vertical space"): it activates purely on
     available vertical space, on any device/browser short enough to need
     it, and leaves anything taller (this app's iPhone testing viewport is
     844px) completely untouched -- confirmed the iPhone renders below are
     pixel-identical to the pre-existing approved ones since 844 > 820.
     Every value below is a moderate tightening of vertical rhythm only --
     horizontal padding, text size, and the icon tiles' color/dimensional
     look (background, box-shadow layers) are all left exactly as they
     are; only the sizes that add up across 12 rows + 3 dividers change,
     which is where the real savings are:
       row vertical padding   12px -> 8px   (x2 x12 rows = 96px)
       icon tile font-size    1.38em -> 1.2em, padding 5.5px 6.5px ->
                               4.5px 5.5px, radius 8.5px -> 7.5px (the
                               icon tile is taller than the row's own text
                               line-height in most rows, so shrinking it
                               reduces row height further, not just its
                               own box)
       divider margin         6px 4px -> 4px 4px (x2 x3 dividers = 12px)
       scroll wrapper padding 8px -> 6px (x2 = 4px)
     Together this removes roughly 110-160px of accumulated dead space
     from the menu's natural height (measured below), which is what
     actually fits Command Center through Sign Out onto one panel on a
     normal Android viewport without scrolling -- the internal scroll
     fallback from the prior fix stays in place underneath for any
     viewport still too short even after compacting, exactly as
     requested. */
  @media (max-height:820px){
    #logo-menu-dropdown .profile-menu-item{
      padding:8px 14px;
    }
    #logo-menu-dropdown .profile-menu-item::first-letter{
      font-size:1.2em;
      padding:4.5px 5.5px;
      margin-right:.18em;
      border-radius:7.5px;
    }
    #logo-menu-dropdown .pmi-divider{
      margin:4px 4px;
    }
    #logo-menu-dropdown .startmenu-scroll{
      padding:6px;
    }
  }
  /* Extremely subtle static grain -- a tiled CSS gradient, not an image
     or canvas, so it's a one-time cheap paint with no per-frame cost.
     Sits behind the menu items (z-index:1 above, set on
     .profile-menu-item) so text stays fully crisp. */
  #logo-menu-dropdown::before{
    content:"";position:absolute;inset:0;pointer-events:none;border-radius:inherit;
    background-image:repeating-conic-gradient(rgba(127,127,127,.06) 0% 25%, transparent 0% 50%);
    background-size:3px 3px;opacity:.5;mix-blend-mode:overlay;
  }
  /* Start Menu "toe cap" (2026-08-21, direct owner requirement): a
     metallic accent strip along the very bottom edge, gold or blue to
     match whichever logo style is currently active -- same idea and
     same exact gradients as .brand-plaque-text/.brand-plaque-screw
     above, just applied here instead of the header plaque. Reacts to
     [data-logo-style] exactly like those do (and like the app-active
     watermark swap further up): gold is the base/unscoped rule, blue
     is the explicit override, matching the existing convention.
     A plain rect flush against the bottom edge -- #logo-menu-dropdown
     already has overflow:hidden + border-radius, so the corners of
     this strip get clipped into the menu's own rounded bottom corners
     for free, no need to duplicate the radius value here. Sits in the
     menu's own 8px bottom padding gutter, below the last row's text,
     so it doesn't compete with or crowd "Sign out". Purely decorative:
     pointer-events:none, no effect on click/close behavior. */
  #logo-menu-dropdown::after{
    content:"";position:absolute;left:0;right:0;bottom:0;height:5px;
    background:linear-gradient(180deg, #F0DA96 0%, #D4AF37 45%, #9A7A28 80%, #6B551B 100%);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.18), 0 -1px 2px rgba(0,0,0,.25);
    pointer-events:none;
  }
  [data-logo-style="blue"] #logo-menu-dropdown::after{
    background:linear-gradient(180deg, #C7E2F8 0%, #5DA9E8 45%, #2E6FA8 80%, #1E4C75 100%);
  }
  /* Richer glass under the dedicated Liquid-Glass theme, mirroring how
     header and .sheet already split a modest all-theme look from a
     deeper [data-theme="glass"] one -- --glass-blur/--glass-saturate/
     --glass-highlight only exist in that theme's scope. */
  /* backdrop-filter removed here too, same iOS PWA fix as the base rule
     above -- this override no longer changes blur, only shadow depth. */
  [data-theme="glass"] #logo-menu-dropdown{
    background:
      linear-gradient(180deg, rgba(255,255,255,.05), rgba(0,0,0,.03)),
      color-mix(in srgb, var(--glass-bg) 4%, var(--surface) 96%);
    box-shadow:
      0 0 0 1px var(--border-strong),
      inset 0 1px 0 var(--glass-highlight),
      inset -1px -1px 0 rgba(0,0,0,.16),
      0 4px 16px -6px rgba(0,0,0,.35),
      0 20px 48px -16px rgba(0,0,0,.55);
  }
  .icon-btn{
    background:none;border:none;color:var(--text-secondary);padding:6px;display:flex;
    border-radius:8px;
  }
  .icon-btn:active{background:var(--surface-2);}
  .icon-btn svg{width:19px;height:19px;}

  main{padding:calc(8px + env(safe-area-inset-top) + var(--header-height, 76px) + 16px) 16px 16px;max-width:640px;margin:0 auto;}

  /* Start Menu background blur scrim (2026-08-21, direct owner
     requirement -- "the rest of the application behind the menu should
     become heavily blurred so that the Start Menu is the sharp visual
     focus"). A dedicated element, sibling of <header> and placed
     immediately before <main> in the DOM -- deliberately NOT nested
     inside header, so it can never re-trigger the iOS PWA nested-
     backdrop-filter bug that removing backdrop-filter from
     #logo-menu-dropdown fixed on 2026-08-20 (two nested elements both
     requesting backdrop-filter breaks position:fixed tracking in iOS
     standalone mode). Positioned to start exactly where header ends
     (using the same --header-height custom property main's own padding
     already relies on), so it only ever covers app content, never
     header itself.
     Stacking: z-index:24, one below header's 25 -- since both are
     direct siblings in the same stacking context, header (and
     everything nested inside it, including #logo-menu-dropdown's own
     z-index:30, which only matters *within* header's separate stacking
     context) always paints above the scrim regardless of the scrim's
     own z-index value.
     Visibility is pure CSS: :has(#logo-menu-dropdown.open) keys off the
     exact same .open class the existing menu-toggle JS already applies.
     At rest the filter is blur(0px), a no-op the browser can skip almost
     entirely (no persistent blur cost while the menu is closed); it only
     becomes real work for the brief window the menu is open, which
     matters given this app runs on a Raspberry Pi and phones.
     pointer-events stays none while CLOSED, so it never intercepts
     clicks on the normal app UI. While OPEN it flips to auto (2026-08-21,
     direct owner report: tapping the blurred area over a button both
     closed the menu AND activated that button underneath -- "the user
     is effectively clicking application controls blindly through the
     blur"). Root cause: this scrim was pointer-events:none at all times
     by original design (see history below), so every "outside click"
     actually landed on whatever app element was really underneath it,
     firing that element's own click handler, and only THEN bubbled to
     the document-level outside-click listener that closes the menu --
     one tap, two actions. Making the scrim pointer-events:auto while
     open turns it into a real hit-testing target: the browser now
     delivers the click to the scrim itself (topmost element at that
     screen point), so the app element underneath never receives it and
     its own handler never fires. The scrim's own click handler (JS,
     added 2026-08-21 next to the other logo-menu-dropdown handlers)
     closes the menu and calls stopPropagation() so the click is fully
     consumed -- exactly one action per tap. */
  #startmenu-blur-scrim{
    position:fixed;
    top:calc(8px + env(safe-area-inset-top) + var(--header-height, 76px));
    left:0;right:0;bottom:0;
    z-index:24;
    pointer-events:none;
    backdrop-filter:blur(0px);
    -webkit-backdrop-filter:blur(0px);
    transition:backdrop-filter .28s cubic-bezier(.22,.61,.36,1), -webkit-backdrop-filter .28s cubic-bezier(.22,.61,.36,1);
  }
  /* Blur strength retuned (2026-08-21, direct owner report: "the blur is
     too heavy... i want to see some more of the back not too mutch but
     its to heavy") -- cut from 20px to 10px so the loan list behind the
     menu stays legible as soft shapes/color instead of an almost-flat
     wash, while still keeping enough blur to read as a focus scrim. */
  header:has(#logo-menu-dropdown.open) + #startmenu-blur-scrim{
    backdrop-filter:blur(10px) saturate(1.15);
    -webkit-backdrop-filter:blur(10px) saturate(1.15);
    /* Click-through fix -- see the #startmenu-blur-scrim comment above.
       Same :has(#logo-menu-dropdown.open) condition already used for the
       blur itself, so "scrim is a click barrier" and "scrim is visibly
       blurred" always turn on/off together by construction -- no separate
       state to keep in sync. */
    pointer-events:auto;
  }

  .summary-strip{
    display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:18px;
  }
  .summary-chip{
    background:var(--surface);border-radius:var(--radius);padding:14px;
    box-shadow:0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);
  }
  .summary-chip .label{font-size:.78rem;color:var(--text-secondary);margin-bottom:4px;}
  .summary-chip .value{font-size:1.35rem;font-weight:500;}

  .search-row{
    margin:0;
  }
  #search-dropdown{
    /* Real bug found (2026-07-21): this opened UPWARD (bottom:100%+4px),
       which made sense only if the search bar had lots of room above
       it. It doesn't -- .search-row sits sticky right below the header,
       leaving barely 80px of clearance. A dropdown trying to open
       upward into that tiny gap is exactly what looked "disconnected,
       way up at the top" -- it wasn't near the bar, it was fighting to
       fit into a space too small for it. Opens downward now, into the
       loans list area below, where there's ample real room. */
    position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:20;
    background:var(--surface);border:1px solid var(--border);border-radius:11px;
    box-shadow:0 8px 24px -4px rgba(0,0,0,.25);max-height:min(50vh,340px);overflow-y:auto;
  }
  #search-dropdown .loan-row{border-bottom:1px solid var(--border);}
  #search-dropdown .loan-row:last-child{border-bottom:none;}
  #customer-suggestions{
    position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:20;
    display:none;
  }
  #customer-suggestions.has-content{
    display:block;background:var(--surface);border:1px solid var(--border);border-radius:11px;
    box-shadow:0 8px 24px -4px rgba(0,0,0,.25);padding:10px;
  }
  #customer-suggestions .cs-hint{font-size:.82rem;color:var(--text-secondary);}
  #customer-suggestions .cs-chips{display:flex;flex-wrap:wrap;gap:6px;}
  #customer-suggestions .cs-chip{
    background:var(--teal-bg);color:var(--teal-text);border-radius:16px;
    padding:6px 12px;font-size:.85rem;cursor:pointer;font-weight:500;
  }
  #customer-suggestions .cs-chip:active{background:var(--teal);color:#fff;}
  .search-row svg{
    position:absolute;left:16px;top:50%;transform:translateY(-50%);
    width:20px;height:20px;color:var(--teal);pointer-events:none;
  }
  .search-row input{
    width:100%;padding:16px 44px 16px 46px;border:1px solid var(--border);border-radius:28px;
    font-size:1.05rem;background:var(--glass-bg);color:var(--text-primary);
    backdrop-filter:blur(14px) saturate(1.5);-webkit-backdrop-filter:blur(14px) saturate(1.5);
    box-shadow:0 4px 18px -4px rgba(0,0,0,.14);
    transition:box-shadow .2s ease, border-color .2s ease;
  }
  .search-row input:focus{
    outline:none;border-color:var(--teal);
    box-shadow:0 4px 22px -4px rgba(var(--teal-rgb),.35);
  }
  .search-row input::placeholder{color:var(--text-secondary);opacity:.8;}
  .search-row input:focus{outline:none;border-color:var(--teal-deep);box-shadow:0 0 0 3px var(--teal-bg), 0 2px 10px -2px rgba(0,0,0,.12);}
  header .search-row input{
    padding:12px 40px 12px 42px;font-size:1rem;border-radius:24px;
  }
  header .search-row svg{left:14px;width:18px;height:18px;}
  header .search-clear-btn{right:10px;}
  .search-clear-btn{
    position:absolute;right:8px;top:50%;transform:translateY(-50%);
    width:22px;height:22px;border-radius:50%;border:none;background:var(--surface-2);
    color:var(--text-secondary);font-size:16px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;
  }
  .field .search-clear-btn{right:10px;}

  /* Main customer/loan list -- spaced cards, page background showing
     through the gaps (2026-08-29, direct owner request: "I like the
     Prism theme's main-screen customer list... Apply this same spaced,
     background-visible treatment to every theme"). This IS the look
     Prism/Prism Light pioneered (2026-08-10) -- generalized here into
     the base rule instead of staying a Prism-only override, so every
     theme gets identical spacing/card treatment, just each theme's own
     var(--surface)/var(--border)/var(--page) colors. */
  .loan-list{
    background:transparent;border:none;box-shadow:none;overflow:visible;
    display:flex;flex-direction:column;gap:8px;
  }
  .loan-row{
    display:flex;align-items:center;justify-content:space-between;gap:10px;
    padding:14px 16px;
    background:var(--surface);
    border:1px solid var(--border);border-radius:12px;
    box-shadow:0 2px 8px -6px rgba(0,0,0,.15);
    transition:background-color .14s ease;
  }
  .loan-row:active{background-color:var(--surface-2);}
  @media (hover:hover){
    .loan-row:hover{background-color:var(--surface-2);}
  }
  .loan-left{display:flex;align-items:center;gap:12px;min-width:0;flex:1;}
  .avatar{
    width:38px;height:38px;border-radius:50%;background:var(--surface-2);
    display:flex;align-items:center;justify-content:center;flex-shrink:0;
    font-size:.85rem;font-weight:500;color:var(--text-secondary);
    box-shadow:inset 0 0 0 1px var(--border);
  }
  .avatar.overdue{background:var(--danger-bg);color:var(--danger-text);}
  .loan-info{min-width:0;}
  .loan-name{font-size:.92rem;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
  .loan-type{font-size:.78rem;color:var(--text-secondary);margin-top:1px;}
  .loan-type.overdue{color:var(--danger-text);}
  .loan-right{text-align:right;flex-shrink:0;}
  .loan-balance{font-size:.98rem;font-weight:500;}
  .btn-collect{
    font-size:.76rem;padding:5px 12px;margin-top:5px;border:1px solid var(--border);
    background:var(--surface);color:var(--text-primary);border-radius:7px;font-weight:500;
  }
  .btn-collect:active{background:var(--surface-2);}

  .empty-state{text-align:center;padding:60px 20px;color:var(--text-secondary);}
  .empty-state h3{color:var(--text-primary);margin-bottom:6px;font-weight:500;}

  .fab{
    position:fixed;bottom:22px;right:20px;width:52px;height:52px;border-radius:50%;
    background:var(--teal);color:#fff;border:none;z-index:20;
    display:flex;align-items:center;justify-content:center;
  }
  .fab:active{background:var(--teal-deep);}
  .fab svg{width:22px;height:22px;}

  /* ---------- Sheet / modal ---------- */
  .sheet-backdrop{
    display:none;position:fixed;inset:0;background:rgba(22,24,27,.5);z-index:30;
    backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
  }
  .sheet{
    position:fixed;left:0;right:0;bottom:0;
    background:
      radial-gradient(ellipse 500px 200px at 20% 0%, rgba(var(--teal-rgb),.05), transparent 70%),
      linear-gradient(180deg, var(--page) 0%, var(--page) 100%);
    border-top:3px solid var(--teal);
    border-radius:16px 16px 0 0;padding:20px 20px calc(24px + env(safe-area-inset-bottom));
    max-height:88vh;overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;z-index:31;
    transform:translateY(100%);opacity:.4;
    transition:transform .5s cubic-bezier(.32,.72,.33,1), opacity .4s ease, filter .2s ease;
  }
  /* Sideways scroll fix, app-wide (2026-08-29, direct owner report --
     "for the record a payment it cannot scroll sideways... none of the
     menus should. they should conform to the screen size"). Every sheet
     only ever set `overflow-y:auto`, never `overflow-x`. Per the CSS
     spec, when one overflow axis is non-visible and the other isn't set,
     the unset axis's USED value becomes `auto` too (so a scrollbar can
     appear independently on either axis) -- meaning every sheet in the
     app was always technically horizontally scrollable if its content
     was ever even a pixel wider than the sheet (a stray fixed-width
     element, a long unbroken string, a rounding error), which reads as
     "the menu can scroll sideways" even though nothing was ever supposed
     to cause that. Explicit `overflow-x:hidden` above closes that off
     for every sheet at once, not just Record a Payment -- content that
     would have caused horizontal scroll is simply clipped at the sheet's
     own edge instead. */
  .sheet.sheet-open{transform:translateY(0);opacity:1;}
  /* Search sheet slides all the way to the top instead of stopping at
     88vh (2026-07-30, direct owner requirement): on a phone, the
     on-screen keyboard covers roughly the bottom third of the screen
     the moment you tap the search field -- a sheet that stops short of
     the top leaves almost no room for results between the search bar
     and the keyboard. !important is deliberate here, not a shortcut:
     openSheet() sets this element's inline style.display to "block" on
     every open (confirmed directly), which would otherwise silently
     override this rule's own display:flex and break the layout. */
  #search-sheet.sheet-open{
    display:flex !important;flex-direction:column;
    max-height:100vh;height:100vh;border-radius:0;
    padding-top:calc(20px + env(safe-area-inset-top));
  }
  .sheet.sheet-layered{
    left:10px;right:10px;bottom:10px;
    border-radius:16px;
    box-shadow:0 -4px 24px rgba(0,0,0,.18), 0 0 0 1px var(--border);
    border-top:3px solid var(--teal);
    max-height:82vh;
  }
  /* Whatever sheet is no longer on top gets visibly pushed back -- a
     real color/brightness shift, not just the top sheet's own border,
     so it reads as "something opened" even when both sheets are
     otherwise the same shade. Also disabled for scroll/touch: without
     this, a stacked sheet (e.g. Settings behind Edit User) still has
     its own overflow-y:auto underneath, and a scroll gesture that runs
     past the end of the TOP sheet gets picked up by the one behind it,
     which reads as the whole screen "bouncing back". pointer-events:none
     makes the top sheet the only one that can receive scroll/touch at all. */
  .sheet.sheet-dimmed{filter:brightness(.88) saturate(.85);pointer-events:none;}

  /* ---------- Move Money Wizard (2026-08-26) ---------- */
  /* A genuine full-screen guided flow, replacing the Phase 2 hub the
     owner called out as "just a hyperlink to the boxes below." Opened
     via the app's real sheet-stacking system (openSheet/closeTopSheet)
     while Finance Center is still open underneath, so it naturally
     picks up .sheet-layered -- the ID selector below (specificity beats
     .sheet.sheet-layered's two classes) is what forces true edge-to-edge
     full-screen instead of the smaller floating-card treatment a
     layered sheet gets by default, same technique #search-sheet already
     uses for its own full-screen override. */
  #mm-wizard-sheet.sheet-open{
    display:flex;flex-direction:column;
    left:0;right:0;top:0;bottom:0;
    max-height:100vh;height:100vh;border-radius:0;
    padding:0;
  }
  .mmw-header{
    display:flex;align-items:center;justify-content:space-between;flex-shrink:0;gap:10px;
    padding:calc(14px + env(safe-area-inset-top)) 14px 12px;
    border-bottom:1px solid var(--border);
  }
  .mmw-header-title{font-size:1.02rem;font-weight:700;text-align:center;flex:1;padding:0 4px;}
  .mmw-icon-btn{
    width:34px;height:34px;border-radius:50%;flex-shrink:0;
    background:var(--surface);border:1px solid var(--border);
    display:flex;align-items:center;justify-content:center;
    color:var(--text-secondary);font-size:1rem;
  }
  .mmw-icon-btn:active{background:var(--surface-2);}
  .mmw-progress{display:flex;justify-content:center;gap:6px;padding:12px 0 4px;flex-shrink:0;}
  .mmw-dot{width:7px;height:7px;border-radius:50%;background:var(--border-strong);}
  .mmw-dot-active{background:var(--teal);}
  .mmw-body{flex:1;overflow-y:auto;overscroll-behavior:contain;padding:18px 18px calc(24px + env(safe-area-inset-bottom));}

  .mmw-direction-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:12px;}
  /* (2026-08-29, direct owner request: "when you click on Move Money in
     finance center, the two button[s] that show up... should be
     Debossed (nicely) and the icons inside Embossed, and the letters
     with a bit of shadows. That or similar idea should carry through
     out that guided menu until the end." Owner reviewed a mockup of
     this before it shipped and approved it ("kinda ok"), with one
     revision -- see .mmw-review-amount further down.
     Concept: the direction cards (and the reason-list rows just below)
     read as a shallow recessed tray you choose FROM, rather than a
     raised card -- dark inset shadow along the top (where light can't
     reach into the recess) + a soft light inset along the bottom inner
     lip (where light grazes the far wall), the exact highlight/shadow
     positions of the owner-approved raised .sheet-hero recipe, just
     swapped, with the outer lifting shadow dropped entirely (a recess
     doesn't lift off the page). The forward-motion buttons later in
     this same flow (#mm-wizard-sheet .btn-primary, below) get the
     OPPOSITE, actually-raised treatment on purpose -- a button you
     push should look pushable, not like it's already been pressed in;
     that's the "carry through" part, not literal deboss-everything. */
  .mmw-direction-card{
    background:
      linear-gradient(180deg, rgba(0,0,0,.05) 0%, rgba(0,0,0,0) 46%),
      var(--surface-2);
    border:1px solid var(--border);border-radius:16px;
    box-shadow:
      inset 0 3px 6px -1px rgba(0,0,0,.20),
      inset 0 1px 3px rgba(0,0,0,.10),
      inset 0 -1px 0 rgba(255,255,255,.55),
      inset 1px 0 0 rgba(0,0,0,.04),
      inset -1px 0 0 rgba(255,255,255,.20);
    padding:26px 14px;display:flex;flex-direction:column;align-items:center;gap:8px;text-align:center;
    transition:transform .16s cubic-bezier(.22,.61,.36,1), border-color .16s ease, background .16s ease;
  }
  .mmw-direction-card:active{
    transform:scale(.97);background:var(--surface);
    box-shadow:
      inset 0 4px 8px -1px rgba(0,0,0,.26),
      inset 0 1px 3px rgba(0,0,0,.12),
      inset 0 -1px 0 rgba(255,255,255,.4);
  }
  @media (hover:hover){
    .mmw-direction-card:hover{border-color:var(--teal);background:var(--teal-bg);}
  }
  /* Icon medallion: a small raised coin the emoji sits ON, rather than
     a text-shadow directly on the glyph -- multi-colour emoji +
     text-shadow renders as a muddy smudge, not a clean emboss (the
     exact mistake made -- and reverted -- on the menu headings earlier
     this session). Bright top inset + soft dark bottom inset + a real
     outer drop shadow so it visibly lifts off the recessed card. */
  .mmw-direction-icon{
    width:50px;height:50px;margin:0 auto;
    display:flex;align-items:center;justify-content:center;
    font-size:1.55rem;line-height:1;border-radius:50%;
    background:linear-gradient(155deg, var(--surface) 0%, var(--surface-2) 100%);
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,.7),
      inset 0 -1.5px 1.5px rgba(0,0,0,.10),
      0 1px 2px rgba(0,0,0,.10),
      0 4px 8px -3px rgba(0,0,0,.22);
  }
  .mmw-direction-label{font-weight:700;font-size:.95rem;text-shadow:0 1px 1.5px rgba(0,0,0,.16);}
  .mmw-direction-desc{font-size:.76rem;color:var(--text-secondary);}

  .mmw-reason-list{display:flex;flex-direction:column;gap:8px;margin-top:8px;}
  /* Same recessed-tray idea as the direction cards above, lighter
     touch since these are list rows, not big cards. */
  .mmw-reason-row{
    display:flex;align-items:center;gap:12px;width:100%;
    background:
      linear-gradient(180deg, rgba(0,0,0,.04) 0%, rgba(0,0,0,0) 55%),
      var(--surface-2);
    border:1px solid var(--border);border-radius:12px;padding:14px;
    box-shadow:
      inset 0 2px 4px -1px rgba(0,0,0,.16),
      inset 0 -1px 0 rgba(255,255,255,.5);
    text-align:left;
  }
  .mmw-reason-row:active{background:var(--surface);box-shadow:inset 0 3px 5px -1px rgba(0,0,0,.20);}
  @media (hover:hover){
    .mmw-reason-row:hover{border-color:var(--border-strong);}
  }
  .mmw-reason-icon{
    width:32px;height:32px;flex-shrink:0;
    display:flex;align-items:center;justify-content:center;
    font-size:1.02rem;border-radius:50%;
    background:linear-gradient(155deg, var(--surface) 0%, var(--surface-2) 100%);
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,.65),
      inset 0 -1px 1px rgba(0,0,0,.10),
      0 2px 4px -2px rgba(0,0,0,.20);
  }
  .mmw-reason-title{flex:1;font-size:.9rem;font-weight:500;text-shadow:0 1px 1px rgba(0,0,0,.10);}
  .mmw-reason-chevron{color:var(--text-muted);font-size:1.1rem;}

  .mmw-empty-state{color:var(--text-muted);font-size:.88rem;text-align:center;padding:40px 10px;}

  .mmw-handoff{display:flex;flex-direction:column;align-items:center;text-align:center;gap:14px;padding:30px 6px 10px;}
  .mmw-handoff-text{font-size:.88rem;color:var(--text-secondary);max-width:320px;}
  .mmw-handoff .btn-primary{width:100%;max-width:320px;margin-top:6px;}

  .mmw-review{background:var(--surface-2);border-radius:14px;padding:20px 16px;margin-bottom:18px;text-align:center;}
  /* (2026-08-29, owner correction after reviewing the mockup above:
     "on the review menu before confirming, the amount should look
     larger. should look like the Affirm Available money size.")
     1.7rem -> 2.75rem, a real hero number the way a balance display
     reads in that kind of app, not just a slightly-bigger line of
     text -- tabular-nums so the digits stay evenly spaced at this
     size, tightened letter-spacing so a large bold numeral doesn't
     read as loose. */
  .mmw-review-amount{
    font-size:2.75rem;font-weight:800;line-height:1.1;letter-spacing:-.01em;
    font-variant-numeric:tabular-nums;margin-bottom:12px;
  }
  .mmw-review-message{font-size:.86rem;color:var(--text-secondary);line-height:1.5;text-align:left;}

  .mmw-success{display:flex;flex-direction:column;align-items:center;text-align:center;gap:14px;padding:50px 6px 10px;}
  .mmw-success-text{font-size:.92rem;color:var(--text-secondary);max-width:320px;white-space:pre-line;}
  .mmw-success .btn-primary{width:100%;max-width:320px;margin-top:10px;}
  /* Handoff + success icons: the same medallion idea as the direction
     cards, larger, carrying the language through to the very end of
     the flow. */
  .mmw-handoff-icon, .mmw-success-icon{
    width:74px;height:74px;margin:0 auto;
    display:flex;align-items:center;justify-content:center;
    font-size:2.2rem;border-radius:50%;
    background:linear-gradient(155deg, var(--surface) 0%, var(--surface-2) 100%);
    box-shadow:
      inset 0 1.5px 0 rgba(255,255,255,.7),
      inset 0 -2px 2px rgba(0,0,0,.10),
      0 2px 4px rgba(0,0,0,.12),
      0 6px 14px -4px rgba(0,0,0,.24);
  }
  .mmw-success-icon{font-size:2.6rem;}
  /* Forward-motion buttons throughout this flow (Continue / Confirm /
     Done / the handoff "go" buttons) read as the raised, pushable
     counterpart to the recessed selection surfaces above -- carrying
     the same dimensional language through to the end without making a
     call-to-action look like it's already been pressed. Scoped to this
     wizard only; the rest of the app's buttons are untouched. */
  #mm-wizard-sheet .btn-primary{
    box-shadow:
      inset 0 1px 0 rgba(255,255,255,.3),
      inset 0 -2px 3px rgba(0,0,0,.14),
      0 1px 2px rgba(0,0,0,.12),
      0 4px 10px -3px rgba(0,0,0,.28);
    text-shadow:0 1px 1.5px rgba(0,0,0,.20);
  }
  #mm-wizard-sheet .btn-primary:active{box-shadow:inset 0 2px 4px rgba(0,0,0,.25);}
  .sheet-handle{width:36px;height:4px;background:var(--border);border-radius:2px;margin:0 auto 16px;}
  .sheet-close-wrap{
    position:sticky;top:4px;z-index:10;
    display:flex;justify-content:flex-end;margin:-6px -6px 0 0;height:0;
  }
  .sheet-close-btn{
    position:relative;top:6px;right:6px;width:34px;height:34px;border-radius:50%;
    background:var(--surface);border:1px solid var(--border);display:flex;align-items:center;justify-content:center;
    color:var(--text-secondary);cursor:pointer;box-shadow:0 2px 8px rgba(0,0,0,.12);
  }
  .sheet-close-btn:active{background:var(--border);}
  .sheet h2{font-size:1.1rem;margin-bottom:4px;}
  .sheet .sheet-sub{color:var(--text-secondary);font-size:.85rem;margin-bottom:18px;}
  /* New Loan header "plaque" -- restyled 2026-08-21, round 2, per direct
     owner request: "USE CONCEPT #4 -- DARK GLASS LUXURY -- AS THE VISUAL
     REFERENCE". No reference image reached this session (same gap as a
     couple of earlier rounds), so this is built from the owner's own
     written spec: "dark premium glass surface, refined gold accent, very
     thin gold precision rim, subtle dimensional depth, restrained gold
     illumination, dimensional circular icon treatment... private banking
     + premium fintech + modern mobile OS... restrained, NOT
     neon/gaming/glowing/flashy."

     Theme handling: rather than hardcoding one look everywhere (fights
     the app's own theme system) or inventing a new token set (owner
     explicitly ruled this out -- "do not create a new global design
     system"), this defines a small set of LOCAL custom properties on
     #loan-sheet itself -- visible to the plaque, the icon medallion, the
     close button, and the money-label accent below, since all of those
     are descendants of #loan-sheet. They default to this app's own
     existing --teal-family tokens, so every light theme and Gold/Eclipse
     (whose own accent already IS a restrained gold) need zero override
     -- they just keep their own theme's already-approved personality,
     which is exactly what the owner asked for: "adapt the same design
     language... rather than forcing a black rectangle into the middle of
     the light UI." Dark and Glass specifically get a hardcoded gold
     override, since the owner named exactly those two for "the full
     dark-glass/gold treatment" and neither theme's native accent (Dark=
     green, Glass=blue) is gold on its own. The gold hex (#D4AF37 /
     #B8860B / #F5D061) is NOT a new color invented for this -- it's
     copied exactly from this app's own existing Gold theme tokens
     (--teal/--teal-deep/--teal-text under [data-theme="gold"]) and from
     #menu-cashcapital-btn's own icon tint, so it's the same "gold" this
     app already has elsewhere, not a new one. */
  #loan-sheet{
    --nlh-rim: var(--border-strong);
    --nlh-accent: var(--teal-deep);
    --nlh-accent-deep: var(--teal-deep);
    --nlh-accent-bright: var(--teal-text);
  }
  :root:not([data-theme]) #loan-sheet,
  [data-theme="prism-light"] #loan-sheet,
  [data-theme="dark"] #loan-sheet,
  [data-theme="glass"] #loan-sheet{
    /* --nlh-rim swapped from gold to deep black (2026-08-21 follow-up,
       direct owner request after reviewing the gold-ring render: "like
       everything except the yellow rim around and the X surrounded by
       yellow. make those lines Deep Black instead of Yellow... leave the
       yellow twinkle stars and the yellow Ring around the credit card").
       --nlh-rim is used at exactly two places, both being the "lines" the
       owner means -- the outer plaque rim/box-shadow (.nl-header-plaque,
       below) and the close button's ring (#loan-sheet .sheet-close-btn,
       below) -- so changing this one variable recolors both together
       without touching the icon's ring or the stars, which use the
       SEPARATE --nlh-accent / --nlh-accent-bright variables (still gold,
       unchanged) exactly as requested. Deep black rather than pure #000
       so it still reads as a defined line against this theme's own
       surface-2 (#262A2F on Dark / #1A1E27 on Glass) instead of vanishing
       into a same-color background.
       2026-08-21, THIRD follow-up: owner confirmed the full Dark/Glass
       treatment (black rim/title/ring/X + gold stars) should ALSO apply
       to Prism Light/Default ("did you make a version for the light
       prism as well? we need both" -> "Same full treatment, with gold
       stars too"), so this selector list grows from 2 themes to the 4
       themes this whole New Loan header round has ever been tested
       against (Default/Dark/Glass/Prism Light -- the minimum theme set
       named in the ORIGINAL round-1 spec). The 5 untested themes (Gold,
       Neon, Prism, Eclipse, Aurora) are deliberately left out of this
       selector list -- they were never part of this thread, so they keep
       falling back to the un-scoped #loan-sheet block below (their own
       existing --teal-family accent), unchanged. Default has no
       data-theme attribute in production (confirmed via grep on where
       THEME gets applied: `if (THEME !== "light" && THEME)
       document.documentElement.setAttribute("data-theme", THEME)` --
       "light" deliberately sets none), hence the :root:not([data-theme])
       half of this selector. */
    --nlh-rim: rgba(0,0,0,.74);
    --nlh-accent:#D4AF37;
    --nlh-accent-deep:#B8860B;
    --nlh-accent-bright:#F5D061;
  }
  /* White theme ONLY -- contrast fix (2026-08-29, direct owner report:
     "the yellow elements, especially yellow stars, do not have enough
     contrast against the white background"). ":root:not([data-theme])"
     is this app's own internal "light"/Default theme -- the only one of
     the 4 gold-treatment themes with a near-white plaque (--surface-2
     is #F1F3F1 here vs charcoal on Dark/Glass, or Prism Light's own
     tinted surface) -- so it's the only one where the pale highlight
     tone above (#F5D061, tuned for a DARK backdrop) goes nearly
     invisible. Dark/Glass/Prism Light are untouched -- they already
     look right per the owner ("All New Loan themes look good except
     the White theme").
     Fix is one variable: --nlh-accent-bright deepens from pale yellow
     to a real antique-gold/bronze (#8B6B0F) that actually holds
     contrast against near-white -- not a new color invented for this,
     it's a deepened step along the same gold family already used two
     lines up (--nlh-accent-deep:#B8860B is the halfway point). Since
     --nlh-accent-bright is the ONE variable driving the stars, the
     icon's inner glow, and the icon ring's inner highlight, this one
     change keeps all three consistent with each other, same as before.
     Explicitly NOT the old "black outline" approach the owner asked to
     avoid if harsh -- instead the stars get a soft warm-toned shadow
     (not black) for a touch of polish/dimension, tasteful rather than
     a stark stroke. */
  :root:not([data-theme]) #loan-sheet{
    --nlh-accent-bright:#8B6B0F;
  }
  :root:not([data-theme]) .nl-header-star{
    text-shadow:0 1px 2px rgba(139,107,15,.5), 0 0 5px rgba(184,134,11,.35);
  }
  /* Title text: gold gradient text-clip swapped for a Deep Black embossed
     treatment (2026-08-21 follow-up, same owner request above: "make the
     word New Loan Deep Black Embossed"). Solid near-black fill (not pure
     #000, so it still separates from the plaque's own dark surface color)
     plus a two-layer text-shadow -- a faint light-source highlight offset
     up-left and a darker shadow offset down-right -- is the same raised/
     embossed light-direction convention already used for the form fields
     a round earlier ("faint upper/left highlight, darker lower/right
     edge"), just applied to text instead of an input's box-shadow, so the
     title reads as a raised black-on-black plate rather than a flat color
     change. 2026-08-21 THIRD follow-up: extended to Prism Light/Default
     too (see the #loan-sheet variable block above for the full "why" --
     same owner request, same 4-theme scope). On the light themes the
     base text color was already near-black (var(--text-primary)), so
     this mainly ADDS the embossed edge; the highlight-side alpha is
     bumped up for those two themes specifically (below) since a faint
     white highlight is far less visible against an already-near-white
     plaque than against Dark/Glass's charcoal one -- same effect,
     different theme-appropriate intensity, not a different design.
     #nl-sheet-title's real text is still set only via el.textContent by
     applyTranslations(); this only recolors/shadows the rendered glyphs,
     nothing about the element's content model changes. */
  /* 2026-08-28 follow-up, direct owner request ("the heading needs to be
     in the center... the emboss/deboss is not even, it looks different"
     -- comparing this header against the rest of the app after the
     app-wide heading-centering pass). Root cause: the deep-black-emboss
     treatment above was only ever wired up for 4 of the app's 9 themes
     (Dark/Glass/Default/Prism Light); the other 5 (Gold, Neon, Prism,
     Eclipse, Aurora) fell through to plain flat text with no emboss at
     all -- THAT was the "not even". Fix: one universal rule instead of
     two theme-scoped ones, using var(--text-primary) (this app's own
     normal heading color, already correct light-on-dark or dark-on-light
     per theme with zero extra logic) plus the same faint upper-left
     highlight / lower-right shadow emboss convention, at an intensity
     tuned to read on every theme instead of the two different hand-tuned
     alpha pairs the 4-theme version needed. Reviewed and approved via
     rendered mockup (dark/neon/gold shown side by side) before shipping,
     owner: "go live with it. i love it". */
  #nl-sheet-title{
    color:var(--text-primary);
    text-shadow:
      -1px -1px 0 rgba(255,255,255,.14),
      1px 1px 1px rgba(0,0,0,.45);
  }
  .nl-header-plaque{
    position:relative;background:
      radial-gradient(ellipse 150px 60px at 16% 0%, color-mix(in srgb, var(--nlh-accent-bright) 14%, transparent), transparent 70%),
      linear-gradient(180deg, rgba(var(--teal-rgb),.05), transparent 65%),
      var(--surface-2);
    border:1px solid var(--nlh-rim);border-radius:12px;
    padding:10px 14px 8px;margin:0 0 14px;
    box-shadow:
      0 0 0 1px var(--nlh-rim),
      inset 1px 1px 0 rgba(255,255,255,.06),
      inset -1px -1px 0 rgba(0,0,0,.06),
      0 3px 10px -6px rgba(0,0,0,.16);
  }
  .nl-header-plaque::before{
    content:"";position:absolute;inset:4px;border-radius:8px;
    border-style:solid;border-width:1px;
    border-top-color:rgba(0,0,0,.16);
    border-left-color:rgba(0,0,0,.12);
    border-bottom-color:rgba(255,255,255,.20);
    border-right-color:rgba(255,255,255,.12);
    pointer-events:none;
  }
  /* 2026-08-28: row -> column, direct owner request to center this
     heading (skipped from the app-wide heading-centering pass since this
     header uses its own markup, not the generic .sheet h2 / .sheet-hero
     patterns those got applied to). align-items:center now centers
     horizontally along the column's cross-axis, so the icon and the
     title/flourish group beneath it land on the same vertical axis. */
  .nl-header-title-row{display:flex;flex-direction:column;align-items:center;gap:8px;position:relative;z-index:1;}
  .nl-header-title-row h2{margin:0;font-weight:800;}
  /* Flourish lines flanking the title text, added 2026-08-28 alongside
     the centering above -- direct owner request, picking the "Stacked
     Medallion" header layout but with the flanking lines from the
     "Banner Plaque" concept wrapped around the words "New loan" instead
     of around the icon. Reuses --nlh-accent (the same variable
     #nl-the-money-label's own accent bar already reads from below), so
     it's automatically this theme's gold on Dark/Glass/Default/Prism
     Light and this theme's own native accent everywhere else -- no new
     color introduced. */
  .nl-title-flourish-row{display:flex;align-items:center;justify-content:center;gap:10px;}
  .nl-flourish{height:1px;width:36px;flex:none;background:linear-gradient(90deg, var(--nlh-accent), transparent);}
  .nl-flourish.right{background:linear-gradient(270deg, var(--nlh-accent), transparent);}
  /* Icon: flat inline emoji -> dimensional circular medallion (owner:
     "dimensional circular icon treatment... polished/brushed luxury
     metal"). Mirrors the .loan-hero-badge circular-badge technique
     already established elsewhere in this app (round, gradient fill,
     drop shadow) instead of inventing a new circular-icon system --
     just themed with this header's own --nlh-accent variables so it
     reads as gold specifically on Dark/Glass, and as this theme's own
     accent everywhere else. */
  /* Icon corrected 2026-08-21 (owner sent the actual reference image
     after the first "dimensional circular medallion" pass and pointed
     out it should be a THIN RING with a dark/transparent interior, not a
     solid filled gold disc). Interior swapped from the gold gradient
     fill to var(--surface-2) (a soft radial tint kept for a touch of
     depth, same as before). Still the exact same 38px circular medallion
     footprint, same position in the header -- purely a fill/ring swap. */
  /* 2026-08-28: grown from 38px to 52px and the embossed ring (previously
     Dark/Glass/Default/Prism Light ONLY, see the now-removed override
     that used to sit here) folded into this base rule for every theme --
     same "not even" fix as the title text above. The directional
     highlight/shadow border IS the ring now, at a lighter alpha than the
     old 4-theme-only version (that one was tuned for a hardcoded-gold
     ring specifically; this one has to read against each theme's own
     native accent too), so the gold box-shadow ring layer is dropped in
     favor of the border doing that job everywhere. */
  .nl-header-icon{
    width:52px;height:52px;border-radius:50%;flex:none;
    display:flex;align-items:center;justify-content:center;
    font-size:1.4rem;line-height:1;position:relative;z-index:1;
    background:
      radial-gradient(circle at 32% 26%, color-mix(in srgb, var(--nlh-accent-bright) 18%, transparent), transparent 62%),
      var(--surface-2);
    border-style:solid;border-width:1.5px;
    border-top-color:rgba(255,255,255,.22);
    border-left-color:rgba(255,255,255,.14);
    border-bottom-color:rgba(0,0,0,.55);
    border-right-color:rgba(0,0,0,.45);
    box-shadow:
      inset 0 0 0 1px color-mix(in srgb, var(--nlh-accent-bright) 35%, transparent),
      inset 0 1px 2px rgba(0,0,0,.28),
      0 2px 6px -2px rgba(0,0,0,.35);
  }
  /* Twinkling sparkle accents beside the icon (2026-08-21, direct owner
     request on seeing the reference image: "with the golden stars and
     everything and the stars move... you already do something similar
     with the stars movement elsewhere... but this is more liked" --
     referring to the app's existing .login-star-particle /
     .glass-ambient-particle twinkle, both driven by the shared
     @keyframes glass-float). This intentionally mirrors that SAME
     twinkle *shape* (an opacity dip paired with a scale pulse) in a new
     @keyframes nlh-star-twinkle immediately below, rather than literally
     reusing glass-float by name: glass-float's translateY(-16px) drift
     was tuned for particles floating in open background space, and at
     this element's tiny size, glued to the edge of a 38px icon, that
     same 16px vertical drift would carry the sparkle off the header
     plaque every cycle instead of reading as a twinkle in place. Purely
     decorative (aria-hidden, "&#10022;"/"✦" glyphs, not part of any id
     applyTranslations() touches) and gated to display:none by default so
     only the 4 themes carrying this header treatment show them; the 5
     untested themes (Gold, Neon, Prism, Eclipse, Aurora) render nothing
     extra here, unchanged. 2026-08-21 THIRD follow-up: Prism Light/
     Default added to the display:block list below, alongside Dark/Glass
     -- direct owner request ("did you make a version for the light prism
     as well? we need both" -> "Same full treatment, with gold stars
     too"). --nlh-accent-bright is gold on all 4 of these themes now (see
     the #loan-sheet variable block above), so the stars render gold on
     Prism Light/Default too, not just Dark/Glass.
     2026-08-28: display:none default flipped to display:block and the
     4-theme-only override removed -- same "not even" fix as the ring and
     title text, all 9 themes now show the twinkle, tinted by that
     theme's own --nlh-accent-bright. Positions also moved from diagonal
     corners to mirrored top-left/top-right of the now-bigger, centered
     icon, to match the icon's new bilateral symmetry (owner's "Stacked
     Medallion" pick). */
  @keyframes nlh-star-twinkle{
    0%,100%{transform:scale(.7);opacity:.35;}
    50%{transform:scale(1.15);opacity:1;}
  }
  .nl-header-star{
    display:block;position:absolute;pointer-events:none;
    color:var(--nlh-accent-bright);line-height:1;
    animation:nlh-star-twinkle 2.6s ease-in-out infinite;
  }
  .nl-header-star--1{top:16px;right:-16px;font-size:.6rem;animation-delay:0s;}
  .nl-header-star--2{top:16px;left:-16px;font-size:.6rem;opacity:.85;animation-delay:1.3s;}
  /* Had no reduce-motion/Plain-Mode override at all before 2026-08-27 --
     same infinite-loop-plus-.01ms-duration flicker bug documented at
     [data-motion="plain"] * (see the "REAL BUG" note there), just never
     caught here since these two stars are easy to miss on the New Loan
     header. */
  @media (prefers-reduced-motion: reduce){ .nl-header-star{animation:none;} }
  [data-motion="plain"] .nl-header-star{animation:none;}
  .nl-header-plaque .sheet-sub{position:relative;z-index:1;margin-bottom:0;}
  /* Close button: restyled to match this header specifically (round 2)
     -- scoped with #loan-sheet since .sheet-close-btn is the SAME class
     every other sheet's close button uses (18+ occurrences app-wide, per
     applyTranslations()'s own comment on that selector); only the New
     Loan sheet's button changes. Purely visual -- keeps its shared
     class, its onclick="closeTopSheet()" attribute, and its aria-label
     (still set by applyTranslations()'s existing
     querySelectorAll(".sheet-close-btn") loop, completely untouched). */
  #loan-sheet .sheet-close-btn{
    background:var(--surface-2);
    border-color:var(--nlh-rim);
    color:var(--nlh-accent-bright);
    box-shadow:0 0 0 1px var(--nlh-rim), 0 2px 8px rgba(0,0,0,.16);
  }
  /* X glyph corrected 2026-08-21, third follow-up, direct owner request:
     "changed my mind.... make the X black as well not yellow". The base
     rule above (color:var(--nlh-accent-bright)) is left untouched, so any
     theme NOT in this list keeps its own existing accent color exactly as
     before. Only the X glyph's color changes here -- background, border,
     and box-shadow ring already come out black automatically for these
     same 4 themes via the --nlh-rim variable override above, no separate
     rule needed for those.
     2026-08-21 FOURTH follow-up: Prism Light/Default added below,
     alongside Dark/Glass, same 4-theme scope as the rest of this round
     ("did you make a version for the light prism as well? we need
     both" -> "Same full treatment"). */
  :root:not([data-theme]) #loan-sheet .sheet-close-btn,
  [data-theme="prism-light"] #loan-sheet .sheet-close-btn,
  [data-theme="dark"] #loan-sheet .sheet-close-btn,
  [data-theme="glass"] #loan-sheet .sheet-close-btn{
    color:#0A0B0D;
  }
  .receipt-block{
    background:linear-gradient(155deg, var(--surface) 0%, var(--surface-2) 160%);
    border:1px solid var(--border);border-radius:11px;padding:14px;margin-bottom:18px;
    box-shadow:0 1px 3px rgba(0,0,0,.05);
  }
  /* Record Payment logo watermark (2026-08-29, direct owner request:
     "the Record a Payment screen feels too plain... add the company
     logo as a subtle, tasteful watermark"). Reuses the exact same
     assets + blue/gold swap mechanism (and fixed-width/auto-height
     sizing, so logo-gold-full-bg.png's true 2:3 portrait ratio isn't
     squashed -- see body::before's own comment on this) as the app's
     existing login/main-screen watermark, just scoped to this one
     sheet and at a much fainter opacity (.05 vs the main screen's
     .30/.40) -- this sits directly behind form fields and their labels,
     not behind a loosely-packed list, so it has to stay closer to
     invisible to never compete with legibility. pointer-events:none so
     it can never intercept a tap meant for a field behind/around it.

     BUG FIX #1 (2026-08-29, owner report -- "I click Record a Payment,
     choose someone, the screen just gets hazed, nothing pops up"): an
     earlier version of this rule also declared `position:relative;
     overflow:hidden;` on #payment-sheet itself, which (being an ID
     selector) silently overrode .sheet's own `position:fixed`, breaking
     the sheet entirely. Fixed by not re-declaring position/overflow on
     #payment-sheet for layout purposes.

     BUG FIX #2 (2026-08-29, same day, owner report -- "the logo still
     scroll with the page", after an intermediate attempt at fixing this
     with `position:fixed` relying on .sheet's own `transform` to create
     a containing block -- correct per spec, but apparently not reliable
     enough in practice on the owner's actual device/browser to trust
     going forward): the real, robust fix restructures the DOM instead
     of fighting positioning edge cases. #payment-sheet used to BE the
     scrolling element (.sheet's shared `overflow-y:auto`), so any
     positioned descendant of it -- absolute OR (per the previous,
     apparently-unreliable attempt) fixed-inside-a-transformed-ancestor
     -- was still a descendant of the thing that scrolls, and remained
     at the mercy of how faithfully a given browser implements that
     specific spec corner case. Instead: #payment-sheet's markup now
     wraps everything (handle, close button, title, fields, buttons --
     unchanged order/behavior) in a new inner `.payment-sheet-scroll`
     div, and SCROLLING MOVED TO THAT INNER DIV (see its own CSS rule
     below) instead of #payment-sheet itself. #payment-sheet is now a
     plain, non-scrolling box (`overflow:hidden`, ID-scoped -- fine here
     since this is intentional and #payment-sheet is not sharing this
     override with any other sheet), so a plain `position:absolute`
     watermark on it (back to the ORIGINAL, simplest technique -- no
     position:fixed, no transform-containing-block reasoning needed at
     all) can never move on scroll, full stop, in any browser: it is
     simply not inside the element that scrolls anymore. z-index:-1
     keeps it behind #payment-sheet's own children (now inside the
     scroll wrapper) instead of painting over them. */
  /* (2026-08-29, direct owner report: "when I click on a customer
     profile, then go to a loan, and click record a payment the window
     ... is trimmed at the bottom and I cannot see the 2 buttons ...
     thats not the case when I go directly to start menu ... because
     its not window on top of window on top of window.") Root cause:
     .payment-sheet-scroll (below) used to hardcode its own height as
     `calc(88vh - 44px - safe-area)` -- a duplicate of #payment-sheet's
     OWN sizing (.sheet's `max-height:88vh` + its 20px/24px top/bottom
     padding = "44px"). That hardcoded 88vh is only correct when
     #payment-sheet is opened standalone (Start Menu -> Record a
     Payment, sheetStack depth 0). Opened from inside another sheet
     (Customer -> Loan -> Record a Payment, depth > 0), openSheet()
     adds `.sheet-layered` to it -- a smaller floating-card treatment,
     `max-height:82vh` instead of 88vh (see .sheet.sheet-layered
     above) -- and the inner scroll div, still assuming 88vh, ends up
     taller than the outer box's real (now 82vh) height. #payment-sheet
     is `overflow:hidden` (needed so the watermark can never scroll,
     see above), so that ~6vh excess -- exactly enough to include both
     buttons at the very bottom -- is silently clipped off outside the
     visible box, with no way to scroll to it: the content isn't
     off-screen inside a scrollable area, it's cut off outside the
     outer box entirely.
     Fix: stop duplicating the outer box's height as a magic number.
     #payment-sheet becomes a flex column with a single child
     (.payment-sheet-scroll, its only child in the DOM -- see the HTML
     comment above it); the scroll div uses `flex:1;min-height:0`
     instead of a hardcoded max-height, so it always fills exactly
     whatever height #payment-sheet actually ends up being --88vh
     standalone, 82vh layered, or anything else in the future -- with
     nothing to keep in sync by hand. This is the same flex-fill-
     available-space pattern already used for #mm-wizard-sheet's
     .mmw-body elsewhere in this file.
     CORRECTION, same day (owner: "the record a payment is stiff and
     dont scroll"): the first version of this fix put `display:flex`
     directly on the bare `#payment-sheet{...}` rule, with no
     `!important`. That silently never took effect: openSheet() sets
     this element's INLINE `style.display = "block"` every time it
     opens (confirmed in openSheet() -- same mechanism the
     #search-sheet.sheet-open comment above already documents), and an
     inline style always wins over an external stylesheet rule
     regardless of selector specificity, `!important` aside. So
     #payment-sheet was actually rendering as plain `display:block`
     the whole time, never `flex` -- meaning .payment-sheet-scroll's
     `flex:1` had no flex container to size against, fell back to its
     default auto/content-based height, and with no max-height of its
     own left to cap it (removed in this same fix, in favor of
     flex:1), `overflow-y:auto` had nothing to actually scroll: the
     div was always exactly as tall as its content, never shorter.
     That's the reported "stiff and doesn't scroll." Fixed the same
     way #search-sheet.sheet-open already does it just above: move the
     flex declaration onto the `.sheet-open` class combination with
     `!important`, which -- unlike a plain inline style -- DOES beat
     the inline `style="display:block"` openSheet() sets. */
  #payment-sheet{ overflow:hidden; }
  #payment-sheet.sheet-open{ display:flex !important;flex-direction:column; }
  /* (2026-08-29, direct owner correction: "why is it clipped when
     accessed through the customer profile... make it at least same
     size or very close to the other windows. Just a tiny sorter so
     you can see depth ... not so short like it is now ... the more
     windows the smaller they get.") The nested-window shrink itself
     (.sheet-layered, max-height:82vh, see above) was never actually
     the bug -- it's by design, the "floating card" depth cue. The real
     issue: EVERY other sheet in the app (#customer-sheet,
     #loan-detail-sheet, #analytics-sheet, #document-center-sheet, and
     so on) has its own inline `style="...max-height:92vh"` (or 90/94/
     96vh -- each sheet's own opening <div> tag sets its own value)
     written directly in the HTML. An inline style always wins over a
     class-selector CSS rule -- the exact same mechanism already
     documented above for openSheet()'s inline display:block -- so
     .sheet.sheet-layered's `max-height:82vh` has never actually
     applied to any of those sheets, nested or not: they always render
     at their own pinned inline height. Verified directly with
     getComputedStyle(): #customer-sheet and #loan-detail-sheet both
     computed to 92vh even with .sheet-layered applied; #payment-sheet,
     the one sheet with no inline max-height override of its own (it
     just inherits the base .sheet's 88vh), was the ONLY one actually
     honoring the 82vh shrink -- an 80px gap on a typical 800px-tall
     screen next to the very sheets (#customer-sheet, #loan-detail-
     sheet) it's nested inside in this exact flow. Not "the more
     windows the smaller they get" -- one sheet was shrinking as
     designed while its siblings were silently exempt the whole time.
     Fix: give #payment-sheet its own override for the layered case,
     matching its actual siblings' convention instead of falling
     through to the generic 82vh -- 90vh, close to their 92vh (a "very
     close... tiny" difference, per the request) while still reading
     as one step back. ID+class beats the two-class `.sheet.sheet-
     layered` selector, so only max-height changes here; the rest of
     the floating-card treatment (insets, rounded corners, shadow) is
     untouched -- that part IS the intended depth cue and was never
     what the owner objected to. */
  #payment-sheet.sheet-layered{ max-height:90vh; }
  .payment-sheet-scroll{
    flex:1;min-height:0;
    overflow-y:auto;overflow-x:hidden;overscroll-behavior:contain;
  }
  #payment-sheet::before{
    content:"";position:absolute;
    top:50%;left:50%;transform:translate(-50%,-50%);
    width:min(60vw,260px);height:80%;
    background-image:url('/logo-blue.png');
    background-repeat:no-repeat;background-position:center;background-size:min(60vw,260px) auto;
    opacity:.05;pointer-events:none;z-index:-1;
  }
  html[data-logo-style="gold"] #payment-sheet::before{
    background-image:url('/logo-gold-full-bg.png');
  }
  /* LOANS section visual importance pass (2026-08-21, direct owner
     request: "this particular LOANS area [should have] more visual
     importance... premium financial UI + subtle physical depth +
     precision-finished panel... NOT neon/glowing/giant colored box/huge
     shadows/redesign"). Reuses the exact rim+highlight+shade+two-layer-
     shadow technique already established (and owner-approved) for
     #logo-menu-dropdown ("Thin double-line rim... faint upper-left
     highlight / lower-right shade... soft two-layer shadow") instead of
     inventing a new visual language, per "consistent with the existing
     MoneyFlow visual language":
       - border swapped for a `0 0 0 1px var(--border-strong)` box-shadow
         ring -- crisper corners at this radius than a real border, same
         "precision rim" technique as the Start Menu, and var(--border-
         strong) is already defined per-theme (8 themes, incl. Glass's
         translucent rgba and Prism/Gold's translucent gold) so this is
         automatically theme-correct with zero new tokens.
       - inset 1px/-1px highlight+shade pair = the "subtle inner
         highlight" + "subtle inner shadow/groove/deboss" the owner asked
         for, at very low alpha (.06/.07) so it reads as depth, not a
         visible line.
       - two shadow layers (tight near + soft far) = "restrained
         elevation" + "slightly stronger separation from surrounding
         Customer 360 cards" -- deliberately closer to .loan-hero-card's
         own 0 4px 14px shadow in the near layer than to anything
         dramatic; the far layer is what actually lifts it off the page.
     The second, more-inset "precision-machined recess" line the owner
     specifically asked about ("If appropriate... a VERY subtle debossed/
     inset groove just inside the perimeter... someone should notice...
     without immediately thinking 'there's a border around this'") is the
     new ::before pseudo-element directly below this rule -- see its own
     comment for why a pseudo-element rather than another box-shadow
     layer was needed for a true offset-inward line. Background/padding/
     margin/border-radius all unchanged. */
  .loans-panel{
    position:relative;
    background:
      repeating-linear-gradient(45deg, rgba(var(--teal-rgb),.028) 0 8px, transparent 8px 16px),
      linear-gradient(180deg, rgba(var(--teal-rgb),.05), transparent 60%),
      var(--surface-2);
    border-radius:14px;
    padding:14px;margin:4px 0 18px;
    box-shadow:
      0 0 0 1px var(--border-strong),
      inset 1px 1px 0 rgba(255,255,255,.06),
      inset -1px -1px 0 rgba(0,0,0,.07),
      0 4px 14px -8px rgba(0,0,0,.16),
      0 14px 32px -16px rgba(0,0,0,.22);
  }
  /* The "precision-machined recess" groove, offset a few px IN from the
     panel's own edge rather than flush with it -- a plain box-shadow can
     only draw at the box's own edge (or a uniformly-thick inset band via
     spread, which reads as a fill, not a crisp offset line), so getting
     an actual second line "slightly inside the perimeter" needs a
     separate box. Sits entirely inside the panel's 14px padding gutter
     (inset:6px, i.e. 6-7px in from the edge -- real content never starts
     before the 14px padding line), so it can never visually cross over
     the header, WhatsApp/Call buttons, or the loan card, and being
     `pointer-events:none` it never intercepts a tap either.
     2026-08-21 follow-up (direct owner report: "the current inner deboss
     is technically there, but MUCH too subtle... I can barely see it...
     this is a DEBOSS, not an emboss, I do NOT want it to look raised, I
     want it to look pressed/carved inward"). Strengthened from a single
     flat-colored 1px line + a near-invisible inset shadow to an actual
     directionally-lit carved channel, using the same per-side-border-
     color technique a physical recessed groove needs: darker on the
     top/left (the near wall of the pit blocking light from the
     established top-left light source used everywhere else in this app,
     e.g. the profile-menu-item emboss) and a faint highlight on the
     bottom/right (the far wall catching a little light) -- the exact
     inverse of an embossed/raised edge, which is what makes this read as
     pressed-in rather than raised. The inset box-shadow pair adds the
     actual carved depth (a real shadow gradient inside the channel, not
     just a colored line), concentrated at the top like light falling
     into a real recess, with a soft answering highlight at the bottom.
     Kept to raw low-alpha black/white (not a theme token) for the
     directional shading -- same technique already used and approved for
     #logo-menu-dropdown's own rim/highlight/shade -- so it reads as
     "material catching light" rather than a flat theme-colored line, and
     still stays restrained (thin 1px lines, no fill, no glow) per "thin
     precision trim... not a glowing neon line / bright rectangle". */
  .loans-panel::before{
    content:"";position:absolute;inset:6px;border-radius:9px;
    border-style:solid;border-width:1.5px;
    border-top-color:rgba(0,0,0,.42);
    border-left-color:rgba(0,0,0,.34);
    border-bottom-color:rgba(255,255,255,.46);
    border-right-color:rgba(255,255,255,.30);
    box-shadow:
      inset 0 3px 6px -1px rgba(0,0,0,.30),
      inset 0 -1px 0 rgba(255,255,255,.18);
    pointer-events:none;
  }
  .loan-hero-card{
    background:
      radial-gradient(ellipse 260px 120px at 12% 0%, rgba(var(--teal-rgb),.08), transparent 70%),
      var(--surface);
    border:1px solid var(--border);border-left:4px solid var(--teal);
    border-radius:13px;padding:14px 16px;margin-bottom:10px;cursor:pointer;
    box-shadow:0 4px 14px -4px rgba(0,0,0,.12);
  }
  .loan-hero-card.closed{border-left-color:var(--border);opacity:.72;}
  .loan-hero-card:active{transform:scale(.99);}
  .loan-hero-badge{
    width:34px;height:34px;border-radius:50%;flex:none;
    background:var(--teal);color:#fff;font-size:1.05rem;font-weight:700;
    display:flex;align-items:center;justify-content:center;
    box-shadow:0 2px 8px -2px rgba(var(--teal-rgb),.55);
  }
  .loan-hero-card.closed .loan-hero-badge{background:var(--text-muted);box-shadow:none;}
  .floating-card{
    background:
      radial-gradient(ellipse 300px 150px at 15% 0%, rgba(var(--teal-rgb),.05), transparent 70%),
      var(--surface);
    border:1px solid var(--border);border-radius:14px;padding:16px;
    margin-bottom:22px;box-shadow:0 4px 16px -4px rgba(0,0,0,.1), 0 1px 3px rgba(0,0,0,.04);
  }
  #perf-dashboard-card.perf-fullscreen-active{
    position:fixed;inset:0;z-index:70;margin:0;border-radius:0;
    overflow-y:auto;padding:calc(16px + env(safe-area-inset-top)) 16px calc(16px + env(safe-area-inset-bottom));
  }
  #perf-dashboard-card.perf-fullscreen-active .perf-chart-wrap{
    height:calc(100vh - 340px) !important;
  }
  .receipt-row{display:flex;justify-content:space-between;font-size:.86rem;padding:4px 0;color:var(--text-secondary);}
  .btn-row{display:flex;gap:10px;margin-top:8px;}
  .btn-secondary{
    flex:1;padding:12px;border:1px solid var(--border);background:var(--surface);
    border-radius:9px;font-weight:500;color:var(--text-secondary);
    transition:transform .16s cubic-bezier(.22,.61,.36,1), background .16s ease, border-color .16s ease;
  }
  .btn-secondary:active{transform:scale(.98);background:var(--surface-2);}
  @media (hover:hover){
    .btn-secondary:hover:not(:disabled){border-color:var(--border-strong);background:var(--surface-2);}
  }
  .btn-secondary:disabled{opacity:.55;}
  /* Accent variant for secondary actions that should visibly read as
     clickable buttons (Add note / Add reference) -- thin colored rim +
     tinted fill using the app's existing teal accent tokens, so it
     stays correct across every theme automatically. (2026-08-20) */
  .btn-secondary.btn-accent{
    border:1.5px solid var(--teal-deep);
    background:var(--teal-bg);
    color:var(--teal-text);
    font-weight:600;
  }
  @media (hover:hover){
    .btn-secondary.btn-accent:hover:not(:disabled){border-color:var(--teal);box-shadow:0 0 0 3px var(--teal-bg);}
  }
  .btn-row .btn-primary{flex:1.4;margin-top:0;}
  .due-banner{
    background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);
    margin-bottom:18px;overflow:hidden;box-shadow:0 1px 3px rgba(0,0,0,.06), 0 1px 2px rgba(0,0,0,.04);
  }
  /* Container-level accent (2026-08-21, direct owner report: "the colored
     accent only remains beside the header/top portion" when the overdue
     / past-due-in-grace sections expand -- wants ONE continuous strip
     down the whole expanded panel, not per-row). Root cause: the accent
     lived on .due-banner-header (the clickable header row) only, via
     border-left -- #overdue-rows / #overdue-soon-rows is a separate
     sibling <div> below it with no accent of its own, so the strip
     visually stopped exactly where the header ended.
     Fix: move the accent to this OUTER .due-banner container instead --
     it already wraps the header AND the rows div, and already owns the
     rounded corners + overflow:hidden clip, so a border-left here
     automatically runs the full height of the box in both the collapsed
     and expanded states with zero JS and zero markup changes, and stays
     perfectly aligned with the existing rounded corners. :has() picks
     the color from whichever header variant is inside, same red/yellow
     tokens as before (var(--danger-text) / var(--status-pending)), and
     its specificity (0,3,0) safely outranks every existing per-theme
     `.due-banner{border:...}` override (Prism, Neon/Aurora -- both
     (0,2,0)) without needing to touch those theme blocks. */
  .due-banner:has(.due-banner-header.overdue){border-left:4px solid var(--danger-text);}
  .due-banner:has(.due-banner-header.today){border-left:3px solid var(--status-pending);}
  .due-banner-header{
    display:flex;align-items:center;gap:8px;padding:12px 16px;cursor:pointer;
    font-size:.85rem;font-weight:500;background:var(--surface);border-left:3px solid transparent;
    transition:background-color .14s ease;
  }
  /* border-left removed from the header itself (2026-08-21, see the
     .due-banner:has(...) comment above) -- the accent now lives on the
     outer container so it doesn't stop at the header; keeping a second
     colored border here would just double it up. Header falls back to
     the base rule's transparent 3px, so its own box/padding is otherwise
     unchanged. */
  .due-banner-header.overdue{
    color:var(--danger-text);background:var(--surface);
    padding:18px;
  }
  .due-banner-header.overdue .due-count{font-size:1.7rem;font-weight:700;line-height:1;margin-right:2px;}
  .due-banner-header.overdue .due-label{font-size:.85rem;font-weight:600;opacity:.85;}
  .due-banner-header.today{color:var(--status-pending);}
  .due-banner-header:active{background-color:var(--surface-2);}
  .due-banner-header.overdue:active{background-color:var(--surface-2);filter:brightness(.96);}
  .due-row{
    display:flex;justify-content:space-between;align-items:center;padding:10px 16px;
    border-top:1px solid var(--border);font-size:.85rem;cursor:pointer;
  }
  .due-row:active{background:var(--surface-2);}
  .status-dot{width:8px;height:8px;border-radius:50%;display:inline-block;flex-shrink:0;}
  .status-active{background:var(--status-active);}
  .status-no_active_loan{background:var(--status-no-loan);}
  .status-pending_review{background:var(--status-pending);}
  .status-suspended{background:var(--status-suspended);}
  .status-current{background:var(--status-active);}
  .status-grace{background:var(--status-pending);}
  .status-overdue{background:var(--status-suspended);}
  .status-blacklisted{background:var(--status-blacklisted);}
  .status-label{font-size:.72rem;font-weight:500;color:var(--text-secondary);text-transform:capitalize;}
  .tag-chip{
    display:inline-flex;align-items:center;gap:5px;font-size:.72rem;font-weight:500;padding:4px 6px 4px 10px;border-radius:20px;
    background:var(--surface-2);color:var(--text-secondary);margin:2px 4px 2px 0;border:1px solid var(--border);
  }
  .tag-chip.negative{background:var(--danger-bg);color:var(--danger-text);}
  /* System (automatic) tags -- Global Search + Tags v2 (2026-08-20):
     dashed border is the "subtle" distinction the owner asked for
     between a manually-assigned tag and one the system computed and
     will remove on its own once the condition it reflects stops being
     true. No remove control -- see renderTagChipDisplay(). */
  .tag-chip.auto{border-style:dashed;opacity:.88;}
  .tag-chip .tag-remove{
    cursor:pointer;width:15px;height:15px;border-radius:50%;display:flex;align-items:center;justify-content:center;
    background:rgba(0,0,0,.08);font-size:.65rem;line-height:1;
  }
  .search-icon-btn{background:none;border:none;color:var(--text-secondary);padding:6px;display:flex;border-radius:8px;}
  .search-icon-btn svg{width:20px;height:20px;flex-shrink:0;}
  .search-icon-btn:active{background:var(--surface-2);}
  .search-icon-btn:disabled{opacity:.55;}
  /* Refresh button spin feedback (2026-08-27, direct owner report: "I
     have to hit it two or three times... sometimes it just never
     works"). The button used to give zero visible response to a click,
     so a slow or silently-failed refresh looked identical to a click
     that did nothing -- the real fix (see the click handler) is a more
     reliable refresh path, but a spinning icon while that's in flight is
     what actually answers "did my click register" in the moment, which
     is most of what made this feel broken. Infinite loop -- same
     Plain-Mode/reduced-motion treatment as every other one in this file
     (see the [data-motion="plain"] * "REAL BUG" note). */
  .search-icon-btn.refreshing svg{animation:refresh-spin .8s linear infinite;}
  @keyframes refresh-spin{ to{ transform:rotate(360deg); } }
  @media (prefers-reduced-motion: reduce){ .search-icon-btn.refreshing svg{animation:none;} }
  [data-motion="plain"] .search-icon-btn.refreshing svg{animation:none;}
  .search-result-row{
    display:flex;align-items:center;justify-content:space-between;padding:12px 4px;
    border-bottom:1px solid var(--border);cursor:pointer;
  }
  .search-result-row:last-child{border-bottom:none;}
  .timeline-item{display:flex;gap:10px;padding:8px 0;border-bottom:1px solid var(--border);}
  .timeline-item:last-child{border-bottom:none;}
  .timeline-dot{width:6px;height:6px;border-radius:50%;background:var(--teal);margin-top:6px;flex-shrink:0;}
  .timeline-text{font-size:.85rem;display:flex;align-items:center;gap:6px;flex-wrap:wrap;}
  .timeline-loan-badge{font-size:.68rem;font-weight:500;color:var(--teal-text);background:var(--teal-bg);padding:2px 7px;border-radius:10px;}
  .timeline-date{font-size:.72rem;color:var(--text-muted);margin-top:2px;}
  .milestone-track{display:flex;overflow-x:auto;gap:0;padding:8px 0 16px;}
  .milestone{display:flex;flex-direction:column;align-items:center;min-width:56px;flex-shrink:0;position:relative;}
  .milestone::before{
    content:"";position:absolute;top:11px;left:-50%;width:100%;height:2px;background:var(--border);z-index:0;
  }
  .milestone:first-child::before{display:none;}
  .milestone-marker{
    width:24px;height:24px;border-radius:50%;background:var(--surface-2);border:2px solid var(--border);
    display:flex;align-items:center;justify-content:center;z-index:1;font-size:.68rem;font-weight:500;color:var(--text-secondary);
  }
  .milestone.paid .milestone-marker{background:var(--teal);border-color:var(--teal);color:#fff;cursor:pointer;box-shadow:0 2px 6px -1px rgba(var(--teal-rgb),.5);}
  .milestone.paid .milestone-marker:active{background:var(--teal-deep);}
  .milestone-end-marker{
    box-shadow:0 0 0 2px var(--surface), 0 0 0 4px var(--border);
  }
  .milestone-end.paid .milestone-end-marker{
    background:var(--status-pending);border-color:var(--status-pending);color:#fff;cursor:default;
    box-shadow:0 0 0 2px var(--surface), 0 0 0 4px var(--status-pending);
  }
  .milestone-amt{font-size:.68rem;color:var(--text-secondary);margin-top:4px;text-align:center;}
  .section-label{font-size:1rem;font-weight:800;color:var(--text-primary);text-transform:uppercase;letter-spacing:.02em;margin:20px 0 10px;}
  .section-label:first-child{margin-top:0;}
  /* Partner Workflow sheet: each of the 3 numbered sections gets its own
     card panel instead of running together as one flat list (2026-08-28,
     direct owner report + annotated screenshot -- "see how section 1 and
     the heading is all together? dont like this... every section need to
     be either bigger... or on a stripe or embossed... make it stand out
     by section"). Scoped to .pw-section only -- doesn't touch the shared
     .section-label definition or any other screen. The left-accent color
     mirrors the Command Center teaser card's existing border-left idiom
     for these same three sections (danger for the two that need action,
     muted for history, which has none) -- extending an established
     pattern rather than inventing a new one. */
  .pw-section{
    background:var(--surface-2);border-radius:var(--radius);
    border-left:4px solid var(--pw-accent, var(--border));
    padding:16px;margin:16px 0;
    box-shadow:0 3px 10px -4px rgba(0,0,0,.28), inset 0 1px 0 rgba(255,255,255,.03);
  }
  .pw-section:first-child{margin-top:0;}
  .pw-section .section-label{
    margin:0 0 14px;font-size:1.08rem;padding-bottom:8px;
    border-bottom:1px solid var(--border);position:relative;
  }
  .pw-section .section-label::after{
    content:"";position:absolute;left:0;bottom:-1px;width:34px;height:2px;
    background:linear-gradient(90deg, var(--pw-accent, var(--teal-deep)), transparent);
    border-radius:2px;
  }
  /* Heading-to-first-section breathing room (same report): the shared
     ".sheet h2{margin-bottom:4px}" rule left almost no gap before the
     first section, so the sheet title and "Close Requests..." visually
     ran together. Scoped to this one heading only. */
  #pw-sheet-title{margin-bottom:18px;text-align:center;}
  /* Centered sheet titles, app-wide rollout (2026-08-28, direct owner
     request + approved mockup): every plain sheet heading that matches
     Partner Workflow's pattern gets the same treatment. NOT done via the
     shared ".sheet h2" rule -- #loan-detail-customer-name (embedded
     inline content, not a standalone top heading) also matches that
     selector and shouldn't move, so each heading that SHOULD center is
     listed by id instead.

     #pay-sheet-title was ORIGINALLY excluded here too (small-caps label,
     not a real heading) -- reversed 2026-08-29, direct owner report: "I
     chose [a customer], the name... it's off to the left, and it's to
     be on the center... the customer name and recorded payment, all
     that stuff should be centered." Both #pay-sheet-title and its
     sibling #pay-sheet-sub (the customer's name underneath) are centered
     via inline style="text-align:center" right on the two elements in
     the Payment Sheet markup instead of being added to this id group --
     they already carry several other one-off inline styles (small-caps
     uppercase label styling; large bold clickable name with an
     underline), so keeping the centering inline alongside those matches
     the existing pattern for these two elements specifically, rather
     than splitting their styling across an inline block AND this
     shared rule. */
  #search-sheet-title, #list-sheet-title, #ac-title, #cc-sheet-title,
  #nc-sheet-title, #pv-sheet-title, #user-form-title{
    text-align:center;
  }
  /* Search sheet title clearance (2026-08-29, direct owner report: "the
     menu that comes up... it's the letters that says who you are
     recording a payment for... it's way too high because it's behind
     the X... too close... should be more down"). #search-sheet is the
     one sheet whose close button doesn't naturally clear the title:
     .sheet-close-wrap is `position:sticky; height:0`, so it never
     pushes later content down in normal flow -- on every OTHER sheet
     the title is one short line and happens to clear the button anyway,
     but this title got centered (this file, run before this one) and is
     long enough to wrap to two lines, so the second line ("...for?")
     was rendering right under/behind the close button. Fix is scoped to
     this one title only. First pass shipped at 36px (a render check had
     tried 28/36/44 and 36 looked cleanest on-screen); owner's follow-up
     preferred 44 in practice ("should of gone for 44 instead of 36"), so
     bumped to that. Deliberately NOT touched: the space below the
     search input (owner explicitly asked to preserve room for results
     between the search bar and the on-screen keyboard) -- this only
     pushes the title/subtitle down, nothing else moves. */
  #search-sheet-title{
    margin-top:44px;
  }
  /* Same rollout, glass "hero" headings (Settings/Analytics/Finance
     Center) -- centers the icon+text as a unit and the accent line
     beneath it. The line's gradient also flips from "solid at the left
     edge, fading right" to "brightest in the middle, fading out to both
     tips" (direct owner request), which only reads correctly once the
     line itself is centered under the now-centered title. */
  .sheet-hero-title{ text-align:center; }
  .sheet-hero-line{
    margin-left:auto;margin-right:auto;
    background:linear-gradient(90deg, transparent, var(--teal) 50%, transparent);
  }
  /* A same-day "double-lined accent" (a second, fainter line echoing
     this one) was added here and then removed -- direct owner
     correction: the emboss request was about the .sheet-hero BOX, not
     the title text or the line under it. See .sheet-hero's own comment
     above for the box treatment that replaced it. */
  /* "THE MONEY" heading, New Loan sheet (2026-08-21, direct owner
     request: "give that section heading slightly better visual
     hierarchy... improve spacing... use a restrained accent... a subtle
     divider/rim"). Scoped to this one id only -- explicitly NOT touching
     "the customer" section label right below it, or any other
     .section-label in the app, since the owner scoped this to "THE
     MONEY section" specifically. The label's own text (translated via
     nl_the_money, incl. the 💵/💸 emoji) is untouched -- this only adds
     spacing/weight/a thin rim under the existing text, it doesn't wrap
     or restyle the emoji individually (the element's content is set via
     .textContent in applyTranslations(), so it can't contain nested
     markup anyway). 2026-08-21 round 2 follow-up: the accent segment now
     reuses --nlh-accent (the same header-plaque variable defined on
     #loan-sheet above) instead of the plain --teal token, purely so the
     heading "transitions naturally from the premium header" per the
     owner's Concept #4 follow-up request -- on Dark/Glass this is now
     the same restrained gold as the header's icon/rim; everywhere else
     it's unchanged (--nlh-accent falls back to var(--teal-deep) exactly
     like before). */
  #nl-the-money-label{
    font-weight:900;letter-spacing:.03em;
    padding-bottom:8px;margin-bottom:14px;
    border-bottom:1px solid var(--border);
    position:relative;
  }
  #nl-the-money-label::after{
    content:"";position:absolute;left:0;bottom:-1px;width:34px;height:2px;
    background:linear-gradient(90deg, var(--nlh-accent), transparent);
    border-radius:2px;
  }
  /* Title icons a tad bigger than their word (2026-08-20, direct owner
     request, customer profile page) -- em-based so it scales with
     whatever font-size the title around it already has, instead of a
     fixed px size that would look right in one spot and wrong in
     another. Kept modest (1.3x) per "don't exaggerate but make it look
     nice." */
  .title-icon{font-size:1.3em;line-height:1;vertical-align:-3px;display:inline-block;}
  /* LOANS header: icon + "LOANS" forced onto one line (2026-08-21, direct
     owner report + screenshot: on mobile the card-icon renders ABOVE the
     word "LOANS" instead of beside it). Root cause, confirmed by direct
     measurement (not guessed): the title div (.section-label, a plain
     block with white-space:normal) and the "New loan" button sit in a
     `display:flex;justify-content:space-between` row. .btn-primary's
     base rule is `width:100%` (correct for its normal full-width-button
     use elsewhere, e.g. sheet submit buttons) -- inside this flex row
     that makes the button claim the whole row's width as its flex-basis,
     so flex-shrink then squeezes the TITLE down to ~65px, below the
     combined width of the icon span + the word "Loans", which forces a
     wrap exactly at the space between them (icon on its own line, word
     on the next). Confirmed via getBoundingClientRect() at 390px width:
     title box collapsed to 65.7px while the button rendered at 262px.
     Fix is two scoped rules (.loans-header-title / .loans-header
     .btn-primary below) rather than touching .title-icon, .section-label
     or .btn-primary globally -- those are shared by every other section
     header and button in the app (Payment History, Notes, Tags, Rating
     History, References, Timeline, Notifications, sheet submit buttons,
     etc.), so a global change here would be exactly the kind of
     unintended redesign-by-side-effect the owner explicitly ruled out. */
  .loans-header-title{
    display:flex;align-items:center;gap:6px;
    white-space:nowrap;flex:0 0 auto;min-width:max-content;
  }
  /* Icon "slightly larger/more prominent" (owner's word), scoped to just
     this header so every other .title-icon in the app (Payment History,
     Notes, Tags, Timeline, etc.) is untouched -- higher selector
     specificity (0,2,0) safely overrides the shared base rule (0,1,0)
     regardless of source order without needing !important.
     2026-08-21 follow-up: switched from `1.6em` (relative to this title
     div's own font-size) to a fixed `1.52rem` (relative to the root,
     same computed size as before: .95rem * 1.6 = 1.52rem). This was
     REQUIRED, not cosmetic -- the owner approved this icon size exactly
     as it was ("the credit-card icon is now appropriately larger...
     correctly staying on one line") and separately asked to enlarge only
     the word "LOANS". Since the icon's size was `em` (relative to its
     *parent*, this same title div), simply increasing the title div's
     own font-size for the text would have scaled the icon up right along
     with it. Fixing the icon to a rem value decouples it from the
     title's font-size entirely, so the text below can grow on its own
     without moving the already-approved icon. */
  .loans-header-title .title-icon{font-size:1.52rem;}
  .loans-header{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:10px;}
  /* Let the New loan button size to its own content and shrink instead
     of claiming the full row (see root-cause comment above) -- scoped to
     `.loans-header .btn-primary` only, so every other .btn-primary in
     the app (which correctly wants width:100%, e.g. sheet submit
     buttons) is unaffected. Colors/gradient/action/text/permissions all
     untouched -- purely a sizing override. */
  .loans-header .btn-primary{width:auto;flex:0 1 auto;min-width:0;white-space:nowrap;}
  .tag-add-row{display:flex;gap:8px;margin-bottom:10px;}
  .tag-add-select{
    flex:1;padding:9px 11px;border:1px solid var(--border);border-radius:8px;font-size:.85rem;
    background:var(--surface);color:var(--text-secondary);
  }
  .link-row{display:flex;align-items:center;gap:8px;color:var(--teal-text);font-size:.9rem;padding:10px 0;cursor:pointer;transition:background-color .14s ease;border-radius:8px;}
  .link-row:active{background-color:var(--surface-2);}
  @media (hover:hover){
    .link-row:hover{background-color:var(--surface-2);}
  }
  .opstats-row{transition:background-color .14s ease;}
  .opstats-row:active{background-color:var(--surface-2);}
  @media (hover:hover){
    .opstats-row:hover{background-color:var(--surface-2);}
  }
  .pay-type-btn{color:var(--text-secondary);}
  .pay-type-btn.selected, .fund-source-btn.selected, .theme-btn.selected{background:var(--teal);color:#fff;border-color:var(--teal);}

  /* ---------- iOS-style toggle switch ---------- */
  .ios-toggle-row{display:flex;align-items:center;justify-content:space-between;padding:4px 0;}
  .ios-toggle-label{font-size:.92rem;color:var(--text-primary);}
  .ios-toggle{position:relative;display:inline-block;width:51px;height:31px;flex-shrink:0;}
  .ios-toggle input{opacity:0;width:0;height:0;position:absolute;}
  .ios-toggle-track{
    position:absolute;inset:0;background:var(--border);border-radius:31px;
    cursor:pointer;transition:background .2s ease;
  }
  .ios-toggle-thumb{
    position:absolute;left:2px;top:2px;width:27px;height:27px;background:#fff;
    border-radius:50%;transition:transform .2s cubic-bezier(.2,.8,.2,1);
    box-shadow:0 2px 4px rgba(0,0,0,.25);
  }
  .ios-toggle input:checked ~ .ios-toggle-track{background:var(--teal);}
  .ios-toggle input:checked ~ .ios-toggle-track .ios-toggle-thumb{transform:translateX(20px);}
  .ios-toggle input:focus-visible ~ .ios-toggle-track{box-shadow:0 0 0 3px var(--teal-bg);}
  .summary-line{
    display:flex;align-items:flex-start;gap:8px;font-size:.85rem;color:var(--text-secondary);
    padding:6px 0;border-bottom:1px solid var(--border);
  }
  .summary-line:last-child{border-bottom:none;}
  .drill-stat{
    cursor:pointer;
    transition:transform .18s cubic-bezier(.22,.61,.36,1), box-shadow .18s cubic-bezier(.22,.61,.36,1), opacity .12s ease;
  }
  .drill-stat:active{transform:scale(.97);opacity:.85;}
  @media (hover:hover){
    .drill-stat:hover{transform:translateY(-2px);box-shadow:0 8px 20px -6px rgba(0,0,0,.14), 0 2px 6px -2px rgba(0,0,0,.06);}
  }
  .drill-stat.danger .value{color:var(--danger-text);}
  .list-sheet-row{
    display:flex;justify-content:space-between;align-items:center;padding:11px 4px;
    border-bottom:1px solid var(--border);cursor:pointer;font-size:.85rem;
  }
  .list-sheet-row:last-child{border-bottom:none;}
  .toast{
    position:fixed;left:50%;bottom:28px;transform:translateX(-50%);
    background:rgba(20,20,24,.92);color:#fff;padding:12px 18px;border-radius:9px;
    font-size:.86rem;z-index:50;display:none;
    backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px);
    border:1px solid rgba(255,255,255,.08);box-shadow:0 8px 24px rgba(0,0,0,.35);
  }
  /* Loading ring overlay (2026-08-29, owner report: Start Menu items that
     fetch data before opening -- Analytics, Finance Center, Settings,
     etc. -- looked "frozen" for the second or two the fetch takes, with
     zero feedback anywhere in the app. This is a single shared overlay
     rather than a per-sheet loading state: every affected menu item
     already closes the Start Menu synchronously and THEN awaits its own
     Promise.all of api() calls before calling openSheet() (see each
     open___Sheet() function) -- wrapping that existing await with
     showLoadingRing()/hideLoadingRing() needed no change to any sheet's
     own render/open logic. Visual language: a glowing conic-gradient
     ring (a "comet" sweep, not a flat spinner) in the app's own teal,
     no antique hourglass/clock -- direct owner spec after reviewing
     reference images ("something newer... more modern"). The center
     glyph reuses whichever emoji already marks that row in the Start
     Menu, so the overlay reads as "that same item, now loading" rather
     than a generic spinner. Zero network cost: this is pure CSS/JS
     layered on top of the SAME api() calls the app already made before
     this change -- no new requests, nothing "pulled" more often than
     today, and nothing added to the page after first load (owner asked
     specifically whether this would tax the app or the connection). */
  /* Fifth pass (2026-08-29, same evening, owner follow-up on the fourth
     pass -- rated 70/100): "sometimes... other menus pop up really fast,
     then you get the black background... it kinda blinds you... it just
     doesn't go with the app, specifically with the white one... it looks
     amazing with the dark themes... maybe a little haze... so you could
     see the background." Direct owner quote, and correct: the full-bleed
     near-opaque panel (rgba(7,9,15,.86) over the WHOLE screen) is a solid
     black takeover blinking in and out over a few seconds, which reads
     fine layered over a dark-theme app (similar tone, low contrast jump)
     but is jarring over the light theme (a hard cut to black and back).
     The glow still genuinely needs real darkness to read as a glow (see
     the rewrite note above) -- that part of the earlier reasoning holds,
     it just doesn't need the WHOLE screen to go dark to get it. Fix:
     #loading-ring-overlay now uses the exact same translucent scrim
     already used everywhere else in this app for "something appears over
     the current screen" (.sheet-backdrop's own rgba(22,24,27,.5) + 6px
     blur) -- the same soft dim/haze every sheet already opens with, in
     every theme, so this no longer introduces a second, harsher visual
     language of its own. The darkness the glow needs now lives on a
     small floating .loading-ring-card instead of the full viewport --
     same idea as a toast or a compact sheet, sized to its own content,
     so the light-theme page underneath stays visibly itself (hazy, not
     blacked out) while the ring still has a genuinely dark surface to
     glow against, right where it needs it. */
  #loading-ring-overlay{
    display:none;position:fixed;inset:0;z-index:60;
    align-items:center;justify-content:center;
    background:rgba(22,24,27,.5);backdrop-filter:blur(6px);-webkit-backdrop-filter:blur(6px);
    opacity:0;transition:opacity .18s ease;
  }
  #loading-ring-overlay.show{display:flex;opacity:1;}
  /* Sixth pass (2026-08-29, same evening, owner: "I love this... fade
     out the edges... not blur the edges... so it could kinda blend in
     some more" -- the card's own rounded-rect edge, even soft-shadowed,
     still reads as a distinct sticker sitting on top of the haze behind
     it). Fix: the card is no longer a filled rounded-rect at all --
     `background` is now a radial gradient that is solid in the middle
     and fades all the way to fully transparent before it reaches the
     box's own edge (padding widened to give that fade room), and the
     old box-shadow is gone entirely, since ANY hard-edged shadow around
     a shape -- even one whose fill fades -- still draws a visible
     boundary. With no fill and no shadow left at the true edge of the
     box, there's nothing there to see: just the vignette dissolving
     into the same haze around it. `ellipse closest-side` shapes the
     fade to this box's own (wider-than-tall) proportions rather than a
     circle, so it hugs the ring+label content instead of looking like a
     stamped-out circle. */
  /* Seventh pass (2026-08-29, same evening, owner: "no... why you made
     it a circle... a circle on top of a circle looks nasty... what you
     had before was just perfect. It just needed to soften the edges").
     The sixth pass over-corrected -- a radial-gradient vignette doesn't
     "soften a box's edges," it replaces the box with an ellipse, which
     on top of an already-round ring read as two stacked circles. Back
     to the rounded-RECTANGLE card from the fifth pass (owner explicitly
     confirmed that shape was right), with just its boundary feathered:
     ::before is a solid rounded-rect the same shape as the card, sized
     very slightly larger, rendered on its own layer and blurred --
     `filter:blur()` on a plain rectangle softens/dissolves ITS edges in
     place without reshaping the rectangle into a circle the way a
     radial-gradient's math does, because the blur radius here is small
     relative to the rect's own size. The card itself carries no
     background of its own any more -- only this blurred rect behind it. */
  .loading-ring-card{
    position:relative;
    display:flex;flex-direction:column;align-items:center;gap:20px;
    padding:34px 42px 28px;
    transform:scale(.94);transition:transform .22s cubic-bezier(.32,.72,.33,1);
  }
  .loading-ring-card::before{
    content:"";position:absolute;inset:-9px;z-index:-1;
    background:rgba(9,11,17,.94);border-radius:26px;
    filter:blur(11px);
  }
  #loading-ring-overlay.show .loading-ring-card{transform:scale(1);}
  /* Rewrite (2026-08-29, direct owner report on the first version --
     "looks cheesy... looks old... like from the nineties" against his
     reference images of a thick glowing blue-to-mint ring with real
     bloom around a plain flat glyph). The first pass reused the app's
     ::first-letter icon-badge recipe (bordered box, inset bevel
     highlights, modest single-layer shadow) for visual consistency with
     the rest of the menu -- that beveled-box, restrained-glow look is
     exactly what read as dated next to a bloom-heavy neon reference, and
     it was sitting on the app's own light/dim backdrop, which mutes any
     glow to begin with. Neither change here is a performance
     consideration -- this was always plain CSS regardless of how subtle
     or bold the glow is, so making it bigger and brighter costs nothing
     extra. Fixes, matched to the reference: (1) forced-dark panel above,
     so the glow has something to glow against; (2) bigger ring (100px,
     was 64px) with a richer blue-to-mint gradient and two stacked
     drop-shadow blurs (tight+bright, wide+soft) for real bloom instead
     of one faint 7px shadow; (3) the center glyph now sits plain, with
     no bordered/beveled chip and no glow filter of its own -- a glow
     FILTER directly on a multi-colour emoji glyph smudges into a muddy
     blob rather than a clean halo (the exact failure mode already
     documented on .sheet-hero-icon/color-emoji elsewhere in this file);
     the ring alone -- a smooth solid-colour shape, not a font glyph --
     is what carries the glow safely. */
  /* Second rewrite (2026-08-29, same evening, direct owner follow-up:
     "fifty percent better... but I need more three-d to it... more
     glow... instead of a solid ring... multiple layers... like a light
     has glow"). The first rewrite fixed the dated bevel-box and the
     washed-out glow (see the rewrite note above) but was still one flat
     conic-gradient shape -- a single ring, single blur pass. */
  /* Third rewrite (2026-08-29, same evening, owner sent his actual
     reference image this time instead of describing it). Comparing
     against it directly: it is exactly TWO ring shapes, not four
     abstract layers -- a bigger, blurred, dim ring behind (same
     blue-teal-green hue family, just heavily muted and out of focus,
     which is ALSO what gives it the soft bleed into the dark -- there
     is no separate glow blob doing that job, the big blurred ring IS
     the glow) and a smaller, crisp, bright comet ring in front. The
     previous round's .loading-ring-halo (a soft blob) and
     .loading-ring-track (a thin same-size ring) were both reaching for
     that effect indirectly and landed short of it; .loading-ring-back
     below replaces both with the thing actually in the reference -- a
     genuinely bigger ring, out of focus. Left as-is from the previous
     round: the bright comet ring + its screen-blended core hairline
     (.loading-ring-spin > .loading-ring + .loading-ring-core), which
     was already correct against the same reference. Also added: a soft
     borderless glow disc behind the center glyph (.loading-ring-icon-glow)
     for a little lift/dimension on the icon without reintroducing the
     bevelled-box look already rejected once, and without putting a glow
     FILTER directly on the glyph itself (still the muddy-emoji-shadow
     risk documented in the first rewrite note above) -- the glow sits
     on its own layer behind the glyph instead. Deliberately NOT
     replacing every menu item's icon with a literal "$" -- the
     reference is a generic "payment" stock loading animation, and this
     overlay covers 8 different destinations (Settings, Notifications,
     Activity Center, etc.) where a dollar sign would be flatly wrong;
     each row keeps its own icon so the loading state still reads as
     "that item, loading." One honest limit: the reference reads as a
     rendered/illustrated static image (soft ray-traced-looking bloom,
     a bevelled metal glyph) -- this is live, lightweight, animated CSS
     covering 8 different icons, not a one-off illustration, so it can
     get close in structure and glow without being pixel-identical to a
     rendered piece of art. */
  /* Fourth pass (2026-08-29, same evening, owner follow-up on the third
     rewrite): "put those two rings one on top of the other, making the
     moving one on top of the solid one" -- the third rewrite's back
     ring was made a genuinely bigger diameter (136px vs the front
     ring's ~92px), which reads as two separate concentric circles with
     a visible dark gap between them rather than one ring with a dim
     layer directly under a bright one. Re-reading his "bigger ring in
     back" wording from the round before: that's what blur naturally
     does to an edge (it visually spreads outward), not an instruction
     to make the diameter itself larger. Fix: .loading-ring-back now
     shares the EXACT SAME inset as .loading-ring (both 6px) -- same
     circle, not a bigger one -- so the blurred dim layer sits directly
     underneath the crisp bright arc with no gap; DOM order already put
     .loading-ring-spin after .loading-ring-back, so the moving arc was
     already painting on top, that part just needed the sizing fixed to
     actually read that way.
     Also, per the same message ("in the middle... can you do the money
     sign like I told you... like I told you" -- second direct ask):
     the center glyph is now a plain "$" character rather than each
     row's own emoji. This is DIFFERENT from putting a glow filter on a
     color emoji (the muddy-smudge failure documented above) -- "$" is
     a plain text glyph with no built-in colour, so it can safely take
     the same background-clip:text gradient + drop-shadow bevel trick
     already used on .sheet-hero-title elsewhere in this file, and get
     an actual metallic look, not just a flat character. Reframed as a
     consistent BRAND loading mark shown on every menu item (like a
     splash-screen logo mark), rather than a per-item content icon --
     that resolves the "wrong on Settings" concern from the round
     before without refusing the direct request twice. */
  .loading-ring-wrap{position:relative;width:104px;height:104px;display:flex;align-items:center;justify-content:center;}
  .loading-ring-back{
    position:absolute;inset:6px;border-radius:50%;
    background:conic-gradient(from 0deg, #2F6FED 0%, #22D3A6 50%, #E8FFF6 75%, #2F6FED 100%);
    opacity:.4;filter:blur(4px);
    -webkit-mask:radial-gradient(farthest-side, transparent calc(100% - 8px), #000 calc(100% - 8px));
    mask:radial-gradient(farthest-side, transparent calc(100% - 8px), #000 calc(100% - 8px));
  }
  .loading-ring-icon-glow{
    position:absolute;inset:0;margin:auto;width:56px;height:56px;border-radius:50%;
    background:radial-gradient(circle, rgba(232,255,246,.32) 0%, rgba(34,211,166,.16) 55%, transparent 76%);
  }
  .loading-ring-dollar{
    font-family:'Inter',-apple-system,BlinkMacSystemFont,sans-serif;font-weight:800;
    font-size:2.35rem;line-height:1;
    background:linear-gradient(160deg, #ffffff 0%, #cfe9ff 38%, #6fa8ff 68%, #eafff6 100%);
    -webkit-background-clip:text;background-clip:text;color:transparent;-webkit-text-fill-color:transparent;
    filter:drop-shadow(0 2px 3px rgba(0,0,0,.5)) drop-shadow(0 -1px 0 rgba(255,255,255,.3));
  }
  .loading-ring-spin{position:absolute;inset:0;animation:loading-ring-spin 1.1s linear infinite;}
  @keyframes loading-ring-spin{ to{ transform:rotate(360deg); } }
  /* Same class of infinite-loop-animation bug documented at the
     [data-motion="plain"] * "REAL BUG" note near the top of this file:
     the blanket `animation-duration:.01ms !important` rule turns any
     animation-iteration-count:infinite loop into up to ~100,000 replays
     a second instead of stopping it, which renders as chaotic flicker
     and differs by device/GPU. Every other infinite loop in this file
     has an explicit `animation:none` sibling under both signals -- this
     one (and the halo pulse above) need the same, since they're new. */
  @media (prefers-reduced-motion: reduce){ .loading-ring-spin{animation:none;} }
  [data-motion="plain"] .loading-ring-spin{animation:none;}
  .loading-ring{
    position:absolute;inset:6px;border-radius:50%;
    background:conic-gradient(from 0deg, transparent 0%, #2F6FED 30%, #22D3A6 70%, #E8FFF6 100%);
    -webkit-mask:radial-gradient(farthest-side, transparent calc(100% - 8px), #000 calc(100% - 8px));
    mask:radial-gradient(farthest-side, transparent calc(100% - 8px), #000 calc(100% - 8px));
    filter:
      drop-shadow(0 0 4px rgba(232,255,246,.95))
      drop-shadow(0 0 16px rgba(34,211,166,.75))
      drop-shadow(0 0 34px rgba(34,211,166,.5))
      drop-shadow(0 0 58px rgba(47,111,237,.4));
  }
  .loading-ring-core{
    position:absolute;inset:9.5px;border-radius:50%;
    background:conic-gradient(from 0deg, transparent 0%, rgba(90,150,255,.9) 30%, rgba(255,255,255,.95) 70%, #fff 100%);
    -webkit-mask:radial-gradient(farthest-side, transparent calc(100% - 1.6px), #000 calc(100% - 1.6px));
    mask:radial-gradient(farthest-side, transparent calc(100% - 1.6px), #000 calc(100% - 1.6px));
    mix-blend-mode:screen;opacity:.85;
  }
  .loading-ring-icon{position:relative;font-size:2.05rem;line-height:1;}
  .loading-ring-label{color:#EAF6FF;font-size:.84rem;font-weight:600;letter-spacing:.01em;text-shadow:0 1px 3px rgba(0,0,0,.5);}
  /* Inline (in-menu) loading ring (2026-09-02, direct owner request):
     brings back an earlier, never-shipped mockup's interaction -- the
     Start Menu stays open, the tapped row shows a loading state right
     there in place, then the menu closes once the data is ready and
     the sheet opens already populated -- instead of the full-screen
     #loading-ring-overlay above taking over the whole viewport. Only
     Analytics uses this (the sole existing showLoadingRing() call site
     -- see the click handler and openAnalyticsSheet() below); the
     full-screen overlay above is left untouched and still available
     for any future menu item that wants it.
     That earlier mockup's own "loading" treatment was just the row's
     existing emoji tile (::first-letter) pulsing with a teal tint --
     no ring at all -- which is what the owner remembered as "green and
     stale." Per his explicit ask, this replaces that pulse with a
     scaled-down version of the SAME ring recipe used by the full-
     screen overlay above (back blurred ring + bright comet arc + core
     hairline, same #2F6FED/#22D3A6/#E8FFF6 gradient), not a new style.
     Sizing/positioning: #logo-menu-dropdown .profile-menu-item is
     already position:relative (see above), so this wrap is a plain
     absolutely-positioned overlay sized and placed to land directly on
     top of that row's own icon tile (14px left padding matches the
     row's own `padding:12px 14px`; ~32px matches the tile's own
     rendered size at 1.38em font-size + 5.5/6.5px padding) -- confirmed
     against a real render, not just computed. The tile itself is
     hidden (visibility, not display, so its box keeps reserving that
     exact space) only while this class is present.
     Glow is deliberately lighter than the full-screen version: that
     one sits on a forced-dark card specifically because a big bloom
     needs real darkness to read (see the rewrite notes above) -- this
     one sits directly on whichever of the app's 8 themes is active,
     several of them light, so a heavy blur here would just wash out
     into a smudge instead of glowing. Fewer, tighter drop-shadow
     layers keep the ring shape legible on every theme without needing
     per-theme overrides. */
  .pmi-item-loading::first-letter{visibility:hidden;}
  /* Command Center and Notifications don't use this technique at all --
     they're the only two Start Menu rows built as a flex container with
     the icon+label wrapped in a nested <span> (to make room for their
     own unread-count badge, a second flex item). A flex CONTAINER's own
     ::first-letter never applies (browsers only support ::first-letter
     on block containers) -- confirmed directly: the existing
     #menu-commandcenter-btn::first-letter / #menu-notifications-btn::
     first-letter accent-tint rules a few lines up have silently never
     applied for exactly this reason, pre-existing and unrelated to this
     feature. What DOES still support ::first-letter is the nested
     <span> itself: a flex item's outer display is auto-blockified per
     the flexbox spec, confirmed with a real render before relying on
     it. So hiding the emoji here needs the child-span selector instead
     of the bare class selector above. */
  .pmi-item-loading > span:first-child::first-letter{
    visibility:hidden;
    /* The generic #logo-menu-dropdown .profile-menu-item::first-letter
       rule above (font-size/padding/margin-right/etc.) never applied to
       these two rows either -- same reason, wrong element. visibility
       alone hides the emoji but keeps its box at plain-text size, with
       none of that rule's margin-right reserving room after it, so the
       label text was sliding left to sit right against (and under) the
       ring. Repeating just the layout-affecting subset (not the
       background/box-shadow -- pointless on a hidden box) reserves the
       same footprint here that the tile occupies on every other row,
       so "Command Center"/"Notifications" starts in the same place
       relative to the ring that every other row's label starts
       relative to its own icon. */
    font-size:1.38em;
    padding:5.5px 6.5px;
    margin-right:.21em;
  }
  .pmi-inline-ring-wrap{
    position:absolute;left:14px;top:50%;transform:translateY(-50%);
    width:32px;height:32px;pointer-events:none;
  }
  .pmi-inline-ring-back{
    position:absolute;inset:1px;border-radius:50%;
    background:conic-gradient(from 0deg, #2F6FED 0%, #22D3A6 50%, #E8FFF6 75%, #2F6FED 100%);
    opacity:.4;filter:blur(1.5px);
    -webkit-mask:radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px));
    mask:radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px));
  }
  .pmi-inline-ring-spin{position:absolute;inset:0;animation:loading-ring-spin 1.1s linear infinite;}
  @media (prefers-reduced-motion: reduce){ .pmi-inline-ring-spin{animation:none;} }
  [data-motion="plain"] .pmi-inline-ring-spin{animation:none;}
  .pmi-inline-ring{
    position:absolute;inset:1px;border-radius:50%;
    background:conic-gradient(from 0deg, transparent 0%, #2F6FED 30%, #22D3A6 70%, #E8FFF6 100%);
    -webkit-mask:radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px));
    mask:radial-gradient(farthest-side, transparent calc(100% - 3px), #000 calc(100% - 3px));
    filter:
      drop-shadow(0 0 1.5px rgba(232,255,246,.95))
      drop-shadow(0 0 4px rgba(34,211,166,.6));
  }
  .pmi-inline-ring-core{
    position:absolute;inset:2.5px;border-radius:50%;
    background:conic-gradient(from 0deg, transparent 0%, rgba(90,150,255,.9) 30%, rgba(255,255,255,.95) 70%, #fff 100%);
    -webkit-mask:radial-gradient(farthest-side, transparent calc(100% - 1px), #000 calc(100% - 1px));
    mask:radial-gradient(farthest-side, transparent calc(100% - 1px), #000 calc(100% - 1px));
    mix-blend-mode:screen;opacity:.85;
  }
  /* Frosted glass card -- separates distinct settings sections into
     visibly different boxes, per the owner's consistency request. */
  .glass-card {
    background: color-mix(in srgb, var(--surface-2) 82%, transparent);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 14px 16px;
    margin-bottom: 16px;
  }
  /* Customer 360 Summary -- smooth max-height/opacity accordion, still
     collapsible on tap (owner spec 2026-07-18). Widely compatible
     transition style, unlike grid-template-rows animation which isn't
     universal yet. Customer 360 Intelligence consolidation (2026-08-19,
     direct owner requirement) added risk/delinquency status and an
     attention-count badge to this card's header -- the badge lives in
     .c360-header, so it stays visible whether the card is expanded or
     collapsed. Default state reverted to collapsed (2026-08-19, direct
     owner requirement, same day) -- same accordion, same toggle, just
     starts collapsed like every other card using this component;
     attention state is still visible via the header badge without
     opening it. */
  .c360-card {
    border: 1px solid var(--border);
    border-radius: 12px;
    background: color-mix(in srgb, var(--surface-2) 82%, transparent);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    margin-bottom: 16px;
    overflow: hidden;
  }
  .c360-header {
    display: flex; align-items: center; justify-content: space-between;
    padding: 14px 16px; cursor: pointer; user-select: none;
  }
  .c360-chevron {
    width: 34px; height: 34px; flex: none; border-radius: 50%;
    background: var(--surface-2);
    display: flex; align-items: center; justify-content: center;
    transition: background-color .2s ease, transform .25s cubic-bezier(.22,.61,.36,1);
  }
  .c360-chevron svg { width: 20px; height: 20px; color: var(--text-secondary); transition: color .2s ease; }
  .c360-card.open .c360-chevron { background: var(--teal-bg); transform: rotate(180deg); }
  .c360-card.open .c360-chevron svg { color: var(--teal-deep); }
  .c360-body {
    max-height: 0; opacity: 0;
    overflow: hidden;
    transition: max-height .38s cubic-bezier(.4,0,.2,1), opacity .28s ease;
  }
  /* Real bug found (2026-07-21): this was a hardcoded 620px, silently
     clipping ANY content taller than that with no scrollbar and no
     visual indication anything was missing -- confirmed to affect
     Settings categories (Permissions' full user list, Partner Rules'
     many toggles), the Customer 360 summary panel, and the Loan
     Lifecycle panel, since all three share this one component. Raised
     generously and made scrollable specifically while open, so even if
     content someday exceeds this too, it's reachable by scrolling
     rather than silently lost again. */
  .c360-card.open .c360-body { max-height: 1400px; opacity: 1; overflow-y: auto; }
  /* Collapse flicker fix (2026-08-19, direct owner requirement): Customer
     360 is the one card using this shared accordion component whose body
     realistically grows tall enough to hit the 1400px cap above (Lifetime
     Stats + Risk & Delinquency + Behavioral Patterns + Behavioral
     Observations + Open Loan Exposure, all in one body) -- when it does,
     overflow-y:auto shows a real scrollbar while open. overflow isn't a
     transitionable property, so collapsing snaps it back to hidden in one
     frame, and the scrollbar's ~15px width disappears instantly, reflowing
     every line of text at the exact moment the collapse starts -- reported
     as a "flicker" on close only, never on open (confirmed: Settings
     categories and the Loan Lifecycle panel use this same component but
     never grow tall enough to show a scrollbar, so they don't hit this).
     scrollbar-gutter:stable reserves that width permanently while this
     card is a scroll container, so losing the scrollbar on collapse no
     longer changes the content's available width. Scoped to #c360-card by
     id specifically so Settings/Loan Lifecycle are entirely unaffected. */
  #c360-card.open .c360-body { scrollbar-gutter: stable; }
  /* Section shading + texture pass (2026-08-20, direct owner request:
     "too dull... too cluttery... shades or textures to make it more
     interesting"). Gives each sub-section its own tinted panel plus a
     faint dot-grid texture behind everything, using only colors already
     in the theme palette (same color-mix pattern as .c360-card itself)
     so it stays correct across every theme, including glass, without
     inventing a new palette. Padding+texture live on a new .c360-inner
     wrapper INSIDE .c360-body rather than on .c360-body directly,
     matching the existing settingsCategory() convention (its own
     padding-top wrapper) so the collapse/expand max-height animation
     never has to animate padding. Everything here is scoped to
     #c360-card by id, same convention as the scrollbar fix above, so
     Settings categories and the Loan Lifecycle panel -- which share the
     same .c360-card/.c360-body classes -- are entirely unaffected. */
  #c360-card .c360-inner{
    padding: 0 16px 16px;
    background-image: radial-gradient(circle at 1px 1px, var(--border) 1px, transparent 0);
    background-size: 16px 16px;
  }
  #c360-card .c360-section{ border-radius: 10px; padding: 12px 12px 14px; margin-top: 14px; }
  #c360-card .c360-section-a{ background: color-mix(in srgb, var(--surface) 82%, transparent); }
  #c360-card .c360-section-b{ background: color-mix(in srgb, var(--teal-bg) 55%, transparent); }
  /* New-Loan "Collateral" toggle reveal -- same max-height/opacity
     accordion technique as .c360-body above (owner spec 2026-07-19: no
     page reload, no layout jump, iOS-style slide-down). Kept as its own
     class rather than reusing .c360-body directly since this one isn't
     nested in a .c360-card header/chevron structure. */
  .collateral-reveal {
    max-height: 0; opacity: 0;
    overflow: hidden;
    transition: max-height .38s cubic-bezier(.4,0,.2,1), opacity .28s ease;
  }
  .collateral-reveal.open { max-height: 640px; opacity: 1; }
  .collateral-reveal-inner { padding-top: 4px; }

  /* ---------- Document Center ----------
     Uses the app's existing --teal/--surface/--page variables rather
     than new hardcoded tokens, so it automatically matches whichever
     theme is active -- including the Gold theme, whose --teal IS the
     gold accent (#D4AF37). Reusing the design system, not duplicating
     it, per the "one design system, everywhere" rule. */
  /* Embossed PANEL (2026-08-29, direct owner correction -- see
     .sheet-hero's comment above for the full story: the emoss request
     was always about the BOX, not the heading text). Document Center's
     box was the one the owner pointed to as the roundness/shape to
     match ("I like round like document center is") so its
     border-radius is untouched here -- only the emboss box-shadow is
     added, using the exact same recipe as .sheet-hero so all 4 heading
     boxes (Analytics/Finance Center/Settings/Document Center) now share
     one consistent raised-panel treatment. */
  .dc-hero{
    position:relative;overflow:hidden;border-radius:16px;padding:22px 18px 20px;
    margin-bottom:16px;background:
      radial-gradient(120% 140% at 15% 0%, color-mix(in srgb, var(--teal) 16%, transparent), transparent 60%),
      var(--surface-2);
    border:1px solid var(--border);
    box-shadow:
      inset 0 1.5px 0 rgba(255,255,255,.22),
      inset 1.5px 0 0 rgba(255,255,255,.08),
      inset 0 -1.5px 0 rgba(0,0,0,.30),
      inset -1.5px 0 0 rgba(0,0,0,.16),
      0 8px 20px -10px rgba(0,0,0,.4);
  }
  .dc-hero-title{font-size:1.35rem;font-weight:700;letter-spacing:-.01em;color:var(--text-primary);margin-bottom:4px;}
  .dc-hero-sub{font-size:.86rem;color:var(--text-secondary);margin-bottom:14px;}
  .dc-hero-line{height:2px;width:64px;border-radius:2px;background:linear-gradient(90deg,var(--teal),transparent);}
  .dc-hero-particle{
    position:absolute;width:5px;height:5px;border-radius:50%;
    background:var(--teal);opacity:.5;box-shadow:0 0 10px 2px color-mix(in srgb, var(--teal) 55%, transparent);
    animation:dc-float 9s ease-in-out infinite;pointer-events:none;
  }
  @keyframes dc-float{
    0%,100%{ transform:translateY(0) scale(1); opacity:.35; }
    50%{ transform:translateY(-14px) scale(1.3); opacity:.7; }
  }
  @media (prefers-reduced-motion: reduce){ .dc-hero-particle{ animation:none; } }
  [data-motion="plain"] .dc-hero-particle{ animation:none; }
  .dc-grid{display:grid;grid-template-columns:1fr;gap:10px;}
  @media (min-width:640px){ .dc-grid{grid-template-columns:1fr 1fr;} }
  .dc-card{
    display:flex;gap:12px;align-items:flex-start;text-align:left;
    background:var(--surface-2);border:1px solid var(--border);border-radius:13px;
    padding:12px 13px;cursor:pointer;transition:transform .18s cubic-bezier(.22,.61,.36,1), box-shadow .18s cubic-bezier(.22,.61,.36,1), border-color .18s ease;
  }
  .dc-card:active{transform:scale(.98);}
  @media (hover:hover){
    .dc-card:hover{
      border-color:var(--teal);
      box-shadow:0 8px 22px -8px color-mix(in srgb, var(--teal) 45%, transparent);
      transform:translateY(-2px);
    }
  }
  .dc-card-icon{
    flex-shrink:0;width:40px;height:40px;border-radius:10px;background:var(--surface);
    display:flex;align-items:center;justify-content:center;font-size:1.2rem;border:1px solid var(--border);
  }
  .dc-card-title{font-size:.88rem;font-weight:600;color:var(--text-primary);margin-bottom:2px;line-height:1.25;}
  .dc-card-meta{font-size:.74rem;color:var(--text-secondary);}
  .dc-card-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px;}
  .dc-tag{font-size:.66rem;padding:2px 7px;border-radius:8px;background:var(--surface);color:var(--text-secondary);border:1px solid var(--border);}
  .dc-folder-tab{flex:0 0 auto;padding:8px 12px;font-size:.8rem;}
  .dc-folder-tab.selected{background:var(--teal);color:#171310;border-color:var(--teal);}

  /* All-tab customer grouping -- same max-height/opacity accordion
     technique as .c360-card, kept as its own class since a customer's
     document list can run longer than Customer 360's fixed 620px cap. */
  .dc-customer-card{
    border:1px solid var(--border);border-radius:13px;
    background:var(--surface-2);margin-bottom:10px;overflow:hidden;
  }
  .dc-customer-header{
    display:flex;align-items:center;justify-content:space-between;
    padding:13px 14px;cursor:pointer;user-select:none;
  }
  .dc-customer-name{font-size:.94rem;font-weight:700;color:var(--text-primary);}
  .dc-customer-avatar{
    width:36px;height:36px;border-radius:50%;flex-shrink:0;
    border:2px solid var(--border);
  }
  .dc-customer-count{font-size:.74rem;color:var(--text-secondary);margin-left:8px;font-weight:400;}
  .dc-customer-chevron{
    width:34px;height:34px;flex:none;border-radius:50%;
    background:var(--surface-2);
    display:flex;align-items:center;justify-content:center;
    transition:background-color .2s ease, transform .25s cubic-bezier(.22,.61,.36,1);
  }
  .dc-customer-chevron svg{width:20px;height:20px;color:var(--text-secondary);transition:color .2s ease;}
  .dc-customer-card.open .dc-customer-chevron{background:var(--teal-bg);transform:rotate(180deg);}
  .dc-customer-card.open .dc-customer-chevron svg{color:var(--teal-deep);}
  .dc-customer-body{max-height:0;opacity:0;overflow:hidden;transition:max-height .38s cubic-bezier(.4,0,.2,1), opacity .28s ease;}
  .dc-customer-card.open .dc-customer-body{max-height:3000px;opacity:1;}
  .dc-customer-body-inner{padding:0 14px 14px;}
  .dc-doc-row{
    display:flex;align-items:center;gap:10px;padding:9px 0;
    border-top:1px solid var(--border);cursor:pointer;
  }
  .dc-doc-row:first-child{border-top:none;}
  .dc-pictures-label{font-size:.74rem;font-weight:700;color:var(--text-secondary);text-transform:uppercase;letter-spacing:.03em;margin:10px 0 4px;}
  .dc-pictures-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(64px,1fr));gap:6px;}
  .dc-picture-thumb{width:100%;aspect-ratio:1;object-fit:cover;border-radius:8px;cursor:pointer;border:1px solid var(--border);}
  .c360-grid {
    display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
    padding: 4px 16px 16px;
  }
  .c360-stat {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: 10px; padding: 10px 12px;
  }
  .c360-stat .c360-label { font-size: .68rem; color: var(--text-secondary); margin-bottom: 3px; }
  .c360-stat .c360-value { font-size: 1.05rem; font-weight: 700; }
