:root{
  --navy-950:#0a1220;
  --navy-925:#0e1a2c;
  --navy-900:#0f1f33;
  --navy-800:#152a45;
  --navy-700:#1c3557;
  --gold-500:#c8992e;
  --gold-400:#dcae4a;
  --gold-600:#a97c2a;
  --gold-bg:#faf1de;
  --bg:#f4f6fa;
  --card:#ffffff;
  --border:#e2e6ee;
  --text-900:#1a2233;
  --text-600:#4b5568;
  --text-400:#8792a6;
  --green:#1f8a5f;
  --green-bg:#e7f6ef;
  --red:#c0392b;
  --red-bg:#fbe9e7;
  --amber:#b8790a;
  --amber-bg:#fdf1de;
  --blue:#2a5fb0;
  --blue-bg:#e8f0fc;
  --purple:#7c3aed;
  --purple-bg:#ede9fe;
  /* A softer, paler cousin of --purple, kept as its own pair rather than
     retuning --purple (which the Recently Deleted page's "Permanent
     Deletions" card and the .card-accent-purple tiles also render off).
     Currently used by the Wealth Dashboard's Active SIPs card. */
  --lavender:#8368c8;
  --lavender-bg:#f1edfc;
  --teal:#0f766e;
  --teal-bg:#d1fae5;
  --pink:#db2777;
  --pink-bg:#fde2ea;
  /* Subtle neutral fill — hover states, disabled fields, placeholder tiles,
     chips (previously scattered as literal hex grays across the file;
     unified into one variable so dark mode only needs to redefine it once). */
  --subtle-bg:#eef1f6;
  --radius:10px;
  --shadow:0 1px 3px rgba(15,31,51,.08), 0 1px 2px rgba(15,31,51,.06);
  --shadow-md:0 6px 20px rgba(15,31,51,.12);
}

/* ---------- Dark mode (Profile menu → Light mode / Dark mode toggle) ----------
   Applied via [data-theme="dark"] on <html> (see toggleThemeMode() in app.js),
   persisted in localStorage. Every component below already renders off these
   same variables, so redefining them here is enough to re-theme the whole
   app — sidebar/topbar/gold accents are intentionally left close to their
   light-mode values since they're already dark and read fine either way. */
:root[data-theme="dark"]{
  --navy-950:#05080e; --navy-925:#070d16; --navy-900:#0a1220; --navy-800:#101c30; --navy-700:#182a44;
  --gold-500:#d9ac52; --gold-400:#e6c179; --gold-600:#c8992e; --gold-bg:#332a17;
  --bg:#0c121c; --card:#161e2c; --border:#2b3548;
  --text-900:#edf1f7; --text-600:#aab4c8; --text-400:#7c869c;
  --green:#3fbf85; --green-bg:#123024;
  --red:#e2685a; --red-bg:#3a1c19;
  --amber:#e0a83f; --amber-bg:#3a2d14;
  --blue:#6c9ee0; --blue-bg:#132340;
  --purple:#ab8dfa; --purple-bg:#261c40;
  --lavender:#c4b3f0; --lavender-bg:#2a2445;
  --teal:#4fd1c0; --teal-bg:#0f2f2b;
  --pink:#f472b6; --pink-bg:#3a1a2b;
  --subtle-bg:#202a3d;
  --shadow:0 1px 3px rgba(0,0,0,.45), 0 1px 2px rgba(0,0,0,.35);
  --shadow-md:0 6px 20px rgba(0,0,0,.55);
}

*{box-sizing:border-box;}
html,body{height:100%;}
body{
  margin:0;
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  background:var(--bg);
  color:var(--text-900);
  font-size:14.5px;
  line-height:1.5;
}
/* Form controls don't inherit the page font by default in most browsers
   (buttons/selects/inputs fall back to the OS's own UI font, e.g. Arial on
   Chrome/Windows, regardless of what body{} sets) - without this, every
   button, dropdown, and text field on every page kept showing the old
   system font even after body{} switched to Inter. */
button,select,input,textarea{font-family:inherit;}

/* ---------- Typography ----------
   Single-family system: Inter everywhere (page/section/card headings
   included) - matches the reference app's own approach of one typeface,
   hierarchy carried by weight/size alone rather than a second decorative
   face. Previously these heading selectors ran an italic Cormorant
   Garamond serif; kept as their own rule block (rather than folded back
   into the plain body{} rule) since a couple still carry their own
   font-weight, and to keep this an easy single spot to revisit. Fonts
   loaded via Google Fonts <link> in index.html/auth.html. Typography only -
   no layout, spacing, color, or structural changes. */
.topbar-title,
.hero-greeting h2,
.card-title,
.nav-group-title,
.famexp-hero-title,
.tk-hero-title,
.tk-card-title,
.auth-success h2,
.idle-warning-card h3,
.dd-card h3{
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  font-style:normal;
  font-weight:600;
}
.stat-value{font-weight:600;}
/* Family Expenses' own hero banner heading, parallel to .hero-greeting h2
   on the Wealth Dashboard. */
.famexp-hero-title{font-size:23px;}
/* Travel Kit's hero title was already a serif (Georgia) one-off; switched
   to the same Cormorant Garamond Italic as every other heading instead of
   leaving it as the odd one out. */
.tk-hero-title{font-size:30px;}
.tk-card-title{font-size:17px;}
/* Wealth Dashboard - "everything on this page", not just headings: the
   welcome paragraph, Getting Started checklist items, card subtitles
   ("Last conversion rate update...", "N of 6 steps left", etc.), stat
   labels/footers, donut/bar legends, Family Members/Nominee/Timeline text,
   and the Update Conversion Rate button. Uses !important scoped to just
   .dashboard-page - this page has dozens of distinct text-bearing classes
   (many shared with other pages, e.g. .card-sub, .btn, .r-title), and a
   plain override here would silently lose the cascade to whichever same-
   specificity rule for those shared classes happens to come later in the
   file (the exact bug already found and fixed once for .nav-group-title) -
   !important sidesteps that source-order fragility entirely, and is safe
   here since it's purely cosmetic (font-family/style only, never
   layout/color/size). */
.dashboard-page, .dashboard-page *{
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif !important;
  font-style:normal !important;
}
/* Amount/number displays (KPI stat values, donut chart center total, asset
   bubble amounts, liability amounts) are reverted back to the plain Inter
   numeral style - italic serif digits read less cleanly at a glance than
   the rest of the page's prose/labels, and these are exactly the figures a
   user needs to scan fastest. Higher specificity than the blanket rule
   above (two classes vs. class+universal) wins even with both !important,
   regardless of source order.
   .dash-plain-num (opt-in, set in dashboard.js) covers the other places on
   this page that carry a real number even though they're not a currency
   amount - Personal Details' Date of Birth/Contact Number/Email Address,
   the whole Address Details block, and the Currency Conversion card's
   "Last conversion rate update: ..." timestamp - so those stay plain
   Inter too instead of italic serif digits. */
.dashboard-page .stat-value,
.dashboard-page .donut-center-value,
.dashboard-page .ads-bubble-value,
.dashboard-page .l-amount,
.dashboard-page .dash-plain-num{
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif !important;
  font-style:normal !important;
}
/* Re-tuned for plain Inter (the earlier +2/+3px stack here was sized for
   the ornate italic serif treatment this page used to carry - at those
   same sizes in plain Inter, text ran noticeably large/loose, e.g. the
   sidebar's nav labels wrapping/truncating once every page switched to
   one font). Restored to a compact, weight-driven scale close to the
   page's pre-decorative sizing, with only a touch of breathing room -
   the same density this app used before, and the same "hierarchy via
   weight/size, not decoration" approach the Kinviyo reference uses. */
.dashboard-page .hero-greeting h2{font-size:26px !important;}
.dashboard-page .hero-greeting p{font-size:14px !important;}
.dashboard-page .card-title{font-size:16px !important;}
.dashboard-page .card-sub{font-size:13px !important;}
.dashboard-page .stat-label{font-size:12px !important;}
.dashboard-page .stat-value{font-size:24px !important;}
.dashboard-page .stat-foot{font-size:12.5px !important;}
.dashboard-page .ads-footer{font-size:13px !important;}
.dashboard-page .ads-bubble-name{font-size:14px !important;}
.dashboard-page .ads-bubble-value{font-size:16px !important;}
.dashboard-page .ads-bubble-meta{font-size:12.5px !important;}
.dashboard-page .text-sm{font-size:13.5px !important;}
.dashboard-page .kicker{font-size:12px !important;}
.dashboard-page .donut-center-value{font-size:15px !important;}
.dashboard-page .donut-center-label{font-size:11px !important;}
.dashboard-page .donut-legend{font-size:13px !important;}
.dashboard-page .gbar-legend{font-size:12.5px !important;}
.dashboard-page .timeline-h-date{font-size:11.5px !important;}
.dashboard-page .security-item .s-label{font-size:12px !important;}
.dashboard-page .security-item .s-value{font-size:13.5px !important;}
.dashboard-page .member-card .m-name{font-size:14px !important;}
.dashboard-page .member-card .m-rel{font-size:12px !important;}
.dashboard-page .liability-card .l-name{font-size:14px !important;}
.dashboard-page .liability-card .l-sub{font-size:12px !important;}
.dashboard-page .liability-card .l-amount{font-size:20px !important;}
.dashboard-page .liability-card .l-meta{font-size:12px !important;}
.dashboard-page .dropzone .dz-title{font-size:13.5px !important;}
.dashboard-page .dropzone .dz-sub{font-size:12px !important;}
.dashboard-page .reminder-card .r-title{font-size:13.5px !important;}
.dashboard-page .reminder-card .r-desc{font-size:12px !important;}
.dashboard-page{font-size:14.5px;}
/* Data & Privacy card (.security-grid/.security-item) - the fixed 200px
   column width + tight padding/centered icon alignment were sized for the
   original, much smaller Inter labels/values. At this page's enlarged
   serif sizes each item's text wraps to 3-4 lines, so a vertically-centered
   icon no longer lines up with the label and boxes in the same row end up
   very different heights. Wider columns cut the wrapping down, and start-
   aligning the icon (with a small top nudge) keeps it level with the label
   regardless of how many lines the value wraps to. */
.dashboard-page .security-grid{grid-template-columns:repeat(auto-fill,minmax(235px,1fr)) !important;gap:14px !important;}
.dashboard-page .security-item{align-items:flex-start !important;padding:14px 16px !important;gap:12px !important;}
.dashboard-page .security-item .s-icon{font-size:18px !important;margin-top:2px;}
.dashboard-page .disclaimer-box{font-size:12.2px !important;padding:12px 16px !important;line-height:1.55 !important;}

/* Family Balance Sheet page - same "everything in the new serif style"
   treatment as the Wealth Dashboard (headings, card titles/subs, the view
   filter, table headers, and the Category/Asset/Owner and Category/
   Obligation/Borrower table cell text), scoped to .balance-sheet-page so
   other pages sharing these same classes are unaffected. */
.balance-sheet-page, .balance-sheet-page *{
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif !important;
  font-style:normal !important;
}
/* Amount figures kept exactly as they already were - plain, non-italic
   Inter - both the Total Assets/Liabilities/Net Worth KPI values and every
   table's rightmost Value/Outstanding money column. Higher specificity
   than the blanket rule above wins even with both !important. */
.balance-sheet-page .stat-value,
.balance-sheet-page table tbody tr:not(.empty-row) td:last-child{
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif !important;
  font-style:normal !important;
}
.balance-sheet-page .bs-filter-select{font-size:13.5px !important;}
/* Re-tuned for plain Inter, same reasoning as the Wealth Dashboard block
   above - a compact, weight-driven scale instead of the earlier +3px
   stack sized for the italic serif treatment this page used to carry. */
.balance-sheet-page .card-title{font-size:16px !important;}
.balance-sheet-page .card-sub{font-size:13px !important;}
/* Welcome card only — same title/subtitle sizing as the Wealth Dashboard's
   own hero-greeting (.hero-greeting h2/p, dashboard.js/styles.css), more
   specific than the two page-wide rules just above so it wins over them
   despite both carrying !important. */
.balance-sheet-page .bs-top-hero .card-title{font-size:20px !important;font-weight:800;}
.balance-sheet-page .bs-top-hero .card-sub{font-size:13.2px !important;}
.balance-sheet-page .stat-label{font-size:12px !important;}
.balance-sheet-page .stat-value{font-size:24px !important;}
.balance-sheet-page .stat-foot{font-size:12.5px !important;}
.balance-sheet-page table th{font-size:12px !important;}
.balance-sheet-page table td{font-size:13.5px !important;}
.balance-sheet-page{font-size:14.5px;}

/* Top-of-page card (title + member filter) — a slow golden shine sweeps
   across it, same construction as Business Details Hub's .bd-hero::before
   (reuses its bdHeroShine keyframes) but tinted gold via color-mix rather
   than a mix-blend-mode:overlay white glint, since this card sits on the
   plain theme-adaptive .card background (light in light mode) rather than
   a fixed dark navy fill — overlay-blended white barely reads against a
   light card, a real gold-tinted band does regardless of theme. */
/* Same navy gradient + white/gray text as the Wealth Dashboard's own
   welcome banner (.hero-greeting, dashboard.js/styles.css) — .card-title/
   .card-sub are shared, generic classes used on nearly every card in the
   app, so the color overrides are scoped to .bs-top-hero specifically
   rather than touching those classes themselves. */
.bs-top-hero{position:relative;overflow:hidden;}
/* .card.bs-top-hero (two classes), not just .bs-top-hero alone: the base
   .card{background:var(--card);...} rule is also a single-class selector
   defined later in the file, so at equal specificity source order would
   let it win and silently cancel this gradient — happened once already
   while building this. */
.card.bs-top-hero{
  background:linear-gradient(135deg, var(--navy-950), var(--navy-800));
  color:#fff;
}
.bs-top-hero .card-title{color:#fff;}
.bs-top-hero .card-sub{color:#c3ccdc;}
@media (prefers-reduced-motion: no-preference){
  /* One soft gold band travelling left-to-right (on request: was three
     bright/medium/soft stripes peaking at 85% gold every 6s). Now a single,
     wider, feathered band peaking at ~32% gold, sweeping more slowly (10s)
     so it reads as a gentle sheen rather than a flash. Same bsShineLTR
     background-position animation. */
  .bs-top-hero::before{
    content:'';
    position:absolute;inset:0;z-index:2;
    pointer-events:none;
    background:linear-gradient(100deg,
      transparent 34%,
      color-mix(in srgb, var(--gold-500) 10%, transparent) 40%,
      color-mix(in srgb, var(--gold-500) 24%, transparent) 45%,
      color-mix(in srgb, var(--gold-500) 32%, transparent) 48%,
      color-mix(in srgb, var(--gold-500) 24%, transparent) 51%,
      color-mix(in srgb, var(--gold-500) 10%, transparent) 56%,
      transparent 62%);
    background-size:260% 100%;
    background-repeat:no-repeat;
    background-position:180% 0;
    animation:bsShineLTR 10s ease-in-out infinite;
  }
  /* bdHeroShine (reused elsewhere — Business Details Hub, Bank Locker)
     actually sweeps right-to-left: increasing background-position on a
     background wider than its element moves the image's own left edge
     rightward, which slides whatever's inside it — including the
     highlight band — from the container's right edge toward its left.
     Left uncorrected there since those pages' look is already shipped;
     this dedicated keyframe (same easing/duration shape, positions
     simply swapped) is what actually gives the Balance Sheet elements a
     true left-to-right sweep. */
  @keyframes bsShineLTR{
    0%       {background-position:180% 0;}
    55%,100% {background-position:-80% 0;}
  }

  /* Assets/Liabilities cards: a slow, subtle Ken-Burns zoom on the
     background photo, plus a white/overlay-blend shine sweep — these two
     already carry their own accent tint (green/red overlay), so a
     neutral highlight reads as a glint crossing whichever color it's
     over instead of needing a second color choice. Staggered
     animation-delay so the two cards don't shine in lockstep. */
  .bs-col-bg{animation:bsColBgIn 1.3s ease-out both, bsColBgFloat 14s ease-in-out 1.3s infinite alternate;}
  @keyframes bsColBgIn{from{opacity:0;transform:scale(1.08);}to{opacity:1;transform:scale(1);}}
  @keyframes bsColBgFloat{from{transform:scale(1);}to{transform:scale(1.05);}}
  .bs-col-card::after{
    content:'';
    position:absolute;inset:0;z-index:2;
    pointer-events:none;
    background:linear-gradient(100deg,
      transparent 40%,
      rgba(255,255,255,.10) 47%,
      rgba(255,255,255,.28) 50%,
      rgba(255,255,255,.10) 53%,
      transparent 60%);
    background-size:260% 100%;
    background-repeat:no-repeat;
    background-position:180% 0;
    mix-blend-mode:overlay;
    animation:bsShineLTR 7s ease-in-out infinite;
  }
  .bs-col-card-liabilities::after{animation-delay:3.2s;}
}

/* Balance Sheet Redesign — two-column ASSETS | LIABILITIES layout with a
   full-width Net Worth footer. */
/* align-items:stretch (grid's default, stated explicitly here) - both
   column cards stretch to match the taller one's height instead of each
   sizing to its own content, so Total Assets/Total Liabilities end up
   the same size even when one category list is much longer than the
   other. */
.bs-columns{display:grid;grid-template-columns:1fr 1fr;gap:16px;align-items:stretch;}
@media (max-width:820px){ .bs-columns{grid-template-columns:1fr;} }
/* Assets/Liabilities cards — the supplied FBS_Assets_BG.png/FBS_Liable_BG.png
   (their own baked-in "Assets"/"Liabilities" title band cropped off before
   use as assets/fbs-*-bg.png, so it never duplicates .bs-col-title below)
   as a genuine full-cover background, same edge-to-edge video/overlay/
   content-layer technique as .bl-video-card (bankLocker.js) and
   .will-video-card (familyTree.js): .card's own padding is zeroed here so
   the image (position:absolute; inset:0) can fill the card's full padding
   box, and .bs-col-content puts that 18px 20px padding back so the real
   content sits inset exactly as an unmodified .card would.
   object-fit:cover scales/crops the image to the card's actual rendered
   size regardless of its own aspect ratio — this is what "adjusts the
   image to the card size" rather than a fixed image size that could
   under/overflow a taller or shorter card.
   The overlay is a strong tinted scrim (not the app's light/dark theme
   tokens — a fixed dark wash so this reads the same regardless of theme,
   same reasoning as .bl-video-overlay), needed because the image itself
   is fully visible/opaque now rather than a faded corner bleed: without
   it the category table's default text colors would be unreadable
   against the photo. Text inside is switched to white to match. */
.bs-col-card{position:relative;overflow:hidden;padding:0;}
/* The photo is deliberately low-contrast here (on request): dimmed and
   flattened so it reads as texture behind the table rather than artwork
   competing with it. The overlay below then only has to even it out. */
.bs-col-bg{
  position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;
  /* Dimming lives INSIDE filter, never as an opacity declaration: the
     entrance animation below (bsColBgIn, fill-mode both) sets opacity itself
     and overrides a plain opacity property outright - which is why the first
     pass at this looked unchanged. */
  filter:contrast(.66) saturate(.58) brightness(.9) opacity(.5);
}
.bs-col-overlay{position:absolute;inset:0;}
/* Top-heavy, not bottom-heavy: the source image's busiest content (a ring
   of small icon badges) sits right at the top of the cropped art, exactly
   where the table header + first rows need the most contrast, so the
   vignette darkens there and over the reflection at the very bottom,
   leaving the nicer mid-card "hero object" cluster the most visible part. */
.bs-col-card-assets .bs-col-overlay{background:linear-gradient(180deg, rgba(4,20,14,.91) 0%, rgba(4,20,14,.87) 45%, rgba(4,20,14,.91) 100%);}
.bs-col-card-liabilities .bs-col-overlay{background:linear-gradient(180deg, rgba(30,6,10,.91) 0%, rgba(30,6,10,.87) 45%, rgba(30,6,10,.91) 100%);}
.bs-col-content{
  position:relative;z-index:1;padding:18px 20px;
  display:flex;flex-direction:column;height:100%;color:#fff;
}
/* Category names/links, table borders and the muted "no records" empty
   row all default to theme tokens tuned for a plain card — every one
   needs a legible override against the photo background. .bs-total-row
   keeps its own solid green/red pill background untouched (opaque
   regardless of what's behind it), so only the plain text/table chrome
   needs overriding here. */
/* Uppercased, centered, and tinted to each card's own accent (a fixed
   vivid green/red, not the light/dark theme --green/--red tokens, so it
   reads the same regardless of theme against this fixed-color photo
   background — same reasoning as the fixed overlay colors above) rather
   than the plain left-aligned dark-text title a bare .card would get. */
.bs-col-content .bs-col-title{
  text-align:center;text-transform:uppercase;letter-spacing:.06em;
  font-size:17px;margin-bottom:14px;
}
.bs-col-card-assets .bs-col-content .bs-col-title{color:#3fbf85;text-shadow:0 0 14px rgba(63,191,133,.5);}
.bs-col-card-liabilities .bs-col-content .bs-col-title{color:#e2685a;text-shadow:0 0 14px rgba(226,104,90,.5);}
.bs-col-content table{color:#fff;}
.bs-col-content th{color:rgba(255,255,255,.7);border-bottom-color:rgba(255,255,255,.25);}
.bs-col-content td{border-bottom-color:rgba(255,255,255,.14);}
.bs-col-content tbody tr:hover{background:rgba(255,255,255,.08);}
.bs-col-content .link{color:#ffd76a;}
.bs-col-content .link:hover{color:#ffe6a3;}
.bs-col-content .empty-row td{color:rgba(255,255,255,.65);}
.bs-col-title{font-size:15px;font-weight:800;color:var(--text-900);margin-bottom:10px;}
.bs-total-row{
  display:flex;align-items:center;justify-content:space-between;
  /* auto pushes this row to the bottom of the stretched flex column
     above, rather than sitting immediately under a short table. */
  margin-top:auto;padding:12px 14px;border-radius:10px;
  font-size:14px;font-weight:800;
}
.bs-total-assets{background:var(--green-bg);color:var(--green);}
.bs-total-liabilities{background:var(--red-bg);color:var(--red);}
/* Centered label + amount, with an always-on ambient shine sweep plus
   twinkling star glyphs scattered around it — its own bs-networth-
   sparkle (star glyph, not a plain dot) rather than reusing the
   Dashboard's shared .stat-card-ambient-sparkle, so restyling this
   footer's glitter never touches the Wealth Dashboard's own Net Worth
   card. position:relative/overflow:hidden here is what lets the shimmer
   sweep (::before below) and the stars (positioned absolutely, placed
   around the amount in balanceSheet.js) stay clipped to this footer
   instead of spilling out of it. */
.bs-networth-footer{
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
  position:relative;overflow:hidden;
  background:var(--blue-bg);border:1px solid var(--blue);border-radius:12px;
  padding:20px 22px;
}
.bs-networth-footer::before{
  content:'';
  position:absolute;top:0;left:-60%;
  width:32%;height:100%;
  pointer-events:none;
  background:linear-gradient(75deg, transparent 0%, rgba(255,255,255,.32) 45%, rgba(255,255,255,.58) 50%, rgba(255,255,255,.32) 55%, transparent 100%);
  animation:stat-card-ambient-sweep 4.5s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce){
  .bs-networth-footer::before{animation:none;}
}
.bs-networth-center{position:relative;z-index:1;display:flex;flex-direction:column;align-items:center;gap:2px;}
.bs-networth-label{font-size:14px;font-weight:700;color:var(--blue);text-transform:uppercase;letter-spacing:.03em;}
.bs-networth-value{font-size:26px;font-weight:800;color:var(--blue);}
.bs-networth-sparkle{
  position:absolute;
  font-size:13px;line-height:1;
  color:#fff;
  text-shadow:0 0 6px #fff, 0 0 12px var(--blue);
  pointer-events:none;
  opacity:0;
  transform:translate(-50%,-50%) scale(.4) rotate(0deg);
  animation:bs-networth-star-twinkle 3.6s ease-in-out infinite;
}
@keyframes bs-networth-star-twinkle{
  0%,100%{opacity:0;transform:translate(-50%,-50%) scale(.4) rotate(0deg);}
  50%{opacity:1;transform:translate(-50%,-50%) scale(1.3) rotate(25deg);}
}
@media (prefers-reduced-motion: reduce){
  .bs-networth-sparkle{animation:none;display:none;}
}

/* ============================================================
   Balance Sheet Overview Charts — KPI cards, Assets vs Liabilities /
   Net Worth by Member bar charts, Asset/Liability Composition donuts,
   and the collapsible data table, everything below the Net Worth footer
   above. Own bs- prefixed classes throughout (not the app's existing
   generic .stat-card/.donut-chart/.hbar-chart, which have a different
   visual treatment and are shared by other pages) so this section can't
   collide with or accidentally restyle anything elsewhere. Tokens are
   the app's own real custom properties, so light/dark mode both follow
   with no extra rules here.
   ============================================================ */

/* ---------- KPI cards ----------
   A flat fill in the card's accent tint, edge to edge, matching the solid
   .stat-card treatment used on the Dashboard. */
.bs-kpi-card{
  background:var(--card);
  border:1px solid var(--border);border-top:3px solid var(--border);
  border-radius:var(--radius);padding:16px 18px;box-shadow:var(--shadow);
  position:relative;overflow:hidden;
  transition:box-shadow .2s ease-out, transform .3s cubic-bezier(0.16,1,0.3,1), opacity .3s cubic-bezier(0.16,1,0.3,1);
}
/* --kpi-accent is the card's own solid colour (the -bg tokens above are the
   pale wash); the bloom and sheen below tint themselves from it, so all four
   cards animate in their own colour with one set of rules. */
.bs-kpi-card.accent-green{background:var(--green-bg);border-top-color:var(--green-bg);--kpi-accent:var(--green);}
.bs-kpi-card.accent-red  {background:var(--red-bg);border-top-color:var(--red-bg);--kpi-accent:var(--red);}
.bs-kpi-card.accent-blue {background:var(--blue-bg);border-top-color:var(--blue-bg);--kpi-accent:var(--blue);}
.bs-kpi-card.accent-gold {background:var(--gold-bg);border-top-color:var(--gold-bg);--kpi-accent:var(--gold-500);}

/* Slow ambient bloom in the card's accent, bottom-left. Net Worth already had
   its own .bs-kpi-glow (a JS-rendered span, top-right); this gives the other
   three the same sense of being alive without duplicating that element. */
.bs-kpi-card::before{
  content:'';
  position:absolute;left:-20px;bottom:-24px;
  width:78px;height:78px;border-radius:50%;
  background:var(--kpi-accent,var(--blue));
  opacity:.10;filter:blur(16px);
  pointer-events:none;z-index:0;
  animation:bs-kpi-bloom 5.5s ease-in-out infinite alternate;
}
@keyframes bs-kpi-bloom{
  from{transform:scale(.85);opacity:.07;}
  to{transform:scale(1.25);opacity:.18;}
}
/* Sheen sweeping across on hover - same construction as stat-card-shine-sweep
   elsewhere in this file. Sits at z-index 0, under the label/value/foot which
   are already lifted to z-index 1. */
.bs-kpi-card::after{
  content:'';
  position:absolute;top:0;left:-60%;
  width:45%;height:100%;
  pointer-events:none;z-index:0;
  background:linear-gradient(75deg, transparent 0%, rgba(255,255,255,.35) 45%, rgba(255,255,255,.7) 50%, rgba(255,255,255,.35) 55%, transparent 100%);
  opacity:0;
}
@keyframes bs-kpi-sheen{
  0%{left:-60%;opacity:0;}
  15%{opacity:1;}
  70%{opacity:.9;}
  100%{left:130%;opacity:0;}
}
/* Oversized faded illustration bleeding off the bottom-right corner - purely
   decorative (aria-hidden in balanceSheet.js's renderKpis). The image is the
   card's own 3D icon; held well back in opacity so the label, figure and
   footnote all stay first in the reading order. */
.bs-kpi-art{
  position:absolute;right:-18px;bottom:-22px;width:132px;height:132px;
  opacity:.22;transform:rotate(-6deg);pointer-events:none;z-index:0;
  transition:opacity .3s ease-out, transform .45s cubic-bezier(0.16,1,0.3,1);
}
/* Dark mode has less headroom before the art starts competing with the text,
   so it sits a little further back there. */
:root[data-theme="dark"] .bs-kpi-art{opacity:.16;}
.bs-kpi-art img{width:100%;height:100%;display:block;object-fit:contain;}

/* Small solid badge above the label - the same illustration at full strength,
   on a card-coloured tile so it reads as an icon rather than more watermark. */
.bs-kpi-icon{
  position:relative;z-index:1;
  display:flex;align-items:center;justify-content:center;
  width:38px;height:38px;margin-bottom:10px;
  border-radius:10px;background:var(--card);
  border:1px solid color-mix(in srgb, var(--kpi-accent, var(--blue)) 22%, transparent);
  box-shadow:0 1px 3px rgba(0,0,0,.08);
  transition:transform .3s cubic-bezier(0.16,1,0.3,1);
}
.bs-kpi-icon img{width:26px;height:26px;display:block;object-fit:contain;}

/* ---------- KPI card click effect (BalanceSheetPage.onKpiCardClick) ----------
   The Wealth Dashboard's stat-card click feedback, reused here. The particle
   spans carry the dashboard's own .stat-card-ripple / -star / -glitter
   classes, and those selectors are not scoped to .stat-card, so the rules at
   "Stat card click feedback" further down drive both pages from one place.
   All this page has to add is --star-glow (what tints each star's halo and
   the glitter) and the flash below.

   The flash is what the white stars are read against: on a pale tint they
   would wash out, exactly as on the dashboard, where the same solid-accent
   flash is what gives them something to show up on. It lasts about 850ms
   and the card returns to its normal flat tint. */
.bs-kpi-card{--star-glow:var(--kpi-accent, var(--blue));}
/* On the dashboard these spans carry no z-index and simply paint last, which
   puts them over the label and figure. This card lifts its own text to z-index
   1 (to clear the ambient bloom and hover sheen at 0), so the particles have
   to be lifted past it to fly over the numbers the same way. */
.bs-kpi-card .stat-card-ripple,
.bs-kpi-card .stat-card-ripple-glow,
.bs-kpi-card .stat-card-star,
.bs-kpi-card .stat-card-star-glow,
.bs-kpi-card .stat-card-glitter,
.bs-kpi-card .bs-kpi-shine{z-index:2;}
.bs-kpi-card.bs-kpi-card-active{transition:background .2s ease-out;}
.bs-kpi-card.accent-green.bs-kpi-card-active{background:var(--green);}
.bs-kpi-card.accent-red.bs-kpi-card-active{background:var(--red);}
.bs-kpi-card.accent-blue.bs-kpi-card-active{background:var(--blue);}
.bs-kpi-card.accent-gold.bs-kpi-card-active{background:var(--gold-500);}
.bs-kpi-card.bs-kpi-card-active .bs-kpi-label,
.bs-kpi-card.bs-kpi-card-active .bs-kpi-foot{color:rgba(255,255,255,.85);}
.bs-kpi-card.bs-kpi-card-active .bs-kpi-value{color:#fff;}

/* Net Worth's extra shimmer sweep. The dashboard runs this off ::after, but
   both of this card's pseudo-elements are already taken (::before is the
   ambient bloom, ::after the hover sheen) and a hover sheen has always
   already fired by the time a click lands, so it cannot simply be
   re-triggered. A real span sidesteps both problems and still rides the
   dashboard's own stat-card-shine-sweep keyframes. */
.bs-kpi-shine{
  position:absolute;top:0;left:-60%;
  width:45%;height:100%;
  pointer-events:none;
  background:linear-gradient(75deg, transparent 0%, rgba(255,255,255,.55) 45%, rgba(255,255,255,.85) 50%, rgba(255,255,255,.55) 55%, transparent 100%);
  animation:stat-card-shine-sweep .9s ease-out;
}
@media (prefers-reduced-motion: reduce){
  .bs-kpi-shine{display:none;}
}
/* A slow bloom behind the Net Worth figure - the one card that gets an
   always-on ambient treatment, since it is the page's headline number. */
.bs-kpi-glow{
  position:absolute;top:14%;right:11%;width:54px;height:54px;border-radius:50%;
  background:var(--glow-hue,var(--blue));opacity:.2;filter:blur(15px);pointer-events:none;z-index:0;
  animation:bs-kpi-glow-pulse 4s ease-in-out infinite alternate;
}
@keyframes bs-kpi-glow-pulse{from{transform:scale(1);opacity:.14;}to{transform:scale(1.4);opacity:.30;}}
.bs-kpi-label,.bs-kpi-value,.bs-kpi-foot{position:relative;z-index:1;}
.bs-kpi-label{font-size:12px;color:var(--text-400);font-weight:600;text-transform:uppercase;letter-spacing:.04em;}
.bs-kpi-value{font-size:24px;font-weight:800;margin-top:6px;font-variant-numeric:tabular-nums;transition:transform .28s cubic-bezier(0.22,1,0.36,1);transform-origin:left center;}
.bs-kpi-value.green{color:var(--green);}
.bs-kpi-value.red{color:var(--red);}
.bs-kpi-value.blue{color:var(--blue);}
.bs-kpi-value.gold{color:var(--gold-600);}
.bs-kpi-foot{font-size:12.5px;color:var(--text-400);margin-top:4px;}
@media (hover:hover) and (pointer:fine){
  .bs-kpi-card:hover{
    box-shadow:var(--shadow-md), 0 0 22px color-mix(in srgb, var(--kpi-accent, var(--blue)) 20%, transparent);
    transform:translateY(-3px);
    border-top-color:var(--kpi-accent, var(--blue));
  }
  .bs-kpi-card:hover .bs-kpi-art{opacity:.34;transform:rotate(-6deg) scale(1.06);}
  .bs-kpi-card:hover .bs-kpi-icon{transform:translateY(-2px) scale(1.06);}
  .bs-kpi-card:hover .bs-kpi-value{transform:scale(1.04);}
  .bs-kpi-card:hover::after{animation:bs-kpi-sheen 1.1s ease-out;}
}

/* ---------- Horizontal bar charts (Assets vs Liabilities / Net Worth by Member) ---------- */
.bs-hbar-chart{display:flex;flex-direction:column;gap:12px;}
.bs-hbar-row{display:grid;grid-template-columns:150px 1fr 104px;align-items:center;gap:10px;position:relative;}
.bs-hbar-label{font-size:12.4px;font-weight:600;color:var(--text-600);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.bs-hbar-track{background:var(--subtle-bg);border-radius:6px;height:14px;overflow:hidden;transition:box-shadow .18s ease-out;}
/* 4px rounded data-end, anchored flush to the zero baseline on the left. */
.bs-hbar-fill{height:100%;border-radius:0 4px 4px 0;width:0;position:relative;overflow:hidden;transition:width .8s cubic-bezier(0.16,1,0.3,1), filter .18s ease-out;}
/* Highlight flowing along each bar, sharing the liability bars' keyframe.
   On the fill rather than the track here: these bars clamp to a 2% minimum so
   there is always a coloured run to travel, and a white sheen reads best on
   the saturated green/red/blue. */
.bs-hbar-fill::after{
  content:'';
  position:absolute;top:0;left:-60%;
  width:45%;height:100%;
  pointer-events:none;
  background:linear-gradient(75deg, transparent 0%, rgba(255,255,255,.34) 45%, rgba(255,255,255,.72) 50%, rgba(255,255,255,.34) 55%, transparent 100%);
  animation:progress-bar-sweep 2.6s ease-in-out infinite;
}
.bs-hbar-value{font-size:12.4px;font-weight:700;text-align:right;color:var(--text-900);font-variant-numeric:tabular-nums;}
.bs-hbar-value.neg{color:var(--red);}
/* A value that clamps to the 2% minimum is not drawn to scale, so it is
   marked rather than left to read as a genuine tiny positive bar. */
.bs-hbar-row.is-clamped .bs-hbar-track{background:repeating-linear-gradient(135deg,var(--subtle-bg) 0 6px,transparent 6px 12px);box-shadow:inset 0 0 0 1px var(--border);position:relative;}
/* A clamped bar's fill is pinned to 2% (~6px), so the sheen inside it has
   nowhere to travel and the row looked completely static - which is exactly
   what happens to "Mariappan E — Parent" at -58,000 against a 93.9L scale.
   For these rows the highlight runs along the full track instead, tinted with
   the row's own --bar-color so it still reads as that series (red for a
   negative net worth, not a generic grey). */
.bs-hbar-row.is-clamped .bs-hbar-fill::after{display:none;}
.bs-hbar-row.is-clamped .bs-hbar-track::after{
  content:'';
  position:absolute;top:0;left:-60%;
  width:45%;height:100%;
  pointer-events:none;
  background:linear-gradient(75deg,
    transparent 0%,
    color-mix(in srgb, var(--bar-color, var(--text-600)) 26%, transparent) 42%,
    color-mix(in srgb, var(--bar-color, var(--text-600)) 58%, transparent) 50%,
    color-mix(in srgb, var(--bar-color, var(--text-600)) 26%, transparent) 58%,
    transparent 100%);
  animation:progress-bar-sweep 2.6s ease-in-out infinite;
}
/* The 2% stub itself pulses, so the eye is drawn to where the value actually
   sits rather than only to the sweep crossing empty track. */
.bs-hbar-row.is-clamped .bs-hbar-fill{animation:bs-clamped-pulse 2.6s ease-in-out infinite;}
@keyframes bs-clamped-pulse{
  0%,100%{filter:brightness(1);box-shadow:none;}
  50%{filter:brightness(1.25);box-shadow:0 0 7px var(--bar-color, var(--text-600));}
}
@media (hover:hover) and (pointer:fine){
  .bs-hbar-row:hover .bs-hbar-fill{filter:brightness(1.08);}
  .bs-hbar-row:hover .bs-hbar-fill::after{animation-duration:1.1s;}
  .bs-hbar-row:hover .bs-hbar-track{box-shadow:0 0 0 1px var(--border);}
  .bs-hbar-row:hover .bs-hbar-value{transform:translateX(-2px);}
}
.bs-hbar-value{transition:transform .2s ease-out, color .2s ease-out;}

/* ---------- Composition donuts ---------- */
.bs-donut-wrap{display:flex;align-items:center;gap:22px;flex-wrap:wrap;min-width:0;}
.bs-donut-figure{position:relative;width:min(168px,100%);aspect-ratio:1/1;flex-shrink:1;min-width:112px;}
.bs-donut-svg{width:100%;height:100%;display:block;transform:rotate(-90deg);overflow:visible;transition:filter .3s ease-out;}
/* Pulsing ring + glow while the cursor is anywhere on the donut, reusing the
   donut-ripple keyframe the Wealth Dashboard's charts already use so both
   pages' pie charts react the same way. */
.bs-donut-figure::after{
  content:'';
  position:absolute;inset:0;border-radius:50%;
  border:2px solid var(--gold-500);
  opacity:0;pointer-events:none;
}
@media (hover:hover) and (pointer:fine){
  .bs-donut-wrap:hover .bs-donut-figure::after{animation:donut-ripple 1.4s ease-out infinite;}
  .bs-donut-wrap:hover .bs-donut-svg{filter:drop-shadow(0 0 5px rgba(200,153,46,.38)) saturate(1.06);}
}
.bs-donut-seg{
  fill:none;stroke-linecap:butt;cursor:pointer;
  transition:opacity .2s ease-out, stroke-width .2s ease-out, stroke-dasharray .8s cubic-bezier(0.16,1,0.3,1);
}
.bs-donut-figure.dimmed .bs-donut-seg{opacity:.26;}
.bs-donut-figure.dimmed .bs-donut-seg.is-active{opacity:1;stroke-width:26;}
.bs-donut-center{position:absolute;inset:22%;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;pointer-events:none;}
.bs-donut-center-value{font-size:15px;font-weight:800;color:var(--text-900);font-variant-numeric:tabular-nums;transition:opacity .12s ease-out;}
.bs-donut-center-label{font-size:9.5px;color:var(--text-400);text-transform:uppercase;letter-spacing:.03em;margin-top:3px;line-height:1.25;transition:opacity .12s ease-out;}
.bs-donut-legend{display:flex;flex-direction:column;gap:9px;font-size:12.6px;flex:1 1 150px;min-width:0;}
.bs-donut-legend-item{
  display:flex;align-items:center;gap:8px;min-width:0;
  border-radius:6px;padding:3px 6px;margin:-3px -6px;
  transition:background .18s ease-out, opacity .35s cubic-bezier(0.22,1,0.36,1), transform .35s cubic-bezier(0.22,1,0.36,1);
}
.bs-donut-legend-item:hover,.bs-donut-legend-item.is-active{background:var(--subtle-bg);}
.bs-donut-dot{width:9px;height:9px;border-radius:50%;flex-shrink:0;}
.bs-donut-legend-label{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-600);}
.bs-donut-legend-val{font-weight:700;color:var(--text-900);font-variant-numeric:tabular-nums;}
.bs-donut-legend-pct{color:var(--text-400);font-variant-numeric:tabular-nums;min-width:32px;text-align:right;}

/* ---------- Shared tooltip (bar charts + donuts) ---------- */
.bs-tip{
  position:fixed;z-index:50;pointer-events:none;
  background:var(--navy-700);color:#fff;border-radius:8px;padding:7px 10px;
  box-shadow:var(--shadow-md);font-size:11.5px;line-height:1.45;white-space:nowrap;
  opacity:0;transform:translate(-50%,-100%) translateY(4px);
  transition:opacity .15s ease-out, transform .15s ease-out;
}
/* .show is added by showTip() and removed by hideTip(), and that toggling was
   never the bug: this tooltip is a direct child of the page root, so
   initPageReveal() was sweeping it into the reveal and
   .page-reveal.page-reveal-in{opacity:1} then pinned it visible no matter what
   .show did. app.js now excludes position:fixed overlays from the reveal, so
   this rule is authoritative again. */
.bs-tip.show{opacity:1;transform:translate(-50%,-100%) translateY(-8px);}
.bs-tip-title{font-weight:700;margin-bottom:2px;}
.bs-tip-row{opacity:.85;font-variant-numeric:tabular-nums;}
.bs-tip-accent{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:6px;}

/* ---------- Collapsible data table ---------- */
/* Styled as a gold pill rather than a plain text toggle, so the control reads
   as an action and picks up the app's brand colour. Filled when open, so the
   button itself shows the table's state. */
.bs-data-details summary{
  cursor:pointer;font-size:12.8px;font-weight:700;
  color:var(--gold-600);
  background:linear-gradient(135deg, var(--gold-bg), var(--card));
  border:1px solid color-mix(in srgb, var(--gold-500) 38%, var(--border));
  border-radius:999px;
  padding:8px 16px;list-style:none;display:inline-flex;align-items:center;gap:7px;
  transition:background .2s ease-out, color .2s ease-out, border-color .2s ease-out,
             box-shadow .2s ease-out, transform .2s ease-out;
}
.bs-data-details summary::-webkit-details-marker{display:none;}
.bs-data-details summary::before{content:'▸';transition:transform .18s ease-out;display:inline-block;}
.bs-data-details[open] summary::before{transform:rotate(90deg);}
.bs-data-details summary:hover{
  background:var(--gold-500);color:#1a1305;
  border-color:var(--gold-500);
  box-shadow:0 5px 16px color-mix(in srgb, var(--gold-500) 38%, transparent);
  transform:translateY(-1px);
}
.bs-data-details[open] summary{
  background:var(--gold-500);color:#1a1305;border-color:var(--gold-500);
}
.bs-data-details summary:focus-visible{outline:2px solid var(--gold-500);outline-offset:2px;border-radius:4px;}
.bs-data-table{width:100%;border-collapse:collapse;margin-top:8px;background:var(--card);border:1px solid var(--border);border-radius:var(--radius);overflow:hidden;}
.bs-data-table th,.bs-data-table td{padding:9px 14px;text-align:left;font-size:13px;border-bottom:1px solid var(--border);}
/* Gold header + gold section rows, so the table reads as grouped at a glance
   instead of one flat grey block. */
.bs-data-table th{
  font-size:11.5px;text-transform:uppercase;letter-spacing:.04em;
  color:var(--gold-600);font-weight:700;
  background:linear-gradient(180deg, var(--gold-bg), var(--bg));
  border-bottom:2px solid color-mix(in srgb, var(--gold-500) 30%, var(--border));
}
.bs-data-table td+td,.bs-data-table th+th{text-align:right;font-variant-numeric:tabular-nums;}
.bs-data-table tr:last-child td{border-bottom:none;}
/* .table-group-row carries the same treatment out to ordinary app tables -
   Asset Management's All Assets tab groups its rows under one of these per
   category. Declared alongside .grp, and sharing its declarations, so the
   two cannot drift apart into two different-looking gold headings.
   !important on colour and alignment only: these sit inside .table-wrap
   tables whose own td rules (right-aligned numeric columns, cell colour)
   are more specific than a bare class and would otherwise win. */
.bs-data-table .grp td,
.table-group-row td{
  font-weight:800;text-align:left !important;
  color:var(--gold-600) !important;
  background:var(--gold-bg);
  box-shadow:inset 3px 0 0 var(--gold-500);
  text-transform:uppercase;letter-spacing:.03em;font-size:11.6px;
}
/* Tone modifiers, so a grouped table can head its sections in the colour its
   own page already speaks in: green on Asset Management, red on Obligations /
   Liabilities, matching those pages' heroes and pill strips (and the Balance
   Sheet's own green/red asset and liability columns). Gold above stays the
   default for anything that doesn't ask for a tone.
   Each tone moves all three of colour, ground and the inset edge bar
   together - leaving the bar gold under a green ground reads as an oversight
   rather than an accent. Both take their values from the same --green/--red
   tokens the pills and status chips use, so they re-theme in dark mode for
   free. */
.table-group-row-green td{
  color:var(--green) !important;
  background:var(--green-bg);
  box-shadow:inset 3px 0 0 var(--green);
}
.table-group-row-red td{
  color:var(--red) !important;
  background:var(--red-bg);
  box-shadow:inset 3px 0 0 var(--red);
}
/* A highlight sweeping along the heading text, on the same nav-group-shimmer
   loop the sidebar section labels run - one shine vocabulary across the app.
   It rides on the label's own <span> (see groupedRowsHtml in assets.js /
   liabilities.js), not on the <td>: background-clip:text clips the whole of
   an element's background to its glyphs, so putting this on the cell would
   have clipped away the tinted ground the heading sits on.
   Unlike the greeting headings, the band is built from the tone itself
   rather than running up to white - these sit on a pale green/red ground,
   and a white peak would wash the word out against it just as it passed. */
.table-group-shine{
  display:inline-block;
  background-size:220% auto;
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
  animation:nav-group-shimmer 3.2s linear infinite;
}
.table-group-row-green .table-group-shine{
  background-image:linear-gradient(90deg,
    color-mix(in srgb, var(--green) 72%, #000) 0%,
    var(--green) 24%,
    color-mix(in srgb, var(--green) 68%, #fff) 50%,
    var(--green) 76%,
    color-mix(in srgb, var(--green) 72%, #000) 100%);
}
.table-group-row-red .table-group-shine{
  background-image:linear-gradient(90deg,
    color-mix(in srgb, var(--red) 72%, #000) 0%,
    var(--red) 24%,
    color-mix(in srgb, var(--red) 68%, #fff) 50%,
    var(--red) 76%,
    color-mix(in srgb, var(--red) 72%, #000) 100%);
}
@media (prefers-reduced-motion: reduce){
  /* Sweep stops, colour stays - same treatment .nav-group-title gets. */
  .table-group-shine{animation:none;background-position:35% center;}
}
.bs-data-table tbody tr:not(.grp) td{transition:background-color .16s ease-out;}
@media (hover:hover) and (pointer:fine){
  .bs-data-table tbody tr:not(.grp):hover td{background:var(--subtle-bg);}
}
/* The table slides in when the details opens. */
.bs-data-details[open] .bs-data-table{animation:bs-table-in .38s cubic-bezier(0.22,1,0.36,1) both;}
@keyframes bs-table-in{
  0%{opacity:0;transform:translateY(-7px);}
  100%{opacity:1;transform:translateY(0);}
}

.bs-chart-empty{color:var(--text-400);font-size:13px;padding:26px 0;text-align:center;}

@media (prefers-reduced-motion: reduce){
  .bs-kpi-card:hover{transform:none;}
  .bs-kpi-glow{animation:none;opacity:.18;}
  .bs-kpi-card::before{animation:none;opacity:.12;}
  .bs-kpi-card:hover::after{animation:none;}
  .bs-kpi-card:hover .bs-kpi-value{transform:none;}
  .bs-kpi-card:hover .bs-kpi-icon{transform:none;}
  .bs-hbar-fill::after{animation:none;display:none;}
  .bs-hbar-row.is-clamped .bs-hbar-track::after{animation:none;display:none;}
  .bs-hbar-row.is-clamped .bs-hbar-fill{animation:none;}
  .bs-donut-wrap:hover .bs-donut-figure::after{animation:none;}
  .bs-data-details summary:hover{transform:none;}
  .bs-data-details[open] .bs-data-table{animation:none;}
  .bs-hbar-row:hover .bs-hbar-value{transform:none;}
}

/* Smart Alerts page - same "everything in the new serif style" treatment,
   scoped to .alerts-page. dash-plain-num (opt-in, set per-item in
   alerts.js when its .desc text actually contains a date/day-count) keeps
   those specific alert descriptions in plain Inter, same carve-out as the
   Wealth Dashboard's Upcoming Reminders and the High/Medium/Low counts. */
.alerts-page, .alerts-page *{
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif !important;
  font-style:normal !important;
}
.alerts-page .stat-value,
.alerts-page .dash-plain-num{
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif !important;
  font-style:normal !important;
}
/* Re-tuned for plain Inter, same reasoning as the Wealth Dashboard block
   above - a compact, weight-driven scale instead of the earlier +2px
   stack sized for the italic serif treatment this page used to carry. */
.alerts-page .card-title{font-size:16px !important;}
.alerts-page .card-sub{font-size:13px !important;}
.alerts-page .stat-label{font-size:12px !important;}
.alerts-page .stat-value{font-size:23px !important;}
.alerts-page .alert-item .icon{font-size:17px !important;}
.alerts-page .alert-item .title{font-size:13.5px !important;}
.alerts-page .alert-item .desc{font-size:12.5px !important;}
.alerts-page .pill{font-size:11.5px !important;}
.alerts-page{font-size:14.5px;}

/* Travel Kit page - same "everything in the new serif style" treatment,
   scoped to .travel-kit-page (hero filter/Add button, all 4 doc cards,
   the Travel Tickets boarding-pass card, and the Required Assets/Photos
   sub-cards). dash-plain-num (opt-in, set per-value in travelKit.js) keeps
   document numbers, dates, flight times, PNRs and record counts in plain
   Inter, so only the alphabetic labels/titles get the serif treatment. */
.travel-kit-page, .travel-kit-page *{
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif !important;
  font-style:normal !important;
}
.travel-kit-page .dash-plain-num{
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif !important;
  font-style:normal !important;
}
/* The Add/Edit Document modal (openModal()) renders into the app's shared
   #modal, outside #content - so it sits outside .travel-kit-page and its
   blanket rule above never reaches it. :has() scopes the same serif
   style (and the same date/dash-plain-num carve-out, e.g. the View All
   modal's reused docRowHtml rows) to the modal only while the Travel Kit
   page is the page currently mounted, so this shared modal keeps its
   plain look on every other page's Add/Edit forms. */
body:has(.travel-kit-page) .modal,
body:has(.travel-kit-page) .modal *{
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif !important;
  font-style:normal !important;
}
body:has(.travel-kit-page) .modal input[type=date],
body:has(.travel-kit-page) .modal .date-input-display,
body:has(.travel-kit-page) .modal .dash-plain-num{
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif !important;
  font-style:normal !important;
}

a{color:inherit;}


/* ---------- Layout ---------- */
.app-shell{
  display:flex;
  min-height:100vh;
}

.sidebar{
  width:250px;
  flex-shrink:0;
  background:linear-gradient(180deg,var(--navy-950),var(--navy-925));
  color:#fff;
  display:flex;
  flex-direction:column;
  position:sticky;
  top:0;
  height:100vh;
  overflow-y:auto;
  z-index:20;
  transition:width .28s cubic-bezier(0.16,1,0.3,1);
  /* Scrolling stays available as a fallback (e.g. a very short viewport
     with the Security section's 5 items open) but the visual scrollbar
     track/thumb is hidden - the sidebar accordion normally keeps only one
     section's items on screen at a time, short enough that a visible
     scrollbar was just clutter rather than a real affordance. */
  scrollbar-width:none;
  -ms-overflow-style:none;
}
.sidebar::-webkit-scrollbar{display:none;}
.brand{
  position:relative;
  display:flex;
  flex-direction:column;
  align-items:center;
  gap:8px;
  padding:18px 14px 16px;
  border-bottom:1px solid rgba(255,255,255,.08);
  background:#0a1220;
}
.brand-mark{width:84px;height:84px;object-fit:contain;flex-shrink:0;border-radius:6px;transition:width .18s ease,height .18s ease;}
.brand-words{width:100%;max-width:216px;height:auto;object-fit:contain;}

/* ---------- Logo shine ----------
   A highlight sweeps across the logo, glinting off the gold while leaving the
   navy behind it alone.

   Done with mix-blend-mode, NOT an alpha mask: every logo PNG here is fully
   opaque - each has the panel's own #0a1220 navy baked in, 0% transparent -
   so masking the band with the artwork just produced a plain rectangle.
   `overlay` keys off the backdrop's brightness instead: it lifts the gold
   strokes strongly and near-black navy barely at all, so the sweep traces the
   logo's own shape with no alpha channel needed.

   isolation:isolate confines the blend to the wrapper, so the band blends
   with its own logo rather than with the sidebar behind it. An <img> cannot
   host a ::after, which is why the wrapper exists at all. */
.logo-shine{position:relative;display:inline-block;line-height:0;max-width:100%;isolation:isolate;}
.logo-shine::after{
  content:'';
  position:absolute;inset:0;
  pointer-events:none;
  background:linear-gradient(100deg,
    transparent 40%,
    rgba(255,255,255,.45) 47%,
    rgba(255,255,255,.98) 50%,
    rgba(255,255,255,.45) 53%,
    transparent 60%);
  background-size:260% 100%;
  background-repeat:no-repeat;
  background-position:-80% 0;
  mix-blend-mode:overlay;
  animation:logo-shine-sweep 5s ease-in-out infinite;
}
/* Travels for the first half of the cycle, then rests - a periodic glint
   rather than a constant strobe. */
@keyframes logo-shine-sweep{
  0%       {background-position:-80% 0;}
  55%,100% {background-position:180% 0;}
}
.shine-words::after{animation-delay:.45s;}   /* wordmark glints just after the emblem */
/* The wordmark image is width:100%, so its wrapper needs the width the image
   used to take from .brand directly. */
.brand .shine-words{display:block;width:100%;max-width:216px;margin:0 auto;}
@media (prefers-reduced-motion: reduce){
  .logo-shine::after{animation:none;opacity:0;}
}
/* ---------- Collapsed sidebar (icon-only rail) ----------
   Toggled via toggleSidebarCollapse() in app.js, from the topbar's
   navigator icon — clicking it again reveals the full sidebar.
   Preference persists in localStorage (see SIDEBAR_COLLAPSE_KEY) like the
   theme toggle already does. Below the mobile breakpoint this class is
   never applied — the existing .sidebar.open drawer pattern is used
   instead (see toggleSidebarCollapse()). Each nav item's own `title`
   attribute (set in buildNav(), app.js) gives the page name as a native
   hover tooltip once the label text itself is hidden here. */
.app-shell.sidebar-collapsed .sidebar{width:72px;}
.app-shell.sidebar-collapsed .brand{padding:18px 6px 14px;}
/* Hide the wrapper, not just the image - otherwise the shine highlight would
   still be painted over an empty box in the collapsed rail. */
.app-shell.sidebar-collapsed .brand-words,
.app-shell.sidebar-collapsed .shine-words{display:none;}
.app-shell.sidebar-collapsed .brand-mark{width:38px;height:38px;}
.app-shell.sidebar-collapsed .nav-group-title{display:none;}
.app-shell.sidebar-collapsed .nav-item{justify-content:center;padding:9px;gap:0;}
.app-shell.sidebar-collapsed .nav-item span:not(.icon){opacity:0;max-width:0;}
.app-shell.sidebar-collapsed .sidebar-footer{display:none;}

.nav{padding:10px 10px;flex:1;}
/* Sidebar section labels ("OVERVIEW", "WEALTH TRACKING", "ESTATE & LEGACY",
   "FAMILY CAPITAL", "ALERTS & INTELLIGENCE", "SECURITY") - sized to 2x the
   nav items' own text size for strong visual separation between sections.
   Gold gradient + animated background-position is the standard CSS
   "shimmer" technique - a metallic sweep drifts across the gold gradient
   text continuously, reusing the same --gold-600/--gold-400 tokens as the
   rest of the app's gold accents instead of a plain flat gold fill.
   NOTE: this used to be two separate .nav-group-title{} rule blocks in
   this file (this one, plus a second with the size/color further up) -
   the later one silently won the cascade and wiped out the font-size/
   color from the earlier one every time, which is exactly why size/color
   changes kept appearing to "not take". Keep this the ONLY block that
   sets font-size/color/background for .nav-group-title. */
.nav-group-title{
  text-transform:uppercase;
  letter-spacing:.08em;
  padding:8px 10px 4px;
  font-size:12px;
  font-weight:700;
  background:linear-gradient(90deg, var(--gold-600) 0%, var(--gold-400) 22%, #fff6d8 50%, var(--gold-400) 78%, var(--gold-600) 100%);
  background-size:220% auto;
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
  animation:nav-group-shimmer 3.2s linear infinite;
  cursor:pointer;user-select:none;
}
@keyframes nav-group-shimmer{
  0%{background-position:0% center;}
  100%{background-position:220% center;}
}
@media (prefers-reduced-motion: reduce){
  .nav-group-title{animation:none;background-position:35% center;}
}
/* Every group's items are always visible now - "closed" is a small,
   muted rendering (see .nav-group-items:not(.nav-group-open) below), not
   a hidden one, so there's no height to collapse/expand here anymore. */
.nav-group-items{
  display:flex;
  flex-direction:column;
}
.nav-item{
  display:flex;
  align-items:center;
  gap:10px;
  padding:7px 10px;
  border-radius:9px;
  color:#b9c3da;
  cursor:pointer;
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  font-style:normal;
  font-size:13.4px;
  font-weight:600;
  margin:1px 0;
  text-decoration:none;
  transition:background .15s ease, color .15s ease, transform .15s ease, box-shadow .2s ease, padding .2s ease, gap .2s ease;
}
.nav-item span:not(.icon){
  overflow:hidden;white-space:nowrap;text-overflow:ellipsis;
  max-width:200px;opacity:1;
  transition:opacity .15s ease, max-width .22s ease, font-size .2s ease, color .2s ease;
}
.nav-item .icon{
  width:33px;height:33px;flex-shrink:0;
  display:flex;align-items:center;justify-content:center;
  font-size:17px;border-radius:7px;
  background:rgba(255,255,255,.05);
  transition:background .15s ease, transform .15s ease, width .2s ease, height .2s ease;
}
.nav-item .icon img{
  width:26px;height:26px;object-fit:contain;
  border-radius:4px;
  transition:opacity .15s ease;
}
/* ---------- Closed-group compact rendering ----------
   Any group that isn't the currently-open one (NAV_EXPANDED_GROUP, see
   app.js) renders its items small instead of hiding them: a muted dot in
   place of the icon (the real icon/emoji stays in the DOM, just visually
   hidden via font-size:0/hidden <img> - no markup difference from the
   open state), smaller muted label text, tighter spacing. Guarded against
   the separate icon-only rail (.sidebar-collapsed) which already shows
   every item uniformly and has nothing to do with this accordion. */
.app-shell:not(.sidebar-collapsed) .nav-group-items:not(.nav-group-open) .nav-item{
  padding:3px 10px 3px 18px;
  gap:9px;
  margin:0;
  background:none;
  box-shadow:none;
  font-weight:500;
}
.app-shell:not(.sidebar-collapsed) .nav-group-items:not(.nav-group-open) .nav-item .icon{
  width:5px;height:5px;
  border-radius:50%;
  background:rgba(255,255,255,.25);
  font-size:0;
  overflow:hidden;
}
.app-shell:not(.sidebar-collapsed) .nav-group-items:not(.nav-group-open) .nav-item .icon img{opacity:0;}
.app-shell:not(.sidebar-collapsed) .nav-group-items:not(.nav-group-open) .nav-item span:not(.icon){
  font-size:12.3px;
  color:#8b96a7;
}
.app-shell:not(.sidebar-collapsed) .nav-group-items:not(.nav-group-open) .nav-item .count{display:none;}
.app-shell:not(.sidebar-collapsed) .nav-group-items:not(.nav-group-open) .nav-item.active{
  background:none;
  box-shadow:none;
}
.app-shell:not(.sidebar-collapsed) .nav-group-items:not(.nav-group-open) .nav-item.active .icon{background:var(--gold-500);}
.app-shell:not(.sidebar-collapsed) .nav-group-items:not(.nav-group-open) .nav-item.active span:not(.icon){color:var(--gold-400);font-weight:600;}
@media (hover:hover) and (pointer:fine){
  .nav-item:hover{background:rgba(255,255,255,.07);color:#fff;transform:translateX(2px);}
  .nav-item:hover .icon{background:rgba(255,255,255,.1);transform:scale(1.08);}
}
.nav-item.active{
  background:linear-gradient(90deg, rgba(200,153,46,.16), rgba(200,153,46,.05));
  color:var(--gold-400);
  font-weight:700;
  box-shadow:inset 3px 0 0 var(--gold-500);
}
.nav-item.active .icon{background:rgba(200,153,46,.22);transform:scale(1.04);}
.nav-item .count{
  margin-left:auto;
  background:rgba(255,255,255,.13);
  border-radius:20px;
  padding:1px 7px;
  font-size:10.8px;
  font-weight:700;
}
.nav-item.active .count{background:rgba(200,153,46,.25);color:var(--gold-400);}
.nav-group-title{position:relative;}
.nav-group-title::after{
  content:'';
  display:block;
  height:1px;
  margin-top:5px;
  background:linear-gradient(90deg, rgba(255,255,255,.08), transparent);
}

.sidebar-footer{
  padding:14px;
  border-top:1px solid rgba(255,255,255,.08);
}
.version{font-size:10.5px;color:#69759a;text-align:center;}

.main{flex:1;min-width:0;display:flex;flex-direction:column;}

.topbar{
  display:flex;
  align-items:center;
  gap:14px;
  padding:12px 24px;
  background:linear-gradient(180deg,var(--navy-950),var(--navy-925));
  border-bottom:1px solid rgba(255,255,255,.08);
  position:sticky;
  top:0;
  z-index:10;
}
.hamburger{
  display:none;
  background:none;border:none;font-size:20px;cursor:pointer;color:#e7ecf6;
}
.topbar-nav-toggle{
  display:flex;align-items:center;justify-content:center;flex-shrink:0;
  width:34px;height:34px;border-radius:8px;
  background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.12);
  color:#e7ecf6;font-size:15px;cursor:pointer;
  transition:background .12s ease;
}
.topbar-nav-toggle:hover{background:rgba(255,255,255,.14);}
.topbar-title{font-size:16px;font-weight:700;color:#fff;flex-shrink:0;}
.topbar-search{
  position:relative;
  display:flex;align-items:center;gap:8px;
  flex:1;max-width:380px;min-width:0;
  transition:border-color .18s ease-out, box-shadow .18s ease-out;
  background:rgba(255,255,255,.07);
  border:1px solid rgba(255,255,255,.12);
  border-radius:10px;
  padding:8px 12px;
  margin-left:12px;
}
.topbar-search-icon{font-size:12.5px;opacity:.65;flex-shrink:0;}
.topbar-search input{
  flex:1;min-width:0;background:none;border:none;outline:none;
  color:#fff;font-size:15px;
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  font-style:normal;
  font-weight:500;
}
.topbar-search input::placeholder{color:rgba(255,255,255,.5);font-family:inherit;font-style:inherit;}
.topbar-search:focus-within{border-color:var(--gold-500);box-shadow:0 0 0 3px rgba(200,153,46,.18);}
.topbar-search-results{
  display:none;
  position:absolute;top:calc(100% + 8px);left:0;right:0;
  background:var(--card);border:1px solid var(--border);border-radius:12px;
  box-shadow:var(--shadow-md);
  max-height:60vh;overflow-y:auto;
  z-index:60;
  padding:8px;
}
.topbar-search-results.open{display:block;animation:pop .18s cubic-bezier(0.4,0,0.2,1);}
.gsr-group-label{font-size:10.2px;font-weight:700;text-transform:uppercase;letter-spacing:.05em;color:var(--text-400);padding:8px 10px 4px;}
.gsr-item{
  display:flex;align-items:center;gap:10px;
  padding:8px 10px;border-radius:8px;cursor:pointer;
  color:var(--text-900);font-size:13px;
}
.gsr-item:hover,.gsr-item.active{background:var(--subtle-bg);}
.gsr-item-icon{width:20px;text-align:center;flex-shrink:0;font-size:14px;}
.gsr-item-sub{font-size:11.2px;color:var(--text-400);margin-left:auto;flex-shrink:0;padding-left:8px;}
.gsr-empty{padding:16px;text-align:center;color:var(--text-400);font-size:12.6px;}
.topbar-actions{margin-left:auto;display:flex;align-items:center;gap:10px;position:relative;flex-shrink:0;}
.badge{
  position:relative;
  background:rgba(255,255,255,.07);
  border:1px solid rgba(255,255,255,.12);
  color:#e7ecf6;
  width:34px;height:34px;border-radius:50%;flex-shrink:0;
  display:flex;align-items:center;justify-content:center;
  font-size:14.5px;
  cursor:pointer;
  transition:background .12s ease;
}
.badge:hover{background:rgba(255,255,255,.14);}
.badge-count{
  position:absolute;top:-2px;right:-2px;
  background:var(--red);color:#fff;
  border-radius:20px;min-width:16px;height:16px;
  font-size:9.2px;font-weight:800;
  display:flex;align-items:center;justify-content:center;
  padding:0 3px;
  border:2px solid var(--navy-950);
  animation:badge-pulse 2.4s ease-in-out infinite;
}
/* A slow, subtle "still here" pulse on the bell's unread count — not a
   blink, just a gentle breathing scale so it never reads as an alarm. */
@keyframes badge-pulse{
  0%,100%{transform:scale(1);}
  50%{transform:scale(1.12);}
}
/* The bell grows, then holds that larger size while it shakes for a full 2s
   (0%-40% of this 5s cycle), then settles back down and rests for the
   remainder - only ever visible while #alertBadge is shown (display:none
   whenever there are zero alerts, per refreshAlertBadge()), so this only
   plays when there's actually something to notice. Scale and rotate are
   combined in every keyframe of ONE animation (rather than two separate
   animations both targeting `transform`) because when two animations
   compete for the same property, one silently overrides the other each
   frame instead of combining - only a single keyframe set that states both
   together keeps the bell big for the same 2s it's swinging. Placement/
   position of #alertBadge itself is untouched - only the bell's own
   size+angle change. Percentages are double their old (10s-cycle) values so
   the 2s active window's real-time speed is unchanged; only the 5s-vs-10s
   gap between bursts got shorter. */
.badge-bell{display:inline-block;transform-origin:50% 15%;animation:bell-shake-grow 5s ease-in-out infinite;}
@keyframes bell-shake-grow{
  0%,100%{transform:scale(1) rotate(0deg);}
  4%{transform:scale(1.35) rotate(16deg);}
  8%{transform:scale(1.35) rotate(-14deg);}
  12%{transform:scale(1.35) rotate(11deg);}
  16%{transform:scale(1.35) rotate(-8deg);}
  20%{transform:scale(1.35) rotate(5deg);}
  24%{transform:scale(1.35) rotate(-3deg);}
  28%{transform:scale(1.35) rotate(2deg);}
  32%{transform:scale(1.35) rotate(-1deg);}
  36%{transform:scale(1.35) rotate(0deg);}
  40%{transform:scale(1) rotate(0deg);}
}
@media (prefers-reduced-motion: reduce){
  .badge-count{animation:none;}
  .badge-bell{animation:none;}
}
.topbar .theme-toggle-btn{background:rgba(255,255,255,.07);border-color:rgba(255,255,255,.12);color:#e7ecf6;}
.topbar .theme-toggle-btn:hover{background:rgba(255,255,255,.14);}
@media(max-width:880px){
  .topbar-nav-toggle,.topbar-search{display:none;}
}

.content{
  padding:24px;
  max-width:1280px;
  width:100%;
  margin:0 auto;
}

/* ---------- Cards / Grid ---------- */
.card{
  background:var(--card);
  border:1px solid var(--border);
  border-radius:var(--radius);
  box-shadow:var(--shadow);
  padding:18px 20px;
}
.card + .card{margin-top:16px;}
/* ...but only for cards genuinely stacked in normal flow. Inside a grid the
   container's own gap already does the spacing, and this margin instead
   pushed every card after the first DOWN by 16px - and because grid items
   stretch, that 16px came straight off the card's height, so the second card
   in a row ended up both offset and shorter than the first (e.g. the Balance
   Sheet's Liabilities card at 575px against Assets' 591px, with a strip of
   page background showing above it).
   This was previously patched one class at a time - see .dashboard-chart-card
   and .dashboard-profile-card below - which left every other grid unfixed.
   Handling it by container catches them all, those two included. */
.grid > .card + .card,
.bs-columns > .card + .card{margin-top:0;}
.card-header{
  display:flex;
  align-items:center;
  gap:10px;
  margin-bottom:14px;
}
.card-title{font-size:15px;font-weight:700;color:var(--text-900);}
.card-sub{font-size:12.5px;color:var(--text-400);margin-top:2px;}
.card-header .spacer{flex:1;}
/* Wealth Dashboard card accent system - a semantic colored top border per
   card (dashboard.js assigns one card-accent-* class per section), the
   same idea as the 4 KPI cards' own accent-* treatment further up this
   file, extended to every other card on that page. One shared --accent
   custom property per color class means the border rule and the hover
   glow below are each written once instead of 8 times over; color-mix()
   in the hover rule reads whichever value var(--green)/var(--blue)/etc
   currently resolves to, so both are already dark/light-mode-correct for
   free with no separate [data-theme="dark"] override needed here. */
.dashboard-page .card-accent-green{--accent:var(--green);}
.dashboard-page .card-accent-red{--accent:var(--red);}
.dashboard-page .card-accent-blue{--accent:var(--blue);}
.dashboard-page .card-accent-gold{--accent:var(--gold-500);}
.dashboard-page .card-accent-purple{--accent:var(--purple);}
.dashboard-page .card-accent-teal{--accent:var(--teal);}
.dashboard-page .card-accent-pink{--accent:var(--pink);}
.dashboard-page .card-accent-amber{--accent:var(--amber);}
.dashboard-page [class*="card-accent-"]{border-top:3px solid var(--accent);}

/* Wealth Dashboard per-card animation styles - dashboard.js pairs one
   card-anim-* class with each card-accent-* above, so every section has
   its own distinct hover motion instead of all 15 sharing one identical
   lift. These only ever touch transform/box-shadow/pseudo-elements, so
   they layer on top of (and where a rule needs to win outright, override
   via the same 4-class specificity trick) the shared
   [class*="card-accent-"]:hover lift+glow already defined in the hover
   media block further down - none of them replace that glow, only add
   or restyle the motion. Every color reference is var(--accent)/
   color-mix(), so all of these are already dark/light-mode-correct with
   no separate override block. */
@keyframes card-anim-bounce-kf{
  0%{transform:translateY(0);}
  30%{transform:translateY(-7px);}
  55%{transform:translateY(-1px);}
  75%{transform:translateY(-4px);}
  100%{transform:translateY(-2px);}
}
@keyframes card-anim-wiggle-kf{
  0%{transform:rotate(0) translateY(0);}
  20%{transform:rotate(-1.2deg) translateY(-2px);}
  40%{transform:rotate(1.2deg) translateY(-2px);}
  60%{transform:rotate(-.8deg) translateY(-2px);}
  80%{transform:rotate(.6deg) translateY(-2px);}
  100%{transform:rotate(0) translateY(-2px);}
}
@keyframes card-anim-shake-kf{
  0%{transform:translateX(0) translateY(-2px);}
  20%{transform:translateX(-4px) translateY(-2px);}
  40%{transform:translateX(4px) translateY(-2px);}
  60%{transform:translateX(-3px) translateY(-2px);}
  80%{transform:translateX(3px) translateY(-2px);}
  100%{transform:translateX(0) translateY(-2px);}
}
@keyframes card-anim-grow-kf{
  0%{transform:scale(.97) translateY(0);}
  100%{transform:scale(1) translateY(-2px);}
}
@keyframes card-anim-pulse-kf{
  0%,100%{box-shadow:0 10px 24px color-mix(in srgb, var(--accent) 20%, transparent), var(--shadow-md);}
  50%{box-shadow:0 12px 30px color-mix(in srgb, var(--accent) 45%, transparent), var(--shadow-md);}
}
@keyframes card-anim-shimmer-kf{
  0%{left:-60%;opacity:0;}
  15%{opacity:1;}
  70%{opacity:.9;}
  100%{left:115%;opacity:0;}
}
.dashboard-page [class*="card-accent-"].card-anim-shimmer{position:relative;overflow:hidden;}
.dashboard-page [class*="card-accent-"].card-anim-shimmer::before{
  content:'';position:absolute;top:0;left:-60%;width:35%;height:100%;
  pointer-events:none;
  background:linear-gradient(75deg, transparent 0%, color-mix(in srgb, var(--accent) 35%, transparent) 45%, color-mix(in srgb, var(--accent) 55%, transparent) 50%, color-mix(in srgb, var(--accent) 35%, transparent) 55%, transparent 100%);
  opacity:0;
}
.dashboard-page [class*="card-accent-"].card-anim-underline .card-title{position:relative;display:inline-block;}
.dashboard-page [class*="card-accent-"].card-anim-underline .card-title::after{
  content:'';position:absolute;left:0;bottom:-3px;height:2px;width:0;
  background:var(--accent);
  transition:width .3s ease-out;
}
@media (prefers-reduced-motion: reduce){
  .dashboard-page [class*="card-accent-"][class*="card-anim-"]:hover{animation:none !important;transform:none !important;}
  .dashboard-page [class*="card-accent-"].card-anim-underline .card-title::after{transition:none;}
}

/* Overall Changes_v1 §2 — plain `1fr` tracks still size to at least each
   grid item's min-content width, so a wide item (e.g. a donut chart card
   with a fixed-min-width legend) forces the whole grid wider than its
   container instead of shrinking — the classic cause of horizontal page
   overflow at high browser zoom (zoom shrinks the effective CSS viewport
   width, same as narrowing the window). minmax(0,1fr) lets tracks shrink
   below content size instead, so overflow is handled inside the card
   (wrapping/scrolling) rather than blowing out the grid. Identical to 1fr
   whenever content already fits, so this never changes normal layout. */
.grid{display:grid;gap:16px;}
.grid-2{grid-template-columns:minmax(0,1fr) minmax(0,1fr);}
.grid-3{grid-template-columns:repeat(3,minmax(0,1fr));}
.grid-4{grid-template-columns:repeat(4,minmax(0,1fr));}
.grid-6{grid-template-columns:repeat(6,minmax(0,1fr));}
@media(max-width:980px){.grid-4{grid-template-columns:repeat(2,minmax(0,1fr));} .grid-3{grid-template-columns:repeat(2,minmax(0,1fr));} .grid-2{grid-template-columns:1fr;} .grid-6{grid-template-columns:repeat(3,minmax(0,1fr));}}
@media(max-width:640px){.grid-4,.grid-3{grid-template-columns:1fr;} .grid-6{grid-template-columns:repeat(2,minmax(0,1fr));}}

.stat-card{
  background:var(--card);
  border:1px solid var(--border);
  border-top:3px solid var(--border);
  border-radius:var(--radius);
  padding:16px 18px;
  box-shadow:var(--shadow);
  position:relative;overflow:hidden;
}
/* Tinted fill (Lightmode.jpeg's KPI card treatment) instead of a plain
   white card with only a colored top stripe - border-top now just
   matches the tint itself so the card reads as one soft pastel surface
   rather than a white box with a stripe. Uses the same --*-bg tokens
   already used for pills/avatars elsewhere, so dark mode (which
   redefines them to dark-safe values) needs no separate handling here. */
.stat-card.accent-green{background:var(--green-bg);border-top-color:var(--green-bg);}
.stat-card.accent-red{background:var(--red-bg);border-top-color:var(--red-bg);}
.stat-card.accent-blue{background:var(--blue-bg);border-top-color:var(--blue-bg);}
.stat-card.accent-gold{background:var(--gold-bg);border-top-color:var(--gold-bg);}
.stat-card.accent-purple{background:var(--purple-bg);border-top-color:var(--purple-bg);}
.stat-card.accent-lavender{background:var(--lavender-bg);border-top-color:var(--lavender-bg);}
/* AI Usage redesign's "Tokens Out" card - the one accent color this set
   didn't have yet (--pink/--pink-bg already exist, used elsewhere for
   pills/avatars). */
.stat-card.accent-pink{background:var(--pink-bg);border-top-color:var(--pink-bg);}
.stat-value.pink{color:var(--pink);}
/* Oversized faded illustration bleeding off the bottom-right corner —
   same treatment and same image files as the Family Balance Sheet page's
   .bs-kpi-art (dashboard.js's Total Assets/Total Liabilities/Net Worth
   cards only; Documents has no equivalent there and keeps its plain
   badge).

   .stat-card has position:relative but no z-index, so it never actually
   forms its own stacking context — a negative-z-index child here would
   escape to whatever ancestor context does, painting behind the *page's*
   background instead of just this card's, which is why z-index:-1 made
   it invisible in testing. z-index:0 plus the sibling rule below (mirrors
   .bs-kpi-card's own .bs-kpi-label/-value/-foot{z-index:1}) keeps the fix
   local to cards that actually carry this element — .stat-icon/-label/
   -value/-foot themselves are untouched, since those classes are shared
   across several other pages. */
.stat-kpi-art{
  position:absolute;right:-18px;bottom:-22px;width:132px;height:132px;
  opacity:.22;transform:rotate(-6deg);pointer-events:none;z-index:0;
  transition:opacity .3s ease-out, transform .45s cubic-bezier(0.16,1,0.3,1);
}
.stat-kpi-art ~ *{position:relative;z-index:1;}
:root[data-theme="dark"] .stat-kpi-art{opacity:.16;}
.stat-kpi-art img{width:100%;height:100%;display:block;object-fit:contain;}
@media (hover:hover) and (pointer:fine){
  .stat-card:hover .stat-kpi-art{opacity:.34;transform:rotate(-6deg) scale(1.06);}
  :root[data-theme="dark"] .stat-card:hover .stat-kpi-art{opacity:.26;}
}
.stat-icon{
  width:38px;height:38px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  font-size:17px;margin-bottom:10px;
  background:rgba(255,255,255,.6);
  animation:stat-icon-pop .45s cubic-bezier(0.34,1.56,0.64,1) both;
}
/* Small "just landed" overshoot on each KPI card's own icon badge - a
   fresh DOM node's CSS animation (unlike a transition) always plays once
   on insertion with no JS needed, so this fires on every fresh
   navigate()/remount() the same way the stat value's own count-up does. */
@keyframes stat-icon-pop{0%{transform:scale(.8);opacity:0;}60%{transform:scale(1.12);opacity:1;}100%{transform:scale(1);}}
[data-theme="dark"] .stat-icon{background:rgba(255,255,255,.08);}
.stat-label{font-size:12px;color:var(--text-400);font-weight:600;text-transform:uppercase;letter-spacing:.04em;}
.stat-value{font-size:23px;font-weight:800;color:var(--text-900);margin-top:6px;}
.stat-value.green{color:var(--green);}
.stat-value.red{color:var(--red);}
.stat-value.blue{color:var(--blue);}
.stat-value.gold{color:var(--gold-600);}
.stat-value.purple{color:var(--purple);}
.stat-value.lavender{color:var(--lavender);}
/* Digit-roll ("lottery result") state on a KPI amount while
   Dashboard.animateStatValueRoll() is scrambling its digits - tabular
   figures keep every 0-9 the same width so the line doesn't jitter as
   the random digits change. */
.stat-value-rolling{font-variant-numeric:tabular-nums;}
.stat-foot{font-size:12px;color:var(--text-400);margin-top:4px;display:flex;align-items:center;gap:5px;}
/* Click feedback on all 4 KPI cards (Total Assets, Total Liabilities,
   Net Worth, Documents) - a brief solid-color flash (the card's own
   accent color), 3 staggered concentric rings with a soft radial glow,
   a 16-star shower (3 waves of 5/7/4, dashboard.js) along curved paths
   sweeping across the whole card, and the amount
   itself digit-rolling (see .stat-value-rolling above). Net Worth
   additionally gets a scattered glitter burst and a shimmer sweep - see
   .stat-card-glitter and .accent-blue.stat-card-active::after below.
   Ring size is capped by both card axes (not just the wider one) so it
   isn't mostly clipped away by this card's own overflow:hidden. */
.stat-card.accent-green{--star-glow:var(--green);}
.stat-card.accent-red{--star-glow:var(--red);}
.stat-card.accent-blue{--star-glow:var(--blue);}
.stat-card.accent-gold{--star-glow:var(--gold-500);}
.stat-card.accent-lavender{--star-glow:var(--lavender);}
.stat-card-ripple{
  position:absolute;left:0;top:0;
  border-radius:50%;
  border:3px solid rgba(255,255,255,.9);
  pointer-events:none;
  transform:translate(-50%,-50%) scale(0);
  opacity:0;
  animation:stat-card-ripple-expand .72s cubic-bezier(.15,.65,.25,1) forwards;
}
.stat-card-ripple.ripple-2{border-width:2.5px;animation-delay:70ms;}
.stat-card-ripple.ripple-3{border-width:2px;animation-delay:140ms;}
@keyframes stat-card-ripple-expand{
  0%{transform:translate(-50%,-50%) scale(0);opacity:0;}
  8%{opacity:.95;}
  25%{opacity:.8;}
  55%{opacity:.45;}
  80%{opacity:.18;}
  100%{transform:translate(-50%,-50%) scale(1);opacity:0;}
}
.stat-card-ripple-glow{
  position:absolute;left:0;top:0;
  width:90px;height:90px;
  border-radius:50%;
  pointer-events:none;
  transform:translate(-50%,-50%) scale(.15);
  opacity:0;
  background:radial-gradient(circle, rgba(255,255,255,.35) 0%, rgba(255,255,255,.16) 35%, transparent 72%);
  animation:stat-card-ripple-glow-expand .72s ease-out forwards;
}
@keyframes stat-card-ripple-glow-expand{
  0%{transform:translate(-50%,-50%) scale(.15);opacity:0;}
  12%{opacity:1;}
  55%{opacity:.75;}
  100%{transform:translate(-50%,-50%) scale(3.2);opacity:0;}
}
/* Comet head (bright dot + trailing streak) and its own soft glow,
   both riding the same dynamic offset-path set inline by JS; offset-
   rotate:auto keeps the ::before streak pointed back along the curve's
   direction of travel at every point, so it reads as a single arcing
   trail rather than a dot dragging a fixed-angle tail. */
.stat-card-star{
  position:absolute;left:0;top:0;
  width:6px;height:6px;
  border-radius:50%;
  background:#fff;
  box-shadow:0 0 6px 2px #fff, 0 0 14px 4px var(--star-glow, #fff);
  pointer-events:none;
  offset-distance:0%;
  offset-rotate:auto;
  opacity:0;
  animation:stat-card-star-flight .62s cubic-bezier(.3,.05,.2,1) forwards;
}
.stat-card-star::before{
  content:'';
  position:absolute;
  top:50%;right:100%;
  width:34px;height:2px;
  transform:translateY(-50%);
  background:linear-gradient(to left, rgba(255,255,255,.85), transparent);
  border-radius:2px;
}
.stat-card-star-glow{
  position:absolute;left:0;top:0;
  width:22px;height:22px;
  margin:-11px 0 0 -11px;
  border-radius:50%;
  pointer-events:none;
  offset-distance:0%;
  background:radial-gradient(circle, var(--star-glow, #fff) 0%, transparent 70%);
  filter:blur(2px);
  opacity:0;
  animation:stat-card-star-glow-flight .62s ease-out forwards;
}
@keyframes stat-card-star-flight{
  0%{offset-distance:0%;opacity:0;transform:scale(.4);}
  8%{opacity:1;transform:scale(1);}
  82%{opacity:1;}
  100%{offset-distance:100%;opacity:0;transform:scale(.6);}
}
@keyframes stat-card-star-glow-flight{
  0%{offset-distance:0%;opacity:0;}
  10%{opacity:.85;}
  82%{opacity:.55;}
  100%{offset-distance:100%;opacity:0;}
}
/* Net Worth-only scattered sparkle burst (Dashboard.onStatCardClick()
   creates one .stat-card-glitter span per particle with --gx/--gy set
   inline to a random outward travel distance) plus a shimmer sweep
   across the whole card - together these are what make Net Worth read
   as visibly "shinier" than the other 3 cards' shared click effect. */
.stat-card-glitter{
  position:absolute;left:0;top:0;
  border-radius:50%;
  pointer-events:none;
  background:radial-gradient(circle, #fff 0%, var(--star-glow, #fff) 55%, transparent 75%);
  box-shadow:0 0 5px 1px rgba(255,255,255,.8);
  opacity:0;
  transform:translate(-50%,-50%) scale(.3);
  animation:stat-card-glitter-fly .85s ease-out forwards;
}
@keyframes stat-card-glitter-fly{
  0%{transform:translate(-50%,-50%) translate(0,0) scale(.3);opacity:0;}
  18%{opacity:1;transform:translate(-50%,-50%) translate(calc(var(--gx) * .3),calc(var(--gy) * .3)) scale(1);}
  55%{opacity:1;}
  100%{transform:translate(-50%,-50%) translate(var(--gx),var(--gy)) scale(.15);opacity:0;}
}
/* Always-on ambient shine for Net Worth (.stat-card-ambient, set only on
   that card in dashboard.js) - a slow shimmer sweep plus a handful of
   twinkling sparkle dots at fixed positions, both looping forever with
   no click/hover required, independent of the click-triggered
   .stat-card-active effect below. Kept as its own ::before (the click
   sweep uses ::after) so the two can run at the same time without
   clobbering each other. */
.stat-card.accent-blue.stat-card-ambient::before{
  content:'';
  position:absolute;top:0;left:-60%;
  width:32%;height:100%;
  pointer-events:none;
  background:linear-gradient(75deg, transparent 0%, rgba(255,255,255,.32) 45%, rgba(255,255,255,.58) 50%, rgba(255,255,255,.32) 55%, transparent 100%);
  animation:stat-card-ambient-sweep 4.5s ease-in-out infinite;
}
@keyframes stat-card-ambient-sweep{
  0%{left:-60%;}
  25%{left:130%;}
  100%{left:130%;}
}
.stat-card-ambient-sparkle{
  position:absolute;
  width:6px;height:6px;
  border-radius:50%;
  pointer-events:none;
  background:radial-gradient(circle, #fff 0%, rgba(255,255,255,.8) 50%, transparent 75%);
  box-shadow:0 0 7px 2px rgba(255,255,255,.85), 0 0 12px 4px var(--blue);
  opacity:0;
  transform:translate(-50%,-50%) scale(.4);
  animation:stat-card-ambient-twinkle 3.6s ease-in-out infinite;
}
@keyframes stat-card-ambient-twinkle{
  0%,100%{opacity:0;transform:translate(-50%,-50%) scale(.4);}
  50%{opacity:1;transform:translate(-50%,-50%) scale(1.3);}
}
@media (prefers-reduced-motion: reduce){
  .stat-card.accent-blue.stat-card-ambient::before,
  .stat-card-ambient-sparkle{animation:none;display:none;}
}
.stat-card.accent-blue.stat-card-active::after{
  content:'';
  position:absolute;top:0;left:-60%;
  width:45%;height:100%;
  pointer-events:none;
  background:linear-gradient(75deg, transparent 0%, rgba(255,255,255,.55) 45%, rgba(255,255,255,.85) 50%, rgba(255,255,255,.55) 55%, transparent 100%);
  animation:stat-card-shine-sweep .9s ease-out;
}
@keyframes stat-card-shine-sweep{
  0%{left:-60%;opacity:0;}
  15%{opacity:1;}
  70%{opacity:.9;}
  100%{left:115%;opacity:0;}
}
@media (prefers-reduced-motion: reduce){
  .stat-card-ripple,.stat-card-ripple-glow,.stat-card-star,.stat-card-star-glow,.stat-card-glitter{display:none;}
  .stat-card.accent-blue.stat-card-active::after{animation:none;content:none;}
}
.stat-card.stat-card-active{transition:background .2s ease-out;}
.stat-card.accent-green.stat-card-active{background:var(--green);}
.stat-card.accent-red.stat-card-active{background:var(--red);}
.stat-card.accent-blue.stat-card-active{background:var(--blue);}
.stat-card.accent-gold.stat-card-active{background:var(--gold-500);}
/* Recently Deleted.png's "Permanent Deletions" card - added alongside the
   other four accents above, which this .accent-purple card (added for that
   card) had been missing: without it, the card's own background never
   filled solid purple on the hover/click flash, and .stat-value.purple's
   own colour (var(--purple)) stayed exactly as illegible against the
   pale/no-longer-pale background as every other accent would be without
   this same pair of rules. */
.stat-card.accent-purple.stat-card-active{background:var(--purple);}
/* Same pair again for the Wealth Dashboard's Active SIPs card, which uses
   the softer --lavender accent (see its own note in :root). */
.stat-card.accent-lavender.stat-card-active{background:var(--lavender);}
.stat-card.stat-card-active .stat-label,
.stat-card.stat-card-active .stat-foot{color:rgba(255,255,255,.85);}
.stat-card.accent-green.stat-card-active .stat-value.green,
.stat-card.accent-red.stat-card-active .stat-value.red,
.stat-card.accent-blue.stat-card-active .stat-value.blue,
.stat-card.accent-gold.stat-card-active .stat-value.gold,
.stat-card.accent-purple.stat-card-active .stat-value.purple,
.stat-card.accent-lavender.stat-card-active .stat-value.lavender{color:#fff;}

/* ---------- Buttons ---------- */
.btn{
  display:inline-flex;
  align-items:center;
  gap:6px;
  padding:8px 14px;
  border-radius:8px;
  border:1px solid var(--border);
  background:var(--card);
  color:var(--text-900);
  font-size:13.2px;
  font-weight:600;
  cursor:pointer;
  transition:all .12s ease;
  white-space:nowrap;
}
.btn:hover{background:var(--subtle-bg);}
.btn-primary{background:var(--navy-800);border-color:var(--navy-800);color:#fff;}
.btn-primary:hover{background:var(--navy-900);}
.btn-gold{background:var(--gold-500);border-color:var(--gold-500);color:#1a1305;}
.btn-gold:hover{background:var(--gold-400);}
.btn-danger{background:var(--red-bg);border-color:var(--red-bg);color:var(--red);}
.btn-danger:hover{background:var(--red-bg);}
/* Recently Deleted.png - soft green pill for "Restore", mirroring
   .btn-danger's own soft-red pill exactly (same construction, green
   tokens instead of red) rather than inventing a new visual language. */
.btn-success{background:var(--green-bg);border-color:var(--green-bg);color:var(--green);}
.btn-success:hover{background:var(--green-bg);}
.btn-sm{padding:5px 10px;font-size:12.2px;}
.btn-ghost{background:transparent;border-color:transparent;}
/* Icon-only row actions (Lightmode.jpeg's Assets table treatment) - a
   compact circular button just for the icon. title/aria-label on the
   button itself (see assets.js) still carry the accessible name, so
   this is never the only way to understand what the icon does. */
.btn-icon{padding:5px;width:28px;height:28px;justify-content:center;background:transparent;border-color:transparent;}
.btn-icon:hover{background:var(--subtle-bg);}
.btn-icon.btn-danger{background:transparent;}
.btn-icon.btn-danger:hover{background:var(--red-bg);}
.btn-ghost:hover{background:var(--subtle-bg);}
.btn[disabled]{opacity:.5;cursor:not-allowed;}

/* ---------- Tables ---------- */
.table-wrap{overflow-x:auto;overflow-y:hidden;}
table{width:100%;border-collapse:collapse;font-size:13.4px;}
th{
  text-align:left;
  font-size:11.5px;
  text-transform:uppercase;
  letter-spacing:.04em;
  color:var(--text-400);
  padding:9px 12px;
  border-bottom:2px solid var(--border);
  white-space:nowrap;
}
td{
  padding:11px 12px;
  border-bottom:1px solid var(--border);
  vertical-align:middle;
}
tr:last-child td{border-bottom:none;}
tbody tr:hover{background:var(--subtle-bg);}
.table-actions{display:flex;gap:6px;justify-content:flex-end;}

/* ---------- Wealth Dashboard Assets card (dashboard.js assetsPreview) ----------
   Read-only summary widget - donut + grouped table side by side on
   desktop, stacked on narrower screens (same breakpoint the rest of the
   dashboard already uses for its 2-column rows). */
.ads-layout{display:flex;gap:24px;flex-wrap:wrap;align-items:flex-start;}
.ads-donut-col{flex:0 0 220px;display:flex;flex-direction:column;align-items:center;gap:14px;}
.ads-table-col{flex:1 1 420px;min-width:0;}
.ads-legend{display:flex;flex-direction:column;gap:8px;width:100%;}
/* These legend rows now drive the wedge spotlight (see app.js's
   CHART_SEG_ITEM_SEL), so they get the same hover affordance and padded hit
   area .donut-legend-item has. The opacity/transform transitions are the
   staggered reveal app.js drives - background is appended, not replacing. */
.ads-legend-item{display:flex;align-items:center;gap:8px;font-size:12.6px;color:var(--text-600);border-radius:6px;padding:3px 6px;margin:-3px -6px;cursor:default;transition:opacity .35s cubic-bezier(0.22,1,0.36,1), transform .35s cubic-bezier(0.22,1,0.36,1), background .18s ease-out;}
@media (hover:hover) and (pointer:fine){
  .ads-legend-item:hover{background:var(--subtle-bg);}
}
.ads-total-row td{font-weight:700;border-top:2px solid var(--border);background:var(--subtle-bg);}
.ads-footer{font-size:12px;color:var(--text-400);margin-top:10px;}
.ads-view-cell{text-align:center;color:var(--text-400);font-size:14px;}
.ads-expand-row td{background:var(--subtle-bg);padding:12px 16px;}
@media(max-width:640px){.ads-donut-col{flex:1 1 100%;}}

/* Row hover for the Assets card's three tabs (By Category / By Type / All
   Assets). Hovering a line tints it in that row's own colour - the same
   --row-accent the donut uses for the matching wedge - and emits a wifi-style
   pulse from the row's leading edge. Same ripple vocabulary as the stat
   cards' .stat-card-ripple, but hover-driven and CSS-only (no JS-spawned
   nodes). The rings are anchored on the left border so .table-wrap's
   overflow-x clip leaves just the outward-opening arcs, which is what gives
   it the wifi read. Painted on the cells, not the <tr>: tables here are
   border-collapse:collapse, where row box-shadow is unreliable. */
.ads-hover-row td{transition:background-color .22s ease, box-shadow .22s ease;}
.ads-hover-row td:first-child{position:relative;}
.ads-hover-row:hover td{
  background:color-mix(in srgb, var(--row-accent, var(--gold-500)) 16%, transparent);
}
.ads-hover-row:hover td:first-child{
  box-shadow:inset 3px 0 0 var(--row-accent, var(--gold-500));
}
.ads-hover-row td:first-child::before,
.ads-hover-row td:first-child::after{
  content:'';
  position:absolute;
  left:0;top:50%;
  width:22px;height:22px;
  margin:-11px 0 0 -11px;
  border-radius:50%;
  border:1.5px solid var(--row-accent, var(--gold-500));
  opacity:0;
  pointer-events:none;
}
.ads-hover-row:hover td:first-child::before{animation:ads-wifi-wave 1.5s ease-out infinite;}
.ads-hover-row:hover td:first-child::after{animation:ads-wifi-wave 1.5s ease-out .5s infinite;}
@keyframes ads-wifi-wave{
  0%{transform:scale(.55);opacity:0;}
  15%{opacity:.7;}
  100%{transform:scale(2.8);opacity:0;}
}
@media (prefers-reduced-motion: reduce){
  .ads-hover-row td{transition:none;}
  .ads-hover-row:hover td:first-child::before,
  .ads-hover-row:hover td:first-child::after{animation:none;}
}

/* "All Assets" tab - Table <-> Card view switcher (Section by Section
   §4's dual-view toggle) */
.ads-view-switch{display:flex;gap:4px;background:var(--subtle-bg);border-radius:8px;padding:3px;flex-shrink:0;}
.ads-view-btn{border:none;background:none;padding:6px 12px;border-radius:6px;font-size:12.6px;font-weight:600;color:var(--text-400);cursor:pointer;transition:background .15s ease, color .15s ease;}
.ads-view-btn.active{background:var(--card);color:var(--text-900);box-shadow:var(--shadow);}
/* Cross-fades in on every tab/view switch (a fresh element each remount,
   so the keyframe replays automatically - the same technique used for
   .table-wrap tbody tr's row-in, just without needing a no-replay guard
   since replaying on every switch IS the point here). */
.ads-view-anim{animation:ads-view-in .3s cubic-bezier(0.16,1,0.3,1) both;}
@keyframes ads-view-in{from{opacity:0;transform:scale(.97);}to{opacity:1;transform:scale(1);}}

.ads-bubble-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:16px;}
.ads-bubble-card{
  display:flex;flex-direction:column;align-items:center;text-align:center;gap:6px;
  background:var(--card);border:1px solid var(--border);border-radius:16px;
  padding:18px 14px;cursor:pointer;position:relative;
  transition:transform .22s cubic-bezier(0.34,1.56,0.64,1), box-shadow .22s ease-out, z-index 0s;
}
.ads-bubble-icon{width:46px;height:46px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:19px;margin-bottom:2px;}
.ads-bubble-name{font-weight:700;font-size:13px;color:var(--text-900);}
.ads-bubble-value{font-weight:800;font-size:15px;color:var(--gold-600);}
.ads-bubble-meta{font-size:11.5px;color:var(--text-400);}
@media (prefers-reduced-motion: reduce){
  .ads-bubble-card:hover{transform:none;}
}

/* ---------- Column Adjustment (resizable table columns) ----------
   Applied via app.js's initColumnResize() on Asset Management, Obligations/
   Liabilities, Insurance, Family Income/Spend, and Reports — a thin drag
   handle on the right edge of each header cell so users can adjust column
   widths to their own liking; widths persist per table in localStorage. */
.col-resizable th{position:relative;}
.col-resize-handle{
  position:absolute;top:0;right:0;bottom:0;
  width:8px;cursor:col-resize;
  touch-action:none;
}
.col-resize-handle::after{
  content:'';position:absolute;top:2px;bottom:2px;right:3px;width:2px;
  background:var(--border);border-radius:2px;
}
.col-resize-handle:hover::after,
.col-resize-handle.resizing::after{background:var(--gold-500);}
.empty-row td{text-align:center;color:var(--text-400);padding:32px;}

/* ---------- Pills / tags ---------- */
.pill{
  display:inline-block;
  padding:3px 10px;
  border-radius:20px;
  font-size:11.5px;
  font-weight:700;
  white-space:nowrap;
}
.pill-green{background:var(--green-bg);color:var(--green);}
.pill-red{background:var(--red-bg);color:var(--red);}
.pill-amber{background:var(--amber-bg);color:var(--amber);}
.pill-blue{background:var(--blue-bg);color:var(--blue);}
.pill-gray{background:var(--subtle-bg);color:var(--text-600);}
.pill-gold{background:var(--gold-bg);color:var(--gold-600);}

/* Calculation Perspectives §1 — small "shared with others" indicator next
   to an Owner/Borrower name (list rows and View pages alike). */
.joint-badge{font-size:12px;cursor:help;opacity:.85;margin-left:2px;}

/* Scanning_V1 — marks a field whose value came from an uploaded document
   rather than the user, next to that field's own label. */
.ai-populated-badge{
  display:inline-block;
  margin-left:6px;
  padding:1px 7px;
  border-radius:20px;
  font-size:10px;
  font-weight:700;
  background:var(--green-bg);
  color:var(--green);
  vertical-align:middle;
}
/* Medium-confidence AI-extracted fields (Scanning_v2 §Confidence Handling —
   "Medium confidence -> populate and mark as Review") — same badge shape,
   amber instead of green so it reads as "check this one" at a glance. */
.ai-populated-badge-review{
  background:var(--amber-bg);
  color:var(--amber);
}

/* ---------- Forms ---------- */
.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px 16px;}
@media(max-width:760px){.form-grid{grid-template-columns:1fr;}}
.field{display:flex;flex-direction:column;gap:5px;}
.field.full{grid-column:1/-1;}
.field label{font-size:12.3px;font-weight:600;color:var(--text-600);}
.field label .req{color:var(--red);}
.field input[type=text],
.field input[type=number],
.field input[type=date],
.field input[type=email],
.field input[type=tel],
.field input[type=password],
.field select,
.field textarea{
  border:1px solid var(--border);
  border-radius:7px;
  padding:8px 10px;
  font-size:13.4px;
  font-family:inherit;
  background:var(--card);
  color:var(--text-900);
  width:100%;
}
.field textarea{resize:vertical;min-height:60px;}
/* Amount/number fields — remove the native up/down spinner buttons app-wide
   (typing is the only intended way to change a value; the scroll-wheel
   value-change quirk this pairs with is blocked separately in app.js). Not
   scoped to .field so this also covers any hand-rolled number input outside
   the shared form system. */
input[type=number]::-webkit-inner-spin-button,
input[type=number]::-webkit-outer-spin-button{
  -webkit-appearance:none;
  margin:0;
}
input[type=number]{
  -moz-appearance:textfield;
}
.field input:disabled,.field select:disabled,.field textarea:disabled{
  background:var(--subtle-bg);color:var(--text-400);cursor:not-allowed;
}
.field input:focus,.field select:focus,.field textarea:focus{
  outline:none;
  border-color:var(--gold-500);
  box-shadow:0 0 0 3px rgba(200,153,46,.18);
}
.field-hint{font-size:11px;color:var(--text-400);}
.field-hint-error{color:var(--red);}
.field-hint-success{color:var(--green);}
/* IFSC/other inline-validated fields (Fetch Bank details from IFSC_v1 §5) -
   red border highlight, the field's own .field-hint carries the message. */
.field input.field-error{border-color:var(--red);}
.field input.field-error:focus{border-color:var(--red);box-shadow:0 0 0 3px rgba(192,57,43,.18);}
.field input[readonly]{background:var(--subtle-bg);color:var(--text-600);cursor:default;}

/* PIN Code Auto-Fetch — shared searchable dropdown (LOV) widget, used by
   both the Sign Up page (auth.js) and in-app address forms
   (formRenderer.js/familyTree.js) via pincodeLookup.js. */
.searchselect{position:relative;}
.searchselect-list{
  display:none;
  position:absolute;left:0;right:0;top:calc(100% + 4px);
  max-height:220px;overflow-y:auto;
  background:var(--card);border:1px solid var(--border);border-radius:8px;
  box-shadow:var(--shadow-md);
  z-index:20;
}
.searchselect-list.open{display:block;}
.searchselect-option{
  padding:9px 12px;font-size:13.4px;color:var(--text-900);cursor:pointer;
  min-height:22px;
}
.searchselect-option:hover,.searchselect-option.highlighted{
  background:var(--gold-bg);
}
.searchselect-empty{
  padding:9px 12px;font-size:12.6px;color:var(--text-400);
}
@media (max-width:900px){
  .searchselect-option{padding:11px 12px;min-height:28px;}
}
.form-section-label{
  font-size:12px;
  font-weight:700;
  color:var(--text-600);
  text-transform:uppercase;
  letter-spacing:.04em;
  border-top:1px solid var(--border);
  padding-top:14px;
  margin-top:6px;
}
.form-section-label:first-child{border-top:none;padding-top:0;margin-top:0;}
fieldset.section{
  grid-column:1/-1;
  border:1px dashed var(--border);
  border-radius:8px;
  padding:14px 16px 4px;
  margin:6px 0 0;
}
fieldset.section legend{
  font-size:12px;
  font-weight:700;
  color:var(--text-600);
  text-transform:uppercase;
  letter-spacing:.04em;
  padding:0 6px;
}

.expand-all-toggle{
  display:flex;align-items:center;gap:7px;
  font-size:12.5px;font-weight:600;color:var(--text-600);
  margin-bottom:12px;cursor:pointer;user-select:none;
}
.expand-all-toggle input{cursor:pointer;}

/* Progressive disclosure — collapsed advanced-field groups.
   Each section gets a soft pastel header (same bg/text pairing already
   used by pills and avatars elsewhere in the app) so sections read as
   distinct at a glance without the UI turning busy. A colored left accent
   bar plus chevron rotation make expanded vs collapsed unambiguous. */
.form-collapse{
  grid-column:1/-1;
  border:1px solid var(--border);
  border-radius:10px;
  margin-top:10px;
  background:var(--card);
  overflow:hidden;
}
.form-collapse + .form-collapse{margin-top:8px;}
.form-collapse summary{
  list-style:none;
  cursor:pointer;
  padding:11px 14px;
  font-size:12.6px;
  font-weight:700;
  display:flex;
  align-items:center;
  gap:8px;
  user-select:none;
  background:var(--subtle-bg);
  color:var(--text-600);
  border-left:3px solid transparent;
  transition:background .12s ease, border-color .12s ease, filter .12s ease;
}
.form-collapse summary::-webkit-details-marker{display:none;}
.form-collapse summary::before{
  content:'▸';
  color:currentColor;
  opacity:.65;
  font-size:11px;
  transition:transform .12s ease, opacity .12s ease;
}
.form-collapse[open] summary::before{transform:rotate(90deg);opacity:.9;}
.form-collapse summary:hover{filter:brightness(.97);}

/* Soft color hierarchy — reuses the app's existing pastel palette */
.form-collapse.fc-blue summary{background:var(--blue-bg);color:var(--blue);}
.form-collapse.fc-blue[open] summary{border-left-color:var(--blue);}
.form-collapse.fc-green summary{background:var(--green-bg);color:var(--green);}
.form-collapse.fc-green[open] summary{border-left-color:var(--green);}
.form-collapse.fc-amber summary{background:var(--amber-bg);color:var(--amber);}
.form-collapse.fc-amber[open] summary{border-left-color:var(--amber);}
.form-collapse.fc-purple summary{background:var(--purple-bg);color:var(--purple);}
.form-collapse.fc-purple[open] summary{border-left-color:var(--purple);}
.form-collapse.fc-teal summary{background:var(--teal-bg);color:var(--teal);}
.form-collapse.fc-teal[open] summary{border-left-color:var(--teal);}
.form-collapse.fc-red summary{background:var(--red-bg);color:var(--red);}
.form-collapse.fc-red[open] summary{border-left-color:var(--red);}
.form-collapse.fc-pink summary{background:var(--pink-bg);color:var(--pink);}
.form-collapse.fc-pink[open] summary{border-left-color:var(--pink);}
.form-collapse.fc-gray summary{background:var(--subtle-bg);color:var(--text-600);}
.form-collapse.fc-gray[open] summary{border-left-color:var(--text-400);}
.form-collapse.fc-gold summary{background:var(--gold-bg);color:var(--gold-600);}
.form-collapse.fc-gold[open] summary{border-left-color:var(--gold-500);}

.form-collapse-body{
  padding:14px 14px 16px;
  border-top:1px solid var(--border);
  background:var(--card);
}
.form-actions{
  display:flex;
  justify-content:flex-end;
  gap:8px;
  margin-top:18px;
  padding-top:14px;
  border-top:1px solid var(--border);
}

/* nominee widget */
.nominee-row-wrap{margin-bottom:6px;}
.nominee-row{display:grid;grid-template-columns:1fr 100px 36px;gap:8px;align-items:center;}
/* Changes-Bharathi Change 05 — Guardian Name/Mobile Number, shown only
   while the row's selected nominee is a minor (see nomineeRowHtml()). */
.nominee-guardian-wrap{display:flex;gap:8px;margin:6px 0 0 0;padding-left:2px;}
.nominee-guardian-wrap input{flex:1;}
.nominee-total{font-size:12px;margin-top:4px;color:var(--text-400);}
.nominee-total.bad{color:var(--red);font-weight:700;}
.joint-owner-entry{margin-bottom:2px;}
.joint-owner-other-fields{display:flex;gap:8px;margin:0 0 6px 0;}
.joint-owner-other-fields input,.joint-owner-other-fields select{flex:1;}

/* ---------- Tabs ---------- */
.tabs{display:flex;gap:4px;border-bottom:1px solid var(--border);margin-bottom:18px;flex-wrap:wrap;}
.tab{
  /* No-op on the existing <div class="tab"> usages everywhere else in
     the app; needed so EMI Schedule_v1's <button class="tab"> (real
     buttons, for free keyboard accessibility) don't show native button
     chrome (background/border/font) on top of this class's own styling. */
  background:none;
  border:none;
  font:inherit;
  padding:9px 15px;
  font-size:13.2px;
  font-weight:600;
  color:var(--text-400);
  cursor:pointer;
  border-bottom:2px solid transparent;
  margin-bottom:-1px;
}
.tab:hover{color:var(--text-900);}

/* ---------- Reports export bar ---------- */
.report-export-bar{
  display:flex;align-items:center;gap:10px;margin-bottom:14px;flex-wrap:wrap;
}

/* ---------- Print (Reports → Export PDF) ---------- */
@media print{
  /* Without this, Chrome's print output omits CSS background-colors and
     gradients by default (e.g. the donut chart's conic-gradient in
     charts.js) unless the user manually enables "Background graphics" in
     the print dialog - this makes it appear unconditionally instead. */
  *{ print-color-adjust:exact !important; -webkit-print-color-adjust:exact !important; }
  .sidebar, .topbar, .tabs, .report-export-bar, .ai-widget, .claude-widget, .modal-overlay, .toast{
    display:none !important;
  }
  body, .app-shell, .main, .content{background:#fff !important;}
  .content{padding:0 !important;margin:0 !important;}
  .card{box-shadow:none !important;border:1px solid #ccc !important;break-inside:avoid;}
}
.tab.active{color:var(--text-900);border-bottom-color:var(--gold-500);}

/* ---------- Modal ---------- */
.modal-overlay{
  display:none;
  position:fixed;inset:0;
  background:rgba(15,20,30,.5);
  z-index:100;
  align-items:flex-start;
  justify-content:center;
  padding:40px 16px;
  overflow-y:auto;
}
.modal-overlay.open{display:flex;}
.modal{
  background:var(--card);
  border-radius:12px;
  width:100%;
  max-width:760px;
  box-shadow:var(--shadow-md);
  animation:pop .12s ease;
}
@keyframes pop{from{transform:translateY(6px);opacity:0;}to{transform:translateY(0);opacity:1;}}
.modal-header{
  display:flex;align-items:center;justify-content:space-between;
  padding:16px 20px;border-bottom:1px solid var(--border);
}
.modal-header h3{margin:0;font-size:16px;color:var(--text-900);}
.modal-close{background:none;border:none;font-size:16px;cursor:pointer;color:var(--text-400);padding:4px 8px;}
.modal-close:hover{color:var(--text-900);}
.modal-body{padding:20px;max-height:76vh;overflow-y:auto;}

/* ---------- Non-Family Member Access confirmation (Reminder popup_v1) ----------
   Own fade/scale entrance+exit, scoped to .modal-nfm-confirm only - every
   other modal in the app keeps the shared .modal's existing instant-close/
   simple pop-in animation untouched. */
.modal.modal-nfm-confirm{
  max-width:420px;
  animation:nfm-pop-in .18s cubic-bezier(0.16,1,0.3,1);
}
.modal.modal-nfm-confirm.nfm-closing{
  animation:nfm-pop-out .14s ease forwards;
}
@keyframes nfm-pop-in{
  from{opacity:0;transform:scale(.94) translateY(6px);}
  to{opacity:1;transform:scale(1) translateY(0);}
}
@keyframes nfm-pop-out{
  from{opacity:1;transform:scale(1);}
  to{opacity:0;transform:scale(.96);}
}
@media (prefers-reduced-motion: reduce){
  .modal.modal-nfm-confirm, .modal.modal-nfm-confirm.nfm-closing{animation:none;}
}
.nfm-confirm-icon{
  width:44px;height:44px;border-radius:50%;
  background:var(--amber-bg);color:var(--amber);
  display:flex;align-items:center;justify-content:center;
  font-size:20px;margin:4px auto 14px;
}
.nfm-confirm-message{
  text-align:center;color:var(--text-600);font-size:13.6px;line-height:1.55;margin:0;
}

/* ---------- Toast ---------- */
.toast{
  position:fixed;bottom:22px;left:50%;transform:translateX(-50%) translateY(20px);
  background:var(--navy-900);color:#fff;
  padding:10px 20px;border-radius:8px;font-size:13px;
  box-shadow:var(--shadow-md);
  opacity:0;pointer-events:none;
  transition:all .2s ease;
  z-index:200;
}
.toast.show{opacity:1;transform:translateX(-50%) translateY(0);}
/* The toast (e.g. the "Conversion rates updated..." message after
   clicking Update Conversion Rate) is a single shared element rendered
   outside #content, as a fixed-position sibling of the app shell - so it
   sits outside the .dashboard-page wrapper and its blanket font rule
   never reaches it. :has() scopes the new serif style to only apply while
   the Wealth Dashboard is the page currently mounted, so this same shared
   .toast element keeps its plain look on every other page's save/delete/
   error confirmations. */
body:has(.dashboard-page) .toast{
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
  font-style:normal;
  font-size:14.5px;
}

/* ---------- Alerts list ---------- */
.alert-item{
  display:flex;gap:12px;align-items:flex-start;
  padding:12px 14px;border-radius:8px;border:1px solid var(--border);margin-bottom:8px;
}
.alert-item .icon{font-size:17px;line-height:1.3;}
.alert-item .body{flex:1;}
.alert-item .title{font-weight:700;font-size:13.4px;}
.alert-item .desc{font-size:12.6px;color:var(--text-600);margin-top:2px;}
.alert-sev-high{border-left:4px solid var(--red);}
.alert-sev-med{border-left:4px solid var(--amber);}
.alert-sev-low{border-left:4px solid var(--blue);}

/* ---------- Bars / progress ---------- */
.bar-row{display:flex;align-items:center;gap:10px;margin-bottom:10px;}
.bar-label{width:130px;font-size:12.6px;color:var(--text-600);flex-shrink:0;}
.bar-track{flex:1;background:var(--subtle-bg);border-radius:6px;height:10px;overflow:hidden;}
.bar-fill{height:100%;border-radius:6px;background:var(--navy-700);}

/* ---------- Liability progress bar: left-to-right shimmer ----------
   A highlight runs along the filled (blue) portion of each liability card's
   bar. Same 75deg gradient construction as .stat-card-ambient::before and
   .bs-networth-footer::before, but with its OWN keyframe rather than reusing
   stat-card-ambient-sweep: that one travels during just the first 25% of its
   cycle and parks off-screen for the remaining 75%, which reads as an
   occasional glint on a big card. On a 10px bar whose highlight is only ~63px
   wide, that dead time makes the effect look broken - most of the time there
   is simply nothing moving. This keyframe travels for 70% of a shorter cycle,
   so the bar reads as a steady left-to-right flow.

   It rides the TRACK, not the fill. The fill is only as wide as the paid-off
   share, so a liability with nothing paid off yet (0%) has a zero-width fill
   and would show no motion at all. The track is always full width, so every
   bar animates whatever its progress. The track already has overflow:hidden
   and the same border-radius, so the highlight stays inside the rounded bar.

   The gradient is two-tone on purpose, because the highlight crosses two very
   different grounds in one pass: the white core reads against the blue fill,
   while the blue-tinted shoulders are what make it visible against the pale
   --subtle-bg of the unpaid remainder. A plain white highlight vanished over
   the empty part of the track.

   .bar-track::after is positioned, so it paints above the in-flow .bar-fill -
   one highlight travelling the whole bar rather than two out-of-sync ones.
   Scoped to .liability-card so bars elsewhere stay static. */
.liability-card .bar-track{position:relative;}
.liability-card .bar-track::after{
  content:'';
  position:absolute;top:0;left:-60%;
  width:45%;height:100%;
  pointer-events:none;
  background:linear-gradient(75deg,
    transparent 0%,
    rgba(42,95,176,.30) 38%,
    rgba(255,255,255,.80) 50%,
    rgba(42,95,176,.30) 62%,
    transparent 100%);
  animation:progress-bar-sweep 2.4s ease-in-out infinite;
}
/* Shared by the liability bars here and the Balance Sheet's .bs-hbar-fill.
   Unlike stat-card-ambient-sweep it travels for most of its cycle rather than
   parking off-screen for 75% of it, which is what a thin progress bar needs. */
@keyframes progress-bar-sweep{
  0%{left:-60%;}
  70%{left:130%;}
  100%{left:130%;}
}
@media (hover:hover) and (pointer:fine){
  .liability-card:hover .bar-track::after{animation-duration:1.1s;}
}
@media (prefers-reduced-motion: reduce){
  .liability-card .bar-track::after{animation:none;display:none;}
}
.bar-value{width:90px;text-align:right;font-size:12.6px;font-weight:700;color:var(--text-900);flex-shrink:0;}

/* ---------- Empty state ---------- */
.empty-state{
  text-align:center;
  padding:50px 20px;
  color:var(--text-400);
}
.empty-state .em-icon{font-size:34px;margin-bottom:10px;}
.empty-state .em-title{font-weight:700;color:var(--text-600);margin-bottom:4px;}
/* Optional description line + action button under .em-title (Visual
   Customization v1 §32) - purely additive, existing icon/title-only empty
   states elsewhere are unaffected since they have no .em-desc sibling. */
.empty-state .em-desc{font-size:12.8px;color:var(--text-400);max-width:360px;margin:0 auto 16px;line-height:1.55;}
.empty-state .btn{margin-top:2px;}

/* ---------- Utility ---------- */
.flex{display:flex;}
.flex-between{display:flex;align-items:center;justify-content:space-between;}
.gap-8{gap:8px;}
.gap-12{gap:12px;}
.mt-8{margin-top:8px;}
.mt-16{margin-top:16px;}
.text-muted{color:var(--text-400);}
.text-sm{font-size:12.4px;}
.mono{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;}
.section-title{font-size:13px;font-weight:700;color:var(--text-600);text-transform:uppercase;letter-spacing:.03em;margin:20px 0 10px;}
.section-title:first-child{margin-top:0;}
.divider{height:1px;background:var(--border);margin:16px 0;}
.avatar{
  width:32px;height:32px;border-radius:50%;
  background:var(--navy-700);color:#fff;
  display:flex;align-items:center;justify-content:center;
  font-size:12.5px;font-weight:700;flex-shrink:0;
}
.avatar-pastel{width:48px;height:48px;font-size:16px;color:#1a2233;}
.avatar-pastel.p-blue{background:var(--blue-bg);color:var(--blue);}
.avatar-pastel.p-pink{background:var(--pink-bg);color:var(--pink);}
.avatar-pastel.p-purple{background:var(--purple-bg);color:var(--purple);}
.avatar-pastel.p-gold{background:var(--gold-bg);color:var(--gold-600);}
.avatar-pastel.p-teal{background:var(--teal-bg);color:var(--teal);}
.avatar-pastel.p-green{background:var(--green-bg);color:var(--green);}
.topbar-avatar{
  width:34px;height:34px;border-radius:50%;
  background:var(--gold-500);color:#1a1305;
  display:flex;align-items:center;justify-content:center;
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;font-style:normal;
  font-size:17px;font-weight:700;flex-shrink:0;
  cursor:pointer;
  position:relative;
}
/* A slim gradient ring continuously sweeps around the avatar - a quiet
   "glitter" on the profile button, distinct from the one-time boot pulse
   below. Built from a padded conic-gradient ring, masked down to just
   its outline via mask-composite so the avatar's own fill shows through. */
.topbar-avatar::before{
  content:'';
  position:absolute;inset:-3px;
  border-radius:50%;padding:2.5px;
  background:conic-gradient(from 0deg, transparent 0deg, transparent 30deg, var(--gold-600) 70deg, #fff6df 110deg, var(--gold-400) 150deg, transparent 190deg, transparent 360deg);
  -webkit-mask:linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0);
  -webkit-mask-composite:xor;
  mask-composite:exclude;
  box-shadow:0 0 6px 0 rgba(255,246,223,.55);
  animation:avatar-glitter-spin 3.2s linear infinite;
  pointer-events:none;
}
@keyframes avatar-glitter-spin{
  to{transform:rotate(360deg);}
}
@media (prefers-reduced-motion: reduce){
  .topbar-avatar::before{animation:none;}
}
/* One-time "here I am" pulse on app boot only - app.js's
   initShellSettleIn() adds this class once and removes it after the
   animation finishes, so it never replays on ordinary navigate()/
   remount() the way a continuous badge-pulse would. */
.topbar-avatar.profile-pulse-once{animation:profile-pulse-once .9s ease-out 1;}
@keyframes profile-pulse-once{
  0%{box-shadow:0 0 0 0 rgba(200,153,46,.55);}
  60%{box-shadow:0 0 0 9px rgba(200,153,46,0);}
  100%{box-shadow:0 0 0 0 rgba(200,153,46,0);}
}
.theme-toggle-btn{
  width:34px;height:34px;border-radius:50%;
  background:var(--subtle-bg);border:1px solid var(--border);color:var(--text-900);
  display:flex;align-items:center;justify-content:center;
  font-size:15px;flex-shrink:0;cursor:pointer;
  transition:background .12s ease;
  position:relative;overflow:visible;
}
.theme-toggle-btn:hover{background:var(--card);}
.theme-toggle-btn .theme-icon{position:relative;z-index:1;display:inline-block;line-height:1;}
/* Light mode (moon shown, offers switching to dark) — a couple of tiny
   stars twinkle in orbit around the moon. */
.theme-star{
  position:absolute;top:50%;left:50%;
  font-size:6.5px;color:var(--gold-500);
  margin:-4px 0 0 -4px;width:8px;height:8px;
  display:flex;align-items:center;justify-content:center;
  pointer-events:none;z-index:0;
  animation:theme-star-orbit 4.4s linear infinite;
}
.theme-star-1{animation-delay:0s;}
.theme-star-2{animation-delay:-2.2s;}
@keyframes theme-star-orbit{
  0%{transform:rotate(0deg) translateX(15px) rotate(0deg) scale(.6);opacity:.35;}
  50%{opacity:1;transform:rotate(180deg) translateX(15px) rotate(-180deg) scale(1);}
  100%{transform:rotate(360deg) translateX(15px) rotate(-360deg) scale(.6);opacity:.35;}
}
/* Dark mode (sun shown, offers switching to light) — a warm glow pulses
   behind the sun like it's shining. */
.theme-toggle-btn--sun::before{
  content:'';
  position:absolute;inset:-5px;
  border-radius:50%;
  background:radial-gradient(circle, rgba(230,193,121,.6), rgba(230,193,121,0) 70%);
  animation:theme-sun-shine 2.6s ease-in-out infinite;
  z-index:0;
}
@keyframes theme-sun-shine{
  0%,100%{opacity:.45;transform:scale(.82);}
  50%{opacity:1;transform:scale(1.2);}
}
@media (prefers-reduced-motion: reduce){
  .theme-star{animation:none;opacity:.8;}
  .theme-toggle-btn--sun::before{animation:none;opacity:.7;}
}

/* ---------- Profile menu (topbar avatar dropdown) ---------- */
.profile-menu{
  display:none;
  position:absolute;
  top:calc(100% + 12px);
  right:0;
  width:260px;
  background:var(--card);
  border:1px solid var(--border);
  border-radius:12px;
  box-shadow:var(--shadow-md);
  padding:14px;
  z-index:50;
  animation:pop .12s ease;
}
.profile-menu.open{display:block;}
.profile-menu-header{display:flex;align-items:center;gap:10px;}
.profile-menu-avatar{
  width:38px;height:38px;border-radius:50%;
  background:var(--gold-500);color:#1a1305;
  display:flex;align-items:center;justify-content:center;
  font-weight:800;font-size:14.5px;flex-shrink:0;
}
/* Profile dropdown (topbar avatar, top-right) - same Cormorant Garamond
   Italic heading treatment as the rest of the app, but in the app's darker
   --gold-600 (not the sidebar's brighter --gold-400) since this menu sits
   on a light --card background rather than dark navy - --gold-400 would
   read as too pale/low-contrast here. */
.profile-menu-name{
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;font-style:normal;
  font-weight:700;font-size:14.5px;color:var(--gold-600);
}
.profile-menu-role{
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;font-style:normal;
  font-size:11.5px;font-weight:600;color:var(--text-600);margin-top:1px;
  text-transform:uppercase;letter-spacing:.03em;
}
.profile-menu-email{
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;font-style:normal;
  font-size:12.5px;color:var(--text-400);margin-top:2px;word-break:break-all;
}
.profile-menu-divider{height:1px;background:var(--border);margin:12px 0;}
.profile-menu-item{
  display:flex;align-items:center;gap:10px;
  padding:9px 8px;border-radius:8px;
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;font-style:normal;
  font-size:13.5px;color:var(--text-900);font-weight:600;
  cursor:pointer;transition:background .12s ease;
}
.profile-menu-item:hover{background:var(--subtle-bg);}
.profile-menu-item-icon{width:18px;text-align:center;font-size:14.5px;flex-shrink:0;}
.member-cell{display:flex;align-items:center;gap:8px;}
.avatar-photo{overflow:hidden;padding:0;background:var(--subtle-bg);}
.avatar-photo img{width:100%;height:100%;object-fit:cover;border-radius:inherit;}

/* ---------- Profile photo upload widget (Family Head / Family Member forms) ---------- */
.photo-upload-widget{display:flex;align-items:center;gap:16px;margin-bottom:16px;}
.photo-preview{
  width:76px;height:76px;border-radius:50%;overflow:hidden;flex-shrink:0;
  background:var(--subtle-bg);border:1px solid var(--border);
  display:flex;align-items:center;justify-content:center;
}
.photo-preview img{width:100%;height:100%;object-fit:cover;}
.photo-preview-placeholder{font-size:30px;opacity:.5;}
.photo-upload-actions{display:flex;flex-direction:row;align-items:center;flex-wrap:wrap;gap:8px;}

/* ---------- Boxed photo widget variant — dashed "+ Photo" tile (Gold/
   Precious Metals Front/Back/Side/Alternate slots, Asset Image - 3D.txt) ---------- */
.photo-upload-widget-box{position:relative;margin-bottom:0;}
.photo-preview-box{
  width:100%;aspect-ratio:4/3;border-radius:10px;
  border:1.5px dashed var(--border);background:var(--subtle-bg);
  display:flex;align-items:center;justify-content:center;
  cursor:pointer;overflow:hidden;transition:border-color .15s ease,background-color .15s ease;
}
.photo-preview-box:hover{border-color:var(--text-500,var(--text-600));background:var(--hover-bg,var(--subtle-bg));}
.photo-preview-box img{width:100%;height:100%;object-fit:cover;}
.photo-preview-placeholder-box{font-size:12.6px;font-weight:600;color:var(--text-500,var(--text-600));}
.photo-upload-actions-box{position:absolute;top:6px;right:6px;}
.photo-box-remove{
  width:20px;height:20px;border-radius:50%;border:none;
  background:rgba(15,23,42,.6);color:#fff;font-size:11px;line-height:1;padding:0;
  display:flex;align-items:center;justify-content:center;cursor:pointer;
}
.photo-box-remove:hover{background:rgba(15,23,42,.8);}
.photo-box-confirm{
  position:absolute;inset:0;background:rgba(15,23,42,.74);color:#fff;border-radius:10px;
  display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;
  font-size:11.5px;font-weight:600;
}
.photo-box-confirm button{font-size:11px;font-weight:700;padding:3px 10px;border-radius:6px;border:none;cursor:pointer;}
.photo-box-confirm button:first-of-type{background:#e11d48;color:#fff;}
.photo-box-confirm button:last-of-type{background:rgba(255,255,255,.2);color:#fff;}

/* ---------- Real 3D Asset Viewer (Gold/Precious Metals — Asset Image - 3D.txt) ---------- */
.gold3d-viewgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-bottom:12px;}
.gold3d-viewslot label{display:block;font-size:11.5px;font-weight:700;color:var(--text-600);margin-bottom:4px;text-transform:uppercase;letter-spacing:.03em;}
.gold3d-modelrow{display:flex;align-items:center;gap:10px;flex-wrap:wrap;margin-bottom:14px;padding:10px 12px;border:1px dashed var(--gold-500);border-radius:8px;background:var(--gold-bg);}
.gold3d-modelrow .gold3d-model-status{font-size:12.6px;font-weight:600;color:var(--gold-600);}
.gold3d-viewer{
  position:relative;border-radius:12px;overflow:hidden;
  background:linear-gradient(160deg,var(--navy-950),var(--navy-800));
  min-height:320px;display:flex;flex-direction:column;
  border:1px solid var(--gold-500);
}
.gold3d-viewer-header{
  display:flex;align-items:center;justify-content:space-between;
  padding:10px 14px;color:var(--gold-400);font-size:11.5px;font-weight:700;
  letter-spacing:.05em;text-transform:uppercase;
}
.gold3d-viewer-badge{font-size:10.5px;font-weight:600;color:var(--gold-bg);background:rgba(200,153,46,.22);border:1px solid var(--gold-500);border-radius:20px;padding:2px 9px;text-transform:none;letter-spacing:0;}
.gold3d-canvas-wrap{
  position:relative;flex:1;min-height:260px;
  /* A light "stage" for the actual model/photo to sit on, distinct from
     the dark gold-accented frame around it (header/controls bar) — keeps
     a real .glb model's own dark-toned material clearly visible while
     rotating, instead of blending into what used to be a dark backdrop. */
  background:#f4f6fa;
  opacity:0;transform:scale(.97);
  transition:opacity .35s ease, transform .35s ease;
}
.gold3d-canvas-wrap.gold3d-visible{opacity:1;transform:scale(1);}
.gold3d-canvas-wrap canvas{display:block;width:100%;height:100%;cursor:grab;}
.gold3d-canvas-wrap canvas:active{cursor:grabbing;}
.gold3d-overlay-msg{
  position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:8px;color:var(--gold-600);font-size:13px;text-align:center;padding:20px;
}
.gold3d-controls-bar{
  display:flex;align-items:center;justify-content:center;gap:14px;
  padding:8px 12px;background:rgba(10,18,32,.55);border-top:1px solid rgba(200,153,46,.35);
}
.gold3d-ctrl-btn{
  background:none;border:none;color:var(--gold-400);font-size:12.6px;font-weight:600;
  cursor:pointer;display:flex;align-items:center;gap:4px;padding:4px 8px;border-radius:6px;
  transition:background .12s ease,color .12s ease;
}
.gold3d-ctrl-btn:hover{background:rgba(200,153,46,.15);color:var(--gold-bg);}
.gold3d-ctrl-btn.active{color:var(--gold-500);background:rgba(200,153,46,.22);}
.gold3d-photo-tabs{display:flex;gap:6px;flex-wrap:wrap;margin-top:10px;}
.gold3d-photo-tab{
  padding:6px 12px;border-radius:20px;font-size:12px;font-weight:600;cursor:pointer;
  border:1px solid var(--gold-500);color:var(--gold-600);background:var(--gold-bg);
}
.gold3d-photo-tab.active{background:var(--gold-500);color:#1a1305;}
@media (prefers-reduced-motion: reduce){
  .gold3d-canvas-wrap{transition:none;}
}

/* Change 2 — always-visible Uploaded Photos preview strip at the bottom
   of the Gold form, independent of whether "3D Asset View" itself is
   expanded. Hidden entirely (via inline style, toggled in JS) when no
   Front/Back/Side/Alternate photo has been uploaded yet. */
.gold3d-photo-preview-strip{margin-top:14px;}
.gold3d-photo-preview-title{font-size:11.5px;font-weight:700;color:var(--text-400);text-transform:uppercase;letter-spacing:.03em;margin-bottom:8px;}
.gold3d-photo-preview-row{display:flex;gap:12px;flex-wrap:wrap;}
.gold3d-photo-preview-item{width:120px;border:1px solid var(--border);border-radius:10px;padding:8px;background:var(--card);}
.gold3d-photo-preview-item img{width:100%;height:80px;object-fit:cover;border-radius:6px;background:var(--subtle-bg);}
.gold3d-photo-preview-label{font-size:11px;font-weight:600;color:var(--text-600);margin-top:6px;text-align:center;}
.gold3d-photo-preview-actions{display:flex;gap:4px;justify-content:center;margin-top:6px;}
.gold3d-photo-preview-actions .btn{padding:3px 8px;font-size:11px;}
.link{color:var(--blue);cursor:pointer;text-decoration:none;font-weight:600;}
.link:hover{text-decoration:underline;}
.row-expand-toggle{
  display:inline-flex;align-items:center;justify-content:center;
  width:18px;height:18px;flex-shrink:0;margin-top:1px;
  cursor:pointer;user-select:none;
  color:var(--text-400);font-size:12px;font-weight:700;
  border-radius:4px;
}
.row-expand-toggle:hover{background:var(--subtle-bg);color:var(--text-900);}
.view-mode-toolbar{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  margin-bottom:14px;padding-bottom:12px;border-bottom:1px solid var(--border);
}
/* Bank Locker "kept in a locker" padlock (linkage.js renderLockerMark).
   Sits beside a record's name on Asset Management / Insurance / Document
   Vault. Deliberately quieter than .caution-mark next to it: the caution
   mark is a warning that wants attention, this is a statement of fact, so
   it is smaller, dimmer and gains full opacity only on hover. */
.locker-mark{
  margin-left:6px;font-size:11.5px;line-height:1;
  vertical-align:middle;cursor:default;
  /* inline-block + relative so the gleam below can be positioned against it;
     the small padding keeps overflow:hidden from shaving the glyph. */
  display:inline-block;position:relative;overflow:hidden;padding:1px 2px;
  opacity:.62;transition:opacity .15s ease;
}
.locker-mark:hover{opacity:1;}
/* A gleam crosses the padlock once every 5s, and the glyph itself picks up a
   brief gold glint at the same moment. Both spend ~85% of the cycle at rest -
   this marks a fact about the record, so it should catch the eye in passing
   rather than pulse continuously in a table of twenty rows. */
.locker-mark::after{
  content:"";position:absolute;top:-40%;left:0;
  width:55%;height:180%;pointer-events:none;
  background:linear-gradient(100deg, transparent, rgba(255,255,255,.9), transparent);
  transform:translateX(-220%) rotate(18deg);
}
@keyframes locker-gleam{
  0%,62%{transform:translateX(-220%) rotate(18deg);opacity:0;}
  66%{opacity:1;}
  84%{transform:translateX(320%) rotate(18deg);opacity:0;}
  100%{transform:translateX(320%) rotate(18deg);opacity:0;}
}
@keyframes locker-glint{
  0%,62%,100%{filter:none;}
  70%{filter:drop-shadow(0 0 4px rgba(245,210,122,.95)) brightness(1.22);}
  80%{filter:drop-shadow(0 0 2px rgba(245,210,122,.45)) brightness(1.06);}
}

.caution-mark-wrap{position:relative;display:inline-flex;margin-left:6px;cursor:default;vertical-align:middle;}
/* inline-block so the shake's transform applies at all - transforms are
   ignored on a plain inline box. */
.caution-mark{font-size:13px;display:inline-block;transform-origin:50% 60%;}
/* A short double-take every 5s, not a permanent jitter. Rotation only, with a
   1px sway - enough to catch the eye without moving the icon far enough to
   shift the hover target under the cursor. */
@keyframes caution-shake{
  0%,84%,100%{transform:rotate(0deg) translateX(0);}
  86%{transform:rotate(-10deg) translateX(-1px);}
  88.5%{transform:rotate(9deg) translateX(1px);}
  91%{transform:rotate(-7deg) translateX(-1px);}
  93.5%{transform:rotate(5deg) translateX(1px);}
  96%{transform:rotate(-3deg) translateX(0);}
  98%{transform:rotate(1.5deg);}
}
@media (prefers-reduced-motion: no-preference){
  .locker-mark::after{animation:locker-gleam 5s ease-in-out infinite;}
  .locker-mark{animation:locker-glint 5s ease-in-out infinite;}
  .caution-mark{animation:caution-shake 5s ease-in-out infinite;}
  /* Staggered by row so a column of marks does not glint or shake in unison,
     which reads as a machine rather than a flourish. */
  tr:nth-child(even) .locker-mark, tr:nth-child(even) .locker-mark::after{animation-delay:1.6s;}
  tr:nth-child(3n) .locker-mark, tr:nth-child(3n) .locker-mark::after{animation-delay:3.1s;}
  tr:nth-child(even) .caution-mark{animation-delay:2.2s;}
  tr:nth-child(3n) .caution-mark{animation-delay:3.7s;}
}
@media (prefers-reduced-motion: reduce){
  .locker-mark::after{display:none;}
  .locker-mark,.caution-mark{animation:none;}
}
/* Positioned via JS (LinkagePage.showCautionTip) as position:fixed against the
   viewport, not the row — a table row is too short for a below-anchored
   absolute tooltip to fit without spilling into the next row, and .table-wrap's
   overflow-x:auto/overflow-y:hidden clips absolute descendants that spill past
   its box (invisible tooltip on the last row). Fixed positioning escapes both. */
.caution-tip{
  display:none;position:fixed;z-index:500;
  background:#2b2110;color:#fff;padding:8px 10px;border-radius:8px;
  font-size:12px;font-weight:500;line-height:1.4;white-space:nowrap;
  box-shadow:var(--shadow-md);border:1px solid rgba(255,255,255,.12);
}
.caution-tip .link{color:#ffd77a;}

/* Family Calendar month grid */
.cal-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:4px;}
.cal-grid-header{margin-bottom:6px;}
.cal-dayname{text-align:center;font-size:11.5px;font-weight:700;color:var(--text-400);text-transform:uppercase;padding:4px 0;}
.cal-cell{position:relative;min-height:64px;border:1px solid var(--border);border-radius:8px;padding:6px;background:var(--card);}
.cal-cell-other{background:var(--subtle-bg);}
.cal-cell-other .cal-daynum{opacity:.45;}
.cal-cell-today{border-color:var(--gold-500);border-width:2px;}
.cal-daynum{font-size:12.5px;font-weight:600;color:var(--text-900);}
.cal-icons{margin-top:6px;font-size:15px;line-height:1;}
.cal-cell-marked{cursor:default;}
.cal-tip{
  display:none;position:absolute;left:0;top:100%;margin-top:4px;z-index:60;
  background:#2b2110;color:#fff;padding:8px 10px;border-radius:8px;
  font-size:12px;font-weight:500;line-height:1.6;white-space:nowrap;box-shadow:var(--shadow-md);
}
.cal-cell-marked:hover .cal-tip{display:block;}
/* Rightmost two columns (Fri/Sat) would overflow past the page edge with
   the default left:0 anchor, since white-space:nowrap tooltips can run
   wide (a day with both a birthday and a custom event) — flip those to
   anchor from the right instead. */
.cal-grid > div:nth-child(7n) .cal-tip,
.cal-grid > div:nth-child(7n-1) .cal-tip{left:auto;right:0;}
@media(max-width:768px){
  .cal-cell{min-height:46px;padding:4px;}
  .cal-daynum{font-size:11px;}
  .cal-icons{font-size:12px;}
  .cal-dayname{font-size:10px;}
}
.kicker{font-size:11.5px;color:var(--text-400);font-weight:700;text-transform:uppercase;letter-spacing:.05em;}
.disclaimer-box{
  background:#fbfaf6;border:1px solid #ecdfb9;color:#6b5a24;
  border-radius:8px;padding:10px 14px;font-size:12.2px;margin-top:16px;
}

/* ---------- SIP Growth Comparison donut cards — brighten + lift on hover
   (SIP Tracker tab), same lift/shadow language as .tree-card:hover. ---------- */
.sipg-donut-card{cursor:pointer;transition:box-shadow .15s ease, transform .15s ease, filter .15s ease;}
.sipg-donut-card:hover{box-shadow:var(--shadow-md);transform:translateY(-2px);filter:brightness(1.08);}

/* ---------- Family Expense Dashboard intro (Family_Expense_v3 §1) ---------- */
.famexp-hero{
  background:linear-gradient(135deg, var(--gold-bg), #fff);
  border:1px solid rgba(200,153,46,.3);
  border-left:3px solid var(--gold-500);
  border-radius:var(--radius);
  padding:18px 22px;
  margin-bottom:16px;
}
.famexp-hero-title{margin:0;font-size:21px;font-weight:800;color:var(--text-900);letter-spacing:.01em;}
.famexp-hero-sub{margin:5px 0 0;font-size:12.8px;color:var(--text-400);max-width:560px;}
@media(max-width:640px){
  .famexp-hero{padding:14px 16px;}
  .famexp-hero-title{font-size:17px;}
  .famexp-hero-sub{font-size:12px;}
}

/* ---------- Homepage dashboard components ---------- */
.hero-greeting{
  /* Still painted, not decoration: this gradient is the card's ground before
     the backdrop clip has decoded, and the whole card if it never does. */
  background:linear-gradient(135deg, var(--navy-950), var(--navy-800));
  border-radius:var(--radius);
  color:#fff;
  margin-bottom:16px;
  position:relative;
  overflow:hidden;
}
.hero-greeting-content{
  position:relative;z-index:2;
  padding:22px 26px;
  display:flex;align-items:center;justify-content:space-between;gap:20px;
}
/* ---- Welcome card backdrop (assets/welcome-card-bg.mp4) ----
   Gold arrows climbing over a bar chart on near-black navy, which is the
   card's own palette already - so it goes full-bleed behind both the
   greeting and the mascot rather than being penned into one half the way
   the category heroes' clips are (.cat-hero-clip): there is no painted
   ground to meet here, and no seam to hide.

   The card is roughly 6.6:1 while the clip is 16:9, so cover shows only a
   ~27% horizontal band of it, and which band is the whole decision. 30%
   down was picked by rendering the candidate crops across all ten seconds:
   it keeps the left third dark and quiet under the greeting while the
   arrows climb through the middle and finish behind the mascot, and it is
   near-empty at both the first and last second, so the loop turns over
   without a jump. Lower crops (45-65%) put the bar chart's bright base and
   a large glowing ring directly behind the text instead. */
.hero-bg-video{
  position:absolute;inset:0;z-index:0;
  width:100%;height:100%;
  object-fit:cover;object-position:center 30%;
  display:block;
  pointer-events:none;
}
/* Scrim, weighted to the left where the greeting sits. It clears almost
   completely by the right third so the arrows still read as the feature -
   the text needs the cover, the mascot does not. */
.hero-greeting-overlay{
  position:absolute;inset:0;z-index:1;
  pointer-events:none;
  background:
    linear-gradient(90deg, rgba(6,15,34,.90) 0%, rgba(6,15,34,.78) 26%, rgba(6,15,34,.44) 52%, rgba(6,15,34,.16) 74%, rgba(6,15,34,.06) 100%),
    linear-gradient(180deg, rgba(6,15,34,.30) 0%, rgba(6,15,34,0) 40%, rgba(6,15,34,.34) 100%);
}
/* An animated backdrop is motion for its own sake - the one thing a reduced
   motion preference is asking not to be given. The gradient ground above is
   already a complete card on its own, so the clip simply doesn't load. */
@media (prefers-reduced-motion: reduce){
  .hero-bg-video{display:none;}
  .hero-greeting-overlay{display:none;}
}
/* Below 640px the mascot is hidden and the text runs the full width, so it
   needs cover across the whole card rather than only the left half. */
@media(max-width:640px){
  .hero-greeting-content{padding:18px 18px;}
  .hero-greeting-overlay{
    background:linear-gradient(90deg, rgba(6,15,34,.90) 0%, rgba(6,15,34,.84) 55%, rgba(6,15,34,.64) 100%);
  }
}
.hero-text{min-width:0;position:relative;z-index:1;}
.hero-greeting h2{margin:0;font-size:20px;font-weight:800;}
.hero-greeting p{margin:6px 0 0;font-size:13.2px;color:#c3ccdc;max-width:560px;}
.hero-illustration{
  flex-shrink:0;position:relative;z-index:1;
  width:140px;height:140px;border-radius:50%;
  /* Opaque navy, not the translucent white it used to be: the clip behind
     the card now shows through anything translucent, and the mascot's own
     navy ground reads as a smudge over moving arrows. The ring and shadow
     do the separating the flat header used to not need. */
  background:var(--navy-950);
  box-shadow:0 0 0 1px rgba(220,174,74,.35), 0 8px 22px rgba(0,0,0,.45);
  overflow:hidden;
}
@media(max-width:640px){.hero-illustration{display:none;}}
/* The generated boy/girl clips are 1280x720 with the character centered
   and a small Gemini/Veo watermark tucked in the bottom-right corner.
   object-fit:cover on this square box crops the video down to a centered
   720px-wide vertical strip (full height kept, left/right edges cut)
   before the circular border-radius clips the remaining corners - the
   watermark sits outside both crops so it never appears. object-position
   41% (measured directly off real video frames via canvas pixel-scan at
   the wave's peak, both genders match closely) shifts that strip left
   just enough to keep the raised waving hand in frame alongside the head
   and shoulders - centering at 50% cropped the hand out entirely, and
   adding any extra zoom past plain cover re-crops it out again, so
   neither is used here despite looking like an obvious next step. Paused
   on its poster frame (a still of the resting pose) until app.js's
   playHeroWaveVideo() plays it - see dashboard.js's
   heroCharacterIllustration() for why that's JS-driven rather than a CSS
   loop. */
.hero-character-video{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:41% 50%;
}
/* "Hello, {name} 👋" wave icon - shakes a few times, then rests for the
   remainder of every 30s cycle. */
.hello-wave-icon{
  display:inline-block;
  animation:hello-shake 30s ease-in-out infinite;
  transform-origin:70% 70%;
}
@keyframes hello-shake{
  0%,10%,100%{transform:rotate(0deg);}
  1%,3%,5%,7%{transform:rotate(18deg);}
  2%,4%,6%,8%{transform:rotate(-12deg);}
}
@media (prefers-reduced-motion: reduce){
  .hello-wave-icon{animation:none;}
}
/* ---------- Batch-update clip cards (Currency Conversion / Vehicle
   Depreciation, dashboard.js updateClipCard()) ----------
   Both were full-width strips of text with a button on the right. They are
   now a matched pair, side by side, each one shaped to the clip it carries:
   the source files are 960x720, so the card is 4:3 and the clip fills it
   with nothing letterboxed and nothing cropped off.

   The clip is the card's whole surface, not a thumbnail beside the copy -
   the card keeps its accent top border and its own padding moves inward to
   .dash-clip-body, which is why .card's padding is zeroed here rather than
   the class being dropped. */
/* Each card is sized to the Total Assets + Total Liabilities cards below it
   taken together, so the pair spans the content width and its inner edge
   falls exactly on the seam between Total Liabilities and Net Worth. The
   stat cards are a .grid-4 sharing this container and this 16px gap, so two
   of their columns plus the gap between them is 2*(W-48)/4 + 16 = W/2 - 8,
   which is precisely what a .grid-2 column is here - the two rows line up at
   any container width without either being told a pixel size.

   The card is a wide band rather than the clips' own 4:3, so the height
   stays low at that width; the clips are cover-fitted, so they lose their
   top and bottom to the crop rather than distorting. Which band survives is
   set per card by --clip-focus below. */
.dash-clip-row{
  grid-template-columns:minmax(0,1fr) minmax(0,1fr);
}
@media(max-width:640px){
  .dash-clip-row{grid-template-columns:1fr;}
}
.dash-clip-card{
  position:relative;
  overflow:hidden;
  padding:0;
  aspect-ratio:16 / 5;
  display:flex;
}
.dash-clip-video{
  position:absolute;inset:0;z-index:0;
  width:100%;height:100%;
  /* --clip-focus is which horizontal band of the 4:3 frame the wide card
     keeps; dashboard.js sets it per card, since the two clips put their
     subject at different heights. Defaults to the middle. */
  object-fit:cover;object-position:center var(--clip-focus, 50%);
  display:block;
  /* Shows through only in the moment between the poster being cleared by
     play() and the first decoded frame arriving. Matching the clips' own
     near-black ground keeps that moment from reading as a white flash. */
  background:#0a1220;
  pointer-events:none;
}
/* Weighted left, not top-and-bottom. Every piece of copy on these cards -
   title, status lines, button - sits in the left third, and on a band this
   short a vertical scrim's dark top and dark bottom meet in the middle and
   veil the whole clip. Running it across instead covers the text side
   properly and lets the right half of the frame play in the clear, which on
   both clips is where the subject is. */
.dash-clip-scrim{
  position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(90deg, rgba(6,15,34,.90) 0%, rgba(6,15,34,.80) 30%, rgba(6,15,34,.48) 56%, rgba(6,15,34,.18) 80%, rgba(6,15,34,.08) 100%);
}
.dash-clip-body{
  position:relative;z-index:2;
  flex:1;min-width:0;
  display:flex;flex-direction:column;justify-content:space-between;gap:12px;
  padding:16px 18px;
}
.dash-clip-head{min-width:0;}
/* The card's own text colors are set for a paper card; over a clip they need
   to be light, and to carry a shadow so they hold up over whichever frame
   happens to be underneath. */
.dash-clip-card .card-title{color:#fff;font-size:15px;text-shadow:0 2px 10px rgba(0,0,0,.6);}
.dash-clip-card .card-sub{color:rgba(255,255,255,.84);font-size:12px;text-shadow:0 1px 6px rgba(0,0,0,.65);}
/* Centred on the card's foot rather than tucked under the copy. The scrim
   is weighted left for the text, so the button carries its own gold ground
   out here over the clearer half of the clip. */
.dash-clip-btn{align-self:center;}
/* Single column below 640px: the band would be only ~90px tall across a
   phone, too short to hold the title, the status lines and the button, so
   it deepens to 2:1 there. */
@media(max-width:640px){
  .dash-clip-card{aspect-ratio:2 / 1;}
}
/* ---------- Shine letter headings ----------
   Used by the dashboard's "Hello, {name} 👋", the Family Balance Sheet's top
   card title and the Travel Kit hero title; storage.js's shineLetters()
   builds the per-letter markup all three wrap in .shine-letters.

   The layout half is the sidebar section labels' (.nav-group-title): the
   same letter-spacing and weight, and the same nav-group-shimmer keyframes
   sweeping a highlight across the text - so the headings and the nav read as
   one family. Two things are deliberately not taken from there. Size: each
   host keeps its own (the hero's 20px h2, the card title's own, the Travel
   Kit hero's 32px) rather than the sidebar's 12px. And case: the sidebar
   shouts "OVERVIEW", while these headings stay in the proper case their
   source strings are already written in, so text-transform is held off
   rather than left to inherit from a host that might set it.
   The colour half is a separate modifier, because these headings do not all
   want gold - see .shine-letters-gold / .shine-letters-white below.
   On top of that, each letter scales up in turn, once every 15s - twice as
   often as the dashboard's 👋 waves, so every second ripple runs alongside
   the wave and the one between it runs alone. Both still spend most of the
   cycle at rest rather than fidgeting continuously; the shine is the
   exception, running its own faster loop unbroken the way it does in the
   nav. */
.shine-letters{
  display:inline-block;
  text-transform:none;
  letter-spacing:.08em;
  font-weight:700;
  /* Stated rather than inherited, unlike .nav-group-title which simply takes
     the body font. The Travel Kit hero forces Georgia on .tk-hero-title, so
     an inheriting rule would have come out serif there and broken step with
     the other two headings. */
  font-family:'Inter',-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
}
/* Each letter carries its own clipped gradient, rather than the heading
   carrying one that gets clipped to the glyphs inside it the way
   .nav-group-title does. That looks like the roundabout way round, and it is
   deliberate: a transformed child is painted into its own layer, so the
   parent's text-clipped background never reaches it and the pop below turned
   the entire heading invisible. Clipping per letter keeps every glyph
   painting its own colour while it scales.
   The sweep is put back together out of those per-letter gradients by the
   staggered nav-group-shimmer delay shineLetters() writes inline: each letter
   runs the same 3.2s loop a fraction of a second behind the one before it,
   so the bright band still crosses the heading left to right instead of all
   the letters flashing at once. That stagger is why this needs no JS
   measurement of glyph offsets.
   Everything here is mechanism only - the gradient itself comes from a
   colour modifier, so a heading can take the motion without taking gold. */
/* One word, as one unbreakable unit. shineLetters() wraps each word's letters
   in this; without it the per-letter inline-blocks below would each be a legal
   line-break point, and a heading on a narrow screen split mid-word. */
.shine-word{display:inline-block;white-space:nowrap;}
.shine-letter{
  display:inline-block;
  transform-origin:50% 90%;
  background-size:220% auto;
  -webkit-background-clip:text;background-clip:text;
  -webkit-text-fill-color:transparent;color:transparent;
  /* Order matters: shineLetters() writes the two animation-delays in this
     same order, pop first, shine second. */
  animation:shine-letter-pop 15s ease-in-out infinite,
            nav-group-shimmer 3.2s linear infinite;
}
/* Gold: the sidebar section labels' exact gradient and tokens. */
.shine-letters-gold .shine-letter{
  background-image:linear-gradient(90deg, var(--gold-600) 0%, var(--gold-400) 22%, #fff6d8 50%, var(--gold-400) 78%, var(--gold-600) 100%);
}
/* White: for headings that were already white and are keeping that - the
   Family Balance Sheet card title and the Travel Kit hero. A highlight band
   cannot be brighter than white, so the sweep is built the other way round,
   dimming the run either side of it and letting full white be the crest.
   The dip is deliberately shallow: enough for the shine to travel, not
   enough to read as grey text. */
.shine-letters-white .shine-letter{
  background-image:linear-gradient(90deg, rgba(255,255,255,.72) 0%, rgba(255,255,255,.86) 22%, #fff 50%, rgba(255,255,255,.86) 78%, rgba(255,255,255,.72) 100%);
}

/* ---------- The pop on its own, without the shine ----------
   The Business Details Hub heading's movement - each letter growing and
   settling in turn, once every 15s - with none of its colour treatment.

   .shine-letters pairs that pop with a travelling gradient clipped to the
   glyphs, so the letters are painted by the gradient and nothing else. That
   works on the two dark hero banners it was built for and nowhere else: on
   the headings this is applied to (Insurance, Bank Locker, the two vaults,
   the Family & Estate tabs, the calculator tabs) it would wash them out, and
   on a light card a near-white fill makes them vanish outright. So this
   modifier takes the motion and leaves the colour to the page.

   Built on the same per-letter markup shineLetters() already emits, so the
   two treatments cannot drift apart. That helper writes two animation-delays
   (pop first, shine second); with one animation declared here the second is
   ignored, which is exactly what keeps the stagger identical between them. */
.pop-letters{display:inline-block;}
.pop-letters .shine-letter{
  display:inline-block;
  transform-origin:50% 90%;
  /* Undoes .shine-letter's clipped-gradient fill. text-fill-color has to be
     restored explicitly - it is what .shine-letter sets to transparent, and
     resetting `color` alone would leave every glyph invisible. */
  background:none;
  -webkit-background-clip:border-box;background-clip:border-box;
  -webkit-text-fill-color:currentColor;
  color:inherit;
  animation:shine-letter-pop 15s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce){
  .pop-letters .shine-letter{animation:none;}
}
/* Percentages are of the 15s cycle above, not of the pop - so a letter's
   grow-and-shrink runs 1% -> 2.67%, i.e. peaks at 150ms and is back down by
   400ms. 1% is 150ms here; retime these if the 15s ever moves. */
@keyframes shine-letter-pop{
  0%,2.67%,100%{transform:scale(1);}
  1%{transform:scale(1.5);}
}
@media (prefers-reduced-motion: reduce){
  /* Drops both the pop and the sweep, but keeps the colour: same treatment
     .nav-group-title gets, parked where the gradient reads brightest. */
  .shine-letter{animation:none;background-position:35% center;}
}

.quick-actions{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:16px;}

.member-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:14px;}
.member-card{
  border:1px solid var(--border);
  border-radius:var(--radius);
  padding:16px 14px;
  text-align:center;
  background:var(--card);
}
.member-card .avatar-pastel{margin:0 auto 10px;}
/* Illustrated role portrait (or the member's uploaded photo) - same treatment
   as the Family Tree tab's .tc-avatar, just sized for this smaller card. */
.member-card .m-avatar{
  width:56px;height:56px;border-radius:50%;overflow:hidden;
  margin:0 auto 10px;
  background:var(--subtle-bg);
  display:flex;align-items:center;justify-content:center;
}
.member-card .m-avatar img{width:100%;height:100%;object-fit:cover;background:var(--subtle-bg);}
.member-card .m-name{font-weight:700;font-size:13.6px;color:var(--text-900);}
.member-card .m-rel{font-size:11.8px;color:var(--text-400);margin-top:2px;}
.member-card .m-age{font-size:11.8px;color:var(--text-400);margin-top:2px;}
.member-card .m-tag{margin-top:8px;}

.liability-card{
  border:1px solid var(--border);
  border-radius:var(--radius);
  padding:15px 16px;
  background:var(--card);
}
.liability-card .l-head{display:flex;align-items:center;gap:8px;}
.liability-card .l-icon{font-size:17px;}
.liability-card .l-name{font-weight:700;font-size:13.6px;color:var(--text-900);}
.liability-card .l-sub{font-size:11.5px;color:var(--text-400);}
.liability-card .l-amount{font-size:19px;font-weight:800;color:var(--red);margin-top:10px;}
.liability-card .l-meta{display:flex;justify-content:space-between;font-size:11.5px;color:var(--text-400);margin-top:8px;margin-bottom:5px;}

.dropzone{
  border:1.5px dashed var(--border);
  border-radius:var(--radius);
  padding:28px 20px;
  text-align:center;
  color:var(--text-400);
  cursor:pointer;
  transition:border-color .12s ease, background .12s ease;
}
.dropzone:hover{border-color:var(--gold-500);background:var(--gold-bg);}
.dropzone .dz-icon{font-size:26px;margin-bottom:6px;}
.dropzone .dz-title{font-weight:700;font-size:13px;color:var(--text-600);}
.dropzone .dz-sub{font-size:11.5px;margin-top:2px;}

/* Charts */
.chart-empty{padding:36px 10px;text-align:center;color:var(--text-400);font-size:12.6px;}
.donut-wrap{display:flex;align-items:center;gap:22px;flex-wrap:wrap;min-width:0;max-width:100%;}
/* The donut used to be a hard-coded 150px with flex-shrink:0, so it could
   never actually shrink when its column got narrower - at higher browser
   zoom (or any narrow window) it just overflowed straight past the card's
   edge into whatever sat next to it instead of resizing. width:min(150px,
   100%) + aspect-ratio keeps the circle's proportions while letting it
   shrink to whatever room the card actually has; flex-shrink:1 lets the
   flex algorithm take space from it (not just the legend) once both are
   already at their minimums; min-width keeps it from shrinking into an
   illegibly tiny ring. .donut-center's inset is a percentage of the
   chart's own size for the same reason - a fixed 20px inset would leave a
   disproportionately thick ring once the chart itself shrinks. */
.donut-chart{
  width:min(150px, 100%);aspect-ratio:1/1;border-radius:50%;
  position:relative;flex-shrink:1;min-width:90px;
}
.donut-center{
  position:absolute;inset:13.3%;
  background:var(--card);border-radius:50%;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  text-align:center;
}
.donut-center-value{font-size:14px;font-weight:800;color:var(--text-900);}
.donut-center-label{font-size:10px;color:var(--text-400);text-transform:uppercase;letter-spacing:.04em;}
/* Overall Changes_v1 §2 — a hard min-width floor here was the other half
   of the overflow bug: even after the grid track (above) could shrink,
   this forced the legend column itself to stay >=140px wide, so at high
   zoom the donut + this + the 22px gap could still exceed the card's
   available width. min-width:0 lets it shrink freely; flex-wrap:wrap on
   .donut-wrap above stacks the legend under the chart (instead of
   clipping/forcing width) once there truly isn't room side-by-side. */
.donut-legend{display:flex;flex-direction:column;gap:9px;font-size:12.6px;flex:1 1 140px;min-width:0;max-width:100%;}
.donut-legend-item{display:flex;align-items:center;gap:8px;min-width:0;overflow-wrap:break-word;}
.donut-dot{width:9px;height:9px;border-radius:50%;display:inline-block;flex-shrink:0;}

.bar-chart{display:flex;align-items:flex-end;gap:10px;height:140px;padding-top:10px;}
.bar-chart-col{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;height:100%;gap:6px;}
.bar-chart-bar{width:100%;max-width:34px;background:var(--blue-bg);border-radius:6px 6px 0 0;transition:height .2s ease;}
.bar-chart-bar-last{background:var(--gold-500);}
.bar-chart-label{font-size:10.6px;color:var(--text-400);}

.gbar-chart{display:flex;align-items:flex-end;gap:8px;height:160px;padding-top:10px;overflow-x:auto;}
.gbar-col{flex:1;min-width:34px;display:flex;flex-direction:column;align-items:center;justify-content:flex-end;height:100%;gap:6px;}
.gbar-pair{display:flex;align-items:flex-end;gap:3px;width:100%;height:100%;justify-content:center;}
.gbar-bar{width:11px;max-width:14px;border-radius:4px 4px 0 0;transition:height .2s ease;}
.gbar-label{font-size:10px;color:var(--text-400);white-space:nowrap;}
.gbar-legend{display:flex;gap:16px;justify-content:center;margin-top:10px;font-size:12px;color:var(--text-600);}
.gbar-legend-item{display:flex;align-items:center;gap:6px;}
.gbar-dot{width:9px;height:9px;border-radius:3px;display:inline-block;}

.hbar-chart{display:flex;flex-direction:column;gap:12px;}
.hbar-row{display:grid;grid-template-columns:120px 1fr 90px;align-items:center;gap:10px;}
.hbar-label{font-size:12.4px;font-weight:600;color:var(--text-600);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.hbar-track{background:var(--blue-bg);border-radius:6px;height:14px;overflow:hidden;}
.hbar-fill{height:100%;background:var(--blue);border-radius:6px;transition:width .2s ease;}
.hbar-value{font-size:12.4px;font-weight:700;text-align:right;color:var(--text-900);}

.line-chart-wrap{width:100%;}
.line-chart-svg{width:100%;height:140px;display:block;}
.line-chart-area{fill:rgba(200,153,46,.14);stroke:none;}
.line-chart-line{fill:none;stroke:var(--gold-500);stroke-width:1.6;vector-effect:non-scaling-stroke;}
.line-chart-dot{fill:var(--gold-500);}
/* Sits directly under .line-chart-dot and shares its centre - the halo that
   pulses out of the latest data point on hover (see the hover block further
   down). transform-box/origin make scale() grow from the circle's own centre
   rather than the SVG's 0,0. */
.line-chart-dot-pulse{fill:var(--gold-500);opacity:0;transform-box:fill-box;transform-origin:center;}
.line-chart-footer{display:flex;justify-content:space-between;font-size:11px;color:var(--text-400);margin-top:6px;}

/* ---------- Net Worth by Month: headline figure + month-on-month change ----
   The card previously showed only an unlabelled area shape and two month
   names, with no value anywhere on it. */
.nw-summary{display:flex;align-items:flex-end;justify-content:space-between;gap:10px;flex-wrap:wrap;margin:0 0 10px;}
.nw-current-label{font-size:10.5px;text-transform:uppercase;letter-spacing:.05em;color:var(--text-400);}
.nw-current-value{font-size:20px;font-weight:800;color:var(--text-900);line-height:1.15;margin-top:2px;}
.nw-delta{
  display:inline-flex;align-items:center;gap:5px;
  font-size:11.5px;font-weight:700;
  padding:3px 9px;border-radius:999px;white-space:nowrap;
}
.nw-delta-arrow{font-size:9px;}
.nw-delta-since{font-weight:600;opacity:.72;}
.nw-up{color:var(--green);background:var(--green-bg);}
.nw-down{color:var(--red);background:var(--red-bg);}
.nw-flat{color:var(--text-600);background:var(--subtle-bg);}
/* Figure and pill slide up as the card reveals, the pill just behind it. */
.dashboard-chart-card .nw-current-value{animation:nw-value-in .55s cubic-bezier(0.22,1,0.36,1) both;}
.dashboard-chart-card .nw-delta{animation:nw-value-in .55s cubic-bezier(0.22,1,0.36,1) .12s both;}
@keyframes nw-value-in{
  0%{opacity:0;transform:translateY(7px);}
  100%{opacity:1;transform:translateY(0);}
}

/* The latest month's point marks "you are here" on the line, so it pulses
   continuously rather than only on hover - scoped to .dashboard-chart-card so
   renderLineChart()'s other callers (EMI schedule, financial calculator) keep
   a plain static dot. Hover just speeds it up. */
/* ---------- Trend-line comet ----------
   A shooting star running along the line from the previous month to the
   current one: a bright head with a fading streak behind it, same idea as
   .stat-card-star's comet but built from a travelling stroke dash instead of
   an offset-path rider. That matters here - this SVG is
   preserveAspectRatio="none", so anything positioned along the path (a
   rotated arrowhead, an offset-path element) would be stretched by the
   chart's non-uniform scale. A dash rides the path itself, so it stays true
   at any aspect ratio and follows every segment when there are more months.

   pathLength="100" (set in charts.js) normalises the dash maths to 0-100
   regardless of the line's real length, so these offsets hold whatever the
   data does. Head spans [x, x+3]; tail spans [x-15, x+3], i.e. 18 long and
   ending at the head - hence the tail's offset running 15 ahead of the
   head's. */
/* Deliberately NOT vector-effect:non-scaling-stroke (which .line-chart-line
   does use). With it, the dash pattern resolves in screen pixels instead of
   the pathLength="100" units, so the 203-unit period repeated twice along the
   rendered line and TWO beads flew instead of one. Without it the dash maths
   stays in normalised path units and exactly one bead is ever on the line;
   the trade is that stroke width is scaled by the chart's non-uniform
   viewBox, which just makes the streak read as slightly elongated along its
   direction of travel - the same distortion .line-chart-dot already has. */
.line-chart-comet-head,
.line-chart-comet-tail{
  fill:none;
  stroke-linecap:round;
  pointer-events:none;
  opacity:0;
}
/* --amber, not a near-white: the line sits on a pale cream area fill, where a
   white-hot head reads as a GAP erasing the line rather than a bright streak.
   --amber is deeper and more saturated than the line's --gold-500 in light
   mode (#b8790a vs #c8992e) and lighter than the ground in dark mode
   (#e0a83f), so the bead pops either way. */
/* Widths are in viewBox units now (see above), so they render ~1.4x
   vertically - kept small to suit that. */
.line-chart-comet-head{
  stroke:var(--amber);stroke-width:2.6;
  stroke-dasharray:3 200;
  filter:drop-shadow(0 0 5px var(--amber));
}
.line-chart-comet-tail{
  stroke:var(--amber);stroke-width:1.7;
  stroke-dasharray:18 200;
}
.dashboard-chart-card .line-chart-comet-head{opacity:1;animation:nw-comet-head 2.6s linear infinite;}
.dashboard-chart-card .line-chart-comet-tail{opacity:.45;animation:nw-comet-tail 2.6s linear infinite;}
@keyframes nw-comet-head{
  0%{stroke-dashoffset:3;}
  100%{stroke-dashoffset:-103;}
}
@keyframes nw-comet-tail{
  0%{stroke-dashoffset:18;}
  100%{stroke-dashoffset:-88;}
}

.dashboard-chart-card .line-chart-dot-pulse{animation:line-chart-pulse 2.4s ease-out infinite;}
.dashboard-chart-card .line-chart-dot{animation:nw-dot-breathe 2.4s ease-in-out infinite;transform-box:fill-box;transform-origin:center;}
.dashboard-chart-card .line-chart-line{stroke-width:2;}
@keyframes nw-dot-breathe{
  0%,100%{opacity:1;transform:scale(1);}
  50%{opacity:.72;transform:scale(1.22);}
}
@media (hover:hover) and (pointer:fine){
  .dashboard-chart-card:hover .line-chart-dot-pulse{animation-duration:1.1s;}
  .dashboard-chart-card:hover .line-chart-dot{animation-duration:1.1s;}
  .dashboard-chart-card:hover .line-chart-comet-head,
  .dashboard-chart-card:hover .line-chart-comet-tail{animation-duration:1.1s;}
}
@media (prefers-reduced-motion: reduce){
  .dashboard-chart-card .nw-current-value,
  .dashboard-chart-card .nw-delta{animation:none;}
  .dashboard-chart-card .line-chart-dot-pulse,
  .dashboard-chart-card .line-chart-dot{animation:none;}
  .line-chart-comet-head,
  .line-chart-comet-tail{animation:none;opacity:0;}
}

/* Wealth Dashboard's Asset Distribution / Liability Distribution / Net
   Worth by Month row — forced to a common height so a donut's legend
   (which can run to 10-12 lines) doesn't make that card taller than its
   siblings; the legend scrolls internally instead of growing the card.
   donut-wrap must override its base align-items:center to flex-start here —
   center-aligning a scrollable overflow child is a known flexbox quirk that
   opens the scroll position already scrolled to the middle of the content,
   clipping an item at both the top and bottom instead of starting at the
   first item. */
/* Overall Changes_v1 §2 — grid items get an implicit min-width:auto
   (content-based) by default, which can still force this card wider than
   its minmax(0,1fr) track even after the track itself can shrink; min-
   width:0 here removes that floor so the card actually shrinks to fit.
   height was a hard 360px here, which looked fine at normal zoom but had
   nowhere to go once .donut-wrap's flex-wrap (below) switches the legend
   from beside the donut to stacked underneath it - at higher browser zoom
   the column gets narrower, side-by-side stops fitting, and the stacked
   legend needs more vertical room than a fixed 360px allows. Since the
   card never clipped its own overflow, that extra content just rendered
   straight through the card's bottom edge into the reminders section
   below it. min-height keeps the exact same 360px look whenever the
   content fits (unchanged at normal zoom), but now lets the card grow
   taller instead of overflowing once it doesn't - CSS Grid rows stretch
   every card in the row to match the tallest one by default, so the other
   two cards grow with it and everything below is simply pushed down,
   never overlapped. */
.dashboard-chart-card{display:flex;flex-direction:column;min-height:360px;min-width:0;}
/* .card + .card's site-wide margin-top:16px (meant for vertically stacked
   cards in normal flow) also matches these three as grid siblings, adding
   an unwanted top margin to the 2nd/3rd card only and knocking them out of
   alignment with the 1st — the grid's own gap:16px already spaces them
   correctly, so drop it here. Needs the .card.dashboard-chart-card double
   class to match .card + .card's specificity and win on source order. */
.card.dashboard-chart-card{margin-top:0;}
/* .card + .card's site-wide margin-top:16px (meant for vertically stacked
   cards in normal flow) also matches the Personal Details / Address
   Details pair as grid siblings, adding an unwanted top margin to the
   2nd card only and knocking it out of alignment with the 1st - the
   grid's own gap:16px already spaces them correctly, so drop it here. */
.card.dashboard-profile-card{margin-top:0;}
.dashboard-chart-card .card-header{flex-shrink:0;}
.dashboard-chart-card .donut-wrap{flex:1;min-height:0;align-items:flex-start;}
/* The legend used to be capped at max-height:100% with its own internal
   scrollbar so it could never grow past the card's old fixed height. Now
   that the card itself can grow (above), the legend is left to render at
   its natural full height instead — the whole point of "bigger the box to
   show the data" is seeing every category without a second, nested
   scroll area hiding some of them. */
.dashboard-chart-card .donut-legend{padding-right:4px;}
.dashboard-chart-card .bar-chart,
.dashboard-chart-card .line-chart-wrap{flex:1;min-height:0;display:flex;flex-direction:column;justify-content:center;}
.dashboard-chart-card .chart-empty{flex:1;min-height:0;display:flex;align-items:center;justify-content:center;padding:0;}

/* ============================================================
   "Personal Details.txt" spec — premium reveal + micro-interaction pass
   for Family Members and Nominee Information (Personal/Address reverted
   back to the earlier two-card layout/animation - see .card-anim-underline
   above). Entrance choreography reuses the site's existing
   IntersectionObserver reveal (initPageReveal() in app.js) rather than a
   second observer: these are plain CSS animations gated behind the
   `.page-reveal-in` class that reveal already adds to each dashboard
   section, so they fire exactly once, right when their section scrolls
   into view, with no extra JS. animateEntranceEffects() in app.js gets
   one small addition (below, see app.js) so .member-card entries inside
   .member-grid get the same left-to-right stagger .stat-card already has.
   ============================================================ */

/* Shared small circular icon badge for a section's own card-header, used
   by Family Members / Nominee Information - reads var(--accent) from the
   existing [class*="card-accent-"] system so it re-colors per section
   for free. */
.dashboard-page [class*="card-accent-"] .section-icon-badge{
  flex-shrink:0;width:38px;height:38px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  background:color-mix(in srgb, var(--accent) 20%, var(--card));
  color:var(--accent);
}
.section-icon-badge svg{width:19px;height:19px;}
.dashboard-page [class*="card-accent-"] .card-header:has(.section-icon-badge){gap:12px;}

/* Family Members - staggered card entrance is handled by
   animateEntranceEffects() (app.js) reusing its existing .stat-card
   stagger for .member-grid > .member-card. Hover/active/tint stay here. */
.member-grid{position:relative;isolation:isolate;}
/* Extremely subtle animated flow behind the cards ("FAMILY → CONNECTION →
   LEGACY") - slow, low-opacity, never reads as a neon/gaming effect. */
.member-grid::before{
  content:'';position:absolute;inset:6px 0;z-index:-1;pointer-events:none;border-radius:var(--radius);
  background:linear-gradient(90deg, transparent, color-mix(in srgb, var(--pink) 14%, transparent) 30%, color-mix(in srgb, var(--purple) 14%, transparent) 60%, transparent);
  background-size:220% 100%;
  animation:family-connection-flow 10s linear infinite;
}
@keyframes family-connection-flow{0%{background-position:0% 0;}100%{background-position:220% 0;}}
.member-card{
  position:relative;overflow:hidden;
  background:linear-gradient(135deg, var(--card), color-mix(in srgb, var(--m-accent, var(--border)) 10%, var(--card)));
  border-color:color-mix(in srgb, var(--m-accent, var(--border)) 28%, var(--border));
}
.member-card::before{
  content:'';position:absolute;left:50%;top:44px;width:70px;height:70px;border-radius:50%;
  background:radial-gradient(circle, color-mix(in srgb, var(--m-accent, var(--accent)) 35%, transparent), transparent 70%);
  transform:translate(-50%,-50%) scale(.6);opacity:0;pointer-events:none;
  transition:opacity .3s ease, transform .3s ease;
}
.member-card-bar{width:26px;height:3px;border-radius:2px;margin:8px auto 0;background:var(--m-accent, var(--accent));}
.member-card .m-rel{transition:color .25s ease;}
@media (hover:hover) and (pointer:fine){
  .member-card:hover{
    transform:translateY(-7px) scale(1.015);
    box-shadow:0 14px 32px rgba(30,50,90,.14);
    border-color:color-mix(in srgb, var(--m-accent, var(--accent)) 55%, var(--border));
  }
  [data-theme="dark"] .member-card:hover{box-shadow:0 14px 32px rgba(0,0,0,.45);}
  .member-card:hover::before{opacity:1;transform:translate(-50%,-50%) scale(1);}
  .member-card:hover .avatar-pastel,
  .member-card:hover .m-avatar{transform:translateY(-2px) scale(1.06);}
  .member-card:hover .m-rel{color:var(--text-600);}
}
.member-card:active{transform:scale(.98);transition-duration:.15s;}

/* ---------- Wealth Dashboard's Family Members roster (.member-tile) ----------
   The boxes are gone and the portrait leads instead: a much larger circle with
   the member's details set beneath it. Everything is written as
   .member-card.member-tile because the base class is shared with the Family
   Tree tab's minors list, which keeps its bordered card - and because these
   have to out-specify the base .member-card rules they are undoing. */
.member-grid-tiles{grid-template-columns:repeat(auto-fill,minmax(150px,1fr));gap:22px 12px;}
.member-card.member-tile{
  border:0;
  background:none;
  box-shadow:none;
  padding:4px 4px 8px;
  overflow:visible;
}
.member-card.member-tile .m-avatar{
  width:112px;height:112px;
  margin:0 auto 9px;
  /* The palette colour moves from the card's border, which no longer exists,
     onto a ring around the portrait - so each member keeps their accent. */
  box-shadow:0 0 0 3px color-mix(in srgb, var(--m-accent, var(--border)) 45%, transparent),
             0 8px 20px rgba(30,50,90,.16);
}
[data-theme="dark"] .member-card.member-tile .m-avatar{
  box-shadow:0 0 0 3px color-mix(in srgb, var(--m-accent, var(--border)) 55%, transparent),
             0 8px 20px rgba(0,0,0,.45);
}
/* The zoom that hides each portrait's baked-in white margin is measured per
   image by app.js's fitAvatarImages(), because the margins vary from 2% to
   13% across the set and an uploaded photo has none at all. Only the origin
   and the easing live here; the scale itself is set inline per element. */
.m-avatar img,
.tc-avatar img{transform-origin:center;transition:transform .2s ease-out;}
/* A highlight travels once around the inside of each portrait, then waits.
   The lap takes ~3s of a 15s cycle and the ring is invisible for the rest,
   so it reads as an occasional glint rather than a spinner. Kept at inset:0
   because .m-avatar clips to its own circle - anything outside would be cut
   off by that same overflow:hidden. */
.member-card.member-tile .m-avatar{position:relative;}
.member-card.member-tile .m-avatar::after{
  content:"";position:absolute;inset:0;border-radius:50%;
  pointer-events:none;
  background:conic-gradient(from 0deg, transparent 0deg, transparent 296deg, rgba(255,255,255,.92) 342deg, transparent 360deg);
  -webkit-mask:radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 5px));
  mask:radial-gradient(farthest-side, transparent calc(100% - 5px), #000 calc(100% - 5px));
  opacity:0;
  animation:fam-ring-sweep 15s linear infinite;
}
/* Offset alternate portraits so the family does not glint in unison, which
   reads as a machine rather than a flourish. */
.member-card.member-tile:nth-child(even) .m-avatar::after{animation-delay:.9s;}
.member-card.member-tile:nth-child(3n) .m-avatar::after{animation-delay:1.8s;}
/* Point at a portrait and its highlight stops waiting between laps: it swaps
   to a continuous fast circuit for as long as the cursor is on the circle,
   then drops back to the every-15-seconds glint. A shorter version of the
   same keyframes would only have sped the lap up and left the idle gap in,
   which still reads as blinking rather than spinning - so hover runs its own
   gapless rotation instead, and the delay is zeroed so it starts the instant
   it is pointed at rather than finishing someone else's stagger. */
@media (hover:hover) and (pointer:fine){
  .member-card.member-tile .m-avatar:hover::after{
    animation:fam-ring-spin 1.5s linear infinite;
    animation-delay:0s;
    opacity:1;
  }
}
@keyframes fam-ring-spin{
  from{transform:rotate(0deg);}
  to{transform:rotate(360deg);}
}
/* The lap takes ~6s of the 15s cycle - it was 3s, which crossed the ring
   faster than the eye could follow it round and read as a blink rather than
   a circuit. The fade in and out are held to the first and last few percent
   so almost all of that time is the highlight actually travelling. */
@keyframes fam-ring-sweep{
  0%{opacity:0;transform:rotate(0deg);}
  2%{opacity:1;transform:rotate(7deg);}
  38%{opacity:1;transform:rotate(353deg);}
  40%,100%{opacity:0;transform:rotate(360deg);}
}
/* A shade sweeping the whole card every 10 seconds. Same shape of idea as the
   ring: the pass takes ~1.8s and the rest of the cycle is the card sitting
   still. */
.fam-members-card{position:relative;overflow:hidden;}
.fam-card-shine{
  position:absolute;top:0;bottom:0;left:0;width:42%;
  z-index:3;pointer-events:none;
  background:linear-gradient(105deg, transparent 0%, rgba(255,255,255,.34) 48%, transparent 100%);
  transform:translateX(-150%);
  animation:fam-card-shine 10s ease-in-out infinite;
}
[data-theme="dark"] .fam-card-shine{
  background:linear-gradient(105deg, transparent 0%, rgba(255,255,255,.13) 48%, transparent 100%);
}
@keyframes fam-card-shine{
  0%,82%{transform:translateX(-150%);}
  100%{transform:translateX(340%);}
}
@media (prefers-reduced-motion: reduce){
  .fam-card-shine{display:none;}
  .member-card.member-tile .m-avatar::after,
  .member-card.member-tile .m-avatar:hover::after{animation:none;opacity:0;}
}
/* The portrait carries the tile, so the text under it is deliberately small
   and tightly set - it is a caption to the face, not a paragraph. The
   margins come down with the sizes; leaving them would have left the lines
   floating apart once the type shrank. */
/* !important only because it is the only way past the dashboard typography
   block's own !important on these two (.dashboard-page .member-card .m-name
   / .m-rel, near the top of this file), which was written for the boxed card
   these tiles replace. Scoped to .member-tile, so the Family Tree tab's
   minors list keeps the sizes that block intends for it. */
.member-card.member-tile .m-name{font-size:13px !important;line-height:1.25;}
.member-card.member-tile .m-rel{font-size:10.6px !important;margin-top:1px;line-height:1.3;}
.member-card.member-tile .m-meta{font-size:10.4px;color:var(--text-400);margin-top:1px;line-height:1.3;}
.member-card.member-tile .m-dob{font-size:10.2px;color:var(--text-400);margin-top:0;line-height:1.3;}
.member-card.member-tile .member-card-bar{margin-top:6px;}
.member-card.member-tile .m-tag{margin-top:6px;}
/* The hover glow was placed for the old 56px avatar; re-centre and grow it
   under the new one. */
.member-card.member-tile::before{top:60px;width:128px;height:128px;}
@media (hover:hover) and (pointer:fine){
  /* A drop shadow and a border colour on a card with neither a box nor a
     border read as a shadow cast by nothing, so the lift is all that is
     kept - the avatar's own scale (inherited from the base rule) carries the
     rest of the response. */
  .member-card.member-tile:hover{
    transform:translateY(-5px);
    box-shadow:none;
    border-color:transparent;
  }
}

/* ---------- Family Head tile in the Wealth Dashboard roster ----------
   Shown to a Family Member only (dashboard.js gates on viewerIsHead), first
   in the grid. Deliberately the same .member-tile as everyone else, so the
   Head reads as the first member of the family rather than a separate widget;
   what marks them out is a gold ring in place of their palette accent, and an
   animated crown over the portrait.

   position:relative goes on the tile, not on .m-avatar: the avatar clips to
   its own circle (overflow:hidden, for the portrait), so a crown positioned
   against it would be cut off by the very element it sits above.
   .member-tile already carries overflow:visible, which is what lets the crown
   overhang the top of the tile. */
.member-card.member-tile{position:relative;}
/* Warmer and brighter than a member's palette ring - same three-layer shape
   as the base .m-avatar shadow so light and dark keep their own weights. */
.member-card.member-tile-head .m-avatar{
  box-shadow:0 0 0 3px color-mix(in srgb, var(--gold-500) 70%, transparent),
             0 0 18px rgba(200,153,46,.30),
             0 8px 20px rgba(30,50,90,.18);
}
[data-theme="dark"] .member-card.member-tile-head .m-avatar{
  box-shadow:0 0 0 3px color-mix(in srgb, var(--gold-400) 78%, transparent),
             0 0 20px rgba(230,193,121,.28),
             0 8px 20px rgba(0,0,0,.5);
}
.member-card.member-tile-head .m-rel{color:var(--gold-600);font-weight:700;}
[data-theme="dark"] .member-card.member-tile-head .m-rel{color:var(--gold-400);}

/* Centred over the top edge of the 112px portrait rather than pinned to a
   corner the way .tc-crown is on the Family Tree: the roster tile has no
   border for a badge to sit on, and the portrait is the whole of its upper
   half, so a crown ON the circle reads as worn rather than stuck. Sized and
   animated to match .tc-crown, so both say the same thing in the same voice. */
.m-crown{
  position:absolute;top:-9px;left:50%;transform:translateX(-50%);
  width:34px;height:30px;z-index:3;pointer-events:none;
}
.m-crown-icon{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-size:23px;line-height:1;
  filter:drop-shadow(0 1px 3px rgba(168,124,20,.6));
  animation:m-crown-float 2.6s ease-in-out infinite;
}
.m-crown-sparkle{
  position:absolute;width:5px;height:5px;border-radius:50%;
  background:#fff6d8;box-shadow:0 0 7px 2px #ffe27a;
  animation:m-crown-twinkle 1.6s ease-in-out infinite;
}
.m-crown-sparkle.s1{top:-1px;left:0;animation-delay:0s;}
.m-crown-sparkle.s2{top:6px;right:-2px;animation-delay:.5s;}
.m-crown-sparkle.s3{bottom:-2px;left:12px;animation-delay:1s;}
@keyframes m-crown-float{
  0%,100%{transform:translateY(0) rotate(-3deg);}
  50%{transform:translateY(-2px) rotate(3deg);}
}
@keyframes m-crown-twinkle{
  0%,100%{opacity:0;transform:scale(.4);}
  50%{opacity:1;transform:scale(1.2);}
}
/* With motion off the crown still marks the Head - it stops moving rather
   than vanishing with its animation, and the sparkles settle lit. */
@media (prefers-reduced-motion: reduce){
  .m-crown-icon{animation:none;}
  .m-crown-sparkle{animation:none;opacity:.9;transform:scale(1);}
}

/* Nominee Information - empty state gets a gentle pop-in, then (only
   here) a slow continuous float, per the spec's one deliberate exception
   to "no continuous animation". */
@keyframes nominee-empty-in{0%{opacity:0;transform:scale(.85);}100%{opacity:1;transform:scale(1);}}
@keyframes nominee-empty-float{0%,100%{transform:translateY(0);}50%{transform:translateY(-3px);}}
@keyframes nominee-text-in{0%{opacity:0;transform:translateY(6px);}100%{opacity:1;transform:translateY(0);}}
.nominee-empty-state{display:flex;flex-direction:column;align-items:center;gap:10px;padding:8px 0;}
.nominee-empty-icon{color:var(--amber);width:40px;height:40px;}
.nominee-empty-icon svg{width:100%;height:100%;}
.mt-16.page-reveal-in .nominee-empty-icon{
  animation:nominee-empty-in .5s cubic-bezier(0.22,1,0.36,1) both, nominee-empty-float 3.5s ease-in-out .5s infinite;
}
.mt-16.page-reveal-in .nominee-empty-text{animation:nominee-text-in .4s ease both;animation-delay:.3s;}

@media (prefers-reduced-motion: reduce){
  .nominee-empty-icon,.nominee-empty-text{animation:none !important;}
  .member-grid::before{animation:none;}
  .member-card{transition:none;}
  .member-card:hover,.member-card:active{transform:none;}
  .member-card:hover::before{opacity:0;}
}

/* ============================================================
   Visualization brief — Site-wide Premium Interaction Layer. Adds
   micro-interactions, hover feedback, entrance/reveal animation and
   chart motion to the EXISTING app everywhere it's used - same layout,
   content, data and functionality, only added motion (see app.js's
   initPageReveal/animateEntranceEffects/animateCountUp/
   initGlobalChartInteractions for the JS half of this). Every color
   below is an existing design token (var(--shadow-md), var(--gold-500),
   etc.) that dark mode already overrides via [data-theme="dark"], so
   this needs no separate dark-mode styling - it just inherits whichever
   theme is active. Hover-only effects are gated to real pointer devices
   (see the (hover:hover) block below) so touch never gets a "stuck"
   hover state and essential controls (e.g. table row actions) stay at
   full visibility by default on touch instead of relying on a hover
   that can never fire there.
   ============================================================ */
.card{transition:box-shadow .2s ease-out, transform .2s ease-out, border-color .2s ease-out;}
/* opacity/transform here double as both the hover-lift transition AND
   the entrance stagger app.js's animateEntranceEffects() plays on first
   reveal (it only ever sets transition-delay/opacity/transform, never
   overrides this transition list itself, so one definition drives both -
   see app.js for why that split matters). */
.stat-card{transition:box-shadow .2s ease-out, transform .3s cubic-bezier(0.16,1,0.3,1), opacity .3s cubic-bezier(0.16,1,0.3,1);}
.avatar-pastel,
.member-card .m-avatar{transition:transform .2s ease-out;}
.member-card{transition:box-shadow .3s cubic-bezier(0.22,1,0.36,1), transform .3s cubic-bezier(0.22,1,0.36,1), opacity .3s cubic-bezier(0.22,1,0.36,1), border-color .25s ease;}
.liability-card{transition:box-shadow .2s ease-out, transform .3s cubic-bezier(0.16,1,0.3,1), opacity .3s cubic-bezier(0.16,1,0.3,1);}
.reminder-card{transition:box-shadow .2s ease-out, transform .2s ease-out;}
.timeline-item .t-label{transition:color .2s ease-out;}
.security-item{transition:box-shadow .2s ease-out, transform .2s ease-out;}
.donut-legend-item{border-radius:6px;padding:3px 6px;margin:-3px -6px;transition:background .18s ease-out, opacity .35s cubic-bezier(0.22,1,0.36,1), transform .35s cubic-bezier(0.22,1,0.36,1);cursor:default;}
.bar-chart-bar{position:relative;}
/* Custom fade-in tooltip (native title's delay reads sluggish next to
   every other 150ms hover response here) - content comes from charts.js's
   data-tooltip attribute on the bar itself. */
.bar-chart-bar::after{
  content:attr(data-tooltip);
  position:absolute;bottom:100%;left:50%;
  transform:translate(-50%,4px);margin-bottom:7px;
  background:var(--navy-700);color:#fff;
  font-size:11px;font-weight:600;white-space:nowrap;
  padding:4px 9px;border-radius:6px;box-shadow:var(--shadow-md);
  opacity:0;pointer-events:none;
  transition:opacity .15s ease-out, transform .15s ease-out;
}
.btn{transition:background .18s ease, box-shadow .18s ease-out, transform .12s ease-out;}
.btn:active{transform:scale(.97);box-shadow:none;}
.btn:focus-visible{outline:2px solid var(--gold-500);outline-offset:2px;}

@media (hover:hover) and (pointer:fine){
  .card:hover{box-shadow:var(--shadow-md);transform:translateY(-2px);border-color:var(--gold-500);}
  /* Wealth Dashboard accent cards (see .card-accent-* above) keep their
     own top-border color on hover instead of flashing gold like a plain
     card, and get a soft glow tinted to match via the same --accent
     custom property + color-mix() (see the definition above). */
  .dashboard-page [class*="card-accent-"]:hover{
    border-top-color:var(--accent);
    box-shadow:0 10px 24px color-mix(in srgb, var(--accent) 32%, transparent);
  }
  /* Per-card motion (see card-anim-* keyframes above) - each overrides
     just the transform (and, for pulse, the box-shadow animation) the
     rule above leaves alone, at higher selector specificity so it always
     wins over the plain .card:hover translateY. */
  .dashboard-page [class*="card-accent-"].card-anim-tilt:hover{
    transform:perspective(700px) rotateX(2deg) rotateY(-2deg) translateY(-3px);
  }
  .dashboard-page [class*="card-accent-"].card-anim-pop:hover{
    transform:scale(1.025) translateY(-2px);
    transition-timing-function:cubic-bezier(.34,1.56,.64,1);
  }
  .dashboard-page [class*="card-accent-"].card-anim-skew:hover{
    transform:skewX(-1.5deg) translateY(-2px);
  }
  .dashboard-page [class*="card-accent-"].card-anim-bounce:hover{
    animation:card-anim-bounce-kf .55s cubic-bezier(.34,1.56,.64,1) both;
  }
  .dashboard-page [class*="card-accent-"].card-anim-wiggle:hover{
    animation:card-anim-wiggle-kf .5s ease-out both;
  }
  .dashboard-page [class*="card-accent-"].card-anim-shake:hover{
    animation:card-anim-shake-kf .45s ease-out both;
  }
  .dashboard-page [class*="card-accent-"].card-anim-grow:hover{
    animation:card-anim-grow-kf .35s cubic-bezier(.22,1,.36,1) both;
  }
  .dashboard-page [class*="card-accent-"].card-anim-pulse:hover{
    animation:card-anim-pulse-kf 1.1s ease-in-out infinite;
  }
  .dashboard-page [class*="card-accent-"].card-anim-shimmer:hover::before{
    animation:card-anim-shimmer-kf .9s ease-out;
  }
  .dashboard-page [class*="card-accent-"].card-anim-underline:hover .card-title::after{width:100%;}
  /* .stat-card's border-top carries a semantic accent color (green/red/
     blue/gold) via its .accent-* class - lift/shadow only on hover, so
     that accent is never masked by a hover border-color. */
  .stat-card:hover{box-shadow:var(--shadow-md);transform:translateY(-2px);}
  /* Total Assets/Liabilities/Net Worth get a stronger scale+glow (in
     their own accent color) than the plain lift every other stat-card
     gets - these three specifically requested to match the reference
     video's KPI-card hover treatment. */
  .stat-card.accent-green:hover{transform:translateY(-2px) scale(1.03);box-shadow:0 10px 28px rgba(31,138,95,.35);z-index:2;}
  .stat-card.accent-red:hover{transform:translateY(-2px) scale(1.03);box-shadow:0 10px 28px rgba(192,57,43,.35);z-index:2;}
  .stat-card.accent-blue:hover{transform:translateY(-2px) scale(1.03);box-shadow:0 10px 28px rgba(42,95,176,.35);z-index:2;}
  /* The Wealth Dashboard's Active SIPs card moved from accent-green to
     accent-lavender, so lavender needs the same treatment or that card would
     silently drop to the plain lift above. Deliberately not extended to
     .accent-purple, whose only user is the Recently Deleted page's own card. */
  .stat-card.accent-lavender:hover{transform:translateY(-2px) scale(1.03);box-shadow:0 10px 28px rgba(131,104,200,.35);z-index:2;}
  /* .member-card:hover's fuller treatment (lift+scale, glow, avatar,
     relationship label) lives with the rest of the "Personal Details.txt"
     pass earlier in this file - not duplicated here. */
  .liability-card:hover{box-shadow:var(--shadow-md);transform:translateY(-2px);}
  .reminder-card:hover{box-shadow:var(--shadow);transform:translateX(2px);}
  .timeline-item:hover .t-label{color:var(--gold-600);}
  .security-item:hover{box-shadow:var(--shadow);transform:translateY(-1px);}
  .donut-legend-item:hover{background:var(--subtle-bg);}
  .bar-chart-bar:hover{filter:brightness(1.08);}
  .bar-chart-bar:hover::after{opacity:1;transform:translate(-50%,0);}
  .btn:hover{box-shadow:var(--shadow);transform:translateY(-1px);}
  .table-actions{opacity:.6;transition:opacity .15s ease-out;}
  tbody tr:hover .table-actions{opacity:1;}
  /* .timeline-h-node itself (not its ::before/::after pulse rings, which
     already run their own continuous keyframe animation - a plain hover
     transform on those would just fight the animation every frame). */
  .timeline-h-node{transition:transform .18s cubic-bezier(0.4,0,0.2,1);}
  .timeline-h-node:hover{transform:scale(1.08);}
  .ads-bubble-card:hover{transform:scale(1.08);box-shadow:var(--shadow-md);z-index:10;}
}

/* Legend-hover -> segment spotlight (see app.js's
   initGlobalChartInteractions): an overlay conic-gradient dims every
   wedge except the hovered one, and the donut center swaps from Total to
   that segment's own value/label. Also doubles as this chart's entrance
   animation - .donut-chart starts hidden/scaled down and app.js adds
   .chart-in once its section reveals (see animateEntranceEffects). */
.donut-chart{
  /* filter carries the hover glow/intensify below - appended to the existing
     entrance transitions rather than declared separately, which would replace
     them and kill the chart-in fade/scale. */
  transition:opacity .5s cubic-bezier(0.16,1,0.3,1), transform .5s cubic-bezier(0.16,1,0.3,1), background .15s ease-out, filter .3s ease-out;
  opacity:0;transform:scale(.86);
}
.donut-chart.chart-in{opacity:1;transform:scale(1);}
.donut-chart::before{
  content:'';position:absolute;inset:0;border-radius:50%;
  background:var(--donut-overlay,none);transition:background .15s ease-out;pointer-events:none;
}
/* Same "spreading wave" ripple family as the Family Timeline nodes, but
   hover-triggered here instead of always-on - reads as the chart
   responding to the cursor landing on its card, rather than ambient
   decoration. Uses ::after since ::before above is already the legend-
   hover spotlight overlay. */
.donut-chart::after{
  content:'';position:absolute;inset:0;border-radius:50%;
  border:2px solid var(--gold-500);opacity:0;pointer-events:none;
}
@media (hover:hover) and (pointer:fine){
  .dashboard-chart-card:hover .donut-chart::after{animation:donut-ripple 1.4s ease-out infinite;}
  /* Same pair of hover effects Net Worth by Month gets - a glow plus a small
     intensify - translated to the donut. The ripple ring above is already the
     donut's equivalent of that card's pulsing halo, so it stays as-is.
     Tinted with the card's own --accent (.card-accent-* sets it), so Asset
     Distribution glows green and Liability Distribution red rather than both
     borrowing the line chart's gold. */
  .dashboard-chart-card:hover .donut-chart{
    filter:drop-shadow(0 0 5px color-mix(in srgb, var(--accent, var(--gold-500)) 45%, transparent)) saturate(1.08);
  }
}
@keyframes donut-ripple{
  0%{transform:scale(1);opacity:.55;}
  100%{transform:scale(1.28);opacity:0;}
}
.donut-center-value,
.donut-center-label{transition:opacity .12s ease-out;}

/* Line/area charts (charts.js renderLineChart) - the line itself "draws"
   via a JS-set stroke-dashoffset transition (see animateEntranceEffects);
   the area fill just fades in alongside it. */
.line-chart-area{opacity:0;transition:opacity .6s ease-out .15s, fill .3s ease-out;}
.line-chart-area.chart-in{opacity:1;}

/* Net Worth by Month's hover response. Its two row-mates (Asset / Liability
   Distribution) already pulse a ring on card hover via donut-ripple, so this
   uses the same 1.4s ease-out infinite timing and the same
   .dashboard-chart-card:hover gate - the three chart cards then react as one
   set rather than two-plus-a-static-one. The halo rides the LAST data point,
   which is the current net worth, so it reads as a live reading.

   The glow goes on .line-chart-svg, not .line-chart-line: app.js's draw-in
   sets an inline `transition:stroke-dashoffset` on the path, and an inline
   style would beat any transition declared here. */
.line-chart-svg{transition:filter .3s ease-out;}
@media (hover:hover) and (pointer:fine){
  .dashboard-chart-card:hover .line-chart-dot-pulse{animation:line-chart-pulse 1.4s ease-out infinite;}
  .dashboard-chart-card:hover .line-chart-svg{filter:drop-shadow(0 0 3px color-mix(in srgb, var(--gold-500) 65%, transparent));}
  .dashboard-chart-card:hover .line-chart-area{fill:rgba(200,153,46,.22);}
}
@keyframes line-chart-pulse{
  0%{transform:scale(1);opacity:.5;}
  100%{transform:scale(3.6);opacity:0;}
}

/* Liability/progress bars render at width:0 with their real value in
   data-pct, and app.js's animateEntranceEffects() sets the final width
   once the containing section reveals - this transition is what turns
   that width change into the "fill animates from 0 -> value" grow-in. */
.bar-fill{transition:width .8s cubic-bezier(0.16,1,0.3,1);}

/* Table rows - a light, self-contained entrance animation that plays on
   insertion (no JS/observer needed): every render of a table naturally
   re-creates its <tr> elements, so this replays automatically on every
   page load, filter change, or remount. Capped/staggered by row position
   so a long table doesn't feel like it's crawling in one row at a time. */
@keyframes row-in{from{opacity:0;transform:translateY(6px);}to{opacity:1;transform:translateY(0);}}
.table-wrap tbody tr{animation:row-in .32s ease-out both;}
.table-wrap tbody tr.empty-row{animation:none;}
.table-wrap tbody tr:nth-child(1){animation-delay:0ms;}
.table-wrap tbody tr:nth-child(2){animation-delay:35ms;}
.table-wrap tbody tr:nth-child(3){animation-delay:70ms;}
.table-wrap tbody tr:nth-child(4){animation-delay:105ms;}
.table-wrap tbody tr:nth-child(5){animation-delay:140ms;}
.table-wrap tbody tr:nth-child(6){animation-delay:175ms;}
.table-wrap tbody tr:nth-child(7){animation-delay:210ms;}
.table-wrap tbody tr:nth-child(8){animation-delay:245ms;}
.table-wrap tbody tr:nth-child(n+9){animation-delay:280ms;}
/* app.js's initTableRowReveal() adds this once a table (keyed by route +
   header signature) has already played its stagger once, so refreshing
   the same table's data (filter/sort/pagination/modal-close, all of which
   remount() rather than navigate()) never replays it. */
.table-wrap tbody.tw-no-stagger tr{animation:none;}

/* Page-load + scroll reveal - see app.js's initPageReveal(), which adds
   .page-reveal only when it can actually animate (JS enabled,
   IntersectionObserver supported, prefers-reduced-motion not set), so a
   page that never gets .page-reveal added just renders normally instead
   of ever being stuck invisible. */
.page-reveal{opacity:0;transform:translateY(10px);transition:opacity .42s ease-out, transform .42s ease-out;}
.page-reveal.page-reveal-in{opacity:1;transform:translateY(0);}
@media (prefers-reduced-motion: reduce){
  .page-reveal{opacity:1;transform:none;transition:none;}
  .donut-chart{opacity:1;transform:none;transition:none;}
  .dashboard-chart-card:hover .donut-chart::after{animation:none;opacity:0;}
  .line-chart-area{opacity:1;transition:none;}
  .line-chart-line{transition:none !important;}
  .line-chart-svg{transition:none;}
  .line-chart-dot-pulse{animation:none;}
  .donut-chart{transition:opacity .5s ease-out;}
  .donut-chart::after{animation:none;}
  .bar-fill{transition:none;}
  .bar-chart-bar::after{transition:opacity .15s ease-out;}
  .bar-chart-bar:hover::after{transform:translate(-50%,4px);}
  .card:hover,.stat-card:hover,.member-card:hover,.liability-card:hover,.reminder-card:hover,.security-item:hover,.btn:hover{transform:none;}
  .stat-card.accent-green:hover,.stat-card.accent-red:hover,.stat-card.accent-blue:hover,.stat-card.accent-lavender:hover{transform:none;box-shadow:var(--shadow-md);}
  .sidebar{transition:none;}
  .nav-item{transition:background .15s ease, color .15s ease;}
  .nav-item span:not(.icon){transition:none;}
  .nav-item .icon{transition:none;}
  .table-wrap tbody tr{animation:none;}
  .ai-toggle{animation:none;}
  .ai-panel{transition:none;}
  .claude-toggle{animation:none;}
  .claude-panel{transition:none;}
  .stat-icon{animation:none;}
  .topbar-avatar.profile-pulse-once{animation:none;}
  .ads-view-anim{animation:none;}
  /* initShellSettleIn() (which adds .ai-in/.profile-pulse-once and does
     the header stagger) returns immediately under reduced motion and
     never runs, so nothing would ever add .ai-in - force the widget
     visible unconditionally here instead of relying on that class. */
  .ai-widget{opacity:1;transform:none;transition:none;}
  .claude-widget{opacity:1;transform:none;transition:none;}
  .donut-legend-item,.ads-legend-item{transition:none;}
  .timeline-h-item{transition:none;}
  .timeline-h-connector{transition:none;}
  .topbar-title,.topbar-search,.topbar-actions>*{transition:none;}
}

.reminder-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:12px;}
/* Wealth Dashboard's own "Upcoming Reminders" card only (dashboard.js's
   remindersSection()) - a single horizontal-scroll row instead of
   .reminder-grid's wrapping grid, so a longer reminder list grows the
   page sideways (scroll within this one card) rather than taller
   (needing the whole page scrolled up/down to see past it). Same
   overflow-x pattern already used for the Family Timeline row right
   below it. The full Reminders page (reminders.js) and the Getting
   Started checklist keep the original wrapping .reminder-grid.

   The row is driven by arrow buttons rather than its own scrollbar: a native
   horizontal bar sitting under the cards cut across the bottom of the card
   and was the only scrollbar on the page pointing sideways, which read as a
   rendering fault rather than an affordance. The scroller carries .cat-tabs
   as well, which is what wires it into the existing strip machinery in
   storage.js - initCatTabs() keys off that class (by design, so any page can
   adopt the strip), and that is what restores the scroll position and
   re-dims the arrows after each remount. The rules below re-state the few
   .cat-tabs declarations that were tuned for pill strips rather than cards
   (gap, padding), and repeat the scrollbar hiding so this block still stands
   on its own if .cat-tabs is ever dropped from the markup. */
.reminder-strip{display:flex;align-items:center;gap:2px;}
.reminder-strip .reminder-grid-h{
  display:flex;gap:12px;
  /* flex:1 + min-width:0 is what lets the row be narrower than its contents;
     the old min-width:min-content forced the card itself wide instead, which
     is what pushed the scrollbar onto the page in the first place. */
  flex:1;min-width:0;
  overflow-x:auto;overflow-y:hidden;
  padding:8px 2px 6px;
  scrollbar-width:none;-ms-overflow-style:none;
}
.reminder-strip .reminder-grid-h::-webkit-scrollbar{display:none;}
.reminder-strip .reminder-grid-h .reminder-card{flex-shrink:0;width:260px;}
/* Quieter than the category strips' arrows: those sit on their own tinted
   bar, these sit on the card's plain background beside the reminder cards. */
.reminder-strip .cat-tabs-arrow{color:var(--text-400);}
.reminder-strip .cat-tabs-arrow:hover{color:var(--text-900);}
.reminder-card{
  display:flex;gap:11px;align-items:flex-start;
  border:1px solid var(--border);border-left:3px solid var(--border);
  border-radius:8px;padding:11px 13px;background:var(--card);
}
.reminder-card.sev-high{border-left-color:var(--red);}
.reminder-card.sev-med{border-left-color:var(--amber);}
.reminder-card.sev-low{border-left-color:var(--blue);}
.reminder-card .r-icon{font-size:17px;}
.reminder-card .r-title{font-weight:700;font-size:12.8px;color:var(--text-900);}
.reminder-card .r-desc{font-size:11.6px;color:var(--text-400);margin-top:2px;}

/* ---------- Getting Started: filling bar + step queue ----------
   Taken from the reference clip (Getting_STARTED_VID.mp4): a bar that fills
   left to right beneath a stationary green-to-blue ramp, diagonal stripes
   marching over the fill while it moves, and a solid arrowhead riding the
   leading edge that tapers back into a plain cap as the bar lands. Every
   selector is scoped .gs-* because .bar-track/.bar-fill are shared with the
   KPI, liability and Balance Sheet bars, and none of those wants any of it. */
.gs-bar{
  position:relative;
  height:12px;
  /* .bar-track ships flex:1 for its usual home, a horizontal row where that
     grows it along the row. Here it is a child of the column-flex .gs-progress
     box, where flex-basis:0 resolves against HEIGHT instead and silently beat
     the 12px above - the bar rendered as a hairline whenever the box had no
     spare height to grow into. Pinning the basis to the height puts the
     declaration back in charge. */
  flex:0 0 12px;
  border-radius:999px;
  /* The arrowhead stands taller than the track and has to be allowed out of
     it; .bar-track's own overflow:hidden would slice the point off. Nothing
     is lost by lifting it - the fill carries its own rounding rather than
     borrowing the track's clip. */
  overflow:visible;
}
.gs-bar-fill{
  position:relative;
  border-radius:999px;
  /* The opening fill, slowed right down from the shared .bar-fill transition
     (.8s on cubic-bezier(0.16,1,0.3,1)). Two things were making that read as
     a snap rather than a fill: it was brief, and that curve is a hard ease-out
     that spends three quarters of its distance in the first quarter of its
     time, so the bar was essentially already there before the eye caught it.
     Longer, on a curve that leaves gently and arrives gently, so the travel
     itself is the thing you see. Scoped here rather than changed on .bar-fill
     because the KPI and liability bars share that rule and are not being
     asked to slow down. */
  transition:width 4s cubic-bezier(0.4, 0, 0.2, 1);
  /* Two layers, stripes over a colour ramp.

     The ramp is sized in px rather than %, and that is the whole trick. A
     percentage sizes it to the FILL, so the leading edge would land on the
     same colour at every width and a longer bar would only ever be a longer
     green. Pinning it to a fixed 720px leaves the ramp stationary and lets
     the fill uncover more of it as it grows: a little progress finishes
     green, a lot has travelled through teal into blue. Past 720px there is
     no more ramp to uncover, which is what the background-color underneath
     is for - it continues the ramp's own end colour so the tail of a very
     wide bar joins on invisibly instead of falling back to .bar-fill's navy. */
  background-color:#3f7fe0;
  background-image:
    repeating-linear-gradient(115deg, rgba(255,255,255,.20) 0 7px, rgba(255,255,255,0) 7px 16px),
    linear-gradient(90deg, #34c98b 0%, #2bb59c 28%, #2f9fc4 60%, #3f7fe0 100%);
  background-repeat:repeat, no-repeat;
  background-size:60px 60px, 720px 100%;
  background-position:0 0, 0 0;
  /* Two animations, neither touching the other's property.

     gs-stripe-march runs the whole time the bar is on screen, not only while
     it fills or while hovered - the line is meant to read as live. Slow, so
     it is movement you notice rather than movement that nags. The -120px step
     is exactly two 60px stripe tiles, so the loop closes with no visible jump.

     gs-line-push is the periodic nudge forward, and it moves the FILL rather
     than the arrow. An earlier version translated the arrow alone and drew a
     separate segment behind it to keep the two joined, but that segment could
     never quite match the bar: it is its own element, so it restarts the
     colour ramp at its own left edge and carries its own stripe phase, and
     the join showed. Growing the fill instead means the line the arrow leads
     IS the bar - same ramp, same stripes, same phase, no join to notice - and
     the arrow needs no animation at all, since it is pinned to left:100% of
     the very box being widened.

     The 4.6s delay lets the reveal's own 0 -> pct width transition finish
     first; before an animation's delay elapses it applies nothing, so the
     inline width still governs and the fill animation is untouched. It has
     to stay clear of the 4s transition above - the two drive the same
     property, and an animation outranks a transition, so a shorter delay
     would cut the opening fill off partway and snap the bar to its final
     width. Lengthen that transition and this has to follow it. */
  animation:gs-stripe-march 4.6s linear infinite,
            gs-line-push 15s ease-in-out 4.6s infinite;
}
@keyframes gs-line-push{
  0%,50%{width:var(--w);}
  76%{width:calc(var(--w) + 22px);}
  97%,100%{width:var(--w);}
}
/* The arrowhead now stays on the bar for good, marking where progress has
   reached, instead of tapering away once the fill lands. Kept taller than
   the track so it breaks the bar's silhouette the way the clip does, and
   tucked 2px back so its base disappears under the fill's cap rather than
   showing a seam. It rides the leading edge for free during the initial
   fill, since left:100% is measured against a box whose width is what is
   being animated. */
.gs-bar-fill::after{
  content:"";
  position:absolute;
  left:100%;top:50%;
  width:13px;height:20px;
  margin-left:-2px;
  background:linear-gradient(90deg, #2f9fc4, #3f7fe0);
  clip-path:polygon(0 0, 100% 50%, 0 100%);
  transform:translateY(-50%);
  pointer-events:none;
}
/* Hover puts the whole line in motion rather than just the tip: the stripes
   march continuously instead of their one finite pass, a highlight travels
   the full track - the empty stretch included, which is the part the resting
   state never animates - and the arrow drops from its 15s idle cycle to a 3s
   one so it answers the pointer instead of leaving you waiting for the next
   scheduled drift. */
.gs-bar::after{
  content:"";
  position:absolute;inset:0;
  border-radius:inherit;
  pointer-events:none;
  opacity:0;
  background-image:linear-gradient(90deg, rgba(63,127,224,0) 0%, rgba(63,127,224,.42) 50%, rgba(63,127,224,0) 100%);
  background-repeat:no-repeat;
  background-size:38% 100%;
  background-position:-45% 0;
  transition:opacity .2s ease-out;
}
.gs-progress:hover .gs-bar::after{opacity:1;animation:gs-track-sheen 1.6s linear infinite;}
@keyframes gs-track-sheen{
  from{background-position:-45% 0;}
  to{background-position:145% 0;}
}
/* Both of the fill's animations quicken together - the durations are given
   in the same order they are declared above (stripes, then the push). */
.gs-progress:hover .gs-bar-fill{animation-duration:2.2s, 6s;}
/* Only the stripe layer's position is keyframed; the colour ramp's stays
   pinned at 0 so it cannot drift along with the stripes. */
@keyframes gs-stripe-march{
  from{background-position:0 0, 0 0;}
  to{background-position:-120px 0, 0 0;}
}
/* Step tiles enter one after another, each scaling up out of nothing rather
   than sliding - the clip grows each card from a small faint ghost into
   place. `backwards` rather than `forwards` on purpose: it holds the from-
   state through the delay but retains nothing afterwards, so the finished
   tile hands its transform back to the stylesheet and .reminder-card's own
   hover lift still works. */
.gs-in > .gs-step{
  animation:gs-step-in .5s cubic-bezier(0.16,1,0.3,1) backwards;
  animation-delay:calc(var(--i, 0) * .11s);
}
@keyframes gs-step-in{
  from{opacity:0;transform:scale(.84);}
  to{opacity:1;transform:scale(1);}
}
/* The arrow keeps its place here, it just stops drifting - it is carrying
   meaning (where progress has reached), so hiding it would remove
   information rather than only motion. */
@media (prefers-reduced-motion: reduce){
  .gs-bar-fill,
  .gs-in > .gs-step,
  .gs-progress:hover .gs-bar-fill,
  .gs-progress:hover .gs-bar::after{animation:none;}
  .gs-progress:hover .gs-bar::after{opacity:0;}
  /* The shared .bar-fill{transition:none} for reduced motion sits earlier in
     this file, and .gs-bar-fill's own transition above ties it on specificity
     and wins on order - so the opt-out has to be restated here or this one bar
     would keep animating for someone who asked for no motion. */
  .gs-bar-fill{transition:none;}
}
/* The row from the clip: the progress readout is its own box on the left and
   the outstanding tasks sit to its right, on the same line, rather than the
   bar spanning the full card with the tasks stacked underneath.

   The box is sized by what is left over, which is the point - .gs-steps takes
   only the width its tasks actually need (flex-basis auto, no grow), so the
   box widens when little is outstanding and gives ground as tasks pile up,
   exactly as it does in the clip when the second card lands. Once the tasks
   would squeeze it past its min-width they wrap to another line inside their
   own half instead, and the box stops surrendering width. */
.gs-row{display:flex;align-items:stretch;gap:12px;}
.gs-progress{
  flex:1 1 220px;min-width:200px;
  display:flex;flex-direction:column;justify-content:center;gap:10px;
  border:1px solid var(--border);border-radius:8px;
  padding:14px 16px;
}
.gs-progress-head{display:flex;align-items:baseline;justify-content:space-between;gap:10px;}
.gs-progress-label{font-size:14.5px;font-weight:700;color:var(--text-900);}
.gs-progress-pct{font-size:14.5px;font-weight:800;color:var(--text-900);}
.gs-steps{
  flex:0 1 auto;
  display:flex;flex-wrap:wrap;gap:10px;align-content:flex-start;
}
/* Deliberately narrow. These tiles hold nothing but a tick and a short
   label, so they do not need the width, and every pixel taken off them is a
   pixel the progress box gains - the box is sized by what is left over, so
   narrowing the tasks IS how the bar gets longer. */
.gs-step{flex:0 0 168px;}
.gs-step.reminder-card{padding:10px 11px;gap:8px;}
.gs-step .r-icon{font-size:14px;}
/* Narrow viewports have no room for two columns of anything - the box takes
   the full width with the tasks beneath it, which is where this started. */
@media(max-width:760px){
  .gs-row{flex-wrap:wrap;}
  .gs-progress,
  .gs-steps,
  .gs-step{flex:1 1 100%;}
}
.gs-step-body{min-width:0;}

/* ---------- Video-backed Nominee Information card ----------
   The odd one out among the backdrops on this page: its clip is light - pale
   grey with a gold shield and tree - where the profile clips are near-black.
   So the veil over it is WHITE, not navy, and the table underneath keeps its
   ordinary dark-on-light text instead of being repainted. It also has to be
   heavier than the profile scrims: a table is a grid of thin dark strokes and
   a busy backdrop showing through it costs legibility in a way a heading and
   five fields never do. */
.nominee-card{position:relative;overflow:hidden;isolation:isolate;}
.nominee-bg{
  position:absolute;inset:0;z-index:0;
  width:100%;height:100%;
  /* The card is ~4.5:1, so cover keeps barely a third of the 16:9 frame.
     Centring it landed on bare branches; 22% brings the gold shield - the
     one thing in the clip that says "nominee" - into the band. */
  object-fit:cover;object-position:center 22%;
  display:block;pointer-events:none;
}
/* Weighted left, where the table's text starts, and lifting toward the right
   where the rows run out - so the clip is readable as a backdrop without ever
   competing with a cell. */
.nominee-scrim{
  position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(95deg, rgba(255,255,255,.90) 0%, rgba(255,255,255,.82) 42%, rgba(255,255,255,.58) 100%);
}
.nominee-body{position:relative;z-index:2;}
/* Dark mode cannot just swap the veil's colour: laying navy over a pale clip
   only greys it out, and the card ends up washed rather than dark. The clip
   itself is dimmed first - which keeps the gold, since brightness scales the
   bright ground down much further than it does the already-mid gold - and a
   lighter navy veil then finishes the job. */
[data-theme="dark"] .nominee-bg{filter:brightness(.42) contrast(1.3) saturate(1.75);}
[data-theme="dark"] .nominee-scrim{
  background:linear-gradient(95deg, rgba(10,18,32,.62) 0%, rgba(10,18,32,.48) 42%, rgba(10,18,32,.24) 100%);
}
@media (prefers-reduced-motion: reduce){
  .nominee-bg{display:none;}
}

/* ---------- Video-backed profile card (My Personal Details) ----------
   The card keeps its accent border and its .card padding; the clip goes
   behind everything and the copy moves into .dash-profile-clip-body so it
   can sit above both the clip and its scrim. */
.dash-profile-clip{position:relative;overflow:hidden;isolation:isolate;}
.dash-profile-clip-bg{
  position:absolute;inset:0;z-index:0;
  width:100%;height:100%;
  /* These cards are far wider than they are tall, so cover shows only a band
     of the 16:9 clip and which band matters. Each card picks its own inline
     (profileClipHtml() in dashboard.js) - the two with figures in them sit
     higher, the address clip stays centred on its house and pin. This is only
     the fallback for anything that doesn't say. */
  object-fit:cover;object-position:center 45%;
  display:block;
  /* Seen only between the poster clearing and the first decoded frame -
     matched to the clip's own ground so that moment isn't a pale flash. */
  background:#0a1220;
  pointer-events:none;
}
/* Heaviest on the left where the labels begin, easing off toward the right
   where the family group stands, so the clip is least covered exactly where
   it is worth seeing. */
.dash-profile-clip-scrim{
  position:absolute;inset:0;z-index:1;pointer-events:none;
  background:linear-gradient(100deg, rgba(6,15,34,.90) 0%, rgba(6,15,34,.80) 40%, rgba(6,15,34,.58) 76%, rgba(6,15,34,.46) 100%);
}
.dash-profile-clip-body{position:relative;z-index:2;}
/* This card is now dark in both themes, so its text cannot keep inheriting
   the paper-card colours - they would be near-black on navy in light mode. */
.dash-profile-clip .card-title{color:#fff;text-shadow:0 2px 10px rgba(0,0,0,.55);}
.dash-profile-clip .kicker{color:rgba(255,255,255,.70);}
.dash-profile-clip .text-sm{color:#fff;text-shadow:0 1px 6px rgba(0,0,0,.6);}
/* An autoplaying loop is motion nobody asked for; the poster alone is a
   complete card, so the clip simply doesn't show. */
@media (prefers-reduced-motion: reduce){
  .dash-profile-clip-bg{display:none;}
}
/* A cleared step: still readable, plainly finished, and no longer competing
   for attention with the ones that still need doing. The border colour has
   to out-specify .reminder-card.sev-low's blue, hence the two-class
   selector. */
.reminder-card.gs-step-done{border-left-color:var(--green);opacity:.62;}
.gs-step-done .r-title{color:var(--text-400);text-decoration:line-through;text-decoration-thickness:1px;}
.gs-see-all{flex-shrink:0;color:var(--blue);font-weight:600;}
.gs-see-all-caret{margin-left:4px;font-weight:700;}

/* ---------- Birthday reminder balloons ----------
   Balloons drift up behind the name and date on birthday reminder cards
   only (dashboard.js tags those .is-birthday). Ambient and continuous, in
   the same family as .stat-card-ambient's sweep - kept to ~30% opacity at
   its peak so the title/date on top stay fully legible.

   .rc-balloons is absolutely positioned, so it is NOT a flex item and does
   not disturb the card's icon + text layout. The z-index pair matters: an
   absolutely positioned box paints above static siblings, so the balloons
   would cover the text without lifting the content to z-index:1. */
.reminder-card.is-birthday{position:relative;overflow:hidden;}
.reminder-card.is-birthday > *{position:relative;z-index:1;}
/* Must restate position:absolute, not just z-index: the `> *` rule above is
   more specific than a bare .rc-balloons selector, so it would otherwise make
   this container position:relative - which collapses it to a 0x0 empty flex
   item whose own overflow:hidden then clips every balloon out of sight. */
.reminder-card.is-birthday > .rc-balloons{position:absolute;inset:0;z-index:0;}
.rc-balloons{pointer-events:none;overflow:hidden;border-radius:inherit;}
.rc-balloon{
  position:absolute;
  bottom:-20px;
  width:10px;height:13px;
  border-radius:50% 50% 48% 48% / 55% 55% 45% 45%;
  /* Glossy sheen so each one reads as a balloon rather than a flat dot; the
     body colour comes from that balloon's own `color` via currentColor. */
  background:radial-gradient(circle at 32% 28%, rgba(255,255,255,.6), currentColor 62%);
  opacity:0;
  animation:rc-balloon-rise 5s ease-in-out infinite;
}
/* The string, hanging from the balloon's base. */
.rc-balloon::after{
  content:'';
  position:absolute;top:100%;left:50%;
  width:1px;height:7px;
  background:currentColor;opacity:.5;
  transform:translateX(-50%);
}
/* Seven colours across the card, each launching ~0.7s after the last so the
   5s cycle reads as a continuous drift instead of one synchronised burst. */
.rc-balloon.b1{left:8%;  color:var(--pink);     animation-delay:0s;}
.rc-balloon.b2{left:22%; color:var(--gold-500); animation-delay:.7s;}
.rc-balloon.b3{left:37%; color:var(--purple);   animation-delay:1.4s;}
.rc-balloon.b4{left:52%; color:var(--blue);     animation-delay:2.1s;}
.rc-balloon.b5{left:66%; color:var(--green);    animation-delay:2.8s;}
.rc-balloon.b6{left:80%; color:var(--red);      animation-delay:3.5s;}
.rc-balloon.b7{left:92%; color:var(--teal);     animation-delay:4.2s;}
/* Cursor on the card and they take off - 5s cycle drops to 1.6s. */
@media (hover:hover) and (pointer:fine){
  .reminder-card.is-birthday:hover .rc-balloon{animation-duration:1.6s;}
}
/* --rc-rise is how far a balloon travels before fading out; it defaults to
   the 92px that suits the dashboard's reminder card and is overridden for
   shorter hosts (see the Family Event table rows). The 0.478 at the midpoint
   keeps the original easing shape at any rise distance. */
@keyframes rc-balloon-rise{
  0%{transform:translate(0,0) scale(.8);opacity:0;}
  12%{opacity:.38;}
  50%{transform:translate(5px, calc(var(--rc-rise, 92px) * -0.478)) scale(1);}
  78%{opacity:.30;}
  100%{transform:translate(-3px, calc(var(--rc-rise, 92px) * -1)) scale(1.05);opacity:0;}
}
/* ---------- Same balloons on the Family Event tab's birthday rows ----------
   Hosted by the Name cell (familyTree.js explains why not the <tr>), so the
   balloons drift up behind the person's name. A table row is far shorter than
   the dashboard's reminder card, so the travel is cut to 58px and the start
   offset tightened to match - otherwise most of the flight happens out of
   sight below the row. */
.fe-name-cell{position:relative;}
.fe-name-cell > .rc-balloons{position:absolute;inset:0;z-index:0;}
.fe-name-cell > .fe-name{position:relative;z-index:1;}
.is-birthday-row .rc-balloon{--rc-rise:58px;bottom:-14px;}
@media (hover:hover) and (pointer:fine){
  .is-birthday-row:hover .rc-balloon{animation-duration:1.6s;}
}

@media (prefers-reduced-motion: reduce){
  .rc-balloon{animation:none;display:none;}
}

.timeline{position:relative;padding-left:26px;}
.timeline::before{content:'';position:absolute;left:6px;top:4px;bottom:4px;width:2px;background:var(--border);}
.timeline-item{position:relative;padding-bottom:20px;}
.timeline-item:last-child{padding-bottom:0;}
.timeline-dot{
  position:absolute;left:-26px;top:2px;width:13px;height:13px;border-radius:50%;
  background:var(--gold-500);border:2px solid var(--card);box-shadow:0 0 0 2px var(--border);
}
.timeline-item .t-label{font-weight:700;font-size:13px;color:var(--text-900);}
.timeline-item .t-date{font-size:11.5px;color:var(--text-400);margin-top:1px;}

/* Horizontal Family Timeline (dashboard.js's timelineSection()) - same
   real events as the vertical .timeline above, just laid out as a
   scrollable row of connected nodes instead of a dotted list. */
/* overflow-x alone leaves overflow-y at its default 'visible', but CSS
   promotes that to 'auto' automatically whenever the other axis isn't
   'visible' too - so the sonar-pulse rings' brief scale-up past the row's
   own height (see .timeline-pulse below) was enough to pop a vertical
   scrollbar in and out at different zoom levels/screen widths. Only
   horizontal scrolling is ever actually wanted here, so overflow-y is
   pinned to hidden explicitly instead of left to that default. */
.timeline-h-wrap{overflow-x:auto;overflow-y:hidden;padding:8px 4px 6px;}
.timeline-h{display:flex;align-items:center;min-width:min-content;}
.timeline-h-item{display:flex;flex-direction:column;align-items:center;gap:8px;flex-shrink:0;width:112px;text-align:center;}
.timeline-h-tag{
  color:#fff;font-size:10.6px;font-weight:700;
  padding:3px 9px;border-radius:20px;white-space:nowrap;
  max-width:104px;overflow:hidden;text-overflow:ellipsis;
}
.timeline-h-node{
  position:relative;width:40px;height:40px;border-radius:50%;
  border:2px solid var(--border);background:var(--card);
  display:flex;align-items:center;justify-content:center;flex-shrink:0;
}
/* Sonar-style "spreading wave" pulse - three staggered rings (one real
   element + two pseudo-elements, all reading --node-color set inline per
   node in dashboard.js) expanding out from the node and fading, looped
   continuously for a gentle ambient glitter at rest. Hovering a specific
   node speeds the loop up (still the same stagger/spacing between the
   three rings, just a shorter cycle); moving the cursor away reverts to
   the normal 2.4s pace automatically, since this is a plain :hover swap
   of animation-duration - no JS needed. */
.timeline-h-ring,
.timeline-h-node::before,
.timeline-h-node::after{
  content:'';position:absolute;inset:-6px;border-radius:50%;
  background:var(--node-color);pointer-events:none;z-index:0;
  animation:timeline-pulse 2.4s ease-out infinite;
}
.timeline-h-ring{animation-delay:0s;}
.timeline-h-node::before{animation-delay:.8s;}
.timeline-h-node::after{animation-delay:1.6s;}
@media (hover:hover) and (pointer:fine){
  .timeline-h-node:hover .timeline-h-ring,
  .timeline-h-node:hover::before,
  .timeline-h-node:hover::after{
    animation-duration:.8s;
  }
}
@keyframes timeline-pulse{
  0%{transform:scale(.85);opacity:.4;}
  100%{transform:scale(2.3);opacity:0;}
}
@media (prefers-reduced-motion: reduce){
  .timeline-h-ring,.timeline-h-node::before,.timeline-h-node::after{animation:none;opacity:0;}
}
.timeline-h-icon{position:relative;z-index:1;font-size:15px;}
.timeline-h-date{font-size:10.8px;color:var(--text-400);white-space:nowrap;}
.timeline-h-connector{height:2px;background:var(--border);flex:1 0 28px;margin:0 -2px 26px;min-width:28px;transform-origin:left;}
/* Left-to-right build: each node pops in, then the connector after it
   draws from 0 -> full width - see app.js's animateEntranceEffects(),
   which sets the initial hidden state inline right before triggering
   these (same technique as the .stat-card nested stagger). */
.timeline-h-item{transition:opacity .55s cubic-bezier(0.22,1,0.36,1), transform .55s cubic-bezier(0.22,1,0.36,1);}
.timeline-h-connector{transition:transform .5s cubic-bezier(0.22,1,0.36,1);}

/* ================= Family Tree scene (Family Tree UI_v2) =================
   Scoped to the .tree-* / .tc-* classes familyTree.js's renderTree() emits.

   The scene is a surface in its own right â€” a warm ivory "paper" that the
   page background never shows through â€” so it carries a LOCAL token set
   instead of the global --card / --text-* pair, and declares both themes
   here. That is deliberate: v1 washed the background photograph to near-white
   in every theme while the cards on top stayed var(--card), which in dark
   mode dropped near-black cards onto a bright plate. The tokens below keep
   the same warmth at low light instead.

   Colour roles: emerald = lineage (descent lines, botanical marks), teal =
   controls and grouping labels, gold = the Family Head and anything
   celebratory, blue/rose = the gender accents the legend documents, lavender
   = relatives not yet placed. Accents sit at 5-24% on surfaces; full strength
   is for marks, badges and hover only. ==================================== */

.tree-scene, .tree-others{
  --tree-paper:#fdfaf2;
  --tree-paper-2:#f6efe1;
  --tree-card:#ffffff;
  --tree-ink:#1b2333;
  --tree-ink-2:#4d576b;
  --tree-ink-3:#8c95a8;
  --tree-line:#e8dfcd;
  --tree-line-soft:#f0e9db;
  --tree-navy:#152a45;
  --tree-gold:#c0921f;
  --tree-gold-soft:#e8c766;
  --tree-teal:#2f8f89;
  --tree-emerald:#2e8b62;
  --tree-blue:#3f6fb5;
  --tree-rose:#c26b8a;
  --tree-lav:#8578b8;
}
[data-theme="dark"] .tree-scene,
[data-theme="dark"] .tree-others{
  --tree-paper:#151b26;
  --tree-paper-2:#101620;
  --tree-card:#1b2432;
  --tree-ink:#eef2f8;
  --tree-ink-2:#aeb8ca;
  --tree-ink-3:#7d8799;
  --tree-line:#333d51;
  --tree-line-soft:#2a3344;
  --tree-gold:#dcae4e;
  --tree-gold-soft:#8a6c25;
  --tree-teal:#4bb3ab;
  --tree-emerald:#4cb383;
  --tree-blue:#6f9ada;
  --tree-rose:#d98fab;
  --tree-lav:#a99cd8;
}

/* --- 1. The scene --- */
.tree-scene{
  position:relative;
  overflow:hidden;
  padding:26px 26px 22px;
  border-radius:20px;
  border:1px solid var(--tree-line);
  background:
    radial-gradient(95% 75% at 10% 0%, color-mix(in srgb, var(--tree-teal) 7%, transparent) 0%, transparent 62%),
    radial-gradient(85% 70% at 100% 6%, color-mix(in srgb, var(--tree-gold) 10%, transparent) 0%, transparent 58%),
    linear-gradient(165deg, var(--tree-paper) 0%, var(--tree-paper-2) 100%);
  box-shadow:0 1px 2px rgba(15,31,51,.05), 0 26px 52px -34px rgba(15,31,51,.42);
}
/* Heritage texture: the same assets/tree-bg.jpg v1 used, dropped to a whisper
   so it reads as grain in the paper rather than as scenery competing with the
   cards. */
.tree-scene::before{
  content:'';
  position:absolute;
  inset:0;
  background:url('../assets/tree-bg.jpg') center/cover no-repeat;
  opacity:.085;
  /* Blurred as well as faded: at full sharpness the branches in the corner
     still read as a photograph rather than as grain. */
  filter:saturate(.45) blur(2px);
  transform:scale(1.04); /* hides the blur's soft edge at the crop */
  pointer-events:none;
}
[data-theme="dark"] .tree-scene::before{opacity:.05;filter:saturate(.35) brightness(.5) blur(2px);}
/* Botanical motif â€” one faint leaf arc in the top corner, clipped by the
   scene's own overflow so it never becomes a shape in its own right. */
.tree-scene::after{
  content:'';
  position:absolute;
  right:-96px;top:-118px;
  width:350px;height:350px;
  border-radius:0 62% 0 62%;
  border:1px solid color-mix(in srgb, var(--tree-emerald) 15%, transparent);
  transform:rotate(16deg);
  pointer-events:none;
}

/* --- 2. Header --- */
.tree-scene-header{
  position:relative;
  z-index:1;
  text-align:center;
  padding:6px 40px 0;
}
.tree-scene-mark{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:44px;height:44px;
  margin-bottom:13px;
  border-radius:15px;
  color:var(--tree-emerald);
  background:color-mix(in srgb, var(--tree-emerald) 10%, var(--tree-card));
  border:1px solid color-mix(in srgb, var(--tree-emerald) 22%, transparent);
  box-shadow:0 8px 18px -12px color-mix(in srgb, var(--tree-emerald) 95%, transparent);
}
.tree-scene-mark svg{display:block;}
.tree-eyebrow{
  font-size:10.4px;
  font-weight:700;
  letter-spacing:.17em;
  text-transform:uppercase;
  color:color-mix(in srgb, var(--tree-emerald) 58%, var(--tree-ink));
}
.tree-title{
  margin:7px 0 0;
  font-size:26px;
  font-weight:700;
  letter-spacing:-.028em;
  line-height:1.15;
  color:var(--tree-ink);
}
.tree-sub{
  margin:9px auto 0;
  max-width:620px;
  font-size:13.2px;
  font-weight:450;
  line-height:1.6;
  color:var(--tree-ink-2);
}

/* --- 3. Control bar: legend left, view controls right --- */
/* One row above the canvas holding everything that is ABOUT the tree rather
   than part of it. Nothing in here is ever positioned over .tree-stage, so a
   control can never cover a member card, an avatar or a connector. */
.tree-controlbar{
  display:flex;
  align-items:center;
  justify-content:space-between;
  flex-wrap:wrap;
  gap:12px;
  padding:18px 2px 15px;
  position:relative;
  z-index:1;
}
.tree-legend{
  display:flex;
  gap:5px;
  flex-wrap:wrap;
  align-items:center;
  justify-content:center;
  padding:5px;
  border-radius:15px;
  border:1px solid var(--tree-line);
  background:color-mix(in srgb, var(--tree-card) 76%, transparent);
  box-shadow:0 1px 2px rgba(15,31,51,.04);
}
.tree-legend-item{
  --k:var(--tree-ink-2);
  display:inline-flex;
  align-items:center;
  gap:7px;
  padding:5px 11px;
  border-radius:11px;
  font-size:11.3px;
  font-weight:600;
  letter-spacing:.02em;
  white-space:nowrap;
  color:color-mix(in srgb, var(--k) 72%, var(--tree-ink));
  background:color-mix(in srgb, var(--k) 9%, transparent);
}
.tree-legend-item[data-key="male"]{--k:var(--tree-blue);}
.tree-legend-item[data-key="female"]{--k:var(--tree-rose);}
.tree-legend-item[data-key="head"]{--k:var(--tree-gold);}
.tree-legend-item[data-key="married"]{--k:var(--tree-rose);}
.tree-legend-swatch{
  width:9px;height:9px;
  border-radius:3px;
  flex-shrink:0;
  background:var(--k);
}
.tree-legend-mark{display:inline-flex;align-items:center;color:var(--k);}
.tree-legend-mark svg{display:block;}
/* The Married chip carries a miniature of the badge used on the tree itself,
   so the legend shows the actual mark rather than a stand-in for it. */
.tree-legend-marriage{
  width:18px;height:18px;
  justify-content:center;
  border-radius:50%;
  color:#fff;
  background:linear-gradient(145deg,
    color-mix(in srgb, var(--tree-rose) 68%, #fff) 0%,
    var(--tree-rose) 58%,
    color-mix(in srgb, var(--tree-rose) 80%, var(--tree-lav)) 100%);
  box-shadow:0 2px 5px -2px color-mix(in srgb, var(--tree-rose) 90%, transparent),
             inset 0 1px 0 rgba(255,255,255,.45);
}
.tree-legend-marriage svg{width:10px;height:10px;}

/* --- 4. Stage: the scrolling canvas plus the floating control panel --- */
.tree-stage{position:relative;z-index:1;}
.tree-canvas-viewport{
  position:relative;
  overflow:auto;
  /* Reserve the vertical scrollbar's width up front. Without it, initTreeZoom
     measured the fit with no scrollbar, a sub-pixel height overflow then added
     one, and the tree ended up ~20px too wide after being fitted. */
  scrollbar-gutter:stable;
  /* Generous, so a typical family resolves without any internal scrolling at
     all — the cap only exists to stop a very large tree from pushing the
     footer and Other Relatives off the bottom of the page. */
  max-height:min(1000px, 80vh);
  border-radius:16px;
  border:1px solid var(--tree-line-soft);
  background:color-mix(in srgb, var(--tree-card) 42%, transparent);
  box-shadow:inset 0 1px 3px rgba(15,31,51,.05);
  isolation:isolate;
}
.tree-canvas{
  position:relative;
  z-index:1;
  padding:34px 26px 30px;
  display:inline-block;
  /* As wide as the widest generation, never narrower than the viewport - the
     viewport scrolls sideways (and the Center control recentres) rather than
     the canvas squeezing to fit. */
  width:max-content;
  min-width:100%;
}
/* One generation = one line. This used to be flex-wrap:wrap, which on a tier
   wider than the viewport (Parents + Parents-in-law side by side is ~1,200px)
   dropped the in-laws onto a line of their own: the parents' connector then
   ran behind the in-law cards down to the Head and the extra line pushed the
   children out of view. */
.tree-tier{
  display:flex;
  justify-content:center;
  align-items:flex-end;
  gap:48px;
  margin-bottom:74px;
  flex-wrap:nowrap;
  position:relative;
  z-index:1;
}
.tree-tier:last-child{margin-bottom:0;}
.tree-cluster{display:flex;flex-direction:column;align-items:center;gap:12px;}
.tree-cluster-label{
  font-size:9.8px;
  font-weight:700;
  letter-spacing:.13em;
  text-transform:uppercase;
  white-space:nowrap;
  padding:4px 13px;
  border-radius:20px;
  color:color-mix(in srgb, var(--tree-teal) 62%, var(--tree-ink));
  background:color-mix(in srgb, var(--tree-teal) 10%, var(--tree-card));
  border:1px solid color-mix(in srgb, var(--tree-teal) 24%, transparent);
  box-shadow:0 1px 2px rgba(15,31,51,.04);
}

/* --- 5. Marriage indicator --- */
/* A rose hairline spanning the gap between the two cards with an ornamental
   badge seated at its centre: the marriage reads as a segment of the
   relationship line, not as a symbol floating between two cards. The outer
   .tc-heart spans the whole gap (its centre is what resolveAnchorPoint()
   uses as the couple's connector origin) and never overlaps either card, so
   both partners' accent strips and names stay clear. */
.tree-couple{display:flex;align-items:center;gap:0;}
.tc-heart{
  position:relative;
  z-index:2;
  flex-shrink:0;
  width:64px;
  height:36px;
  display:flex;
  align-items:center;
  justify-content:center;
}
/* The two halves of the line. The second is mirrored so the gradient fades
   away from the badge on both sides rather than sweeping across it. */
.tc-heart::before,
.tc-heart::after{
  content:'';
  position:absolute;
  top:50%;
  width:calc(50% - 15px);
  height:2px;
  margin-top:-1px;
  border-radius:2px;
  background:linear-gradient(90deg,
    color-mix(in srgb, var(--tree-rose) 22%, transparent) 0%,
    color-mix(in srgb, var(--tree-rose) 78%, transparent) 100%);
}
.tc-heart::before{left:0;}
.tc-heart::after{right:0;transform:scaleX(-1);}
.tc-marriage-badge{
  position:relative;
  width:32px;height:32px;
  border-radius:50%;
  display:flex;
  align-items:center;
  justify-content:center;
  color:#fff;
  background:linear-gradient(145deg,
    color-mix(in srgb, var(--tree-rose) 66%, #fff) 0%,
    var(--tree-rose) 56%,
    color-mix(in srgb, var(--tree-rose) 78%, var(--tree-lav)) 100%);
  border:1px solid color-mix(in srgb, var(--tree-rose) 50%, #fff);
  box-shadow:
    0 0 0 4px color-mix(in srgb, var(--tree-rose) 9%, transparent),
    0 6px 14px -7px color-mix(in srgb, var(--tree-rose) 95%, transparent),
    inset 0 1px 0 rgba(255,255,255,.5);
  transition:transform .3s cubic-bezier(.22,1,.36,1), box-shadow .3s ease-out;
}
.tc-marriage-badge svg{display:block;}
/* Two glints, held at rest and brought up on approach — the whole sparkle
   idea stays a hover reward rather than a permanently animating tree. */
.tc-spark{
  position:absolute;
  width:4px;height:4px;
  border-radius:50%;
  background:#fff;
  opacity:0;
  transform:scale(.4);
  box-shadow:0 0 6px 2px color-mix(in srgb, var(--tree-rose) 50%, transparent);
  transition:opacity .3s ease-out, transform .3s cubic-bezier(.34,1.35,.5,1);
}
.tc-spark.s1{top:-2px;right:0;}
.tc-spark.s2{bottom:0;left:-3px;transition-delay:.06s;}
@media (hover:hover) and (pointer:fine){
  .tc-heart:hover .tc-marriage-badge,
  .tree-couple:hover .tc-marriage-badge{
    transform:scale(1.12);
    box-shadow:
      0 0 0 6px color-mix(in srgb, var(--tree-rose) 12%, transparent),
      0 9px 20px -8px color-mix(in srgb, var(--tree-rose) 95%, transparent),
      inset 0 1px 0 rgba(255,255,255,.55);
  }
  .tc-heart:hover .tc-spark,
  .tree-couple:hover .tc-spark{opacity:1;transform:scale(1);}
}

/* --- 6. Member cards --- */
/* --tc-accent is the gender accent the legend documents; the Head overrides
   it with gold below, which is also in the legend ("Head of Family"), so the
   card that outranks everything on the page reads that way at a glance. */
.tree-card{
  --tc-accent:var(--tree-lav);
  position:relative;
  display:flex;
  align-items:center;
  gap:14px;
  min-width:236px;
  padding:15px 22px;
  border:1px solid var(--tree-line);
  border-radius:16px;
  background:linear-gradient(160deg, color-mix(in srgb, var(--tc-accent) 5%, var(--tree-card)) 0%, var(--tree-card) 60%);
  box-shadow:0 1px 2px rgba(15,31,51,.05), 0 12px 26px -20px rgba(15,31,51,.45);
  cursor:pointer;
  transition:transform .22s cubic-bezier(.22,1,.36,1), box-shadow .22s ease-out, border-color .22s ease-out;
}
/* The accent edge is a gradient strip rather than a flat border-left, and it
   is a pseudo-element so .tc-crown can still hang outside the card. */
.tree-card::before{
  content:'';
  position:absolute;
  left:0;top:0;bottom:0;
  width:5px;
  border-radius:16px 0 0 16px;
  background:linear-gradient(180deg, var(--tc-accent) 0%, color-mix(in srgb, var(--tc-accent) 40%, transparent) 100%);
  transition:width .22s cubic-bezier(.22,1,.36,1);
}
.tree-card[data-gender="male"]{--tc-accent:var(--tree-blue);}
.tree-card[data-gender="female"]{--tc-accent:var(--tree-rose);}
.tree-card[data-gender="other"]{--tc-accent:var(--tree-lav);}
.tree-card[data-role="head"]{
  --tc-accent:var(--tree-gold);
  border-color:color-mix(in srgb, var(--tree-gold) 40%, transparent);
  background:
    radial-gradient(125% 125% at 100% 0%, color-mix(in srgb, var(--tree-gold) 13%, transparent) 0%, transparent 60%),
    linear-gradient(160deg, color-mix(in srgb, var(--tree-gold) 7%, var(--tree-card)) 0%, var(--tree-card) 62%);
  box-shadow:0 2px 4px rgba(15,31,51,.06), 0 18px 36px -22px color-mix(in srgb, var(--tree-gold) 95%, transparent);
}
.tree-card[data-role="head"]::before{
  width:6px;
  background:linear-gradient(180deg, var(--tree-gold) 0%, var(--tree-navy) 100%);
}
/* A life already lived reads quieter, without being greyed out of legibility. */
.tree-card[data-status="deceased"]{
  background:linear-gradient(160deg, color-mix(in srgb, var(--tree-ink-3) 8%, var(--tree-card)) 0%, var(--tree-card) 60%);
}
.tree-card[data-status="deceased"]::before{
  background:linear-gradient(180deg,
    color-mix(in srgb, var(--tc-accent) 55%, var(--tree-ink-3)) 0%,
    color-mix(in srgb, var(--tree-ink-3) 35%, transparent) 100%);
}
.tree-card[data-status="deceased"] .tc-avatar img{filter:saturate(.5);}
.tc-avatar{
  width:58px;height:58px;
  border-radius:50%;
  overflow:hidden;
  display:flex;
  align-items:center;
  justify-content:center;
  flex-shrink:0;
  background:color-mix(in srgb, var(--tc-accent) 8%, var(--tree-card));
  box-shadow:0 0 0 2px var(--tree-card), 0 0 0 4px color-mix(in srgb, var(--tc-accent) 32%, transparent);
  transition:box-shadow .22s ease-out;
}
.tc-avatar img{width:100%;height:100%;object-fit:cover;background:transparent;}
.tc-body{min-width:0;}
.tc-name{
  display:flex;
  align-items:center;
  gap:8px;
  flex-wrap:wrap;
  font-size:15.5px;
  font-weight:700;
  letter-spacing:-.015em;
  line-height:1.25;
  color:var(--tree-ink);
}
.tc-meta{
  margin-top:5px;
  display:flex;
  align-items:center;
  gap:8px;
  flex-wrap:wrap;
  font-size:12.2px;
  font-weight:500;
  letter-spacing:.01em;
  font-variant-numeric:tabular-nums;
  /* Secondary to the name, but not so faint it stops being readable — the
     hierarchy comes from the 15.5/700 vs 12.2/500 contrast, not from grey. */
  color:color-mix(in srgb, var(--tree-ink-3) 45%, var(--tree-ink-2));
}
.tc-badge{
  display:inline-flex;
  align-items:center;
  padding:3px 9px;
  border-radius:20px;
  font-size:9.6px;
  font-weight:700;
  letter-spacing:.11em;
  text-transform:uppercase;
  white-space:nowrap;
}
.tc-badge-head{
  color:#2a1f05;
  background:linear-gradient(135deg, var(--tree-gold-soft) 0%, var(--tree-gold) 100%);
  border:1px solid color-mix(in srgb, var(--tree-gold) 72%, #000);
  box-shadow:0 3px 8px -4px color-mix(in srgb, var(--tree-gold) 95%, transparent),
             inset 0 1px 0 rgba(255,255,255,.45);
}
.tc-badge-rest{
  color:var(--tree-ink-3);
  background:color-mix(in srgb, var(--tree-ink-3) 12%, transparent);
  border:1px solid color-mix(in srgb, var(--tree-ink-3) 24%, transparent);
}
@media (hover:hover) and (pointer:fine){
  .tree-card:hover{
    transform:translateY(-3px);
    border-color:color-mix(in srgb, var(--tc-accent) 46%, transparent);
    box-shadow:0 2px 4px rgba(15,31,51,.06), 0 20px 36px -20px color-mix(in srgb, var(--tc-accent) 85%, transparent);
  }
  .tree-card:hover::before{width:7px;}
  .tree-card:hover .tc-avatar{
    box-shadow:0 0 0 2px var(--tree-card), 0 0 0 5px color-mix(in srgb, var(--tree-gold) 50%, transparent);
  }
}
.tree-card:focus-visible{
  outline:2px solid color-mix(in srgb, var(--tree-gold) 70%, transparent);
  outline-offset:2px;
}

/* --- 7. Connectors --- */
.tree-connector-svg{position:absolute;top:0;left:0;pointer-events:none;z-index:0;}
.tree-connector-line{
  fill:none;
  stroke-width:1.3;
  stroke-linecap:round;
  stroke-linejoin:round;
}
/* Descent (parent â†’ child) is a solid lineage line; a same-generation link
   (Sibling / Guardian to its anchor) is dashed, so the two kinds of
   connection are told apart without reading the layout. */
.tree-line-descent{stroke:color-mix(in srgb, var(--tree-emerald) 60%, transparent);}
.tree-line-lateral{
  stroke:color-mix(in srgb, var(--tree-teal) 55%, transparent);
  stroke-dasharray:5 5;
}

/* --- 8. View controls --- */
/* Laid out inside .tree-controlbar above the canvas — deliberately NOT
   absolutely positioned over .tree-stage. */
.tree-toolbar{
  display:flex;
  align-items:center;
  gap:5px;
  flex-wrap:wrap;
  padding:6px;
  border-radius:15px;
  border:1px solid var(--tree-line);
  background:color-mix(in srgb, var(--tree-card) 88%, transparent);
  backdrop-filter:blur(8px);
  -webkit-backdrop-filter:blur(8px);
  box-shadow:0 2px 6px rgba(15,31,51,.08), 0 18px 32px -20px rgba(15,31,51,.5);
}
.tree-toolbar-group{
  display:flex;
  align-items:center;
  gap:2px;
  padding:2px;
  border-radius:11px;
  background:color-mix(in srgb, var(--tree-teal) 8%, transparent);
  border:1px solid color-mix(in srgb, var(--tree-teal) 16%, transparent);
}
.tree-ctl-sep{
  width:1px;height:20px;
  margin:0 2px;
  background:var(--tree-line);
}
.tree-ctl{
  display:inline-flex;
  align-items:center;
  gap:6px;
  font-family:inherit;
  font-size:12px;
  font-weight:600;
  letter-spacing:.015em;
  color:var(--tree-ink-2);
  background:transparent;
  border:1px solid transparent;
  border-radius:10px;
  padding:6px 10px;
  cursor:pointer;
  transition:color .18s ease-out, background-color .18s ease-out,
             border-color .18s ease-out, transform .18s cubic-bezier(.22,1,.36,1);
}
.tree-ctl svg{display:block;opacity:.85;}
.tree-ctl-icon{padding:6px;}
.tree-ctl:hover{
  color:color-mix(in srgb, var(--tree-teal) 58%, var(--tree-ink));
  background:color-mix(in srgb, var(--tree-teal) 12%, transparent);
  border-color:color-mix(in srgb, var(--tree-teal) 26%, transparent);
}
.tree-ctl:hover svg{opacity:1;}
.tree-ctl-head:hover{
  color:color-mix(in srgb, var(--tree-gold) 58%, var(--tree-ink));
  background:color-mix(in srgb, var(--tree-gold) 14%, transparent);
  border-color:color-mix(in srgb, var(--tree-gold) 32%, transparent);
}
.tree-ctl:active{transform:translateY(1px);}
.tree-ctl:focus-visible{
  outline:2px solid color-mix(in srgb, var(--tree-teal) 65%, transparent);
  outline-offset:1px;
}
.tree-zoom-label{
  min-width:42px;
  text-align:center;
  font-size:11.4px;
  font-weight:700;
  letter-spacing:.01em;
  font-variant-numeric:tabular-nums;
  color:color-mix(in srgb, var(--tree-teal) 62%, var(--tree-ink));
}

/* --- 9. Family Head crown badge â€” glittering highlight at the top-right of
   the Head's card, in addition to the crown baked into head.png itself. --- */
.tc-crown{position:absolute;top:-14px;right:-10px;width:34px;height:34px;z-index:3;pointer-events:none;}
.tc-crown-icon{
  position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-size:21px;filter:drop-shadow(0 1px 3px rgba(168,124,20,.55));
  animation:tc-crown-float 2.6s ease-in-out infinite;
}
.tc-sparkle{
  position:absolute;width:5px;height:5px;border-radius:50%;
  background:#fff6d8;box-shadow:0 0 7px 2px #ffe27a;
  animation:tc-sparkle-twinkle 1.6s ease-in-out infinite;
}
.tc-sparkle.s1{top:-2px;left:-3px;animation-delay:0s;}
.tc-sparkle.s2{top:8px;right:-7px;animation-delay:.5s;}
.tc-sparkle.s3{bottom:-1px;left:9px;animation-delay:1s;}
@keyframes tc-crown-float{0%,100%{transform:translateY(0) rotate(-3deg);}50%{transform:translateY(-2px) rotate(3deg);}}
@keyframes tc-sparkle-twinkle{0%,100%{opacity:0;transform:scale(.4);}50%{opacity:1;transform:scale(1.2);}}

/* --- 10. Footer --- */
.tree-scene-footer{
  display:flex;
  justify-content:center;
  margin-top:18px;
  position:relative;
  z-index:1;
}
.tree-scene-quote{
  display:inline-flex;
  align-items:center;
  gap:11px;
  padding:8px 20px;
  border-radius:22px;
  border:1px solid color-mix(in srgb, var(--tree-gold) 26%, transparent);
  background:color-mix(in srgb, var(--tree-gold) 8%, var(--tree-card));
  font-size:12.2px;
  font-weight:500;
  font-style:italic;
  letter-spacing:.012em;
  color:color-mix(in srgb, var(--tree-gold) 42%, var(--tree-ink));
}
.tree-scene-quote::before,
.tree-scene-quote::after{
  content:'';
  width:18px;height:1px;
  flex-shrink:0;
  background:linear-gradient(90deg, transparent, color-mix(in srgb, var(--tree-gold) 65%, transparent));
}
.tree-scene-quote::after{
  background:linear-gradient(90deg, color-mix(in srgb, var(--tree-gold) 65%, transparent), transparent);
}

/* --- 11. Other Relatives â€” the same paper, one step quieter --- */
.card.tree-others{
  position:relative;
  overflow:hidden;
  padding:22px 26px;
  border-radius:20px;
  border:1px solid var(--tree-line);
  background:
    radial-gradient(85% 115% at 0% 0%, color-mix(in srgb, var(--tree-lav) 9%, transparent) 0%, transparent 62%),
    linear-gradient(165deg, var(--tree-paper) 0%, var(--tree-paper-2) 100%);
  box-shadow:0 1px 2px rgba(15,31,51,.04), 0 22px 42px -32px rgba(15,31,51,.36);
}
.tree-others-head{display:flex;align-items:flex-start;gap:14px;margin-bottom:20px;}
.tree-others-ico{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:42px;height:42px;
  flex-shrink:0;
  border-radius:13px;
  color:var(--tree-lav);
  background:color-mix(in srgb, var(--tree-lav) 11%, var(--tree-card));
  border:1px solid color-mix(in srgb, var(--tree-lav) 22%, transparent);
}
.tree-others-ico svg{display:block;}
.tree-others-text{min-width:0;}
.tree-others .tree-eyebrow{color:color-mix(in srgb, var(--tree-lav) 58%, var(--tree-ink));}
.tree-others-title{
  margin:6px 0 0;
  font-size:18px;
  font-weight:700;
  letter-spacing:-.02em;
  color:var(--tree-ink);
}
.tree-others .tree-sub{margin:7px 0 0;max-width:640px;}
.tree-others-tier{margin-bottom:0;gap:20px;justify-content:flex-start;}

/* --- 12. Responsive --- */
@media (max-width:760px){
  .tree-scene{padding:20px 15px 18px;}
  .tree-scene-header{padding:4px 6px 0;}
  .tree-title{font-size:21px;}
  .tree-sub{font-size:12.6px;}
  /* The bar stacks: legend first, controls under it, both centred. */
  .tree-controlbar{justify-content:center;gap:10px;padding:15px 0 12px;}
  .tree-canvas{padding:26px 16px 24px;}
  .tree-tier{gap:28px;margin-bottom:58px;}
  .tree-card{min-width:0;padding:13px 16px;gap:11px;}
  .tc-avatar{width:50px;height:50px;}
  .tc-name{font-size:14.5px;}
  .tc-heart{width:48px;}
  .tc-heart::before,.tc-heart::after{width:calc(50% - 15px);}
  .tc-marriage-badge{width:30px;height:30px;}
  /* Icon-only controls at this width â€” the title tooltips and aria-labels
     still carry each control's name. */
  .tree-toolbar{gap:4px;padding:5px;}
  .tree-ctl{padding:6px;}
  .tree-ctl > span{display:none;}
  .card.tree-others{padding:18px 15px;}
  .tree-others-tier{gap:14px;}
}

@media (prefers-reduced-motion: reduce){
  .tree-card, .tree-card::before, .tc-avatar, .tree-ctl,
  .tc-marriage-badge, .tc-spark{transition:none;}
  .tree-card:hover, .tree-ctl:active,
  .tc-heart:hover .tc-marriage-badge, .tree-couple:hover .tc-marriage-badge{transform:none;}
  .tc-heart:hover .tc-spark, .tree-couple:hover .tc-spark{transform:none;opacity:1;}
  .tc-crown-icon, .tc-sparkle{animation:none;}
  .tc-sparkle{opacity:.7;}
}

.security-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:12px;}
.security-item{
  display:flex;gap:10px;align-items:center;
  border:1px solid var(--border);border-radius:8px;padding:12px 14px;
}
.security-item .s-icon{font-size:18px;}
.security-item .s-label{font-size:11.5px;color:var(--text-400);}
.security-item .s-value{font-size:13px;font-weight:700;color:var(--text-900);margin-top:1px;}

.policy-list{display:flex;flex-direction:column;}
.policy-item{padding:14px 0;border-bottom:1px solid var(--border);}
.policy-item:last-child{border-bottom:none;}
.policy-title{font-weight:700;font-size:13.4px;color:var(--text-900);margin-bottom:3px;}
.policy-text{font-size:12.8px;color:var(--text-600);line-height:1.6;}

.app-footer{
  margin-top:24px;padding-top:20px;border-top:1px solid var(--border);
  text-align:center;color:var(--text-400);font-size:12px;line-height:1.8;
}
.app-footer .footer-links{display:flex;justify-content:center;gap:16px;flex-wrap:wrap;margin-bottom:8px;}
.app-footer .footer-links span{cursor:default;color:var(--text-600);font-weight:600;}

/* ---------- Nest Assistant (AI widget) ---------- */
/* Entrance (scale .8 -> 1, opacity 0 -> 1, ~500ms, ~1s after page load) -
   app.js's initShellSettleIn() adds .ai-in once, on real page boot only,
   same one-time-only rule as the topbar/sidebar settle-in. */
.ai-widget{
  position:fixed;right:22px;bottom:22px;z-index:150;
  opacity:0;transform:scale(.8);
  transition:opacity .5s cubic-bezier(0.22,1,0.36,1), transform .5s cubic-bezier(0.22,1,0.36,1);
}
.ai-widget.ai-in{opacity:1;transform:scale(1);}
.ai-toggle{
  width:56px;height:56px;border-radius:50%;
  background:linear-gradient(135deg, var(--gold-500), var(--gold-600));
  border:none;font-size:24px;cursor:pointer;
  box-shadow:0 6px 18px rgba(200,153,46,.4);
  display:flex;align-items:center;justify-content:center;
  transition:transform .15s ease;
  animation:ai-idle-pulse 4s ease-in-out 1.6s infinite;
}
.ai-toggle:hover{animation-play-state:paused;transform:scale(1.06);}
.ai-toggle:active{animation-play-state:paused;transform:scale(.96);}
/* Idle "still here" breathing scale, paused on hover/press (so those can
   actually take over the transform - an animation keeps re-asserting its
   own value every frame otherwise) and the moment the panel is open, kept
   extremely subtle per the brief (1 -> 1.02 -> 1) rather than a bigger,
   more distracting motion. */
@keyframes ai-idle-pulse{0%,100%{transform:scale(1);}50%{transform:scale(1.02);}}
.ai-widget.ai-open .ai-toggle{animation-play-state:paused;transform:none;}
.ai-panel{
  display:flex;
  position:absolute;right:0;bottom:70px;
  width:360px;max-width:calc(100vw - 44px);
  height:480px;max-height:70vh;
  background:var(--card);
  border:1px solid var(--border);
  border-radius:14px;
  box-shadow:var(--shadow-md);
  flex-direction:column;
  overflow:hidden;
  /* Kept in the layout (not display:none) so open/close can actually
     transition - visibility+pointer-events keep it inert and untabbable
     while closed instead of just invisible. */
  opacity:0;visibility:hidden;pointer-events:none;
  transform:translateY(24px) scale(.97);
  transition:opacity .28s cubic-bezier(0.16,1,0.3,1), transform .28s cubic-bezier(0.16,1,0.3,1), visibility 0s linear .28s;
}
.ai-widget.ai-open .ai-panel{
  opacity:1;visibility:visible;pointer-events:auto;
  transform:translateY(0) scale(1);
  transition:opacity .28s cubic-bezier(0.16,1,0.3,1), transform .28s cubic-bezier(0.16,1,0.3,1);
}
.ai-panel-header{
  display:flex;align-items:flex-start;justify-content:space-between;gap:10px;
  padding:14px 16px;background:linear-gradient(135deg, var(--navy-950), var(--navy-800));color:#fff;
}
.ai-panel-title{font-weight:700;font-size:13.6px;}
.ai-panel-sub{font-size:11px;color:#a9b6cc;margin-top:2px;}
.ai-panel-close{background:none;border:none;color:#cdd6e8;font-size:14px;cursor:pointer;}
.ai-messages{flex:1;overflow-y:auto;padding:14px 16px;display:flex;flex-direction:column;gap:10px;}
.ai-msg{
  max-width:88%;
  padding:9px 12px;
  border-radius:12px;
  font-size:12.8px;
  line-height:1.55;
  white-space:pre-wrap;
}
.ai-msg-bot{align-self:flex-start;background:var(--bg);border:1px solid var(--border);border-bottom-left-radius:3px;color:var(--text-900);}
.ai-msg-user{align-self:flex-end;background:var(--navy-800);color:#fff;border-bottom-right-radius:3px;}
.ai-msg-actions{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px;}
.ai-msg-actions .btn{padding:5px 10px;font-size:11.6px;}
.ai-suggestions{display:flex;flex-wrap:wrap;gap:6px;padding:0 16px 10px;}
.ai-chip{
  border:1px solid var(--border);background:var(--card);color:var(--text-600);
  border-radius:20px;padding:5px 11px;font-size:11.4px;cursor:pointer;
  transition:background .12s ease;
}
.ai-chip:hover{background:var(--gold-bg);border-color:var(--gold-500);color:var(--gold-600);}
.ai-input-row{display:flex;gap:8px;padding:12px 16px;border-top:1px solid var(--border);}
.ai-input-row input{
  flex:1;border:1px solid var(--border);border-radius:8px;padding:8px 11px;font-size:12.8px;font-family:inherit;
}
.ai-input-row input:focus{outline:none;border-color:var(--gold-500);}

/* ---------- Ask Claude AI widget ---------- */
/* Mirrors the Nest Assistant block above 1:1 for visual parity (same
   tokens, same motion), positioned directly above it: Nest Assistant's
   button is 56px tall at bottom:22px, so bottom:90px stacks this one
   cleanly above with a ~12px gap, independent of DOM order. */
.claude-widget{
  position:fixed;right:22px;bottom:90px;z-index:150;
  opacity:0;transform:scale(.8);
  transition:opacity .5s cubic-bezier(0.22,1,0.36,1), transform .5s cubic-bezier(0.22,1,0.36,1);
}
.claude-widget.claude-in{opacity:1;transform:scale(1);}
.claude-toggle{
  width:56px;height:56px;border-radius:50%;
  background:linear-gradient(135deg, var(--gold-500), var(--gold-600));
  border:none;font-size:24px;cursor:pointer;
  box-shadow:0 6px 18px rgba(200,153,46,.4);
  display:flex;align-items:center;justify-content:center;
  transition:transform .15s ease;
  animation:ai-idle-pulse 4s ease-in-out 2.4s infinite;
}
.claude-toggle:hover{animation-play-state:paused;transform:scale(1.06);}
.claude-toggle:active{animation-play-state:paused;transform:scale(.96);}
.claude-widget.claude-open .claude-toggle{animation-play-state:paused;transform:none;}
.claude-panel{
  display:flex;
  position:absolute;right:0;bottom:70px;
  width:360px;max-width:calc(100vw - 44px);
  height:480px;max-height:70vh;
  background:var(--card);
  border:1px solid var(--border);
  border-radius:14px;
  box-shadow:var(--shadow-md);
  flex-direction:column;
  overflow:hidden;
  opacity:0;visibility:hidden;pointer-events:none;
  transform:translateY(24px) scale(.97);
  transition:opacity .28s cubic-bezier(0.16,1,0.3,1), transform .28s cubic-bezier(0.16,1,0.3,1), visibility 0s linear .28s;
}
.claude-widget.claude-open .claude-panel{
  opacity:1;visibility:visible;pointer-events:auto;
  transform:translateY(0) scale(1);
  transition:opacity .28s cubic-bezier(0.16,1,0.3,1), transform .28s cubic-bezier(0.16,1,0.3,1);
}
.claude-panel-header{
  display:flex;align-items:flex-start;justify-content:space-between;gap:10px;
  padding:14px 16px;background:linear-gradient(135deg, var(--navy-950), var(--navy-800));color:#fff;
}
.claude-panel-title{font-weight:700;font-size:13.6px;}
.claude-panel-sub{font-size:11px;color:#a9b6cc;margin-top:2px;}
.claude-panel-close{background:none;border:none;color:#cdd6e8;font-size:14px;cursor:pointer;}
.claude-messages{flex:1;overflow-y:auto;padding:14px 16px;display:flex;flex-direction:column;gap:10px;}
.claude-msg{
  max-width:88%;
  padding:9px 12px;
  border-radius:12px;
  font-size:12.8px;
  line-height:1.55;
  white-space:pre-wrap;
}
.claude-msg-bot{align-self:flex-start;background:var(--bg);border:1px solid var(--border);border-bottom-left-radius:3px;color:var(--text-900);}
.claude-msg-user{align-self:flex-end;background:var(--navy-800);color:#fff;border-bottom-right-radius:3px;}
.claude-msg-thinking{color:var(--text-400);font-style:italic;}
.claude-input-row{display:flex;gap:8px;padding:12px 16px;border-top:1px solid var(--border);}
.claude-input-row input{
  flex:1;border:1px solid var(--border);border-radius:8px;padding:8px 11px;font-size:12.8px;font-family:inherit;
}
.claude-input-row input:focus{outline:none;border-color:var(--gold-500);}
.claude-input-row input:disabled{opacity:.6;}

/* ---------- Date Picker (calendar popup, replaces native date inputs) ---------- */
.date-field-wrap{position:relative;display:flex;align-items:center;}
.date-input-display{cursor:text;padding-right:32px !important;}
.date-input-display::placeholder{color:var(--text-400);}
.date-input-display.date-input-active{border-color:var(--gold-500);box-shadow:0 0 0 3px rgba(200,153,46,.18);}
.date-input-icon{
  position:absolute;right:9px;top:50%;transform:translateY(-50%);
  font-size:13px;cursor:pointer;opacity:.7;
}
.date-picker-popup{
  position:fixed;z-index:400;
  width:270px;
  background:var(--card);
  border:1px solid var(--border);
  border-radius:10px;
  box-shadow:var(--shadow-md);
  padding:10px 12px 12px;
  font-size:12.8px;
}
.dp-header{display:flex;align-items:center;gap:6px;margin-bottom:8px;}
.dp-nav{
  border:1px solid var(--border);background:var(--card);border-radius:6px;
  width:26px;height:26px;flex:0 0 auto;cursor:pointer;
  font-size:15px;line-height:1;color:var(--text-600);
  display:flex;align-items:center;justify-content:center;
}
.dp-nav:hover{border-color:var(--gold-500);color:var(--gold-600);}
.dp-month-select{
  flex:1;min-width:0;border:1px solid var(--border);border-radius:6px;
  padding:5px 6px;font-size:12.6px;font-family:inherit;background:var(--card);color:var(--text-900);
}
.dp-year-input{
  width:64px;border:1px solid var(--border);border-radius:6px;
  padding:5px 6px;font-size:12.6px;font-family:inherit;background:var(--card);color:var(--text-900);text-align:center;
}
.dp-month-select:focus,.dp-year-input:focus{outline:none;border-color:var(--gold-500);box-shadow:0 0 0 2px rgba(200,153,46,.18);}
.dp-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;}
.dp-grid-header{margin-bottom:2px;}
.dp-dayname{
  text-align:center;font-size:10.8px;font-weight:700;color:var(--text-600);
  text-transform:uppercase;letter-spacing:.02em;padding:2px 0;
}
.dp-day{
  border:none;background:transparent;border-radius:50%;
  width:100%;aspect-ratio:1/1;font-size:12.4px;color:var(--text-900);
  cursor:pointer;display:flex;align-items:center;justify-content:center;
}
.dp-day:hover{background:var(--gold-bg);}
.dp-day-other{color:var(--text-400);}
.dp-day-today{border:1.5px solid var(--blue);font-weight:700;color:var(--blue);}
.dp-day-selected,.dp-day-selected:hover{background:var(--blue);color:#fff;font-weight:700;border-color:var(--blue);}
.dp-footer{display:flex;justify-content:space-between;gap:8px;margin-top:10px;padding-top:8px;border-top:1px solid var(--border);}

/* ---------- Category page hero + tab strip ----------
   Asset Management and Obligations / Liabilities, built to the AIS sample
   screens. The two pages are the same layout in two skins, so everything
   structural lives on .cat-* and only colour comes from the -assets /
   -liabilities modifiers.

   The hero art reuses assets/fbs-assets-bg.png and fbs-liabilities-bg.png -
   the same illustrations the Family Balance Sheet's two columns already use,
   which is why no new image files were needed for this. They sit right-
   aligned under a gradient that is near-opaque at the left so the title has
   a solid ground and clears to almost nothing over the artwork. */
.cat-hero{
  position:relative;
  border-radius:var(--radius);
  overflow:hidden;
  min-height:150px;
  margin-bottom:14px;
  box-shadow:var(--shadow-md);
}
.cat-hero-bg{
  position:absolute;inset:0;width:100%;height:100%;
  object-fit:cover;object-position:right center;display:block;
}
.cat-hero-overlay{position:absolute;inset:0;}
/* The thin gold light-sweep along the hero's foot in both sample screens. */
.cat-hero::after{
  content:"";position:absolute;left:0;right:0;bottom:0;height:2px;
  background:linear-gradient(90deg, transparent 0%, var(--gold-400) 18%, #fff6d8 42%, var(--gold-400) 70%, transparent 100%);
  opacity:.85;z-index:2;
}
.cat-hero-content{
  position:relative;z-index:1;
  display:flex;align-items:center;gap:18px;
  padding:26px 28px;min-height:150px;
}
.cat-hero-icon{
  flex-shrink:0;
  width:72px;height:72px;border-radius:16px;
  display:flex;align-items:center;justify-content:center;
  box-shadow:0 6px 18px rgba(0,0,0,.35);
}
.cat-hero-icon img{width:42px;height:42px;object-fit:contain;}
.cat-hero-title{
  margin:0;font-size:34px;font-weight:800;color:#fff;
  font-family:Georgia,'Times New Roman',serif;letter-spacing:.01em;
  text-shadow:0 2px 10px rgba(0,0,0,.45);
}
.cat-hero-sub{
  margin:4px 0 0;font-size:15px;font-weight:500;color:#e9eee9;
  text-shadow:0 1px 6px rgba(0,0,0,.5);
}
.cat-hero-sub .cat-hero-rule{opacity:.5;margin:0 8px;}
.cat-hero-assets{border:1px solid rgba(220,174,74,.55);}
/* ---- Video-backed heroes (.cat-hero-clip, set by catHeroHtml when the
   backdrop file is an .mp4) ----
   The clip is penned into the right half rather than spread edge to edge,
   which keeps the card near the height a still-image hero has. The left half
   is therefore covered by nothing, so each skin paints its own ground for
   the icon and title to sit on (below), picked to meet its clip's own
   near-black left edge so the join reads as one surface rather than a seam.
   Everything here is geometry shared by both pages; only the ground and the
   scrim differ, per skin. */
.cat-hero-clip,
.cat-hero-clip .cat-hero-content{min-height:170px;}
.cat-hero-clip .cat-hero-bg{
  left:auto;right:0;width:50%;
  /* Biased below centre: both clips leave their upper third near-empty and
     put every object worth seeing in the lower half. */
  object-position:center 68%;
  /* Feathers the clip's left edge into the painted ground instead of ending
     on a hard vertical seam halfway across the card. */
  -webkit-mask-image:linear-gradient(90deg, transparent 0%, rgba(0,0,0,.55) 20%, #000 52%);
  mask-image:linear-gradient(90deg, transparent 0%, rgba(0,0,0,.55) 20%, #000 52%);
}
/* No tile behind the icon on these: it sits straight on the hero, which is
   what matching the background it shows against amounts to. Bigger to hold
   its own without a tile framing it, with a drop-shadow doing the separating
   work the tile's own shadow used to.
   Qualified with .cat-hero purely for specificity: the per-skin
   .cat-hero-assets/.cat-hero-liabilities icon rules that set the green and
   red tiles tie with a bare .cat-hero-clip selector, and one of them sits
   later in this file - which left the Liabilities icon still wearing its red
   tile until this rule outranked it rather than merely disagreeing with it. */
.cat-hero.cat-hero-clip .cat-hero-icon{
  background:none;box-shadow:none;
  width:86px;height:86px;
}
.cat-hero.cat-hero-clip .cat-hero-icon img{
  width:80px;height:80px;
  filter:drop-shadow(0 6px 14px rgba(0,0,0,.55));
}
/* Painted ground + scrim per skin. The scrim now only has to take the edge
   off where the text meets the clip - the left half is painted ground and
   the right is the feature, so it clears far earlier than the near-opaque
   one a still-image hero needs. */
.cat-hero-assets.cat-hero-clip{
  background:linear-gradient(100deg, #02120c 0%, #042117 55%, #063020 100%);
}
.cat-hero-assets.cat-hero-clip .cat-hero-overlay{
  background:linear-gradient(90deg, rgba(1,14,10,.72) 0%, rgba(1,14,10,.45) 34%, rgba(1,16,11,.12) 58%, rgba(1,16,11,0) 76%);
}
.cat-hero-liabilities.cat-hero-clip{
  background:linear-gradient(100deg, #170406 0%, #2b0a0d 55%, #380d10 100%);
}
.cat-hero-liabilities.cat-hero-clip .cat-hero-overlay{
  background:linear-gradient(90deg, rgba(18,3,4,.72) 0%, rgba(18,3,4,.45) 34%, rgba(22,4,5,.12) 58%, rgba(22,4,5,0) 76%);
}
.cat-hero-liabilities{border:1px solid rgba(220,174,74,.45);}
.cat-hero-liabilities .cat-hero-overlay{
  background:linear-gradient(90deg, rgba(40,4,4,.985) 0%, rgba(40,4,4,.97) 42%, rgba(52,8,6,.72) 62%, rgba(52,8,6,.18) 82%, rgba(52,8,6,.06) 100%);
}
.cat-hero-liabilities .cat-hero-icon{background:linear-gradient(160deg,#e0342a,#9d1109);}

/* The category pills. One horizontally scrolling row - the sample screens
   both end in a "›" that scrolls it, rather than the plain wrapping rows of
   the generic .tabs this replaces on these two pages. */
.cat-tabs-strip{
  display:flex;align-items:center;gap:8px;
  border-radius:14px;padding:8px 10px;margin-bottom:18px;
}
/* The glide comes from catTabsScroll()'s own behavior:'smooth', not from a
   scroll-behavior here, so a drag or a trackpad swipe across the pills still
   tracks the finger instead of easing after it. */
.cat-tabs{
  display:flex;gap:8px;flex:1;min-width:0;
  overflow-x:auto;
  scrollbar-width:none;-ms-overflow-style:none;
}
.cat-tabs::-webkit-scrollbar{display:none;}
.cat-tab{
  display:inline-flex;align-items:center;gap:8px;white-space:nowrap;
  border-radius:999px;border:1px solid transparent;
  padding:9px 16px;font:inherit;font-size:13.2px;font-weight:600;
  cursor:pointer;transition:background .15s ease,color .15s ease;
}
.cat-tab-icon{display:inline-flex;align-items:center;font-size:16px;line-height:1;}
.cat-tab-icon img{width:20px;height:20px;object-fit:contain;}
.cat-tab-count{font-weight:600;opacity:.72;}
.cat-tabs-arrow{
  flex-shrink:0;border:none;background:none;cursor:pointer;
  font-size:24px;line-height:1;padding:0 6px;font-family:inherit;
  transition:opacity .15s ease;
}
/* Nowhere further to scroll in that direction - kept in place rather than
   hidden so the strip's width doesn't jump as you reach either end. */
.cat-tabs-arrow.is-off{opacity:.28;cursor:default;pointer-events:none;}
/* Assets: dark navy bar, gold ring, gold active pill.
   Reports wears the same skin — it was asked for explicitly, and the two are
   the app's only strips long enough to need the arrows. Shared by extending
   these selectors rather than copying them, so the look cannot drift apart;
   the inner shadow below is Reports' alone. */
.cat-tabs-strip-assets,
.cat-tabs-strip-reports{
  background:linear-gradient(180deg, var(--navy-900), var(--navy-950));
  border:1.5px solid var(--gold-500);
}
.cat-tabs-strip-assets .cat-tab,
.cat-tabs-strip-reports .cat-tab{
  color:#dce5f2;background:rgba(255,255,255,.05);border-color:rgba(255,255,255,.09);
}
.cat-tabs-strip-assets .cat-tab:hover,
.cat-tabs-strip-reports .cat-tab:hover{background:rgba(255,255,255,.13);}
.cat-tabs-strip-assets .cat-tab.active,
.cat-tabs-strip-reports .cat-tab.active{
  background:linear-gradient(180deg, var(--gold-400), var(--gold-600));
  color:#20160a;border-color:var(--gold-400);
  box-shadow:0 2px 10px rgba(200,153,46,.45);
}
.cat-tabs-strip-assets .cat-tabs-arrow,
.cat-tabs-strip-reports .cat-tabs-arrow{color:#dce5f2;}
/* The recessed look asked for on this page: the rail reads as set into the
   surface rather than laid on it. */
.cat-tabs-strip-reports{
  box-shadow:inset 0 2px 7px rgba(0,0,0,.5), inset 0 -1px 0 rgba(255,255,255,.05);
}
/* Liabilities: light card bar, deep red active pill. */
.cat-tabs-strip-liabilities{
  background:var(--card);border:1px solid var(--border);box-shadow:var(--shadow);
}
.cat-tabs-strip-liabilities .cat-tab{
  color:var(--text-600);background:var(--subtle-bg);border-color:var(--border);
}
.cat-tabs-strip-liabilities .cat-tab:hover{color:var(--text-900);}
.cat-tabs-strip-liabilities .cat-tab.active{
  background:linear-gradient(180deg,#8f1d16,#5d100b);
  color:#fff;border-color:#5d100b;
  box-shadow:0 2px 10px rgba(143,29,22,.35);
}
.cat-tabs-strip-liabilities .cat-tabs-arrow{color:var(--red);}

@media(max-width:700px){
  .cat-hero-content{padding:20px;gap:14px;}
  .cat-hero-icon{width:56px;height:56px;border-radius:13px;}
  .cat-hero-icon img{width:32px;height:32px;}
  .cat-hero-title{font-size:26px;}
  .cat-hero-sub{font-size:13.4px;}
}

/* ---------- Responsive ---------- */
@media(max-width:880px){
  .sidebar{position:fixed;left:0;top:0;bottom:0;transform:translateX(-100%);transition:transform .18s ease;box-shadow:var(--shadow-md);}
  .sidebar.open{transform:translateX(0);}
  .hamburger{display:block;}
  .content{padding:16px;}
}

/* ---------- Travel Kit page ---------- */
.tk-hero{position:relative;border-radius:var(--radius);overflow:hidden;min-height:250px;margin-bottom:18px;box-shadow:var(--shadow-md);background:#0a1220;}
/* object-fit:cover - fills the hero edge-to-edge with no pillarboxing
   (contain left black bars down both sides on this wide a card); crops
   the video's own edges as needed instead, same as the original hero. */
.tk-hero-video{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;}
.tk-hero-overlay{position:absolute;inset:0;background:linear-gradient(180deg, rgba(10,18,32,.20) 0%, rgba(10,18,32,.50) 55%, rgba(10,18,32,.85) 100%);}
.tk-hero-content{position:relative;z-index:1;display:flex;justify-content:space-between;align-items:flex-end;flex-wrap:wrap;gap:16px;padding:30px 28px;min-height:250px;}
.tk-hero-title{margin:0;font-size:32px;font-weight:800;color:#fff;font-family:Georgia,'Times New Roman',serif;letter-spacing:.01em;}
.tk-hero-sub{margin:8px 0 0;font-size:13.2px;color:#e8ddc4;max-width:480px;}
.tk-hero-controls{display:flex;gap:10px;align-items:center;flex-wrap:wrap;}
.tk-hero-select{padding:9px 12px;border-radius:8px;border:1px solid rgba(255,255,255,.35);background:rgba(10,18,32,.55);color:#fff;font-size:12.8px;font-weight:600;min-width:190px;}
.tk-hero-select option{color:#1a2233;}
@media(max-width:700px){
  .tk-hero-content{flex-direction:column;align-items:flex-start;}
  .tk-hero-controls{width:100%;}
  .tk-hero-select{flex:1;}
}

/* Digital Will card — assets/digital-will-hero.mp4 looping behind the card
   content, same edge-to-edge video/overlay/content-layer technique as
   .tk-hero above. .card already carries its own padding (18px 20px, see
   the base .card rule); .will-video-card zeroes that back to 0 so the
   video (position:absolute; inset:0) can fill the card's full padding box
   instead of stopping at a padding edge, and .will-video-content puts that
   same 18px 20px padding back so the real content still sits inset from
   the border exactly as an unmodified .card would. The overlay + light
   text colors below exist because the card's normal theme-based text
   colors (var(--text-900) etc.) assume a plain card surface, not a video
   background — same reasoning as .tk-hero-title/.tk-hero-sub's hardcoded
   #fff/#e8ddc4 just above. */
.will-video-card{position:relative;overflow:hidden;padding:0;}
.will-video-bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;}
.will-video-overlay{position:absolute;inset:0;background:linear-gradient(180deg, rgba(10,10,10,.38) 0%, rgba(10,10,10,.55) 60%, rgba(10,10,10,.78) 100%);}
.will-video-content{position:relative;z-index:1;padding:18px 20px;}
.will-video-content .card-title,
.will-video-content .em-title,
.will-video-content .section-title{color:#fff;}
.will-video-content .card-sub,
.will-video-content .kicker,
.will-video-content .text-sm,
.will-video-content .em-icon,
.will-video-content .empty-state{color:#e8ddc4;}
.will-video-content .divider{border-color:rgba(255,255,255,.25);}

.tk-grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:18px;}
@media(max-width:1100px){.tk-grid-4{grid-template-columns:repeat(2,1fr);}}
@media(max-width:640px){.tk-grid-4{grid-template-columns:1fr;}}

.tk-card{
  position:relative;background:linear-gradient(180deg,var(--card),var(--subtle-bg));
  border:1px solid var(--gold-500);border-radius:14px;padding:18px 18px 14px;
  box-shadow:var(--shadow);display:flex;flex-direction:column;
}
/* Four corner brackets: the card's own ::before/::after draw top-left and
   bottom-right, and .tk-card-corners (the empty span travelKit.js emits as
   the card's first child) supplies the other two pseudo-element slots.
   Radii match the card's 14px so the brackets sit on the rounded corner
   instead of cutting across it. */
.tk-card::before,.tk-card::after{content:'';position:absolute;width:20px;height:20px;pointer-events:none;}
.tk-card::before{top:7px;left:7px;border-top:2px solid var(--gold-500);border-left:2px solid var(--gold-500);border-top-left-radius:9px;}
.tk-card::after{bottom:7px;right:7px;border-bottom:2px solid var(--gold-500);border-right:2px solid var(--gold-500);border-bottom-right-radius:9px;}
.tk-card-corners{position:absolute;inset:0;border-radius:inherit;pointer-events:none;}
.tk-card-corners::before,.tk-card-corners::after{content:'';position:absolute;width:20px;height:20px;pointer-events:none;}
.tk-card-corners::before{top:7px;right:7px;border-top:2px solid var(--gold-500);border-right:2px solid var(--gold-500);border-top-right-radius:9px;}
.tk-card-corners::after{bottom:7px;left:7px;border-bottom:2px solid var(--gold-500);border-left:2px solid var(--gold-500);border-bottom-left-radius:9px;}
.tk-card:hover{border-color:var(--gold-400);}
.tk-card:hover::before,.tk-card:hover::after,
.tk-card:hover .tk-card-corners::before,.tk-card:hover .tk-card-corners::after{border-color:var(--gold-400);}

.tk-card-header{display:flex;align-items:center;gap:8px;margin-bottom:12px;}
/* The header mark is inline SVG (TK_ICONS in travelKit.js), not an emoji or
   a PNG: currentColor inherits the gold set here, so one rule tints all
   eight marks and each theme gets the right gold with no second asset. */
.tk-card-icon{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;color:var(--gold-500);flex:none;}
.tk-icon{width:24px;height:24px;display:block;overflow:visible;}
/* The passport cover, the shield body and the folder are the solid dark
   masses in the marks. Against the light card they carry the silhouette;
   against the dark card they sit within a couple of steps of the background
   and the shape dissolves. One value to tune for both themes. */
.tk-art-ink{fill:#122036;}
[data-theme="dark"] .tk-art-ink{fill:#33425f;}
.tk-card-title{font-size:14.3px;font-weight:800;color:var(--text-900);}
.tk-card-body{flex:1;display:flex;flex-direction:column;gap:10px;}
.tk-card-footer{display:flex;justify-content:space-between;align-items:center;gap:8px;margin-top:14px;padding-top:12px;border-top:1px solid rgba(200,153,46,.35);}

.tk-doc-row{display:flex;justify-content:space-between;gap:10px;align-items:flex-start;padding:10px;background:var(--card);border:1px solid rgba(200,153,46,.25);border-radius:9px;}
.tk-doc-title{font-weight:700;font-size:13px;color:var(--text-900);}
.tk-doc-meta{font-size:11.4px;color:var(--text-600);margin-top:2px;}
.tk-doc-side{display:flex;flex-direction:column;align-items:flex-end;gap:6px;}
.tk-doc-actions{display:flex;gap:2px;}
.tk-doc-actions .btn{padding:3px 6px;font-size:12px;}
.tk-empty{font-size:12.4px;color:var(--text-400);padding:14px 4px;text-align:center;}

.tk-boarding-pass{position:relative;display:flex;background:var(--card);border:1px dashed var(--gold-500);border-radius:10px;overflow:hidden;flex-wrap:wrap;}
.tk-bp-left{flex:1;min-width:170px;padding:12px 14px;display:flex;flex-direction:column;gap:4px;}
.tk-bp-route{font-size:18px;font-weight:800;color:var(--text-900);}
.tk-bp-cities{font-size:11.4px;color:var(--text-600);}
.tk-bp-details{display:flex;flex-wrap:wrap;gap:8px;font-size:10.8px;color:var(--text-600);margin-top:4px;}
.tk-bp-class{font-size:11px;font-weight:700;color:var(--gold-600);margin-top:2px;}
.tk-bp-divider{width:0;border-left:1px dashed var(--border);}
.tk-bp-right{padding:12px;display:flex;align-items:center;justify-content:center;}
.tk-bp-actions{padding:8px 12px;justify-content:flex-end;background:rgba(0,0,0,.02);width:100%;}

.tk-required-card{background:linear-gradient(180deg,var(--card),var(--subtle-bg));border:1px solid var(--gold-500);border-radius:14px;padding:20px;box-shadow:var(--shadow);}
.tk-required-header{margin-bottom:14px;}
/* align-items:stretch + the footer's margin-top:auto below is what lines the
   four "View All / + Add" rows up with each other: the cards fill the row
   height and each footer is pushed to its own card's floor, so a card with
   two meta lines and one with a preview box still agree on where the
   footer sits. */
.tk-required-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;align-items:stretch;}
@media(max-width:1100px){.tk-required-grid{grid-template-columns:repeat(2,1fr);}}
@media(max-width:640px){.tk-required-grid{grid-template-columns:1fr;}}

/* Same four-bracket model as .tk-card, at the smaller 15px scale the
   sub-card's 10px radius and 14px padding can carry. */
.tk-sub-card{
  position:relative;display:flex;flex-direction:column;
  background:var(--card);border:1px solid rgba(200,153,46,.3);border-radius:10px;padding:14px;
}
.tk-sub-card::before,.tk-sub-card::after{content:'';position:absolute;width:15px;height:15px;pointer-events:none;}
.tk-sub-card::before{top:6px;left:6px;border-top:2px solid var(--gold-500);border-left:2px solid var(--gold-500);border-top-left-radius:7px;}
.tk-sub-card::after{bottom:6px;right:6px;border-bottom:2px solid var(--gold-500);border-right:2px solid var(--gold-500);border-bottom-right-radius:7px;}
.tk-sub-card .tk-card-corners::before,.tk-sub-card .tk-card-corners::after{width:15px;height:15px;}
.tk-sub-card .tk-card-corners::before{top:6px;right:6px;border-top-right-radius:7px;}
.tk-sub-card .tk-card-corners::after{bottom:6px;left:6px;border-bottom-left-radius:7px;}
.tk-sub-card:hover{border-color:var(--gold-400);}
.tk-sub-card:hover::before,.tk-sub-card:hover::after,
.tk-sub-card:hover .tk-card-corners::before,.tk-sub-card:hover .tk-card-corners::after{border-color:var(--gold-400);}

.tk-sub-title{display:flex;align-items:center;gap:7px;margin-bottom:8px;font-weight:700;font-size:13px;color:var(--text-900);}
.tk-sub-icon{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;flex:none;color:var(--gold-500);}
.tk-sub-icon .tk-icon{width:21px;height:21px;}
.tk-sub-footer{
  display:flex;justify-content:space-between;align-items:center;gap:8px;
  margin-top:auto;padding-top:10px;border-top:1px solid rgba(200,153,46,.3);
}
.tk-sub-meta{font-size:11.5px;color:var(--text-600);}

/* ---------- The gold stage ----------
   .tk-empty-state (document cards) and .tk-sub-stage (Required Assets) are
   the same object, so they share one rule rather than drifting apart. It
   gives an empty card a deliberate gold-dashed panel instead of a line of
   grey text floating in white space. --gold-bg is defined for both themes,
   so no dark-mode override is needed. */
.tk-empty-state,.tk-sub-stage{
  flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;
  background:var(--gold-bg);
  border:1px dashed rgba(200,153,46,.42);
  border-radius:9px;padding:10px 8px 8px;
}
/* The document card's footer already contributes margin-top:14px, so its
   stage adds none; the Required Assets card has no such footer margin. */
.tk-empty-state{margin:2px 0 0;}
.tk-sub-stage{margin:2px 0 10px;}
/* .tk-empty carries 14px of its own padding for the bare (modal) case; inside
   a stage the panel supplies the room, so it drops back to almost none. */
.tk-empty-state .tk-empty,.tk-sub-stage .tk-empty{padding:2px 4px 0;}

/* ---------- Empty-state illustrations (TK_ART in travelKit.js) ---------- */
.tk-art{width:112px;height:90px;display:block;overflow:visible;color:var(--gold-500);}
.tk-sub-stage .tk-art{width:104px;height:84px;}
.tk-art-shadow{fill:rgba(20,28,44,.13);}
[data-theme="dark"] .tk-art-shadow{fill:rgba(0,0,0,.45);}
/* Fires only on hover — parked transparent so a reduced-motion visitor never
   sees a stray ellipse floating under the stamp. */
.tk-art-ripple{opacity:0;}
.tk-art-trail{opacity:.42;}
/* The glow disc and the sparkles are decoration: they must never be the
   thing a reader notices first, so they rest well below full strength. */
.tk-artp-glow{opacity:.22;filter:blur(6px);}
.tk-art-sparkle{opacity:.5;}
.tk-arth-win{fill:#b9873a;}
[data-theme="dark"] .tk-arth-win{fill:#8a6528;}
.tk-thumb-row{display:flex;gap:6px;margin:10px 0;}
.tk-thumb{width:44px;height:44px;border-radius:6px;object-fit:cover;border:1px solid var(--border);}
.tk-thumb-placeholder{display:flex;align-items:center;justify-content:center;background:var(--subtle-bg);font-size:16px;color:var(--text-400);}
.tk-file-row{display:flex;gap:6px;margin:10px 0;flex-wrap:wrap;}
.tk-file-chip{font-size:11px;padding:4px 8px;border-radius:6px;background:var(--subtle-bg);color:var(--text-600);}
.tk-preview-box{width:100%;height:88px;object-fit:cover;border-radius:8px;margin:10px 0;border:1px solid var(--border);}
.tk-preview-placeholder{display:flex;align-items:center;justify-content:center;background:var(--subtle-bg);font-size:26px;color:var(--text-400);}

/* ---------- Travel Kit motion ----------
   Everything above is structure and colour; everything below is movement,
   gated on prefers-reduced-motion: no-preference rather than overridden
   afterwards in a `reduce` block. That matters here specifically: the icon
   marks carry SVG transform ATTRIBUTES for their layout position, and a
   blanket `transform:none !important` in a reduce block would override
   those attributes too and fling the artwork to the origin. Not writing the
   rule at all is the only safe way to opt out. */
@media (prefers-reduced-motion: no-preference){

  /* ---------- Card lift + corner brackets opening out ---------- */
  .tk-card,.tk-sub-card{
    transition:transform .32s cubic-bezier(.16,1,.3,1),
               box-shadow .32s ease-out,
               border-color .32s ease-out;
  }
  .tk-card:hover{transform:translateY(-4px);box-shadow:0 16px 30px -14px rgba(200,153,46,.5), var(--shadow);}
  .tk-sub-card:hover{transform:translateY(-3px);box-shadow:0 12px 22px -14px rgba(200,153,46,.5);}
  .tk-card::before,.tk-card::after,
  .tk-card-corners::before,.tk-card-corners::after,
  .tk-sub-card::before,.tk-sub-card::after{
    transition:width .34s cubic-bezier(.16,1,.3,1),
               height .34s cubic-bezier(.16,1,.3,1),
               border-color .34s ease-out;
  }
  .tk-card:hover::before,.tk-card:hover::after,
  .tk-card:hover .tk-card-corners::before,.tk-card:hover .tk-card-corners::after{width:30px;height:30px;}
  .tk-sub-card:hover::before,.tk-sub-card:hover::after,
  .tk-sub-card:hover .tk-card-corners::before,.tk-sub-card:hover .tk-card-corners::after{width:24px;height:24px;}

  .tk-empty-state,.tk-sub-stage{transition:border-color .3s ease-out, background-color .3s ease-out;}
  .tk-card:hover .tk-empty-state,
  .tk-sub-card:hover .tk-sub-stage{border-color:rgba(200,153,46,.7);}

  /* ---------- Header mark micro-motion, one gesture per card ---------- */
  /* Passport — the cover swings open on its spine, the emblem warms. */
  .tk-passport-book{
    transform-box:fill-box;transform-origin:left center;
    transition:transform .4s cubic-bezier(.4,0,.2,1);
  }
  .tk-card-passport:hover .tk-passport-book{transform:perspective(320px) rotateY(-15deg) scale(1.05);}
  .tk-passport-emblem{transition:filter .4s ease;}
  .tk-card-passport:hover .tk-passport-emblem{filter:drop-shadow(0 0 3px rgba(220,174,74,.85));}

  /* Visa — the stamp presses down and throws a ring. */
  .tk-visa-stamp{
    transform-box:fill-box;transform-origin:center;
    transition:transform .3s cubic-bezier(.34,1.56,.64,1);
  }
  .tk-card-visa:hover .tk-visa-stamp{transform:translateY(2.5px) scale(1.02);}
  .tk-card-visa:hover .tk-stamp-ripple{animation:tkStampRipple .55s ease-out forwards;}
  @keyframes tkStampRipple{
    0%{r:2;opacity:.8;stroke-width:1;}
    100%{r:8;opacity:0;stroke-width:.5;}
  }

  /* ID Cards — the card lifts, the security shield pulses. */
  .tk-id-shape{transform-box:fill-box;transition:transform .35s cubic-bezier(.4,0,.2,1);}
  .tk-card-idcard:hover .tk-id-shape{transform:translateY(-3px);}
  .tk-security-shield{transform-box:fill-box;transform-origin:center;}
  .tk-card-idcard:hover .tk-security-shield{animation:tkShieldPulse .45s ease-in-out;}
  @keyframes tkShieldPulse{
    0%{transform:scale(1);fill:transparent;}
    50%{transform:scale(1.15);fill:rgba(220,174,74,.25);}
    100%{transform:scale(1);fill:transparent;}
  }

  /* Travel Tickets — the jet banks off the stub. */
  .tk-ticket-plane{
    transform-box:fill-box;transform-origin:center;
    transition:transform .45s cubic-bezier(.4,0,.2,1);
  }
  .tk-card-travelticket:hover .tk-ticket-plane{transform:translate(3px,-2px) rotate(-5deg);}
  .tk-ticket-body{transition:transform .45s ease;}
  .tk-card-travelticket:hover .tk-ticket-body{transform:translateY(-1px);}

  /* ---------- Empty-state illustrations: the same gesture, full size ---------- */
  .tk-art-shadow{
    transform-box:fill-box;transform-origin:center;
    transition:transform .45s cubic-bezier(.16,1,.3,1), opacity .45s ease-out;
  }
  .tk-card:hover .tk-art-shadow,
  .tk-sub-card:hover .tk-art-shadow{transform:scaleX(.86);opacity:.72;}

  /* Passport — the cover swings open on its spine and the gold globe warms. */
  .tk-art-cover{
    transform-box:fill-box;transform-origin:left center;
    transition:transform .55s cubic-bezier(.16,1,.3,1);
  }
  .tk-card-passport:hover .tk-art-cover{transform:perspective(420px) rotateY(-34deg);}
  .tk-art-globe{transition:filter .5s ease;}
  .tk-card-passport:hover .tk-art-globe{filter:drop-shadow(0 0 5px rgba(220,174,74,.9));}

  /* Visa — the stamp drops, bounces, and throws a ring across the page. */
  .tk-art-stamp{transform-box:fill-box;transform-origin:center bottom;}
  .tk-card-visa:hover .tk-art-stamp{animation:tkArtStampPress .85s cubic-bezier(.34,1.56,.64,1);}
  @keyframes tkArtStampPress{
    0%{transform:translateY(-9px);}
    32%{transform:translateY(5px);}
    58%{transform:translateY(-2px);}
    100%{transform:translateY(0);}
  }
  .tk-art-ripple{transform-box:fill-box;transform-origin:center;}
  .tk-card-visa:hover .tk-art-ripple{animation:tkArtRipple .9s ease-out .12s;}
  @keyframes tkArtRipple{
    0%{opacity:.85;transform:scale(.3);}
    100%{opacity:0;transform:scale(2);}
  }

  /* ID Card — the badge lifts and the security shield pops forward. */
  .tk-art-badge{transform-box:fill-box;transition:transform .4s cubic-bezier(.16,1,.3,1);}
  .tk-card-idcard:hover .tk-art-badge{transform:translateY(-5px);}
  .tk-art-shield{
    transform-box:fill-box;transform-origin:center;
    transition:transform .4s cubic-bezier(.34,1.56,.64,1);
  }
  .tk-card-idcard:hover .tk-art-shield{transform:translate(3px,-4px) scale(1.14);}

  /* Travel Tickets — the jet runs the dashed arc it left behind. */
  .tk-art-ticketface{
    transform-box:fill-box;transform-origin:center;
    transition:transform .45s cubic-bezier(.16,1,.3,1);
  }
  .tk-card-travelticket:hover .tk-art-ticketface{transform:translateY(-4px) rotate(-2deg);}
  .tk-art-stub{
    transform-box:fill-box;transform-origin:center;
    transition:transform .45s cubic-bezier(.16,1,.3,1);
  }
  .tk-card-travelticket:hover .tk-art-stub{transform:translate(-3px,2px) rotate(1.5deg);}
  /* The jet's parent <g> carries the translate/rotate that positions it, so
     this inner translateX runs along the jet's own nose axis — it retreats
     back down the arc and flies forward, no second set of coordinates. */
  .tk-card-travelticket:hover .tk-art-jet{animation:tkArtJetFly 1.1s cubic-bezier(.5,0,.3,1);}
  @keyframes tkArtJetFly{
    0%{transform:translateX(-78px) scale(.88);opacity:0;}
    22%{opacity:1;}
    100%{transform:translateX(0) scale(1);opacity:1;}
  }
  .tk-art-trail{transition:opacity .45s ease-out;}
  .tk-card-travelticket:hover .tk-art-trail{opacity:.85;animation:tkArtTrailDrift 1.1s linear;}
  @keyframes tkArtTrailDrift{to{stroke-dashoffset:-32;}}

  /* ---------- Required Assets / Photos: per-card gesture ----------
     400ms on the overshooting cubic-bezier(.34,1.56,.64,1), so each piece
     settles with a small bounce rather than easing flat. */
  .tk-art-sparkle{
    transform-box:fill-box;transform-origin:center;
    transition:opacity .4s ease-out, transform .4s cubic-bezier(.34,1.56,.64,1);
  }
  .tk-sub-card:hover .tk-art-sparkle{opacity:1;transform:scale(1.35) rotate(25deg);}
  .tk-sub-card:hover .tk-art-sparkle-2{transition-delay:.08s;}
  .tk-sub-card:hover .tk-art-sparkle-3{transition-delay:.16s;}
  /* 120 exceeds all three swoosh paths (longest ~85 units), so the dash
     covers the whole stroke and offsetting it sweeps the trail on rather
     than marching a dashed line along it. This has to be a keyframe, not a
     transition: the resting and hovered offsets are both 0 — the trail is
     drawn in both states — and only the traversal between them animates.
     Transitioning to -120 instead would leave the trail sitting invisible
     for as long as the pointer stayed on the card. */
  .tk-art-swoosh{stroke-dasharray:120;stroke-dashoffset:0;transition:opacity .4s ease-out;}
  .tk-sub-card:hover .tk-art-swoosh{opacity:.9;animation:tkSwooshDraw .7s cubic-bezier(.16,1,.3,1);}
  @keyframes tkSwooshDraw{from{stroke-dashoffset:120;}to{stroke-dashoffset:0;}}

  /* Photos — the print lifts and the glow blooms behind it.
     The .tk-artp-book rules that used to sit here have gone with the booklet
     they moved. The photo's own lift lost its sideways component with them:
     the 4px of translate-x existed to slide it out from behind the book, and
     with nothing left to clear it only read as the picture drifting off
     centre. */
  .tk-artp-photo{
    transform-box:fill-box;transform-origin:center;
    transition:transform .4s cubic-bezier(.34,1.56,.64,1), filter .4s ease;
  }
  .tk-sub-photo:hover .tk-artp-photo{
    transform:translateY(-7px) scale(1.06);
    filter:drop-shadow(0 7px 12px rgba(200,153,46,.4));
  }
  .tk-artp-glow{transform-box:fill-box;transform-origin:center;transition:opacity .4s ease, transform .4s ease;}
  .tk-sub-photo:hover .tk-artp-glow{opacity:.5;transform:scale(1.2);}

  /* Travel Insurance — shield rises, plane banks across it, seal pops. */
  .tk-arti-shield,.tk-arti-plane,.tk-arti-badge{
    transform-box:fill-box;transform-origin:center;
    transition:transform .4s cubic-bezier(.34,1.56,.64,1), filter .4s ease;
  }
  .tk-sub-insurance:hover .tk-arti-shield{
    transform:translateY(-4px) scale(1.06);
    filter:drop-shadow(0 6px 12px rgba(37,99,235,.35));
  }
  .tk-sub-insurance:hover .tk-arti-plane{transform:translate(4px,-5px) rotate(10deg);}
  .tk-sub-insurance:hover .tk-arti-badge{transform:translate(1px,-2px) scale(1.15);}

  /* Hotel Booking — the building lifts, its windows light, the bell tips. */
  .tk-arth-building,.tk-arth-bell,.tk-arth-leaf{
    transform-box:fill-box;transform-origin:center;
    transition:transform .4s cubic-bezier(.34,1.56,.64,1), filter .4s ease;
  }
  .tk-sub-hotel:hover .tk-arth-building{
    transform:translateY(-4px) scale(1.04);
    filter:drop-shadow(0 6px 10px rgba(200,153,46,.38));
  }
  .tk-sub-hotel:hover .tk-arth-bell{transform:translateY(-3px) rotate(8deg);}
  .tk-arth-leaf{transform-origin:bottom center;}
  .tk-sub-hotel:hover .tk-arth-leaf{transform:rotate(-5deg);}
  .tk-sub-hotel:hover .tk-arth-leaf-2{transform:rotate(4deg);}
  .tk-arth-win{transition:fill .3s ease, filter .3s ease;}
  .tk-sub-hotel:hover .tk-arth-win{fill:#fde047;filter:drop-shadow(0 0 4px #fde047);}

  /* Other Documents — the papers lift clear of the folder, the star spins. */
  .tk-artd-folder-back,.tk-artd-paper,.tk-artd-badge{
    transform-box:fill-box;transform-origin:center;
    transition:transform .4s cubic-bezier(.34,1.56,.64,1), filter .4s ease;
  }
  .tk-sub-docs:hover .tk-artd-folder-back{transform:translateY(-2px);}
  .tk-sub-docs:hover .tk-artd-paper{transform:translateY(-9px) scale(1.03);}
  .tk-sub-docs:hover .tk-artd-badge{
    transform:rotate(12deg) scale(1.15);
    filter:drop-shadow(0 0 5px rgba(200,153,46,.8));
  }
}

/* ---------- Travel Kit: populated-state hover ----------
   The block above animates the empty-state illustrations. Once a card holds
   records the illustration is gone, and without these rules a populated card
   would answer the cursor with nothing but its own lift — so the rows, passes,
   thumbs and chips that replace the artwork get their own responses, in the
   same gold and on the same curves.

   Guarded on (hover:hover) and (pointer:fine) — the convention the rest of
   this stylesheet uses — so a touch device never latches a hover state after
   a tap, and on prefers-reduced-motion for the same reason as above. */
@media (hover:hover) and (pointer:fine) and (prefers-reduced-motion: no-preference){

  /* The hero: a slow push-in on the footage, nothing else. It is the largest
     surface on the page, so anything faster than this reads as the page
     twitching rather than responding. */
  .tk-hero-video{transition:transform 1.2s cubic-bezier(.16,1,.3,1);}
  .tk-hero:hover .tk-hero-video{transform:scale(1.04);}
  .tk-hero-overlay{transition:opacity .6s ease-out;}
  .tk-hero:hover .tk-hero-overlay{opacity:.88;}

  /* Document rows — the populated stand-in for the illustration. */
  .tk-doc-row{
    transition:transform .28s cubic-bezier(.16,1,.3,1),
               border-color .28s ease-out,
               box-shadow .28s ease-out,
               background-color .28s ease-out;
  }
  .tk-doc-row:hover{
    transform:translateY(-2px);
    border-color:var(--gold-400);
    background:var(--card);
    box-shadow:0 8px 18px -12px rgba(200,153,46,.55);
  }
  .tk-doc-title{transition:color .28s ease-out;}
  .tk-doc-row:hover .tk-doc-title{color:var(--gold-600);}

  /* The four icon buttons on each row: small targets, so they answer with
     scale rather than a colour change that would be easy to miss. */
  .tk-doc-actions .btn{transition:transform .2s cubic-bezier(.34,1.56,.64,1), color .2s ease-out, background-color .2s ease-out;}
  .tk-doc-actions .btn:hover{transform:scale(1.18);color:var(--gold-600);}

  /* Boarding pass — lifts as one piece; the dashed edge warms to match the
     card's brackets, and the QR leans forward a little. */
  .tk-boarding-pass{
    transition:transform .32s cubic-bezier(.16,1,.3,1),
               border-color .32s ease-out,
               box-shadow .32s ease-out;
  }
  .tk-boarding-pass:hover{
    transform:translateY(-3px);
    border-color:var(--gold-400);
    box-shadow:0 10px 22px -14px rgba(200,153,46,.6);
  }
  .tk-bp-right svg{transition:transform .32s cubic-bezier(.34,1.56,.64,1);}
  .tk-boarding-pass:hover .tk-bp-right svg{transform:scale(1.06);}

  /* Attachment thumbs and the preview box — the image is the content, so it
     is the thing that grows. */
  .tk-thumb{transition:transform .3s cubic-bezier(.34,1.56,.64,1), border-color .3s ease-out, box-shadow .3s ease-out;}
  .tk-thumb:hover{
    transform:translateY(-3px) scale(1.1);
    border-color:var(--gold-400);
    box-shadow:0 8px 16px -10px rgba(200,153,46,.7);
  }
  .tk-preview-box{transition:transform .35s cubic-bezier(.16,1,.3,1), border-color .35s ease-out;}
  .tk-sub-card:hover .tk-preview-box{transform:scale(1.02);border-color:var(--gold-400);}

  /* File-type chips deal out in sequence rather than all shifting at once —
     the same staggered read as the sparkles on the illustrations. */
  .tk-file-chip{transition:transform .3s cubic-bezier(.34,1.56,.64,1), background-color .3s ease-out, color .3s ease-out;}
  .tk-sub-card:hover .tk-file-chip{transform:translateY(-2px);background:var(--gold-bg);color:var(--gold-600);}
  .tk-sub-card:hover .tk-file-chip:nth-child(2){transition-delay:.05s;}
  .tk-sub-card:hover .tk-file-chip:nth-child(3){transition-delay:.1s;}
  .tk-sub-card:hover .tk-file-chip:nth-child(4){transition-delay:.15s;}

  /* Footer buttons rise with their card so the whole card reads as one
     object lifting, not a panel with a static strip along the bottom. */
  .tk-card-footer .btn,.tk-sub-footer .btn{transition:transform .28s cubic-bezier(.16,1,.3,1), background-color .18s ease, border-color .18s ease;}
  .tk-card:hover .tk-card-footer .btn,
  .tk-sub-card:hover .tk-sub-footer .btn{transform:translateY(-1px);}
  .tk-card-footer .btn:hover,.tk-sub-footer .btn:hover{border-color:var(--gold-400);}
}

.tk-list-modal{display:flex;flex-direction:column;gap:8px;max-height:50vh;overflow-y:auto;}

/* ---------- File Preview modal (Update_Travel Kit File_Instruction) ----------
   .modal-preview is applied to #modal (via openModal()'s extraClass param)
   only while previewing a file, so every other modal in the app keeps its
   normal 760px form width. */
.modal.modal-preview{max-width:min(92vw,1200px);}
.modal-preview .modal-body{max-height:85vh;}
.tk-filepreview-toolbar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:10px;}
.tk-filepreview-nav{display:flex;align-items:center;gap:8px;font-size:12.5px;color:var(--text-600);}
.tk-filepreview-nav button{background:none;border:1px solid var(--border);border-radius:6px;width:26px;height:26px;cursor:pointer;font-size:14px;line-height:1;color:var(--text-600);}
.tk-filepreview-nav button:disabled{opacity:.35;cursor:not-allowed;}
.tk-filepreview-stage{display:flex;align-items:center;justify-content:center;background:#f4f5f8;border-radius:8px;overflow:auto;min-height:200px;max-height:72vh;padding:10px;}
.tk-filepreview-stage img{max-width:100%;max-height:70vh;object-fit:contain;border-radius:4px;}
.tk-filepreview-stage iframe{width:100%;height:70vh;border:none;border-radius:4px;background:#fff;}
.tk-filepreview-unsupported{padding:40px 10px;text-align:center;color:var(--text-400);font-size:13.5px;}
@media(max-width:640px){
  .modal.modal-preview{max-width:96vw;}
  .tk-filepreview-stage, .tk-filepreview-stage img, .tk-filepreview-stage iframe{max-height:55vh;}
}

/* ---------- Real Estate — Property Location map picker ---------- */
/* Second, stacked modal overlay (#mapPickerOverlay in index.html) — layers
   above the shared #modalOverlay (already showing the Add/Edit Real Estate
   form when this opens), so it needs its own higher z-index. */
#mapPickerOverlay{
  display:none; position:fixed; inset:0; background:rgba(15,20,30,.55); z-index:200;
  align-items:flex-start; justify-content:center; padding:40px 16px; overflow-y:auto;
}
#mapPickerOverlay.open{display:flex;}
.modal.modal-map{max-width:min(92vw,760px);}
.map-picker-toolbar{display:flex;gap:8px;margin-bottom:12px;}
.map-picker-toolbar input{
  flex:1;border:1px solid var(--border);border-radius:7px;padding:8px 10px;
  font-size:13.4px;font-family:inherit;background:var(--card);color:var(--text-900);
}
.map-picker-toolbar button{flex-shrink:0;white-space:nowrap;}
.map-picker-canvas{width:100%;height:360px;border-radius:8px;overflow:hidden;background:var(--subtle-bg);}
.map-picker-address{font-size:12.6px;color:var(--text-600);margin-top:10px;}

.property-location-widget{margin-top:12px;}
.property-location-preview{
  display:flex;gap:14px;border:1px solid var(--border);border-radius:10px;padding:12px;background:var(--subtle-bg);
}
.property-location-thumb{
  width:140px;height:100px;border-radius:8px;object-fit:cover;flex-shrink:0;background:var(--card);
}
.property-location-thumb-placeholder{
  display:flex;align-items:center;justify-content:center;font-size:28px;color:var(--text-400);
}
.property-location-preview-info{flex:1;min-width:0;}
.property-location-preview-title{font-size:13px;font-weight:700;color:var(--text-900);}
.property-location-preview-address{font-size:12.4px;color:var(--text-600);margin-top:4px;}
.property-location-preview-coords{font-size:11.6px;color:var(--text-400);margin-top:2px;font-variant-numeric:tabular-nums;}
.property-location-preview-info .btn{margin-top:8px;}
@media(max-width:640px){
  .modal.modal-map{max-width:96vw;}
  .map-picker-canvas{height:280px;}
  .property-location-preview{flex-direction:column;}
  .property-location-thumb{width:100%;height:140px;}
}

/* ---------- Obligations/Liabilities — EMI Schedule drawer ---------- */
/* Third, stacked modal overlay (#emiScheduleOverlay in index.html) — see
   the comment on #mapPickerOverlay above; same reasoning, own overlay so
   closing it never discards an in-progress Add/Edit Obligation form. */
#emiScheduleOverlay{z-index:200;}

/* ---------- Roles & Access (rolesAccess.js) ---------- */
.modal.modal-lg{max-width:min(92vw,900px);}
.modal.modal-xl{max-width:min(94vw,1100px);}
.modal-xl .modal-body{max-height:85vh;}
/* Tabs rendered inside a modal body (EMI Schedule_v1's drawer, and its
   own Monthly/Yearly sub-toggle) — same .tabs/.tab visual language as
   the page-level tab bars, just a touch tighter for the smaller space. */
.modal-body .tabs{margin-bottom:14px;}
.emi-progress-track{height:10px;border-radius:6px;background:var(--subtle-bg);overflow:hidden;}
.emi-progress-fill{height:100%;background:linear-gradient(90deg, var(--gold-500), var(--gold-600));border-radius:6px;transition:width .4s cubic-bezier(0.16,1,0.3,1);}
.emi-compare-best{background:var(--green-bg);color:var(--green);font-weight:700;}

/* ---------- Two-Step Verification setup (securitySettings.js) ---------- */
/* Taller body + slightly tighter step spacing so "Verify & Enable 2FA" is
   visible without scrolling on typical screen heights. */
.modal-2fa .modal-body{max-height:92vh;}
/* Unified with the app-wide .tabs/.tab visual language (Visual
   Customization v1 §11) - 1px neutral border, gold 2px active underline
   (previously var(--gold, var(--amber)), which fell back to amber since
   --gold was never a defined token). No consumer outside this page. */
.ra-tabbar{display:flex;gap:4px;border-bottom:1px solid var(--border);margin-bottom:18px;flex-wrap:wrap;}
.ra-tab{background:none;border:none;padding:9px 15px;font-size:13.2px;font-weight:600;color:var(--text-400);cursor:pointer;border-bottom:2px solid transparent;margin-bottom:-1px;transition:color .12s ease, border-color .12s ease;}
.ra-tab:hover{color:var(--text-900);}
.ra-tab.active{color:var(--text-900);border-bottom-color:var(--gold-500);}
.ra-members-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px;gap:12px;flex-wrap:wrap;}
/* .ra-node-row is display:flex for its original div-based permission-tree
   usage - the admin accounts table reuses the class (and raToggleChildren())
   on real <tr> elements for the same expand/collapse behavior, which needs
   table-row layout restored or the cells collapse into a wrapped flex list. */
tr.ra-node-row{display:table-row;}
.ra-admin-search{
  width:100%;max-width:340px;border:1px solid var(--border);border-radius:8px;padding:8px 12px;
  font-size:13px;font-family:inherit;background:var(--card);color:var(--text-900);
}
/* System Admin Reports - Nest Users: table skeleton loader (spec SS25/SS27) -
   a handful of pulsing bars shown while a report's GET is in flight, using
   the same theme-adaptive tokens every other card/border already uses. */
.ra-report-skeleton{display:flex;flex-direction:column;gap:10px;}
.ra-skel-row{height:38px;border-radius:8px;background:linear-gradient(90deg, var(--card) 25%, var(--border) 50%, var(--card) 75%);background-size:200% 100%;animation:raSkelPulse 1.4s ease-in-out infinite;}
@keyframes raSkelPulse{0%{background-position:200% 0;}100%{background-position:-200% 0;}}
.ra-report-kpis{margin-bottom:16px;}
.ra-report-filters{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-bottom:12px;}
.ra-report-filters select, .ra-report-filters input[type="date"]{border:1px solid var(--border);border-radius:8px;padding:7px 10px;font-size:12.8px;font-family:inherit;background:var(--card);color:var(--text-900);}
.ra-pager{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-top:14px;flex-wrap:wrap;}

/* DPDP Compliance Center - Phase 1 Notice Versions strip (Purpose Registry). */
.ra-dpdp-nv-panel{border:1px solid var(--border);border-radius:10px;padding:10px 14px;margin-bottom:14px;background:var(--card);}
.ra-dpdp-nv-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:8px;}
.ra-dpdp-nv-list{display:flex;flex-direction:column;gap:8px;}
.ra-dpdp-nv-pill{display:flex;align-items:center;gap:10px;flex-wrap:wrap;padding:6px 0;border-top:1px solid var(--border);}
.ra-dpdp-nv-pill:first-child{border-top:none;padding-top:0;}
.ra-dpdp-nv-pill .btn{margin-left:auto;}

/* DPDP Compliance Center - Phase 8 Evidence Report. */
.ra-evidence-header{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:6px 18px;padding:12px 16px;border:1px solid var(--border);border-radius:10px;background:var(--subtle-bg);margin-bottom:12px;font-size:13px;}
.ra-evidence-section{margin:18px 0;}
.ra-evidence-section h4{margin:0 0 10px;font-size:14.5px;}

/* Recently Deleted background.png - card treatment scoped to .rd-page ONLY
   (never the shared .stat-card base rules above, which Dashboard/Balance
   Sheet/Alerts/etc. all still render plainly) - the same page-scoped-
   override convention already used elsewhere in this file (.dashboard-page
   .stat-value, .ins-page .pill-green, ...) rather than changing every
   .stat-card in the app.
   The right-side illustration + its own wave/dot-pattern are real crops
   taken directly from that reference image (rd-illust-*.png,
   assets/icons/) - a from-scratch CSS re-creation could get close but
   never pixel-match the actual artwork the way "exactly" asked for. Each
   is cropped tight to just that zone (no baked-in label/value text), laid
   over this same CSS gradient as the base fill so the two blend at the
   image's own soft edges instead of showing a hard seam. Card content
   (icon/label/value/foot) keeps using the exact same .stat-icon/.stat-
   label/.stat-value/.stat-foot markup and classes as every other .stat-
   card in the app; only the card's own background and sizing are
   page-specific here. */
.rd-page .stat-card{
  overflow:hidden; position:relative;
  padding:14px 16px 46px 16px; min-height:108px;
}
.rd-page .stat-card > *{position:relative;z-index:1;}
.rd-page .stat-card::before{
  content:''; position:absolute; z-index:0; inset:0;
  background-repeat:no-repeat; background-position:right bottom; background-size:auto 100%;
}
.rd-page .stat-card.accent-red{background:linear-gradient(135deg, #fef2f3 0%, #fbdfe3 100%);}
.rd-page .stat-card.accent-blue{background:linear-gradient(135deg, #f0f5fe 0%, #dde9fc 100%);}
.rd-page .stat-card.accent-green{background:linear-gradient(135deg, #eefaf3 0%, #dcf3e7 100%);}
.rd-page .stat-card.accent-purple{background:linear-gradient(135deg, #f3f0fe 0%, #e6def9 100%);}
.rd-page .stat-card.accent-red::before{background-image:url("../assets/icons/rd-illust-total-deleted.png");}
.rd-page .stat-card.accent-blue::before{background-image:url("../assets/icons/rd-illust-deleted-today.png");}
.rd-page .stat-card.accent-green::before{background-image:url("../assets/icons/rd-illust-recoverable-items.png");}
.rd-page .stat-card.accent-purple::before{background-image:url("../assets/icons/rd-illust-permanent-deletions.png");}
/* Solid white circle (was translucent) - the emoji glyphs inside render
   with their own built-in colour regardless of CSS `color`, so there's no
   equivalent per-accent tint rule to add here the way .stat-value has. */
.rd-page .stat-icon{background:#fff;box-shadow:0 3px 8px rgba(15,31,51,.1);}
/* "reduce the box size" - a more compact scale for this page's cards only,
   smaller than the shared .stat-card defaults (38px icon / 23px value)
   used elsewhere in the app. */
.rd-page .stat-icon{width:28px;height:28px;font-size:13px;margin-bottom:6px;}
.rd-page .stat-label{font-size:11px;}
.rd-page .stat-value{font-size:18px;margin-top:3px;}
.rd-page .stat-foot{font-size:11px;margin-top:2px;}
/* Fixed, theme-INDEPENDENT colours below - this page's cards are real
   raster artwork (rd-illust-*.png) with a permanently light background
   baked in, so the shared --text-400/--text-600/--text-900 tokens (which
   flip to light tones in dark mode, expecting a dark card background the
   way every other page's cards go dark too) must not apply here: with
   dark mode on, those lighter tokens landed on this page's still-light
   card and the labels/values went nearly invisible - reported as "text
   are not clearly view". Hardcoding the same light-mode hex this page
   already uses everywhere else keeps it looking identical regardless of
   the app's theme toggle. */
.rd-page .stat-card .stat-label{color:#5b6478;}
.rd-page .stat-card .stat-foot{color:#6b7280;}
.rd-page .stat-card .stat-value{color:#1a2233;}
.rd-page .stat-card.accent-red .stat-value{color:#c0392b;}
.rd-page .stat-card.accent-blue .stat-value{color:#2a5fb0;}
.rd-page .stat-card.accent-green .stat-value{color:#1f8a5f;}
.rd-page .stat-card.accent-purple .stat-value{color:#7c3aed;}
.ra-table{width:100%;border-collapse:collapse;font-size:13px;}
.ra-table th{text-align:left;font-size:11.5px;text-transform:uppercase;letter-spacing:.03em;color:var(--text-400);padding:8px 10px;border-bottom:2px solid var(--border);}
.ra-table td{padding:10px;border-bottom:1px solid var(--border);color:var(--text-900);vertical-align:middle;}
.ra-row-actions{display:flex;gap:6px;flex-wrap:wrap;}
.ra-badge{border-radius:20px;padding:4px 10px;font-size:11.8px;font-weight:600;display:inline-block;}
.ra-badge-green{background:var(--green-bg);color:var(--green);}
.ra-badge-gray{background:var(--border);color:var(--text-600);}
.ra-badge-amber{background:var(--amber-bg);color:var(--amber);}
.ra-badge-blue{background:var(--blue-bg);color:var(--blue);}
.ra-badge-red{background:var(--red-bg);color:var(--red);}
.ra-badge-purple{background:var(--purple-bg);color:var(--purple);}
.ra-manage-head{padding-bottom:12px;border-bottom:1px solid var(--border);margin-bottom:4px;}
.ra-module-block{border:1px solid var(--border);border-radius:10px;padding:10px 14px;margin-bottom:10px;transition:border-color .12s ease;}
.ra-module-block:hover{border-color:var(--gold-500);}
.ra-module-flat{padding:8px 14px;}
.ra-category-block{margin:8px 0 8px 14px;padding:8px 0 0 12px;border-left:2px solid var(--border);}
.ra-record-block{margin:6px 0 6px 14px;padding:6px 0 0 12px;border-left:2px dashed var(--border);}
.ra-node-row{display:flex;align-items:center;gap:9px;flex-wrap:wrap;}
.ra-node-row .ra-node-cb{flex-shrink:0;width:15px;height:15px;}
.ra-node-label{font-size:13.3px;color:var(--text-900);flex:1;min-width:120px;}
.ra-module-block > .ra-node-row .ra-node-label{font-weight:700;}
.ra-node-cb{accent-color:var(--gold-500);}
.ra-tier-select{
  border:1px solid var(--border);border-radius:7px;padding:5px 8px;
  font-size:12.6px;font-family:inherit;background:var(--card);color:var(--text-900);
  transition:border-color .12s ease, box-shadow .12s ease;
}
.ra-tier-select:focus{border-color:var(--gold-500);box-shadow:0 0 0 3px rgba(200,153,46,.18);outline:none;}
.ra-expand-btn{font-size:11.8px;padding:3px 9px;}
.ra-node-children{margin-top:6px;}
.ra-fixed-section{margin-bottom:12px;}
.ra-fixed-section-title{font-size:11px;font-weight:700;text-transform:uppercase;letter-spacing:.03em;color:var(--text-400);margin-bottom:6px;}
/* ---------- Family Activity tab ----------
   A row per change (who / what / where / which record / when) with the full
   story behind a View details button. The three activity words are colour-
   coded on the same green-amber-red reading the rest of the app uses, so a
   deletion is findable by colour in a long list without reading every row. */
.ra-activity-table td{vertical-align:middle;}
.ra-act-user{font-weight:600;}
.ra-act-record{font-weight:600;color:var(--text-900);}
/* The category under the page name: present for almost every row, and the
   thing that tells a Bank Account from a Gold holding, but not important
   enough to take a column of its own. */
.ra-act-sub{font-size:11.4px;color:var(--text-400);margin-top:2px;}
.ra-act-when{white-space:nowrap;color:var(--text-600);}
.ra-act-actions{text-align:right;white-space:nowrap;}
.ra-act-pill{
  display:inline-block;border-radius:999px;padding:3px 11px;
  font-size:11.5px;font-weight:700;letter-spacing:.01em;
  border:1px solid transparent;white-space:nowrap;
}
.ra-act-created{background:var(--green-bg);color:var(--green);border-color:color-mix(in srgb, var(--green) 30%, transparent);}
.ra-act-updated{background:var(--blue-bg);color:var(--blue);border-color:color-mix(in srgb, var(--blue) 30%, transparent);}
.ra-act-deleted{background:var(--red-bg);color:var(--red);border-color:color-mix(in srgb, var(--red) 30%, transparent);}
.ra-act-view{padding:5px 12px;font-size:12.2px;font-weight:600;}

/* ---------- Activity detail popup ---------- */
.ra-activity-detail{font-size:13.2px;color:var(--text-900);}
/* The plain-language sentence, given the weight of a lead paragraph - it is
   the answer for most readers, and the table below it is the follow-up. */
.ra-ad-story{
  margin:0 0 16px;font-size:14px;line-height:1.65;color:var(--text-900);
  background:var(--subtle-bg);border-radius:10px;padding:13px 15px;
  border-left:3px solid var(--gold-500);
}
.ra-ad-facts{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));
  gap:10px 14px;margin-bottom:18px;
}
.ra-ad-facts > div{display:flex;flex-direction:column;gap:3px;min-width:0;}
.ra-ad-facts span{font-size:10.8px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-400);font-weight:700;}
.ra-ad-facts b{font-weight:600;font-size:13px;color:var(--text-900);overflow-wrap:anywhere;}
.ra-ad-heading{margin:0 0 8px;font-size:12.5px;text-transform:uppercase;letter-spacing:.03em;color:var(--text-400);}
.ra-ad-table{width:100%;border-collapse:collapse;font-size:12.8px;}
.ra-ad-table th{
  text-align:left;font-size:11px;text-transform:uppercase;letter-spacing:.03em;
  color:var(--text-400);padding:7px 10px;border-bottom:2px solid var(--border);
}
.ra-ad-table td{padding:9px 10px;border-bottom:1px solid var(--border);vertical-align:top;overflow-wrap:anywhere;}
.ra-ad-field{font-weight:600;color:var(--text-900);width:34%;}
/* Before is struck through rather than merely greyed: at a glance the eye
   should be able to tell which column is the old value without reading the
   header, and colour alone would not survive a monochrome print. */
.ra-ad-old{color:var(--text-400);text-decoration:line-through;text-decoration-thickness:1px;}
.ra-ad-new{color:var(--text-900);font-weight:600;}
.ra-ad-blank{color:var(--text-400);font-style:italic;font-weight:400;}
.ra-ad-note{margin:14px 0 0;font-size:11.8px;line-height:1.55;color:var(--text-400);}
/* Before/after diff masking (Security & Audit Report.txt §11) - a tiny
   inline reveal toggle next to a masked mobile/email/address value. */
.ra-mask-toggle{
  border:none;background:none;padding:0;margin-left:6px;font-size:11px;
  color:var(--gold-600,var(--gold));cursor:pointer;text-decoration:underline;
}
.ra-mask-toggle:hover{opacity:.8;}
/* Rule Thresholds tab (Security & Audit Report.txt §14). */
.ra-rule-groups{display:flex;flex-direction:column;gap:16px;margin:14px 0;}
.ra-rule-group{border:1px solid var(--border);border-radius:10px;padding:14px 16px;}
.ra-rule-group h4{margin:0 0 10px;font-size:13.5px;color:var(--text-900);}
.ra-rule-fields{display:flex;flex-wrap:wrap;gap:14px;}
.ra-rule-fields .field{width:220px;}
@media(max-width:560px){
  .ra-ad-field{width:auto;}
  .ra-ad-table th,.ra-ad-table td{padding:7px 6px;}
}

/* ---------- Members & Access page redesign ---------- */
.ra-members-header{margin-bottom:2px;}
.ra-members-title{font-size:21px;font-weight:800;color:var(--text-900);margin:0 0 4px;}
.ra-members-subtitle{font-size:13.3px;color:var(--text-600);margin:0;max-width:640px;line-height:1.55;}
.ra-info-banner{
  display:flex;align-items:center;gap:9px;background:var(--gold-bg);border:1px solid var(--gold-500);
  border-radius:10px;padding:10px 14px;margin:14px 0 18px;font-size:12.6px;color:var(--gold-600);font-weight:600;
}
.ra-info-banner-icon{font-size:14.5px;flex-shrink:0;}

.ra-summary-cards{display:grid;grid-template-columns:repeat(4,1fr);gap:12px;margin-bottom:18px;}
.ra-summary-card{
  border:1px solid var(--border);border-radius:12px;padding:16px;background:var(--card);
  box-shadow:var(--shadow);transition:box-shadow .15s ease, transform .15s ease;
}
.ra-summary-card:hover{box-shadow:var(--shadow-md);transform:translateY(-1px);}
.ra-summary-icon{width:32px;height:32px;border-radius:9px;display:flex;align-items:center;justify-content:center;font-size:15px;margin-bottom:10px;}
.ra-summary-icon-green{background:var(--green-bg);}
.ra-summary-icon-gray{background:var(--subtle-bg);}
.ra-summary-icon-gold{background:var(--gold-bg);}
.ra-summary-icon-blue{background:var(--blue-bg);}
.ra-summary-value{font-size:21px;font-weight:800;color:var(--text-900);line-height:1.1;font-variant-numeric:tabular-nums;}
.ra-summary-label{font-size:11.5px;color:var(--text-400);margin-top:4px;font-weight:700;text-transform:uppercase;letter-spacing:.02em;}

.ra-members-actions{display:flex;gap:10px;flex-wrap:wrap;margin-bottom:18px;}
.ra-btn-unlock{background:var(--blue-bg);color:var(--blue);border:1px solid var(--blue);transition:background .12s ease,color .12s ease;}
.ra-btn-unlock:hover{background:var(--blue);color:#fff;}

.ra-member-subtabs{display:flex;gap:4px;margin-bottom:14px;background:var(--subtle-bg);border-radius:10px;padding:4px;width:fit-content;flex-wrap:wrap;}
.ra-subtab{
  background:none;border:none;padding:7px 16px;font-size:12.6px;font-weight:700;color:var(--text-600);
  border-radius:8px;cursor:pointer;display:flex;align-items:center;gap:7px;transition:background .12s ease,color .12s ease;
}
.ra-subtab:hover{color:var(--text-900);}
.ra-subtab.active{background:var(--card);color:var(--text-900);box-shadow:var(--shadow);}
.ra-subtab-count{background:var(--border);color:var(--text-600);border-radius:20px;padding:1px 7px;font-size:10.8px;}
.ra-subtab.active .ra-subtab-count{background:var(--gold-bg);color:var(--gold-600);}

.ra-member-name-cell{display:flex;align-items:center;gap:10px;}
.ra-member-avatar{
  width:30px;height:30px;border-radius:50%;background:var(--gold-bg);color:var(--gold-600);
  display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12.5px;flex-shrink:0;
}
.ra-access-pill{border-radius:20px;padding:3px 10px;font-size:11.3px;font-weight:700;display:inline-block;}
.ra-access-pill-full{background:var(--gold-bg);color:var(--gold-600);}
.ra-access-pill-custom{background:var(--blue-bg);color:var(--blue);}
.ra-access-pill-none{background:var(--border);color:var(--text-400);}

@media (max-width:900px){
  .ra-summary-cards{grid-template-columns:repeat(2,1fr);}
}
@media (max-width:560px){
  .ra-summary-cards{grid-template-columns:1fr;}
}

.pending-invite-banner{
  position:sticky;top:0;z-index:50;display:flex;align-items:center;justify-content:space-between;
  gap:12px;padding:10px 20px;background:var(--amber-bg);color:var(--amber);font-size:13.5px;font-weight:600;
}
.pending-invite-banner .btn{font-weight:600;}

/* ---------- Bulk Data Import/Export ---------- */
/* ============================================================
   Bulk Data Import — ported from "Bulk import data.png"
   ------------------------------------------------------------
   The banner is fixed cream and gold like this file's other painted surfaces;
   everything below it stays on theme tokens so the working part of the page
   still follows light and dark.
   ============================================================ */
.ie-hero{
  position:relative;display:flex;align-items:center;gap:22px;
  padding:24px 28px;margin-bottom:4px;border-radius:16px;overflow:hidden;
  /* Sampled off the comp: near-white through the middle where the copy runs,
     warming to gold at the right-hand end behind the tree. */
  background:linear-gradient(100deg,#FDF9F1 0%,#FDFBF7 34%,#FCF7EA 74%,#F7F0E1 100%);
  border:1px solid rgba(214,182,110,.4);
}
.ie-hero-tile{
  flex:none;width:62px;height:62px;border-radius:16px;
  display:inline-flex;align-items:center;justify-content:center;color:#fff;
  background:linear-gradient(150deg,#E4B45B,#C0872A);
  box-shadow:0 8px 18px -8px rgba(180,132,40,.65);
}
.ie-hero-tile svg{width:27px;height:27px;stroke-width:1.7;}
.ie-hero-copy{flex:1 1 auto;min-width:0;position:relative;z-index:1;}
.ie-hero-title{
  margin:0;font-family:Georgia,'Times New Roman',serif;
  font-size:27px;font-weight:700;letter-spacing:-.3px;color:#1c2a4a;
}
.ie-hero-sub{margin:8px 0 0;font-size:13.6px;line-height:1.65;color:#5a6377;}
/* Anchored to the banner's right edge and clipped by it, so the crop's own
   right side never has to be feathered — the radius does that job. */
.ie-hero-art{
  position:absolute;right:0;top:50%;transform:translateY(-50%);
  width:408px;height:auto;pointer-events:none;
}
@media(max-width:1100px){ .ie-hero-art{opacity:.5;} }
@media(max-width:760px){
  .ie-hero{flex-direction:column;align-items:flex-start;gap:14px;}
  .ie-hero-art{display:none;}
  .ie-hero-title{font-size:23px;}
}

/* ---------- Tabs ----------
   Its own strip rather than the shared .tabs, which carries no icon slot. */
.ie-tabs{
  display:flex;gap:4px;flex-wrap:wrap;
  border-bottom:1px solid var(--border);margin:14px 0 18px;
}
.ie-tab{
  appearance:none;cursor:pointer;font-family:inherit;
  display:inline-flex;align-items:center;gap:9px;
  padding:12px 18px;border:none;background:transparent;
  font-size:13.8px;font-weight:600;color:var(--text-600);
  border-bottom:2.5px solid transparent;margin-bottom:-1px;
  border-radius:9px 9px 0 0;
  transition:background .16s ease,color .16s ease;
}
.ie-tab-icon{display:inline-flex;align-items:center;opacity:.8;}
@media (hover:hover) and (pointer:fine){
  .ie-tab:hover{background:var(--subtle-bg);color:var(--text-900);}
}
.ie-tab.active{color:#a97c2a;border-bottom-color:#c8992e;}
.ie-tab.active .ie-tab-icon{opacity:1;}

/* ---------- Dropzone ---------- */
.ie-upload-card{padding:20px;}
/* A painted surface, fixed light in both themes — the same call the hero above
   and this file's other artwork panels make, and for the same two reasons.
   First, the illustration inside it is a crop with a white page and pale blue
   cloud in it; on a dark well it read as a lit rectangle floating on the card.
   Second, once the ground is known, the text on it can be chosen against that
   ground rather than against a variable.
   The first version got both of these wrong: it tinted the card colour, so in
   dark mode the well went dark and the navy heading on it — #1c2a4a on #161e2c
   — was very nearly invisible. */
.ie-dropzone{
  border:2px dashed #cad9e9;border-radius:16px;
  padding:34px 20px 30px;text-align:center;
  background:linear-gradient(180deg,#FBFDFF 0%,#F2F7FD 100%);
  transition:border-color .12s ease, background .12s ease;
}
.ie-dropzone:hover{border-color:#C4892A;}
/* The drag state stays on this surface's own palette rather than --gold-bg,
   which is a dark brown in dark mode and would have dropped a near-black patch
   into a light panel. */
.ie-dropzone.drag{border-color:#C4892A;background:linear-gradient(180deg,#FDF8EB 0%,#FAF0D9 100%);}
.ie-dz-art{display:block;margin:0 auto 6px;width:200px;height:auto;}
.ie-dz-title{
  font-family:Georgia,'Times New Roman',serif;
  font-size:24px;font-weight:700;color:#1c2a4a;letter-spacing:-.2px;
}
.ie-dz-or{margin-top:6px;font-size:13.4px;color:#6d798c;}
.ie-browse{
  appearance:none;cursor:pointer;font-family:inherit;
  display:inline-flex;align-items:center;gap:9px;margin-top:14px;
  padding:12px 24px;border-radius:11px;
  font-size:14.4px;font-weight:700;
  color:#2a1f06;background:linear-gradient(180deg,#DFAE55,#C4892A);
  border:none;box-shadow:0 8px 18px -8px rgba(180,132,40,.7);
  transition:filter .15s ease, box-shadow .15s ease;
}
@media (hover:hover) and (pointer:fine){
  .ie-browse:hover{filter:brightness(1.05);box-shadow:0 10px 22px -8px rgba(180,132,40,.8);}
}
/* Also on the panel's own palette, not --text-400: that token is a pale grey
   in dark mode and would have gone almost white on this light ground. */
.ie-dz-note{
  display:flex;align-items:center;justify-content:center;gap:8px;flex-wrap:wrap;
  margin-top:16px;font-size:12.2px;color:#7d8898;
}
.ie-dz-note-icon{display:inline-flex;align-items:center;}
.ie-dz-note-sep{opacity:.6;}
.ie-error{margin-top:14px;font-size:13px;color:var(--red);}

/* ---------- Selected file + Submit ---------- */
.ie-filebar{
  display:flex;align-items:center;gap:14px;
  margin-top:16px;padding:14px 16px;border-radius:12px;
  border:1px solid var(--border);background:var(--card);
}
.ie-filebar-tile{
  flex:none;width:40px;height:40px;border-radius:11px;
  display:inline-flex;align-items:center;justify-content:center;
  background:var(--subtle-bg);color:var(--text-400);
}
.ie-filebar-copy{flex:1 1 auto;min-width:0;}
.ie-filebar-name{font-size:13.6px;font-weight:700;color:var(--text-900);}
.ie-filebar-sub{margin-top:2px;font-size:12.2px;color:var(--text-400);}
/* Once a file is chosen the bar says so in its own colour, not only its words
   — the tile and border go gold, which is the same signal the Submit button
   uses when it comes alive. */
.ie-filebar.has-file{border-color:rgba(200,153,46,.45);}
.ie-filebar.has-file .ie-filebar-tile{background:rgba(200,153,46,.14);color:var(--gold-600);}
.ie-submit{
  appearance:none;cursor:pointer;font-family:inherit;flex:none;
  display:inline-flex;align-items:center;gap:9px;
  padding:11px 20px;border-radius:11px;border:none;
  font-size:13.6px;font-weight:700;
  color:#2a1f06;background:linear-gradient(180deg,#DFAE55,#C4892A);
  box-shadow:0 8px 18px -9px rgba(180,132,40,.7);
  transition:filter .15s ease;
}
@media (hover:hover) and (pointer:fine){
  .ie-submit:not([disabled]):hover{filter:brightness(1.05);}
}
.ie-submit[disabled]{
  cursor:not-allowed;background:var(--subtle-bg);color:var(--text-400);
  box-shadow:none;
}

/* ---------- How it works ---------- */
.ie-how{
  display:flex;align-items:center;gap:20px;flex-wrap:wrap;
  margin-top:16px;padding:16px 18px;border-radius:12px;
  border:1px solid var(--border);background:var(--card);
}
.ie-how-label{
  flex:none;display:inline-flex;align-items:center;gap:10px;
  padding-right:20px;border-right:1px solid var(--border);
  font-size:14px;font-weight:700;color:var(--text-900);
}
.ie-how-bulb{
  width:30px;height:30px;border-radius:50%;
  display:inline-flex;align-items:center;justify-content:center;
  background:rgba(200,153,46,.15);color:var(--gold-600);
}
.ie-how-steps{
  flex:1 1 420px;display:flex;align-items:center;gap:14px;flex-wrap:wrap;
}
.ie-how-step{display:flex;align-items:center;gap:11px;flex:1 1 190px;min-width:0;}
/* Inverted against the card rather than painted a fixed navy: --navy-900 is
   almost black in dark mode, so the disc disappeared into the card behind it
   and the numbers read as bare digits. Taking the text colour as the disc and
   the card colour as the digit gives a dark disc on white and a light disc on
   dark, with full contrast either way and no second rule to keep in step. */
.ie-how-num{
  flex:none;width:22px;height:22px;border-radius:50%;
  display:inline-flex;align-items:center;justify-content:center;
  background:var(--text-900);color:var(--card);
  font-size:11.4px;font-weight:700;
}
.ie-how-icon{
  flex:none;width:38px;height:38px;border-radius:50%;
  display:inline-flex;align-items:center;justify-content:center;
  border:1px solid var(--border);background:var(--card);color:var(--text-600);
}
.ie-how-title{font-size:13px;font-weight:700;color:var(--text-900);}
.ie-how-body{margin-top:2px;font-size:11.8px;line-height:1.45;color:var(--text-400);}
.ie-how-arrow{flex:none;display:inline-flex;align-items:center;color:var(--text-400);opacity:.6;}
@media(max-width:900px){
  .ie-how-label{border-right:none;padding-right:0;}
  .ie-how-arrow{display:none;}
  .ie-how-step{flex:1 1 100%;}
}
.ie-timeline{display:flex;flex-wrap:wrap;align-items:center;gap:2px;}
.ie-stage{
  border:1px solid var(--border);border-radius:8px;padding:6px 10px;font-size:11.6px;
  display:flex;flex-direction:column;gap:2px;min-width:110px;background:var(--card);
}
.ie-stage-name{font-weight:600;color:var(--text-900);}
.ie-stage-status{color:var(--text-400);text-transform:uppercase;font-size:10.4px;letter-spacing:.03em;}
.ie-stage-passed .ie-stage-status{color:var(--green);}
.ie-stage-failed .ie-stage-status,.ie-stage-rejected .ie-stage-status{color:var(--red);}
.ie-stage-partial .ie-stage-status{color:var(--amber);}
.ie-stage-sep{color:var(--text-400);font-size:12px;}
.ie-summary-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(110px,1fr));gap:10px;margin-top:14px;}
.ie-summary-card{border:1px solid var(--border);border-radius:10px;padding:12px;text-align:center;transition:box-shadow .12s ease;}
.ie-summary-card:hover{box-shadow:var(--shadow);}
.ie-summary-num{font-size:22px;font-weight:700;color:var(--text-900);}
.ie-summary-label{font-size:11.5px;color:var(--text-400);margin-top:2px;}
.ie-filter-chip{
  border:1px solid var(--border);border-radius:20px;padding:5px 12px;font-size:12px;cursor:pointer;
  color:var(--text-600);transition:background .12s ease, color .12s ease;
}
.ie-filter-chip:hover{background:var(--border);}
.ie-filter-chip.active{background:var(--gold-500);color:#1a1206;border-color:var(--gold-500);font-weight:600;}
.ie-badge{border-radius:20px;padding:4px 10px;font-size:11.8px;font-weight:600;display:inline-block;background:var(--border);color:var(--text-600);}
.ie-badge-success{background:var(--green-bg);color:var(--green);}
.ie-badge-danger{background:var(--red-bg);color:var(--red);}
/* Duplicate import rows - not an error (nothing was wrong with the row), but
   nothing was written either, so amber rather than green or red */
.ie-badge-warning{background:var(--amber-bg);color:var(--amber);}
.ie-stage-found .ie-stage-status{color:var(--amber);}
.ie-dup-list{margin-top:14px;border:1px solid var(--border);border-radius:10px;overflow:hidden;}
.ie-dup-list-head{padding:9px 12px;background:var(--amber-bg);color:var(--amber);font-weight:600;font-size:13px;}
.ie-dup-row{padding:9px 12px;border-top:1px solid var(--border);font-size:12.8px;line-height:1.45;}
.ie-dup-row-meta{color:var(--text-400);font-size:11.8px;margin-bottom:2px;}
/* Rows the import refused (mandatory field missing, bad value, ...) - same
   shape as the duplicate list above but red, not amber: a duplicate is only
   "already there", a rejection needs the file fixed and imported again. */
.ie-rej-list{margin-top:14px;border:1px solid var(--border);border-radius:10px;overflow:hidden;}
.ie-rej-list-head{padding:9px 12px;background:var(--red-bg);color:var(--red);font-weight:600;font-size:13px;}
.ie-rej-row{padding:9px 12px;border-top:1px solid var(--border);font-size:12.8px;line-height:1.45;}

/* ---------- Security Settings — Two-Step Verification (securitySettings.js) ---------- */
/* ============================================================
   Two-Step Verification banner
   Ported from the "Two step verification" comp.

   A dark security surface instead of the app's white .card: this is the
   only control on the page, and the comp leans on the colour to say what
   the section is before the copy does. Fixed dark greens rather than theme
   tokens — it is the same slab in light and dark mode, the way the Family
   Diary artwork stays warm in both.
   ============================================================ */
.ss-2fa-hero{
  position:relative;overflow:hidden;
  display:flex;align-items:center;gap:30px;
  padding:30px 34px;margin-bottom:18px;
  border-radius:14px;
  color:#eafaf0;
  /* Lighter glowing green at the top-left, falling away to forest green in
     the bottom-right, with a lit corner over it. */
  background:
    radial-gradient(circle at 14% 16%, rgba(110,231,160,.30) 0, transparent 46%),
    radial-gradient(circle at 86% 88%, rgba(5,44,26,.55) 0, transparent 56%),
    linear-gradient(135deg,#1a7c47 0%,#12603a 38%,#0b3d24 72%,#082e1b 100%);
  box-shadow:var(--shadow);
}

/* ---------- The encrypted texture ----------
   Two tiled SVG layers, both pointer-events:none so neither can eat a
   click on the button underneath. Split by subject rather than crammed
   into one tile: ::before carries the fine circuit traces, junction pads
   and binary, ::after the locks and shields they connect. Two tiles at
   different sizes drifting opposite ways is also what makes the texture
   read as dense — a single tile at one scale shows its repeat.

   Inline data URIs rather than assets: /assets has no 2FA art, and a tile
   that has to stay in step with a keyframe is better read next to it.

   Each tile draws its artwork twice — once through a feGaussianBlur for
   the halo, then again sharp on top. The blur is baked in when the browser
   rasterises the tile, so the glow costs nothing per frame, unlike a CSS
   filter on a drifting layer.

   Traces are laid out to cross each tile edge at matching coordinates
   (::before at y=34/104 and x=46/112), so a trace leaving one tile meets
   the one entering the next and the circuitry runs unbroken across the
   whole slab.

   IMPORTANT: each layer's drift keyframe shifts by exactly one tile of its
   own background-size, which is what makes the loop seamless. Change a
   size here and ssGridPan / ssLockDrift has to move with it. */
.ss-2fa-hero::before{
  content:'';position:absolute;inset:0;pointer-events:none;opacity:.30;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140' viewBox='0 0 140 140'%3E%3Cdefs%3E%3Cfilter id='b' x='-60%25' y='-60%25' width='220%25' height='220%25'%3E%3CfeGaussianBlur stdDeviation='2'/%3E%3C/filter%3E%3Cg id='t' fill='none' stroke='%235fe39b' stroke-width='1.2'%3E%3Cpath d='M0 34H32V64H74V34H140'/%3E%3Cpath d='M0 104H50V126H100V104H140'/%3E%3Cpath d='M46 0V26H20V70H46V140'/%3E%3Cpath d='M112 0V44H132V80H112V140'/%3E%3C/g%3E%3Cg id='d' fill='%235fe39b'%3E%3Ccircle cx='32' cy='34' r='2.2'/%3E%3Ccircle cx='74' cy='34' r='2.2'/%3E%3Ccircle cx='50' cy='104' r='2.2'/%3E%3Ccircle cx='100' cy='104' r='2.2'/%3E%3Ccircle cx='46' cy='26' r='2.2'/%3E%3Ccircle cx='112' cy='44' r='2.2'/%3E%3C/g%3E%3Cg id='n' fill='%235fe39b' font-family='monospace' font-size='9'%3E%3Ctext x='84' y='18'%3E1011%3C/text%3E%3Ctext x='6' y='90'%3E0110%3C/text%3E%3Ctext x='58' y='134'%3E1001%3C/text%3E%3C/g%3E%3C/defs%3E%3Cg filter='url(%23b)' opacity='.85'%3E%3Cuse href='%23t'/%3E%3Cuse href='%23d'/%3E%3Cuse href='%23n'/%3E%3C/g%3E%3Cuse href='%23t'/%3E%3Cuse href='%23d'/%3E%3Cuse href='%23n'/%3E%3C/svg%3E");
  background-size:140px 140px;
}
.ss-2fa-hero::after{
  content:'';position:absolute;inset:0;pointer-events:none;opacity:.34;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='200' height='200' viewBox='0 0 200 200'%3E%3Cdefs%3E%3Cfilter id='b' x='-60%25' y='-60%25' width='220%25' height='220%25'%3E%3CfeGaussianBlur stdDeviation='2.4'/%3E%3C/filter%3E%3Cg id='s' fill='none' stroke='%236ee7a0' stroke-width='1.6'%3E%3Crect x='28' y='64' width='30' height='24' rx='5'/%3E%3Cpath d='M35 64v-8a8 8 0 0 1 16 0v8'/%3E%3Ccircle cx='43' cy='75' r='3'/%3E%3Cpath d='M140 32l16 6v12c0 10-7 16-16 19c-9-3-16-9-16-19V38z'/%3E%3Cpath d='M140 45v13'/%3E%3Crect x='140' y='158' width='22' height='17' rx='4'/%3E%3Cpath d='M145 158v-6a6 6 0 0 1 12 0v6'/%3E%3Cpath d='M48 138l12 4.5v9c0 7.5-5 12-12 14c-7-2-12-6.5-12-14v-9z'/%3E%3Cpath d='M58 76h44'/%3E%3Cpath d='M60 150h60'/%3E%3Cpath d='M140 88v42'/%3E%3C/g%3E%3Cg id='p' fill='%236ee7a0'%3E%3Ccircle cx='102' cy='76' r='2.4'/%3E%3Ccircle cx='120' cy='150' r='2.4'/%3E%3C/g%3E%3C/defs%3E%3Cg filter='url(%23b)' opacity='.8'%3E%3Cuse href='%23s'/%3E%3Cuse href='%23p'/%3E%3C/g%3E%3Cuse href='%23s'/%3E%3Cuse href='%23p'/%3E%3C/svg%3E");
  background-size:200px 200px;
}
/* The travelling sweep. Sits idle here and is moved entirely by the
   animation further down, so with motion off it is an invisible overlay
   rather than a light band frozen across the middle of the banner. */
.ss-2fa-scan{
  position:absolute;inset:0;pointer-events:none;z-index:1;
  background:linear-gradient(to bottom,
    transparent 0,
    rgba(170,255,210,.14) 46%,
    rgba(214,255,232,.26) 50%,
    rgba(170,255,210,.14) 54%,
    transparent 100%);
  background-size:100% 260%;
  background-repeat:no-repeat;
  background-position:0 -160%;
  opacity:0;
}

/* Above all three decorative layers. */
.ss-2fa-copy{position:relative;z-index:2;flex:1 1 46%;min-width:0;}
.ss-2fa-title{margin:0;font-size:23px;font-weight:800;letter-spacing:-.3px;color:#fff;}
.ss-2fa-sub{margin:9px 0 0;font-size:13.6px;line-height:1.5;color:#c7e6d4;max-width:520px;}

/* The status pill and the secondary buttons are the shared components, so
   they arrive styled for a white card. Re-tinted for this dark ground —
   scoped to the banner so nothing else that uses them is touched.
   .btn-gold and .btn-danger already read correctly here and are left be. */
.ss-2fa-hero .ss-status-badge.off{background:rgba(255,255,255,.17);color:#dcf2e5;}
.ss-2fa-hero .ss-status-badge.on{background:rgba(78,206,132,.24);color:#a6f0c5;}
.ss-2fa-hero .ss-method-row{color:#bcdfca;}
.ss-2fa-hero .btn:not(.btn-gold):not(.btn-danger){
  background:rgba(255,255,255,.13);
  border-color:rgba(190,240,210,.42);
  color:#eafaf0;
}

/* ---------- The process panel ----------
   The comp's rendered illustration, rebuilt as a small inset card. */
.ss-2fa-diagram{
  position:relative;z-index:2;flex:0 1 430px;
  padding:16px 18px 18px;border-radius:12px;
  background:linear-gradient(160deg, rgba(12,58,84,.92), rgba(9,42,62,.92));
  border:1px solid rgba(140,205,235,.28);
  box-shadow:0 16px 30px -18px rgba(0,0,0,.75);
  text-align:center;
}
.ss-dg-head{font-size:15.4px;font-weight:800;color:#fff;}
.ss-dg-sub{margin:5px 0 13px;font-size:11.4px;line-height:1.4;color:#a9c9dd;}
.ss-dg-flow{display:flex;align-items:center;gap:12px;}
.ss-dg-step{
  flex:1;min-width:0;padding:11px 9px 13px;
  border-radius:10px;
  background:rgba(255,255,255,.07);
  border:1px solid rgba(140,205,235,.22);
}
.ss-dg-cap{font-size:11.4px;font-weight:800;color:#8fd8ff;}
.ss-dg-name{margin-top:3px;font-size:12px;font-weight:700;line-height:1.25;color:#fff;}
.ss-dg-hint{margin-top:2px;font-size:10.4px;line-height:1.3;color:#9dbdd2;}
.ss-dg-tile{
  margin:9px auto 0;width:42px;height:42px;border-radius:10px;
  display:flex;align-items:center;justify-content:center;font-size:21px;
  background:rgba(255,255,255,.1);
  border:1px solid rgba(140,205,235,.26);
}
.ss-dg-arrow{flex:none;font-size:19px;color:#8fd8ff;}

/* The panel is the first thing to go: it only restates the copy, so on a
   narrow screen the copy alone is the whole message. */
@media(max-width:1080px){
  .ss-2fa-hero{flex-direction:column;align-items:stretch;gap:22px;padding:24px 22px;}
  .ss-2fa-diagram{flex:none;}
}
@media(max-width:620px){
  .ss-2fa-diagram{display:none;}
}

/* ============================================================
   Two-Step Verification banner — motion

   Gated behind prefers-reduced-motion like every other animated surface in
   this file, and written so the still frame underneath is already correct:
   nothing here is the only thing positioning or revealing an element, so
   with motion off the banner simply sits there fully drawn.
   ============================================================ */
@media (prefers-reduced-motion: no-preference){

  /* ---------- Entry ----------
     The slab rises, then its copy follows in reading order and the panel
     comes in from the side it sits on. */
  @keyframes ssRise{from{opacity:0;transform:translateY(14px);}to{opacity:1;transform:none;}}
  @keyframes ssPanelIn{
    from{opacity:0;transform:translateX(26px) scale(.97);}
    to{opacity:1;transform:none;}
  }
  .ss-2fa-hero{animation:ssRise .8s cubic-bezier(.16,1,.3,1) both;}
  .ss-2fa-title{animation:ssRise .7s cubic-bezier(.16,1,.3,1) .10s both;}
  .ss-2fa-sub{animation:ssRise .7s cubic-bezier(.16,1,.3,1) .18s both;}
  .ss-2fa-hero .ss-status-row{animation:ssRise .7s cubic-bezier(.16,1,.3,1) .26s both;}
  .ss-2fa-hero .ss-actions-row{animation:ssRise .7s cubic-bezier(.16,1,.3,1) .34s both;}
  .ss-2fa-diagram{animation:ssPanelIn .9s cubic-bezier(.16,1,.3,1) .20s both;}

  /* ---------- The ground ----------
     Both texture layers drift, slowly and in opposite directions, so the
     circuitry reads as live rather than as wallpaper. Each shift is exactly
     one tile of its own background-size, which is what makes the loop
     seamless — change a background-size here and its keyframe has to move
     with it. Very long durations: this banner is on screen the whole time
     the page is, and anything quick enough to notice twice would nag. */
  .ss-2fa-hero::before{animation:ssGridPan 30s linear infinite;}
  @keyframes ssGridPan{
    from{background-position:0 0;}
    to{background-position:140px 140px;}
  }
  .ss-2fa-hero::after{animation:ssLockDrift 52s linear infinite;}
  @keyframes ssLockDrift{
    from{background-position:0 0;}
    to{background-position:-200px 200px;}
  }

  /* A scan passes down the slab every few seconds, then rests — the pause
     in the keyframe is most of the cycle, so it reads as an occasional
     sweep rather than a strobe. */
  .ss-2fa-scan{animation:ssScan 7s ease-in-out 1s infinite;}
  @keyframes ssScan{
    0%      {opacity:0;background-position:0 -160%;}
    8%      {opacity:1;}
    45%     {opacity:1;}
    55%,100%{opacity:0;background-position:0 160%;}
  }

  /* ---------- The process panel ----------
     The two steps light in turn, half a cycle apart, so the panel performs
     the sequence it describes instead of only labelling it. The resting
     appearance is restated at 0%/42%/100% because these properties are also
     set on the base rule — a keyframe that only declared the lit state
     would leave the step stuck bright once the animation filled. */
  .ss-dg-step.s1{animation:ssStepGlow 5s ease-in-out infinite;}
  .ss-dg-step.s2{animation:ssStepGlow 5s ease-in-out 2.5s infinite;}
  @keyframes ssStepGlow{
    0%,42%,100%{
      background:rgba(255,255,255,.07);
      border-color:rgba(140,205,235,.22);
      box-shadow:0 0 0 0 rgba(120,200,255,0);
    }
    12%,30%{
      background:rgba(120,200,255,.15);
      border-color:rgba(150,215,255,.6);
      box-shadow:0 0 20px -4px rgba(120,200,255,.55);
    }
  }
  /* Each tile lifts with its own step. */
  .ss-dg-step.s1 .ss-dg-tile{animation:ssTileLift 5s ease-in-out infinite;}
  .ss-dg-step.s2 .ss-dg-tile{animation:ssTileLift 5s ease-in-out 2.5s infinite;}
  @keyframes ssTileLift{
    0%,42%,100%{transform:scale(1) translateY(0);}
    12%,30%{transform:scale(1.08) translateY(-2px);}
  }
  /* The arrow brightens and nudges across at the handover — between the
     two steps' lit windows, so it reads as the thing passing control. */
  .ss-dg-arrow{animation:ssArrowHand 5s ease-in-out infinite;}
  @keyframes ssArrowHand{
    0%,30%,70%,100%{opacity:.5;transform:translateX(0);}
    45%{opacity:1;transform:translateX(4px);}
  }

  /* A slow halo on the one call to action. Box-shadow only — nothing here
     moves the button, so it never shifts under a cursor mid-click. */
  .ss-2fa-hero .btn-gold{animation:ssEnableHalo 2.9s ease-in-out infinite;}
  @keyframes ssEnableHalo{
    0%,100%{box-shadow:0 0 0 0 rgba(230,178,54,0);}
    50%{box-shadow:0 0 0 6px rgba(230,178,54,.20);}
  }
}

.ss-status-row{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin:10px 0 4px;}
.ss-status-badge{border-radius:20px;padding:4px 12px;font-size:12.6px;font-weight:700;display:inline-block;}
.ss-status-badge.on{background:var(--green-bg);color:var(--green);}
.ss-status-badge.off{background:var(--subtle-bg);color:var(--text-400);}
.ss-method-row{font-size:12.6px;color:var(--text-400);margin-bottom:14px;}
.ss-actions-row{display:flex;gap:10px;flex-wrap:wrap;margin-top:14px;}
.ss-step{display:flex;gap:14px;padding:9px 0;border-top:1px solid var(--border);}
.ss-step:first-of-type{border-top:none;padding-top:4px;}
.ss-step-num{
  flex-shrink:0;width:28px;height:28px;border-radius:50%;
  background:var(--gold-500);color:#1a1305;font-weight:800;font-size:12.6px;
  display:flex;align-items:center;justify-content:center;
}
.ss-step-title{font-weight:700;font-size:13.6px;color:var(--text-900);margin-bottom:3px;}
.ss-step-body{font-size:12.6px;color:var(--text-400);line-height:1.55;}
.ss-qr-box{
  display:flex;align-items:center;justify-content:center;
  background:#fff;border:1px solid var(--border);border-radius:12px;
  padding:12px;margin:8px 0;
  width:min(220px,100%);aspect-ratio:1/1;box-sizing:border-box;
}
.ss-manual-key{
  font-family:'SFMono-Regular',Consolas,'Liberation Mono',Menlo,monospace;
  font-size:12.6px;letter-spacing:.04em;background:var(--subtle-bg);
  border:1px solid var(--border);border-radius:8px;padding:8px 10px;
  word-break:break-all;margin-top:8px;
}
.ss-recovery-grid{
  display:grid;grid-template-columns:1fr 1fr;gap:8px 14px;
  background:var(--subtle-bg);border:1px solid var(--border);border-radius:10px;
  padding:16px;margin:14px 0;
}
@media(max-width:520px){.ss-recovery-grid{grid-template-columns:1fr;}}
.ss-recovery-code{
  font-family:'SFMono-Regular',Consolas,'Liberation Mono',Menlo,monospace;
  font-size:13.4px;font-weight:700;color:var(--text-900);letter-spacing:.03em;
}
/* ============================================================
   Manage Trusted Devices
   ------------------------------------------------------------
   Was a stack of flex rows, each carrying its three timestamps as one
   middot-separated sentence, with a full-size .btn-danger on the right. Three
   problems with that: comparing two devices meant reading two sentences to
   the end rather than glancing down a column; nothing told one row's device
   from the next but the words; and a solid red button on every row made
   revoking look like the purpose of the panel rather than its exception.

   Now a real table, so the timestamps line up and the eye runs down one
   column at a time. It collapses to a card per device below 860px — see the
   end of this block — because five columns cannot survive a narrow screen,
   and a horizontally scrolling table is worse than a stack.

   The old .ss-device-row / -label / -meta rules are deleted rather than left
   to dangle: nothing renders those class names any more, and this file's own
   Insurance section is a standing lesson in what a trail of dead selectors
   costs the next reader.
   ============================================================ */
.ss-devices-card{padding-bottom:6px;}
.ss-devices-head{display:flex;align-items:center;gap:10px;margin-bottom:4px;}
.ss-devices-title{margin:0;font-size:16px;font-weight:700;color:var(--text-900);}
/* The count sits with the heading rather than over the table: it answers
   "what is in here", which is a property of the panel, not a column. */
.ss-devices-count{
  font-size:11.6px;font-weight:700;letter-spacing:.03em;
  padding:3px 9px;border-radius:20px;
  background:var(--subtle-bg);color:var(--text-400);
}
.ss-devices-note{margin:0 0 14px;font-size:12.8px;line-height:1.5;color:var(--text-400);max-width:640px;}

.ss-dev-tablewrap{overflow-x:auto;}
.ss-dev-table{width:100%;border-collapse:collapse;}
.ss-dev-table th{
  text-align:left;padding:0 14px 8px 0;
  font-size:11px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;
  color:var(--text-400);
  border-bottom:1px solid var(--border);
  white-space:nowrap;
}
.ss-dev-table td{
  padding:13px 14px 13px 0;
  border-bottom:1px solid var(--border);
  vertical-align:middle;
}
.ss-dev-table tbody tr:last-child td{border-bottom:none;}
/* A tint on the whole row rather than on the button: the row is the thing
   being identified, and it makes the revoke target obvious without the
   button having to shout at rest. */
@media (hover:hover) and (pointer:fine){
  .ss-dev-table tbody tr:hover td{background:var(--subtle-bg);}
}

/* ---------- The device cell ---------- */
.ss-dev-id{display:flex;align-items:center;gap:11px;min-width:0;}
/* The mark sits in a tile rather than bare, so a pale logo — Chrome's white
   core, Safari's needle — still has an edge against the card. */
.ss-dev-mark{
  flex:none;width:34px;height:34px;border-radius:9px;
  display:inline-flex;align-items:center;justify-content:center;
  background:var(--subtle-bg);
  border:1px solid var(--border);
  color:var(--text-400);
}
.ss-dev-mark svg{display:block;}
.ss-dev-names{display:flex;flex-direction:column;gap:1px;min-width:0;}
.ss-dev-browser{font-size:13.4px;font-weight:700;color:var(--text-900);}
.ss-dev-os{
  display:inline-flex;align-items:center;gap:5px;
  font-size:11.8px;color:var(--text-400);
}
.ss-dev-os svg{flex:none;opacity:.85;}

/* ---------- Timestamps ----------
   Date and time on separate lines at different weights, so a column reads as
   a list of dates with a detail under each rather than a block of
   same-length strings. Tabular figures keep the digits in step down the
   column, which is the whole reason for putting them in one. */
.ss-dev-date,
.ss-dev-time,
.ss-dev-rel{display:block;font-variant-numeric:tabular-nums;white-space:nowrap;}
.ss-dev-date{font-size:12.8px;font-weight:600;color:var(--text-900);}
.ss-dev-time{font-size:11.4px;color:var(--text-400);margin-top:1px;}
.ss-dev-rel{font-size:11.4px;font-weight:600;color:var(--text-400);margin-top:1px;}
/* Only the near ones take colour. Tint every expiry and the tint says
   nothing — this panel exists so a device that should not still be trusted
   can be picked out of the list. */
.ss-dev-rel.is-urgent{color:var(--gold-600);}

/* ---------- Revoke ----------
   A pill: outlined and in the body colour at rest, red only on hover or
   focus. It is a destructive action on a row someone is usually only
   browsing, so it should be findable rather than loud — a column of solid
   red buttons made the panel look like a list of problems. The column
   shrink-wraps and right-aligns, so the pills form one clean edge instead of
   landing wherever each row's text happens to end. */
.ss-dev-table .ss-dev-action{width:1%;padding-right:0;text-align:right;white-space:nowrap;}
.ss-revoke{
  appearance:none;cursor:pointer;
  padding:6px 15px;border-radius:20px;
  font-family:inherit;font-size:12.2px;font-weight:700;
  color:var(--text-600);
  background:transparent;
  border:1px solid var(--border);
  transition:background .18s ease-out, border-color .18s ease-out, color .18s ease-out;
}
/* --red / --red-bg, the same pair .btn-danger uses, so revoking reads as the
   same kind of action here as it does everywhere else in the app — and so it
   follows the theme, which a hard-coded red would not. */
.ss-revoke:hover{
  color:var(--red);
  border-color:var(--red);
  background:var(--red-bg);
}
.ss-revoke:focus-visible{
  outline:2px solid var(--red);
  outline-offset:2px;
}
/* Visually hidden, still announced. Scoped rather than added as a global
   .sr-only: this app has no such utility, and defining one here would be a
   name any later stylesheet could collide with. */
.ss-dev-table .ss-sr{
  position:absolute;width:1px;height:1px;overflow:hidden;
  clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;
}

/* ---------- Empty state ---------- */
.ss-dev-empty{
  display:flex;align-items:center;gap:13px;
  padding:16px;margin-bottom:12px;
  border:1px dashed var(--border);border-radius:12px;
  background:var(--subtle-bg);
}
.ss-dev-empty-mark{
  flex:none;width:38px;height:38px;border-radius:10px;
  display:inline-flex;align-items:center;justify-content:center;
  background:var(--card);border:1px solid var(--border);color:var(--text-400);
}
.ss-dev-empty-title{font-size:13.4px;font-weight:700;color:var(--text-900);}
.ss-dev-empty-sub{font-size:12px;color:var(--text-400);margin-top:2px;}

/* ---------- Narrow screens: one card per device ----------
   The header row is hidden and each cell carries its own label from the
   data-label the markup already writes, so neither layout needs a duplicate
   copy of anything in the DOM. */
@media(max-width:860px){
  .ss-dev-table thead{
    position:absolute;width:1px;height:1px;overflow:hidden;
    clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap;
  }
  .ss-dev-table,
  .ss-dev-table tbody,
  .ss-dev-table tr,
  .ss-dev-table td{display:block;width:auto;}
  .ss-dev-table tr{
    border:1px solid var(--border);border-radius:12px;
    padding:13px 14px;margin-bottom:10px;
    position:relative;
  }
  .ss-dev-table td{border-bottom:none;padding:0;}
  /* The device line keeps the top of the card; the three timestamps become
     label/value rows beneath it. */
  .ss-dev-table td[data-label="Device"]{margin-bottom:10px;padding-right:96px;}
  /* margin-right:auto on the label rather than justify-content:space-between
     on the row: each of these cells holds a label and *two* values (a date and
     its time, or a date and how long is left), and space-between would spread
     all three evenly — stranding the time at the far edge, a column away from
     the date it belongs to. Pushing only the label left keeps the pair
     together at the right where they read as one value. */
  .ss-dev-table td[data-label]:not([data-label="Device"]){
    display:flex;align-items:baseline;gap:9px;
    padding:5px 0;border-top:1px solid var(--border);
  }
  .ss-dev-table td[data-label]:not([data-label="Device"])::before{
    content:attr(data-label);
    margin-right:auto;
    font-size:11px;font-weight:700;letter-spacing:.05em;text-transform:uppercase;
    color:var(--text-400);
  }
  .ss-dev-table td[data-label]:not([data-label="Device"]) > span{text-align:right;}
  /* Pinned to the card's top-right, level with the device name — the padding
     reserved on the device cell above is what keeps the two apart. */
  .ss-dev-table .ss-dev-action{position:absolute;top:14px;right:14px;width:auto;}
  .ss-dev-table tbody tr:hover td{background:transparent;}
}

/* Reused inside the Security Settings modal (securitySettings.js) so the
   2FA setup/verify screens match auth.html's Sign In card exactly, per
   the 2FA spec's "reuse the existing authentication card design"
   requirement - same class names as auth.css, since this context (a main-
   app modal) only loads styles.css. */
.auth-subtitle{font-size:13.4px;color:var(--text-400);margin:0 0 24px;}
.auth-otp-input{width:100%;font-size:26px;letter-spacing:10px;text-align:center;font-weight:700;padding:14px 10px;color:var(--text-900);}
.auth-field-error{font-size:11.8px;color:var(--red);margin-top:5px;}
.auth-form-error{font-size:12.6px;color:var(--red);background:var(--red-bg);border-radius:8px;padding:9px 12px;margin-bottom:16px;}
.auth-link{color:var(--gold-600);font-weight:600;cursor:pointer;text-decoration:none;background:none;border:none;padding:0;font-size:inherit;}
.auth-link:hover{text-decoration:underline;}
.auth-submit{width:100%;justify-content:center;margin-top:6px;}
.auth-success{text-align:center;padding:20px 6px;}
.auth-success-icon{font-size:48px;margin-bottom:14px;}
.auth-success h2{font-size:20px;font-weight:800;color:var(--text-900);margin:0 0 10px;}
.auth-success p{font-size:13.6px;color:var(--text-400);margin:0 0 26px;line-height:1.6;}

/* ---------- Idle timeout warning — see startIdleTimeoutWatch() in app.js.
   Its own overlay, deliberately above .modal-overlay (z-index 100), so it
   can appear even while an Add/Edit record modal is open without touching
   that modal's DOM. */
.idle-warning-overlay{
  position:fixed;inset:0;z-index:200;
  display:flex;align-items:center;justify-content:center;
  background:rgba(10,18,32,.6);
  padding:16px;
  animation:pop .12s ease;
}
.idle-warning-card{
  background:var(--card);
  border-radius:14px;
  box-shadow:var(--shadow-md);
  max-width:380px;
  width:100%;
  padding:28px 26px;
  text-align:center;
}
.idle-warning-icon{font-size:34px;margin-bottom:10px;}
.idle-warning-card h3{margin:0 0 8px;font-size:18px;font-weight:800;color:var(--text-900);}
.idle-warning-card p{margin:0 0 20px;font-size:13.4px;color:var(--text-600);line-height:1.6;}
.idle-warning-card p #idleCountdown{font-weight:700;color:var(--red);}
.idle-warning-actions{display:flex;gap:10px;justify-content:center;}

/* ---------- Duplicate detection popup (duplicateCheck.js) ----------
   Own stacked overlay, same technique as .idle-warning-overlay above,
   so it can appear on top of the still-open Add Asset/Liability modal
   without touching that modal's DOM (form values stay intact). */
.dd-overlay{
  position:fixed;inset:0;z-index:190;
  display:flex;align-items:flex-start;justify-content:center;
  background:rgba(10,18,32,.6);
  padding:40px 16px;overflow-y:auto;
  animation:pop .12s ease;
}
.dd-card{
  background:var(--card);border-radius:14px;box-shadow:var(--shadow-md);
  max-width:640px;width:100%;padding:24px 26px;
}
.dd-card h3{margin:0 0 6px;font-size:17px;font-weight:800;color:var(--text-900);}
.dd-candidate-card{border:1px solid var(--border);border-radius:10px;padding:14px;margin:14px 0;}
.dd-compare-grid td{font-size:12.8px;}
.dd-field-diff td:nth-child(2),.dd-field-diff td:nth-child(3){color:var(--amber);font-weight:700;}
.dd-field-match td:nth-child(2),.dd-field-match td:nth-child(3){color:var(--green);}

/* ============================================================
   Visual Customization v1 — Roles & Access / Bulk Data Import /
   Bulk Data Export / Family Diary / WhatsApp Notifications / Reports
   ============================================================
   Visual-only redesign scoped to these six modules (each module's
   render() wraps its output in the page class used below). Several
   classes these modules use are ALSO used by modules explicitly out of
   scope for this redesign (.ra-table/.ra-badge* - emiSchedule.js,
   whatsappSettings.js, duplicateCheck.js; .alert-item - alerts.js) -
   restyling those base rules directly would silently redesign those other
   pages too, so every rule touching a shared class here is scoped under
   one of these six page classes instead, the same pattern already used
   above for .dashboard-page/.balance-sheet-page/.alerts-page. */

/* ---------- Shared page header scale (reuses the existing .card-header/
   .card-title/.card-sub/.spacer markup already used by Family Diary and
   Reports rather than a new header component) ---------- */
.roles-access-page .card-title,
.bulk-import-page .card-title,
.bulk-export-page .card-title,
.family-diary-page .card-title,
.whatsapp-notifications-page .card-title,
.reports-page .card-title{
  font-size:16px;
}
.roles-access-page .card-sub,
.bulk-import-page .card-sub,
.bulk-export-page .card-sub,
.family-diary-page .card-sub,
.whatsapp-notifications-page .card-sub,
.reports-page .card-sub{
  font-size:13px;
}

/* ---------- .ra-table / .ra-badge* — shared with out-of-scope modules,
   so scoped to just the three in-scope consumers. ---------- */
.roles-access-page .ra-table,
.bulk-import-page .ra-table,
.whatsapp-notifications-page .ra-table{
  font-size:13.2px;
}
.roles-access-page .ra-table th,
.bulk-import-page .ra-table th,
.whatsapp-notifications-page .ra-table th{
  padding:10px 12px;
}
.roles-access-page .ra-table td,
.bulk-import-page .ra-table td,
.whatsapp-notifications-page .ra-table td{
  padding:12px;
}
.roles-access-page .ra-table tbody tr,
.bulk-import-page .ra-table tbody tr,
.whatsapp-notifications-page .ra-table tbody tr{
  transition:background .12s ease;
}
.roles-access-page .ra-table tbody tr:hover,
.bulk-import-page .ra-table tbody tr:hover,
.whatsapp-notifications-page .ra-table tbody tr:hover{
  background:var(--subtle-bg);
}
.roles-access-page .ra-badge,
.bulk-import-page .ra-badge,
.whatsapp-notifications-page .ra-badge{
  font-weight:700;
}

/* ---------- .alert-item — shared with the out-of-scope Smart Alerts page,
   scoped to Family Diary (Received tab) and Reports (Legacy Readiness). */
.family-diary-page .alert-item,
.reports-page .alert-item{
  border-radius:var(--radius);
}

/* ---------- Reports — the report picker ----------
   Now the shared .cat-tabs-strip component (see catTabsHtml in storage.js
   and .cat-tabs-strip-reports further up), the same one Asset Management
   uses: arrows at both ends and a scroller between them. The page no longer
   renders a .tabs/.tab strip at all, so nothing is scoped here. */

/* ---------- WhatsApp Notifications — stat-card grid + filter bar (both
   previously inline `style=` attributes in whatsappNotifications.js). ---------- */
.whatsapp-notifications-page .wn-stat-grid{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(190px,1fr));gap:14px;margin-bottom:18px;
}

/* ---------- The five summary cards ----------
   Their own .wn-card rather than the shared .stat-card these used to be:
   .stat-card is on the Dashboard, Reports and Balance Sheet too, and none of
   those wants a dark slab with a chat pattern behind it.

   Fixed colours rather than theme tokens, like the other painted surfaces in
   this app — each card is the same in light and dark mode, which is also
   what lets the text colours be chosen against a known background. */
.whatsapp-notifications-page .wn-card{
  position:relative;overflow:hidden;
  border-radius:14px;padding:16px 18px 19px;
  min-height:120px;
  box-shadow:var(--shadow);
  /* Same list, same easing as .stat-card. app.js's entrance stagger drives
     opacity/transform through it and only ever sets transition-delay, so the
     duration and feel stay defined here in one place — and the hover lift
     below rides the same declaration. */
  transition:box-shadow .2s ease-out,
             transform .3s cubic-bezier(0.16,1,0.3,1),
             opacity .3s cubic-bezier(0.16,1,0.3,1);
}
/* Card content sits above the artwork and above the Sent card's animated
   layers, via z-index on the children rather than a stacking context on the
   card — its own overflow clip is doing enough work. */
.whatsapp-notifications-page .wn-card-label,
.whatsapp-notifications-page .wn-card-value,
.whatsapp-notifications-page .wn-card-caption,
.whatsapp-notifications-page .wn-card-icon{position:relative;z-index:1;}

.whatsapp-notifications-page .wn-card-label{
  font-size:11.6px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;
}
.whatsapp-notifications-page .wn-card-value{
  margin-top:4px;font-size:38px;font-weight:800;line-height:1.05;
  letter-spacing:-.5px;
  /* Tabular figures: these counts refresh in place, and proportional digits
     make the number jump sideways as, say, 9 ticks over to 10. */
  font-variant-numeric:tabular-nums;
}
.whatsapp-notifications-page .wn-card-caption{
  margin-top:2px;font-size:13px;font-weight:600;
}
/* The badge is a ring, not a bare glyph — that is what the comp puts in the
   corner of each of the three dark cards. */
.whatsapp-notifications-page .wn-card-icon{
  position:absolute;top:14px;right:15px;
  width:36px;height:36px;border-radius:50%;
  display:inline-flex;align-items:center;justify-content:center;
}

/* Sent — the comp's own artwork rather than the generated pattern the other
   two dark cards use, so this card has neither ::before nor ::after.
   Only the right-hand half of that comp is shipped as the asset: its left
   side carries "SENT TODAY / 245 / Messages" baked in, and the count has to
   come from the API, not from a picture. Three layers, painted front to
   back: a scrim that fades out across the card so the live text keeps its
   contrast over the artwork, the artwork anchored right at the card's own
   height, and the base green filling whatever the artwork doesn't reach. */
.whatsapp-notifications-page .wn-card-sent{
  background:linear-gradient(155deg,#0f4535 0%,#072a20 100%);
}
/* The artwork, on its own element so the hover push below can transform it.
   Anchored right at the card's own height, exactly as the background layer was.
   Its left edge is feathered in the PNG itself, so it needs no scrim to blend —
   and the card's text sits above it at z-index 1 regardless. */
.whatsapp-notifications-page .wn-card-sent .wn-card-art{
  position:absolute;inset:0;pointer-events:none;
  background:url('../assets/wn-sent-art.png') right center / auto 100% no-repeat;
  /* Scaled up a touch at rest so the push never exposes the card's edge. */
  transform:scale(1.04);
}
/* The delivery ticks that trail Sent Today's caption. Declared here rather
   than in the motion block because the box has to hold its 18px whether or not
   anything ever animates in it — sizing it on hover instead would shove the
   caption sideways the moment the pointer arrived. The three glyphs stack on
   each other and are all transparent at rest, so the space simply reads as the
   caption's own trailing margin until there is something to show. */
.whatsapp-notifications-page .wn-card-sent .wn-send-status{
  position:relative;display:inline-block;
  width:18px;height:14px;margin-left:7px;vertical-align:-2px;
}
.whatsapp-notifications-page .wn-card-sent .wn-send-glyph{
  position:absolute;left:0;top:0;
  opacity:0;
  /* Its own shadow rather than the caption's text-shadow, which does nothing
     for an SVG stroke sitting over the artwork. */
  filter:drop-shadow(0 1px 2px rgba(0,0,0,.45));
}
/* The send arrow is square where the three tick glyphs are 18x14, so it is
   nudged in by half the difference to share their centre line. A left offset
   rather than a translate: every stage animates `transform`, and a centring
   translate would have to be repeated in each of that keyframe's steps just to
   avoid being wiped out by the pop-in scale. */
.whatsapp-notifications-page .wn-card-sent .wn-send-arrow{left:2px;color:#25d366;}

/* Pending's trailing clock, on the same terms as Sent's glyphs above: the box
   holds its 18px whether or not anything animates in it, so the caption never
   shifts sideways when the pointer arrives, and the glyph itself is
   transparent until then. */
.whatsapp-notifications-page .wn-card-pending .wn-wait-status{
  position:relative;display:inline-block;
  width:18px;height:14px;margin-left:7px;vertical-align:-2px;
}
.whatsapp-notifications-page .wn-card-pending .wn-wait-clock{
  position:absolute;left:0;top:0;
  opacity:0;
  filter:drop-shadow(0 1px 2px rgba(0,0,0,.45));
}
/* The reference's three weights. The dotted track is the quietest thing in the
   glyph — it is a groove for the arc to run in, not a border — so it takes a
   thinner stroke as well as a dimmer colour; at the arc's own weight the dots
   fatten into a dashed line and the ring stops reading as dots at all. */
.whatsapp-notifications-page .wn-card-pending .wn-wait-track{
  stroke:rgba(198,222,255,.4);stroke-width:1.5;
}
.whatsapp-notifications-page .wn-card-pending .wn-wait-arc{
  stroke:rgba(214,232,255,.95);stroke-width:1.8;
}
.whatsapp-notifications-page .wn-card-pending .wn-wait-hands{
  stroke:rgba(214,232,255,.95);stroke-width:1.8;
}

/* Failed's and Opted Out's trailing glyphs, on the same terms as the two
   above: the box holds its 18px whether or not anything animates in it, so no
   caption shifts sideways when the pointer arrives, and the glyph is
   transparent until then.
   Opacity lives on these wrappers rather than on the glyphs themselves,
   unlike Pending's — both of these animate `transform` on the glyph (a shake,
   a draw-on), and a fade declared on the same element would be a second
   animation fighting the first for the same property. */
.whatsapp-notifications-page .wn-fail-status,
.whatsapp-notifications-page .wn-skip-status{
  position:relative;display:inline-block;
  width:18px;height:14px;margin-left:7px;vertical-align:-2px;
  opacity:0;
}
.whatsapp-notifications-page .wn-fail-glyph,
.whatsapp-notifications-page .wn-skip-glyph{
  position:absolute;left:0;top:0;
}
/* Failed is a dark card, so its glyph is lit and carries a shadow off the
   artwork behind it; Opted Out is a light one, where a drop shadow would read
   as a smudge and the muted body colour is already the right weight. */
.whatsapp-notifications-page .wn-card-failed .wn-fail-glyph{
  color:rgba(255,205,205,.95);
  filter:drop-shadow(0 1px 2px rgba(0,0,0,.45));
}
.whatsapp-notifications-page .wn-card-skipped .wn-skip-glyph{
  color:var(--text-600);
}
/* The bar starts fully retracted so its first frame is an empty circle — the
   dash length is the bar's own, 7.6 across and 7.6 down giving 10.75. */
.whatsapp-notifications-page .wn-skip-slash{
  stroke-dasharray:10.75;stroke-dashoffset:10.75;
}
/* Pending — its comp's artwork, same treatment as Sent above: only the
   right-hand side is shipped, because the left carries "PENDING / 42 /
   Pending Messages" baked in and the count comes from the API. This comp's
   caption runs further right than Sent's, so it is cut further right too. */
.whatsapp-notifications-page .wn-card-pending{
  background:
    linear-gradient(100deg,
      #1d3049 0%,
      rgba(29,48,73,.94) 30%,
      rgba(24,40,62,.55) 58%,
      rgba(19,33,51,.12) 80%,
      rgba(16,29,46,0) 100%),
    url('../assets/wn-pending-art.png') right center / auto 100% no-repeat,
    linear-gradient(155deg,#1d3049 0%,#101d2e 100%);
}
/* Failed — its comp's artwork, same treatment as the two above: only the
   right-hand side ships, since the left carries "FAILED / 5 / Failed
   Messages" baked in and the count comes from the API. */
.whatsapp-notifications-page .wn-card-failed{
  background:
    linear-gradient(100deg,
      #5d1d1d 0%,
      rgba(93,29,29,.94) 30%,
      rgba(78,25,25,.55) 57%,
      rgba(62,20,20,.12) 79%,
      rgba(51,16,16,0) 100%),
    url('../assets/wn-failed-art.png') right center / auto 100% no-repeat,
    linear-gradient(155deg,#5d1d1d 0%,#331010 100%);
}
/* A hairline of light along the top edge, and a deeper shadow than the shared
   --shadow: a dark card on a light page needs both to sit on the surface
   rather than float greyly above it. */
.whatsapp-notifications-page .wn-card-sent,
.whatsapp-notifications-page .wn-card-pending,
.whatsapp-notifications-page .wn-card-failed{
  border:1px solid rgba(255,255,255,.09);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.10),
             0 10px 22px -14px rgba(10,20,35,.75);
}
/* The three dark cards carry their text over busy artwork, so the number and
   caption get the faintest lift off it. Not on the label — it is small and
   uppercase, where a shadow reads as blur rather than depth. */
.whatsapp-notifications-page .wn-card-sent .wn-card-value,
.whatsapp-notifications-page .wn-card-pending .wn-card-value,
.whatsapp-notifications-page .wn-card-failed .wn-card-value,
.whatsapp-notifications-page .wn-card-sent .wn-card-caption,
.whatsapp-notifications-page .wn-card-pending .wn-card-caption,
.whatsapp-notifications-page .wn-card-failed .wn-card-caption{
  text-shadow:0 1px 3px rgba(0,0,0,.35);
}
.whatsapp-notifications-page .wn-card-sent .wn-card-label,
.whatsapp-notifications-page .wn-card-pending .wn-card-label,
.whatsapp-notifications-page .wn-card-failed .wn-card-label{color:rgba(255,255,255,.72);}
.whatsapp-notifications-page .wn-card-sent .wn-card-value,
.whatsapp-notifications-page .wn-card-pending .wn-card-value,
.whatsapp-notifications-page .wn-card-failed .wn-card-value{color:var(--gold-400);}
.whatsapp-notifications-page .wn-card-sent .wn-card-caption,
.whatsapp-notifications-page .wn-card-pending .wn-card-caption,
.whatsapp-notifications-page .wn-card-failed .wn-card-caption{color:rgba(255,255,255,.88);}

/* The three dark cards take their badge from their artwork; Scheduled has
   none in its illustration, so it gets a real one — the same ringed clock
   the Pending card's artwork shows. Tinted for a light ground rather than a
   dark one: gold ring, gold clock, over a wash of the card's own colour so
   the stopwatch behind it doesn't read through the glyph. */
.whatsapp-notifications-page .wn-card-scheduled .wn-card-icon{
  color:var(--gold-600);
  background:rgba(255,255,255,.62);
  border:1px solid rgba(200,153,46,.45);
  /* Drop shadow plus an inset highlight along the top of the ring, so the
     badge reads as a disc resting on the card rather than a flat circle
     printed on it. */
  box-shadow:0 2px 8px -3px rgba(120,90,20,.45),
             inset 0 1px 0 rgba(255,255,255,.85);
  backdrop-filter:blur(1.5px);
}

/* Scheduled — the amber card. Theme tokens rather than fixed cream: this one
   is a light card in a set that otherwise isn't, and --amber-bg already
   carries the right tint in both modes, so it stays readable in dark rather
   than glaring as a lit slab. Same reasoning for Opted Out below. */
.whatsapp-notifications-page .wn-card-scheduled{
  background:
    linear-gradient(100deg,
      var(--amber-bg) 0%,
      var(--amber-bg) 34%,
      rgba(0,0,0,0) 74%),
    url('../assets/wn-scheduled-art.png') right center / auto 100% no-repeat,
    var(--amber-bg);
  border:1px solid rgba(200,153,46,.32);
}
.whatsapp-notifications-page .wn-card-scheduled .wn-card-label{color:var(--text-400);}
.whatsapp-notifications-page .wn-card-scheduled .wn-card-value{color:var(--gold-600);}
.whatsapp-notifications-page .wn-card-scheduled .wn-card-caption{color:var(--text-600);}

/* ---------- Page entrance ----------
   The whole view deals itself in: the five summary cards left to right, then
   the history panel, then its rows. Driven by CSS on insertion rather than
   the app's shared JS stagger — paint() replaces this page's markup wholesale
   the moment the stats land, so every element here is brand new at exactly
   the point there is something worth animating, and the animations simply
   run. It also lets the cards rise AND scale, where the shared mechanism
   only moves opacity and translateY.

   fill-mode is `backwards`, never `both`. `both` would leave the final
   keyframe's transform:none applied for good, which outranks the hover lift
   below and silently kills it; `backwards` holds the start frame during the
   delay and then hands the element back to its own rules.
   ============================================================ */
@media (prefers-reduced-motion: no-preference){
  @keyframes wnCardIn{
    from{opacity:0;transform:translateY(18px) scale(.96);}
    to{opacity:1;transform:none;}
  }
  @keyframes wnPanelIn{
    from{opacity:0;transform:translateY(22px);}
    to{opacity:1;transform:none;}
  }
  @keyframes wnRowIn{
    from{opacity:0;transform:translateY(9px);}
    to{opacity:1;transform:none;}
  }

  .whatsapp-notifications-page .wn-card{
    animation:wnCardIn .62s cubic-bezier(.22,1,.36,1) backwards;
  }
  .whatsapp-notifications-page .wn-card:nth-child(2){animation-delay:.07s;}
  .whatsapp-notifications-page .wn-card:nth-child(3){animation-delay:.14s;}
  .whatsapp-notifications-page .wn-card:nth-child(4){animation-delay:.21s;}
  .whatsapp-notifications-page .wn-card:nth-child(5){animation-delay:.28s;}

  /* The history panel follows the last card in. */
  .whatsapp-notifications-page .card{
    animation:wnPanelIn .6s cubic-bezier(.22,1,.36,1) .34s backwards;
  }
  .whatsapp-notifications-page .wn-filter-bar{
    animation:wnRowIn .5s cubic-bezier(.22,1,.36,1) .46s backwards;
  }

  /* Rows deal in behind the panel, capped at six: past that the last row
     would start visibly late, and a history long enough to scroll should not
     animate rows nobody has reached yet. */
  .whatsapp-notifications-page tbody tr{
    animation:wnRowIn .42s cubic-bezier(.22,1,.36,1) .5s backwards;
  }
  .whatsapp-notifications-page tbody tr:nth-child(2){animation-delay:.56s;}
  .whatsapp-notifications-page tbody tr:nth-child(3){animation-delay:.62s;}
  .whatsapp-notifications-page tbody tr:nth-child(4){animation-delay:.68s;}
  .whatsapp-notifications-page tbody tr:nth-child(5){animation-delay:.74s;}
  .whatsapp-notifications-page tbody tr:nth-child(n+6){animation-delay:.8s;}
}

/* ---------- Sent Today — motion, only while the pointer is on the card ----------
   At rest this card is a still picture. It used to drift, ping and sweep
   permanently, and this row sits at the top of the page the whole time the
   page is open — three loops running up there pulled the eye off the history
   table, which is the part anyone actually came to read. The motion now waits
   for the pointer, and says one thing while it has it: a message going out.

   Gated on a real pointer as well as on motion preference — the same pairing
   the hover lift further down uses — so a touch device never latches a
   half-finished send after a tap. Everything sits under the label/value/
   caption, which carry z-index 1.

   The rings and the sheen are declared inside the hover rule rather than
   parked at opacity 0 outside it: a pseudo-element that only exists on hover
   costs nothing at rest, and its animation starts on insertion, which is
   exactly the moment the pointer arrives.
   ============================================================ */
@media (prefers-reduced-motion: no-preference) and (hover:hover) and (pointer:fine){
  /* The artwork leans in and holds there for as long as the pointer stays.
     A transition rather than a keyframe animation, because this is a state
     and not a loop — and because it then eases back out on its own when the
     pointer leaves, where an animation would snap. */
  .whatsapp-notifications-page .wn-card-sent .wn-card-art{
    transition:transform .9s cubic-bezier(.22,1,.36,1);
  }
  .whatsapp-notifications-page .wn-card-sent:hover .wn-card-art{
    transform:scale(1.09) translate(-4px,-2px);
  }

  /* Two rings pushing out of the WhatsApp badge in the artwork, half a cycle
     apart, so there is always one on its way out — a message leaving, then
     the next. Placed in percentages so they stay on the badge as the card
     resizes, since the artwork is anchored right at the card's own height.

     The second one rides the artwork element rather than the card only
     because the card's own ::before is carrying the sheen. It sits inside the
     push above and so travels a few percent further, which happens to read as
     two rings on their way out rather than as one shape drawn twice. */
  .whatsapp-notifications-page .wn-card-sent:hover::after,
  .whatsapp-notifications-page .wn-card-sent:hover .wn-card-art::after{
    content:'';position:absolute;pointer-events:none;
    left:84%;top:21%;width:34px;height:34px;margin:-17px 0 0 -17px;
    border-radius:50%;
    border:2.5px solid rgba(150,255,200,.95);
    box-shadow:0 0 10px rgba(80,230,150,.5);
    opacity:0;
    animation:wnSentPing 1.8s cubic-bezier(.2,.6,.35,1) infinite;
  }
  .whatsapp-notifications-page .wn-card-sent:hover .wn-card-art::after{
    animation-delay:.9s;
  }
  /* Held near full opacity through the first third rather than fading from
     the very first frame: a thin ring easing out over a busy green artwork
     spends most of its cycle too faint to notice, which read as nothing
     happening at all. */
  @keyframes wnSentPing{
    0%{opacity:0;transform:scale(.45);}
    12%{opacity:.95;transform:scale(.72);}
    45%{opacity:.55;}
    75%,100%{opacity:0;transform:scale(2.1);}
  }

  /* The send itself: a light sweeps the card left to right, once per ring and
     in the direction the message is travelling. Same background-position
     technique as .fd-hero::before and .logo-shine.

     Plain alpha rather than the mix-blend-mode:overlay those two use: they
     sweep across light cream surfaces, where overlay brightens. Over this
     card's dark green it does almost nothing, and the sweep was invisible.

     No long tail on the keyframe any more. That tail existed to make the
     sweep an occasional glint on a card that never stopped animating; now the
     sweep only runs while it is being looked at, and a pause with nothing in
     it just reads as the animation having stopped. */
  .whatsapp-notifications-page .wn-card-sent:hover::before{
    content:'';position:absolute;inset:0;pointer-events:none;
    background:linear-gradient(100deg,
      transparent 38%,
      rgba(255,255,255,.07) 46%,
      rgba(255,255,255,.20) 50%,
      rgba(255,255,255,.07) 54%,
      transparent 62%);
    background-size:250% 100%;
    background-repeat:no-repeat;
    animation:wnSentSheen 1.8s ease-in-out infinite;
  }
  @keyframes wnSentSheen{
    0%      {background-position:-70% 0;}
    80%,100%{background-position:190% 0;}
  }

  /* And the message arrives. The glyphs after the caption run the whole life
     of a WhatsApp message in order — the send arrow it leaves on, the queued
     clock, one tick, two ticks, then two ticks blue — which is what turns the
     rings above from "something is moving" into "a message went out and was
     read".

     5.4s, exactly three ring cycles, so the two never drift apart: a ring goes
     out under each of the first three stages, and the pair of ticks lands
     behind the last of them. (It was 3.6s over two cycles before the arrow was
     added at the front; keeping that length would have meant four stages in
     the time three were already using, and the clock in particular went by too
     fast to read as a clock.) Each glyph gets its own keyframe on the same
     clock rather than one animation on the wrapper, because they are stacked
     and only one may be visible at a time. */
  .whatsapp-notifications-page .wn-card-sent:hover .wn-send-arrow{
    animation:wnTickArrow 5.4s ease-in-out infinite;
  }
  .whatsapp-notifications-page .wn-card-sent:hover .wn-send-queued{
    animation:wnTickQueued 5.4s ease-in-out infinite;
  }
  .whatsapp-notifications-page .wn-card-sent:hover .wn-send-single{
    animation:wnTickSingle 5.4s ease-in-out infinite;
  }
  .whatsapp-notifications-page .wn-card-sent:hover .wn-send-double{
    animation:wnTickDouble 5.4s ease-in-out infinite;
  }

  /* Each stage pops in from slightly small — the same beat WhatsApp's own
     status change has — holds, then cuts to the next. */
  /* The arrow alone gets a shove to the right as it goes, rather than only the
     pop-in the other three share — it is the one stage that depicts an action
     rather than a state, and a paper plane that sits perfectly still reads as
     a button waiting to be pressed. */
  @keyframes wnTickArrow{
    0%       {opacity:0;transform:translateX(-3px) scale(.7);}
    4%       {opacity:1;transform:translateX(0) scale(1);}
    18%      {opacity:1;transform:translateX(0) scale(1);}
    24%,100% {opacity:0;transform:translateX(5px) scale(.9);}
  }
  @keyframes wnTickQueued{
    0%,22%   {opacity:0;transform:scale(.7);}
    28%      {opacity:.9;transform:none;}
    40%      {opacity:.9;}
    46%,100% {opacity:0;}
  }
  @keyframes wnTickSingle{
    0%,44%   {opacity:0;transform:scale(.7);}
    50%      {opacity:.95;transform:none;}
    62%      {opacity:.95;}
    68%,100% {opacity:0;}
  }
  /* The last stage carries the colour change as well as the fade: white while
     it is merely delivered, WhatsApp's read blue once it has been seen. Held
     to the end of the cycle and faded out only on the final frames, so the
     blue is what the card rests on for the longest — hover it and the story
     finishes rather than looping through the middle forever. */
  @keyframes wnTickDouble{
    0%,66%   {opacity:0;transform:scale(.7);color:rgba(255,255,255,.92);}
    72%      {opacity:1;transform:none;color:rgba(255,255,255,.92);}
    86%      {opacity:1;color:rgba(255,255,255,.92);}
    92%      {opacity:1;color:#53bdeb;}
    97%      {opacity:1;color:#53bdeb;}
    100%     {opacity:0;color:#53bdeb;}
  }

  /* ---------- Pending — waiting, on hover ----------
     Sent's card depicts a journey, so it gets a sequence. This one depicts the
     absence of a journey, so it gets a loop that goes nowhere: a clock hand
     going round, and a slow ring off the clock in the artwork. Nothing here
     ever resolves, which is the point — the message has not moved.

     Timings deliberately unlike Sent's. Its beats are quick and locked to each
     other; these two are long and share no common multiple, so hovering the
     two cards in turn feels like two different states rather than the same
     animation in another colour. */

  /* The glyph fades up once and stays, rather than pulsing: it is a label for
     the state, and the motion belongs to the hand inside it. */
  .whatsapp-notifications-page .wn-card-pending:hover .wn-wait-clock{
    animation:wnWaitIn .35s ease-out forwards;
  }
  @keyframes wnWaitIn{
    from{opacity:0;transform:scale(.7);}
    to  {opacity:.95;transform:none;}
  }
  /* The arc runs the circuit; the dotted track and the hands hold still. Only
     the arc carries an origin, because only the arc turns — and it is given in
     user units against the SVG's own view box (9,7 is the circle's centre),
     since a percentage origin would resolve against the element's bounding box
     rather than the drawing.
     Linear and a full turn: an eased sweep reads as a gauge settling on a
     value, where this one should never arrive anywhere. 2.2s is the reference's
     own unhurried pace — fast enough to be plainly moving, slow enough that it
     is still a wait and not a loading throbber. */
  .whatsapp-notifications-page .wn-card-pending .wn-wait-arc{
    transform-origin:9px 7px;
  }
  .whatsapp-notifications-page .wn-card-pending:hover .wn-wait-arc{
    animation:wnWaitSweep 2.2s linear infinite;
  }
  @keyframes wnWaitSweep{
    from{transform:rotate(0deg);}
    to  {transform:rotate(360deg);}
  }

  /* A ring off the clock in the card's own artwork, echoing the glyph. Cool
     blue-white rather than Sent's green — it belongs to the clock in this
     comp, not to the WhatsApp mark behind it — and far slower than Sent's
     rings, which are a message leaving. This one is just a minute passing.
     Declared inside the hover rule so it costs nothing at rest, the same way
     Sent's are. */
  .whatsapp-notifications-page .wn-card-pending:hover::after{
    content:'';position:absolute;pointer-events:none;
    left:85%;top:23%;width:38px;height:38px;margin:-19px 0 0 -19px;
    border-radius:50%;
    border:2px solid rgba(198,222,255,.85);
    box-shadow:0 0 12px rgba(150,190,255,.45);
    opacity:0;
    animation:wnPendingWait 4.2s cubic-bezier(.35,.7,.4,1) infinite;
  }
  /* Swells out of the clock, lingers half-lit across the middle, then fades.
     The long dead stretch at the end is deliberate: a ring that restarts
     immediately reads as a pulse monitor rather than as waiting. */
  @keyframes wnPendingWait{
    0%      {opacity:0;transform:scale(.62);}
    18%     {opacity:.85;transform:scale(.86);}
    55%     {opacity:.5;transform:scale(1.28);}
    85%,100%{opacity:0;transform:scale(1.6);}
  }

  /* ---------- Failed — the message that would not go ----------
     An alert, so it is the one card in the row that moves sharply. The glyph
     shudders and the ring behind it flashes twice; everything else on this row
     eases. Both run on the same 1.6s clock so the shudder lands on the first
     flash rather than drifting across it. */
  .whatsapp-notifications-page .wn-card-failed:hover .wn-fail-status{
    animation:wnGlyphIn .3s ease-out forwards;
  }
  /* A shudder, then nothing for most of the cycle. A glyph that shakes without
     pause reads as broken rendering rather than as an error. */
  .whatsapp-notifications-page .wn-card-failed:hover .wn-fail-glyph{
    animation:wnFailShake 1.6s ease-in-out infinite;
  }
  @keyframes wnFailShake{
    0%,14%,100%{transform:translateX(0);}
    3%         {transform:translateX(-1.6px);}
    6%         {transform:translateX(1.6px);}
    9%         {transform:translateX(-1px);}
    12%        {transform:translateX(.6px);}
  }
  /* On the warning triangle in the card's own artwork. Two flashes rather than
     one swell — a single expanding ring is the vocabulary Sent and Pending
     already use for a message travelling and for time passing, and Failed
     needs to read as neither. */
  .whatsapp-notifications-page .wn-card-failed:hover::after{
    content:'';position:absolute;pointer-events:none;
    left:84%;top:21%;width:36px;height:36px;margin:-18px 0 0 -18px;
    border-radius:50%;
    border:2px solid rgba(255,178,178,.9);
    box-shadow:0 0 12px rgba(255,90,90,.45);
    opacity:0;
    animation:wnFailAlert 1.6s ease-out infinite;
  }
  @keyframes wnFailAlert{
    0%      {opacity:0;transform:scale(.72);}
    8%      {opacity:.9;transform:scale(1);}
    20%     {opacity:.12;transform:scale(1.06);}
    30%     {opacity:.85;transform:scale(1.12);}
    58%,100%{opacity:0;transform:scale(1.4);}
  }

  /* ---------- Opted Out / Skipped — the bar comes down ----------
     The only glyph in the row that depicts a decision rather than a state, so
     it is drawn on rather than simply shown: the circle sits empty, the bar
     strikes through it, it holds, then it retracts and the circle is empty
     again. Slow, and the one animation here with no glow, no shake and no
     travel — this is the quiet outcome, and it should look like one.
     No ring on the artwork either, unlike Failed and the two dark cards: this
     is a light card, where a lit halo reads as a smudge rather than a signal. */
  .whatsapp-notifications-page .wn-card-skipped:hover .wn-skip-status{
    animation:wnGlyphIn .3s ease-out forwards;
  }
  .whatsapp-notifications-page .wn-card-skipped:hover .wn-skip-slash{
    animation:wnSkipStrike 2.6s ease-in-out infinite;
  }
  @keyframes wnSkipStrike{
    0%,8%   {stroke-dashoffset:10.75;}
    32%     {stroke-dashoffset:0;}
    76%     {stroke-dashoffset:0;}
    96%,100%{stroke-dashoffset:10.75;}
  }

  /* Shared by both wrappers above: they only ever fade up and stay, because
     the character of each card lives in the glyph inside them. */
  @keyframes wnGlyphIn{
    from{opacity:0;}
    to  {opacity:1;}
  }

  /* ---------- Scheduled — the alarm goes off on hover ----------
     The glyph shakes, not the badge it sits in. Rotating .wn-card-icon would
     turn the disc as well, and that disc carries an inset highlight along its
     top edge — a lit rim that swings round to the side reads as the badge
     coming loose, where the whole point is a clock rattling inside a fixed
     ring. The circle itself would show nothing anyway, being a circle.

     Pivot below centre, at 50%/70%: an alarm clock rings on its feet, so it
     rocks about its base rather than about its middle.

     A burst then a rest, rather than one continuous wobble: a real alarm rings
     in bursts, and a glyph that never stops shaking stops reading as an alarm
     within about two seconds. The swing decays across the burst — 14deg down
     to 3 — so it settles rather than being cut off mid-rattle. */
  .whatsapp-notifications-page .wn-card-scheduled:hover .wn-card-icon svg{
    transform-origin:50% 70%;
    animation:wnAlarmRing 1.25s ease-in-out infinite;
  }
  @keyframes wnAlarmRing{
    0%      {transform:rotate(0);}
    4%      {transform:rotate(-14deg);}
    10%     {transform:rotate(12deg);}
    16%     {transform:rotate(-11deg);}
    22%     {transform:rotate(9deg);}
    28%     {transform:rotate(-7deg);}
    34%     {transform:rotate(5deg);}
    40%     {transform:rotate(-3deg);}
    46%,100%{transform:rotate(0);}
  }
  /* The sound of it. Hung off the badge itself rather than positioned against
     the card, because unlike the three dark cards' rings this badge is a real
     element with a known box — inset:-6px puts the ring just outside its rim
     with no percentages to keep in sync.
     Gold at low alpha: this card is cream, and the white-hot rings the dark
     cards use would burn a hole in it. 2.5s against the shake's 1.25s, so a
     ring leaves on every second burst rather than on every one — an alarm that
     is heard across the room, not one going off twice a second. */
  .whatsapp-notifications-page .wn-card-scheduled:hover .wn-card-icon::after{
    content:'';position:absolute;inset:-6px;pointer-events:none;
    border-radius:50%;
    border:2px solid rgba(200,153,46,.5);
    opacity:0;
    animation:wnSchedHalo 2.5s ease-out infinite;
  }
  @keyframes wnSchedHalo{
    0%      {opacity:0;transform:scale(.75);}
    12%     {opacity:.8;transform:scale(1);}
    60%,100%{opacity:0;transform:scale(1.55);}
  }
}

/* Hover lift. Real pointers only, the convention used throughout this file,
   so a touch device never latches the raised state after a tap. The
   transform/box-shadow ride .wn-card's own transition above. */
@media (hover:hover) and (pointer:fine){
  .whatsapp-notifications-page .wn-card:hover{transform:translateY(-3px);}
  .whatsapp-notifications-page .wn-card-sent:hover,
  .whatsapp-notifications-page .wn-card-pending:hover,
  .whatsapp-notifications-page .wn-card-failed:hover{
    box-shadow:inset 0 1px 0 rgba(255,255,255,.14),
               0 16px 30px -16px rgba(10,20,35,.9);
  }
  .whatsapp-notifications-page .wn-card-scheduled:hover,
  .whatsapp-notifications-page .wn-card-skipped:hover{
    box-shadow:var(--shadow-md, 0 14px 26px -16px rgba(20,39,70,.5));
  }
}

/* Opted Out / Skipped — its comp's artwork over a warm card. The comp is
   cream rather than white, but a fixed cream would be a lit slab in dark
   mode, so the tint is a gold wash laid over var(--card): white reads as
   cream in light, and the card colour reads as warm-dark in dark. */
.whatsapp-notifications-page .wn-card-skipped{
  /* No scrim over the artwork here, unlike the four cards above: its own
     left edge is already feathered, and a scrim would have to be painted in
     the composite of the wash and the card to avoid laying flat white back
     over the tint. Letting the feather do the blending keeps one source of
     truth for the edge. */
  /* Pushed a few pixels past the right edge. The crop's own rounded corner
     was trimmed off, which left the artwork stopping just short of the card
     and showing a pale vertical strip down the last few pixels; overhanging
     it lets the card's own radius do the clipping instead. */
  background:
    url('../assets/wn-skipped-art.png') right -7px center / auto 100% no-repeat,
    linear-gradient(155deg, rgba(200,153,46,.10) 0%, rgba(200,153,46,.20) 100%),
    var(--card);
  border:1px solid var(--border);
}
.whatsapp-notifications-page .wn-card-skipped .wn-card-label{color:var(--text-400);}
.whatsapp-notifications-page .wn-card-skipped .wn-card-value{color:var(--text-900);}
.whatsapp-notifications-page .wn-card-skipped .wn-card-caption{color:var(--text-600);}
.whatsapp-notifications-page .wn-filter-bar{
  display:flex;gap:10px;flex-wrap:wrap;margin-bottom:16px;
}
.whatsapp-notifications-page .wn-filter-bar input,
.whatsapp-notifications-page .wn-filter-bar select{
  padding:8px 10px;border:1px solid var(--border);border-radius:8px;
  background:var(--card);color:var(--text-900);font-size:13.2px;font-family:inherit;
  transition:border-color .12s ease, box-shadow .12s ease;
}
.whatsapp-notifications-page .wn-filter-bar input:focus,
.whatsapp-notifications-page .wn-filter-bar select:focus{
  border-color:var(--gold-500);box-shadow:0 0 0 3px rgba(200,153,46,.18);outline:none;
}

/* ============================================================
   Business Details (Family Capital) — businessDetails.js
   Hub banner · one card per ownership style · record table.

   Ported from Design/business-details-overview.html. Only the bd-*
   classes are defined here: the page's table, pills, buttons and card
   chrome are the app's own shared classes, so it inherits every future
   change to them rather than carrying a second copy.
   ============================================================ */

/* ---------- Hub banner ----------
   Deliberately dark in BOTH themes. The artwork is gold-on-navy;
   recolouring it for light mode would turn a lit scene into pale
   smudges on white. */
.bd-hero{
  position:relative;overflow:hidden;
  background:linear-gradient(135deg,#0d1e3a 0%,#0a1322 100%);
  border:1px solid rgba(212,175,55,.24);border-radius:16px;
  box-shadow:0 10px 30px rgba(0,0,0,.32);
  margin-bottom:18px;min-height:232px;display:flex;align-items:center;
}
/* Sized past the banner on purpose: the scene is close to square while the
   banner is wide and short, so at height:100% the briefcase renders small.
   Overhanging 11% top and bottom crops only the empty sky and floor, which
   makes the subject bigger without losing the arrowhead or the rings.

   The left edge is faded with mask-image so the scene dissolves into the
   banner rather than ending on a hard vertical cut. */
.bd-hero-art{
  position:absolute;top:-11%;right:0;height:122%;width:auto;max-width:64%;
  object-fit:cover;object-position:left center;pointer-events:none;user-select:none;
  -webkit-mask-image:linear-gradient(to right, transparent 0%, #000 26%);
          mask-image:linear-gradient(to right, transparent 0%, #000 26%);
}
.bd-hero-inner{position:relative;z-index:1;display:flex;align-items:center;gap:20px;padding:22px 26px;max-width:60%;}
/* Enlarged from 58px. At that size the hub icon read as a small bullet beside
   a three-line block of text, and the banner is 232px tall, so the room was
   already there. The glow widens with it - a ring half again as large under
   the same 25px glow looks unlit next to the old one. */
.bd-hero-ring{
  width:86px;height:86px;border-radius:50%;flex:none;
  display:flex;align-items:center;justify-content:center;
  border:1px solid #e6ca65;
  background:radial-gradient(circle, rgba(212,175,55,.16) 0%, transparent 70%);
  box-shadow:0 0 34px rgba(230,202,101,.42);
}
.bd-hero-ring svg{width:38px;height:38px;fill:#e6ca65;display:block;}
/* BD_HUB_ICON is full-colour art (assets/icons/business-details-hub.png), not
   a tintable glyph, so it's sized larger than the old svg to read clearly. */
.bd-hero-ring img{width:60px;height:60px;object-fit:contain;display:block;}
/* Below 620px the artwork is hidden and the text takes the full width, where
   a ring this size starts eating the row rather than introducing it. */
@media(max-width:620px){
  .bd-hero-ring{width:64px;height:64px;box-shadow:0 0 26px rgba(230,202,101,.4);}
  .bd-hero-ring svg{width:28px;height:28px;}
  .bd-hero-ring img{width:44px;height:44px;}
}
.bd-hero-text{flex:1;min-width:230px;}
.bd-hero-title{
  margin:0;font-size:21px;font-weight:700;letter-spacing:.3px;color:#f1f5f9;
  font-family:Georgia,'Times New Roman',serif;text-shadow:0 1px 10px rgba(10,19,34,.8);
}
.bd-hero-sub{margin:6px 0 0;font-size:12.6px;line-height:1.45;color:#9aa8bd;max-width:520px;text-shadow:0 1px 10px rgba(10,19,34,.8);}
/* Under the description, not opposite it: the artwork owns the right of the
   banner, and figures over a lit skyline are unreadable however much shadow
   sits behind them. */
.bd-hero-stats{display:flex;gap:22px;flex-wrap:wrap;margin-top:12px;}
.bd-hero-stat-value{font-size:21px;font-weight:800;color:#e6ca65;line-height:1.1;text-shadow:0 1px 8px rgba(10,19,34,.95);}
.bd-hero-stat-label{font-size:10px;letter-spacing:.9px;text-transform:uppercase;color:#a6b2c4;margin-top:2px;text-shadow:0 1px 8px rgba(10,19,34,.95);}
@media(max-width:980px){
  .bd-hero-inner{max-width:100%;}
  .bd-hero-art{max-width:100%;opacity:.28;}
  .bd-hero-stats{width:100%;gap:20px;}
}
@media(max-width:620px){ .bd-hero-art{display:none;} }

/* ---------- One card per ownership style ---------- */
.bd-entity-grid{display:grid;grid-template-columns:repeat(5,1fr);gap:16px;margin-bottom:18px;}
@media(max-width:1240px){.bd-entity-grid{grid-template-columns:repeat(3,1fr);}}
@media(max-width:780px){.bd-entity-grid{grid-template-columns:repeat(2,1fr);}}
@media(max-width:520px){.bd-entity-grid{grid-template-columns:1fr;}}

.bd-entity-card{
  background:var(--card);border:1px solid var(--border);border-radius:14px;
  padding:22px 16px 14px;box-shadow:var(--shadow);
  display:flex;flex-direction:column;align-items:center;text-align:center;
}
.bd-entity-card:hover{border-color:var(--gold-500);}
/* The card currently driving the table filter — a border alone reads too
   close to :hover, so this also warms the fill. */
.bd-entity-card-on{
  border-color:var(--gold-500);
  box-shadow:0 0 0 1px var(--gold-500) inset, var(--shadow);
  background:linear-gradient(180deg,var(--gold-bg),var(--card));
}
/* Enlarged from 64px so the artwork leads the card rather than captioning it -
   these five are the page's entry points, and at the old size the icon was
   smaller than the title beneath it. position:relative is what lets the
   travelling highlight below anchor to the ring. */
.bd-entity-ring{
  position:relative;
  width:92px;height:92px;border-radius:50%;margin-bottom:14px;flex:none;
  display:flex;align-items:center;justify-content:center;
  border:1px solid rgba(200,153,46,.4);
  background:radial-gradient(circle, rgba(200,153,46,.12) 0%, transparent 70%);
}
/* A highlight travels once around the ring, then waits - the same mechanism
   the Wealth Dashboard's member portraits use (fam-ring-sweep), so the two
   read as one idea rather than two similar ones. Gold rather than the
   dashboard's white: these rings sit on a light card, where white would be
   invisible for the whole lap.
   inset:-2px puts the band ON the border rather than inside the fill, and the
   radial mask keeps it to the outer 4px so it lights the rim and not the
   artwork. */
.bd-entity-ring::after{
  content:"";position:absolute;inset:-2px;border-radius:50%;
  pointer-events:none;
  background:conic-gradient(from 0deg, transparent 0deg, transparent 296deg, var(--gold-400) 342deg, transparent 360deg);
  -webkit-mask:radial-gradient(farthest-side, transparent calc(100% - 4px), #000 calc(100% - 4px));
          mask:radial-gradient(farthest-side, transparent calc(100% - 4px), #000 calc(100% - 4px));
  opacity:0;
  animation:fam-ring-sweep 15s linear infinite;
}
/* Staggered so the five do not glint in unison, which reads as a machine
   rather than a flourish - same reasoning as the member portraits. */
.bd-entity-card:nth-child(2) .bd-entity-ring::after{animation-delay:.6s;}
.bd-entity-card:nth-child(3) .bd-entity-ring::after{animation-delay:1.2s;}
.bd-entity-card:nth-child(4) .bd-entity-ring::after{animation-delay:1.8s;}
.bd-entity-card:nth-child(5) .bd-entity-ring::after{animation-delay:2.4s;}
/* Point at a card and its highlight stops waiting between laps: it swaps to a
   continuous fast circuit for as long as the cursor is on the card, then drops
   back to the every-15-seconds glint. Merely shortening fam-ring-sweep would
   speed the lap up but keep its idle gap, which reads as blinking rather than
   spinning - so hover runs its own gapless rotation, with the delay zeroed so
   it starts the instant it is pointed at instead of finishing someone else's
   stagger. Keyed off the whole card, not the ring: the card is the thing the
   reader is aiming at, and a 92px target inside a 230px card is a mean one. */
@media (hover:hover) and (pointer:fine){
  .bd-entity-card:hover .bd-entity-ring::after{
    animation:fam-ring-spin 1.4s linear infinite;
    animation-delay:0s;
    opacity:1;
  }
}
@media (prefers-reduced-motion: reduce){
  .bd-entity-ring::after,
  .bd-entity-card:hover .bd-entity-ring::after{animation:none;opacity:0;}
}
/* 30, not 26: the handshake and the shield-and-document need the extra
   pixels to keep their internal shapes apart. */
.bd-entity-ring svg{width:42px;height:42px;fill:var(--gold-500);display:block;}
/* BD_ICONS is full-colour art (assets/icons/business-*.png), not a tintable
   glyph, so it's sized larger than the old svg to read clearly and isn't
   recoloured on hover the way .bd-entity-ring svg is below. */
.bd-entity-ring img{width:64px;height:64px;object-fit:contain;display:block;}
.bd-entity-title{font-size:14.6px;font-weight:700;color:var(--text-900);margin-bottom:10px;}
.bd-entity-pill{
  display:inline-flex;align-items:center;gap:5px;
  background:var(--subtle-bg);border:1px solid var(--border);border-radius:20px;
  padding:3px 11px;font-size:11.2px;color:var(--text-600);margin-bottom:10px;
}
.bd-entity-desc{font-size:11.8px;color:var(--text-400);line-height:1.45;min-height:34px;margin-bottom:14px;}
.bd-entity-actions{
  display:flex;align-items:center;justify-content:space-between;gap:8px;width:100%;
  margin-top:auto;padding-top:12px;border-top:1px solid var(--border);
}
.bd-entity-link{
  background:none;border:none;padding:0;cursor:pointer;font-family:inherit;
  font-size:11.8px;font-weight:600;color:var(--text-600);
  display:inline-flex;align-items:center;gap:3px;
}
.bd-entity-link:hover{color:var(--gold-600);}
.bd-entity-chev{font-size:14px;line-height:1;}

/* ---------- Record table controls ---------- */
.bd-search,.bd-filter{
  padding:8px 12px;border:1px solid var(--border);border-radius:8px;
  font-size:13px;font-family:inherit;background:var(--card);color:var(--text-900);
}
.bd-search{min-width:230px;}
.bd-filter{min-width:150px;cursor:pointer;}
.bd-search:hover,.bd-filter:hover{border-color:var(--gold-400);}
.bd-search:focus,.bd-filter:focus{outline:none;border-color:var(--gold-500);box-shadow:0 0 0 3px rgba(200,153,46,.18);}
@media(max-width:900px){ .bd-search,.bd-filter{min-width:0;flex:1;} }

.bd-biz-name{font-weight:600;}
.bd-biz-sub{font-size:11.6px;color:var(--text-400);margin-top:2px;}

/* ============================================================
   Business Details — MOTION

   Gated on prefers-reduced-motion: no-preference rather than overridden
   afterwards in a `reduce` block: the entity glyphs sit inside SVGs whose
   layout depends on their own attributes, and a blanket
   `transform:none !important` is a blunt instrument next to simply not
   writing the rule.
   ============================================================ */
@media (prefers-reduced-motion: no-preference){

  /* The scene drifts in from the right, then breathes. It is a flat render,
     so the movement comes from the element — slow, and small enough that the
     skyline never visibly slides against its own reflection. */
  .bd-hero-art{
    animation:bdArtIn 1.1s cubic-bezier(.16,1,.3,1) both,
              bdArtFloat 9s ease-in-out 1.1s infinite alternate;
  }
  @keyframes bdArtIn{
    from{opacity:0;transform:translateX(34px) scale(1.03);}
    to{opacity:1;transform:translateX(0) scale(1);}
  }
  @keyframes bdArtFloat{
    from{transform:translateY(0) scale(1);}
    to{transform:translateY(-7px) scale(1.012);}
  }

  /* A soft diagonal highlight glides across the whole banner on a loop —
     same background-position sweep + mix-blend-mode:overlay technique as
     .logo-shine, so it glints off the gold ring/title and the art beneath
     without ever flattening them to flat white. z-index:2 keeps it above
     .bd-hero-inner (1) and the unlayered .bd-hero-art so it reads as a
     glaze over the finished card rather than being masked by either. */
  .bd-hero::before{
    content:'';
    position:absolute;inset:0;z-index:2;
    pointer-events:none;
    background:linear-gradient(100deg,
      transparent 40%,
      rgba(255,255,255,.16) 47%,
      rgba(255,255,255,.55) 50%,
      rgba(255,255,255,.16) 53%,
      transparent 60%);
    background-size:260% 100%;
    background-repeat:no-repeat;
    background-position:-80% 0;
    mix-blend-mode:overlay;
    animation:bdHeroShine 6s ease-in-out infinite;
  }
  @keyframes bdHeroShine{
    0%       {background-position:-80% 0;}
    55%,100% {background-position:180% 0;}
  }

  /* The ring breathes rather than blinks — the glow only, never the size, so
     it cannot nudge the banner's layout. */
  .bd-hero-ring{animation:bdRingPulse 3s ease-in-out infinite alternate;}
  @keyframes bdRingPulse{
    from{box-shadow:0 0 15px rgba(212,175,55,.3);}
    to{box-shadow:0 0 35px rgba(212,175,55,.65);}
  }
  .bd-hero-title{animation:bdRise .7s cubic-bezier(.16,1,.3,1) both;}
  .bd-hero-sub{animation:bdRise .7s cubic-bezier(.16,1,.3,1) .09s both;}
  .bd-hero-stats{animation:bdRise .7s cubic-bezier(.16,1,.3,1) .18s both;}
  @keyframes bdRise{from{opacity:0;transform:translateY(14px);}to{opacity:1;transform:none;}}

  /* The five cards deal out left to right on entry. Unlike the reference's
     JS stagger this is pure CSS keyed off nth-child, so it survives the
     partial re-render that a filter change performs. */
  .bd-entity-card{animation:bdRise .55s cubic-bezier(.16,1,.3,1) both;}
  .bd-entity-card:nth-child(2){animation-delay:.07s;}
  .bd-entity-card:nth-child(3){animation-delay:.14s;}
  .bd-entity-card:nth-child(4){animation-delay:.21s;}
  .bd-entity-card:nth-child(5){animation-delay:.28s;}

  .bd-entity-card{
    transition:transform .35s cubic-bezier(.165,.84,.44,1),
               box-shadow .35s ease-out, border-color .35s ease-out, background-color .35s ease-out;
  }
  .bd-entity-ring{transition:transform .3s cubic-bezier(.34,1.56,.64,1), box-shadow .3s ease-out, border-color .3s ease-out;}
  .bd-entity-ring svg{transition:fill .3s ease-out;}
  .bd-entity-chev{transition:transform .25s cubic-bezier(.34,1.56,.64,1);}
  .bd-entity-pill{transition:border-color .3s ease-out, background-color .3s ease-out;}
  .bd-search,.bd-filter{transition:border-color .2s ease-out, box-shadow .2s ease-out, transform .2s cubic-bezier(.16,1,.3,1);}
}

/* Hover responses, on real pointers only — the convention used elsewhere in
   this stylesheet, so a touch device never latches a hover state after a tap.
   Kept out of the motion block above because the colour changes here are
   still worth having when a visitor has asked for no motion; only the
   transitions that carry them are gated. */
@media (hover:hover) and (pointer:fine){
  .bd-entity-card:hover{transform:translateY(-8px);box-shadow:0 16px 30px -12px rgba(200,153,46,.5), var(--shadow);}
  .bd-entity-card:hover .bd-entity-ring{
    transform:scale(1.08);border-color:var(--gold-500);box-shadow:0 0 20px rgba(200,153,46,.42);
  }
  .bd-entity-card:hover .bd-entity-ring svg{fill:var(--gold-400);}
  .bd-entity-link:hover .bd-entity-chev{transform:translateX(3px);}
  .bd-entity-card:hover .bd-entity-pill{border-color:var(--gold-500);}
  /* The search and filter controls lift a hair so the whole control bar
     answers the cursor, not just the cards below it. */
  .bd-search:hover,.bd-filter:hover{transform:translateY(-1px);}
  .business-details-page tbody tr{transition:background-color .18s ease-out;}
  .business-details-page tbody tr:hover{background:var(--subtle-bg);}
}

/* ============================================================
   Bank Locker — vault hero (bankLocker.js renderLanding())
   Deliberately dark in BOTH themes, same rationale as .bd-hero /
   .tk-hero: gold-on-navy vault art would wash out to pale smudges on a
   white light-mode surface. No video asset exists for this page (unlike
   Travel Kit's .tk-hero-video), so the motion comes from a pure-CSS
   animated safe/vault icon instead, adapted from the Bank-Locker mockup's
   own .safe/.safe-body/.safe-door/.safe-wheel/.safe-platform/.sparkle
   shapes (renamed bl-* here, collision-free with the mockup's own names).
   ============================================================ */
.bl-hero{
  position:relative;overflow:hidden;
  background:radial-gradient(120% 120% at 20% 0%, rgba(212,175,55,.10) 0%, transparent 60%), linear-gradient(180deg,#0d1526 0%,#0a1220 100%);
  border:1px solid rgba(212,175,55,.24);border-radius:16px;
  box-shadow:0 10px 30px rgba(0,0,0,.32);
  margin-bottom:18px;
}
.bl-hero-inner{
  position:relative;z-index:1;display:flex;align-items:center;gap:26px;
  padding:26px 28px;flex-wrap:wrap;
}
.bl-safe-wrap{
  position:relative;flex:none;width:150px;height:130px;
  display:flex;align-items:center;justify-content:center;
}
.bl-safe-glow{
  position:absolute;bottom:6px;left:50%;transform:translateX(-50%);
  width:130px;height:38px;border-radius:50%;
  background:radial-gradient(ellipse at center, rgba(212,175,55,.38) 0%, rgba(212,175,55,.10) 45%, transparent 75%);
}
.bl-safe{
  position:relative;width:96px;height:82px;
  filter:drop-shadow(0 14px 22px rgba(0,0,0,.55));
}
.bl-safe-body{
  position:absolute;inset:0;border-radius:12px;
  background:linear-gradient(180deg,#F6E27A 0%,#D4AF37 22%,#B8962E 52%,#8A6E1F 100%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.7), inset 0 -8px 16px rgba(0,0,0,.28);
}
.bl-safe-door{
  position:absolute;left:5px;right:5px;top:5px;bottom:11px;border-radius:9px;
  background:linear-gradient(135deg,#F8E89A,#C9A030);
  border:1px solid rgba(0,0,0,.15);
  display:flex;align-items:center;justify-content:center;
}
.bl-safe-wheel{
  width:36px;height:36px;border-radius:50%;border:3px solid #5A4510;
  background:radial-gradient(circle at 30% 30%,#F6E27A,#8A6E1F);
  box-shadow:0 2px 8px rgba(0,0,0,.4);
  display:flex;align-items:center;justify-content:center;
}
.bl-safe-wheel::before{content:'◍';font-size:17px;color:#2A1F00;}
.bl-sparkle{position:absolute;font-size:12px;color:#F6E27A;}
.bl-hero-text{min-width:230px;flex:1;}
.bl-hero-title{
  margin:0;font-size:23px;font-weight:700;letter-spacing:.3px;color:#f1f5f9;
  font-family:Georgia,'Times New Roman',serif;text-shadow:0 1px 10px rgba(10,19,34,.8);
}
.bl-hero-sub{margin:6px 0 0;font-size:12.8px;line-height:1.5;color:#9aa8bd;max-width:480px;text-shadow:0 1px 10px rgba(10,19,34,.8);}
.bl-hero-badge{
  display:inline-flex;align-items:center;gap:6px;margin-top:12px;
  background:rgba(212,175,55,.10);border:1px solid rgba(212,175,55,.28);
  border-radius:999px;padding:5px 13px;font-size:11.4px;font-weight:600;color:#E9D49A;
}
@media(max-width:980px){
  .bl-hero-inner{padding:22px 20px;}
}
/* Same treatment as .tk-hero-art/.bd-hero-art at their own narrow
   breakpoints: on very narrow screens the decorative icon is dropped
   rather than squeezed, so title/tagline/badge keep full width. */
@media(max-width:620px){
  .bl-safe-wrap{display:none;}
}

/* ---------- Bank Locker hero motion ---------- */
@media (prefers-reduced-motion: no-preference){
  .bl-safe{animation:bl-float 3s ease-in-out infinite;}
  @keyframes bl-float{0%,100%{transform:translateY(0);}50%{transform:translateY(-10px);}}
  .bl-safe-glow{animation:bl-glow-pulse 2.8s ease-in-out infinite;}
  @keyframes bl-glow-pulse{0%,100%{opacity:.55;transform:translateX(-50%) scale(1);}50%{opacity:1;transform:translateX(-50%) scale(1.08);}}
  .bl-sparkle{animation:bl-twinkle 2.2s ease-in-out infinite;}
  @keyframes bl-twinkle{0%,100%{opacity:.15;transform:scale(.8) rotate(0deg);}50%{opacity:1;transform:scale(1.2) rotate(180deg);}}
}
@media (prefers-reduced-motion: reduce){
  .bl-safe,.bl-safe-glow,.bl-sparkle{animation:none;}
}

/* ---------- Bank Locker stat cards (per reference screenshot "Bank
   Locker.png") ----------
   Deliberately dark/fixed-colour like .bl-hero above, NOT theme-adaptive
   pastel .stat-card accents — the reference's per-card colours are too
   saturated to double as a light-mode tint. Six distinct hues (c1-c6)
   rather than forced into 4 buckets, sampled directly from the reference
   image rather than guessed. */
.bl-stat-card{
  position:relative;display:flex;align-items:center;gap:14px;
  padding:18px 20px;border-radius:16px;overflow:hidden;
  border:1px solid rgba(255,255,255,.07);
  box-shadow:0 6px 18px rgba(0,0,0,.28);
}
.bl-stat-icon{
  flex:none;width:52px;height:52px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;font-size:22px;
}
.bl-stat-label{font-size:10.8px;letter-spacing:.09em;text-transform:uppercase;font-weight:700;color:rgba(255,255,255,.62);}
.bl-stat-value{font-size:27px;font-weight:800;color:#fff;margin-top:4px;line-height:1.1;}
.bl-stat-sub{font-size:11.4px;color:rgba(255,255,255,.4);margin-top:3px;}
.bl-stat-card.c1{background:linear-gradient(135deg,#0d3868 0%,#05264d 100%);}
.bl-stat-card.c2{background:linear-gradient(135deg,#0f5548 0%,#0b3645 100%);}
.bl-stat-card.c3{background:linear-gradient(135deg,#352e63 0%,#27234d 100%);}
.bl-stat-card.c4{background:linear-gradient(135deg,#5a4530 0%,#3e3025 100%);}
.bl-stat-card.c5{background:linear-gradient(135deg,#12336b 0%,#0b2150 100%);}
.bl-stat-card.c6{background:linear-gradient(135deg,#0d4562 0%,#052e4a 100%);}
.bl-stat-icon.c1{background:radial-gradient(circle at 35% 30%, #2f7ecb, #12447a);box-shadow:0 0 16px rgba(30,120,200,.45);}
.bl-stat-icon.c2{background:radial-gradient(circle at 35% 30%, #17b894, #06715f);box-shadow:0 0 16px rgba(20,180,140,.45);}
.bl-stat-icon.c3{background:radial-gradient(circle at 35% 30%, #8a6ee0, #503693);box-shadow:0 0 16px rgba(140,110,220,.45);}
.bl-stat-icon.c4{background:radial-gradient(circle at 35% 30%, #d9a03f, #7d592c);box-shadow:0 0 16px rgba(210,160,60,.45);}
.bl-stat-icon.c5{background:radial-gradient(circle at 35% 30%, #3f79d6, #0f3d72);box-shadow:0 0 16px rgba(60,120,210,.45);}
.bl-stat-icon.c6{background:radial-gradient(circle at 35% 30%, #2896c4, #104966);box-shadow:0 0 16px rgba(40,150,195,.45);}
@media (hover:hover) and (pointer:fine){
  .bl-stat-card{transition:transform .2s ease-out, box-shadow .2s ease-out;}
  .bl-stat-card:hover{transform:translateY(-2px);box-shadow:0 10px 24px rgba(0,0,0,.38);}
}

/* ---------- Bank Locker vault panel ----------
   Wraps the (unchanged, theme-adaptive) toolbar + locker list in the same
   gold framing as .bl-hero for visual continuity, without forcing the
   panel's own background — or the real search/select/button controls
   inside it — into a fixed dark palette, which would need every one of
   those controls' theme-token styling redone too. */
.bl-vault-panel{border-color:rgba(212,175,55,.35) !important;box-shadow:0 4px 22px rgba(212,175,55,.07), var(--shadow);}

/* ---------- Bank Locker empty state ----------
   A bigger version of the .bl-hero's safe icon (via the .lg modifiers
   below) plus a couple of small decorative shield/leaf glyphs scattered
   around it, matching the reference's illustration. Sits on the vault
   panel's own theme-adaptive background, so — unlike the hero/stat cards
   above — its title/sub text stay on the normal var(--text-*) tokens
   rather than fixed white, or they'd vanish on a light-mode panel. */
/* grid-column:1/-1 - this sits as the sole child of the locker list's
   .grid.grid-3 (bankLocker.js renderLanding()) when there are zero
   lockers, so without spanning every column it would be squeezed into
   just the first third of the row instead of centering across the full
   panel width. */
.bl-empty-vault{grid-column:1 / -1;text-align:center;padding:38px 20px 34px;}
.bl-safe-wrap.lg{width:230px;height:190px;margin:0 auto;}
.bl-safe.lg{width:150px;height:128px;}
.bl-safe-wrap.lg .bl-safe-glow{width:200px;height:56px;}
.bl-safe-wrap.lg .bl-safe-wheel{width:56px;height:56px;}
.bl-safe-wrap.lg .bl-safe-wheel::before{font-size:26px;}
.bl-safe-wrap.lg .bl-sparkle{font-size:17px;}
.bl-empty-deco{position:absolute;font-size:20px;opacity:.55;}
.bl-empty-title{font-size:17px;font-weight:700;color:var(--text-900);margin-top:14px;}
.bl-empty-sub{font-size:12.8px;color:var(--text-400);margin-top:4px;}

/* ============================================================
   Vault-page hero + footer — shared by Archived Vault
   (archivedVault.js) and Document Vault (documents.js), per their own
   reference screenshots ("Archived_Vault.png"/"Document_Vault.png").
   Unlike .bl-hero/.tk-hero/.bd-hero (deliberately dark in both themes),
   these references are a light cream/gold "heirloom paper" look, so this
   is deliberately LIGHT in both themes instead — same reasoning, opposite
   direction: fixed hex colours because they're a brand moment, not
   because dark would wash out (light would, here, on a dark-mode page
   otherwise built from var(--card)/var(--text-*) tokens).
   No illustration assets exist for the reference's photographic vault/
   folder/book collages, so the hero icon is a plain colour-badge glyph
   instead of an attempt at that artwork. */
.vault-page-hero{
  position:relative;overflow:hidden;border-radius:16px;margin-bottom:18px;
  padding:26px 30px;display:flex;align-items:center;gap:24px;flex-wrap:wrap;
  background:linear-gradient(120deg,#fdf6e3 0%,#faf0d7 45%,#f5e6c0 100%);
  border:1px solid rgba(200,153,46,.28);box-shadow:0 10px 26px rgba(120,90,20,.10);
}
.vault-hero-icon{
  flex:none;width:60px;height:60px;border-radius:16px;
  display:flex;align-items:center;justify-content:center;font-size:28px;
  background:linear-gradient(135deg,#F6E27A,#B8962E);color:#1A1200;
  box-shadow:0 8px 20px rgba(180,140,40,.35);
}
.vault-hero-text{flex:1;min-width:240px;}
.vault-hero-kicker{font-size:10.5px;font-weight:700;letter-spacing:.14em;color:#a9821f;text-transform:uppercase;}
.vault-hero-title{margin:2px 0 0;font-size:25px;font-weight:700;color:#3a2e0d;font-family:Georgia,'Times New Roman',serif;}
.vault-hero-sub{margin:6px 0 0;font-size:13px;color:#6b5a2e;max-width:480px;}
.vault-hero-badges{display:flex;gap:22px;margin-top:16px;flex-wrap:wrap;}
.vault-hero-badge{display:flex;align-items:center;gap:9px;}
.vault-hero-badge-icon{
  flex:none;width:32px;height:32px;border-radius:50%;
  background:rgba(255,255,255,.65);display:flex;align-items:center;justify-content:center;
  font-size:15px;box-shadow:inset 0 0 0 1px rgba(180,140,40,.25);
}
.vault-hero-badge-title{font-size:12.4px;font-weight:700;color:#3a2e0d;line-height:1.3;}
.vault-hero-badge-desc{font-size:11px;color:#8a763f;line-height:1.3;}
/* The reference's cursive "Your Legacy, Our Priority" script uses a font
   this app never loads elsewhere - an italic serif (same Georgia fallback
   stack as .vault-hero-title) reads as the same "heirloom" register
   without adding a new webfont dependency for one decorative line. */
.vault-hero-tagline{
  flex:none;margin-left:auto;font-family:Georgia,'Times New Roman',serif;font-style:italic;
  font-size:16px;line-height:1.35;color:#a9821f;text-align:right;
}
.vault-footer{
  margin-top:20px;padding:18px 24px;border-radius:14px;text-align:right;
  background:linear-gradient(120deg,#faf0d7 0%,#f5e6c0 100%);
  border:1px solid rgba(200,153,46,.22);
}
.vault-footer-tagline{
  font-family:Georgia,'Times New Roman',serif;font-style:italic;font-size:14.5px;color:#a9821f;
  letter-spacing:.02em;
}
@media(max-width:760px){
  .vault-page-hero{padding:20px;}
  .vault-hero-tagline{display:none;}
  .vault-footer{text-align:center;}
}

/* ---------- Archived Vault tab bar + search + row icons ----------
   A separate pill-style tab strip (.av-tabs) rather than reusing the
   app's plain underline .tabs/.tab, per the reference; scoped with an
   av- prefix so it can't drift the shared .tabs used everywhere else. */
.av-tabs{
  display:flex;gap:6px;flex-wrap:wrap;padding:6px;margin-bottom:16px;
  background:var(--subtle-bg);border:1px solid var(--border);border-radius:14px;
}
.av-tab{
  display:flex;align-items:center;gap:8px;padding:10px 16px;border-radius:10px;
  font-size:13px;font-weight:600;color:var(--text-600);cursor:pointer;
  background:transparent;border:1px solid transparent;white-space:nowrap;
  transition:background .15s ease-out, border-color .15s ease-out, color .15s ease-out;
}
.av-tab:hover{background:var(--card);}
.av-tab.active{background:var(--gold-bg);border-color:var(--gold-500);color:var(--text-900);}
.av-tab-count{color:var(--text-400);font-weight:600;}
.av-tab.active .av-tab-count{color:var(--gold-600);}
.av-search-row input{
  padding:8px 12px;border:1px solid var(--border);border-radius:999px;font-size:13px;min-width:240px;
}
.av-search-clear{
  width:34px;height:34px;flex:none;border-radius:50%;border:1px solid var(--border);background:var(--card);
  display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:14px;color:var(--text-600);
}
.av-search-clear:hover{background:var(--subtle-bg);}
/* [hidden] alone loses to .av-search-clear's own display:flex (same
   specificity, author origin beats the UA stylesheet's [hidden] rule
   regardless) - this attribute+class combo outranks the plain class. */
.av-search-clear[hidden]{display:none;}
.av-row-icon,.dv-row-icon{
  width:34px;height:34px;border-radius:10px;flex:none;
  background:var(--gold-bg);display:flex;align-items:center;justify-content:center;font-size:16px;
}

/* ---------- Bank Locker video card ----------
   assets/bank-locker-hero.mp4 (a "vault opening to reveal jewelry" clip)
   plays on loop behind the empty state when no locker has been added yet,
   and behind EACH individual locker's own card once lockers exist — same
   edge-to-edge video/overlay/content-layer technique as .will-video-card
   (js/familyTree.js's Digital Will card): .card already carries its own
   padding (18px 20px); .bl-video-card zeroes that back to 0 so the video
   (position:absolute; inset:0) can fill the card's full padding box, and
   .bl-video-content puts that padding back so the real content still sits
   inset from the border exactly as an unmodified .card would. The base
   white text colour (plus a muted-tone override for .text-muted, which
   sets its own explicit colour and would otherwise win) exists because
   the card's normal theme-based text colours assume a plain card surface,
   not a video background — same reasoning as .will-video-content. */
.bl-video-card{position:relative;overflow:hidden;padding:0;}
/* background: same reason as .bl-hero-media's - the clip has no poster, so
   the card needs its own dark ground to sit on until the footage paints. */
.bl-video-bg{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;display:block;background:#061224;}
.bl-video-overlay{position:absolute;inset:0;background:linear-gradient(180deg, rgba(8,8,10,.35) 0%, rgba(8,8,10,.55) 60%, rgba(8,8,10,.80) 100%);}
.bl-video-content{position:relative;z-index:1;padding:18px 20px;color:#fff;}
.bl-video-content .text-muted{color:#c7c2b8;}
/* .bl-empty-title/.bl-empty-sub set their own theme-based colour (meant
   for the plain-card empty state elsewhere) - a rule targeting the
   element directly always wins over the parent's inherited colour
   regardless of specificity, so these need an explicit override. */
.bl-video-content .bl-empty-title{color:#fff;}
.bl-video-content .bl-empty-sub{color:#e8ddc4;}
/* Empty-state variant: spans the full grid width (sole child of the
   locker list's .grid.grid-3, same reasoning as .bl-empty-vault) and
   centers its (now much shorter, since the video itself is the
   illustration) text in a tall card instead of a small icon-sized one. */
.bl-video-card.empty{grid-column:1 / -1;}
.bl-video-card.empty .bl-video-content{
  min-height:320px;padding:50px 20px;text-align:center;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
}

/* ============================================================
   Bank Locker — premium vault treatment (Bank_Locker_Animation.txt +
   Reference_Image.png, 2026-09-10)

   Layered ON TOP of the .bl-* block above rather than replacing it: the
   hero, the six stat cards and their colours were already built to an
   earlier reference and are what the brief calls "existing"; this adds the
   cinematic half — the vault-room right side, the gold light trail, the
   card light-waves, the cursor tilt/glow and the entrance stagger.

   The brief's rule "do not make every element glow" is the one that shapes
   this: the hierarchy is safe > title > cards > icons > gold accents >
   decoration, and everything below is deliberately held under the intensity
   it could have had. Every animation here is transform/opacity/filter only,
   so none of it triggers layout.
   ============================================================ */

/* ---------- Hero: vault-room right side ----------
   The brief asks for "a dark safety deposit locker room, one open locker,
   warm golden light" and says to use an existing asset if one fits.
   assets/bank-locker-hero.mp4 is exactly that footage - already on this page
   behind the empty state and each locker card, so it costs no new download.
   Masked to the right half and faded out towards the text, so it reads as
   the room the safe is standing in rather than a video pasted behind a
   heading. */
/* No poster attribute on these videos, on purpose. The replacement clip
   (assets/locker-background.mp4) carries its moov atom at the END of the
   file, so the browser must fetch all 4.4MB before it can paint a frame -
   which leaves whatever sits underneath visible for a moment on a cold
   load. The old poster belonged to the previous clip and showed a different
   scene, so reusing it produced a visible jump, and a still could not be
   extracted here without ffmpeg. A deep-navy ground is the honest answer:
   it matches the hero's own gradient, so the footage fades up out of the
   banner rather than arriving over a white gap or the wrong picture. */
.bl-hero-media{
  position:absolute;top:0;right:0;height:100%;width:62%;
  background:#061224;
  object-fit:cover;object-position:center;
  pointer-events:none;user-select:none;
  opacity:.72;
  -webkit-mask-image:linear-gradient(to right, transparent 0%, #000 38%);
          mask-image:linear-gradient(to right, transparent 0%, #000 38%);
  /* Parallax is written to this variable by bankLocker.js's afterRender();
     the brief caps the movement at 5-8px and asks for transform, not left. */
  /* No extra scale needed for this clip's Gemini watermark. The banner is
     corner one - a four-point star at about 90% of the frame width. The
     banner is far wider than 16:9, so object-fit:cover fills the width and
     the whole frame is always in view; there is no horizontal overflow to
     pan away with object-position. Scaling about the centre is what pushes
     it out: a point at 90% width clears the element's right edge once the
     scale passes 1.25 (0.4*S > 0.5), and .bl-hero's overflow:hidden clips
     the rest. */
  transform:translate3d(var(--bl-par-x, 0px), 0, 0) scale(1.06);
  will-change:transform;
}
/* Keeps the title/subtitle legible over the brightest part of the footage
   (the open locker's warm light) without dimming the whole banner. */
.bl-hero-scrim{
  position:absolute;inset:0;pointer-events:none;
  background:linear-gradient(100deg, rgba(6,18,36,.94) 0%, rgba(6,18,36,.80) 38%, rgba(6,18,36,.30) 62%, rgba(6,18,36,.12) 100%);
}
/* min-height dropped and the vertical padding tightened from the base rule's
   26px. With the corner panel down to 132px the banner no longer needs to
   reserve 214px, and the tighter padding is what actually brings the height
   down - min-height alone would have left the padding holding it open. The
   panel is now the tallest thing in the row, so it sets the height. */
/* Pulled left and tightened. The base .bl-hero-inner sets padding 26px 28px
   and gap 26px; the left padding and the gap are both cut here so the clip
   sits closer to the banner edge and the safe's opening door lands just
   short of the "Bank Locker" heading - close, but with the door still
   clearing the text rather than touching it. */
.bl-hero-cine .bl-hero-inner{position:relative;z-index:2;min-height:0;align-items:center;padding:16px 28px 16px 6px;gap:10px;}

/* ---------- The corner clip (assets/locker-corner.mp4) ----------
   Replaces the CSS-drawn safe. The clip is a gold safe opening onto gold
   bars, shot centred on a grey studio background - so it is cropped to a
   portrait box by object-fit:cover (the subject is dead centre, so a centre
   crop keeps all of it) and then feathered with a radial mask, which is what
   dissolves that grey ground into the navy banner instead of leaving a grey
   rectangle sitting on it.
   No poster, same reasoning as .bl-hero-media above; the wrap's own dark
   ground covers the gap. */
/* A framed rectangular panel, not the feathered oval this started as. The
   oval hid the clip's grey studio ground by dissolving everything that was
   not the safe - but it ate the safe's own edges along with it. A rectangle
   keeps the whole locker in view, and the gold hairline turns that grey
   ground into a deliberate window cut into the banner rather than a smudge
   floating on it. */
/* Negative margins absorb the panel's own dead space. Keying the background
   out leaves the locker's actual pixels inset ~14px inside the 176px box on
   each side, so the visible gap was 26px where the CSS said 13 - measured off
   the canvas's alpha channel, not guessed. Pulling the box out by 12px each
   way brings the locker itself close to the banner edge and to the heading. */
.bl-corner-wrap{margin-left:-12px;margin-right:-12px;}
.bl-corner-wrap{
  position:relative;flex:none;
  /* 250x132 is ~1.89:1, deliberately WIDER than the clip's own 16:9, and
     that ratio is doing the work. While the box was TALLER than the source
     aspect (the old 250x172), object-fit:cover filled the height and trimmed
     the sides - so the frame's empty headroom above the safe came along with
     it. Once the box is wider than 1.78 the same rule fills the width and
     trims top and bottom instead, and object-position decides which end goes:
     biased low, so the panel ends just under the stage the safe stands on and
     the dead space above is what gets cropped. */
  width:176px;height:132px;
  /* No frame, no clipping, no shadow any more. Those existed to make the
     clip's grey studio ground read as a deliberate window in the banner;
     keyCornerVideo() now removes that ground outright, so the locker sits
     directly on the hero and a border would just outline empty navy. */
  overflow:visible;
}
/* Wider than its own frame, and pulled left by half the excess, so the wrap's
   overflow:hidden trims an equal slice off each side. That crop is the point:
   the clip carries a Gemini watermark (the four-point star) low on the RIGHT,
   at roughly 90% of the frame width, and cropping to the middle ~71% removes
   it outright rather than covering it up. Taking the same slice off the left
   keeps the safe centred in the panel. */

/* The raw <video> stays in the DOM as the canvas's source (and as the
   fallback if the 2d context is unavailable); .bl-corner-keyed is added by
   keyCornerVideo() only once it is actually drawing, so the plain clip is
   what shows if keying never starts. */
.bl-corner-canvas{display:none;width:100%;height:100%;}
.bl-corner-keyed .bl-corner-video{visibility:hidden;position:absolute;pointer-events:none;}
.bl-corner-keyed .bl-corner-canvas{display:block;}
.bl-corner-video{
  width:100%;height:100%;
  object-fit:cover;object-position:center center;
  display:block;
}
@media (prefers-reduced-motion: no-preference){
  /* The brief's 2-4px float, on the clip rather than the old CSS safe. */
  /* Negative margins absorb the panel's own dead space. Keying the background
   out leaves the locker's actual pixels inset ~14px inside the 176px box on
   each side, so the visible gap was 26px where the CSS said 13 - measured off
   the canvas's alpha channel, not guessed. Pulling the box out by 12px each
   way brings the locker itself close to the banner edge and to the heading. */
.bl-corner-wrap{margin-left:-12px;margin-right:-12px;}
.bl-corner-wrap{animation:bl-corner-float 6s ease-in-out infinite;}
  @keyframes bl-corner-float{
    0%,100%{transform:translateY(0);}
    50%{transform:translateY(-4px);}
  }
}
@media(max-width:620px){
  /* Negative margins absorb the panel's own dead space. Keying the background
   out leaves the locker's actual pixels inset ~14px inside the 176px box on
   each side, so the visible gap was 26px where the CSS said 13 - measured off
   the canvas's alpha channel, not guessed. Pulling the box out by 12px each
   way brings the locker itself close to the banner edge and to the heading. */
.bl-corner-wrap{margin-left:-12px;margin-right:-12px;}
.bl-corner-wrap{display:none;}
}

/* ---------- The gold light trail ----------
   "A thin elegant gold light streak that starts near the safe, travels
   horizontally, curves slightly, fades and repeats" - and explicitly NOT a
   cartoon lightning effect. So: one soft elongated ellipse, blurred, at low
   opacity, crossing once every 7s and absent for most of that. Two of them,
   the second offset and slower, which is what stops the repeat reading as a
   metronome. */
.bl-hero-trail,
.bl-hero-trail::after{
  position:absolute;pointer-events:none;
  border-radius:50%;
  background:radial-gradient(ellipse at center, rgba(245,210,122,.55) 0%, rgba(212,165,62,.22) 40%, transparent 72%);
  filter:blur(6px);
  opacity:0;
}
.bl-hero-trail{left:60px;top:44%;width:190px;height:10px;z-index:1;}
.bl-hero-trail::after{content:"";left:40px;top:26px;width:130px;height:7px;filter:blur(5px);}

/* ---------- Hero entrance ----------
   The brief's own order and timings: hero 0-500, locker 200-900, title
   250-750, description 450-900, badge 700-1100. Written as delays on one
   shared keyframe rather than five keyframes, so the curve stays identical
   across all of them. */
@keyframes bl-rise{
  from{opacity:0;transform:translateY(16px);}
  to{opacity:1;transform:translateY(0);}
}
@keyframes bl-rise-left{
  from{opacity:0;transform:translateX(-18px);}
  to{opacity:1;transform:translateX(0);}
}

@media (prefers-reduced-motion: no-preference){
  .bl-hero-cine{animation:bl-rise .5s cubic-bezier(.22,1,.36,1) both;}
  .bl-hero-cine .bl-safe-wrap{animation:bl-rise .7s cubic-bezier(.22,1,.36,1) .2s both;}
  .bl-hero-cine .bl-hero-title{animation:bl-rise-left .5s cubic-bezier(.22,1,.36,1) .25s both;}
  .bl-hero-cine .bl-hero-sub{animation:bl-rise .45s cubic-bezier(.22,1,.36,1) .45s both;}
  .bl-hero-cine .bl-hero-badge{animation:bl-rise .4s cubic-bezier(.22,1,.36,1) .7s both;}

  /* 7s cycle, visible for about a fifth of it. */
  .bl-hero-trail{animation:bl-trail 7s ease-in-out infinite;}
  .bl-hero-trail::after{animation:bl-trail 9s ease-in-out 2.2s infinite;}
  @keyframes bl-trail{
    0%{opacity:0;transform:translateX(0) scaleX(.6);}
    8%{opacity:.85;}
    22%{opacity:0;transform:translateX(230px) scaleX(1.5);}
    100%{opacity:0;transform:translateX(230px) scaleX(1.5);}
  }

  /* The badge's slow pulse - "every 4-5 seconds, not a strong blink". */
  .bl-hero-cine .bl-hero-badge{
    animation:bl-rise .4s cubic-bezier(.22,1,.36,1) .7s both,
              bl-badge-pulse 4.5s ease-in-out 1.2s infinite;
  }
  @keyframes bl-badge-pulse{
    0%,100%{box-shadow:0 0 0 0 rgba(212,165,62,0);}
    50%{box-shadow:0 0 16px 1px rgba(212,165,62,.28);}
  }
}

/* ---------- Stat cards: light wave ----------
   "A smooth silk/light wave, curved, partially transparent, blurred, with a
   brighter thin leading edge, travelling left to right" - and explicitly not
   a hard rectangular gradient. The curve comes from a wide ellipse clipped
   by the card's own overflow:hidden, so it enters and leaves as an arc. */
.bl-stat-wave{
  position:absolute;left:-40%;bottom:-42px;
  width:70%;height:96px;border-radius:50%;
  pointer-events:none;z-index:0;
  background:radial-gradient(ellipse at center, var(--bl-wave, rgba(120,170,255,.5)) 0%, transparent 70%);
  filter:blur(10px);
  opacity:.5;
}
/* The brighter leading edge, kept thin so the wave reads as lit rather than
   merely coloured. */
.bl-stat-wave::after{
  content:"";position:absolute;right:14%;top:34%;
  width:26%;height:3px;border-radius:999px;
  background:var(--bl-wave-edge, rgba(190,220,255,.85));
  filter:blur(1.5px);
}
.bl-stat-card.c1{--bl-wave:rgba(60,140,235,.55); --bl-wave-edge:rgba(170,210,255,.9);}
.bl-stat-card.c2{--bl-wave:rgba(0,201,139,.5);  --bl-wave-edge:rgba(160,255,220,.85);}
.bl-stat-card.c3{--bl-wave:rgba(139,92,246,.5); --bl-wave-edge:rgba(210,190,255,.85);}
.bl-stat-card.c4{--bl-wave:rgba(212,165,62,.55);--bl-wave-edge:rgba(250,225,160,.9);}
.bl-stat-card.c5{--bl-wave:rgba(0,184,230,.5);  --bl-wave-edge:rgba(165,235,255,.85);}
.bl-stat-card.c6{--bl-wave:rgba(80,120,240,.5); --bl-wave-edge:rgba(175,195,255,.85);}

/* ---------- Stat cards: cursor tilt + cursor-following glow ----------
   Both are driven by CSS variables that afterRender() writes from a single
   rAF-throttled pointermove, so the JS never touches a layout property.
   The tilt caps are the brief's: rotateX +/-4deg, rotateY +/-5deg,
   translateZ 5px. */
.bl-stat-card{
  /* transform-style:preserve-3d removed here. It positioned nothing - the card
     has no 3D-transformed children - but it flattens descendants into the
     card's own transformed space, which took the playing video off its own
     compositor layer and forced a re-rasterise on every frame the cursor
     moved. That is what made the footage stutter under the pointer. */
  will-change:transform;
  transform:
    perspective(900px)
    rotateX(var(--bl-rx, 0deg))
    rotateY(var(--bl-ry, 0deg))
    translateZ(var(--bl-tz, 0px))
    translateY(var(--bl-ty, 0px));
  transition:transform .6s cubic-bezier(.22,1,.36,1), box-shadow .35s ease, border-color .35s ease;
}
/* Targeted at the text block, NOT `> *`. The blanket version also hit
   .bl-stat-wave, whose position:absolute it overrode (same specificity,
   declared later) - that put the wave back into the flex flow as a 236px
   item, which shoved the figures off the right-hand end of the card and out
   over the bright part of the footage. */
.bl-stat-body{position:relative;z-index:1;}
/* Full-bleed clip with the figures set over its right-hand end.
   The card's own padding is dropped so the footage reaches all four borders;
   the text carries its own padding instead. */
.bl-stat-card{padding:0;gap:0;overflow:hidden;justify-content:flex-end;min-height:126px;}
.bl-stat-video{
  position:absolute;inset:0;z-index:0;
  width:100%;height:100%;object-fit:cover;
  pointer-events:none;
  opacity:0;transition:opacity .7s ease;
}
.bl-stat-video[src]{opacity:1;}
/* Keeps the clip on its own compositor layer, so the card's hover tilt moves
   a ready-made texture instead of re-rasterising live video every frame. */
.bl-stat-video{transform:translateZ(0);backface-visibility:hidden;}
/* Linked Insurance and Linked Documents only. Both clips carry a faint
   vertical seam a few pixels in from their own left edge, and it shows here
   because the card is a wider aspect than the footage (2.88:1 against 1.78:1)
   - object-fit:cover therefore fills the WIDTH and crops top and bottom,
   leaving the full frame width on display including that edge.
   With no horizontal overflow there is nothing to pan, so the element is
   widened past the card first and then pulled left: the seam travels out of
   the card on the left while the extra width covers the right. The other four
   clips have clean edges and are left alone. */
.bl-stat-card.c5 .bl-stat-video,
.bl-stat-card.c6 .bl-stat-video{
  width:112%;
  left:-11%;
}
/* The one thing holding this together. Clear on the left so the footage is
   seen properly, then ramping to near-solid under the text - a flat overlay
   at a single opacity would either wash the clip out or leave the numbers
   unreadable, and these cards exist to be read. Angled slightly (100deg) so
   the edge follows the eye down the card rather than cutting a hard vertical
   line through it. */
.bl-stat-scrim{
  position:absolute;inset:0;z-index:0;pointer-events:none;
  background:linear-gradient(100deg,
    rgba(4,12,24,0) 0%,
    rgba(4,12,24,.28) 30%,
    rgba(4,12,24,.72) 55%,
    rgba(4,12,24,.90) 78%,
    rgba(4,12,24,.94) 100%);
}
/* Right-aligned, and deliberately narrow: the text block stays over the solid
   end of the scrim instead of drifting back across the footage. */
.bl-stat-body{
  position:relative;z-index:1;
  flex:0 1 auto;max-width:58%;
  padding:16px 18px;text-align:right;
}
/* No wrapping. "Currently active", "Policies linked" and "Documents linked"
   were each breaking across two lines, which left the six cards with
   different text heights and read as an accident rather than a layout. */
.bl-stat-label,.bl-stat-sub,.bl-stat-value{white-space:nowrap;}
.bl-stat-value{font-size:30px;letter-spacing:-.5px;text-shadow:0 2px 10px rgba(0,0,0,.55);}
.bl-stat-label{color:rgba(255,255,255,.78);text-shadow:0 1px 6px rgba(0,0,0,.6);}
.bl-stat-sub{color:rgba(255,255,255,.55);text-shadow:0 1px 6px rgba(0,0,0,.6);}
/* A hairline of light along the top edge - the one piece of gloss these cards
   get, and what stops a full-bleed video card reading as a flat photo tile. */
.bl-stat-card::before{
  content:"";position:absolute;left:0;right:0;top:0;height:1px;z-index:1;
  background:linear-gradient(90deg, transparent, rgba(255,255,255,.34), transparent);
  pointer-events:none;
}
@media(max-width:1100px){
  .bl-stat-body{max-width:66%;}
}
@media (prefers-reduced-motion: reduce){
  .bl-stat-video{display:none;}
  .bl-stat-scrim{display:none;}
  .bl-stat-body{max-width:none;flex:1 1 auto;}
}
/* The glow rides above the card's own gradient but under its content, and
   is invisible until the pointer is actually over the card - a glow parked
   at the centre of every card at rest reads as a rendering artefact. */
.bl-stat-glow{
  position:absolute;inset:0;pointer-events:none;z-index:0;
  opacity:0;transition:opacity .35s ease;
  background:radial-gradient(circle at var(--bl-mx, 50%) var(--bl-my, 50%), var(--bl-wave, rgba(120,170,255,.5)) 0%, transparent 45%);
  mix-blend-mode:screen;
}



@media (hover:hover) and (pointer:fine){
  .bl-stat-card:hover{
    --bl-ty:-5px;
    border-color:rgba(255,255,255,.16);
    box-shadow:0 18px 38px rgba(0,0,0,.38);
    transition:transform .18s ease-out, box-shadow .35s ease, border-color .35s ease;
  }
  .bl-stat-card:hover .bl-stat-glow{opacity:.28;}

  .bl-stat-card:hover .bl-stat-icon{transform:scale(1.05) rotate(1.5deg);}
  .bl-stat-card:hover .bl-stat-wave{opacity:.78;}
}
.bl-stat-icon{transition:transform .3s ease, box-shadow .3s ease;}

@media (prefers-reduced-motion: no-preference){
  /* 8s, and the wave spends the whole cycle moving - this is the one
     continuous animation the brief actually asks to run without pause. */
  .bl-stat-wave{animation:bl-wave 8s linear infinite;}
  @keyframes bl-wave{
    from{transform:translateX(0) scaleY(1);}
    50%{transform:translateX(105%) scaleY(1.18);}
    to{transform:translateX(210%) scaleY(1);}
  }
  /* "Very subtle breathing" on the icons, staggered so six cards do not
     pulse as one. */
  .bl-stat-icon{animation:bl-breathe 6s ease-in-out infinite;}
  .bl-stat-card.c2 .bl-stat-icon{animation-delay:1s;}
  .bl-stat-card.c3 .bl-stat-icon{animation-delay:2s;}
  .bl-stat-card.c4 .bl-stat-icon{animation-delay:3s;}
  .bl-stat-card.c5 .bl-stat-icon{animation-delay:4s;}
  .bl-stat-card.c6 .bl-stat-icon{animation-delay:5s;}
  @keyframes bl-breathe{
    0%,100%{filter:brightness(1);}
    50%{filter:brightness(1.14);}
  }
  /* The gold sweep the brief reserves for "selected premium elements" -
     the hero border and the gold Linked Assets card only, not everything. */
  .bl-hero-cine::after{
    content:"";position:absolute;inset:0;border-radius:16px;pointer-events:none;z-index:3;
    background:linear-gradient(115deg, transparent 38%, rgba(245,210,122,.10) 48%, transparent 58%);
    background-size:280% 100%;
    animation:bl-sweep 8s ease-in-out infinite;
  }
  @keyframes bl-sweep{
    0%,100%{background-position:180% 0;}
    50%{background-position:-60% 0;}
  }
}

/* The brief's accessibility section, in full: parallax, particles,
   continuous waves, floating and light sweeps all stop; the layout, the
   colours and the hover affordances all stay. */
@media (prefers-reduced-motion: reduce){
  .bl-hero-media{transform:none;}
  .bl-hero-trail,.bl-hero-trail::after{display:none;}
  .bl-stat-wave{animation:none;}
  .bl-stat-icon{animation:none;}
  .bl-hero-cine::after{display:none;}
  .bl-stat-card{transition:box-shadow .2s ease, border-color .2s ease;}
}

/* ---------- Room for the arrow ----------
   The arrow is positioned over the card's right edge, so it takes no layout
   width - but it still has to not sit on top of the text. Without the
   reserved padding here every label broke to two lines ("TOTAL / LOCKERS")
   at four-up on a 1536px screen, which is narrower than the reference image
   was composed at. Icon and paddings are trimmed a little to buy the label
   back its single line rather than shrinking the type. */
.bl-stat-card{padding:16px 16px;}
/* Targeted by class, not by `> div`: the icon is a div too, and a
   :not()-chain aimed at the text block caught it as well - 42px of padding
   inside a 48px border-box circle left 6px of content and shunted every
   emoji off centre. */
.bl-stat-body{min-width:0;}
.bl-stat-label{white-space:nowrap;letter-spacing:.07em;}

/* ---------- The safe, as brushed steel rather than a gold block ----------
   It was built when this hero was flat navy; against the vault footage now
   behind it a solid-yellow box read as a sticker on a photograph. Same
   shapes and the same animation - only the materials change: a dark steel
   body, gold trim and dial, and a warm rim light on the top-left edge that
   agrees with the lighting in the footage. */
.bl-hero-cine .bl-safe-body{
  background:linear-gradient(160deg,#4a5568 0%,#2c3648 26%,#1b2432 62%,#131a26 100%);
  box-shadow:
    inset 0 1px 0 rgba(255,225,160,.45),
    inset 1px 0 0 rgba(255,225,160,.18),
    inset 0 -10px 20px rgba(0,0,0,.5),
    0 0 0 1px rgba(212,165,62,.45);
}
.bl-hero-cine .bl-safe-door{
  background:linear-gradient(150deg,#3b4658 0%,#232d3d 55%,#18202c 100%);
  border:1px solid rgba(212,165,62,.5);
  box-shadow:inset 0 1px 0 rgba(255,225,160,.22);
}
.bl-hero-cine .bl-safe-wheel{
  border:3px solid #C9A030;
  background:radial-gradient(circle at 32% 28%,#F6E27A 0%,#C9A030 45%,#6b5216 100%);
  box-shadow:0 2px 10px rgba(0,0,0,.55), 0 0 14px rgba(212,165,62,.4);
}
.bl-hero-cine .bl-safe-wheel::before{color:#2A1F00;}
/* Warmer and tighter than the original pool of light, so it reads as the
   safe standing on a lit floor rather than glowing by itself. */
.bl-hero-cine .bl-safe-glow{
  background:radial-gradient(ellipse at center, rgba(245,210,122,.5) 0%, rgba(212,165,62,.16) 42%, transparent 72%);
}

@media(max-width:1400px){
  /* Four cards below this width leave the label no room even trimmed, so it
     is allowed its second line back rather than being clipped. */
  .bl-stat-label{white-space:normal;}
}
@media(max-width:980px){
  .bl-hero-media{width:100%;opacity:.34;-webkit-mask-image:none;mask-image:none;}
  .bl-hero-scrim{background:linear-gradient(180deg, rgba(6,18,36,.86) 0%, rgba(6,18,36,.92) 100%);}
}
@media(max-width:620px){
  .bl-stat-body{padding-right:0;}
}

/* ============================================================
   Family Diary — familyDiary.js
   Tab strip · hero banner · empty state.

   Ported from Design/family_diary.html. Only the fd-* classes are
   defined here (plus a scoped override of the shared .tabs/.tab): the
   page's table, pills, buttons and card chrome stay the app's own
   shared classes, so it inherits every future change to them rather
   than carrying a second copy.

   The mockup set the pull-quote and the tab-strip note in a cursive
   face. Not carried over — the Typography block near the top of this
   file made Inter the single family app-wide and dropped the
   decorative serif that used to run alongside it. Italic Inter keeps
   the aside-voice those two bits of copy were after without reopening
   that decision.
   ============================================================ */

/* ---------- Tab strip ----------
   Rounded-top tabs that sit ON the border rather than under it, so the
   active one reads as a page behind the content. Scoped to this page:
   .tabs/.tab are shared with ~10 other modules that want the plain
   underline treatment. */
.family-diary-page .tabs{gap:22px;margin-bottom:0;align-items:flex-end;}
.family-diary-page .tab{
  padding:0 26px;height:44px;display:flex;align-items:center;gap:10px;
  font-size:14.4px;font-weight:700;color:var(--text-600);
  border-radius:14px 14px 0 0;
}
.family-diary-page .tab.active{
  background:var(--card);color:var(--text-900);
  box-shadow:0 4px 18px rgba(20,45,85,.08);
}
.fd-tab-icon{font-size:16px;opacity:.85;}
.fd-tabs-note{
  margin-left:auto;align-self:center;white-space:nowrap;
  font-style:italic;font-size:13.2px;color:var(--text-400);
}
.fd-tabs-note b{color:var(--gold-500);font-size:17px;font-style:normal;}

/* ---------- Hero banner ---------- */
.fd-hero{
  position:relative;overflow:hidden;
  border:1.5px solid var(--gold-500);border-radius:15px;
  background:
    radial-gradient(circle at 60% 28%, var(--gold-bg) 0, transparent 34%),
    linear-gradient(135deg, var(--card) 0%, var(--card) 48%, var(--gold-bg) 100%);
  box-shadow:var(--shadow);
  margin:21px 0 18px;min-height:340px;
}
/* Both banners sit in the DOM at once and are swapped with the hidden
   attribute (familyDiary.js render()/showTab()). .fd-hero-received sets
   display:flex, which outranks the UA sheet's [hidden]{display:none} —
   without this the Received banner also shows on My Diary / All Family
   Notes, stacked under the default one. Class+attribute beats the plain
   class, so this holds wherever it sits in the file. */
.fd-hero[hidden]{display:none;}
/* The soft sweep across the bottom-right corner. Sized past the banner
   on all three sides so only its top-left arc is ever visible. */
.fd-hero:after{
  content:"";position:absolute;right:-20px;bottom:-100px;
  width:430px;height:220px;
  background:linear-gradient(135deg, var(--card), var(--gold-bg) 65%, var(--gold-400));
  border-radius:55% 0 0 0;opacity:.55;transform:rotate(-5deg);
}
/* Bottom padding, and a max-width that fits all three .fd-feature tiles
   on one row (3×148 + 2×22 gap = 488): at a narrower width the third
   tile wraps under the first and lands on top of the .fd-corner-leaf. */
.fd-hero-copy{position:relative;z-index:2;padding:30px 0 34px 42px;max-width:530px;}
.fd-hero-pill{
  display:inline-flex;align-items:center;gap:8px;
  background:var(--gold-bg);color:var(--gold-600);
  border:1px solid rgba(200,153,46,.35);border-radius:20px;
  padding:6px 14px;font-weight:700;font-size:12.6px;
}
.fd-hero-title{
  margin:12px 0 8px;font-size:27px;line-height:1.14;letter-spacing:-.6px;
  font-weight:800;color:var(--text-900);
}
.fd-hero-title span{color:var(--gold-600);}
.fd-hero-intro{margin:0;font-size:14px;line-height:1.5;color:var(--text-600);max-width:400px;}

.fd-features{display:flex;gap:22px;margin-top:30px;flex-wrap:wrap;}
.fd-feature{width:148px;}
.fd-feature-icon{
  width:52px;height:52px;border-radius:50%;margin-bottom:8px;
  display:flex;align-items:center;justify-content:center;font-size:21px;
}
.fd-feature-icon.fd-fi-heart{background:var(--pink-bg);color:var(--pink);}
.fd-feature-icon.fd-fi-people{background:var(--blue-bg);color:var(--blue);}
.fd-feature-icon.fd-fi-shield{background:var(--green-bg);color:var(--green);}
/* The glyph sits in its own element so the ambient loops in the motion block
   can move it without touching the circle's transform, which the hover lift
   owns — an animation on .fd-feature-icon itself would outrank that hover
   rule and quietly kill it. */
.fd-fi-glyph{display:block;line-height:1;}
/* The ring that travels out of the Stay Informed circle. On ::after rather
   than the icon's own box-shadow, for the same reason: the hover state
   already owns that property. Idle and invisible here — the animation
   supplies everything — so with motion off there is no ring parked on the
   icon. */
.fd-fi-people{position:relative;}
.fd-fi-people::after{
  content:'';position:absolute;inset:0;border-radius:50%;
  border:2px solid var(--blue);
  opacity:0;pointer-events:none;
}
.fd-feature b{font-size:13px;color:var(--text-900);}
.fd-feature p{font-size:11.6px;color:var(--text-400);margin:3px 0 0;line-height:1.35;}

.fd-cta-area{position:absolute;right:50px;top:50px;z-index:4;text-align:center;}
/* Explicit, because the UA's [hidden]{display:none} is beaten by any rule
   that sets display — and the narrow breakpoint below turns this block
   into a static, padded footer. */
.fd-cta-area[hidden]{display:none;}
.fd-quote{font-style:italic;color:var(--text-400);font-size:14px;line-height:1.25;margin-top:30px;}
.fd-quote b{display:block;color:var(--gold-500);font-size:20px;font-style:normal;margin-top:4px;}

/* ---------- Hero illustration ----------
   Pure CSS, no asset: the mockup drew the scene with borders and
   gradients and there is no artwork file to port. Kept warm in BOTH
   themes for the same reason .bd-hero is kept dark in both — it is a
   lit scene, and recolouring it per theme only washes it out. */
/* Holds the journal artwork. Wider than the 330px the old CSS scene used,
   to match the share of the banner the comp gives it. Height is stated
   rather than left to the image (390 x 520/744 = 273), so the glints below
   have a definite box to be positioned against. */
.fd-illustration{
  position:absolute;left:50%;top:30px;width:390px;height:273px;z-index:1;
  pointer-events:none;user-select:none;
}
.fd-journal-set{position:relative;width:100%;height:100%;}

/* ============================================================
   Cover peek: pointing at the diary lifts its cover
   ------------------------------------------------------------
   Resting state first. Every rule here describes the diary SHUT with the pen
   beside it, which, because the pieces are cut from the render itself, is the
   original artwork. The lift is at the end of this block.

   The numbers are measured off the 744x520 render and scaled to this 390px
   box (x0.5242), so they only hold while .fd-illustration keeps its size.

   Stacking, bottom to top: the render without its pen; the book (page and
   cover); the heart patch and its glint; the pen.
   ============================================================ */
/* Transparent at rest, so the shut diary is the untouched render rather than
   the un-skewed cover laid back over it, which leaves a faint line along the
   top edge. Still hit-tested at opacity 0, so pointing at it works. */
.fd-book{position:absolute;inset:0;z-index:2;perspective:1400px;pointer-events:none;opacity:0;}
.fd-journal-heart{z-index:3;}
.fd-glint.g1{z-index:4;}

/* The page plane. The render's cover is a parallelogram whose opposite sides
   are parallel to within a pixel — an affine projection of a flat rectangle,
   which is exactly what matrix() is. Laid out flat inside this, a page or a
   cover lands on the render where the real one is. Origin is the cover's far
   corner; x runs toward the fore-edge, y down the spine. */
.fd-plane{
  position:absolute;left:0;top:0;width:0;height:0;
  transform-origin:0 0;
  transform:matrix(0.8591,0.5118,-0.8031,0.5959,185.77,31.12);
  transform-style:preserve-3d;
}

/* The first page, lying exactly where the cover lies: hidden under it at
   rest, seen as it lifts. Ruled, with the spine's gutter shadow on the left
   and a few hairlines on the right for the pages stacked behind it. It is
   also the hover target — it stays put while the cover moves, so the pointer
   does not fall off the thing it lifted. */
.fd-leaf{
  position:absolute;left:0;top:0;width:139.8px;height:180.11px;
  border-radius:0 5px 5px 0;
  pointer-events:auto;
  background:
    linear-gradient(90deg, rgba(110,80,30,.26) 0, rgba(110,80,30,0) 16px),
    repeating-linear-gradient(180deg, rgba(120,140,190,0) 0 17px, rgba(120,140,190,.2) 17px 18px),
    linear-gradient(160deg,#fffdf7 0%,#f6eedc 100%);
  box-shadow:inset -1px 0 0 #e6dac2, inset -3px 0 0 #fbf6ea, inset -4px 0 0 #e8ddc6;
}
/* The shadow the lifted cover throws on the page, darkest at the spine. */
.fd-leaf::after{
  content:'';position:absolute;inset:0;border-radius:inherit;
  background:linear-gradient(90deg, rgba(30,24,12,.34) 0%, rgba(30,24,12,0) 70%);
  opacity:0;
}

/* The cover, hinged on the spine (its left edge in this plane). */
.fd-cover{
  position:absolute;left:0;top:0;width:139.8px;height:180.11px;
  transform-origin:0 50%;
  transform:rotateY(0deg);
  background:url('../assets/family-diary-cover.png') center / 100% 100% no-repeat;
  -webkit-backface-visibility:hidden;backface-visibility:hidden;
}
/* Light falling off the leather as it tips toward the viewer. Zero at rest. */
.fd-cover-shade{position:absolute;inset:0;background:#0b1430;opacity:0;pointer-events:none;}

/* The pen, cut from the render and laid back exactly where it lay. */
.fd-pen{
  position:absolute;left:167.74px;top:113.75px;width:167.74px;
  z-index:5;pointer-events:none;
}
.fd-pen-img{display:block;width:100%;height:auto;}
/* The same spots on the artwork as before, measured from the pen's own box. */
.fd-pen .fd-glint.g2{left:19.26px;top:101.25px;}
.fd-pen .fd-glint.g3{left:122.26px;top:33.25px;}

/* The lift. Only for a real pointer (a tap has no "leave" to close it again)
   and only with motion allowed — without it the cover stays shut rather than
   snapping open. Closing is declared on the resting rules and opening on
   :hover, so each direction gets its own feel: a lift that overshoots a
   touch and settles, and a close that eases down and lands.

   The heart patch and its glint are copies laid OVER the cover, so they
   would hang in the air as it lifts. They step aside at once on the way
   open — the cover image carries its own heart — and come back only after
   the cover has landed. */
@media (hover:hover) and (pointer:fine) and (prefers-reduced-motion: no-preference){
  .fd-book{transition:opacity 0s linear .62s;}
  .fd-journal-set:hover .fd-book{opacity:1;transition-delay:0s;}
  .fd-cover{transition:transform .6s cubic-bezier(.5,0,.25,1);}
  .fd-cover-shade, .fd-leaf::after{transition:opacity .6s cubic-bezier(.5,0,.25,1);}
  .fd-journal-heart{transition:opacity .15s ease .55s;}
  .fd-glint.g1{transition:visibility 0s linear .6s;}

  .fd-journal-set:hover .fd-cover{
    transform:rotateY(-38deg);
    transition:transform .75s cubic-bezier(.34,1.35,.5,1);
  }
  .fd-journal-set:hover .fd-cover-shade{opacity:.22;transition-duration:.45s;}
  .fd-journal-set:hover .fd-leaf::after{opacity:1;transition-duration:.45s;}
  .fd-journal-set:hover .fd-journal-heart{opacity:0;transition:opacity .08s ease 0s;}
  .fd-journal-set:hover .fd-glint.g1{visibility:hidden;transition-delay:0s;}
}

/* The comp's journal-and-pen art. Its own cream backdrop is feathered to
   transparent in the file, so what lands on the banner is the journal and
   its shadow rather than a pasted rectangle. */
.fd-journal{display:block;width:100%;height:auto;}
/* The cover heart, laid back over its own place in the artwork so it can
   beat on its own. Cut as a circle and feathered into the surrounding
   leather, so scaling it moves the heart without showing the edge of a
   patch. Position is measured off the artwork: the heart's centre sits at
   (186.8, 102.3) in this 390x273 box, and this 63px square is placed to
   centre on it — both this and the size are tied to .fd-illustration's
   dimensions, so they move together or not at all. */
.fd-journal-heart{
  position:absolute;left:155px;top:71px;width:63px;height:63px;
  pointer-events:none;
}
/* Three glints, placed over the gold in the artwork: the heart embossed on
   the cover, the pen's nib and its clip. Coordinates are measured off the
   744x520 asset and scaled to this box, so they only hold while the art and
   the box keep their sizes. They sit at opacity:0 and are lit entirely by
   the animation, so with motion off they stay invisible rather than
   freezing as three stars stuck on the artwork. */
.fd-glint{
  position:absolute;opacity:0;pointer-events:none;
  font-size:13px;line-height:1;color:#ffd97a;
  text-shadow:0 0 8px rgba(255,214,110,.9);
}
.fd-glint.g1{left:181px;top:96px;}
.fd-glint.g2{left:187px;top:215px;font-size:11px;}
.fd-glint.g3{left:290px;top:147px;font-size:10px;}
/* Behind the copy and smaller than the mockup's 65px: the mockup's hero
   was a fixed 400px tall with room to spare under the feature tiles,
   whereas this one sizes to its content and the flourish otherwise
   clips the first tile's caption. */
.fd-corner-leaf{position:absolute;left:-6px;bottom:-14px;font-size:48px;color:#d7bf72;z-index:0;}

/* ---------- Empty state ----------
   Its own block rather than a modifier on the shared .empty-state: the
   gold rule-and-heart divider and the pill-shaped title chip have no
   counterpart there, and .empty-state is used by ~20 other modules. */
.fd-empty{
  display:flex;flex-direction:column;align-items:center;text-align:center;
  padding:26px 20px 40px;
}
.fd-divider{display:flex;align-items:center;gap:12px;color:var(--gold-500);font-size:19px;}
.fd-divider:before,.fd-divider:after{content:"";width:135px;height:1px;background:var(--gold-400);}
.fd-empty-title{
  margin-top:10px;background:var(--gold-bg);border-radius:28px;
  padding:11px 70px;font-weight:800;font-size:15.5px;color:var(--text-900);
}
.fd-empty p{color:var(--text-600);line-height:1.45;font-size:13px;margin:10px 0 16px;max-width:420px;}

/* ---------- Responsive ----------
   Same escalation as .bd-hero/.tk-hero: fade the artwork, then drop it
   and let the copy have the full width rather than squeezing both. */
@media(max-width:1180px){
  .fd-illustration{opacity:.85;}
  .fd-hero-copy{max-width:400px;}
  .fd-tabs-note{display:none;}
}
@media(max-width:940px){
  .fd-hero{min-height:0;}
  .fd-hero:after,.fd-illustration{display:none;}
  .fd-hero-copy{max-width:100%;padding:26px 24px 0;}
  .fd-cta-area{position:static;text-align:left;padding:20px 24px 26px;}
  .fd-quote{margin-top:16px;}
  .fd-empty-title{padding:11px 30px;}
  .fd-divider:before,.fd-divider:after{width:60px;}
}
@media(max-width:640px){
  .family-diary-page .tabs{gap:8px;}
  .family-diary-page .tab{padding:0 14px;font-size:13.2px;}
  .fd-features{gap:18px;}
  .fd-feature{width:calc(50% - 9px);}
}

/* ---------- Insurance overview (Design/Web Design/insurance_dashboard.html) ---------- */
/* Attention Required / All Clear card - amber and green use the same
   theme-aware tokens as .pill-amber/.stat-card.accent-green elsewhere,
   so this stays correct in dark mode without its own overrides. */
.ins-alert-card{padding:16px 18px;background:var(--amber-bg);border-color:var(--amber-bg);}
.ins-alert-card .ins-alert-title{font-weight:800;color:var(--amber);margin-bottom:8px;}
.ins-alert-card.ins-alert-ok{background:var(--green-bg);border-color:var(--green-bg);}
.ins-alert-card.ins-alert-ok .ins-alert-title{color:var(--green);}
/* Turns the plain table into individually-boxed policy rows (each <tr>
   its own rounded, shadowed "card") without giving up <table>'s real
   semantics/accessibility or the .table-wrap markup every other list
   page already shares - border-collapse:separate + spacing between rows,
   then radius/shadow applied via the row's own first/last cell (the
   standard way to round a table row's corners, since border-radius on
   <tr> itself has no effect). Scoped to .ins-page so this page's table
   alone gets the boxed-row treatment. */
.ins-page .table-wrap table{border-collapse:separate;border-spacing:0 10px;}
.ins-page .table-wrap thead th{border-bottom:0;color:var(--text-400);}
.ins-page .table-wrap tbody tr{box-shadow:var(--shadow);}
.ins-page .table-wrap tbody td{border-top:1px solid var(--border);border-bottom:1px solid var(--border);padding-top:14px;padding-bottom:14px;}
.ins-page .table-wrap tbody td:first-child{border-left:1px solid var(--border);border-radius:12px 0 0 12px;}
.ins-page .table-wrap tbody td:last-child{border-right:1px solid var(--border);border-radius:0 12px 12px 0;}
/* Active/status pill gets a soft matching glow on this page only, echoing
   the mockup's glowing green pill - .pill-green/.pill-red etc. stay plain
   everywhere else in the app. */
.ins-page .pill-green{box-shadow:0 3px 10px rgba(31,138,95,.25);}
.ins-page .pill-red{box-shadow:0 3px 10px rgba(192,57,43,.2);}


/* Shimmer & shine. A plain white diagonal band is invisible on a white
   card (same-color alpha blending shows nothing), so this uses a gold
   tint via color-mix() instead - already the technique this app's own
   .dashboard-page card-accent hover glow uses (color-mix(in srgb,
   var(--accent) ..., transparent)) - both reads as "shine" and stays
   visible on every card here, tinted or plain white alike, in both
   themes since --gold-500 itself is theme-aware. The 3 overview cards
   get an always-on ambient sweep (reuses the app's existing Net Worth
   ambient-shine motion, stat-card-ambient-sweep, rather than a new
   keyframe); each policy row instead sweeps once on hover, echoing
   Bank Locker's .bl-page hover shine. overflow:hidden on the <tr> only
   clips its own content/pseudo-elements, never the row's own
   box-shadow (that paints outside the border box), so this doesn't
   undo the boxed-row look above. */
.ins-page .stat-card,
.ins-page > .card,
.ins-page .table-wrap tbody tr{
  position:relative;overflow:hidden;
}
.ins-page .stat-card::before,
.ins-page > .card::before{
  content:'';
  position:absolute;top:0;left:-60%;
  width:34%;height:100%;
  pointer-events:none;
  background:linear-gradient(75deg,
    transparent 0%,
    color-mix(in srgb, var(--gold-500) 30%, transparent) 45%,
    color-mix(in srgb, var(--gold-500) 55%, transparent) 50%,
    color-mix(in srgb, var(--gold-500) 30%, transparent) 55%,
    transparent 100%);
}
.ins-page .stat-card::before,
.ins-page > .card::before{
  animation:stat-card-ambient-sweep 4.5s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce){
  .ins-page .stat-card::before,.ins-page > .card::before{animation:none;display:none;}
}

/* ---------- Privacy & Consent Center (js/privacy.js) ----------
   Theme-adaptive (var(--card)/var(--border)/var(--gold-500)) rather than
   the Document/Archived Vault pages' fixed light-gold palette — this is
   a functional settings-style page, not a "trust vault" showcase. */
/* ============================================================
   Privacy & Consent Center — ported from "Privacy and Security.png"
   ------------------------------------------------------------
   The hero is fixed cream and gold rather than theme tokens, the same call the
   other painted surfaces in this file make: the comp is one warm banner, and a
   hero that recolours per theme washes out to nothing. Everything below it
   stays on theme tokens.
   ============================================================ */
.pn-hero{
  position:relative;display:flex;align-items:center;gap:26px;
  padding:0 30px;margin-bottom:20px;border-radius:18px;
  min-height:170px;overflow:hidden;
  /* Sampled off the comp at the points these stops sit on: cream at both ends
     deepening to gold in the corners, near-white through the middle where the
     copy runs. That is what lets the two cropped illustrations — which carry
     their own background with them — sit on it without a seam. */
  background:linear-gradient(103deg,
    #F9F1DF 0%, #FDFBF4 22%, #FEFCF9 50%, #FDFAF0 78%, #FAF2DD 100%);
  border:1px solid rgba(214,182,110,.42);
  box-shadow:0 10px 26px -18px rgba(140,105,35,.5);
}
/* The gold sweep across the bottom of the comp's banner. A wide, very flat
   radial rather than an asset: it is a soft wash with no detail in it, and a
   PNG would be a few hundred KB to say what one gradient says. */
/* Covering the whole hero with the radial's centre pushed below it, rather
   than a half-height element with the centre on its top edge. The first
   version did the latter and drew a hard line straight across the banner: a
   radial is at full strength at its centre, so putting the centre on an
   element's own edge means the wash starts at maximum with nothing above it to
   fade from. Centred off-canvas, the falloff does all the work and there is no
   edge to see. */
.pn-hero::after{
  content:'';position:absolute;inset:0;pointer-events:none;
  background:radial-gradient(140% 118% at 50% 142%,
    rgba(226,183,92,.42) 0%, rgba(233,199,124,.17) 46%, rgba(255,255,255,0) 78%);
}
.pn-hero-art{flex:none;width:250px;height:auto;align-self:center;position:relative;z-index:1;}
.pn-hero-copy{flex:1 1 auto;min-width:0;position:relative;z-index:1;}
/* Serif, with the second half in gold — the comp sets this one display line
   apart from the app's Inter UI type, and the colour break is what makes
   "Center" read as part of a name rather than a third word. */
.pn-hero-title{
  margin:0;font-family:Georgia,'Times New Roman',serif;
  font-size:31px;font-weight:700;letter-spacing:-.4px;color:#1c2a4a;
}
.pn-hero-title span{color:#c8992e;}
.pn-hero-sub{margin:10px 0 0;font-size:14.4px;line-height:1.62;color:#4a5568;max-width:610px;}
.pn-hero-script{flex:none;width:158px;height:auto;align-self:center;position:relative;z-index:1;}
@media(max-width:1180px){ .pn-hero-script{display:none;} }
@media(max-width:820px){
  .pn-hero{flex-direction:column;align-items:flex-start;padding:22px;gap:14px;}
  .pn-hero-art{width:170px;}
  .pn-hero-title{font-size:25px;}
}

/* ---------- Tab strip ----------
   Its own strip rather than the shared .tabs: the comp fills the active tab
   with a cream pill over a gold rule, which the shared component has no notion
   of, and bending .tabs to do it would change every other page using it. */
.pn-tabs{
  display:flex;align-items:stretch;gap:6px;flex-wrap:wrap;
  border-bottom:1px solid var(--border);margin-bottom:18px;
}
.pn-tab{
  appearance:none;cursor:pointer;font-family:inherit;
  display:inline-flex;align-items:center;gap:9px;
  padding:13px 20px;border:none;background:transparent;
  font-size:13.8px;font-weight:600;color:var(--text-600);
  border-radius:10px 10px 0 0;
  border-bottom:2.5px solid transparent;margin-bottom:-1px;
  transition:background .16s ease, color .16s ease;
}
.pn-tab-icon{display:inline-flex;align-items:center;opacity:.85;}
@media (hover:hover) and (pointer:fine){
  .pn-tab:hover{background:var(--subtle-bg);color:var(--text-900);}
}
.pn-tab.active{
  background:linear-gradient(180deg, rgba(250,240,215,.95), rgba(253,248,236,.6));
  color:#a97c2a;border-bottom-color:#c8992e;
}
.pn-tab.active .pn-tab-icon{opacity:1;}

/* ---------- Consent cards ---------- */
.pn-consent-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:18px;}
@media(max-width:900px){.pn-consent-grid{grid-template-columns:1fr;}}
.pn-consent-card{display:flex;flex-direction:column;padding:20px 22px 18px;border-radius:14px;}
.pn-consent-head{display:flex;align-items:flex-start;gap:14px;}
/* The tinted tile. Fixed colours rather than themed, for the reason a brand
   mark's are: they say which consent this is before the title is read, and
   four tints that shift per theme stop doing that. Alpha-blended onto the card
   so they stay soft in dark mode instead of glowing. */
.pn-consent-tile{
  flex:none;width:52px;height:52px;border-radius:14px;
  display:inline-flex;align-items:center;justify-content:center;
}
.pn-tint-blue  {background:rgba(59,130,246,.13);color:#2f6fd0;}
.pn-tint-violet{background:rgba(147,51,234,.12);color:#8b3fd4;}
.pn-tint-green {background:rgba(16,194,84,.14);color:#0f9b45;}
.pn-tint-indigo{background:rgba(79,90,229,.12);color:#4a56c8;}
.pn-consent-headings{flex:1 1 auto;min-width:0;padding-top:3px;}
/* --text-900, not the comp's fixed navy: this title sits on .card, which
   follows the theme, and #1c2a4a on dark mode's #161e2c is very nearly
   invisible. The token is #1a2233 in light — the comp's navy to within a
   shade — so nothing is lost by taking it from the theme. (The hero's title
   above keeps the fixed navy because the hero is a painted cream surface that
   does not follow the theme at all.) */
.pn-consent-name{
  font-family:Georgia,'Times New Roman',serif;
  font-size:16.5px;font-weight:700;color:var(--text-900);line-height:1.3;
}
/* The chip runs inline with the title and wraps with it rather than being
   pinned beside it — several of these names take two lines at this column
   width, and a pinned chip would either squeeze the name or fall off it. */
.pn-chip{
  display:inline-block;vertical-align:2px;margin-left:8px;
  padding:3px 10px;border-radius:20px;
  font-size:11.4px;font-weight:600;white-space:nowrap;
}
.pn-chip-need{background:rgba(59,130,246,.14);color:#2563c9;}
/* #2563c9 is right on white and too dark on dark mode's navy card — the chip
   read as dim blue on dim blue. Same hue, lifted, with a slightly stronger
   tint so the pill still has an edge. */
:root[data-theme="dark"] .pn-chip-need{background:rgba(96,150,240,.2);color:#8fb8ff;}
.pn-chip-opt {background:var(--subtle-bg);color:var(--text-400);}
/* "Necessary to provide the service" — a purpose relying on necessity rather
   than consent (privacy.js C001). Deliberately NOT the blue of .pn-chip-need:
   that one means "we need you to agree to this", and the whole point of this
   chip is that no agreement is being asked for. Neutral slate reads as a
   statement of fact rather than a prompt. */
.pn-chip-basis{background:rgba(100,116,139,.16);color:#475569;}
:root[data-theme="dark"] .pn-chip-basis{background:rgba(148,163,184,.2);color:#cbd5e1;}
/* The state read-out: shaped like the comp's toggle — a pill with a dot at one
   end — but it reports rather than switches. See privacy.js for why. */
.pn-state{
  flex:none;display:inline-flex;align-items:center;gap:7px;
  padding:5px 11px 5px 8px;border-radius:20px;
  font-size:11.4px;font-weight:700;letter-spacing:.03em;white-space:nowrap;
}
.pn-state-dot{
  width:11px;height:11px;border-radius:50%;background:currentColor;
  box-shadow:inset 0 0 0 3px rgba(255,255,255,.55);
}
.pn-state-active   {background:rgba(16,194,84,.15);color:#0f9b45;}
.pn-state-pending,
.pn-state-rejected {background:var(--subtle-bg);color:var(--text-400);}
.pn-state-withdrawn{background:var(--red-bg);color:var(--red);}
.pn-consent-desc{margin:12px 0 0;font-size:13.4px;line-height:1.62;color:var(--text-600);}
/* Purpose and data category in their own recessed strip, as in the comp — they
   are metadata about the consent rather than part of its description, and the
   strip says so without needing a heading. */
.pn-consent-meta{
  display:flex;align-items:center;gap:8px;flex-wrap:wrap;
  margin-top:14px;padding:9px 13px;border-radius:9px;
  background:var(--subtle-bg);
  font-size:12.4px;color:var(--text-600);
}
.pn-consent-meta-icon{display:inline-flex;align-items:center;color:var(--text-400);}
.pn-consent-meta-sep{color:var(--text-400);}
/* Pushed to the card's bottom edge. Grid rows stretch to the tallest card, and
   without margin-top:auto the buttons in a short one would float at whatever
   height its text happened to end — the comp lines them up across the row. */
.pn-consent-actions{
  display:flex;justify-content:flex-end;gap:10px;flex-wrap:wrap;
  margin-top:auto;padding-top:18px;
}
.pn-btn{
  appearance:none;cursor:pointer;font-family:inherit;
  display:inline-flex;align-items:center;gap:8px;
  padding:8px 15px;border-radius:9px;
  font-size:12.8px;font-weight:600;
  color:var(--text-600);background:var(--card);
  border:1px solid var(--border);
  transition:background .16s ease, border-color .16s ease, color .16s ease;
}
@media (hover:hover) and (pointer:fine){
  .pn-btn:hover{background:var(--subtle-bg);color:var(--text-900);}
}
.pn-btn-gold{
  background:linear-gradient(180deg,#e8c069,#cfa03f);
  border-color:#cfa03f;color:#2a1f06;
}
@media (hover:hover) and (pointer:fine){
  .pn-btn-gold:hover{filter:brightness(1.05);color:#2a1f06;}
}
.pn-btn-danger{color:var(--red);border-color:var(--red);}
@media (hover:hover) and (pointer:fine){
  .pn-btn-danger:hover{background:var(--red-bg);color:var(--red);}
}
.pn-notice-body{margin-top:14px;font-size:13.4px;line-height:1.7;color:var(--text-600);white-space:normal;}
/* The gold shimmer that used to sweep across a policy row on hover is gone -
   asked for on 23-Sep-2026. It lived twice: once on <tr>::before (which Chrome
   could not place as a table cell and turned into a phantom column that pushed
   Policy/Type/Owner/... over by one) and then again, correctly, per <td>. Both
   are removed; the ambient sweep on this page's stat cards above is a separate
   effect and still runs. The row's hover highlight (background + left rule +
   shadow, further down this file) is not an animation and stays.
   The cell rule itself is left exactly as it was, so nothing else on the row
   shifts: `overflow:hidden` also keeps a long policy name inside its column. */
.ins-page .table-wrap tbody tr::before{content:none !important;display:none !important;}
.ins-page .table-wrap tbody td{position:relative;overflow:hidden;}

/* ================= Family Members page (Family Members UI_v2) =================
   Scoped entirely to the .fm-* classes emitted by familyTree.js's
   renderMembers(), plus the .fam-tabs strip the Family & Estate module owns.
   Nothing here touches .card / table / .pill / .avatar globally, so every
   other page renders exactly as before.

   UI_v2 is a presentation-only pass over UI_v1's markup and data: the flat
   table became a stack of bordered member cards, the four summary chips
   became accented stat cards, and the tab strip became a rounded segmented
   bar. Two rules keep it from turning garish:

     1. Borders are light-toned only â€” two weights (--fm-line, --fm-line-soft),
        both mixed down from --border, never a hard dark rule.
     2. Accent colour appears at 6-22% strength on surfaces; full strength is
        reserved for icons, numbers, badges and hover states. Every accent
        TEXT colour is mixed *towards* --text-900, so it darkens in light mode
        and lightens in dark mode â€” which is why there is no separate dark
        palette below. ========================================================= */

.card.fm-card{
  /* UI_v3 — navy and gold are the page's identity; everything else supports.
     v2 ran TEAL as the identity: header wash, row marker, avatar ring, KYC
     pill and one summary card were all teal, and with the emerald Living card
     next to them the page read as a green sheet. Green is now reserved for
     the one place it carries meaning (Living). --fm-teal is kept as an alias
     so nothing that still references it breaks, but nothing should. */
  --fm-navy:#1b3358;
  --fm-royal:#3b6fb8;
  --fm-gold:#b8912f;
  --fm-lavender:#7d70b5;
  --fm-coral:#c9705f;
  --fm-emerald:#2e8b62;   /* Living, and nowhere else */
  --fm-cream:#fdf8ec;
  --fm-teal:var(--fm-gold);
  --fm-amber:var(--fm-gold);
  --fm-line:color-mix(in srgb, var(--border) 85%, transparent);
  --fm-line-soft:color-mix(in srgb, var(--border) 52%, transparent);

  /* The card owns no padding of its own â€” the header band, the summary strip
     and the list each set their own, so the bands can run edge to edge. */
  padding:0;
  overflow:hidden;
  border-radius:18px;
  border:1px solid var(--fm-line);
  /* A warm ground rather than plain white, faint enough that the row cards
     still read as the lit surfaces sitting on top of it. */
  background:
    radial-gradient(66% 44% at 100% 100%, color-mix(in srgb, var(--fm-lavender) 6%, transparent) 0%, transparent 66%),
    radial-gradient(58% 36% at 0% 92%, color-mix(in srgb, var(--fm-navy) 4%, transparent) 0%, transparent 66%),
    linear-gradient(178deg,
      color-mix(in srgb, var(--fm-cream) 62%, var(--card)) 0%,
      color-mix(in srgb, var(--fm-cream) 22%, var(--card)) 46%,
      var(--card) 88%);
  box-shadow:0 1px 2px rgba(15,31,51,.04), 0 20px 44px -30px rgba(15,31,51,.34);
}

/* --- 1. Header band: a tinted plate rather than a plain row of text --- */
.fm-intro{
  position:relative;
  overflow:hidden;
  display:flex;
  align-items:flex-start;
  gap:22px;
  flex-wrap:wrap;
  padding:24px 28px 22px;
  border-bottom:1px solid var(--fm-line);
  background:
    radial-gradient(115% 165% at 0% 0%, color-mix(in srgb, var(--fm-navy) 11%, transparent) 0%, transparent 56%),
    radial-gradient(95% 150% at 100% 0%, color-mix(in srgb, var(--gold-500) 13%, transparent) 0%, transparent 54%),
    linear-gradient(180deg, color-mix(in srgb, var(--fm-cream) 72%, var(--card)) 0%, var(--card) 100%);
}
/* Family & Estate header art: the module's own family banner, anchored to the
   right of the band and dissolved into it with a horizontal mask so the
   eyebrow, title, subtitle and count keep a clean ground to read against.
   Purely decorative - pointer-events:none, and it paints beneath
   .fm-intro-lead and the CTA (both z-index 1), so nothing on the band moves
   or changes behaviour. Sized to cover the box rather than to its own
   aspect ratio, so the art's left edge lands exactly where the mask is still
   transparent and no seam can show. */
.fm-card .fm-intro::before{
  content:'';
  position:absolute;
  top:0;right:0;bottom:0;
  width:min(58%, 540px);
  background:url('../assets/family-members-header.png') right center / cover no-repeat;
  /* A blue-and-gold banner over a cream band, so it is held well back and fed
     in gradually from the left - the same treatment .bs-kpi-art and the
     WhatsApp Sent card art already use. */
  opacity:.5;
  -webkit-mask-image:linear-gradient(90deg, transparent 0%, rgba(0,0,0,.30) 30%, #000 72%);
          mask-image:linear-gradient(90deg, transparent 0%, rgba(0,0,0,.30) 30%, #000 72%);
  pointer-events:none;
  z-index:0;
}
/* Dark mode has less headroom before the art starts competing with the text. */
:root[data-theme="dark"] .fm-card .fm-intro::before{opacity:.3;}
/* Past the wrap point the CTA drops beneath the text and the band shortens, so
   the art narrows with it - then steps aside entirely on phones rather than
   crowding the title. */
@media (max-width:860px){
  .fm-card .fm-intro::before{width:min(64%, 340px);opacity:.36;}
}
@media (max-width:620px){
  .fm-card .fm-intro::before{display:none;}
}
/* Heritage motif: one faint gold arc off the top-right, clipped by the band. */
.fm-intro::after{
  content:'';
  position:absolute;
  right:-64px;top:-88px;
  width:210px;height:210px;
  border-radius:50%;
  border:1px solid color-mix(in srgb, var(--gold-500) 18%, transparent);
  box-shadow:0 0 0 18px color-mix(in srgb, var(--gold-500) 4%, transparent);
  pointer-events:none;
}
.fm-intro-lead{position:relative;z-index:1;display:flex;align-items:flex-start;gap:15px;flex:1 1 300px;min-width:0;}
/* Identity mark for the page â€” the same teal the Members tab carries. */
.fm-intro-badge{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:46px;height:46px;
  flex-shrink:0;
  border-radius:14px;
  /* Navy body with a gold rim light and a gold mark: the module's two core
     colours on one object, which is what sets the page's key. */
  color:var(--gold-400);
  background:linear-gradient(145deg, #2c4a75 0%, var(--fm-navy) 52%, #101f36 100%);
  box-shadow:0 8px 18px -10px rgba(12,24,42,.85),
             inset 0 1px 0 rgba(255,255,255,.22),
             0 0 0 1px color-mix(in srgb, var(--gold-500) 34%, transparent);
}
.fm-intro-badge svg{display:block;}
.fm-intro-text{flex:1 1 auto;min-width:0;}
/* Eyebrow â†’ title â†’ subtitle â†’ count: four distinct weights, so the block
   reads as a hierarchy instead of a paragraph. */
.fm-eyebrow{
  font-size:10.6px;
  font-weight:700;
  letter-spacing:.14em;
  text-transform:uppercase;
  color:color-mix(in srgb, var(--fm-teal) 62%, var(--text-900));
}
.fm-title{
  margin:5px 0 0;
  font-size:23px;
  font-weight:700;
  letter-spacing:-.022em;
  color:var(--text-900);
  line-height:1.2;
}
.fm-subtitle{
  margin:6px 0 0;
  font-size:13.6px;
  font-weight:450;
  color:var(--text-600);
  line-height:1.55;
}
/* The count is a fact about the page, so it sits in a quiet bordered chip
   rather than competing with the title. */
.fm-count{
  display:inline-flex;
  align-items:center;
  gap:7px;
  margin-top:13px;
  padding:5px 12px;
  border:1px solid color-mix(in srgb, var(--fm-teal) 24%, transparent);
  border-radius:20px;
  background:color-mix(in srgb, var(--fm-teal) 9%, var(--card));
  font-size:11.8px;
  font-weight:600;
  letter-spacing:.03em;
  color:color-mix(in srgb, var(--fm-teal) 55%, var(--text-900));
}
.fm-count::before{
  content:'';
  width:6px;height:6px;
  border-radius:50%;
  background:var(--fm-teal);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--fm-teal) 18%, transparent);
}
.btn.fm-cta{
  /* Lifted above the header art (.fm-intro::before) with the text block. */
  position:relative;
  z-index:1;
  flex-shrink:0;
  display:inline-flex;
  align-items:center;
  gap:8px;
  padding:11px 20px;
  border-radius:11px;
  font-size:13.4px;
  font-weight:600;
  letter-spacing:.01em;
  box-shadow:0 2px 4px rgba(15,31,51,.08), 0 10px 22px -14px color-mix(in srgb, var(--gold-500) 90%, transparent);
  transition:transform .2s cubic-bezier(.22,1,.36,1), box-shadow .2s ease-out, filter .2s ease-out;
}
.btn.fm-cta:hover{
  transform:translateY(-2px);
  box-shadow:0 4px 8px rgba(15,31,51,.10), 0 14px 28px -12px color-mix(in srgb, var(--gold-500) 95%, transparent);
}
.btn.fm-cta:active{transform:translateY(0);}

/* --- 2. Summary cards: four accents, one shape --- */
.fm-summary{
  display:grid;
  grid-template-columns:repeat(4, minmax(0,1fr));
  gap:14px;
  padding:22px 28px;
  border-bottom:1px solid var(--fm-line);
  background:linear-gradient(180deg,
    color-mix(in srgb, var(--fm-cream) 55%, var(--card)) 0%,
    color-mix(in srgb, var(--subtle-bg) 18%, var(--card)) 100%);
}
.fm-stat{
  --fm-accent:var(--fm-royal);
  position:relative;
  display:flex;
  align-items:center;
  gap:13px;
  min-width:0;
  padding:15px 16px;
  border:1px solid color-mix(in srgb, var(--fm-accent) 20%, var(--fm-line));
  border-radius:14px;
  overflow:hidden;
  background:
    radial-gradient(125% 135% at 100% 0%, color-mix(in srgb, var(--fm-accent) 12%, transparent) 0%, transparent 62%),
    linear-gradient(180deg, color-mix(in srgb, var(--fm-accent) 6%, var(--card)) 0%, var(--card) 100%);
  box-shadow:0 1px 2px rgba(15,31,51,.04);
  transition:transform .22s cubic-bezier(.22,1,.36,1), box-shadow .22s ease-out, border-color .22s ease-out;
}
/* Keyed on data-accent rather than nth-child so the four keep their colours
   whatever order or subset renderMembers() emits. */
.fm-stat[data-accent="total"]{--fm-accent:var(--fm-royal);}
.fm-stat[data-accent="living"]{--fm-accent:var(--fm-emerald);}
.fm-stat[data-accent="kyc-done"]{--fm-accent:var(--fm-gold);}
.fm-stat[data-accent="kyc-pending"]{--fm-accent:var(--fm-coral);}
/* Hairline of accent along the top edge â€” the card's colour signature. */
.fm-stat::before{
  content:'';
  position:absolute;
  left:0;right:0;top:0;
  height:3px;
  background:linear-gradient(90deg, var(--fm-accent) 0%, color-mix(in srgb, var(--fm-accent) 22%, transparent) 100%);
  opacity:.9;
  transition:opacity .22s ease-out;
}
.fm-stat-ico{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:38px;height:38px;
  flex-shrink:0;
  border-radius:12px;
  color:var(--fm-accent);
  background:color-mix(in srgb, var(--fm-accent) 13%, transparent);
  border:1px solid color-mix(in srgb, var(--fm-accent) 20%, transparent);
  transition:transform .22s cubic-bezier(.22,1,.36,1);
}
.fm-stat-ico svg{display:block;}
.fm-stat-body{min-width:0;}
.fm-stat-value{
  display:block;
  font-size:26px;
  font-weight:700;
  line-height:1.05;
  letter-spacing:-.025em;
  font-variant-numeric:tabular-nums;
  color:color-mix(in srgb, var(--fm-accent) 52%, var(--text-900));
}
.fm-stat-label{
  display:block;
  margin-top:5px;
  font-size:11px;
  font-weight:600;
  letter-spacing:.07em;
  text-transform:uppercase;
  color:var(--text-400);
}
@media (hover:hover) and (pointer:fine){
  .fm-stat:hover{
    transform:translateY(-2px);
    border-color:color-mix(in srgb, var(--fm-accent) 40%, transparent);
    box-shadow:0 12px 26px -16px color-mix(in srgb, var(--fm-accent) 90%, transparent);
  }
  .fm-stat:hover .fm-stat-ico{transform:scale(1.06);}
}

/* --- 3. Member list --- */
/* Own wrapper rather than the global .table-wrap: that class carries the
   staggered row-in animation whose `both` fill leaves a transform on every
   <tr>, which would fight the row hover lift below. */
.fm-list-wrap{overflow-x:auto;padding:18px 28px 26px;}
/* border-spacing (not collapse) is what lets each row read as its own card:
   the cells keep their own borders and the 10px gutter separates the rows. */
.fm-table{
  width:100%;
  border-collapse:separate;
  border-spacing:0 10px;
  font-size:13.4px;
}
.fm-table th{
  text-align:left;
  font-size:10.6px;
  font-weight:700;
  text-transform:uppercase;
  letter-spacing:.1em;
  color:var(--text-400);
  padding:0 13px 4px;
  white-space:nowrap;
}
/* Needs the .fm-table qualifier to outrank the `th{text-align:left}` above. */
.fm-table th.fm-th-actions{text-align:right;}
.fm-table td{
  padding:14px 13px;
  vertical-align:middle;
  background:linear-gradient(180deg, color-mix(in srgb, var(--subtle-bg) 26%, var(--card)) 0%, var(--card) 100%);
  border-top:1px solid var(--fm-line-soft);
  border-bottom:1px solid var(--fm-line-soft);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 45%, transparent);
  transition:background-color .2s ease-out, border-color .2s ease-out;
}
.fm-table td:first-child{
  border-left:1px solid var(--fm-line-soft);
  border-radius:14px 0 0 14px;
}
.fm-table td:last-child{
  border-right:1px solid var(--fm-line-soft);
  border-radius:0 14px 14px 0;
}

/* --- 4. Row interaction: the card warms and its accent edge grows in --- */
.fm-row{
  cursor:pointer;
  transition:transform .2s cubic-bezier(.22,1,.36,1);
}
.fm-row td:first-child{position:relative;}
.fm-row td:first-child::before{
  content:'';
  position:absolute;
  left:0;top:12px;bottom:12px;
  width:3px;
  border-radius:0 3px 3px 0;
  background:linear-gradient(180deg, var(--fm-navy) 0%, var(--gold-500) 100%);
  opacity:0;
  transition:opacity .2s ease-out, top .2s ease-out, bottom .2s ease-out;
}
@media (hover:hover) and (pointer:fine){
  .fm-row:hover{transform:translateY(-1px);}
  .fm-row:hover td{
    background:linear-gradient(180deg, color-mix(in srgb, var(--fm-teal) 8%, var(--card)) 0%, color-mix(in srgb, var(--fm-teal) 3%, var(--card)) 100%);
    border-color:color-mix(in srgb, var(--fm-teal) 26%, transparent);
  }
  .fm-row:hover td:first-child::before{opacity:1;top:6px;bottom:6px;}
  .fm-row:hover .avatar.fm-avatar{
    transform:translateY(-1px);
    box-shadow:inset 0 1px 0 rgba(255,255,255,.32),
               inset 0 -3px 7px rgba(6,14,28,.42),
               0 0 0 1px color-mix(in srgb, var(--gold-500) 55%, transparent),
               0 0 0 5px color-mix(in srgb, var(--gold-500) 18%, transparent),
               0 7px 16px -8px rgba(12,24,42,.7);
  }
}
.fm-row:focus-visible{outline:none;}
.fm-row:focus-visible td{
  background:color-mix(in srgb, var(--fm-teal) 7%, var(--card));
  border-color:color-mix(in srgb, var(--gold-500) 55%, transparent);
}
.fm-row:focus-visible td:first-child::before{opacity:1;}

/* --- 5. Avatar + name block (the name is the row's visual anchor) --- */
.fm-member{display:flex;align-items:center;gap:13px;min-width:0;}
.avatar.fm-avatar{
  width:42px;height:42px;
  font-size:13.5px;
  font-weight:700;
  letter-spacing:.02em;
  border-radius:13px;
  /* Modelled rather than flat: a lit upper-left, a navy body, a deep lower
     corner, a gloss along the top edge and a hairline gold rim. */
  background:
    radial-gradient(120% 110% at 28% 22%, #40679b 0%, transparent 58%),
    linear-gradient(150deg, #2a4a78 0%, var(--fm-navy) 55%, #112340 100%);
  /* Soft accent ring â€” the row's one piece of decoration. */
  box-shadow:inset 0 1px 0 rgba(255,255,255,.28),
             inset 0 -3px 7px rgba(6,14,28,.4),
             0 0 0 1px color-mix(in srgb, var(--gold-500) 30%, transparent),
             0 0 0 4px color-mix(in srgb, var(--gold-500) 10%, transparent),
             0 4px 10px -6px rgba(12,24,42,.6);
  transition:box-shadow .22s ease-out, transform .22s cubic-bezier(.22,1,.36,1);
}
.avatar.fm-avatar.avatar-photo{background:var(--subtle-bg);}
.fm-member-text{min-width:0;}
.fm-name{
  font-size:15px;
  font-weight:700;
  letter-spacing:-.012em;
  color:var(--text-900);
  line-height:1.3;
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}
.fm-meta{
  margin-top:3px;
  font-size:11.6px;
  font-weight:500;
  letter-spacing:.015em;
  color:var(--text-400);
  white-space:nowrap;
  overflow:hidden;
  text-overflow:ellipsis;
}
.fm-meta-sep{margin:0 6px;opacity:.55;}

/* --- 6. Data columns: three deliberate weights, never one flat grey --- */
/* Relationship carries real meaning on this page, so it reads at body weight
   in a lavender-leaning ink rather than as muted metadata. */
.fm-cell-relation{
  max-width:190px;
  font-size:13.2px;
  font-weight:600;
  line-height:1.4;
  letter-spacing:.005em;
  color:color-mix(in srgb, var(--fm-lavender) 34%, var(--text-600));
}
.fm-cell-quiet{color:var(--text-600);font-size:12.9px;font-weight:500;}
.fm-cell-dob{font-variant-numeric:tabular-nums;letter-spacing:.01em;white-space:nowrap;}
/* Both of these clamp rather than wrap: the row's height is set by the name
   block, and an address-length email must not be allowed to double it. */
.fm-cell-email{max-width:180px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--text-400);}
.fm-cell-updated{white-space:nowrap;}
.fm-upd-date{
  font-size:12.4px;
  font-weight:600;
  color:var(--text-600);
  font-variant-numeric:tabular-nums;
}
.fm-upd-time{font-size:11.2px;font-weight:500;color:var(--text-400);margin-top:2px;}

/* --- 7. Status / KYC badges: soft fill, hairline border, bold label --- */
.fm-pill{
  display:inline-flex;
  align-items:center;
  gap:6px;
  padding:5px 11px;
  border:1px solid transparent;
  border-radius:20px;
  font-size:11.2px;
  font-weight:700;
  letter-spacing:.025em;
  white-space:nowrap;
}
.fm-dot{
  width:6px;height:6px;
  border-radius:50%;
  background:currentColor;
  flex-shrink:0;
  box-shadow:0 0 0 3px color-mix(in srgb, currentColor 18%, transparent);
}
.fm-pill-living{
  background:color-mix(in srgb, var(--fm-emerald) 11%, var(--card));
  border-color:color-mix(in srgb, var(--fm-emerald) 26%, transparent);
  color:color-mix(in srgb, var(--fm-emerald) 58%, var(--text-900));
}
.fm-pill-deceased{
  background:color-mix(in srgb, var(--fm-lavender) 10%, var(--card));
  border-color:color-mix(in srgb, var(--fm-lavender) 24%, transparent);
  color:color-mix(in srgb, var(--fm-lavender) 50%, var(--text-900));
}
.fm-pill-kyc{
  background:color-mix(in srgb, var(--fm-teal) 11%, var(--card));
  border-color:color-mix(in srgb, var(--fm-teal) 26%, transparent);
  color:color-mix(in srgb, var(--fm-teal) 58%, var(--text-900));
}
/* Coral, not amber: amber now reads as "KYC held" everywhere else on the
   page, so the missing state needs its own warm colour to stay distinct. */
.fm-pill-none{
  background:color-mix(in srgb, var(--fm-coral) 11%, var(--card));
  border-color:color-mix(in srgb, var(--fm-coral) 28%, transparent);
  color:color-mix(in srgb, var(--fm-coral) 55%, var(--text-900));
}
.fm-pill svg{display:block;flex-shrink:0;}

/* --- 8. Row actions: KYC / Edit / Delete, each with its own accent --- */
.fm-cell-actions{width:1%;white-space:nowrap;}
.fm-actions{display:flex;gap:6px;justify-content:flex-end;align-items:center;}
.fm-act{
  --fm-act-accent:var(--fm-royal);
  display:inline-flex;
  align-items:center;
  gap:5px;
  font-family:inherit;
  font-size:12.1px;
  font-weight:600;
  letter-spacing:.015em;
  color:var(--text-600);
  background:color-mix(in srgb, var(--subtle-bg) 50%, var(--card));
  border:1px solid var(--fm-line);
  border-radius:10px;
  padding:6px 10px;
  cursor:pointer;
  transition:background-color .18s ease-out, border-color .18s ease-out,
             color .18s ease-out, transform .18s cubic-bezier(.22,1,.36,1),
             box-shadow .18s ease-out;
}
.fm-act svg{display:block;flex-shrink:0;opacity:.85;}
.fm-act-kyc{--fm-act-accent:var(--fm-teal);}
.fm-act-edit{--fm-act-accent:var(--fm-royal);}
.fm-act-del{--fm-act-accent:var(--red);}
.fm-act:hover{
  color:color-mix(in srgb, var(--fm-act-accent) 62%, var(--text-900));
  background:color-mix(in srgb, var(--fm-act-accent) 11%, var(--card));
  border-color:color-mix(in srgb, var(--fm-act-accent) 34%, transparent);
  transform:translateY(-1px);
  box-shadow:0 6px 14px -9px color-mix(in srgb, var(--fm-act-accent) 90%, transparent);
}
.fm-act:hover svg{opacity:1;}
.fm-act:active{transform:translateY(0);}
.fm-act:focus-visible{
  outline:2px solid color-mix(in srgb, var(--fm-act-accent) 65%, transparent);
  outline-offset:1px;
}
/* Delete is icon-only and stays neutral at rest, so it never shouts across a
   list of family members â€” the red is revealed on approach. */
.fm-act-icon{
  justify-content:center;
  padding:6px 8px;
  color:var(--text-400);
}

/* --- 9. Empty state --- */
.fm-empty{
  text-align:center;
  padding:58px 24px 54px;
  background:
    radial-gradient(70% 90% at 50% 0%, color-mix(in srgb, var(--fm-teal) 6%, transparent) 0%, transparent 70%),
    var(--card);
}
/* Three overlapping rings standing in for a family group â€” abstract and
   small on purpose (no illustration weight). */
.fm-empty-art{
  display:flex;
  justify-content:center;
  margin-bottom:22px;
}
.fm-empty-art span{
  width:34px;height:34px;
  border-radius:50%;
  border:1.5px solid color-mix(in srgb, var(--fm-teal) 34%, transparent);
}
.fm-empty-art span:nth-child(2){
  margin:0 -11px;
  width:44px;height:44px;
  align-self:center;
  border-color:color-mix(in srgb, var(--gold-500) 62%, transparent);
  box-shadow:0 0 0 5px color-mix(in srgb, var(--gold-500) 8%, transparent);
}
.fm-empty-art span:nth-child(1),
.fm-empty-art span:nth-child(3){align-self:center;}
.fm-empty-title{
  font-size:17.5px;
  font-weight:700;
  letter-spacing:-.015em;
  color:var(--text-900);
}
.fm-empty-sub{
  margin:8px auto 24px;
  max-width:380px;
  font-size:13.4px;
  font-weight:450;
  color:var(--text-600);
  line-height:1.6;
}

/* --- 10. Responsive: below 900px each row becomes a member card rather than
   a squeezed table row. Order puts Name â†’ Relationship â†’ Status â†’ KYC first,
   with the metadata following; nothing is hidden. --- */
@media (max-width:1080px){
  .fm-summary{grid-template-columns:repeat(2, minmax(0,1fr));}
}
@media (max-width:900px){
  .fm-intro{padding:20px 18px 18px;}
  .fm-summary{padding:18px;gap:12px;}
  .fm-list-wrap{overflow-x:visible;padding:16px 18px 22px;}
  .fm-table thead{
    position:absolute;width:1px;height:1px;
    overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;
  }
  .fm-table, .fm-table tbody{display:block;width:100%;border-spacing:0;}
  .fm-row{
    display:flex;
    flex-direction:column;
    gap:10px;
    padding:16px;
    margin-bottom:12px;
    border:1px solid var(--fm-line);
    border-radius:16px;
    background:linear-gradient(180deg, color-mix(in srgb, var(--subtle-bg) 26%, var(--card)) 0%, var(--card) 100%);
    box-shadow:0 1px 2px rgba(15,31,51,.04);
  }
  .fm-table td{
    display:flex;
    align-items:center;
    justify-content:space-between;
    gap:14px;
    padding:0;
    background:none;
    border:none;
    border-radius:0;
    box-shadow:none;
  }
  .fm-table td[data-label]::before{
    content:attr(data-label);
    flex-shrink:0;
    font-size:10.6px;
    font-weight:700;
    text-transform:uppercase;
    letter-spacing:.09em;
    color:var(--text-400);
  }
  .fm-cell-member{order:0;}
  .fm-cell-member::before{display:none;}
  /* The desktop clamp would leave the value floating mid-row here, where
     every other field reads label-left / value-right. */
  .fm-cell-relation{order:1;max-width:none;text-align:right;}
  .fm-table td[data-label="Status"]{order:2;}
  .fm-table td[data-label="KYC"]{order:3;}
  .fm-cell-quiet{order:4;}
  .fm-cell-updated{order:5;}
  /* Three flex children here (the ::before label, the date, the time), so
     space-between would strand the time on its own at the far edge — the
     auto margin instead absorbs the gap and keeps date+time together. */
  .fm-cell-updated > div{text-align:right;}
  .fm-cell-updated > div:first-of-type{margin-left:auto;}
  /* .fm-table td above is 0,1,1, so the td qualifier is needed to win. */
  .fm-table td.fm-cell-actions{
    order:6;
    width:auto;
    justify-content:flex-end;
    padding-top:13px;
    margin-top:3px;
    border-top:1px solid var(--fm-line-soft);
  }
  /* The accent hover marker is a desktop affordance; the card border is the
     boundary on touch. */
  .fm-row td:first-child::before{display:none;}
  .fm-name{white-space:normal;}
  .fm-cell-email{max-width:none;}
  .btn.fm-cta{width:100%;justify-content:center;}
}
@media (max-width:560px){
  .fm-summary{grid-template-columns:1fr;}
  .fm-title{font-size:20px;}
  .fm-table td{align-items:flex-start;}
  .fm-actions{flex-wrap:wrap;}
}

/* --- 11. Family & Estate tab strip (scoped by .fam-tabs from familyTree.js)
   A rounded segmented bar rather than an underlined strip: the plate carries
   the border and the inner tabs carry the colour. Each tab's accent lives in
   css/family-estate.css (keyed on data-tab); this file owns the geometry and
   the active/hover states. --- */
.tabs.fam-tabs{
  gap:5px;
  margin-bottom:22px;
  padding:6px;
  border:1px solid color-mix(in srgb, var(--border) 75%, transparent);
  border-radius:18px;
  background:
    radial-gradient(80% 200% at 0% 0%, color-mix(in srgb, var(--subtle-bg) 75%, transparent) 0%, transparent 60%),
    radial-gradient(70% 180% at 100% 100%, color-mix(in srgb, var(--gold-bg) 60%, transparent) 0%, transparent 60%),
    var(--card);
  box-shadow:0 1px 2px rgba(15,31,51,.04), 0 14px 30px -26px rgba(15,31,51,.40);
}
.fam-tabs .tab{
  position:relative;
  border-bottom:none;
  margin-bottom:0;
  border-radius:12px;
  padding:9px 15px;
  font-size:13.1px;
  font-weight:600;
  color:var(--text-600);
  transition:color .22s cubic-bezier(.22,1,.36,1),
             background-color .22s cubic-bezier(.22,1,.36,1),
             box-shadow .22s ease-out,
             transform .22s cubic-bezier(.22,1,.36,1);
}
@media (hover:hover) and (pointer:fine){
  .fam-tabs .tab:hover{
    color:color-mix(in srgb, var(--fam-accent) 58%, var(--text-900));
    background:color-mix(in srgb, var(--fam-accent) 10%, transparent);
    transform:translateY(-1px);
  }
  /* The active tab is already resolved â€” it shouldn't react to hover. */
  .fam-tabs .tab.active:hover{color:#fff;transform:none;}
}
/* Active: a navy tab tinted with its own accent, lifted off the plate, with
   the gold underline drawn open from the centre on every tab switch. */
.fam-tabs .tab.active{
  color:#fff;
  background:linear-gradient(135deg, color-mix(in srgb, var(--fam-accent) 62%, var(--navy-900)) 0%, var(--navy-900) 100%);
  box-shadow:0 8px 20px -10px color-mix(in srgb, var(--fam-accent) 95%, transparent),
             inset 0 1px 0 rgba(255,255,255,.16);
}
.fam-tabs .tab.active::before{
  content:'';
  position:absolute;
  left:15px;right:15px;bottom:6px;
  height:2px;
  border-radius:2px;
  background:linear-gradient(90deg, transparent 0%, var(--gold-400) 50%, transparent 100%);
  animation:fam-tab-underline .38s cubic-bezier(.22,1,.36,1) both;
}
@keyframes fam-tab-underline{
  from{transform:scaleX(.2);opacity:0;}
  to{transform:scaleX(1);opacity:1;}
}
.fam-tabs .tab:focus-visible{
  outline:2px solid color-mix(in srgb, var(--fam-accent) 70%, transparent);
  outline-offset:-2px;
}

/* ================= Profile tab (Family Profile UI_v3) =================
   Scoped to .pf-*. A colour-led pass over the same markup: a navy-to-royal
   gradient hero carrying the identity, completeness ring and actions, then
   a set of tinted information cards — one soft accent per category, all
   mixed off the app's existing tokens so light and dark both resolve.
   Gold stays reserved for primary actions and highlights; navy and royal
   blue carry structure; the pastels only ever appear at 5-14% strength. */
.pf-page{display:flex;flex-direction:column;gap:16px;}
.pf-page > .disclaimer-box{margin-top:0;}

/* --- Hero --- */
.card.pf-hero-card{
  padding:0;
  overflow:hidden;
  border:none;
  border-radius:18px;
  box-shadow:0 8px 28px rgba(10,18,32,.20);
}
.pf-hero{
  position:relative;
  display:flex;
  align-items:center;
  justify-content:space-between;
  gap:24px;
  flex-wrap:wrap;
  padding:32px 34px;
  background:linear-gradient(135deg, var(--navy-950) 0%, var(--navy-800) 46%, #1e4a86 100%);
  isolation:isolate;
}
/* Heritage motif: a soft gold bloom off the top-right and a pair of wide
   concentric arcs, drawn as gradients so there is no image to load and
   nothing to go stale. Deliberately faint — it should read as depth, not
   as pattern. */
.pf-hero::before{
  content:'';
  position:absolute;
  top:-58%;right:-12%;
  width:460px;height:460px;
  border-radius:50%;
  background:radial-gradient(circle at 50% 50%,
    color-mix(in srgb, var(--gold-400) 26%, transparent) 0%,
    transparent 62%);
  z-index:-1;
  pointer-events:none;
}
.pf-hero::after{
  content:'';
  position:absolute;
  right:-90px;bottom:-150px;
  width:340px;height:340px;
  border-radius:50%;
  border:1px solid color-mix(in srgb, var(--gold-400) 22%, transparent);
  box-shadow:0 0 0 26px color-mix(in srgb, var(--gold-400) 7%, transparent);
  z-index:-1;
  pointer-events:none;
}
/* A gold hairline along the very top edge of the card. */
.card.pf-hero-card::before{
  content:'';
  display:block;
  height:3px;
  background:linear-gradient(90deg,
    var(--gold-600) 0%, var(--gold-400) 34%, var(--gold-500) 62%, var(--gold-600) 100%);
}
/* --- Family Head hero (Family Head Hero UI_v1) ---
   The illustrated background — a sunrise scene and a gold sprig, both inline
   SVG — was removed on request, along with the washes that kept the identity
   block readable over it. The hero is back to .pf-hero's own navy gradient
   with the gold bloom (::before) and arcs (::after) it always had; what stays
   from that pass is the crown over the avatar and the motto, both gated on
   [data-head] so a Family Member's own Profile is unaffected. */
.pf-hero-card[data-head] .pf-hero{padding-right:38px;}
/* The motto, set as a quiet serif so it reads as an
   inscription rather than as UI copy. Decorative: aria-hidden in the markup,
   and the first thing dropped when the hero gets narrow. */
.pf-motto{
  display:flex;
  flex-direction:column;
  align-items:flex-end;
  gap:2px;
  font-family:Georgia, 'Times New Roman', serif;
  font-style:italic;
  font-size:19px;
  line-height:1.32;
  letter-spacing:.005em;
  color:rgba(255,255,255,.90);
  text-shadow:0 1px 10px rgba(8,16,28,.55);
  white-space:nowrap;
}
.pf-motto em{
  font-style:italic;
  color:var(--gold-400);
}
.pf-hero-aside{
  display:flex;
  flex-direction:column;
  align-items:flex-end;
  gap:16px;
  flex-shrink:0;
  position:relative;
  z-index:1;
}

/* Champagne crown, centred over the avatar and overlapping its top edge.
   Anchored to .pf-avatar-wrap (not to the hero), so it follows the avatar
   through every breakpoint without a second set of coordinates. */
.pf-crown{
  position:absolute;
  left:50%;
  top:-14px;
  transform:translateX(-50%);
  z-index:3;
  display:block;
  line-height:0;
  pointer-events:none;
  animation:pf-crown-glow 4.6s ease-in-out infinite;
}
.pf-crown svg{display:block;}
/* Three layered motions, each deliberately small: the whole mark breathes a
   soft gold halo, the metal drifts about 1.5px, and a highlight sweeps the
   face roughly every seven seconds. Long periods and low amplitude are what
   keep it reading as light on metal rather than as an animation. */
@keyframes pf-crown-glow{
  0%, 100%{filter:drop-shadow(0 2px 4px rgba(10,18,32,.5))
                  drop-shadow(0 0 7px color-mix(in srgb, var(--gold-400) 42%, transparent));}
  50%     {filter:drop-shadow(0 2px 4px rgba(10,18,32,.5))
                  drop-shadow(0 0 13px color-mix(in srgb, var(--gold-400) 78%, transparent));}
}
.pf-crown-art{animation:pf-crown-float 4.6s ease-in-out infinite;}
@keyframes pf-crown-float{
  0%, 100%{transform:translateY(0);}
  50%     {transform:translateY(-1.5px);}
}
.pf-crown-sheen{opacity:0;animation:pf-crown-sheen 7s ease-in-out 1.4s infinite;}
@keyframes pf-crown-sheen{
  0%, 68%  {transform:translateX(0);opacity:0;}
  73%      {opacity:.95;}
  86%, 100%{transform:translateX(58px);opacity:0;}
}
/* A soft gold halo behind the Family Head's avatar — the completeness ring
   and the crown then sit on light rather than straight on the navy. */
.pf-hero-card[data-head] .pf-avatar-wrap::before{
  content:'';
  position:absolute;
  inset:-16px;
  border-radius:50%;
  background:radial-gradient(circle at 50% 42%,
    color-mix(in srgb, var(--gold-400) 26%, transparent) 0%,
    transparent 66%);
  pointer-events:none;
  z-index:0;
}
/* z-index only. .pf-ring is already position:absolute;inset:0 — giving it
   position:relative here would drop it out of that and collapse the ring to
   an inline SVG beside the avatar. */
.pf-hero-card[data-head] .pf-ring{z-index:1;}
.pf-hero-card[data-head] .avatar.pf-avatar{position:relative;z-index:1;}
@media (prefers-reduced-motion: reduce){
  .pf-crown, .pf-crown-art, .pf-crown-sheen{animation:none;}
  .pf-crown{filter:drop-shadow(0 2px 4px rgba(10,18,32,.5))
                   drop-shadow(0 0 9px color-mix(in srgb, var(--gold-400) 55%, transparent));}
}

.pf-hero-id{display:flex;align-items:center;gap:22px;min-width:0;position:relative;z-index:1;}

/* Completeness ring wraps the avatar — the indicator costs no extra
   vertical space and the header stays one idea. */
.pf-avatar-wrap{
  position:relative;
  flex-shrink:0;
  width:88px;height:88px;
  display:flex;
  align-items:center;
  justify-content:center;
}
.pf-ring{
  position:absolute;
  inset:0;
  width:88px;height:88px;
  transform:rotate(-90deg); /* start the arc at 12 o'clock */
}
.pf-ring-track{fill:none;stroke:rgba(255,255,255,.18);stroke-width:3.5;}
.pf-ring-fill{
  fill:none;
  stroke:var(--gold-400);
  stroke-width:3.5;
  stroke-linecap:round;
  filter:drop-shadow(0 0 5px color-mix(in srgb, var(--gold-400) 55%, transparent));
  transition:stroke-dashoffset .7s cubic-bezier(.22,1,.36,1);
}
.avatar.pf-avatar{
  width:66px;height:66px;
  font-size:24px;
  font-weight:600;
  letter-spacing:.01em;
  background:linear-gradient(150deg, #24406b 0%, #16283f 100%);
  color:#fff;
  box-shadow:0 0 0 3px rgba(255,255,255,.10), 0 4px 14px rgba(0,0,0,.30);
}
.pf-identity{min-width:0;}
.pf-name{
  margin:0;
  font-size:24px;
  font-weight:700;
  letter-spacing:-.015em;
  line-height:1.2;
  color:#fff;
}
.pf-role{
  margin-top:5px;
  font-size:13.6px;
  color:rgba(255,255,255,.74);
}
.pf-badges{display:flex;flex-wrap:wrap;gap:8px;margin-top:13px;}
/* Badges re-tinted for the dark hero — the .fm-pill shapes are reused, only
   their colours are overridden inside .pf-badges. */
.pf-badges .fm-pill{
  display:inline-flex;
  align-items:center;
  gap:5px;
  backdrop-filter:blur(2px);
}
.pf-badges .fm-pill-living{background:rgba(63,191,133,.20);color:#8ff0c2;}
.pf-badges .fm-pill-deceased{background:rgba(255,255,255,.14);color:rgba(255,255,255,.82);}
.pf-badges .fm-pill-kyc{
  background:color-mix(in srgb, var(--gold-400) 24%, transparent);
  color:#f6dfa8;
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--gold-400) 34%, transparent);
}
.pf-badges .fm-pill-none{background:rgba(255,255,255,.13);color:rgba(255,255,255,.68);}
.pf-badges .fm-pill svg{flex-shrink:0;}
/* The "Profile complete / Profile N% complete" line was removed from the
   hero; its rules went with it. The completeness RING around the avatar
   (.pf-ring) is a separate element and is untouched — pfCompleteness() still
   drives it, and it still carries the percentage in its aria-label. */

/* --- Actions: gold primary, glass secondary --- */
.pf-actions{display:flex;gap:10px;flex-shrink:0;position:relative;}
.btn.pf-act{
  padding:10px 19px;
  border-radius:10px;
  font-size:13px;
  font-weight:600;
  background:rgba(255,255,255,.10);
  border:1px solid rgba(255,255,255,.22);
  color:#fff;
  transition:transform .18s ease-out, box-shadow .18s ease-out, background-color .18s ease-out;
}
.btn.pf-act:hover{
  background:rgba(255,255,255,.17);
  transform:translateY(-1px);
  box-shadow:0 4px 14px rgba(0,0,0,.22);
}
.btn.pf-act:active{transform:translateY(0);}
.btn.pf-act-primary{
  background:linear-gradient(135deg, var(--gold-400) 0%, var(--gold-500) 100%);
  border-color:transparent;
  color:#241a05;
}
.btn.pf-act-primary:hover{
  background:linear-gradient(135deg, var(--gold-400) 20%, var(--gold-500) 100%);
  box-shadow:0 5px 18px color-mix(in srgb, var(--gold-500) 45%, transparent);
}

/* --- Information cards: one soft accent per category --- */
.pf-cards{
  display:grid;
  grid-template-columns:repeat(2, minmax(0,1fr));
  gap:16px;
}
.card.pf-info-card{
  --pf-accent:var(--blue);
  position:relative;
  margin-top:0;
  padding:22px 24px;
  overflow:hidden;
  border:1px solid color-mix(in srgb, var(--pf-accent) 17%, transparent);
  border-radius:15px;
  background:
    linear-gradient(160deg,
      color-mix(in srgb, var(--pf-accent) 7%, var(--card)) 0%,
      var(--card) 58%);
  box-shadow:0 2px 10px rgba(15,31,51,.05);
  transition:border-color .2s ease-out, box-shadow .2s ease-out, transform .2s ease-out;
}
/* Accent hairline across the top of each card. */
.card.pf-info-card::before{
  content:'';
  position:absolute;
  top:0;left:0;right:0;
  height:2px;
  background:linear-gradient(90deg,
    var(--pf-accent) 0%,
    color-mix(in srgb, var(--pf-accent) 30%, transparent) 65%,
    transparent 100%);
}
@media (hover:hover) and (pointer:fine){
  .card.pf-info-card:hover{
    transform:translateY(-2px);
    border-color:color-mix(in srgb, var(--pf-accent) 34%, transparent);
    box-shadow:0 6px 20px color-mix(in srgb, var(--pf-accent) 15%, transparent);
  }
}
/* Royal blue for identity, sage-teal for contact, gold-cream for address. */
.pf-info-card[data-accent="person"]{--pf-accent:#3b6fb8;}
.pf-info-card[data-accent="contact"]{--pf-accent:#3f8f86;}
.pf-info-card[data-accent="address"]{--pf-accent:#b08a3c;}

/* Personal Details holds the most fields, so it spans the row and lays out
   three across; Contact and Address share the row beneath it. */
.pf-info-wide{grid-column:1 / -1;}
.pf-info-wide .pf-grid{grid-template-columns:repeat(3, minmax(0,1fr));}

.pf-section-title{
  display:flex;
  align-items:center;
  gap:11px;
  margin:0 0 18px;
  font-size:13px;
  font-weight:600;
  letter-spacing:.01em;
  color:var(--text-900);
  text-transform:none; /* was uppercase .section-title */
}
/* Colourful icon container — the card's accent at readable strength. */
.pf-ico{
  position:relative;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:30px;height:30px;
  flex-shrink:0;
  overflow:hidden;
  border-radius:9px;
  background:color-mix(in srgb, var(--pf-accent) 14%, transparent);
  color:var(--pf-accent);
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--pf-accent) 18%, transparent);
  transition:transform .3s cubic-bezier(.22,1,.36,1),
             background-color .3s ease-out, box-shadow .3s ease-out;
}
.pf-ico svg{
  display:block;
  transition:transform .3s cubic-bezier(.22,1,.36,1), filter .3s ease-out;
}
/* Section chips host the same modelled marks as the tab strip, so they get
   the same well treatment: an inset top highlight, a floor shade, and a
   perspective so the mark can tilt towards the light on hover rather than
   just scaling. */
.fam-module .pf-ico,
.fam-module .pf-meta-ico,
.fam-module .fam-ico,
.fam-module .fam-field-ico .fam-fico{
  perspective:170px;
  background:linear-gradient(160deg,
    color-mix(in srgb, var(--pf-accent, var(--fam-accent)) 6%, transparent) 0%,
    color-mix(in srgb, var(--pf-accent, var(--fam-accent)) 18%, transparent) 100%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent),
             inset 0 -2px 4px color-mix(in srgb, var(--pf-accent, var(--fam-accent)) 12%, transparent);
}
.fam-module .pf-ico svg,
.fam-module .pf-meta-ico svg,
.fam-module .fam-ico svg,
.fam-module .fam-field-ico .fam-fico svg{
  filter:drop-shadow(0 1px 1.5px rgba(15,31,51,.2));
}
@media (hover:hover) and (pointer:fine){
  .fam-module .card:hover .pf-ico svg,
  .fam-module .pf-meta-item:hover .pf-meta-ico svg,
  .fam-module .card:hover > .fam-sec-title .fam-ico svg{
    transform:translateY(-1px) rotateX(12deg) rotateY(-12deg) scale(1.06);
    filter:drop-shadow(0 3px 4px rgba(15,31,51,.28));
  }
}
/* Shared light sweep. One bar per icon chip, parked off to the left and
   animated across on approach — the module's whole "shimmer" vocabulary is
   this single keyframe, reused by the tab strip in css/family-estate.css so
   every icon in Family & Estate reacts the same way. */
/* ===== One animated icon chip for the whole Family & Estate module =====
   Every icon container in the module — tab chips, section headings, the KYC /
   Last Updated footer, the Family Members summary cards and page badge, the
   Family Tree scene marks, the Family Event card marks and the Estate &
   Legacy field marks — shares this behaviour: it can host the sweep, it
   transitions the same way, and its glyph scales the same amount. Each keeps
   its own size, radius and accent; only the motion is centralised, which is
   what makes the module read as one icon system rather than six. */
.fam-module .pf-ico,
.fam-module .pf-meta-ico,
.fam-module .fam-ico,
.fam-module .fam-field-ico .fam-fico,
.fam-module .fm-stat-ico,
.fam-module .fm-intro-badge,
.fam-module .tree-scene-mark,
.fam-module .tree-others-ico,
.fam-module .fe-icon,
.fam-tabs .tab-ico{
  position:relative;
  overflow:hidden;
  transition:transform .3s cubic-bezier(.22,1,.36,1),
             background-color .3s ease-out, border-color .3s ease-out,
             box-shadow .3s ease-out, color .3s ease-out;
}
.fam-module .pf-meta-ico svg,
.fam-module .fam-ico svg,
.fam-module .fam-field-ico .fam-fico svg,
.fam-module .tree-others-ico svg,
.fam-module .fe-icon svg{
  display:block;
  transition:transform .3s cubic-bezier(.22,1,.36,1);
}
@media (hover:hover) and (pointer:fine){
  /* The card is the hover target wherever a chip sits inside one; the chips
     that live in their own interactive element (tab, stat, row) are driven
     by that element's own rule elsewhere. */
  .fam-module .card:hover > .fam-sec-title .fam-ico,
  .fam-module .card:hover .fam-field-ico .fam-fico,
  .fam-module .pf-meta-item:hover .pf-meta-ico,
  .fam-module .card:hover .tree-others-ico,
  .fam-module .fe-card:hover .fe-icon{
    transform:translateY(-1px) scale(1.07);
  }
  .fam-module .card:hover > .fam-sec-title .fam-ico svg,
  .fam-module .pf-meta-item:hover .pf-meta-ico svg,
  .fam-module .fe-card:hover .fe-icon svg{transform:scale(1.06);}
  .fam-module .card:hover > .fam-sec-title .fam-ico::after,
  .fam-module .pf-meta-item:hover .pf-meta-ico::after,
  .fam-module .card:hover .tree-others-ico::after,
  .fam-module .fe-card:hover .fe-icon::after,
  .fam-module .fm-stat:hover .fm-stat-ico::after{
    animation:fam-ico-shimmer .9s ease-out;
  }
}
@media (prefers-reduced-motion: reduce){
  .fam-module .pf-meta-ico, .fam-module .fam-ico, .fam-module .fm-stat-ico,
  .fam-module .fm-intro-badge, .fam-module .tree-scene-mark,
  .fam-module .tree-others-ico, .fam-module .fe-icon{transition:none;}
  .fam-module .card:hover > .fam-sec-title .fam-ico,
  .fam-module .card:hover .fam-field-ico .fam-fico,
  .fam-module .pf-meta-item:hover .pf-meta-ico,
  .fam-module .card:hover .tree-others-ico,
  .fam-module .fe-card:hover .fe-icon{transform:none;}
}

.pf-ico::after,
.fam-module .pf-meta-ico::after,
.fam-module .fam-ico::after,
.fam-module .fm-stat-ico::after,
.fam-module .tree-others-ico::after,
.fam-module .fe-icon::after,
.fam-tabs .tab-ico::after{
  content:'';
  position:absolute;
  top:-35%;bottom:-35%;
  left:0;
  width:42%;
  pointer-events:none;
  opacity:0;
  background:linear-gradient(90deg,
    transparent 0%,
    rgba(255,255,255,.72) 50%,
    transparent 100%);
}
@keyframes fam-ico-shimmer{
  from{transform:translateX(-190%) rotate(18deg);opacity:0;}
  30%{opacity:.9;}
  to{transform:translateX(320%) rotate(18deg);opacity:0;}
}
/* Settle: a one-shot arrival, played when a chip is (re)rendered in its
   active state. Not a loop — the strip is still after it lands. */
@keyframes fam-ico-settle{
  from{transform:scale(.8) rotate(-7deg);opacity:.35;}
  to{transform:none;opacity:1;}
}
@media (hover:hover) and (pointer:fine){
  .card.pf-info-card:hover .pf-ico{
    transform:translateY(-1px) scale(1.07);
    background:color-mix(in srgb, var(--pf-accent) 20%, transparent);
    box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--pf-accent) 32%, transparent),
               0 6px 15px -9px color-mix(in srgb, var(--pf-accent) 95%, transparent);
  }
  .card.pf-info-card:hover .pf-ico svg{transform:scale(1.06);}
  .card.pf-info-card:hover .pf-ico::after{animation:fam-ico-shimmer .9s ease-out;}
}
@media (prefers-reduced-motion: reduce){
  .pf-ico, .pf-ico svg{transition:none;}
  .card.pf-info-card:hover .pf-ico,
  .card.pf-info-card:hover .pf-ico svg{transform:none;}
  .pf-ico::after, .fam-tabs .tab-ico::after,
  .fam-module .pf-meta-ico::after, .fam-module .fam-ico::after,
  .fam-module .fm-stat-ico::after, .fam-module .tree-others-ico::after,
  .fam-module .fe-icon::after{display:none;}
}
.pf-grid{
  display:grid;
  grid-template-columns:repeat(2, minmax(0,1fr));
  gap:18px 26px;
}
.pf-field{min-width:0;}
.pf-label{
  font-size:11.8px;
  font-weight:500;
  letter-spacing:.01em;
  color:var(--text-400);
  text-transform:none; /* was uppercase .kicker */
}
.pf-value{
  margin-top:3px;
  font-size:14px;
  font-weight:500;
  color:var(--text-900);
  line-height:1.45;
  overflow-wrap:anywhere;
}
/* Blank values read as a deliberate state, not a dash to decode. */
.pf-value-empty{
  font-weight:400;
  font-style:italic;
  color:var(--text-400);
}
.pf-address{
  margin:0;
  font-size:14px;
  color:var(--text-900);
  line-height:1.65;
  max-width:46ch;
}

/* --- Footer: metadata on a warm cream band --- */
/* KYC Status / Last Updated. Same card vocabulary as the information cards
   above — accent chip, label over value, hover lift — so the footer reads as
   part of the set instead of as a strip of loose text. Still one card, with
   the two facts as inset panels; nesting real .card elements here would pick
   up the global `.card + .card` stacking margin. */
.card.pf-meta{
  margin-top:0;
  display:grid;
  grid-template-columns:repeat(2, minmax(0,1fr));
  gap:12px;
  padding:14px;
  border-radius:16px;
  border:1px solid color-mix(in srgb, var(--gold-500) 20%, transparent);
  background:linear-gradient(135deg,
    color-mix(in srgb, var(--gold-bg) 72%, var(--card)) 0%,
    var(--card) 68%);
  box-shadow:0 1px 2px rgba(15,31,51,.04);
}
.pf-meta-item{
  --pf-accent:var(--gold-600);
  display:flex;
  align-items:center;
  gap:12px;
  min-width:0;
  padding:12px 14px;
  border-radius:12px;
  border:1px solid color-mix(in srgb, var(--pf-accent) 18%, transparent);
  background:color-mix(in srgb, var(--card) 80%, transparent);
  transition:transform .22s cubic-bezier(.22,1,.36,1),
             border-color .22s ease-out, box-shadow .22s ease-out;
}
.pf-meta-item[data-accent="updated"]{--pf-accent:#3f8f86;}
.pf-meta-ico{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:30px;height:30px;
  flex-shrink:0;
  border-radius:9px;
  color:var(--pf-accent);
  background:color-mix(in srgb, var(--pf-accent) 14%, transparent);
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--pf-accent) 20%, transparent);
}
.pf-meta-ico svg{display:block;}
.pf-meta-text{display:flex;flex-direction:column;gap:3px;min-width:0;}
.pf-meta-label{
  font-size:11.4px;
  font-weight:500;
  letter-spacing:.02em;
  color:var(--text-400);
}
.pf-meta-value{
  font-size:13.4px;
  font-weight:700;
  letter-spacing:-.005em;
  color:var(--text-900);
}
@media (hover:hover) and (pointer:fine){
  .pf-meta-item:hover{
    transform:translateY(-2px);
    border-color:color-mix(in srgb, var(--pf-accent) 34%, transparent);
    box-shadow:0 6px 18px -12px color-mix(in srgb, var(--pf-accent) 95%, transparent);
  }
}
@media (prefers-reduced-motion: reduce){
  .pf-meta-item{transition:none;}
  .pf-meta-item:hover{transform:none;}
}

@media (max-width:900px){
  .pf-cards{grid-template-columns:1fr;}
  .pf-info-wide .pf-grid{grid-template-columns:repeat(2, minmax(0,1fr));}
}
@media (max-width:720px){
  .pf-hero{padding:26px 20px;gap:18px;}
  .pf-hero-id{gap:16px;}
  .pf-avatar-wrap, .pf-ring{width:74px;height:74px;}
  .avatar.pf-avatar{width:56px;height:56px;font-size:20px;}
  .pf-name{font-size:20px;}
  .pf-hero::before{width:300px;height:300px;top:-46%;}
  .pf-hero::after{display:none;}
  /* The motto needs room the narrow hero does not have; the scene and the
     crown carry the same meaning without it. */
  .pf-motto{display:none;}
  .pf-hero-aside{width:100%;gap:0;}
  .pf-hero-card[data-head] .pf-hero{padding-right:20px;}
  .pf-crown{top:-9px;}
  .pf-crown svg{width:29px;height:24px;}
  .card.pf-info-card{padding:18px;}
  .card.pf-meta{padding:14px 18px;}
  /* Actions take the full row rather than crowding the name. */
  .pf-actions{width:100%;}
  .btn.pf-act{flex:1;}
}
@media (max-width:560px){
  .pf-grid, .pf-info-wide .pf-grid{grid-template-columns:1fr;gap:15px;}
  .card.pf-meta{grid-template-columns:1fr;gap:10px;}
}
@media (prefers-reduced-motion: reduce){
  .btn.pf-act, .card.pf-info-card, .pf-ring-fill{transition:none;}
  .btn.pf-act:hover, .card.pf-info-card:hover{transform:none;}
}

/* ================= Family Event tab (Family Event UI_v3) =================
   Scoped to .fe-*. Every date, bucket, countdown and action is unchanged;
   this is presentation only.

   v3 fixes two things about v2. First, colour: v2 gave birthdays coral, and
   since most events are birthdays the whole page went pink. Birthdays now
   take royal gold (the brand's own celebration colour), custom events take
   lavender, and coral is held back for the one thing that should interrupt â€”
   an event happening today. Navy carries structure.

   Second, layering: v2 was one warm sheet. There are now three surfaces â€”
   a cool neutral outer ground, a warm cream header plate, and lit cream
   cards â€” separated by a deliberate border system: a hairline between
   sections, a gold rule under the header, an accent edge per card, and
   vertical rules inside each card between date, detail and status. ====== */

.card.fe-card-wrap{
  --fe-navy:#1b3358;
  --fe-gold:#b8912f;
  --fe-lav:#7d70b5;
  --fe-coral:#c9705f;
  --fe-teal:#2f8f89;
  --fe-cream:#fdf8ec;
  --fe-line:color-mix(in srgb, var(--border) 88%, transparent);
  --fe-line-soft:color-mix(in srgb, var(--border) 46%, transparent);

  position:relative;
  overflow:hidden;
  padding:0;
  border:1px solid var(--fe-line);
  border-radius:18px;
  /* The outer ground is cool and neutral; warmth is reserved for the header
     plate and the cards, so the page reads as layers rather than one wash. */
  background:
    radial-gradient(60% 40% at 100% 96%, color-mix(in srgb, var(--fe-lav) 5%, transparent) 0%, transparent 66%),
    linear-gradient(180deg,
      color-mix(in srgb, var(--subtle-bg) 42%, var(--card)) 0%,
      color-mix(in srgb, var(--subtle-bg) 22%, var(--card)) 100%);
  box-shadow:0 1px 2px rgba(15,31,51,.04), 0 22px 46px -32px rgba(15,31,51,.38);
}

/* --- 1. Header plate --- */
.fe-intro{
  position:relative;
  z-index:1;
  display:flex;
  align-items:flex-start;
  justify-content:space-between;
  gap:20px;
  flex-wrap:wrap;
  padding:26px 28px 22px;
  overflow:hidden;
  border-bottom:1px solid var(--fe-line);
  /* Warm cream plate with a navy wash left and gold right. */
  background:
    radial-gradient(105% 150% at 0% 0%, color-mix(in srgb, var(--fe-navy) 9%, transparent) 0%, transparent 58%),
    radial-gradient(85% 130% at 100% 0%, color-mix(in srgb, var(--gold-500) 13%, transparent) 0%, transparent 56%),
    linear-gradient(180deg, color-mix(in srgb, var(--fe-cream) 80%, var(--card)) 0%, color-mix(in srgb, var(--fe-cream) 40%, var(--card)) 100%);
}
/* Gold rule along the bottom of the plate: the page's first hard division. */
.fe-intro::after{
  content:'';
  position:absolute;
  left:0;right:0;bottom:-1px;
  height:2px;
  background:linear-gradient(90deg,
    color-mix(in srgb, var(--gold-500) 70%, transparent) 0%,
    color-mix(in srgb, var(--gold-500) 26%, transparent) 42%,
    transparent 92%);
}
.fe-intro-text{flex:1 1 300px;min-width:0;}
.fe-eyebrow{
  font-size:10.4px;
  font-weight:700;
  letter-spacing:.17em;
  text-transform:uppercase;
  color:color-mix(in srgb, var(--gold-600) 66%, var(--text-900));
}
.fe-title{
  margin:6px 0 0;
  font-size:24px;
  font-weight:700;
  letter-spacing:-.024em;
  line-height:1.18;
  color:var(--text-900);
}
/* The quote sits between the title and the facts, set as an inscription
   rather than as UI copy: serif, italic, and led by a short gold rule. */
.fe-quote{
  display:flex;
  align-items:baseline;
  gap:11px;
  margin:9px 0 0;
  font-family:Georgia, 'Times New Roman', serif;
  font-style:italic;
  font-size:14.2px;
  line-height:1.45;
  letter-spacing:.005em;
  color:color-mix(in srgb, var(--fe-navy) 68%, var(--text-600));
}
.fe-quote::before{
  content:'';
  flex-shrink:0;
  width:22px;height:2px;
  border-radius:2px;
  transform:translateY(-4px);
  background:linear-gradient(90deg, var(--gold-500), color-mix(in srgb, var(--gold-500) 25%, transparent));
}
.fe-subtitle{
  margin:10px 0 0;
  font-size:13.2px;
  font-weight:450;
  line-height:1.5;
  color:var(--text-600);
}
/* Legend: a key, carrying the same two marks the cards use. */
.fe-legend{display:flex;flex-wrap:wrap;gap:8px;margin-top:14px;}
.fe-legend-item{
  --k:var(--fe-gold);
  display:inline-flex;
  align-items:center;
  gap:8px;
  padding:5px 12px 5px 5px;
  border-radius:20px;
  border:1px solid color-mix(in srgb, var(--k) 26%, transparent);
  background:color-mix(in srgb, var(--card) 78%, transparent);
  font-size:11.6px;
  font-weight:600;
  letter-spacing:.01em;
  color:color-mix(in srgb, var(--k) 62%, var(--text-900));
}
.fe-legend-ico{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:24px;height:24px;
  flex-shrink:0;
  border-radius:50%;
  background:color-mix(in srgb, var(--k) 14%, var(--card));
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent);
  transition:transform .3s cubic-bezier(.22,1,.36,1);
}
.fe-legend-ico svg{
  display:block;width:15px;height:15px;
  filter:drop-shadow(0 1px 1px rgba(15,31,51,.18));
}
.fe-legend-bday{--k:var(--fe-gold);}
.fe-legend-custom{--k:var(--fe-lav);}
@media (hover:hover) and (pointer:fine){
  .fe-legend-item:hover .fe-legend-ico{transform:translateY(-1px) scale(1.1) rotate(-6deg);}
}
.btn.fe-cta{
  flex-shrink:0;
  padding:11px 20px;
  border-radius:11px;
  font-size:13.4px;
  font-weight:600;
  letter-spacing:.01em;
  box-shadow:0 2px 4px rgba(15,31,51,.08),
             0 10px 22px -14px color-mix(in srgb, var(--gold-500) 90%, transparent);
  transition:transform .2s cubic-bezier(.22,1,.36,1), box-shadow .2s ease-out;
}
.btn.fe-cta:hover{
  transform:translateY(-2px);
  box-shadow:0 4px 8px rgba(15,31,51,.10),
             0 14px 28px -12px color-mix(in srgb, var(--gold-500) 95%, transparent);
}
.btn.fe-cta:active{transform:translateY(0);}

/* --- 2. Buckets, on the timeline --- */
.fe-groups{position:relative;z-index:1;padding:24px 28px 26px;}
/* A hairline between one bucket and the next, so Today / This Week / This
   Month / Later This Year are visibly separate sections. */
.fe-group + .fe-group{
  margin-top:24px;
  padding-top:24px;
  border-top:1px solid var(--fe-line-soft);
}
/* The rail runs the height of the whole group, so its heading marker and its
   event nodes sit on one continuous line. */
.fe-group{position:relative;padding-left:34px;}
.fe-group::before{
  content:'';
  position:absolute;
  left:11px;top:14px;bottom:10px;
  width:2px;
  border-radius:2px;
  background:linear-gradient(180deg,
    color-mix(in srgb, var(--fe-navy) 34%, transparent) 0%,
    color-mix(in srgb, var(--gold-500) 34%, transparent) 55%,
    color-mix(in srgb, var(--fe-lav) 24%, transparent) 100%);
}
.fe-group-title{
  position:static;
  display:inline-flex;
  align-items:center;
  gap:9px;
  margin:0 0 14px;
  padding:6px 14px;
  border-radius:8px;
  border:1px solid color-mix(in srgb, var(--fe-navy) 20%, transparent);
  border-left:3px solid color-mix(in srgb, var(--gold-500) 70%, transparent);
  background:linear-gradient(180deg, color-mix(in srgb, var(--fe-cream) 62%, var(--card)) 0%, var(--card) 100%);
  font-size:10.6px;
  font-weight:700;
  letter-spacing:.12em;
  text-transform:uppercase;
  color:color-mix(in srgb, var(--fe-navy) 72%, var(--text-900));
  box-shadow:0 1px 2px rgba(15,31,51,.04);
}
/* Section marker: a gold lozenge seated on the rail beside the heading. */
.fe-group-title::before{
  content:'';
  position:absolute;
  left:6px;top:14px;
  width:12px;height:12px;
  border-radius:3px;
  transform:rotate(45deg);
  background:linear-gradient(135deg, var(--gold-400) 0%, var(--gold-600) 100%);
  box-shadow:0 0 0 3px var(--card), 0 0 0 4px color-mix(in srgb, var(--gold-500) 28%, transparent);
}
.fe-group-count{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  min-width:19px;height:19px;
  padding:0 6px;
  border-radius:20px;
  background:color-mix(in srgb, var(--fe-navy) 12%, transparent);
  font-size:10.4px;
  font-weight:700;
  letter-spacing:0;
  color:color-mix(in srgb, var(--fe-navy) 78%, var(--text-900));
}
.fe-list{
  position:relative;
  display:flex;
  flex-direction:column;
  gap:12px;
}

/* --- 3. Event card --- */
.fe-card{
  --fe-accent:var(--fe-lav);
  position:relative;      /* also the containing block for .rc-balloons */
  display:flex;
  align-items:stretch;
  gap:0;
  padding:14px 16px;
  border:1px solid var(--fe-line);
  border-left:3px solid color-mix(in srgb, var(--fe-accent) 62%, transparent);
  border-radius:14px;
  background:linear-gradient(170deg,
    color-mix(in srgb, var(--fe-cream) 55%, var(--card)) 0%,
    var(--card) 62%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent),
             0 1px 2px rgba(15,31,51,.04),
             0 12px 26px -22px rgba(15,31,51,.4);
  transition:border-color .22s ease-out, box-shadow .22s ease-out,
             transform .22s cubic-bezier(.22,1,.36,1);
}
/* Node on the rail, in the card's own accent. */
.fe-card::before{
  content:'';
  position:absolute;
  left:-25px;top:50%;
  width:10px;height:10px;
  margin-top:-5px;
  border-radius:50%;
  background:var(--fe-accent);
  box-shadow:0 0 0 3px var(--card),
             0 0 0 4px color-mix(in srgb, var(--fe-accent) 34%, transparent);
  transition:transform .22s cubic-bezier(.22,1,.36,1), box-shadow .22s ease-out;
}
/* Gold for birthdays (the brand's celebration colour), lavender for custom
   events. Coral is deliberately NOT a card accent â€” it is reserved for the
   Today badge, so the one urgent state is the only warm-red on the page. */
.fe-card-birthday{--fe-accent:var(--fe-gold);}
.fe-card-custom{--fe-accent:var(--fe-lav);}
@media (hover:hover) and (pointer:fine){
  .fe-card:hover{
    transform:translateY(-2px);
    border-color:color-mix(in srgb, var(--fe-accent) 34%, transparent);
    border-left-color:var(--fe-accent);
    box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 60%, transparent),
               0 2px 4px rgba(15,31,51,.05),
               0 18px 32px -20px color-mix(in srgb, var(--fe-accent) 80%, transparent);
  }
  .fe-card:hover::before{
    transform:scale(1.25);
    box-shadow:0 0 0 3px var(--card),
               0 0 0 6px color-mix(in srgb, var(--fe-accent) 42%, transparent);
  }
  .fe-card:hover .fe-icon{transform:translateY(-1px) rotate(-7deg) scale(1.08);}
  .fe-card:hover .fe-date{border-color:color-mix(in srgb, var(--fe-accent) 46%, transparent);}
}

/* Date block â€” a bordered plate with an accent band across its head, so the
   day reads as a torn-off calendar leaf rather than a tinted square. */
.fe-date{
  position:relative;
  flex-shrink:0;
  align-self:center;
  display:flex;
  flex-direction:column;
  align-items:center;
  justify-content:center;
  width:56px;height:58px;
  padding-top:5px;
  overflow:hidden;
  border-radius:12px;
  border:1px solid color-mix(in srgb, var(--fe-accent) 30%, transparent);
  background:linear-gradient(180deg, var(--card) 0%, color-mix(in srgb, var(--fe-accent) 9%, var(--card)) 100%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 60%, transparent),
             0 2px 5px -3px rgba(15,31,51,.24);
  transition:border-color .22s ease-out;
}
.fe-date::before{
  content:'';
  position:absolute;
  left:0;right:0;top:0;
  height:5px;
  background:linear-gradient(90deg,
    color-mix(in srgb, var(--fe-accent) 88%, transparent) 0%,
    color-mix(in srgb, var(--fe-accent) 52%, transparent) 100%);
}
.fe-date-day{
  font-size:19px;
  font-weight:700;
  line-height:1.05;
  letter-spacing:-.02em;
  font-variant-numeric:tabular-nums;
  color:var(--text-900);
}
.fe-date-mon{
  margin-top:2px;
  font-size:9.8px;
  font-weight:700;
  letter-spacing:.11em;
  text-transform:uppercase;
  color:color-mix(in srgb, var(--fe-accent) 62%, var(--text-400));
}
.fe-icon{
  flex-shrink:0;
  align-self:center;
  display:flex;
  align-items:center;
  justify-content:center;
  width:32px;height:32px;
  margin-left:14px;
  border-radius:11px;
  perspective:160px;
  border:1px solid color-mix(in srgb, var(--fe-accent) 22%, transparent);
  background:linear-gradient(160deg,
    color-mix(in srgb, var(--fe-accent) 6%, transparent) 0%,
    color-mix(in srgb, var(--fe-accent) 18%, transparent) 100%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent);
  transition:transform .3s cubic-bezier(.22,1,.36,1);
}
.fe-icon svg{
  display:block;width:18px;height:18px;
  filter:drop-shadow(0 1px 1.5px rgba(15,31,51,.2));
}
/* Vertical rules between the card's three zones â€” when, what, and status.
   This is the divider system at card level. */
.fe-main{
  flex:1;
  min-width:0;
  display:flex;
  flex-direction:column;
  justify-content:center;
  margin-left:15px;
  padding-left:15px;
  border-left:1px solid var(--fe-line-soft);
}
.fe-name{
  font-size:15px;
  font-weight:700;
  letter-spacing:-.012em;
  line-height:1.3;
  color:var(--text-900);
}
.fe-detail{
  margin-top:3px;
  font-size:12.4px;
  font-weight:500;
  line-height:1.45;
  color:var(--text-400);
}
.fe-sep{margin:0 6px;opacity:.55;}
.fe-side{
  display:flex;
  align-items:center;
  justify-content:flex-end;
  gap:14px;
  flex-shrink:0;
  margin-left:16px;
  padding-left:16px;
  border-left:1px solid var(--fe-line-soft);
}
/* A badge in every state, so the countdown reads as a status rather than as
   trailing text. */
.fe-count{
  padding:6px 13px;
  border-radius:8px;
  border:1px solid color-mix(in srgb, var(--fe-accent) 30%, transparent);
  background:linear-gradient(180deg, var(--card) 0%, color-mix(in srgb, var(--fe-accent) 9%, var(--card)) 100%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent);
  font-size:11.5px;
  font-weight:700;
  letter-spacing:.02em;
  white-space:nowrap;
  font-variant-numeric:tabular-nums;
  color:color-mix(in srgb, var(--fe-accent) 58%, var(--text-900));
}
/* Today is the one state that should interrupt, and the only coral on the
   page â€” which is exactly why it stands out among the golds and lavenders. */
.fe-count-today{
  border-color:color-mix(in srgb, var(--fe-coral) 52%, transparent);
  background:linear-gradient(180deg,
    color-mix(in srgb, var(--fe-coral) 12%, var(--card)) 0%,
    color-mix(in srgb, var(--fe-coral) 22%, var(--card)) 100%);
  color:color-mix(in srgb, var(--fe-coral) 66%, var(--text-900));
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 50%, transparent),
             0 3px 10px -6px color-mix(in srgb, var(--fe-coral) 95%, transparent);
}

/* --- 4. Actions --- */
.fe-actions{display:flex;gap:6px;align-items:center;}
.fe-act{
  --fe-act-accent:var(--fe-navy);
  display:inline-flex;
  align-items:center;
  justify-content:center;
  font-family:inherit;
  font-size:12.1px;
  font-weight:600;
  letter-spacing:.015em;
  color:var(--text-600);
  background:linear-gradient(180deg, var(--card) 0%, color-mix(in srgb, var(--subtle-bg) 45%, var(--card)) 100%);
  border:1px solid var(--fe-line);
  border-radius:9px;
  padding:6px 11px;
  cursor:pointer;
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent);
  transition:background-color .18s ease-out, border-color .18s ease-out,
             color .18s ease-out, transform .18s cubic-bezier(.22,1,.36,1),
             box-shadow .18s ease-out;
}
.fe-act:hover{
  color:color-mix(in srgb, var(--fe-act-accent) 68%, var(--text-900));
  background:color-mix(in srgb, var(--fe-act-accent) 10%, var(--card));
  border-color:color-mix(in srgb, var(--fe-act-accent) 36%, transparent);
  transform:translateY(-1px);
  box-shadow:0 6px 14px -9px color-mix(in srgb, var(--fe-act-accent) 90%, transparent);
}
.fe-act:active{transform:translateY(0);}
.fe-act:focus-visible{
  outline:2px solid color-mix(in srgb, var(--fe-act-accent) 65%, transparent);
  outline-offset:1px;
}
.fe-act-icon{padding:6px 8px;color:var(--text-400);}
.fe-act-icon svg{display:block;}
.fe-act-del{--fe-act-accent:var(--red);color:var(--text-400);}

/* Balloons sit behind the card content (existing .rc-balloon animation and
   the .is-birthday-row rise tuning are reused untouched). */
.fe-card > .fe-name-cell > .rc-balloons{position:absolute;inset:0;z-index:0;}
.fe-card .fe-name, .fe-card .fe-detail{position:relative;z-index:1;}

/* --- 5. Empty state --- */
.fe-empty{
  position:relative;
  z-index:1;
  text-align:center;
  padding:56px 22px 52px;
}
.fe-empty-art{
  display:flex;
  justify-content:center;
  align-items:center;
  gap:14px;
  margin-bottom:20px;
}
.fe-empty-art svg{
  width:34px;height:34px;
  filter:drop-shadow(0 3px 5px rgba(15,31,51,.16));
}
.fe-empty-title{
  font-size:17.5px;
  font-weight:700;
  letter-spacing:-.015em;
  color:var(--text-900);
}
.fe-empty-sub{
  margin:8px auto 24px;
  max-width:380px;
  font-size:13.4px;
  font-weight:450;
  line-height:1.6;
  color:var(--text-600);
}

@media (max-width:820px){
  .fe-intro{padding:22px 18px 20px;}
  .fe-groups{padding:20px 18px 22px;}
  .btn.fe-cta{width:100%;}
  .fe-title{font-size:21px;}
  .fe-quote{font-size:13.4px;}
  /* The rail costs 34px the narrow card cannot spare; each card's accent
     edge carries the same grouping cue on its own. */
  .fe-group{padding-left:0;}
  .fe-group::before{display:none;}
  .fe-group-title::before{display:none;}
  .fe-card::before{display:none;}
  /* The card reflows to two rows: date + identity on top, status beneath,
     so nothing is squeezed or clipped. */
  .fe-card{flex-wrap:wrap;align-items:center;}
  .fe-main{
    flex:1 1 auto;
    min-width:140px;
    margin-left:14px;
    padding-left:14px;
  }
  .fe-side{
    flex:1 1 100%;
    justify-content:space-between;
    margin-left:0;
    padding-left:0;
    padding-top:12px;
    margin-top:12px;
    border-left:none;
    border-top:1px solid var(--fe-line-soft);
  }
}
@media (max-width:460px){
  .fe-date{width:50px;height:52px;}
  .fe-date-day{font-size:17px;}
  .fe-icon{display:none;} /* meaning is already carried by the accent colour */
  .fe-main{margin-left:12px;padding-left:12px;}
}
@media (prefers-reduced-motion: reduce){
  .fe-card, .fe-card::before, .btn.fe-cta, .fe-act, .fe-icon,
  .fe-date, .fe-legend-ico{transition:none;}
  .fe-card:hover, .fe-card:hover::before, .btn.fe-cta:hover, .fe-act:hover,
  .fe-card:hover .fe-icon, .fe-legend-item:hover .fe-legend-ico{transform:none;}
}

@media (prefers-reduced-motion: reduce){
  .fm-row, .fm-table td, .btn.fm-cta, .fm-act, .fm-row td:first-child::before,
  .fam-tabs .tab, .fam-tabs .tab-ico, .fm-stat, .fm-stat::before, .fm-stat-ico,
  .avatar.fm-avatar{transition:none;}
  .fm-row:hover, .btn.fm-cta:hover, .fm-act:hover, .fam-tabs .tab:hover,
  .fm-stat:hover, .fm-stat:hover .fm-stat-ico,
  .fam-tabs .tab:hover .tab-ico{transform:none;}
  .fam-tabs .tab.active::before{animation:none;}
}

/* ---------- Insurance page redesign ("Web Design/Insurance page.png") ---------- */

/* Greeting hero - same dark-navy gradient the topbar/sidebar already use
   (var(--navy-900)/var(--navy-700), not a new fixed hex pair), unlike
   .vault-page-hero's deliberately-light "heirloom paper" look - this
   reference is a dark banner with a gold-accented name, not a cream one. */
.ins-hero{
  display:flex;align-items:center;gap:18px;flex-wrap:wrap;
  border-radius:16px;margin-bottom:18px;padding:20px 26px;
  background:linear-gradient(120deg, var(--navy-900) 0%, var(--navy-700) 100%);
  box-shadow:var(--shadow-md);
}
.ins-hero-icon{
  flex:none;width:52px;height:52px;border-radius:14px;
  display:flex;align-items:center;justify-content:center;font-size:24px;
  background:linear-gradient(135deg, var(--gold-400), var(--gold-600));
  box-shadow:0 8px 20px rgba(0,0,0,.25);
}
.ins-hero-text{flex:1;min-width:220px;}
.ins-hero-text h2{margin:0;font-size:21px;font-weight:800;color:#fff;}
.ins-hero-text p{margin:4px 0 0;font-size:12.8px;color:rgba(255,255,255,.65);}
.ins-hero-date{
  flex:none;display:flex;align-items:center;gap:10px;
  background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.14);
  border-radius:12px;padding:9px 16px;
}
.ins-hero-date-icon{font-size:16px;}
.ins-hero-date-label{font-size:10.5px;color:rgba(255,255,255,.55);text-transform:uppercase;letter-spacing:.05em;}
.ins-hero-date-value{font-size:13px;color:#fff;font-weight:700;}

/* Pill / segmented tabs - scoped to .ins-page so this stays this page's
   own look (the shared .tabs/.tab elsewhere in the app stays the plain
   underline style). Reuses .fam-tabs's navy-900 active-tab treatment
   (styles.css, Family & Estate) but fully rounded rather than
   rounded-top-only, since this reference's tabs read as free-floating
   pills, not folder tabs sitting on a baseline. Applies to BOTH tab
   strips inside .ins-page (the subtype filter and the Action Center),
   which already share the same plain .tabs/.tab markup. */
.ins-page .tabs{
  display:flex;flex-wrap:wrap;gap:8px;border-bottom:0;
  background:var(--subtle-bg);border-radius:14px;padding:6px;
}
.ins-page .tabs .tab{
  border-radius:10px;border-bottom:0;padding:9px 16px;
  background:transparent;color:var(--text-600);
  transition:background-color .2s ease-out, color .2s ease-out;
}
.ins-page .tabs .tab.active{
  background:var(--navy-900);color:#fff;
  box-shadow:0 2px 10px color-mix(in srgb, var(--navy-900) 20%, transparent);
}
@media (hover:hover) and (pointer:fine){
  .ins-page .tabs .tab:not(.active):hover{background:color-mix(in srgb, var(--navy-900) 8%, transparent);}
}

/* Total Insured Value + By Type card accents. */
.ins-value-card{position:relative;}
.ins-type-card .card-header{align-items:flex-start;}
.ins-type-icon{
  flex:none;width:34px;height:34px;border-radius:9px;
  display:flex;align-items:center;justify-content:center;font-size:15px;
  background:var(--subtle-bg);color:var(--text-400);
}

/* Attention Required / All Clear - bulleted list + a "View Details" jump
   into the Action Center below (id="insActionCenter", see insurance.js). */
.ins-alert-card .ins-alert-title{display:flex;align-items:center;gap:8px;}
.ins-alert-bell{font-size:15px;}
.ins-alert-list{margin:2px 0 10px;padding-left:18px;font-size:12.8px;line-height:1.7;}
.ins-alert-btn{
  background:rgba(255,255,255,.55);border:1px solid var(--amber);color:var(--amber);
  font-weight:700;
}
.ins-alert-btn:hover{background:#fff;}
.ins-alert-card.ins-alert-ok .ins-alert-list{display:none;}

/* "All Insurances" card-header icon badge, matching Bank Locker/Document
   Vault's existing convention of a small tinted icon next to a card
   title (not a new pattern - see e.g. .bl-icon-* / .vault-hero-icon). */
.ins-card-title-icon{
  flex:none;width:34px;height:34px;border-radius:9px;margin-right:10px;
  display:flex;align-items:center;justify-content:center;font-size:15px;
  background:var(--gold-bg);color:var(--gold-600);
}

/* Policy cell - a small circular icon badge ahead of the expand toggle
   and name, echoing the reference's blue shield glyph. */
.ins-policy-cell{display:flex;align-items:flex-start;gap:8px;}
.ins-policy-icon{
  flex:none;width:26px;height:26px;border-radius:50%;margin-top:1px;
  display:flex;align-items:center;justify-content:center;font-size:12px;
  background:var(--blue-bg);
}

/* Row overflow (⋮) menu - originally a port of Family Members' .fm-menu*,
   which UI_v2 dropped in favour of three labelled row buttons; Insurance
   keeps its own copy (JS half: InsurancePage.toggleRowMenu/closeRowMenus in
   js/insurance.js) under ins- prefixed classes, since this was a separate
   instance on a different page, not a shared component. */
.ins-row-menu-wrap{position:relative;display:flex;justify-content:flex-end;}
.ins-row-menu-btn{
  border:none;background:transparent;color:var(--text-400);
  font-size:18px;line-height:1;padding:4px 8px;border-radius:6px;cursor:pointer;
}
.ins-row-menu-btn:hover{background:var(--subtle-bg);color:var(--text-600);}
.ins-row-menu{
  display:none;position:fixed;z-index:80;min-width:176px;padding:5px;
  background:var(--card);border:1px solid var(--border);border-radius:9px;
  box-shadow:var(--shadow-md);
}
.ins-row-menu.open{display:block;}
.ins-row-menu-item{
  display:block;width:100%;text-align:left;font-family:inherit;font-size:13px;
  padding:8px 10px;border:none;border-radius:6px;background:transparent;
  color:var(--text-600);cursor:pointer;
}
.ins-row-menu-item:hover{background:var(--subtle-bg);}
.ins-row-menu-danger{color:var(--red);}
.ins-row-menu-danger:hover{background:color-mix(in srgb, var(--red-bg) 70%, transparent);}

/* Action Center - one wide row per policy (icon, title/detail, a "View
   Policy" jump into that record's own edit form) instead of
   reminders.js's vertical .reminder-grid cards, matching this
   reference's list-row layout for just this one section. */
.ins-action-row{
  display:flex;align-items:center;gap:14px;
  padding:14px 16px;border:1px solid var(--border);border-radius:12px;
  margin-top:10px;
}
.ins-action-row:first-child{margin-top:0;}
.ins-action-icon{
  flex:none;width:38px;height:38px;border-radius:10px;
  display:flex;align-items:center;justify-content:center;font-size:17px;
  background:var(--amber-bg);
}
.ins-action-text{flex:1;min-width:0;}
.ins-action-title{font-weight:700;font-size:13.6px;}
.ins-action-desc{font-size:12.4px;color:var(--text-400);margin-top:2px;}
.ins-action-link{
  flex:none;background:none;border:none;padding:0;font:inherit;font-size:13px;
}

@media (prefers-reduced-motion: reduce){
  .ins-page .tabs .tab{transition:none;}
}

/* ---------- Received tab — due row ----------
   The Received table is the shared table markup, so it needs no styling
   of its own beyond this: a row whose reminder has fired gets a warm
   tint and a gold edge. That edge is an inset box-shadow rather than a
   border-left so it cannot shift the cell's box and knock the row's
   columns out of line with the other tabs' tables. */
.family-diary-page tbody tr.fd-row-due{
  background:var(--amber-bg);
  box-shadow:inset 3px 0 0 var(--gold-500);
}
.fd-due-pill{margin-left:6px;}

/* ---------- Responsive ----------
   Same escalation as .bd-hero/.tk-hero: fade the artwork, then drop it
   and let the copy have the full width rather than squeezing both. */
@media(max-width:1180px){
  .fd-illustration{opacity:.85;}
  .fd-hero-copy{max-width:400px;}
  .fd-tabs-note{display:none;}
}
@media(max-width:940px){
  .fd-hero{min-height:0;}
  .fd-hero:after,.fd-illustration{display:none;}
  .fd-hero-copy{max-width:100%;padding:26px 24px 0;}
  .fd-cta-area{position:static;text-align:left;padding:20px 24px 26px;}
  .fd-quote{margin-top:16px;}
  .fd-empty-title{padding:11px 30px;}
  .fd-divider:before,.fd-divider:after{width:60px;}
}
@media(max-width:640px){
  .family-diary-page .tabs{gap:8px;}
  .family-diary-page .tab{padding:0 14px;font-size:13.2px;}
  .fd-features{gap:18px;}
  .fd-feature{width:calc(50% - 9px);}
}

/* ============================================================
   Family Diary â€” MOTION

   Same gating as the Business Details motion block above: written
   inside prefers-reduced-motion: no-preference rather than switched
   off afterwards, so a visitor who asked for no motion simply never
   gets the rules.

   Note on the illustration: it is a single image now, so the only thing
   moving in it is the float below. The elements that still carry a static
   rotate() from their layout rules are on the other two banners (the
   fanned .fd-an-card sheets), and every keyframe that moves one of those
   restates its rotation — a transform in a keyframe replaces the
   element's own transform outright rather than composing with it, so
   omitting it would snap the sheet square on the first frame.
   ============================================================ */
@media (prefers-reduced-motion: no-preference){

  /* ---------- Entry ----------
     The copy rises in reading order, the scene drifts in from the right
     alongside it. Keyed off the element, not a JS-added class, so it
     replays correctly whenever the page is genuinely remounted. */
  @keyframes fdRise{from{opacity:0;transform:translateY(14px);}to{opacity:1;transform:none;}}
  @keyframes fdFadeIn{from{opacity:0;}to{opacity:1;}}
  @keyframes fdArtIn{
    from{opacity:0;transform:translateX(30px) scale(1.03);}
    to{opacity:1;transform:none;}
  }

  .fd-hero-pill{animation:fdRise .7s cubic-bezier(.16,1,.3,1) both;}
  .fd-hero-title{animation:fdRise .7s cubic-bezier(.16,1,.3,1) .08s both;}
  .fd-hero-intro{animation:fdRise .7s cubic-bezier(.16,1,.3,1) .16s both;}
  .fd-feature{animation:fdRise .55s cubic-bezier(.16,1,.3,1) .24s both;}
  .fd-feature:nth-child(2){animation-delay:.31s;}
  .fd-feature:nth-child(3){animation-delay:.38s;}
  .fd-cta-area{animation:fdRise .7s cubic-bezier(.16,1,.3,1) .28s both;}
  .fd-illustration{animation:fdArtIn 1.1s cubic-bezier(.16,1,.3,1) both;}
  .fd-corner-leaf{animation:fdFadeIn .9s ease-out .5s both;}

  /* ---------- Ambient loops ----------
     All of it slow and small. This banner sits above the notes table and
     is on screen the whole time the page is, so anything quick enough to
     catch the eye twice would become a nuisance. */

  /* The journal and pen lift and tilt as they settle, as though resting on
     air rather than riding a lift. The tilt is only 1.2deg — the artwork is
     drawn in perspective, so a larger angle fights the vanishing point it
     was rendered with and reads as the whole scene skewing. */
  .fd-journal-set{animation:fdJournalFloat 6s ease-in-out infinite alternate;}
  @keyframes fdJournalFloat{
    from{transform:translateY(0) rotate(0deg);}
    to{transform:translateY(-9px) rotate(-1.2deg);}
  }

  /* The cover heart beats continuously, like a resting pulse: a lub-dub every
     1.2s (50 bpm) with only a short pause between beats, rather than the old
     two thumps and a long rest. It still double-thumps, since an even pulse
     reads mechanical. Swells to 1.15 on the first thump and 1.1 on the
     second. This is a patch of the artwork laid over itself, so the swell
     also drags a little leather around the heart with it; much past this it
     starts to show as a moving disc. */
  .fd-journal-heart{animation:fdCoverHeart 1.2s ease-in-out infinite;}
  @keyframes fdCoverHeart{
    0%,62%,100%{transform:scale(1);}
    14%{transform:scale(1.15);}
    28%{transform:scale(1);}
    42%{transform:scale(1.1);}
  }

  /* Gold catches the light in turn — cover heart, then nib, then clip.
     Staggered well apart so it reads as light moving across the scene
     rather than three lamps blinking. */
  .fd-glint{animation:fdGlint 3.2s ease-in-out infinite;}
  .fd-glint.g2{animation-delay:1.1s;}
  .fd-glint.g3{animation-delay:2.1s;}
  @keyframes fdGlint{
    0%,70%,100%{opacity:0;transform:scale(.3) rotate(0deg);}
    12%{opacity:1;transform:scale(1.2) rotate(80deg);}
    30%{opacity:0;transform:scale(.5) rotate(140deg);}
  }

  /* Kept here, not with the artwork it was written for: .fd-an-heart on the
     All Family Notes banner and the Share Memories icon below use this too.
     A steady pulse reads mechanical, so it double-thumps then rests for most
     of the cycle. */
  @keyframes fdHeartBeat{
    0%,34%,100%{transform:scale(1);}
    8%{transform:scale(1.18);}
    16%{transform:scale(1);}
    24%{transform:scale(1.11);}
  }

  /* ---------- The three feature icons ----------
     One loop each, picked to act out what its caption says, and on three
     different periods (2.8 / 3.6 / 4.2s) so they drift out of step instead
     of pulsing as a row. Each moves the glyph, never the circle: the circle's
     transform belongs to the hover lift further down. */

  /* Share Memories — the same double-thump as the artwork's heart. */
  .fd-fi-heart .fd-fi-glyph{animation:fdHeartBeat 2.8s ease-in-out infinite;}

  /* Stay Informed — a ring travels out of the circle, like an update
     reaching everyone, with a small nod from the glyph as it goes. */
  .fd-fi-people::after{animation:fdSignalRing 3.6s ease-out infinite;}
  @keyframes fdSignalRing{
    0%{opacity:.55;transform:scale(1);}
    70%,100%{opacity:0;transform:scale(1.45);}
  }
  .fd-fi-people .fd-fi-glyph{animation:fdNod 3.6s ease-in-out infinite;}
  @keyframes fdNod{
    0%,14%,100%{transform:translateY(0);}
    6%{transform:translateY(-3px);}
  }

  /* Plan Ahead — the tick steps forward and settles back. */
  .fd-fi-shield .fd-fi-glyph{animation:fdTickAhead 4.2s ease-in-out infinite;}
  @keyframes fdTickAhead{
    0%,26%,100%{transform:translateX(0) scale(1);}
    9%{transform:translateX(3px) scale(1.16);}
    18%{transform:translateX(0) scale(1);}
  }

  /* A gold glaze crosses the banner on a loop â€” the same
     background-position sweep + mix-blend-mode:overlay technique as
     .bd-hero::before and .logo-shine. z-index sits above .fd-cta-area (4)
     so it glints off the button too, and :before is used because :after
     is already the banner's corner sweep. */
  .fd-hero::before{
    content:'';
    position:absolute;inset:0;z-index:5;
    pointer-events:none;
    background:linear-gradient(100deg,
      transparent 40%,
      rgba(255,255,255,.14) 47%,
      rgba(255,255,255,.5) 50%,
      rgba(255,255,255,.14) 53%,
      transparent 60%);
    background-size:260% 100%;
    background-repeat:no-repeat;
    background-position:-80% 0;
    mix-blend-mode:overlay;
    animation:fdHeroShine 7s ease-in-out infinite;
  }
  @keyframes fdHeroShine{
    0%       {background-position:-80% 0;}
    55%,100% {background-position:180% 0;}
  }

  /* ---------- Empty state ----------
     Same rise as the hero copy. This one is worth replaying on every tab
     change: the body genuinely swaps between My Diary / Received / All
     Family Notes, so the movement marks that the content changed. */
  .fd-divider{animation:fdRise .6s cubic-bezier(.16,1,.3,1) both;}
  .fd-empty-title{animation:fdRise .6s cubic-bezier(.16,1,.3,1) .08s both;}
  .fd-empty p{animation:fdRise .6s cubic-bezier(.16,1,.3,1) .16s both;}
  .fd-empty .btn{animation:fdRise .6s cubic-bezier(.16,1,.3,1) .24s both;}

  /* Note rows deal in on the populated tabs, capped at six: past that the
     last row would start visibly late, and a table long enough to need
     scrolling shouldn't animate rows the visitor cannot see yet. */
  .family-diary-page tbody tr{animation:fdRise .45s cubic-bezier(.16,1,.3,1) both;}
  .family-diary-page tbody tr:nth-child(2){animation-delay:.05s;}
  .family-diary-page tbody tr:nth-child(3){animation-delay:.10s;}
  .family-diary-page tbody tr:nth-child(4){animation-delay:.15s;}
  .family-diary-page tbody tr:nth-child(5){animation-delay:.20s;}
  .family-diary-page tbody tr:nth-child(n+6){animation-delay:.25s;}

  /* The Received tab's due rows are the only actionable thing in any of
     the three tables, so the pill keeps a slow halo after the entry
     stagger has settled. On the pill, not the row: putting an animation
     shorthand on tr.fd-row-due would reset the nth-child delays above
     (equal specificity, later rule) and drop those rows out of the deal-in
     sequence. Box-shadow only — nothing here changes the row's geometry. */
  .fd-due-pill{animation:fdDuePulse 2.6s ease-in-out infinite;}
  @keyframes fdDuePulse{
    0%,100%{box-shadow:0 0 0 0 rgba(192,57,43,0);}
    50%{box-shadow:0 0 0 4px rgba(192,57,43,.16);}
  }

  /* ---------- Transitions that carry the hover states below ---------- */
  .family-diary-page .tab{transition:background-color .25s ease-out, color .2s ease-out, box-shadow .25s ease-out;}
  .fd-tab-icon{transition:transform .25s cubic-bezier(.34,1.56,.64,1);}
  .fd-feature-icon{transition:transform .3s cubic-bezier(.34,1.56,.64,1), box-shadow .3s ease-out;}
  .fd-cta-area .btn,.fd-empty .btn{transition:transform .25s cubic-bezier(.16,1,.3,1), box-shadow .25s ease-out;}
}

/* Hover responses, real pointers only â€” the convention used throughout
   this stylesheet, so a touch device never latches a hover state after a
   tap. Kept outside the motion block for the same reason the Business
   Details hover block is: the states themselves are worth having even
   when motion is off; only the transitions carrying them are gated. */
@media (hover:hover) and (pointer:fine){
  .family-diary-page .tab:hover .fd-tab-icon{transform:translateY(-2px);}
  .fd-feature:hover .fd-feature-icon{transform:scale(1.09);box-shadow:0 6px 16px -6px rgba(200,153,46,.5);}
  .fd-cta-area .btn:hover,.fd-empty .btn:hover{
    transform:translateY(-2px);
    box-shadow:0 10px 20px -8px rgba(200,153,46,.6);
  }
  .family-diary-page tbody tr{transition:background-color .18s ease-out;}
  .family-diary-page tbody tr:hover{background:var(--subtle-bg);}
  /* A due row keeps its warm tint on hover — the neutral hover fill would
     otherwise read as the row losing its due state under the cursor. */
  .family-diary-page tbody tr.fd-row-due:hover{background:var(--amber-bg);}
  .fd-note-card:hover{transform:translateY(-3px);box-shadow:0 14px 26px -14px rgba(200,153,46,.55), var(--shadow);}
  .fd-note-card:hover .fd-note-icon{transform:scale(1.06);border-color:var(--gold-500);}
  .family-diary-page td .btn:hover{transform:translateY(-1px);box-shadow:0 6px 14px -6px rgba(200,153,46,.55);}
}

/* ============================================================
   Received tab — its own banner + expandable rows
   Ported from the "Received Tab" comp.

   Shares .fd-hero's shell (border, cream gradient, corner sweep, shine)
   so the two banners read as the same component with different content,
   but lays itself out with flex rather than the absolute positioning the
   default banner uses — this one has no artwork to anchor a caption
   against, and a flex row reflows to narrow screens on its own.
   ============================================================ */
.fd-hero-received{
  display:flex;align-items:center;gap:30px;
  padding:28px 40px;min-height:0;
}
.fd-rx-copy{flex:0 0 400px;max-width:400px;}
.fd-rx-right{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;gap:16px;}

/* ---------- Envelope illustration ----------
   Pure CSS, no asset — same approach as the default banner's book-and-pen
   scene, and kept warm in both themes for the same reason. */
.fd-rx-art{position:relative;width:236px;height:150px;flex:none;}
.fd-rx-env{
  position:absolute;left:34px;bottom:4px;width:168px;height:106px;
  background:linear-gradient(160deg,#f6d99b,#e6ba63);
  border:2px solid #b8862f;border-radius:9px;
  box-shadow:0 14px 22px -12px rgba(20,39,70,.5);
}
/* Sits above the envelope mouth so it reads as a letter being drawn out;
   the flap after it in source order paints over its lower half. */
.fd-rx-letter{
  position:absolute;left:15px;top:-34px;width:134px;height:88px;z-index:2;
  background:
    repeating-linear-gradient(to bottom, transparent 0 13px, #e7dcd0 13px 15px),
    #fffdf8;
  border:2px solid #d9c9a5;border-radius:5px;
  /* Cropped away entirely while the envelope is sealed. Cropping only at
     the envelope's mouth isn't enough: the flap is a downward triangle, so
     the sheet behind it stays visible through the two corner gaps either
     side — white paper showing out of a closed envelope. A full crop is
     also the honest closed state, since the sheet sits at z-index 2 over
     the envelope's face and would otherwise read as lying on top of it
     rather than inside. fdLetterReveal opens the inset from the bottom
     edge up once the flap is out of the way, which is what makes the sheet
     read as being drawn out. Declared here, not only in the keyframes, so
     the envelope still reads as sealed when motion is off and that
     animation is never declared. */
  clip-path:inset(100% 0 0 0);
}
/* ---------- What the sheet says ----------
   The one piece of real information on the artwork: who most recently
   sent this account a note, filled in by paintLatestSender() once the
   fetch lands. It rides inside .fd-rx-letter, so the crop that keeps the
   sheet inside a sealed envelope hides this too — the name arrives with
   the paper rather than before it.

   Colours are fixed rather than themed, like the rest of this scene: the
   sheet is a constant warm #fffdf8 in both themes, so a themed text token
   would go invisible on it in dark mode.

   Each line carries the paper's own colour as a background so the ruling
   behind it knocks out around the glyphs instead of striking through them.

   Note this sits inside .fd-rx-art, which is aria-hidden — the artwork is
   decorative. The same fact is in the table below, spelled out per note in
   its From column, so nothing is only available here. */
.fd-rx-note{
  position:absolute;inset:0;
  display:flex;flex-direction:column;align-items:center;justify-content:center;
  gap:2px;padding:0 7px;text-align:center;
}
.fd-rx-note-label{
  background:#fffdf8;padding:0 4px;
  font-size:7.4px;letter-spacing:.07em;text-transform:uppercase;color:#a89066;
}
.fd-rx-from{
  background:#fffdf8;padding:0 5px;max-width:100%;
  font-size:12.5px;line-height:1.2;color:#4b3a1d;
  overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
}
.fd-rx-when{
  background:#fffdf8;padding:0 4px;
  font-size:8.2px;color:#a89066;
}

/* z-index:3 is the CLOSED state — the flap covers the letter. The opening
   animation steps it down to 0 as the flap passes vertical, so the letter
   ends up in front. Stated here, not only in the keyframes, so the closed
   envelope still stacks correctly when motion is switched off and the
   animation is never declared. */
.fd-rx-flap{
  position:absolute;left:-2px;top:-2px;width:168px;height:66px;z-index:3;
  background:linear-gradient(160deg,#f2cd85,#d9a94f);
  border:2px solid #b8862f;border-radius:9px 9px 0 0;
  clip-path:polygon(0 0, 100% 0, 50% 100%);
  transform-origin:top center;
}

/* ---------- Hearts drifting off the opened envelope ----------
   Decorative only. They sit at opacity:0 and are moved entirely by the
   animation, so when prefers-reduced-motion is set — and the animation is
   therefore never declared — they stay invisible rather than freezing as
   five static glyphs stuck over the artwork. --dx gives each its own
   sideways drift so they fan out instead of rising in a column. */
.fd-rx-heart{
  position:absolute;opacity:0;pointer-events:none;
  z-index:4;font-size:14px;line-height:1;color:var(--pink);
}
.fd-rx-heart.h1{left:58px;bottom:62px;--dx:-18px;font-size:13px;}
.fd-rx-heart.h2{left:94px;bottom:74px;--dx:5px;font-size:17px;color:var(--gold-500);}
.fd-rx-heart.h3{left:128px;bottom:66px;--dx:19px;font-size:12px;}
.fd-rx-heart.h4{left:158px;bottom:56px;--dx:27px;font-size:15px;color:var(--gold-400);}
.fd-rx-heart.h5{left:76px;bottom:86px;--dx:-28px;font-size:11px;}
.fd-rx-bubble{
  position:absolute;width:34px;height:34px;border-radius:11px;
  display:flex;align-items:center;justify-content:center;font-size:15px;
  box-shadow:0 7px 13px -7px rgba(20,39,70,.45);
}
.fd-rx-bubble.b1{left:0;top:14px;background:var(--pink-bg);color:var(--pink);}
.fd-rx-bubble.b2{right:10px;top:0;background:var(--green-bg);color:var(--green);}
.fd-rx-bubble.b3{right:0;top:70px;background:var(--blue-bg);color:var(--blue);}

/* ---------- The three hub captions ----------
   Shared with the All Family Notes banner further down, which lays its
   captions out identically — the two banners are the same component with
   different content, so the row that carries them is written once. */
.fd-rx-stats,.fd-an-stats{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;width:100%;text-align:center;}
.fd-rx-ring,.fd-an-ring{
  width:56px;height:56px;border-radius:50%;margin:0 auto;
  display:flex;align-items:center;justify-content:center;font-size:22px;
  border:1px solid rgba(200,153,46,.42);
  background:radial-gradient(circle, rgba(200,153,46,.14) 0%, transparent 70%);
  box-shadow:0 0 18px -6px rgba(200,153,46,.55);
}
/* The glyph is its own element so the shakes in the motion block can move it
   while .fd-rx-ring keeps its own animation slot — the ring already runs
   fdRingPulse, and a second animation shorthand on the same element would
   replace that rather than join it. Shaking the glyph inside a steady badge
   is the better reading anyway: the bell rings, its ring does not. */
.fd-rx-glyph{display:block;line-height:1;}
/* A bell pivots where it hangs, not about its middle. */
.fd-rx-glyph.g-bell{transform-origin:50% 18%;}
/* Same split on the All Family Notes banner. Its ring carries no animation
   today, but keeping the two banners built the same way means a glow added
   there later won't have to unpick a shake first. */
.fd-an-glyph{display:block;line-height:1;}
.fd-rx-stat b,.fd-an-stat b{display:block;margin-top:8px;font-size:13.4px;color:var(--text-900);}
.fd-rx-stat p,.fd-an-stat p{margin:3px 0 0;font-size:11.6px;line-height:1.35;color:var(--text-400);}

/* ---------- Expandable row ----------
   tr[hidden] needs saying explicitly: the UA's [hidden]{display:none} is
   beaten by anything that sets display, and table rows are a common place
   for that to happen. */
.family-diary-page tr[hidden]{display:none;}
.fd-chev-cell{width:1%;text-align:right;padding-right:14px;}
.fd-chev{
  background:none;border:none;cursor:pointer;font-family:inherit;
  display:inline-block;padding:2px 6px;line-height:1;
  font-size:20px;font-weight:700;color:var(--text-600);
}
.fd-chev:hover{color:var(--gold-600);}
.fd-chev-open{transform:rotate(180deg);}
.fd-detail-row > td{padding-top:0;}
.fd-detail{
  border-left:3px solid var(--gold-500);
  background:var(--subtle-bg);border-radius:0 8px 8px 0;
  padding:12px 16px;margin-bottom:4px;
}
.fd-detail-msg{margin:0;font-size:13px;line-height:1.5;color:var(--text-600);white-space:pre-wrap;}
.fd-detail-files{margin-top:10px;display:flex;gap:8px;flex-wrap:wrap;}

/* Both flex banners collapse the same way — the copy stops sharing a row
   with the artwork, then the artwork goes entirely and the captions stack. */
@media(max-width:1100px){
  .fd-hero-received,.fd-hero-all{flex-direction:column;align-items:flex-start;gap:22px;padding:24px 26px;}
  .fd-rx-copy,.fd-an-copy{flex:none;max-width:100%;}
  .fd-rx-right,.fd-an-right{width:100%;}
}
@media(max-width:700px){
  .fd-rx-art,.fd-an-art{display:none;}
  .fd-rx-stats,.fd-an-stats{grid-template-columns:1fr;gap:14px;text-align:left;}
  .fd-rx-ring,.fd-an-ring{margin:0;}
}

/* ============================================================
   All Family Notes — its own banner

   Shares .fd-hero's shell and .fd-hero-received's flex row so all three
   tabs read as one component with different content. Its own copy because
   this tab is the family-wide archive, not your outbox: the default
   banner is written at someone about to write a note, which is the wrong
   thing to say above a browse view of what everyone has already written,
   and its Add Diary Note duplicates the one the notes panel already has.
   ============================================================ */
.fd-hero-all{
  display:flex;align-items:center;gap:30px;
  padding:28px 40px;min-height:0;
}
.fd-an-copy{flex:0 0 400px;max-width:400px;}
.fd-an-right{flex:1;min-width:0;display:flex;flex-direction:column;align-items:center;gap:16px;}

/* ---------- Pinned stack illustration ----------
   Pure CSS, no asset — the same approach as the default banner's
   book-and-pen and the Received banner's envelope. A stack rather than a
   single sheet because the stack IS this tab: everyone's notes piling up
   in one place. Sized to .fd-rx-art's box so the two banners' artwork sits
   on the same line. */
.fd-an-art{position:relative;width:236px;height:150px;flex:none;}
.fd-an-card{
  position:absolute;left:46px;top:18px;width:144px;height:112px;
  background:#fffdf8;
  border:2px solid #d9c9a5;border-radius:7px;
  box-shadow:0 12px 20px -12px rgba(20,39,70,.5);
  /* Rotating about the bottom edge fans the sheets out from where the pin
     holds them, rather than pivoting them about their own middles. */
  transform-origin:50% 100%;
}
.fd-an-card.c3{transform:rotate(-9deg);background:#fdf3dd;border-color:#e0cb9c;}
.fd-an-card.c2{transform:rotate(5deg);background:#fef8ea;border-color:#ddc99e;}
.fd-an-card.c1{z-index:2;}
/* Ruled lines on the front sheet only. The two behind read as paper from
   their edges alone, and ruling all three would turn the fan into moiré. */
.fd-an-line{
  position:absolute;left:14px;height:2px;border-radius:2px;background:#e7dcd0;
}
.fd-an-line.l1{top:26px;width:96px;}
.fd-an-line.l2{top:44px;width:112px;}
.fd-an-line.l3{top:62px;width:104px;}
.fd-an-line.l4{top:80px;width:74px;}
.fd-an-heart{
  position:absolute;right:12px;bottom:9px;
  font-size:17px;line-height:1;color:var(--gold-500);
}
/* The pin holding the stack together — above every sheet, so it reads as
   going through them rather than sitting under the top one. */
.fd-an-pin{
  position:absolute;left:112px;top:9px;z-index:3;
  width:17px;height:17px;border-radius:50%;
  background:radial-gradient(circle at 35% 32%, #f7d98f, #c8992e 70%);
  border:2px solid #a8801f;
  box-shadow:0 4px 8px -3px rgba(20,39,70,.6);
}

/* ---------- All Family Notes — panel + card grid ----------
   Ported from the "All Family Note Reference" comp. The panel reuses the
   hero's cream-and-gold shell so the two boxes on this tab read as a
   pair, and carries the same corner flourish; the cards sit on --card so
   they lift off it. Card internals are fd-* only — the buttons and pills
   inside them stay the app's shared classes. */
.fd-notes-panel{
  position:relative;
  border:1.5px solid var(--gold-500);border-radius:15px;
  background:linear-gradient(135deg, var(--gold-bg) 0%, var(--card) 55%, var(--gold-bg) 100%);
  padding:28px 28px 22px;
}
.fd-note-grid{
  display:grid;grid-template-columns:repeat(auto-fit,minmax(340px,1fr));
  gap:18px;align-items:start;
}
.fd-note-card{
  background:var(--card);border:1px solid var(--border);border-radius:13px;
  padding:16px 18px;box-shadow:var(--shadow);
  display:flex;flex-direction:column;gap:12px;
}
.fd-note-head{display:flex;gap:14px;align-items:flex-start;}
.fd-note-icon{
  flex:none;width:52px;height:52px;border-radius:11px;
  display:flex;align-items:center;justify-content:center;font-size:24px;
  background:var(--gold-bg);border:1px solid rgba(200,153,46,.3);
}
.fd-note-meta{min-width:0;}
.fd-note-title{
  margin:0 0 3px;font-size:16px;font-weight:700;color:var(--text-900);
  line-height:1.25;overflow-wrap:anywhere;
}
.fd-note-date{font-size:12.4px;color:var(--text-600);}
.fd-note-author{font-size:12.4px;color:var(--text-400);}
.fd-note-foot{
  display:flex;align-items:flex-end;justify-content:space-between;gap:14px;margin-top:auto;
}
/* Two lines then ellipsis. line-clamp rather than truncating the string
   in JS, so the full message stays in the DOM for in-page search and for
   anyone reading with a screen reader. */
.fd-note-excerpt{
  margin:0;font-size:13.2px;line-height:1.45;color:var(--text-600);min-width:0;
  display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;
}
.fd-note-actions{display:flex;gap:8px;flex:none;}
.fd-notes-foot{
  display:flex;flex-direction:column;align-items:center;gap:8px;
  margin-top:22px;position:relative;z-index:1;
}
.fd-notes-count{font-style:italic;font-size:13px;color:var(--text-600);}
@media(max-width:640px){
  .fd-notes-panel{padding:20px 16px 18px;}
  .fd-note-grid{grid-template-columns:1fr;}
  .fd-note-foot{flex-direction:column;align-items:stretch;}
  .fd-note-actions{justify-content:flex-end;}
}

/* Cards deal in like the table rows on the other two tabs, and the
   transitions carrying the hover states above. Same reduced-motion gate
   as the rest of this page's motion. */
@media (prefers-reduced-motion: no-preference){
  .fd-note-card{
    animation:fdRise .5s cubic-bezier(.16,1,.3,1) both;
    transition:transform .3s cubic-bezier(.165,.84,.44,1), box-shadow .3s ease-out;
  }
  .fd-note-card:nth-child(2){animation-delay:.07s;}
  .fd-note-card:nth-child(3){animation-delay:.14s;}
  .fd-note-card:nth-child(4){animation-delay:.21s;}
  .fd-note-card:nth-child(5){animation-delay:.28s;}
  .fd-note-card:nth-child(n+6){animation-delay:.35s;}
  .fd-note-icon{transition:transform .3s cubic-bezier(.34,1.56,.64,1), border-color .3s ease-out;}
  .fd-notes-foot{animation:fdRise .6s cubic-bezier(.16,1,.3,1) .3s both;}

  /* ---------- Received banner ---------- */
  .fd-rx-copy{animation:fdRise .7s cubic-bezier(.16,1,.3,1) both;}
  .fd-rx-art{animation:fdArtIn 1s cubic-bezier(.16,1,.3,1) both;}
  .fd-rx-stat{animation:fdRise .55s cubic-bezier(.16,1,.3,1) .2s both;}
  .fd-rx-stat:nth-child(2){animation-delay:.27s;}
  .fd-rx-stat:nth-child(3){animation-delay:.34s;}
  /* The envelope drops in, overshoots slightly and settles, then rocks on
     the ambient float for as long as the tab is open; the letter rides
     with it either way because it is a child. Two animations rather than
     one because the landing must not repeat and the float must never stop
     — the float's delay is set just past the landing's end so the two are
     never live at once and can't fight over transform, and the landing's
     `both` fill holds the resting position in the gap between them. */
  .fd-rx-env{
    animation:
      fdEnvLand 1s cubic-bezier(.34,1.4,.64,1) both,
      fdEnvFloat 5.5s ease-in-out 1.15s infinite alternate;
  }
  @keyframes fdEnvLand{
    0%{transform:translateY(-22px) rotate(-7deg) scale(.93);}
    100%{transform:translateY(0) rotate(0deg) scale(1);}
  }
  @keyframes fdEnvFloat{
    from{transform:translateY(0) rotate(0deg);}
    to{transform:translateY(-8px) rotate(-2.5deg);}
  }

  /* A warm bloom behind the envelope at the moment it opens, so the letter
     has something to emerge into rather than appearing against flat cream.
     On .fd-rx-art::before, which paints behind the envelope — it is the
     first child in tree order and everything else here is positioned at
     z-index auto. Centred with margins, not a translate, so the transform
     is left free for the scale. Declared only inside this block: with
     motion off there is no opening to punctuate and a static glow would
     just be a smudge on the artwork. */
  .fd-rx-art::before{
    content:'';
    position:absolute;left:50%;top:56%;
    width:250px;height:180px;margin:-90px 0 0 -125px;
    border-radius:50%;
    pointer-events:none;
    background:radial-gradient(ellipse at center,
      rgba(255,214,110,.8) 0,
      rgba(255,214,110,.35) 42%,
      rgba(255,214,110,0) 70%);
    opacity:0;
    animation:fdOpenGlow 1.5s ease-out .95s both;
  }
  @keyframes fdOpenGlow{
    0%{opacity:0;transform:scale(.45);}
    30%{opacity:.95;transform:scale(1);}
    100%{opacity:0;transform:scale(1.3);}
  }

  /* A gold glaze crossing the envelope face on a slow loop, the same
     background-position sweep as .fd-hero::before. Delayed past the
     opening so it doesn't compete with it, and left at z-index auto so it
     glazes the envelope body only — the letter (2) and flap (3) paint over
     it. Long tail on the keyframe so the sweep is a glint every few
     seconds rather than a strobe. */
  .fd-rx-env::after{
    content:'';
    position:absolute;inset:0;
    border-radius:9px;
    pointer-events:none;
    background:linear-gradient(100deg,
      transparent 42%,
      rgba(255,255,255,.55) 50%,
      transparent 58%);
    background-size:250% 100%;
    background-repeat:no-repeat;
    mix-blend-mode:overlay;
    animation:fdEnvShine 6.5s ease-in-out 2.2s infinite;
  }
  @keyframes fdEnvShine{
    0%       {background-position:-60% 0;}
    45%,100% {background-position:190% 0;}
  }
  /* ---------- All Family Notes banner ----------
     The stack fans open as it arrives, the pin drops in to hold it, then
     the top sheet breathes. Each sheet's keyframe restates its resting
     rotation, because a transform in a keyframe replaces the element's own
     transform outright rather than composing with it — the same reason the
     default banner's book float restates its rotate. */
  .fd-an-art{animation:fdArtIn 1s cubic-bezier(.16,1,.3,1) both;}
  .fd-an-stat{animation:fdRise .55s cubic-bezier(.16,1,.3,1) .2s both;}
  .fd-an-stat:nth-child(2){animation-delay:.27s;}
  .fd-an-stat:nth-child(3){animation-delay:.34s;}
  /* ---------- The three caption icons shake in turn ----------
     Same rhythm as the Received banner's — a short burst, a long rest, and
     1.5s between each so only one is ever going. Each burst decays rather
     than stopping dead, which is what reads as knocked rather than switched
     on. Three separate keyframes because the motions genuinely differ: a
     book rocks on its spine, people jostle sideways without turning, and a
     magnifier sweeps as though scanning a page. */
  .fd-an-glyph.g-book{animation:fdBookRock 4.6s ease-in-out infinite;}
  .fd-an-glyph.g-people{animation:fdJostle 4.6s ease-in-out 1.5s infinite;}
  .fd-an-glyph.g-find{animation:fdScanSweep 4.6s ease-in-out 3s infinite;}
  @keyframes fdBookRock{
    0%,18%,100%{transform:rotate(0deg);}
    3%{transform:rotate(-7deg);}
    7%{transform:rotate(6deg);}
    11%{transform:rotate(-4deg);}
    15%{transform:rotate(2deg);}
  }
  @keyframes fdJostle{
    0%,18%,100%{transform:translateX(0);}
    3%{transform:translateX(-3px);}
    6%{transform:translateX(3px);}
    9%{transform:translateX(-2px);}
    12%{transform:translateX(2px);}
    15%{transform:translateX(-1px);}
  }
  @keyframes fdScanSweep{
    0%,18%,100%{transform:translate(0,0) rotate(0deg);}
    4%{transform:translate(-3px,1px) rotate(-8deg);}
    9%{transform:translate(3px,-1px) rotate(8deg);}
    13%{transform:translate(-2px,1px) rotate(-4deg);}
    16%{transform:translate(1px,0) rotate(2deg);}
  }

  .fd-an-card.c3{animation:fdFanBack .9s cubic-bezier(.34,1.3,.64,1) .25s both;}
  .fd-an-card.c2{animation:fdFanMid .9s cubic-bezier(.34,1.3,.64,1) .35s both;}
  @keyframes fdFanBack{from{transform:rotate(0deg);}to{transform:rotate(-9deg);}}
  @keyframes fdFanMid{from{transform:rotate(0deg);}to{transform:rotate(5deg);}}
  /* Only the front sheet floats: drifting all three would break the fan,
     since they'd each keep their own offset instead of moving as a stack. */
  .fd-an-card.c1{animation:fdStackFloat 5.5s ease-in-out 1.2s infinite alternate;}
  @keyframes fdStackFloat{
    from{transform:translateY(0);}
    to{transform:translateY(-7px);}
  }
  .fd-an-pin{animation:fdPinPop .5s cubic-bezier(.34,1.6,.64,1) .6s both;}
  @keyframes fdPinPop{
    from{opacity:0;transform:scale(0);}
    to{opacity:1;transform:scale(1);}
  }
  /* Reuses the book heart's double-thump, delayed until the stack settles. */
  .fd-an-heart{animation:fdHeartBeat 2.8s ease-in-out 1.4s infinite;}

  /* Offset so the three chips bob out of step rather than as a block. */
  .fd-rx-bubble{animation:fdBubbleFloat 3.6s ease-in-out infinite alternate;}
  .fd-rx-bubble.b2{animation-delay:.5s;}
  .fd-rx-bubble.b3{animation-delay:1s;}
  @keyframes fdBubbleFloat{
    from{transform:translateY(0);}
    to{transform:translateY(-9px);}
  }
  /* ---------- The three hub icons shake in turn ----------
     A short burst then a long rest, and the three offset by 1.5s each so
     only one is ever going — three icons rattling at once reads as an error
     state rather than as attention. Each burst is a decaying oscillation:
     the swings get smaller instead of stopping dead, which is what makes it
     look like something was knocked rather than switched on.

     On the glyph, never the ring — see .fd-rx-glyph above for why. */
  .fd-rx-glyph.g-bell{animation:fdBellRing 4.6s ease-in-out infinite;}
  .fd-rx-glyph.g-doc{animation:fdPaperShake 4.6s ease-in-out 1.5s infinite;}
  .fd-rx-glyph.g-cal{animation:fdAlarmShake 4.6s ease-in-out 3s infinite;}
  @keyframes fdBellRing{
    0%,18%,100%{transform:rotate(0deg);}
    3%{transform:rotate(14deg);}
    6%{transform:rotate(-11deg);}
    9%{transform:rotate(8deg);}
    12%{transform:rotate(-5deg);}
    15%{transform:rotate(2deg);}
  }
  @keyframes fdPaperShake{
    0%,18%,100%{transform:translateX(0) rotate(0deg);}
    3%{transform:translateX(-2px) rotate(-6deg);}
    6%{transform:translateX(2px) rotate(6deg);}
    9%{transform:translateX(-2px) rotate(-4deg);}
    12%{transform:translateX(1px) rotate(3deg);}
    15%{transform:translateX(-1px) rotate(-1deg);}
  }
  @keyframes fdAlarmShake{
    0%,18%,100%{transform:rotate(0deg);}
    3%{transform:rotate(-9deg);}
    6%{transform:rotate(9deg);}
    9%{transform:rotate(-6deg);}
    12%{transform:rotate(4deg);}
    15%{transform:rotate(-2deg);}
  }

  .fd-rx-ring{animation:fdRingPulse 3.4s ease-in-out infinite alternate;}
  .fd-rx-stat:nth-child(2) .fd-rx-ring{animation-delay:.6s;}
  .fd-rx-stat:nth-child(3) .fd-rx-ring{animation-delay:1.2s;}
  @keyframes fdRingPulse{
    from{box-shadow:0 0 14px -8px rgba(200,153,46,.5);}
    to{box-shadow:0 0 26px -4px rgba(200,153,46,.75);}
  }

  /* Detail rows are revealed by the chevron, not dealt in with the table,
     so they sit out the row stagger — otherwise they'd consume nth-child
     slots and hold a fdRise "both" fill while hidden. */
  .family-diary-page tbody tr.fd-detail-row{animation:none;}
  .fd-chev{transition:transform .25s cubic-bezier(.34,1.56,.64,1), color .2s ease-out;}

  /* The panel wipes down from its top edge rather than just fading: the
     row above it stays put and the text arrives from under it, which
     reads as the row opening rather than something appearing on top of
     the table. Applied by toggleReceivedRow() on each open, not sitting
     on .fd-detail permanently, so it replays every time. */
  .fd-detail-in{animation:fdDetailIn .3s cubic-bezier(.16,1,.3,1) both;}
  @keyframes fdDetailIn{
    from{opacity:0;transform:translateY(-7px);clip-path:inset(0 0 100% 0);}
    to{opacity:1;transform:none;clip-path:inset(0 0 0 0);}
  }
  /* Carries the open-row tint below. Declared here rather than in the
     hover block so it works on touch, where there is no hover. */
  .family-diary-page tbody tr{transition:background-color .18s ease-out;}

  /* The letter drifts against the envelope instead of riding it rigidly —
     6.5s against the envelope's 5.5s, so the two never settle into a
     visibly repeating pair. */
  /* Two animations on one element: the crop opening once the envelope is
     open, and the slow drift that runs forever after. A clip-path reveal
     rather than sliding the sheet up, because clip-path is resolved in the
     element's own box — a translate would carry the crop edge along with
     the paper instead of leaving it parked at the envelope's mouth.
     The 1.05s delay is deliberate: fdFlapOpen runs .55s→1.45s and passes
     vertical at its halfway point (1.0s), which is where it drops behind
     the letter. Starting just after that means the sheet only ever emerges
     from an open envelope — while the flap is still coming up there is
     nothing to see. Revealing from the bottom edge upward (inset shrinking
     from the top) makes it rise out of the envelope rather than fade in. */
  .fd-rx-letter{
    animation:
      fdLetterReveal .75s cubic-bezier(.16,1,.3,1) 1.05s both,
      fdLetterOut 6.5s ease-in-out infinite alternate;
  }
  @keyframes fdLetterReveal{
    from{clip-path:inset(100% 0 0 0);}
    to{clip-path:inset(0 0 0 0);}
  }
  @keyframes fdLetterOut{
    from{transform:translateY(0) rotate(0deg);}
    to{transform:translateY(-7px) rotate(1.5deg);}
  }

  /* The flap swings back once, shortly after the artwork has drifted in,
     and stays open: the story is "a message arrived and has been opened",
     not an envelope that keeps resealing itself. rotateX needs the
     perspective on the parent or the flap just squashes flat instead of
     tipping away. z-index steps at the halfway point, where the flap
     passes vertical and should fall behind the letter. */
  .fd-rx-env{perspective:620px;}
  .fd-rx-flap{animation:fdFlapOpen .9s cubic-bezier(.34,1.15,.64,1) .55s both;}
  @keyframes fdFlapOpen{
    0%{transform:rotateX(0deg);z-index:3;}
    49%{z-index:3;}
    50%{z-index:0;}
    100%{transform:rotateX(-162deg);z-index:0;}
  }

  /* Staggered so one heart is always mid-flight rather than all five
     pulsing together. Starts once the letter is out (fdLetterReveal ends
     at 1.8s), so the hearts read as coming off the note rather than
     crossing it while it is still rising. */
  .fd-rx-heart{animation:fdHeartFloat 4.4s ease-out 1.9s infinite;}
  .fd-rx-heart.h2{animation-delay:2.8s;}
  .fd-rx-heart.h3{animation-delay:3.7s;}
  .fd-rx-heart.h4{animation-delay:4.6s;}
  .fd-rx-heart.h5{animation-delay:5.5s;}
  @keyframes fdHeartFloat{
    0%{opacity:0;transform:translate(0,0) scale(.5) rotate(0deg);}
    14%{opacity:.9;}
    70%{opacity:.5;}
    100%{opacity:0;transform:translate(var(--dx,0), -64px) scale(1.15) rotate(8deg);}
  }

  /* Row action buttons answer the cursor the same way the banner's do. */
  .family-diary-page td .btn{transition:transform .2s cubic-bezier(.16,1,.3,1), box-shadow .2s ease-out;}
}

/* The row whose text is currently showing. Not a :hover state — it
   persists while the panel is open, so it lives outside the hover block.
   The due variant restates its own tint, which would otherwise lose to
   this rule and make an open due row stop looking due. */
.family-diary-page tbody tr.fd-row-open{background:var(--subtle-bg);}
.family-diary-page tbody tr.fd-row-due.fd-row-open{background:var(--amber-bg);}

/* ---------- Insurance page - leaf animation + row hover ("Insurance page animated.mp4") ---------- */

/* Static leaf silhouette (Insurance.html's own .leaf-deco SVG, copied
   verbatim) sits behind the card's real content; a handful of small
   leaves drift slowly upward on top of it. Both live behind
   .ins-value-content (z-index:1) via DOM order alone under
   position:absolute/z-index:auto stacking - painted in the order they
   appear, so value-content simply needs to come last and be positioned
   to join that same stacking order. */
.ins-leaf-deco{
  position:absolute;right:-6px;bottom:-6px;
  opacity:.55;pointer-events:none;
}
.ins-leaf-particle{
  position:absolute;bottom:-10px;
  font-size:13px;opacity:0;
  pointer-events:none;
  animation:ins-leaf-rise linear infinite;
}
@keyframes ins-leaf-rise{
  0%{transform:translate(0,0) rotate(0deg);opacity:0;}
  12%{opacity:.85;}
  85%{opacity:.6;}
  100%{transform:translate(var(--x), -170px) rotate(50deg);opacity:0;}
}
.ins-value-content{position:relative;z-index:1;}
@media (prefers-reduced-motion: reduce){
  .ins-leaf-particle{animation:none;display:none;}
}

/* Row hover - a blue left-edge accent + tinted background + lift,
   matching the reference video's row-hover state (distinct from the
   already-existing shine sweep on the same rows). Widens the existing
   td:first-child border-left (already there for the boxed-row look)
   rather than adding a new border on the <tr> itself - a <tr>::before
   already broke this table's column layout once (see the shimmer/shine
   fix above), so new box properties go on <td>, never on <tr>. */
@media (hover:hover) and (pointer:fine){
  .ins-page .table-wrap tbody tr{transition:box-shadow .18s ease-out;}
  .ins-page .table-wrap tbody tr:hover{box-shadow:0 6px 16px rgba(20,45,85,.10);}
  .ins-page .table-wrap tbody tr:hover td{background:var(--blue-bg);}
  .ins-page .table-wrap tbody tr:hover td:first-child{border-left:3px solid var(--blue);}
}

/* ---------- Total Insured Value hero banner ("Total Insured Value.png") ---------- */
.ins-value-hero{
  position:relative;overflow:hidden;
  display:flex;align-items:center;gap:24px;flex-wrap:wrap;
  padding:26px 32px;
  background:linear-gradient(120deg, var(--green-bg) 0%, var(--card) 55%, var(--card) 100%);
  border-color:color-mix(in srgb, var(--green) 18%, var(--border));
}
.ins-value-hero-left{position:relative;z-index:2;flex:1;min-width:260px;}
.ins-value-hero-icon{
  width:52px;height:52px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  background:var(--card);box-shadow:0 6px 16px rgba(31,138,95,.22);
  border:2px solid var(--green-bg);margin-bottom:12px;
}
.ins-value-hero-icon svg{width:24px;height:24px;}
.ins-value-hero-title{font-size:24px;font-weight:800;color:var(--text-900);}
.ins-value-hero-sub{font-size:13px;color:var(--text-400);margin-top:3px;}
.ins-value-hero-rule{width:52px;height:3px;border-radius:2px;background:var(--gold-500);margin:12px 0 16px;}
.ins-value-hero-amount-wrap{
  display:inline-block;background:color-mix(in srgb, var(--card) 70%, transparent);
  border-radius:16px;padding:14px 26px;
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--green) 12%, transparent);
}
.ins-value-hero-amount{font-size:38px;font-weight:800;color:var(--text-900);letter-spacing:-.5px;}
.ins-value-hero-wave{
  position:absolute;left:-10%;bottom:-18%;width:60%;height:70%;
  background:radial-gradient(ellipse at bottom left, color-mix(in srgb, var(--green) 30%, transparent) 0%, transparent 70%);
  pointer-events:none;z-index:1;
}
.ins-value-hero-art{position:relative;flex:none;width:220px;height:190px;z-index:2;}
.ins-value-hero-badge{
  position:absolute;width:32px;height:32px;border-radius:50%;
  background:var(--card);box-shadow:var(--shadow-md);
  display:flex;align-items:center;justify-content:center;font-size:14px;
}
@media(max-width:820px){
  .ins-value-hero{padding:22px;}
  .ins-value-hero-art{display:none;}
}

/* ---------- Total Insured Value - real illustration background ("Total Insured Value.png") ---------- */
/* The reference's own umbrella/shield/coins artwork, cropped down to just
   that illustration (its baked-in title/amount text and card chrome
   trimmed off - this app renders those itself, same reasoning as
   balanceSheet.js's fbs-*-bg.png convention) and background-removed to a
   transparent PNG so it sits on this card's own green tint rather than
   a visible white patch. Card is back to its original compact size (one
   of 3 equal columns), not the earlier full-width hero. Combined into one
   shorthand (image layer + var(--green-bg) layer) at 3-class specificity
   so it wins over .stat-card.accent-green's own plain background - two
   separate declarations would lose to that rule's higher specificity. */
.stat-card.accent-green.ins-value-card{
  background:url('assets/insurance-value-art.png') no-repeat right -14px bottom -10px / 150px auto, var(--green-bg);
}

/* Fix: url() in a stylesheet resolves relative to the stylesheet's own
   folder (css/), not the page root - the rule above pointed at
   css/assets/... instead of the real assets/ folder one level up. */
.stat-card.accent-green.ins-value-card{
  background:url('../assets/insurance-value-art.png') no-repeat right -14px bottom -10px / 150px auto, var(--green-bg);
}
@media(max-width:480px){
  /* This block used to sit directly above the "Fix: url()" rule just
     above (both apply at equal 0-3-0 specificity), which meant the
     unconditional url() fix - being LATER in the file - always won and
     silently reset background-size/-position straight back to the
     150px desktop values on every screen size. A media query does not
     raise specificity on its own, so at equal specificity the tie is
     broken by source order alone, media-wrapped or not - this mobile
     rule was dead code from the day it was added, regardless of theme or
     viewport. Moved to after the url() fix so it is actually the last
     rule for this selector and wins when its condition is true.
     Same proportional-bleed fix as .ins-type-card's own mobile rule
     (search this file for "proportionally MORE cropped" for the full
     reasoning): shrinking the art from 150px to 110px without also
     scaling its -14px/-10px bleed offset left it more cropped than at
     desktop size (9.3%/6.7% of the image cut off at 150px vs. 12.7%/9.1%
     of it at 110px) - position scaled down by the same 110/150 ratio the
     art itself was. This layer's second background layer is a plain
     colour (var(--green-bg), not an image/gradient), so - unlike
     .ins-type-card's gradient layer - it is never affected by
     background-size regardless of how many values are listed, and
     doesn't need a second value here. */
  .stat-card.accent-green.ins-value-card{background-size:110px auto; background-position:right -10px bottom -7px;}
}
/* ---------- Total Insured Value - icon + subtitle text ("Total Insured Value.png") ---------- */
.ins-value-icon{
  width:36px;height:36px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  background:var(--card);box-shadow:0 4px 10px rgba(31,138,95,.22);
  margin-bottom:8px;
}
.ins-value-icon svg{width:18px;height:18px;}
.ins-value-sub{font-size:11px;color:var(--text-400);margin-top:2px;margin-bottom:8px;}

/* ---------- By Type - real illustration background ("By Type.png") ---------- */
/* Same treatment as Total Insured Value's own background art: the
   reference's shield-on-a-podium + leaves + heart-pulse badge, cropped
   down to just that cluster (its baked-in title/donut/legend trimmed
   off - this app renders those itself) and background-removed to a
   transparent PNG. url() is relative to this stylesheet's own folder
   (css/), so ../assets/ - not assets/ - reaches the real assets folder
   (see Total Insured Value's own fix for this same mistake). Card keeps
   its existing compact size - .ins-type-card and .card share equal
   (0,1,0) specificity, so this rule (appended later in the file) wins
   by source order alone without needing an extra class like the green
   accent card did. */
.ins-type-card{
  position:relative;overflow:hidden;
  background:url('../assets/insurance-bytype-art.png') no-repeat right -10px bottom -18px / 95px auto, var(--card);
}
@media(max-width:480px){
  .ins-type-card{background-size:72px auto;}
}

/* ---------- Profile page shimmer & shine ("Profile.html") ---------- */
/* Purely additive - targets elements that already have position:relative
   (.pf-hero) or position:relative;overflow:hidden (.pf-info-card, per
   its own existing rule), so nothing here touches those existing rules
   or risks colliding with whatever else is being edited on this page
   right now. Ambient sweep reuses the app's existing
   stat-card-ambient-sweep keyframe (same one Insurance's overview cards
   use) rather than a new one. */
@media (hover:hover) and (pointer:fine){
  .pf-hero::before{
    content:'';
    position:absolute;top:0;left:-60%;
    width:32%;height:100%;
    pointer-events:none;
    background:linear-gradient(75deg, transparent 0%, rgba(255,255,255,.14) 45%, rgba(255,255,255,.26) 50%, rgba(255,255,255,.14) 55%, transparent 100%);
    animation:stat-card-ambient-sweep 4.5s ease-in-out infinite;
  }
  .pf-info-card::before{
    content:'';
    position:absolute;top:0;left:-60%;
    width:34%;height:100%;
    pointer-events:none;
    transition:left .6s ease;
    background:linear-gradient(75deg,
      transparent 0%,
      color-mix(in srgb, var(--pf-accent) 30%, transparent) 45%,
      color-mix(in srgb, var(--pf-accent) 50%, transparent) 50%,
      color-mix(in srgb, var(--pf-accent) 30%, transparent) 55%,
      transparent 100%);
  }
  .pf-info-card:hover::before{left:115%;}
}
@media (prefers-reduced-motion: reduce){
  .pf-hero::before{display:none;}
  .pf-info-card::before{display:none;}
}

/* Fix: .pf-hero::before was already the hero card's own gold bloom
   decoration (see the original rule above) - the shimmer added for
   this task's Step 2 accidentally overrode it entirely (a later
   ::before declaration replaces the whole pseudo-element, not just the
   properties that differ). .pf-hero::after and .card.pf-hero-card::before
   are also already spoken for (concentric arc + top hairline), so there
   is no free pseudo-element slot on the hero card for an ambient
   shimmer without a structural change - restoring the original bloom
   here and dropping the hero shimmer from this pass; the info-card
   hover shine below is unaffected (that selector had no prior rule). */
.pf-hero::before{
  content:'';
  position:absolute;
  top:-58%;right:-12%;
  width:460px;height:460px;
  border-radius:50%;
  background:radial-gradient(circle at 50% 50%,
    color-mix(in srgb, var(--gold-400) 26%, transparent) 0%,
    transparent 62%);
  z-index:-1;
  pointer-events:none;
  animation:none;
}
/* Fix: .pf-info-card::before was already this card's thin top accent
   line (see the original rule above) - restoring it, and moving the
   hover shine sweep from Step 2 to ::after instead, which was actually
   free. Same lesson as the .pf-hero fix just above: check for an
   existing rule on the exact selector+pseudo-element pair before
   assuming ::before is available, not just position/overflow. */
.card.pf-info-card::before{
  content:'';
  position:absolute;
  top:0;left:0;right:0;
  height:2px;
  background:linear-gradient(90deg,
    var(--pf-accent) 0%,
    color-mix(in srgb, var(--pf-accent) 30%, transparent) 65%,
    transparent 100%);
}
@media (hover:hover) and (pointer:fine){
  .pf-info-card::after{
    content:'';
    position:absolute;top:0;left:-60%;
    width:34%;height:100%;
    pointer-events:none;
    transition:left .6s ease;
    background:linear-gradient(75deg,
      transparent 0%,
      color-mix(in srgb, var(--pf-accent) 30%, transparent) 45%,
      color-mix(in srgb, var(--pf-accent) 50%, transparent) 50%,
      color-mix(in srgb, var(--pf-accent) 30%, transparent) 55%,
      transparent 100%);
  }
  .pf-info-card:hover::after{left:115%;}
}
@media (prefers-reduced-motion: reduce){
  .pf-info-card::after{display:none;}
}

/* ---------- By Type - maximized illustration + reference gradient background ("By Type.png") ---------- */
/* Corrective override: an earlier .ins-type-card rule already exists further
   up in the file (95px icon, plain var(--card) background). Equal (0,1,0)
   specificity, so this rule - appended later - wins by source order alone.
   Size pushed from 95px to 105px (as large as it can go without the shield
   art overlapping the "Health Insurance 67%" legend line - verified at
   115px/130px it does overlap); right/bottom offsets retuned to match.
   Background gradient colors sampled directly from By Type.png's own
   background (near-white top-left fading to pale blue bottom-right), using
   the app's existing --card / --blue-bg tokens so it stays theme-aware
   instead of hardcoding the sampled hex values. */
.ins-type-card{
  position:relative;overflow:hidden;
  background:url('../assets/insurance-bytype-art.png') no-repeat right -20px bottom -22px / 105px auto, linear-gradient(135deg, var(--card) 0%, var(--blue-bg) 100%);
}
@media(max-width:480px){
  /* Shrinking the art to 78px without also scaling its -20px/-22px bleed
     offset left it proportionally MORE cropped than at desktop size (19%/
     21% of the image cut off at 105px vs. 26%/28% of it at 78px, same
     absolute pixel offset over a smaller image) - background-position
     scaled down by the same 78/105 ratio the art itself was to keep the
     bleed the same proportion of the image at every size instead of
     eating more of it the smaller the card gets.
     A second, real bug found alongside that one: this rule's own
     background-size:78px auto (a single value) was being applied to BOTH
     of this card's background layers - the art image AND the
     linear-gradient behind it - since a background-size list shorter than
     the number of background-image layers repeats to fill the gap. That
     shrank the gradient itself down to a 78px no-repeat swatch in one
     corner instead of it covering the card, which is arguably the bigger
     part of "the background image isn't fully shown" than the art crop
     ratio above. Two comma-separated values (one per layer) fixes both at
     once - "auto" for a gradient layer resolves to the full background
     positioning area per spec, restoring the cover-the-whole-card
     behavior the single-value version had been quietly breaking. (Dark
     theme needs this same pair of fixes too, but :root[data-theme="dark"]
     .ins-type-card further down sets its own full `background` shorthand
     at higher specificity than this plain-class mobile rule, so a
     dark-mode copy of this override has to sit after that rule, not here
     - see further down this file.) */
  .ins-type-card{
    background-size:78px auto, auto;
    background-position:right -15px bottom -16px, 0 0;
  }
}


/* ---------- Insurance: Total Insured Value icon+label row, By Type legend % contrast ---------- */
/* Shield icon now sits to the left of the bold "Total Insured Value" label
   instead of stacked above it (ins-value-icon's own margin-bottom, from its
   earlier stacked layout, is zeroed out here so the row centers cleanly). */
.ins-value-header{display:flex;align-items:center;gap:8px;margin-bottom:6px;}
.ins-value-header .ins-value-icon{margin-bottom:0;}
.ins-value-card .stat-label{font-weight:700;}

/* The donut legend's percentage span is shared markup (charts.js
   renderDonutChart -> .text-muted), used across every donut in the app, so
   this is scoped to just the By Type card rather than touching
   .text-muted's own color everywhere else. Swaps the low-contrast
   --text-400 gray for --text-900 (dark navy on light theme, near-white on
   dark theme) since the new light-blue gradient background made the
   original gray percentages hard to read. */
.ins-type-card .donut-legend .text-muted{color:var(--text-900);font-weight:600;}


/* ---------- By Type - decorative gradient glow beneath the donut ("By Type.png") ---------- */
/* The reference shows a colorful teal->blue->gold wave sweeping across the
   bottom of the card, under the donut/legend. ::before on .ins-page cards is
   already taken by the app-wide gold ambient shimmer sweep
   (".ins-page > .card::before" - see its own rule earlier in this file), so
   this uses the confirmed-free ::after slot instead of colliding with it.
   Colors reuse the app's existing --teal/--blue/--gold-500 tokens (same
   palette as the reference's wave) rather than hardcoding the sampled
   hex values, so it stays theme-aware. Blurred + low-opacity so it reads as
   an ambient glow, not a hard-edged bar; donut-wrap/card-header are lifted
   to z-index:1 so this - being position:absolute - doesn't paint over them
   (absolutely-positioned pseudo-elements otherwise paint above normal-flow
   siblings regardless of source order). */
.ins-type-card::after{
  content:'';
  position:absolute;left:-15%;right:-15%;bottom:-30px;
  height:90px;
  background:linear-gradient(100deg, var(--teal) 0%, var(--blue) 32%, var(--gold-500) 55%, transparent 80%);
  opacity:.55;
  filter:blur(10px);
  border-radius:50%;
  pointer-events:none;
  z-index:0;
}
.ins-type-card .card-header, .ins-type-card .donut-wrap{position:relative;z-index:1;}


/* ---------- Total Insured Value label - stronger bold ---------- */
/* Already 700 (see earlier .ins-value-card .stat-label rule) but at this
   label's small 12px uppercase size 700 can read as barely-bold - bumping
   to 800 for a size where boldness is unambiguous at a glance. */
.ins-value-card .stat-label{font-weight:800;}


/* ---------- Insurance page title card ("Insurance - Title card.png") ---------- */
/* Photographic family-in-sunset from the reference isn't reused (this app's
   cards are all flat icon/illustration art, never photographic people - see
   Total Insured Value/By Type's own illustrated shield art) - the shield
   outline + warm-to-cool glow keeps the "protection at sunset" motif as a
   simple abstract shape instead of importing a stock-style photo of people.
   Tagline follows this app's own existing convention for a reference's
   cursive/script line (Archived Vault's .vault-hero-tagline/
   .vault-footer-tagline) - italic Georgia, not a new webfont - since the
   app deliberately consolidated to a single Inter typeface everywhere else
   (see this file's own Typography section comment above). Heading uses
   --text-900 (not --navy-900) so it stays legible in dark mode too, where
   --navy-900 is itself near-black. */
.ins-title-card{
  display:flex;align-items:center;gap:20px;
  padding:18px 26px;margin-bottom:16px;
  border-radius:var(--radius);
  background:linear-gradient(90deg, var(--blue-bg) 0%, var(--card) 60%, var(--gold-bg) 100%);
  border:1px solid var(--border);
}
.ins-title-icon{
  width:52px;height:52px;border-radius:50%;flex-shrink:0;
  display:flex;align-items:center;justify-content:center;
  background:linear-gradient(135deg, var(--gold-400), var(--gold-600));
  box-shadow:0 4px 14px rgba(200,153,46,.35);
}
.ins-title-icon svg{width:26px;height:26px;}
.ins-title-text{flex:1;min-width:0;}
.ins-title-heading{font-size:22px;font-weight:800;color:var(--text-900);margin:0 0 2px;}
.ins-title-sub{font-size:13px;color:var(--text-600);margin:0;max-width:360px;}
.ins-title-art{
  flex:0 0 140px;height:72px;border-radius:12px;overflow:hidden;
  display:flex;align-items:center;justify-content:center;
  background:radial-gradient(120% 150% at 50% 115%, #ffd98a 0%, #f5a95f 25%, #6ea8d8 60%, transparent 82%);
}
.ins-title-art svg{width:38px;height:38px;filter:drop-shadow(0 0 5px rgba(255,255,255,.65));}
.ins-title-tagline{
  flex-shrink:0;text-align:right;font-family:Georgia,'Times New Roman',serif;font-style:italic;
  font-size:15px;line-height:1.4;color:#a9821f;
}
@media(max-width:860px){
  .ins-title-art{display:none;}
}
@media(max-width:620px){
  .ins-title-card{flex-wrap:wrap;}
  .ins-title-tagline{text-align:left;width:100%;margin-top:6px;}
}


/* ---------- By Type - animate the glow beneath the donut ---------- */
/* Gives the static glow (.ins-type-card::after, appended earlier) a slow
   drift/pulse - same "ambient, not attention-grabbing" register as this
   page's other motion (stat-card-ambient-sweep, the leaf particles), and
   respects prefers-reduced-motion the same way those do. */
@keyframes ins-bytype-glow-drift{
  0%,100%{ transform:translateX(0) scale(1); opacity:.5; }
  50%{ transform:translateX(8%) scale(1.08); opacity:.7; }
}
.ins-type-card::after{
  animation:ins-bytype-glow-drift 6s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce){
  .ins-type-card::after{animation:none;}
}


/* ---------- Insurance title card - real reference art (hills/family/shield) ---------- */
/* Corrective override: replaces the earlier abstract gradient+SVG shield
   placeholder for .ins-title-art with the actual cropped illustration from
   "Insurance - Title card.png" (insurance-title-art.png - hills at sunset,
   the family silhouette, and the glowing shield outline, all baked into the
   photo itself), per explicit request to match the reference exactly rather
   than substitute an abstract shape. Equal (0,1,0) specificity to the
   earlier .ins-title-art rule, so this - appended later - wins by source
   order; widened from 140px to better match the reference's proportions
   now that it's showing real scenery instead of a small icon glyph. */
.ins-title-art{
  flex:0 0 260px;height:80px;
  background:url('../assets/insurance-title-art.png') no-repeat center / cover;
}
@media(max-width:1000px){
  .ins-title-art{flex-basis:200px;}
}


/* ---------- Insurance title card art - maximized + centered ---------- */
/* Corrective override: enlarges the art panel (from the earlier 260x80) to
   fill more of the card's available middle space, matching the reference's
   proportions more closely. background-position explicitly center/center
   (was already center via the shorthand, kept explicit here since that's
   the point of this change) - .stat-sub's max-width trimmed slightly so
   the subtitle doesn't collide with the now-wider art panel. */
.ins-title-art{
  flex:0 0 380px;height:92px;
  background:url('../assets/insurance-title-art.png') no-repeat center center / cover;
}
.ins-title-sub{max-width:300px;}
@media(max-width:1150px){
  .ins-title-art{flex-basis:280px;}
}
@media(max-width:1000px){
  .ins-title-art{flex-basis:220px;height:80px;}
}


/* ---------- Insurance title card art - centered + fit to card size ---------- */
/* Corrective override: previously a fixed-size box (380x92) off to the
   right of center. Switching .ins-title-art to flex:1 1 auto with no fixed
   height, plus align-items:stretch on the row, makes it the sole flexible
   child between the two fixed-size siblings (text, tagline) - so it fills
   the full available middle width (visually centering it in the card) and
   stretches to the row's full height (fitting the card's own size) instead
   of a fixed height guess. Text/tagline get align-self:center so they stay
   vertically centered rather than stretching with it. */
.ins-title-card{align-items:stretch;}
.ins-title-text{align-self:center;flex:0 1 320px;}
.ins-title-tagline{align-self:center;}
.ins-title-art{
  flex:1 1 auto;min-width:200px;max-width:520px;height:auto;
  border-radius:10px;
  background:url('../assets/insurance-title-art.png') no-repeat center center / cover;
}


/* ---------- Insurance title card icon ("Insurance - Title card Icon.png") ---------- */
/* Corrective override: replaces the earlier gold-circle+SVG badge with the
   actual reference icon (shield, coins, sprout), background-removed to
   transparent - same asset pipeline as the Total Insured Value/By Type
   cards' own cropped art. Floats gently (translateY) and carries a shimmer
   sweep (reusing the exact stat-card-ambient-sweep keyframe already used
   elsewhere on this page) per explicit request to animate it; ::before on
   .ins-title-icon is a brand-new selector so there's no existing rule to
   collide with. */
.ins-title-icon{
  width:64px;height:56px;flex-shrink:0;
  position:relative;overflow:hidden;
  background:url('../assets/insurance-title-icon.png') no-repeat center / contain;
  animation:ins-title-icon-float 3.6s ease-in-out infinite;
}
.ins-title-icon::before{
  content:'';position:absolute;top:0;left:-60%;width:40%;height:100%;
  background:linear-gradient(75deg, transparent 0%, rgba(255,255,255,.6) 45%, rgba(255,255,255,.85) 50%, rgba(255,255,255,.6) 55%, transparent 100%);
  animation:stat-card-ambient-sweep 4s ease-in-out infinite;
  pointer-events:none;
}
@keyframes ins-title-icon-float{
  0%,100%{transform:translateY(0);}
  50%{transform:translateY(-5px);}
}
@media (prefers-reduced-motion: reduce){
  .ins-title-icon{animation:none;}
  .ins-title-icon::before{animation:none;display:none;}
}


/* ---------- Insurance title card - drop background art, maximize icon ---------- */
/* Per explicit request: the .ins-title-art photo panel is removed from the
   markup entirely (see insurance.js), so its earlier CSS rules are now
   dead and harmless (no matching element) rather than deleted outright -
   consistent with this file's append-only deploy pattern. The floating
   icon takes over as the banner's visual focus in the freed space. */
.ins-title-icon{width:96px;height:88px;}
.ins-title-text{flex:1 1 auto;}


/* ---------- Insurance filter tab icons ("Insurance Icons.png") ---------- */
/* Cropped/background-removed per subtype, same asset pipeline as this
   page's other art. Class suffix keyed off TAB_ICON_SLUG in insurance.js
   rather than the full label, so the markup stays simple. */
.ins-page .tabs .tab.ins-tab-icon-item{display:flex;align-items:center;gap:6px;}
.ins-tab-icon{
  width:18px;height:18px;flex-shrink:0;
  background-repeat:no-repeat;background-position:center;background-size:contain;
}
.ins-tab-icon-all{background-image:url('../assets/ins-tab-all.png');}
.ins-tab-icon-term{background-image:url('../assets/ins-tab-term.png');}
.ins-tab-icon-health{background-image:url('../assets/ins-tab-health.png');}
.ins-tab-icon-life{background-image:url('../assets/ins-tab-life.png');}
.ins-tab-icon-vehicle{background-image:url('../assets/ins-tab-vehicle.png');}


/* ---------- Insurance title card - full-card background restored ("Insurance - Title card - final1.png") ---------- */
/* Corrective override: puts a background back on .ins-title-card itself
   (the earlier small side-panel approach was dropped per a prior request)
   using this newer, richer reference (family+shield+sunset with orbiting
   insurance-type icon badges). background-size:cover centers on the
   image's vertical middle by default, which lands right on the family/
   shield focal point given how short+wide this card is versus the source.
   A white scrim (heavier over the text/tagline zones, lighter in the
   middle so the photo still reads) keeps "Insurance"/subtitle/tagline
   legible over a busy photo instead of the plain gradient card. */
.ins-title-card{
  background:linear-gradient(90deg, rgba(255,255,255,.93) 0%, rgba(255,255,255,.6) 32%, rgba(255,255,255,.3) 58%, rgba(255,255,255,.8) 100%), url('../assets/insurance-title-bg.png') no-repeat center center / cover;
}


/* ---------- Insurance title card - let the background photo show fully ---------- */
/* Corrective override: the earlier scrim (up to .93 opacity white) hid too
   much of the reference photo across most of the card, leaving only a
   narrow strip actually visible - per explicit request ("cover the full
   image"), the scrim is lightened substantially (max .55) so the photo
   reads across the whole card, with a soft text-shadow on the heading/
   subtitle taking over legibility duty instead of a heavy background wash.
   Tagline already carries its own gold color/italic weight and reads fine
   over the lighter scrim without an added shadow. */
.ins-title-card{
  background:linear-gradient(90deg, rgba(255,255,255,.55) 0%, rgba(255,255,255,.25) 30%, rgba(255,255,255,.15) 55%, rgba(255,255,255,.5) 100%), url('../assets/insurance-title-bg.png') no-repeat center center / cover;
}
.ins-title-heading, .ins-title-sub{text-shadow:0 1px 3px rgba(255,255,255,.7);}


/* ---------- Insurance title card - maximized overall size ---------- */
/* Corrective override: scales up the whole banner (padding, min-height,
   icon, heading/subtitle/tagline type sizes) per explicit request, now
   that it carries a full photographic background worth giving room to
   breathe rather than the earlier compact single-line-height banner. */
.ins-title-card{padding:34px 44px;min-height:190px;}
.ins-title-icon{width:140px;height:128px;}
.ins-title-heading{font-size:30px;}
.ins-title-sub{font-size:15px;max-width:420px;}
.ins-title-tagline{font-size:19px;}


/* ---------- Insurance title card - maximized further (round 2) ---------- */
/* Follow-up corrective override on top of the previous maximize pass -
   same rationale, pushed larger again per explicit follow-up request. */
.ins-title-card{padding:48px 56px;min-height:280px;}
.ins-title-icon{width:190px;height:172px;}
.ins-title-heading{font-size:40px;}
.ins-title-sub{font-size:18px;max-width:520px;}
.ins-title-tagline{font-size:24px;}


/* ---------- Insurance title card - revert to the round-1 maximize size ---------- */
/* Corrective override: round 2 (padding 48/56, icon 190x172, heading 40px)
   was too big per follow-up feedback - restores round 1's values. Appended
   later than round 2 so it wins by source order without needing to touch
   either earlier block. */
.ins-title-card{padding:34px 44px;min-height:190px;}
.ins-title-icon{width:140px;height:128px;}
.ins-title-heading{font-size:30px;}
.ins-title-sub{font-size:15px;max-width:420px;}
.ins-title-tagline{font-size:19px;}


/* ---------- Insurance title card - zoom out background to show the full scene ---------- */
/* Corrective override: switches from background-size:cover (which crops
   the very tall/wide source down to a thin center strip, cutting off the
   orbiting icon badges at top and the mountains at the bottom) to contain,
   per explicit request to keep all the people, insurance-type icons, the
   shield, and the mountains visible at once instead of cropped. A plain
   var(--card) fallback layer fills the letterboxed edges contain leaves
   on the sides, blending into the rest of the page's card color. */
.ins-title-card{
  background:linear-gradient(90deg, rgba(255,255,255,.55) 0%, rgba(255,255,255,.25) 30%, rgba(255,255,255,.15) 55%, rgba(255,255,255,.5) 100%), url('../assets/insurance-title-bg.png') no-repeat center center / contain, var(--card);
}


/* ---------- Insurance title card - fill the contain letterbox with matching sky-blue ---------- */
/* Corrective override: contain (kept, per the "show everything" request)
   leaves empty side gaps since the source is proportionally narrower than
   this very wide/short card - those gaps previously fell back to plain
   var(--card) white, reading as broken/empty. Replaced with a vertical
   gradient sampled from the photo's own left/right edge colors (vivid sky
   blue at top fading to a paler blue), so the letterboxed sides blend into
   the photo instead of looking blank. The earlier white text-scrim is
   dropped since text now mostly sits over this blue fill or the photo's
   own bright sky (both light enough for the dark heading/subtitle to read
   without it). */
.ins-title-card{
  background:url('../assets/insurance-title-bg.png') no-repeat center center / contain, linear-gradient(180deg, #3aa4e8 0%, #bcd3e2 100%);
}


/* ---------- Insurance - reduce floating icon size, shine tab labels ---------- */
/* Icon dialed back down from the maximize pass (140x128) - reads better at
   this size next to the now-full-width photo background. */
.ins-title-icon{width:82px;height:74px;}

/* Text-shine on the filter tab labels: a moving bright band clipped to the
   glyph shapes (classic background-clip:text shine technique), applied to
   a dedicated .ins-tab-label span rather than the whole .tab element -
   background-clip:text clips the WHOLE background stack of an element,
   including a solid background-color, so applying it straight to .tab
   would have also clipped the active tab's own navy fill down to just the
   letter shapes (verified live - it did, and looked broken). Base color
   matches each tab's own existing text color (var(--text-600) inactive,
   white active) so only the sweeping highlight is new, not the letters'
   resting color. */
.ins-tab-label{
  background-image:linear-gradient(100deg, var(--text-600) 0%, var(--text-600) 45%, #fff 50%, var(--text-600) 55%, var(--text-600) 100%);
  background-size:250% 100%;
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent;
  animation:ins-tab-shine 4s linear infinite;
}
.ins-page .tabs .tab.active .ins-tab-label{
  background-image:linear-gradient(100deg, #fff 0%, #fff 45%, var(--gold-400) 50%, #fff 55%, #fff 100%);
}
@keyframes ins-tab-shine{
  0%{background-position:200% 0;}
  100%{background-position:-100% 0;}
}
@media (prefers-reduced-motion: reduce){
  .ins-tab-label{animation:none;-webkit-text-fill-color:unset;color:inherit;background:none;}
}


/* ---------- Document Vault hero icon - real illustration ("Document valut.png") ---------- */
/* .vault-hero-icon-art overrides the shared .vault-hero-icon's gold-gradient
   emoji-badge look (used as-is by Archived Vault) with the reference's own
   folder/document/lock-shield artwork, cropped and background-removed to
   docvault-hero-art.png (same pipeline as Insurance's card art). Both
   classes in the selector (2 classes) beat the shared single-class rule's
   specificity, so this doesn't touch that rule or affect Archived Vault's
   own emoji icon. */
.vault-hero-icon.vault-hero-icon-art{
  width:100px;height:92px;border-radius:0;box-shadow:none;
  background:url('../assets/docvault-hero-art.png') no-repeat center / contain;
}


/* ---------- Document Vault - category tabs, table restyle, notice bar ("Document valut.png") ---------- */
/* Filter tabs mirror Insurance's own .ins-page .tabs pill styling exactly
   (same pill/active-navy look), scoped to .dv-page instead so the two
   pages' tab strips can't drift into each other's rules. All 13 real
   categories get a tab with a real count (see documents.js) rather than
   the reference's smaller illustrative subset - same call Insurance's own
   tabs made for its 4 real subtypes. */
.dv-page .tabs{display:flex;flex-wrap:wrap;gap:8px;border-bottom:0;background:var(--subtle-bg);border-radius:14px;padding:6px;margin:0 0 16px;}
.dv-page .tabs .tab{border-radius:10px;border-bottom:0;padding:9px 16px;background:transparent;color:var(--text-600);transition:background-color .2s ease-out, color .2s ease-out;}
.dv-page .tabs .tab.active{background:var(--navy-900);color:#fff;box-shadow:0 2px 10px color-mix(in srgb, var(--navy-900) 20%, transparent);}
.dv-page .tabs .tab.dv-tab-icon-item{display:flex;align-items:center;gap:6px;}
.dv-tab-icon{font-size:15px;flex-shrink:0;}
/* Reuses Insurance's own .ins-tab-label text-shine (unscoped base rule
   already applies); this just adds the active-tab white/gold variant,
   same technique as .ins-page's own active-tab shine. */
.dv-page .tabs .tab.active .ins-tab-label{background-image:linear-gradient(100deg, #fff 0%, #fff 45%, var(--gold-400) 50%, #fff 55%, #fff 100%);}
@media (hover:hover) and (pointer:fine){
  .dv-page .tabs .tab:not(.active):hover{background:color-mix(in srgb, var(--navy-900) 8%, transparent);}
}

/* Category/Document cells: icon + bold label + muted subtitle, replacing
   the old single-line pill + separate icon column. */
.dv-cat-cell{display:flex;gap:10px;align-items:center;}

/* Pill-shaped action buttons (View/Download/Edit/Archive/Delete),
   matching the reference's rounded action buttons - !important since
   .btn.btn-sm's own border-radius otherwise wins on specificity. */
.dv-action-pill{border-radius:999px !important;}

/* Notice bar: was a plain tan disclaimer box; now an icon + text + a real
   "View Archived Vault" link (â†’ the real #/archived-vault route), styled
   as a light blue notice per the reference. Text itself is unchanged real
   content (how documents are organized) - not replaced with the
   reference's own encryption-specific wording, since this app doesn't
   independently verify that claim. */
.dv-notice{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  background:var(--blue-bg);border-color:color-mix(in srgb, var(--blue) 30%, transparent);color:var(--text-600);
}
.dv-notice-icon{flex-shrink:0;font-size:16px;}
.dv-notice-link{margin-left:auto;white-space:nowrap;color:var(--blue);font-weight:600;text-decoration:none;}
.dv-notice-link:hover{text-decoration:underline;}


/* ---------- Insurance - dark mode readability fixes ---------- */
/* The title card sits on a fixed photographic background (never changes
   with theme), so its heading/subtitle must NOT follow --text-900 into
   its dark-mode value (near-white) - that made "Insurance" and its
   subtitle nearly invisible against the still-bright photo. Forces the
   same dark navy used in light mode, regardless of theme. */
:root[data-theme="dark"] .ins-title-heading, :root[data-theme="dark"] .ins-title-sub{color:#1a2233;}


/* ---------- Total Insured Value / By Type art - dark-mode visibility fix ---------- */
/* Root cause: both illustrations have a translucent/glowing family-icon
   glyph baked in (partial alpha, not fully opaque) - a deliberate "glow"
   effect in the original artwork that reads fine blended against a light
   card but blends toward near-black against a dark card, making the icon
   look broken/invisible ("unable to view clearly"). insurance-value-art.png
   was also further cleaned of a lingering faint translucent wash (visible
   composited on dark, invisible on the original light green). Rather than
   keep chasing pixel-level fixes blind (no access to the original
   pre-crop source to redo this cleanly), a soft radial highlight is added
   behind the art in dark mode only, giving the translucent icon something
   lighter to blend against - same visual language as this app's existing
   glow/shimmer effects, not a new pattern. Light mode is untouched (it
   already looks correct there).

   2026-09-15 - still reported "not clear" in dark mode: the .35-alpha/55%
   radius glow above was too faint and too small to actually read as a
   white backdrop, just a slight lightening most of the art's leaves/mini-
   badges still sat on top of the dark card fill outside that small
   radius. Bumped to .6 alpha and a 70% radius - genuinely more "white
   space" behind the art, not just a subtler version of the same glow. */
:root[data-theme="dark"] .stat-card.accent-green.ins-value-card{
  background:url('../assets/insurance-value-art.png') no-repeat right -14px bottom -10px / 150px auto,
    radial-gradient(circle at 82% 78%, rgba(255,255,255,.6) 0%, rgba(255,255,255,.25) 45%, transparent 70%),
    var(--green-bg);
}
@media(max-width:480px){
  /* Same missing-dark-mobile-override bug as .ins-type-card (see that
     rule's own mobile block further down for the full reasoning): this
     selector's higher specificity (an extra :root[data-theme="dark"])
     than the light-mode mobile rule above means dark mode never picked up
     that rule and kept rendering the art at full 150px desktop size on a
     narrow card. Needs its own copy at matching specificity, placed after
     the base rule above so it wins by source order. The radial-gradient
     highlight (this card's own dark-only addition, absent in light mode)
     gets an explicit "auto" size for the same reason .ins-type-card's
     gradient layer needed one - a background-size list shorter than the
     number of background-image layers repeats across the rest, which
     would otherwise shrink this highlight down to a 110px swatch instead
     of it covering the card. */
  :root[data-theme="dark"] .stat-card.accent-green.ins-value-card{
    background-size:110px auto, auto, auto;
    background-position:right -10px bottom -7px, 0 0, 0 0;
  }
}
/* 2026-09-15 - same "not clear" report as the Total Insured Value card
   above: this glow was even fainter relative to the card's own blue-
   tinted gradient background (linear-gradient below, not a flat fill),
   so the art had less contrast to begin with. Same fix - .6 alpha, wider
   70% radius - genuine white space behind the art rather than a subtle
   tint. */
:root[data-theme="dark"] .ins-type-card{
  background:url('../assets/insurance-bytype-art.png') no-repeat right 5px bottom -22px / 105px auto,
    radial-gradient(circle at 88% 85%, rgba(255,255,255,.6) 0%, rgba(255,255,255,.25) 45%, transparent 70%),
    linear-gradient(135deg, var(--card) 0%, var(--blue-bg) 100%);
}
/* Still "not clear" after the glow/outline pass: the source art's own
   small heart-pulse badge (top-right of insurance-bytype-art.png, ~80x98
   of its 320x422) sits almost exactly on this card's right edge at the
   old "right -20px" bleed - measured via the card/art's actual rendered
   rects, its center lands at ~399px of a 400px-wide card, so roughly half
   of it was clipped by .ins-type-card's own overflow:hidden, on top of
   the separate contrast problem. Not just a dark-mode issue by rights
   (light mode crops it identically) but light mode's contrast was good
   enough that half a gold badge on white still read fine, while dark
   mode compounded a hard crop with a barely-visible badge into something
   that looked broken rather than just cropped. Fixed dark-mode-only
   (matching the scope of what was actually reported) by nudging the bleed
   from -20px to 5px - a 25px leftward shift, clearing the badge fully
   inside the card. The shield SVG overlay's own position has to move the
   same 25px or it drifts off the shield it's meant to cover - see its
   dark-mode override below (.ins-type-shield-badge is otherwise a single
   theme-agnostic rule, tuned only for light mode's -20px crop). */
:root[data-theme="dark"] .ins-type-shield-badge{left:300px;}
@media(max-width:480px){
  /* Same pair of mobile fixes as the light-mode .ins-type-card rule
     further up this file (proportional bleed offset + a per-layer size/
     position list, so the two extra background layers - the radial
     highlight and the base gradient, three layers total here versus
     light mode's two - don't get shrunk down to the art's own 78px too),
     repeated here at matching specificity (this selector, not just a bare
     .ins-type-card) so it actually wins over the full `background`
     shorthand directly above. A plain-class mobile override placed
     earlier in the file (like the light-mode one) would lose to this
     rule's higher specificity regardless of source order, which was the
     actual bug: dark mode never picked up the light-mode mobile fix and
     kept rendering the art at full desktop size/offset on a narrow card
     (and, independently, still had its gradient/highlight layers exposed
     to the single-value background-size bug even where it did apply). */
  :root[data-theme="dark"] .ins-type-card{
    background-size:78px auto, auto, auto;
    background-position:right -15px bottom -16px, 0 0, 0 0;
  }
}

/* ================= Family Expenses (Family Expenses UI_v1) =================
   Scoped to .fx-page, the wrapper familyExpenses.js's render() puts around
   the whole module. Every rule below is written as `.fx-page ...`, so this
   colour system cannot reach any other page even though it restyles shared
   classes (.card, .tabs, table, .btn, select). No figure, filter, sort,
   period or handler is touched â€” this is presentation only.

   The module had no design system at all: text-only tabs, plain white cards
   and bare tables. It now uses the same language as Family & Estate and the
   Financial Calculator â€” navy and gold as the identity, soft blue for income,
   coral for spend, lavender for projection, and a cool page ground with lit
   cards on top of it. Green survives in exactly one place, on the annual
   income figures the JS already inks with var(--green), because there it
   means something. ======================================================== */

.fx-page{
  --fx-navy:#1b3358;
  --fx-gold:#b8912f;
  --fx-ivory:#fdf8ec;
  --fx-dash:#2c4f86;      /* navy-royal  */
  --fx-income:#4a7ec2;    /* soft blue   */
  --fx-spend:#c9705f;     /* warm coral  */
  --fx-proj:#7d70b5;      /* lavender    */
  --fx-accent:var(--fx-dash);
  --fx-line:color-mix(in srgb, var(--border) 86%, transparent);
  --fx-line-soft:color-mix(in srgb, var(--border) 46%, transparent);

  position:relative;
  padding:20px 22px 24px;
  border-radius:20px;
  border:1px solid var(--fx-line);
  background:
    radial-gradient(55% 34% at 100% 0%, color-mix(in srgb, var(--fx-gold) 7%, transparent) 0%, transparent 62%),
    radial-gradient(50% 32% at 0% 100%, color-mix(in srgb, var(--fx-proj) 5%, transparent) 0%, transparent 64%),
    linear-gradient(180deg,
      color-mix(in srgb, var(--subtle-bg) 40%, var(--card)) 0%,
      color-mix(in srgb, var(--subtle-bg) 18%, var(--card)) 100%);
  box-shadow:0 1px 2px rgba(15,31,51,.04), 0 22px 46px -34px rgba(15,31,51,.34);
}

/* --- Tab strip: a rounded segmented plate ------------------------------- */
.fx-page .fx-tabs .tab[data-branch="Family Dashboard"]{--fx-accent:var(--fx-dash);}
.fx-page .fx-tabs .tab[data-branch="Family Income"]   {--fx-accent:var(--fx-income);}
.fx-page .fx-tabs .tab[data-branch="Family Spend"]    {--fx-accent:var(--fx-spend);}
.fx-page .fx-tabs .tab[data-branch="Projection"]      {--fx-accent:var(--fx-proj);}

.fx-page .fx-tabs{
  gap:5px;
  margin-bottom:22px;
  padding:6px;
  border:1px solid color-mix(in srgb, var(--border) 74%, transparent);
  border-bottom:1px solid color-mix(in srgb, var(--border) 74%, transparent);
  border-radius:18px;
  background:
    radial-gradient(70% 200% at 100% 100%, color-mix(in srgb, var(--gold-bg) 52%, transparent) 0%, transparent 62%),
    var(--card);
  box-shadow:0 1px 2px rgba(15,31,51,.04), 0 14px 30px -26px rgba(15,31,51,.4);
}
.fx-page .fx-tabs .tab{
  position:relative;
  display:inline-flex;
  align-items:center;
  gap:10px;
  border-bottom:none;
  margin-bottom:0;
  border-radius:12px;
  padding:9px 15px;
  font-size:13px;
  font-weight:600;
  color:var(--text-600);
  transition:color .22s cubic-bezier(.22,1,.36,1),
             background-color .22s cubic-bezier(.22,1,.36,1),
             box-shadow .22s ease-out, transform .22s cubic-bezier(.22,1,.36,1);
}
.fx-page .fx-tabs .tab-label{white-space:nowrap;}
/* The mark sits in a shallow well and tilts toward the light on approach. */
.fx-page .fx-tabs .tab-ico{
  position:relative;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  flex-shrink:0;
  width:30px;height:30px;
  overflow:hidden;
  border-radius:10px;
  perspective:190px;
  background:linear-gradient(160deg,
    color-mix(in srgb, var(--fx-accent) 6%, transparent) 0%,
    color-mix(in srgb, var(--fx-accent) 17%, transparent) 100%);
  border:1px solid color-mix(in srgb, var(--fx-accent) 20%, transparent);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent),
             inset 0 -2px 4px color-mix(in srgb, var(--fx-accent) 12%, transparent);
  transition:background-color .22s ease-out, border-color .22s ease-out;
}
.fx-page .fx-tabs .tab-ico svg{
  display:block;width:21px;height:21px;
  filter:drop-shadow(0 1px 1.5px rgba(15,31,51,.22));
  transition:transform .32s cubic-bezier(.22,1,.36,1), filter .32s ease-out;
}
@media (hover:hover) and (pointer:fine){
  .fx-page .fx-tabs .tab:hover{
    color:color-mix(in srgb, var(--fx-accent) 58%, var(--text-900));
    background:color-mix(in srgb, var(--fx-accent) 10%, transparent);
    transform:translateY(-1px);
  }
  .fx-page .fx-tabs .tab:hover .tab-ico{
    background:linear-gradient(160deg,
      color-mix(in srgb, var(--fx-accent) 10%, transparent) 0%,
      color-mix(in srgb, var(--fx-accent) 24%, transparent) 100%);
    border-color:color-mix(in srgb, var(--fx-accent) 34%, transparent);
  }
  .fx-page .fx-tabs .tab:hover .tab-ico svg{
    transform:translateY(-1.5px) rotateX(13deg) rotateY(-13deg) scale(1.07);
    filter:drop-shadow(0 3px 5px rgba(15,31,51,.3));
  }
  .fx-page .fx-tabs .tab.active:hover{transform:none;color:#fff;}
}
/* Active: navy tinted with the branch accent, lifted, gold underline. */
.fx-page .fx-tabs .tab.active{
  color:#fff;
  background:linear-gradient(135deg,
    color-mix(in srgb, var(--fx-accent) 58%, var(--navy-900)) 0%,
    var(--navy-900) 100%);
  box-shadow:0 8px 20px -10px color-mix(in srgb, var(--fx-accent) 95%, transparent),
             inset 0 1px 0 rgba(255,255,255,.16);
}
.fx-page .fx-tabs .tab.active .tab-ico{
  background:linear-gradient(160deg, rgba(255,255,255,.24) 0%, rgba(255,255,255,.10) 100%);
  border-color:rgba(255,255,255,.26);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.32), inset 0 -2px 5px rgba(0,0,0,.16);
}
.fx-page .fx-tabs .tab.active .tab-ico svg{animation:fx3d-float 4.2s ease-in-out infinite;}
@keyframes fx3d-float{
  0%, 100%{transform:translateY(0) rotateY(0deg);}
  50%     {transform:translateY(-1.6px) rotateY(-7deg);}
}
.fx-page .fx-tabs .tab.active::before{
  content:'';
  position:absolute;
  left:15px;right:15px;bottom:6px;
  height:2px;
  border-radius:2px;
  background:linear-gradient(90deg, transparent 0%, var(--gold-400) 50%, transparent 100%);
  animation:fx-tab-underline .38s cubic-bezier(.22,1,.36,1) both;
}
@keyframes fx-tab-underline{
  from{transform:scaleX(.2);opacity:0;}
  to{transform:scaleX(1);opacity:1;}
}

/* --- Cards: lit surfaces on the page ground ---------------------------- */
.fx-page .card{
  border:1px solid var(--fx-line);
  border-radius:16px;
  background:linear-gradient(178deg,
    color-mix(in srgb, var(--fx-ivory) 40%, var(--card)) 0%,
    var(--card) 52%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 50%, transparent),
             0 1px 2px rgba(15,31,51,.04),
             0 16px 34px -28px rgba(15,31,51,.34);
}
.fx-page .card-header{
  padding-bottom:14px;
  border-bottom:1px solid color-mix(in srgb, var(--fx-gold) 20%, transparent);
  margin-bottom:14px;
}
.fx-page .card-title{
  font-size:16px;
  font-weight:700;
  letter-spacing:-.018em;
}
.fx-page .card-sub{
  margin-top:4px;
  font-size:12.8px;
  line-height:1.5;
  color:var(--text-600);
}

/* --- Tables: a directory, not a spreadsheet ---------------------------- */
.fx-page table th{
  text-transform:uppercase;
  font-size:10.4px;
  font-weight:700;
  letter-spacing:.1em;
  color:color-mix(in srgb, var(--fx-navy) 70%, var(--text-900));
  border-bottom:1px solid var(--fx-line);
  padding:0 12px 11px;
  white-space:nowrap;
}
.fx-page table td{
  border-bottom:1px solid var(--fx-line-soft);
  padding:13px 12px;
  font-size:13.2px;
}
.fx-page tbody tr:nth-child(even) td{
  background:color-mix(in srgb, var(--fx-ivory) 40%, transparent);
}
.fx-page tbody tr{transition:background-color .18s ease-out;}
@media (hover:hover) and (pointer:fine){
  .fx-page tbody tr:hover td{background:color-mix(in srgb, var(--fx-gold) 8%, transparent);}
}
.fx-page tbody td:first-child{letter-spacing:-.006em;color:var(--text-900);}

/* --- Controls: buttons, the month filter, inputs ----------------------- */
.fx-page .btn{
  border-radius:10px;
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 45%, transparent);
  transition:transform .18s cubic-bezier(.22,1,.36,1), box-shadow .18s ease-out,
             border-color .18s ease-out, background-color .18s ease-out;
}
.fx-page .btn:hover{transform:translateY(-1px);}
.fx-page .btn:active{transform:translateY(0);}
.fx-page .btn.btn-gold{
  box-shadow:0 2px 4px rgba(15,31,51,.08),
             0 10px 22px -14px color-mix(in srgb, var(--gold-500) 90%, transparent);
}
.fx-page .btn.btn-gold:hover{
  transform:translateY(-2px);
  box-shadow:0 4px 8px rgba(15,31,51,.10),
             0 14px 28px -12px color-mix(in srgb, var(--gold-500) 95%, transparent);
}
.fx-page .table-actions .btn{padding:5px 11px;font-size:12.1px;}
/* The month filter is emitted with inline sizing by famExpMonthFilterHtml();
   these are the properties it does not set, so the select gains the page's
   surface treatment without its markup changing. */
.fx-page select,
.fx-page input[type="text"],
.fx-page input[type="number"],
.fx-page input[type="date"]{
  background:linear-gradient(180deg, var(--card) 0%, color-mix(in srgb, var(--subtle-bg) 32%, var(--card)) 100%);
  border-color:var(--fx-line);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 50%, transparent);
  color:var(--text-900);
  transition:border-color .18s ease-out, box-shadow .18s ease-out;
}
.fx-page select:focus,
.fx-page input:focus{
  border-color:color-mix(in srgb, var(--fx-gold) 52%, transparent);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--fx-gold) 14%, transparent);
  outline:none;
}

/* --- Empty states: a held position, not a gap -------------------------- */
.fx-page .fx-empty{padding:48px 22px 44px;}
.fx-page .fx-empty-mark{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:66px;height:66px;
  margin-bottom:18px;
  border-radius:20px;
  border:1px solid color-mix(in srgb, var(--fx-gold) 28%, transparent);
  background:linear-gradient(160deg,
    color-mix(in srgb, var(--fx-gold) 6%, transparent) 0%,
    color-mix(in srgb, var(--fx-gold) 15%, transparent) 100%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent),
             0 10px 22px -14px color-mix(in srgb, var(--fx-gold) 90%, transparent);
  animation:fx-mark-float 5s ease-in-out infinite;
}
.fx-page .fx-empty-mark svg{
  display:block;width:36px;height:36px;
  filter:drop-shadow(0 2px 3px rgba(15,31,51,.24));
}
@keyframes fx-mark-float{
  0%, 100%{transform:translateY(0);}
  50%     {transform:translateY(-3px);}
}

@media (max-width:820px){
  .fx-page{padding:16px 14px 18px;border-radius:16px;}
  .fx-page .fx-tabs{padding:5px;gap:4px;}
  .fx-page .fx-tabs .tab{padding:8px 11px;gap:8px;}
  .fx-page .fx-tabs .tab-ico{width:27px;height:27px;}
  .fx-page .fx-tabs .tab-ico svg{width:19px;height:19px;}
}
@media (prefers-reduced-motion: reduce){
  .fx-page .fx-tabs .tab, .fx-page .fx-tabs .tab-ico,
  .fx-page .fx-tabs .tab-ico svg, .fx-page .btn, .fx-page tbody tr{transition:none;}
  .fx-page .fx-tabs .tab:hover, .fx-page .fx-tabs .tab:hover .tab-ico svg,
  .fx-page .btn:hover{transform:none;}
  .fx-page .fx-tabs .tab.active .tab-ico svg,
  .fx-page .fx-tabs .tab.active::before,
  .fx-page .fx-empty-mark{animation:none;}
}


/* ---------- Insurance - title layout, tagline shine, alert button fix, bigger tabs ---------- */
/* Title card: icon now sits in a row with "Insurance" (icon moved inside
   .ins-title-text as a new .ins-title-top-row wrapper in insurance.js),
   subtitle flows below that row instead of beside the icon. */
.ins-title-top-row{display:flex;align-items:center;gap:14px;}
.ins-title-text{align-self:flex-start;flex:0 1 480px;}
.ins-title-heading{margin:0;}
.ins-title-sub{margin-top:6px;}

/* Tagline: bold + the same text-shine technique used on the tab labels,
   built off its own fixed gold (#a9821f, matches this app's existing
   .vault-hero-tagline/.vault-footer-tagline convention) rather than a
   theme variable, since a script tagline's color is deliberately fixed
   everywhere else it's used. */
.ins-title-tagline{
  margin-left:auto;font-weight:700;
  background-image:linear-gradient(100deg, #a9821f 0%, #a9821f 40%, #ffe9a8 50%, #a9821f 60%, #a9821f 100%);
  background-size:250% 100%;
  -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent;
  animation:ins-tagline-shine 4.5s linear infinite;
}
@keyframes ins-tagline-shine{0%{background-position:200% 0;}100%{background-position:-100% 0;}}
@media (prefers-reduced-motion: reduce){
  .ins-title-tagline{animation:none;-webkit-text-fill-color:unset;color:#a9821f;background:none;}
}

/* Attention Required's "View Details" button used a hardcoded translucent
   WHITE background (rgba(255,255,255,.55)) with amber text - fine over the
   light-mode amber-bg, but in dark mode --amber becomes a lighter gold and
   --amber-bg goes dark brown, so light gold text on a near-white pill had
   almost no contrast ("not visible clearly"). Deriving the translucent fill
   from --card (the theme's own surface color) instead fixes both themes:
   white-ish in light mode, dark slate in dark mode. */
.ins-alert-btn{background:color-mix(in srgb, var(--card) 55%, transparent);}

/* Filter tabs maximized (padding/font/icon size) per explicit request -
   .ins-tab-icon was 18x18, tabs 9px/16px padding with inherited font-size. */
.ins-page .tabs .tab{padding:13px 22px;font-size:15px;}
.ins-tab-icon{width:24px;height:24px;}

/* --- Family Dashboard: the Wealth Dashboard's visualisation language ----
   The Wealth Dashboard's motion is already generic â€” animateEntranceEffects()
   in app.js replays chart draw-ins, bar fills, the donut sweep, the KPI-card
   left-to-right stagger and the stat count-up for any section revealed by
   initPageReveal(), matched on shared classes (.hbar-fill, .donut-chart,
   .grid > .stat-card, .stat-value), not on .dashboard-page. This tab already
   emits all of those, so it inherits the animation as-is; nothing needed
   wiring, and nothing here re-implements it.

   What did NOT carry over is the dashboard's *visual* system, because that
   half is scoped to .dashboard-page: the semantic accent rule on each card,
   the icon badge in each card header, the KPI icon disc, and the per-card
   hover treatment. Those are mirrored below under .fx-page rather than by
   putting .dashboard-page on this module, which would drag in the whole
   dashboard layout with them. ------------------------------------------- */

/* Semantic accent per section, as [class*="card-accent-"] does on the
   dashboard: a coloured top rule and an accent-tinted hover glow. */
.fx-page .card[class*="fx-accent-"]{
  --fx-sec:var(--fx-dash);
  position:relative;
  overflow:hidden;
  transition:transform .22s cubic-bezier(.22,1,.36,1),
             box-shadow .22s ease-out, border-color .22s ease-out;
}
.fx-page .fx-accent-blue {--fx-sec:var(--fx-income);}
.fx-page .fx-accent-coral{--fx-sec:var(--fx-spend);}
.fx-page .fx-accent-lav  {--fx-sec:var(--fx-proj);}
.fx-page .fx-accent-gold {--fx-sec:var(--fx-gold);}
.fx-page .card[class*="fx-accent-"]::before{
  content:'';
  position:absolute;
  top:0;left:0;right:0;
  height:2px;
  background:linear-gradient(90deg,
    var(--fx-sec) 0%,
    color-mix(in srgb, var(--fx-sec) 30%, transparent) 62%,
    transparent 100%);
}
@media (hover:hover) and (pointer:fine){
  .fx-page .card[class*="fx-accent-"]:hover{
    transform:translateY(-2px);
    border-color:color-mix(in srgb, var(--fx-sec) 34%, transparent);
    box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent),
               0 10px 26px -14px color-mix(in srgb, var(--fx-sec) 55%, transparent);
  }
}
/* The dashboard's card-anim-underline: the title's own rule draws out from
   the left on hover, in the section's accent. */
.fx-page .fx-anim-underline .card-title{position:relative;display:inline-block;}
.fx-page .fx-anim-underline .card-title::after{
  content:'';
  position:absolute;
  left:0;bottom:-3px;
  height:2px;width:0;
  border-radius:2px;
  background:var(--fx-sec);
  transition:width .3s ease-out;
}
.fx-page .fx-anim-underline:hover .card-title::after{width:100%;}

/* Section icon badge, the counterpart of .section-icon-badge. */
.fx-page .fx-sec-ico{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:38px;height:38px;
  flex-shrink:0;
  margin-right:12px;
  border-radius:12px;
  overflow:hidden;
  perspective:180px;
  border:1px solid color-mix(in srgb, var(--fx-sec) 24%, transparent);
  background:linear-gradient(160deg,
    color-mix(in srgb, var(--fx-sec) 7%, transparent) 0%,
    color-mix(in srgb, var(--fx-sec) 18%, transparent) 100%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 58%, transparent),
             0 7px 16px -11px color-mix(in srgb, var(--fx-sec) 92%, transparent);
  transition:transform .3s cubic-bezier(.22,1,.36,1);
}
.fx-page .fx-sec-ico svg{
  display:block;width:24px;height:24px;
  filter:drop-shadow(0 1px 2px rgba(15,31,51,.22));
  transition:transform .3s cubic-bezier(.22,1,.36,1);
}
@media (hover:hover) and (pointer:fine){
  .fx-page .card[class*="fx-accent-"]:hover .fx-sec-ico svg{
    transform:rotateX(12deg) rotateY(-12deg) scale(1.07);
  }
}

/* KPI cards. .stat-icon already carries the dashboard's "just landed"
   overshoot (stat-icon-pop) and .stat-value already gets the count-up, so
   this only re-houses the mark: a modelled object in an accent well instead
   of an emoji on a white disc. */
.fx-page .stat-card{
  border-radius:15px;
  background:linear-gradient(170deg,
    color-mix(in srgb, var(--fx-ivory) 42%, var(--card)) 0%,
    var(--card) 58%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 52%, transparent),
             0 1px 2px rgba(15,31,51,.04),
             0 14px 30px -26px rgba(15,31,51,.36);
}
.fx-page .stat-card .stat-icon{
  width:42px;height:42px;
  border-radius:13px;
  overflow:hidden;
  perspective:180px;
  border:1px solid color-mix(in srgb, var(--accent, var(--fx-gold)) 24%, transparent);
  background:linear-gradient(160deg,
    color-mix(in srgb, var(--accent, var(--fx-gold)) 8%, transparent) 0%,
    color-mix(in srgb, var(--accent, var(--fx-gold)) 20%, transparent) 100%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 58%, transparent);
}
.fx-page .stat-card .stat-icon svg{
  display:block;width:26px;height:26px;
  filter:drop-shadow(0 1px 2px rgba(15,31,51,.22));
  transition:transform .3s cubic-bezier(.22,1,.36,1);
}
@media (hover:hover) and (pointer:fine){
  .fx-page .stat-card:hover .stat-icon svg{
    transform:rotateX(12deg) rotateY(-12deg) scale(1.08);
  }
}
.fx-page .stat-value{font-variant-numeric:tabular-nums;letter-spacing:-.02em;}

/* The greeting, given the same plate treatment as the module's other bands. */
.fx-page .famexp-hero{
  position:relative;
  overflow:hidden;
  padding:22px 26px;
  margin-bottom:18px;
  border:1px solid var(--fx-line);
  border-radius:16px;
  background:
    radial-gradient(105% 150% at 0% 0%, color-mix(in srgb, var(--fx-navy) 10%, transparent) 0%, transparent 58%),
    radial-gradient(85% 130% at 100% 0%, color-mix(in srgb, var(--gold-500) 12%, transparent) 0%, transparent 56%),
    linear-gradient(180deg, color-mix(in srgb, var(--fx-ivory) 76%, var(--card)) 0%, var(--card) 100%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent),
             0 1px 2px rgba(15,31,51,.04);
}
.fx-page .famexp-hero::after{
  content:'';
  position:absolute;
  left:0;right:0;bottom:-1px;
  height:2px;
  background:linear-gradient(90deg,
    color-mix(in srgb, var(--gold-500) 68%, transparent) 0%,
    color-mix(in srgb, var(--gold-500) 24%, transparent) 44%,
    transparent 92%);
}
.fx-page .famexp-hero-title{
  margin:0;
  font-size:22px;
  font-weight:700;
  letter-spacing:-.024em;
  line-height:1.2;
  color:var(--text-900);
}
.fx-page .famexp-hero-sub{
  margin:7px 0 0;
  max-width:640px;
  font-size:13.2px;
  font-weight:450;
  line-height:1.55;
  color:var(--text-600);
}
/* The Date Range bar is a bare .card-header in the markup; this gives it the
   same bordered plate the rest of the page uses. */
.fx-page > .card-header{
  align-items:center;
  padding:14px 18px;
  border:1px solid var(--fx-line);
  border-radius:14px;
  background:linear-gradient(180deg, var(--card) 0%, color-mix(in srgb, var(--subtle-bg) 26%, var(--card)) 100%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 52%, transparent);
}
.fx-page > .card-header .card-title{font-size:13px;letter-spacing:.01em;}

@media (max-width:820px){
  .fx-page .famexp-hero{padding:18px 16px;}
  .fx-page .famexp-hero-title{font-size:19px;}
  .fx-page .fx-sec-ico{width:34px;height:34px;margin-right:10px;}
  .fx-page .fx-sec-ico svg{width:21px;height:21px;}
}
@media (prefers-reduced-motion: reduce){
  .fx-page .card[class*="fx-accent-"], .fx-page .fx-sec-ico,
  .fx-page .fx-sec-ico svg, .fx-page .stat-card .stat-icon svg,
  .fx-page .fx-anim-underline .card-title::after{transition:none;}
  .fx-page .card[class*="fx-accent-"]:hover,
  .fx-page .card[class*="fx-accent-"]:hover .fx-sec-ico svg,
  .fx-page .stat-card:hover .stat-icon svg{transform:none;}
}


/* ---------- Total Insured Value / By Type - clean vector shield+family overlay ---------- */
/* Positions the new inline SVG badge (insurance.js's shieldFamilySvg())
   directly over the same spot the background-art PNG's own (now
   unreliable) shield sits, sized to fully cover it. Both parent cards
   already have position:relative (.ins-value-card, .ins-type-card's own
   base rule), so this just needs its own absolute placement - values
   tuned by eye against each card's actual background-position/size. */
.ins-shield-badge{position:absolute;pointer-events:none;z-index:5;}
.ins-value-shield-badge{left:278px;top:132px;width:98px;height:98px;}
.ins-type-shield-badge{left:325px;top:148px;width:80px;height:80px;}

/* ---------- Shield+family badge - stronger golden outline in dark mode ---------- */
/* Explicit "improve... the golden outline for dark mode" follow-up to the
   white-space fix above. shieldFamilySvg()'s own stroke="#dcae4a"
   stroke-width="5" (insurance.js) is tuned for the light-mode card and
   reads thin/washed-out once the card itself goes dark - CSS can
   override an SVG's presentation attributes, so the outline is thickened
   here rather than forking the SVG markup per theme. :nth-of-type(1)
   targets the shield's own outline path specifically (the first <path> -
   <defs> is technically the SVG's first child but isn't a path, so
   :first-child would never match here) without touching the second
   path's shadow overlay or any of the white family-glyph shapes. The
   added drop-shadow glow reinforces the same gold rather than
   introducing a second color, and lifts the whole badge off the dark
   card a bit further on top of the wider white radial highlight above. */
:root[data-theme="dark"] .ins-shield-badge svg path:nth-of-type(1){
  stroke-width:7;
}
:root[data-theme="dark"] .ins-shield-badge{
  filter:drop-shadow(0 0 4px rgba(220,174,74,.85)) drop-shadow(0 0 1px rgba(255,255,255,.6));
}


/* ---------- Insurance title card - golden animated ring behind the floating icon ---------- */
/* .ins-title-icon-wrap holds a new .ins-title-icon-ring (a spinning conic-
   gradient gold disc, sized larger than the icon itself so a ring of gold
   shows around its edges) BEHIND the icon, and the icon on top - both
   absolutely centered on the same spot. A plain ::after on .ins-title-icon
   wouldn't work here since pseudo-elements paint on top of their own
   element's background regardless of z-index (verified live), so the ring
   needed to be a real sibling element instead. */
.ins-title-icon-wrap{position:relative;flex-shrink:0;width:82px;height:74px;display:flex;align-items:center;justify-content:center;}
.ins-title-icon-ring{
  position:absolute;top:50%;left:50%;transform:translate(-50%,-50%) rotate(0deg);
  width:96px;height:96px;border-radius:50%;
  background:conic-gradient(from 0deg, var(--gold-500), var(--gold-400), #fff5d6, var(--gold-400), var(--gold-500));
  box-shadow:0 0 14px rgba(200,153,46,.55);
  animation:ins-gold-ring-spin 5s linear infinite;
  z-index:1;
}
.ins-title-icon{position:relative;z-index:2;}
@keyframes ins-gold-ring-spin{
  0%{transform:translate(-50%,-50%) rotate(0deg);}
  100%{transform:translate(-50%,-50%) rotate(360deg);}
}
@media (prefers-reduced-motion: reduce){
  .ins-title-icon-ring{animation:none;}
}

/* Total Insured Value's own label ("TOTAL INSURED VALUE") brought in line
   with By Type's card-title styling (15px, normal case, --text-900) per
   explicit request, rather than the small uppercase caption size/weight
   it inherited from the shared .stat-label default. */
.ins-value-card .stat-label{font-weight:800;font-size:15px;text-transform:none;letter-spacing:normal;color:var(--text-900);}


/* ---------- Document Vault title card icon - maximized ---------- */
/* Corrective override: enlarges the illustration (was 100x92) per explicit
   request, matching the more prominent icon scale used on the Insurance
   title card. */
.vault-hero-icon.vault-hero-icon-art{width:160px;height:148px;}


/* ---------- Insurance filter tab icons - made distinct/unique ---------- */
/* Corrective override: at the earlier 18-24px size these all read as
   similar small blue blobs (details in the source art - document, shield,
   heart+stethoscope, umbrella, car - got lost at that scale). Enlarged so
   each icon is actually recognizable, per explicit "made unique" request. */
.ins-tab-icon{width:34px;height:34px;}
.ins-page .tabs .tab.ins-tab-icon-item{gap:10px;}

/* --- Family Expenses: supplied artwork (Family Expenses.docx) -----------
   The five PNGs are RGB with no alpha, so each sits on a light tile instead
   of in the accent-tinted well the drawn SVG marks use — otherwise the
   image's white ground would read as a square pasted into a coloured chip.
   The tile takes the section's accent as a hairline and a soft shadow, which
   is what keeps them looking placed rather than dropped in. object-fit is
   `contain` everywhere, so no image is ever cropped or stretched. */
.fx-page .fx-art{
  display:block;
  width:100%;
  height:100%;
  object-fit:contain;
  object-position:center;
}

/* ---- "Family Expense_v2" artwork ------------------------------------------
   These six renders were cut out to a transparent background, so unlike the
   v1 set they need no light tile to hide a white ground: they sit in the
   module's own accent-tinted well, exactly as the drawn SVG marks do, and
   read as part of the card rather than as a picture pasted onto it.

   Depth comes from a two-stop drop-shadow, which follows the subject's own
   alpha outline instead of a bounding box - a contact shadow tight under the
   form plus a softer cast below it, which is what makes a flat render sit IN
   the surface. object-fit stays `contain` (inherited from .fx-art), so the
   aspect ratio is never touched and nothing is cropped.

   Scoped entirely to .fx-art-3d and its three wells: the v1 artwork and every
   other image in the module keep the treatment they already had. */
.fx-page .fx-art-3d{
  filter:drop-shadow(0 1px 1px rgba(15,31,51,.22))
         drop-shadow(0 4px 7px rgba(15,31,51,.20));
  transition:transform .3s cubic-bezier(.22,1,.36,1), filter .3s ease-out;
}
/* Tab chip. Same 30px well the SVG marks use, with a little breathing room so
   the render is not cropped by the well's own radius. */
.fx-page .fx-tabs .tab-ico-3d{padding:3px;}
.fx-page .fx-tabs .tab.active .tab-ico-3d .fx-art-3d{
  filter:drop-shadow(0 1px 1px rgba(0,0,0,.45))
         drop-shadow(0 4px 8px rgba(0,0,0,.35));
}
.fx-page .fx-tabs .tab:hover .tab-ico-3d .fx-art-3d{transform:translateY(-1px) scale(1.04);}
/* KPI box on Projection. Matches the 56px the Dashboard KPI art already uses,
   so the two rows of cards keep one icon scale between them. White ground
   (instead of the inherited accent-tinted well) on these three boxes only. */
.fx-page .stat-card .stat-icon.fx-icon-3d{
  width:56px;height:56px;
  padding:5px;
  border-radius:15px;
  background:#fff;
}
.fx-page .stat-card:hover .stat-icon.fx-icon-3d .fx-art-3d{transform:translateY(-2px) scale(1.05);}
/* Branch header. The v1 tile is a white plate built for white-ground art; a
   transparent render wants the branch's own accent instead. */
.fx-page .fx-branch-art.fx-branch-3d{
  padding:6px;
  background:linear-gradient(160deg,
    color-mix(in srgb, var(--fx-b) 7%, transparent) 0%,
    color-mix(in srgb, var(--fx-b) 19%, transparent) 100%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 62%, transparent),
             0 6px 14px -9px color-mix(in srgb, var(--fx-b) 80%, transparent);
}
@media (max-width:820px){
  .fx-page .stat-card .stat-icon.fx-icon-3d{width:48px;height:48px;border-radius:13px;}
}
/* Tab chips carrying artwork. Slightly larger than the drawn-mark chips so
   the render still reads at tab scale; the strip grows 4px, no more. */
.fx-page .fx-tabs .tab-ico-art{
  width:34px;height:34px;
  padding:3px;
  border-radius:11px;
  background:linear-gradient(170deg, #fff 0%, color-mix(in srgb, var(--fx-accent) 5%, #fff) 100%);
  border:1px solid color-mix(in srgb, var(--fx-accent) 26%, transparent);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.9),
             0 2px 5px -3px color-mix(in srgb, var(--fx-accent) 70%, transparent);
}
/* On the active navy tab the tile keeps its light ground — the artwork needs
   it — and gains a brighter rim so it still reads as selected. */
.fx-page .fx-tabs .tab.active .tab-ico-art{
  background:linear-gradient(170deg, #fff 0%, #f4f7fc 100%);
  border-color:rgba(255,255,255,.5);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.95),
             0 3px 8px -4px rgba(0,0,0,.45);
}
/* KPI boxes on the Family Dashboard and Projection. .stat-icon already
   reserves the slot; this only swaps the well for the artwork tile. */
.fx-page .stat-card .stat-icon.fx-icon-art{
  width:56px;height:56px;
  padding:4px;
  border-radius:15px;
  border:1px solid color-mix(in srgb, var(--accent, var(--fx-gold)) 24%, transparent);
  background:linear-gradient(170deg, #fff 0%, color-mix(in srgb, var(--accent, var(--fx-gold)) 6%, #fff) 100%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.9),
             0 4px 10px -6px color-mix(in srgb, var(--accent, var(--fx-gold)) 70%, transparent);
}
/* Empty states show the artwork at full size, so the tile grows with it. */
.fx-page .fx-empty-mark:has(.fx-art){
  width:112px;height:112px;
  padding:8px;
  border-radius:26px;
  background:linear-gradient(170deg, #fff 0%, color-mix(in srgb, var(--fx-gold) 6%, #fff) 100%);
  border:1px solid color-mix(in srgb, var(--fx-gold) 24%, transparent);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.9),
             0 12px 26px -16px rgba(15,31,51,.4);
}
.fx-page .fx-empty-mark .fx-art{width:100%;height:100%;}

@media (max-width:820px){
  .fx-page .fx-tabs .tab-ico-art{width:30px;height:30px;border-radius:10px;}
  .fx-page .stat-card .stat-icon.fx-icon-art{width:48px;height:48px;border-radius:13px;}
  .fx-page .fx-empty-mark:has(.fx-art){width:92px;height:92px;border-radius:22px;}
}

/* ---------- Insurance policy list card icon - replaced with the shield+family badge ---------- */
/* Swapped the plain folder emoji for the same clean vector shield+family
   icon already built for Total Insured Value/By Type (shieldFamilySvg()),
   consistent with this page's own branding rather than a generic glyph.
   Small padding keeps it from touching the gold-bg square's edges at this
   34x34 size. */
.ins-card-title-icon-shield{padding:4px;}

/* --- Family Expenses: per-card identity + reading order -----------------
   Each summary box gets its own tinted ground and border so the four read as
   a set of distinct facts rather than four identical white tiles, and the
   order inside each is enforced: artwork -> label -> value -> supporting
   note, with the value carrying the most weight by a clear margin.

   Identity, per the module brief:
     income  green / blue-green      spend  orange / coral
     savings green / teal            rate   blue / purple / gold
   The Projection cards reuse the first three keys, so the two pages read as
   one family. Values keep their existing semantic colour classes (.blue /
   .red / .green / .gold) — only the surfaces are tinted here. ----------- */
.fx-page .stat-card[data-fx]{
  --fx-id:var(--fx-dash);
  --fx-id2:var(--fx-dash);
  position:relative;
  overflow:hidden;
  padding:18px 18px 16px;
  border:1px solid color-mix(in srgb, var(--fx-id) 26%, transparent);
  border-radius:16px;
  background:
    radial-gradient(115% 120% at 100% 0%, color-mix(in srgb, var(--fx-id2) 13%, transparent) 0%, transparent 62%),
    linear-gradient(168deg, color-mix(in srgb, var(--fx-id) 9%, var(--card)) 0%, var(--card) 66%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 60%, transparent),
             0 1px 2px rgba(15,31,51,.04),
             0 16px 32px -26px color-mix(in srgb, var(--fx-id) 60%, rgba(15,31,51,.4));
}
/* A colour rule across the head of each card — the quickest cue that these
   four are different things. */
.fx-page .stat-card[data-fx]::before{
  content:'';
  position:absolute;
  top:0;left:0;right:0;
  height:3px;
  background:linear-gradient(90deg, var(--fx-id) 0%, color-mix(in srgb, var(--fx-id2) 45%, transparent) 100%);
}
.fx-page .stat-card[data-fx="income"] {--fx-id:#2f8f89;--fx-id2:#3f9f7d;}  /* blue-green  */
.fx-page .stat-card[data-fx="spend"]  {--fx-id:#d97a55;--fx-id2:#c9705f;}  /* orange/coral */
.fx-page .stat-card[data-fx="savings"]{--fx-id:#2e8b62;--fx-id2:#37a08b;}  /* green/teal  */
.fx-page .stat-card[data-fx="rate"]   {--fx-id:#5b62c4;--fx-id2:#b8912f;}  /* blue-purple to gold */

/* Reading order. The artwork tile picks up the card's own identity instead
   of the generic accent, the label sits quiet above, and the value is the
   element the eye lands on. */
.fx-page .stat-card[data-fx] .stat-icon.fx-icon-art{
  width:58px;height:58px;
  margin-bottom:12px;
  border-color:color-mix(in srgb, var(--fx-id) 28%, transparent);
  background:linear-gradient(170deg, #fff 0%, color-mix(in srgb, var(--fx-id) 7%, #fff) 100%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.92),
             0 5px 12px -7px color-mix(in srgb, var(--fx-id) 80%, transparent);
}
.fx-page .stat-card[data-fx] .stat-label{
  font-size:10.8px;
  font-weight:700;
  letter-spacing:.11em;
  text-transform:uppercase;
  color:color-mix(in srgb, var(--fx-id) 52%, var(--text-600));
}
.fx-page .stat-card[data-fx] .stat-value{
  margin-top:6px;
  font-size:27px;
  font-weight:700;
  letter-spacing:-.028em;
  line-height:1.1;
  font-variant-numeric:tabular-nums;
}
.fx-page .stat-card[data-fx] .stat-foot{
  margin-top:8px;
  padding-top:8px;
  border-top:1px solid color-mix(in srgb, var(--fx-id) 18%, transparent);
  font-size:11.6px;
  line-height:1.45;
  color:var(--text-400);
}

/* --- Chart and summary sections, matched to the same identities -------- */
.fx-page .fx-accent-teal{--fx-sec:#2f8f89;}
.fx-page .fx-sec-art{
  padding:3px;
  background:linear-gradient(170deg, #fff 0%, color-mix(in srgb, var(--fx-sec) 7%, #fff) 100%);
  border-color:color-mix(in srgb, var(--fx-sec) 26%, transparent);
}
.fx-page .fx-sec-art .fx-art{width:100%;height:100%;}
/* Legends and value labels on the shared chart primitives. */
.fx-page .hbar-label{font-size:12.4px;font-weight:600;color:var(--text-600);}
.fx-page .hbar-value{
  font-size:12.8px;
  font-weight:700;
  letter-spacing:-.01em;
  font-variant-numeric:tabular-nums;
  color:var(--text-900);
}
.fx-page .donut-legend-item{font-size:12.4px;}
.fx-page .donut-legend-item b,
.fx-page .donut-legend-pct{font-variant-numeric:tabular-nums;}

/* Monthly summary: one tint per money column, so a row can be read across
   without checking the header each time. Values and order are unchanged. */
.fx-page .fx-accent-blue tbody td:nth-child(2){color:#2f8f89;font-weight:600;}
.fx-page .fx-accent-blue tbody td:nth-child(3){color:#c25f45;font-weight:600;}
.fx-page .fx-accent-blue tbody td:nth-child(4),
.fx-page .fx-accent-lav tbody td:nth-child(4){font-weight:700;}
.fx-page .fx-accent-lav tbody td:nth-child(2){color:#2f8f89;font-weight:600;}
.fx-page .fx-accent-lav tbody td:nth-child(3){color:#c25f45;font-weight:600;}
.fx-page .fx-accent-lav tbody td:nth-child(5),
.fx-page .fx-accent-blue tbody td:nth-child(5){
  font-weight:700;
  font-variant-numeric:tabular-nums;
  color:color-mix(in srgb, #5b62c4 55%, var(--text-900));
}
.fx-page table td{font-variant-numeric:tabular-nums;}

/* --- Financial Insights: a read-at-a-glance list ----------------------- */
.fx-page .fx-insights{
  display:flex;
  flex-direction:column;
  gap:10px;
  margin:0;
  padding:0;
  list-style:none;
}
.fx-page .fx-insight{
  display:flex;
  align-items:flex-start;
  gap:12px;
  padding:13px 15px;
  border:1px solid color-mix(in srgb, var(--fx-gold) 20%, var(--fx-line-soft));
  border-left:3px solid color-mix(in srgb, var(--fx-gold) 55%, transparent);
  border-radius:12px;
  background:linear-gradient(170deg, color-mix(in srgb, var(--fx-ivory) 52%, var(--card)) 0%, var(--card) 70%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent);
}
.fx-page .fx-insight-mark{
  flex-shrink:0;
  width:9px;height:9px;
  margin-top:5px;
  border-radius:50%;
  background:linear-gradient(140deg, var(--gold-400) 0%, var(--gold-600) 100%);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--fx-gold) 16%, transparent);
}
.fx-page .fx-insight-text{
  font-size:13px;
  font-weight:500;
  line-height:1.55;
  color:var(--text-600);
}

@media (max-width:820px){
  .fx-page .stat-card[data-fx]{padding:15px 15px 14px;}
  .fx-page .stat-card[data-fx] .stat-icon.fx-icon-art{width:48px;height:48px;margin-bottom:10px;}
  .fx-page .stat-card[data-fx] .stat-value{font-size:22px;}
  .fx-page .fx-insight{padding:11px 13px;gap:10px;}
}

/* ---------- Document Vault hero icon - float + glow animation (user-provided) ---------- */
.vault-hero-icon-art {
  animation: vaultHeroFloat 3.2s ease-in-out infinite,
             vaultHeroGlow 3.2s ease-in-out infinite;
  transform-origin: 50% 85%;
  will-change: transform, filter;
}
@keyframes vaultHeroFloat {
  0%   { transform: translateY(0) rotate(0deg); }
  25%  { transform: translateY(-6px) rotate(-2deg); }
  50%  { transform: translateY(-2px) rotate(0deg); }
  75%  { transform: translateY(-6px) rotate(2deg); }
  100% { transform: translateY(0) rotate(0deg); }
}
@keyframes vaultHeroGlow {
  0%, 100% { filter: drop-shadow(0 4px 6px rgba(212, 175, 55, 0.25)); }
  50%      { filter: drop-shadow(0 10px 18px rgba(212, 175, 55, 0.55)); }
}
@media (prefers-reduced-motion: reduce) {
  .vault-hero-icon-art { animation: none !important; }
}


/* ---------- Document Vault title card icon - maximized further ---------- */
/* Corrective override on top of the earlier 160x148 size, per explicit
   follow-up request. */
.vault-hero-icon.vault-hero-icon-art{width:230px;height:212px;}


/* ---------- Document Vault hero icon - swing + masked shine sweep (user-provided) ---------- */
/* Same selector as the earlier float+glow animation - this supersedes it
   via normal cascade (later wins for the animation/will-change/transform-
   origin properties), no need to remove that block. mask-image path
   corrected to ../assets/docvault-hero-art.png: url() in this stylesheet
   resolves relative to css/, not the page root (the as-given assets/...
   path would 404) - the exact bug already fixed for every other
   background-image in this file. */
.vault-hero-icon-art {
  position: relative;
  transform-origin: 50% 8%;
  animation: vaultSwing 2.8s ease-in-out infinite;
  will-change: transform;
}

.vault-hero-icon-art::after {
  content: '';
  position: absolute;
  inset: 0;
  background-image: linear-gradient(
    120deg,
    transparent 35%,
    rgba(255, 255, 255, 0.95) 48%,
    rgba(255, 255, 255, 0.95) 52%,
    transparent 65%
  );
  background-size: 300% 300%;

  -webkit-mask-image: url("../assets/docvault-hero-art.png");
  -webkit-mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-image: url("../assets/docvault-hero-art.png");
  mask-size: contain;
  mask-repeat: no-repeat;
  mask-position: center;

  mix-blend-mode: overlay;
  pointer-events: none;
  animation: vaultShine 3.6s ease-in-out infinite;
}

@keyframes vaultSwing {
  0%, 100% { transform: rotate(0deg); }
  20%      { transform: rotate(-4deg); }
  50%      { transform: rotate(3deg); }
  80%      { transform: rotate(-2deg); }
}

@keyframes vaultShine {
  0%   { background-position: -60% -60%; }
  55%  { background-position: 160% 160%; }
  100% { background-position: 160% 160%; }
}

@media (prefers-reduced-motion: reduce) {
  .vault-hero-icon-art { animation: none !important; }
  .vault-hero-icon-art::after { animation: none !important; content: none; }
}

/* --- Family Income / Family Spend: two tabs, one language ---------------
   Both branches share this block and differ only by --fx-b*, so the pages
   read as a pair. Income takes green / teal / blue-green (growth,
   stability); Spend takes orange / coral / amber (awareness, control). No
   chart is introduced anywhere here — the record table stays the detailed
   view, and every figure in the summary blocks is either a count of those
   same records or the annual total the header already showed. */
.fx-page .fx-branch{
  --fx-b:#2f8f89;
  --fx-b2:#3f9f7d;
  display:flex;
  flex-direction:column;
  gap:16px;
}
.fx-page .fx-branch-spend{--fx-b:#d97a55;--fx-b2:#c9705f;}

/* Header plate: artwork, eyebrow, title, quote, then the existing counts. */
.fx-page .card.fx-branch-head{
  position:relative;
  overflow:hidden;
  display:flex;
  align-items:flex-start;
  justify-content:space-between;
  gap:20px;
  flex-wrap:wrap;
  padding:22px 24px;
  border:1px solid color-mix(in srgb, var(--fx-b) 24%, var(--fx-line));
  border-radius:18px;
  background:
    radial-gradient(105% 150% at 0% 0%, color-mix(in srgb, var(--fx-b) 12%, transparent) 0%, transparent 58%),
    radial-gradient(85% 130% at 100% 0%, color-mix(in srgb, var(--fx-b2) 10%, transparent) 0%, transparent 56%),
    linear-gradient(180deg, color-mix(in srgb, var(--fx-ivory) 62%, var(--card)) 0%, var(--card) 100%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 60%, transparent),
             0 1px 2px rgba(15,31,51,.04),
             0 18px 36px -30px rgba(15,31,51,.36);
}
.fx-page .card.fx-branch-head::after{
  content:'';
  position:absolute;
  left:0;right:0;bottom:-1px;
  height:2px;
  background:linear-gradient(90deg,
    var(--fx-b) 0%,
    color-mix(in srgb, var(--fx-b2) 34%, transparent) 46%,
    transparent 92%);
}
.fx-page .fx-branch-lead{display:flex;align-items:flex-start;gap:16px;flex:1 1 340px;min-width:0;}
.fx-page .fx-branch-art{
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:62px;height:62px;
  flex-shrink:0;
  padding:5px;
  border-radius:17px;
  border:1px solid color-mix(in srgb, var(--fx-b) 26%, transparent);
  background:linear-gradient(170deg, #fff 0%, color-mix(in srgb, var(--fx-b) 7%, #fff) 100%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.92),
             0 6px 14px -8px color-mix(in srgb, var(--fx-b) 80%, transparent);
}
.fx-page .fx-branch-art .fx-art{width:100%;height:100%;}
.fx-page .fx-branch-text{min-width:0;}
.fx-page .fx-branch-eyebrow{
  font-size:10.2px;
  font-weight:700;
  letter-spacing:.18em;
  text-transform:uppercase;
  color:color-mix(in srgb, var(--fx-b) 62%, var(--text-900));
}
.fx-page .fx-branch-title{
  margin:6px 0 0;
  font-size:23px;
  font-weight:700;
  letter-spacing:-.024em;
  line-height:1.18;
  color:var(--text-900);
}
/* The quote is an inscription, not UI copy: serif, italic, led by a short
   rule in the branch colour. */
.fx-page .fx-branch-quote{
  display:flex;
  align-items:baseline;
  gap:11px;
  margin:9px 0 0;
  font-family:Georgia, 'Times New Roman', serif;
  font-style:italic;
  font-size:13.8px;
  line-height:1.45;
  color:color-mix(in srgb, var(--fx-b) 42%, var(--text-600));
}
.fx-page .fx-branch-quote::before{
  content:'';
  flex-shrink:0;
  width:20px;height:2px;
  border-radius:2px;
  transform:translateY(-4px);
  background:linear-gradient(90deg, var(--fx-b), color-mix(in srgb, var(--fx-b) 22%, transparent));
}
.fx-page .fx-branch-sub{
  margin:10px 0 0;
  font-size:13px;
  font-weight:500;
  color:var(--text-600);
}
.fx-page .fx-branch-controls{
  display:flex;
  align-items:center;
  gap:10px;
  flex-wrap:wrap;
  flex-shrink:0;
}

/* Summary blocks. The first is the headline figure and gets the weight. */
.fx-page .fx-sum{
  display:grid;
  grid-template-columns:1.6fr 1fr 1fr;
  gap:14px;
}
.fx-page .fx-sum-card{
  position:relative;
  overflow:hidden;
  padding:16px 18px;
  border:1px solid color-mix(in srgb, var(--fx-b) 22%, var(--fx-line));
  border-radius:15px;
  background:
    radial-gradient(115% 120% at 100% 0%, color-mix(in srgb, var(--fx-b2) 11%, transparent) 0%, transparent 62%),
    linear-gradient(168deg, color-mix(in srgb, var(--fx-b) 7%, var(--card)) 0%, var(--card) 66%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 58%, transparent),
             0 1px 2px rgba(15,31,51,.04),
             0 14px 28px -24px color-mix(in srgb, var(--fx-b) 55%, rgba(15,31,51,.4));
}
.fx-page .fx-sum-card::before{
  content:'';
  position:absolute;
  top:0;left:0;right:0;
  height:3px;
  background:linear-gradient(90deg, var(--fx-b) 0%, color-mix(in srgb, var(--fx-b2) 40%, transparent) 100%);
}
.fx-page .fx-sum-label{
  font-size:10.6px;
  font-weight:700;
  letter-spacing:.11em;
  text-transform:uppercase;
  color:color-mix(in srgb, var(--fx-b) 52%, var(--text-600));
}
.fx-page .fx-sum-value{
  margin-top:7px;
  font-size:21px;
  font-weight:700;
  letter-spacing:-.026em;
  line-height:1.1;
  font-variant-numeric:tabular-nums;
  color:var(--text-900);
}
.fx-page .fx-sum-lead .fx-sum-value{
  font-size:28px;
  color:color-mix(in srgb, var(--fx-b) 58%, var(--text-900));
}
.fx-page .fx-sum-foot{
  margin-top:7px;
  padding-top:7px;
  border-top:1px solid color-mix(in srgb, var(--fx-b) 16%, transparent);
  font-size:11.4px;
  line-height:1.45;
  color:var(--text-400);
}

/* Record table: the page's detailed view, kept as a table and only polished.
   The key column (Annual Income / Annual Spend) takes the branch colour so
   the eye lands on the figure the page is really about. */
.fx-page .card.fx-branch-body{padding:6px 8px 8px;border-radius:16px;}
.fx-page .fx-table th{
  color:color-mix(in srgb, var(--fx-b) 46%, var(--text-900));
  border-bottom:1px solid color-mix(in srgb, var(--fx-b) 22%, transparent);
  padding:14px 14px 12px;
}
.fx-page .fx-table th.fx-col-key{color:color-mix(in srgb, var(--fx-b) 72%, var(--text-900));}
.fx-page .fx-table td{padding:14px;}
.fx-page .fx-table tbody tr:nth-child(even) td{
  background:color-mix(in srgb, var(--fx-b) 4%, transparent);
}
@media (hover:hover) and (pointer:fine){
  .fx-page .fx-table tbody tr:hover td{background:color-mix(in srgb, var(--fx-b) 9%, transparent);}
}
.fx-page .fx-cell-name{
  font-weight:700;
  letter-spacing:-.008em;
  color:var(--text-900);
}
.fx-page .fx-cell-key{
  font-weight:700;
  font-size:13.6px;
  letter-spacing:-.012em;
  font-variant-numeric:tabular-nums;
  color:color-mix(in srgb, var(--fx-b) 62%, var(--text-900));
}
.fx-page .fx-table .table-actions{display:flex;gap:6px;justify-content:flex-end;}
.fx-page .fx-table .table-actions .btn{padding:5px 11px;font-size:12.1px;border-radius:9px;}

/* Closing thought, filling the space under the table without crowding it. */
.fx-page .fx-note{
  display:flex;
  align-items:flex-start;
  gap:13px;
  padding:15px 18px;
  border:1px solid color-mix(in srgb, var(--fx-b) 20%, var(--fx-line-soft));
  border-left:3px solid color-mix(in srgb, var(--fx-b) 55%, transparent);
  border-radius:14px;
  background:linear-gradient(170deg, color-mix(in srgb, var(--fx-b) 5%, var(--card)) 0%, var(--card) 72%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 55%, transparent);
}
.fx-page .fx-note-mark{
  flex-shrink:0;
  width:9px;height:9px;
  margin-top:5px;
  border-radius:50%;
  background:linear-gradient(140deg, var(--fx-b2) 0%, var(--fx-b) 100%);
  box-shadow:0 0 0 3px color-mix(in srgb, var(--fx-b) 15%, transparent);
}
.fx-page .fx-note-text{
  margin:0;
  font-family:Georgia, 'Times New Roman', serif;
  font-style:italic;
  font-size:13.4px;
  line-height:1.55;
  color:color-mix(in srgb, var(--fx-b) 34%, var(--text-600));
}

@media (max-width:1000px){
  .fx-page .fx-sum{grid-template-columns:1fr 1fr;}
  .fx-page .fx-sum-lead{grid-column:1 / -1;}
}
@media (max-width:820px){
  .fx-page .card.fx-branch-head{padding:18px 16px;gap:14px;}
  .fx-page .fx-branch-art{width:52px;height:52px;border-radius:15px;}
  .fx-page .fx-branch-title{font-size:20px;}
  .fx-page .fx-branch-quote{font-size:13px;}
  .fx-page .fx-branch-controls{width:100%;}
  .fx-page .fx-branch-controls .btn{flex:1;}
  .fx-page .fx-sum{grid-template-columns:1fr;}
  .fx-page .fx-sum-lead .fx-sum-value{font-size:24px;}
}

/* ---------- Document Vault hero - icon reduced + text block moved left ---------- */
/* Same restructuring as the Insurance title card (icon+heading grouped in
   a .vault-hero-top-row, subtitle/badges below) per explicit "similarly"
   request - icon moved from a standalone sibling into the text block so
   the whole heading/subtitle/badges group reads as one compact left-
   aligned unit instead of being pushed right by a large standalone icon.
   Icon size dialed back down (was 230x212) to fit this row comfortably. */
.vault-hero-icon.vault-hero-icon-art{width:96px;height:88px;}
.vault-hero-top-row{display:flex;align-items:center;gap:16px;margin-bottom:6px;}
.vault-hero-title{margin:0;}


/* ---------- Archived Vault hero - real icon + safe/documents art ("Archived Vault-Hero section.png") ---------- */
/* Replaces the plain emoji icon and closes the gap this file's own
   comment flagged (no asset existed for the reference's photographic
   safe/passport/photo/plant collage) - both cropped straight from the new
   reference image. Kept as opaque photo panels rather than transparent
   cutouts: their own warm cream backdrop already closely matches this
   hero's own cream gradient, so background-removal wasn't needed (and
   risked repeating the pixel-damage problem hit earlier on Insurance's
   shield art, where a similarly warm-toned background couldn't be
   reliably separated from similarly warm foreground colors). */
.vault-hero-icon.vault-hero-icon-av{
  border-radius:50%;background:url('../assets/archivedvault-hero-icon.png') no-repeat center / contain;
}
.vault-hero-art{
  flex:0 0 300px;height:140px;border-radius:12px;overflow:hidden;
  background:url('../assets/archivedvault-hero-art.png') no-repeat center / cover;
}
@media(max-width:960px){
  .vault-hero-art{display:none;}
}


/* ---------- Archived Vault hero - decorative tree-branch watermark ---------- */
/* .vault-page-hero already has position:relative;overflow:hidden (its own
   base rule), so this just needs its own absolute placement + a z-index
   below the tagline text that sits over it. */
.vault-hero-tree-bg{
  position:absolute; right:0; top:0; bottom:0; width:260px;
  pointer-events:none; opacity:.35; z-index:0;
}
.vault-hero-tagline{position:relative;z-index:1;}


/* ---------- Insurance title card - extend the reference image into the side gaps ---------- */
/* Corrective override: replaces the earlier flat sky-blue gradient fill
   (used to cover the empty side gaps left by background-size:contain)
   with a smooth stretch of the reference photo's own left/right edge
   columns (insurance-title-bg-edge-left.png / -edge-right.png, each a
   40px-wide strip cropped straight from insurance-title-bg.png), so the
   fill is genuinely derived from the image instead of a guessed color.
   Tried first with the full image repeated on each side at auto-100% -
   visibly showed 2-3 overlapping copies of the family/icons scene, which
   read as a repetition glitch rather than an extension; a thin edge strip
   stretched avoids that entirely since it has no repeatable subject
   matter, just sky/ground color.
   The sharp, undistorted scene now lives in its own .ins-title-bg-focus
   layer (contain, centered, on top via z-index) so it isn't itself
   stretched - only the two edge fills are. */
.ins-title-card{
  background:
    url('../assets/insurance-title-bg-edge-left.png') no-repeat left center / auto 100%,
    url('../assets/insurance-title-bg-edge-right.png') no-repeat right center / auto 100%;
}
.ins-title-bg-focus{
  position:absolute;inset:0;
  background:url('../assets/insurance-title-bg.png') no-repeat center center / contain;
  z-index:1;
}
.ins-title-text, .ins-title-tagline{position:relative;z-index:2;}


/* ---------- Insurance title card - fit subtitle text to the left side ---------- */
/* The subtitle was overlapping ~200px into the sharp contain-ed photo
   (measured live: text right edge at 798px, photo's own left edge at
   594px within the card) - shifting the focus layer's background-position
   rightward frees up room on the left, and capping the text block's width
   keeps it from reaching into the photo at all. */
.ins-title-bg-focus{background-position: 78% center;}
.ins-title-text{max-width:300px;}


/* ---------- Insurance title card - real shield/icon artwork on both edges ---------- */
/* Per "insurance-hero-edges.css" (Web Design/insurance) + "Left side.png"/
   "Right side.png" - replaces the earlier plain edge-color-stretch fill
   with the user's own dedicated shield+insurance-icon artwork panels,
   full card height (auto 100%) exactly as that file specified. Deployed
   as real asset files (insurance-title-bg-left.png / -right.png) rather
   than the source file's inline base64, since this app already has
   direct filesystem access to the originals - no need to bloat the
   stylesheet with embedded image data.
   At auto-100% sizing these render ~180px/~148px wide - wide enough to
   collide with the text/tagline that used to start right at the card's
   own padding edge, so .ins-title-text/.ins-title-tagline get matching
   margins to clear them (verified live against the actual rendered
   widths, not just estimated from the source aspect ratios). */
.ins-title-card{
  background-image:
    url('../assets/insurance-title-bg-left.png'),
    url('../assets/insurance-title-bg-right.png');
  background-position: left center, right center;
  background-size: auto 100%, auto 100%;
  background-repeat: no-repeat, no-repeat;
}
.ins-title-text{margin-left:150px;}
.ins-title-tagline{margin-right:110px;}


/* ---------- Insurance title card - final alignment pass ---------- */
/* Fixes found on review:
   1. .ins-title-text had align-self:flex-start (from the earlier icon-row
      restructure), sitting at the top of the stretched row while the
      photo rendered vertically centered - now align-self:center on both
      text and tagline so everything shares the same vertical center.
   2. The contain-sized photo (~582px at full card height) was simply too
      wide to fit between the left-artwork-cleared text zone and the
      right-artwork-cleared tagline zone (the two clearance zones' gap is
      only ~430px) - no repositioning could avoid overlapping one side or
      the other. Switched from contain to an explicit fixed size (390px)
      small enough to fit that gap with room on both sides, position
      tuned by eye against the actual rendered zones rather than computed
      from the source aspect ratio (which doesn't account for the text's
      real wrapped width).
   3. Text/tagline margins increased further for clean separation from
      the now-adjacent left/right artwork instead of just barely clearing
      it. */
.ins-title-text{align-self:center; margin-left:210px; max-width:280px;}
.ins-title-tagline{align-self:center; margin-right:190px;}
.ins-title-bg-focus{background-size:390px auto; background-position:570px center;}


/* ---------- Insurance title card - photo as a real flex child (robust fix) ---------- */
/* Replaces the absolutely-positioned .ins-title-bg-focus overlay + hand-
   tuned px margins/positions (fragile - verified it broke on a second,
   differently-sized browser window since the tagline's position came
   from flexbox math while the photo's came from a fixed px offset, two
   independent systems with no guaranteed relationship). The photo
   (insurance.js's new .ins-title-photo div) is now a genuine flex
   sibling between .ins-title-text and .ins-title-tagline, so flexbox
   itself guarantees no overlap at any container width - no more manual
   position/margin math to keep in sync.
   background-position "center 62%" (not plain center) since a straight
   center-crop of the source at this box's aspect ratio lands between the
   icon ring and the family group, cropping both; weighting down toward
   62% keeps the family clearly in frame.
   .ins-title-sub gets a touch more top margin since the icon ring
   (96px, larger than its own 74px-tall wrap) overflows the wrap's box by
   a few px and was just barely reaching into the subtitle's first line. */
.ins-title-text{flex:0 1 320px; margin-left:0; max-width:320px; align-self:center;}
.ins-title-photo{
  flex:0 0 390px; align-self:stretch; border-radius:10px; overflow:hidden;
  background:url('../assets/insurance-title-bg.png') no-repeat center 62% / cover;
}
.ins-title-tagline{align-self:center; margin-right:0; margin-left:auto;}
.ins-title-sub{margin-top:16px;}


/* ---------- Insurance title card - no white gaps, text overlaid on left artwork ---------- */
/* Per explicit request: the light card-background gaps that used to sit
   between the left/right artwork and the center photo are gone - the left
   and right artwork now each fill their own flex-grow panel edge-to-edge
   (background-size:cover, cropping the tall source art to whatever width
   remains around the fixed-width center photo), with the icon+heading/
   subtitle and the tagline overlaid directly on top of those panels
   (insurance.js's new .ins-title-left-panel/.ins-title-right-panel wrap
   them) instead of occupying a separate plain-background zone.
   Heading/subtitle text is now a fixed white (not a theme variable) since
   it always sits on this same fixed-color blue artwork regardless of
   app theme - verified in both light and dark mode, unaffected either
   way (matches the tagline's own existing fixed-gold-color convention). */
.ins-title-card{gap:0;}


/* ---------- Insurance title card - remove top/bottom white space ---------- */
/* The card's own vertical padding (34px top/bottom) was still showing the
   card's own light background above/below the artwork panels, even after
   the earlier fix removed the horizontal gaps - align-self:stretch only
   fills the padded content box, not the padding itself. Zeroing vertical
   padding lets the artwork/photo run fully edge-to-edge top-to-bottom;
   horizontal padding is untouched (still needed inside the left/right
   panels for the overlaid text/tagline). */
.ins-title-card{padding-top:0; padding-bottom:0;}


/* ---------- Insurance title card - maximize center photo, minimize side panels ---------- */
/* Corrective override: center photo widened from 390px to 650px per
   explicit request; the left/right panels (flex:1 1 auto) shrink to
   whatever remains automatically - verified the overlaid heading/
   subtitle and tagline still fit and read clearly at the resulting
   narrower panel width. */
.ins-title-photo{flex:0 0 650px;}




/* ---------- Insurance title card - remove the now-redundant card-level background ---------- */
/* Root cause of the "left side image doubled" report: .ins-title-card
   itself still carried its own left/right artwork background-image
   (from the earlier two-layer approach, before the icon+text/tagline
   were moved onto dedicated .ins-title-left-panel/.ins-title-right-panel
   divs) - that old background was painting BEHIND the new panels using a
   different size/position (auto 100% vs the panels' own cover-fit), so
   at the panel's rounded corners and edges a second, differently-cropped
   copy of the same artwork could peek through. The panels now own their
   background entirely; the card itself needs none. */
.ins-title-card{background:none;}


/* ---------- Insurance title banner — the comp's single photo ----------
   Everything the card used to carry besides the photo is gone: the two blue
   artwork panels, the overlaid heading/subtitle and the script tagline, the
   shimmer that swept those panels, and the dark-mode colour fix that existed
   only to keep that overlaid text readable. The comp is one uninterrupted
   photo behind a gold rule, so there is nothing left for any of it to sit
   on. Their rules are deleted rather than left to dangle — this card already
   carries a long trail of corrective overrides, and dead selectors for
   elements that no longer render are what made that trail hard to follow.

   The photo is the sole child now, so it simply fills the card. A fixed
   height rather than an aspect ratio: the source is a wide 2170x725 pan and
   the page wants a band, not a 3:1 block pushing the summary cards down. */
.ins-title-card{
  display:block;padding:0;overflow:hidden;
  border:1.5px solid var(--gold-500);
  border-radius:12px;
}
.ins-title-photo{
  /* 265px, not the 210 first tried: the source is a 2170x725 pan (≈3:1) and
     the band is far wider than that, so `cover` scales to the width and
     throws away most of the height. At 210 the crop window was tight enough
     that the arc of insurance icons across the top was cut off — the very
     thing the comp leads with. This height keeps the whole arc and the
     family together, at roughly the comp's own banner proportion. */
  width:100%;height:265px;border-radius:0;
  /* Nudged just above centre for the same reason the old centre panel sat
     below it: the scene's subject runs from the icon arc down to the family,
     and a plain centre crop splits the difference badly. */
  background:url('../assets/insurance-title-bg.png') no-repeat center 46% / cover;
}
@media(max-width:900px){
  .ins-title-photo{height:180px;}
}


/* ---------- Document Vault hero - "Preserve Your Legacy" family+tree image on the left ---------- */
/* Cropped straight from "PreserveYour Legacy.png" (Web Design/Document
   Vault) to just the family+goals-tree scene, excluding the reference's
   own baked "Preserve/Your Legacy" text on its left edge - this hero
   already renders that exact phrase live via .vault-hero-tagline
   (top-right), so keeping the baked text too would have doubled it up
   (same reasoning as Archived Vault's tree watermark and Insurance's
   title-bg letterbox fixes earlier this session). Placed as its own flex
   panel at the very start of the hero row; the existing icon/heading/
   subtitle/badges and tagline are unchanged, just shifted right to make
   room. */
.vault-page-hero{gap:20px;}
.vault-hero-legacy-img{
  flex:0 0 190px; align-self:stretch; border-radius:12px; overflow:hidden;
  background:url('../assets/docvault-title-legacy.png') no-repeat center / cover;
}
@media(max-width:860px){
  .vault-hero-legacy-img{display:none;}
}


/* ---------- Document Vault hero - move legacy image to the right side ---------- */
/* Corrective override: moved from the start of the flex row to the end
   via flex `order` (simplest fix, no markup reordering needed) per
   explicit follow-up request. */
.vault-hero-legacy-img{order:3;}


/* ---------- Insurance title card - swap in "Hero section.png" ---------- */
/* Corrective override: swaps the single-photo band's source from the
   older insurance-title-bg.png (2170x725, richer scene with leaf/gold
   decorative accents) to the new insurance-hero-section.png (650x217,
   per "Hero section.png") - a cleaner crop of the same family+icon-arc
   composition, explicitly requested as "only one image" for this card.
   Vertical position re-tuned for this image's own framing: at plain
   center the icon arc's top edge was clipped; 20% keeps the whole arc
   and the family group both in frame, same goal the previous position
   comment described for the old asset. */
.ins-title-photo{
  background:url('../assets/insurance-hero-section.png') no-repeat center 20% / cover;
}


/* ---------- Insurance title card - revert to the previous image ---------- */
/* Undoes the "Hero section.png" swap - restores the exact background rule
   that was in place before that change (insurance-title-bg.png at
   center 46%, per the concurrent editor's own tuned values). */
.ins-title-photo{
  background:url('../assets/insurance-title-bg.png') no-repeat center 46% / cover;
}


/* ---------- Insurance title card - "1001509864.png", full image visible ---------- */
/* Corrective override: swaps to the new reference image and, per explicit
   request to see the FULL image (not cropped), replaces the fixed
   height:265px with aspect-ratio:1536/513 - the source's own exact
   dimensions - so the box's shape always matches the image, eliminating
   any need to crop (cover) or letterbox (contain); the two behave
   identically once the container's aspect ratio already matches the
   image's own. */
.ins-title-photo{
  height:auto; aspect-ratio:1536/513;
  background:url('../assets/insurance-hero-1001509864.png') no-repeat center / cover;
}


/* ---------- Insurance title card - reduce hero height, keep full width + full image ---------- */
/* Per explicit confirmation: card stays full width (unchanged), height
   reduced to a fixed 150px, and background-size switched from cover to
   contain so the whole image still fits with zero cropping - at this
   much shorter/wider box than the image's own 3:1 aspect ratio, contain
   letterboxes (empty space) on the left/right rather than top/bottom. */
.ins-title-photo{
  height:150px;
  background:url('../assets/insurance-hero-1001509864.png') no-repeat center / contain;
}


/* ---------- Insurance title card - fill the hero box completely (cover) ---------- */
/* Per explicit request: reverts the previous contain/letterbox treatment
   back to cover, so the image fills the existing hero box (unchanged
   outer size, border, radius, spacing) with zero empty space, centered.
   This intentionally re-introduces cropping at this box's aspect ratio -
   that trade-off (crop vs. letterbox) was the explicit ask here. */
.ins-title-photo{
  background:url('../assets/insurance-hero-1001509864.png') no-repeat center center / cover;
}


/* ---------- Insurance title card - size the box to the image, not the image to the box ---------- */
/* Root cause of "the box doesn't fit the image": two rules were fighting, and
   both were losing.

   The aspect-ratio:1536/513 added earlier — the source's own exact shape, so
   that cover and contain become the same thing and nothing is cropped or
   letterboxed — was then overridden by a later height:150px. A fixed height
   beats an aspect ratio, so the box went back to roughly 8:1 against a 3:1
   image and `cover` threw away most of the height again, taking the arc of
   insurance icons with it — the thing the comp leads with.

   Meanwhile .ins-title-card still carried min-height:190px from the old
   three-panel layout, when the photo it now wraps was only 150px tall. That
   difference is the white band that was sitting under the photo.

   So: the height goes back to auto and the aspect ratio is re-asserted after
   every rule that set a height, and the card's min-height is dropped so it
   simply takes the photo's own. The box is now whatever shape the image is,
   at any width, and there is nothing left to crop or pad. */
.ins-title-card{min-height:0;}
.ins-title-photo{
  width:100%;height:auto;aspect-ratio:1536/513;
}
/* The narrow-width rule set its own fixed 180px, which would re-open exactly
   the same fight below 900px. The aspect ratio needs no breakpoint — it is
   already proportional — so this simply lets it through. */
@media(max-width:900px){
  .ins-title-photo{height:auto;}
}


/* ---------- Insurance title card - full artwork visible, fixed box unchanged ---------- */
/* Per explicit final instruction: the hero box's own height (150px, set
   in the earlier "reduce hero size" fix) and the card's border/radius/
   spacing are untouched here - only the image's own background-size
   changes, back from cover to contain, so the complete original
   composition (family, shield, all 5 insurance icons, sky, mountains)
   is visible and scaled proportionally with zero cropping/stretching.
   This does reintroduce empty space on the sides at this box's aspect
   ratio - an explicit, accepted trade-off (full artwork visible takes
   priority over filling every pixel). */
.ins-title-photo{
  background:url('../assets/insurance-hero-1001509864.png') no-repeat center center / contain;
}


/* ---------- Insurance hero - matched to the Business Details hero box ---------- */
/* Every metric below is read straight off .bd-hero so the two pages open the
   same way: 232px tall, a 16px radius, a 0.8px gold hairline at .24 alpha
   rather than the heavier 1.5px solid rule this card carried, an 18px gap to
   the row beneath, and the same soft drop shadow.
   Sizing the box to the image (the aspect-ratio pass above) had made it 408px
   at full width — nearly double .bd-hero, and enough to push the summary row
   most of the way off the first screen.

   aspect-ratio has to be released explicitly rather than just overridden by a
   height: with both set it still governs the box's used height here, and the
   232px would simply be ignored.

   And back to a crop, necessarily. The source is 3:1 and this box is 5.3:1, so
   the whole frame cannot fill it — `contain` would letterbox the sides, which
   is the empty-gap look an earlier revision of this card already rejected as
   reading broken.

   58% is where the crop window costs least. Only 292px of the source's 513
   survive, so something has to go, and the three candidates were checked
   against each other on the page: at 25% the whole badge arc including the
   crowning shield is kept but the family loses both parents' bodies and all
   three children; at 78% the family stands complete on the grass but the
   heart, car and shield badges are all gone. 58% keeps the entire family in
   frame together with the shield arch behind them and four of the five badges,
   losing only the top of the arc — the least of the three losses. */
.ins-title-card{
  min-height:232px;
  margin-bottom:18px;
  border:0.8px solid rgba(212,175,55,.24);
  border-radius:16px;
  box-shadow:0 10px 30px rgba(0,0,0,.32);
}
/* 2026-09-15 - back to a still image, per explicit request
   ("ChatGPT Image Sep 15, 2026, 04_58_48 PM.png" - the same family+icon-
   arc scene, now a 2170x725 (~3:1) photo instead of the 1280x720 (16:9)
   video). .ins-title-photo reverts to a background-image div
   (insurance.js dropped the <video>/<source> markup back to the plain
   div it was before the video swap).

   Kept the box height this card settled on for the video (340px desktop,
   280px at the 900px breakpoint) rather than picking a new value from
   scratch - this new image's ~3:1 ratio is wide enough that cover barely
   needs to crop anything at that height: at a ~1083px-wide card, cover
   only crops ~6% off the top/bottom at 340px, and ~23% at 280px, in both
   cases far gentler than the crop the same box gave the video's taller
   16:9 frame. object-position:center 45% mirrors the same family-biased
   position settled on for the video, kept here for consistency even
   though this image's shallower crop makes the exact position much less
   consequential than it was there. */
.ins-title-photo{
  height:340px;aspect-ratio:auto;
  background:url('../assets/insurance-hero-v2.png') no-repeat center 45% / cover;
}
@media(max-width:900px){
  .ins-title-photo{height:280px;aspect-ratio:auto;}
}


/* ---------- Insurance hero - floating icon + heading + subtitle, left side ---------- */
/* New overlay sitting on top of the hero photo (whatever crop/height the
   photo currently is - this only needs .ins-title-card to be a positioning
   context, it doesn't touch the photo's own sizing rules). Fresh class
   names (.ins-hero-*) rather than reusing the old .ins-title-icon/-heading/
   -sub/-text trail: those carry ~15 accumulated overrides from the earlier
   three-panel layout (fixed widths/animations/font-sizes per breakpoint)
   that would cascade in unpredictable ways here. White text with a strong
   drop-shadow reads on the photo in both themes without a data-theme
   branch, since the background is a photo, not a theme-tinted panel. */
.ins-title-card{position:relative;}
.ins-hero-overlay{
  position:absolute; z-index:2; left:28px; top:50%; transform:translateY(-50%);
  display:flex; flex-direction:column; align-items:flex-start; gap:6px;
  max-width:250px; pointer-events:none;
}
.ins-hero-icon{
  width:48px; height:48px; margin-bottom:2px;
  background:url('../assets/insurance-title-bg-left.png') no-repeat center / contain;
  filter:drop-shadow(0 4px 10px rgba(0,0,0,.45));
  animation:ins-hero-icon-float 3s ease-in-out infinite;
}
@keyframes ins-hero-icon-float{
  0%,100%{transform:translateY(0);}
  50%{transform:translateY(-7px);}
}
.ins-hero-heading{
  margin:0; font-size:22px; font-weight:800; color:#fff; line-height:1.1;
  text-shadow:0 2px 8px rgba(0,0,0,.6), 0 0 3px rgba(0,0,0,.45);
}
.ins-hero-sub{
  margin:0; font-size:12.5px; line-height:1.4; color:#fff; max-width:230px;
  text-shadow:0 1px 6px rgba(0,0,0,.65), 0 0 2px rgba(0,0,0,.5);
}
@media(max-width:900px){
  .ins-hero-overlay{left:16px; max-width:190px; gap:4px;}
  .ins-hero-icon{width:34px; height:34px;}
  .ins-hero-heading{font-size:16px;}
  .ins-hero-sub{font-size:10.5px; max-width:170px;}
}
@media(prefers-reduced-motion: reduce){
  .ins-hero-icon{animation:none;}
}


/* ---------- Insurance hero - swap floating icon to "Insurance - Title card Icon.png" ---------- */
/* Corrective override: replaces the earlier insurance-title-bg-left.png
   (a cropped panel edge, square box) with the dedicated title-card icon
   asset (shield + coins, 159x90 native). Width/height switched from the
   old 48x48 square to match this asset's own ~1.77:1 ratio so it doesn't
   stretch. */
.ins-hero-icon{
  width:64px; height:36px;
  background:url('../assets/insurance-title-card-icon.png') no-repeat center / contain;
}
@media(max-width:900px){
  .ins-hero-icon{width:46px; height:26px;}
}


/* ---------- Insurance hero - spin the floating icon ---------- */
/* Corrective override: replaces the up/down float with a continuous
   rotation, per explicit request. */
@keyframes spin{
  0%{transform:rotate(0deg);}
  100%{transform:rotate(360deg);}
}
.ins-hero-icon{
  animation:spin 2s linear infinite;
}
@media(prefers-reduced-motion: reduce){
  .ins-hero-icon{animation:none;}
}


/* ---------- Security Settings - the 2FA banner's real artwork ---------- */
/* The banner's ground was drawn rather than photographed: a green gradient
   under two tiled SVG layers, one carrying circuit traces and binary, the
   other locks and shields, each drifting on its own keyframe. It was a good
   approximation, but the comp's ground is a rendered image with depth, glow
   spots and motifs at varying scale that a repeating tile cannot reach — at
   any tile size the repeat eventually shows.

   So the artwork itself now goes in, cropped from the comp: a clean band of it
   with no baked copy and clear of the process panel, which is why the crop is
   wide and short. `cover` then scales it to whatever the banner is.

   Both tile layers are re-purposed rather than switched off and left hanging.
   ::before becomes the photo — it was already inset:0 and pointer-events:none,
   and putting the image on an element rather than on the card is what lets the
   drift below be a transform. (background-position drifts on the main thread
   and judder; that is why the artwork on the WhatsApp cards sits on its own
   element too.) ::after becomes the scrim the photo now needs.

   ssGridPan and ssLockDrift are left in place but no longer referenced — the
   tiles they panned are gone. */
.ss-2fa-hero{
  /* Kept as the ground under the photo: it shows through nothing at full
     width, but it is what the banner falls back to if the image 404s, and it
     is the colour the rounded corners clip against. */
  background:linear-gradient(135deg,#1a7c47 0%,#12603a 38%,#0b3d24 72%,#082e1b 100%);
}
.ss-2fa-hero::before{
  content:'';position:absolute;inset:0;pointer-events:none;
  opacity:1;
  background:url('../assets/security-2fa-circuit.png') no-repeat center center / cover;
  /* Scaled up at rest so the drift never exposes the banner's edge. */
  transform:scale(1.06);
  animation:none;
}
/* The scrim. Heaviest at the left where the heading, the status pill and the
   button sit, thinning to nothing before the process panel so the artwork
   reads at full strength across the middle and right — the same falloff the
   comp has. Paints above the photo and below the copy: all three of ::before,
   ::after and the scan span sit at z-index auto or 1, and the copy's z-index:2
   clears the lot. */
.ss-2fa-hero::after{
  content:'';position:absolute;inset:0;pointer-events:none;
  opacity:1;
  background:
    linear-gradient(100deg,
      rgba(4,26,15,.62) 0%,
      rgba(5,32,19,.42) 28%,
      rgba(6,38,22,.14) 54%,
      rgba(6,38,22,0) 74%),
    radial-gradient(circle at 88% 90%, rgba(3,22,13,.34) 0, transparent 62%);
  animation:none;
}
@media (prefers-reduced-motion: no-preference){
  /* The ground still moves, but as one photograph pushing slowly in rather
     than as two tiles panning past each other. Long and small, for the reason
     the old tile drift was: this banner is the whole page, on screen the
     entire time, and anything quick enough to notice twice would nag. */
  .ss-2fa-hero::before{
    animation:ss2faDrift 34s ease-in-out infinite alternate;
  }
  @keyframes ss2faDrift{
    from{transform:scale(1.06) translate(0,0);}
    to  {transform:scale(1.13) translate(-14px,-8px);}
  }
}


/* ---------- Insurance hero - icon swings left/right instead of spinning full-circle ---------- */
/* Corrective override: a full 360deg rotation reads as "spinning" in one
   direction, not "left to right" - replaced with a pendulum-style rock
   between a left and right tilt. */
@keyframes ins-hero-icon-swing{
  0%,100%{transform:rotate(-18deg);}
  50%{transform:rotate(18deg);}
}
.ins-hero-icon{
  animation:ins-hero-icon-swing 2s ease-in-out infinite;
}
@media(prefers-reduced-motion: reduce){
  .ins-hero-icon{animation:none;}
}


/* ---------- Insurance hero - rounded icon badge with a polished gold border ---------- */
/* Corrective override: square badge (was a wide 64x36 rectangle) with
   rounded corners, a thick gold border, and a bevel/glow box-shadow for a
   "polished" look. Switched background-size from contain to cover so the
   square frame has no letterbox gaps - the shield+coins art is centered
   in the source image so a slight side crop still reads fine. The swing
   animation from the previous override is untouched (not redeclared
   here), so it keeps rocking left/right exactly as before. */
.ins-hero-icon{
  width:58px; height:58px;
  border-radius:18px;
  border:3px solid var(--gold-400);
  background:url('../assets/insurance-title-card-icon.png') no-repeat center / cover;
  box-shadow:
    0 0 0 1px var(--gold-600) inset,
    inset 0 1px 3px rgba(255,255,255,.55),
    0 6px 16px rgba(0,0,0,.4),
    0 0 14px 2px rgba(216,180,80,.55);
}
@media(max-width:900px){
  .ins-hero-icon{width:44px; height:44px; border-radius:14px; border-width:2px;}
}


/* ---------- Insurance hero - heading beside the icon, black text ---------- */
/* Corrective override: heading moved out of the vertical stack onto the
   same row as the icon (.ins-hero-row, new wrapper in insurance.js), with
   the subtitle staying below that row. Text color switched from white to
   black per explicit request; a light halo replaces the old dark
   drop-shadow so black text still separates from the darker parts of the
   photo behind it. */
.ins-hero-row{
  display:flex; align-items:center; gap:10px;
}
.ins-hero-heading{
  color:#000;
  text-shadow:0 1px 3px rgba(255,255,255,.85), 0 0 6px rgba(255,255,255,.6);
}
.ins-hero-sub{
  color:#000;
  text-shadow:0 1px 3px rgba(255,255,255,.85), 0 0 6px rgba(255,255,255,.6);
}
@media(max-width:900px){
  .ins-hero-row{gap:6px;}
}


/* ---------- Insurance - darker/denser "Your financial security, our priority" ---------- */
/* Corrective override: was var(--text-400), the app's lightest/most muted
   text token - low contrast against the card's pale green background.
   Bumped to var(--text-600) (the mid gray used for regular body copy
   elsewhere) and a heavier weight, in both themes. */
.ins-value-sub{color:var(--text-600); font-weight:600;}


/* ---------- Insurance hero - circular icon, glossy metallic-gold ring ---------- */
/* Corrective override: matches the reference screenshot - a perfect
   circle (border-radius:50%, was 18px rounded-square) wrapped in a thick
   glossy gold ring rather than a plain flat gold line. The ring is built
   from three stacked box-shadows (a bright highlight edge, the base gold
   body, a darker outer rim) instead of `border`, so it reads as a raised
   metallic band the way the reference's ring does. The swing animation
   from the earlier override is untouched (not redeclared here). */
.ins-hero-icon{
  border-radius:50%;
  border:0;
  background:url('../assets/insurance-title-card-icon.png') no-repeat center / cover;
  box-shadow:
    0 0 0 2px #fff4cf,
    0 0 0 5px var(--gold-400),
    0 0 0 6.5px var(--gold-600),
    0 6px 16px rgba(0,0,0,.4),
    0 0 14px 2px rgba(216,180,80,.5);
}
@media(max-width:900px){
  .ins-hero-icon{
    box-shadow:
      0 0 0 1.5px #fff4cf,
      0 0 0 3.5px var(--gold-400),
      0 0 0 4.5px var(--gold-600),
      0 4px 10px rgba(0,0,0,.4);
  }
}


/* ---------- Document Vault hero - legacy image swapped for a video ---------- */
/* The element is now a <video> (was a background-image div), so the old
   background-image no longer paints anything - the video needs its own
   width/height/object-fit to fill the same 190px flex slot instead. */
.vault-hero-legacy-img{
  background:none;
  width:100%; height:100%; display:block;
  object-fit:cover;
}


/* ---------- Insurance hero - icon/heading/subtitle anchored to the top-left ---------- */
/* Corrective override: was vertically centered (top:50% + translateY),
   moved to sit at the top of the hero box instead, per explicit request. */
.ins-hero-overlay{
  top:22px; transform:none;
}
@media(max-width:900px){
  .ins-hero-overlay{top:14px;}
}


/* ---------- Document Vault hero - taller video, tagline removed ---------- */
/* .vault-hero-tagline's markup ("Preserve / Your Legacy") was removed from
   documents.js per explicit request, so its CSS rules are now dead and
   are left in place rather than hunted down across the file. The video
   was 190x~107 (stretched only to whatever height the text column next to
   it needed); an explicit min-height makes it taller outright, and since
   it's a flex row this also grows .vault-page-hero itself to match -
   object-fit:cover (set in the earlier video-swap override) crops for the
   new proportions without distorting. */
.vault-hero-legacy-img{
  min-height:220px;
}
@media(max-width:900px){
  .vault-hero-legacy-img{min-height:160px;}
}


/* ---------- Manage Trusted Devices - light beige card ---------- */
/* The panel is painted a fixed light beige rather than the app's --card, so
   it reads the same in light and dark mode. That is the convention the other
   painted surfaces here already follow — the WhatsApp summary cards say it
   outright ("Fixed colours rather than theme tokens ... each card is the same
   in light and dark mode, which is also what lets the text colours be chosen
   against a known background").

   And that last clause is the catch this block exists to handle. Everything
   inside the panel — headings, the count pill, column rules, the icon tiles,
   the timestamps, the Revoke pills — is written against --text-900/--text-400/
   --border/--subtle-bg. In dark mode those resolve to pale colours, which on a
   light beige ground would be all but invisible.

   So rather than restating a colour on each of the fifteen-odd rules, the
   theme tokens are re-declared on the card itself. Custom properties resolve
   where they are used, so every descendant rule already written against these
   names picks up the beige set with no edit, and .card's own
   `background:var(--card)` paints this element beige for the same reason.
   Add a rule inside the panel later and it inherits the palette for free.

   Values are the light theme's own relationships shifted warm, not invented:
   the same tonal steps between 900/600/400 that :root defines, over a beige
   ground instead of white. Beige rather than the sand this first carried —
   there is less gold in it, which keeps the greys underneath neutral and
   leaves the gold-600 "expiring soon" tint as the one warm thing in the
   panel. That contrast is the only reason it reads as a warning; on the
   sandier ground it was closer to blending in. --red/--red-bg are pinned to
   their light-mode values too, or Revoke's hover would go dark-mode maroon on
   a pale card. */
.ss-devices-card{
  --card:#f7f3e6;
  --border:#e4ddc8;
  --subtle-bg:#efeada;
  --text-900:#3a362a;
  --text-600:#665f4e;
  --text-400:#8a8370;
  --gold-600:#9a7414;
  --red:#c0392b;
  --red-bg:#fbe9e7;
  border-color:var(--border);
}


/* ---------- Document Vault hero video - crop away the baked-in "Preserve Your Legacy" text ---------- */
/* The source video itself has "Preserve/Your Legacy" text baked into its
   left portion (it's an animated version of the same reference art the
   old static image was cropped from). At the video's original ~16:9 slot
   that text sat outside the crop window, but the taller box above
   (min-height:220px) narrowed the object-fit:cover crop enough to bring
   it into frame from the left edge. Shifting the crop's focal point
   rightward keeps the family+tree scene and drops the text. */
.vault-hero-legacy-img{
  object-position:75% center;
}


/* ---------- Insurance hero icon - add a shine sweep + pulsing glow, keep the swing ---------- */
/* `animation` is not additive across cascaded rules - the last one wins
   outright - so keeping the existing swing meant re-declaring it here
   alongside the new glow rather than only adding the glow on its own.
   The shine is a separate ::before sweeping highlight, clipped to the
   circle via overflow:hidden (safe to add - overflow only clips content/
   pseudo-elements, never the element's own box-shadow rings). Glow uses
   filter:drop-shadow rather than animating box-shadow directly, since the
   ring's box-shadow list differs at the 900px breakpoint - drop-shadow
   layers on top of it identically at any size with no interpolation
   mismatch. */
.ins-hero-icon{
  position:relative; overflow:hidden;
  animation:ins-hero-icon-swing 2s ease-in-out infinite, ins-hero-icon-glow 2.2s ease-in-out infinite;
}
.ins-hero-icon::before{
  content:'';
  position:absolute; top:-60%; left:-60%; width:45%; height:220%;
  background:linear-gradient(120deg, transparent 0%, rgba(255,255,255,.9) 50%, transparent 100%);
  transform:rotate(20deg);
  animation:ins-hero-icon-shine 2.6s ease-in-out infinite;
  pointer-events:none;
}
@keyframes ins-hero-icon-shine{
  0%{left:-60%;}
  55%{left:135%;}
  100%{left:135%;}
}
@keyframes ins-hero-icon-glow{
  0%,100%{filter:drop-shadow(0 0 3px rgba(216,180,80,.5));}
  50%{filter:drop-shadow(0 0 11px rgba(216,180,80,.95));}
}
@media(prefers-reduced-motion: reduce){
  .ins-hero-icon{animation:none;}
  .ins-hero-icon::before{animation:none; display:none;}
}


/* ---------- Insurance hero - bigger icon, heading and subtitle ---------- */
/* Explicit request, following the hero photo's own aspect-ratio height
   increase (.ins-title-photo, above) - the taller photo left the
   icon/heading/sub overlay looking small and lost against it, so all
   three are scaled up to match: icon 58px->78px (44px->58px at the
   900px breakpoint), heading 22px->30px, sub 12.5px->15px. Only size
   properties are touched here - color, shadow, animation, and the ring's
   box-shadow are all untouched, still governed by the rules above. */
.ins-hero-icon{width:78px; height:78px;}
.ins-hero-heading{font-size:30px;}
.ins-hero-sub{font-size:15px; max-width:280px;}
.ins-hero-overlay{max-width:300px;}
@media(max-width:900px){
  .ins-hero-icon{width:58px; height:58px;}
  .ins-hero-heading{font-size:22px;}
  .ins-hero-sub{font-size:13px; max-width:220px;}
  .ins-hero-overlay{max-width:250px;}
}


/* ---------- Insurance summary row - smaller Total Insured Value / By Type / Attention Required cards ---------- */
/* Explicit request to reduce these three cards' box size. All three share
   the base .stat-card/.card padding (16px 18px) plus their own internal
   spacing/font sizes - trimmed here rather than adding a min-height cap,
   so the row still grows if content genuinely needs it (e.g. two or three
   Attention Required issues). Selectors are scoped to .ins-value-card/
   .ins-type-card/.ins-alert-card specifically so nothing here touches the
   shared .stat-card, .card-header or .donut-chart used elsewhere (Wealth
   Dashboard's own Asset/Liability Distribution donut in particular). */
.ins-value-card,
.ins-type-card,
.ins-alert-card{
  padding:12px 14px;
}
.ins-value-icon{width:30px;height:30px;margin-bottom:6px;}
.ins-value-icon svg{width:15px;height:15px;}
.ins-value-header{margin-bottom:4px;}
.ins-value-card .stat-label{font-size:13px;}
.ins-value-card .stat-value{font-size:19px;margin-top:3px;}
.ins-value-sub{margin-bottom:5px;}
.ins-type-card .card-header{margin-bottom:8px;}
.ins-type-icon{width:28px;height:28px;font-size:13px;}
.ins-type-card .donut-chart{width:min(105px, 100%);min-width:70px;}
.ins-type-card .donut-legend{gap:6px;font-size:11.5px;}
.ins-alert-title{margin-bottom:6px;}
.ins-alert-list{margin:1px 0 8px;font-size:12px;line-height:1.5;}


/* ---------- Document Vault hero - light overall shrink + reset video crop for new source ---------- */
/* Per explicit "lightly reduce" request: padding and the legacy video's
   min-height both trimmed back a bit from the previous pass (220px), not
   removed - a light reduction, not a return to the original compact
   size. Video crop reset to plain center: the 75% offset was there only
   to dodge baked-in text in the old "Preserve Your Legacy" footage, which
   doesn't apply to the new "only tree.mp4" source. */
.vault-page-hero{padding:20px 26px;}
.vault-hero-legacy-img{
  min-height:190px;
  object-position:center;
}
@media(max-width:900px){
  .vault-hero-legacy-img{min-height:145px;}
}


/* ---------- Document Vault hero video - "only tree.mp4" also has baked-in text ---------- */
/* Corrective override: the new "only tree.mp4" source turned out to carry
   the same baked "Preserve Your Legacy" text as the previous video, just
   in a tree-focused crop - plain center (the previous fix) still showed
   a sliver of it at the left edge. Tested 80%/100% live against the
   actual box size; 100% is the first position clean of any text. */
.vault-hero-legacy-img{
  object-position:100% center;
}


/* ---------- 2FA banner - the two secondary actions as Reports' gold pills ---------- */
/* "Change Google Authenticator" and "Generate New Recovery Codes" now wear the
   same pill the Reports strip gives its selected category — the Family Net
   Worth button. Same gradient, same #20160a ink, same gold ring, same 999px
   radius, the same 9px/16px padding and 13.2px/600 type, and the same gold
   cast shadow, all read off `.cat-tabs-strip-reports .cat-tab.active` rather
   than re-picked by eye, so the two cannot drift apart.

   They do NOT extend .cat-tab itself. That class lives on a scrolling category
   rail with its own arrows and overflow behaviour; pulling it onto a pair of
   ordinary buttons in a banner would tie two unrelated components together,
   and the next change to the rail would land here.

   The long selector is about weight, not nesting: the banner already re-tints
   its plain buttons through `.ss-2fa-hero .btn:not(.btn-gold):not(.btn-danger)`,
   which counts four classes. Matching that count and coming later in the file
   is what lets this win without an !important.

   Disable Two-Step Verification is deliberately left alone. It is the one
   destructive action in the row, and dressing it as a gold pill would put it
   at exactly the same weight as the two safe ones beside it. */
.ss-2fa-hero .ss-actions-row .btn.ss-pill-btn{
  display:inline-flex;align-items:center;gap:8px;white-space:nowrap;
  border-radius:999px;
  padding:9px 16px;
  font-size:13.2px;font-weight:600;
  background:linear-gradient(180deg, var(--gold-400), var(--gold-600));
  color:#20160a;
  border:1px solid var(--gold-400);
  box-shadow:0 2px 10px rgba(200,153,46,.45);
  transition:filter .15s ease, box-shadow .15s ease;
}
/* The rail's active pill has no hover state — it marks a current selection,
   not something you press. These are buttons, so they get one: a brightening
   and a deeper cast rather than a new colour, so it stays the same object. */
@media (hover:hover) and (pointer:fine){
  .ss-2fa-hero .ss-actions-row .btn.ss-pill-btn:hover{
    filter:brightness(1.07);
    box-shadow:0 3px 14px rgba(200,153,46,.55);
  }
}
/* Sits on the 13.2px label's own line the way .cat-tab-icon does in the rail,
   rather than dragging the pill taller. */
.ss-2fa-hero .ss-pill-icon{
  display:inline-flex;align-items:center;
  font-size:15px;line-height:1;
}


/* ---------- Document Vault hero video - blend into the hero's own color instead of sitting in a boxed frame ---------- */
/* Per explicit request: removes the hard rounded-rectangle crop (was
   border-radius:12px+overflow:hidden) that made the video read as a
   separate inserted clip sitting on top of the cream/gold hero, in favour
   of two effects tested live against the real page first:
     - a radial mask that fades the video's edges to transparent, so its
       rectangular boundary disappears into the hero's own background
       instead of cutting off sharply.
     - mix-blend-mode:multiply, which tints the video with whatever sits
       behind it (the hero's cream/gold gradient) rather than showing its
       own blue/green sky colour untouched - this is what actually makes
       it read as part of the hero's own palette rather than a foreign
       clip, since a mask alone only softens the edges. */
.vault-hero-legacy-img{
  border-radius:0;
  mix-blend-mode:multiply;
  mask-image:radial-gradient(ellipse 70% 70% at center, black 55%, transparent 100%);
  -webkit-mask-image:radial-gradient(ellipse 70% 70% at center, black 55%, transparent 100%);
}


/* ---------- Archived Vault hero art - blend into the hero's cream background ---------- */
/* Per explicit request: the source PNG's own near-white backdrop sat
   inside a hard-edged rounded box that read as a mismatched "white patch
   with a border" against the hero's warmer cream/gold gradient. Dropping
   the border-radius/crop and switching to mix-blend-mode:multiply lets
   the image's white background multiply away into whatever's behind it
   (the hero's own cream gradient) while the safe/photos/passport artwork
   itself stays visible - same fix as the Document Vault hero video. */
.vault-hero-art{
  border-radius:0;
  mix-blend-mode:multiply;
}


/* ---------- Archived Vault hero icon - swap to the supplied "ICON.png" ---------- */
/* Corrective override: replaces the plain folder+lock glyph with the
   user-supplied illustration (safe + folders + shield accent), cropped
   to a 520x520 square centered on the safe door so it reads cleanly in
   this small circular badge instead of showing the full wide scene.
   mix-blend-mode:multiply carries over the same fix just used on this
   page's hero art and Document Vault's hero video - the source's own
   near-white backdrop multiplies away into the badge's gold gradient
   instead of showing as a mismatched square patch. */
.vault-hero-icon.vault-hero-icon-av{
  background:url('../assets/archivedvault-hero-icon-v2.png') no-repeat center / contain;
  mix-blend-mode:multiply;
}


/* ---------- Document Vault hero video - background matches the hero's own gradient ---------- */
/* The video element itself was set to background:none, so at the masked/
   faded edges (the radial mask from the earlier blend override) whatever
   the browser paints behind an actively-decoding <video> surface shows
   through instead of the page's own cream/gold tone - not guaranteed to
   match. Setting the video's own background to the exact same gradient
   as .vault-page-hero means the fade-out at its edges blends into a
   background that's identical to the hero's, not just "whatever was
   there". */
.vault-hero-legacy-img{
  background:linear-gradient(120deg,#fdf6e3 0%,#faf0d7 45%,#f5e6c0 100%);
}


/* ---------- Document Vault hero video - exact-match the video's background to the hero's color ---------- */
/* Follow-up "still slightly mismatched... change exactly hero section
   color": the sepia/saturate/brightness(1.03) guess above was tuned by
   eye and still left a measurable gap - confirmed with real pixel
   sampling (html2canvas can't capture <video>+blend+mask correctly, so
   sampled actual screenshots instead): the video's own "empty" background
   is already a close cream match to the hero on its own (~245,232,200 vs
   the hero's own ~247,233,199 at that x-position), but mix-blend-mode:
   multiply still produced a visibly darker, off-hue patch (~237,212,156)
   - multiplying two ALREADY-similar off-white fractions together is not
   the same as matching them: 0.96*0.97≈0.93 but 0.78*0.78≈0.61, so the
   channel already furthest from pure white (blue, here) gets crushed
   hardest, regardless of how close the two colours looked to begin with.
   Root cause is multiply itself, not a hue/warmth mismatch - sepia was
   solving the wrong problem. multiply(white, X) = X exactly, so the real
   fix is pushing the video's own background toward true (255,255,255)
   with brightness alone (no hue shift needed, the video's native colour
   already matches) - 1.28 was the factor that cleared the video's own
   ~200 blue channel to ~255. The tree/leaf artwork itself starts much
   darker than the background, so it stays clearly visible after the same
   boost rather than blowing out. Verified after the fix: sampled "empty"
   points inside the video now differ from the hero's own background by
   1-2 RGB units (previously ~40 on the blue channel) - real pixel
   measurements, not a visual guess. */
.vault-hero-legacy-img{
  filter:brightness(1.28);
}


/* ---------- Archived Vault hero icon - swap the static crop for the animated video ---------- */
/* The element is now a <video> (was a background-image div), so it needs
   its own width/height/object-fit to fill the circular 60x60 badge -
   same fix as Document Vault's hero video and this same badge's own
   earlier icon swap. mix-blend-mode:multiply carries over so the video's
   pale backdrop still disappears into the badge's gold gradient. */
.vault-hero-icon.vault-hero-icon-av{
  background:none;
  width:100%; height:100%; display:block;
  object-fit:cover;
  mix-blend-mode:multiply;
}


/* ---------- Business Details hero icon - thicker border, left/right swing, shine sweep ---------- */
/* Per explicit request: keeps the ring's existing glow-pulse animation
   (bdRingPulse, set in the prefers-reduced-motion:no-preference block
   above) and adds a swing rotation alongside it in the same `animation`
   list - same pendulum technique as Insurance's floating icon ("spin
   left to right" there meant rock between a left and right tilt, not a
   360deg spin). Shine is a ::before sweep clipped to the circle via
   overflow:hidden (safe to add - overflow only clips content, never the
   ring's own box-shadow glow). Border thickened from 1px to 3px. */
.bd-hero-ring{
  border-width:3px;
  position:relative;
  overflow:hidden;
}
@media (prefers-reduced-motion: no-preference){
  .bd-hero-ring{
    animation:bdRingPulse 3s ease-in-out infinite alternate, bdRingSwing 2s ease-in-out infinite;
  }
  @keyframes bdRingSwing{
    0%,100%{transform:rotate(-16deg);}
    50%{transform:rotate(16deg);}
  }
  .bd-hero-ring::before{
    content:'';
    position:absolute; top:-60%; left:-60%; width:45%; height:220%;
    background:linear-gradient(120deg, transparent 0%, rgba(255,255,255,.9) 50%, transparent 100%);
    transform:rotate(20deg);
    animation:bdRingShine 2.6s ease-in-out infinite;
    pointer-events:none;
  }
  @keyframes bdRingShine{
    0%{left:-60%;}
    55%{left:135%;}
    100%{left:135%;}
  }
}
@media (prefers-reduced-motion: reduce){
  .bd-hero-ring::before{display:none;}
}


/* ---------- Archived Vault hero icon video - fix runaway size ---------- */
/* Bug in the previous override: `.vault-hero-icon` has flex:none (=
   flex-basis:auto), which means a flex item's main-axis size falls back
   to its `width` property. Setting width:100%/height:100% there computed
   against the flex CONTAINER's width (the whole hero banner), not this
   60x60 badge's own box, blowing the video up to fill almost the entire
   hero. The base .vault-hero-icon rule already sizes this box correctly
   at 60x60 (and 44x44 on narrow screens) - the video only needed
   object-fit and display, not its own width/height. */
.vault-hero-icon.vault-hero-icon-av{
  width:60px; height:60px;
  object-fit:cover;
  display:block;
}
@media(max-width:960px){
  .vault-hero-icon.vault-hero-icon-av{width:44px; height:44px;}
}


/* ---------- Archived Vault hero icon - bigger, square (not circular) ---------- */
/* Per explicit request: was 60x60 circular (border-radius:50%, set when
   this was still a static icon crop). Increased to 96x96 and switched to
   border-radius:0, matching the convention Document Vault's own
   .vault-hero-icon-art variant already uses for its square icon. */
.vault-hero-icon.vault-hero-icon-av{
  width:96px; height:96px;
  border-radius:0;
}
@media(max-width:960px){
  .vault-hero-icon.vault-hero-icon-av{width:64px; height:64px;}
}


/* ---------- Business Details hero icon - white background ---------- */
/* Corrective override: was a near-transparent gold radial gradient. Per
   explicit request, switched to a solid white fill so the icon art sits
   on a clean white disc instead of picking up the dark hero backdrop
   through the gaps. */
.bd-hero-ring{
  background:#fff;
}


/* ---------- Archived Vault hero art - swap the static image for the animate video ---------- */
/* The element is now a <video> (was a background-image div), so it needs
   its own width/height/object-fit to fill the same 300x140 flex slot -
   flex-basis here is an explicit 300px (not "auto" like the icon badge
   was), so width:100%/height:100% correctly resolves against this box's
   own size rather than the whole hero row (the bug hit on the icon swap
   doesn't apply here). mix-blend-mode:multiply carries over from the
   earlier white-background fix. */
.vault-hero-art{
  background:none;
  width:100%; height:100%; display:block;
  object-fit:cover;
}



/* ---------- Archived Vault hero icon - back to the static ICON.png, rounded, gold border, fully animated ---------- */
/* Reverts the icon from a video back to the cropped ICON.png image (the
   video moved to .vault-hero-art instead, per this request), and
   re-asserts background/size explicitly since several earlier overrides
   in this cascade (background:none, width/height:100% for the since-
   removed video, border-radius:0 for the "square" request) would
   otherwise still apply from further up the file. Shape switched back to
   circular per "rounded". Animation is one transform keyframe combining
   the swing (rotate) and floating (translateY) requests - two separate
   animations can't both drive `transform` at once, so they're combined
   into a single keyframe - plus a separate glow/shimmer (filter
   drop-shadow pulse) and a masked shine sweep on ::before, same
   techniques already used for Insurance's floating icon. */
.vault-hero-icon.vault-hero-icon-av{
  width:96px; height:96px;
  border-radius:50%;
  background:url('../assets/archivedvault-hero-icon-v2.png') no-repeat center / contain;
  mix-blend-mode:multiply;
  border:5px solid var(--gold-400);
  box-shadow:
    0 0 0 1px var(--gold-600) inset,
    inset 0 1px 3px rgba(255,255,255,.55),
    0 6px 16px rgba(0,0,0,.35),
    0 0 16px 3px rgba(216,180,80,.55);
  position:relative;
  overflow:hidden;
}
@media(max-width:960px){
  .vault-hero-icon.vault-hero-icon-av{width:64px; height:64px; border-width:3px;}
}
@media (prefers-reduced-motion: no-preference){
  .vault-hero-icon.vault-hero-icon-av{
    animation:av-icon-float-swing 2.6s ease-in-out infinite, av-icon-glow 2.2s ease-in-out infinite;
  }
  @keyframes av-icon-float-swing{
    0%{transform:rotate(-14deg) translateY(0);}
    50%{transform:rotate(14deg) translateY(-7px);}
    100%{transform:rotate(-14deg) translateY(0);}
  }
  @keyframes av-icon-glow{
    0%,100%{filter:drop-shadow(0 0 3px rgba(216,180,80,.5));}
    50%{filter:drop-shadow(0 0 12px rgba(216,180,80,.95));}
  }
  .vault-hero-icon.vault-hero-icon-av::before{
    content:'';
    position:absolute; top:-60%; left:-60%; width:45%; height:220%;
    background:linear-gradient(120deg, transparent 0%, rgba(255,255,255,.9) 50%, transparent 100%);
    transform:rotate(20deg);
    animation:av-icon-shine 2.8s ease-in-out infinite;
    pointer-events:none;
  }
  @keyframes av-icon-shine{
    0%{left:-60%;}
    55%{left:135%;}
    100%{left:135%;}
  }
}
@media (prefers-reduced-motion: reduce){
  .vault-hero-icon.vault-hero-icon-av{animation:none;}
  .vault-hero-icon.vault-hero-icon-av::before{display:none;}
}


/* ---------- Business Details hero icon - white background back to gold ---------- */
/* Corrective override: reverts the white fill from the previous request
   to a gold gradient, using the same gold-gradient tokens as this app's
   other icon badges (e.g. .vault-hero-icon's own default gold-gradient
   look). */
.bd-hero-ring{
  background:linear-gradient(135deg, var(--gold-400), var(--gold-600));
}


/* ---------- Archived Vault hero icon - slow continuous clockwise spin instead of the left/right swing ---------- */
/* Corrective override: replaces the fast rock-left-right swing with a
   slow, one-direction clockwise rotation ("spin right"), still combined
   with the floating bob in the same keyframe (rotate and translate can't
   be driven by two separate animations on the same element without one
   silently overriding the other, so both live in one keyframe as
   before). Duration stretched from 2.6s to 8s for a visibly slow spin. */
@media (prefers-reduced-motion: no-preference){
  .vault-hero-icon.vault-hero-icon-av{
    animation:av-icon-slow-spin 8s linear infinite, av-icon-glow 2.2s ease-in-out infinite;
  }
  @keyframes av-icon-slow-spin{
    0%{transform:rotate(0deg) translateY(0);}
    25%{transform:rotate(90deg) translateY(-7px);}
    50%{transform:rotate(180deg) translateY(0);}
    75%{transform:rotate(270deg) translateY(-7px);}
    100%{transform:rotate(360deg) translateY(0);}
  }
}



/* ---------- Archived Vault hero icon - bigger still ---------- */
/* Corrective override: was 96x96/64x64, bumped up further per explicit
   follow-up request alongside the slow-spin change. */
.vault-hero-icon.vault-hero-icon-av{
  width:120px; height:120px;
}
@media(max-width:960px){
  .vault-hero-icon.vault-hero-icon-av{width:80px; height:80px;}
}


/* ---------- Archived Vault hero icon - floating only, no spin ---------- */
/* Corrective override: removes the rotation entirely per explicit
   request, leaving just a gentle up/down float. */
@media (prefers-reduced-motion: no-preference){
  .vault-hero-icon.vault-hero-icon-av{
    animation:av-icon-float-only 3s ease-in-out infinite, av-icon-glow 2.2s ease-in-out infinite;
  }
  @keyframes av-icon-float-only{
    0%,100%{transform:translateY(0);}
    50%{transform:translateY(-9px);}
  }
}


/* ---------- Archived Vault hero icon - show the full ICON.png, not the tight safe-door crop ---------- */
/* Corrective override: the earlier 520x520 crop (archivedvault-hero-icon-v2.png)
   deliberately zoomed in on just the safe door, which read as "only the
   locker is visible" once the badge got bigger. Per explicit request,
   swapped to the full, uncropped ICON.png so the whole illustration
   (shield, folders, safe, leaves) shows inside the badge - background-size
   was already `contain` (never cropped), so this is purely a source-image
   swap. The image is wider than tall (929x708) against a circular box, so
   `contain` still letterboxes top/bottom slightly - the explicit trade-off
   here, full artwork over filling every pixel. */
.vault-hero-icon.vault-hero-icon-av{
  background:url('../assets/archivedvault-hero-icon-full.png') no-repeat center / contain;
}


/* ---------- Archived Vault - real icons for the tab strip + row icons ("Icons for Archived valut.png") ---------- */
/* Cropped from the supplied sprite sheet (5 columns: Assets/Insurances/
   Liabilities/Wills/Documents), each already a self-contained circular
   badge, so no background-removal step was needed - just a per-icon crop.
   Replaces the plain emoji glyphs used both by the tab strip
   (.av-tab-icon, new - matches Insurance's own .ins-tab-icon pattern) and
   each row's leading icon (.av-row-icon, already existed as a 34x34
   gold-bg box shared with Document Vault's .dv-row-icon - the emoji text
   node is swapped for a sized background-image instead). */
.av-tab-icon{
  width:20px;height:20px;flex:none;
  background-repeat:no-repeat;background-position:center;background-size:contain;
}
.av-tab-icon-assets{background-image:url('../assets/av-tab-assets.png');}
.av-tab-icon-insurances{background-image:url('../assets/av-tab-insurances.png');}
.av-tab-icon-liabilities{background-image:url('../assets/av-tab-liabilities.png');}
.av-tab-icon-wills{background-image:url('../assets/av-tab-wills.png');}
.av-tab-icon-documents{background-image:url('../assets/av-tab-documents.png');}

.av-row-icon.av-row-icon-assets,
.av-row-icon.av-row-icon-insurances,
.av-row-icon.av-row-icon-liabilities,
.av-row-icon.av-row-icon-wills,
.av-row-icon.av-row-icon-documents{
  background-repeat:no-repeat;background-position:center;background-size:contain;
  background-color:var(--gold-bg);
}
.av-row-icon.av-row-icon-assets{background-image:url('../assets/av-tab-assets.png');}
.av-row-icon.av-row-icon-insurances{background-image:url('../assets/av-tab-insurances.png');}
.av-row-icon.av-row-icon-liabilities{background-image:url('../assets/av-tab-liabilities.png');}
.av-row-icon.av-row-icon-wills{background-image:url('../assets/av-tab-wills.png');}
.av-row-icon.av-row-icon-documents{background-image:url('../assets/av-tab-documents.png');}


/* ---------- Archived Vault row icons - zoom in a bit ---------- */
/* Corrective override: the crops carry a fair margin of empty circular
   halo around the actual icon subject, which read small/faint at the
   34x34 row-icon size with background-size:contain. 140% crops that
   margin off (verified by test-cropping the same ratio locally) so the
   subject itself reads larger, still centered. */
.av-row-icon.av-row-icon-assets,
.av-row-icon.av-row-icon-insurances,
.av-row-icon.av-row-icon-liabilities,
.av-row-icon.av-row-icon-wills,
.av-row-icon.av-row-icon-documents{
  background-size:140%;
}


/* ---------- Archived Vault - bigger tab icons + a matching folder icon on each section's card header ---------- */
/* Tab icons bumped from 20px to 26px per explicit request. The card
   header (each tab's "Archived X / N of M record(s)" title row) had no
   icon at all - added one using the exact same per-section image already
   used for the tab strip, styled like Insurance's own .ins-card-title-icon
   (34x34 gold-bg rounded box) rather than inventing a new look. */
.av-tab-icon{
  width:26px;height:26px;
}
.av-card-title-icon{
  flex:none;width:34px;height:34px;border-radius:9px;margin-right:10px;
  background:var(--gold-bg);
  background-repeat:no-repeat;background-position:center;background-size:contain;
}
.av-card-title-icon-assets{background-image:url('../assets/av-tab-assets.png');}
.av-card-title-icon-insurances{background-image:url('../assets/av-tab-insurances.png');}
.av-card-title-icon-liabilities{background-image:url('../assets/av-tab-liabilities.png');}
.av-card-title-icon-wills{background-image:url('../assets/av-tab-wills.png');}
.av-card-title-icon-documents{background-image:url('../assets/av-tab-documents.png');}


/* ---------- Archived Vault tab icons - bigger still ---------- */
/* Corrective override: was 26px (bumped from the original 20px earlier
   this session), increased further per explicit follow-up request. */
.av-tab-icon{
  width:34px;height:34px;
}


/* ---------- Archived Vault hero art video - blend into the hero's own cream/gold background ---------- */
/* Same treatment already applied to Document Vault's hero video: a radial
   mask fades the video's hard rectangular edges to transparent instead of
   cutting off sharply, and an explicit background matching
   .vault-page-hero's own gradient shows through at those faded edges so
   the video reads as part of the hero rather than a separate inserted
   clip. mix-blend-mode:multiply (already set) then tints the video's own
   blue/green footage toward the same warm gold palette instead of
   standing out against it. */
.vault-hero-art{
  background:linear-gradient(120deg,#fdf6e3 0%,#faf0d7 45%,#f5e6c0 100%);
  mask-image:radial-gradient(ellipse 70% 70% at center, black 55%, transparent 100%);
  -webkit-mask-image:radial-gradient(ellipse 70% 70% at center, black 55%, transparent 100%);
}


/* ---------- Archived Vault hero art video - exact-match the video's background to the hero's color ---------- */
/* Follow-up "video color is exact hero section color" - the sepia+
   saturate+brightness(1.03) guess above was tuned by eye; replaced with
   the same measured fix applied to Document Vault's hero video for the
   identical root cause. Sampled the raw video (filter/blend/mask
   disabled) against the real hero background at matching points: the
   video's own "empty" background wasn't uniformly cooler/whiter as
   originally assumed - at some points it was already brighter than the
   hero, at others close to an exact match - but mix-blend-mode:multiply
   still measurably darkened the result everywhere, because multiplying
   two fractions that are each individually close to (but short of) pure
   white compounds the shortfall rather than cancelling it out (e.g.
   0.78*0.78≈0.61 on a channel that's only "close" to 1.0, not exactly
   it). Sepia was correcting hue, which was never the actual problem here
   either. brightness(1.28) - the same factor that fixed Document Vault -
   pushes the video's own background close enough to true white that
   multiply(white, hero_bg) ≈ hero_bg; verified after the fix by sampling
   several "empty" points against the real hero gradient at matching
   x-positions, all within a few RGB units (previously off by 15-30+ on
   individual channels). */
.vault-hero-art{
  filter:brightness(1.28);
}


/* ---------- Insurance policy list - real per-category icon instead of a plain shield ---------- */
/* Every row showed the same hardcoded shield emoji regardless of policy
   type. Reuses the same per-subtype images already cropped for the tab
   strip (ins-tab-*.png) - a plain background-image longhand added on top
   of .ins-policy-icon's own shorthand background (its blue-bg circle
   stays as the backdrop; only the image sub-property is added here). */
.ins-policy-icon{
  background-repeat:no-repeat;background-position:center;background-size:60%;
}
.ins-policy-icon-all{background-image:url('../assets/ins-tab-all.png');}
.ins-policy-icon-term{background-image:url('../assets/ins-tab-term.png');}
.ins-policy-icon-health{background-image:url('../assets/ins-tab-health.png');}
.ins-policy-icon-life{background-image:url('../assets/ins-tab-life.png');}
.ins-policy-icon-vehicle{background-image:url('../assets/ins-tab-vehicle.png');}


/* ---------- Document Vault / Archived Vault hero - narrow-phone fix for the title/icon row ---------- */
/* At phone widths (<480px) the 96x88 icon + 16px gap left too little room
   next to the heading for even a single word ("Vault") to fit, so the
   browser broke it mid-word ("Document V" / "ault"). Shrinks the icon and
   heading font-size specifically at this width - the existing 900px
   breakpoint elsewhere in this file already handles tablet widths fine,
   this fills the gap below it for actual phones. */
@media(max-width:480px){
  .vault-hero-icon.vault-hero-icon-art{width:56px;height:52px;}
  .vault-hero-top-row{gap:10px;}
  .vault-hero-title{font-size:19px;}
}

/* ---- Family Dashboard hero banner ("Family Dashboard.png") ---------------
   The supplied artwork joins the washes .fx-page .famexp-hero already had,
   inside the same background stack - the bar itself is untouched: same
   padding, border, radius, margin and the same gold rule from its ::after.
   Nothing is added to the markup and nothing moves.

   The banner is 2256x464 and composed with its left half deliberately empty,
   so it is anchored RIGHT at its own height (auto 100%, never cover): scaled,
   never stretched, never cropped through the artwork. Its own empty left then
   falls where the title sits, and a scrim finishes the job so "Know your
   spending. Shape your tomorrow." keeps a clean ground. */
.fx-page .famexp-hero{
  background:
    radial-gradient(105% 150% at 0% 0%, color-mix(in srgb, var(--fx-navy) 10%, transparent) 0%, transparent 58%),
    radial-gradient(85% 130% at 100% 0%, color-mix(in srgb, var(--gold-500) 12%, transparent) 0%, transparent 56%),
    linear-gradient(100deg,
      var(--card) 0%,
      var(--card) 46%,
      color-mix(in srgb, var(--card) 92%, transparent) 58%,
      color-mix(in srgb, var(--card) 58%, transparent) 70%,
      color-mix(in srgb, var(--card) 20%, transparent) 82%,
      transparent 92%),
    url('../assets/family-dashboard-hero.png') right center / auto 100% no-repeat,
    linear-gradient(180deg, color-mix(in srgb, var(--fx-ivory) 76%, var(--card)) 0%, var(--card) 100%);
}
/* Dark theme: a bright ivory studio banner, so it is held further back. */
:root[data-theme="dark"] .fx-page .famexp-hero{
  background:
    radial-gradient(105% 150% at 0% 0%, color-mix(in srgb, var(--fx-navy) 10%, transparent) 0%, transparent 58%),
    radial-gradient(85% 130% at 100% 0%, color-mix(in srgb, var(--gold-500) 12%, transparent) 0%, transparent 56%),
    linear-gradient(100deg,
      var(--card) 0%,
      color-mix(in srgb, var(--card) 96%, transparent) 28%,
      color-mix(in srgb, var(--card) 84%, transparent) 46%,
      color-mix(in srgb, var(--card) 64%, transparent) 68%,
      color-mix(in srgb, var(--card) 55%, transparent) 100%),
    url('../assets/family-dashboard-hero.png') right center / auto 100% no-repeat,
    linear-gradient(180deg, color-mix(in srgb, var(--fx-ivory) 76%, var(--card)) 0%, var(--card) 100%);
}


/* ---------- Insurance policy list icon - bigger ---------- */
/* Corrective override: was 26x26, bumped per explicit request now that
   it shows a real per-category image instead of a plain shield glyph. */
.ins-policy-icon{
  width:38px;height:38px;
}


/* ---------- Insurance policy list icon - bigger still ---------- */
/* Corrective override: was 38x38, bumped further per explicit follow-up
   request. */
.ins-policy-icon{
  width:48px;height:48px;
}


/* ---------- Insurance "By Type" card - flying butterfly over the background art ---------- */
/* Same ambient-particle technique as the Total Insured Value card's
   leaves (.ins-leaf-particle) - .ins-type-card already has position:
   relative;overflow:hidden set (its own background-art rule above), so
   this just adds the particle layer on top. A horizontal flight path
   (left -10% to 110%, clipped by the card) with a wing-flutter wobble
   (small rotate oscillation) instead of the leaves' slow upward drift. */
.ins-butterfly-particle{
  position:absolute;left:-10%;
  font-size:16px;opacity:0;
  pointer-events:none;
  animation:ins-butterfly-fly linear infinite;
  z-index:1;
}
@keyframes ins-butterfly-fly{
  0%{left:-10%;transform:rotate(-8deg) translateY(0);opacity:0;}
  8%{opacity:.9;}
  25%{left:20%;transform:rotate(8deg) translateY(var(--y));}
  50%{left:50%;transform:rotate(-8deg) translateY(0);}
  75%{left:80%;transform:rotate(8deg) translateY(var(--y));}
  92%{opacity:.9;}
  100%{left:110%;transform:rotate(-8deg) translateY(0);opacity:0;}
}
@media (prefers-reduced-motion: reduce){
  .ins-butterfly-particle{animation:none;display:none;}
}

/* ======================================================================
   Family Dashboard - financial summary cards

   Four metrics telling one story: Income -> Spend -> Savings -> Health.
   Each card is a layered surface rather than a flat tile - a tinted plate,
   an inner highlight along the top edge, a coloured cap rule, a decorative
   flow line drawn in the metric's own colour, and the existing 3D render
   seated on a lit chip. One system, four identities.

   The two record-backed metrics are <button>s and say so: pointer, focus
   ring, a "View records" affordance that arrives on hover. Net Savings and
   Savings Rate are derived figures with no record set of their own, so they
   lift very slightly on hover but never look clickable.
   ====================================================================== */
.fx-page .fx-kpis{
  display:grid;
  grid-template-columns:repeat(4, minmax(0,1fr));
  gap:16px;
  margin-bottom:18px;
}
.fx-page .fx-kpi{
  --kpi:#3b6fb8;
  --kpi-soft:#eaf1fb;
  position:relative;
  isolation:isolate;
  display:flex;
  flex-direction:column;
  align-items:flex-start;
  gap:0;
  min-width:0;
  margin:0;
  padding:17px 18px 15px;
  border-radius:18px;
  border:1px solid color-mix(in srgb, var(--kpi) 22%, var(--fx-line, #e6e1d6));
  background:
    radial-gradient(120% 100% at 100% 0%, color-mix(in srgb, var(--kpi) 11%, transparent) 0%, transparent 62%),
    linear-gradient(168deg, color-mix(in srgb, var(--kpi-soft) 72%, var(--card)) 0%, var(--card) 58%);
  box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 78%, transparent),
             0 1px 2px rgba(15,31,51,.05),
             0 14px 30px -22px color-mix(in srgb, var(--kpi) 70%, rgba(15,31,51,.5));
  text-align:left;
  font:inherit;
  overflow:hidden;
  transition:transform .32s cubic-bezier(.22,1,.36,1),
             box-shadow .32s ease-out,
             border-color .28s ease-out;
  animation:fx-kpi-in .5s cubic-bezier(.22,1,.36,1) both;
}
.fx-page .fx-kpi:nth-child(2){animation-delay:.06s;}
.fx-page .fx-kpi:nth-child(3){animation-delay:.12s;}
.fx-page .fx-kpi:nth-child(4){animation-delay:.18s;}
@keyframes fx-kpi-in{
  from{opacity:0;transform:translateY(10px);}
  to{opacity:1;transform:none;}
}
/* Cap rule: the card's identity, read before anything else. */
.fx-page .fx-kpi::before{
  content:'';
  position:absolute;
  left:0;right:0;top:0;
  height:3px;
  background:linear-gradient(90deg,
    var(--kpi) 0%,
    color-mix(in srgb, var(--kpi) 42%, transparent) 58%,
    transparent 100%);
  z-index:2;
}
/* A slow diagonal sheen, the one thing that suggests glass. */
.fx-page .fx-kpi-sheen{
  position:absolute;
  inset:-40% -10% auto;
  height:150%;
  z-index:0;
  pointer-events:none;
  background:linear-gradient(122deg, rgba(255,255,255,.62) 0%, rgba(255,255,255,0) 42%);
}
/* The growth / outflow line, in the card's own colour. */
.fx-page .fx-kpi-flow{
  position:absolute;
  left:0;right:0;bottom:0;
  height:46px;
  z-index:0;
  pointer-events:none;
  color:var(--kpi);
  opacity:.5;
}
.fx-page .fx-kpi-flow svg{display:block;width:100%;height:100%;}
/* Concentric rings for the accumulation card. */
.fx-page .fx-kpi-rings{
  position:absolute;
  right:-34px;bottom:-42px;
  width:150px;height:150px;
  z-index:0;
  pointer-events:none;
  border-radius:50%;
  border:1px solid color-mix(in srgb, var(--kpi) 24%, transparent);
  box-shadow:0 0 0 13px color-mix(in srgb, var(--kpi) 6%, transparent),
             0 0 0 27px color-mix(in srgb, var(--kpi) 4%, transparent);
}
/* The supplied render, seated on a lit chip so it reads as an object. */
.fx-page .fx-kpi-art{
  position:relative;
  z-index:1;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:50px;height:50px;
  margin-bottom:11px;
  padding:0;
  border-radius:15px;
  overflow:hidden;
  border:1px solid color-mix(in srgb, var(--kpi) 26%, transparent);
  background:linear-gradient(160deg, #fff 0%, color-mix(in srgb, var(--kpi) 8%, #fff) 100%);
  box-shadow:inset 0 1px 0 rgba(255,255,255,.9),
             0 6px 14px -9px color-mix(in srgb, var(--kpi) 85%, transparent);
  transition:transform .35s cubic-bezier(.22,1,.36,1);
}
.fx-page .fx-kpi-art .fx-art{width:100%;height:100%;object-fit:cover;border-radius:inherit;}
/* Label sits back; the amount carries the card. */
.fx-page .fx-kpi-label{
  position:relative;z-index:1;
  font-size:10.4px;
  font-weight:800;
  letter-spacing:.15em;
  text-transform:uppercase;
  color:color-mix(in srgb, var(--kpi) 66%, #2a3244);
}
.fx-page .fx-kpi-value{
  position:relative;z-index:1;
  margin-top:5px;
  font-size:25px;
  font-weight:800;
  letter-spacing:-.03em;
  line-height:1.1;
  font-variant-numeric:tabular-nums;
  color:#141b29;
  overflow-wrap:anywhere;
}
/* --- Identities ------------------------------------------------------- */
.fx-page .fx-kpi-income{--kpi:#2f7fb8;--kpi-soft:#e6f2fa;}
.fx-page .fx-kpi-spend{--kpi:#d2704f;--kpi-soft:#fdeee8;}
.fx-page .fx-kpi-savings{--kpi:#2e8b62;--kpi-soft:#e7f5ec;}
.fx-page .fx-kpi-savings.is-negative{--kpi:#c0524a;--kpi-soft:#fdecea;}
.fx-page .fx-kpi-rate{--kpi:#5b52a8;--kpi-soft:#eeecf9;}
/* --- Savings Rate: a dial, because it is a proportion, not an amount --- */
.fx-page .fx-kpi-rate .fx-kpi-dial{
  position:relative;z-index:1;
  display:inline-flex;
  align-items:center;
  justify-content:center;
  width:104px;height:104px;
  margin-top:8px;
}
.fx-page .fx-kpi-dial-svg{
  position:absolute;inset:0;
  width:100%;height:100%;
  transform:rotate(-90deg);
}
.fx-page .fx-kpi-dial-track{
  fill:none;
  stroke:color-mix(in srgb, var(--kpi) 16%, #e8e5f3);
  stroke-width:7;
}
.fx-page .fx-kpi-dial-fill{
  fill:none;
  stroke:var(--kpi);
  stroke-width:7;
  stroke-linecap:round;
  filter:drop-shadow(0 2px 4px color-mix(in srgb, var(--kpi) 45%, transparent));
  transition:stroke-dasharray .6s cubic-bezier(.22,1,.36,1);
}
.fx-page .fx-kpi-dial-num{
  position:relative;
  font-size:21px;
  font-weight:800;
  letter-spacing:-.028em;
  font-variant-numeric:tabular-nums;
  color:#141b29;
}
/* --- Interaction ------------------------------------------------------ */
.fx-page button.fx-kpi{cursor:pointer;-webkit-appearance:none;appearance:none;}
.fx-page button.fx-kpi:focus-visible{
  outline:2px solid color-mix(in srgb, var(--kpi) 80%, transparent);
  outline-offset:3px;
}
@media (hover:hover) and (pointer:fine){
  .fx-page button.fx-kpi:hover{
    transform:translateY(-3px);
    border-color:color-mix(in srgb, var(--kpi) 42%, transparent);
    box-shadow:inset 0 1px 0 color-mix(in srgb, #fff 82%, transparent),
               0 2px 4px rgba(15,31,51,.07),
               0 24px 44px -24px color-mix(in srgb, var(--kpi) 85%, rgba(15,31,51,.55));
  }
  .fx-page button.fx-kpi:hover .fx-kpi-art{transform:translateY(-2px) scale(1.05);}
  /* Derived cards acknowledge the pointer without pretending to navigate. */
  .fx-page .fx-kpi-savings:hover,
  .fx-page .fx-kpi-rate:hover{transform:translateY(-1px);}
}
@media (prefers-reduced-motion: reduce){
  .fx-page .fx-kpi{animation:none;transition:none;}
  .fx-page .fx-kpi-art,
  .fx-page .fx-kpi-dial-fill{transition:none;}
  .fx-page button.fx-kpi:hover{transform:none;}
  .fx-page button.fx-kpi:hover .fx-kpi-art{transform:none;}
}
/* --- Responsive ------------------------------------------------------- */
@media (max-width:1080px){
  .fx-page .fx-kpis{grid-template-columns:repeat(2, minmax(0,1fr));}
}
@media (max-width:560px){
  .fx-page .fx-kpis{grid-template-columns:1fr;gap:12px;}
  .fx-page .fx-kpi{padding:15px 16px 13px;}
  .fx-page .fx-kpi-value{font-size:22px;}
  .fx-page .fx-kpi-rate .fx-kpi-dial{width:92px;height:92px;}
  .fx-page .fx-kpi-dial-num{font-size:19px;}
}



/* ---------- Bulk Data Export - full page redesign ("Bulk data Export.png") ---------- */
/* Replaces the plain Roles & Access-style checkbox tree with the
   reference's hero banner + two illustrated module cards + bottom action
   bar. Category lists themselves are unchanged (still BULK_EXPORT_MODULES'
   real, permission-filtered categories from ASSET_TYPES/LIABILITY_TYPES,
   never a hardcoded list) - only the presentation changed. Hero/module art
   cropped directly from the supplied reference PNG (already self-contained
   illustrations, no background removal needed); the two module icons and
   hero icon likewise. Follows this app's existing cream/gold hero gradient
   convention (same formula as Document Vault's .vault-page-hero) rather
   than inventing a new palette. */
.bde-hero{
  position:relative;overflow:hidden;border-radius:16px;margin-bottom:18px;
  padding:26px 30px;display:flex;align-items:center;gap:22px;flex-wrap:wrap;
  background:linear-gradient(120deg,#fdf6e3 0%,#faf0d7 45%,#f5e6c0 100%);
  border:1px solid rgba(200,153,46,.28);box-shadow:0 10px 26px rgba(120,90,20,.10);
}
.bde-hero-icon{
  flex:none;width:64px;height:64px;border-radius:16px;
  background:url('../assets/bde-hero-icon.png') no-repeat center / cover;
  box-shadow:0 8px 20px rgba(180,140,40,.35);
}
.bde-hero-text{flex:1;min-width:240px;}
.bde-hero-title{margin:0;font-size:26px;font-weight:700;color:#3a2e0d;font-family:Georgia,'Times New Roman',serif;}
.bde-hero-sub{margin:6px 0 0;font-size:14px;color:#6b5a2e;max-width:560px;}
.bde-hero-art{flex:0 0 220px;height:105px;background:url('../assets/bde-hero-art.png') no-repeat center / contain;}
@media(max-width:860px){.bde-hero-art{display:none;}}

.bde-module-card{margin-bottom:18px;padding:24px;}
.bde-module-header{display:flex;align-items:flex-start;gap:16px;flex-wrap:wrap;margin-bottom:18px;}
.bde-module-icon{flex:none;width:56px;height:56px;background-size:contain;background-repeat:no-repeat;background-position:center;}
.bde-module-icon-assets{background-image:url('../assets/bde-assets-icon.png');}
.bde-module-icon-liab{background-image:url('../assets/bde-liab-icon.png');}
.bde-module-text{flex:1;min-width:240px;}
.bde-module-title{margin:0;font-size:20px;font-weight:700;color:var(--navy-800,#1a2a4a);font-family:Georgia,'Times New Roman',serif;}
.bde-module-sub{margin:6px 0 0;font-size:13.5px;color:var(--text-600);max-width:520px;}
.bde-head-badge{
  flex:none;align-self:center;display:flex;align-items:center;gap:6px;
  padding:8px 14px;border-radius:999px;font-size:12.5px;font-weight:600;
  background:var(--blue-bg,#eaf2ff);color:var(--navy-800,#1a2a4a);
  border:1px solid rgba(70,110,200,.25);
}
.bde-module-body{
  display:flex;align-items:center;gap:20px;flex-wrap:wrap;
  border:1px solid var(--border);border-radius:12px;padding:20px 24px;
}
.bde-cat-grid{
  flex:1;min-width:260px;
  display:grid;grid-template-columns:repeat(auto-fit,minmax(210px,1fr));
  gap:14px 24px;
}
.bde-cat-row{display:flex;align-items:center;gap:10px;cursor:pointer;}
.bde-cat-cb{flex:none;width:16px;height:16px;accent-color:var(--gold-500);cursor:pointer;}
.bde-cat-icon{flex:none;width:22px;height:22px;display:flex;align-items:center;justify-content:center;font-size:16px;}
.bde-cat-label{font-size:13.5px;font-weight:600;color:var(--text-900);}
.bde-module-art{flex:none;width:150px;height:130px;background-size:contain;background-repeat:no-repeat;background-position:center;}
.bde-module-art-assets{background-image:url('../assets/bde-assets-art.png');}
.bde-module-art-liab{background-image:url('../assets/bde-liab-art.png');}
@media(max-width:760px){.bde-module-art{display:none;}}

.bde-actions-card{padding:20px 24px;}
.bde-actions-row{display:flex;align-items:center;justify-content:space-between;gap:16px;flex-wrap:wrap;}
.bde-actions-left{display:flex;align-items:center;gap:14px;flex-wrap:wrap;}
.bde-btn-outline{
  background:transparent;border:1.5px solid var(--gold-500);color:var(--gold-600);
}
.bde-btn-outline:hover{background:var(--gold-bg);}

@media(prefers-reduced-motion: reduce){.bde-hero-icon,.bde-module-icon{animation:none;}}


/* ---------- Bulk Data Export - dark-mode text fix ---------- */
/* .bde-module-title and .bde-head-badge used var(--navy-800) as a text
   color - in dark theme that token flips to a very dark navy (#101c30,
   meant for backgrounds), making both nearly invisible against the
   card's own dark background. var(--text-900) is the theme-aware body
   text token (light navy in light mode, near-white in dark) and reads
   correctly in both - the hero above is untouched, it's deliberately a
   fixed light palette regardless of theme, same as Document Vault's own
   hero. */
.bde-module-title{color:var(--text-900);}
.bde-head-badge{color:var(--text-900);}


/* ---------- Bulk Data Export hero art - blend into the hero's own cream/gold background ---------- */
/* The cropped illustration's own backdrop is a very pale blue-white
   (~246,249,251), not neutral - plain mix-blend-mode:multiply alone (as
   used successfully elsewhere this session) still left a visible cooler
   rectangle against this warmer cream hero. Same combined fix as
   Document Vault/Archived Vault's hero video: a radial mask fades the
   hard edges to transparent, and a sepia/saturate/brightness filter
   warms the illustration's palette before compositing - tested live
   against the real page first. */
.bde-hero-art{
  filter:sepia(.5) saturate(1.3) brightness(1.02);
  mask-image:radial-gradient(ellipse 75% 75% at center, black 60%, transparent 100%);
  -webkit-mask-image:radial-gradient(ellipse 75% 75% at center, black 60%, transparent 100%);
}


/* ---------- Bulk Data Export module art - blend into the module box background ---------- */
/* Same edge-fade technique as the hero art, minus the warming filter -
   the module art's own backdrop is already near-neutral white (253,253,254,
   not blue-tinted like the hero crop was), and it sits on the module
   body's plain --card/white box rather than the hero's warm cream
   gradient, so mix-blend-mode:multiply alone reproduces that background
   exactly and adapts correctly to either theme without a color-shift
   filter (which would otherwise tint it away from neutral). */
.bde-module-art{
  mix-blend-mode:multiply;
  mask-image:radial-gradient(ellipse 75% 75% at center, black 60%, transparent 100%);
  -webkit-mask-image:radial-gradient(ellipse 75% 75% at center, black 60%, transparent 100%);
}


/* ---------- Bulk Data Export category icons - fix unwanted white squares in dark mode ---------- */
/* The reused asset/liability icon PNGs (ASSET_TYPES/LIABILITY_TYPES) are
   opaque RGB with no alpha - each already sits on its own baked-in white
   tile (same reason Family Expenses' icons needed the same treatment
   elsewhere in this file). That's invisible against this page's white
   module box in light mode, but reads as an ugly white square in dark
   mode. mix-blend-mode:multiply was tried first and rejected - it removes
   the white tile but also darkens the icon artwork itself toward black
   against a dark card, since multiply darkens everything, not just white.
   The fix instead gives every icon (image or the one plain-emoji
   exception, Insurance) an explicit small light chip of its own - a
   fixed light background regardless of theme, same as the icon PNGs were
   always designed to sit on, so it reads as an intentional badge in both
   themes instead of a theme-dependent glitch. */
.bde-cat-icon{
  width:26px;height:26px;border-radius:7px;
  background:#fdfcf8;
  box-shadow:0 1px 3px rgba(0,0,0,.18), 0 0 0 1px rgba(0,0,0,.04);
}


/* ---------- Bulk Data Export category icons - real transparent icons, no chip needed ---------- */
/* The previous "light chip" background (added to hide the reused icon
   PNGs' baked-in white tile) is no longer needed - bulkExport.js now
   points these icons at background-removed copies (icons-nobg/, see
   bdeIconSrc()), so there's nothing left to hide. Chip removed, box
   enlarged to match the bigger icon size requested (34px, up from 22px). */
.bde-cat-icon{
  width:34px;height:34px;border-radius:0;
  background:none;
  box-shadow:none;
}


/* ---------- Bulk Data Export - new hero art, circular module icon clip, dark-mode-visible module art ---------- */

/* 1) Hero art swapped to the new supplied image ("Bulk data - hero
   section right side image.png"), same edge-fade + warm-filter blend as
   before (this source's own backdrop is the same pale blue-white,
   ~242,250,254). */
.bde-hero-art{
  background-image:url('../assets/bde-hero-art-v2.png');
  filter:sepia(.5) saturate(1.3) brightness(1.02);
  mask-image:radial-gradient(ellipse 75% 75% at center, black 60%, transparent 100%);
  -webkit-mask-image:radial-gradient(ellipse 75% 75% at center, black 60%, transparent 100%);
}

/* 2) Module header icons (Asset Management/Obligations circular badges) -
   the source PNGs are opaque squares with the circle inset from the
   canvas edge, so a sliver of the square's own white background could
   show in the box's corners depending on exactly how `contain` scales
   it. border-radius:50% clips that away outright regardless of crop
   precision, safe here since these were always meant to read as round
   badges. */
.bde-module-icon{border-radius:50%;}

/* 3) Module art (house+coins+shield / loan+coins+shield) - the light-mode
   fix (mix-blend-mode:multiply + edge mask, appended earlier) made these
   nearly disappear in dark mode: multiply darkens the art's own colours
   toward black against a dark card, not just its white backdrop. This
   app's dark mode is applied purely via [data-theme="dark"] on <html>
   (toggleThemeMode(), app.js) - no prefers-color-scheme fallback exists
   anywhere else in this file, so this follows the same single-selector
   convention. Dark mode gets its own treatment here: blend mode off,
   plain white rounded backdrop behind the artwork (contain at 80% so the
   backdrop reads as a deliberate small frame, not a flush background),
   so the illustration's real colours stay fully visible instead of being
   hidden. */
:root[data-theme="dark"] .bde-module-art{
  mix-blend-mode:normal;
  filter:none;
  background-color:transparent;
  background-size:78%;
  border-radius:0;
}

/* Fix: the block above still left a solid white plate behind the art
   (its own near-white corners/halo, now shown at full opacity since the
   blend mode and mask were both disabled) - exactly the "white space"
   the multiply/mask combo was originally there to hide, just via a
   different mechanism. Re-enabling the mask (without multiply, which is
   what made the art unreadably dark against a dark card in the first
   place) fades those pale corners into the card's own background
   instead, so nothing shows but the illustration itself. */
:root[data-theme="dark"] .bde-module-art{
  mask-image:radial-gradient(ellipse 65% 65% at center, black 55%, transparent 100%);
  -webkit-mask-image:radial-gradient(ellipse 65% 65% at center, black 55%, transparent 100%);
}

/* ---------- Bulk Data Export: bigger category icons in dark mode ---------- */
/* iconHtml() sets width/height as an inline style on the <img> itself, so
   overriding it needs !important; the chip's own box is sized to match
   so the bigger icon doesn't overflow its row. */
:root[data-theme="dark"] .bde-cat-icon{width:46px;height:46px;font-size:24px;}
:root[data-theme="dark"] .bde-cat-icon img{width:46px !important;height:46px !important;}


/* ================================================================
   Family & Estate "Profile" tab redesign ("Profile.png")
   Replaces the dark-navy hero (completeness only ever visible via an
   SVG aria-label, never on screen) with the reference's cream/gold hero
   (same gradient formula as every other page's own hero, e.g. Document
   Vault's .vault-page-hero) carrying a standalone completeness ring with
   a real visible percentage, a title/subtitle and a progress bar, plus
   a tree+family illustration cropped from the reference. The previously
   grouped, separately-titled info cards (Personal Details / Contact /
   Address as three boxed sections) are flattened into one continuous
   icon+label+value grid in a single card, matching the reference -
   renderProfileCard() (familyTree.js) now emits that structure. The
   Family-Head-only blurred heritage-photo wash behind the whole page
   (family-estate.css, built for the old dark hero) is turned off: the
   reference's page background is plain, and a blurred photo behind
   otherwise-opaque cards read wrong against it.
   ================================================================ */

.fam-module[data-tab="head"]:has(.pf-hero-card[data-head])::before{
  content:none;
}
.fam-module[data-tab="head"]:has(.pf-hero-card[data-head]) .card.pf-info-card{
  background:var(--card);
}

/* --- Hero: cream/gold, same formula as this app's other page heroes --- */
.pf-hero{
  background:linear-gradient(120deg,#fdf6e3 0%,#faf0d7 45%,#f5e6c0 100%);
}
.pf-hero-card[data-head] .pf-hero{padding-right:34px;}
.pf-name{color:#3a2e0d;}
.pf-role{color:#6b5a2e;}
.pf-badges .fm-pill-living{background:rgba(63,191,133,.16);color:#1f7a4f;}
.pf-badges .fm-pill-deceased{background:rgba(58,46,13,.08);color:#6b5a2e;}
.pf-badges .fm-pill-kyc{
  background:color-mix(in srgb, var(--gold-500) 22%, transparent);
  color:#7a5a0f;
  box-shadow:inset 0 0 0 1px color-mix(in srgb, var(--gold-500) 34%, transparent);
}
.pf-badges .fm-pill-none{background:rgba(58,46,13,.07);color:#8a7a52;}

/* Avatar keeps its own dark navy circle (matches the reference exactly)
   but needs an explicit gold ring now that the old SVG completeness ring
   (which used to wrap it) has moved out into its own standalone widget. */
.pf-avatar-wrap{
  box-shadow:0 0 0 3px #fff, 0 0 0 5px var(--gold-500);
  border-radius:50%;
}
.pf-hero-card[data-head] .pf-avatar-wrap::before{content:none;}
/* Small checkmark badge on the avatar's bottom-right edge, shown once KYC
   is on file - the "Verified" state made visible right on the photo, not
   only as a pill below the name. */
.pf-verified-badge{
  position:absolute;
  right:-2px; bottom:-2px;
  width:24px; height:24px;
  border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  background:var(--blue,#2a5fb0);
  color:#fff;
  box-shadow:0 0 0 3px #fff;
  z-index:2;
}
.pf-verified-badge svg{display:block;}

/* --- Standalone completeness widget (ring + %, title, sub, bar) --- */
.pf-comp{
  display:flex;align-items:center;gap:16px;
  flex-shrink:0;
  position:relative;z-index:1;
}
.pf-comp-ring-wrap{position:relative;flex-shrink:0;width:80px;height:80px;}
.pf-comp-ring{position:absolute;inset:0;width:80px;height:80px;transform:rotate(-90deg);}
.pf-comp-ring-track{fill:none;stroke:rgba(58,46,13,.14);stroke-width:6;}
.pf-comp-ring-fill{
  fill:none;
  stroke:var(--gold-500);
  stroke-width:6;
  stroke-linecap:round;
  transition:stroke-dashoffset .7s cubic-bezier(.22,1,.36,1);
}
.pf-comp-pct{
  position:absolute;inset:0;
  display:flex;align-items:center;justify-content:center;
  font-size:18px;font-weight:700;color:#3a2e0d;
}
.pf-comp-text{min-width:0;}
.pf-comp-title{font-size:14.5px;font-weight:700;color:#3a2e0d;}
.pf-comp-sub{margin-top:2px;font-size:12.5px;color:#6b5a2e;max-width:190px;}
.pf-comp-bar{
  margin-top:9px;width:170px;max-width:100%;height:6px;border-radius:999px;
  background:rgba(58,46,13,.12);overflow:hidden;
}
.pf-comp-bar-fill{
  height:100%;border-radius:999px;
  background:linear-gradient(90deg, var(--gold-600), var(--gold-400));
  transition:width .7s cubic-bezier(.22,1,.36,1);
}

/* --- Actions restyled for the light hero (were translucent-on-navy) --- */
.btn.pf-act{
  background:#fff;
  border:1.5px solid var(--gold-500);
  color:#7a5a0f;
}
.btn.pf-act:hover{background:var(--gold-bg);box-shadow:0 4px 14px rgba(120,90,20,.16);}
.btn.pf-act-primary{color:#241a05;}

/* --- Hero art: tree + family silhouette, cropped from the reference --- */
.pf-hero-art{
  flex:0 0 190px;height:150px;align-self:stretch;
  background:url('../assets/pf-hero-art.png') no-repeat center / contain;
  mix-blend-mode:multiply;
  mask-image:radial-gradient(ellipse 78% 78% at center, black 62%, transparent 100%);
  -webkit-mask-image:radial-gradient(ellipse 78% 78% at center, black 62%, transparent 100%);
}
@media(max-width:900px){.pf-hero-art{display:none;}}

/* --- Flat info grid: one continuous card, icon + label + value tiles --- */
.pf-info-card{
  --pf-accent:initial;
  padding:26px 28px;
}
.pf-info-card::before{content:none;}
.pf-grid{
  display:grid;
  grid-template-columns:repeat(3, minmax(0,1fr));
  gap:22px 28px;
}
.pf-field{display:flex;align-items:flex-start;gap:12px;min-width:0;}
.pf-field-ico{
  flex:none;width:38px;height:38px;border-radius:50%;
  display:flex;align-items:center;justify-content:center;
  font-size:17px;
  background:var(--gold-bg);
  color:var(--gold-600);
}
.pf-field-text{min-width:0;}
.pf-address-row{
  display:flex;align-items:flex-start;gap:12px;
  margin-top:22px;padding:18px 20px;border-radius:12px;
  background:var(--gold-bg);
}
@media(max-width:900px){
  .pf-grid{grid-template-columns:repeat(2, minmax(0,1fr));}
}
@media(max-width:640px){
  .pf-grid{grid-template-columns:1fr;}
  .pf-comp{width:100%;}
  .pf-comp-bar{width:100%;}
}

/* --- Dark mode --- */
/* The hero is a deliberate fixed light palette (same call as every other
   page's own hero in this app), so it is untouched here - only the flat
   info grid (a plain .card, which does respond to theme) needs its icon
   chip and address strip re-tinted so they don't read as stray light
   patches against a dark card. */
:root[data-theme="dark"] .pf-field-ico,
:root[data-theme="dark"] .pf-address-row{
  background:color-mix(in srgb, var(--gold-500) 16%, transparent);
}
:root[data-theme="dark"] .pf-field-ico{color:var(--gold-400);}


/* ---------- Profile redesign - restore the Edit button's gold gradient ---------- */
/* The Profile redesign's .btn.pf-act override (KYC/Edit buttons restyled
   for the new light hero) shares .btn.pf-act-primary's own specificity,
   and landed later in the cascade - its plain white background was
   winning over .pf-act-primary's gold gradient for the Edit button.
   Re-asserted here, after both, so the primary action stays gold. */
.btn.pf-act-primary{
  background:linear-gradient(135deg, var(--gold-400) 0%, var(--gold-500) 100%);
}
.btn.pf-act-primary:hover{
  background:linear-gradient(135deg, var(--gold-400) 20%, var(--gold-500) 100%);
}


/* ============================================================
   MOBILE — whole-site pass
   ------------------------------------------------------------
   Audited every route at a 390px viewport by measuring, not by eye: any
   element whose box ran past the viewport edge without an overflow-x
   scroller above it to contain it. Nine of twenty-five routes pushed the
   page sideways, the worst to 918px on a 375px screen (Bank Locker). Nearly
   all of it traced back to two shared components rather than to the pages,
   which is why the fixes live here once instead of per page.

   Kept at the end of the file on purpose. This stylesheet is a long run of
   per-page overrides, and a mobile rule written earlier would lose to any of
   them that happen to restate the same property later.

   880px is the app's own mobile breakpoint — where the sidebar becomes an
   off-canvas drawer (see "Responsive" further up) — so tablet-in-portrait is
   covered by the same rules as a phone rather than falling in a gap between
   two of them.
   ============================================================ */
@media(max-width:880px){

  /* ---------- 1. Card toolbars ----------
     .card-header is a single flex row holding a title, a .spacer, then every
     filter and button the page has. It never wraps, so on a phone the
     controls either run off the right edge (Assets, Liabilities, Documents,
     Bank Locker) or get flex-shrunk into slivers — Business Details' search
     box and three filters were 26px wide each, which is not a smaller
     control but a broken one.

     The .spacer is what makes this fixable in one rule. Every one of these
     headers already puts it exactly between the title group and the
     controls, so giving it a full-width basis turns it into a line break:
     whatever precedes it keeps row one, and the controls start row two.
     That matters because the title group is not always one element —
     Archived Vault leads with an icon, then the title, and a "first child
     takes the full row" rule would have stranded the icon on a line of its
     own. */
  .card-header{flex-wrap:wrap;row-gap:10px;}
  .card-header > .spacer{flex:0 0 100%;height:0;}
  /* Filters grow to share a row two-up and drop to one-up when there is only
     one, rather than keeping whatever fixed width the desktop layout gave
     them — Bank Locker's search box was 220px on its own. */
  .card-header > select,
  .card-header > input{flex:1 1 150px;min-width:0;width:auto;max-width:100%;}
  /* Buttons size to their label and wrap as whole buttons; the last one on a
     row stretches to fill it, so a wrapped row ends on an edge rather than a
     ragged gap. */
  .card-header > .btn{flex:1 1 auto;justify-content:center;}
  /* Archived Vault nests its actions one level deeper, in a .flex group. */
  .card-header > .flex{flex:1 1 100%;flex-wrap:wrap;min-width:0;}

  /* ---------- 2. Topbar ----------
     Hamburger, page title and the three action badges all refused to shrink
     (the title and actions are flex-shrink:0), inside 24px of padding each
     side. A short title fits; "Obligations / Liabilities" and "WhatsApp
     Notifications" pushed the actions off the screen. The title is the one
     that should give — it already appears as the page's own heading below —
     so it truncates with an ellipsis and the actions stay reachable. */
  .topbar{padding:10px 14px;gap:10px;}
  .topbar-title{
    flex:1 1 auto;min-width:0;
    overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
  }
  .topbar-actions{gap:8px;}

  /* ---------- 3. Grids that could not get narrower than their content ----------
     A plain 1fr track is minmax(auto,1fr), and "auto" there means the
     longest unbreakable run inside the item — so a single wide descendant
     sets the column width and the grid pushes past the screen. The Privacy
     consent cards did exactly this, holding their column at 406px on a 375px
     screen. minmax(0,1fr) lets the track shrink to the space available and
     makes the content wrap inside it instead. */
  .pn-consent-grid{grid-template-columns:minmax(0,1fr);}
  .pn-consent-card{min-width:0;}

  /* ---------- 4. Data tables ----------
     makeColumnsResizable (app.js) is a desktop feature — its handles listen
     for mousedown — and on mount it switches the table to table-layout:fixed
     and auto-fits every column, writing the widths inline. On a phone it
     measures the squeezed layout and freezes it: Liabilities' columns were
     60px each, and the headers ran into one another ("BORROWE" over
     "OUTSTANDI" over "STATUS").

     The .table-wrap around every one of these tables already has
     overflow-x:auto. The table simply never got wide enough to use it. Back
     on auto layout, with nowrap headers setting each column's minimum, a
     table that fits fills its card and one that does not scrolls sideways
     inside it — the page itself never moves.

     !important is unavoidable and deliberate: every value it overrides is an
     inline style written by that script, which a stylesheet cannot beat any
     other way. It is confined to this breakpoint, so dragged column widths
     are untouched on desktop, where the feature actually works. */
  .table-wrap > table.col-resizable{
    table-layout:auto !important;
    width:auto !important;
    min-width:100%;
  }
  .table-wrap > table.col-resizable th{width:auto !important;}
  /* Nothing to drag with on touch, and the handles still take a few pixels
     of every header. */
  .col-resize-handle{display:none;}

  /* ---------- 5. Navigation drawer scrim ----------
     The drawer already closes on a tap outside it — but with nothing between
     it and the page, that tap also landed on whatever was underneath. Tap
     "outside" on a button and you shut the menu and pressed the button in the
     same motion. The page also stayed at full brightness, so there was no
     sign that tapping it would close anything.

     A fixed scrim on .main takes the tap instead. The click's target becomes
     .main itself, which the existing outside-tap handler already treats as
     outside, so the drawer closes exactly as before and nothing beneath is
     activated. No script change needed.

     It works as a pseudo-element because .main is the drawer's immediate
     sibling and carries no transform, filter or containment — any of those
     would turn position:fixed into position-relative-to-.main. z-index 15
     sits above the sticky topbar (10) and below the drawer (20). */
  .sidebar.open + .main::after{
    content:'';position:fixed;inset:0;z-index:15;
    background:rgba(5,10,20,.52);
    animation:mobScrimIn .18s ease-out;
  }
  @keyframes mobScrimIn{from{opacity:0;}to{opacity:1;}}
}

@media(max-width:640px){
  /* Privacy consent card head. Tile, title and the state pill in one row left
     the serif title about 140px to wrap in on a phone, breaking after almost
     every word. The pill drops under the title instead and the title keeps
     the width. */
  .pn-consent-head{flex-wrap:wrap;row-gap:8px;}
  /* A real basis, not flex:1 1 0. With a zero basis the title never claims
     the row, so nothing forces the pill onto the next line — the first pass
     at this did exactly that and the title still broke after every word.
     100% minus the tile (52px) and the gap (14px) fills row one to the edge,
     and the pill has to wrap. */
  .pn-consent-headings{flex:1 1 calc(100% - 66px);}
  /* ...and then lines up under the title rather than under the tile. */
  .pn-state{margin-left:66px;}
  .pn-consent-actions{justify-content:stretch;}
  .pn-consent-actions .pn-btn{flex:1 1 auto;justify-content:center;}

  /* Insurance hero. On desktop the icon, heading and subtitle float over the
     left of the family photo, where the sky is empty. At phone width that
     empty sky is gone: the text landed across the icon badges and the family,
     and came out black on a bright busy picture. There was also a 52px blank
     band under the photo, because the card kept the 232px minimum height from
     desktop while the photo itself drops to 180px here.
     So on a phone the photo is just the banner, and the copy moves off it
     into a band beneath — back in normal flow, on the card surface, in theme
     colours, so it reads in light and dark alike. */
  .ins-title-card{min-height:0;background:var(--card);}
  .ins-hero-overlay{
    position:static;transform:none;
    max-width:none;padding:14px 16px 16px;gap:6px;
    pointer-events:auto;
  }
  /* The icon's gold ring renders a little wider than its own box (it spins,
     and its artwork overhangs), so it was touching the heading's first
     letter. */
  .ins-hero-row{gap:12px;}
  .ins-hero-heading{font-size:20px;color:var(--text-900);text-shadow:none;}
  .ins-hero-heading .shine-letter{color:inherit;-webkit-text-fill-color:currentColor;}
  .ins-hero-sub{font-size:13px;max-width:none;color:var(--text-600);text-shadow:none;}

  /* Family Balance Sheet hero. Title-and-subtitle and the view selector sat
     side by side in a non-wrapping row, and the selector carries an inline
     min-width:220px — so on a phone the text column was left 89px, the
     subtitle broke after every word ("Maps all / accounts, / investments, /
     and / obligations…"), and the selector ran to the card's edge. Stacked,
     the copy gets the full width and the selector sits under it full width.
     !important only because the minimum it overrides is an inline style in
     balanceSheet.js. */
  .bs-top-hero .flex-between{flex-direction:column;align-items:stretch;}
  .bs-top-hero .bs-filter-select{min-width:0 !important;width:100%;}

  /* Family Expenses banner. The illustration is a background layer on the
     right, under a scrim that is solid card colour only across the left 46%
     and gone by 92%. On desktop the copy stays inside that solid half; on a
     phone the copy spans nearly the whole card and ran out over the artwork.
     The scrim holds solid much further across here, so the text always has a
     ground under it, and the picture stays as a faint accent on the right
     edge rather than being removed. Both theme rules are restated because the
     dark-mode one upstream is more specific than the light one. */
  .fx-page .famexp-hero,
  :root[data-theme="dark"] .fx-page .famexp-hero{
    background:
      linear-gradient(100deg,
        var(--card) 0%,
        var(--card) 64%,
        color-mix(in srgb, var(--card) 86%, transparent) 80%,
        color-mix(in srgb, var(--card) 62%, transparent) 100%),
      url('../assets/family-dashboard-hero.png') right center / auto 100% no-repeat,
      var(--card);
  }
}


/* ---------- Profile redesign - normalize field-tile icon sizes ---------- */
/* The flat field grid pulls icons from several different constants
   (PF_ICONS at 18px, FAM_TAB_ICONS.calendar at 21px, FM_ICONS.people at
   22px, TREE_ICONS.heartSolid at 13px) since a matching per-field icon
   set didn't already exist - each keeps its own native width/height
   attribute otherwise, which read as an inconsistent icon scale across
   the grid. Forces every one to the same visual size inside the chip. */
.pf-field-ico svg{width:18px;height:18px;}


/* ---------- Profile hero art swapped from static PNG to looping video ---------- */
/* renderProfileCard() now emits <video class="pf-hero-art" ...> instead of the
   background-image div. The clip's own backdrop is already the same cream/gold
   tone as .pf-hero, so no mix-blend-mode/mask is needed to hide a white edge
   like the old PNG required - object-fit just frames the tree+family scene. */
.pf-hero-art{
  background:none;
  object-fit:cover;
  object-position:72% 42%;
  border-radius:14px;
}

/* ---------- "View Calculation" on auto-calculated fields ----------
   The button is rendered INSIDE the field's own <label> (formRenderer.js's
   calcBtn), so it reads as belonging to that field rather than floating
   loose beside it. :has() turns just those labels into a flex row and pushes
   the button to the right; where :has() is unsupported the button simply
   sits inline after the label text, which still works - this is an
   enhancement, never load-bearing. */
.field label:has(.field-calc-btn){display:flex;align-items:center;gap:8px;}
.field-calc-btn{
  margin-left:auto;
  flex-shrink:0;
  border:1px solid var(--border);
  background:var(--subtle-bg);
  color:var(--text-600);
  font-size:10.8px;
  font-weight:600;
  font-family:inherit;
  padding:2px 8px;
  border-radius:999px;
  cursor:pointer;
  line-height:1.7;
  transition:background .18s ease-out, color .18s ease-out, border-color .18s ease-out;
}
.field-calc-btn:hover{background:var(--gold-500);border-color:var(--gold-500);color:#fff;}
.field-calc-btn:focus-visible{outline:2px solid var(--gold-500);outline-offset:2px;}

/* The explanation popup's own content (#calcExplainBody). Plain-language
   prose first, formula second - someone who does not think in formulas can
   still follow why the number is what it is, then see the arithmetic under
   it if they want to. */
.calc-explain .calc-lead{
  font-size:13px;
  line-height:1.6;
  color:var(--text-600);
  margin:0 0 10px;
}
.calc-explain .calc-table{width:100%;border-collapse:collapse;margin:14px 0;}
.calc-explain .calc-table td{
  padding:7px 10px;
  border-bottom:1px solid var(--border);
  font-size:12.4px;
  color:var(--text-600);
}
.calc-explain .calc-table tr:last-child td{border-bottom:none;}
.calc-formula{
  border:1px solid var(--border);
  border-left:3px solid var(--gold-500);
  border-radius:8px;
  background:var(--subtle-bg);
  padding:12px 14px;
  margin:14px 0;
}
.calc-formula-label{
  font-size:10px;
  font-weight:700;
  letter-spacing:.06em;
  text-transform:uppercase;
  color:var(--text-400);
  margin-bottom:3px;
}
.calc-formula-label + .calc-formula-line{margin-bottom:10px;}
.calc-formula-line{
  font-family:ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size:12.4px;
  color:var(--text-900);
  line-height:1.6;
  word-break:break-word;
}
.calc-answer{
  margin-top:8px;
  padding-top:9px;
  border-top:1px dashed var(--border);
  font-size:15px;
  font-weight:800;
  color:var(--gold-500);
}
.calc-note{
  font-size:11.6px;
  line-height:1.55;
  color:var(--text-400);
  background:var(--subtle-bg);
  border-radius:7px;
  padding:9px 11px;
  margin-top:10px;
}
.calc-note strong{color:var(--text-600);}

/* ---------- Profile hero video: fade + tone-match into the cream hero ---------- */
/* The clip's own footage runs a shade darker/more saturated gold than
   .pf-hero's pale cream gradient, and object-fit:cover left a visible
   rounded-rectangle edge. Lightened/desaturated to the hero's palette and
   faded on a wide radial mask so it reads as part of the hero surface
   rather than a pasted-in box. */
.pf-hero-art{
  filter:brightness(1.28) saturate(.65) sepia(.1);
  mask-image:radial-gradient(ellipse 95% 95% at center, black 35%, transparent 100%);
  -webkit-mask-image:radial-gradient(ellipse 95% 95% at center, black 35%, transparent 100%);
}

/* ---------- Family Head crown: golden vector badge over the avatar ---------- */
/* Reuses this file's existing .pf-crown/.pf-crown-art/.pf-crown-sheen
   glow+float+sheen animations (already defined above for the old navy
   hero) - renderProfileCard() now emits a real inline SVG crown again for
   isHead profiles, instead of the emoji used on Family Tree cards. */
.pf-crown-art{transform-origin:19px 16px;}

/* ---------- Avatar: more black (navy) fill, thicker gold outline ---------- */
/* The 88px wrap held only a 66px avatar, leaving an 11px gap of hero
   background showing between the navy circle and the gold ring - reading
   as a thin, washed-out border. Avatar now nearly fills the wrap so the
   gold ring sits flush against it, and the ring itself is thicker. */
.pf-avatar-wrap{width:84px;height:84px;}
.avatar.pf-avatar{width:78px;height:78px;font-size:27px;}
.pf-avatar-wrap{box-shadow:0 0 0 4px #fff, 0 0 0 9px var(--gold-500);}
@media(max-width:900px){
  .pf-avatar-wrap{width:70px;height:70px;}
  .avatar.pf-avatar{width:64px;height:64px;font-size:23px;}
}

/* ---------- Field icon chips: morph on hover/click ---------- */
/* .pf-field-ico is a circular icon-container badge (Member ID, DOB, etc).
   Hover/click now eases its border-radius through an organic blob on a
   soft loop instead of jumping straight to a fixed shape, and the chip's
   gold tint deepens to confirm the interaction. */
.pf-field-ico{
  transition:background-color .3s ease, box-shadow .3s ease;
}
.pf-field-ico:hover,
.pf-field-ico:active,
.pf-field-ico:focus-visible{
  animation:pf-icon-morph 3s ease-in-out infinite alternate;
  background:color-mix(in srgb, var(--gold-500) 30%, var(--gold-bg));
  box-shadow:0 0 0 3px color-mix(in srgb, var(--gold-500) 22%, transparent);
}
@keyframes pf-icon-morph{
  0%   {border-radius:12px;}
  50%  {border-radius:60% 40% 30% 70% / 60% 30% 70% 40%;}
  100% {border-radius:50%;}
}
@media (prefers-reduced-motion: reduce){
  .pf-field-ico:hover, .pf-field-ico:active, .pf-field-ico:focus-visible{
    animation:none;border-radius:50%;
  }
}

/* ---------- Avatar round 2: black fill flush to a thicker gold ring ---------- */
/* Avatar now matches the wrap's own size exactly (no cream gap left
   showing), the white separator hairline is trimmed to 2px so the navy
   reads as running right up to the gold, and the gold band itself is
   roughly doubled (visible gold thickness = outer spread minus the 2px
   white inset). */
.pf-avatar-wrap{width:86px;height:86px;}
.avatar.pf-avatar{width:86px;height:86px;font-size:29px;}
.pf-avatar-wrap{box-shadow:0 0 0 2px #fff, 0 0 0 14px var(--gold-500);}
@media(max-width:900px){
  .pf-avatar-wrap{width:70px;height:70px;}
  .avatar.pf-avatar{width:70px;height:70px;font-size:23px;}
  .pf-avatar-wrap{box-shadow:0 0 0 2px #fff, 0 0 0 11px var(--gold-500);}
}

/* ---------- Avatar: automatic rollover shimmer on the gold ring ---------- */
/* A bright arc continuously circles the outer gold band with no hover
   needed - "rollover" here reads as a self-playing highlight sweep rather
   than a discrete user-triggered state. Built as a ring-shaped conic
   gradient (mask-composite:exclude hollows the centre so it never washes
   out the avatar photo/initial), independent of the crown's own glow. */
.pf-avatar-wrap::after{
  content:'';
  position:absolute;
  inset:-14px;
  border-radius:50%;
  padding:4px;
  background:conic-gradient(from 0deg,
    transparent 0deg, transparent 320deg,
    rgba(255,255,255,.95) 344deg, transparent 360deg);
  -webkit-mask:linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite:xor;
  mask-composite:exclude;
  filter:drop-shadow(0 0 4px rgba(255,246,222,.7));
  animation:pf-avatar-rollover 3.5s linear infinite;
  pointer-events:none;
  z-index:2;
}
@keyframes pf-avatar-rollover{to{transform:rotate(360deg);}}
@media(max-width:900px){
  .pf-avatar-wrap::after{inset:-11px;}
}
@media (prefers-reduced-motion: reduce){
  .pf-avatar-wrap::after{animation:none;display:none;}
}

/* ---------- Family Members hero: swap banner art for the reference photo ---------- */
/* Family Tree.png's header uses a warm mountain-and-family photograph
   instead of this band's original cool navy/gold vector banner. Same
   recipe as before (right-anchored, horizontal mask so the eyebrow/title
   block keeps a clean ground) - just a warmer, photographic replacement
   that reads closer to the reference. */
.fm-card .fm-intro::before{
  background-image:url('../assets/fm-hero-photo-v3.png');
  opacity:.62;
}
:root[data-theme="dark"] .fm-card .fm-intro::before{opacity:.34;}

/* ---------- Family Members stat cards: faint corner leaf sprig ---------- */
/* Family Tree.png's four summary cards each carry a pale plant watermark
   bottom-right, tinted to the card's own accent and bleeding off the
   rounded corner (.fm-stat already has overflow:hidden, so it clips
   naturally). Built as a CSS mask rather than an image so it re-tints
   per data-accent for free via the same --fm-accent custom property the
   card, icon and value already read from.
   The sprig shape itself is now overridden per card by the icon watermarks
   in the dark-mode block further down, which apply in light mode as well. */
.fm-stat-ico, .fm-stat-body{position:relative;z-index:1;}
.fm-stat::after{
  content:'';
  position:absolute;
  right:-4px;
  bottom:-14px;
  width:52px;
  height:82px;
  background:var(--fm-accent);
  opacity:.16;
  -webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 90'><path fill='white' d='M30 88 Q30 60 22 40 Q16 26 22 12 Q30 22 30 40 Q30 22 38 12 Q44 26 38 40 Q30 60 30 88 Z'/><ellipse fill='white' cx='14' cy='34' rx='10' ry='16' transform='rotate(-35 14 34)'/><ellipse fill='white' cx='30' cy='10' rx='9' ry='15'/><ellipse fill='white' cx='46' cy='34' rx='10' ry='16' transform='rotate(35 46 34)'/></svg>");
  mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 60 90'><path fill='white' d='M30 88 Q30 60 22 40 Q16 26 22 12 Q30 22 30 40 Q30 22 38 12 Q44 26 38 40 Q30 60 30 88 Z'/><ellipse fill='white' cx='14' cy='34' rx='10' ry='16' transform='rotate(-35 14 34)'/><ellipse fill='white' cx='30' cy='10' rx='9' ry='15'/><ellipse fill='white' cx='46' cy='34' rx='10' ry='16' transform='rotate(35 46 34)'/></svg>");
  -webkit-mask-repeat:no-repeat;mask-repeat:no-repeat;
  -webkit-mask-size:contain;mask-size:contain;
  -webkit-mask-position:bottom right;mask-position:bottom right;
  pointer-events:none;
  z-index:0;
}


/* ============================================================
   Roles & Access — Members tab, ported from "Roles and Access.png"
   ------------------------------------------------------------
   New ram-* classes rather than restyling the ra-* ones the old tab used:
   .ra-badge and .ra-row-actions are shared with the Sysadmin "All Accounts"
   and Support Analyst tables in the same file, and restyling them for this
   tab would have changed those too.

   The hero is a painted surface, fixed light in both themes like every other
   hero in this file: its artwork is cropped from the comp with a light ground
   baked in, and its text colours are chosen against that ground. Everything
   below the hero follows the theme tokens, so the working part of the page —
   the counts, the table, the buttons — reads correctly in dark mode. The
   Bulk Import page is the reminder of what happens otherwise.
   ============================================================ */

/* ---------- Hero ---------- */
.ram-hero{
  position:relative;overflow:hidden;
  display:grid;
  grid-template-columns:auto minmax(0,1fr) auto auto;
  grid-template-areas:
    "tile   copy   art    script"
    "notice notice notice script";
  column-gap:24px;row-gap:14px;align-items:center;
  padding:24px 30px 22px;margin-bottom:20px;border-radius:18px;
  /* Sampled off the comp: near-white through the copy, cooling to lavender
     behind the artwork and the handwriting on the right. */
  background:
    radial-gradient(60% 120% at 100% 0%, rgba(214,220,255,.75) 0%, rgba(214,220,255,0) 70%),
    radial-gradient(40% 90% at 100% 100%, rgba(253,233,247,.8) 0%, rgba(253,233,247,0) 70%),
    linear-gradient(100deg,#FEFDFD 0%,#F7FAFF 45%,#EEF1FE 100%);
  border:1px solid #e2e7f5;
  box-shadow:0 10px 26px -20px rgba(40,60,120,.45);
}
.ram-hero-tile{
  grid-area:tile;align-self:start;
  width:86px;height:86px;border-radius:22px;
  display:inline-flex;align-items:center;justify-content:center;
  background:linear-gradient(145deg,#ffffff,#eef3fb);
  box-shadow:0 12px 24px -12px rgba(40,70,120,.4), inset 0 1px 0 #fff;
}
.ram-hero-copy{grid-area:copy;min-width:0;}
.ram-kicker{font-size:12.4px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:#2563c9;}
.ram-hero-title{margin:8px 0 0;font-size:23px;font-weight:800;letter-spacing:-.2px;line-height:1.25;color:#1c2a4a;}
.ram-hero-sub{margin:6px 0 0;font-size:14.2px;line-height:1.55;color:#4a5568;}
.ram-hero-art{grid-area:art;width:300px;height:auto;align-self:center;}
.ram-hero-script{grid-area:script;width:176px;height:auto;align-self:center;}
.ram-notice{
  grid-area:notice;
  display:flex;align-items:center;gap:10px;
  padding:11px 16px;border-radius:10px;
  background:#FDF3DE;border:1px solid #F2DDAE;
  font-size:13.4px;color:#7a5a17;
}
.ram-notice-icon{flex:none;display:inline-flex;color:#d49a1e;}

/* ---------- Summary cards ---------- */
.ram-stats{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:16px;margin-bottom:18px;}
.ram-tone-green {--ram-tone:#1fae7a;}
.ram-tone-blue  {--ram-tone:#3b82f6;}
.ram-tone-violet{--ram-tone:#7c5ce0;}
.ram-tone-amber {--ram-tone:#e0a106;}
.ram-stat{
  display:flex;align-items:flex-start;gap:16px;
  padding:20px 22px;border-radius:14px;
  border:1px solid var(--border);
  /* A wash of the card's own tone over the theme's card colour, so the four
     read as four distinct counts in light mode and still sit correctly on a
     dark card instead of glowing. */
  background:linear-gradient(135deg, color-mix(in srgb, var(--ram-tone) 8%, var(--card)) 0%, var(--card) 62%);
  box-shadow:var(--shadow);
  transition:transform .18s ease, box-shadow .18s ease;
}
@media (hover:hover) and (pointer:fine){
  .ram-stat:hover{transform:translateY(-2px);box-shadow:var(--shadow-md, 0 12px 24px -14px rgba(20,40,70,.4));}
}
.ram-stat-icon{
  flex:none;width:56px;height:56px;border-radius:50%;
  display:inline-flex;align-items:center;justify-content:center;
  background:color-mix(in srgb, var(--ram-tone) 16%, transparent);
  color:var(--ram-tone);
}
/* The glyphs are drawn at 22px for reuse elsewhere; inside the 56px disc the
   comp has them nearer half its width, and a heavier stroke so they hold up
   against the tint. */
.ram-stat-icon svg{width:28px;height:28px;stroke-width:2.1;}
.ram-stat-body{flex:1 1 auto;min-width:0;}
.ram-stat-value{font-size:30px;font-weight:800;line-height:1.05;color:var(--text-900);font-variant-numeric:tabular-nums;}
.ram-stat-label{margin-top:6px;font-size:11.6px;font-weight:700;letter-spacing:.08em;text-transform:uppercase;color:var(--text-600);}
.ram-stat-note{
  display:flex;align-items:center;gap:6px;margin-top:10px;
  font-size:12.2px;color:var(--text-400);
}
.ram-stat-note svg{flex:none;color:var(--ram-tone);}
/* The one card whose note is a proportion gets drawn as one. */
.ram-meter{margin-top:10px;height:7px;border-radius:99px;background:var(--subtle-bg);overflow:hidden;}
.ram-meter span{display:block;height:100%;border-radius:inherit;background:linear-gradient(90deg,#8b7cf6,#6f8cf8);}

/* ---------- Invite / Unlock ---------- */
.ram-cta{display:flex;gap:12px;flex-wrap:wrap;margin-bottom:18px;}
.ram-cta-invite,
.ram-cta-unlock{
  appearance:none;cursor:pointer;font-family:inherit;
  display:inline-flex;align-items:center;gap:10px;
  padding:12px 22px;border-radius:11px;
  font-size:14.4px;font-weight:700;
  transition:filter .15s ease, background .15s ease, color .15s ease;
}
/* Dark ink on gold, as on every other gold button in the app — the comp's
   white-on-gold is under 3:1. */
.ram-cta-invite{
  border:none;color:#2a1f06;
  background:linear-gradient(180deg,#DDA84F,#C4872A);
  box-shadow:0 8px 18px -10px rgba(180,132,40,.8);
}
.ram-cta-invite[disabled]{cursor:not-allowed;filter:grayscale(.5);opacity:.55;box-shadow:none;}
.ram-cta-unlock{border:1.5px solid var(--blue);color:var(--blue);background:var(--card);}
@media (hover:hover) and (pointer:fine){
  .ram-cta-invite:not([disabled]):hover{filter:brightness(1.05);}
  .ram-cta-unlock:hover{background:var(--blue-bg);}
}

/* ---------- Members panel ---------- */
.ram-panel{
  padding:16px 18px 8px;border-radius:16px;
  border:1px solid var(--border);background:var(--card);box-shadow:var(--shadow);
}
.ram-panel-bar{display:flex;align-items:center;justify-content:space-between;gap:14px;flex-wrap:wrap;margin-bottom:14px;}
.ram-pills{display:flex;gap:10px;flex-wrap:wrap;}
.ram-pill{
  appearance:none;cursor:pointer;font-family:inherit;
  display:inline-flex;align-items:center;gap:9px;
  padding:9px 18px;border-radius:999px;border:none;
  font-size:13.6px;font-weight:600;
  background:var(--subtle-bg);color:var(--text-600);
  transition:background .15s ease,color .15s ease;
}
@media (hover:hover) and (pointer:fine){ .ram-pill:not(.active):hover{color:var(--text-900);} }
.ram-pill-count{
  min-width:20px;padding:1px 7px;border-radius:99px;text-align:center;
  font-size:11.4px;font-weight:700;background:var(--card);color:var(--text-600);
}
.ram-pill.active{background:linear-gradient(180deg,#DDA84F,#C4872A);color:#2a1f06;}
.ram-pill.active .ram-pill-count{background:rgba(0,0,0,.14);color:#2a1f06;}
.ram-search{
  display:flex;align-items:center;gap:10px;
  flex:0 1 300px;min-width:220px;height:42px;padding:0 14px;
  border:1px solid var(--border);border-radius:11px;background:var(--card);
}
.ram-search:focus-within{border-color:var(--gold-500);box-shadow:0 0 0 3px rgba(200,153,46,.18);}
.ram-search-icon{display:inline-flex;color:var(--text-400);}
.ram-search input{
  flex:1 1 auto;min-width:0;border:none;outline:none;background:transparent;
  font-family:inherit;font-size:13.6px;color:var(--text-900);
}

/* ---------- Table ---------- */
/* Horizontal padding is 11px, not the 14 first tried, and header tracking is
   .04em, not .06: at those first values the table's natural width was 1270px
   in a 1144px panel, so it scrolled and the Deactivate buttons sat past the
   edge. Most columns here are only as wide as their header, which made the
   header's own letter-spacing the deciding width for half the table. */
.ram-table thead th{
  background:var(--subtle-bg);
  font-size:11px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;
  color:var(--text-600);padding:13px 11px;white-space:nowrap;
}
.ram-table thead th:first-child{border-radius:10px 0 0 10px;}
.ram-table thead th:last-child{border-radius:0 10px 10px 0;}
.ram-table tbody td{padding:15px 11px;border-bottom:1px solid var(--border);vertical-align:middle;}
.ram-table tbody tr:last-child td{border-bottom:none;}
.ram-code{font-size:12.6px;color:var(--text-400);font-variant-numeric:tabular-nums;white-space:nowrap;}
.ram-member{display:flex;align-items:center;gap:12px;}
.ram-avatar{
  flex:none;width:40px;height:40px;border-radius:50%;
  display:inline-flex;align-items:center;justify-content:center;
  font-size:16px;font-weight:700;
}
.ram-av-0{background:#fde7c7;color:#b86d05;}
.ram-av-1{background:#dde8ff;color:#2356d8;}
.ram-av-2{background:#d7f4e8;color:#13805a;}
.ram-av-3{background:#ebe4ff;color:#6844d4;}
.ram-av-4{background:#ffe1e8;color:#bf2f57;}
.ram-name{font-size:14px;font-weight:700;color:var(--text-900);}
.ram-email{font-size:13.2px;color:var(--text-600);}
.ram-muted{font-size:13px;color:var(--text-400);}
/* Last Activity as date over time — see memberWhenHtml in rolesAccess.js. */
.ram-date,.ram-time{display:block;white-space:nowrap;font-variant-numeric:tabular-nums;}
.ram-date{font-size:13px;font-weight:600;color:var(--text-900);}
.ram-time{margin-top:2px;font-size:11.8px;color:var(--text-400);}
.ram-status{
  display:inline-flex;align-items:center;gap:7px;
  padding:5px 12px;border-radius:999px;
  font-size:12.4px;font-weight:600;white-space:nowrap;
}
.ram-dot{width:7px;height:7px;border-radius:50%;background:currentColor;}
.ram-status-active  {background:rgba(31,174,122,.14);color:#138a5e;}
.ram-status-inactive{background:var(--subtle-bg);color:var(--text-400);}
.ram-status-locked  {background:var(--amber-bg);color:var(--amber);}
.ram-access{display:inline-block;padding:5px 14px;border-radius:999px;font-size:12.4px;font-weight:600;}
.ram-access-full  {background:rgba(224,161,6,.16);color:#9c6c00;}
.ram-access-custom{background:var(--blue-bg);color:var(--blue);}
.ram-access-none  {background:rgba(124,92,224,.13);color:#6547cf;}
.ram-actions{display:flex;align-items:center;gap:7px;flex-wrap:nowrap;}
.ram-btn{
  appearance:none;cursor:pointer;font-family:inherit;white-space:nowrap;
  display:inline-flex;align-items:center;gap:7px;
  padding:7px 12px;border-radius:9px;
  font-size:12.8px;font-weight:600;
  border:1px solid var(--border);background:var(--card);color:var(--text-900);
  transition:background .15s ease,border-color .15s ease,color .15s ease;
}
.ram-btn svg{flex:none;}
@media (hover:hover) and (pointer:fine){ .ram-btn:hover{background:var(--subtle-bg);} }
.ram-btn[disabled]{cursor:not-allowed;opacity:.5;}
.ram-btn-danger{background:rgba(225,29,72,.08);border-color:transparent;color:#cf2a51;}
@media (hover:hover) and (pointer:fine){ .ram-btn-danger:hover{background:rgba(225,29,72,.15);} }
.ram-btn-gold{background:linear-gradient(180deg,#DDA84F,#C4872A);border-color:transparent;color:#2a1f06;}

/* ---------- Dark mode ----------
   Only the fixed tints need restating — everything else above is already on
   theme tokens. Each is the same hue lifted for a dark card: the light-mode
   values are chosen for white and go muddy on navy. */
:root[data-theme="dark"] .ram-av-0{background:rgba(245,166,35,.18);color:#f7b955;}
:root[data-theme="dark"] .ram-av-1{background:rgba(96,140,255,.2);color:#9dbbff;}
:root[data-theme="dark"] .ram-av-2{background:rgba(40,190,130,.18);color:#5fd6a4;}
:root[data-theme="dark"] .ram-av-3{background:rgba(150,120,255,.2);color:#bba7ff;}
:root[data-theme="dark"] .ram-av-4{background:rgba(255,100,140,.18);color:#ff9bb4;}
:root[data-theme="dark"] .ram-status-active{background:rgba(63,191,133,.16);color:#5fd6a4;}
:root[data-theme="dark"] .ram-access-full{background:rgba(230,178,54,.16);color:#f0c05a;}
:root[data-theme="dark"] .ram-access-none{background:rgba(150,120,255,.16);color:#b9a6ff;}
:root[data-theme="dark"] .ram-btn-danger{background:rgba(255,90,130,.14);color:#ff8fa8;}

/* ---------- Narrower screens ----------
   Handwriting goes first, then the illustration, then the cards halve and
   halve again — the order in which each stops earning its space. */
@media(max-width:1240px){
  .ram-hero{grid-template-columns:auto minmax(0,1fr) auto;grid-template-areas:"tile copy art" "notice notice notice";}
  .ram-hero-script{display:none;}
}
@media(max-width:1100px){
  .ram-stats{grid-template-columns:repeat(2,minmax(0,1fr));}
}
@media(max-width:960px){
  .ram-hero{grid-template-columns:auto minmax(0,1fr);grid-template-areas:"tile copy" "notice notice";}
  .ram-hero-art{display:none;}
}
@media(max-width:640px){
  .ram-hero{grid-template-columns:minmax(0,1fr);grid-template-areas:"tile" "copy" "notice";padding:20px;}
  .ram-hero-tile{width:64px;height:64px;border-radius:18px;}
  .ram-hero-tile svg{width:34px;height:34px;}
  .ram-hero-title{font-size:20px;}
  .ram-notice{align-items:flex-start;}
  .ram-stats{grid-template-columns:minmax(0,1fr);}
  .ram-cta > button{flex:1 1 auto;justify-content:center;}
  .ram-panel{padding:14px 12px 6px;}
  .ram-search{flex:1 1 100%;min-width:0;}
}

/* ================================================================
   Family Members - dark-mode redesign (Hero section_polished.png)
   Scoped entirely under [data-theme="dark"] (this app's only dark-mode
   selector - no prefers-color-scheme fallback exists anywhere else in
   the app, so none is introduced here either). Light mode keeps the
   palette already matched to the light reference; dark mode gets its
   own much richer near-black/glowing-gold treatment per this later,
   dark-specific reference image.
   ================================================================ */
:root[data-theme="dark"] .card.fm-card{
  background:
    radial-gradient(70% 60% at 100% 0%, color-mix(in srgb, var(--gold-500) 10%, transparent) 0%, transparent 60%),
    radial-gradient(60% 50% at 0% 100%, color-mix(in srgb, var(--fm-royal) 10%, transparent) 0%, transparent 60%),
    linear-gradient(165deg, #0a1420 0%, #060c16 60%, #04080f 100%);
  border-color:color-mix(in srgb, var(--gold-500) 22%, transparent);
}
:root[data-theme="dark"] .fm-intro{
  background:
    radial-gradient(90% 160% at 0% 0%, color-mix(in srgb, var(--gold-500) 9%, transparent) 0%, transparent 60%),
    linear-gradient(120deg, #0b1626 0%, #060d18 55%, #04080f 100%);
  border-color:color-mix(in srgb, var(--gold-500) 22%, transparent);
}
:root[data-theme="dark"] .fm-card .fm-intro::before{opacity:.68;}
:root[data-theme="dark"] .fm-eyebrow{color:var(--gold-400);}
:root[data-theme="dark"] .fm-title{color:#fff;}
:root[data-theme="dark"] .fm-subtitle{color:color-mix(in srgb, #fff 68%, transparent);}

/* Glowing swoosh: a big soft ring, mostly clipped off by the band's own
   overflow:hidden, so only a glowing gold arc shows through - the same
   "circle clipped by a box" trick .fm-intro::after already uses for the
   small light-mode heritage ring, just much larger and brighter here. */
:root[data-theme="dark"] .fm-intro::after{
  right:auto;left:38%;top:auto;bottom:-260px;
  width:520px;height:520px;
  border-color:color-mix(in srgb, var(--gold-400) 55%, transparent);
  box-shadow:0 0 24px 2px color-mix(in srgb, var(--gold-400) 45%, transparent),
             0 0 0 1px color-mix(in srgb, var(--gold-500) 10%, transparent);
}

/* Identity badge: circular glowing ring instead of the rounded-square
   navy plate, matching this dark reference specifically. */
:root[data-theme="dark"] .fm-intro-badge{
  border-radius:50%;
  background:radial-gradient(120% 120% at 30% 25%, #2c4a75 0%, #101f36 62%, #060c16 100%);
  box-shadow:0 0 0 2px color-mix(in srgb, var(--gold-400) 70%, transparent),
             0 0 18px 2px color-mix(in srgb, var(--gold-400) 55%, transparent),
             inset 0 1px 0 rgba(255,255,255,.22);
}

/* Stat cards: near-black per-accent glass with a glowing accent border,
   plus a big icon-shaped watermark (people / heart / shield / document)
   in place of the light-mode leaf sprig - same --fm-accent each card
   already carries per data-accent, so no new colour is introduced. */
:root[data-theme="dark"] .fm-summary{background:transparent;}
:root[data-theme="dark"] .fm-stat{
  background:
    radial-gradient(130% 140% at 100% 0%, color-mix(in srgb, var(--fm-accent) 20%, transparent) 0%, transparent 65%),
    linear-gradient(165deg, color-mix(in srgb, var(--fm-accent) 14%, #0a0f18) 0%, #06090f 100%);
  border-color:color-mix(in srgb, var(--fm-accent) 45%, transparent);
  box-shadow:0 0 0 1px color-mix(in srgb, var(--fm-accent) 20%, transparent),
             0 10px 26px -18px color-mix(in srgb, var(--fm-accent) 80%, transparent);
}
:root[data-theme="dark"] .fm-stat-value{color:#fff;}
:root[data-theme="dark"] .fm-stat-label{color:color-mix(in srgb, #fff 62%, transparent);}
:root[data-theme="dark"] .fm-stat-ico{
  box-shadow:0 0 12px 1px color-mix(in srgb, var(--fm-accent) 55%, transparent),
             inset 0 0 0 1px color-mix(in srgb, var(--fm-accent) 45%, transparent);
}
/* The icon watermarks below are shared by both themes (they replaced the
   light-mode leaf sprig there too); only their strength differs. Light
   mode's was raised well above the sprig's .16 so the shapes read
   clearly against the pale tinted cards. */
.fm-stat::after{
  right:-2px;bottom:-10px;
  width:60px;height:60px;
  opacity:.44;
}
:root[data-theme="dark"] .fm-stat::after{opacity:.22;}
.fm-stat[data-accent="total"]::after{
  -webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'><circle fill='white' cx='24' cy='20' r='11'/><path fill='white' d='M24 34C10 34 2 43 2 55v5h44v-5c0-12-8-21-22-21z'/><circle fill='white' cx='47' cy='23' r='8'/><path fill='white' d='M47 35c10 0 17 7 17 17v4H50v-4c0-6-1-11-5-15 1-1 1-1 2-2z'/></svg>");
  mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'><circle fill='white' cx='24' cy='20' r='11'/><path fill='white' d='M24 34C10 34 2 43 2 55v5h44v-5c0-12-8-21-22-21z'/><circle fill='white' cx='47' cy='23' r='8'/><path fill='white' d='M47 35c10 0 17 7 17 17v4H50v-4c0-6-1-11-5-15 1-1 1-1 2-2z'/></svg>");
}
.fm-stat[data-accent="living"]::after{
  -webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'><path fill='white' d='M32 57C15 46 4 35 4 22 4 12 12 5 21 5c5 0 9 3 11 7 2-4 6-7 11-7 9 0 17 7 17 17 0 13-11 24-28 35z'/></svg>");
  mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'><path fill='white' d='M32 57C15 46 4 35 4 22 4 12 12 5 21 5c5 0 9 3 11 7 2-4 6-7 11-7 9 0 17 7 17 17 0 13-11 24-28 35z'/></svg>");
}
.fm-stat[data-accent="kyc-done"]::after{
  -webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'><path fill='white' d='M32 4l23 9v15c0 17-11 29-23 32C20 57 9 45 9 28V13z'/></svg>");
  mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'><path fill='white' d='M32 4l23 9v15c0 17-11 29-23 32C20 57 9 45 9 28V13z'/></svg>");
}
.fm-stat[data-accent="kyc-pending"]::after{
  -webkit-mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'><path fill='white' d='M14 4L34 4 44 14 44 60 14 60Z'/><rect fill='white' x='20' y='24' width='18' height='3'/><rect fill='white' x='20' y='32' width='18' height='3'/><rect fill='white' x='20' y='40' width='12' height='3'/></svg>");
  mask-image:url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'><path fill='white' d='M14 4L34 4 44 14 44 60 14 60Z'/><rect fill='white' x='20' y='24' width='18' height='3'/><rect fill='white' x='20' y='32' width='18' height='3'/><rect fill='white' x='20' y='40' width='12' height='3'/></svg>");
}

/* Tagline banner: not in the light reference, so hidden by default and
   shown only in dark mode - a quiet gold motto with divider lines. */
.fm-tagline{
  display:none;
  align-items:center;
  justify-content:center;
  gap:16px;
  padding:14px 28px 18px;
}
:root[data-theme="dark"] .fm-tagline{display:flex;}
.fm-tagline-line{
  flex:1 1 auto;
  height:1px;
  max-width:220px;
  background:linear-gradient(90deg, transparent, color-mix(in srgb, var(--gold-400) 55%, transparent), transparent);
}
.fm-tagline-text{
  flex:none;
  font-size:11px;
  font-weight:700;
  letter-spacing:.22em;
  text-transform:uppercase;
  color:color-mix(in srgb, var(--gold-400) 82%, #fff);
  white-space:nowrap;
}
.fm-tagline-dot{color:var(--gold-500);margin:0 2px;}
@media(max-width:620px){
  .fm-tagline-line{display:none;}
  .fm-tagline-text{white-space:normal;text-align:center;}
}

/* ---------- Family Members roster: the Family Head's own row ----------
   Shown to a Family Member viewer only (familyTree.js familyHeadRow). The
   Head is not a member record, so this row has to read as a different KIND
   of entry rather than just another person: a gold wash, a gold rule down
   the leading edge, a crown beside the name, and a slow sheen travelling
   across it. The sheen is the "shining" part - a moving highlight rather
   than a static gradient, so the row catches the eye once and then settles
   instead of shouting permanently.

   Colours come from the existing --gold-* tokens rather than new hexes, so
   the row re-themes with the rest of the app; the dark-mode block only
   re-weights the wash, since gold on near-black needs less of it. */
.fm-table tbody tr.fm-row-head{
  position:relative;
  isolation:isolate;
  background:linear-gradient(100deg,
    color-mix(in srgb, var(--gold-500) 16%, transparent) 0%,
    color-mix(in srgb, var(--gold-400) 9%, transparent) 45%,
    transparent 100%);
  box-shadow:inset 3px 0 0 0 var(--gold-500);
}
.fm-table tbody tr.fm-row-head:hover{
  background:linear-gradient(100deg,
    color-mix(in srgb, var(--gold-500) 24%, transparent) 0%,
    color-mix(in srgb, var(--gold-400) 14%, transparent) 45%,
    transparent 100%);
}
/* The travelling sheen. On the first cell only, clipped to the row's own
   box - a full-width pseudo-element on the <tr> is unreliable across table
   layout engines, whereas a td-anchored one is not. */
.fm-table tbody tr.fm-row-head > td:first-child::after{
  content:"";
  position:absolute;
  inset:0;
  pointer-events:none;
  background:linear-gradient(100deg,
    transparent 0%,
    color-mix(in srgb, var(--gold-300, var(--gold-400)) 55%, transparent) 50%,
    transparent 100%);
  opacity:0;
  animation:fm-head-sheen 4.5s ease-in-out infinite;
  z-index:-1;
}
.fm-table tbody tr.fm-row-head > td{position:relative;}
@keyframes fm-head-sheen{
  0%   {transform:translateX(-60%); opacity:0;}
  35%  {opacity:.55;}
  70%  {opacity:0;}
  100% {transform:translateX(160%); opacity:0;}
}
/* The crown sits with the name, taking the row's gold rather than its own. */
.fm-head-crown{
  display:inline-flex;
  align-items:center;
  margin-left:6px;
  color:var(--gold-500);
  vertical-align:-2px;
}
.fm-head-crown svg{filter:drop-shadow(0 1px 2px color-mix(in srgb, var(--gold-500) 45%, transparent));}
.fm-row-head .fm-name{font-weight:800;letter-spacing:.01em;}
/* Gold ring on the avatar, the same mark the dashboard's head tile uses.
   Selector carries .avatar.fm-avatar as well, because that pair (0,2,0) sets
   the modelled navy-and-hairline-gold box-shadow every roster avatar gets and
   would otherwise win over a lone .fm-avatar-head (0,1,0) no matter how late
   this lands in the file. Keeps that rule's inner modelling and replaces only
   the rim, so the Head's avatar reads as the same object wearing a ring
   rather than a differently-built one. */
.avatar.fm-avatar.fm-avatar-head{
  box-shadow:inset 0 1px 0 rgba(255,255,255,.32),
             inset 0 -3px 7px rgba(6,14,28,.42),
             0 0 0 2px var(--gold-500),
             0 0 0 6px color-mix(in srgb, var(--gold-500) 22%, transparent),
             0 0 14px color-mix(in srgb, var(--gold-500) 45%, transparent);
}
/* "Family Head" reads as a title, not a relationship like the rows below. */
.fm-head-tag{
  display:inline-block;
  padding:3px 10px;
  border-radius:999px;
  font-size:11px;
  font-weight:800;
  letter-spacing:.04em;
  text-transform:uppercase;
  color:#6b4708;
  background:linear-gradient(135deg, var(--gold-400) 0%, var(--gold-500) 100%);
  box-shadow:0 1px 3px color-mix(in srgb, var(--gold-500) 45%, transparent);
  white-space:nowrap;
}
:root[data-theme="dark"] .fm-table tbody tr.fm-row-head{
  background:linear-gradient(100deg,
    color-mix(in srgb, var(--gold-500) 22%, transparent) 0%,
    color-mix(in srgb, var(--gold-400) 11%, transparent) 45%,
    transparent 100%);
}
:root[data-theme="dark"] .fm-head-tag{color:#3d2800;}
/* A permanently moving highlight is exactly what reduced-motion asks you to
   drop - the gold wash, rule, crown and ring still mark the row without it. */
@media (prefers-reduced-motion: reduce){
  .fm-table tbody tr.fm-row-head > td:first-child::after{animation:none;opacity:0;}
}

/* ---------- Family Members: dark-mode hero photo swap ---------- */
/* Hero section_polished.png uses a moodier golden-hour sunset photo for
   the dark theme instead of the paler daytime crop the light reference
   used - swapped in for dark mode only, same right-anchored mask recipe. */
:root[data-theme="dark"] .fm-card .fm-intro::before{
  background-image:url('../assets/fm-hero-photo-dark-v2.png');
}

/* ---------- Family Members: stat-card icons made circular (dark mode) ---------- */
/* Hero section_polished.png's four stat icons are circular glowing badges,
   not the rounded-square well used elsewhere - shape-only change, the
   glow/background already added for dark mode is untouched. */
:root[data-theme="dark"] .fm-stat-ico{border-radius:50%;}

/* ---------- INR twin line under a foreign-currency amount ----------
   Sits directly beneath the input, reading "USD 478 = Rs 45,620.32 at 95.44"
   so the field states BOTH figures and the rate connecting them. Styled as a
   quiet companion to the value rather than a second value competing with it:
   .field-hint's size and muted tone, with only the rupee figure carrying
   weight, since that is the currency the app's own totals are in. */
.inr-twin{
  display:flex;
  align-items:baseline;
  flex-wrap:wrap;
  gap:5px;
  margin-top:3px;
  font-size:11px;
  line-height:1.5;
  color:var(--text-400);
}
.inr-twin-fx{
  font-variant-numeric:tabular-nums;
  color:var(--text-600);
}
.inr-twin-eq{opacity:.6;}
.inr-twin-inr{
  font-weight:700;
  font-variant-numeric:tabular-nums;
  color:var(--gold-600, var(--gold-500));
}
:root[data-theme="dark"] .inr-twin-inr{color:var(--gold-400);}
/* The rate is provenance, not a figure to read - smallest and quietest, and
   allowed to wrap to its own line before the two amounts ever do. */
.inr-twin-rate{
  font-size:10px;
  opacity:.75;
  white-space:nowrap;
}

/* ---------- Family Members stat cards: real icon badges (Background icons.png) ---------- */
/* Each stat now renders a pre-made glossy circular badge image instead of
   FM_ICONS' flat inline SVG - the image already is a complete coloured
   badge, so .fm-stat-ico's own tint/border/background well is turned off
   here rather than layered underneath it (which would show through the
   badge's own transparent corners as a mismatched square). Applies in
   both themes - the badge art has no light/dark variant, it is just the
   icon now. Dark mode's existing glow (box-shadow) and circular radius
   overrides still apply on top, unchanged. */
.fm-stat-ico.fm-stat-ico-img{
  background:none;
  border:none;
  box-shadow:none;
  width:44px;height:44px;
}
.fm-stat-ico-img img{
  display:block;
  width:100%;
  height:100%;
  object-fit:contain;
}

/* ---------- Consent card for a purpose that is declared but not yet built ----
   Asking someone to consent to processing the app does not perform is what a
   DPDP audit flags, so C004's card says so plainly instead of offering an
   Accept button. Same muted treatment as a field hint - it is an explanation,
   not a warning, and the card is not in an error state. */
.pn-consent-soon{
  margin:8px 0 0;
  padding:9px 11px;
  border-radius:8px;
  border:1px dashed var(--border);
  background:var(--subtle-bg);
  font-size:11.6px;
  line-height:1.55;
  color:var(--text-400);
}

/* ---------- Bulk Data Export: hero art fit to the hero's own colour ---------- */
/* The previous fade still left a faint rectangular seam visible against
   the cream hero, most noticeable in dark mode (hero band itself stays
   the fixed light cream/gold, same as every other page's own hero) -
   widened/softened the mask so the fade starts well inside the image
   instead of near its edge, and warmed the tone a touch more to match. */
/* ---------- Bulk Data Export hero art - actually blend into the hero, not just fade its edges ---------- */
/* The mask+filter above faded the art's hard rectangular edge out, but the
   pale blue-white backdrop inside that faded edge was still a visibly
   cooler, separate rectangle sitting on the hero's warm cream - "not
   fitting" the hero background rather than any hard-edge problem. Tried
   removing that backdrop outright first (same edge-flood-fill used on
   this page's own icons and the Assets/Liabilities module art below), but
   this illustration's laptop screen is itself near-white, close enough in
   colour to the surrounding background that the fill leaks straight
   through it and erases the laptop along with the backdrop - confirmed by
   testing it, not assumed. mix-blend-mode:multiply is what was actually
   missing: it composites the art's remaining pale tones directly against
   the cream hero underneath instead of painting over it, so the backdrop
   merges into the hero's own colour rather than just fading at the edge.
   Mask tightened (65% ellipse fading in from 35%, versus 90%/40%) since
   multiply alone still left a faint halo at the old, wider fade radius. */
.bde-hero-art{
  mix-blend-mode:multiply;
  filter:sepia(.6) saturate(1.25) brightness(1.05);
  mask-image:radial-gradient(ellipse 65% 65% at center, black 35%, transparent 100%);
  -webkit-mask-image:radial-gradient(ellipse 65% 65% at center, black 35%, transparent 100%);
}

/* ---------- Shine letter headings: less exaggerated letter-spacing ---------- */
/* .shine-letters originally copied .nav-group-title's 0.08em letter-spacing
   outright so these large proper-case headings ("Hello, {name}", Family
   Balance Sheet's title, Business Details Hub, Travel Kit) would read as
   "one family" with the sidebar's small ALL-CAPS labels. At heading scale
   (20-32px) that value reads as an unintended gap between letters rather
   than a deliberate style, so it is dialled back rather than removed -
   .nav-group-title itself is untouched, this only affects the heading
   reuse of the value. */
.shine-letters{letter-spacing:.02em;}

/* ---------- Bulk Data Export: module header icons spin like a coin ---------- */
/* Only the two module-header badges (Asset Management / Obligations &
   Liabilities - the icon sitting right before each module's own title),
   not the hero icon or the category checkboxes' icons. A continuous
   clockwise rotation around the vertical axis, the same technique any
   "spinning coin" effect uses - perspective gives the turn actual depth
   instead of a flat horizontal squash. Already covered by the
   .bde-module-icon prefers-reduced-motion rule above, so this respects
   that preference for free. */
.bde-module-icon{
  animation:bde-coin-spin 3.2s linear infinite;
}
@keyframes bde-coin-spin{
  from{transform:perspective(240px) rotateY(0deg);}
  to{transform:perspective(240px) rotateY(360deg);}
}

/* ---------- Bulk Data Export: gold outline on the spinning module icons ---------- */
/* Same two icons the coin-spin animation targets (.bde-module-icon) - a
   solid gold ring plus a soft glow, so the "coin" reads as gold-rimmed
   in both themes rather than just a plain circular badge. */
.bde-module-icon{
  box-shadow:0 0 0 3px var(--gold-500),
             0 0 10px 1px color-mix(in srgb, var(--gold-500) 45%, transparent);
}

/* ---------- Bulk Data Export: module header icon, static illustration ---------- */
/* Replaces the small spinning gold coin with a static, larger icon that
   matches each module's own imagery (house+shield for Asset Management,
   loan/calculator/calendar for Obligations & Liabilities) - cropped from
   user-supplied reference "Asset Management and  Obligations
   Liabilities.png" (icon area only; its own title-text row is excluded
   since .bde-module-title already renders that text separately). The
   coin-spin animation, circular clip and gold ring above suited a small
   round coin glyph, not a wide illustrated icon, so all three are reset
   here for these two badges specifically. */
.bde-module-icon-assets{background-image:url('../assets/bde-assets-icon-v2.png');}
.bde-module-icon-liab{background-image:url('../assets/bde-liab-icon-v2.png');}
.bde-module-icon{
  width:100px;height:64px;
  border-radius:14px;
  animation:none;
  box-shadow:0 1px 4px rgba(20,32,52,.18);
  background-color:#fff;
  background-size:82%;
  background-repeat:no-repeat;
  background-position:center;
  /* Tried mix-blend-mode:multiply first (the same fix used on this page's
     .bde-module-art for a near-white backdrop) since the crop's own
     backdrop is near-white, opaque, edge to edge - but multiply blends
     against whatever sits behind the element, and dark mode's card fill
     (--card:#161e2c) is dark enough that multiply crushed every icon
     colour along with the backdrop, not just the white - confirmed live,
     rejected. A fixed white chip instead (the icon's own crop already
     assumes a near-white backdrop, so this matches it exactly, same
     white in both themes) reproduces the reference mockup's own card
     treatment and needs no per-theme tuning.

     2026-09-15 - a follow-up request asked for this badge animated again
     with a rolling golden border (::before/::after two-layer ring, same
     day) then asked to revert it back to this exact static style within
     the same session - kept here as the deliberately-settled version. */
}

/* ---------- Bulk Data Export: module art, background-removed ("Icons.png") ---------- */
/* The -v3 poster crops carried their own dark-navy backdrop baked in as a
   rectangular panel - meant to be a self-contained "poster", but composited
   into this app's own card it just read as an unwanted black box sitting on
   a light card in light mode (and a mismatched-edge dark box even in dark
   mode, its navy not quite matching --card's own dark tone). The -v4 set
   is the same illustrations with that backdrop removed (edge-flood-fill,
   same technique used on this page's own category icons) - just the house/
   coins/shield/document/tagline group floating on nothing, so whatever the
   card's real background is (light or dark) shows through cleanly instead
   of a second, competing background. background-size switches from cover
   (fill a rectangle) to contain (show the whole floating group, nothing
   cropped) now that there is no rectangle to fill; the border-radius/
   box-shadow that gave the old poster its own card-like edges are dropped
   for the same reason - there is no edge left to round or shadow. */
.bde-module-art-assets{background-image:url('../assets/bde-assets-art-v5.png');}
.bde-module-art-liab{background-image:url('../assets/bde-liab-art-v5.png');}
.bde-module-art{
  width:170px;height:178px;
  background-size:contain;
  background-position:center;
  border-radius:0;
  box-shadow:none;
  mix-blend-mode:normal;
  filter:none;
  mask-image:none;
  -webkit-mask-image:none;
  background-color:transparent;
}
:root[data-theme="dark"] .bde-module-art{
  mix-blend-mode:normal;
  filter:none;
  mask-image:none;
  -webkit-mask-image:none;
  background-color:transparent;
  background-size:contain;
  border-radius:0;
  box-shadow:none;
}

/* ---------- Bulk Data Export: module art tagline recolor is light-mode only ---------- */
/* -v5 recolors the tagline's glossy sky-blue lettering to a flat dark
   navy (--navy-900) so it reads clearly on the light card background -
   a fix dark mode doesn't need, since its own dark card is already
   close to what the tagline's original colouring was designed to sit
   on. Dark mode keeps the untouched -v4 source (same swap technique
   already used for the Family Members hero photo). */
:root[data-theme="dark"] .bde-module-art-assets{background-image:url('../assets/bde-assets-art-v4.png');}
:root[data-theme="dark"] .bde-module-art-liab{background-image:url('../assets/bde-liab-art-v4.png');}


/* ---------- Bulk Data Export: bigger category icons, both themes ---------- */
/* User feedback: icons still read small / some content not filling the
   box even at the current 34px (light) / 46px (dark) sizes set earlier
   this session. Bumping both further; iconHtml() sets width/height as an
   inline style on the <img> itself, so overriding it needs !important,
   same pattern already used for the dark-mode bump above. */
.bde-cat-icon{width:44px;height:44px;}
.bde-cat-icon img{width:44px !important;height:44px !important;}

/* ---------- Bulk Data Export: one shared icon size in both themes ---------- */
/* The dark-mode bump above (56px) was requested back when the icons were
   the old sprite-sheet crop and looked thin/washed-out at the light-mode
   size - now that every icon is its own individually-cropped image (the
   -v4 set, bulkExport.js), a single size that reads the same regardless
   of theme is what was actually asked for. Settled on 48px (a little
   over the old 44px light-mode value, well under the old 56px dark-mode
   one) for both themes, since several -v4 icons (e.g. Vehicle, a wide
   silhouette rather than a square one) render noticeably smaller than
   their box at any size once object-fit:contain is scaling by their own
   width - a bigger shared box is the one thing that helps every icon,
   wide ones included, without cropping or distorting any of them. */
.bde-cat-icon,
:root[data-theme="dark"] .bde-cat-icon{width:48px;height:48px;font-size:18px;}
.bde-cat-icon img,
:root[data-theme="dark"] .bde-cat-icon img{width:48px !important;height:48px !important;}

/* ---------- Bulk Data Export: dark backdrop for low-contrast category icons ---------- */
/* Insurance/Vehicle/Retirement Benefits/Gold Loan/Education Loan (flagged
   by BDE_ICON_CONTRAST, bulkExport.js) lean on white/near-white fills -
   shield checkmark, car body highlights, cap surface, hand outlines - that
   read fine against those -v3/-v4 icons' own dark preview backdrop but
   nearly vanish directly on this page's white card in light mode
   (user-reported). A dark rounded chip behind just these five restores
   the contrast their art was designed against. Dark mode's card is
   already dark, so the chip is reset to transparent there - it would only
   add an unwanted double-dark edge. */
.bde-cat-icon-contrast{
  width:56px;height:56px;
  background:var(--navy-900, #0f1f33);
  border-radius:12px;
  box-shadow:0 1px 3px rgba(20,32,52,.25);
}
:root[data-theme="dark"] .bde-cat-icon-contrast{
  background:transparent;
  box-shadow:none;
}

/* ---------- Nest Users > DPDP Compliance Consents (Sysadmin) ---------- */
.roles-access-page .ra-consent-table td.ra-consent-indent{padding-left:44px;position:relative;}
.roles-access-page .ra-consent-table td.ra-consent-indent::before{content:"";position:absolute;left:24px;top:0;bottom:50%;width:10px;border-left:1px solid var(--border);border-bottom:1px solid var(--border);border-bottom-left-radius:6px;}
.ra-consent-table tr.ra-consent-child td{background:var(--surface-2, rgba(0,0,0,.015));}
.ra-consent-chips{display:flex;flex-direction:column;gap:5px;min-width:250px;}
.ra-consent-chip{display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:12.3px;color:var(--text-600);}
.ra-consent-timeline{list-style:none;margin:0;padding:0 0 0 18px;border-left:2px solid var(--border);}
.ra-consent-event{position:relative;padding:0 0 16px 14px;}
.ra-consent-event:last-child{padding-bottom:2px;}
.ra-consent-event::before{content:"";position:absolute;left:-25px;top:3px;width:12px;height:12px;border-radius:50%;background:var(--text-400);box-shadow:0 0 0 3px var(--card-bg, #fff);}
.ra-consent-event-green::before{background:var(--green);}
.ra-consent-event-amber::before{background:var(--amber);}
.ra-consent-event-when{font-size:11.5px;color:var(--text-400);}
.ra-consent-event-what{font-weight:600;color:var(--text-900);margin:1px 0;}
.ra-consent-event-meta{font-size:12.3px;color:var(--text-600);}
.ra-consent-optional{display:inline-block;margin-left:6px;padding:1px 7px;border-radius:10px;font-size:10.5px;font-weight:600;letter-spacing:.02em;text-transform:uppercase;color:var(--text-400);border:1px solid var(--border);vertical-align:1px;}


/* ============================================================
   Roles & Access - Family Activity and Access Audit tabs (redesign,
   15 Sept 2026). Built on the Members tab's ram-* parts (stats, panel,
   pills, search, avatars, access pills); the raa-* classes below add only
   what those two tabs need. Colours come from theme tokens or from the ram
   tints that already carry dark-mode overrides, so both tabs follow the
   light/dark toggle with just the few restatements at the bottom.
   ============================================================ */
.ram-tone-red{--ram-tone:#e0475f;}

/* ---------- Intro strip ---------- */
.raa-intro{
  display:flex;align-items:center;gap:18px;flex-wrap:wrap;
  padding:18px 22px;margin-bottom:18px;border-radius:16px;
  border:1px solid var(--border);
  background:
    radial-gradient(50% 140% at 100% 0%, color-mix(in srgb, var(--gold-500) 16%, transparent) 0%, transparent 70%),
    var(--card);
  box-shadow:var(--shadow);
}
.raa-intro-icon{
  flex:none;width:52px;height:52px;border-radius:14px;
  display:inline-flex;align-items:center;justify-content:center;
  color:#2a1f06;background:linear-gradient(180deg,#DDA84F,#C4872A);
  box-shadow:0 8px 18px -10px rgba(180,132,40,.8);
}
.raa-intro-icon svg{width:26px;height:26px;stroke-width:2;}
.raa-intro-copy{flex:1 1 320px;min-width:0;}
.raa-intro-title{margin:0;font-size:19px;font-weight:800;letter-spacing:-.2px;color:var(--text-900);}
.raa-intro-sub{margin:3px 0 0;font-size:13.4px;line-height:1.55;color:var(--text-600);}
.raa-intro-meta{
  display:flex;flex-direction:column;align-items:flex-end;gap:2px;
  padding:9px 14px;border-radius:11px;background:var(--subtle-bg);
}
.raa-intro-meta span{font-size:10.8px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--text-400);}
.raa-intro-meta b{font-size:13.4px;font-weight:700;color:var(--text-900);white-space:nowrap;font-variant-numeric:tabular-nums;}

/* ---------- Clickable summary cards (Activity) ---------- */
.raa-stat-btn{cursor:pointer;outline:none;}
.raa-stat-btn:focus-visible{box-shadow:0 0 0 3px color-mix(in srgb, var(--ram-tone) 35%, transparent);}
.raa-stat-btn.is-selected{
  border-color:var(--ram-tone);
  box-shadow:0 0 0 1px var(--ram-tone), 0 10px 22px -16px var(--ram-tone);
}
.raa-mini-meter{display:inline-block;width:54px;height:6px;border-radius:99px;background:var(--subtle-bg);overflow:hidden;vertical-align:middle;}
.raa-mini-meter span{display:block;height:100%;border-radius:inherit;background:var(--ram-tone);}
.raa-stat-date{font-size:21px;padding-top:5px;}

/* ---------- Layout ---------- */
.raa-layout{display:grid;grid-template-columns:minmax(0,1fr) 300px;gap:18px;align-items:start;}
.raa-main{padding-bottom:14px;}
.raa-side{display:flex;flex-direction:column;gap:14px;position:sticky;top:12px;}
.raa-side-card{padding:16px 18px;}
.raa-side-title{margin:0 0 12px;font-size:12px;font-weight:700;letter-spacing:.06em;text-transform:uppercase;color:var(--text-600);}
.raa-tools{display:flex;align-items:center;justify-content:flex-end;gap:10px;flex:1 1 420px;min-width:0;}
.raa-tools .ram-search{flex:1 1 220px;max-width:300px;min-width:160px;}
.raa-select{
  flex:0 1 190px;min-width:130px;
  height:42px;padding:0 12px;border-radius:11px;
  border:1px solid var(--border);background:var(--card);color:var(--text-900);
  font-family:inherit;font-size:13.4px;font-weight:600;cursor:pointer;
}
.raa-select:focus{outline:none;border-color:var(--gold-500);box-shadow:0 0 0 3px rgba(200,153,46,.18);}

/* ---------- Day headings ---------- */
.raa-day{display:flex;align-items:center;gap:12px;margin:16px 0 10px;}
.raa-day:first-child{margin-top:2px;}
.raa-day span{font-size:11.6px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--gold-600);white-space:nowrap;}
.raa-day::after{content:"";flex:1;height:1px;background:linear-gradient(90deg,var(--border),transparent);}

/* ---------- Activity rows ---------- */
.raa-row{
  --raa-tone:var(--blue);
  position:relative;display:flex;align-items:center;gap:14px;
  padding:12px 14px 12px 16px;margin-bottom:8px;border-radius:12px;
  border:1px solid var(--border);background:var(--card);
  transition:border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
.raa-row::before{content:"";position:absolute;left:0;top:10px;bottom:10px;width:3px;border-radius:0 3px 3px 0;background:var(--raa-tone);}
.raa-row-created{--raa-tone:var(--green);}
.raa-row-deleted{--raa-tone:var(--red);}
@media (hover:hover) and (pointer:fine){
  .raa-row:hover{border-color:color-mix(in srgb, var(--gold-500) 45%, var(--border));box-shadow:var(--shadow-md);transform:translateY(-1px);}
}
.raa-row-icon{flex:none;width:40px;height:40px;border-radius:11px;display:inline-flex;align-items:center;justify-content:center;}
.raa-row-main{flex:1 1 auto;min-width:0;}
.raa-row-title{font-size:13.8px;color:var(--text-600);line-height:1.45;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.raa-row-title strong{color:var(--text-900);font-weight:700;}
.raa-row-sub{margin-top:2px;font-size:12.2px;color:var(--text-400);}
.raa-crumb{margin:0 6px;color:var(--text-400);}
.raa-row-time{flex:none;min-width:66px;text-align:right;font-size:12.6px;font-weight:600;color:var(--text-600);font-variant-numeric:tabular-nums;white-space:nowrap;}
.raa-row-view{flex:none;padding:6px 10px 6px 12px;font-size:12.4px;}
.raa-row-view svg{margin-left:-2px;}
.raa-foot{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-top:12px;font-size:12.4px;color:var(--text-400);}

/* ---------- Side panels ---------- */
.raa-mix{display:flex;height:12px;border-radius:99px;overflow:hidden;background:var(--subtle-bg);margin-bottom:12px;}
.raa-mix span{height:100%;}
.raa-mix-created{background:var(--green);}
.raa-mix-updated{background:var(--blue);}
.raa-mix-deleted{background:var(--red);}
.raa-legend,.raa-bars,.raa-people-list,.raa-levels{list-style:none;margin:0;padding:0;}
.raa-legend{display:flex;flex-direction:column;gap:8px;font-size:13px;color:var(--text-600);}
.raa-legend li{display:flex;align-items:center;gap:9px;}
.raa-legend b{margin-left:auto;color:var(--text-900);font-variant-numeric:tabular-nums;}
.raa-dot{width:10px;height:10px;border-radius:3px;flex:none;}
.raa-bars{display:flex;flex-direction:column;gap:12px;}
.raa-bar-head{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:6px;font-size:13px;}
.raa-bar-name{display:flex;align-items:center;gap:8px;min-width:0;font-weight:600;color:var(--text-900);}
.raa-bar-icon{flex:none;width:24px;height:24px;border-radius:7px;display:inline-flex;align-items:center;justify-content:center;}
.raa-bar-icon svg{width:14px;height:14px;}
.raa-bar-head b{color:var(--text-600);font-variant-numeric:tabular-nums;}
.raa-bar-track{height:7px;border-radius:99px;background:var(--subtle-bg);overflow:hidden;}
.raa-bar-track span{display:block;height:100%;border-radius:inherit;background:linear-gradient(90deg,#DDA84F,#C4872A);}
.raa-people-list{display:flex;flex-direction:column;gap:10px;}
.raa-people-list li{display:flex;align-items:center;gap:10px;}
.raa-people-list .ram-avatar{width:34px;height:34px;font-size:14px;}
.raa-people-name{flex:1 1 auto;min-width:0;font-size:13.4px;font-weight:600;color:var(--text-900);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.raa-people-name small{display:block;font-size:11.6px;font-weight:500;color:var(--text-400);}
.raa-people-list b{font-size:13.4px;color:var(--text-600);font-variant-numeric:tabular-nums;}
.raa-current .ram-access{padding:4px 12px;font-size:12px;}
.raa-levels{display:flex;flex-direction:column;gap:12px;}
.raa-levels li{display:flex;align-items:flex-start;gap:10px;}
.raa-levels .ram-access{flex:none;min-width:66px;text-align:center;padding:4px 10px;font-size:12px;}
.raa-levels p{margin:1px 0 0;font-size:12.6px;line-height:1.5;color:var(--text-600);}

/* ---------- Access Audit cards ---------- */
.raa-audit{
  --raa-tone:var(--blue);
  position:relative;padding:14px 16px 14px 18px;margin-bottom:10px;border-radius:14px;
  border:1px solid var(--border);background:var(--card);
  transition:border-color .15s ease, box-shadow .15s ease;
}
.raa-audit::before{content:"";position:absolute;left:0;top:12px;bottom:12px;width:4px;border-radius:0 4px 4px 0;background:var(--raa-tone);}
.raa-kind-first{--raa-tone:var(--green);}
.raa-kind-widened{--raa-tone:var(--green);}
.raa-kind-narrowed{--raa-tone:var(--amber);}
@media (hover:hover) and (pointer:fine){
  .raa-audit:hover{border-color:color-mix(in srgb, var(--gold-500) 45%, var(--border));box-shadow:var(--shadow-md);}
}
.raa-audit-top{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;}
.raa-audit-people{display:flex;align-items:center;gap:8px;min-width:0;flex:1 1 280px;}
.raa-audit-people .ram-avatar{width:36px;height:36px;font-size:14px;box-shadow:0 0 0 2px var(--card);}
.raa-audit-link{display:inline-flex;color:var(--text-400);}
.raa-audit-text{margin-left:4px;min-width:0;font-size:13.8px;color:var(--text-600);line-height:1.45;}
.raa-audit-text strong{color:var(--text-900);font-weight:700;}
.raa-audit-text small{display:block;font-size:12px;color:var(--text-400);font-variant-numeric:tabular-nums;}
.raa-kind-pill{
  display:inline-flex;align-items:center;gap:6px;padding:4px 11px;border-radius:999px;
  font-size:11.6px;font-weight:700;letter-spacing:.02em;white-space:nowrap;
  color:var(--raa-tone);background:color-mix(in srgb, var(--raa-tone) 14%, transparent);
  border:1px solid color-mix(in srgb, var(--raa-tone) 30%, transparent);
}
.raa-shift{
  display:flex;align-items:center;gap:14px;flex-wrap:wrap;
  margin-top:12px;padding:11px 14px;border-radius:11px;background:var(--subtle-bg);
}
.raa-shift-side{display:flex;flex-direction:column;gap:5px;}
.raa-shift-label{font-size:10.6px;font-weight:700;letter-spacing:.07em;text-transform:uppercase;color:var(--text-400);}
.raa-shift-arrow{
  width:32px;height:32px;margin-top:17px;border-radius:50%;
  display:inline-flex;align-items:center;justify-content:center;
  background:var(--card);border:1px solid var(--border);color:var(--raa-tone);
}
.raa-shift-note{
  flex:1 1 240px;display:flex;align-items:flex-start;gap:7px;margin:0 0 0 auto;
  font-size:12.6px;line-height:1.5;color:var(--text-600);
}
.raa-shift-note svg{flex:none;width:15px;height:15px;margin-top:2px;color:var(--raa-tone);}

/* ---------- Dark mode ----------
   The gold kicker and intro wash are the only fixed tones above. */
:root[data-theme="dark"] .raa-day span{color:var(--gold-400);}
:root[data-theme="dark"] .raa-intro{
  background:radial-gradient(50% 140% at 100% 0%, rgba(217,172,82,.14) 0%, transparent 70%), var(--card);
}

/* ---------- Narrower screens ---------- */
@media(max-width:1180px){
  .raa-layout{grid-template-columns:minmax(0,1fr);}
  .raa-side{position:static;display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));}
}
@media(max-width:720px){
  .raa-intro-meta{align-items:flex-start;}
  .raa-row{flex-wrap:wrap;}
  .raa-row-main{flex-basis:calc(100% - 54px);}
  .raa-row-title{white-space:normal;}
  .raa-row-time{min-width:0;text-align:left;margin-left:54px;}
  .raa-row-view{margin-left:auto;}
  .raa-tools,.raa-tools .ram-search,.raa-select{flex:1 1 100%;max-width:none;}
  .raa-shift-note{margin-left:0;}
}

/* ---------- Reports - Family Net Worth: KPI card artwork ----------
   Illustration on the right of the Total Assets card (coins and a rising
   arrow), the Total Liabilities card (balance scale) and the Family Net Worth
   card (phoenix on a rising arrow), all in reports.js
   netWorthReport; the class names say "assets" for history, not scope. The PNG's background was made transparent so
   it sits on the card surface in both themes; the left edge is faded with a
   mask so the label and amount on the left always stay readable, even when
   the card narrows. Scoped to .rpt-assets-card so the shared .stat-card used
   on other pages is untouched. */
.rpt-assets-card{min-height:120px;}
.rpt-assets-art{
  position:absolute;right:14px;top:8px;bottom:8px;
  height:calc(100% - 16px);width:auto;max-width:46%;object-fit:contain;object-position:right center;
  pointer-events:none;user-select:none;z-index:0;
  -webkit-mask-image:linear-gradient(90deg, transparent 0%, #000 28%);
          mask-image:linear-gradient(90deg, transparent 0%, #000 28%);
  transition:transform .45s cubic-bezier(0.16,1,0.3,1);
}
.rpt-assets-card > :not(.rpt-assets-art){position:relative;z-index:1;}
@media (hover:hover) and (pointer:fine){
  .rpt-assets-card:hover .rpt-assets-art{transform:scale(1.05);}
}
:root[data-theme="dark"] .rpt-assets-art{opacity:.8;}
@media(max-width:520px){ .rpt-assets-art{max-width:42%;opacity:.85;} }
/* The balance scale is wider than the coins, so its left pan sits where the
   standard fade starts - a shorter fade keeps the pan visible. */
.rpt-assets-art.rpt-art-wide{
  -webkit-mask-image:linear-gradient(90deg, transparent 0%, #000 10%);
          mask-image:linear-gradient(90deg, transparent 0%, #000 10%);
}

/* ---------- Bulk Data Export / Bulk Data Import hero icons - float + shine + shimmer ---------- */
/* Two small, symmetric treatments on each page's own gold hero tile:
   - a gentle vertical float (bde-hero-icon starts high and drifts down,
     ie-hero-tile the mirror - starts low and drifts up, as asked)
   - a shine sweep (a soft diagonal highlight crossing the tile, mostly
     idle so it reads as an occasional glint rather than a constant
     effect) via a new ::after, since neither tile already uses one
   - a shimmer glow (the tile's own drop-shadow gaining a soft gold halo
     at the peak of the same cycle) layered onto the existing box-shadow
   Both tiles need position:relative added (neither had it) so the ::after
   sweep has something to anchor to; overflow:hidden keeps the sweep from
   spilling past the tile's own rounded corners. */
.bde-hero-icon{position:relative;overflow:hidden;}
.ie-hero-tile{position:relative;overflow:hidden;}

@keyframes bde-hero-icon-float{
  0%,100%{transform:translateY(-5px);}
  50%{transform:translateY(5px);}
}
@keyframes bde-hero-icon-glow{
  0%,100%{box-shadow:0 8px 20px rgba(180,140,40,.35);}
  50%{box-shadow:0 8px 20px rgba(180,140,40,.35), 0 0 18px 5px rgba(255,215,120,.55);}
}
@keyframes ie-hero-tile-float{
  0%,100%{transform:translateY(5px);}
  50%{transform:translateY(-5px);}
}
@keyframes ie-hero-tile-glow{
  0%,100%{box-shadow:0 8px 18px -8px rgba(180,132,40,.65);}
  50%{box-shadow:0 8px 18px -8px rgba(180,132,40,.65), 0 0 18px 5px rgba(255,215,120,.55);}
}
@keyframes hero-tile-shine-sweep{
  0%,80%{left:-60%;}
  95%,100%{left:160%;}
}
.bde-hero-icon::after,
.ie-hero-tile::after{
  content:'';
  position:absolute;top:0;left:-60%;
  width:35%;height:100%;
  background:linear-gradient(100deg, transparent, rgba(255,255,255,.55) 45%, rgba(255,255,255,.9) 50%, rgba(255,255,255,.55) 55%, transparent);
  transform:skewX(-18deg);
  pointer-events:none;
  animation:hero-tile-shine-sweep 4.5s ease-in-out infinite;
}
.bde-hero-icon{animation:bde-hero-icon-float 3.2s ease-in-out infinite, bde-hero-icon-glow 3.2s ease-in-out infinite;}
.ie-hero-tile{animation:ie-hero-tile-float 3.2s ease-in-out infinite, ie-hero-tile-glow 3.2s ease-in-out infinite;}

@media(prefers-reduced-motion: reduce){
  .ie-hero-tile{animation:none;}
  .bde-hero-icon::after,
  .ie-hero-tile::after{animation:none;display:none;}
}

/* ---------- Financial Calculator: chart hover pulses ----------
   The Wealth Dashboard's chart vocabulary, brought to every chart on this
   page: hovering a chart's card sends the same expanding gold ring off a
   donut (donut-ripple), the same pulse off a line chart's latest point
   (line-chart-pulse), plus a light glow. Bars have no round edge to ripple
   from, so each bar sends out a halo of the same colour and timing instead,
   staggered left to right so the row reads as one wave. Scoped to .fc-page;
   hover-only, like the dashboard, and off under reduced motion. */
.fc-page .bar-chart-bar::before{
  content:'';position:absolute;inset:0;border-radius:inherit;pointer-events:none;
  box-shadow:0 0 0 0 color-mix(in srgb, var(--gold-500) 55%, transparent);
}
@media (hover:hover) and (pointer:fine){
  .fc-page .card:hover .donut-chart::after{animation:donut-ripple 1.4s ease-out infinite;}
  .fc-page .card:hover .donut-chart{
    filter:drop-shadow(0 0 5px color-mix(in srgb, var(--gold-500) 45%, transparent)) saturate(1.08);
  }
  .fc-page .card:hover .line-chart-dot-pulse{animation:line-chart-pulse 1.4s ease-out infinite;}
  .fc-page .card:hover .line-chart-svg{filter:drop-shadow(0 0 3px color-mix(in srgb, var(--gold-500) 65%, transparent));}
  .fc-page .card:hover .bar-chart-bar::before{animation:fc-bar-ripple 1.4s ease-out infinite;}
  .fc-page .card:hover .bar-chart-col:nth-child(2) .bar-chart-bar::before{animation-delay:.12s;}
  .fc-page .card:hover .bar-chart-col:nth-child(3) .bar-chart-bar::before{animation-delay:.24s;}
  .fc-page .card:hover .bar-chart-col:nth-child(4) .bar-chart-bar::before{animation-delay:.36s;}
  .fc-page .card:hover .bar-chart-col:nth-child(n+5) .bar-chart-bar::before{animation-delay:.48s;}
}
@keyframes fc-bar-ripple{
  0%{box-shadow:0 0 0 0 color-mix(in srgb, var(--gold-500) 55%, transparent);}
  100%{box-shadow:0 0 0 11px color-mix(in srgb, var(--gold-500) 0%, transparent);}
}
@media (prefers-reduced-motion: reduce){
  .fc-page .card:hover .donut-chart::after,
  .fc-page .card:hover .line-chart-dot-pulse,
  .fc-page .card:hover .bar-chart-bar::before{animation:none;}
}
/* ---------- Family Event: Past Events button + popup ----------
   Sits left of "+ Add Event" as its quieter partner: outlined navy rather
   than gold, with the count of past custom events. The popup reuses the
   tab's own event cards and year groups (.fe-group/.fe-list/.fe-card), so it
   needs the same palette tokens .card.fe-card-wrap defines. */
.fe-cta-group{display:flex;align-items:center;gap:10px;flex-shrink:0;flex-wrap:wrap;}
.btn.fe-cta-past{
  display:inline-flex;align-items:center;gap:8px;
  background:var(--surface, #fff);
  color:#1b3358;
  border:1px solid color-mix(in srgb, #1b3358 28%, transparent);
  box-shadow:0 2px 4px rgba(15,31,51,.06);
}
.btn.fe-cta-past:hover{
  border-color:color-mix(in srgb, #1b3358 55%, transparent);
  box-shadow:0 4px 8px rgba(15,31,51,.08), 0 12px 24px -14px rgba(27,51,88,.55);
}
.fe-cta-count{
  min-width:20px;height:20px;padding:0 6px;border-radius:10px;
  display:inline-flex;align-items:center;justify-content:center;
  font-size:11.5px;font-weight:700;
  background:color-mix(in srgb, #1b3358 10%, transparent);color:#1b3358;
}
.fe-past{
  --fe-navy:#1b3358;
  --fe-gold:#b8912f;
  --fe-lav:#7d70b5;
  --fe-coral:#c9705f;
  --fe-teal:#2f8f89;
  --fe-cream:#fdf8ec;
  --fe-line:color-mix(in srgb, var(--border) 88%, transparent);
  --fe-line-soft:color-mix(in srgb, var(--border) 46%, transparent);
  display:flex;flex-direction:column;gap:18px;
}
.fe-past-sub{margin:0;font-size:12.8px;color:var(--text-400);}
@media(max-width:640px){
  .fe-cta-group{width:100%;}
  .fe-cta-group .btn.fe-cta{flex:1 1 0;width:auto;}
}
/* ---------- Family Event: custom event effects by Event Type ----------
   Same always-on-but-gentle rhythm as the birthday balloons, quickening on
   hover. "Other" events: shooting stars streak across the card behind its
   content. Wedding Anniversary: the icon slot is a 🎉 that pops, then bursts
   into confetti. Both are pointer-events:none and switch off entirely under
   reduced motion. */

/* -- Shooting stars (fe-card-other) -- */
.fe-card-other > .fe-date,
.fe-card-other > .fe-icon,
.fe-card-other > .fe-main,
.fe-card-other > .fe-side{position:relative;z-index:1;}
.fe-shoot{
  position:absolute;inset:0;z-index:0;
  overflow:hidden;border-radius:inherit;pointer-events:none;
}
.fe-shoot-star{
  position:absolute;left:-130px;top:-34px;
  width:118px;height:2.5px;border-radius:2px;
  background:linear-gradient(90deg,
    color-mix(in srgb, var(--fe-gold, #b8912f) 0%, transparent) 0%,
    color-mix(in srgb, var(--fe-gold, #b8912f) 60%, transparent) 55%,
    color-mix(in srgb, var(--fe-gold, #b8912f) 85%, #fff) 100%);
  opacity:0;
  transform-origin:right center;
  animation:fe-shoot 5.6s cubic-bezier(.35,.1,.6,1) infinite;
}
.fe-shoot-star::after{
  content:'';position:absolute;right:-3px;top:50%;
  width:6px;height:6px;margin-top:-3px;border-radius:50%;
  background:#fff3c4;
  box-shadow:0 0 6px 2px color-mix(in srgb, var(--fe-gold, #b8912f) 85%, transparent),
             0 0 14px 4px color-mix(in srgb, var(--fe-gold, #b8912f) 35%, transparent);
}
.fe-shoot-star.s1{top:-34px;left:-130px;animation-delay:0s;}
.fe-shoot-star.s2{top:-58px;left:18%;animation-delay:1.9s;width:92px;}
.fe-shoot-star.s3{top:-26px;left:44%;animation-delay:3.6s;width:104px;}
@keyframes fe-shoot{
  0%  {transform:translate(0,0) rotate(14deg);opacity:0;}
  3%  {opacity:1;}
  18% {transform:translate(760px,190px) rotate(14deg);opacity:0;}
  100%{transform:translate(760px,190px) rotate(14deg);opacity:0;}
}
@media (hover:hover) and (pointer:fine){
  .fe-card-other:hover .fe-shoot-star{animation-duration:2.4s;}
}

/* -- 🎉 pop + confetti burst (fe-card-anniv) -- */
.fe-card-anniv{--fe-accent:var(--fe-coral, #c9705f);}
.fe-icon.fe-icon-party{position:relative;overflow:visible;}
.fe-party-pop{
  display:block;font-size:18px;line-height:1;
  transform-origin:50% 60%;
  animation:fe-party-pop 3.4s cubic-bezier(.3,1.4,.5,1) infinite;
}
@keyframes fe-party-pop{
  0%  {transform:scale(.35) rotate(-24deg);opacity:0;}
  7%  {transform:scale(1.5) rotate(14deg);opacity:1;}
  13% {transform:scale(.9) rotate(-8deg);}
  19% {transform:scale(1.1) rotate(3deg);}
  26% {transform:scale(1) rotate(0);}
  84% {transform:scale(1) rotate(0);opacity:1;}
  90% {transform:scale(.96) rotate(-10deg);}
  95% {transform:scale(.96) rotate(10deg);}
  100%{transform:scale(.35) rotate(-24deg);opacity:0;}
}
.fe-confetti{
  position:absolute;left:50%;top:50%;z-index:2;
  width:5px;height:8px;margin:-4px 0 0 -2.5px;border-radius:1.5px;
  opacity:0;pointer-events:none;
  animation:fe-confetti 3.4s ease-out infinite;
}
@keyframes fe-confetti{
  0%,5% {transform:translate(0,0) rotate(0) scale(.3);opacity:0;}
  8%    {opacity:1;}
  36%   {transform:translate(var(--dx), var(--dy)) rotate(var(--rot)) scale(1);opacity:1;}
  56%   {transform:translate(var(--dx), calc(var(--dy) + 12px)) rotate(calc(var(--rot) * 1.6)) scale(.9);opacity:0;}
  100%  {transform:translate(var(--dx), calc(var(--dy) + 12px)) scale(.9);opacity:0;}
}
.fe-confetti.c1 {--dx:0px;   --dy:-34px;--rot:160deg; background:#d9a93a;}
.fe-confetti.c2 {--dx:22px;  --dy:-26px;--rot:-120deg;background:#c9705f;}
.fe-confetti.c3 {--dx:33px;  --dy:-6px; --rot:200deg; background:#7d70b5;}
.fe-confetti.c4 {--dx:27px;  --dy:18px; --rot:-90deg; background:#2f8f89;width:6px;height:6px;border-radius:50%;}
.fe-confetti.c5 {--dx:9px;   --dy:30px; --rot:140deg; background:#e0679a;}
.fe-confetti.c6 {--dx:-13px; --dy:29px; --rot:-160deg;background:#3a78c9;}
.fe-confetti.c7 {--dx:-29px; --dy:15px; --rot:110deg; background:#d9a93a;width:6px;height:6px;border-radius:50%;}
.fe-confetti.c8 {--dx:-34px; --dy:-8px; --rot:-200deg;background:#c9705f;}
.fe-confetti.c9 {--dx:-24px; --dy:-27px;--rot:90deg;  background:#2f8f89;}
.fe-confetti.c10{--dx:-6px;  --dy:-38px;--rot:-140deg;background:#7d70b5;width:6px;height:6px;border-radius:50%;}
@media (hover:hover) and (pointer:fine){
  .fe-card-anniv:hover .fe-party-pop,
  .fe-card-anniv:hover .fe-confetti{animation-duration:1.7s;}
  /* The card's own hover tilt would fight the pop; let the emoji do it. */
  .fe-card-anniv:hover .fe-icon.fe-icon-party{transform:none;}
}

@media (prefers-reduced-motion: reduce){
  .fe-shoot{display:none;}
  .fe-confetti{display:none;}
  .fe-party-pop{animation:none;opacity:1;transform:none;}
}
/* ---------- Floating chat launchers: tuck, reveal, drag (floatingDock.js) ----------
   Position (left/top) and the tuck offset (the independent `translate`
   property, so the widgets' own entrance `transform` is untouched) are set
   inline by floatingDock.js. The panel flips to open toward the page. */
.ai-widget.fdock,
.claude-widget.fdock{
  transition:opacity .5s cubic-bezier(0.22,1,0.36,1), transform .5s cubic-bezier(0.22,1,0.36,1),
             translate .38s cubic-bezier(0.22,1,0.36,1), top .3s cubic-bezier(0.22,1,0.36,1), left .3s cubic-bezier(0.22,1,0.36,1);
  touch-action:none;
}
.fdock .ai-toggle,
.fdock .claude-toggle{cursor:grab;touch-action:none;}
/* Mid-drag: follow the pointer with no easing, lift it, pause the pulse. */
.ai-widget.fdock.fdock-dragging,
.claude-widget.fdock.fdock-dragging{transition:opacity .5s ease, transform .5s ease;z-index:160;}
.fdock.fdock-dragging .ai-toggle,
.fdock.fdock-dragging .claude-toggle{
  cursor:grabbing;animation:none;transform:scale(1.08);
  box-shadow:0 14px 30px rgba(15,31,51,.28), 0 6px 18px rgba(200,153,46,.45);
}
/* Tucked: most of the badge still peeks from the edge (FloatingDock.PEEK) -
   quieter and with no idle pulse, but at full opacity and keeping a soft
   shadow, so the two assistants stay legible as "available" rather than
   fading into the page edge. */
.fdock.fdock-hidden .ai-toggle,
.fdock.fdock-hidden .claude-toggle{
  animation:none;opacity:1;
  box-shadow:0 4px 14px rgba(15,31,51,.22);
}
/* Tucked, the badge hangs off the edge, so its centred icon would sit in the
   hidden part. Padding on the off-screen side slides the glyph into the part
   that still shows - the 🤖 / ✨ stays readable, which is the whole point of
   leaving the launchers peeking out. --fdock-tuck-pad is set inline by
   FloatingDock.applyHidden() from the same peek it tucks to, so the two stay
   in step. Padding animates, so it travels with the tuck rather than snapping. */
.fdock.fdock-hidden .ai-toggle,
.fdock.fdock-hidden .claude-toggle{padding-right:var(--fdock-tuck-pad, 26px);padding-left:6px;}
.fdock.fdock-hidden.fdock-left .ai-toggle,
.fdock.fdock-hidden.fdock-left .claude-toggle{padding-left:var(--fdock-tuck-pad, 26px);padding-right:6px;}
.fdock .ai-toggle,
.fdock .claude-toggle{transition:transform .15s, padding .38s cubic-bezier(0.22,1,0.36,1), opacity .3s ease;}
.fdock.fdock-hidden:hover .ai-toggle,
.fdock.fdock-hidden:hover .claude-toggle{opacity:1;}
/* Panels open toward the page. */
.fdock.fdock-left .ai-panel,
.fdock.fdock-left .claude-panel{right:auto;left:0;}
.fdock.fdock-top .ai-panel,
.fdock.fdock-top .claude-panel{bottom:auto;top:70px;}
@media (prefers-reduced-motion: reduce){
  .ai-widget.fdock, .claude-widget.fdock{transition:none;}
}
/* ---------- SIP Calculator: blinking "Estimated Returns" wedge ----------
   fcBlinkDonutSegment() (financialCalculator.js) adds a layer that covers only
   that wedge; it fades a light wash in and out so the wedge blinks between
   its own green and a bright green. The legend dot pulses in step. */
.donut-chart .fc-seg-blink{
  position:absolute;inset:0;border-radius:50%;pointer-events:none;
  animation:fc-seg-blink var(--blink-dur, 1.3s) ease-in-out infinite;
}
@keyframes fc-seg-blink{
  0%,100%{opacity:0;}
  50%    {opacity:1;}
}
.fc-seg-blink-legend .donut-dot{animation:fc-seg-dot-blink var(--blink-dur, 1.3s) ease-in-out infinite;}
@keyframes fc-seg-dot-blink{
  0%,100%{box-shadow:0 0 0 0 rgba(31,138,95,0);filter:none;}
  50%    {box-shadow:0 0 0 4px rgba(31,138,95,.28);filter:brightness(1.45);}
}
@media (prefers-reduced-motion: reduce){
  .donut-chart .fc-seg-blink{animation:none;opacity:0;}
  .fc-seg-blink-legend .donut-dot{animation:none;}
}
/* ---------- SWP Calculator: Balance vs Withdrawals chart (fcSwpChartHtml) ----------
   Two series on one rupee axis: balance (blue area + 2px line) and total
   withdrawn (deep gold 2px line). Hairline solid grid, muted ticks, text in
   text tokens (never series colour), status red reserved for the run-out
   marker. Own steps for dark mode. */
.swpc{
  --swpc-balance:#2a5fb0;
  --swpc-withdrawn:#9a6f12;
  --swpc-runout:#c0392b;
  --swpc-grid:color-mix(in srgb, var(--border) 70%, transparent);
  position:relative;
}
:root[data-theme="dark"] .swpc{
  --swpc-balance:#7aa7ee;
  --swpc-withdrawn:#e0b454;
  --swpc-runout:#ff7a6b;
}
.swpc-legend{display:flex;flex-wrap:wrap;gap:6px 18px;margin:2px 0 10px;font-size:12.5px;color:var(--text-600);}
.swpc-key{display:inline-flex;align-items:center;gap:7px;}
.swpc-swatch{display:inline-block;width:14px;height:3px;border-radius:2px;flex-shrink:0;}
.swpc-swatch-balance{background:var(--swpc-balance);box-shadow:0 3px 0 -1px color-mix(in srgb, var(--swpc-balance) 22%, transparent);}
.swpc-swatch-withdrawn{background:var(--swpc-withdrawn);}
.swpc-plot{position:relative;outline:none;border-radius:8px;touch-action:pan-y;}
.swpc-plot:focus-visible{box-shadow:0 0 0 2px color-mix(in srgb, var(--swpc-balance) 55%, transparent);}
.swpc-svg{display:block;width:100%;height:auto;overflow:visible;}
.swpc-grid{stroke:var(--swpc-grid);stroke-width:1;}
.swpc-axis{stroke:color-mix(in srgb, var(--text-400) 45%, transparent);stroke-width:1;}
.swpc-tick{fill:var(--text-400);font-size:11px;font-variant-numeric:tabular-nums;}
.swpc-balance-area{fill:color-mix(in srgb, var(--swpc-balance) 13%, transparent);}
.swpc-balance-line{fill:none;stroke:var(--swpc-balance);stroke-width:2;stroke-linejoin:round;stroke-linecap:round;}
.swpc-withdrawn-line{fill:none;stroke:var(--swpc-withdrawn);stroke-width:2;stroke-linejoin:round;stroke-linecap:round;}
.swpc-end{stroke:var(--card);stroke-width:2;}
.swpc-end-balance,.swpc-cross-balance{fill:var(--swpc-balance);}
.swpc-end-withdrawn,.swpc-cross-withdrawn{fill:var(--swpc-withdrawn);}
.swpc-endlabel-name{fill:var(--text-600);font-size:11px;}
.swpc-endlabel-val{fill:var(--text-900);font-size:12.5px;font-weight:700;}
.swpc-runout{stroke:var(--swpc-runout);stroke-width:1.5;}
.swpc-runout-label{fill:var(--swpc-runout);font-size:11.5px;font-weight:700;}
.swpc-cross-line{stroke:color-mix(in srgb, var(--text-400) 60%, transparent);stroke-width:1;}
.swpc-cross-dot{stroke:var(--card);stroke-width:2;}
.swpc-tip{
  position:absolute;z-index:5;pointer-events:none;min-width:210px;
  padding:9px 11px;border-radius:10px;
  background:var(--card);border:1px solid var(--border);
  box-shadow:0 10px 26px -12px rgba(15,31,51,.35);
  font-size:12.3px;color:var(--text-600);
}
.swpc-tip-title{font-weight:700;color:var(--text-900);margin-bottom:5px;}
.swpc-tip-row,.swpc-tip-sub{display:flex;align-items:center;gap:7px;padding:2px 0;}
.swpc-tip-row b,.swpc-tip-sub b{margin-left:auto;padding-left:14px;color:var(--text-900);font-variant-numeric:tabular-nums;}
.swpc-tip-sub{padding-left:21px;font-size:11.8px;}
.swpc-tip-sub:first-of-type{margin-top:4px;border-top:1px solid var(--border);padding-top:6px;}
.swpc-table{margin-top:12px;font-size:12.5px;}
.swpc-table summary{cursor:pointer;color:var(--text-600);font-weight:600;width:max-content;}
.swpc-table .table-wrap{margin-top:8px;overflow-x:auto;}
.swpc-table table{width:100%;border-collapse:collapse;}
.swpc-table th,.swpc-table td{padding:6px 8px;border-bottom:1px solid var(--border);text-align:left;white-space:nowrap;}
.swpc-table th{font-size:11.5px;color:var(--text-400);font-weight:600;}
.swpc-table .num{text-align:right;font-variant-numeric:tabular-nums;}
/* ---------- EMI Calculator: repayment split + yearly principal/interest ----------
   Shares the SWP chart's grid/tick/tooltip styles (.swpc-*). Principal is
   blue, interest a warm orange (identity colours, not status red); a 2px
   surface gap separates the stacked segments; hovering a year dims the rest. */
.emic, .emic-split{
  --emic-principal:#2a5fb0;
  --emic-interest:#c4661f;
}
:root[data-theme="dark"] .emic, :root[data-theme="dark"] .emic-split{
  --emic-principal:#7aa7ee;
  --emic-interest:#f0975a;
}
.emic-swatch{display:inline-block;width:10px;height:10px;border-radius:3px;flex-shrink:0;vertical-align:-1px;margin-right:6px;}
.emic-swatch-principal{background:var(--emic-principal);}
.emic-swatch-interest{background:var(--emic-interest);}
.emic-split{margin:4px 0 18px;}
.emic-split-head{font-size:12.5px;color:var(--text-600);margin-bottom:8px;}
.emic-split-head b{color:var(--text-900);}
.emic-split-bar{display:flex;gap:2px;height:14px;border-radius:4px;overflow:hidden;}
.emic-seg{display:block;min-width:4px;}
.emic-seg-principal{background:var(--emic-principal);}
.emic-seg-interest{background:var(--emic-interest);}
.emic-split-keys{display:flex;flex-wrap:wrap;justify-content:space-between;gap:6px 18px;margin-top:8px;font-size:12.5px;color:var(--text-600);}
.emic-split-keys b{color:var(--text-900);margin-left:4px;font-variant-numeric:tabular-nums;}
.emic-split-keys em{font-style:normal;color:var(--text-400);margin-left:4px;}
.emic-title{font-size:13px;font-weight:600;color:var(--text-900);margin:0 0 6px;}
.emic .swpc-grid{stroke:color-mix(in srgb, var(--border) 70%, transparent);}
.emic-hit{fill:transparent;}
.emic-bar-principal{fill:var(--emic-principal);}
.emic-bar-interest{fill:var(--emic-interest);}
.emic .emic-col rect.emic-bar-principal, .emic .emic-col .emic-bar-interest{transition:opacity .15s ease-out;}
.emic.has-active .emic-col:not(.is-active) .emic-bar-principal,
.emic.has-active .emic-col:not(.is-active) .emic-bar-interest{opacity:.4;}
.emic .emic-col.is-active .emic-hit{fill:color-mix(in srgb, var(--text-400) 8%, transparent);}
/* ---------- Travel Kit: row actions ----------
   On the cards themselves the four actions stay icon-only, so a row reads as
   the document rather than as a toolbar. The words are kept in the markup and
   revealed only inside the View All popup, where there is room for them (and
   every button carries a title tooltip either way). */
.tk-doc-actions .btn{padding:3px 6px;white-space:nowrap;}
.tk-act-label{display:none;font-size:11.5px;font-weight:600;}
.tk-list-modal .tk-doc-actions .btn{padding:3px 8px;}
.tk-list-modal .tk-act-label{display:inline;}
/* ------------------------------------------------------------------
   Dashboard KPI cards: the star shower flies BEHIND the wording.
   The shower now sweeps in at the left edge (vertically centred) and
   leaves at the bottom-right corner (Dashboard.onStatCardClick sets the
   offset-path inline), so its curve crosses straight over the label and
   the amount. Lifting the card's own text to z-index 2 and dropping the
   stars to 1 lets the comets pass underneath the figures instead of
   over them - the distraction the flight path used to cause. Scoped to
   .dashboard-page so the Balance Sheet's own KPI stars are untouched.
   ------------------------------------------------------------------ */
.dashboard-page .stat-card > .stat-icon,
.dashboard-page .stat-card > .stat-label,
.dashboard-page .stat-card > .stat-value,
.dashboard-page .stat-card > .stat-foot{position:relative;z-index:2;}
.dashboard-page .stat-card > .stat-card-star,
.dashboard-page .stat-card > .stat-card-star-glow{z-index:1;}

/* Family Balance Sheet KPI cards: star shower flies BEHIND the wording,
   matching the Wealth Dashboard's own KPI cards. The particle spans sit at
   z-index 2 (lifted there to clear this card's ambient bloom / hover sheen
   at 0), so the text is lifted one step further to 3 rather than dropping
   the particles back down into the bloom. Same intent as the
   .dashboard-page .stat-card rule further down the file. */
.bs-kpi-card .bs-kpi-icon,
.bs-kpi-card .bs-kpi-label,
.bs-kpi-card .bs-kpi-value,
.bs-kpi-card .bs-kpi-foot{position:relative;z-index:3;}

/* ---------- Profile hero video: full-card backdrop, blended into the hero ----------
   Was a 190x150 tile floated at the right end of the hero's flex row, which
   read as a pasted-in thumbnail. On request the clip now fills the whole
   .pf-hero-card and blends into its cream/gold surface instead:

   - Taken out of the flex row (absolutely positioned children are not flex
     items, so the old flex:0 0 190px no longer applies) and stretched to the
     card with object-fit:cover.
   - z-index 0 puts it under the card's own content, all of which already
     carries position:relative;z-index:1 (.pf-hero-id, .pf-comp,
     .pf-hero-aside) - nothing there needed changing.
   - Blending is three things working together: multiply against the cream
     gradient so the footage takes the hero's own colour rather than sitting
     on top of it, a brightness/saturation lift so the clip's darker gold does
     not muddy that cream, and a mask that fades it out towards the left,
     where the avatar, name and role sit. The fade is what keeps the text
     legible over a moving picture.
   - .pf-hero keeps isolation:isolate (set where it is defined), so the
     multiply blends against the hero only and never punches through to the
     page behind it.
   ---------------------------------------------------------------------- */
.pf-hero-art{
  position:absolute;
  inset:0 0 0 auto;   /* right-hand side of the card only, on request */
  width:52%;
  height:100%;
  border-radius:0;
  z-index:0;
  pointer-events:none;
  object-fit:cover;
  object-position:center 45%;
  filter:brightness(1.22) saturate(.7) contrast(.94);
  mix-blend-mode:multiply;
  opacity:.8;
  /* The fade runs across the panel's own width, so its inner edge dissolves
     into the cream instead of stopping on a hard vertical line. */
  mask-image:linear-gradient(90deg, transparent 0%, rgba(0,0,0,.34) 24%, rgba(0,0,0,.8) 54%, #000 80%);
  -webkit-mask-image:linear-gradient(90deg, transparent 0%, rgba(0,0,0,.34) 24%, rgba(0,0,0,.8) 54%, #000 80%);
}
/* The clip no longer occupies layout space, so the hero would collapse to the
   height of the avatar block alone - this holds the card at the size it had
   while the 150px tile was in the row. */
.pf-hero{min-height:214px;}
/* It cost nothing on a narrow screen now that it is a backdrop rather than a
   tile competing for row space, so the old display:none is lifted. */
@media(max-width:900px){
  .pf-hero-art{display:block;}
  .pf-hero{min-height:0;}
}

/* ---------- Insurance "By Type": bigger circle, slimmer ring ----------
   The shared donut is 105px here with .donut-center's default inset:13.3%,
   which leaves a 77px hole - narrower than the "₹20,00,000" total it has to
   hold, so the amount spilled out over the purple ring. Widened to 128px and
   the ring thinned to ~9px (inset 7%), giving a 110px hole: the figure now
   sits well inside it with room to spare, and a longer total (a crore-plus
   book) still fits. Scoped to .ins-type-card so the Wealth Dashboard's own
   Asset/Liability donuts keep their existing proportions. */
.ins-type-card .donut-chart{width:min(128px, 100%);min-width:96px;}
.ins-type-card .donut-center{inset:7%;}

/* ---------- Insurance hero photo: show the whole image, no crop ----------
   insurance-hero-v2.png is 2170x725 (~2.993:1) but the box was a fixed 340px
   tall at a ~1230px card, i.e. 3.618:1 - so `cover` was cutting 17% off the
   image's height (measured on the live page), taking the top of the icon arc
   with it. Giving the box the image's own aspect ratio instead of a fixed
   height means cover has nothing left to crop: the full picture shows, and
   without the side bars `contain` would have left in a box this wide.
   The height now follows the card's width (~411px at 1230px).

   Below 900px the derived height gets short enough to crowd the overlay
   heading, so a floor is kept there and cover trims a little again - the
   phone layout is the one place a small crop is the lesser evil. */
.ins-title-photo{
  height:auto;
  aspect-ratio:2170 / 725;
  background-position:center center;
}
@media(max-width:900px){
  .ins-title-photo{height:auto;aspect-ratio:2170 / 725;min-height:190px;}
}

/* ---------- Insurance hero photo: slow zoom in / zoom out ----------
   A "Ken Burns" breathe on the hero image, on request. Animating the photo
   div's own transform (rather than its background-size) keeps it on the
   compositor, and .ins-title-card is already overflow:hidden with a 16px
   radius, so the scaled-up frame is clipped cleanly to the card's rounded
   corners with no layout shift - transforms do not affect layout, so the
   card stays exactly 411px tall throughout.

   .ins-hero-overlay (heading + subtitle) is a SIBLING of the photo div, not
   a child, so the wording stays pin-sharp and perfectly still while the
   picture behind it moves.

   One full cycle is 26s - slow enough to read as the scene breathing rather
   than as a moving element competing for attention. It rests at scale 1 at
   both ends of the cycle, which is where the whole uncropped image shows. */
@keyframes ins-hero-kenburns{
  0%{transform:scale(1);}
  50%{transform:scale(1.085);}
  100%{transform:scale(1);}
}
.ins-title-photo{
  transform-origin:center 48%;
  animation:ins-hero-kenburns 26s ease-in-out infinite;
  will-change:transform;
}
@media (prefers-reduced-motion: reduce){
  .ins-title-photo{animation:none;}
}

/* ---------- Insurance hero photo: trim the foreground grass, shorter card ----------
   The full 2170x725 frame gave a 411px-tall card at ~1230px wide, and the
   bottom ~30% of it is just foreground grass. Cropping that back shortens the
   card without touching what the picture is actually about - the sky, the arc
   of cover icons and the family all sit in the upper two-thirds.

   Done by narrowing the box ratio rather than setting a fixed pixel height, so
   the crop stays the same proportion at every width: 2170/565 keeps 78% of the
   image's height (~320px at a 1230px card, down from 411px). Anchoring the
   background to the TOP is what makes cover take the difference off the
   BOTTOM - the default centre anchor would have split the loss between the
   grass and the sky.

   The Ken Burns origin moves up to match (center 34%): with the foreground
   gone, the family and the shield arch are the new visual centre, and zooming
   about the old 48% would have pushed them off the bottom edge. */
.ins-title-photo{
  aspect-ratio:2170 / 565;
  background-position:center top;
  transform-origin:center 34%;
}
@media(max-width:900px){
  .ins-title-photo{aspect-ratio:2170 / 565;min-height:170px;}
}

/* ---------- Sysadmin Nest Users > All Accounts: email under the name ----------
   rolesAccess.js adminAccountsTableHtml() prints the email as a second, quieter
   line under each person's name instead of in its own column. .ra-acct-cell
   keeps a Family Head's expand arrow beside the name/email block, so the email
   lines up under the name rather than under the arrow. */
.roles-access-page .ra-acct-cell{display:flex;align-items:flex-start;gap:6px;}
.roles-access-page .ra-acct-cell .ra-expand-btn{flex:none;margin-top:-2px;}
.roles-access-page .ra-acct-email{margin-top:2px;font-size:12px;font-weight:400;color:var(--text-400);word-break:break-all;}

/* ============================================================
   Market prices - NSE/BSE stocks (js/marketPrice.js)
   Existing tokens only, so dark mode follows automatically. Direction is
   always spelled out with a +/- sign in the text; colour is reinforcement.
   ============================================================ */
.mp-green{color:var(--green);font-weight:700;}
.mp-red{color:var(--red);font-weight:700;}
.mp-muted{color:var(--text-400);}
.mp-symbol{letter-spacing:.02em;}
.mp-line{font-weight:500;margin-top:2px;white-space:normal;}
.mp-summary{margin:0 0 16px;}
.mp-summary-head{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:10px;}
.mp-summary-title{font-weight:700;color:var(--text-900);font-size:15px;}
.mp-summary .stat-value{font-size:21px;}
.mp-card{padding:12px 14px;margin-top:6px;}
.mp-card-head{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-bottom:10px;}
.mp-card-title{font-weight:700;color:var(--text-900);}
.mp-cell{font-weight:600;color:var(--text-900);overflow-wrap:anywhere;}
.mp-note{margin-top:10px;padding:8px 10px;border-radius:8px;}
.mp-note-warn{background:var(--amber-bg);color:var(--amber);}
.mp-card-foot{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;margin-top:10px;}
.mp-form-status{font-weight:600;}
.mp-form-info{color:var(--text-400);}
.mp-form-ok{color:var(--green);}
.mp-form-warn{color:var(--amber);}
.mp-form-error{color:var(--red);}
.btn[data-mp-refresh][disabled],.btn[data-mp-portfolio][disabled]{opacity:.65;cursor:progress;}
@media(max-width:640px){
  .mp-summary-head{align-items:flex-start;}
  .mp-summary .stat-value{font-size:19px;}
}
.mp-nowrap{white-space:nowrap;}

/* ============================================================
   Copy & Paste from Excel (js/pasteImport.js) - import only.
   Uses the app's own tokens so dark mode follows automatically.
   ============================================================ */
.cpi-ico{vertical-align:-3px;margin-right:2px;flex:none;}
.btn.cpi-entry{display:inline-flex;align-items:center;gap:6px;}
.btn.cpi-entry .cpi-ico{margin-right:0;}
.cpi-page-card{text-align:center;padding:36px 20px;display:flex;flex-direction:column;align-items:center;gap:10px;}
.cpi-page-card .ie-dz-or{max-width:520px;}
.cpi-page-icon{color:var(--gold-600);}
.cpi-page-card .btn{margin-top:8px;}

.modal.cpi-modal{max-width:min(96vw,1240px);}
.cpi-modal .form-actions.cpi-actions{flex-wrap:wrap;gap:8px;justify-content:flex-end;position:sticky;bottom:0;background:var(--card);padding-top:12px;margin-top:12px;border-top:1px solid var(--border);}

.cpi-steps{list-style:none;display:flex;gap:6px;margin:0 0 14px;padding:0;flex-wrap:wrap;}
.cpi-steps li{display:flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600;color:var(--text-400);}
.cpi-steps li + li::before{content:"";width:18px;height:1px;background:var(--border);margin-right:2px;}
.cpi-step-n{width:20px;height:20px;border-radius:50%;background:var(--border);color:var(--text-600);display:inline-flex;align-items:center;justify-content:center;font-size:11px;}
.cpi-steps li.active{color:var(--text-900);}
.cpi-steps li.active .cpi-step-n{background:var(--gold-500);color:#fff;}
.cpi-steps li.done .cpi-step-n{background:var(--green);color:#fff;}

.cpi-instruction{font-size:15px;font-weight:700;color:var(--text-900);margin:14px 0 8px;}
.cpi-paste{width:100%;min-height:260px;max-height:55vh;resize:vertical;box-sizing:border-box;padding:14px;border:2px dashed var(--border);border-radius:12px;background:var(--bg);color:var(--text-900);font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre;overflow:auto;}
.cpi-paste:focus{outline:none;border-color:var(--gold-500);background:var(--card);}
.cpi-paste:disabled{opacity:.6;}

.cpi-alert{border-radius:10px;padding:10px 12px;font-size:13px;margin:10px 0;line-height:1.45;}
.cpi-alert-err{background:var(--red-bg);color:var(--red);}
.cpi-alert-warn{background:var(--amber-bg);color:var(--amber);}
.cpi-chip{border:1px solid currentColor;background:transparent;color:inherit;border-radius:20px;padding:3px 10px;font-size:12px;font-weight:600;cursor:pointer;margin:2px 0;}
.cpi-chip:hover{background:var(--card);}

.cpi-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(130px,1fr));gap:10px;margin:12px 0 6px;}
.cpi-stat{background:var(--bg);border:1px solid var(--border);border-radius:12px;padding:10px 14px;}
.cpi-stat-n{font-size:22px;font-weight:800;color:var(--text-900);line-height:1.2;}
.cpi-stat-l{font-size:12px;color:var(--text-600);font-weight:600;}
.cpi-stat-ok{background:var(--green-bg);border-color:transparent;}
.cpi-stat-ok .cpi-stat-n{color:var(--green);}
.cpi-stat-bad{background:var(--red-bg);border-color:transparent;}
.cpi-stat-bad .cpi-stat-n{color:var(--red);}
.cpi-stat-warn{background:var(--amber-bg);border-color:transparent;}
.cpi-stat-warn .cpi-stat-n{color:var(--amber);}
/* Portfolio totals strip on the review step (PasteImport.totalsHtml) - what the
   pasted sheet is actually worth, so the figures can be checked against the
   statement's own Grand Total before anything is written. Visually quieter than
   .cpi-stat above: those are a verdict on the import, these are just the data. */
.cpi-totals{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin:8px 0 4px;}
.cpi-tot{background:var(--bg-50,var(--bg));border:1px dashed var(--border);border-radius:12px;padding:9px 14px;}
.cpi-tot-n{font-size:18px;font-weight:800;color:var(--text-900);line-height:1.25;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.cpi-tot-l{font-size:11.5px;color:var(--text-600);font-weight:600;}
.cpi-tot-part{font-weight:500;color:var(--text-400);}
.cpi-tot-up .cpi-tot-n{color:var(--green);}
.cpi-tot-down .cpi-tot-n{color:var(--red);}
.cpi-tot-split{font-size:12.5px;color:var(--text-600);font-weight:600;margin:2px 0 6px;}
.cpi-summary-line{font-size:13.5px;font-weight:700;color:var(--text-900);margin:4px 0;}
.cpi-summary-extra{font-size:12.5px;color:var(--text-600);}

.cpi-map-box{border:1px solid var(--border);border-radius:12px;padding:8px 12px;margin:12px 0;background:var(--card);}
.cpi-map-box > summary{cursor:pointer;font-weight:700;font-size:13.5px;padding:4px 0;}
.cpi-hdr-toggle{display:flex;align-items:center;gap:8px;font-size:13px;margin:10px 0;cursor:pointer;}
.cpi-map-table{width:100%;border-collapse:collapse;font-size:13px;}
.cpi-map-table th{text-align:left;font-size:11.5px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-400);padding:6px 8px;}
.cpi-map-table td{padding:6px 8px;border-top:1px solid var(--border);vertical-align:middle;}
.cpi-map-table select{width:100%;max-width:320px;padding:7px 8px;border:1px solid var(--border);border-radius:8px;background:var(--card);color:var(--text-900);font-size:13px;}
.cpi-map-h{font-weight:700;}
.cpi-map-sample{font-size:11.5px;color:var(--text-400);max-width:220px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;}
.cpi-badge{border-radius:20px;padding:3px 10px;font-size:11.5px;font-weight:600;display:inline-block;background:var(--border);color:var(--text-600);white-space:nowrap;}
.cpi-badge-ok{background:var(--green-bg);color:var(--green);}
.cpi-badge-warn{background:var(--amber-bg);color:var(--amber);}

.cpi-grid-wrap{max-height:52vh;overflow:auto;border:1px solid var(--border);border-radius:12px;margin-top:12px;}
.cpi-grid{border-collapse:separate;border-spacing:0;font-size:13px;min-width:100%;}
.cpi-grid th{position:sticky;top:0;z-index:2;background:var(--bg);text-align:left;padding:8px 10px;font-size:11.5px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-600);white-space:nowrap;border-bottom:1px solid var(--border);}
.cpi-grid td{padding:4px 6px;border-bottom:1px solid var(--border);vertical-align:middle;background:var(--card);}
.cpi-grid td input,.cpi-grid td select{width:100%;min-width:110px;box-sizing:border-box;padding:6px 8px;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--text-900);font:inherit;}
.cpi-grid td input:hover,.cpi-grid td select:hover{border-color:var(--border);}
.cpi-grid td input:focus,.cpi-grid td select:focus{outline:none;border-color:var(--gold-500);background:var(--card);}
.cpi-c-n{width:38px;min-width:38px;text-align:right;color:var(--text-400);font-size:12px;position:sticky;left:0;z-index:1;}
.cpi-c-s{width:44px;min-width:44px;text-align:center;position:sticky;left:38px;z-index:1;}
.cpi-grid th.cpi-c-n{z-index:3;}
.cpi-grid th.cpi-c-s{z-index:3;left:38px;}
.cpi-c-x{width:44px;text-align:center;}
.cpi-st{display:inline-flex;align-items:center;justify-content:center;width:22px;height:22px;border-radius:50%;font-size:12px;font-weight:800;}
.cpi-st-ok{background:var(--green-bg);color:var(--green);}
.cpi-st-bad{background:var(--red-bg);color:var(--red);}
.cpi-st-warn{background:var(--amber-bg);color:var(--amber);}
.cpi-st-pending{background:var(--border);color:var(--text-600);}
.cpi-grid tr.cpi-row-bad td{background:var(--red-bg);}
.cpi-grid tr.cpi-row-warn td{background:var(--amber-bg);}
.cpi-grid td.cpi-cell-err input,.cpi-grid td.cpi-cell-err select{border-color:var(--red);background:var(--card);}
.cpi-msg-row td{padding:0 12px 8px 90px;border-bottom:1px solid var(--border);}
.cpi-grid tr.cpi-row-bad + .cpi-msg-row td{background:var(--red-bg);}
.cpi-grid tr.cpi-row-warn + .cpi-msg-row td{background:var(--amber-bg);}
.cpi-msgs{margin:4px 0 0;padding-left:18px;font-size:12.5px;}
.cpi-msg-err{color:var(--red);}
.cpi-msg-warn{color:var(--amber);}
.cpi-empty{padding:26px;text-align:center;color:var(--text-600);font-size:13.5px;}

.cpi-done{text-align:center;padding:18px 8px;}
.cpi-done-icon{width:56px;height:56px;border-radius:50%;background:var(--green-bg);color:var(--green);font-size:28px;font-weight:800;display:inline-flex;align-items:center;justify-content:center;margin-bottom:10px;}
.cpi-done-title{font-size:19px;font-weight:800;color:var(--text-900);margin-bottom:4px;}
.cpi-done .cpi-alert{text-align:left;}

@media(max-width:820px){
  .cpi-grid-wrap{max-height:46vh;}
  .cpi-c-n{display:none;}
  .cpi-c-s{left:0;}
  .cpi-grid th.cpi-c-s{left:0;}
  .cpi-msg-row td{padding-left:12px;}
}
@media(max-width:640px){
  .cpi-paste{min-height:200px;font-size:12.5px;}
  .cpi-stats{grid-template-columns:repeat(2,1fr);}
  .cpi-steps .cpi-step-t{display:none;}
  .cpi-steps li.active .cpi-step-t{display:inline;}
  /* on a phone the action bar scrolls with the content (a sticky 4-button stack would cover a quarter of the screen); Import Data leads */
  .cpi-modal .form-actions.cpi-actions{position:static;display:grid;grid-template-columns:repeat(2,minmax(0,1fr));justify-content:stretch;gap:8px;border-top:none;padding-top:4px;}
  .cpi-modal .form-actions.cpi-actions .btn{width:100%;min-width:0;padding-left:8px;padding-right:8px;white-space:normal;line-height:1.25;text-align:center;justify-content:center;}
  .cpi-modal .form-actions.cpi-actions .btn-primary{grid-column:1 / -1;order:-1;}
  /* the mapping table becomes stacked cards */
  .cpi-map-table thead{display:none;}
  .cpi-map-table,.cpi-map-table tbody,.cpi-map-table tr,.cpi-map-table td{display:block;width:100%;box-sizing:border-box;}
  .cpi-map-table tr{border:1px solid var(--border);border-radius:10px;padding:6px 8px;margin-bottom:8px;}
  .cpi-map-table td{border-top:none;padding:4px 0;}
  .cpi-map-table td::before{content:attr(data-label);display:block;font-size:11px;text-transform:uppercase;letter-spacing:.04em;color:var(--text-400);}
  .cpi-map-table select{max-width:none;}
  .cpi-map-sample{max-width:none;}
}
:root[data-theme="dark"] .cpi-grid-wrap,:root[data-theme="dark"] .cpi-paste,:root[data-theme="dark"] .cpi-modal .modal-body{color-scheme:dark;}

/* "Needed but not in your pasted data" - one value for every row */
.cpi-fill-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:10px;margin-top:10px;}
.cpi-fill{background:var(--card);border:1px solid var(--border);border-radius:10px;padding:8px 10px;color:var(--text-900);}
.cpi-fill label{display:block;font-size:12px;font-weight:700;color:var(--text-600);margin-bottom:4px;}
.cpi-fill select,.cpi-fill input{width:100%;box-sizing:border-box;padding:7px 8px;border:1px solid var(--border);border-radius:8px;background:var(--card);color:var(--text-900);font-size:13px;}
.cpi-fill select:focus,.cpi-fill input:focus{outline:none;border-color:var(--gold-500);}
.cpi-fill-btns{display:flex;flex-wrap:wrap;align-items:center;gap:6px;margin-top:6px;}
.cpi-fill-btns .cpi-chip{color:var(--text-600);border-color:var(--border);}.cpi-suggests{display:flex;flex-wrap:wrap;align-items:center;gap:8px;margin-top:10px;font-size:12.5px;}
.cpi-suggests > span{font-weight:700;}
.btn.cpi-suggest{white-space:normal;text-align:left;line-height:1.3;}
/* row messages stay in view when the grid is scrolled sideways */
.cpi-msg-row td{padding:0;}
.cpi-msg-row .cpi-msgs{position:sticky;left:14px;margin:4px 0 8px;padding-left:22px;width:min(780px,calc(100vw - 150px));box-sizing:border-box;white-space:normal;}
@media(max-width:820px){ .cpi-msg-row .cpi-msgs{width:calc(100vw - 110px);} }
/* a multi-line cell (an address) in the review grid */
.cpi-grid td textarea{width:100%;min-width:180px;box-sizing:border-box;padding:6px 8px;border:1px solid transparent;border-radius:6px;background:transparent;color:var(--text-900);font:inherit;line-height:1.35;resize:vertical;overflow:auto;}
.cpi-grid td textarea:hover{border-color:var(--border);}
.cpi-grid td textarea:focus{outline:none;border-color:var(--gold-500);background:var(--card);}
.cpi-grid td.cpi-cell-err textarea{border-color:var(--red);background:var(--card);}.cpi-fill select.cpi-copy{margin-top:6px;font-size:12px;color:var(--text-600);}
/* ---------- SIP Forecast > Payment Schedule: user-entered Units / Unit Price /
   Market Value columns (sipForecast.js instalmentInputCell) ---------- */
.sipf-payments-table .sipf-num{text-align:right;white-space:nowrap;}
.sipf-cell-input{
  width:110px;max-width:100%;padding:5px 8px;text-align:right;
  border:1px solid var(--border);border-radius:7px;background:var(--card);color:var(--text-900);
  font:inherit;font-size:13px;font-variant-numeric:tabular-nums;
}
.sipf-cell-input:focus{outline:none;border-color:var(--gold-500);box-shadow:0 0 0 3px color-mix(in srgb, var(--gold-500) 22%, transparent);}
.sipf-cell-input:disabled{background:var(--subtle-bg);color:var(--text-400);}

/* =====================================================================
   Foreign Investments (js/foreignInvestmentsPage.js) - .fi-*
   ===================================================================== */
.fi-page{display:flex;flex-direction:column;gap:16px;margin-top:4px;}
.fi-stats{gap:12px;}
.fi-stat-value{font-variant-numeric:tabular-nums;}
.fi-stat-sub{font-size:.62em;font-weight:600;opacity:.75;}
.fi-green{color:var(--green);font-weight:600;}
.fi-red{color:var(--red);font-weight:600;}
.fi-muted{color:var(--text-400);}
.fi-owner-select{padding:8px 12px;border:1px solid var(--border);border-radius:8px;font-size:13px;font-weight:600;min-width:200px;background:var(--card);color:var(--text-900);}
.fi-empty{text-align:center;padding:32px 20px;}
.fi-empty-icon{font-size:44px;line-height:1;margin-bottom:8px;}
.fi-empty-actions{display:flex;flex-wrap:wrap;gap:8px;justify-content:center;margin-top:14px;}
.fi-chart-card{min-width:0;}
.fi-charts{gap:16px;}
.fi-table td{vertical-align:top;}
.fi-table .fi-name{font-weight:600;}
.fi-row-click{cursor:pointer;}
.fi-row-click:hover td{background:var(--subtle-bg);}
.fi-th-sort{cursor:pointer;user-select:none;white-space:nowrap;}
.fi-th-sort:hover{color:var(--gold-500);}
.fi-th-sort.active{color:var(--gold-500);}
.fi-actions-cell{white-space:nowrap;}
.fi-filters{display:flex;flex-wrap:wrap;gap:8px;align-items:center;padding:0 16px 12px;}
.fi-filters input[type=search],.fi-filters select,.fi-filters input[type=date]{padding:8px 10px;border:1px solid var(--border);border-radius:8px;font-size:13px;background:var(--card);color:var(--text-900);min-width:0;}
.fi-filters input[type=search]{min-width:200px;flex:1 1 200px;}
.fi-date-filter{display:inline-flex;align-items:center;gap:6px;font-size:13px;color:var(--text-600);}
/* Country-wise view */
.fi-country-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:12px;padding:0 16px 16px;}
.fi-country-card{border:1px solid var(--border);border-radius:12px;padding:14px;background:var(--card);display:flex;flex-direction:column;gap:6px;}
.fi-country-head{display:flex;justify-content:space-between;align-items:baseline;gap:8px;}
.fi-country-name{font-weight:700;font-size:15px;}
.fi-country-share{font-weight:700;color:var(--gold-500);}
.fi-country-value{font-size:21px;font-weight:700;font-variant-numeric:tabular-nums;}
.fi-country-bar{height:6px;border-radius:999px;background:var(--subtle-bg);overflow:hidden;}
.fi-country-bar span{display:block;height:100%;background:var(--gold-500);border-radius:999px;}
.fi-country-cats{display:flex;flex-wrap:wrap;gap:6px;}
.fi-chip{display:inline-flex;align-items:center;gap:4px;font-size:12.4px;padding:3px 8px;border-radius:999px;background:var(--subtle-bg);color:var(--text-600);}
/* Detail view */
.fi-detail-head{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-bottom:12px;}
.fi-sec{margin:16px 0 8px;font-size:13px;letter-spacing:.04em;text-transform:uppercase;color:var(--text-600);}
.fi-kv-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:10px 16px;}
.fi-kv{min-width:0;}
.fi-kv-l{font-size:12px;color:var(--text-600);}
.fi-kv-v{font-size:13.4px;overflow-wrap:anywhere;}
.fi-detail-actions{flex-wrap:wrap;}
/* Add / edit form helpers */
.fi-rate-note{margin-top:4px;font-size:12px;line-height:1.35;color:var(--text-600);}
.fi-rate-note.fi-note-warn{color:var(--red);}
input.fi-locked{background:var(--subtle-bg);color:var(--text-600);cursor:not-allowed;}
/* Narrow screens: every table row becomes a labelled card (no sideways scrolling to read a holding). */
@media (max-width:720px){
  .fi-table thead{display:none;}
  .fi-table,.fi-table tbody,.fi-table tr,.fi-table td{display:block;width:100%;}
  .fi-table tr{border:1px solid var(--border);border-radius:12px;margin:0 0 10px;padding:8px 10px;background:var(--card);}
  .fi-table td{border:none !important;padding:5px 0;display:flex;justify-content:space-between;gap:12px;text-align:right;}
  .fi-table td::before{content:attr(data-label);font-size:12px;color:var(--text-600);text-align:left;flex:0 0 40%;}
  .fi-table td.fi-actions-cell{justify-content:flex-end;}
  .fi-table td.fi-actions-cell::before{content:none;}
  .fi-table tr.empty-row td::before{content:none;}
  .fi-table .table-actions{justify-content:flex-end;}
  .fi-owner-select{min-width:0;width:100%;}
  .fi-filters{padding:0 12px 12px;}
  .fi-filters select,.fi-filters input[type=date]{flex:1 1 140px;}
}

/* =====================================================================
   Foreign Investments - page design (hero, KPI cards, category tiles, allocation, promo)
   ===================================================================== */
.fi-hero{position:relative;overflow:hidden;border-radius:20px;height:207px;margin-bottom:16px;
  background:linear-gradient(90deg,#020f22 0%,#032040 55%,#01253f 100%);box-shadow:0 8px 24px rgba(3,20,44,.28);}
.fi-hero-art{position:absolute;top:0;right:0;bottom:0;height:100%;aspect-ratio:890/198;background-size:100% 100%;background-position:right center;background-repeat:no-repeat;
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 34%);mask-image:linear-gradient(90deg,transparent 0,#000 34%);}
.fi-hero-content{position:relative;z-index:1;padding:18px 36px 16px;display:flex;flex-direction:column;gap:1px;}
.fi-hero-brand{font-size:12px;letter-spacing:.3em;font-weight:500;color:#e8c26a;}
.fi-hero-titlerow{display:flex;align-items:center;gap:14px;}
.fi-hero-icon{width:52px;height:52px;border-radius:14px;box-shadow:0 6px 16px rgba(2,10,25,.45);flex:0 0 auto;}
.fi-hero-title{margin:2px 0 4px;font-size:34px;line-height:1.1;font-weight:800;color:#fff;letter-spacing:-.01em;}
/* "Foreign Investments" uses the shared shineLetters() heading (as Asset Management does): white first word, gold second. */
.fi-hero-title .shine-word + .shine-word .shine-letter{background-image:linear-gradient(90deg, var(--gold-600) 0%, var(--gold-400) 22%, #fff6d8 50%, var(--gold-400) 78%, var(--gold-600) 100%);}
.fi-hero-sub{font-size:15px;font-weight:600;color:#fff;}
.fi-hero-sub i{font-style:normal;font-weight:400;opacity:.85;margin:0 12px;}
.fi-hero-rule{width:52px;height:3px;border-radius:2px;background:#e8b64d;margin:6px 0 8px;}
.fi-hero-tag{font-size:12.4px;color:#e6ecf7;max-width:490px;line-height:1.45;}
@media (min-width:1760px){.fi-hero-tag{max-width:none;}.fi-hero-content{padding:24px 40px;}}
.fi-hero-tag b{margin:0 7px;font-weight:400;opacity:.8;}
/* Tab strip in this page's skin */
.cat-tabs-strip-foreign{background:#061f3c;border:1px solid rgba(255,255,255,.07);border-radius:16px;padding:10px 14px;gap:10px;}
.cat-tabs-strip-foreign .cat-tabs{gap:14px;}
.cat-tabs-strip-foreign .cat-tab{color:#eaf0fa;background:#0e2441;border-color:rgba(255,255,255,.07);padding:10px 22px;font-size:13.2px;gap:10px;}
.cat-tabs-strip-foreign .cat-tab:hover{background:#15305a;}
.cat-tabs-strip-foreign .cat-tab.active{background:linear-gradient(180deg,#f8d98c,#e7b24a);color:#1b2233;border-color:#f3cf7c;box-shadow:0 2px 10px rgba(232,182,77,.4);}
.cat-tabs-strip-foreign .cat-tab-icon img{width:20px;height:20px;}
.cat-tabs-strip-foreign .cat-tab.active .cat-tab-icon img{filter:invert(1);}
.cat-tabs-strip-foreign .cat-tabs-arrow{color:#eaf0fa;}
/* KPI cards */
.fi-kpis{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px;}
.fi-kpi{container-type:inline-size;position:relative;overflow:hidden;border-radius:16px;padding:16px 20px 14px;min-height:132px;border:1px solid rgba(255,255,255,.75);box-shadow:0 1px 3px rgba(20,40,80,.06);display:flex;flex-direction:column;}
.fi-kpi-blue{background:linear-gradient(135deg,#e6f1fe,#dcebfc);}
.fi-kpi-gold{background:linear-gradient(135deg,#fef6e8,#fcf2dc);}
.fi-kpi-lav{background:linear-gradient(135deg,#eaeefe,#e7ebfe);}
.fi-kpi-green{background:linear-gradient(135deg,#e6f9f2,#e2f8ef);}
.fi-kpi-mint{background:linear-gradient(135deg,#e6f9f2,#dff6ec);}
.fi-kpi-purple{background:linear-gradient(135deg,#efebfe,#ece7ff);}
.fi-kpi-pink{background:linear-gradient(135deg,#fce9f5,#fbe6f2);}
.fi-kpi-sky{background:linear-gradient(135deg,#e6f1ff,#dbebfe);}
.fi-kpi.fi-kpi-neg.fi-kpi-pink{background:linear-gradient(135deg,#fdeaea,#fbe1e1);}
.fi-kpi.fi-kpi-neg .fi-kpi-badge,.fi-kpi.fi-kpi-neg .fi-kpi-art{filter:hue-rotate(150deg) saturate(1.15);}
.fi-kpi-head,.fi-kpi-value,.fi-kpi-foot{position:relative;z-index:1;}
.fi-kpi-head{display:flex;align-items:center;gap:12px;}
.fi-kpi-badge{width:40px;height:40px;border-radius:12px;flex:none;mix-blend-mode:multiply;}
.fi-kpi-label{font-size:12px;letter-spacing:.07em;font-weight:600;color:#4c5872;}
.fi-kpi-value{margin-top:8px;font-size:21px;font-weight:800;color:#0d1c3d;line-height:1.15;font-variant-numeric:tabular-nums;}
.fi-kpi-value .fi-green{color:#0e9a62;}
.fi-kpi-value .fi-red{color:#d43c3c;}
.fi-stat-sub{font-size:.55em;font-weight:600;color:#5b6780;}
.fi-kpi-foot{margin-top:auto;padding-top:6px;font-size:12px;color:#5a6580;}
.fi-kpi-foot b{font-weight:600;color:#3b4763;}
.fi-kpi-art{position:absolute;right:0;bottom:0;height:100%;max-width:36%;width:auto;object-fit:contain;object-position:right center;pointer-events:none;mix-blend-mode:multiply;
  -webkit-mask-image:radial-gradient(farthest-side at 62% 52%,#000 62%,transparent 100%);mask-image:radial-gradient(farthest-side at 62% 52%,#000 62%,transparent 100%);}
.fi-art-pink{filter:hue-rotate(95deg) saturate(.8);}
.fi-kpi-coin{position:absolute;right:26px;top:50%;transform:translateY(-38%);width:66px;height:66px;border-radius:50%;background:#fff;border:3px solid #cdbdfb;display:flex;align-items:center;justify-content:center;
  font-weight:800;color:#6b3fd9;font-size:13.2px;box-shadow:0 6px 16px rgba(107,63,217,.2);}
/* Cards used by the sections beneath */
.fi-card{background:var(--card);border:1px solid #e9edf5;border-radius:16px;box-shadow:0 1px 3px rgba(20,40,80,.05);}
.fi-page .card{border-radius:16px;}
.fi-mid{display:grid;grid-template-columns:minmax(0,1fr) minmax(300px,32%);gap:16px;align-items:stretch;}
.fi-mid-left{display:flex;flex-direction:column;gap:14px;min-width:0;}
.fi-cat-card{padding:16px 18px 18px;}
.fi-cat-head{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:14px;}
.fi-cat-icon{width:34px;height:34px;border-radius:50%;border:1.5px solid #e2b64f;color:#c8992e;display:inline-flex;align-items:center;justify-content:center;flex:none;}
.fi-cat-icon svg{width:20px;height:20px;}
.fi-cat-titles{flex:1;min-width:200px;}
.fi-cat-title{font-size:15px;font-weight:700;color:var(--text-900);}
.fi-cat-sub{font-size:12.5px;color:var(--text-600);}
.fi-seg{display:inline-flex;background:#f1f4f8;border-radius:999px;padding:3px;}
.fi-seg button{border:none;background:transparent;font:inherit;font-size:12.2px;font-weight:600;color:#3d4a63;padding:7px 16px;border-radius:999px;cursor:pointer;}
.fi-seg button.active{background:#122c4f;color:#fff;}
.fi-tiles{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:10px;}
.fi-tile{display:grid;grid-template-columns:36px minmax(0,1fr);grid-template-areas:"ic name" "ic val" "bar bar";column-gap:9px;row-gap:1px;align-items:center;text-align:left;
  padding:11px 11px 10px;border:1px solid #edf0f6;border-radius:12px;background:#fdfdfe;cursor:pointer;font:inherit;transition:box-shadow .15s ease,transform .15s ease;}
.fi-tile:hover{box-shadow:0 4px 14px rgba(20,40,80,.1);transform:translateY(-1px);}
.fi-tile-ic{grid-area:ic;width:34px;height:34px;}
.fi-tile-name{grid-area:name;font-size:12.4px;color:#4c5872;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
.fi-tile-val{grid-area:val;font-size:13.4px;font-weight:700;color:#0d1c3d;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-variant-numeric:tabular-nums;}
.fi-tile-val .fi-green{color:#0e9a62;}.fi-tile-val .fi-red{color:#d43c3c;}
.fi-tile-bar{grid-area:bar;margin-top:9px;height:5px;border-radius:999px;background:#e6eaf3;overflow:hidden;}
.fi-tile-bar i{display:block;height:100%;border-radius:999px;}
.fi-promo{position:relative;overflow:hidden;display:flex;align-items:center;gap:14px;min-height:56px;padding:0 16px;border-radius:14px;
  background:linear-gradient(90deg,#f7e4c2,#fdf2d8 62%);border:1px solid #f0dcae;}
.fi-promo-plant{width:34px;height:34px;border-radius:50%;flex:none;mix-blend-mode:multiply;position:relative;z-index:1;}
.fi-promo-text{position:relative;z-index:1;}
.fi-promo-title{font-size:15px;font-weight:700;color:#3a2a10;}
.fi-promo-sub{font-size:12.4px;color:#7a6845;}
.fi-promo-art{position:absolute;top:0;right:0;bottom:0;width:58%;background-size:cover;background-position:right center;opacity:.95;
  -webkit-mask-image:linear-gradient(90deg,transparent 0,#000 40%);mask-image:linear-gradient(90deg,transparent 0,#000 40%);}
.fi-alloc{padding:16px 18px;display:flex;flex-direction:column;}
.fi-alloc-title{display:flex;align-items:center;gap:8px;font-size:15px;font-weight:700;color:var(--text-900);margin-bottom:10px;}
.fi-alloc-title svg{width:18px;height:18px;}
.fi-alloc-body{display:flex;align-items:center;justify-content:center;gap:18px;flex:1;flex-wrap:wrap;}
.fi-donut{position:relative;width:150px;height:150px;flex:none;}
.fi-donut-svg{width:100%;height:100%;display:block;}
.fi-donut-center{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;}
.fi-donut-center b{font-size:15px;color:#0d1c3d;}
.fi-donut-center span{font-size:12px;color:#5a6580;}
.fi-legend{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:9px;min-width:170px;flex:1;max-width:240px;}
.fi-legend li{display:flex;align-items:center;gap:9px;font-size:12.4px;color:#39445c;}
.fi-legend li i{width:9px;height:9px;border-radius:50%;flex:none;}
.fi-legend li span{flex:1;}
.fi-legend li b{font-weight:600;font-variant-numeric:tabular-nums;color:#39445c;}
.fi-charts{display:grid;grid-template-columns:repeat(auto-fit,minmax(300px,1fr));gap:16px;}
@media (max-width:1180px){
  .fi-kpis{grid-template-columns:repeat(2,minmax(0,1fr));}
  .fi-mid{grid-template-columns:1fr;}
  .fi-tiles{grid-template-columns:repeat(3,minmax(0,1fr));}
}
@media (max-width:720px){
  .fi-kpis{grid-template-columns:1fr;}
  .fi-tiles{grid-template-columns:repeat(2,minmax(0,1fr));}
  .fi-hero-content{padding:20px 20px 22px;}
  .fi-hero-art{width:100%;opacity:.5;}
  .fi-hero-tag{font-size:13px;}
  .cat-tabs-strip-foreign .cat-tab{padding:9px 16px;font-size:13.2px;}
}
/* Dark theme: the illustrations are painted for a light card, so the cards go flat and quiet. */
:root[data-theme="dark"] .fi-kpi{background:var(--card);border-color:var(--border);}
:root[data-theme="dark"] .fi-kpi-art{display:none;}
:root[data-theme="dark"] .fi-kpi-badge,:root[data-theme="dark"] .fi-promo-plant{mix-blend-mode:normal;}
:root[data-theme="dark"] .fi-kpi-label,:root[data-theme="dark"] .fi-kpi-foot,:root[data-theme="dark"] .fi-stat-sub,:root[data-theme="dark"] .fi-tile-name,:root[data-theme="dark"] .fi-donut-center span{color:var(--text-600);}
:root[data-theme="dark"] .fi-kpi-value,:root[data-theme="dark"] .fi-tile-val,:root[data-theme="dark"] .fi-donut-center b{color:var(--text-900);}
:root[data-theme="dark"] .fi-kpi-foot b,:root[data-theme="dark"] .fi-legend li,:root[data-theme="dark"] .fi-legend li b{color:var(--text-900);}
:root[data-theme="dark"] .fi-card{border-color:var(--border);}
:root[data-theme="dark"] .fi-tile{background:var(--card);border-color:var(--border);}
:root[data-theme="dark"] .fi-tile-bar{background:var(--subtle-bg);}
:root[data-theme="dark"] .fi-seg{background:var(--subtle-bg);}
:root[data-theme="dark"] .fi-seg button{color:var(--text-600);}
:root[data-theme="dark"] .fi-seg button.active{background:var(--gold-500);color:#1b1408;}
:root[data-theme="dark"] .fi-promo{background:linear-gradient(90deg,#3a2f17,#2b2412);border-color:#4a3c1c;}
:root[data-theme="dark"] .fi-promo-title{color:#f3e2b8;}
:root[data-theme="dark"] .fi-promo-sub{color:#cbb98a;}
:root[data-theme="dark"] .fi-promo-art{opacity:.5;}
:root[data-theme="dark"] .fi-kpi-coin{background:var(--card);}
.fi-kpi-long .fi-kpi-value{font-size:18px;}
.fi-cat-card{container-type:inline-size;}
@container (max-width: 940px){
  .fi-tiles{grid-template-columns:repeat(3,minmax(0,1fr));}
}
@container (max-width: 520px){
  .fi-tiles{grid-template-columns:repeat(2,minmax(0,1fr));}
}
.fi-charts{align-items:stretch;}
.fi-charts .fi-chart-card{margin:0;}

/* =====================================================================
   Foreign Investments - motion. Ambient effects are slow and small; all of it
   (and the JS entrance in foreignInvestmentsPage.js) stands down for
   prefers-reduced-motion.
   ===================================================================== */
/* The page's sections are now direct children of #content (so each reveals on its own): keep them spaced. */
#content:has(> .fi-hero) > *:not(.fi-hero):not(.cat-tabs-strip){margin-bottom:16px;}
#content:has(> .fi-hero) .card{border-radius:16px;}
.fi-hero-art{transform-origin:right center;animation:fi-drift 26s ease-in-out infinite alternate;}

.fi-hero-play .fi-hero-content > .fi-hero-rule{transform-origin:left center;animation:fi-rule 1s cubic-bezier(.16,1,.3,1) .35s both;}
.fi-hero-play .fi-hero-content > *{animation:fi-rise .7s cubic-bezier(.16,1,.3,1) both;}
.fi-hero-play .fi-hero-content > *:nth-child(2){animation-delay:.08s;}
.fi-hero-play .fi-hero-content > *:nth-child(3){animation-delay:.16s;}
.fi-hero-play .fi-hero-content > *:nth-child(4){animation-delay:.24s;}
.fi-hero-play .fi-hero-content > *:nth-child(5){animation-delay:.32s;}
.fi-hero-icon{animation:fi-float 5.5s ease-in-out infinite;}
.fi-hero-play .fi-hero-icon{animation:fi-art-pop .7s cubic-bezier(.16,1,.3,1) .1s both,fi-float 5.5s ease-in-out 1s infinite;}
.fi-promo-art{animation:fi-drift-l 30s ease-in-out infinite alternate;}
.fi-kpi{transition:transform .22s ease,box-shadow .22s ease;}
.fi-kpi:hover{transform:translateY(-3px);box-shadow:0 10px 24px rgba(20,40,80,.14);}
.fi-kpi-art,.fi-kpi-coin{animation:fi-float 6s ease-in-out infinite;}
.fi-kpi:nth-child(2n) .fi-kpi-art,.fi-kpi:nth-child(2n) .fi-kpi-coin{animation-duration:7.2s;animation-delay:-2s;}
.fi-kpi:nth-child(3n) .fi-kpi-art,.fi-kpi:nth-child(3n) .fi-kpi-coin{animation-duration:6.6s;animation-delay:-4s;}
.fi-kpi .fi-kpi-art.fi-art-in,.fi-kpi .fi-kpi-coin.fi-art-in{animation:fi-art-pop .8s cubic-bezier(.16,1,.3,1) both,fi-float 6s ease-in-out 1.2s infinite;}
.fi-kpi-badge{transition:transform .25s ease;}
.fi-kpi:hover .fi-kpi-badge{transform:scale(1.1) rotate(-4deg);}
.fi-tile{transition:box-shadow .2s ease,transform .2s ease;}
.fi-tile-ic{transition:transform .25s ease;}
.fi-tile:hover .fi-tile-ic{transform:scale(1.12);}
.fi-tile-bar i{transition:width .9s cubic-bezier(.16,1,.3,1);}
.fi-legend li{transition:background .15s ease,transform .15s ease;border-radius:8px;padding:2px 6px;margin:-2px -6px;}
.fi-legend li:hover{background:var(--subtle-bg);transform:translateX(3px);}
.fi-donut{transition:transform .3s ease;}
.fi-donut:hover{transform:scale(1.03);}
.fi-donut-svg circle{transition:stroke-width .2s ease;}
.cat-tabs-strip-foreign .cat-tab{transition:background .18s ease,color .18s ease,transform .18s ease,box-shadow .18s ease;}
.cat-tabs-strip-foreign .cat-tab:hover{transform:translateY(-2px);}
.cat-tabs-strip-foreign .cat-tab.active{animation:fi-glow 3.4s ease-in-out infinite;}
.fi-seg button{transition:background .2s ease,color .2s ease;}
.fi-promo-plant{animation:fi-sway 5s ease-in-out infinite;transform-origin:50% 90%;}
@keyframes fi-drift{from{transform:scale(1) translateX(0);}to{transform:scale(1.07) translateX(-12px);}}
@keyframes fi-drift-l{from{transform:scale(1);}to{transform:scale(1.06) translateX(-8px);}}
@keyframes fi-shimmer{from{background-position:0% 0;}to{background-position:-260% 0;}}
@keyframes fi-rule{from{transform:scaleX(0);}to{transform:scaleX(1);}}
@keyframes fi-rise{from{opacity:0;transform:translateY(12px);}to{opacity:1;transform:translateY(0);}}
@keyframes fi-float{0%,100%{transform:translateY(0);}50%{transform:translateY(-4px);}}
@keyframes fi-art-pop{from{opacity:0;transform:translate(10px,8px) scale(.9);}to{opacity:1;transform:translate(0,0) scale(1);}}
@keyframes fi-glow{0%,100%{box-shadow:0 2px 10px rgba(232,182,77,.4);}50%{box-shadow:0 2px 18px rgba(232,182,77,.75);}}
@keyframes fi-sway{0%,100%{transform:rotate(-4deg);}50%{transform:rotate(5deg);}}
@media (prefers-reduced-motion: reduce){
  .fi-hero-art,.fi-hero-play .fi-hero-content > *,.fi-hero-icon,.fi-hero-play .fi-hero-icon,.fi-promo-art,.fi-kpi-art,.fi-kpi-coin,.fi-kpi .fi-kpi-art.fi-art-in,.fi-kpi .fi-kpi-coin.fi-art-in,
  .cat-tabs-strip-foreign .cat-tab.active,.fi-promo-plant{animation:none !important;}
  .fi-kpi,.fi-tile,.fi-tile-ic,.fi-tile-bar i,.fi-legend li,.fi-donut,.fi-kpi-badge,.cat-tabs-strip-foreign .cat-tab{transition:none !important;}
  .fi-kpi:hover,.fi-tile:hover,.fi-donut:hover,.fi-legend li:hover,.cat-tabs-strip-foreign .cat-tab:hover{transform:none;}
}
.fi-hero-content{height:100%;justify-content:center;box-sizing:border-box;}
.fi-hero-play .fi-hero-content > *:nth-child(1){animation-delay:0s;}

/* Phone-width category tiles: icon on top so the whole rupee value fits (it was being cut with an ellipsis). */
@container (max-width: 520px){
  .fi-tile{grid-template-columns:1fr;grid-template-areas:"ic" "name" "val" "bar";row-gap:2px;}
  .fi-tile-ic{margin-bottom:4px;}
  .fi-tile-val{font-size:13.4px;overflow:visible;text-overflow:clip;}
}
/* Gains never break between the sign and the amount ("+" alone on a line), on desktop or as phone cards. */
.fi-green,.fi-red{white-space:nowrap;}
@media (max-width:720px){
  .fi-table td{flex-wrap:wrap;column-gap:6px;}
  .fi-table td > *{max-width:100%;}
}
@media (max-width:720px){
  /* The holding's name and its detail line stack (they were sitting side by side in a cramped row). */
  .fi-table td[data-label="Holding"]{display:block;text-align:left;}
  .fi-table td[data-label="Holding"]::before{display:none;}
  .fi-table td[data-label="Holding"] .fi-name{font-size:14px;}
}
@media (max-width:720px){
  /* Card headers on the Foreign Investments tabs: title, then owner filter and buttons wrap onto their own lines. */
  #content:has(> .fi-hero) .card-header{flex-wrap:wrap;gap:8px;}
  #content:has(> .fi-hero) .card-header .spacer{display:none;}
  #content:has(> .fi-hero) .card-header .btn{flex:1 1 auto;justify-content:center;}
}

/* Nest Users > AI Usage (rolesAccess.js) */
.ra-aiu-controls{display:flex;flex-wrap:wrap;gap:10px;align-items:center;margin-bottom:14px;}
.ra-aiu-controls select{padding:8px 12px;border:1px solid var(--border);border-radius:8px;font-size:13px;font-weight:600;background:var(--card);color:var(--text-900);}
.ra-aiu-block{margin-top:18px;}
.ra-aiu-title{font-size:15px;font-weight:700;margin-bottom:8px;color:var(--text-900);}
.ra-table .ra-aiu-num,.ra-aiu-num{text-align:right;font-variant-numeric:tabular-nums;white-space:nowrap;}
.ra-aiu-bad{color:var(--red);font-weight:700;}
.ra-aiu-good{color:var(--green);font-weight:700;}

.ra-aiu-kpis{margin-bottom:18px;}
.ra-aiu-kpi-card{display:flex;align-items:center;gap:14px;}
.ra-aiu-kpi-icon{display:flex;align-items:center;justify-content:center;flex:0 0 auto;width:44px;height:44px;border-radius:12px;}
.ra-aiu-kpi-icon svg{width:22px;height:22px;}
.ra-aiu-kpi-icon.blue{background:var(--blue-bg);color:var(--blue);}
.ra-aiu-kpi-icon.pink{background:var(--pink-bg, #ffe4f0);color:var(--pink, #d6336c);}
.ra-aiu-kpi-icon.green{background:var(--green-bg);color:var(--green);}
.ra-aiu-kpi-icon.gold{background:var(--gold-bg, #fff6e0);color:var(--gold-500);}
.ra-aiu-kpi-icon.purple{background:var(--purple-bg, #f1e9fe);color:var(--purple, #7c4dff);}
.ra-aiu-kpi-text{min-width:0;flex:1 1 auto;}
.stat-value.pink{color:var(--pink, #d6336c);}

.ra-aiu-badge{display:inline-flex;align-items:center;justify-content:center;width:26px;height:26px;border-radius:8px;margin-right:8px;vertical-align:middle;flex:0 0 auto;}
.ra-aiu-badge svg{width:15px;height:15px;}
.ra-aiu-badge.blue{background:var(--blue-bg);color:var(--blue);}
.ra-aiu-badge.green{background:var(--green-bg);color:var(--green);}
.ra-aiu-badge.purple{background:var(--purple-bg, #f1e9fe);color:var(--purple, #7c4dff);}
.ra-aiu-badge.gold{background:var(--gold-bg, #fff6e0);color:var(--gold-500);}

.ra-aiu-title-row{display:flex;align-items:center;flex-wrap:wrap;gap:8px 12px;margin-bottom:8px;}
.ra-aiu-title-row .ra-aiu-title{margin-bottom:0;display:flex;align-items:center;flex:1 1 auto;min-width:0;}

.ra-aiu-pill{display:inline-flex;align-items:center;gap:5px;padding:3px 10px;border-radius:999px;font-size:12px;font-weight:700;white-space:nowrap;border:1px solid transparent;}
.ra-aiu-pill-ok{background:var(--green-bg);color:var(--green);border-color:var(--green);}
.ra-aiu-pill-warn{background:var(--amber-bg);color:var(--amber);border-color:var(--amber);}
.ra-aiu-pill-low{background:var(--red-bg, #fde8e8);color:var(--red);border-color:var(--red);}

.ra-aiu-trend-row{display:flex;flex-wrap:wrap;gap:16px;margin-top:18px;}
.ra-aiu-trend-chart{flex:2 1 420px;min-width:280px;}
.ra-aiu-trend-donut{flex:1 1 240px;min-width:220px;}
.ra-aiu-trend-row .ra-aiu-block{margin-top:0;}
@media (max-width:900px){
  .ra-aiu-trend-row{flex-direction:column;}
  .ra-aiu-trend-chart,.ra-aiu-trend-donut{flex:1 1 auto;}
}

.ra-aiu-barcell{width:28%;min-width:120px;}
.ra-aiu-bar{display:block;height:8px;border-radius:999px;background:var(--subtle-bg);overflow:hidden;}
.ra-aiu-bar i{display:block;height:100%;border-radius:999px;background:var(--gold-500);}
@media (max-width:720px){.ra-aiu-barcell{display:none;}}
.ra-aiu-top{margin:0 0 10px;padding:10px 14px;border:1px solid var(--border);border-left:3px solid var(--gold-500);border-radius:10px;background:var(--subtle-bg);font-size:13.4px;color:var(--text-900);}

.ra-aiu-bill-msg{margin:0 0 12px;padding:10px 14px;border-radius:10px;border:1px solid var(--border);font-size:13.4px;line-height:1.5;}
.ra-aiu-bill-ok{background:var(--green-bg);border-color:var(--green);color:var(--green);}
.ra-aiu-bill-warn{background:var(--amber-bg);border-color:var(--amber);color:var(--amber);}
.ra-aiu-bill-low{background:var(--red-bg);border-color:var(--red);color:var(--red);}
.ra-aiu-form{align-items:flex-end;margin-top:12px;}
.ra-aiu-form label{display:flex;flex-direction:column;gap:4px;color:var(--text-700, var(--text-900));}
.ra-aiu-form input[type="number"]{width:150px;border:1px solid var(--border);border-radius:8px;padding:7px 10px;font-size:12.8px;font-family:inherit;background:var(--card);color:var(--text-900);}
.ra-aiu-form .ra-aiu-check{flex-direction:row;align-items:center;gap:6px;padding-bottom:8px;}
.ra-aiu-form a.btn{text-decoration:none;}
@media (max-width:640px){.ra-aiu-form label{flex:1 1 100%;}.ra-aiu-form input[type="number"]{width:100%;}.ra-aiu-form .ra-aiu-check{flex:1 1 100%;}}
.ra-aiu-form input[type="text"],.ra-aiu-form input[type="date"]{border:1px solid var(--border);border-radius:8px;padding:7px 10px;font-size:12.8px;font-family:inherit;background:var(--card);color:var(--text-900);text-transform:none;}
.ra-aiu-form input[type="text"]{text-transform:uppercase;}
@media (max-width:640px){.ra-aiu-form input[type="text"],.ra-aiu-form input[type="date"]{width:100% !important;}}

.ra-aiu-controls .ra-aiu-check{display:inline-flex;align-items:center;gap:6px;cursor:pointer;white-space:nowrap;}
.ra-aiu-controls .ra-aiu-check input{margin:0;}
.ra-aiu-updated{white-space:nowrap;}
@media (max-width:640px){.ra-aiu-controls{gap:8px;}.ra-aiu-controls select{flex:1 1 100%;}.ra-aiu-controls .btn{flex:1 1 auto;}.ra-aiu-controls .ra-aiu-check{flex:1 1 100%;}.ra-aiu-updated{flex:1 1 100%;}}

/* ---------- Nest Users > AI Usage > List of API (rolesAccess.js) ----------
   Own copy of the sort-button glyph (matches .ra-ud-sort-btn's look) rather
   than reusing that class directly, per this codebase's per-page-copy
   convention. Status badges reuse the existing .ra-badge-green/-amber; KPI
   cards reuse the existing .ra-aiu-kpi-card/.ra-aiu-kpi-icon. */
.ra-api-sort-btn{
  display:inline-flex;align-items:center;gap:4px;border:none;background:transparent;
  font:inherit;font-size:inherit;font-weight:inherit;color:inherit;cursor:pointer;padding:0;
}
.ra-api-actions{display:flex;gap:6px;flex-wrap:wrap;}
.ra-api-detail-grid{display:flex;flex-direction:column;gap:6px;}

/* ---------- Nest Users > Payment Details (rolesAccess.js) ----------
   Redesigned per "Payment Details.png". KPI cards combine the two
   half-patterns this app already has elsewhere (AI Usage's solid left
   icon badge + User Details' pale corner "ghost" icon) since the
   reference uses both on the same card. */
.ra-pay-kpis{margin-bottom:18px;}
.ra-pay-kpi-card{display:flex;align-items:center;gap:14px;}
.ra-pay-kpi-icon{display:flex;align-items:center;justify-content:center;flex:0 0 auto;width:44px;height:44px;border-radius:12px;z-index:1;}
.ra-pay-kpi-icon svg{width:22px;height:22px;}
.ra-pay-kpi-icon.blue{background:var(--blue-bg);color:var(--blue);}
.ra-pay-kpi-icon.green{background:var(--green-bg);color:var(--green);}
.ra-pay-kpi-icon.purple{background:var(--purple-bg, #f1e9fe);color:var(--purple, #7c4dff);}
.ra-pay-kpi-icon.gold{background:var(--gold-bg, #fff6e0);color:var(--gold-600);}
.ra-pay-kpi-icon.red{background:var(--red-bg);color:var(--red);}
.ra-pay-kpi-icon.pink{background:var(--pink-bg, #ffe4f0);color:var(--pink, #d6336c);}
.ra-pay-kpi-text{min-width:0;flex:1 1 auto;z-index:1;}
.ra-pay-kpi-label{font-size:11.5px;font-weight:700;letter-spacing:.03em;text-transform:uppercase;color:var(--text-500);}
.ra-pay-kpi-value{font-size:21px;font-weight:800;margin-top:2px;color:var(--text-900);}
.ra-pay-kpi-value.blue{color:var(--blue);}
.ra-pay-kpi-value.green{color:var(--green);}
.ra-pay-kpi-value.purple{color:var(--purple, #7c4dff);}
.ra-pay-kpi-value.gold{color:var(--gold-600);}
.ra-pay-kpi-value.red{color:var(--red);}
.ra-pay-kpi-value.pink{color:var(--pink, #d6336c);}
.ra-pay-kpi-ghost{position:absolute;right:-6px;bottom:-10px;opacity:.12;z-index:0;pointer-events:none;}
.ra-pay-kpi-ghost svg{width:56px;height:56px;}
.ra-pay-kpi-ghost.blue{color:var(--blue);}
.ra-pay-kpi-ghost.green{color:var(--green);}
.ra-pay-kpi-ghost.purple{color:var(--purple, #7c4dff);}
.ra-pay-kpi-ghost.gold{color:var(--gold-600);}
.ra-pay-kpi-ghost.red{color:var(--red);}
.ra-pay-kpi-ghost.pink{color:var(--pink, #d6336c);}

.ra-pay-search-box{
  display:flex;align-items:center;gap:8px;max-width:340px;flex:1 1 260px;
  border:1px solid var(--border);border-radius:10px;padding:9px 14px;
  background:var(--card);color:var(--text-400);
}
.ra-pay-search-box input{flex:1;border:none;background:transparent;font:inherit;font-size:13.4px;color:var(--text-900);}
.ra-pay-search-box input:focus{outline:none;}
.ra-pay-export-row{display:flex;gap:8px;flex-wrap:wrap;margin-left:auto;}
.ra-pay-export-btn{
  display:inline-flex;align-items:center;gap:6px;border-radius:8px;padding:8px 14px;
  font-size:12.8px;font-weight:700;border:1px solid transparent;cursor:pointer;white-space:nowrap;
}
.ra-pay-export-btn.excel{background:var(--green-bg);border-color:var(--green);color:var(--green);}
.ra-pay-export-btn.csv{background:var(--blue-bg);border-color:var(--blue);color:var(--blue);}
.ra-pay-export-btn.pdf{background:var(--red-bg);border-color:var(--red);color:var(--red);}
@media (max-width:640px){
  .ra-pay-search-box{max-width:none;flex:1 1 100%;}
  .ra-pay-export-row{margin-left:0;flex:1 1 100%;}
  .ra-pay-export-btn{flex:1 1 auto;justify-content:center;}
}

.ra-pay-empty-art{position:relative;display:inline-flex;align-items:center;justify-content:center;
  width:110px;height:110px;border-radius:50%;margin:0 auto 14px;
  background:radial-gradient(circle, #fbe9b8 0%, #f6dfa0 60%, transparent 78%);color:#b8962e;
}
.ra-pay-empty-art svg{width:46px;height:46px;}
.ra-pay-empty-spark{position:absolute;color:#d9b84a;font-size:14px;}
.ra-pay-empty-spark.s1{top:6px;right:10px;font-size:11px;}
.ra-pay-empty-spark.s2{bottom:14px;left:2px;font-size:16px;}
.ra-pay-empty-spark.s3{top:38%;right:-6px;font-size:9px;}

/* ---------- WhatsApp Settings (whatsappSettings.js) ----------
   Redesigned per "Whatsapp Settings.png". Unlike Payment Details' hero
   (a fixed-hex "brand moment"), this is a functional settings page, so the
   hero and every icon tile stay theme-aware (var(--*-bg)/var(--*)) the same
   way AI Usage/List of API's icon badges do, rather than fixed light-mode
   hex colours. */
.ws-hero{
  position:relative;overflow:hidden;border-radius:14px;margin-bottom:16px;
  padding:20px 26px;display:flex;align-items:center;gap:18px;flex-wrap:wrap;
  background:linear-gradient(120deg, var(--subtle-bg) 0%, var(--green-bg) 130%);
  border:1px solid var(--border);
}
.ws-hero-icon{
  flex:none;width:52px;height:52px;border-radius:14px;
  display:flex;align-items:center;justify-content:center;
  background:linear-gradient(135deg,#31d675,#1fa855);color:#fff;
  box-shadow:0 6px 16px rgba(31,168,85,.35);
}
.ws-hero-icon svg{width:24px;height:24px;}
.ws-hero-text{flex:1;min-width:200px;}
.ws-hero-title{font-size:21px;font-weight:800;color:var(--text-900);}
.ws-hero-sub{font-size:13px;color:var(--text-500);margin-top:2px;}
.ws-hero-status{flex:none;display:flex;flex-direction:column;align-items:flex-end;gap:4px;}
.ws-status-pill{display:inline-flex;align-items:center;gap:5px;padding:5px 14px;border-radius:999px;font-size:13px;font-weight:700;}
.ws-status-pill.on{background:var(--green-bg);color:var(--green);}
.ws-status-pill.off{background:var(--amber-bg);color:var(--amber);}
.ws-hero-caption{font-size:11.6px;color:var(--text-400);}
.ws-hero-art{flex:none;display:flex;align-items:center;justify-content:center;}
.ws-hero-art-blob{
  position:relative;width:74px;height:74px;border-radius:50%;
  background:radial-gradient(circle, var(--green-bg) 0%, transparent 75%);
  display:flex;align-items:center;justify-content:center;
}
.ws-hero-art-phone{
  width:38px;height:38px;border-radius:11px;background:linear-gradient(135deg,#31d675,#1fa855);
  color:#fff;display:flex;align-items:center;justify-content:center;box-shadow:0 6px 14px rgba(31,168,85,.3);
}
.ws-hero-art-bell{position:absolute;top:-4px;right:0;color:var(--gold-600);background:var(--card);border-radius:50%;padding:3px;}
.ws-hero-art-leaf{position:absolute;bottom:-2px;left:-6px;color:var(--green);background:var(--card);border-radius:50%;padding:3px;}
@media (max-width:760px){
  .ws-hero{padding:16px 18px;}
  .ws-hero-status{align-items:flex-start;}
  .ws-hero-art{display:none;}
}

.ws-tabbar .ra-tab{display:inline-flex;align-items:center;gap:6px;}

.ws-row{display:flex;align-items:center;gap:14px;padding:14px 4px;border-top:1px solid var(--border);flex-wrap:wrap;}
.ws-row:first-child{border-top:none;padding-top:4px;}
.ws-row-icon{display:flex;align-items:center;justify-content:center;flex:0 0 auto;width:42px;height:42px;border-radius:11px;}
.ws-row-icon svg{width:20px;height:20px;}
.ws-row-icon.blue{background:var(--blue-bg);color:var(--blue);}
.ws-row-icon.green{background:var(--green-bg);color:var(--green);}
.ws-row-icon.purple{background:var(--purple-bg, #f1e9fe);color:var(--purple, #7c4dff);}
.ws-row-icon.gold{background:var(--gold-bg, #fff6e0);color:var(--gold-600);}
.ws-row-text{flex:1 1 220px;min-width:180px;}
.ws-row-label{font-weight:700;font-size:14px;color:var(--text-900);}
.ws-row-desc{font-size:12.4px;color:var(--text-500);margin-top:1px;}
.ws-row-control{flex:1 1 280px;max-width:420px;}
.ws-row-control input,.ws-row-control select{
  border:1px solid var(--border);border-radius:8px;padding:9px 12px;font-size:13.4px;
  font-family:inherit;background:var(--card);color:var(--text-900);width:100%;
}
.ws-row-control input:focus,.ws-row-control select:focus{outline:none;border-color:var(--gold-500);}
@media (max-width:640px){
  .ws-row{padding:12px 0;}
  .ws-row-control{max-width:none;flex:1 1 100%;}
}

.ws-band{
  display:flex;align-items:center;gap:14px;flex-wrap:wrap;position:relative;overflow:hidden;
  border-radius:12px;padding:16px 18px;margin-top:14px;
}
.ws-band.green{background:var(--green-bg);}
.ws-band.gold{background:var(--gold-bg, #fff6e0);}
.ws-band .ws-row-text{flex:1 1 260px;}
.ws-band-checks{display:flex;gap:16px;flex-wrap:wrap;z-index:1;}
.ws-check{display:inline-flex;align-items:center;gap:6px;font-size:13px;color:var(--text-700, var(--text-900));white-space:nowrap;}
.ws-band-ghost{position:absolute;right:6px;bottom:-10px;opacity:.14;pointer-events:none;z-index:0;}
.ws-band.green .ws-band-ghost{color:var(--green);}
.ws-band.gold .ws-band-ghost{color:var(--gold-600);}
@media (max-width:640px){.ws-band-checks{gap:10px 16px;}}

.ws-actions{display:flex;align-items:center;flex-wrap:wrap;gap:10px;margin-top:20px;}
.ws-actions button{display:inline-flex;align-items:center;gap:6px;}
.ws-actions-spacer{flex:1 1 auto;}
@media (max-width:640px){
  .ws-actions{flex-direction:column;align-items:stretch;}
  .ws-actions-spacer{display:none;}
}

.ws-subsection{margin-top:24px;padding-top:20px;border-top:1px solid var(--border);}
.ws-subsection-title{display:flex;align-items:center;gap:8px;font-weight:700;font-size:14.5px;color:var(--text-900);}

/* ---------- WhatsApp Settings > Templates (whatsappSettings.js) ----------
   Redesigned per "Template.png". ws-tpl-stats sits inside the shared
   .ra-tabbar row (margin-left:auto), injected only on the Templates tab. */
.ws-tpl-stats{margin-left:auto;display:flex;gap:10px;flex-wrap:wrap;align-items:center;}
.ws-tpl-stat{display:flex;align-items:center;gap:8px;border:1px solid var(--border);border-radius:10px;padding:6px 12px;background:var(--card);}
.ws-tpl-stat-icon{display:flex;align-items:center;justify-content:center;flex:0 0 auto;width:28px;height:28px;border-radius:8px;}
.ws-tpl-stat-icon svg{width:16px;height:16px;}
.ws-tpl-stat-icon.blue{background:var(--blue-bg);color:var(--blue);}
.ws-tpl-stat-icon.green{background:var(--green-bg);color:var(--green);}
.ws-tpl-stat-value{font-size:15px;font-weight:800;color:var(--text-900);line-height:1.1;}
.ws-tpl-stat-label{font-size:10.6px;color:var(--text-500);white-space:nowrap;}

.ws-tpl-banner{
  display:flex;align-items:center;gap:8px;background:var(--subtle-bg);border-radius:10px;
  padding:10px 14px;font-size:12.8px;color:var(--text-600);margin-bottom:16px;
}
.ws-tpl-banner svg{flex:none;color:var(--gold-600);}

.ws-tpl-grid{display:grid;grid-template-columns:1fr 1fr;gap:18px;}
@media (max-width:900px){.ws-tpl-grid{grid-template-columns:1fr;}}
.ws-tpl-card{border:1px solid var(--border);border-radius:14px;padding:16px 18px;background:var(--card);}
.ws-tpl-card-head{display:flex;align-items:flex-start;gap:12px;flex-wrap:wrap;margin-bottom:12px;}
.ws-tpl-icon{display:flex;align-items:center;justify-content:center;flex:0 0 auto;width:44px;height:44px;border-radius:12px;}
.ws-tpl-icon svg{width:22px;height:22px;}
.ws-tpl-icon.pink{background:var(--pink-bg, #ffe4f0);color:var(--pink, #d6336c);}
.ws-tpl-icon.purple{background:var(--purple-bg, #f1e9fe);color:var(--purple, #7c4dff);}
.ws-tpl-icon.blue{background:var(--blue-bg);color:var(--blue);}
.ws-tpl-icon.gold{background:var(--gold-bg, #fff6e0);color:var(--gold-600);}
.ws-tpl-head-text{flex:1 1 180px;min-width:140px;}
.ws-tpl-title-row{display:flex;align-items:center;gap:8px;flex-wrap:wrap;}
.ws-tpl-title{font-weight:800;font-size:15.5px;color:var(--text-900);}
.ws-status-pill.sm{padding:2px 9px;font-size:11px;gap:3px;}
.ws-tpl-desc{font-size:12.4px;color:var(--text-500);margin-top:2px;}
.ws-tpl-edit-btn{display:inline-flex;align-items:center;gap:6px;flex:0 0 auto;}
.ws-tpl-preview-label{font-size:11.6px;font-weight:700;color:var(--text-500);margin-bottom:6px;}
.ws-tpl-preview{
  position:relative;background:var(--green-bg);border-radius:10px;padding:14px 40px 14px 14px;
  font-size:13px;line-height:1.55;color:var(--text-800, var(--text-900));white-space:pre-line;
}
.ws-tpl-preview-icon{position:absolute;right:10px;bottom:10px;color:var(--green);opacity:.7;}
.ws-tpl-vars{display:flex;align-items:center;gap:8px;flex-wrap:wrap;margin-top:10px;}
.ws-tpl-vars-label{display:inline-flex;align-items:center;gap:5px;font-size:12px;font-weight:700;color:var(--text-500);}
.ws-tpl-var-tag{
  background:var(--gold-bg, #fff6e0);color:var(--gold-600);border-radius:999px;
  padding:3px 10px;font-size:11.6px;font-family:'SFMono-Regular',Consolas,'Liberation Mono',Menlo,monospace;
}
@media (max-width:640px){
  .ws-tpl-card-head{flex-direction:column;}
  .ws-tpl-edit-btn{align-self:flex-start;}
}

/* ---------- Admin > Reports hub (rolesAccess.js reportsHubHtml()) ----------
   A consolidation landing page - own dedicated copy of the icon-badge +
   chevron-row pattern (mirrors familyTree.js's .pf-settings-row) rather
   than reusing that class directly, per this codebase's per-page-copy
   convention. */
.ra-reports-grid{display:grid;grid-template-columns:1fr 1fr;gap:14px;}
@media (max-width:800px){.ra-reports-grid{grid-template-columns:1fr;}}
.ra-reports-card{
  display:flex;align-items:center;gap:14px;padding:16px 18px;
  border:1px solid var(--border);border-radius:12px;background:var(--card);
  cursor:pointer;transition:box-shadow .15s ease-out, border-color .15s ease-out;
}
.ra-reports-card:hover,.ra-reports-card:focus-visible{box-shadow:var(--shadow);border-color:var(--gold-500);outline:none;}
.ra-reports-icon{display:flex;align-items:center;justify-content:center;flex:0 0 auto;width:44px;height:44px;border-radius:12px;}
.ra-reports-icon svg{width:22px;height:22px;}
.ra-reports-icon.blue{background:var(--blue-bg);color:var(--blue);}
.ra-reports-icon.green{background:var(--green-bg);color:var(--green);}
.ra-reports-icon.purple{background:var(--purple-bg, #f1e9fe);color:var(--purple, #7c4dff);}
.ra-reports-icon.gold{background:var(--gold-bg, #fff6e0);color:var(--gold-600);}
.ra-reports-icon.red{background:var(--red-bg);color:var(--red);}
.ra-reports-text{flex:1;min-width:0;}
.ra-reports-title{font-weight:700;font-size:14.5px;color:var(--text-900);}
.ra-reports-desc{font-size:12.4px;color:var(--text-500);margin-top:2px;}
.ra-reports-chevron{flex:none;font-size:20px;color:var(--text-400);}

/* ---------- Profile — Account Settings (Under Profile, add options.txt) ----------
   Family Head only (familyTree.js accountSettingsCardHtml()) - a plain list
   of settings rows with a trailing chevron, appended into the existing
   Profile page's .pf-page via renderProfileCard()'s cfg.extraHtml slot
   (never rendered for a Family Member's own-profile view, which never
   passes that slot). Matches the doc's own "PROFILE UI" mockup: label,
   description, chevron. */
.pf-settings-card{padding:6px 0;}
.pf-settings-title{font-size:13px;font-weight:700;color:var(--text-900);padding:10px 20px 4px;}
.pf-settings-row{
  display:flex;align-items:center;justify-content:space-between;gap:14px;
  padding:14px 20px;cursor:pointer;border-top:1px solid var(--border);
  transition:background .15s ease;
}
.pf-settings-row:hover, .pf-settings-row:focus-visible{background:var(--subtle-bg);}
.pf-settings-row:focus-visible{outline:2px solid var(--gold-500);outline-offset:-2px;}
.pf-settings-label{font-size:14px;font-weight:600;color:var(--text-900);}
.pf-settings-desc{font-size:12.4px;color:var(--text-400);margin-top:2px;}
.pf-settings-chevron{font-size:20px;line-height:1;color:var(--text-400);flex:none;}
.pf-settings-row-danger .pf-settings-label{color:var(--red);}
.pf-settings-row-danger:hover, .pf-settings-row-danger:focus-visible{background:var(--red-bg);}
@media(max-width:640px){
  .pf-settings-row{padding:14px 16px;}
  .pf-settings-title{padding:10px 16px 4px;}
}

/* ---------- My Subscription (Profile, Family Head only) ---------- */
.msub-card-header{justify-content:space-between;flex-wrap:wrap;}
.msub-summary-top{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:14px;}
.msub-plan-name{font-size:19px;font-weight:700;color:var(--text-900);}
.msub-cancel-note{margin:0 0 16px;}
.msub-grid{margin-bottom:18px;}
.msub-actions{display:flex;flex-wrap:wrap;gap:10px;}
.msub-history{margin-top:22px;padding-top:18px;border-top:1px solid var(--border);}
.msub-history-title{font-size:13px;font-weight:700;color:var(--text-900);margin-bottom:6px;}
@media(max-width:640px){
  .msub-card-header{flex-direction:column;align-items:flex-start;}
  .msub-actions .btn{flex:1 1 auto;}
}
/* ---------- Delete My Account — "email me a backup" offer ---------- */
.pf-del-backup{
  margin:0 0 14px;padding:12px 14px;border:1px solid var(--border);border-radius:10px;
  background:var(--subtle-bg);
}
.pf-del-backup-text{font-size:12.8px;color:var(--text-700, var(--text-900));line-height:1.5;margin-bottom:10px;}
.pf-del-backup-status-ok{color:var(--green);margin:8px 0 0;}
.pf-del-backup-status-error{color:var(--red);margin:8px 0 0;}
/* ============================================================
   User Details redesign (Security -> Nest Users -> User Details),
   per "User Details-Admin.png". Row-menu classes are this page's OWN copy
   (ra-ud- prefix) rather than reusing .ins-row-menu*, matching this
   codebase's existing "separate copy per page instance" convention (see
   the comment above .ins-row-menu-wrap).
   ============================================================ */
.ra-ud-kpis{margin-bottom:16px;gap:14px;}
.ra-ud-kpi-card{padding:16px 18px 20px;}
.ra-ud-kpi-icon{
  position:absolute;right:14px;bottom:14px;width:34px;height:34px;
  display:flex;align-items:center;justify-content:center;border-radius:50%;
  opacity:.55;z-index:0;
}
.ra-ud-kpi-icon.blue{color:var(--blue);}
.ra-ud-kpi-icon.green{color:var(--green);}
.ra-ud-kpi-icon.purple{color:var(--purple);}
.ra-ud-kpi-icon.gold{color:var(--gold-600);}
.ra-ud-kpi-icon.red{color:var(--red);}

.ra-ud-filters{margin-bottom:14px;}
.ra-ud-search-row{margin-bottom:10px;}
.ra-ud-search-box{
  display:flex;align-items:center;gap:8px;max-width:480px;
  border:1px solid var(--border);border-radius:10px;padding:9px 14px;
  background:var(--card);color:var(--text-400);
}
.ra-ud-search-box input{
  flex:1;border:none;background:transparent;font:inherit;font-size:13.4px;
  color:var(--text-900);outline:none;
}
.ra-ud-pill-row{display:flex;flex-wrap:wrap;gap:10px;align-items:center;}
.ra-ud-pill{
  display:inline-flex;align-items:center;gap:6px;
  border:1px solid var(--border);border-radius:20px;padding:7px 12px;
  background:var(--card);color:var(--text-600);cursor:pointer;
}
.ra-ud-pill select{
  border:none;background:transparent;font:inherit;font-size:12.8px;
  color:var(--text-900);outline:none;cursor:pointer;
  /* native arrow hidden - the pill supplies its own chevron icon */
  appearance:none;-webkit-appearance:none;padding-right:2px;
}
.ra-ud-pill svg:last-child{color:var(--text-400);flex:none;}
.ra-ud-export-wrap{position:relative;margin-left:auto;}
/* position:relative only - NOT display:flex - on the <td> itself: a flex
   display on a table cell breaks its normal row-height stretching (it sizes
   to its own content instead of the row), which visibly floated this cell's
   content out of line with its sibling cells' vertical centering. The single
   child button is right-aligned via text-align instead. */
.ra-ud-row-menu-wrap{position:relative;text-align:right;}
.ra-ud-row-menu-btn{
  border:none;background:transparent;color:var(--text-400);
  font-size:18px;line-height:1;padding:4px 8px;border-radius:6px;cursor:pointer;
}
.ra-ud-row-menu-btn:hover{background:var(--subtle-bg);color:var(--text-600);}
.ra-ud-row-menu{
  display:none;position:fixed;z-index:80;min-width:176px;padding:5px;
  background:var(--card);border:1px solid var(--border);border-radius:9px;
  box-shadow:var(--shadow-md);
}
.ra-ud-row-menu.open{display:block;}
.ra-ud-row-menu-item{
  display:block;width:100%;text-align:left;font-family:inherit;font-size:13px;
  padding:8px 10px;border:none;border-radius:6px;background:transparent;
  color:var(--text-600);cursor:pointer;
}
.ra-ud-row-menu-item:hover{background:var(--subtle-bg);}
.ra-ud-row-menu-danger{color:var(--red);}
.ra-ud-row-menu-danger:hover{background:color-mix(in srgb, var(--red-bg) 70%, transparent);}

.ra-ud-table td, .ra-ud-table th{vertical-align:middle;}
.ra-ud-name-cell{display:flex;align-items:center;gap:8px;min-width:0;}
.ra-ud-crown{display:inline-flex;color:var(--gold-600);flex:none;}
.ra-ud-avatar{
  width:var(--ra-ud-avatar-size, 32px);height:var(--ra-ud-avatar-size, 32px);
  border-radius:50%;flex:none;display:flex;align-items:center;justify-content:center;
  font-size:12px;font-weight:700;color:#fff;
}
.ra-ud-avatar.blue{background:var(--blue);}
.ra-ud-avatar.purple{background:var(--purple);}
.ra-ud-avatar.green{background:var(--green);}
.ra-ud-avatar.gold{background:var(--gold-600);}
.ra-ud-avatar.pink{background:var(--pink);}
.ra-ud-avatar.teal{background:#0d9488;}
.ra-ud-type-pill{font-size:11.5px;font-weight:600;white-space:nowrap;}
.ra-ud-status-dot{display:inline-flex;align-items:center;gap:6px;font-size:12.8px;color:var(--text-700, var(--text-900));}
.ra-ud-status-dot i{width:8px;height:8px;border-radius:50%;flex:none;}
.ra-ud-status-dot.active i{background:var(--green);}
.ra-ud-status-dot.inactive i{background:var(--text-400);}
/* inline-flex on an inner span (see userReportTableHtml()), not on the <td>
   itself - same reason as .ra-ud-row-menu-wrap above. */
.ra-ud-members-cell{display:inline-flex;align-items:center;gap:6px;font-size:12.8px;color:var(--text-600);white-space:nowrap;}
.ra-ud-members-cell svg{color:var(--purple);flex:none;}
.ra-ud-sort-btn{
  display:inline-flex;align-items:center;gap:4px;border:none;background:transparent;
  font:inherit;font-size:inherit;font-weight:inherit;color:inherit;cursor:pointer;padding:0;
}
.ra-ud-sort-arrow{display:inline-flex;color:var(--text-400);}
.ra-ud-sort-btn.active .ra-ud-sort-arrow{color:var(--gold-600);}
.ra-ud-sort-arrow.desc{transform:rotate(180deg);}

.ra-ud-pager-controls{display:flex;align-items:center;gap:8px;}
.ra-ud-pager-btn{
  display:flex;align-items:center;justify-content:center;width:30px;height:30px;
  border:1px solid var(--border);border-radius:8px;background:var(--card);
  color:var(--text-600);cursor:pointer;
}
.ra-ud-pager-btn:disabled{opacity:.4;cursor:not-allowed;}
.ra-ud-pager-btn:not(:disabled):hover{background:var(--subtle-bg);}
.ra-ud-pager-page{
  min-width:30px;height:30px;padding:0 4px;border-radius:8px;
  background:var(--gold-500);color:#fff;font-weight:700;font-size:13px;
  display:flex;align-items:center;justify-content:center;
}
@media(max-width:880px){
  .ra-ud-search-box{max-width:none;}
  .ra-ud-export-wrap{margin-left:0;}
  .ra-ud-pill{flex:1 1 auto;}
}
/* Foreign Investments: the Investment Type chooser on the merged
   "Market Investments" tab (Stocks / Mutual Funds / ETFs share one tab, see
   ForeignInvestmentsPage.marketTypeSelectHtml). The label sits beside the
   dropdown so the control reads as a choice, not a filter that might be off. */
.fi-type-select-wrap{display:inline-flex;align-items:center;gap:8px;}
.fi-type-select-label{font-size:12px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--text-400);white-space:nowrap;}
.fi-type-select-wrap .fi-owner-select{min-width:180px;}
@media (max-width:720px){
  .fi-type-select-wrap{width:100%;}
  .fi-type-select-wrap .fi-owner-select{min-width:0;flex:1;}
}

/* Foreign Investments category card: its header carries a title, two selects
   and up to four buttons. With the longer "Global Market Investments" title
   and the new Investment Type select, the row ran out of width and squeezed
   the title into three lines - so it wraps instead, and the title keeps a
   sensible minimum. */
.fi-cat-card .card-header{flex-wrap:wrap;row-gap:10px;}
.fi-cat-card .card-header > :first-child{flex:1 1 240px;min-width:200px;}
.fi-cat-card .card-header .card-title{white-space:normal;}

/* ================= Update Balance — three update methods =================
   "Insurance Market value Update.txt". Scoped entirely to the .ub-* classes
   assets.js emits inside the Update Balance modal, so no other table, modal or
   segmented control in the app is affected. Colours/radii/spacing all come from
   the existing tokens - nothing new is introduced. */
.ub-method{margin:0 0 14px;}
.ub-method-label{font-size:12px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:var(--text-400);margin-bottom:6px;}
.ub-seg{display:inline-flex;flex-wrap:wrap;gap:2px;padding:3px;border:1px solid var(--border);border-radius:10px;background:var(--subtle-bg);}
.ub-seg-btn{border:none;background:transparent;font-family:inherit;font-size:13px;font-weight:600;color:var(--text-600);
  padding:7px 14px;border-radius:8px;cursor:pointer;transition:background .15s ease,color .15s ease;}
.ub-seg-btn:hover{background:var(--card);}
.ub-seg-btn.active{background:var(--card);color:var(--blue);box-shadow:var(--shadow);}
.ub-method-help{margin-top:7px;}
.ub-table table{width:100%;}
.ub-table .ub-value-input{width:100%;min-width:120px;max-width:170px;}
.ub-table .ub-nav-input,.ub-table .ub-units-input{width:100%;min-width:90px;max-width:120px;}
.ub-msg{font-size:12px;color:var(--red);margin-top:3px;min-height:0;}
.ub-unavailable{font-size:12.5px;color:var(--text-400);margin-bottom:5px;}
.ub-auto-value{font-weight:700;}
.ub-group-head{cursor:pointer;background:var(--subtle-bg);}
.ub-group-head:hover{background:var(--blue-bg);}
.ub-caret{display:inline-block;width:14px;color:var(--text-400);}
.ub-group-total{font-weight:700;text-align:right;}
.ub-folio-row > td:first-child{padding-left:26px;}
.ub-folio-name{font-size:13px;color:var(--text-600);}
.ub-total-bar{display:flex;justify-content:space-between;align-items:center;gap:12px;
  margin-top:14px;padding:11px 14px;border:1px solid var(--border);border-radius:10px;background:var(--subtle-bg);font-size:14px;}
.ub-total-bar strong{font-size:16px;}
.ub-error{margin-top:10px;padding:9px 12px;border-radius:8px;font-size:13px;
  color:var(--red);background:var(--red-bg);border:1px solid var(--red);}
.ub-review{display:flex;flex-direction:column;gap:8px;max-height:44vh;overflow:auto;}
.ub-review-row{display:flex;justify-content:space-between;align-items:baseline;gap:14px;
  padding:9px 11px;border:1px solid var(--border);border-radius:8px;font-size:13.5px;}

/* §19 — on a narrow screen the table becomes stacked cards rather than scrolling
   sideways; each cell keeps its column name as a label so nothing loses meaning. */
@media (max-width: 720px){
  .ub-table table,.ub-table thead,.ub-table tbody,.ub-table tr,.ub-table td{display:block;width:100%;}
  .ub-table thead{display:none;}
  .ub-table tr{border:1px solid var(--border);border-radius:10px;margin-bottom:10px;padding:8px 10px;}
  .ub-table td{border:none;padding:5px 0;display:flex;justify-content:space-between;align-items:center;gap:12px;}
  .ub-table td::before{content:attr(data-label);font-size:12px;color:var(--text-400);font-weight:600;}
  .ub-table .ub-value-input{max-width:60%;}
  .ub-folio-row > td:first-child{padding-left:0;}
  .ub-seg{display:flex;width:100%;}
  .ub-seg-btn{flex:1 1 auto;text-align:center;}
}
.ub-group:not(.open) .ub-folio-row{display:none;}
.ub-total-bar label{cursor:pointer;}
.ub-total-note{display:block;font-weight:400;margin-top:2px;}
.ub-total-ro{display:flex;flex-direction:column;align-items:flex-end;}
.ub-total-input-wrap{display:inline-flex;align-items:center;gap:4px;font-size:16px;font-weight:700;}
.ub-total-input{width:170px;font-size:16px;font-weight:700;text-align:right;padding:7px 10px;
  border:1px solid var(--border);border-radius:8px;background:var(--card);color:inherit;font-family:inherit;}
.ub-total-input:focus{outline:none;border-color:var(--blue);box-shadow:0 0 0 3px var(--blue-bg);}
.ub-total-input.field-error{border-color:var(--red);}
@media (max-width: 720px){
  .ub-total-bar{flex-direction:column;align-items:stretch;gap:8px;}
  .ub-total-input{width:100%;}
  .ub-total-ro{align-items:flex-start;}
}
/* "Existing Market Value" - the read-only twin of the editable figure beside it.
   Muted and non-selectable-looking so it reads as a reference, never a field
   someone might try to type into. */
.ub-existing{white-space:nowrap;font-weight:600;color:var(--text-400);background:var(--subtle-bg);}
.ub-existing-th{white-space:nowrap;}
.ub-group-head .ub-existing{background:transparent;}
/* The EMI-vs-End-Date discrepancy mark on an obligation row (liabilities.js).
   Amber, not red: the record is usable, one of the two figures just disagrees
   with the other. */
.liab-tenure-mark{color:var(--amber, #b7791f);cursor:help;font-size:12.5px;vertical-align:1px;}

/* Auto mode: the "N of M rows calculated" note when some rows have no
   automatic value. Amber, not red - nothing is wrong, but the total beside it
   deliberately does not cover the whole holding. */
.ub-total-note-warn { color: #92400E; font-weight: 600; }
