/* ============================================================================
 * Bonfire — visual identity.
 * Premium dark crypto dashboard for the OG ecosystem on PulseChain.
 * Style: bento-grid financial dashboard, burn-centric gold identity.
 * Palette: surfaces + cyan accent taken from the official finvesta.eco exchange
 * (bg/panel/panel2/panel3, --line/--line2, #00f6ff cyan with #65dbe8 as the
 * softer companion, graph-paper body bg).
 * The gold family stays ours — it is the dashboard's FINVESTA-burn identity.
 * Fonts: Fira Sans (UI) + Fira Code (numbers/addresses — tabular by nature).
 * Heroes: "Supply & Burn" stats and the goal progress bars.
 *
 * Invariants preserved from the functional build:
 *   - numeric cells never shift layout (mono/tabular + min-width where inline);
 *   - no layout shift on data refresh (card shells persist, only bodies rerender);
 *   - AA contrast, visible focus, reduced-motion respected, mobile-first.
 * ========================================================================== */

:root {
  /* surfaces — finvesta.eco stack: bg < panel < panel2 < panel3 (nested
     surfaces are LIGHTER than their card, matching the official layering) */
  --bg:          #070a0c;
  --bg-glass:    rgba(7, 10, 12, .82);
  --card:        #0d1215;   /* panel */
  --card-2:      #11191d;   /* panel2 — card gradient top */
  --input:       #162126;   /* panel3 — inputs, buttons, nested tiles/rows */
  --border:      #274052;
  --border-soft: rgba(101, 219, 232, .16);   /* finvesta.eco --line2 */
  --hair:        rgba(255, 255, 255, .06);

  /* text */
  --text:  #f4fbff;
  --muted: #8da5b0;
  --faint: #7d939e;   /* AA on all surfaces: 5.87:1 on --card, 5.11:1 on --input (lightest) */

  /* brand + semantic */
  --gold:   #f5b035;   /* signature: burn + goal progress. SWAPPED to Missor blue
                          by the hero-token toggle (theme accent for the token cards). */
  --gold-2: #f7c56a;
  /* Brand gold that NEVER swaps with the token toggle: the app chrome (section
     titles, wizard, header title, ticker FIN, footer/Donate) must stay gold in
     both token modes, or the app loses its own identity when MISSOR is active. */
  --brand-gold:   #f5b035;
  --brand-gold-2: #f7c56a;
  --missor-blue: #8fd0ff;   /* Missor identity — fixed, never swapped */
  --burn-a: #f59838;
  --burn-b: #f6c34d;
  /* "fire" gradient for the Supply & Burn bar: hot pink -> coral -> amber */
  --fire-1: #f5327a;
  --fire-2: #fb5c3a;
  --fire-3: #f7a838;
  --accent:   #00f6ff;   /* finvesta.eco cyan — interactive / links / In LP */
  --accent-2: #65dbe8;   /* softer companion — large fills, borders, hover washes */
  --up:     #34d399;
  --down:   #f87171;
  --tracked: #ff9142;   /* orange — "wallets in YOUR tracker" highlight + combined-balance row */

  /* geometry */
  --radius:    14px;
  --radius-sm: 10px;
  --pill:      999px;

  --shadow: 0 1px 0 rgba(255,255,255,.03) inset, 0 10px 30px -18px rgba(0,0,0,.75);
  --glow-gold: 0 0 16px rgba(245, 152, 56, .35);

  --t-fast: 130ms;
  --t:      200ms;
  --ease:   cubic-bezier(.4, 0, .2, 1);

  --maxw: 1600px;
  color-scheme: dark;
}

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--bg);
  color: var(--text);
  font-family: 'Fira Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  font-size: 15px;
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* the official finvesta.eco page background: cyan glow top-left + pink glow
   top-right + the faint 48px cyan graph-paper grid. The pink (#ff3bd4) lives
   ONLY here — never as a UI accent. */
body {
  background-color: var(--bg);
  background-image:
    radial-gradient(circle at 8% 16%, rgba(0, 246, 255, .07), transparent 25%),
    radial-gradient(circle at 93% 8%, rgba(255, 59, 212, .07), transparent 23%),
    linear-gradient(rgba(101, 219, 232, .024) 1px, transparent 1px),
    linear-gradient(90deg, rgba(101, 219, 232, .024) 1px, transparent 1px);
  background-size: auto, auto, 48px 48px, 48px 48px;
}

/* ambient brand glow — fixed, non-interactive, behind everything. Gold only:
   the burn identity crowns the page centre; the official cyan/pink corner
   glows (on body above) own the sides. */
body::before {
  content: '';
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(1100px 560px at 50% -12%, rgba(245, 176, 53, .06), transparent 60%);
}

[hidden] { display: none !important; }

a { color: var(--accent); text-decoration: none; transition: color var(--t-fast) var(--ease); }
a:hover { color: var(--gold-2); text-decoration: underline; text-underline-offset: 3px; }

h1, h2, h3 { margin: 0; font-weight: 600; letter-spacing: -.01em; }

/* ---- shared numeric styling: Fira Code is tabular by construction ---- */
.num {
  font-family: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 13.5px;
  min-width: 9ch;
  display: inline-block;
  text-align: right;
  white-space: nowrap;
}

.hint  { color: var(--muted); font-size: 13px; }
.err   { color: var(--down);  font-size: 13px; }

/* visually hidden, still announced — live-region notes and the like */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* ---- focus: single, visible, on-brand ---- */
:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
  border-radius: 6px;
}

/* ============================================================================
 * Controls
 * ========================================================================== */
button {
  font: inherit;
  font-weight: 500;
  background: var(--input);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 8px 14px;
  min-height: 40px;
  cursor: pointer;
  transition: border-color var(--t-fast) var(--ease),
              background var(--t-fast) var(--ease),
              color var(--t-fast) var(--ease),
              transform var(--t-fast) var(--ease);
}
button:hover { border-color: var(--accent-2); background: var(--card-2); }
button:active { transform: translateY(1px); }

/* primary CTA — the one gold action. Brand gold (not the token accent): submit
   CTAs are app chrome, so they stay gold in Missor mode too. This element+attr
   selector (0,1,1) outranks .wz-btn (0,1,0), so the Track submit button takes
   its fill from HERE — it must be brand gold, not the swapping --gold. */
button[type="submit"] {
  background: linear-gradient(180deg, var(--brand-gold-2), var(--brand-gold));
  color: #1a1205;
  border-color: transparent;
  font-weight: 600;
}
button[type="submit"]:hover {
  filter: brightness(1.06);
  background: linear-gradient(180deg, var(--brand-gold-2), var(--brand-gold));
  box-shadow: var(--glow-gold);   /* --glow-gold is a fixed gold rgba (line ~62), never swapped */
}

/* subtle utility buttons */
.btn-copy {
  padding: 5px 11px;
  min-height: 32px;
  font-size: 13px;
  background: transparent;
}

input {
  font: inherit;
  background: var(--input);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 9px 11px;
  min-height: 42px;
  transition: border-color var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
input::placeholder { color: var(--faint); }
input:hover { border-color: #35566e; }
input:focus { outline: none; border-color: var(--gold); box-shadow: 0 0 0 3px rgba(245,176,53,.15); }
input[type="number"] { font-family: 'Fira Code', ui-monospace, monospace; }

code, .addr {
  font-family: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;
}

/* ============================================================================
 * Skeleton shimmer
 * ========================================================================== */
.skeleton { position: relative; overflow: hidden; min-height: 72px; }
.skeleton > * { opacity: 0; }
.skeleton::before {
  content: '';
  position: absolute;
  inset: 12px;
  border-radius: var(--radius-sm);
  background: var(--input);
}
.skeleton::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, .05), transparent);
  animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}
.printer-row.skeleton { min-height: 44px; }
.printer-row.skeleton::before { inset: 8px; }

/* ============================================================================
 * Status strip — slim, sticky, always above the hero (and everything else):
 * live/stale/offline badges. Lives before #topbar in the DOM so the hero can
 * scroll away underneath it while this stays pinned.
 * ========================================================================== */
#badges {
  position: sticky;
  top: 0;
  z-index: 30;
  /* A floor, not a height: the ticker below wraps whenever its tracked tokens
     (however many there are) stop fitting beside the status pills, and the
     strip has to grow with it. A fixed height with the overflow hidden would
     cut prices off in silence in the band between the phone block (~720px) and
     the width where the whole ticker fits on one line (~1030px). */
  min-height: 28px;
  overflow: visible;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  flex-wrap: nowrap;
  padding: 0 clamp(14px, 4vw, 28px);
  font-size: 12px;
  color: var(--muted);
  background: var(--bg-glass);
  border-bottom: 1px solid var(--border-soft);
  backdrop-filter: blur(10px) saturate(140%);
  -webkit-backdrop-filter: blur(10px) saturate(140%);
}

/* ============================================================================
 * Bonfire header — "Ember Hero": a fire wordmark + rising embers above a live
 * ecosystem stats bar (both hero tokens' burn%, printers, Trifecta Bonds).
 * FIXED app identity — it never swaps with the active token: the fire/gold/blue
 * accents are fixed literals or --brand-gold-2, never the swapping --gold.
 * ========================================================================== */
#topbar {
  position: relative;
  overflow: hidden;
  isolation: isolate;
  border-bottom: 1px solid var(--border-soft);
  background:
    radial-gradient(120% 90% at 50% 118%, rgba(232, 67, 31, .20), rgba(10, 12, 16, 0) 62%),
    radial-gradient(70% 60% at 18% 0%, rgba(63, 169, 255, .05), rgba(10, 12, 16, 0) 60%);
}
#topbar .bf-glow {
  position: absolute; left: 50%; top: 34%; transform: translate(-50%, -50%);
  width: 640px; height: 640px; max-width: 150%; pointer-events: none; z-index: 0;
  background: radial-gradient(circle, rgba(255, 122, 24, .22) 0%, rgba(232, 67, 31, .10) 42%, rgba(10, 12, 16, 0) 72%);
}
#topbar .bf-embers { position: absolute; inset: 0; overflow: hidden; pointer-events: none; z-index: 1; }
#topbar .bf-ember {
  position: absolute; bottom: -12px; left: var(--x, 50%); width: var(--size, 3px); height: var(--size, 3px);
  border-radius: 50%;
  background: radial-gradient(circle, #f7c56a 0%, #ff7a18 55%, rgba(232, 67, 31, 0) 100%);
  box-shadow: 0 0 6px rgba(255, 140, 40, .85), 0 0 12px rgba(255, 122, 24, .45);
  opacity: 0; animation: bf-rise var(--dur, 7s) ease-in var(--delay, 0s) infinite;
}
@keyframes bf-rise {
  0%   { transform: translate(0, 0) scale(.6); opacity: 0; }
  8%   { opacity: 1; }
  55%  { transform: translate(var(--drift, 10px), -150px) scale(1); opacity: .85; }
  100% { transform: translate(calc(var(--drift, 10px) * 2), -300px) scale(.25); opacity: 0; }
}
#topbar .bf-content { position: relative; z-index: 2; max-width: 960px; margin: 0 auto; padding: 48px 40px 40px; text-align: center; }
#topbar .bf-eyebrow {
  display: inline-flex; align-items: center; gap: 8px; font-size: 12px; letter-spacing: .14em;
  font-weight: 700;
  text-transform: uppercase; color: var(--muted); background: var(--card); border: 1px solid var(--border-soft);
  padding: 6px 14px; border-radius: var(--pill); margin: 0 0 22px;
}
#topbar .bf-eyebrow-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0;
  background: linear-gradient(90deg, #f5b035, #ff7a18); box-shadow: 0 0 8px rgba(255, 122, 24, .85);
  animation: bf-dot-pulse 1.4s ease-in-out infinite; }
/* Live-indicator blink: opacity only, so the glow (box-shadow on the same
   element) breathes with the dot at zero paint cost. */
@keyframes bf-dot-pulse { 0%, 100% { opacity: 1; } 50% { opacity: .3; } }
#topbar .bf-wordmark {
  margin: 0; font-size: clamp(46px, 8vw, 92px); font-weight: 800; letter-spacing: -.02em; line-height: 1;
  background: linear-gradient(180deg, #f5b035 0%, #ff7a18 55%, #e8431f 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;
  filter: drop-shadow(0 0 26px rgba(255, 122, 24, .45)) drop-shadow(0 0 60px rgba(232, 67, 31, .25));
  animation: bf-flicker 4.2s ease-in-out infinite;
}
@keyframes bf-flicker {
  0%, 100% { filter: drop-shadow(0 0 26px rgba(255, 122, 24, .45)) drop-shadow(0 0 60px rgba(232, 67, 31, .25)); }
  50%      { filter: drop-shadow(0 0 34px rgba(255, 144, 53, .6)) drop-shadow(0 0 72px rgba(232, 67, 31, .35)); }
}
#topbar .bf-tagline { margin: 16px 0 30px; font-size: clamp(15px, 1.6vw, 19px); color: var(--muted); font-weight: 500; }
#topbar .bf-stats {
  display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 16px 20px;
  background: var(--card); border: 1px solid var(--border); border-radius: 14px; padding: 18px 24px;
}
#topbar .bf-stat { display: flex; align-items: center; gap: 11px; }
#topbar .bf-stat-logo { width: 34px; height: 34px; border-radius: 50%; object-fit: cover; background: var(--card); flex-shrink: 0; }
#topbar .bf-printer-stack { display: flex; align-items: center; flex-shrink: 0; }
#topbar .bf-printer-logo { width: 28px; height: 28px; border-radius: 50%; object-fit: cover; background: var(--card);
  margin-left: -9px; border: 1.5px solid var(--bg); box-shadow: 0 0 8px rgba(0, 246, 255, .22); }
#topbar .bf-printer-logo:first-child { margin-left: 0; }
#topbar .bf-stat-icon.bf-icon-fire { width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0;
  display: grid; place-items: center; font-size: 16px;
  background: linear-gradient(135deg, #f5b035, #ff7a18 55%, #e8431f);
  border: 1px solid rgba(255, 154, 64, .55); box-shadow: 0 0 12px rgba(255, 122, 24, .45); }
#topbar .bf-stat-text { display: flex; flex-direction: column; align-items: flex-start; text-align: left; line-height: 1.22; gap: 1px; }
#topbar .bf-stat-value { font-family: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace; font-variant-numeric: tabular-nums;
  font-weight: 700; font-size: 18px; color: var(--text); }
#topbar .bf-stat-value.bf-v-gold { color: var(--brand-gold-2); }
#topbar .bf-stat-value.bf-v-blue { color: var(--missor-blue); }
#topbar .bf-stat-value.bf-v-teal { color: var(--accent); }
#topbar .bf-stat-value.bf-v-fire {
  background: linear-gradient(90deg, #f5b035, #ff7a18 55%, #e8431f);
  -webkit-background-clip: text; background-clip: text; color: transparent; -webkit-text-fill-color: transparent;
}
#topbar .bf-stat-name { font-size: 11.5px; color: var(--muted); white-space: nowrap; }
/* One row, or no dividers at all. `.bf-stats` wraps the moment its content
   needs more than its inner width — measured live: the four stats plus three
   dividers need 666px, the box offers 670px at a 800px viewport, so it breaks
   into two rows just under 796px and leaves a divider dangling at the end of
   the first line, separating nothing (which is what it looked like on a
   tablet). So the divider is OFF by default and only earns its place on the
   single-row layout. `min-width`, not a max-width cutoff at the measured
   breaking point, because the figures grow with the data: a five-digit bond
   count alone adds ~33px, and 900px leaves ~100px of slack over that worst
   case while a cutoff at 800 would start dangling again. */
#topbar .bf-divider { width: 1px; height: 30px; background: var(--border); flex-shrink: 0; display: none; }
@media (min-width: 900px) { #topbar .bf-divider { display: block; } }
@media (max-width: 480px) {
  #topbar .bf-content { padding: 36px 18px 28px; }
  #topbar .bf-stats { padding: 16px; gap: 16px 14px; }
  #topbar .bf-stat-name { white-space: normal; }
}
@media (prefers-reduced-motion: reduce) {
  #topbar .bf-embers { display: none; }
  #topbar .bf-wordmark { animation: none; filter: drop-shadow(0 0 30px rgba(255, 130, 40, .5)) drop-shadow(0 0 66px rgba(232, 67, 31, .3)); }
  #topbar .bf-eyebrow-dot { animation: none; }
}


#badge-updated {
  font-family: 'Fira Code', ui-monospace, monospace;
  font-size: 11.5px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--muted);
}
#badge-updated:not(:empty)::before {
  content: '';
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--up);
  box-shadow: 0 0 8px var(--up);
  animation: livepulse 2.4s ease-in-out infinite;
}
@keyframes livepulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }

/* header price ticker (left of the "Updated" badge); margin-right:auto pins it
   left while the badges stay right in the flex-end row */
#badge-prices {
  margin-right: auto;
  display: flex;
  align-items: center;
  gap: 12px;
  min-width: 0;
  /* Wrap rather than clip: this is a shrinkable flex item, so when the row is
     short of space it shrinks below its content width, and a hidden overflow
     would silently eat whole prices. Wrapping keeps every price on the page and
     is self-triggering, so no breakpoint has to guess where it stops fitting.
     white-space:nowrap stays: it keeps an individual price from breaking. */
  flex-wrap: wrap;
  row-gap: 3px;
  font-family: 'Fira Code', ui-monospace, monospace;
  font-size: 11.5px;
  white-space: nowrap;
}
#badge-prices .hp { display: inline-flex; align-items: baseline; gap: 5px; }
#badge-prices .hp-t { font-weight: 600; }
#badge-prices .tp { color: var(--text); }
#badge-prices .tp sub { font-size: .72em; }
#badge-prices .hp-sep { color: var(--border-soft); }
/* Mobile: keep the ticker visible. The 28px bar can't fit every tracked
   token's price plus the status badges on one line, so it grows: the prices
   take a full-width block that WRAPS across rows (every token stays visible,
   no swipe), with the status badges on the row below. Separators are dropped
   in favour of the flex gap so a stray "·" never lands at a line break. */
@media (max-width: 719px) {
  #badges {
    position: static;   /* the price/status bar is NOT sticky on a phone: it scrolls away with the page */
    flex-wrap: wrap;    /* the pills drop to their own row under the full-width ticker */
    padding-top: 4px;
    padding-bottom: 4px;
    row-gap: 2px;
  }
  #badge-prices {
    flex-basis: 100%;
    margin-right: 0;
    justify-content: flex-start;
    font-size: 10px;          /* a touch smaller so all of them fit in two rows, keeping the bar slim */
    column-gap: 11px;
    row-gap: 3px;
  }
  #badge-prices .hp-sep { display: none; }
}

.badge-pill {
  border-radius: var(--pill);
  padding: 3px 10px;
  font-weight: 500;
  font-size: 11.5px;
  display: inline-flex;
  align-items: center;
  gap: 5px;
}
#badge-stale   { background: rgba(245, 152, 56, .16); color: var(--gold-2); border: 1px solid rgba(245,152,56,.3); }
#badge-offline { background: rgba(248, 113, 113, .16); color: var(--down);   border: 1px solid rgba(248,113,113,.3); }


/* ============================================================================
 * Layout
 * ========================================================================== */
main { max-width: var(--maxw); margin: 0 auto; padding: clamp(16px, 3vw, 28px) clamp(14px, 4vw, 28px) 40px; }

.section-title {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .13em;
  color: var(--faint);
  margin: 28px 2px 12px;
  display: flex;
  align-items: center;
  gap: 10px;
}
/* "Track Wallets" label: gold, 50% larger than the base section title */
.section-title-track {
  font-size: 18px; /* 12px base × 1.5 */
  color: var(--brand-gold);
}
.section-title::after {
  content: '';
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--border-soft), transparent);
}
#holders-updated {
  white-space: nowrap;
}

/* ============================================================================
 * Card primitive
 * ========================================================================== */
.card {
  background: linear-gradient(180deg, var(--card-2), var(--card));
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  padding: 16px;
  box-shadow: var(--shadow);
}
.card h2, .card h3 { font-size: 14px; margin: 0 0 12px; letter-spacing: .01em; }
.card .body, .card > ul.body { margin: 0; padding: 0; list-style: none; }

/* ============================================================================
 * Setup / onboarding
 * ========================================================================== */
#setup { margin-bottom: 8px; }

/* ============================================================================
 * Track Wallet wizard — the flagship UX. Four "moments" (invite → scan →
 * reveal → celebrate) shown one at a time inside a single stable shell.
 * Reference: docs/superpowers/specs/assets/2026-07-18-wizard-flow-mockup.html
 * NOTE: moment 2 here is the "printer prints a receipt" variant (chosen by
 * the owner), not the mockup's radar sweep.
 * ========================================================================== */
.wz {
  background: linear-gradient(180deg, var(--card-2), var(--card));
  border: 1px solid var(--border-soft);
  border-radius: var(--radius);
  padding: 20px;
  margin-bottom: 14px;
  box-shadow: var(--shadow);
}
.wz .hl { color: var(--brand-gold); }
.wz .mono { font-family: 'Fira Code', ui-monospace, monospace; }

.wz-dots { display: flex; gap: 6px; margin-bottom: 16px; }
.wz-dots span {
  width: 26px; height: 4px; border-radius: 2px;
  background: var(--border-soft);
  transition: background var(--t) var(--ease);
}
.wz-dots span.on   { background: var(--brand-gold); }
.wz-dots span.done { background: var(--accent); }

.wz-step {
  font-size: 10px; letter-spacing: .18em; text-transform: uppercase;
  color: var(--accent); font-weight: 600; margin-bottom: 8px;
}
.wz-q { font-size: 18px; font-weight: 700; margin: 0 0 14px; color: var(--text); }

.wz-input { display: flex; gap: 10px; flex-wrap: wrap; }
.wz-input input { flex: 1 1 240px; min-width: 0; font-size: 13.5px; }

/* ---- wizard buttons: gold primary / cyan confirm / ghost secondary ---- */
.wz-btn {
  background: linear-gradient(180deg, var(--brand-gold-2), var(--brand-gold));
  color: #1a1205;
  font-weight: 700;
  border: 0;
  border-radius: var(--radius-sm);
  padding: 11px 22px;
  font-size: 14px;
  min-height: 42px;
  cursor: pointer;
  transition: filter var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
/* Re-assert the gold fill on hover/focus: the generic `button:hover`
   (background: var(--card-2), specificity 0,1,1) otherwise beats plain
   `.wz-btn` (0,1,0) and turns the gold CTA dark-grey with unreadable dark
   text. (.teal and .ghost carry their own higher-specificity backgrounds.) */
.wz-btn:hover,
.wz-btn:focus-visible { filter: brightness(1.06); box-shadow: var(--glow-gold); background: linear-gradient(180deg, var(--brand-gold-2), var(--brand-gold)); }
.wz-btn:active  { transform: translateY(1px); }
.wz-btn:disabled { opacity: .55; cursor: default; filter: none; box-shadow: none; }
.wz-btn.teal {
  /* Confirm CTA (Save wallet / Done): the SAME brand gold as the primary
     .wz-btn, so it stays gold in both hero themes, never the token accent.
     Text #1a1205 matches the base button. */
  background: linear-gradient(180deg, var(--brand-gold-2), var(--brand-gold));
  color: #1a1205;
}
.wz-btn.teal:hover { box-shadow: var(--glow-gold); }
.wz-btn.ghost {
  background: transparent;
  color: var(--faint);
  border: 1px solid var(--border);
}
.wz-btn.ghost:hover { border-color: var(--accent-2); color: var(--text); background: var(--card-2); }

/* ---- moment 2: scanning — printer + printed receipt ---- */
.scan { display: flex; align-items: flex-start; gap: 18px; padding: 4px 0 6px; }
.printer { flex: none; }
.printer-led { animation: printer-blink 1s ease-in-out infinite; transform-origin: center; }
@keyframes printer-blink { 0%, 100% { opacity: 1; } 50% { opacity: .25; } }

.receipt {
  flex: 1;
  min-width: 0;
  min-height: 96px;
  background: #f3ecdd;
  border-radius: 4px 4px 10px 10px;
  padding: 12px 14px;
  box-shadow: 0 6px 18px -8px rgba(0, 0, 0, .5);
  display: flex;
  flex-direction: column;
  gap: 7px;
}
.receipt-line {
  font-family: 'Fira Code', ui-monospace, monospace;
  font-size: 12.5px;
  font-weight: 600;
  color: #2a2418;
  display: flex;
  justify-content: space-between;
  gap: 10px;
}
.receipt-line .ok { color: #0f6b47; }
.receipt-line .no { color: #6b6355; font-weight: 500; }
.receipt-line.anim { animation: receipt-in 320ms var(--ease) both; }
@keyframes receipt-in {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: translateY(0); }
}
.scan-caption { margin-top: 10px; font-size: 12px; color: var(--muted); }

/* ---- moment 3: reveal rows ---- */
#wz-rows { display: flex; flex-direction: column; gap: 10px; margin-bottom: 4px; }
.drow {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--input);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
}
.drow img { width: 34px; height: 34px; border-radius: 50%; flex: none; object-fit: cover; background: var(--card); }
.drow .info { flex: 1; min-width: 0; }
.drow .n {
  font-weight: 700; font-size: 14px; color: var(--text);
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
}
.drow .n .found {
  font-size: 10px; font-weight: 600; letter-spacing: .05em;
  color: var(--up); background: rgba(52, 211, 153, .12);
  border-radius: var(--pill); padding: 2px 8px;
}
.drow .sub { font-size: 12px; color: var(--muted); margin-top: 3px; }
.drow .sub .rw  { color: var(--accent); }
.drow .sub .bal { color: var(--gold-2); }
/* inline goal — "<logo> [SYMBOL Goal (Optional)]", cyan dashed border.
 * Shared by the wizard's step-3 rows AND the wallet-settings modal
 * (#ws-modal), hence unscoped from .drow. */
.drow .sub .goal-sep { color: var(--faint); margin: 0 8px; }
.goal-inline {
  display: inline-flex; align-items: center; gap: 6px;
  vertical-align: middle;
}
.goal-inline img { width: 18px; height: 18px; border-radius: 50%; flex: none; background: var(--card); }
.goal-inline input {
  width: 190px; background: var(--bg);
  border: 1px dashed var(--accent-2);
  border-radius: 8px; padding: 6px 10px; font-size: 12px; min-height: 32px;
}
.goal-inline input:focus { border-color: var(--accent); border-style: solid; outline: none; }
/* A goal the config API would refuse: the value is not stored, so the field has
 * to say so on its own — nothing else in the modal reports it. */
.goal-inline input[aria-invalid="true"],
.goal-inline input[aria-invalid="true"]:focus { border-color: var(--down); border-style: solid; }
.drow .goal { flex: none; }
.drow .goal input {
  width: 150px; background: var(--bg); border: 1px dashed var(--border);
  border-radius: 8px; padding: 8px 10px; font-size: 12px; min-height: 36px;
}
/* Non-held rows are de-emphasized WITHOUT a whole-row opacity: that used to
 * composite every text pixel against the row's own background at 55%,
 * dropping the sub-text to ~3.2:1 against the wizard card behind it (fails
 * AA). Instead only the logo is dimmed (purely decorative, no text/contrast
 * concern), and the text colors are explicit, AA-verified choices computed
 * against the row's own solid background (--input #162126, unchanged from
 * held rows — never blended):
 *   .n     var(--muted) #8da5b0 on #162126 -> 6.36:1
 *   .sub   var(--faint) #7d939e on #162126 -> 5.11:1
 *   .found var(--faint) #7d939e on the badge's own rgba(...,.08) chip
 *          (chip effective bg ~#1e2a30) -> 4.58:1
 * all comfortably clear the 4.5:1 AA threshold for normal-weight text. */
.drow.dim img { opacity: .55; }
.drow.dim .n { color: var(--muted); }
.drow.dim .sub { color: var(--faint); }
.drow.dim .n .found { color: var(--faint); background: rgba(125, 147, 158, .08); }

/* Narrow screens: the inline goal ("earned so far » [logo] [190px input]")
 * does not fit on the info line, so it wraps — dangling the "»" and leaving
 * the fixed-width input floating half-indented (unbalanced card heights and
 * rhythm). Below 560px the goal becomes its own full-width block under the
 * info line: drop the separator, top-align the logo so it tracks the name,
 * and let the input fill the row. Every printer card then shares one clean
 * structure (name → earned line → full-width goal), and FINVESTA staying
 * shorter is honest — it simply has no goal. */
@media (max-width: 560px) {
  .drow { align-items: flex-start; }
  .drow img { margin-top: 1px; }
  .drow .sub .goal-sep { display: none; }
  .drow .sub .goal-inline { display: flex; width: 100%; margin-top: 8px; }
  .drow .sub .goal-inline input { flex: 1; width: auto; min-width: 0; }
}

/* ---- moment 3: empty state ---- */
#wz-empty { padding: 10px 2px 4px; }
.wz-empty-title { font-size: 15px; font-weight: 700; color: var(--text); margin: 0 0 6px; }

/* ---- moment 3: label + save + privacy ---- */
.wz-label { margin-top: 14px; }
.wz-label input { width: 100%; }
/* Save sits on the RIGHT on desktop; the saving hint stays visually to its
   left (order: it's not focusable, so the DOM-vs-visual swap has no
   tab-order cost). */
.wz-save-row { display: flex; align-items: center; gap: 12px; margin-top: 14px; flex-wrap: wrap; justify-content: flex-end; }
#wz-saving-msg { order: -1; }
.privacy {
  display: flex; gap: 10px; align-items: flex-start;
  background: rgba(101, 219, 232, .07); border: 1px solid rgba(101, 219, 232, .3);
  border-radius: var(--radius-sm); padding: 10px 12px;
  font-size: 12px; color: var(--muted); margin-top: 14px;
}
.privacy svg { flex: none; margin-top: 1px; }
.privacy b { color: var(--text); }

/* ---- moment 4: celebrate — gold ID box + confetti, or a discreet toast ---- */
.idbox {
  text-align: center; padding: 26px 18px; border-radius: var(--radius);
  position: relative; overflow: hidden;
  background: radial-gradient(ellipse at 50% -20%, rgba(245, 176, 53, .16), transparent 60%), var(--card);
  border: 1px solid rgba(245, 176, 53, .4);
}
.idbox .t { font-size: 11px; letter-spacing: .18em; text-transform: uppercase; color: var(--faint); }
.idbox .id {
  font-size: clamp(20px, 6vw, 28px); font-weight: 800; letter-spacing: 3px;
  /* same recovery ID as the persistent "YOUR ID" chip — always gold, never the
     token accent */
  color: var(--brand-gold-2); margin: 10px 0 6px; text-shadow: 0 0 24px rgba(245, 176, 53, .45);
  word-break: break-all;
}
.idbox .hint { max-width: 380px; margin: 8px auto 14px; }
.idbox .row { display: flex; gap: 10px; justify-content: center; }

.conf {
  position: absolute; width: 6px; height: 6px; border-radius: 1px; opacity: .85;
  animation: conf-fall 2.2s ease-in infinite;
}
.conf.c1 { top: -8px; left: 14%; background: var(--gold); animation-delay: 0ms; }
.conf.c2 { top: -8px; left: 38%; background: var(--accent); animation-delay: 260ms; transform: rotate(20deg); }
.conf.c3 { top: -8px; left: 62%; background: var(--up); animation-delay: 520ms; transform: rotate(-15deg); }
.conf.c4 { top: -8px; left: 84%; background: var(--gold-2); animation-delay: 780ms; transform: rotate(35deg); }
@keyframes conf-fall {
  0%   { transform: translateY(0) rotate(0deg); opacity: .9; }
  100% { transform: translateY(160px) rotate(220deg); opacity: 0; }
}

.wz-toast {
  display: flex; align-items: center; gap: 8px;
  background: rgba(52, 211, 153, .1); border: 1px solid rgba(52, 211, 153, .3);
  color: var(--text); border-radius: var(--radius-sm);
  padding: 12px 14px; font-size: 13.5px;
}

.wz-final-actions { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin-top: 18px; }

@media (max-width: 480px) {
  .wz { padding: 16px; }
  .wz-q { font-size: 16px; }
  .scan { flex-direction: column; align-items: center; text-align: center; }
  .receipt { width: 100%; }
}

#config-err { display: block; margin: 0 0 10px; }

#wallet-list { display: flex; flex-direction: column; gap: 10px; margin-bottom: 8px; }
/* zero tracked wallets: no empty-state line, and no stray gap either */
#wallet-list:empty { display: none; }
/* Saved-wallet row: name + live holdings chips +
 * gear. Display-only — all editing lives in the settings modal (#ws-modal).
 * Contrast (measured): name --text on --card 18.03:1, chip balance --text on
 * --input 15.70:1, gear glyph --muted on --card 7.31:1 (≥3:1 graphical). */
.wallet-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 14px;
  align-items: center;
  background: var(--card);
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  padding: 8px 8px 8px 14px;
  transition: border-color var(--t-fast) var(--ease);
}
.wallet-row:hover { border-color: var(--border); }
.wallet-row .w-name {
  flex: 1 1 auto;
  min-width: 8ch;
  font-weight: 600;
  font-size: 14px;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* holdings: one chip per tracked token — 18px logo + tabular balance (Fira
 * Code). min-width on the number reserves room so "—" -> a live balance
 * never shifts the row on a poll tick. */
.wallet-row .w-holdings {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}
.w-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  background: var(--input);
  border: 1px solid var(--border-soft);
  border-radius: var(--pill);
  padding: 4px 12px 4px 5px;
}
.w-chip img {
  width: 18px;
  height: 18px;
  border-radius: 50%;
  flex: none;
  object-fit: cover;
  background: var(--card);
}
.w-chip .w-bal {
  font-family: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 12.5px;
  color: var(--text);
  /* reserve the common holding width so the "—" placeholder doesn't shift the
     row when the first real balance lands; extreme values (10-digit+ printer
     holdings) still settle once on the first tick — a fixed reserve can't
     cover every magnitude without an absurd column, and live updates after
     that are same-magnitude so they never shift. */
  min-width: 10ch;
  text-align: right;
  white-space: nowrap;
}
/* muted $-value after each holding amount (balance × live price) */
.w-chip .w-usd {
  font-family: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 11px;
  color: var(--faint);
  white-space: nowrap;
}
.w-chip .w-usd:empty { display: none; }
/* Holdings-value tile under a wallet's section heading (#printers): total
   holdings in USD + the PLS equivalent, one per wallet. */
.hold-tile {
  display: inline-flex;
  align-items: baseline;
  gap: 11px;
  margin: 0 0 14px;
  padding: 8px 14px;
  background: var(--input);
  border: 1px solid var(--border-soft);
  border-radius: 10px;
  font-family: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;
}
.hold-tile .hold-label { font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.hold-tile .hold-usd { font-size: 16px; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }
.hold-tile .hold-pls { font-size: 12px; color: var(--up); font-variant-numeric: tabular-nums; }
/* gear: 20px glyph centered in a 44x44 hit area. Hover changes color/tint
 * only — the 1px border is pre-reserved as transparent, so no layout shift;
 * press is a transform-only scale (inherits the button base transition,
 * 130ms). */
.btn-gear {
  width: 44px;
  height: 44px;
  min-height: 44px;
  padding: 0;
  flex: none;
  margin-left: auto;
  display: grid;
  place-items: center;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--muted);
}
.btn-gear .svgi { width: 20px; height: 20px; }
.btn-gear:hover {
  color: var(--accent);
  background: rgba(101, 219, 232, .08);
  border-color: var(--border-soft);
}
.btn-gear:active { transform: scale(.97); }
/* Below large-desktop the row can't fit name + all six holding chips + gear on
   one line, so the gear would wrap to a lonely second line at the right edge
   (measured onset ~1360px with six filled chips). Pin the gear to the name's
   row (its margin-left:auto already holds it right) and drop the holdings onto
   their own full-width line below. Every wallet renders all six chips, so the
   rows wrap in step — the whole list stacks uniformly the moment it leaves
   large-desktop width. >1400px keeps the compact single-line inline layout. */
@media (max-width: 1400px) {
  .wallet-row .w-holdings { order: 3; flex: 1 1 100%; }
}

/* ---- persistent recovery-ID chip (shown whenever Store.id exists — the
 * wizard's gold celebrate box is a one-time moment, this is the permanent,
 * always-reachable place to find/copy it again on a later visit). A quiet
 * echo of #wz-idbox: gold-tinted mono pill on a bordered card row.
 * Contrast: .mic-label --faint on the card gradient 5.54:1+, the pill's
 * gold-2 on --input 10.28:1, .mic-copy gold-2 on its .07 gold tint 9.84:1,
 * the copied-state --up on its .08 tint 8.02:1 — all past AA. ---- */
.my-id-chip {
  display: flex; flex-wrap: wrap; align-items: center; gap: 12px;
  background: linear-gradient(180deg, var(--card-2), var(--card));
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  padding: 10px 14px;
  margin: 0 0 10px;
}
.mic-label {
  font-size: 10px; font-weight: 600; letter-spacing: .18em;
  text-transform: uppercase; color: var(--faint);
}
.my-id-chip code {
  background: var(--input);
  border: 1px solid rgba(245, 176, 53, .3);
  border-radius: 8px;
  padding: 4px 10px;
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: .12em;
  color: var(--brand-gold-2);   /* recovery-ID chrome: always gold, never the token accent */
}
/* gold-tinted copy — min-width reserves room for the wider of "⧉ Copy" /
   "Copied ✓" so the transient swap never shifts the row */
.my-id-chip .mic-copy {
  margin-left: auto;
  min-width: 98px;
  text-align: center;
  color: var(--brand-gold-2);   /* always gold, never the token accent */
  border-color: rgba(245, 176, 53, .35);
  background: rgba(245, 176, 53, .07);
}
.my-id-chip .mic-copy:hover {
  border-color: rgba(245, 176, 53, .6);
  color: var(--brand-gold-2);
  background: rgba(245, 176, 53, .12);
  box-shadow: 0 0 12px rgba(245, 176, 53, .22);
}
.my-id-chip .mic-copy.copied {
  color: var(--up);
  border-color: rgba(52, 211, 153, .45);
  background: rgba(52, 211, 153, .08);
}
/* danger twin of .mic-copy — same shape/size family, --down tint instead of
   gold. Contrast: --down #f87171 over the .07 red tint composited on the
   chip gradient (~#211f23) 5.91:1 — past AA at 13px/500. */
.my-id-chip .mic-delete {
  min-width: 74px;
  text-align: center;
  color: var(--down);
  border-color: rgba(248, 113, 113, .35);
  background: rgba(248, 113, 113, .07);
}
.my-id-chip .mic-delete:hover {
  border-color: var(--down);
  color: #fca5a5;
  background: rgba(248, 113, 113, .12);
  box-shadow: 0 0 12px rgba(248, 113, 113, .22);
}
/* Both buttons are an icon + a label. The svg needs an explicit size: `.svgi`
   only carries the stroke recipe, and an <svg> with neither attribute nor rule
   would lay out at its default 300x150. */
.my-id-chip .btn-copy { display: inline-flex; align-items: center; justify-content: center; gap: 7px; }
.my-id-chip .btn-copy .svgi { width: 14px; height: 14px; flex: none; }
/* ≤480px the labels go and the icons stand alone. Measured: with both labels
   the row needs 396px and a 375px phone offers 315px, so Delete dropped onto a
   second line — the labels are worth ~100px, comfortably more than that 81px
   shortfall. The min-widths go with them: they exist only to reserve room for
   the "Copied ✓" swap, which has no label to land in here. */
@media (max-width: 480px) {
  .my-id-chip .btn-copy .btn-label { display: none; }
  .my-id-chip .mic-copy,
  .my-id-chip .mic-delete { min-width: 0; }
  /* …and that swap was also the copy confirmation, so the icon becomes the
     tick itself, over the green tint `.copied` already applies. */
  .my-id-chip .mic-copy.copied .svgi { display: none; }
  .my-id-chip .mic-copy.copied::after { content: '✓'; font-size: 14px; font-weight: 700; }
}
/* Below ~360px even the icons do not fit beside the ID: the row still needs
   300px and a 320px phone offers 262. The caption takes a line of its own
   rather than Delete taking one alone — the pill and both buttons then travel
   together, which is what the row is for. Not a breakpoint the 375px class of
   phone ever reaches. */
@media (max-width: 360px) {
  .my-id-chip .mic-label { flex: 1 0 100%; }
}

/* ---- config self-delete confirmation modal:
 * fixed dark overlay + centered card. z-index above the sticky badge strip
 * (30). Subtle open animation on both layers — nulled under
 * prefers-reduced-motion at the bottom of this sheet. Contrast: title
 * --text on --card, body copy --muted on --card (the standard .hint pair),
 * "Delete forever" #2b0b0b on the --down gradient ≈ 6.4:1 — all past AA. */
.del-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(4, 7, 9, .75);
  overscroll-behavior: contain;   /* touch gestures on the veil must not scroll-chain to the page */
  animation: del-fade 160ms var(--ease);
}
.del-card {
  width: min(440px, 100%);
  background: linear-gradient(180deg, var(--card-2), var(--card));
  border: 1px solid rgba(248, 113, 113, .3);
  border-radius: var(--radius);
  padding: 22px 22px 18px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .55);
  animation: del-pop 200ms var(--ease);
}
.del-card h3 { font-size: 17px; font-weight: 700; margin: 0 0 10px; }
.del-card p  { font-size: 13.5px; color: var(--muted); line-height: 1.55; margin: 0 0 6px; }
#del-err { display: block; margin-top: 8px; }
.del-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 16px;
}
.btn-danger {
  background: linear-gradient(180deg, #fa8f8f, var(--down));
  color: #2b0b0b;
  border-color: transparent;
  border-radius: 10px;
  padding: 10px 18px;
  font-weight: 600;
}
.btn-danger:hover {
  filter: brightness(1.06);
  background: linear-gradient(180deg, #fa8f8f, var(--down));
  box-shadow: 0 0 16px rgba(248, 113, 113, .4);
}
.btn-danger:disabled { opacity: .55; cursor: default; filter: none; box-shadow: none; }
@keyframes del-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes del-pop {
  from { opacity: 0; transform: translateY(8px) scale(.97); }
  to   { opacity: 1; transform: none; }
}

/* ---- per-wallet settings modal: same recipe as
 * #del-modal — fixed scrim + centered gradient card, same z-index/elevation —
 * but with a neutral border (red lives ONLY in the danger group). Scrim at
 * .6: enough to isolate the graph-paper body behind the form. Enter: fade +
 * scale(.96 -> 1) 200ms ease-out; exit 130ms ease-in (faster out than in);
 * both nulled under prefers-reduced-motion at the bottom of this sheet.
 * Contrast (measured): title --text 17.02:1 and addr --muted 6.90:1 on
 * --card-2 (gradient's lightest end), group labels --faint 5.54:1, danger
 * label --down 6.43:1, ghost Remove --down over its .07 tint 5.91:1, armed
 * Remove #2b0b0b on the --down gradient 6.57:1 — all past AA. */
.ws-overlay {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(4, 7, 9, .6);
  overscroll-behavior: contain;   /* touch gestures on the veil must not scroll-chain to the page */
  animation: del-fade 200ms ease-out;
}
.ws-card {
  width: min(480px, 100%);
  max-height: calc(100vh - 40px);
  overflow-y: auto;
  background: linear-gradient(180deg, var(--card-2), var(--card));
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 22px 22px 16px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .55);
  animation: ws-pop 200ms ease-out;
}
@keyframes ws-pop {
  from { opacity: 0; transform: scale(.96); }
  to   { opacity: 1; transform: none; }
}
/* exit (JS adds .closing, then [hidden] lands after WS_EXIT_MS): inert to
 * pointers while fading out */
.ws-overlay.closing { animation: ws-fade-out 130ms ease-in both; pointer-events: none; }
.ws-overlay.closing .ws-card { animation: ws-pop-out 130ms ease-in both; }
@keyframes ws-fade-out { to { opacity: 0; } }
@keyframes ws-pop-out { to { opacity: 0; transform: scale(.97); } }

.ws-card h3 { font-size: 17px; font-weight: 700; margin: 0 0 4px; }
.ws-addr {
  font-family: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 12.5px;
  color: var(--muted);
  margin: 0 0 16px;
}

/* three groups — Name / Printer goals / Danger zone — hairline dividers on a
 * 16/24px rhythm; small uppercase labels above every field (visible labels,
 * placeholders only hint) */
.ws-group { border-top: 1px solid var(--hair); padding: 16px 0; margin: 0; }
.ws-lbl {
  display: block;
  font-size: 10px;
  font-weight: 600;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0 0 8px;
}
.ws-group input[type="text"] { width: 100%; }

.ws-goal-row { display: flex; align-items: center; gap: 10px; }
.ws-goal-row + .ws-goal-row { margin-top: 8px; }
.ws-goal-name {
  flex: none;
  width: 92px;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}
.ws-goal-row .goal-inline { flex: 1 1 auto; min-width: 0; }
.ws-goal-row .goal-inline input { flex: 1 1 auto; width: auto; min-width: 0; }

/* "Shown on the dashboard" group (per-printer hide/show): one row per
 * printer, name + a real <input type="checkbox">
 * (paired with a <label for>) styled as a switch — checked = shown/visible.
 * `appearance: none` replaces only the PAINT; it stays a real checkbox, so
 * screen readers get native checked/unchecked state and the label's `for`
 * keeps "click the label toggles it" for free — no role="switch"/aria-checked
 * hand-rolling needed. Track: --input/--border unchecked, gold-tinted/--gold
 * checked (same family as the goal pill); thumb: --muted unchecked, --gold
 * checked — reuses this dialog's existing AA-measured palette (--muted 6.90:1
 * and --gold well past AA on this card's gradient, see the block comment
 * above .ws-overlay). Motion (thumb slide) is a `transition`, already zeroed
 * for every element under prefers-reduced-motion (see that media block). */
.ws-hidden-row { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.ws-hidden-row + .ws-hidden-row { margin-top: 8px; }
.ws-hidden-name { font-size: 12px; font-weight: 600; color: var(--muted); }
.ws-hidden-row input[type="checkbox"] {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  width: 38px;
  height: 22px;
  min-height: 22px;
  margin: 0;
  padding: 0;
  border-radius: var(--pill);
  background: var(--input);
  border: 1px solid var(--border);
  position: relative;
  cursor: pointer;
  transition: background var(--t-fast) var(--ease), border-color var(--t-fast) var(--ease);
}
.ws-hidden-row input[type="checkbox"]::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--muted);
  transition: transform var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.ws-hidden-row input[type="checkbox"]:hover { border-color: #35566e; }
.ws-hidden-row input[type="checkbox"]:checked { background: rgba(245, 176, 53, .18); border-color: var(--gold); }
.ws-hidden-row input[type="checkbox"]:checked:hover { border-color: var(--gold-2); }
.ws-hidden-row input[type="checkbox"]:checked::after { background: var(--gold); transform: translateX(16px); }
/* This toggle needs its own focus ring: the site-wide `input:focus` rule
 * (Controls block, above) sets `outline: none` and a border-color/box-shadow
 * pair that's imperceptible here — the CHECKED resting border is already
 * var(--gold) so focus changes nothing visible, and the unchecked box-shadow
 * ring composites to ~1.3:1 against the modal card, well under the 3:1 WCAG
 * 1.4.11/2.4.11 floor — so a keyboard user tabbing onto this toggle got no
 * visible indicator at all (WCAG 2.4.7). This selector's specificity
 * (0,3,1) beats `input:focus` (0,1,1), so it wins regardless of source
 * order, and it reuses the page's single on-brand focus treatment (see the
 * global `:focus-visible { outline: 2px solid var(--gold); ... }` rule near
 * the top of this file, "focus: single, visible, on-brand") rather than
 * inventing a new one. Applies identically in both checked and unchecked
 * states. */
.ws-hidden-row input[type="checkbox"]:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

/* danger zone: the only red in the dialog, spatially fenced at the bottom */
.ws-danger { border-top: 1px solid rgba(248, 113, 113, .25); }
.ws-lbl-danger { color: var(--down); }
.ws-remove {
  display: block;
  width: 100%;
  text-align: center;
  color: var(--down);
  border-color: rgba(248, 113, 113, .35);
  background: rgba(248, 113, 113, .07);
}
.ws-remove:hover {
  border-color: var(--down);
  color: #fca5a5;
  background: rgba(248, 113, 113, .12);
  box-shadow: 0 0 12px rgba(248, 113, 113, .22);
}
.ws-remove:active { transform: scale(.97); }
/* armed confirm step: filled danger, same geometry (color/background swap
 * only — no size change, no layout shift) */
.ws-remove.armed {
  background: linear-gradient(180deg, #fa8f8f, var(--down));
  color: #2b0b0b;
  border-color: transparent;
  font-weight: 600;
}
.ws-remove.armed:hover { filter: brightness(1.06); }

.ws-actions {
  display: flex;
  justify-content: flex-end;
  border-top: 1px solid var(--hair);
  padding-top: 14px;
}

/* ---- restore configuration: gold toggle button + inline panel expanding
 * directly below it.
 * The open animation is a short fade/slide (retriggered each time [hidden]
 * comes off) — disabled under prefers-reduced-motion at the bottom of this
 * sheet. ---- */
/* Setup actions row: Restore (secondary, left) ↔ Add wallet (primary, right).
   The recover ID panel sits full-width below. */
#setup-actions { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-top: 14px; }
.recover-panel { text-align: left; margin-top: 12px; }
/* Wizard reveal when "+ Add wallet" opens it: a short fade + slide from above,
   so it clearly slides into its spot rather than snapping in. */
@keyframes wz-reveal { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: none; } }
.wz.wz-reveal { animation: wz-reveal 200ms var(--ease); }
@media (prefers-reduced-motion: reduce) { .wz.wz-reveal { animation: none; } }
@media (max-width: 480px) {
  /* small screens: stack the actions full-width, primary (Add wallet) on top */
  #setup-actions { flex-direction: column-reverse; }
  #setup-actions .wz-btn { width: 100%; }
  .wz-save-row .wz-btn, #btn-recover-toggle { width: 100%; }
  /* Track →: drop it onto its own full-width row below the address input */
  #wz-form .wz-btn { flex-basis: 100%; }
}
.recover-panel {
  margin-top: 10px;
  background: linear-gradient(180deg, var(--card-2), var(--card));
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  display: flex;
  flex-direction: column;
  gap: 10px;
  animation: recover-in 200ms var(--ease);
}
@keyframes recover-in {
  from { opacity: 0; transform: translateY(-5px); }
  to   { opacity: 1; transform: none; }
}
.recover-panel .hint { margin: 0; }
.recover-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.recover-row input { flex: 1 1 160px; min-width: 0; max-width: 240px; }
#recover-err { color: var(--down); font-size: 13px; }

/* ============================================================================
 * Printers — one gold section heading per wallet (`.section-title
 * .section-title-track`, same treatment as "Track Wallets"), its printer
 * cards sitting directly underneath — no card wrapper. `#printers` is a flex
 * column with gap:0 rather than block layout on purpose: with `gap:0` and
 * every spacing value coming solely from the elements' own margins, the flex
 * box model is purely additive (flex items never collapse margins with each
 * other or the container — CSS Flexbox §Margin Collapsing), so heading-to-
 * card = the heading's own 12px bottom margin, card-to-card (same wallet) =
 * the existing `.printer-row + .printer-row` 10px rule, and last-card-to-
 * next-heading = the next heading's own 28px top margin — the exact three
 * numbers `.section-title` already uses everywhere else on the page (e.g.
 * Holders Explorer -> LP Pairs), without depending on ambient block-margin
 * collapsing rules that would be fragile here (#printers holds MANY headings,
 * not one heading followed by one content block like the rest of the page).
 *
 * That same additive rule bites the
 * FIRST wallet heading — flex items never collapse margins with their
 * container either, so #setup's 8px margin-bottom (style.css, Setup /
 * onboarding block) stacks fully under the first heading's own 28px
 * margin-top (36px total) instead of collapsing to 28px like every other
 * .section-title transition on the page. Trim just that first heading's
 * top margin by the 8px #setup already contributes so the total lands back
 * on the page's 28px rhythm. */
#printers { display: flex; flex-direction: column; gap: 0; }
#printers > .section-title:first-child { margin-top: 20px; }

/* Unlike every other .section-title
 * on the page, this one's text is user-authored (up to 30 chars, no
 * guaranteed break opportunity — see config.py LABEL_MAX) rather than a
 * short static string, so it needs its own overflow clamp — the old
 * .wb-title it replaced had exactly this (overflow:hidden; text-overflow:
 * ellipsis; white-space:nowrap). It's applied to the inner <span
 * class="st-label"> app.js wraps the label in (see _walletSection), not to
 * .section-title itself, so the h2's flex layout and its ::after
 * horizontal-rule sibling (flex:1, keeps spanning full width) are
 * untouched, and every other .section-title (Track Wallets, Holders
 * Explorer, ...) — none of which have an .st-label child — renders exactly
 * as before. min-width:0 overrides the flex item's default min-width:auto
 * (= min-content), which is what actually lets the label shrink below its
 * intrinsic width and ellipsize instead of pushing the page wider. Scoped
 * under #printers because that's the only place .st-label is ever used. */
#printers .st-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ============================================================================
 * Bond ecosystem — global snapshot (bonds minted + vault balances), shared
 * across every wallet. Sits below #printers, above the Constellation.
 * ========================================================================== */
#bond-ecosystem { margin-top: clamp(20px, 3vw, 28px); }   /* separate it from the printer/goal cards above */
/* #v3-ecosystem is the Bond card's sibling, not its child — same clamp so
   it doesn't sit flush against #bond-ecosystem when both render. */
#v3-ecosystem { margin-top: clamp(20px, 3vw, 28px); }
#bond-ecosystem .be-title, #v3-ecosystem .be-title { margin: 0 0 12px; font-size: 15px; }
/* The V3 title wears the real Finvesta logo instead of the 🔷 emoji (A8);
   the Bond title keeps its ⚜️ text emoji — no official bond asset exists —
   so this rule only needs to lay out an optional <img> next to the text. */
.be-title { display: flex; align-items: center; gap: 8px; }
.be-title .be-logo { width: 26px; height: 26px; border-radius: 50%; object-fit: cover; flex: none; }
#bond-ecosystem .be-grid, #v3-ecosystem .be-grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 12px; }
/* position: relative is load-bearing: .partial (the ∗ marker) is
   position: absolute and needs THIS as its containing block, same as .pk
   (style.css ~1634) — without it the marker escapes to the document root
   and paints under the header, nowhere near the tile it annotates. */
#bond-ecosystem .be-tile, #v3-ecosystem .be-tile { background: var(--input); border: 1px solid var(--border-soft); border-radius: var(--radius-sm); padding: 12px 13px; position: relative; }
#bond-ecosystem .be-k, #v3-ecosystem .be-k { font-size: 10.5px; letter-spacing: .12em; text-transform: uppercase; color: var(--faint); font-weight: 600; }
/* Same pk-val recipe (style.css ~1915) at pk scale: white 22px values,
   self-clipping instead of wrapping — these global tiles now read at the
   same scale as the hero/printer/earnings KPIs instead of a smaller cyan one. */
#bond-ecosystem .be-v, #v3-ecosystem .be-v { font-family: 'Fira Code', ui-monospace, monospace; font-size: 22px; font-weight: 700; color: var(--text); margin-top: 5px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@media (max-width: 560px) { #bond-ecosystem .be-grid, #v3-ecosystem .be-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* ============================================================================
 * Portfolio Earnings — collapsed-by-default card above the wallet sections.
 * Header mirrors .card-h; the 4 KPI tiles reuse the .pk recipe — the SAME
 * tiles as the hero holding card and the printer cards, so the data reads at
 * the same 22px scale everywhere and the labels/subs self-clip instead of
 * wrapping at narrow widths. The chevron is the .holders-toggle recipe scoped
 * to this card.
 * ========================================================================== */
.pe-card .card-h { margin-bottom: 0; justify-content: flex-start; }
/* Title anchors LEFT like every other card: .card-h's space-between would
   float it between the icon and the right-hand meta with 4 children. */
.pe-card .card-h-note { margin-left: auto; }
/* KPI value accents on the shared .pk recipe: gold for the headline total,
   teal for the recent window — same hues the old .be tiles carried. */
/* Brand gold, not the swapped token accent: Portfolio Earnings is app chrome
   (the owner's USD earnings), so the expanded headline matches the collapsed
   header's pin — the same figure must not change identity when the card
   opens (owner follow-up to A5). */
.pe-card .pk-val.pe-gold { color: var(--brand-gold-2); }
.pe-card .pk-val.pe-teal { color: var(--accent-2); }
.pe-card:not(.collapsed) .card-h { margin-bottom: 12px; }
.pe-ico { width: 26px; height: 26px; border-radius: 8px; background: rgba(245,176,53,.14); color: var(--brand-gold); display: inline-flex; align-items: center; justify-content: center; flex: none; }
.pe-ico svg { width: 15px; height: 15px; stroke: currentColor; stroke-width: 2; fill: none; stroke-linecap: round; stroke-linejoin: round; }
/* No position: relative here, unlike every other _partialMark host (.be-tile,
   .pk): this card's ∗ does not live in the summary. Pinned to THIS box's
   top-right it sat on top of the "/day" tail at every width — the box hugs its
   own text, so its corner is inside the sentence. It trails the title instead,
   in flow; see the .card-h .t .partial rule below. */
.pe-sum { margin-left: auto; font-family: 'Fira Code', ui-monospace, monospace; font-variant-numeric: tabular-nums; font-size: 13.5px; font-weight: 700; color: var(--brand-gold-2); white-space: nowrap; }
/* nowrap keeps "≈ $X/day" whole when the collapsed summary wraps on a phone:
   the pace moves to the second line as one piece instead of leaving a stranded
   "≈" hanging beside the total. The one break we DO want is the space before
   it, which Render.portfolioEarnings emits as its own text node outside these
   spans for exactly that reason. */
.pe-sum .pe-dot,
.pe-sum .pe-pace { color: var(--faint); font-weight: 400; white-space: nowrap; }
.pe-card:not(.collapsed) .pe-sum { display: none; }
.pe-card.collapsed .card-h-note { display: none; }
/* The ∗ trailing the title. `.card-h .t` is a flex row, so the mark becomes a
   flex item spaced by the title's own 9px gap; top/right are reset because the
   shared .partial rule offsets them for the absolute case. It stays
   `position: relative` so it is still the containing block for its own
   ::after bubble — static would let the bubble anchor to the page instead.
   Collapsed only: once open, the "Total printed" tile carries its own mark for
   the same figure and a second one in the header would just be noise. */
.pe-card .card-h .t .partial { position: relative; top: auto; right: auto; }
.pe-card:not(.collapsed) .card-h .t .partial { display: none; }
.pe-toggle { width: 30px; height: 30px; flex: none; background: var(--input); border: 1px solid var(--border-soft); border-radius: 8px; cursor: pointer; position: relative; }
.pe-toggle::after { content: ''; position: absolute; left: 50%; top: 50%; width: 7px; height: 7px; border-right: 2px solid var(--accent-2); border-bottom: 2px solid var(--accent-2); transform: translate(-50%, -70%) rotate(45deg); transition: transform var(--t) var(--ease); }
.pe-card:not(.collapsed) .pe-toggle::after { transform: translate(-50%, -30%) rotate(225deg); }
.pe-toggle:hover { border-color: var(--accent-2); background: var(--card-2); }
.pe-toggle:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
.pe-card.collapsed .pe-body { display: none; }
/* The summary must never slide under the chevron. `.pe-sum` also carries the
   global `.num`, whose explicit `min-width: 9ch` REPLACES the flex auto-minimum
   — so unlike `.card-h-note` (no min-width, refuses to shrink, makes the title
   wrap instead) this item shrinks below its own nowrap text and the overflow
   spills over the button, which paints on top of it: both are positioned and
   the button comes later in the DOM. Letting the text wrap inside its own box
   is the width-agnostic guard, for any portfolio long enough to not fit. */
.pe-card.collapsed .pe-sum { white-space: normal; }
/* …and on a phone the header becomes an identity block: one big icon chip
   spanning both text rows, the title on row 1, the second line under it, the
   chevron pinned right and centred across both. Grid, not flex-wrap on the
   shared header — that scatters this header over four rows instead.
   BOTH states take the layout, and each drops its own line into row 2: the
   money when collapsed, the "N wallets · M printers" note when open. The note
   is the flex bug's mirror image — no min-width at all, so it refuses to
   shrink, squeezes the title to its own min-content and pushes the chevron 9px
   past the card's padding at 320px. A row of its own removes the contest.
   The summary additionally overrides `.num`: left-aligned to sit under the
   title rather than right-aligned to the column edge, and min-width 0 so
   `.num`'s 9ch floor can never push it past a narrow column — that floor is
   what let it slide under the chevron in the first place. */
@media (max-width: 560px) {
  .pe-card .card-h {
    display: grid;
    grid-template-columns: auto minmax(0, 1fr) auto;
    align-items: center;
    gap: 2px 10px;
  }
  .pe-card .pe-ico { grid-area: 1 / 1 / 3 / 2; width: 40px; height: 40px; border-radius: 10px; }
  .pe-card .pe-ico svg { width: 20px; height: 20px; }
  .pe-card .card-h .t { grid-area: 1 / 2 / 2 / 3; }
  .pe-card.collapsed .pe-sum {
    grid-area: 2 / 2 / 3 / 3;
    min-width: 0;
    margin-left: 0;
    text-align: left;
  }
  /* margin-left: 0 undoes the flex-only `auto` that pushed the note right. */
  .pe-card .card-h-note { grid-area: 2 / 2 / 3 / 3; margin-left: 0; }
  .pe-card .pe-toggle { grid-area: 1 / 3 / 3 / 4; }
  /* No "·" on a phone. CSS cannot ask whether the text wrapped, so the choice
     is per-layout, not per-line: here the summary sits on a row of its own
     under the title and the pace can wrap to a line of its own below that —
     in both cases the dot separates nothing, and the space + the gold/faint
     colour split already read as two figures. Desktop keeps it (one line,
     never wraps). */
  .pe-sum .pe-dot { display: none; }
}
@media (prefers-reduced-motion: reduce) { .pe-toggle::after { transition: none; } }

.pe-lb-hd { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin: 18px 0 8px; }
.pe-lb-t { font-size: 12px; font-weight: 600; letter-spacing: .1em; text-transform: uppercase; color: var(--muted); }
.pe-lb-s { font-size: 12px; color: var(--faint); }
.pe-lb { display: flex; flex-direction: column; gap: 8px; }
.pe-lb-row { display: grid; grid-template-columns: minmax(0, 200px) 1fr auto; align-items: center; gap: 12px; background: var(--input); border: 1px solid var(--border-soft); border-radius: var(--radius-sm); padding: 10px 14px; }
.pe-lb-id { display: flex; align-items: center; gap: 10px; min-width: 0; }
.pe-lb-logo { border-radius: 50%; object-fit: cover; background: var(--card); flex: none; }
.pe-lb-name { display: block; font-weight: 600; font-size: 14px; white-space: nowrap; }
.pe-lb-rw { display: block; font-size: 11.5px; color: var(--faint); }
.pe-lb-barbox { display: block; height: 10px; border-radius: var(--pill); background: rgba(255,255,255,.05); overflow: hidden; }
.pe-lb-bar { display: block; height: 100%; border-radius: var(--pill); background: linear-gradient(90deg, var(--accent-2), var(--accent)); }
/* Same pin as the headline above: the leader's gold bar is chrome too. */
.pe-lb-bar.top { background: linear-gradient(90deg, var(--brand-gold), var(--brand-gold-2)); box-shadow: var(--glow-gold); }
.pe-lb-bar.est { background: repeating-linear-gradient(135deg, rgba(101,219,232,.5) 0 4px, transparent 4px 8px); outline: 1px dashed var(--accent); outline-offset: -1px; }
.pe-lb-val { text-align: right; display: flex; flex-direction: column; gap: 2px; align-items: flex-end; }
.pe-lb-v7 { font-size: 15px; font-weight: 700; }
.pe-lb-sub { font-size: 11.5px; color: var(--faint); }
.pe-lb-verified { color: var(--up); }
.pe-lb-chip { font-family: 'Fira Code', ui-monospace, monospace; font-size: 11px; color: var(--faint); background: rgba(255,255,255,.03); border: 1px solid var(--border); border-radius: var(--pill); padding: 1.5px 8px; white-space: nowrap; }
/* Phone-only caption on the ≤560px gold mini-card lead ("last 7d") — desktop
 * and tablet already say it in the section subtitle, so it stays hidden there. */
.pe-lb-lead-cap { display: none; font-size: 10px; color: var(--faint); }
/* ≤900px: drop the bar (no room to read it) and keep identity | value
   on ONE line with the value right-aligned — stacking the value left under the
   name read as chaos. The sub line may wrap; its pills wrap as whole units. */
@media (max-width: 900px) {
  .pe-lb-row { grid-template-columns: minmax(0, 1fr) auto; }
  .pe-lb-barbox { display: none; }
  .pe-lb-sub { white-space: normal; }
}
/* ≤560px: the long sub line ("all-time … · verified ✓") cannot fit beside the
   identity at phone widths without overlapping it — it gets its OWN full-width
   second line, right-aligned, leaving line 1 to identity | 7d value.
   display: contents promotes the value block's children to grid items so the
   grid can re-home them.
   Variant B (owner, img 10): the 7d leader is a gold mini-card with its bar
   and sub-line; everyone else is a slim identity|value row — at ~$0 a bar
   separates nothing, and the sub-line was what pushed rows to two lines. The
   measuring/syncing chip is appended as its own .pe-lb-val child (app.js),
   not nested in .pe-lb-sub, so hiding the sub on non-lead rows never hides it. */
@media (max-width: 560px) {
  .pe-lb-val { display: contents; }
  .pe-lb-v7 { grid-column: 2; justify-self: end; }
  .pe-lb-sub { grid-column: 1 / 3; text-align: right; margin-top: 2px; }
  /* The chip only ever becomes a grid item here (display:contents above
     promotes it out of .pe-lb-val, same as v7/sub/barbox/lead-cap) — on
     every wider layout it's a flex child of .pe-lb-val and this rule is
     inert. Without it, auto-placement stretches the chip to fill whatever
     track it lands in on a slim (no bar, no sub) row. */
  .pe-lb-chip { justify-self: end; }
  .pe-lb-row:not(.pe-lb-lead) .pe-lb-barbox { display: none; }
  .pe-lb-row:not(.pe-lb-lead) .pe-lb-sub { display: none; }
  .pe-lb-lead {
    border-color: rgba(245, 176, 53, .35);
    background: linear-gradient(180deg, rgba(245, 176, 53, .07), var(--input));
    padding: 12px 14px;
  }
  /* Full width on its own grid row: without an explicit column, auto-placement
     drops the bar into column 2 right beside the identity (the next free cell
     after .pe-lb-id claims column 1), rendering it ~87px wide instead of
     spanning the row underneath. */
  .pe-lb-lead .pe-lb-barbox { grid-column: 1 / -1; display: block; height: 6px; margin-top: 5px; }
  .pe-lb-lead .pe-lb-logo { width: 32px; height: 32px; }
  .pe-lb-lead .pe-lb-v7 { color: var(--brand-gold-2); font-size: 16px; }
  /* Same auto-placement problem as the barbox: with no explicit column this
     lands wherever the cursor happens to be after the barbox's full-width row,
     which is not reliably under the value. Column 2, right-aligned, keeps it
     under .pe-lb-v7 regardless of what else is in the row. */
  .pe-lb-lead-cap { grid-column: 2; justify-self: end; display: block; }
}

/* ============================================================================
 * Since-you-were-away recap — one-shot card under the topbar (AwayRecap in
 * app.js). Gold identity border + glow: this is the page greeting the OWNER,
 * the one card that talks about their absence. Rows: icon chip | label |
 * detail | pill; ≤900px the detail drops under the label, the pill follows.
 * ========================================================================== */
.away-recap { border: 1px solid rgba(245, 176, 53, .38); box-shadow: var(--shadow), 0 0 26px -6px rgba(245, 152, 56, .28); }
.ar-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ar-title { font-size: 15px; font-weight: 700; margin: 0; letter-spacing: .01em; }
.ar-when { margin-left: auto; font-size: 11.5px; color: var(--faint); border: 1px solid var(--border); border-radius: var(--pill); padding: 2.5px 9px; white-space: nowrap; }
.ar-ic { width: 26px; height: 26px; border-radius: 8px; background: rgba(101, 219, 232, .14); color: var(--accent-2); display: inline-flex; align-items: center; justify-content: center; flex: none; }
.ar-ic svg { width: 14px; height: 14px; stroke: currentColor; stroke-width: 2; fill: none; stroke-linecap: round; stroke-linejoin: round; }
.ar-gold-ic { background: rgba(245, 176, 53, .14); color: var(--gold); }
.ar-list { list-style: none; margin: 12px 0 0; padding: 0; }
.ar-row { display: grid; grid-template-columns: 26px minmax(0, 172px) minmax(0, 1fr) auto; gap: 10px; align-items: center; padding: 10px 4px; border-top: 1px solid var(--hair); }
.ar-label { font-size: 13px; color: var(--muted); }
.ar-det { font-family: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace; font-variant-numeric: tabular-nums; font-size: 13.5px; overflow-wrap: anywhere; }
.ar-pill { font-family: 'Fira Code', ui-monospace, monospace; font-size: 11.5px; font-weight: 700; border-radius: var(--pill); padding: 2.5px 9px; white-space: nowrap; justify-self: end; }
.ar-up   { color: var(--up); background: rgba(52, 211, 153, .10); border: 1px solid rgba(52, 211, 153, .32); }
.ar-down { color: var(--down); background: rgba(248, 113, 113, .10); border: 1px solid rgba(248, 113, 113, .32); }
.ar-gold { color: var(--gold-2); background: rgba(245, 176, 53, .10); border: 1px solid rgba(245, 176, 53, .32); }
.ar-cta { display: flex; gap: 10px; margin-top: 14px; flex-wrap: wrap; }
.ar-btn { border: 0; border-radius: var(--radius-sm); cursor: pointer; padding: 11px 18px; font-size: 14px; font-weight: 700; font-family: inherit; transition: filter var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease); }
.ar-btn-gold { color: #1a1205; background: linear-gradient(180deg, var(--brand-gold-2), var(--brand-gold)); }
/* Re-assert the gold fill on hover/focus: the generic `button:hover` (background:
   var(--card-2), specificity 0,1,1) otherwise beats plain `.ar-btn-gold` (0,1,0)
   and repaints the CTA #11191d under its #1a1205 text — contrast ~1.0, the
   label vanishes. Same trap as .wz-btn and .bond-claim. */
.ar-btn-gold:hover, .ar-btn-gold:focus-visible { filter: brightness(1.07); box-shadow: var(--glow-gold);
  background: linear-gradient(180deg, var(--brand-gold-2), var(--brand-gold)); border-color: transparent; }
.ar-btn-ghost { color: var(--muted); background: transparent; border: 1px solid var(--border); font-weight: 600; }
.ar-btn-ghost:hover, .ar-btn-ghost:focus-visible { border-color: var(--accent-2); color: var(--text); background: var(--card-2); }
.ar-btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }
@media (max-width: 900px) {
  .ar-row { grid-template-columns: 26px minmax(0, 1fr) auto; }
  .ar-det { grid-column: 2; }
  .ar-pill { grid-column: 3; grid-row: 1; }
}

/* ============================================================================
 * Movers & New Holders — the League's story blocks (Render._movers). Two
 * cards side by side + a promotions strip; ≤900px stacks to one column.
 * ========================================================================== */
.mv-grid { display: grid; grid-template-columns: 1.25fr 1fr; gap: 12px; margin-top: 12px; }
.mv-card h3 .sub, .mv-promos h3 .sub { color: var(--faint); font-weight: 400; font-size: 12.5px; }
.mv-hint { color: var(--faint); font-size: 13px; margin: 10px 0 2px; }
.mv-list { list-style: none; margin: 10px 0 0; padding: 0; }
.mv-row { display: grid; grid-template-columns: 30px minmax(0, 1fr) auto; gap: 10px; align-items: center; padding: 9px 2px; border-top: 1px solid var(--hair); }
.mv-tier { font-size: 16px; text-align: center; }
.mv-who { min-width: 0; }
.mv-addr { font-family: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; display: block; }
.mv-sub { font-size: 11.5px; color: var(--faint); display: block; }
.mv-bal { font-size: 13.5px; }
.mv-delta { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; font-weight: 700; }
.mv-up { color: var(--up); }
.mv-down { color: var(--down); }
.mv-meter { width: 92px; height: 5px; border-radius: var(--pill); background: rgba(255, 255, 255, .06); overflow: hidden; display: flex; }
.mv-meter i { display: block; height: 100%; background: var(--up); }
.mv-meter-down { justify-content: flex-end; }
.mv-meter-down i { background: var(--down); }
.mv-foot { margin-top: 10px; padding-top: 8px; border-top: 1px solid var(--hair); font-size: 12px; }
.mv-promos { margin-top: 12px; }
.mv-promo-strip { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.mv-promo { display: inline-flex; align-items: center; gap: 6px; background: var(--input); border: 1px solid var(--border-soft); border-radius: var(--pill); padding: 6px 13px; font-size: 13px; }
.mv-promo .mv-addr { display: inline; font-size: 12.5px; color: var(--muted); }
.mv-to { color: var(--gold-2); font-weight: 600; }
@media (max-width: 900px) { .mv-grid { grid-template-columns: 1fr; } }

.printer-row {
  padding: 12px;
  border-radius: var(--radius-sm);
  background: rgba(22, 33, 38, .55);   /* --input at 55% over the page --bg */
  border: 1px solid var(--border-soft);
}
.printer-row + .printer-row { margin-top: 10px; }

.p-head { display: flex; align-items: center; flex-wrap: wrap; gap: 9px; margin-bottom: 11px; }
.p-name { font-weight: 600; font-size: 14px; color: var(--text); }
.p-reward {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: var(--pill);
  padding: 1px 8px;
}
/* Grouped provenance chips: which tracked wallets hold this token. Quiet
   pills — provenance, not data — so they never compete with the figures. */
.holder-chips { display: inline-flex; flex-wrap: wrap; gap: 6px; margin-left: 2px; min-width: 0; align-items: center; }
.holder-chip { font-size: 10.5px; font-weight: 600; color: var(--muted); background: var(--input);
  border: 1px solid var(--border-soft); border-radius: var(--pill); padding: 2px 8px; white-space: nowrap; }
/* Hero-token holding recap card: shared .printer-row bento, per-token identity below */
.fin-row .p-logo { width: 20px; height: 20px; border-radius: 50%; flex: none; object-fit: cover; }
/* Token-scoped identity: each hero card wears its OWN colour, never the
   active token's (--gold swaps under the Missor toggle — img 5's bug). */
.fin-row.hero-finvesta .p-name,
.fin-row.hero-finvesta .pk.fin-hero .pk-val { color: var(--brand-gold-2); }
.fin-row.hero-missor .p-name,
.fin-row.hero-missor .pk.fin-hero .pk-val { color: var(--missor-blue); }
/* Goal pill, right-aligned in the head via margin-left:auto — gold-tinted,
 * same recipe as the FINVESTA card-h icon chips (gold-2 on rgba(gold,.14)
 * over --card-2, measured 8.53:1, comfortably AA). */
.goal-pill {
  margin-left: auto;
  flex: none;
  font-family: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: .03em;
  color: var(--brand-gold-2);   /* printer KPI: always gold, never the token accent */
  background: rgba(245, 176, 53, .14);
  border: 1px solid rgba(245, 176, 53, .35);
  border-radius: var(--pill);
  padding: 2px 9px;
  white-space: nowrap;
}

/* ---- Trifecta Bond card (per wallet) ---- */
.bond-row .bond-active { font-size: 10.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--up); margin-left: 8px; }
.bond-row .bond-pos { margin-left: auto; font-family: 'Fira Code', ui-monospace, monospace; font-size: 12px; font-weight: 700; color: var(--brand-gold-2); background: rgba(245, 176, 53, .10); border: 1px solid rgba(245, 176, 53, .32); border-radius: var(--pill); padding: 3px 10px; }
.bond-rewards .pk.bond-rw .pk-val { color: var(--brand-gold-2); }
/* The DAO platform's tile reads as the same bento with a gold identity, so
   the two PLS figures can't be mistaken for one another. */
.bond-rewards .bond-rw-dao { border-color: rgba(245, 176, 53, .35);
  background: linear-gradient(180deg, rgba(245, 176, 53, .10), var(--input)); }
.bond-rewards .bond-rw-dao .pk-label { color: var(--brand-gold-2); }
.bond-tiers { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; }
.bond-tier { flex: 1 1 130px; background: var(--input); border: 1px solid var(--border-soft); border-radius: var(--radius-sm); padding: 9px 11px; display: flex; align-items: baseline; gap: 6px; flex-wrap: wrap; }
.bond-tier .bt-name { font-size: 12px; font-weight: 700; color: var(--muted); }
.bond-tier .bt-bonus { font-family: 'Fira Code', ui-monospace, monospace; font-weight: 800; color: var(--brand-gold-2); margin-left: auto; }
.bond-tier .bt-state { flex-basis: 100%; font-size: 11px; color: var(--muted); }
.bond-tier.on .bt-state { color: var(--up); }
.bond-claim { margin-top: 14px; width: 100%; border: 0; border-radius: var(--radius-sm); cursor: pointer; padding: 13px; font-size: 14px; font-weight: 700; color: #1a1205; font-family: inherit; background: linear-gradient(180deg, var(--brand-gold-2), var(--brand-gold)); transition: filter var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease); }
/* Re-assert the gold fill on hover: the generic `button:hover` (background:
   var(--card-2), specificity 0,1,1) otherwise beats plain `.bond-claim` (0,1,0)
   and turns the CTA dark-grey with unreadable dark text (same trap as .wz-btn). */
.bond-claim:hover:not(:disabled) { filter: brightness(1.06); box-shadow: var(--glow-gold);
  background: linear-gradient(180deg, var(--brand-gold-2), var(--brand-gold)); border-color: transparent; }
.bond-claim:disabled { opacity: .5; cursor: default; background: var(--input); color: var(--faint); border: 1px solid var(--border-soft); }

/* Two claim platforms, one row: side by side, stacked on phones. The lone
   legacy button (unknown DAO) spans both columns instead. */
.bond-claim-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; margin-top: 14px; }
.bond-claim-row .bond-claim { margin-top: 0; }
.bond-claim-row .bond-claim-solo { grid-column: 1 / -1; }
@media (max-width: 560px) { .bond-claim-row { grid-template-columns: 1fr; } }
.bond-activate { display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border-soft); border-radius: var(--radius-sm);
  padding: 13px; font-size: 14px; font-weight: 700; color: var(--accent-2);
  text-decoration: none; }
.bond-activate:hover { border-color: var(--accent-2); }
.bcl-btns { display: flex; gap: 6px; flex-wrap: wrap; margin-left: auto; }
.bcl-btns .bond-claim { margin-top: 0; width: auto; padding: 8px 12px; font-size: 12px; }
.bcl-btns .bond-activate { padding: 8px 12px; font-size: 12px; }

/* Grouped Bond card: summed pending tiles above, one claim row per bond below.
   Claiming is one transaction per tokenId signed by that bond's owner, so the
   ids, tiers and buttons stay per-bond and the owner label must be visible —
   the user has to know which wallet signs. */
.bond-claim-list { display: flex; flex-direction: column; gap: 6px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border-soft); }
.bcl-row { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; background: var(--input); border: 1px solid var(--border-soft); border-radius: var(--radius-sm); padding: 7px 8px 7px 12px; }
.bcl-id { font-family: 'Fira Code', ui-monospace, monospace; font-size: 12.5px; color: var(--brand-gold-2); font-weight: 600; min-width: 5ch; }
.bcl-who { font-size: 12.5px; color: var(--muted); flex: 1 1 10ch; min-width: 8ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bcl-tiers { display: flex; gap: 5px; }
.bcl-t { font-family: 'Fira Code', ui-monospace, monospace; font-size: 10.5px; color: var(--faint); background: var(--card); border: 1px solid var(--border-soft); border-radius: var(--pill); padding: 2px 7px; }
.bcl-t.on { color: var(--brand-gold-2); border-color: rgba(245, 176, 53, .3); }

/* ---- LP position cards (per wallet, and grouped) — split V2/V3 (Render.
   _lpV2Card/_lpV3Card): two containers, .lp-v2-card cyan and .lp-v3-card
   gold, sharing this base .lp-row identity plus the .lpk-* KPI-card bento
   below. Per-row bits still shared across both cards: pair label, delisted
   note, as-of staleness note, the measured-APR chip. ---- */
.lp-pos-list { display: flex; flex-direction: column; gap: 6px; }
.lp-pair { font-size: 12.5px; font-weight: 600; color: var(--text); }
.lp-delisted { font-size: 10.5px; color: var(--down); background: rgba(248, 113, 113, .10); border: 1px solid rgba(248, 113, 113, .35); border-radius: var(--pill); padding: 1px 8px; }
.lp-asof { font-size: 10.5px; color: var(--faint); }
.lp-apr {
  margin-left: auto;
  font-family: 'Fira Code', ui-monospace, monospace;
  font-size: 11px;
  font-weight: 600;
  color: var(--accent-2);
  background: var(--card);
  border: 1px solid rgba(101, 219, 232, .35);
  border-radius: var(--pill);
  padding: 2px 9px;
  white-space: nowrap;
  cursor: help;
}

/* V3 (concentrated-liquidity NFT) rows, listed inside their own .lp-v3-card
   (row wrapper/pair/delisted/as-of classes above are shared with V2's card).
   Gold identity (--gold-2) keeps V3 visually distinct from the cyan V2 chip
   above — two different on-chain read paths, never to be mistaken for one
   pool-share row. */
.v3-pos-list { display: flex; flex-direction: column; gap: 6px; }
.v3-badge { font-family: 'Fira Code', ui-monospace, monospace; font-size: 11px; font-weight: 700; color: var(--gold-2); background: rgba(245, 176, 53, .10); border: 1px solid rgba(245, 176, 53, .32); border-radius: var(--pill); padding: 1px 8px; white-space: nowrap; }
/* Tri-state range dot: filled up-green (earning fees), filled faint (out of
   range, not earning), hollow amber (unreadable pool state — never folded
   into "out", which would claim a definite non-earning state). */
.v3-dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 auto; cursor: help; }
.v3-dot.in { background: var(--up); }
.v3-dot.out { background: var(--faint); }
.v3-dot.unk { background: transparent; border: 1.5px solid var(--gold-2); }
.v3-owner { font-size: 11px; color: var(--muted); }

/* ---- LP KPI cards: token logos (spec §4b) — 20px in tile labels, 26px
   (-lg) in pair identity / the V3 card header. The glyph branch has no
   circle: the emoji itself is the token's visual. ---- */
.tok-logo { display: inline-flex; width: 20px; height: 20px; flex: 0 0 auto; }
.tok-logo-lg { width: 26px; height: 26px; }
.tok-img { display: block; width: 100%; height: 100%; border-radius: 50%; object-fit: cover; background: var(--card-2); }
.tok-emoji { font-size: 18px; line-height: 1; }
.tok-emoji-lg { font-size: 23px; }
.tok-avatar { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; border-radius: 50%; color: #fff; font-family: 'Fira Sans', sans-serif; font-weight: 700; font-size: 11px; flex: 0 0 auto; }
.tok-avatar-lg { width: 26px; height: 26px; font-size: 14px; }
.pair-logos2 { display: inline-flex; align-items: center; flex: 0 0 auto; }
.pair-logos2 > * + * { margin-left: -9px; }
.pair-logos2 .tok-img, .pair-logos2 .tok-avatar { border: 1.5px solid var(--card); box-sizing: border-box; }
.lpk-toklabel { display: inline-flex; align-items: center; gap: 5px; min-width: 0; }
.lpk-tlt { overflow: hidden; text-overflow: ellipsis; }

/* ---- LP KPI cards (V2/V3 split): summary row + horizontal position rows.
   Fixed equal-width KPI columns (owner: never auto-fit reflow). Base
   .lpk-grid MUST stay before the media query — the CSS-source test matches
   the first occurrence. ---- */
.lpk-sum { display: grid; grid-template-columns: repeat(3, minmax(0, 220px)); gap: 8px; margin: 2px 0 4px; }
.lpk-s { position: relative; background: var(--input); border: 1px solid var(--border-soft); border-radius: var(--radius-sm); padding: 8px 10px; text-align: center; }
.lpk-sl { font-size: 9.5px; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); }
.lpk-sv { font-family: 'Fira Code', ui-monospace, monospace; font-variant-numeric: tabular-nums; font-weight: 700; font-size: 16px; margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lp-v2-card .lpk-sv.lpk-accent { color: var(--accent-2); }
.lpk-sv.lpk-dim { font-size: 12px; color: var(--faint); }
.lpk-sv.lpk-up { color: var(--up); }
.lpk-pos { display: grid; grid-template-columns: 210px 1fr; gap: 12px; align-items: center; background: var(--input); border: 1px solid var(--border-soft); border-radius: var(--radius-sm); padding: 8px 10px; }
.lpk-id { display: flex; flex-direction: column; gap: 4px; min-width: 0; align-items: flex-start; }
.lpk-id .lp-pair { display: flex; align-items: center; gap: 7px; min-width: 0; }
.lpk-id .lp-apr { margin-left: 0; }
.lpk-dex { font-size: 10.5px; color: var(--faint); }
.lpk-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 7px; min-width: 0; }
.lpk-tile { position: relative; min-width: 0; display: flex; flex-direction: column; justify-content: center; background: var(--card); border: 1px solid var(--border-soft); border-radius: var(--radius-sm); padding: 8px 9px; text-align: center; }
.lpk-tl { display: flex; align-items: center; justify-content: center; gap: 6px; min-height: 24px; font-size: 11px; letter-spacing: .05em; text-transform: uppercase; color: var(--muted); white-space: nowrap; overflow: hidden; }
.lpk-tv { font-family: 'Fira Code', ui-monospace, monospace; font-variant-numeric: tabular-nums; font-weight: 700; font-size: 15.5px; margin-top: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.lpk-sub { font-size: 10px; color: var(--faint); margin-top: 2px; }
.lpk-tile.lpk-gold .lpk-tv { color: var(--gold-2); }
.lpk-collapsed { display: none; }
.lpk-more { display: block; width: 100%; margin-top: 6px; background: none; border: 1px dashed var(--border-soft); border-radius: var(--radius-sm); color: var(--accent-2); font-family: inherit; font-size: 11.5px; padding: 6px; cursor: pointer; }
.lpk-more:hover, .lpk-more:focus-visible { border-color: var(--accent-2); }

/* V3 card accents: gold identity over the same lpk-* bento. */
.lp-v3-card .lpk-grid { grid-template-columns: repeat(5, 1fr); }
.lp-v3-card .lpk-sv.lpk-accent { color: var(--gold-2); }
.v3-head-img { width: 26px; height: 26px; border-radius: 50%; display: block; }
.lpk-idtop { display: flex; align-items: center; gap: 7px; }
.v3-id { font-family: 'Fira Code', ui-monospace, monospace; font-size: 11px; color: var(--gold-2); }
.lpk-rng { display: flex; align-items: center; gap: 5px; font-size: 11px; color: var(--muted); }

@media (max-width: 900px) { .lpk-grid, .lp-v3-card .lpk-grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 760px) { .lpk-pos { grid-template-columns: 1fr; } }

/* ---- Bond claim safety modal (#bond-claim-modal, BondClaim in app.js) ----
 * Generic .scrim/.modal recipe (same fixed-overlay + centered-gradient-card
 * shape as #del-overlay/#ws-overlay) kept under its own class names so any
 * future confirm-style dialog can reuse it without depending on the
 * del-/ws- prefixes. Contrast: title --text on --card-2, .lead/--muted on
 * --card-2, .kv value --text (verified rows get --up, ~8.5:1), .warn copy
 * --muted on the gold-tinted strip, .m-foot --faint — all past AA on this
 * gradient's lightest stop, matching the measured pairs used elsewhere on
 * this sheet. */
.scrim {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 20px;
  background: rgba(4, 7, 9, .7);
  overscroll-behavior: contain;
  animation: scrim-fade 160ms var(--ease);
}
.modal {
  width: min(440px, 100%);
  max-height: calc(100vh - 40px);
  overflow-y: auto;
  background: linear-gradient(180deg, var(--card-2), var(--card));
  border: 1px solid rgba(245, 176, 53, .3);
  border-radius: var(--radius);
  padding: 22px 22px 18px;
  box-shadow: 0 18px 50px rgba(0, 0, 0, .55);
  animation: modal-pop 200ms var(--ease);
}
.modal h3 {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 17px;
  font-weight: 700;
  margin: 0 0 10px;
}
.modal .x {
  margin-left: auto;
  border: 0;
  background: none;
  color: var(--muted);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  padding: 2px 4px;
}
.modal .x:hover { color: var(--text); }
.modal .lead { font-size: 13.5px; color: var(--muted); line-height: 1.55; margin: 0 0 14px; }
.kv { display: flex; flex-direction: column; gap: 8px; margin-bottom: 14px; }
.kv .r { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; font-size: 13px; }
.kv .k { color: var(--muted); }
.kv .v { color: var(--text); font-weight: 600; text-align: right; }
.kv .v.ok { color: var(--up); }
.warn {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  background: rgba(245, 176, 53, .1);
  border: 1px solid rgba(245, 176, 53, .3);
  border-radius: var(--radius-sm);
  padding: 10px 12px;
  font-size: 12.5px;
  color: var(--muted);
  line-height: 1.5;
  margin-bottom: 16px;
}
.m-actions { display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 10px; }
.btn {
  border: 0;
  border-radius: 10px;
  padding: 10px 18px;
  font-size: 14px;
  font-weight: 700;
  font-family: inherit;
  cursor: pointer;
  transition: filter var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.btn.primary { color: #1a1205; background: linear-gradient(180deg, var(--brand-gold-2), var(--brand-gold)); }
.btn.primary:hover:not(:disabled) { filter: brightness(1.06); box-shadow: var(--glow-gold); }
.btn.primary:disabled { opacity: .55; cursor: default; filter: none; box-shadow: none; }
.btn.ghost { color: var(--muted); background: none; border: 1px solid var(--border); }
.btn.ghost:hover { border-color: var(--accent-2); color: var(--text); background: var(--card-2); }
.m-foot { margin-top: 14px; font-size: 12px; color: var(--faint); }
#bond-claim-modal .m-foot.bc-err { color: var(--down); }
#bond-claim-modal .m-foot.bc-ok { color: var(--up); }
@keyframes scrim-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes modal-pop {
  from { opacity: 0; transform: translateY(8px) scale(.97); }
  to   { opacity: 1; transform: none; }
}

/* ---- payout history: line under earned metrics + optional History toggle/chart ---- */
.payout-line {
  margin-top: 10px;
  font-size: 12px;
  color: var(--muted);
  display: flex;
  align-items: center;
  gap: 4px;
}
.payout-line .ok { color: var(--up); }
/* GAS MONEY note: native PLS leaves no Transfer logs, so there is no history to show —
 * deliberately the dimmest of the three payout-line variants. --faint on this row's own
 * background (rgba(22,33,38,.55) over --bg now that .printer-row sits directly on the
 * page — these cards sit directly on it, in no card wrapper)
 * measures ~5.65:1, ONLY improving on the pre-change ~5.29:1 (a darker backdrop increases
 * contrast for light text) — clears AA (4.5:1 normal text) with margin either way. */
.payout-line.dim { color: var(--faint); }
.payout-line.syncing .dots { display: inline-block; animation: payout-pulse 1.3s ease-in-out infinite; }
@keyframes payout-pulse { 0%, 100% { opacity: .3; } 50% { opacity: 1; } }

.btn-history {
  margin-top: 8px;
  padding: 5px 11px;
  min-height: 30px;
  font-size: 12px;
  background: transparent;
}

/* Chart hub on goal cards: pill tabs, the active one filled cyan. Fixed
   cyans (goal identity), dark text on the fill. */
.chart-hub { display: inline-flex; gap: 6px; margin-left: auto; flex-wrap: wrap; }
.hub-btn { font-size: 11px; font-weight: 600; color: var(--muted); background: transparent;
  border: 1px solid var(--border); border-radius: var(--pill); padding: 4px 11px;
  min-height: 26px; cursor: pointer; font-family: inherit; }
.hub-btn:hover { border-color: var(--accent-2); color: var(--text); background: var(--card-2); }
.hub-btn.on, .hub-btn.on:hover { color: #0a1014; border-color: transparent; font-weight: 700;
  background: linear-gradient(180deg, #65dbe8, #3fc0d4); }
.hub-btn:focus-visible { outline: 2px solid var(--gold); outline-offset: 2px; }

/* Chart hub panel chrome (owner finding, Batch C review): the approved mockup's
 * chart panels are contained and titled, like every other nested tile in this
 * app — the shipped version was a bare canvas with no visible boundary.
 * `.hub-panel` gives Daily's `.history-wrap` and both `.chart-wrap`s (Goal
 * pace / ROI) the same var(--input) fill + --border-soft ring + --radius-sm
 * corners as the rest of the card's nested boxes; the plain, non-hub History
 * toggle's `.history-wrap` (`_appendHistoryToggle`, no goal set) never carries
 * this class, so it is untouched. */
.hub-panel { background: var(--input); border: 1px solid var(--border-soft); border-radius: var(--radius-sm); padding: 11px; }
/* Small uppercase label, first child of every panel, above its chart —
 * `_appendChartHub` builds it once per panel and (pace/roi) never re-clears
 * it: only `.hub-panel-body` (below) gets wiped and refilled on each open. */
.hub-panel-title { font-size: 10px; letter-spacing: .09em; text-transform: uppercase; color: var(--muted); margin: 0 0 8px; }

/* `.history-wrap` is `height: auto` but carries a `min-height` matching the
 * CSS-bar chart's own fixed geometry (val line ~15px + .h-track's fixed 96px +
 * .h-axis line ~26px ≈ 137px) — since the bars render synchronously on open
 * (no async gap), this is a belt-and-suspenders guarantee against layout
 * shift, not a load-bearing one: no canvas/ResizeObserver feedback loop
 * exists: the History chart is pure HTML/CSS bars, not a canvas. This is the
 * BASE calibration, for the plain non-hub History toggle (`_appendHistoryToggle`,
 * every non-goal card) — its `.history-wrap` never carries `.hub-panel`, so it
 * gets none of that chrome and must stay at the bars' own geometry, not the
 * hub's taller one below. */
.history-wrap { position: relative; width: 100%; height: auto; min-height: 137px; margin-top: 10px; }

/* Daily's panel INSIDE the hub carries `.hub-panel` too (chrome + title —
 * see that rule's own comment above) on top of the same bar stack, so its
 * `min-height` needs the extra room: 11px padding × 2 + 1px border × 2 + the
 * title row's ~23px ≈ 47px more than the 137px base. Scoped to
 * `.hub-panel.history-wrap` (goal cards only) so the plain toggle above is
 * never affected. */
.hub-panel.history-wrap { min-height: 184px; }

/* `.chart-wrap` hosts the Goal pace / ROI Chart.js canvas — same fixed-height
 * positioned wrapper as `.trend-canvas-wrap` (Chart.js is responsive with
 * `maintainAspectRatio: false` and fills this box; sizing the canvas itself
 * would be dead CSS). Shorter on narrow phones so a goal card with the panel
 * open still fits within a reasonable scroll on a small screen. Flex column so
 * `.hub-panel-body` (below) gets exactly the height left over after
 * `.hub-panel-title` — these 190/150px numbers stay the PANEL's total height,
 * unchanged from before the chrome landed; only the canvas inside shrinks to
 * fit the padded, titled box. */
.chart-wrap { position: relative; width: 100%; height: 190px; margin-top: 10px; display: flex; flex-direction: column; }
@media (max-width: 560px) { .chart-wrap { height: 150px; } }
/* Goal pace is the flagship panel — taller than its siblings so the climb
   toward the dashed target reads at a glance. ROI/Daily keep the compact
   height; the attribute selector scopes this to the pace panel alone. */
.chart-wrap[data-panel="pace"] { height: 333px; }
@media (max-width: 560px) { .chart-wrap[data-panel="pace"] { height: 263px; } }

/* The canvas's own immediate parent, not `.chart-wrap` itself: Chart.js sizes
 * the canvas off `getBoundingClientRect()` of `canvas.parentNode` minus that
 * parent's OWN border/padding (helpers.dom's getMaximumSize) — it has no idea
 * a title sits above it in the same box. `flex: 1` hands this div exactly the
 * height left over after `.hub-panel-title`, so the canvas fills the padded
 * box instead of overflowing past the panel's fixed height. */
.hub-panel-body { flex: 1 1 auto; min-height: 0; position: relative; }

/* ---- .h-chart: pure CSS bar chart in og's cyan ---- */
.h-chart { display: flex; align-items: flex-end; gap: 4px; width: 100%; }
.h-col { flex: 1; min-width: 0; display: flex; flex-direction: column; align-items: center; }
.h-val {
  font-family: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 10px;
  color: var(--faint);
  white-space: nowrap;
  margin-bottom: 5px;
  letter-spacing: -.3px;
}
.h-track { width: 100%; height: 96px; display: flex; align-items: flex-end; }
.h-bar {
  width: 100%;
  border-radius: 3px 3px 0 0;
  background: linear-gradient(180deg, var(--accent-2), rgba(101, 219, 232, .35));
  transition: height 500ms var(--ease);   /* nulled under prefers-reduced-motion below */
}
/* Today: incomplete UTC bucket, still growing until midnight — not comparable to the
 * other bars and shouldn't look like it (hatched, dashed outline, no bottom border). */
.h-col.today .h-bar {
  background: repeating-linear-gradient(135deg, rgba(101, 219, 232, .5) 0 4px, transparent 4px 8px);
  border: 1px dashed var(--accent);
  border-bottom: none;
}
.h-col.today .h-val { color: var(--accent); }
/* Zero, non-today day: the tick IS the information — hiding it would compress the
 * axis and make distant days look adjacent. */
.h-col.zero .h-bar { background: var(--border); }
.h-axis {
  width: 100%;
  margin-top: 5px;
  padding-top: 5px;
  border-top: 1px solid var(--border);
  text-align: center;
  font-family: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: 10px;
  color: var(--faint);
  white-space: nowrap;
}

/* ---- bento KPI cards: Today / Received / Pending / Payouts (+ Goal) ----
 * Modelled on the top KPI strip's `.kpi` (same card gradient/border/shadow/
 * radius, same 10.5px uppercase label + Fira Code tabular value recipe) but
 * namespaced `.pk`/`.pk-grid` on purpose — do NOT reuse `.kpi`/`.kpis`, that
 * grid is a different (7-col) layout living at the page's top strip.
 * AA: `.pk`'s surface is the identical card-2->card gradient already measured
 * for `.kpi` (worst case, its lighter --card-2 end): --faint label 5.54:1,
 * --muted sub 6.90:1, --text value 17.02:1, --up 9.25:1, --down 6.43:1 all
 * carry over unchanged. The two colors new to this surface were measured the
 * same way: --gold-2 (Received value) 11.1:1, --accent-2 (Payouts value)
 * 10.9:1, --accent (Goal % figure) >13:1. */
.pk-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 10px;
  margin-top: 12px;
}
.pk {
  display: flex;
  flex-direction: column;
  background: linear-gradient(180deg, var(--card-2), var(--card));
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  padding: 11px 12px;
  position: relative;
  /* Visible, not hidden: the ∗ marker's note bubble is an absolutely positioned
     child of this card and has to be able to leave it. Every child that could
     otherwise spill clips itself — .pk-val, .pk-sub and .pk-label each set their
     own overflow, and .payout-line wraps instead of overflowing. */
  overflow: visible;
  min-width: 0;
  box-shadow: var(--shadow);
}
.pk-label {
  font-size: 10.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--faint);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;          /* self-clipping: the card does not clip for it */
  text-overflow: ellipsis;
}
.pk-val {
  font-family: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 22px;
  font-weight: 700;
  color: var(--text);
  margin-top: 5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pk-sub {
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  margin-top: 3px;
  min-height: 15px;   /* reserved: an empty/short sub never shifts the card below it */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pk .pk-sub .usd { color: var(--up); }

/* TODAY — fresh income: 2px green top hairline (same recipe as .kpi::after),
 * green label/value, sub stays --faint (it's just the word "today"). */
.pk.today::after {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  background: rgba(52, 211, 153, .55);
  /* Its own rounded top corners: the card is overflow:visible, so the hairline
     has to follow the card's radius itself. On a 2px-tall box the radii scale
     down to 2px, which is exactly the shape a clip would give it. */
  border-radius: var(--radius-sm) var(--radius-sm) 0 0;
}
.pk.today .pk-label, .pk.today .pk-val { color: var(--up); }
.pk.today .pk-sub { color: var(--faint); }
/* daily ROI (pnas "ROI giorno" parity) — green to read as a positive return,
   tied to the Today card's own green accent; the plain "today" caption (no
   .roi) stays --faint. */
.pk.today .pk-sub.roi { color: var(--up); }

/* RECEIVED — lifetime payout, the printer's headline number. */
.pk.received .pk-val { color: var(--brand-gold-2); }   /* Realized Rewards: printer KPI, always gold, never the token accent */

/* PAYOUTS — count (cyan) + the History toggle share one flex row (.pay-top),
 * the toggle pinned right and vertically centred against the count; the reused
 * .payout-line badge sits full-width beneath. Inlining the toggle removes its
 * former dedicated bottom row, so this card's natural height now matches its 3
 * siblings (label + value + sub) instead of standing ~50px taller and forcing
 * the whole grid row up with it. The
 * min-height:136px reservation is gone: it existed only to keep the row steady
 * as the STACKED button appeared/disappeared, but an inline toggle never adds
 * vertical height (the 22px count line is taller than the ~30px button), so a
 * printer flipping syncing -> ready stays silent without any reservation. */
.pk.pay .pk-val { color: var(--text); }   /* payouts count: always white, never the token accent */
.pay-top {
  display: flex;
  align-items: center;              /* count + toggle centred on the same band */
  justify-content: space-between;
  gap: 8px;
  margin-top: 5px;                  /* the row carries the count's former top gap */
}
.pay-top .pk-val { margin-top: 0; flex: 1; min-width: 0; }   /* count can ellipsize before the toggle */
/* Badge lines up with the other cards' .pk-sub: 3px below the value row, exactly
 * matching .pk-sub's 3px top gap — the .pay-top row is already the same height as
 * a sibling .pk-val, so 3px puts the badge top on the same line.
 * The short status stamps read uppercase like a sibling symbol ("USDC");
 * text-transform keeps the DOM text lowercase, so screen readers are unaffected.
 * The dim GAS MONEY note is a full sentence — excluded so it isn't shouted. */
.pk.pay .payout-line { margin-top: 3px; font-size: 11px; }   /* 11px matches the sibling .pk-sub (was 12px, read visibly larger) */
.pk.pay .payout-line:not(.dim) { text-transform: uppercase; letter-spacing: .03em; }
.pk.pay .btn-history { margin-top: 0; flex: 0 0 auto; }      /* never shrink; stay one line */

/* A missing reading is not a small figure — it is an absence, and it must not
   read as data. 15px is 0.68 x the 22px figure size, written in px on purpose:
   an `em` in `font-size` resolves against the PARENT's size (the card's, ~15px),
   not the value's own, which would land at ~10px. `color` is the one property
   this shares with the three variant rules above (.pk.today/.pk.received/.pk.pay
   .pk-val), and all four selectors are the SAME specificity — three classes
   each, (0,3,0) — so there is no specificity winner: the cascade falls through
   to source order, and whichever of the tied rules is declared LAST wins. This
   rule is placed after all three variants for exactly that reason; moving it
   back above them (or adding a new variant below it) silently hands the dash's
   colour back to gold/green/white. */
.pk .pk-val.pk-empty {
  font-size: 15px;
  font-weight: 400;
  color: var(--faint);
}

/* GOAL — full-width hero card, cyan glow (mirrors pnas' break-even card
 * treatment): radial cyan tint over the same card gradient + a cyan-tinted
 * border/shadow. Progress bar and figures reuse the card's own tabular
 * value styling; foot line is the plain-language "N <SYM> to go". */
.pk.goal {
  grid-column: 1 / -1;
  background:
    radial-gradient(140% 160% at 12% -10%, rgba(0, 246, 255, .14), transparent 55%),
    linear-gradient(180deg, var(--card-2), var(--card));
  border: 1px solid rgba(0, 246, 255, .35);
  box-shadow: var(--shadow), 0 0 22px rgba(0, 246, 255, .16);
}
/* AA: the radial's hot spot (12% width / -10% height — the top-left corner)
 * sits directly under .pk-label ("Goal progress"). --faint (used everywhere
 * else on the untinted .pk surface, 5.54:1 there) measures only 4.32-4.33:1
 * on this tinted surface's brightest point — just under the 4.5:1 AA minimum
 * for 10.5px/600-weight text. --muted clears it with margin: re-measured
 * 5.38-5.40:1 at the same composited hot-spot pixel (Chromium, 1600px and
 * 375px). The rest
 * of the goal card's text was re-checked on the same tinted surface and
 * already clears AA: .pk-goal-of 6.13:1, .pk-goal-foot 7.20:1, .pk-goal-pct
 * 13.48:1, .pk-goal-cur 14.61:1 — no change needed there. */
.pk.goal .pk-label { color: var(--muted); }
.pk-goal-row {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 5px 10px;
  margin-top: 6px;
  font-family: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;
  font-variant-numeric: tabular-nums;
}
.pk-goal-cur { font-size: 22px; font-weight: 700; color: var(--text); }
.pk-goal-of { font-size: 12.5px; color: var(--muted); }
.pk-goal-pct { margin-left: auto; font-size: 13px; font-weight: 700; color: var(--accent); }
/* Goal bar: the burnbar recipe (14px pill track, gradient fill, glow) in the
   goal's own cyan — visually the same family as Supply & Burn, so "progress
   toward a target" reads the same everywhere. Fixed cyans, not --accent:
   the goal identity must not swap with the hero-token theme. */
.pk-bar {
  position: relative;
  margin-top: 10px;
  height: 14px;
  border-radius: 999px;
  background: var(--input);
  border: 1px solid var(--border-soft);
  overflow: hidden;
}
.pk-bar > i {
  position: absolute;
  inset: 0;
  display: block;
  border-radius: 999px;
  background: linear-gradient(90deg, #1f7d8f 0%, #3fc0d4 52%, #65dbe8 100%);
  box-shadow: 0 0 14px rgba(101, 219, 232, .45);
  transition: width 800ms var(--ease);   /* nulled globally under prefers-reduced-motion */
}
.pk-goal-foot {
  margin-top: 8px;
  font-size: 11.5px;
  color: var(--muted);
}
/* ETA date in cyan — same accent as .pk-goal-pct (13.48:1 on this tinted card,
 * well past AA; the foot sits below the radial hot spot so its surface is even
 * darker → contrast only higher). tabular-nums keeps the day count aligned. */
.pk-goal-eta {
  color: var(--accent);
  font-variant-numeric: tabular-nums;
}

@media (max-width: 900px) { .pk-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

/* Phones: 2×2 tiles instead of a one-per-row stack (owner, img 11 — the page
   scrolled forever with several wallets). minmax(0,1fr) lets the values'
   self-clipping engage; .pk.goal keeps spanning 1/-1 whatever the count.
   17px values: the 22px scale wants ~150px of column, a 2-col phone column
   offers ~160 minus padding — 17px keeps whole figures visible. */
@media (max-width: 560px) {
  .pk-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .pk-val { font-size: 17px; }
}

/* ============================================================================
 * Constellation — the scenic divider that carries the hero-token switch:
 * a star field (.cn-stars), the two hero logos side by side (.cn-toggle, the
 * control that re-scopes the whole dashboard), and the motto beneath them
 * (.cn-title). Nothing here is in normal flow, so .cn's height is explicit —
 * see the derivation on .cn.
 * ========================================================================== */
.cn {
  /* Borderless: no card chrome, the scene floats on the page bg, and the warm
     floor-glow fades to transparent so no rectangle edge is visible.
     --cn-center-y is the vertical centre of .cn-toggle, the only thing
     positioned against it. */
  --cn-center-y: 110px;
  position: relative;
  /* Both children are absolutely positioned, so this height IS the section's
     size — nothing here grows it. Derive it from five terms, top to bottom:
       --cn-center-y                centre of .cn-toggle              110
     + half .cn-toggle's height     195px tall                       + 97.5
     + breathing room to the motto                                   + 49.78
     + .cn-title's height           2 lines of .cn-title .tag at 44px + 84.72
     + .cn-title's bottom offset                                     + 30
                                                                     = 372
     Re-derive it whenever .cn-hero's padding, logo size or label size changes,
     or .cn-title .tag's font-size does: a stale height either clips the motto
     or leaves a void above the KPI strip. */
  height: 372px;
  margin: 20px 0 16px;   /* ~28px above (with #setup's 8px) matching the section rhythm; the motto then flows into the KPI strip */
  text-align: center;
  background: radial-gradient(ellipse at 50% 130%, rgba(42, 22, 0, .5) 0%, transparent 55%);
}
.cn-stars {
  position: absolute;
  inset: 0;
  background-image:
    radial-gradient(1px 1px at 20% 30%, #2a3342 50%, transparent 51%),
    radial-gradient(1px 1px at 80% 20%, #2a3342 50%, transparent 51%),
    radial-gradient(1.5px 1.5px at 65% 65%, #33405a 50%, transparent 51%),
    radial-gradient(1px 1px at 35% 75%, #2a3342 50%, transparent 51%),
    radial-gradient(1px 1px at 90% 55%, #2a3342 50%, transparent 51%),
    radial-gradient(1.5px 1.5px at 10% 60%, #33405a 50%, transparent 51%);
}

/* Hero-token toggle: the two hero logos, centred on --cn-center-y. Active = full
   opacity + a glow in the theme accent (var(--gold) is swapped to blue in missor
   mode, so the glow always matches the active token); inactive = dimmed +
   greyscale. */
.cn-toggle {
  position: absolute;
  left: 50%;
  top: var(--cn-center-y);
  transform: translate(-50%, -50%);
  z-index: 2;
  display: flex;
  gap: 44px;
  align-items: flex-start;
}
.cn-hero {
  background: none; border: none; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; gap: 18px;
  padding: 28px 24px 14px;      /* more top room between the logo and the hover-background edge */
  border-radius: 22px;
  opacity: .55;
  transition: opacity .35s ease, background .2s ease;
}
.cn-hero img {
  width: 80px; height: 80px; border-radius: 50%; object-fit: cover;   /* +30% larger than the base 62px logo size */
  background: var(--card); border: 2px solid var(--bg); display: block;
  filter: grayscale(.5) brightness(.72);
  transition: transform .35s ease, filter .35s ease, box-shadow .35s ease;
}
/* name label under each logo */
.cn-hero-lbl {
  font-family: "Arial Black", Impact, ui-sans-serif, sans-serif;
  font-weight: 900; font-size: 26px; letter-spacing: .02em; text-transform: uppercase;   /* +30% */
  color: var(--muted); margin-bottom: 16px;
  transition: color .35s ease;
}
.cn-hero:hover { opacity: .9; }
.cn-hero:hover img { filter: none; }
.cn-hero.active { opacity: 1; }
.cn-hero.active img {
  filter: none; transform: scale(1.22);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--gold) 70%, transparent),
              0 0 40px 6px color-mix(in srgb, var(--gold) 45%, transparent);
}
.cn-hero.active .cn-hero-lbl { color: var(--gold-2); }
@media (prefers-reduced-motion: reduce) { .cn-hero img { transition: none; } }

.cn-title {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 30px;   /* more breathing room below the motto */
  z-index: 3;
  padding: 0 12px;
}
/* Display face: the finvesta.eco splash's "SWAP
   WITH CLARITY." heading is plain system Arial Black (no webfont), 900,
   uppercase, tracking -0.045em, line-height 0.94 — mirrored here. */
.cn-title h2 {
  margin: 0;
  font-family: "Arial Black", Impact, ui-sans-serif, sans-serif;
  font-size: 44px;
  font-weight: 900;
  letter-spacing: -.045em;
  line-height: .94;
  color: var(--gold-2);
}
/* motto: official FINVESTA splash treatment —
   two hero lines, "MAKING DEFI." solid white + "FUN AGAIN." outlined */
.cn-title .tag {
  margin-top: 2px;   /* uniform tight rhythm with the h2 (reference line-height .94) */
  font-family: "Arial Black", Impact, ui-sans-serif, sans-serif;
  font-size: 44px;
  font-weight: 900;
  letter-spacing: -.045em;
  line-height: .94;
  color: #fff;
  text-transform: uppercase;
}
.cn-title .tag span { display: block; }
/* "Fun Again." outlined in cyan like finvesta.eco's "clarity."
   (their stroke: rgba(101,219,232,.76)); fallback: solid soft cyan */
.cn-title .tag-l2 { color: rgba(101, 219, 232, .6); }
@supports (-webkit-text-stroke: 1px #fff) {
  .cn-title .tag-l2 {
    color: transparent;
    -webkit-text-stroke: 1.5px rgba(101, 219, 232, .9);
  }
}

@media (max-width: 719px) {
  /* Unlike the desktop .cn declaration above, no fixed sum describes this
     bracket: --cn-center-y and .cn-title's height/bottom offset stay put, but
     the clamp() rules on .cn-toggle's children (below) shrink the logos, their
     padding and their labels continuously as the viewport narrows —
     .cn-toggle itself is ~190px tall at this bracket's 719px edge, ~123px at
     375px, ~107px at 320px — so the gap between the toggle and the motto grows
     to match. 336px is sized for the bracket's tightest case, 719px, where the
     toggle is tallest and the gap smallest; every narrower width only has more
     room. Both .cn-toggle and .cn-title stay position: absolute throughout
     this block (only their sizing is clamped, not their placement), so a
     taller toggle can never push .cn-title or collide with it — it can only
     shrink the gap above it. */
  .cn { --cn-center-y: 90px; height: 336px; }
  .cn-title h2 { font-size: 36px; }
  .cn-title .tag { font-size: 36px; }

  /* The switch is a flex row of two logo+label buttons, so its width comes from
     the type inside it, not from the column around it. Pinning it to both column
     edges keeps it from being centred on a point and growing past them, and the
     clamps below keep its intrinsic width inside the narrowest column this
     layout produces (277px at a 320px viewport). Both halves matter: a left-side
     LTR overflow cannot be scrolled to, so this has to fit rather than merely be
     reachable. Each clamp's ceiling is the desktop value, so 719px is seamless. */
  .cn-toggle {
    left: 0;
    right: 0;
    transform: translateY(-50%);
    justify-content: center;
    gap: clamp(10px, 6vw, 44px);
  }
  .cn-hero {
    padding: clamp(8px, 3.5vw, 28px) clamp(6px, 2.5vw, 24px) clamp(6px, 2vw, 14px);
    gap: clamp(8px, 2.5vw, 18px);
    min-width: 0;
  }
  /* Width AND height together: the logo is a circle, and .cn-hero.active img
     scales it 1.22x — at 56px that reaches 68px, which still clears the
     button's own padding, so the active glow never becomes page overflow. */
  .cn-hero img { width: clamp(56px, 17vw, 80px); height: clamp(56px, 17vw, 80px); }
  .cn-hero-lbl { font-size: clamp(13px, 3.8vw, 26px); margin-bottom: clamp(6px, 2vw, 16px); }
}

/* ============================================================================
 * KPI strip — seven market tiles directly under the motto: 7-col grid, 2px
 * hairline top edge at .5 opacity, 10.5px uppercase label, 19px Fira Code value,
 * 11px sub, on og tokens:
 * house card gradient/border/shadow, and the top hairline carries the hero
 * motto's three colours (gold→white→cyan). Same responsive steps as
 * the old sheet: ≤1080px 4-col, ≤760px 2-col. AA (worst case, measured on
 * --card-2, the gradient's light end): .lbl --faint 5.54:1, sub muted 6.90:1,
 * up 9.25:1, down 6.43:1, value --text 17.02:1.
 * ========================================================================== */
.kpis { display: grid; grid-template-columns: repeat(7, 1fr); gap: 10px; margin: 0 0 14px; }
.kpi {
  background: linear-gradient(180deg, var(--card-2), var(--card));
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  padding: 12px 13px;
  position: relative;
  overflow: hidden;
  min-width: 0;
  box-shadow: var(--shadow);
}
.kpi::after {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 2px;
  /* top hairline in the ACTIVE token's accent: fixed gold for FINVESTA, blue
     for MISSOR (--gold is swapped by the theme), no gradient. */
  background: var(--gold);
  opacity: .9;
}
.kpi .lbl {
  font-size: 10.5px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--faint);
  font-weight: 600;
  white-space: nowrap;
}
.kpi .val {
  /* tabular Fira Code + nowrap: a value update can never reflow the tile */
  font-family: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 19px;
  font-weight: 700;
  margin-top: 5px;
  white-space: nowrap;
}
/* sub line: named .kpi-sub, NOT .delta — the global `.delta` further down is
   the League/Holders trend PILL (Fira Code, padding, tinted bg) and equal
   specificity later in the sheet would win; the old strip's sub is plain text. */
.kpi .kpi-sub {
  font-size: 11px;
  font-weight: 600;
  margin-top: 2px;
  min-height: 16px;   /* reserved even while "—": no shift when a real sub lands */
  white-space: nowrap;
}
.kpi .kpi-sub.muted { color: var(--muted); }
.kpi .kpi-sub.up    { color: var(--up); }
.kpi .kpi-sub.down  { color: var(--down); }
@media (max-width: 1080px) { .kpis { grid-template-columns: repeat(4, 1fr); } }
@media (max-width: 760px)  { .kpis { grid-template-columns: repeat(2, 1fr); } }

/* ============================================================================
 * Hero token — 2x2 grid: Supply & Burn / Token Allocation /
 * My Position / Recent Burns, re-expressed with og tokens (gold family in
 * place of the old orange, --accent cyan, --up/--down, Fira Sans/Code).
 * Supersedes the earlier gold-18px h2/h3 treatment for these four cards:
 * they now carry the old `.card-h` header (tinted icon chip + 15px/700
 * title) instead — same recipe the Holders Explorer subsections use.
 * ========================================================================== */
#finvesta { display: grid; grid-template-columns: 1fr; gap: 14px; }
@media (min-width: 760px) { #finvesta { grid-template-columns: 1fr 1fr; } }

/* ---- shared card header: 26px tinted icon chip + 15px/700 title ----
   Icon chips are graphics (WCAG 3:1): measured over the chip tint composited
   on --card-2 (worst end of the card gradient) — gold-2 on rgba(gold,.14)
   8.53:1, cyan on rgba(101,219,232,.14) 9.75:1, up on rgba(up,.13) 7.23:1.
   The faint "N wallets" note: 5.87/5.54:1 (AA for its 12px text). */
.card-h { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 14px; }
.card-h .t {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0;
  font-size: 15px;
  font-weight: 700;
  color: var(--text);
  letter-spacing: .01em;
}
.card-h-note { font-size: 12px; color: var(--faint); white-space: nowrap; }
.svgi { stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; fill: none; }
.ico { width: 26px; height: 26px; display: grid; place-items: center; border-radius: 8px; flex: none; }
.ico .svgi { width: 15px; height: 15px; }
.ico-fire { background: rgba(245, 176, 53, .14); color: var(--brand-gold-2); }
.ico-pie  { background: rgba(101, 219, 232, .14); color: var(--accent); }
.ico-user { background: rgba(52, 211, 153, .13); color: var(--up); }

/* ---- Supply & Burn (top-left) ---- */
#card-supply {
  position: relative;
  overflow: hidden;
  padding: 20px;
  border-color: rgba(245, 176, 53, .22);
  background:
    radial-gradient(560px 200px at 92% -30%, rgba(245, 152, 56, .12), transparent 70%),
    linear-gradient(180deg, var(--card-2), var(--card));
}

/* Worst-case text background inside this card is --card-2 + the .12 gold
   radial (#2c2820): gold-2 bignum 9.19:1, muted meta 5.69:1, up pct
   7.63:1 — all past AA. */
.bignum {
  font-family: 'Fira Code', ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: clamp(26px, 6.4vw, 34px);
  font-weight: 700;
  line-height: 1.08;
  letter-spacing: -.01em;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}
#card-supply .bignum { color: var(--gold-2); }

.sub { color: var(--muted); font-size: 13px; margin: 6px 0 14px; }

.inline-meta { display: flex; align-items: baseline; gap: 12px; margin-top: 6px; font-size: 13px; flex-wrap: wrap; }
.inline-meta .lbl { color: var(--muted); }
/* $-value of the burned FINVESTA, under the "tokens burned · %" meta line */
.burn-usd { display: flex; align-items: baseline; gap: 8px; margin-top: 6px; flex-wrap: wrap; }
.burn-usd-v { font-family: 'Fira Code', ui-monospace, monospace; font-variant-numeric: tabular-nums; font-weight: 700; font-size: 19px; color: var(--up); }
.burn-usd-l { font-size: 11px; color: var(--muted); letter-spacing: .04em; text-transform: uppercase; }
/* live burned% — min-width covers "99.99%" so the trailing label never
   shifts when the digit count changes */
.inline-meta .pct {
  font-family: 'Fira Code', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--up);
  display: inline-block;
  min-width: 6ch;
}

/* FINVESTA neon logo — decorative brand accent in the top-right of Supply &
   Burn. The asset has a transparent background baked in
   (black keyed to alpha), so it composites cleanly on the dark card with no
   blend mode. Absolutely positioned (out of flow → no layout shift) and
   non-interactive; shrinks on mobile so it stays clear of the bignum. */
#card-supply { position: relative; overflow: hidden; }
#card-supply .neon-logo {
  position: absolute;
  top: 6px;
  right: 8px;
  width: clamp(74px, 12vw, 132px);
  height: auto;
  pointer-events: none;
  opacity: .96;
  z-index: 1;
}
@media (max-width: 560px) { #card-supply .neon-logo { width: 66px; top: 4px; right: 6px; opacity: .9; } }
/* The neon mascot is a FINVESTA-only brand asset — hide it in Missor mode;
   Missor has no equivalent art. */
:root[data-hero="missor"] #card-supply .neon-logo { display: none; }

.burnbar {
  position: relative;
  height: 14px;
  border-radius: 999px;               /* pill track */
  background: var(--input);
  border: 1px solid var(--border-soft);
  overflow: hidden;
  margin: 16px 0 6px;
}
/* Fire fill: hot-pink -> coral -> amber gradient with
   a rounded cap and a warm glow, so the burn reads as "flame". Its own pill
   radius rounds the leading cap (the track's overflow:hidden clips the tail). */
.burnbar > span {
  position: absolute;
  inset: 0;
  width: 0;
  border-radius: 999px;
  background: linear-gradient(90deg, var(--fire-1) 0%, var(--fire-2) 52%, var(--fire-3) 100%);
  box-shadow: 0 0 14px rgba(251, 92, 58, .55);
  transition: width 800ms var(--ease);   /* nulled globally under prefers-reduced-motion */
}
/* MISSOR view: the burn fill is a blue-steel gradient + blue glow instead of the
   warm fire — matches the token accent. */
:root[data-hero="missor"] .burnbar > span {
  background: linear-gradient(90deg, #1f6dbf 0%, #3fa9ff 52%, var(--missor-blue) 100%);
  box-shadow: 0 0 14px rgba(63, 169, 255, .5);
}
/* Two labels, one per end of the bar — space-between pins them to the ends. */
.burnbar-lbl { display: flex; justify-content: space-between; font-size: 11px; color: var(--muted); }

.period-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-top: 14px; }
.period-grid .p {
  background: var(--input);
  border: 1px solid var(--border-soft);
  border-radius: 10px;
  padding: 9px 8px;
  text-align: center;
  min-width: 0;
}
.period-grid .pl { font-size: 10px; color: var(--muted); letter-spacing: .06em; text-transform: uppercase; }
.period-grid .pv {
  font-family: 'Fira Code', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 15px;
  font-weight: 600;
  color: var(--gold-2);
  margin-top: 3px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.period-grid .pu { font-size: 10.5px; color: var(--faint); }
/* pv-long (set by Render._supply past 8 chars): a full MISSOR-scale figure
   (11 chars) needs ~73px at 11px — inside the ~80px desktop tile, and inside
   a 2×2 pg-long tile on the narrow bands below. */
.period-grid .pv.pv-long { font-size: 11px; }

/* Burn forecast — two projected-burn tiles below the period grid, gold-tinted
   to read as estimates (distinct from the actuals above), with a hairline
   header carrying the daily pace + how much history it's based on. */
.burn-fc { margin-top: 12px; }
.burn-fc-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; margin-bottom: 8px; }
.burn-fc-head .ttl { font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: var(--muted); }
.burn-fc-head .basis { font-size: 10.5px; color: var(--faint); font-variant-numeric: tabular-nums; white-space: nowrap; }
.burn-fc-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.burn-fc-grid .p {
  background: linear-gradient(180deg, rgba(245, 176, 53, .08), rgba(245, 176, 53, .02));
  border: 1px solid rgba(245, 176, 53, .22);
  border-radius: 10px; padding: 10px 8px; text-align: center; min-width: 0;
}
.burn-fc-grid .pl { font-size: 10px; color: var(--muted); letter-spacing: .05em; text-transform: uppercase; }
.burn-fc-grid .pv {
  font-family: 'Fira Code', ui-monospace, monospace; font-variant-numeric: tabular-nums;
  font-weight: 700; font-size: 20px; color: var(--gold-2); line-height: 1.15; margin: 2px 0; white-space: nowrap;
}
.burn-fc-grid .pu { font-size: 10.5px; color: var(--faint); }
/* Full projections past 8 chars: the half-card tile fits them at 15px on
   every viewport, so one step-down suffices (no grid flip needed here). */
.burn-fc-grid .pv.pv-long { font-size: 15px; }

/* narrow: 4 tiles still fit on one row, but the 15px Fira Code values
   ("653.9K", "96.89%") overflow the ~48px tile content box — step the value
   down and slim the padding so all 6-char values render whole. */
@media (max-width: 480px) {
  .period-grid { gap: 6px; }
  .period-grid .p { padding: 8px 4px; }
  .period-grid .pv { font-size: 12.5px; }
  /* no readable font fits an 11-char figure in a 48px box: go 2×2 instead */
  .period-grid.pg-long { grid-template-columns: repeat(2, 1fr); }
}
/* the 2-col #finvesta grid kicks in at 760px, halving the card width — each
   tile is NARROWER there (~68px at a 338px card, iPad portrait 768px) than at
   any single-col width, so the same step-down is needed across the 760-799px
   band; from 800px up the tiles fit the full 15px values again. */
@media (min-width: 760px) and (max-width: 799px) {
  .period-grid { gap: 6px; }
  .period-grid .p { padding: 8px 4px; }
  .period-grid .pv { font-size: 12.5px; }
  .period-grid.pg-long { grid-template-columns: repeat(2, 1fr); }   /* same 2×2 escape as ≤480px */
}

/* ---- Token Allocation: circulating (In LP + Held) donut, centered, with the
   circulating total in the hole; legend below as two impact stat tiles; a
   hairline footer keeps the burned headline as context. ---- */
.donut-wrap { display: flex; flex-direction: column; align-items: center; gap: 16px; }
.donut-holder { position: relative; width: min(196px, 66%); aspect-ratio: 1; }
.donut-holder canvas { width: 100% !important; height: 100% !important; }
.donut-center {
  position: absolute; inset: 0; display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 3px; pointer-events: none;
}
.donut-center .dc-value {
  font-family: 'Fira Code', ui-monospace, monospace; font-variant-numeric: tabular-nums;
  font-weight: 700; font-size: 27px; line-height: 1; color: var(--text);
}
/* dc-long (set by Render._allocation past 8 chars): 27px fits ~8 chars in the
   ~141px hole, 16px keeps even a 13-char billions figure inside the ring. */
.donut-center .dc-value.dc-long { font-size: 16px; }
.donut-center .dc-label {
  font-size: 10px; letter-spacing: .1em; text-transform: uppercase; color: var(--faint);
}
.legend { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; width: 100%; margin: 0; padding: 0; }
.legend li { list-style: none; }
/* each legend item is a centered mini-stat: dot + label, big value, share */
.rli {
  display: flex; flex-direction: column; align-items: center; gap: 4px;
  padding: 11px 8px; background: var(--input);
  border: 1px solid var(--border-soft); border-radius: 12px; text-align: center;
}
.rli .rl-head { display: inline-flex; align-items: center; gap: 6px; }
.rli .dot { width: 9px; height: 9px; border-radius: 3px; flex: none; }
.rli .rl { color: var(--muted); font-size: 12px; letter-spacing: .01em; }
.rli .rv {
  font-family: 'Fira Code', ui-monospace, monospace; font-variant-numeric: tabular-nums;
  font-weight: 700; font-size: 22px; line-height: 1.05; white-space: nowrap;
}
.rli .rp { font-family: 'Fira Code', ui-monospace, monospace; font-size: 11.5px; color: var(--faint); }
/* Full (un-K'd) token amounts reach 11+ chars: on a phone-width tile the 22px
   figure would clip, 17px keeps it whole. */
@media (max-width: 480px) { .rli .rv { font-size: 17px; } }
.rli .rp .rusd { color: var(--muted); }   /* the $-controvalue reads a touch above the % share */
.alloc-foot { margin: 2px 0 0; font-size: 11.5px; color: var(--faint); text-align: center; }
.alloc-foot b { color: var(--gold-2); font-weight: 700; font-variant-numeric: tabular-nums; }

/* ---- My Position (bottom-left): 3 stat tiles + league banner + wallets ---- */
#card-myposition .body { display: flex; flex-direction: column; min-height: 60px; }
.pos-top { display: flex; gap: 10px; flex-wrap: wrap; }
/* Centred: the three tiles are equal `flex: 1 1 0` columns that rewrap at
   several widths, so a left edge to align to is exactly what they lack — each
   label sits over its own figure instead. `.pl` and `.pv` are both blocks, so
   the tile's text-align carries to both. */
.pos-card {
  flex: 1 1 0;
  min-width: 120px;
  background: var(--input);
  border: 1px solid var(--border-soft);
  border-radius: 11px;
  padding: 11px 13px;
  text-align: center;
}
.pos-card .pl { font-size: 11px; color: var(--muted); white-space: nowrap; }
.pos-card .pl .dim { color: var(--faint); }
/* tile values on --input: text 15.70:1, up 8.54:1, accent 12.21:1 */
.pos-card .pv {
  font-family: 'Fira Code', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: clamp(15px, 1.6vw, 19px);
  font-weight: 700;
  margin-top: 4px;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.pos-card .pv.tok-fin { color: var(--brand-gold-2); }
.pos-card .pv.tok-missor { color: var(--missor-blue); }

/* league banner (cyan-tinted): text on the composited banner bg (#17272b
   worst case) measures 14.76:1, accent <b> 11.48:1, muted next-line 5.98:1,
   up amount 8.02:1 */
/* Two halves: LEFT tier progress + RIGHT combined-rank KPI,
   split by a hairline; stacks under 560px. */
.pos-league {
  margin-top: 14px;
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  gap: 12px;
  background: rgba(101, 219, 232, .07);
  border: 1px solid rgba(101, 219, 232, .25);
  border-radius: 11px;
  padding: 11px 13px;
}
.plg-tier { display: flex; align-items: center; gap: 12px; min-width: 0; }
.plg-badge { font-size: 27px; line-height: 1; flex: none; }
.plg-body { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.plg-top { font-size: 13px; color: var(--text); }
.plg-top b { color: var(--accent); }
.plg-next { font-size: 12px; color: var(--muted); }
.plg-next .num { color: var(--up); font-weight: 700; }
.plg-next b { color: var(--text); }
/* "to rise to 🐬 Dolphin" must never break: a wrap here can strand the emoji
   at the end of one line with its tier name orphaned on the next. */
.plg-rise { white-space: nowrap; }
/* .num's global inline-block/9ch backstop breaks inline prose — restore flow */
.plg-top .num, .plg-next .num { min-width: 0; display: inline; text-align: left; font-size: inherit; }
/* RIGHT half — combined-holdings rank KPI. --accent value >13:1, --muted label
   6.9:1, --faint sub 5.5:1 over the tinted banner: all clear AA. */
.plg-rank { min-width: 0; padding-left: 14px; border-left: 1px solid rgba(101, 219, 232, .2); text-align: center; }
.plg-rank-label { font-size: 11px; color: var(--muted); white-space: nowrap; }
.plg-rank-val {
  font-family: 'Fira Code', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 22px; font-weight: 700; line-height: 1.15;
  color: var(--gold-2);   /* gold, not cyan */
}
.plg-rank-sub { font-size: 11px; color: var(--faint); white-space: nowrap; min-height: 14px; }
@media (max-width: 560px) {
  .pos-league { grid-template-columns: 1fr; }
  .plg-rank { padding-left: 0; border-left: none; border-top: 1px solid rgba(101, 219, 232, .2); padding-top: 10px; }
}

.wallets { margin-top: 14px; display: flex; flex-direction: column; gap: 7px; }
/* flex-wrap tolerance: a pathological label+balance combination folds inside
   the row instead of propagating min-content width up into the grid track */
.wallet {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  font-size: 13px;
  padding: 7px 10px;
  background: var(--input);
  border: 1px solid var(--border-soft);
  border-radius: 9px;
}
/* user label chip (accent on tinted chip over --input: 9.05:1).
   FIXED max-width, not a percentage: percentage max-width is ignored during
   intrinsic (min-content) sizing, so a long user label would otherwise
   propagate its full width up and force the whole grid column (and the page
   at 375px) wider than the viewport. 160px ≈ 24 chars of the 30-char cap;
   the rest ellipsizes, full label on the chip's title. */
.wallet .tag {
  font-size: 10.5px;
  font-weight: 700;
  padding: 2px 8px;
  border-radius: 6px;
  background: rgba(0, 246, 255, .12);
  color: var(--accent);
  flex: 0 1 auto;
  min-width: 0;
  max-width: 160px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wallet .wn {
  font-family: 'Fira Code', ui-monospace, monospace;
  flex: 1;
  color: var(--muted);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
/* holder rank, muted, immediately left of the balance.
   min-width reserves the slot so "—" -> "#42" doesn't nudge the balance. */
.wallet .wrank {
  font-family: 'Fira Code', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 11.5px;
  color: var(--faint);
  white-space: nowrap;
  min-width: 4.5ch;
  text-align: right;
  flex: none;
}
.wallet .wv {
  font-family: 'Fira Code', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
}

/* Per-wallet trade-facts sub-line (since/last buy/last sell, or a
   queued/scanning chip) from walletscan. `.wallet` is `display: flex;
   flex-wrap: wrap;` and its other children (.tag/.wn/.wrank/.wv) are all
   fit-content, so without an explicit full basis this would just trail them
   inline when there's room — flex-basis: 100% is what forces it onto its own
   line under the rest of the row instead. */
.wallet .w-trades {
  flex-basis: 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 5px;
  align-items: center;
  font-size: 10.5px;
  color: var(--faint);
  font-family: 'Fira Code', ui-monospace, monospace;
  margin-top: 2px;
  white-space: normal;
}
.wallet .w-trades .pe-lb-chip { margin-left: 2px; }
/* One tinted pill per trade fact — the fact's semantics colour it (cyan
   provenance, green buy, red sell — the away-recap pill family), never the
   token accent. A null fact renders no chip at all. Contrast on the .10
   tints over the card gradient: accent-2 9.7:1, up 8.0:1, down 5.9:1. */
.wt-chip { font-weight: 700; border-radius: var(--pill); padding: 2px 8px; white-space: nowrap; }
.wt-since { color: var(--accent-2); background: rgba(101, 219, 232, .10); border: 1px solid rgba(101, 219, 232, .32); }
.wt-buy   { color: var(--up);   background: rgba(52, 211, 153, .10); border: 1px solid rgba(52, 211, 153, .32); }
.wt-sell  { color: var(--down); background: rgba(248, 113, 113, .10); border: 1px solid rgba(248, 113, 113, .32); }

/* Aquarium entry: a persistent CTA at the foot of My Position
   opening the full-page canvas aquarium. Sits outside .body so it survives the
   card's per-poll re-render; the card's own 16px padding aligns it. */
/* Aquarium CTA — lives in the My Position header (right slot, where the
   wallet-count note used to sit): a compact gold-2 OUTLINE pill (no fill).
   Wraps to a full-width row under the title on phones. */
.aquarium-link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  padding: 9px 16px;
  min-height: 38px;
  font-size: 12.5px;
  font-weight: 700;
  letter-spacing: .01em;
  white-space: nowrap;
  text-decoration: none;
  color: var(--gold-2);            /* gold-2 on the dark card: ~11.9:1 */
  border: 1px solid var(--gold-2);
  border-radius: var(--radius-sm);
  background: transparent;
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease), transform var(--t-fast) var(--ease);
}
/* re-assert own color + kill the global `a:hover` underline (a:hover, 0,1,1,
   otherwise wins on hover); faint gold tint + glow for affordance */
.aquarium-link:hover  { color: var(--gold-2); text-decoration: none; background: rgba(247, 197, 106, .10); box-shadow: var(--glow-gold); }
.aquarium-link:active { transform: translateY(1px); }
.aquarium-link:focus-visible { outline: 2px solid var(--gold-2); outline-offset: 2px; }
.aquarium-link .fin { font-size: 14px; line-height: 1; }
/* let the pill drop under the title (never overflow) on small screens */
#card-myposition .card-h { flex-wrap: wrap; }
@media (max-width: 480px) {
  #card-myposition .card-h .aquarium-link { flex: 1 1 100%; }
}

/* ---- Recent Burns (bottom-right): old activity-feed port ---- */
.act-feed {
  display: flex;
  flex-direction: column;
  gap: 8px;
  max-height: 300px;
  overflow-y: auto;
  overflow-x: hidden;
  padding-right: 3px;
}
/* Recent Burns sizes to its content (align-self:start defeats the grid's
   stretch) so a sparse feed never leaves a blank gap inside the card. The feed
   itself grows naturally up to a cap, then scrolls — the cap ~matches the
   taller My Position beside it, so a full feed sits roughly level with it while
   a short one just makes a shorter card. */
#card-burns { display: flex; flex-direction: column; align-self: start; }
#card-burns .act-feed { flex: 0 1 auto; min-height: 0; max-height: 500px; }
@media (max-width: 759px) {
  #card-burns .act-feed { max-height: 340px; }
}
.act {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 6px 9px;
  background: var(--input);
  border: 1px solid var(--border-soft);
  border-radius: 10px;
  padding: 8px 11px;
  font-size: 12.5px;
}
/* row bg is --input: BURN badge 7.40:1 (on its tinted chip), amount gold-2
   10.28:1, pct/time faint 5.11:1, address muted 6.36:1, link accent 12.21:1 */
.act .side { font-size: 9.5px; font-weight: 700; padding: 2px 7px; border-radius: 6px; letter-spacing: .04em; flex: none; }
.act .side.burn { background: rgba(245, 176, 53, .16); color: var(--brand-gold-2); }
.act .amt {
  font-family: 'Fira Code', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--gold-2);
  white-space: nowrap;
}
.act .burn-supply { font-family: 'Fira Code', ui-monospace, monospace; font-size: 11px; color: var(--muted); white-space: nowrap; }
.act .tm {
  font-family: 'Fira Code', ui-monospace, monospace;
  font-size: 10.5px;
  color: var(--faint);
  margin-left: auto;
  min-width: 56px;
  text-align: right;
}
/* one-time gold wash on rows that weren't in the previous render — #474029
   is rgba(gold,.22) precomposited over --input, so the animation ends
   exactly on the row's own resting background (no end-of-animation jump) */
@keyframes burnFlash { 0% { background-color: #474029; } 100% { background-color: var(--input); } }
.act.burn-flash { animation: burnFlash 1.2s var(--ease); }
/* ≤560px a burn row is chip · amount · age: the running supply is the
   Supply tile's job, and on a phone it was the one span pushing rows to
   two lines. */
@media (max-width: 560px) { .act .burn-supply { display: none; } }

/* ============================================================================
 * details.card — still used by LP Pairs, the one section that keeps its own
 * collapsible sub-card below its top-level title (owner's earlier explicit
 * "closed by default" choice). League/Trend/Holders Explorer do not use
 * <details> at all — see their own sections below.
 * ========================================================================== */
details.card { padding: 0; overflow: hidden; }
details.card summary {
  cursor: pointer;
  font-size: 14px;
  font-weight: 600;
  list-style: none;
  padding: 15px 16px;
  display: flex;
  align-items: center;
  gap: 8px;
  transition: color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
details.card summary::-webkit-details-marker { display: none; }
/* LP Pairs chevron: identical to the holders subsection toggle (.holders-toggle)
   — a bordered box around the same triangle, so the two collapsible sections
   match. The bare summary marker is dropped in favour of a
   decorative .lp-chevron span; a pseudo-element alone can't be both the box and a
   separately-rotating chevron. */
details.card summary::after { content: none; }
details.card summary:hover { color: var(--gold-2); }
details.card[open] summary { border-bottom: 1px solid var(--border-soft); }
.lp-chevron {
  margin-left: auto;
  flex: none;
  width: 30px; height: 30px;
  padding: 0; min-height: 0;
  background: var(--input);
  border: 1px solid var(--border);
  border-radius: 8px;
  display: grid;
  place-items: center;
}
.lp-chevron::after {
  content: '';
  width: 7px; height: 7px;
  border-right: 2px solid var(--faint);
  border-bottom: 2px solid var(--faint);
  transform: rotate(45deg);
  transition: transform var(--t) var(--ease), border-color var(--t-fast) var(--ease);
}
details.card[open] .lp-chevron::after { transform: rotate(-135deg); }
details.card summary:hover .lp-chevron::after { border-color: var(--gold-2); }
details.card .body { padding: 14px 16px; }

/* shared up/down pill — League's old chip trend reused this; now also the
   Holders Explorer's per-row balance delta (.holders-bal-td .delta below). */
.delta { font-family: 'Fira Code', ui-monospace, monospace; font-size: 12px; padding: 1px 7px; border-radius: var(--pill); }
.delta.up   { color: var(--up);   background: rgba(52, 211, 153, .12); }
.delta.down { color: var(--down); background: rgba(248, 113, 113, .12); }

/* ============================================================================
 * League Holder — top-level section, always visible (no collapse): a
 * responsive row of 6 tier cards (emoji, uppercase name, entry threshold,
 * count, 24h trend, Σ total).
 * ========================================================================== */
.league-grid { display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px; }
@media (max-width: 760px) { .league-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 480px) { .league-grid { grid-template-columns: repeat(2, 1fr); } }
/* the "League data unavailable." fallback is a grid child too — without this
   it would squeeze into the first 1fr track instead of spanning the row */
.league-grid > .hint { grid-column: 1 / -1; }
.lchip {
  background: var(--input);
  border: 1px solid var(--border-soft);
  border-radius: 12px;
  padding: 14px 10px 12px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  text-align: center;
  transition: border-color var(--t-fast) var(--ease);
}
.lchip:hover { border-color: var(--accent-2); }
.lchip .lemoji { font-size: 28px; line-height: 1; }
.lchip .lname { font-size: 10px; color: var(--muted); letter-spacing: .05em; text-transform: uppercase; font-weight: 600; margin-top: 3px; }
.lchip .lreq { font-family: 'Fira Code', ui-monospace, monospace; font-size: 9.5px; color: var(--faint); margin-top: 2px; letter-spacing: .02em; }
.lchip .lcount { font-family: 'Fira Code', ui-monospace, monospace; font-variant-numeric: tabular-nums; font-size: 22px; font-weight: 700; color: var(--text); }
.lchip .ltrend { font-family: 'Fira Code', ui-monospace, monospace; font-size: 11.5px; font-weight: 700; margin-top: 1px; }
.lchip .ltrend.up   { color: var(--up); }
.lchip .ltrend.down { color: var(--down); }
.lchip .ltrend.flat,
.lchip .ltrend.none { color: var(--faint); }
.lchip .ltotal {
  font-family: 'Fira Code', ui-monospace, monospace;
  font-size: 11px;
  font-weight: 600;
  color: var(--gold-2);
  margin-top: 6px;
  padding-top: 6px;
  border-top: 1px solid var(--border-soft);
  width: 100%;
}

/* ---- Trend — top-level section, always visible: metric buttons left,
   period buttons right (both reuse .lp-sort), Chart.js line chart below ---- */
.trend-toolbar { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 14px; }
/* Chart.js (responsive) writes inline width/height styles on the canvas, so
   sizing #chart-trend here would be dead CSS — the chart is created with
   maintainAspectRatio:false and fills this positioned wrapper instead. Fixed
   (not min-) height: the section occupies its final size whether the chart
   has painted yet or not — zero load-time layout shift. */
.trend-canvas-wrap { position: relative; width: 100%; height: 240px; }
@media (min-width: 760px) { .trend-canvas-wrap { height: 340px; } }
/* status message ("No data yet." / "Trend data unavailable.") centered as an
   overlay of the fixed-height wrap — in flow it would shift the sections
   below every time it toggles ([hidden] still wins via the global rule). */
.trend-msg { position: absolute; inset: 0; display: grid; place-items: center; margin: 0; }

/* LP table — wrap-scroll so the page never scrolls horizontally */
#acc-lp { overflow-x: auto; }
.lp-updated { font-size: 11.5px; font-weight: 400; color: var(--faint); font-family: 'Fira Code', ui-monospace, monospace; }

/* header: client-side totals + 3-state sort toggle */
.lp-head { display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 12px; }
.lp-totals { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px; }
.lp-count { font-size: 11px; letter-spacing: .07em; text-transform: uppercase; color: var(--muted); font-weight: 600; }
.lp-sep { color: var(--border); }
.lp-total-value { font-family: 'Fira Code', ui-monospace, monospace; font-size: 12.5px; color: var(--muted); }

/* segmented control — same pill shape as the rest of the design system's inputs */
.lp-sort { display: flex; gap: 3px; background: var(--input); border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 3px; }
.lp-sort button { background: none; border: none; color: var(--muted); font: inherit; font-size: 11.5px; font-weight: 600; padding: 5px 10px; min-height: 0; border-radius: 7px; }
.lp-sort button:hover { border-color: transparent; background: var(--card-2); color: var(--text); }
.lp-sort button.on { background: var(--card-2); color: var(--gold-2); }

.lp-table { width: 100%; border-collapse: collapse; font-size: 13px; min-width: 640px; }
.lp-table th, .lp-table td { padding: 8px 10px; text-align: right; border-top: 1px solid var(--border-soft); white-space: nowrap; }
.lp-table thead th { border-top: none; color: var(--faint); font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: .05em; }
.lp-table th:first-child, .lp-table td:first-child { text-align: left; }
/* .num defaults to inline-block+min-width for inline cells; inside a table that
   would break the cell out of the column grid, so restore table-cell here. */
.lp-table td.num { font-family: 'Fira Code', ui-monospace, monospace; display: table-cell; min-width: 0; }
/* per-column min-widths (placed after the .num reset above so they win the
   min-width cascade): values reflow in digit count on every ~30s poll, and
   these keep the table from jittering as they come in. */
.lp-table th:first-child, .lp-table td:first-child { min-width: 160px; }
.lp-table th:nth-child(2), .lp-table td:nth-child(2) { min-width: 92px; }
.lp-table th:nth-child(3), .lp-table td:nth-child(3) { min-width: 92px; }
.lp-table th:nth-child(4), .lp-table td:nth-child(4) { min-width: 72px; }
.lp-table th:nth-child(5), .lp-table td:nth-child(5) { min-width: 64px; }
.lp-table th:nth-child(6), .lp-table td:nth-child(6) { min-width: 64px; }
.lp-table th:nth-child(7), .lp-table td:nth-child(7) { min-width: 74px; }
.lp-table th:nth-child(8), .lp-table td:nth-child(8) { min-width: 40px; }
.lp-table td.up   { color: var(--up); }
.lp-table td.down { color: var(--down); }
.lp-table tbody tr { transition: background var(--t-fast) var(--ease); }
.lp-table tbody tr:hover { background: rgba(255,255,255,.02); }

/* APR 7d column: same measured-APR source as the LP Positions card's .lp-apr
 * chip, one cell per market row. Tabular figures so the column doesn't
 * jitter horizontally as values refresh on each poll. */
.lp-apr-cell { font-variant-numeric: tabular-nums; white-space: nowrap; }
.lp-apr-measuring { color: var(--faint); }

/* PAIR cell: overlapping base+quote logos + name/dex sub-label */
.pair-cell { display: flex; align-items: center; gap: 10px; }
/* gold highlight on the column header matching the active sort button */
.lp-table th.th-sort-active { color: var(--gold); }

/* pair logos: 30px each; the second's left offset (17px) is less than its own
   width, so it overlaps the first rather than sitting flush beside it */
.pair-logos { position: relative; width: 47px; height: 30px; flex: none; }
.pair-logos .lp-logo { position: absolute; top: 0; width: 30px; height: 30px; }
.pair-logos .lp-logo:first-child { left: 0; z-index: 2; }
.pair-logos .lp-logo:last-child  { left: 17px; z-index: 1; }
.lp-logo img, .lp-avatar {
  display: block;
  width: 30px; height: 30px;
  border-radius: 50%;
  border: 2px solid var(--card);
  box-sizing: border-box;
  background: var(--card-2);
}
.lp-logo img { object-fit: cover; }
.lp-avatar {
  display: flex; align-items: center; justify-content: center;
  font-family: 'Fira Sans', sans-serif; font-weight: 700; font-size: 13px;
  color: var(--accent);
}
.pn { font-family: 'Fira Sans', sans-serif; font-weight: 600; font-size: 13.5px; color: var(--text); }
.pd { font-size: 11px; color: var(--faint); }

.lp-links a { display: inline-flex; color: var(--accent); }
.lp-links a:hover { color: var(--gold-2); }

/* flash: a changed row's numbers wash in cyan (~1s) — never on the very first
   paint, and never from re-sorting the same underlying data (see _lpRow). */
@keyframes lpFlash { 0% { background-color: rgba(101, 219, 232, .22); } 100% { background-color: transparent; } }
.lp-table tbody tr.lp-flash { animation: lpFlash 1s var(--ease); }

/* ============================================================================
 * Holders Explorer — top-level section, no containing card. Three bordered
 * subsections directly on the page: Dead/Burn,
 * LP & Contract, and Holders — ALL closed by default, each lazy-loading its
 * category on first expand. Holders additionally carries the search input
 * and "Load more" pagination in/under its own header.
 * ========================================================================== */
.holders-warm { margin: 0 0 12px; }

.holders-sub {
  /* same chrome as .card: the three explorer subsections read as siblings of
     the LP Pairs card, not bare outlines */
  background: linear-gradient(180deg, var(--card-2), var(--card));
  border: 1px solid var(--border-soft);
  border-radius: var(--radius-sm);
  margin-bottom: 12px;
  overflow: hidden;
  box-shadow: var(--shadow);
}
.holders-sub-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  padding: 11px 12px;
}
.holders-sub-left { display: flex; align-items: center; gap: 9px; flex: 1 1 auto; min-width: 0; }

/* icon chip: 26x26 rounded square, tinted background in the subsection's own
   accent, emoji glyph in the matching bright/semantic color. */
.holders-sub-ico {
  display: inline-grid;
  place-items: center;
  width: 26px;
  height: 26px;
  border-radius: 8px;
  font-size: 14px;
  flex: none;
}
.holders-sub-ico-dead     { background: rgba(245, 176, 53, .14); }   /* amber — Dead/Burn */
.holders-sub-ico-lp       { background: rgba(101, 219, 232, .14); }  /* cyan — LP & Contract */
.holders-sub-ico-regular  { background: rgba(52, 211, 153, .13); }   /* green — Holders */

.holders-sub-title { font-weight: 700; font-size: 13.5px; color: var(--text); }
/* .num's 9ch right-aligned backstop is too wide for a header chip, but the
   counts live-update every 60s poll — 6ch (fits "99,999") left-aligned keeps
   the unit label from shifting when a count crosses a digit boundary. */
.holders-sub-count { min-width: 6ch; text-align: left; }
/* count color matches its subsection's accent (contrast measured on --card /
   --card-2, conservative — the subsections actually sit on the darker --bg):
     amber (--gold-2 #f7c56a)  -> 11.80:1 / 11.14:1
     cyan  (--accent #00f6ff)  -> 14.02:1 / 13.24:1
     green (--up #34d399)      -> 9.80:1 / 9.25:1
   all far past AA (4.5:1). */
.holders-sub-count-dead     { color: var(--gold-2); }
.holders-sub-count-lp       { color: var(--accent); }
.holders-sub-count-regular  { color: var(--up); }
/* faint uppercase unit label ("wallets" / "addresses" / "total") — same
   recipe as the card .sub. */
.holders-sub-unit { font-size: 11px; letter-spacing: .14em; text-transform: uppercase; color: var(--faint); font-weight: 600; }

/* chevron toggle — same triangle trick as the outer details.card summary marker */
.holders-toggle {
  background: var(--input);
  border: 1px solid var(--border);
  border-radius: 8px;
  width: 30px;
  height: 30px;
  min-height: 0;
  padding: 0;
  flex: none;
  display: grid;
  place-items: center;
}
.holders-toggle::after {
  content: '';
  width: 7px; height: 7px;
  border-right: 2px solid var(--faint);
  border-bottom: 2px solid var(--faint);
  transform: rotate(-135deg);
  transition: transform var(--t) var(--ease), border-color var(--t-fast) var(--ease);
}
.holders-toggle:hover::after { border-color: var(--gold-2); }
.holders-sub.collapsed .holders-toggle::after { transform: rotate(45deg); }
.holders-sub.collapsed .holders-sub-body { display: none; }

.holders-sub-body { padding: 0 12px 12px; }
.holders-table-wrap { overflow-x: auto; }

/* Holders search — inline on the header row between the title group and the
   chevron, growing to fill the middle with its input+button group centred
   and width-capped. DOM order left -> search -> chevron == visual order ==
   tab order (WCAG 2.4.3). */
/* On the Holders header the title group stays at its natural width so the
   growing search form can centre its input in the middle (and still push the
   chevron to the far right); dead/lp keep the default flex:1 that pushes their
   lone chevron over. */
#holders-sub-regular .holders-sub-left { flex: 0 1 auto; }
.holders-search-form { flex: 1 1 auto; min-width: 0; display: flex; gap: 6px; justify-content: center; }
.holders-search { flex: 1 1 auto; min-width: 0; max-width: 420px; font-size: 13px; padding: 7px 11px; min-height: 36px; }
/* magnifier trigger — icon-only (aria-label in the DOM), stroke follows color */
.holders-search-btn {
  flex: none;
  width: 40px;
  min-height: 36px;
  padding: 0;
  display: grid;
  place-items: center;
  color: var(--faint);
  background: var(--input);
  border: 1px solid var(--border);
  border-radius: 8px;
}
.holders-search-btn:hover { color: var(--accent); border-color: var(--accent-2); }
.holders-search-btn svg { display: block; }

.holders-table { width: 100%; border-collapse: collapse; font-size: 13px; min-width: 460px; }
.holders-table th, .holders-table td { padding: 8px 10px; text-align: right; border-top: 1px solid var(--border-soft); white-space: nowrap; }
.holders-table thead th { border-top: none; color: var(--faint); font-weight: 600; font-size: 11px; text-transform: uppercase; letter-spacing: .05em; }
/* The Holder (address) column soaks up ALL the table's spare width (width:100%
   in an auto-layout table) so the numeric columns stay at their content width —
   otherwise the free width would spread across every column, and the
   right-aligned # column would grow a large empty left band. */
.holders-table th:first-child, .holders-table td:first-child { width: 1%; white-space: nowrap; }
.holders-table th:nth-child(2), .holders-table td:nth-child(2) { text-align: left; white-space: normal; width: 100%; min-width: 190px; }
.holders-table th:nth-child(3), .holders-table td:nth-child(3) { min-width: 130px; }
.holders-table th:nth-child(4), .holders-table td:nth-child(4) { min-width: 88px; }
.holders-table tbody tr:hover { background: rgba(255,255,255,.02); }
/* .num-cell can be a <td> itself (rank, % supply) or a nested <div> inside
   .holders-bal-td (balance, stacked above the delta) — match both. */
.holders-table .num-cell { font-family: 'Fira Code', ui-monospace, monospace; font-variant-numeric: tabular-nums; }
.holders-empty-row td { text-align: center; color: var(--muted); white-space: normal; }

/* Holder cell: round tier-emoji avatar + mono address (abbreviated <720px) + label */
.holders-cell { display: flex; align-items: center; gap: 9px; min-width: 0; }
.h-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px; height: 26px;
  border-radius: 50%;
  background: var(--card-2);
  border: 1px solid var(--border-soft);
  font-size: 14px;
  flex: none;
}
.holders-info { min-width: 0; }
.h-addr-short { display: none; }
@media (max-width: 719px) {
  .h-addr-full { display: none; }
  .h-addr-short { display: inline; }
}

.holders-bal-td { text-align: right; }
/* delta pill under the balance — reuses .delta (League chip trend) for a
   consistent up/down language across the app, sized down as a sub-line. */
.holders-bal-td .delta { display: inline-block; margin-top: 3px; font-size: 10.5px; }

/* Tracked-wallet highlight: a holder that sits in THIS visitor's tracker and
   holds FINVESTA is washed orange, with a 3px left rail and its address in the
   tracked colour. The "yours" tag (below) is the non-colour signal so meaning
   never rides on colour alone (WCAG 1.4.1). --tracked #ff9142 on the composited
   .06 wash over --card/--card-2 measures ~6.9:1 (address) — clears AA. */
.holders-table tbody tr.holder-tracked td { background: rgba(255, 145, 66, .06); }
.holders-table tbody tr.holder-tracked:hover td { background: rgba(255, 145, 66, .1); }
.holders-table tbody tr.holder-tracked td:first-child { box-shadow: inset 3px 0 0 var(--tracked); }
.holder-tracked .addr { color: var(--tracked); }
.h-tracked-tag {
  display: inline-block;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--tracked);
  background: rgba(255, 145, 66, .14);
  border: 1px solid rgba(255, 145, 66, .38);
  padding: 1px 6px;
  border-radius: var(--pill);
  white-space: nowrap;
}
.holders-addr-wrap { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; min-width: 0; }

/* Synthetic "your combined balance" row — orange, dashed top/bottom rules so it
   reads as a hypothetical (not a real holder). Its ≈/≥ pseudo-rank and title are
   the tracked colour; the layers avatar carries an orange stroke. */
.holders-table tbody tr.holders-agg-row td {
  background: rgba(255, 145, 66, .11);
  border-top: 1px dashed rgba(255, 145, 66, .5);
  border-bottom: 1px dashed rgba(255, 145, 66, .5);
}
.holders-table tbody tr.holders-agg-row:hover td { background: rgba(255, 145, 66, .14); }
.holders-agg-rank { color: var(--tracked); font-weight: 700; }
.holders-agg-title { font-weight: 700; color: var(--tracked); }
.holders-agg-avatar { color: var(--tracked); border-color: rgba(255, 145, 66, .45); background: rgba(255, 145, 66, .1); }
.holders-agg-avatar svg { display: block; }

.holders-loadmore-wrap { text-align: center; margin-top: 12px; }
.holders-loadmore { padding: 7px 14px; min-height: 34px; font-size: 12.5px; background: transparent; }

/* flash: a holder row whose balance changed since the last live re-poll washes
   green (increase) or red (decrease) — never on the first paint, never from a
   search filter or "Load more" (see _holdersRow's diff-by-address guard). */
@keyframes holderFlashUp   { 0% { background-color: rgba(52, 211, 153, .22); } 100% { background-color: transparent; } }
@keyframes holderFlashDown { 0% { background-color: rgba(248, 113, 113, .22); } 100% { background-color: transparent; } }
.holders-table tbody tr.holder-flash-up   { animation: holderFlashUp 1.2s var(--ease); }
.holders-table tbody tr.holder-flash-down { animation: holderFlashDown 1.2s var(--ease); }

/* ============================================================================
 * Footer
 * ========================================================================== */
#stats {
  display: flex;
  flex-direction: column;      /* two stacked rows: stats+tip, then positions+donate */
  gap: 10px;
  padding: 18px clamp(14px, 4vw, 28px);
  color: var(--muted);
  font-size: 12.5px;
  border-top: 1px solid var(--border-soft);
  max-width: var(--maxw);
  margin: 12px auto 0;
}
.foot-row { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px; }
.foot-row > span {
  background: var(--card);
  border: 1px solid var(--border-soft);
  border-radius: var(--pill);
  padding: 4px 12px;
  font-family: 'Fira Code', ui-monospace, monospace;
  font-size: 12px;
  color: var(--muted);
}
/* footer donation prompt: a gold-heart tip line + the gold "Donate" pill,
   grouped to the right (the message carries the margin-left:auto) */
.donate-msg {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 12.5px;
}
.donate-msg .heart { width: 14px; height: 14px; fill: var(--brand-gold-2); flex: none; }
#donate {
  margin-left: auto;           /* right edge of row 2, after Positions */
  font-weight: 600;
  color: #1a1205;
  background: linear-gradient(180deg, var(--brand-gold-2), var(--brand-gold));
  border-radius: var(--pill);
  padding: 6px 16px;
  white-space: nowrap;
  transition: filter var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
#donate:hover { filter: brightness(1.06); box-shadow: var(--glow-gold); text-decoration: none; }

/* Positions as individual token chips, desktop + mobile. #st-positions is a
   flex-wrap chip rail, not a single pill, so the .foot-row > span pill styling
   is overridden here rather than inherited. */
#st-positions {
  background: none; border: none; padding: 0;
  display: flex; flex-wrap: wrap; align-items: center; gap: 6px 7px;
  flex: 1 1 auto; min-width: 0;
}
.st-pos-lbl {
  color: var(--faint); font-size: 10.5px; text-transform: uppercase; letter-spacing: .09em;
  font-weight: 600; margin-right: 2px;
}
.st-chip {
  display: inline-flex; align-items: center; gap: 6px;
  background: var(--card); border: 1px solid var(--border-soft); border-radius: var(--pill);
  padding: 3px 11px 3px 5px; font-size: 11.5px;
  font-family: 'Fira Code', ui-monospace, monospace;
}
.st-chip img { width: 15px; height: 15px; border-radius: 50%; display: block; }
.st-chip b { color: var(--text); font-weight: 600; }
.st-chip-v { color: var(--muted); font-variant-numeric: tabular-nums; }

/* Mobile footer: without this, the two rows would cramp into each
   other (the wrapping tip + the long Positions line + the right-pinned Donate).
   Give each piece its own comfortable line: badges centered on top, the tip on
   its own centered line, the Positions as a full-width readable block, and a
   full-width Donate button (a bigger tap target too). */
@media (max-width: 719px) {
  #stats { gap: 16px; }
  .foot-row { justify-content: center; row-gap: 12px; }
  #st-users, #st-wallets { flex: 0 0 auto; }
  .donate-msg { margin-left: 0; flex-basis: 100%; justify-content: center; text-align: center; padding: 2px 4px; }
  #st-positions { flex-basis: 100%; justify-content: center; }
  #donate { margin-left: 0; flex-basis: 100%; text-align: center; padding: 11px 16px; font-size: 13.5px; }
}

/* ============================================================================
 * Reduced motion
 * ========================================================================== */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
    scroll-behavior: auto !important;
  }

  /* wizard: printer LED stops blinking, receipt lines appear with no fade/slide
     (the JS also skips the stagger delay entirely — see prefersReducedMotion()
     in app.js), and confetti holds its static resting position instead of falling. */
  .printer-led, .receipt-line.anim, .conf { animation: none !important; }

  /* payout history: the "history syncing…" pulse holds a static, fully-opaque
     ellipsis instead of a near-instant single flash. */
  .payout-line.syncing .dots { animation: none !important; opacity: 1; }

  /* payout history CSS bars: bars appear at final height instead of growing into
     place (the near-.001ms transition from the universal rule above already covers
     this — explicit here for clarity, same pattern as the other overrides). */
  .h-bar { transition: none !important; }

  /* delete-confirmation modal: appears instantly, no fade/pop. */
  .del-overlay, .del-card { animation: none !important; }

  /* wallet-settings modal: enter and exit snap instantly (the JS also skips
     the .closing fade entirely — see WalletModal.close). */
  .ws-overlay, .ws-card,
  .ws-overlay.closing, .ws-overlay.closing .ws-card { animation: none !important; }

  /* bond claim safety modal: appears instantly, no fade/pop. */
  .scrim, .modal { animation: none !important; }

  /* restore panel: appears in place, no fade/slide */
  .recover-panel { animation: none !important; }

  /* LP pairs: a changed row's teal flash is skipped entirely instead of a
     near-instant background blink. */
  .lp-table tbody tr.lp-flash { animation: none !important; }

  /* Holders Explorer: a changed row's green/red flash is skipped entirely
     instead of a near-instant background blink. */
  .holders-table tbody tr.holder-flash-up,
  .holders-table tbody tr.holder-flash-down { animation: none !important; }

  /* Recent Burns: a new row's gold flash is skipped entirely too. */
  .act.burn-flash { animation: none !important; }
}

/* Single/Grouped switch bar above the wallet sections. The segmented control
   itself reuses .lp-sort (the LP Pairs sort control) so no new interactive
   pattern enters the design system. */
.view-bar { display: flex; align-items: center; justify-content: center; gap: 12px; flex-wrap: wrap; margin: 0 0 14px; }
.view-bar[hidden] { display: none; }
.view-bar .vb-label { font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--faint); font-weight: 600; }

/* "N wallets" badge on the aggregated heading. --tracked is already the app's
   semantic for a combined-balance row (Holders table), so orange keeps meaning
   "aggregate" here too. */
.st-count { margin-left: 10px; font-family: 'Fira Code', ui-monospace, monospace; font-size: 11.5px; font-weight: 600; color: var(--tracked); background: rgba(255, 145, 66, .10); border: 1px solid rgba(255, 145, 66, .28); border-radius: var(--pill); padding: 3px 10px; vertical-align: middle; letter-spacing: 0; text-transform: none; }

/* Partial-sum marker: this tile's number was summed from fewer wallets than are
   tracked. Pinned to the tile's own top-right corner rather than trailing the
   figure, because `.pk-val` clips its overflow for the ellipsis and nothing
   inside it can ever show a bubble. The glyph, the chip outline and the note all
   carry the meaning, so it never depends on colour alone. */
/* Optical centring of the ∗ inside its ring. U+2217's ink sits just 1.4px above
   the baseline at 13px (canvas TextMetrics on the app's own Fira Code stack),
   while the line box drops that baseline 3.1px BELOW the chip's centre — the
   default padding box therefore leaves the glyph visibly low, in every chip in
   the app. A fixed square that takes its slack out of the BOTTOM lifts the ink
   back onto the centre without moving the ring: 6px of bottom padding raises
   the centred line box by 3px. Flex handles the horizontal axis, which the
   font never skews. */
.partial {
  position: absolute;
  top: 4px;
  right: 5px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 19px;
  height: 19px;
  padding: 0 0 6px;
  font-family: 'Fira Code', ui-monospace, monospace;
  font-size: 13px;
  line-height: 1;
  color: var(--tracked);
  background: rgba(255, 145, 66, .12);
  border: 1px solid rgba(255, 145, 66, .28);
  border-radius: var(--pill);
  cursor: help;
}
/* The note itself. It opens UPWARD and leftward: upward clears the tile it
   belongs to, and leftward means it can only ever overlap tiles EARLIER in the
   grid, which paint first — so it stays on top without any z-index on the tiles.
   pointer-events:none so it can never swallow a tap meant for the tile. */
.partial::after {
  content: attr(data-note);
  position: absolute;
  bottom: calc(100% + 7px);
  right: -1px;
  width: max-content;
  max-width: 210px;
  padding: 7px 9px;
  white-space: normal;
  text-align: left;
  font-size: 11px;
  line-height: 1.35;
  color: var(--text);
  background: var(--card-2);
  border: 1px solid rgba(255, 145, 66, .38);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow);
  opacity: 0;
  pointer-events: none;
}
/* :focus, not only :focus-visible: tapping a tabindex="0" span focuses it but
   does not match :focus-visible, and touch is exactly the case a title cannot
   serve. Mobile browsers also apply :hover on tap, so both routes are covered. */
.partial:hover::after,
.partial:focus::after { opacity: 1; }
.partial-note { display: inline-flex; align-items: center; gap: 6px; margin-left: 10px; font-size: 11px; color: var(--tracked); background: rgba(255, 145, 66, .08); border: 1px solid rgba(255, 145, 66, .24); border-radius: var(--pill); padding: 3px 10px; vertical-align: middle; letter-spacing: 0; text-transform: none; }
