/* ── Self-hosted fonts ───────────────────────────────────────────────────
   Served from /fonts/ on the local Express server. Eliminates the
   Google Fonts external DNS lookup + round-trip (saves 300-500ms on
   mobile cold loads). Only latin subset, only weights actually used.
   Files: ~58KB total, served gzipped ~40KB, cached for 1 year. */

@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 300;
  font-display: swap;
  src: url('/fonts/plus-jakarta-sans-latin-300-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/plus-jakarta-sans-latin-400-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Plus Jakarta Sans';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/plus-jakarta-sans-latin-500-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'JetBrains Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/jetbrains-mono-latin-400-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

/* ── Card Edge — extracted from dashboard.js ── */
  *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

  /* Disable double-tap zoom on all interactive elements — primary CSS signal
     to the browser. JS gesture/touchstart handlers are the fallback. */
  button, a, input, select, label, [onclick] {
    touch-action: manipulation;
  }

  :root {
    --bg: #111113;
    --surface: #18181c;
    --surface2: #141416;
    --border: #222227;
    --border2: #2a2a30;
    --text: #e2e2e6;
    --muted: #6b6b78;
    --hint: #4a4a55;
    --green: #4ade80;
    --green-dim: #0d2015;
    --blue: #60a5fa;
    --blue-dim: #0d1a2e;
    --amber: #fb923c;
    --amber-dim: #1f1208;
    --red: #f87171;
    --coral: #fb923c;
    --mono: 'JetBrains Mono', monospace;
    --sans: 'Plus Jakarta Sans', sans-serif;
  }

  /* ── Light theme — clean, airy, easy-on-eyes ── */
  html.theme-light, body.theme-light {
    --bg: #f5f5f7;
    --surface: #ffffff;
    --surface2: #fafafa;
    --border: #e5e5ea;
    --border2: #d1d1d6;
    --text: #1c1c1e;
    --muted: #8e8e93;
    --hint: #a9a9af;
    --green: #16a34a;
    --green-dim: #dcfce7;
    --blue: #2563eb;
    --blue-dim: #dbeafe;
    --amber: #d97706;
    --amber-dim: #fef3c7;
    --red: #dc2626;
    --coral: #f97316;
  }

  /* ── Fun theme — synthwave vibes, purple + teal ── */
  html.theme-default, body.theme-default {
    --bg: #1a1029;
    --surface: #241640;
    --surface2: #1f1337;
    --border: #3a2563;
    --border2: #4b2f7a;
    --text: #f8ecff;
    --muted: #b79ce0;
    --hint: #8e6ec9;
    --green: #2dd4bf;
    --green-dim: #0e2a27;
    --blue: #c084fc;
    --blue-dim: #2a1542;
    --amber: #fbbf24;
    --amber-dim: #2a1d08;
    --red: #fb7185;
    --coral: #f472b6;
  }

  /* ── Trader theme — Bloomberg terminal, green-on-black, stock ticker energy ── */
  html.theme-trader, body.theme-trader {
    --bg: #030b03;
    --surface: #071007;
    --surface2: #050d05;
    --border: #0f2b0f;
    --border2: #174017;
    --text: #00ff41;
    --muted: #3a7a3a;
    --hint: #235023;
    --green: #00ff41;
    --green-dim: #001a08;
    --blue: #00e5ff;
    --blue-dim: #001a1f;
    --amber: #ffe000;
    --amber-dim: #1a1500;
    --red: #ff3131;
    --coral: #ff6600;
    --mono: 'JetBrains Mono', monospace;
    /* --sans was JetBrains Mono to push the Bloomberg-terminal vibe
       everywhere, but mono glyphs are noticeably wider than proportional
       sans, which on mobile pushed the main page tabs to the screen
       edges and bumped the fourth Ledger filter onto a second row. Use
       the standard proportional stack here so layout matches every other
       theme; --mono stays monospace so anywhere that explicitly opts in
       (numbers, tickers, log lines) still gets the terminal feel. */
    --sans: 'Plus Jakarta Sans', sans-serif;
  }

  /* ── Arcade theme — 80s coin-op: Pac-Man, Galaga, Space Invaders ── */
  html.theme-galaxy, body.theme-galaxy {
    --bg: #00000a;
    --surface: #080818;
    --surface2: #0d0d24;
    --border: #1a1a4a;
    --border2: #0f0f30;
    --text: #e0e0ff;
    --muted: #7070c0;
    --hint: #5050a0;
    --green: #39ff14;
    --green-dim: #071a03;
    --blue: #00e5ff;
    --blue-dim: #001a1f;
    --amber: #ffe600;
    --amber-dim: #1a1500;
    --red: #ff2d6b;
    --coral: #ff6b35;
    --mono: 'JetBrains Mono', monospace;
  }

  /* ── Retro theme — 8/16-bit handheld + cartridge era ─────────────────────
     Warm dusk palette evoking sunset cartridge play. Hot red CTAs,
     warm amber accents, deep cool background. No specific platform IP. */
  /* ── Retro theme — Famicom cream era ──────────────────────────────────────
     Warm cream base · Famicom red · gold · dark brown text.
     Surface colours match bg closely so widgets blend in seamlessly. */
  html.theme-retro, body.theme-retro {
    --bg:        #e8d5a3;
    --surface:   #dfc99a;
    --surface2:  #d6c091;
    --border:    #c4a96e;
    --border2:   #b89558;
    --text:      #2c1810;
    --muted:     #6b3a2a;
    --hint:      #9b6a4a;
    --green:     #2d7a2d;
    --green-dim: #c8dfc8;
    --blue:      #1a4a8a;
    --blue-dim:  #c8d4e8;
    --amber:     #c8860a;
    --amber-dim: #f0e0b0;
    --red:       #d4163c;
    --coral:     #c8400a;
    --mono:      'JetBrains Mono', monospace;
  }


  /* Lock the root viewport so we never horizontally scroll or zoom-to-fit.
     The viewport meta already forbids pinch-zoom; these rules stop a stray
     wide child element from making the browser grow its layout width. */
  html {
    overflow-x: hidden;
    width: 100%;
    -webkit-text-size-adjust: 100%;  /* iOS: don't auto-enlarge text */
  }
  body {
    background: var(--bg);
    color: var(--text);
    font-family: var(--sans);
    min-height: 100vh;
    padding: 2rem;
    overflow-x: hidden;
    width: 100%;
    max-width: 100vw;
    box-sizing: border-box;
    margin: 0;
  }
  /* Freeze the page scroll behind an open eBay modal (toggled in app.js). */
  html.ebay-modal-lock, html.ebay-modal-lock body { overflow: hidden; }
  /* Every block that contains user content respects its parent's width so
     a deeply-nested overflowing child can't expand the page. */
  .page-content,
  .module,
  .controls-grid,
  .control-card,
  .scoring-body {
    max-width: 100%;
    box-sizing: border-box;
  }
  @media (max-width: 800px) {
    body { padding: 1rem; }
  }

  /* App shell — constrains all content including header to mobile-width on desktop */
  .app-shell {
    max-width: 480px;
    width: 100%;
    margin: 0 auto;
    position: relative;
  }
  /* Site-wide notice banner (Switchboard-controlled), prepended to the shell. */
  .beta-banner {
    display: flex; align-items: center; gap: 10px;
    background: var(--blue-dim); color: var(--text);
    border: 1px solid var(--blue); border-radius: 10px;
    padding: 9px 12px; margin: 4px 4px 6px; font-size: 12.5px; line-height: 1.45;
  }
  .beta-banner-text { flex: 1; min-width: 0; }
  .beta-banner-x {
    flex: 0 0 auto; background: none; border: none; color: var(--muted);
    cursor: pointer; font-size: 13px; line-height: 1; padding: 3px 6px; border-radius: 6px;
    -webkit-tap-highlight-color: transparent;
  }
  .beta-banner-x:hover { color: var(--text); background: rgba(255,255,255,0.08); }

  header {
    position: sticky;
    top: 0;
    z-index: 90;
    background: var(--bg);
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 8px;
    padding: 8px 8px 4px 8px;
  }

  .header-inner {
    display: flex;
    flex-direction: column;
    align-items: center;
    flex: 1;
    gap: 0;
  }
  .header-spacer {
    width: 28px; /* matches tier-btn width — keeps logo centred */
    flex-shrink: 0;
  }

  /* Logo helper class. The header logo's src is set imperatively (see
     setLogoSrc() in app.js and the inline boot script in app.html /
     login.html) so we always render exactly one <img> with exactly the
     right file for the active theme. No CSS toggle needed. */
  .surge-logo {
    display: block;
    height: auto;
    width: auto;
  }
  .surge-logo-header { height: 52px; }
  /* Mobile in-shell header logo: 15% smaller than the 52px base. Scoped by ID
     so the desktop rail logo (#rail-logo, also .surge-logo-header) stays 52px.
     Centring is unaffected — .header-inner centres it as a flex item. */
  #header-logo { height: calc(52px * 0.85); } /* 44.2px */

  /* ── Logo refresh-tap animation (mirrors CardEdge exactly) ──────────────
     Tap the header logo to refresh the page. On mobile a blue glow + ring
     ripples out from the logo on press. Identical timing, easing, colour,
     and reduced-motion fallback to CardEdge's .header-logo-link. */
  .header-logo-link {
    position: relative;
    display: inline-block;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    /* Suppress the iOS long-press callout menu ("Open in New Tab", "Copy
       Link", "Download Image") and disable text/image selection so a long
       press just does nothing instead of interrupting the refresh. */
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
    -webkit-user-drag: none;
    /* Padding gives the ripple room to expand beyond the logo's bounds
       without getting clipped by the header. */
    padding: 4px;
  }
  .header-logo-link img {
    /* Inherit no-callout / no-drag on the <img> itself — Safari applies
       the long-press menu to the <img>, not the parent <a>. */
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
    -webkit-user-drag: none;
    pointer-events: none;
  }
  /* Glow layer — radial gradient behind the logo, fades up on tap.
     Sized off the link's HEIGHT (not width) so it stays a circle on the
     wide-aspect "surge.cards" wordmark. CardEdge's logo is a square card
     icon where width:140%; height:140% reads correctly; on a 5:1 mark the
     same rule produces a stretched ellipse that visually disconnects from
     the tap point. Using `aspect-ratio: 1` with a height-relative size
     gives a clean circular halo centred on the link's vertical midline. */
  .header-logo-link::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    /* Width matches height (1.6× the link's height) — keeps it round
       and concentrates the glow over the card-icon area on the left of
       the wordmark, which is where the user's tap naturally lands. */
    height: 160%;
    aspect-ratio: 1 / 1;
    transform: translate(-50%, -50%) scale(0.6);
    background: radial-gradient(
      circle at center,
      rgba(96, 165, 250, 0.5) 0%,
      rgba(96, 165, 250, 0.25) 35%,
      transparent 70%
    );
    border-radius: 50%;
    opacity: 0;
    pointer-events: none;
    z-index: -1;
    will-change: transform, opacity;
    filter: blur(8px);
  }
  /* Ripple layer — circular ring that expands outward. Same fixed
     diameter as CardEdge; the ring scales up via transform so it's
     intrinsically round regardless of the link's aspect ratio. */
  .header-logo-link::after {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 40px;
    height: 40px;
    margin-top: -20px;
    margin-left: -20px;
    border-radius: 50%;
    border: 2px solid rgba(96, 165, 250, 0.7);
    transform: scale(0.3);
    opacity: 0;
    pointer-events: none;
    will-change: transform, opacity;
  }
  /* Mobile-only — Chrome desktop tears down animations the moment the
     click commits, so the effect was either invisible or required holding
     the mouse. Restrict to touch devices (pointer: coarse) where it
     actually works reliably and where the feedback is most needed. */
  @media (pointer: coarse) {
    .header-logo-link.is-refreshing::before {
      animation: logo-glow 550ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
    }
    .header-logo-link.is-refreshing::after {
      animation: logo-ripple 550ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
    }
  }
  /* Front-loaded timing tuned for Chrome's aggressive page-teardown
     behaviour. Chrome kills in-flight animations as early as ~50ms after
     navigation begins (Safari is more lenient at ~200–400ms). To stay
     visible across browsers, both animations:
       - Start at non-zero opacity (0.6) so the first paint is already bright
       - Hit visual peak at 8 % of the timeline (~44 ms) instead of 15 %
       - Use larger peak scale values to land harder in that brief window
     The remaining 92 % is decorative tail for browsers that allow it. */
  @keyframes logo-glow {
    0%   { transform: translate(-50%, -50%) scale(0.7); opacity: 0.6; }
    8%   { transform: translate(-50%, -50%) scale(1.2); opacity: 1; }
    100% { transform: translate(-50%, -50%) scale(1.6); opacity: 0; }
  }
  @keyframes logo-ripple {
    0%   { transform: scale(0.6); opacity: 0.7; border-width: 4px; }
    8%   { transform: scale(2.2); opacity: 1; border-width: 3px; }
    100% { transform: scale(5); opacity: 0; border-width: 1px; }
  }
  /* Respect reduced-motion preferences — fall back to a simple opacity
     pulse so users who disable animations still get refresh feedback. */
  @media (prefers-reduced-motion: reduce) {
    .header-logo-link.is-refreshing::before {
      animation: logo-glow-fade 200ms ease-out forwards;
    }
    .header-logo-link.is-refreshing::after {
      animation: none;
    }
    @keyframes logo-glow-fade {
      0%, 100% { opacity: 0; transform: translate(-50%, -50%) scale(1); }
      50%      { opacity: 1; }
    }
  }

  .header-logo-main {
    height: 72px;
    width: auto;
    object-fit: contain;
    /* Slight downward nudge — previously the dots row sat below the logo
       and grounded the composition. Without it the logo feels too high. */
    margin-top: 8px;
  }

  /* header status dots removed — bot status shown in bot-status widget */

  .section-label {
    font-size: 10px;
    font-weight: 500;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: #4a4a55;
    margin-bottom: 10px;
  }

  .overview-grid {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: 10px;
    margin-bottom: 2rem;
  }

  .metric {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 16px;
  }

  .metric-label {
    font-size: 11px;
    color: #6b6b78;
    margin-bottom: 8px;
  }

  .metric-value {
    font-size: 26px;
    font-weight: 300;
    color: var(--text);
    letter-spacing: -0.03em;
    line-height: 1;
  }

  .metric-sub {
    font-size: 10px;
    color: #4a4a55;
    margin-top: 4px;
  }

  .bots-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    margin-bottom: 2rem;
  }

  .bot-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 1.25rem;
  }

  .bot-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 16px;
  }

  .bot-name {
    font-size: 13px;
    font-weight: 500;
    color: var(--text);
    letter-spacing: -0.01em;
  }

  .badge {
    display: inline-block;
    font-size: 11px;
    padding: 4px 12px;
    border-radius: 20px;
    font-weight: 500;
  }

  .badge.online   { background: var(--green-dim); color: var(--green);  border: 1px solid var(--green-dim);  }
  .badge.sleeping { background: var(--surface2);  color: var(--muted);  border: 1px solid var(--border2);    }
  .badge.capped   { background: var(--amber-dim); color: var(--amber);  border: 1px solid var(--amber-dim);  }
  .badge.offline  { background: var(--surface2);  color: var(--red);    border: 1px solid var(--border2);    }

  /* Remove light-theme badge overrides — no longer needed */

  .bot-stats {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
  }

  .bot-stat {
    background: var(--surface2);
    border-radius: 8px;
    padding: 12px;
  }

  .bot-stat-label {
    font-size: 10px;
    color: #6b6b78;
    margin-bottom: 4px;
  }

  .bot-controls {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
    padding: 16px 0 0;
    margin-top: auto;
  }

  .ctrl-btn {
    flex: 1;
    min-width: 60px;
    padding: 6px 8px;
    border-radius: 6px;
    border: 1px solid var(--border2);
    background: var(--surface2);
    color: var(--muted);
    font-family: var(--sans);
    font-size: 11px;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.15s;
    text-align: center;
  }

  .ctrl-btn:hover { border-color: var(--hint); color: var(--text); background: var(--surface); }
  .ctrl-btn:active { opacity: 0.7; }
  .ctrl-btn.start:hover { border-color: var(--green); color: var(--green); }
  .ctrl-btn.stop:hover { border-color: var(--red); color: var(--red); }
  .ctrl-btn.restart:hover { border-color: var(--amber); color: var(--amber); }
  .ctrl-btn.scan:hover { border-color: var(--blue); color: var(--blue); }
  .ctrl-btn.wake:hover { border-color: var(--green); color: var(--green); }
  .ctrl-btn.sleep:hover { border-color: var(--muted); color: var(--muted); }
  .ctrl-btn.loading { opacity: 0.5; pointer-events: none; }

  .bot-stat-value {
    font-size: 17px;
    font-weight: 600;
    color: var(--text);
    letter-spacing: -0.01em;
  }

  .bot-stat-sub {
    font-size: 9px;
    color: var(--hint);
    margin-top: 3px;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .progress-bar {
    height: 3px;
    background: var(--border2);
    border-radius: 2px;
    margin-top: 8px;
    overflow: hidden;
  }

  .progress-fill {
    height: 100%;
    border-radius: 2px;
    background: var(--blue);
    transition: width 0.5s ease;
  }

  .progress-fill.warning { background: var(--amber); }
  .progress-fill.danger { background: var(--red); }

  .deal-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 1.25rem;
    margin-bottom: 2rem;
  }

  .deal-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text);
    margin-bottom: 14px;
    letter-spacing: -0.01em;
  }

  .deal-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 13px;
    padding: 7px 0;
    border-bottom: 0.5px solid var(--border);
    font-family: var(--mono);
  }

  .deal-row:last-child { border-bottom: none; }
  .deal-key { color: var(--muted); }
  .deal-val { color: var(--text); }
  .deal-val.green { color: var(--green); }
  .deal-val.source { color: var(--blue); }

  /* ── Yesterday's Summary ── */
  .summary-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
    margin-bottom: 2rem;
  }
  .summary-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 1.1rem 1.25rem;
  }
  .summary-card-header {
    display: flex;
    align-items: center;
    gap: 7px;
    font-size: 12px;
    font-weight: 600;
    color: var(--muted);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    margin-bottom: 10px;
  }
  .summary-card-date {
    margin-left: auto;
    font-size: 10px;
    color: var(--hint);
    font-weight: 400;
    text-transform: none;
    letter-spacing: 0;
  }
  .summary-row {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-size: 12px;
    padding: 5px 0;
    border-bottom: 0.5px solid var(--border);
    font-family: var(--mono);
  }
  .summary-row:last-child { border-bottom: none; }
  .summary-key { color: var(--muted); }
  .summary-val { color: var(--text); }
  .summary-val.green { color: var(--green); }
  .summary-val.blue  { color: var(--blue); }
  .summary-top-cards {
    margin-top: 10px;
    padding-top: 10px;
    border-top: 1px solid var(--border);
  }
  .summary-top-label {
    font-size: 10px;
    color: var(--hint);
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-bottom: 5px;
  }
  .summary-top-item {
    font-size: 11px;
    font-family: var(--mono);
    color: var(--muted);
    padding: 2px 0;
  }
  .summary-top-item span { color: var(--text); }
  .summary-empty {
    font-size: 12px;
    color: var(--hint);
    text-align: center;
    padding: 1.5rem 0;
  }

  .logs-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px;
  }

  .log-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 1.25rem;
    overflow: hidden;
    display: flex;
    flex-direction: column;
  }

  .log-header {
    font-size: 13px;
    font-weight: 600;
    color: var(--text);
    margin-bottom: 12px;
    letter-spacing: -0.01em;
  }

  .log-lines {
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 260px;
    overflow-y: auto;
    /* Ensure logs are selectable regardless of any inherited user-select: none
       on ancestor panels (widgets sometimes block text selection to avoid
       accidental drag-select). */
    user-select: text;
    -webkit-user-select: text;
  }

  .log-lines::-webkit-scrollbar { width: 3px; }
  .log-lines::-webkit-scrollbar-track { background: transparent; }
  .log-lines::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 2px; }

  .log-line {
    font-family: var(--mono);
    font-size: 10.5px;
    color: #6b6b78;
    padding: 2px 0;
    white-space: pre-wrap;
    word-break: break-all;
    animation: fadein 0.3s ease;
    user-select: text;
    -webkit-user-select: text;
  }

  /* Drop the translateY from fadein — layout shifts on every new log line
     were perturbing live drag-selections on Chrome. Opacity-only fade is
     enough visual feedback. */
  @keyframes fadein { from { opacity: 0; } to { opacity: 1; } }

  .log-line .tag-skip { color: #383840; }
  .log-line .tag-pass { color: var(--green); }
  .log-line .tag-deal { color: var(--amber); }
  .log-line .tag-alert { color: #f97316; }
  .log-line .tag-info { color: var(--blue); }

  /* Source tag for cross-service live logs (e.g. the standalone scraper). Kept as
     plain muted inline text — same weight/colour as the message — so it reads like
     a normal log line instead of a bright gold badge. */
  .log-line .log-src {
    color: var(--muted); opacity: .7;
  }
  .log-line .tag-error { color: var(--red); }

  .no-deal {
    font-family: var(--mono);
    font-size: 12px;
    color: var(--hint);
    padding: 1rem 0;
    text-align: center;
  }

  .pokemon-grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
    gap: 10px;
    margin-bottom: 2rem;
  }

  .pokemon-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 10px;
    padding: 16px;
    display: flex;
    align-items: center;
    gap: 12px;
  }

  .pokemon-sprite-wrap {
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    position: relative;
  }

  .pokemon-sprite {
    width: 40px;
    height: 40px;
    object-fit: contain;
    image-rendering: pixelated;
  }

  /* Pokéball fallback for the top-Pokémon grid — shows by default, hidden by
     JS when PokéAPI resolves a real sprite. Mirrors the Ledger row's fallback
     so trainer cards / unrecognised names don't render as empty boxes. */
  .pokemon-sprite-fallback {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
  }
  .pokemon-sprite-fallback .ledger-row-pokeball {
    width: 85%;
    height: 85%;
    opacity: 0.85;
  }

  .pokemon-rank {
    font-size: 11px;
    color: var(--hint);
    font-weight: 500;
    min-width: 16px;
  }

  .pokemon-info {
    flex: 1;
    min-width: 0;
  }

  .pokemon-name {
    font-size: 13px;
    font-weight: 500;
    color: var(--text);
    letter-spacing: -0.01em;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  .pokemon-count {
    font-size: 10px;
    color: var(--muted);
    margin-top: 2px;
  }

  .pokemon-empty {
    font-family: var(--mono);
    font-size: 12px;
    color: var(--hint);
    padding: 1rem 0;
    text-align: center;
    grid-column: 1 / -1;
  }

  .alltime-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 12px 8px;
    margin-bottom: 2rem;
  }

  .alltime-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 12px;
    gap: 8px;
  }
  .alltime-tabs {
    display: flex;
    gap: 4px;
    margin-bottom: 0;
  }

  .alltime-tab {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.06em;
    padding: 4px 10px;
    border-radius: 5px;
    border: 1px solid var(--border2);
    background: transparent;
    color: var(--hint);
    cursor: pointer;
    transition: all 0.15s;
    font-family: var(--mono);
  }

  .alltime-tab:hover { color: var(--text); border-color: var(--hint); }
  .alltime-tab.active { background: var(--blue-dim); border-color: var(--blue); color: var(--blue); }

  .alltime-view-btns {
    display: flex;
    gap: 4px;
  }
  .alltime-view-btn {
    font-size: 10px;
    font-weight: 600;
    padding: 4px 10px;
    border-radius: 5px;
    border: 1px solid var(--border2);
    background: transparent;
    color: var(--hint);
    cursor: pointer;
    transition: all 0.15s;
    font-family: var(--mono);
    letter-spacing: 0.04em;
  }

  .alltime-list {
    display: flex;
    flex-direction: column;
    gap: 2px;
    max-height: 420px;
    overflow-y: auto;
  }

  .alltime-list::-webkit-scrollbar { width: 3px; }
  .alltime-list::-webkit-scrollbar-track { background: transparent; }
  .alltime-list::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 2px; }

  /* ── Shared ladder grid — list, movers, insights all use same columns ── */
  /* rank(28) | sprite(40) | name(1fr) | metric(72) | move(24) */
  /* ── Shared data cell classes used across all ladder views ── */
  .lc-rank   { font-size: 11px; color: var(--hint); font-weight: 500; text-align: right; padding-right: 4px; }
  .lc-sprite { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; position: relative; }
  .lc-name   { font-size: 13px; font-weight: 500; color: var(--text); letter-spacing: -0.01em; padding: 0 8px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .lc-sub    { font-size: 10px; color: var(--muted); margin-top: 1px; padding: 0 8px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .lc-data   { font-size: 11px; font-family: var(--mono); color: var(--muted); text-align: right; white-space: nowrap; }
  .lc-data-hi { font-size: 11px; font-family: var(--mono); color: var(--text); font-weight: 500; text-align: right; white-space: nowrap; }
  /* Even digits everywhere a number column exists — no width wobble. */
  .lc-rank, .lc-data, .lc-data-hi, .lc-price, .lc-listed { font-variant-numeric: tabular-nums; }
  .lc-move   { font-size: 11px; font-weight: 700; text-align: center; }
  .lc-move.up   { color: var(--green); }
  .lc-move.down { color: var(--red); }
  .lc-move.new  { color: var(--blue); font-size: 9px; }

  /* ── #90: grade ladder (champion) — under the lookup stat squares ── */
  #ps-meta::-webkit-scrollbar { display: none; }
  .gl-head { font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: var(--hint); font-family: var(--mono); margin-bottom: 5px; }
  .gl-row {
    display: grid;
    /* #159: price + count columns are FIXED width (were `auto`, so a wider
       price like "$2,000.00" shrank that row's 1fr track — the bars no longer
       lined up). Fixed columns → every track is identical length; the fill %
       still encodes the relative price. */
    grid-template-columns: 86px minmax(20px,1fr) 80px 58px;
    column-gap: 10px; align-items: center;
    padding: 4px 2px;
    font-family: var(--mono); font-size: 12px;
  }
  .gl-grade { color: var(--muted); font-weight: 600; white-space: nowrap; }
  .gl-row.current .gl-grade { color: var(--blue); }
  .gl-track { height: 4px; background: var(--border2); border-radius: 2px; overflow: hidden; }
  .gl-fill { height: 100%; background: var(--blue); border-radius: 2px; transition: width 0.4s ease; }
  /* #raw: variant toggle chips under the card image (Holo / Reverse Holo / …). */
  .ps-variant-chip {
    border: 1px solid var(--border2); background: var(--surface); color: var(--muted);
    border-radius: 999px; padding: 4px 11px; font-size: 11.5px; cursor: pointer;
    font-family: var(--mono); white-space: nowrap; transition: all .15s ease;
  }
  .ps-variant-chip:hover { border-color: var(--blue); color: var(--text); }
  .ps-variant-chip.active { border-color: var(--blue); background: var(--blue-dim); color: var(--blue); }
  /* A variant with no ungraded market: muted, dashed, but still clickable. */
  .ps-variant-chip.nodata { opacity: .6; border-style: dashed; }
  /* Lookup card-image slot: a subtle dashed placeholder (like an empty badge slot)
     marks where the card will land; the real photo fades in gently and stays put
     across grade/variant changes of the same card. */
  .ps-card-ph { height: 190px; width: 136px; border-radius: 10px;
    /* --muted (not --border2) so the dashes stay visible on every theme — galaxy's
       border2 is nearly its bg. color-mix keeps them subtle. */
    border: 2px dashed color-mix(in srgb, var(--muted) 52%, transparent);
    background: linear-gradient(180deg, rgba(255,255,255,.02), rgba(0,0,0,.10)); }
  .ps-card-img { transition: opacity .45s ease; }
  .ps-variant-chip.nodata.active { opacity: 1; }
  /* Admin-only ⋯ at the end of the row — opens the "flag a phantom variant" menu. */
  .ps-variant-admin {
    border: 1px solid var(--border2); background: var(--surface); color: var(--muted);
    border-radius: 999px; padding: 3px 9px; font-size: 13px; line-height: 1; cursor: pointer;
    align-self: center;
  }
  .ps-variant-admin:hover { border-color: var(--blue); color: var(--text); }
  .ps-variant-chip .ps-chip-tag { font-size: 9.5px; text-transform: uppercase; letter-spacing: .04em; color: var(--hint); margin-left: 4px; }
  /* #raw: graded-variant toggle row inside the grade ladder. */
  .gl-variants { display: flex; flex-wrap: nowrap; gap: 5px; overflow-x: auto; scrollbar-width: none; margin: 0 0 9px; }
  .gl-vchip {
    border: 1px solid var(--border2); background: var(--surface); color: var(--muted);
    border-radius: 999px; padding: 3px 9px; font-size: 10.5px; cursor: pointer;
    font-family: var(--mono); white-space: nowrap; transition: all .15s ease;
  }
  .gl-vchip:hover { border-color: var(--blue); color: var(--text); }
  .gl-vchip.active { border-color: var(--blue); background: var(--blue-dim); color: var(--blue); }
  /* #raw: the raw (ungraded) rung sits at the bottom in a distinct colour so it
     reads as the grading baseline, not another grade. */
  .gl-raw .gl-grade { color: var(--coral, #ff7a45); }
  .gl-raw .gl-fill { background: var(--coral, #ff7a45); }
  .gl-raw .gl-count { color: var(--coral, #ff7a45); opacity: .8; }
  .gl-price { text-align: right; color: var(--text); white-space: nowrap; }
  .gl-count { text-align: right; color: var(--hint); font-size: 10.5px; white-space: nowrap; }
  .gl-note { font-size: 10px; color: var(--hint); font-family: var(--mono); margin-top: 4px; }
  /* #274: trader grade-ladder lock — one centred "Upgrade…" line, like the starter
     #ps-upgrade-note (no "Champion only"), for consistent lock messaging. */
  .gl-upgrade { text-align: center; font-size: 11px; font-family: var(--mono); color: var(--hint); opacity: 0.7; padding: 8px 0 4px; }
  /* lower tiers: the standard tier-upgrade-row gate renders inside #grade-ladder */

  /* #127: toggle acknowledged while the network catches up — cache hits never dim */
  #dashboard-list { transition: opacity 0.15s; }
  #dashboard-list.dash-loading { opacity: 0.45; pointer-events: none; }

  /* ── Column header row ── */
  .ladder-header {
    display: grid;
    column-gap: 10px;        /* mirrors the row gap so headers align with cells */
    align-items: center;
    padding: 4px 6px 2px;
  }
  .ladder-header-cell {
    font-size: 9px;
    font-family: var(--mono);
    font-weight: 600;
    letter-spacing: 0.08em;
    color: var(--hint);
    text-align: right;
    text-transform: uppercase;
    white-space: nowrap;
  }

  /* ── LIST: 28·44·1fr·auto·22 ──
     The list view has only ONE primary metric, so a single right-aligned
     price stranded short names with a big void in the middle. Instead of a
     spacing trick, the row now carries a SECOND line of data on each side —
     name + rank-movement on the left, price + "N listed" on the right — so
     the right block has real width that pulls the content in and fills the
     row evenly (the same reason the movers/insights views read well: more
     data, less whitespace). The data column is `auto` (sized to its widest
     line) and rank is tightened to 28px so the left cluster sits snug. */
  /* EVERY ladder view (List/Movers/Insights) renders this ONE balanced row:
     rank 24 · sprite 40 · name+movement (1fr) · two-line data block (auto) ·
     arrow 20, uniform 10px gaps. Extra per-view columns left voids between
     short names and the data — the stacked right block fills rows evenly. */
  /* ── Ladder geometry (List/Movers/Insights) ──
     The PARENT (#dashboard-list) owns one shared column system; rows adopt it
     via subgrid. fit-content names = the column is exactly as wide as the
     longest visible name (viewport-capped so it can never force overflow) —
     every track bar starts at the same x, tight against the names: a real
     bar chart with no dead air. Non-subgrid browsers fall back to per-row
     fit-content (tight but ragged — still no voids). */
  #dashboard-list.ladder-list,
  #dashboard-list.ladder-insights {
    display: grid;
    column-gap: 10px;
  }
  #dashboard-list.ladder-list     { grid-template-columns: 24px 40px fit-content(clamp(90px, 26vw, 150px)) minmax(20px,1fr) auto 20px; }
  #dashboard-list.ladder-insights { grid-template-columns: 24px 40px fit-content(clamp(90px, 26vw, 150px)) minmax(54px,1fr) auto; }
  /* Real phones (≤480px): tighter gaps + cells — the row must fit a 360px
     viewport with ZERO horizontal scroll. */
  @media (max-width: 480px) {
    #dashboard-list.ladder-list,
    #dashboard-list.ladder-insights,
    #dashboard-list.ladder-list .list-row,
    #dashboard-list.ladder-list .ladder-header,
    #dashboard-list.ladder-insights .insight-row { column-gap: 7px; }
    #dashboard-list.ladder-list     { grid-template-columns: 20px 36px fit-content(clamp(84px, 26vw, 150px)) minmax(16px,1fr) auto 16px; }
    #dashboard-list.ladder-insights { grid-template-columns: 20px 36px fit-content(clamp(84px, 26vw, 150px)) minmax(48px,1fr) auto; }
  }
  #dashboard-list.ladder-list > *,
  #dashboard-list.ladder-insights > * { grid-column: 1 / -1; }
  .list-row {
    display: grid;
    grid-template-columns: 24px 40px fit-content(clamp(90px, 26vw, 150px)) minmax(20px,1fr) auto 20px;
    column-gap: 10px;
    align-items: center;
    padding: 8px 6px;
    transition: background 0.15s;
  }
  #dashboard-list.ladder-list .list-row,
  #dashboard-list.ladder-list .ladder-header { display: grid; grid-template-columns: subgrid; grid-column: 1 / -1; }
  .list-row:hover { background: var(--surface2); border-radius: 6px; }
  .list-row.tier-dim { opacity: 0.35; pointer-events: none; }
  .list-header { grid-template-columns: 24px 40px fit-content(clamp(90px, 26vw, 150px)) minmax(20px,1fr) auto 20px; column-gap: 10px; }
  /* Track = the view's primary metric as magnitude — full width = the
     visible leader. */
  .lc-track { height: 4px; background: var(--border2); border-radius: 2px; overflow: hidden; align-self: center; }
  .lc-track-fill { height: 100%; border-radius: 2px; background: var(--blue); transition: width 0.4s ease; }
  /* ── INSIGHTS: the market-pulse ladder ──
     rank · sprite · name+supply-change · fixed-width median sparkline ·
     price+Δ% — the fixed spark column keeps every row's geometry identical. */
  .insight-row {
    display: grid;
    grid-template-columns: 24px 40px fit-content(clamp(90px, 26vw, 150px)) minmax(54px,1fr) auto;
    column-gap: 10px;
    align-items: center;
    padding: 8px 6px;
    transition: background 0.15s;
  }
  #dashboard-list.ladder-insights .insight-row { display: grid; grid-template-columns: subgrid; grid-column: 1 / -1; }
  .insight-row:hover { background: var(--surface2); border-radius: 6px; }
  .insight-row .lc-name, .insight-row .lc-sub { padding: 0; }
  .insight-row .lc-rank { text-align: center; padding-right: 0; }
  .insight-row .lc-data-block { text-align: right; }
  .insight-row .lc-price  { font-size: 13px; font-family: var(--mono); color: var(--text); font-weight: 500; text-align: right; white-space: nowrap; }
  .insight-row .lc-listed { font-size: 10px; font-family: var(--mono); color: var(--muted); text-align: right; white-space: nowrap; margin-top: 1px; }
  .ins-spark { width: 100%; height: 22px; display: block; }
  .ins-data-note {
    font-size: 9px; font-family: var(--mono); color: var(--hint);
    text-align: center; padding: 8px 0 2px; letter-spacing: 0.04em;
  }
  /* gap handles spacing, so drop the cells' own padding inside list rows */
  .list-row .lc-name, .list-row .lc-sub { padding: 0; }
  .list-row .lc-rank { text-align: center; padding-right: 0; }
  .list-row .lc-data-block { text-align: right; }
  .list-row .lc-price  { font-size: 13px; font-family: var(--mono); color: var(--text); font-weight: 500; text-align: right; white-space: nowrap; }
  .list-row .lc-listed { font-size: 10px; font-family: var(--mono); color: var(--muted); text-align: right; white-space: nowrap; margin-top: 1px; }











  /* ── Legacy aliases kept for safety ── */
  .ladder-row { display: grid; grid-template-columns: 36px 44px 1fr 80px 36px; align-items: center; padding: 9px 6px; transition: background 0.15s; }
  .ladder-row:hover { background: var(--surface2); border-radius: 6px; }
  .ladder-row.tier-dim { opacity: 0.35; pointer-events: none; }
  .ladder-rank  { font-size: 11px; color: var(--hint); font-weight: 500; text-align: right; padding-right: 4px; }
  .ladder-sprite { width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; position: relative; }
  .ladder-name  { font-size: 13px; font-weight: 500; color: var(--text); letter-spacing: -0.01em; padding: 0 8px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .ladder-metric { font-size: 11px; font-family: var(--mono); color: var(--muted); text-align: right; white-space: nowrap; }
  .ladder-move  { font-size: 11px; font-weight: 700; text-align: center; }
  .ladder-move.up { color: var(--green); }
  .ladder-move.down { color: var(--red); }
  .ladder-move.new { color: var(--blue); font-size: 9px; }
  .alltime-row { display: grid; grid-template-columns: 36px 44px 1fr 80px 36px; align-items: center; padding: 9px 6px; transition: background 0.15s; }
  .alltime-row:hover { background: var(--surface2); border-radius: 6px; }
  .alltime-rank { font-size: 11px; color: var(--hint); font-weight: 500; text-align: right; padding-right: 4px; }
  .alltime-sprite-wrap { width: 44px; height: 44px; display: flex; align-items: center; justify-content: center; position: relative; }
  .alltime-sprite { width: 36px; height: 36px; object-fit: contain; image-rendering: pixelated; }
  .alltime-name { font-size: 13px; font-weight: 500; color: var(--text); letter-spacing: -0.01em; padding: 0 8px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .alltime-count { font-size: 11px; color: var(--muted); font-family: var(--mono); text-align: right; white-space: nowrap; }
  .alltime-move { font-size: 11px; font-weight: 700; text-align: center; }
  .alltime-move.up { color: var(--green); }
  .alltime-move.down { color: var(--red); }
  .alltime-move.new { color: var(--blue); font-size: 9px; font-weight: 500; }

  .alltime-empty {
    font-family: var(--mono);
    font-size: 12px;
    color: var(--hint);
    padding: 1rem 0;
    text-align: center;
  }

  .alltime-view-btn:hover { color: var(--text); border-color: var(--hint); }
  .alltime-view-btn.active { background: var(--surface2); border-color: var(--muted); color: var(--text); }

  /* ── Volume / Trend metric toggle ── */
  .section-label-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 10px;
    position: relative;   /* anchors the insights explainer popover */
  }
  /* #279: round "?" help button, matching the round reset (⟲) button — not the
     square metric toggle it borrows other styling from. Selector carries both
     classes so it beats the later `.listed-metric-btn { border-radius: 4px }`. */
  .listed-metric-btn.insights-help-btn,
  .insights-help-btn {
    width: 20px; height: 20px; min-width: 20px; padding: 0;
    border-radius: 50%; font-size: 12px; font-weight: 700; line-height: 1;
    display: inline-flex; align-items: center; justify-content: center;
  }
  .insights-help-pop {
    position: absolute; top: calc(100% + 4px); right: 0; z-index: 60;
    background: var(--surface2); border: 1px solid var(--border2); border-radius: 10px;
    padding: 12px 14px; width: min(320px, calc(100vw - 28px));
    box-shadow: 0 8px 24px rgba(0,0,0,.35);
    font-size: 12px; line-height: 1.5; color: var(--muted);
    /* These popups can live INSIDE .control-card-title, which sets uppercase +
       wide letter-spacing + 500 weight — reset all of it so a Settings help popup
       reads IDENTICALLY to the dashboard one (only .ihp-title re-opts to uppercase). */
    text-transform: none; letter-spacing: normal; font-weight: 400;
    font-family: var(--sans);
  }
  /* #253: reset-confirm popups are sentence prompts — never uppercase, even though
     they're nested in the uppercase .control-card-title. */
  .widget-reset-popup { text-transform: none; }
  .insights-help-pop p { margin: 0 0 8px; }
  .insights-help-pop p:last-child { margin-bottom: 0; }
  .insights-help-pop b { color: var(--text); font-weight: 600; }
  .ihp-title { font-size: 10px; text-transform: uppercase; letter-spacing: .06em; color: var(--hint); margin-bottom: 6px; }
  .listed-metric-toggle {
    display: flex;
    gap: 4px;
    flex-shrink: 0;
  }
  .listed-metric-btn {
    font-size: 10px;
    font-family: var(--mono);
    font-weight: 600;
    letter-spacing: 0.05em;
    padding: 3px 8px;
    border-radius: 4px;
    border: 1px solid var(--border2);
    background: var(--surface);
    color: var(--muted);
    cursor: pointer;
    transition: all 0.15s;
    -webkit-appearance: none;
    appearance: none;
  }
  .listed-metric-btn:hover { color: var(--text); border-color: var(--hint); }
  .listed-metric-btn.active { background: var(--surface2); border-color: var(--muted); color: var(--text); }
  /* Trend delta label inside today card */
  .pokemon-trend {
    font-size: 10px;
    font-family: var(--mono);
    line-height: 1.2;
  }

  /* ── bar view (A: inline bar rows) ── */
  .at-bar-row {
    display: flex;
    align-items: center;
    gap: 10px;
    padding: 7px 4px;
    transition: background 0.15s;
  }
  .at-bar-row:hover { background: var(--surface2); border-radius: 6px; }
  .at-bar-rank { font-size: 11px; color: var(--hint); width: 18px; text-align: right; flex-shrink: 0; }
  .at-bar-sprite { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; position: relative; }
  .at-bar-sprite img { width: 28px; height: 28px; object-fit: contain; image-rendering: pixelated; }
  .at-bar-name { font-size: 13px; font-weight: 500; color: var(--text); min-width: 90px; }
  .at-bar-track { flex: 1; height: 5px; background: var(--border2); border-radius: 3px; overflow: hidden; }
  .at-bar-fill { height: 100%; background: var(--blue); border-radius: 3px; transition: width 0.4s ease; }
  .at-bar-count { font-size: 11px; color: var(--muted); font-family: var(--mono); min-width: 70px; text-align: right; }
  .at-bar-move { font-size: 10px; font-weight: 600; min-width: 14px; text-align: center; }

  /* ── card view (B: pokemon card grid) ── */
  .at-card-grid {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: 8px;
  }
  .at-card {
    background: var(--surface2);
    border: 1px solid var(--border);
    border-radius: 10px;
    position: relative;
    transition: border-color 0.15s;
    /* the tile's padding + column layout live on .dash-flip-front so the
       WHOLE tile content can 3D-flip into full-tile card art */
    perspective: 600px;
    cursor: pointer;
  }
  .at-card:hover { border-color: var(--muted); }
  .at-card-rank { position: absolute; top: 5px; left: 7px; font-size: 9px; color: var(--hint); font-weight: 500; }
  .at-card-sprite { width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; position: relative; }
  .at-card-sprite img { width: 40px; height: 40px; object-fit: contain; image-rendering: pixelated; }
  /* Tap-to-flip card art: tapping a tile 3D-flips the WHOLE tile into a
     random card of that Pokémon; tap again → back to normal; again →
     another card (cumulative rotation = constant spin direction). */
  .dash-flip-inner {
    position: relative; width: 100%; height: 100%;
    transform-style: preserve-3d;
    transition: transform 0.5s cubic-bezier(0.35, 0.1, 0.25, 1);
  }
  .dash-flip-front {
    /* in flow — it defines the tile's height; carries the old .at-card layout */
    padding: 10px 6px 8px;
    display: flex; flex-direction: column; align-items: center; gap: 5px;
    position: relative;
    backface-visibility: hidden; -webkit-backface-visibility: hidden;
  }
  .dash-flip-back {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    padding: 4px;
    backface-visibility: hidden; -webkit-backface-visibility: hidden;
    transform: rotateY(180deg);
  }
  .dash-flip-back img { width: 100%; height: 100%; object-fit: contain; border-radius: 6px; }
  /* #213: long-pressing a flipped card image on iOS popped the "Save Image" sheet,
     hijacking our right-click/long-press menu. Kill the iOS callout + image drag
     and let the touch fall through to the tile's own long-press handler. */
  .dash-flip-inner img, .dash-flip-front img, .dash-flip-back img {
    -webkit-touch-callout: none; -webkit-user-select: none; user-select: none;
    -webkit-user-drag: none; pointer-events: none;
  }
  .at-card-name { font-size: 11px; font-weight: 500; color: var(--text); text-align: center; }
  .at-card-count { font-size: 10px; color: var(--muted); font-family: var(--mono); }
  .at-card-move { font-size: 9px; font-weight: 600; min-height: 12px; }
  /* Trend mode card — big coloured % change as the primary signal */
  .at-card-pct {
    font-size: 12px;
    font-weight: 700;
    font-family: var(--mono);
    text-align: center;
    line-height: 1.2;
    min-height: 16px;
  }

  /* ── movers — 5-col grid: rank · sprite · name · movement/% · price/#rank ── */
  /* at-mover and at-insight legacy aliases */
  .at-mover-row { display: grid; grid-template-columns: 36px 44px 1fr 80px 50px 36px; align-items: center; padding: 9px 6px; transition: background 0.15s; }
  .at-mover-row:hover { background: var(--surface2); border-radius: 6px; }
  .at-insight-row { display: grid; grid-template-columns: 36px 44px 1fr 72px 60px 36px; align-items: center; padding: 9px 6px; transition: background 0.15s; }
  .at-insight-row:hover { background: var(--surface2); border-radius: 6px; }

  /* ── leaderboard view (C: % share + bar) ── */
  .at-lb-row {
    display: grid;
    grid-template-columns: 20px 34px minmax(0,1fr) auto;
    column-gap: 10px;
    align-items: center;
    padding: 9px 10px;
    border-radius: 8px;
    transition: background 0.15s;
  }
  .at-lb-row:hover { background: var(--surface2); }
  /* Same scale as the Most Listed ladder rank — the old display-size rank
     ate title space for nothing. */
  .at-lb-num { font-size: 11px; font-weight: 500; color: var(--hint); width: 20px; text-align: center; flex-shrink: 0; }
  .at-lb-num.top { color: var(--text); }
  .at-lb-sprite { width: 34px; height: 34px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; position: relative; }
  .at-lb-sprite img { width: 34px; height: 34px; object-fit: contain; image-rendering: pixelated; }

  /* Pokéball fallback for All-Time leaderboard (both card grid and row list).
     Same pattern as the Ledger / dashboard top-Pokémon grid: shown by default,
     hidden by JS when PokéAPI resolves the sprite. Trainer cards and other
     unrecognised names keep the pokéball as their thumbnail. */
  .at-sprite-fallback {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    pointer-events: none;
  }
  .at-sprite-fallback .sprite-fallback-svg {
    width: 80%;
    height: 80%;
  }
  .at-lb-info { flex: 1; display: flex; flex-direction: column; gap: 2px; min-width: 0; }
  /* #130: long card names scroll sideways (scrollbar hidden) instead of
     clipping — slightly smaller so most fit outright */
  .at-lb-name {
    font-size: 12px; font-weight: 500; color: var(--text);
    white-space: nowrap; overflow-x: auto; scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .at-lb-name::-webkit-scrollbar { display: none; }
  .at-lb-sub { font-size: 11px; color: var(--muted); }
  .at-lb-right { display: flex; flex-direction: column; align-items: flex-end; gap: 3px; flex-shrink: 0; }
  .at-lb-count { font-size: 11px; font-weight: 500; font-family: var(--mono); color: var(--muted); }
  .at-lb-pct { font-size: 10px; color: var(--muted); }
  .at-lb-bar-wrap { width: 80px; height: 3px; background: var(--border2); border-radius: 2px; }
  .at-lb-info .at-lb-bar-wrap { width: 100%; margin-top: 3px; }
  .at-lb-bar-inner { height: 100%; border-radius: 2px; background: var(--blue); transition: width 0.4s ease; }

  /* ── Top Flips leaderboard (#226) — reuses the .at-lb-row grid ── */
  .flip-sub {
    display: flex; align-items: center; gap: 6px; min-width: 0;
    font-size: 11px; color: var(--muted); font-family: var(--mono);
  }
  /* Avatar = the nickname's first letter for now (chosen-SVG set comes later). */
  .flip-avatar {
    flex-shrink: 0; width: 16px; height: 16px; border-radius: 50%;
    display: inline-flex; align-items: center; justify-content: center;
    background: var(--blue); color: #fff; font-size: 9px; font-weight: 700;
    font-family: var(--mono); text-transform: uppercase; line-height: 1;
  }
  .flip-avatar--img { background: none; overflow: hidden; }
  .flip-avatar--img img { width: 100%; height: 100%; display: block; }
  /* Nickname scrolls sideways (scrollbar hidden), mirroring .at-lb-name. */
  .flip-nick {
    color: var(--text); font-weight: 500;
    white-space: nowrap; overflow-x: auto; scrollbar-width: none; -webkit-overflow-scrolling: touch;
    min-width: 0; flex: 0 1 auto;
  }
  .flip-nick::-webkit-scrollbar { display: none; }
  .dashboard-flips-wrap { overscroll-behavior: contain; }

  /* Vertical scroll-fade: a subtle bottom fade on any scroll container that can
     still scroll down (class toggled by _markVScroll); gone once at the bottom.
     The mask is anchored to the viewport, so it fades the bottom edge of the
     visible area, not the scrolled content. */
  .scroll-fade-y.can-scroll-down,
  .alltime-list.can-scroll-down {
    -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 30px), transparent);
            mask-image: linear-gradient(to bottom, #000 calc(100% - 30px), transparent);
  }

  /* Language code sits inline right after the card name (muted, like the ledger
     meta lang code). The region flag gets its own small column to the left of the
     profit/ROI, nudged left off the numbers. */
  .flip-lang { color: var(--muted); font-weight: 400; }
  /* Fixed flag + profit columns so the flag never shifts when a price is a
     different width (the profit right-aligns within its own fixed column). The
     flag sits at the LEFT of its column and the profit column is trimmed, giving
     the card name more room. */
  .flip-row { grid-template-columns: 20px 34px minmax(0,1fr) 24px 80px; }
  .flip-codes { display: flex; align-items: center; justify-content: flex-start; }
  .flip-code-region { font-size: 14px; line-height: 1; }
  .flip-row .at-lb-right { width: 80px; }
  .at-lb-count.flip-profit-pos { color: var(--green); }
  .at-lb-count.flip-profit-neg { color: var(--red); }
  .flip-roi { font-size: 10px; font-family: var(--mono); color: var(--muted); }

  /* ===== DROUGHT RING ===== */


  /* ===== HOURS CHART ===== */
  .hours-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 1.25rem;
    margin-bottom: 2rem;
  }

  .hours-chart {
    display: flex;
    align-items: flex-end;
    gap: 4px;
    height: 80px;
    margin-bottom: 6px;
  }

  .hours-empty {
    font-family: var(--mono);
    font-size: 12px;
    color: var(--hint);
    align-self: center;
    width: 100%;
    text-align: center;
  }

  .hour-bar-wrap {
    flex: 1;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: flex-end;
    height: 100%;
    position: relative;
  }

  .hour-bar {
    width: 100%;
    border-radius: 3px 3px 0 0;
    min-height: 2px;
    transition: height 0.4s ease;
    position: relative;
  }

  .hour-bar.edge { background: var(--blue); opacity: 0.85; }
  .hour-bar.scope { background: #a78bfa; opacity: 0.85; }
  .hour-bar.both { background: linear-gradient(180deg, #a78bfa, var(--blue)); opacity: 0.9; }
  .hour-bar.empty { background: var(--border2); opacity: 0.4; }
  .hour-bar.future { background: var(--border); opacity: 0.2; }

  /* ── Schedule listing collapsible row (seller tab) ──
     Mirrors .scoring-rule-row from the Controls scoring widget. Collapsed
     state shows label + summary + chevron; expanded state reveals the
     datetime input. The input must respect the row's content box on mobile
     to avoid the native iOS datetime-local picker icon pushing it past the
     viewport edge. */
  .schedule-row {
    background: var(--surface);
    border: 1px solid var(--border2);
    border-radius: 10px;
    margin-bottom: 8px;
    overflow: hidden;   /* prevent datetime-local UA width from expanding the card */
    min-width: 0;
  }
  .schedule-row-header {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 10px 14px;
    border: none;
    background: transparent;
    color: var(--text);
    text-align: left;
    cursor: pointer;
    font: inherit;
    font-size: 12px;
    border-radius: 10px;
    -webkit-tap-highlight-color: transparent;
  }
  .schedule-row-header:hover { background: var(--surface2); }
  .schedule-row-header:focus { outline: none; }
  .schedule-row-header:focus-visible { outline: 2px solid var(--blue); outline-offset: -2px; }
  .schedule-row.expanded .schedule-row-header {
    border-bottom: 1px solid var(--border2);
    border-radius: 10px 10px 0 0;
  }
  .schedule-row.expanded .schedule-row-header:hover { background: transparent; }

  .schedule-row-label {
    flex: 1;
    min-width: 0;
    font-size: 12px;
    font-weight: 600;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .schedule-row-summary {
    flex: 0 0 auto;
    font-family: var(--mono);
    font-size: 11px;
    color: var(--hint);
  }
  .schedule-row.scheduled .schedule-row-summary { color: var(--amber); }

  .schedule-row-chevron {
    flex: 0 0 auto;
    font-size: 12px;
    color: var(--muted);
    transition: transform 0.15s ease;
    line-height: 1;
    width: 12px;
    text-align: center;
  }
  .schedule-row.expanded .schedule-row-chevron { transform: rotate(180deg); }

  .schedule-row-body {
    padding: 10px 14px 12px;
    overflow: hidden;
    min-width: 0;
    width: 100%;
    box-sizing: border-box;
  }
  .schedule-row-label-text {
    font-size: 10px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
    margin-bottom: 6px;
  }
  /* The datetime-local input must NEVER overflow the row body. Key points:
     - No seller-input class (which sets flex:1 and causes overflow in flex
       containers).
     - display:block so it's a proper block-level box, not a flex item.
     - width:100% fills the body, box-sizing:border-box so padding is
       included in that width, not added to it.
     - min-width:0 overrides browser UA minimums on datetime inputs which
       can otherwise force a width wider than the container on mobile. */
  .schedule-input {
    display: block;
    width: 100%;
    max-width: 100%;
    -webkit-appearance: none;
    appearance: none;
    min-width: 0;
    min-height: 40px;   /* match seller-input height — appearance:none can collapse it */
    box-sizing: border-box;
    background: var(--surface2);
    border: 1px solid var(--border2);
    border-radius: 8px;
    color: var(--text);
    font-family: var(--sans);
    font-size: 13px;
    padding: 9px 12px;
    outline: none;
    color-scheme: dark;
    transition: border-color 0.15s;
    margin-bottom: 4px;
  }
  .schedule-input:focus { border-color: var(--blue); }
  .schedule-row-hint {
    font-size: 10px;
    color: var(--hint);
    font-family: var(--mono);
  }
  .schedule-clear-btn {
    background: none;
    border: none;
    color: var(--red);
    font-size: 10px;
    font-family: var(--mono);
    cursor: pointer;
    margin-top: 6px;
    padding: 0;
  }

  /* ── Listing mode toggle (NEW / EDIT) ── */
  .active-mode-btn {
    background: var(--blue) !important;
    border-color: var(--blue) !important;
    color: #fff !important;
  }
  .active-mode-btn.edit-active {
    background: var(--amber) !important;
    border-color: var(--amber) !important;
    color: #000 !important;
  }
  #listing-picker-refresh.spinning {
    animation: picker-spin 0.7s linear infinite;
  }
  @keyframes picker-spin {
    from { transform: rotate(0deg); }
    to   { transform: rotate(360deg); }
  }
  #listing-picker-dropdown::-webkit-scrollbar { width: 4px; }
  #listing-picker-dropdown::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 2px; }
  .listing-picker-item {
    display: flex; align-items: center; gap: 10px;
    padding: 9px 12px; cursor: pointer; transition: background 0.1s;
    border-bottom: 1px solid var(--border); font-size: 12px;
  }
  .listing-picker-item:last-child { border-bottom: none; }
  .listing-picker-item:hover { background: var(--surface2); }
  .listing-picker-item .lpi-title { flex: 1; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .listing-picker-item .lpi-price { font-family: var(--mono); font-weight: 700; color: var(--green); flex-shrink: 0; }
  .listing-picker-item .lpi-scheduled { font-family: var(--mono); font-size: 10px; color: var(--amber); flex-shrink: 0; }

  /* ── PSA population split badge (seller tab card details header) ── */
  .pop-split-badge {
    display: inline-flex;
    align-items: stretch;
    border: 1px solid var(--border2);
    border-radius: 6px;
    overflow: hidden;
    font-family: var(--mono);
    font-size: 10px;
    white-space: nowrap;
    flex-shrink: 0;
  }
  .pop-split-grade {
    background: var(--border2);
    color: var(--muted);
    padding: 3px 6px;
    font-weight: 700;
  }
  .pop-split-count {
    background: var(--surface2);
    color: var(--text);
    padding: 3px 7px;
    font-weight: 600;
  }
  .pop-split-higher {
    color: var(--hint);
    font-size: 9px;
    margin-left: 4px;
  }

  /* Cert lookup + price lookup small buttons — shared explicit width so
     📷 and Broaden are the same size across both rows. */
  #cert-scan-btn, #cert-force-btn,
  #price-broaden-btn, #price-force-btn {
    width: 72px;
    padding-left: 0;
    padding-right: 0;
    text-align: center;
    flex-shrink: 0;
  }
  /* Suppress ALL focus styling on the three lookup buttons — including iOS WebKit */
  #price-force-btn,
  #price-btn,
  #price-ebay-btn {
    -webkit-tap-highlight-color: transparent;
    outline: none !important;
    -webkit-appearance: none;
    user-select: none;
    -webkit-user-select: none;
  }
  #price-force-btn:focus,
  #price-force-btn:focus-visible,
  #price-btn:focus,
  #price-btn:focus-visible,
  #price-ebay-btn:focus,
  #price-ebay-btn:focus-visible {
    outline: none !important;
    box-shadow: none !important;
    border-color: var(--border2) !important;
  }


  /* ── Tier gates ──────────────────────────────────────────────────────────────
     Philosophy: never break layout. Locked content stays in place, dims to
     show it exists, a single unobtrusive chip communicates the gate.
  ─────────────────────────────────────────────────────────────────────────── */

  /* Dimmed row — used for locked list/bar/card/stats rows and today cards */
  .tier-dim {
    opacity: 0.22;
    pointer-events: none;
    user-select: none;
    -webkit-user-select: none;
  }

  /* Locked stat block in lookup — dim the whole card, show lock in label */
  .price-stat.tier-locked {
    opacity: 0.35;
    pointer-events: none;
    cursor: default;
  }

  /* Locked tab — dimmed, cursor blocked */
  .tier-tab-locked {
    opacity: 0.35;
    cursor: default;
    pointer-events: none;
  }

  /* Locked ledger button — dim in place, no layout change */
  /* ledger-gated-wrap is itself a subgrid — date-wrap aligns to column 2,
     ledger-io aligns to column 3. Date-wrap inherits exactly 1fr from outer grid. */
  #ledger-gated-wrap {
    grid-column: 2 / 4;
    display: grid;
    grid-template-columns: subgrid;
    gap: 8px;
    align-items: center;
    position: relative;
    min-width: 0;
  }
  #ledger-gated-wrap .ledger-date-wrap { min-width: 0; }
  /* Overlay sits on ledger-date-wrap (position:relative).
     Right is negative so it extends past date-wrap to cover ledger-io too.
     The io cluster is 2 buttons (~32px each) + 6px gap = ~70px, plus 6px
     gap between date-wrap and ledger-io = ~76px total extension. */
  #ledger-gate-overlay {
    display: none;
  }
  #ledger-gated-wrap.active #ledger-gate-overlay {
    display: flex;
    position: absolute;
    inset: 0;
    background: var(--surface);
    opacity: 0.9;
    border-radius: 8px;
    align-items: center;
    justify-content: center;
    gap: 4px;
    font-size: 10px;
    font-family: var(--mono);
    color: var(--hint);
    pointer-events: none;
    z-index: 2;
    white-space: nowrap;
  }
  .ledger-io-btn.tier-locked,
  .ledger-date-btn.tier-locked {
    opacity: 0.35;
    pointer-events: none;
  }

  /* Upgrade chip — single small chip shown once over a locked group */
  .tier-upgrade-chip {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 3px 8px;
    background: var(--surface);
    border: 1px solid var(--border2);
    border-radius: 4px;
    font-size: 10px;
    font-family: var(--mono);
    letter-spacing: 0.04em;
    color: var(--hint);
    white-space: nowrap;
    cursor: pointer;
    transition: color 0.15s, border-color 0.15s;
    text-decoration: none;
  }
  .tier-upgrade-chip:hover { color: var(--text); border-color: var(--muted); }

  /* Chip row — centred row that holds the upgrade chip */
  .tier-chip-row {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 6px 0 2px;
  }

  .alltime-list-wrap { position: relative; }

  /* meta line lock — dim ps-meta in place, no movement */
  #ps-meta.tier-locked {
    opacity: 0.3;
    pointer-events: none;
  }

  /* ── Redacted bars for locked rows ──────────────────────────────────────────
     Shown in place of names/counts on tier-locked list rows.
     Two widths so they don't all look identical.
  ─────────────────────────────────────────────────────────────────────────── */
  .tier-redact {
    display: inline-block;
    height: 8px;
    border-radius: 4px;
    background: var(--border2);
    opacity: 0.6;
    vertical-align: middle;
  }
  .tier-redact-name  { width: 72px; }
  .tier-redact-name-sm { width: 48px; }
  .tier-redact-count { width: 36px; }

  /* ── Global tier toast ───────────────────────────────────────────────────────
     Single fixed toast at bottom of screen, fades in/out.
     Used instead of redirecting user on tap of locked element.
  ─────────────────────────────────────────────────────────────────────────── */
  #tier-toast {
    position: fixed;
    bottom: 80px;
    left: 50%;
    transform: translateX(-50%);
    z-index: 300;
    background: var(--surface);
    border: 1px solid var(--border2);
    border-radius: 6px;
    padding: 7px 14px;
    font-size: 11px;
    font-family: var(--mono);
    color: var(--muted);
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.25s;
    box-shadow: 0 4px 16px rgba(0,0,0,0.4);
  }
  #tier-toast.visible { opacity: 1; }

  /* ── Tier gate: inline upgrade row ──────────────────────────────────────────
     Shown as the final row/card in a locked widget.
     Replaces chips, popups, overlays — everything lives here, inline.
  ─────────────────────────────────────────────────────────────────────────── */
  .tier-upgrade-row {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 8px 0;
    color: var(--hint);
    font-size: 11px;
    font-family: var(--mono);
    opacity: 0.7;
    width: 100%;
  }

  /* Redacted pokemon card — centred single bar, no sprite/rank */
  .pokemon-card.tier-redacted {
    pointer-events: none;
    opacity: 0.3;
    justify-content: center;
    align-items: center;
  }

  /* Upgrade message spanning full grid width under redacted cards */
  .pokemon-upgrade-row {
    grid-column: 1 / -1;
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    padding: 4px 0 2px;
    font-size: 11px;
    font-family: var(--mono);
    color: var(--hint);
    opacity: 0.7;
  }

  /* ── QR scanner overlay ── */
  #qr-scanner-overlay {
    position: fixed;
    inset: 0;
    background: #000;
    z-index: 10000;
    display: flex;
    flex-direction: column;
    align-items: center;
  }
  .qr-scanner-header {
    width: 100%;
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 16px 20px 12px;
    flex-shrink: 0;
  }
  .qr-scanner-title {
    font-size: 14px;
    font-weight: 700;
    color: #fff;
  }
  .qr-scanner-close {
    background: rgba(255,255,255,0.12);
    border: none;
    color: #fff;
    font-size: 16px;
    width: 34px;
    height: 34px;
    border-radius: 50%;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
  }
  .qr-scanner-viewport {
    position: relative;
    width: 100%;
    flex: 1;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  #qr-video {
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  .qr-scanner-frame {
    position: absolute;
    width: min(65vw, 260px);
    height: min(65vw, 260px);
    border-radius: 12px;
    /* Corner-only border using box-shadow + clip trick */
    box-shadow:
      inset 0 0 0 0 transparent,
      0 0 0 9999px rgba(0,0,0,0.55);
    pointer-events: none;
  }
  /* Four corner brackets */
  .qr-scanner-frame::before,
  .qr-scanner-frame::after {
    content: "";
    position: absolute;
    width: 28px;
    height: 28px;
    border-color: #fff;
    border-style: solid;
  }
  .qr-scanner-frame::before {
    top: 0; left: 0;
    border-width: 3px 0 0 3px;
    border-radius: 10px 0 0 0;
  }
  .qr-scanner-frame::after {
    top: 0; right: 0;
    border-width: 3px 3px 0 0;
    border-radius: 0 10px 0 0;
  }
  /* Bottom corners via pseudo on a child div — handled in JS innerHTML */
  .qr-corner-bl {
    position: absolute;
    bottom: 0; left: 0;
    width: 28px; height: 28px;
    border: 3px solid #fff;
    border-top: none; border-right: none;
    border-radius: 0 0 0 10px;
    pointer-events: none;
  }
  .qr-corner-br {
    position: absolute;
    bottom: 0; right: 0;
    width: 28px; height: 28px;
    border: 3px solid #fff;
    border-top: none; border-left: none;
    border-radius: 0 0 10px 0;
    pointer-events: none;
  }
  .qr-scanner-frame.found {
    box-shadow:
      inset 0 0 0 0 transparent,
      0 0 0 9999px rgba(0,0,0,0.55);
  }
  .qr-scanner-frame.found::before,
  .qr-scanner-frame.found::after { border-color: var(--green, #4ade80); }
  .qr-scanner-hint {
    color: rgba(255,255,255,0.7);
    font-size: 12px;
    text-align: center;
    padding: 14px 24px 6px;
    flex-shrink: 0;
  }
  .qr-scanner-note {
    color: rgba(255,255,255,0.35);
    font-size: 11px;
    padding: 4px 24px 20px;
    flex-shrink: 0;
    text-align: center;
  }
  .qr-scanner-link {
    color: rgba(255,255,255,0.6);
    cursor: pointer;
    text-decoration: underline;
  }

  /* ── Busiest hours bar tap popup ── */
  #hours-bar-popup {
    position: absolute;
    bottom: calc(100% - 16px);
    min-width: 130px;
    max-width: 200px;
    background: var(--surface2);
    border: 1px solid var(--border2);
    border-radius: 10px;
    padding: 9px 11px;
    z-index: 9999;
    box-shadow: 0 6px 18px rgba(0,0,0,0.45);
    pointer-events: none;
  }
  #hours-bar-popup::after {
    content: "";
    position: absolute;
    bottom: -5px;
    width: 9px;
    height: 9px;
    background: var(--surface2);
    border-right: 1px solid var(--border2);
    border-bottom: 1px solid var(--border2);
    transform: rotate(45deg);
    left: var(--arrow-left, 50%);
  }
  #hours-bar-popup.hours-popup-visible {
    display: block !important;
    pointer-events: auto;
  }

  .hours-labels {
    display: flex;
    gap: 4px;
  }

  .hour-label {
    flex: 1;
    text-align: center;
    font-size: 8px;
    color: var(--hint);
    font-family: var(--mono);
  }


  @media (max-width: 700px) {
    .overview-grid { grid-template-columns: 1fr 1fr; }
    .bots-grid, .logs-grid, .pokemon-grid { grid-template-columns: 1fr; }
    body { padding: 1rem; }

    .hours-chart { height: 60px; }
    .hour-label { font-size: 7px; }
    .at-card-grid { grid-template-columns: repeat(3, 1fr); }
    .at-lb-bar-wrap { width: 60px; }
    .at-lb-info .at-lb-bar-wrap { width: 100%; }
    .at-lb-row { grid-template-columns: 18px 30px minmax(0,1fr) auto; column-gap: 8px; }
    .flip-row { grid-template-columns: 18px 30px minmax(0,1fr) 22px 76px; }
    .flip-row .at-lb-right { width: 76px; }
    .at-lb-sprite, .at-lb-sprite img { width: 30px; height: 30px; }
    .ledger-grade-pill { font-size: 9.5px; padding: 1px 5px; }
    .alltime-header { flex-direction: column; align-items: stretch; gap: 6px; }
    .alltime-tabs { width: 100%; }
    .alltime-tab { flex: 1; text-align: center; }
    .alltime-view-btns { width: 100%; }
    .alltime-view-btn { flex: 1; text-align: center; }
  }

  .margins-row {
    display: flex;
    gap: 10px;
    align-items: flex-end;
    flex-wrap: wrap;
  }

  /* Explicit 3-column grid for Scan Margins (6 fields → 3 cols × 2 rows).
     Flex-wrap on .margins-row would give the same result only at the exact
     right container width; a grid guarantees the 3×2 shape regardless. */
  .margins-grid-3x2 {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 10px 12px;
    align-items: end;
  }

  .margins-grid-2x1 {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: 10px 12px;
    align-items: end;
  }

  .margins-grid-3x1 {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 10px 12px;
    align-items: end;
  }

  .margins-field {
    display: flex;
    flex-direction: column;
    gap: 5px;
    flex: 1;
    min-width: 100px;
  }

  .margins-field label {
    font-size: 10px;
    color: var(--muted);
    font-weight: 500;
    letter-spacing: 0.05em;
    text-transform: uppercase;
  }

  .margins-input {
    background: var(--surface2);
    border: 1px solid var(--border2);
    border-radius: 6px;
    color: var(--text);
    font-family: var(--mono);
    font-size: 13px;
    padding: 7px 10px;
    width: 100%;
    outline: none;
    transition: border-color 0.15s;
  }

  .margins-input:focus { border-color: var(--blue); }

  .margins-apply-wrap {
    display: flex;
    align-items: flex-end;
  }

  .margins-label-tip {
  }

  .margins-apply {
    padding: 7px 18px;
    border-radius: 6px;
    border: 1px solid var(--border2);
    background: var(--surface2);
    color: var(--muted);
    font-family: var(--sans);
    font-size: 11px;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.15s;
    white-space: nowrap;
    align-self: flex-end;
  }
  /* #266: Sprite Style segmented toggle — the active mode stands out. */
  /* #226: Public-profile Yes/No share toggle reuses the same active treatment. */
  .sprite-style-opt, .ledger-sprite-opt, .pp-share-opt, .autolist-opt, .bolt-vis-opt, .bolt-ledger-opt, .mc-vis-opt, .mkt-opt { align-self: stretch; text-align: center; }
  .sprite-style-opt.active, .ledger-sprite-opt.active, .pp-share-opt.active, .autolist-opt.active, .bolt-vis-opt.active, .bolt-ledger-opt.active, .mc-vis-opt.active, .mkt-opt.active, .lookup-format-opt.active {
    border-color: var(--blue);
    color: var(--text);
    background: var(--surface);
    font-weight: 700;
    box-shadow: inset 0 0 0 1px var(--blue);
  }

  /* #226: Public-profile avatar picker — a wrap of circular options. */
  .pp-avatar-picker { display: flex; flex-wrap: wrap; gap: 8px; }
  .pp-avatar-opt {
    width: 40px; height: 40px; padding: 0; border-radius: 50%;
    border: 1px solid var(--border2); background: var(--surface2);
    cursor: pointer; overflow: hidden; flex: 0 0 auto;
    display: flex; align-items: center; justify-content: center;
    transition: border-color 0.15s, box-shadow 0.15s;
  }
  .pp-avatar-opt img { width: 100%; height: 100%; display: block; }
  .pp-avatar-letter { font-family: var(--mono); font-weight: 700; font-size: 15px; color: var(--muted); text-transform: uppercase; }
  .pp-avatar-opt.active { border-color: var(--blue); box-shadow: 0 0 0 2px var(--blue); }
  @media (hover: hover) { .pp-avatar-opt:hover { border-color: var(--blue); } }

  .margins-apply:hover { border-color: var(--blue); color: var(--blue); }
  .margins-apply:active { opacity: 0.7; }
  .margins-apply.pending-apply { border-color: var(--amber); color: var(--amber); background: var(--amber-dim); }
  #sign-out-btn.pending-apply { border-color: var(--red) !important; color: var(--red) !important; background: transparent !important; }

  .controls-grid {
    display: grid;
    /* Always single column — app is capped at 480px so two columns is too cramped */
    grid-template-columns: minmax(0, 1fr);
    gap: 12px;
    margin-bottom: 2rem;
  }

  .control-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 1.25rem;
  }

  .control-card-title {
    font-size: 10px;
    font-weight: 500;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--hint);
    margin-bottom: 12px;
  }

  .grade-toggle {
    display: flex;
    gap: 6px;
    margin-bottom: 0;
  }

  .grade-btn {
    flex: 1;
    padding: 7px 6px;
    border-radius: 6px;
    border: 1px solid var(--border2);
    background: var(--surface2);
    color: var(--muted);
    font-family: var(--sans);
    font-size: 11px;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.15s;
    text-align: center;
  }

  /* Hover lift restricted to genuine pointer devices (mice, trackpads).
     Without the @media guard, mobile/touch browsers latch :hover on the
     last-tapped element until you tap somewhere else — so a tapped-then-
     unselected button shows a brighter "almost-active" border that's
     neither the muted unselected look nor the blue active look.
     `(hover: hover)` is the standard way to detect a true hover-capable
     pointer; touchscreens evaluate to `(hover: none)` and skip this rule. */
  @media (hover: hover) {
    .grade-btn:hover { border-color: var(--hint); color: var(--text); }
  }
  .grade-btn.active { border-color: var(--blue); color: var(--blue); background: var(--blue-dim); }
  .grade-btn.pending { border-color: var(--amber); color: var(--amber); background: var(--amber-dim); }
  .grade-btn:active { opacity: 0.7; }

  @media (max-width: 700px) {
    .controls-grid { grid-template-columns: minmax(0, 1fr); }
  }

  /* ── Cache hit rate ── */
  .gst-popup-visible { display: block !important; }

  /* Scraper-health popup: small bubble anchored below the pill. */
  #scraper-health-popup {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    min-width: 170px;
    max-width: 240px;
    background: var(--surface2);
    border: 1px solid var(--border2);
    border-radius: 10px;
    padding: 10px 12px;
    z-index: 9999;
    box-shadow: 0 6px 18px rgba(0,0,0,0.45);
  }
  /* Little arrow pointing up to the pill */
  #scraper-health-popup::before {
    content: "";
    position: absolute;
    top: -5px;
    right: 14px;
    width: 9px;
    height: 9px;
    background: var(--surface2);
    border-left: 1px solid var(--border2);
    border-top: 1px solid var(--border2);
    transform: rotate(45deg);
  }
  /* Mobile: keep the bubble near the pill but make sure it doesn't
     get clipped by body's overflow-x:hidden. Shift it left so its
     right edge stays inside the viewport. */
  @media (max-width: 700px) {
    #scraper-health-popup {
      right: -4px;
      min-width: 160px;
      max-width: calc(100vw - 32px);
    }
  }
  .scraper-popup-visible { display: block !important; }

  /* ── Per-widget help popup internals ──
     These classes style the DOM nodes that buildLegendSection() creates
     inside each widget's "?" popup (.widget-help-popup). The legend schema
     is shared across all widget-level help popups. */
  .settings-help-section {
    margin-bottom: 10px;
  }
  .settings-help-section:last-child {
    margin-bottom: 0;
  }
  .settings-help-title {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.01em;
    color: var(--muted);
    margin-bottom: 5px;
  }
  .settings-help-row {
    display: flex;
    gap: 6px;
    margin-bottom: 3px;
  }
  .settings-help-key {
    font-family: var(--mono);
    color: var(--blue);
    white-space: normal;
    word-break: break-word;
    width: 90px;
    flex: 0 0 90px;
  }
  .settings-help-val {
    color: var(--hint);
    flex: 1;
    min-width: 0;
  }
  .widget-help-popup.widget-help-popup-wide .settings-help-key {
    width: 90px;
    flex: 0 0 90px;
  }
  .widget-help-popup.widget-help-popup-wide .settings-help-row {
    align-items: baseline;
  }

  /* ── eBay key dot indicator ── */
  .key-dot {
    font-size: 8px;
    margin-left: 3px;
    color: var(--hint);
    transition: color 0.3s;
  }
  .key-dot-active {
    color: var(--green);
    animation: key-pulse 2s ease-in-out infinite;
  }
  @keyframes key-pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.4; }
  }
  .cache-hit-display {
    display: flex;
    align-items: baseline;
    gap: 8px;
    margin-bottom: 10px;
  }
  .cache-hit-pct {
    font-size: 30px;
    font-weight: 300;
    letter-spacing: -0.03em;
    color: var(--text);
    line-height: 1;
    font-family: var(--mono);
  }
  .cache-hit-sub {
    font-size: 10px;
    color: var(--muted);
  }
  .cache-bar-wrap {
    width: 100%;
    height: 4px;
    background: var(--border2);
    border-radius: 2px;
    overflow: hidden;
  }
  .cache-bar-fill {
    height: 100%;
    background: var(--green);
    border-radius: 2px;
    transition: width 0.5s ease;
  }

  /* ── Skip breakdown ── */
  .skip-chart-wrap {
    display: flex;
    align-items: center;
    gap: 14px;
  }
  #skip-pie { flex-shrink: 0; }
  .skip-legend {
    display: flex;
    flex-direction: column;
    gap: 4px;
    flex: 1;
    min-width: 0;
  }
  .skip-legend-item {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 9px;
    color: var(--muted);
  }
  .skip-legend-dot {
    width: 7px;
    height: 7px;
    border-radius: 50%;
    flex-shrink: 0;
  }
  .skip-legend-label { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
  .skip-legend-val { font-family: var(--mono); color: var(--text); font-size: 9px; }
  .skip-empty { font-size: 10px; color: var(--hint); font-family: var(--mono); }

  .margins-label-tip { }

  /* ── Customise + Theme buttons — stacked top-right ── */
  .customise-btn,
  .theme-btn,
  .gear-btn,
  .medal-btn {
    position: fixed;
    /* Align to right edge of the 480px centred shell */
    right: calc(50% - 240px + 4px);
    z-index: 100;
    font-size: 16px;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    border: 1px solid var(--border2);
    background: var(--surface);
    color: var(--muted);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.15s;
    line-height: 1;
    padding: 0;
    -webkit-appearance: none;
    appearance: none;
    font-family: var(--sans);
    flex: 0 0 auto;
  }
  .customise-btn { top: 96px; }   /* #234: pencil is the 3rd (gear · theme · pencil) */
  .medal-btn { top: 96px; }       /* badge shares the pencil slot (shown on Settings) */
  /* Theme (gear) button — SVG icon so it renders identically on all
     platforms. The ⚙︎ glyph approach was unreliable on mobile: the
     U+FE0E text-presentation selector isn't honoured by all mobile
     browsers, causing the emoji renderer to show a double-gear instead
     of the single hairline gear shown on desktop. */
  .theme-btn {
    top: 56px;
  }
  /* #234: Settings gear — top of the right stack (gear · theme · pencil). */
  .gear-btn { top: 16px; }
  .gear-btn svg { width: 17px; height: 17px; display: block; }   /* #234: block removes the inline-SVG baseline gap so it centres in the button */
  @media (hover: hover) { .gear-btn:hover { border-color: var(--blue); color: var(--blue); } }
  .gear-btn:focus, .gear-btn:focus-visible { outline: none; }
  .gear-btn.active { background: var(--blue-dim); border-color: var(--blue); color: var(--blue); }
  /* Decidueye chat-bubble SVG has a tail dropping to the lower-left, which
     pulls its perceived centre down and left. Nudge the icon up and right by
     half a pixel each so the visual centre lines up with the button's
     geometric centre. */
  .decidueye-btn svg { transform: translate(0.5px, -0.5px); }
  @media (hover: hover) {
    .customise-btn:hover,
    .theme-btn:hover { border-color: var(--blue); color: var(--blue); }
  }
  .customise-btn:focus, .theme-btn:focus { outline: none; }
  .customise-btn:focus-visible, .theme-btn:focus-visible { outline: none; }
  .customise-btn.active,
  .theme-btn.active { background: var(--blue-dim); border-color: var(--blue); color: var(--blue); }

  /* ── Decidueye button (third in the top-right stack, below the gear) ──
     Reuses the customise/theme button shape so the toolbar feels like one
     family. The icon is an inline SVG (not a glyph) so it sits centred
     without the per-font baseline adjustments the gear needs. */
  .decidueye-btn {
    position: fixed;
    right: 20px;
    top: 96px;
    z-index: 100;
    width: 32px;
    height: 32px;
    border-radius: 50%;
    border: 1px solid var(--border2);
    background: var(--surface);
    color: var(--muted);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.15s;
    line-height: 1;
    padding: 0;
    -webkit-appearance: none;
    appearance: none;
    flex: 0 0 auto;
  }
  .decidueye-btn svg { display: block; }
  @media (hover: hover) {
    .decidueye-btn:hover { border-color: var(--blue); color: var(--blue); }
  }
  .decidueye-btn:focus, .decidueye-btn:focus-visible { outline: none; }
  .decidueye-btn.active {
    background: var(--blue-dim);
    border-color: var(--blue);
    color: var(--blue);
  }
  /* Subtle pulse while a request is in flight — caps at 0.7 so it never gets
     loud, and only animates the box-shadow so layout/border don't reflow. */
  .decidueye-btn.loading {
    animation: decidueye-pulse 1.4s ease-in-out infinite;
  }
  @keyframes decidueye-pulse {
    0%, 100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--blue) 35%, transparent); }
    50%      { box-shadow: 0 0 0 5px color-mix(in srgb, var(--blue) 0%,  transparent); }
  }

  /* ── Decidueye chat bubble + sprite ──
     Anchored under the button. The wrap is hidden by default and slides in
     from the right when .open is set. Sprite sits to the right of the
     bubble (so it reads as the speaker), bubble's tail points at the
     sprite. We size the bubble in characters so long replies wrap to a
     pleasant width regardless of viewport. */
  .decidueye-wrap {
    position: fixed;
    top: 96px;          /* aligned with the button */
    right: 64px;        /* clear of the button (32px wide + 12px gap + button-right) */
    z-index: 99;        /* below the button so the button stays clickable */
    display: flex;
    align-items: flex-start;
    gap: 10px;
    pointer-events: none;
    opacity: 0;
    transform: translateX(8px) scale(0.96);
    transform-origin: top right;
    transition: opacity 0.18s ease, transform 0.22s cubic-bezier(.34,1.5,.64,1);
  }
  .decidueye-wrap.open {
    opacity: 1;
    transform: translateX(0) scale(1);
    pointer-events: auto;
  }
  .decidueye-bubble {
    position: relative;
    background: var(--surface);
    border: 1px solid var(--border2);
    border-radius: 14px;
    padding: 10px 14px;
    /* Fluid sizing — bubble grows with its content. The previous design had
       min-width: 180px and min-height: 44px which made the bubble look huge
       and half-empty when it only contained an "…" loading indicator. Now
       it starts tight around the ellipsis and expands as Ollama streams in
       tokens. max-width still caps growth so very long replies wrap rather
       than running off the screen. */
    width: max-content;
    max-width: min(360px, calc(100vw - 140px));
    color: var(--text);
    font-family: var(--sans);
    font-size: 13px;
    font-weight: 400;
    line-height: 1.45;
    box-shadow: 0 6px 20px rgba(0,0,0,0.35);
    /* Smooth size transitions when content changes (loading → reply, or
       streaming chunks landing). max-width and max-height transitions
       work better than width/height because the actual content drives
       intrinsic size. */
    transition: max-width 0.18s ease;
  }
  /* Tail — a small triangle on the right edge that points at the sprite.
     Two pseudo-elements: outer (border colour) + inner (background) so the
     tail picks up the bubble's border. */
  .decidueye-bubble::before,
  .decidueye-bubble::after {
    content: "";
    position: absolute;
    top: 16px;
    width: 0;
    height: 0;
    border-style: solid;
  }
  .decidueye-bubble::before {
    right: -8px;
    border-width: 7px 0 7px 8px;
    border-color: transparent transparent transparent var(--border2);
  }
  .decidueye-bubble::after {
    right: -7px;
    border-width: 7px 0 7px 8px;
    border-color: transparent transparent transparent var(--surface);
  }
  .decidueye-text {
    white-space: pre-wrap;       /* preserve any line breaks Ollama emits */
    word-wrap: break-word;
    overflow-wrap: anywhere;
    min-height: 1em;
  }
  /* Blinking caret while streaming — appended via JS as a span with this
     class, removed when streaming completes. */
  .decidueye-caret {
    display: inline-block;
    width: 1px;
    background: var(--blue);
    margin-left: 2px;
    height: 1em;
    vertical-align: -2px;
    animation: decidueye-blink 0.9s steps(2, start) infinite;
  }
  @keyframes decidueye-blink {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0; }
  }
  /* Three-dot loading state shown before the first token arrives. */
  .decidueye-dots {
    display: inline-flex;
    gap: 4px;
    align-items: center;
    height: 1em;
  }
  .decidueye-dots span {
    width: 5px;
    height: 5px;
    border-radius: 50%;
    background: var(--muted);
    animation: decidueye-dot 1.2s ease-in-out infinite;
  }
  .decidueye-dots span:nth-child(2) { animation-delay: 0.15s; }
  .decidueye-dots span:nth-child(3) { animation-delay: 0.30s; }
  @keyframes decidueye-dot {
    0%, 60%, 100% { opacity: 0.3; transform: translateY(0); }
    30%           { opacity: 1;   transform: translateY(-2px); }
  }
  /* The sprite stage is a fixed 64x64 box that hosts both the canvas (real
     PMDCollab Mystery-Dungeon idle animation) and a fallback <img> beneath
     it. Canvas is positioned absolutely over the img so when the canvas
     successfully loads + draws, it covers the fallback completely. If the
     canvas never paints (network failure, parse failure, etc.) the img
     stays visible. */
  .decidueye-sprite-stage {
    position: relative;
    width: 64px;
    height: 64px;
    flex-shrink: 0;
    /* Soft drop-shadow so the sprite reads against any theme background
       without needing a card behind it. Lightened in light theme below. */
    filter: drop-shadow(0 2px 6px rgba(0,0,0,0.4));
  }
  .decidueye-sprite-canvas,
  .decidueye-sprite-fallback {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    image-rendering: pixelated;
    image-rendering: -moz-crisp-edges;
    -ms-interpolation-mode: nearest-neighbor;
  }
  /* Canvas is hidden until the first frame is drawn — JS removes the
     hidden class once the spritesheet loads. Avoids a flash of blank
     canvas during the first ~50ms of fetch/parse. */
  .decidueye-sprite-canvas.hidden { display: none; }
  /* Fallback <img> is hidden by default; JS reveals it only if the
     canvas pipeline fails. The PokéAPI showdown GIF brings its own
     idle motion so behaviour stays close to the canvas path. */
  .decidueye-sprite-fallback { display: none; }
  .decidueye-sprite-fallback.show { display: block; }

  /* Light theme — soften the sprite shadow, deepen the bubble shadow so it
     still has presence on a near-white surface. */
  html.theme-light .decidueye-sprite-stage,
  body.theme-light .decidueye-sprite-stage { filter: drop-shadow(0 2px 4px rgba(0,0,0,0.18)); }
  html.theme-light .decidueye-bubble,
  body.theme-light .decidueye-bubble { box-shadow: 0 4px 14px rgba(0,0,0,0.10); }

  /* Mobile — keep the same layout as desktop (anchored right, sprite on
     right facing left, bubble grows leftward). Earlier builds used
     row-reverse + left-side-tail + sprite flip on mobile, but that made
     the bubble drift further left as it grew, away from the sprite —
     visually unintuitive and the open animation pulled the wrong way.
     The bubble's `width: max-content` + `max-width: calc(100vw - 96px)`
     already prevents it from running off the left edge, so there's no
     real reason for mobile to diverge from the desktop layout.

     What actually changes on mobile:
       - Buttons are smaller (30px vs 32px) and live at right:12px
       - Customise → theme → decidueye stack is tighter (14 → 52 → 90)
       - Bubble sits BELOW the stack instead of beside it (top:128px)
       - Right offset shrinks from 64px → 50px to match the smaller btn

     Everything else (flex direction, tail direction, sprite orientation,
     transform-origin, animation) inherits from desktop. */
  @media (max-width: 700px) {
    .decidueye-btn { top: 90px; right: 12px; width: 30px; height: 30px; }
    .decidueye-btn svg { width: 13px; height: 13px; }
    .decidueye-wrap {
      top: 128px;
      right: 21px;         /* 5px further right — sprite hugs the edge,
                              bubble right edge moves 5px closer to centre */
    }
    /* Centering math on a 390px iPhone:
         wrap right:       21px from viewport right
         sprite + gap:     56px + 10px = 66px
         bubble right edge: 390 - 21 - 66 = 303px from left  (58% across)
         screen centre:    195px from left
         bubble width to reach centre: 303 - 195 = 108px
         left breathing room: 16px
         → ideal max-width: 303 - 16 = 287px = 100vw - 103px

       This lets the bubble grow leftward until its left edge sits 16px
       from the screen edge — well past centre, so long replies feel
       centred on screen rather than bunched to the right. */
    .decidueye-bubble {
      max-width: calc(100vw - 103px);
      width: max-content;
      font-size: 13px;
      padding: 10px 12px;
    }
    .decidueye-sprite-stage { width: 56px; height: 56px; }
  }

  /* ── Scroll-to-top button (bottom-right fixed, fades in while scrolling) ── */
  .scroll-top-btn {
    position: fixed;
    /* Sits ABOVE the Bolt launcher (bottom-right, ~90px tall) so they never overlap. */
    bottom: 102px;
    right: 22px;
    z-index: 100;
    width: 36px;
    height: 36px;
    min-width: 36px;
    min-height: 36px;
    max-width: 36px;
    max-height: 36px;
    padding: 0;
    border-radius: 50%;
    border: 1px solid var(--border2);
    background: var(--surface);
    color: var(--muted);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    -webkit-appearance: none;
    appearance: none;
    flex: 0 0 auto;
    /* Fade in/out */
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.35s ease, border-color 0.15s, color 0.15s, background 0.15s;
  }
  /* CSS chevron arrow — no text rendering issues across platforms */
  .scroll-top-btn::before {
    content: "";
    display: block;
    width: 10px;
    height: 10px;
    border-left: 2px solid currentColor;
    border-top: 2px solid currentColor;
    transform: rotate(45deg) translate(1px, 1px);
    border-radius: 1px;
  }
  .scroll-top-btn.scroll-top-visible {
    opacity: 0.5;
    pointer-events: auto;
  }
  @media (hover: hover) {
    .scroll-top-btn:hover {
      border-color: var(--blue);
      color: var(--blue);
      opacity: 1 !important;
    }
  }
  .scroll-top-btn:focus { outline: none; }
  .scroll-top-btn:focus-visible { outline: none; }
  @media (max-width: 700px) {
    .scroll-top-btn { bottom: 100px; right: 16px; width: 32px; height: 32px; }
    .scroll-top-btn::before { width: 9px; height: 9px; }
  }

  /* Theme popup — opens to the left of the button since it's on the right edge */
  .theme-popup {
    position: fixed;
    top: 56px;
    /* Always opens to left of gear — on desktop aligns to shell edge */
    right: calc(50% - 240px + 40px);
    z-index: 101;
    display: none;
    flex-direction: column;
    min-width: 180px;
    max-width: 240px;
    width: auto;
    background: var(--surface);
    border: 1px solid var(--border2);
    border-radius: 10px;
    padding: 8px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
  }
  .theme-popup-visible { display: flex; }
  .theme-popup::before {
    content: "";
    position: absolute;
    top: 10px;
    right: -5px;
    width: 9px;
    height: 9px;
    background: var(--surface);
    border-right: 1px solid var(--border2);
    border-top: 1px solid var(--border2);
    transform: rotate(45deg);
  }
  .theme-option {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px 10px;
    margin: 0;
    border-radius: 6px;
    border: 1px solid transparent;
    background: transparent;
    color: var(--text);
    font-family: var(--sans);
    font-size: 12px;
    cursor: pointer;
    text-align: left;
    transition: background 0.15s, border-color 0.15s;
    -webkit-appearance: none;
    appearance: none;
    -webkit-tap-highlight-color: transparent;
  }
  .theme-option + .theme-option { margin-top: 2px; }
  .theme-option:hover { background: var(--surface2); border-color: var(--border2); }
  .theme-option.active { background: var(--blue-dim); border-color: var(--blue); color: var(--blue); }
  .theme-option-swatch {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    border: 1px solid var(--border2);
    flex-shrink: 0;
    display: inline-block;
  }

  /* ── Inline upgrade picker ── */
  .upgrade-tier-btn {
    flex: 1;
    position: relative;
    padding: 7px 10px;
    border-radius: 6px;
    border: 1px solid var(--border2);
    background: var(--surface2);
    color: var(--muted);
    font-family: var(--sans);
    cursor: pointer;
    transition: all 0.15s;
    text-align: center;
    -webkit-appearance: none;
    appearance: none;
  }
  .upgrade-tier-btn:hover { border-color: var(--hint); color: var(--text); }
  .upgrade-tier-btn.active { border-color: var(--blue); color: var(--blue); background: var(--blue-dim); }
  /* #300: the Champion button wears the trial gold ring + badge, matching the
     pricing page's featured card so the 7-day trial reads at a glance. */
  .upgrade-tier-btn.trial-featured { border-color: var(--amber); box-shadow: 0 0 0 1px var(--amber) inset; }
  .upgrade-tier-btn.trial-featured:hover { border-color: var(--amber); color: var(--text); }
  .upgrade-tier-btn.trial-featured.active { border-color: var(--amber); color: var(--amber); background: color-mix(in srgb, var(--amber) 12%, var(--surface2)); }
  .upgrade-trial-badge {
    position: absolute;
    top: -14px; left: 50%; transform: translateX(-50%);
    background: var(--amber); color: #1a1208;
    font-size: 9px; font-weight: 800;
    padding: 2px 9px; border-radius: 99px;
    white-space: nowrap; letter-spacing: 0.04em; text-transform: uppercase;
    pointer-events: none;
  }

  .upgrade-period-btn {
    flex: 1;
    padding: 8px 10px;
    border-radius: 6px;
    border: 1px solid var(--border2);
    background: var(--surface2);
    color: var(--text);
    font-family: var(--sans);
    cursor: pointer;
    transition: all 0.15s;
    text-align: center;
    display: flex;
    align-items: baseline;
    justify-content: center;
    gap: 2px;
    -webkit-appearance: none;
    appearance: none;
  }
  .upgrade-period-btn:hover { border-color: var(--hint); }
  .upgrade-period-btn.active { border-color: var(--blue); background: var(--blue-dim); }
  .upgrade-period-price {
    font-size: 15px;
    font-weight: 700;
    font-family: var(--mono);
    color: var(--text);
  }
  .upgrade-period-btn.active .upgrade-period-price { color: var(--blue); }

  .upgrade-perk-list {
    list-style: none;
    padding: 0;
    margin: 0;
    width: 100%;
    /* #300: centre every perk (Trader & Champion alike); long perks wrap centred. */
    text-align: center;
  }
  .upgrade-perks-wrap {
    display: flex;
    justify-content: center;
    /* Vertically centre the shorter (Trader) list inside the uniform box so the gap
       sits evenly, not all before the price buttons. */
    align-items: center;
    padding: 4px 0;
    /* #300: min-height is set dynamically to the CHAMPION list's height (the taller
       one, at the current width) by _sizeUpgradePerks() so toggling Trader ↔
       Champion never resizes the modal. This is just a fallback before JS runs. */
    min-height: 180px;
  }
  .upgrade-perk-list li {
    font-size: 11px;
    font-family: var(--mono);
    color: var(--muted);
    padding: 3px 10px;
    line-height: 1.4;
  }
  .upgrade-perk-list li::before {
    content: "✓ ";
    color: var(--green);
    font-size: 10px;
  }

/* ── Module wrapper ── */
  .module {
    position: relative;
    transition: opacity 0.2s;
  }
  .module.hidden { display: none; }
  .module.dragging { opacity: 0.4; }
  .module.drag-over { outline: 2px dashed var(--blue); outline-offset: 4px; border-radius: 8px; }

  /* ── Edit overlay ── */
  .module-overlay {
    display: none;
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    z-index: 10;
    border-radius: 12px;
    background: rgba(0,0,0,0.45);
    align-items: center;
    justify-content: center;
    gap: 10px;
    pointer-events: none;
  }
  body.customise-mode .module-overlay { display: flex; pointer-events: all; }
  body.customise-mode .module { cursor: grab; }
  body.customise-mode .module:active { cursor: grabbing; }
  /* Controls tab has its own customise mode so the Dashboard pencil and the
     Controls pencil operate independently. Rules mirror .customise-mode. */
  body.controls-customise-mode .module-overlay { display: flex; pointer-events: all; }
  body.controls-customise-mode .module { cursor: grab; }
  body.controls-customise-mode .module:active { cursor: grabbing; }

  /* ── settings card overlays ── */
  .settings-module-header {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 10px;
  }
  .settings-edit-btn {
    font-size: 14px;
    width: 26px;
    height: 26px;
    border-radius: 50%;
    border: 1px solid var(--border2);
    background: transparent;
    color: var(--hint);
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    transition: all 0.15s;
    line-height: 1;
  }
  @media (hover: hover) { .settings-edit-btn:hover { border-color: var(--blue); color: var(--blue); } }
  .settings-edit-btn:focus { outline: none; }
  .settings-edit-btn:focus-visible { outline: none; }
  .settings-edit-btn.active { background: var(--blue-dim); border-color: var(--blue); color: var(--blue); }

  /* Per-widget reset button (⟲) — lives in each settings widget's title row.
     Smaller and more subtle than the settings-header buttons (⟲ / ✎) so the
     widget title stays visually dominant. */
  .widget-reset-btn {
    font-size: 12px;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: 1px solid var(--border2);
    background: transparent;
    color: var(--hint);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: all 0.15s;
    line-height: 1;
    padding: 0;
  }
  .widget-reset-btn svg { width: 10px; height: 10px; display: block; }   /* #234: SVG reset icon (replaces the off-centre ⟲ glyph) */
  @media (hover: hover) {
    .widget-reset-btn:hover { border-color: var(--red); color: var(--red); }
  }
  .widget-reset-btn:focus { outline: none; }
  .widget-reset-btn:focus-visible { outline: none; }

  /* Per-widget help button (?) — same footprint/subtlety as the reset (⟲)
     button, but tints blue on hover to distinguish "info" from "destructive". */
  .widget-help-btn {
    font-size: 11px;
    font-weight: 600;
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: 1px solid var(--border2);
    background: transparent;
    color: var(--hint);
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    transition: all 0.15s;
    line-height: 1;
    padding: 0;
  }
  @media (hover: hover) {
    .widget-help-btn:hover { border-color: var(--blue); color: var(--blue); }
  }
  .widget-help-btn:focus { outline: none; }
  .widget-help-btn:focus-visible { outline: none; }

  /* Per-widget help popup — the ? button next to each widget's ⟲ opens
     one of these inline, anchored below via .widget-help-wrap { position:
     relative }. Uses a subtle tooltip-style arrow. max-width keeps it from
     clipping the right edge on narrow phones. */
  .widget-help-popup {
    position: absolute;
    top: calc(100% + 8px);
    right: 0;
    width: 280px;
    max-width: calc(100vw - 32px);
    background: var(--surface2);
    border: 1px solid var(--border2);
    border-radius: 10px;
    padding: 12px 14px;
    z-index: 9999;
    box-shadow: 0 6px 18px rgba(0,0,0,0.45);
    font-size: 11px;
    line-height: 1.5;
    color: var(--text);
    text-transform: none;
    letter-spacing: normal;
    font-weight: 400;
  }
  /* Scoring legends carry more rows + longer keys. On desktop give them
     extra width; on mobile, max-width will cap this to the viewport. */
  .widget-help-popup.widget-help-popup-wide {
    width: 360px;
  }
  .widget-help-popup::before {
    content: "";
    position: absolute;
    top: -5px;
    right: 10px;
    width: 9px;
    height: 9px;
    background: var(--surface2);
    border-left: 1px solid var(--border2);
    border-top: 1px solid var(--border2);
    transform: rotate(45deg);
  }
  @media (max-width: 700px) {
    /* Stay a small popup anchored below the ? button (matches every other
       popup in the app). Scoring legends carry more rows so a desktop-wide
       360px variant exists, but on mobile we collapse all popups back to
       the standard 280px (which fits comfortably anchored to a button on
       any phone) and rely on key wrapping + vertical scroll to handle the
       extra content. max-width also caps at viewport width as a final
       safety net for very narrow screens. */
    .widget-help-popup,
    .widget-help-popup.widget-help-popup-wide {
      width: 280px;
      max-width: calc(100vw - 28px);
      max-height: calc(100vh - 120px);
      overflow-y: auto;
      -webkit-overflow-scrolling: touch;
    }
    .widget-help-popup .settings-help-key {
      /* Fixed narrow column so description always starts at the same x position. */
      width: 90px;
      flex: 0 0 90px;
      white-space: normal;
      word-break: break-word;
    }
    .widget-help-popup .settings-help-val {
      overflow-wrap: anywhere;
      flex: 1;
      min-width: 0;
    }
    .widget-help-popup .settings-help-row {
      align-items: baseline;
    }
  }
  .control-card { position: relative; }
  .card-overlay {
    display: none;
    position: absolute;
    top: 0; left: 0; right: 0; bottom: 0;
    z-index: 10;
    border-radius: 10px;
    background: rgba(0,0,0,0.5);
    align-items: center;
    justify-content: center;
    gap: 8px;
    pointer-events: none;
  }
  /* Card overlay + drag cursor visibility in edit mode.
     Each GROUPS entry has its own body class (usage-edit-mode,
     rates-edit-mode, settings-edit-mode). Rules are scoped to the matching
     [data-group] so toggling one group's edit mode doesn't make cards in
     other groups also become draggable. updateCardEditVisibility() in
     app.js handles overlay show/hide at the JS level too (belt and braces);
     the CSS handles cursor + pointer events. */
  body.settings-edit-mode .control-card[data-group="settings"] .card-overlay,
  body.usage-edit-mode    .control-card[data-group="usage"]    .card-overlay,
  body.rates-edit-mode    .control-card[data-group="rates"]    .card-overlay {
    display: flex;
    pointer-events: all;
  }
  body.settings-edit-mode .control-card[data-group="settings"],
  body.usage-edit-mode    .control-card[data-group="usage"],
  body.rates-edit-mode    .control-card[data-group="rates"] { cursor: grab; }
  body.settings-edit-mode .control-card[data-group="settings"]:active,
  body.usage-edit-mode    .control-card[data-group="usage"]:active,
  body.rates-edit-mode    .control-card[data-group="rates"]:active { cursor: grabbing; }
  .control-card.card-dragging { opacity: 0.4; }
  .control-card.card-drag-over { outline: 2px dashed var(--blue); outline-offset: 3px; border-radius: 10px; }
  .control-card.hidden-card { display: none; }
  .card-label {
    font-size: 10px;
    font-weight: 600;
    color: #fff;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    pointer-events: none;
  }
  .hidden-tray-sep {
    height: 1px;
    background: var(--border2);
    margin: 2px 0;
  }
  .hidden-tray-section {
    font-size: 8px;
    font-weight: 600;
    letter-spacing: 0.08em;
    color: var(--hint);
    text-transform: uppercase;
    padding: 2px 4px;
  }

  .module-label {
    font-size: 12px;
    font-weight: 600;
    color: #fff;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    pointer-events: none;
  }
  .module-hide-btn {
    width: 28px; height: 28px;
    border-radius: 50%;
    border: 1px solid rgba(255,255,255,0.3);
    background: rgba(255,255,255,0.1);
    color: #fff;
    font-size: 14px;
    cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    transition: background 0.15s;
    flex-shrink: 0;
  }
  .module-hide-btn:hover { background: rgba(248,113,113,0.6); }
  .module-arrow-btn {
    width: 28px; height: 28px;
    border-radius: 50%;
    border: 1px solid rgba(255,255,255,0.3);
    background: rgba(255,255,255,0.1);
    color: #fff;
    font-size: 12px;
    cursor: pointer;
    display: none;
    align-items: center; justify-content: center;
    transition: background 0.15s;
    flex-shrink: 0;
  }
  .module-arrow-btn:hover { background: rgba(255,255,255,0.2); }

  /* ── Hidden modules tray ── */
  .hidden-tray {
    display: none;
    position: fixed;
    top: 60px;
    right: 20px;
    z-index: 99;
    background: var(--surface);
    border: 1px solid var(--border2);
    border-radius: 12px;
    padding: 10px;
    min-width: 180px;
    flex-direction: column;
    gap: 6px;
  }
  .hidden-tray.visible { display: flex; }
  .hidden-tray-title {
    font-size: 9px;
    font-weight: 600;
    letter-spacing: 0.08em;
    color: var(--hint);
    text-transform: uppercase;
    padding: 2px 4px;
    margin-bottom: 2px;
  }
  .hidden-tray-item {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 6px 10px;
    border-radius: 6px;
    background: var(--surface2);
    font-size: 11px;
    color: var(--muted);
    font-weight: 500;
  }
  .hidden-tray-add {
    font-size: 16px;
    color: var(--green);
    cursor: pointer;
    line-height: 1;
    background: none;
    border: none;
    padding: 0;
  }
  .hidden-tray-add:hover { color: #fff; }
  .tray-reset {
    margin-top: 4px;
    padding: 5px 10px;
    border-radius: 6px;
    border: 1px solid var(--border2);
    background: transparent;
    color: var(--hint);
    font-size: 10px;
    font-weight: 600;
    cursor: pointer;
    letter-spacing: 0.05em;
    font-family: var(--sans);
    width: 100%;
    text-align: center;
  }
  .tray-reset:hover { border-color: var(--red); color: var(--red); }

  @media (max-width: 700px) {
    .module-arrow-btn { display: flex; }
    body.customise-mode .module { cursor: default; }
    body.controls-customise-mode .module { cursor: default; }
    .gear-btn      { position: fixed; top: 14px; right: 12px; width: 30px; height: 30px; }
    .theme-btn     { position: fixed; top: 52px; right: 12px; width: 30px; height: 30px; font-size: 14px; }
    .customise-btn { position: fixed; top: 90px; right: 12px; width: 30px; height: 30px; font-size: 14px; }
    .medal-btn     { position: fixed; top: 90px; right: 12px; width: 30px; height: 30px; font-size: 14px; }
    .theme-popup   { top: 52px; right: 50px; }
    .tier-btn      { width: 26px; height: 26px; }

    /* Mobile: tray becomes a collapsed pill at bottom, expands on tap */
    .hidden-tray {
      top: auto;
      bottom: 60px;
      right: 12px;
      left: 12px;
      min-width: unset;
      max-height: 60px;
      overflow: hidden;
      transition: max-height 0.25s ease, padding 0.25s ease;
      padding: 0;
    }
    .hidden-tray.tray-expanded {
      max-height: 70vh;
      overflow-y: auto;
      padding: 10px;
    }
    .tray-pill {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 10px 14px;
      cursor: pointer;
      font-size: 11px;
      font-weight: 600;
      color: var(--muted);
      letter-spacing: 0.04em;
      user-select: none;
    }
    .tray-pill-arrow { transition: transform 0.2s; font-size: 10px; }
    .hidden-tray.tray-expanded .tray-pill-arrow { transform: rotate(180deg); }
    .tray-pill-count { color: var(--blue); }
    /* Hide full content until expanded */
    .hidden-tray:not(.tray-expanded) .hidden-tray-item,
    .hidden-tray:not(.tray-expanded) .hidden-tray-section,
    .hidden-tray:not(.tray-expanded) .hidden-tray-sep,
    .hidden-tray:not(.tray-expanded) .tray-reset { display: none; }
  }
  @media (min-width: 701px) {
    .tray-pill { display: none; }
  }

  /* ── Page tabs ── */
  .page-tabs {
    display: flex;
    justify-content: safe center;   /* #227: 5 tabs — centred when they fit, scrollable if not */
    gap: 3px;
    margin-bottom: 1.2rem;
    /* #234: overflow MUST stay visible — the 4 flex:1 tabs always shrink to fit
       (never overflow horizontally), and `overflow-x: auto` would force
       `overflow-y: auto`, which clips the re-tap HOP as it lifts up (looked like
       the header was cutting it off). Visible + the nav z-index lift in _hopTab
       lets the hop rise above the sticky header cleanly. */
    overflow: visible;
    scrollbar-width: none;
    /* symmetric gutters reserve the top-right chrome column (gear/theme/pencil)
       AND keep the equal-width tab group centred — the tabs shrink a little but
       never run under the pencil (small-phone right:12 and bigger-phone
       right:calc both clear). */
    padding: 0 46px;
  }
  .page-tabs::-webkit-scrollbar { display: none; }
  .page-tab {
    font-family: var(--sans);
    font-size: 12px;
    font-weight: 500;
    letter-spacing: 0.03em;
    padding: 6px 8px;
    border-radius: 20px;
    border: 1px solid var(--border2);
    background: transparent;
    color: var(--muted);
    cursor: pointer;
    transition: all 0.15s;
    white-space: nowrap;
    flex: 1;                 /* #234: all tabs equal width */
    min-width: 0;
    text-align: center;
  }
  .page-tab:hover { color: var(--text); border-color: var(--hint); }
  .page-tab.active { background: var(--blue-dim); border-color: var(--blue); color: var(--blue); }
  /* All three tabs share the same max content width (1100px) centred on
     the viewport. This mirrors what Edge Seller originally did and the
     user asked all tabs to match. body still has padding:2rem, so on very
     wide displays there's clearance on both sides of the 1100-cap. */
  .page-content {
    display: none;
    width: 100%;
    box-sizing: border-box;
  }
  .page-content.active {
    display: block;
    animation: page-fade-in 0.18s ease;
  }
  @keyframes page-fade-in {
    from { opacity: 0; transform: translateX(12px); }
    to   { opacity: 1; transform: translateX(0); }
  }

/* ── Edge Seller ── */
  /* ── Edge Seller ── */
  .seller-layout {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    /* Width cap now lives on .page-content (shared across all three tabs)
       so Edge Seller inherits it instead of duplicating the constraint. */
  }
  .seller-col { display: flex; flex-direction: column; gap: 16px; min-width: 0; }
  .seller-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 12px;
    padding: 1.25rem;
    min-width: 0;
    overflow: hidden;
  }
  .seller-card-title {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    color: var(--muted);
    margin-bottom: 14px;
  }
  .seller-input-row {
    display: flex;
    gap: 8px;
    align-items: center;
    margin-bottom: 10px;
  }
  .seller-input {
    flex: 1;
    background: var(--surface2);
    border: 1px solid var(--border2);
    border-radius: 8px;
    color: var(--text);
    font-family: var(--sans);
    font-size: 13px;
    padding: 9px 12px;
    outline: none;
    transition: border-color 0.15s;
  }
  .seller-input:focus { border-color: var(--blue); }
  .seller-input::placeholder { color: var(--hint); }
  .seller-textarea {
    width: 100%;
    min-height: 140px;
    resize: vertical;
    background: var(--surface2);
    border: 1px solid var(--border2);
    border-radius: 8px;
    color: var(--text);
    font-family: var(--sans);
    font-size: 13px;
    padding: 9px 12px;
    outline: none;
    transition: border-color 0.15s;
    margin-bottom: 10px;
    box-sizing: border-box;
  }
  .seller-textarea:focus { border-color: var(--blue); }
  .seller-textarea::placeholder { color: var(--hint); }
  .ebay-input {
    box-shadow: 0 0 0 1px #1e3a5f, 0 0 8px rgba(96,165,250,0.18);
    border-color: #2a4a6b !important;
  }
  .ebay-input:focus {
    box-shadow: 0 0 0 1px var(--blue), 0 0 12px rgba(96,165,250,0.3);
    border-color: var(--blue) !important;
  }
  .seller-btn {
    padding: 9px 18px;
    border-radius: 8px;
    border: 1px solid var(--border2);
    background: var(--surface2);
    color: var(--muted);
    font-family: var(--sans);
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    transition: all 0.15s;
    white-space: nowrap;
  }
  .seller-btn:hover { border-color: var(--hint); color: var(--text); }
  .seller-btn:focus,
  .seller-btn:focus-visible { outline: none; box-shadow: none; }
  /* "Import all" — green action; FIXED width + centred so the two-tap "Confirm?"
     (amber) never resizes the button. */
  #ebay-import-all-btn {
    border-color: var(--green); color: var(--green);
    font-size: 12px; padding: 4px 10px;
    min-width: 96px; justify-content: center; text-align: center;
  }
  #ebay-import-all-btn:hover { border-color: var(--green); color: var(--green); }
  #ebay-import-all-btn.pending-apply { border-color: var(--amber); color: var(--amber); background: var(--amber-dim); }
  .seller-btn { -webkit-tap-highlight-color: transparent; }
  .seller-btn:disabled { opacity: 0.4; pointer-events: none; }
  .seller-btn[data-disabled] {
    opacity: 0.4;
    pointer-events: none;
    /* Reset all colour variants so all three buttons look identical when disabled */
    background: var(--surface2) !important;
    border-color: var(--border2) !important;
    color: var(--muted) !important;
    filter: none !important;
  }

  .seller-btn.primary { background: var(--blue-dim); border-color: var(--blue); color: var(--blue); }
  .seller-btn.primary:hover { filter: brightness(1.2); }
  .seller-btn.success { background: var(--green-dim); border-color: var(--green); color: var(--green); }
  .seller-btn.success:hover { filter: brightness(1.2); }
  .seller-btn.danger  { background: var(--surface2); border-color: var(--red); color: var(--red); }
  .seller-btn.danger:hover  { filter: brightness(1.2); }
  .seller-btn.loading { opacity: 0.5; pointer-events: none; }
  .seller-label {
    font-size: 11px;
    color: var(--muted);
    margin-bottom: 5px;
    display: block;
  }
  .seller-field { margin-bottom: 12px; }
  /* Ensure inputs (and the composite fees-row widget) inside a .seller-field
     all fill the field's full width on desktop, so that when .seller-field
     items sit in a CSS grid (e.g. the profit calculator's 1fr 1fr grid),
     every cell renders at the same visible width. Without this, plain
     <input> elements sit at their intrinsic default width (~150px) while
     the fees-row flex wrapper stretches to fill the grid cell, making the
     fees field look conspicuously longer than Bought / List Price / Shipping. */
  .seller-field > .seller-input,
  .seller-field > .seller-fees-row {
    width: 100%;
    box-sizing: border-box;
  }
  /* card image — single flipable container */
  .psa-images {
    margin-top: 10px;
    display: flex;
    justify-content: center;
  }
  .psa-img-wrap {
    width: 100%;
    max-height: 320px;
    aspect-ratio: 3/4;
    background: var(--surface2);
    border: 1px solid var(--border2);
    border-radius: 8px;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--hint);
    font-size: 11px;
    cursor: pointer;
    position: relative;
    box-sizing: border-box;
  }
  .psa-img-wrap img { width: 100%; height: 100%; object-fit: contain; }
  .psa-img-flip-hint {
    position: absolute;
    bottom: 6px;
    right: 6px;
    background: rgba(0,0,0,0.55);
    color: #fff;
    font-size: 9px;
    font-family: var(--mono);
    padding: 2px 5px;
    border-radius: 4px;
    pointer-events: none;
  }
  /* User-image upload (no-PSA-image fallback). Replaces the "no image"
     placeholder inside .psa-img-wrap with a 2-slot grid (front + back).
     Each slot is a label that wraps a hidden file input — taps on the slot
     trigger the picker on every browser. accept="image/*" capture="environment"
     (set by the JS) makes mobile offer Camera + Photos; desktop is unchanged. */
  .psa-upload-hint {
    position: absolute;
    top: 8px;
    left: 0;
    right: 0;
    text-align: center;
    color: var(--hint);
    font-size: 10px;
    font-family: var(--mono);
    padding: 0 8px;
    pointer-events: none;
  }
  .psa-upload-slots {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    width: 100%;
    height: 100%;
    padding: 28px 10px 10px; /* top inset for the hint */
    box-sizing: border-box;
  }
  .psa-upload-slot {
    position: relative;
    display: block;
    background: var(--surface);
    border: 1.5px dashed var(--border2);
    border-radius: 6px;
    cursor: pointer;
    overflow: hidden;
    -webkit-tap-highlight-color: transparent;
    transition: border-color 120ms, background 120ms;
  }
  .psa-upload-slot:hover, .psa-upload-slot:active {
    border-color: var(--blue);
    background: var(--surface2);
  }
  /* The <input> stays in the DOM (so the label wrapper triggers it) but is
     visually hidden. Don't use display:none — Safari mobile suppresses the
     picker for some hidden inputs. opacity:0 + zero-size + absolute keeps
     it focusable and clickable through the wrapper. */
  .psa-upload-input {
    position: absolute;
    width: 1px; height: 1px;
    opacity: 0;
    pointer-events: none;
  }
  .psa-upload-slot-inner {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 4px;
    color: var(--muted);
  }
  .psa-upload-slot-icon {
    font-size: 22px;
    line-height: 1;
    color: var(--hint);
  }
  .psa-upload-slot-label {
    font-size: 10px;
    font-family: var(--mono);
    text-transform: uppercase;
    letter-spacing: 0.05em;
  }
  .psa-upload-slot-label-done {
    position: absolute;
    bottom: 4px;
    left: 0; right: 0;
    text-align: center;
    background: rgba(0,0,0,0.55);
    color: #fff;
    padding: 2px 0;
  }
  .psa-upload-thumb {
    width: 100%;
    height: 100%;
    object-fit: contain;
  }
  /* PSA detail rows */
  .psa-details {
    display: flex;
    flex-direction: column;
    margin-top: 10px;
    width: 100%;
    box-sizing: border-box;
    min-width: 0;
  }
  .psa-detail-row {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    font-size: 12px;
    padding: 5px 0;
    border-bottom: 0.5px solid var(--border);
    font-family: var(--mono);
    width: 100%;
    box-sizing: border-box;
    gap: 8px;
    min-width: 0;
  }
  .psa-detail-key { color: var(--muted); white-space: nowrap; flex-shrink: 0; }
  .psa-detail-val { color: var(--text); text-align: right; min-width: 0; word-break: break-word; white-space: normal; }
  /* price suggestion */
  .price-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
    margin-bottom: 12px;
  }
  .price-stat {
    background: var(--surface2);
    border-radius: 8px;
    padding: 10px 12px;
  }
  .price-stat-label { font-size: 10px; color: var(--muted); margin-bottom: 3px; }
  .price-stat-value { font-size: 17px; font-weight: 600; color: var(--text); letter-spacing: -0.01em; font-family: var(--mono); }
  .price-stat-value.green { color: var(--green); }
  .price-stat-value.blue  { color: var(--blue); }
  /* auth badge */
  .ebay-auth-badge {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    padding: 5px 12px;
    border-radius: 20px;
    font-size: 11px;
    font-weight: 500;
    cursor: pointer;
    text-decoration: none;
    transition: opacity 0.15s;
  }
  .ebay-auth-badge.authorised   { background: var(--green-dim); color: var(--green); border: 1px solid var(--green-dim); }
  .ebay-auth-badge.unauthorised { background: var(--surface2);  color: var(--red);   border: 1px solid var(--border2);   }
  .ebay-auth-badge:hover { opacity: 0.8; }

  /* Scraper health pill — blends with surface across all themes */
  .scraper-pill {
    background: var(--surface2);
    border-color: var(--border2);
    color: var(--muted);
  }

  /* PSA usage pill — blue tint across all themes */
  .psa-pill {
    background: var(--blue-dim);
    border-color: var(--border2);
    color: var(--blue);
  }

  /* Light theme button hover — use a tinted background instead of dark text */
  html.theme-light .margins-apply:hover,
  body.theme-light .margins-apply:hover {
    background: var(--blue-dim);
    border-color: var(--blue);
    color: var(--blue);
  }
  html.theme-light .settings-edit-btn:hover,
  body.theme-light .settings-edit-btn:hover {
    background: var(--blue-dim);
    border-color: var(--blue);
    color: var(--blue);
  }
  html.theme-light .customise-btn:hover,
  body.theme-light .customise-btn:hover,
  html.theme-light .theme-btn:hover,
  body.theme-light .theme-btn:hover {
    background: var(--blue-dim);
    border-color: var(--blue);
    color: var(--blue);
  }
  .seller-top-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-bottom: 1.5rem;
    /* No max-width/auto-margin so it spans the same page width as the
       seller-layout below and matches Dashboard/Controls. */
  }
  .seller-page-title {
    font-size: 14px;
    font-weight: 500;
    color: var(--text);
    letter-spacing: -0.01em;
  }
  .seller-status-msg {
    font-size: 12px;
    color: var(--muted);
    font-family: var(--mono);
    margin-top: 8px;
    min-height: 18px;
    transition: color 0.2s;
  }
  .seller-status-msg.ok    { color: var(--green); }
  .seller-status-msg.err   { color: var(--red); }
  .seller-status-msg.info  { color: var(--blue); }
  .seller-divider { height: 1px; background: var(--border); margin: 10px 0; }
  @media (max-width: 800px) {
    .seller-layout { grid-template-columns: 1fr; }
    .seller-top-bar { flex-wrap: wrap; gap: 8px; }
    .seller-input-row { flex-wrap: wrap; }
    .seller-input-row .seller-input { min-width: 0; }
    .seller-input { width: 100%; box-sizing: border-box; }
    #page-edgeseller { overflow-x: hidden; max-width: 100%; }
  }
/* ============================================================================
   SCORING WIDGETS (Bin Bot Scoring + Bid Bot Scoring on the Controls tab)
   ============================================================================
   Each rule renders as a collapsible "bubble": tap the header to expand,
   tap again to collapse. Expanded bodies reveal the tap-pill and any
   range inputs. On mobile everything is centred and inflated for thumbs.
   Changes are local-only until Apply.
   ---------------------------------------------------------------------------- */

  /* Controls tab layout.
     Since the merge, Controls holds exactly two top-level modules:
     Settings and Scoring. Each module handles its own internal card layout
     (Settings has a 2-col .controls-grid; Scoring has a 2-col .controls-grid
     with Bin/Bid cards that can be reordered via Scoring's own ✎ pencil).
     At the page level we just need vertical spacing between the two modules
     — a simple block flow is plenty. */
  #controls-modules-root {
    display: block;
  }
  #controls-modules-root > .module + .module {
    margin-top: 32px;
  }
  @media (max-width: 700px) {
    #controls-modules-root > .module + .module {
      margin-top: 36px;
    }
  }

  .scoring-body {
    padding-top: 8px;
  }

  /* --- Rule row bubble (collapsed or expanded) --- */
  .scoring-rule-row {
    position: relative;
    margin-bottom: 8px;
    background: var(--surface);
    border: 1px solid var(--border2);
    border-radius: 10px;
    /* Body uses its own padding; header is a <button> with its own padding.
       Keeping the wrapper padding-less lets the header cover the full tap
       area cleanly. */
  }

  /* Collapsed header is a full-width tap target. On touch this feels like
     an iOS settings row — big hit zone, no visual clutter, clear chevron. */
  .scoring-row-header {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 12px 30px 12px 14px;   /* right pad clears × button */
    border: none;
    background: transparent;
    color: var(--text);
    text-align: left;
    cursor: pointer;
    font: inherit;
    border-radius: 10px;
    -webkit-tap-highlight-color: transparent;
  }
  .scoring-row-header:hover { background: var(--surface2); }
  .scoring-row-header:focus { outline: none; }
  .scoring-row-header:focus-visible { outline: 2px solid var(--blue); outline-offset: -2px; }

  /* When expanded, the header keeps its look but the chevron rotates. */
  .scoring-expanded .scoring-row-header {
    border-bottom: 1px solid var(--border2);
    border-radius: 10px 10px 0 0;
  }
  .scoring-expanded .scoring-row-header:hover { background: transparent; }

  .scoring-rule-label {
    flex: 1;
    min-width: 0;
    font-size: 13px;
    color: var(--text);
    line-height: 1.35;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* Summary shows the current points (and range, if relevant) in the
     collapsed state so the user doesn't have to expand to see what a rule
     is doing. */
  .scoring-row-summary {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    flex: 0 0 auto;
    font-family: var(--mono);
    font-size: 12px;
    color: var(--hint);
  }
  .scoring-summary-range {
    color: var(--hint);
    opacity: 0.9;
  }
  .scoring-summary-points {
    color: var(--blue);
    font-weight: 500;
  }

  .scoring-row-chevron {
    flex: 0 0 auto;
    font-size: 14px;
    color: var(--muted);
    transition: transform 0.15s ease;
    line-height: 1;
    width: 14px;
    text-align: center;
  }
  .scoring-expanded .scoring-row-chevron { transform: rotate(180deg); }

  /* Body holds the tap-pill + range inputs. Only present when expanded. */
  .scoring-row-body {
    padding: 10px 14px 14px;
  }

  /* --- × remove button — exact GST styling. No hover color change on
         purpose: tapping should feel low-stakes; the row only actually
         disappears on the user's next Apply. --- */
  .scoring-remove-btn {
    position: absolute;
    top: -2px;
    right: -2px;
    width: 16px;
    height: 16px;
    line-height: 14px;
    padding: 0;
    border: 1px solid var(--border2);
    border-radius: 50%;
    background: var(--surface2);
    color: var(--muted);
    font-size: 12px;
    cursor: pointer;
    z-index: 3;
  }

  /* --- Range row (min – max unit) for sweet-spot rules + price bands --- */
  .scoring-range-row {
    display: flex;
    align-items: center;
    gap: 6px;
    margin-bottom: 10px;
    flex-wrap: wrap;
  }
  .scoring-range-input {
    width: 64px;
    padding: 6px 6px;
    background: var(--surface2);
    color: var(--text);
    border: 1px solid var(--border2);
    border-radius: 6px;
    font-family: var(--mono);
    font-size: 13px;
    text-align: center;
    -moz-appearance: textfield;
  }
  .scoring-range-input::-webkit-outer-spin-button,
  .scoring-range-input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
  }
  .scoring-range-input:focus { outline: none; border-color: var(--blue); }
  .scoring-range-dash { color: var(--hint); font-size: 12px; }
  .scoring-range-unit {
    color: var(--hint);
    font-size: 11px;
    letter-spacing: 0.02em;
  }

  /* --- Points pill (tap-to-adjust) --- */
  .scoring-points-row {
    display: flex;
    align-items: center;
    gap: 10px;
    /* Allow the label ("POINTS") to drop below the pill on very narrow
       viewports rather than forcing a min-content width wider than the
       card (which would push the whole scoring module past the viewport). */
    flex-wrap: wrap;
  }
  .scoring-points-label {
    color: var(--hint);
    font-size: 11px;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    flex: 0 0 auto;
  }

  .tap-pill {
    display: inline-flex;
    align-items: stretch;
    position: relative;
    border: 1px solid var(--border2);
    border-radius: 999px;
    background: var(--surface2);
    overflow: hidden;
    flex: 0 0 auto;
    user-select: none;
  }
  .tap-pill-zone {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 36px;
    min-height: 32px;
    padding: 0;
    border: none;
    background: transparent;
    color: var(--muted);
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
    transition: background 0.12s, color 0.12s;
  }
  .tap-pill-zone:hover     { background: var(--surface); color: var(--text); }
  .tap-pill-zone:active    { background: var(--blue-dim); color: var(--blue); }
  .tap-pill-chevron {
    font-size: 18px;
    line-height: 1;
    font-weight: 400;
    pointer-events: none;
  }
  .tap-pill-input {
    width: 44px;
    padding: 4px 0;
    border: none;
    border-left: 1px solid var(--border2);
    border-right: 1px solid var(--border2);
    background: var(--surface);
    color: var(--text);
    text-align: center;
    font-family: var(--mono);
    font-size: 13px;
    font-weight: 500;
    -moz-appearance: textfield;
  }
  .tap-pill-input::-webkit-outer-spin-button,
  .tap-pill-input::-webkit-inner-spin-button {
    -webkit-appearance: none;
    margin: 0;
  }
  .tap-pill-input:focus {
    outline: none;
    background: var(--surface2);
    box-shadow: inset 0 0 0 1px var(--blue);
  }

  /* --- Bands header (label above price-band rows, no divider so it reads
         as part of the same stack of bubbles) --- */
  .scoring-bands-header {
    font-size: 11px;
    color: var(--hint);
    margin: 10px 0 8px;
    letter-spacing: 0.02em;
  }

  /* --- + Add rule button + dropdown popup --- */
  .scoring-add-btn {
    width: 100%;
    background: var(--surface) !important;
    color: var(--text) !important;
    border: 1px dashed var(--border2) !important;
  }
  .scoring-add-btn:hover {
    border-color: var(--blue) !important;
    color: var(--blue) !important;
  }
  .scoring-add-popup {
    position: absolute;
    top: calc(100% + 4px);
    left: 0;
    right: 0;
    max-height: 240px;
    overflow-y: auto;
    background: var(--surface2);
    border: 1px solid var(--border2);
    border-radius: 8px;
    z-index: 50;
    padding: 4px;
    box-shadow: 0 6px 14px rgba(0,0,0,0.35);
  }
  /* Dropdown-row pattern — shared across scoring's "+ Add rule" popup and
     GST's "+ Add country" popup so the two feel like one component. Rows
     have a subtle border2-tone hover background, consistent padding, and
     the mobile media query below inflates them for thumbs.
     Both popups insert rows programmatically; each row gets the class
     .scoring-add-popup-row or .gst-add-popup-row (the alias lets future
     dropdowns opt in without duplicating rules). */
  .scoring-add-popup-row,
  .gst-add-popup-row {
    padding: 8px 10px;
    font-size: 12px;
    color: var(--text);
    cursor: pointer;
    border-radius: 4px;
    transition: background 0.1s;
    -webkit-tap-highlight-color: transparent;
  }
  .scoring-add-popup-row:hover,
  .gst-add-popup-row:hover { background: var(--border2); }
  .gst-add-popup-row {
    /* GST rows list country code + name in monospace, unlike scoring rows. */
    font-family: var(--mono);
  }
  .scoring-add-popup-empty {
    padding: 8px 10px;
    font-size: 12px;
    color: var(--hint);
  }
  @media (max-width: 700px) {
    .scoring-add-popup-row,
    .scoring-add-popup-empty {
      padding: 12px 10px;
      font-size: 13px;
    }
    /* gst-add-popup-row (currency, date format) stays left-aligned and
       compact on mobile — same as desktop for visual uniformity */
    .gst-add-popup-row {
      text-align: left;
      padding: 8px 10px;
      font-size: 12px;
    }
  }

  /* --- Footer: readout on top, then threshold + Apply. No top divider so
         the widget reads as one continuous flow from rules → + Add rule →
         readout → Apply. --- */
  .scoring-footer {
    margin-top: 14px;
  }
  .scoring-readout {
    font-size: 11px;
    color: var(--hint);
    line-height: 1.5;
  }
  .scoring-readout strong {
    color: var(--text);
    font-weight: 500;
  }

  /* --- Threshold row: "Pass if score ≥ [pill]   [Apply]" ---
         Sits below the readout in the footer. The pill (tap-adjust control
         for the threshold) is inserted into `.scoring-threshold-slot` by JS
         on first init. Layout mirrors the pattern used inside rule bodies
         (tap-pill + spacer + button) so the widget feels consistent. */
  .scoring-threshold-row {
    display: flex;
    align-items: center;
    gap: 10px;
    margin-top: 8px;
    flex-wrap: wrap;
  }
  .scoring-threshold-label {
    flex: 0 0 auto;
    font-size: 11px;
    color: var(--hint);
    letter-spacing: 0.04em;
    text-transform: uppercase;
    margin: 0;
  }
  .scoring-threshold-slot {
    flex: 0 0 auto;
    display: inline-flex;
  }

  /* --- Mobile tuning: centre the body, widen inputs, enlarge tap targets.
         The module wrapper itself remains full-width; we centre the inner
         controls so the widget reads as "bubbles stacked down the middle"
         on narrow screens rather than stretched-and-cramped. --- */
  @media (max-width: 700px) {
    /* Centre all the scoring internals inside a single 420px column so the
       "+ Add rule" button, rule rows, and footer visually align. Individual
       children no longer need their own max-width. */
    .scoring-body {
      max-width: 420px;
      margin: 0 auto;
      /* Belt-and-braces: scoring bodies sometimes receive content with
         intrinsic min-width (e.g. the tap-pill when all 3 elements can't
         shrink). Width 100% of the parent + box-sizing ensures we never
         exceed the card's usable width. */
      width: 100%;
      box-sizing: border-box;
    }
    .scoring-row-header {
      padding: 14px 32px 14px 16px;   /* taller tap target */
      gap: 12px;
    }
    .scoring-rule-label { font-size: 14px; }
    .scoring-row-summary { font-size: 13px; }
    .scoring-row-body { padding: 12px 16px 16px; }
    .scoring-range-input {
      width: 72px;
      font-size: 14px;
      padding: 8px 6px;
    }
    .tap-pill-zone { width: 48px; min-height: 38px; }
    .tap-pill-input { width: 56px; font-size: 15px; padding: 6px 0; }
    .tap-pill-chevron { font-size: 20px; }
    .scoring-readout { text-align: center; }

    /* Scoring module's own ⟲ popup is wider (240px) and anchors right:0
       on the reset button. On narrow phones that can push the popup off
       the left edge. Shrink + cap to the viewport so it always fits. */
    #widget-reset-popup-scoring {
      min-width: 0;
      width: max-content;
      max-width: calc(100vw - 32px);
    }
  }
/* ============================================================================
   LEDGER STYLES
   Reuses design tokens already in the dashboard (--surface, --border, etc.)
   and the visual language of .scoring-rule-row so the collapsible row pattern
   feels consistent across tabs.
   ============================================================================ */

  /* "+ Add to Ledger" button on the Edge Seller tab. Distinct teal/cyan
     accent so it doesn't read as the primary CTA (which is Create Listing
     in blue) but is clearly clickable on hover. */
  .seller-btn.ledger-add-btn {
    background: rgba(45, 212, 191, 0.10);
    border-color: rgba(45, 212, 191, 0.55);
    color: #5eead4;
    font-weight: 500;
  }
  .seller-btn.ledger-add-btn:hover {
    background: rgba(45, 212, 191, 0.18);
    border-color: rgba(45, 212, 191, 0.85);
    color: #99f6e4;
  }
  .seller-btn.ledger-add-btn:active {
    background: rgba(45, 212, 191, 0.25);
  }

  /* Toolbar: filter chips on the left, P/L summary on the right.
     Wraps on narrow screens so summary drops below filters. */
  /* ── Re-tap Ledger tab → Analytics (Champion) ─────────────────────────── */
  /* A little dock-style hop on the header tab when re-tapped. */
  @keyframes tabHop {
    0%, 100% { transform: translateY(0); }
    35%      { transform: translateY(-6px); }
    70%      { transform: translateY(-2px); }
  }
  /* #234: bring the hopping tab to the front so it isn't clipped by the sticky
     header (z-index 90) / logo as it lifts up. */
  .page-tab.tab-hop { animation: tabHop 0.45s cubic-bezier(0.28, 0.84, 0.42, 1); position: relative; z-index: 95; }
  /* Tiny "tap again" hint that fades in/out under the Ledger tab — same quiet
     treatment as the lookup "Enter a card name first…" note (no pill/box). */
  #ledger-retap-hint {
    position: fixed;
    z-index: 95;   /* below the theme popup (101), floating buttons (100), modals */
    transform: translate(-50%, 0);
    font-family: var(--mono);
    font-size: 9.5px;
    letter-spacing: 0.05em;
    color: var(--hint);
    white-space: nowrap;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.4s;
  }
  #ledger-retap-hint.show { opacity: 1; }

  /* ── Analytics view ───────────────────────────────────────────────────── */
  .la-range { display: flex; gap: 6px; margin-bottom: 14px; justify-content: center; }   /* #236: centred on mobile (desktop reverts to flex-start below) */
  .la-range-btn {
    font-family: var(--mono); font-size: 11px; font-weight: 600;
    letter-spacing: 0.03em; padding: 5px 12px; border-radius: 7px;
    border: 1px solid var(--border2); background: transparent;
    color: var(--hint); cursor: pointer; transition: all 0.15s;
    -webkit-tap-highlight-color: transparent;
  }
  .la-range-btn:hover { color: var(--text); border-color: var(--hint); }
  .la-range-btn.active { background: var(--blue-dim); border-color: var(--blue); color: var(--blue); }

  .la-hero {
    background: var(--surface); border: 1px solid var(--border);
    border-radius: 14px; padding: 18px 18px 10px; margin-bottom: 12px;
  }
  .la-hero-label {
    font-family: var(--mono); font-size: 10px; letter-spacing: 0.08em;
    text-transform: uppercase; color: var(--hint); margin-bottom: 6px;
  }
  .la-hero-value { font-family: var(--mono); font-size: 30px; font-weight: 800; letter-spacing: -0.02em; line-height: 1.1; }
  .la-hero-sub   { font-family: var(--mono); font-size: 12px; color: var(--muted); margin-top: 4px; }
  .la-chart { width: 100%; height: 120px; display: block; margin-top: 12px; cursor: pointer; }
  /* #portfolio: the two supporting charts stack on mobile, side-by-side on desktop. */
  .la-charts { display: flex; flex-direction: column; }
  .la-chart-col { min-width: 0; }
  /* Reserve the hero heights so their async loads can't shift the layout. All
     three charts are the SAME height now (default .la-chart). */
  #la-portfolio { display: block; min-height: 258px; }
  #la-equity { display: block; min-height: 258px; }
  /* #equity: one switchable chart view at a time. */
  .la-chart-view { min-width: 0; }
  /* Mobile: date pills (top) + chart-view toggle (below), each full-width & equal,
     matching the dashboard most-listed toggle style. Desktop overrides to one row. */
  .la-controls { display: flex; flex-direction: column; gap: 8px; margin-bottom: 12px; }
  .la-controls .alltime-tabs, .la-controls .alltime-view-btns { width: 100%; }
  .la-controls .alltime-tab, .la-controls .alltime-view-btn { flex: 1; text-align: center; }
  .eq-key { display: inline-block; width: 9px; height: 9px; border-radius: 2px; vertical-align: middle; }
  .eq-key-banked { background: rgba(34,197,94,0.85); }
  .eq-key-paper  { background: rgba(34,197,94,0.32); }
  .eq-key { display: inline-block; width: 9px; height: 9px; border-radius: 2px; vertical-align: middle; }
  .eq-key-banked { background: rgba(34,197,94,0.85); }
  .eq-key-paper  { background: rgba(34,197,94,0.32); }
  /* Tapped-point detail line under the chart (date · card · this flip's profit). */
  .la-point {
    display: flex; align-items: center; justify-content: space-between; gap: 10px;
    font-family: var(--mono); font-size: 11px;
    background: var(--surface2); border-radius: 8px; padding: 7px 10px; margin-top: 8px;
  }
  .la-point-left {
    color: var(--muted); min-width: 0; white-space: nowrap;
    overflow-x: auto; overflow-y: hidden; scrollbar-width: none; -ms-overflow-style: none;
  }
  .la-point-left::-webkit-scrollbar { display: none; }
  .la-point-val { flex: 0 0 auto; font-weight: 700; }

  .la-tiles { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 12px; }
  .la-tile  { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px; }
  .la-tile-label { font-family: var(--mono); font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--hint); margin-bottom: 5px; }
  .la-tile-val   { font-family: var(--mono); font-size: 18px; font-weight: 700; color: var(--text); }

  .la-bw { display: grid; grid-template-columns: repeat(2, 1fr); gap: 8px; margin-bottom: 12px; }
  .la-bw-card  { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px; min-width: 0; }
  .la-bw-label { font-family: var(--mono); font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--hint); margin-bottom: 6px; }
  /* Best/worst names scroll horizontally when long (same as the watchlist name). */
  .la-bw-name  { font-size: 13px; font-weight: 600; color: var(--text); white-space: nowrap; overflow-x: auto; overflow-y: hidden; text-overflow: unset; scrollbar-width: none; -ms-overflow-style: none; margin-bottom: 4px; }
  .la-bw-name::-webkit-scrollbar { display: none; }
  .la-bw-val   { font-family: var(--mono); font-size: 15px; font-weight: 700; display: flex; align-items: baseline; gap: 7px; }
  .la-bw-roi   { font-size: 11px; font-weight: 600; opacity: 0.75; }

  .la-unrealised { font-family: var(--mono); font-size: 12px; color: var(--muted); text-align: center; padding: 4px 0; }
  .la-unrealised .pos, .la-unrealised .neg { font-weight: 600; }

  /* Shared pos/neg colouring within the analytics view */
  /* ── #128: analytics widget cards ── */
  .la-grid { display: grid; grid-template-columns: 1fr; gap: 10px; margin-bottom: 12px; }
  .la-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 12px 14px; min-width: 0; }
  .la-card-title { font-family: var(--mono); font-size: 10px; letter-spacing: .06em; text-transform: uppercase; color: var(--hint); margin-bottom: 8px; }
  .la-li { display: grid; grid-template-columns: minmax(0,1fr) auto; column-gap: 10px; align-items: baseline; padding: 4px 0; font-size: 12.5px; }
  .la-li-name { color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; grid-column: 1; }
  .la-li-sub { grid-column: 1; font-size: 10px; color: var(--hint); font-family: var(--mono); }
  .la-li-val { grid-column: 2; grid-row: 1 / span 2; font-family: var(--mono); text-align: right; white-space: nowrap; }
  .la-li-roi { font-size: 10px; color: var(--muted); }
  /* Tappable analytics row (e.g. a stale listing → opens it in the ledger). */
  .la-li-tap { cursor: pointer; margin: 0 -8px; padding: 4px 8px; border-radius: 8px; transition: background 0.12s; -webkit-tap-highlight-color: transparent; }
  .la-li-tap:hover, .la-li-tap:active { background: var(--surface2); }
  /* #255 Tax / COGS report card */
  .tax-sub { font-size: 11.5px; color: var(--hint); line-height: 1.45; margin-bottom: 10px; }
  .tax-controls { display: flex; gap: 8px; margin-bottom: 10px; }
  /* Region + year pickers reuse the Display-Currency dropdown pattern (button +
     .gst-add-popup-row popup) so Settings and Analytics stay visually consistent. */
  .tax-pick-wrap { position: relative; flex: 1; min-width: 0; }
  /* #308: fixed height + line-height so the country (with a flag emoji) and year
     pickers are exactly the same height. */
  .tax-pick-btn { width: 100%; box-sizing: border-box; height: 36px; line-height: 1; text-align: left; display: flex; justify-content: space-between; align-items: center; gap: 8px; }
  .tax-pick-btn > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .tax-pick-caret { font-size: 10px; color: var(--muted); flex: none; }
  .tax-pick-popup { display: none; position: absolute; top: calc(100% + 4px); left: 0; right: 0; max-height: 220px; overflow-y: auto; background: var(--surface2); border: 1px solid var(--border2); border-radius: 6px; z-index: 50; padding: 4px; }
  .tax-summary { margin-bottom: 12px; transition: opacity .28s ease; }
  .tax-row { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 5px 0; font-size: 13px; border-bottom: 1px solid var(--border); }
  .tax-row:last-of-type { border-bottom: 0; }
  .tax-row-l { color: var(--muted); }
  .tax-row-v { font-family: var(--mono); text-align: right; white-space: nowrap; color: var(--text); }
  .tax-row-v.pos { color: var(--green); }
  .tax-row-v.neg { color: var(--red); }
  .tax-meta { font-family: var(--mono); font-size: 10px; color: var(--hint); margin-top: 8px; letter-spacing: .03em; }
  .tax-warn { font-size: 11px; color: var(--amber, #f0a500); margin-top: 8px; line-height: 1.4; }
  .tax-empty { font-size: 12.5px; color: var(--hint); padding: 8px 0; }
  .tax-dl { width: 100%; }
  /* #12 Replay-tutorial control in the Bolt settings card title row */
  .bolt-replay-btn { background: none; border: 0; color: var(--blue); font-family: var(--sans); font-size: 11.5px; font-weight: 600; cursor: pointer; padding: 2px 4px; border-radius: 6px; white-space: nowrap; transition: color .15s, background .15s; }
  .bolt-replay-btn:hover { background: var(--blue-dim); }
  .la-months { display: flex; align-items: flex-end; gap: 4px; height: 86px; }
  .la-mon { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 3px; min-width: 0; height: 100%; }
  .la-mon-wrap { flex: 1; width: 100%; display: flex; align-items: flex-end; }
  .la-mon-bar { width: 100%; border-radius: 3px 3px 0 0; }
  .la-mon-bar.pos { background: var(--green); }
  .la-mon-bar.neg { background: var(--red); }
  .la-mon-l { font-size: 8px; color: var(--hint); font-family: var(--mono); letter-spacing: -0.02em; }
  .la-mon { cursor: pointer; }
  .la-months .la-mon-bar { opacity: .55; transition: opacity .15s, filter .15s; }
  .la-mon:hover .la-mon-bar { opacity: .85; }
  .la-mon.sel .la-mon-bar { opacity: 1; filter: brightness(1.2); }
  .la-mon.sel .la-mon-l { color: var(--text); }
  .la-mon-detail { display: flex; align-items: baseline; gap: 8px; margin-top: 8px; font-family: var(--mono); font-size: 12px; color: var(--muted); min-height: 16px; }
  .la-mon-detail-val { font-weight: 700; }
  /* ROI by grade — ladder language: pill · magnitude bar · value */
  /* #151: FIXED left column — each row is its own grid, so an auto column
     sized per-row made the rails start/end ragged (PSA 10's wide sub-line
     shortened its bar). 112px fits "9 flips · $2,140 in"; longer ellipsizes. */
  /* #159: fixed grade + value columns so every ROI bar (the 1fr track) is the
     SAME length regardless of the profit magnitude's text width. The sub-line
     spans the full width on its own row (#158) — no more ellipsis clip. */
  .la-gr { display: grid; grid-template-columns: 112px minmax(24px,1fr) 88px; column-gap: 12px; align-items: center; padding: 6px 0; }
  .la-gr-left { min-width: 0; }
  /* Only the sub-line is explicitly placed (row 2, full width); pill/track/val
     auto-flow into row 1 in DOM order — don't pin them or auto-placement of the
     un-pinned track breaks the column order. */
  .la-gr-sub { grid-column: 1 / -1; font-size: 9.5px; color: var(--hint); font-family: var(--mono); margin-top: 3px; }
  .la-gr-raw { font-family: var(--mono); font-size: 10.5px; font-weight: 600; color: var(--muted); }
  .la-gr-val { font-family: var(--mono); font-size: 12.5px; text-align: right; white-space: nowrap; }
  .la-gr-val .la-li-roi { margin-top: 2px; }

  /* By-Pokémon tiles: ledger card art is portrait — same back-face fit as the
     dashboard flip */
  .la-poke-grid { grid-template-columns: repeat(3, 1fr); }
  .la-poke .at-card-count.pos { color: var(--green); }
  .la-poke .at-card-count.neg { color: var(--red); }
  #ledger-analytics-view .pos, #ledger-analytics-view .la-hero-value.pos, #ledger-analytics-view .la-tile-val.pos, #ledger-analytics-view .la-bw-val.pos { color: var(--green); }
  #ledger-analytics-view .neg, #ledger-analytics-view .la-hero-value.neg, #ledger-analytics-view .la-tile-val.neg, #ledger-analytics-view .la-bw-val.neg { color: var(--red); }
  #ledger-analytics-view .la-muted { color: var(--muted); }

  .la-empty { text-align: center; color: var(--muted); padding: 44px 20px; font-size: 14px; }
  .la-empty-sub { font-size: 12px; color: var(--hint); margin-top: 6px; }
  .la-locked { text-align: center; padding: 48px 24px; }
  .la-locked-icon  { font-size: 28px; margin-bottom: 10px; }
  .la-locked-title { font-size: 16px; font-weight: 700; color: var(--text); margin-bottom: 8px; }
  .la-locked-sub   { font-size: 13px; color: var(--muted); line-height: 1.6; max-width: 340px; margin: 0 auto; }

  .ledger-toolbar {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-bottom: 16px;
    width: 100%;
    box-sizing: border-box;
  }
  /* Universal order: summary → filters → io-row (same on mobile and desktop) */
  .ledger-summary  { order: 1; width: 100%; justify-content: space-between; }
  .ledger-filters  { order: 2; width: 100%; gap: 6px; flex-wrap: nowrap; }
  .ledger-filters .ledger-filter {
    flex: 1 1 0;
    min-width: 0;
    justify-content: center;
    padding: 6px 6px;
    font-size: 11px;
  }
  .ledger-io-row   {
    order: 3;
    width: 100%;
    gap: 6px;
    flex-wrap: nowrap;
  }
  .ledger-sort-wrap { flex: 1 1 0; min-width: 0; }
  .ledger-date-wrap { flex: 1 1 0; min-width: 0; }
  .ledger-sort-btn,
  .ledger-date-btn  { width: 100%; justify-content: space-between; padding: 6px 8px; min-width: 0; overflow: hidden; }
  .ledger-filters {
    display: flex;
    gap: 6px;
    flex-wrap: nowrap;
    width: 100%;
    box-sizing: border-box;
  }
  .ledger-filters .ledger-filter {
    flex: 1 1 0;
    min-width: 0;
    justify-content: center;
    padding: 6px 6px;
    font-size: 11px;
  }
  .ledger-filter {
    background: var(--surface);
    border: 1px solid var(--border2);
    color: var(--muted);
    border-radius: 999px;
    padding: 6px 12px;
    font: inherit;
    font-size: 12px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: 4px;
    white-space: nowrap;
    overflow: hidden;
    transition: all 0.15s;
    -webkit-tap-highlight-color: transparent;
  }
  .ledger-filter:hover { border-color: var(--hint); color: var(--text); }
  .ledger-filter.active {
    background: var(--blue-dim);
    border-color: var(--blue);
    color: var(--blue);
  }
  .ledger-filter-count {
    font-family: var(--mono);
    font-size: 9px;
    background: var(--surface2);
    border-radius: 6px;
    padding: 1px 4px;
    color: var(--hint);
    flex: 0 0 auto;
  }
  .ledger-filter.active .ledger-filter-count {
    background: rgba(96,165,250,0.15);
    color: var(--blue);
  }

  /* ── Sort + IO row ───────────────────────────────────────────────────
     Wraps the sort chip and the Export/Import cluster. On desktop, this
     container is the rightmost item in .ledger-toolbar (after filters
     and summary), keeping all three clusters on a single row. On mobile,
     the toolbar wraps and this container takes a full-width row of its
     own with the sort chip on the left and IO buttons on the right. */
  .ledger-io-row {
    display: grid;
    grid-template-columns: 1fr 1fr auto;
    gap: 8px;
    align-items: center;
    width: 100%;
    box-sizing: border-box;
  }
  /* Sort and date fill available space evenly on all screen sizes */
  .ledger-sort-wrap { min-width: 0; position: relative; }
  .ledger-date-wrap { min-width: 0; position: relative; }

  /* ── Sort selector ───────────────────────────────────────────────────
     Single chip-style trigger that opens a small popup. The button
     reuses .ledger-filter so it inherits the same pill shape and hover
     rules as the status filters (visual cohesion); we add a direction
     arrow (▼/▲) showing the current sort direction, plus a caret showing
     it's a dropdown. The wrap is a relative positioning context so the
     popup can anchor below the button. */
  /* .ledger-sort-wrap position defined in .ledger-io-row block above */
  .ledger-sort-btn,
  .ledger-date-btn { width: 100%; justify-content: space-between; }
  .ledger-sort-btn .ledger-sort-label { display: inline-flex; gap: 4px; }
  .ledger-sort-btn .ledger-sort-label > span { color: var(--text); }
  .ledger-sort-btn .ledger-sort-arrow {
    font-size: 9px;
    margin-left: 4px;
    color: var(--blue);     /* matches the .active-state colour family */
    line-height: 1;
  }
  .ledger-sort-btn .ledger-sort-caret {
    font-size: 9px;
    margin-left: 4px;
    color: var(--muted);
    transition: transform 0.15s;
  }
  .ledger-sort-btn[aria-expanded="true"] .ledger-sort-caret { transform: rotate(180deg); }

  .ledger-sort-popup {
    position: absolute;
    top: calc(100% + 6px);
    left: 0;
    z-index: 50;
    display: none;
    flex-direction: column;
    min-width: 200px;
    background: var(--surface);
    border: 1px solid var(--border2);
    border-radius: 10px;
    padding: 6px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
  }
  .ledger-sort-popup.open { display: flex; }
  .ledger-sort-option {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    width: 100%;
    padding: 8px 10px;
    margin: 0;
    border-radius: 6px;
    border: 1px solid transparent;
    background: transparent;
    color: var(--text);
    font-family: var(--sans);
    font-size: 12px;
    text-align: left;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
    -webkit-appearance: none;
    appearance: none;
    -webkit-tap-highlight-color: transparent;
  }
  .ledger-sort-option + .ledger-sort-option { margin-top: 2px; }
  .ledger-sort-option:hover { background: var(--surface2); border-color: var(--border2); }
  .ledger-sort-option.active {
    background: var(--blue-dim);
    border-color: var(--blue);
    color: var(--blue);
  }
  /* Per-option direction arrow. Filled in by JS with "▼" / "▲" on the
     active option; blank on inactive ones. Tapping the active option
     flips the arrow (and the sort direction); tapping an inactive
     option switches to it without changing direction. The hint after
     the arrow tells the user that tapping again will flip — only shown
     on the active option. */
  .ledger-sort-option-arrow {
    font-size: 10px;
    color: var(--muted);
    min-width: 10px;
    text-align: right;
    line-height: 1;
  }
  .ledger-sort-option.active .ledger-sort-option-arrow { color: var(--blue); }

  /* ── Ledger date filter chip + popup ────────────────────────────────
     Mirrors the sort chip pattern: a pill that shows the active range,
     opens a popup with preset options + a custom date-range expander.
     The popup absolutely-positions below the chip; .ledger-date-wrap
     is the relatively-positioned anchor. */
  /* .ledger-date-wrap position defined in .ledger-io-row block above */
  .ledger-date-btn .ledger-date-label { display: inline-flex; gap: 4px; }
  .ledger-date-btn .ledger-date-label > span { color: var(--text); }
  .ledger-date-btn .ledger-date-caret {
    margin-left: 4px;
    font-size: 9px;
    color: var(--muted);
    transition: transform 0.15s;
  }
  .ledger-date-btn[aria-expanded="true"] .ledger-date-caret { transform: rotate(180deg); }
  /* Anchor popup to the right edge of the wrap rather than the left. On
     desktop the date chip lives in the rightmost cluster of the toolbar,
     so a left-anchored popup grows toward (and past) the viewport's right
     edge — especially when the Custom range form expands the popup width.
     Right-anchoring keeps the popup on-screen at all widths. On mobile
     the chip sits in a centered row of equal-width slots, and the popup
     (~200-230px) fits comfortably on either side, so this anchor is also
     fine there. */
  .ledger-date-popup {
    position: absolute;
    top: calc(100% + 6px);
    right: 0;
    z-index: 50;
    display: none;
    flex-direction: column;
    min-width: 200px;
    background: var(--surface);
    border: 1px solid var(--border2);
    border-radius: 10px;
    padding: 6px;
    box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
  }
  .ledger-date-popup.open { display: flex; }
  .ledger-date-option {
    display: flex;
    align-items: center;
    width: 100%;
    padding: 8px 10px;
    margin: 0;
    border-radius: 6px;
    border: 1px solid transparent;
    background: transparent;
    color: var(--text);
    font-family: var(--sans);
    font-size: 12px;
    text-align: left;
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
    -webkit-appearance: none;
    appearance: none;
    -webkit-tap-highlight-color: transparent;
  }
  .ledger-date-option + .ledger-date-option { margin-top: 2px; }
  @media (hover: hover) {
    .ledger-date-option:hover { background: var(--surface2); border-color: var(--border2); }
  }
  .ledger-date-option.active {
    background: var(--blue-dim);
    border-color: var(--blue);
    color: var(--blue);
  }
  /* Custom range mini-form. Hidden until the user picks "Custom…"; two
     date inputs stacked vertically with an Apply button below. */
  .ledger-date-custom {
    flex-direction: column;
    gap: 6px;
    padding: 8px 4px 4px;
    border-top: 1px solid var(--border2);
    margin-top: 4px;
  }
  .ledger-date-custom[style*="display: flex"],
  .ledger-date-custom[style*="display:flex"] {
    /* Allow inline display:flex via the toggle; default is display:none. */
  }
  .ledger-date-custom-row {
    display: flex;
    align-items: center;
    gap: 8px;
    font-size: 11px;
    color: var(--muted);
  }
  /* Fixed-width label so the input column starts at the same x position
     on both rows. Without this, "From" (4 chars) and "To" (2 chars) push
     their inputs to slightly different left edges, which reads as a
     misalignment even though the inputs are the same width. */
  .ledger-date-custom-row > span {
    flex: 0 0 36px;
  }
  .ledger-date-input {
    background: var(--surface2);
    border: 1px solid var(--border2);
    color: var(--text);
    border-radius: 6px;
    padding: 5px 8px;
    font: inherit;
    font-size: 12px;
    flex: 1 1 auto;
    min-width: 0;
  }
  .ledger-date-apply-btn {
    background: var(--blue);
    color: #fff;
    border: 1px solid var(--blue);
    border-radius: 6px;
    padding: 6px 10px;
    font: inherit;
    font-size: 12px;
    font-weight: 500;
    cursor: pointer;
    margin-top: 2px;
    -webkit-tap-highlight-color: transparent;
  }

  .ledger-summary {
    width: 100%;
    box-sizing: border-box;
    justify-content: space-between;
    display: flex;
    gap: 14px;
    font-family: var(--mono);
    font-size: 12px;
    overflow-x: auto;
    scrollbar-width: none;
    -ms-overflow-style: none;
  }
  .ledger-summary::-webkit-scrollbar { display: none; }
  /* Terminal-trader ticker: the summary scrolls slowly & loops; finger-draggable. */
  .ledger-summary.lt-viewport {
    display: block; overflow: hidden; white-space: nowrap; gap: 0;
    cursor: grab; touch-action: pan-y; user-select: none; -webkit-user-select: none;
  }
  .ledger-summary.lt-viewport.lt-grabbing { cursor: grabbing; }
  /* When the line FITS (not scrolling), centre it; while scrolling it must start
     at the left so the translateX loop is seamless. */
  .ledger-summary.lt-viewport:not(.lt-scrolling) { text-align: center; }
  .lt-track { display: inline-flex; align-items: center; will-change: transform; }
  .lt-item { display: inline-flex; align-items: baseline; gap: 6px; padding: 0 16px; white-space: nowrap; }
  .lt-label { font-size: 10px; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase; color: var(--muted); }
  .lt-val { font-family: var(--mono); font-size: 13px; font-weight: 700; color: var(--text); }
  .ledger-summary-item {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    color: var(--text);
    white-space: nowrap;
  }
  .ledger-summary-label {
    font-size: 10px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--muted);
    white-space: nowrap;
  }

  /* CSV import / export controls. Sits in the ledger toolbar alongside the
     filter chips and the summary. Pill-shape matches .ledger-filter so the
     two clusters read as the same family of controls. The arrow icon scales
     with the text. */
  .ledger-io {
    display: flex;
    gap: 6px;
    flex-wrap: wrap;
  }
  /* Import/Export labels only show on desktop (where the buttons are widened to
     fill the column, #84); icon-only on mobile. */
  .ledger-io-label { display: none; font-size: 12px; }
  .ledger-io-btn {
    background: var(--surface);
    border: 1px solid var(--border2);
    color: var(--muted);
    border-radius: 999px;
    /* Icon-only buttons — equal padding all sides keeps them visually
       balanced as squircle pills rather than wide rounded rectangles. */
    padding: 6px 10px;
    font: inherit;
    font-size: 12px;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: 6px;
    transition: all 0.15s;
    -webkit-tap-highlight-color: transparent;
  }
  .ledger-io-btn:hover { border-color: var(--hint); color: var(--text); }
  .ledger-io-btn:focus,
  .ledger-io-btn:focus-visible { outline: none; }
  .ledger-io-btn svg { display: block; flex: 0 0 auto; }

  /* ── Ledger select mode — sprite overlay circle ─────────────────────────
     A circular overlay sits on top of the pokéball sprite area. In normal
     mode it's invisible. In select mode it shows as an empty ring;
     on selected rows it fills with a blue checkmark. */
  .ledger-select-overlay {
    display: none;
    position: absolute;
    inset: 0;
    border-radius: 6px;
    z-index: 4;
    align-items: center;
    justify-content: center;
    background: transparent;
    transition: background 0.15s;
  }
  /* Ring — only the ::after pseudo is visible, no container background */
  .ledger-select-overlay::after {
    content: "";
    width: 20px;
    height: 20px;
    border-radius: 50%;
    border: 2px solid rgba(255,255,255,0.7);
    background: rgba(0,0,0,0.15);
    transition: all 0.15s;
    display: block;
    box-shadow: 0 0 0 1px rgba(0,0,0,0.12);
  }
  .ledger-list.select-mode .ledger-select-overlay { display: flex; }
  /* Selected: filled blue circle with checkmark */
  .ledger-list.select-mode .ledger-row.selected .ledger-select-overlay::after {
    background: var(--blue);
    border-color: var(--blue);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
    background-size: 12px 12px;
    background-repeat: no-repeat;
    background-position: center;
    box-shadow: none;
  }
  /* Dim chevron in select mode — not needed for navigation */
  .ledger-list.select-mode .ledger-row-chevron { opacity: 0.25; transition: opacity 0.15s; }
  /* Selected row border highlight */
  .ledger-list.select-mode .ledger-row.selected { border-color: var(--blue); }

  /* ── Ledger bottom action sheet ──────────────────────────────────────────
     Fixed to the bottom of the viewport. Slides up when .open is added,
     slides back down when removed. Only shown when ledger tab is active. */
  .ledger-action-sheet {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 200;
    transform: translateY(100%);
    transition: transform 0.28s cubic-bezier(0.32, 0.72, 0, 1);
    pointer-events: none;
  }
  .ledger-action-sheet.open {
    transform: translateY(0);
    pointer-events: auto;
  }
  .ledger-action-sheet-inner {
    background: var(--surface2);
    border-top: 1px solid var(--border2);
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
    display: flex;
    flex-direction: column;
    gap: 10px;
  }
  .ledger-action-sheet-top {
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .ledger-action-sheet-count {
    font-size: 12px;
    font-family: var(--mono);
    color: var(--muted);
    flex: 1;
  }
  .ledger-action-sheet-select-all {
    font-size: 12px;
    font-family: var(--mono);
    background: none;
    border: none;
    cursor: pointer;
    padding: 2px 0;
    transition: color 0.2s, opacity 0.15s;
    flex-shrink: 0;
    /* colour set dynamically in JS: blue for Select All, red for Deselect All */
    color: var(--blue);
  }
  .ledger-action-sheet-select-all:hover { opacity: 0.75; }
  .ledger-action-sheet-cancel {
    font-size: 12px;
    font-family: var(--mono);
    color: var(--muted);
    background: none;
    border: none;
    cursor: pointer;
    padding: 2px 0;
    transition: color 0.15s;
    flex-shrink: 0;
  }
  .ledger-action-sheet-cancel:hover { color: var(--text); }
  .ledger-action-sheet-btns {
    display: flex;
    flex-direction: column;
    gap: 6px;
  }
  /* Single row when 3 buttons, two rows of 2 when 4 buttons */
  .ledger-action-sheet-btns[data-rows="1"],
  .ledger-action-sheet-btns[data-rows="2"] .ledger-sheet-row {
    display: flex;
    gap: 6px;
  }
  .ledger-sheet-row {
    display: flex;
    gap: 6px;
  }
  .ledger-sheet-btn {
    flex: 1 1 0;
    min-width: 0;
    font-size: 13px;
    font-family: var(--sans);
    font-weight: 500;
    padding: 10px 8px;
    border-radius: 8px;
    border: 1px solid var(--border2);
    background: var(--surface);
    color: var(--text);
    cursor: pointer;
    transition: all 0.15s;
    white-space: nowrap;
    text-align: center;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .ledger-sheet-btn:hover   { border-color: var(--blue); color: var(--blue); }
  .ledger-sheet-btn:disabled { opacity: 0.35; cursor: not-allowed; }
  .ledger-sheet-btn.danger  { color: var(--red); border-color: rgba(239,68,68,0.35); }
  .ledger-sheet-btn.danger:hover { background: var(--red); color: #fff; border-color: var(--red); }
  /* export btn matches other sheet buttons — no special colour */
  /* On mobile give buttons more breathing room */
  @media (max-width: 700px) {
    .ledger-sheet-btn { padding: 12px 10px; font-size: 12px; }
  }

  /* Import-mode picker inside the ledger modal. Three stacked buttons,
     each with a bold mode name and a description in muted colour underneath.
     Buttons take full width so the descriptions get room to breathe. */
  .ledger-import-modes {
    display: flex;
    flex-direction: column;
    gap: 8px;
    margin-top: 4px;
  }
  .ledger-import-modes .seller-btn {
    width: 100%;
    display: flex;              /* without this the title + description run
                                   together inline — they must stack */
    flex-direction: column;
    align-items: flex-start;
    gap: 7px;                   /* breathing room between title and description */
    padding: 12px 14px;
    text-align: left;
    white-space: normal;        /* allow descriptions to wrap */
  }
  .ledger-import-modes .seller-btn strong {
    font-weight: 650;
    font-size: 13.5px;
    line-height: 1.2;
    letter-spacing: 0.1px;
    color: var(--text);         /* full-strength so the title leads the eye */
  }
  .ledger-import-modes .seller-btn span {
    font-size: 11px;
    color: var(--muted);
    line-height: 1.45;
    font-weight: 400;
  }
  /* Highlight the danger one a touch more emphatically so users register
     the destructive option before clicking. */
  .ledger-import-modes .seller-btn.danger strong { color: var(--red); }

  .ledger-list {
    display: flex;
    flex-direction: column;
    gap: 8px;
    -webkit-user-select: none;
    user-select: none;
  }
  /* padding-bottom set dynamically in JS to match actual sheet height */

  /* Virtualized ledger (large lists): the flex gap is replaced by per-row bottom
     margins so the top/bottom spacer divs map EXACTLY to scroll height (a gap
     between every flex item — including the spacers — would throw the math off). */
  .ledger-list.lv-on { gap: 0; }
  .ledger-list.lv-on > .ledger-row { margin-bottom: 8px; }
  .ledger-list.lv-on > .lv-spacer { flex: 0 0 auto; width: 100%; pointer-events: none; }

  /* --- Row (collapsed or expanded). Modelled on .scoring-rule-row. --- */
  .ledger-row {
    background: var(--surface);
    border: 1px solid var(--border2);
    border-radius: 10px;
    /* overflow:hidden clips the status-stripe pseudo-element to follow the
       parent's rounded corners — without it, the stripe's flat top/bottom
       edges stick out past the rounded outer border. CardEdge uses the
       same rule and works correctly on iOS Safari; an earlier Surge build
       removed this thinking it caused touch-event issues, but the real
       culprit there was an unrelated layout bug. */
    overflow: hidden;
    /* background included so the desktop master-detail selection FADES between
       rows on arrow-key nav instead of flashing (#106 polish). */
    transition: border-color 0.15s, background-color 0.15s;
  }
  /* #57: undo toast — bottom-centred, rides above everything */
  #undo-toast {
    position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%) translateY(12px);
    display: flex; align-items: center; gap: 14px;
    background: var(--surface2); border: 1px solid var(--border2); border-radius: 10px;
    padding: 10px 14px; font-size: 12.5px; font-family: var(--mono); color: var(--text);
    box-shadow: 0 8px 24px rgba(0,0,0,.4); z-index: 200;
    opacity: 0; pointer-events: none; transition: opacity .2s, transform .2s;
    max-width: min(420px, calc(100vw - 28px));
  }
  #undo-toast.show { opacity: 1; pointer-events: auto; transform: translateX(-50%) translateY(0); }
  #undo-toast span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  #undo-toast button {
    background: none; border: none; color: var(--blue); font-weight: 700;
    font-family: var(--mono); font-size: 12.5px; cursor: pointer; padding: 2px 4px;
  }

  /* #57: CSV-backup nudge — one quiet line above the ledger list */
  #export-nudge {
    display: flex; align-items: center; gap: 8px;
    font-size: 11px; font-family: var(--mono); color: var(--hint);
    padding: 6px 8px; margin-bottom: 8px;
    background: var(--surface2); border: 1px dashed var(--border2); border-radius: 8px;
  }
  #export-nudge span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .export-nudge-btn {
    background: none; border: 1px solid var(--border2); border-radius: 6px;
    color: var(--blue); font-size: 11px; font-family: var(--mono); padding: 2px 9px; cursor: pointer;
  }
  .export-nudge-btn:hover { border-color: var(--blue); }
  .export-nudge-x { background: none; border: none; color: var(--hint); cursor: pointer; font-size: 12px; padding: 2px 4px; }

  /* #125: just-added row confirmation pulse (mobile) — the row glows twice
     then settles. Scroll-to + flash beats auto-expanding the edit panel. */
  .ledger-row-flash { animation: ledger-row-flash 1.1s ease-in-out 2; }
  @keyframes ledger-row-flash {
    0%, 100% { border-color: var(--border2); box-shadow: none; }
    50%      { border-color: var(--blue); box-shadow: 0 0 0 1px var(--blue); }
  }
  /* Expanded rows previously forced a blue border regardless of status,
     which made a Sold row look "active" the moment you tapped it. Now
     the border picks up the row's status colour so the whole rounded
     rectangle reads the same as the left-edge stripe. */
  .ledger-row.expanded[data-status="active"]   { border-color: var(--blue); }
  .ledger-row.expanded[data-status="sold"]     { border-color: var(--green); }
  .ledger-row.expanded[data-status="archived"] { border-color: var(--muted); }
  /* Fallback for rows whose data-status hasn't loaded yet (shouldn't
     normally happen — every row is rendered with the attribute set). */
  .ledger-row.expanded { border-color: var(--blue); }

  /* Status accent stripe on the left edge — at-a-glance state without
     reading the badge. Active=blue, Sold=green, Archived=muted.
     Using ::before instead of box-shadow because inset shadows interact
     unpredictably with border-radius + overflow:hidden when the border
     colour transitions (expanded ↔ collapsed). The pseudo-element is
     unaffected by the border transition and always visible. */
  .ledger-row { position: relative; }
  .ledger-row::before {
    content: "";
    position: absolute;
    left: 0; top: 0; bottom: 0;
    width: 3px;
    border-radius: 10px 0 0 10px;
  }
  .ledger-row[data-status="active"]::before   { background: var(--blue); }
  .ledger-row[data-status="sold"]::before     { background: var(--green); }
  .ledger-row[data-status="archived"]::before { background: var(--muted); }
  .ledger-row[data-status="archived"] { opacity: 0.78; }

  /* Header is a button: full-width tap target on mobile. */
  .ledger-row-header {
    display: grid;
    grid-template-columns: 44px 1fr auto auto;
    gap: 10px;
    align-items: center;
    width: 100%;
    padding: 10px 12px 10px 14px;
    border: none;
    background: transparent;
    color: var(--text);
    text-align: left;
    cursor: pointer;
    font: inherit;
    -webkit-tap-highlight-color: transparent;
  }
  .ledger-row-header:hover { background: var(--surface2); }
  .ledger-row.expanded .ledger-row-header {
    border-bottom: 1px solid var(--border2);
  }
  .ledger-row.expanded .ledger-row-header:hover { background: transparent; }

  /* Sprite container: 44×44 box with pixelated rendering for the BW
     animated GIFs from PokéAPI. Letter-fallback shows when no sprite
     resolves (e.g. trainer cards). */
  .ledger-row-sprite {
    width: 44px;
    height: 44px;
    border-radius: 6px;
    background: transparent;
    display: flex;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    flex-shrink: 0;
    position: relative;
  }
  .ledger-row-sprite img {
    width: 100%;
    height: 100%;
    object-fit: contain;
    image-rendering: pixelated;
    image-rendering: -moz-crisp-edges;
  }
  /* Canvas from silhouette effect — match container size exactly */
  .ledger-row-sprite .poke-scanner-canvas {
    width: 44px !important;
    height: 44px !important;
  }
  /* Pokéball fallback: shown by default (sits behind the sprite img),
     hidden by JS when a real sprite resolves. SVG fills the sprite tile
     proportionally. Slight opacity so it reads as a placeholder rather
     than the primary subject of the row. */
  .ledger-row-sprite-fallback {
    width: 100%;
    height: 100%;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  /* Hide sprite and fallback in select mode — circle reads cleanly on plain bg.
     Placed after base rules to ensure cascade wins. The silhouette canvas is
     covered here too (it can be (re)rendered AFTER select mode is entered, e.g.
     when the virtualized list re-renders), so a CSS rule beats the timing. */
  .ledger-list.select-mode .ledger-row .ledger-row-sprite img,
  .ledger-list.select-mode .ledger-row .ledger-row-sprite canvas,
  .ledger-list.select-mode .ledger-row .poke-scanner-canvas,
  .ledger-list.select-mode .ledger-row .ledger-row-sprite .ledger-row-sprite-fallback {
    opacity: 0 !important;
    transition: opacity 0.15s;
  }
  .sprite-fallback-svg {
    width: 80%;
    height: 80%;
  }

  /* Title + subtitle (cert#, date) stacked. min-width:0 lets ellipsis work. */
  .ledger-row-main {
    min-width: 0;
    display: flex;
    flex-direction: column;
    gap: 2px;
  }
  .ledger-row-title {
    font-size: 13px;
    color: var(--text);
    line-height: 1.3;
    overflow-x: auto;
    overflow-y: hidden;
    white-space: nowrap;
    text-overflow: unset;
    scrollbar-width: none;
    -ms-overflow-style: none;
  }
  .ledger-row-title::-webkit-scrollbar { display: none; }
  .ledger-row-meta {
    font-family: var(--mono);
    font-size: 11px;
    color: var(--hint);
    /* #129: scroll, don't clip — the full date · cert · lang line stays
       reachable on narrow rows (scrollbar hidden, swipe to see the tail) */
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
    white-space: nowrap;
  }
  .ledger-row-meta::-webkit-scrollbar { display: none; }
  /* #132: horizontal-scroll affordance — JS adds .hscroll-more to any
     hidden-scrollbar scroller whose content overflows. A sticky ellipsis
     pins to the visible right edge; the user's first scroll removes it
     (they've found the scroll, the hint has done its job). */
  /* Ellipsis on whichever side has hidden content. The glyph inherits the
     TEXT's own colour; the fade is painted in the EXACT surface the row
     sits on (--hs-fade, overridden per context below) so it dissolves into
     the background instead of reading as a box. */
  /* Horizontal-scroll affordance: a clean edge FADE, no ellipsis glyph. The
     .hscroll-more / .hscroll-back classes are toggled by scroll position (JS), so
     the cut content fades to transparent only on the side(s) actually cut off. A
     content mask = nothing to colour-match, no readable sliver, consistent app-wide. */
  .hscroll-more {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent);
            mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent);
  }
  .hscroll-back {
    -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 26px), transparent);
            mask-image: linear-gradient(to left, #000 calc(100% - 26px), transparent);
  }
  .hscroll-more.hscroll-back {
    -webkit-mask-image: linear-gradient(to right, transparent, #000 26px, #000 calc(100% - 26px), transparent);
            mask-image: linear-gradient(to right, transparent, #000 26px, #000 calc(100% - 26px), transparent);
  }

  /* P/L pill — colour-coded green/red/grey. Mono font keeps numbers aligned. */
  .ledger-row-pnl {
    font-family: var(--mono);
    font-size: 12px;
    font-weight: 600;
    text-align: right;
    line-height: 1.2;
    flex-shrink: 0;
  }
  .ledger-row-pnl-amount.pos { color: var(--green); }
  .ledger-row-pnl-amount.neg { color: var(--red); }
  .ledger-row-pnl-amount.neutral { color: var(--muted); }
  /* Projected — active rows show profit/ROI projected from list price.
     Blue distinguishes "this is a forecast, don't bank it" from realised
     P/L (green/red). Only the dollar amount goes blue; the small "X.X% ROI"
     line below stays in the standard muted hint colour, same as sold rows.
     The dollar amount is enough to signal "this is projected" — duplicating
     the colour on the ROI line makes both lines compete for attention and
     reads visually heavier than the green/red sold rows. */
  .ledger-row-pnl-amount.proj { color: var(--blue); }
  .ledger-row-pnl-roi {
    font-size: 10px;
    color: var(--hint);
    font-weight: 400;
  }

  .ledger-row-chevron {
    font-size: 14px;
    color: var(--muted);
    transition: transform 0.15s ease;
    width: 14px;
    text-align: center;
    flex-shrink: 0;
  }
  .ledger-row.expanded .ledger-row-chevron { transform: rotate(180deg); }

  /* #pf-rows: per-row live market value on ACTIVE rows (champion).
     Sits under the meta line; the projected (list-based) P/L stays on the right,
     so the gap between the two reads as "your ask vs the market". */
  .lr-mkt { font-family: var(--mono); font-size: 11px; display: flex; align-items: center; gap: 6px; margin-top: 2px; white-space: nowrap; overflow-x: auto; overflow-y: hidden; scrollbar-width: none; -ms-overflow-style: none; }
  .lr-mkt::-webkit-scrollbar { display: none; }
  .lr-mkt > * { flex: 0 0 auto; }
  .lr-mkt-lbl { font-size: 8.5px; font-weight: 700; letter-spacing: 0.06em; color: var(--hint); background: var(--surface2); padding: 1px 4px; border-radius: 4px; }
  /* Amount slots share a fixed min-width + right-align so the columns line up down
     the list: the label is a uniform "MKT" on every row (active + sold), so fixing
     the value width makes the connector + LIST/SOLD column land at the same x on
     every row, in every toggle. Kept tight (just enough for a typical value + the
     arrow's own fixed slot); a value with more digits simply grows its slot (the
     rare bigger-number row is the only one that extends) — nothing clips. */
  .lr-mkt-val,
  .lr-mkt-soft { min-width: 52px; box-sizing: border-box; }
  .lr-mkt-val { color: var(--text); font-weight: 600; }
  .lr-mkt-sep { color: var(--hint); }
  /* The MKT→LIST connector: ▲/▼/· all share one fixed, centred slot so the row
     never shifts whether or not a card moved. */
  .lr-mkt-sep,
  .lr-mkt-delta { min-width: 9px; text-align: center; font-size: 9px; }
  .lr-mkt-delta.up { color: var(--green); }
  .lr-mkt-delta.down { color: var(--red); }
  .lr-mkt-delta.none { color: var(--hint); }
  .lr-mkt-soft { color: var(--hint); font-style: italic; }

  /* --- Expanded body --- */
  /* Body is ALWAYS in the DOM (just hidden when collapsed) so toggle is
     instantaneous and there's no render race when switching tabs. The
     previous render-on-toggle approach left the body stale across page
     switches. */
  .ledger-row-body {
    -webkit-user-select: text;
    user-select: text;
    padding: 10px 12px 12px;
    display: none;
    flex-direction: column;
    gap: 10px;
  }
  .ledger-row.expanded .ledger-row-body {
    display: flex;
  }
  .ledger-row-body-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
  }

  /* #121: grade pill — compact mono chip. Read-only at the start of list-row
     titles; a tappable button (with the per-company dropdown picker) in the
     detail body head. The pill clusters left with the status badge (the
     auto margin absorbs the flex free space before space-between can). */
  .ledger-grade-pill {
    display: inline-block;
    font-family: var(--mono); font-size: 10.5px; font-weight: 600; line-height: 1.5;
    color: var(--muted); background: var(--surface2);
    border: 1px solid var(--border2); border-radius: 6px;
    padding: 1px 6px; white-space: nowrap;
  }
  /* #178/#14: fixed-width grade pill so every row's card name starts at the same
     x — even with a wide "BGS 10BL" pill, and ungraded rows reserve the slot. */
  .ledger-row-title .ledger-grade-pill { margin-right: 7px; vertical-align: 1px; min-width: 68px; text-align: center; box-sizing: border-box; }
  .ledger-row-title .ledger-grade-pill-spacer { border-color: transparent; background: transparent; box-shadow: none; }
  .at-lb-name .ledger-grade-pill { margin-right: 8px; vertical-align: 1px; }
  .la-bw-name .ledger-grade-pill { margin-right: 8px; vertical-align: 1px; }
  /* #144: watchlist pill lives on the META line (title gets the full width);
     the chevron is decoration on mobile — the whole header is the tap target */
  .wl-meta-pill .ledger-grade-pill { margin-right: 7px; vertical-align: 1px; font-size: 9.5px; padding: 0px 5px; }
  @media (max-width: 1023px) {
    .ledger-row-chevron { display: none; }
  }
  .ledger-grade-wrap { position: relative; margin-right: auto; }
  button.ledger-grade-pill { cursor: pointer; }
  .ledger-grade-pill-tap:hover { border-color: var(--blue); color: var(--blue); }
  .ledger-grade-pill-empty { border-style: dashed; color: var(--hint); }

  /* Widget-style edit panel: visually mirrors .ledger-breakdown so the
     dropdown reads as one cohesive card. Each row is a label on the left
     with a tight, right-aligned input on the right. The wide rows (title,
     notes) span the full width with the input below the label so long
     text isn't squeezed. */
  .ledger-edit-panel {
    background: var(--surface2);
    border-radius: 8px;
    padding: 6px 10px;
    font-family: var(--mono);
    font-size: 11.5px;
    display: flex;
    flex-direction: column;
  }
  .ledger-edit-row {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 10px;
    padding: 4px 0;
    min-height: 26px;
  }
  .ledger-edit-row + .ledger-edit-row {
    border-top: 0.5px solid var(--border);
  }
  .ledger-edit-row-wide {
    flex-direction: column;
    align-items: stretch;
    gap: 4px;
  }
  .ledger-edit-label {
    color: var(--muted);
    flex-shrink: 0;
  }

  /* Inline input with optional unit prefix (the $ on prices, % / $
     toggle on fees). Sits right-aligned in its row so labels and values
     line up vertically across rows like the breakdown panel does. */
  .ledger-edit-input-wrap {
    display: inline-flex;
    align-items: center;
    gap: 4px;
  }
  .ledger-edit-prefix {
    color: var(--hint);
    font-family: var(--mono);
    font-size: 11px;
  }
  .ledger-edit {
    background: transparent;
    border: 1px solid transparent;
    border-radius: 5px;
    color: var(--text);
    font-family: var(--mono);
    font-size: 12px;
    padding: 3px 6px;
    outline: none;
    transition: background 0.12s, border-color 0.12s;
    box-sizing: border-box;
  }
  .ledger-edit:hover { background: var(--surface); }
  .ledger-edit:focus {
    background: var(--surface);
    border-color: var(--blue);
  }
  .ledger-edit::placeholder { color: var(--hint); }
  /* Number inputs: tight, right-aligned, no spinner clutter. */
  .ledger-edit-num {
    width: 84px;
    text-align: right;
    -moz-appearance: textfield;
  }
  /* The "Auction ends" field needs more room than a date for "date · time". */
  .ledger-date-field.ledger-date-field-dt { width: 196px; max-width: 62vw; }
  /* RIGHT-align the datetime value (instead of centred) so its AM/PM ends at the
     SAME x as the year in the plain date rows above/below — those end ~31px from
     the field's right edge (centred + the 15px glyph balance), so match that. The
     wider field keeps the long "date · time" from clipping on the left. */
  .ledger-date-field.ledger-date-field-dt .ledger-date-display { text-align: right; padding-right: 31px; }
  /* Visible focus when tabbing onto a date/datetime field (the native input is
     transparent, so without this, keyboard focus looks like it skips the field). */
  .ledger-date-field:focus-within {
    border-color: var(--blue);
    box-shadow: inset 0 0 0 1px var(--blue);
  }
  .ledger-edit-num::-webkit-outer-spin-button,
  .ledger-edit-num::-webkit-inner-spin-button {
    -webkit-appearance: none; margin: 0;
  }
  /* Date field: the visible box. A real <input type="date"> sits ON TOP of it,
     fully transparent (opacity:0), so a tap anywhere opens the native picker
     (incl. mobile) — but its locale-formatted text never shows. We paint the
     value ourselves in .ledger-date-display using the user's chosen format
     (fmtDate), so the format always honours Settings. Synced by
     _updateDateDisplay(). The box (background/border/hover/focus) lives here,
     not on the input. */
  .ledger-date-field {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    box-sizing: border-box;
    width: 130px;
    min-height: 24px;
    padding: 3px 6px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: 5px;
    cursor: pointer;
    transition: background 0.12s, border-color 0.12s;
  }
  .ledger-date-field:hover {
    background: var(--surface2, var(--surface));
    border-color: var(--hint);
  }
  .ledger-date-field:focus-within {
    background: var(--surface);
    border-color: var(--blue);
  }
  /* The value (centred), painted by us in the user's format. */
  .ledger-date-display {
    flex: 1;
    text-align: center;
    font-family: var(--mono);
    font-size: 11.5px;
    color: var(--text);
    pointer-events: none;
    white-space: nowrap;
    overflow: hidden;
    padding-right: 15px;   /* balance the calendar glyph so text reads centred */
  }
  .ledger-date-display.is-empty { color: var(--hint); }
  /* Calendar affordance (we hide the native indicator with the input). */
  .ledger-date-cal {
    position: absolute;
    right: 6px;
    top: 50%;
    transform: translateY(-50%);
    display: flex;
    color: var(--hint);
    pointer-events: none;
  }
  /* Explicit clear (×) — shown only when the field HAS a value, swapped in for the
     calendar glyph. Sits ABOVE the transparent native input (z-index) so the tap
     clears reliably on every platform (iOS Safari's native "Clear" often doesn't
     fire change/blur, so the painted value lingered). */
  .ledger-date-clear {
    position: absolute;
    right: 3px;
    top: 50%;
    transform: translateY(-50%);
    z-index: 3;
    display: none;
    align-items: center;
    justify-content: center;
    width: 20px;
    height: 20px;
    padding: 0;
    margin: 0;
    border: none;
    background: none;               /* iOS Safari renders <button> as a white box without this */
    -webkit-appearance: none;
            appearance: none;
    color: var(--hint);
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .ledger-date-clear:hover { color: var(--text); }
  .ledger-date-field.has-value .ledger-date-clear { display: flex; }
  .ledger-date-field.has-value .ledger-date-cal { display: none; }
  /* Pull the transparent native input clear of the × zone so the tap reliably hits
     the button on iOS (a full inset:0 overlay swallowed it → the picker opened
     instead of clearing). The rest of the field still opens the picker. */
  .ledger-date-field.has-value .ledger-edit-date { right: 26px; }
  /* The native picker, invisible but on top and interactive. */
  .ledger-edit-date {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    margin: 0;
    padding: 0;
    border: 0;
    background: transparent;
    opacity: 0;
    cursor: pointer;
    -webkit-appearance: none;
            appearance: none;
  }
  /* Wide text rows (title, notes): full width below their label. */
  .ledger-edit-row-wide .ledger-edit-text {
    width: 100%;
    text-align: left;
    font-family: var(--sans);
    font-size: 12.5px;
    padding: 5px 8px;
    border: 1px solid var(--border);
    background: var(--surface);
  }
  .ledger-edit-row-wide .ledger-edit-text:focus {
    border-color: var(--blue);
  }

  /* Fees mode toggle (% / $) — visually identical to the .ledger-edit-prefix
     character (a plain hint-coloured mono symbol) so that scanning the column
     of fields top-to-bottom, the leading $ on Bought/List/Sold/Shipping and
     this toggle on eBay fees all line up as the same kind of glyph. The
     button is still clickable; clickability is conveyed by hover state
     (text colours up, light underline) rather than a chunky pill outline. */
  .ledger-fees-toggle {
    appearance: none;
    background: transparent;
    border: 0;
    color: var(--hint);
    font-family: var(--mono);
    font-size: 11px;
    font-weight: 400;
    /* Same horizontal footprint as a single mono char — sits flush in the
       column of $ signs without any extra width or padding to throw off
       alignment. */
    width: auto;
    height: auto;
    padding: 0;
    line-height: 1;
    cursor: pointer;
    transition: color 0.12s;
    -webkit-tap-highlight-color: transparent;
  }
  .ledger-fees-toggle:hover {
    color: var(--blue);
    text-decoration: underline;
    text-underline-offset: 2px;
  }
  .ledger-fees-toggle:active { color: var(--blue); }

  /* #253: editable per-entry cost/fee rows in the ledger detail — name · %/$
     toggle · value · × remove, plus an "+ Add cost/fee" button. */
  .ledger-fees-block { gap: 7px; }
  .ledger-fees-list { display: flex; flex-direction: column; gap: 6px; width: 100%; }
  .ledger-fee-row { display: flex; align-items: center; gap: 6px; }
  .ledger-fee-row .lfee-name {
    flex: 1 1 auto; min-width: 0; width: auto;
    text-align: left; font-family: var(--sans); font-size: 12.5px;
    padding: 5px 8px; border: 1px solid var(--border); background: var(--surface);
  }
  .ledger-fee-row .lfee-name:focus { border-color: var(--blue); }
  /* Value uses the SAME fused %/$ widget as Settings/Lookup (.seller-fees-row).
     Fixed width + the 30px toggle segment keep the values aligned across rows
     regardless of the % vs $ (or multi-char currency) glyph. */
  .ledger-fee-row .lfee-valwrap { flex: 0 0 122px; width: 122px; box-sizing: border-box; }
  .ledger-fee-row .lfee-valwrap .seller-fees-mode { flex: 0 0 auto; }
  .ledger-fee-row .lfee-valwrap .fees-value { flex: 1 1 auto; width: auto; min-width: 0; }
  .ledger-fee-remove {
    flex: 0 0 auto; width: 22px; height: 22px; padding: 0;
    display: flex; align-items: center; justify-content: center;
    border: 1px solid var(--border2); border-radius: 50%;
    background: var(--surface2); color: var(--muted);
    font-size: 14px; line-height: 1; cursor: pointer; transition: all .12s ease;
  }
  .ledger-fee-remove:hover { border-color: var(--red); color: var(--red); }
  .ledger-fee-add {
    align-self: flex-start; margin-top: 2px;
    background: none; border: 1px dashed var(--border2); border-radius: 6px;
    color: var(--muted); font-family: var(--sans); font-size: 12px;
    padding: 5px 10px; cursor: pointer; transition: all .12s ease;
  }
  .ledger-fee-add:hover { border-color: var(--blue); color: var(--blue); }

  /* Seller-tab fees field: button + input fused into one widget with the
     same outer footprint as a plain .seller-input, so it sits in the
     2-column grid in perfect symmetry with the Bought / Shipping fields
     instead of looking awkwardly compressed by a separate toggle button.

     Strategy: the wrapper carries the border/background that .seller-input
     normally would; the button sits flush-left as a segment; the input is
     borderless and fills the rest of the row. */
  .seller-fees-row {
    display: flex;
    align-items: stretch;
    background: var(--surface2);
    border: 1px solid var(--border2);
    border-radius: 8px;
    overflow: hidden;
    transition: border-color 0.15s;
  }
  .seller-fees-row:focus-within { border-color: var(--blue); }
  .seller-fees-mode {
    appearance: none;
    background: transparent;
    border: 0;
    border-right: 1px solid var(--border2);
    color: var(--muted);
    font-family: var(--mono);
    font-size: 13px;
    font-weight: 600;
    width: 30px;
    cursor: pointer;
    padding: 0;
    line-height: 1;
    transition: color 0.12s, background 0.12s;
    flex-shrink: 0;
    -webkit-tap-highlight-color: transparent;
  }
  /* #293: hover/active recolour the symbol only — no background fill spilling
     across the surrounding box. */
  .seller-fees-mode:hover { color: var(--blue); background: transparent; }
  .seller-fees-mode:active { color: var(--blue); background: transparent; }
  .seller-fees-input {
    flex: 1;
    min-width: 0;
    background: transparent !important;
    border: 0 !important;
    border-radius: 0 !important;
    /* Match Bought field's left padding so the input text starts at the
       same x-offset visually that Bought's does — it doesn't of course
       (the button is to the left of it), but the *input itself* lines up
       with the input area of Bought when both are read as widgets. */
  }
  .seller-fees-input:focus { box-shadow: none; }

  /* In-row breakdown panel */
  .ledger-breakdown {
    background: var(--surface2);
    border-radius: 8px;
    padding: 8px 10px;
    font-family: var(--mono);
    font-size: 11.5px;
  }
  .ledger-breakdown-row {
    display: flex;
    justify-content: space-between;
    padding: 2px 0;
  }
  .ledger-breakdown-row + .ledger-breakdown-row {
    border-top: 0.5px solid var(--border);
  }
  .ledger-breakdown-row.total {
    padding-top: 6px;
    font-weight: 600;
  }
  .ledger-breakdown-label { color: var(--muted); }

  /* Action row */
  .ledger-row-actions {
    display: flex;
    gap: 8px;
    flex-wrap: wrap;
  }
  .ledger-row-actions .seller-btn { flex: 1; min-width: 110px; }

  /* Status badge */
  .ledger-status-badge {
    display: inline-block;
    padding: 2px 8px;
    border-radius: 999px;
    font-size: 10px;
    font-family: var(--mono);
    text-transform: uppercase;
    letter-spacing: 0.06em;
  }
  .ledger-status-badge[data-status="active"]   { background: var(--blue-dim);  color: var(--blue); }
  .ledger-status-badge[data-status="sold"]     { background: var(--green-dim); color: var(--green); }
  .ledger-status-badge[data-status="archived"] { background: var(--surface2);  color: var(--muted); }

  .ledger-empty {
    text-align: center;
    color: var(--muted);
    padding: 60px 20px;
  }
  .ledger-empty-hint {
    font-size: 12px;
    color: var(--hint);
    margin-top: 8px;
  }

  /* Save-state indicator */
  .ledger-save-flash {
    font-size: 10px;
    font-family: var(--mono);
    color: var(--green);
    opacity: 0;
    transition: opacity 0.2s;
  }
  .ledger-save-flash.show { opacity: 1; }

  /* --- Modal (replaces native prompt/confirm) --- */
  .ledger-modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(0, 0, 0, 0.55);
    display: none;
    align-items: center;
    justify-content: center;
    z-index: 1000;
    padding: 20px;
    -webkit-tap-highlight-color: transparent;
  }
  .ledger-modal-backdrop.show { display: flex; }
  .ledger-modal {
    background: var(--surface);
    border: 1px solid var(--border2);
    border-radius: 14px;
    width: 100%;
    max-width: 360px;
    padding: 20px;
    box-shadow: 0 16px 40px rgba(0,0,0,0.5);
    display: flex;
    flex-direction: column;
    gap: 14px;
  }
  .ledger-modal-title {
    font-size: 14px;
    font-weight: 600;
    color: var(--text);
    line-height: 1.4;
  }
  .ledger-modal-body {
    font-size: 13px;
    color: var(--muted);
    line-height: 1.5;
  }
  .ledger-modal-body .seller-input { width: 100%; box-sizing: border-box; margin-top: 8px; }
  .ledger-modal-actions {
    display: flex;
    gap: 8px;
    justify-content: flex-end;
  }
  .ledger-modal-actions .seller-btn { padding: 8px 16px; min-width: 80px; }

  /* --- eBay sales review (Step 2): one card per sold order + match chips --- */
  .ebay-sale {
    width: 100%;
    min-width: 0;                 /* let the nowrap title clip instead of widening the modal */
    box-sizing: border-box;
    border: 0.5px solid var(--border);
    border-radius: 10px;
    padding: 12px;
    background: var(--surface2);
  }
  /* Single line, horizontal-scroll with a right-edge fade + a thin draggable
     scrollbar — keeps every row the same height AND lets long titles be read. */
  .ebay-sale-title, .ebay-listing-title {
    font-size: 13px;
    color: var(--text);
    line-height: 1.5;
    font-weight: 600;
    white-space: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: thin;
    scrollbar-color: var(--border2) transparent;
    padding-bottom: 2px;          /* room for the scrollbar so it doesn't clip text */
  }
  .ebay-sale-title::-webkit-scrollbar, .ebay-listing-title::-webkit-scrollbar { height: 5px; }
  .ebay-sale-title::-webkit-scrollbar-track, .ebay-listing-title::-webkit-scrollbar-track { background: transparent; }
  .ebay-sale-title::-webkit-scrollbar-thumb, .ebay-listing-title::-webkit-scrollbar-thumb {
    background: var(--border2);
    border-radius: 3px;
  }
  .ebay-sale-meta {
    font-size: 11px;
    font-family: var(--mono);
    color: var(--muted);
    margin-top: 3px;
  }
  .ebay-sale-nomatch {
    font-size: 11px;
    color: var(--hint);
    margin-top: 8px;
    font-style: italic;
  }
  /* Options are full-width rows (uniform; long labels truncate instead of
     overflowing the modal), in a vertical stack. */
  .ebay-sale-choices {
    display: flex;
    flex-direction: column;
    gap: 6px;
    margin-top: 10px;
  }
  .ebay-choice {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 8px;
    width: 100%;
    box-sizing: border-box;
    padding: 8px 12px;
    border: 0.5px solid var(--border2);
    border-radius: 8px;
    background: var(--surface);
    color: var(--muted);
    font-size: 12px;
    font-family: inherit;
    text-align: left;
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, background 0.15s;
  }
  /* Same single-line scroll as the titles, no ellipsis (fade is set in JS). */
  .ebay-choice-label {
    flex: 1;
    min-width: 0;
    white-space: nowrap;
    overflow-x: auto;
    overflow-y: hidden;
    scrollbar-width: none;
  }
  .ebay-choice-label::-webkit-scrollbar { display: none; }
  /* + Add new and Skip share one row, half each — short labels, no scroll */
  .ebay-sale-actions { display: flex; gap: 6px; }
  .ebay-sale-actions .ebay-choice { flex: 1; justify-content: center; }
  .ebay-sale-actions .ebay-choice-label { flex: 0 1 auto; text-align: center; overflow: visible; }
  .ebay-choice:hover { color: var(--text); }
  .ebay-choice.selected {
    border-color: var(--blue);
    color: var(--blue);
    background: color-mix(in srgb, var(--blue) 12%, transparent);
  }
  .ebay-choice-score {
    flex-shrink: 0;
    font-size: 10px;
    font-family: var(--mono);
    color: var(--hint);
  }
  .ebay-choice-dist {
    font-size: 10px;
    font-family: var(--mono);
    color: var(--hint);
    opacity: 0.85;
  }
  .ebay-choice.selected .ebay-choice-score { color: var(--blue); }
  .ebay-choice-skip { color: var(--hint); }
  .ebay-choice-skip.selected {
    border-color: var(--muted);
    color: var(--text);
    background: color-mix(in srgb, var(--muted) 14%, transparent);
  }
  .ebay-choice-add { color: var(--green); }
  .ebay-choice-add.selected {
    border-color: var(--green);
    color: var(--green);
    background: color-mix(in srgb, var(--green) 13%, transparent);
  }
  .ebay-choice-add.selected .ebay-choice-score { color: var(--green); }

  /* eBay listings import rows — checkbox + title/meta */
  /* Filter + sort controls atop the import-listings modal — mirror the Ledger's
     search bar (muted magnifier + borderless input) and filter pills. */
  .ebay-listings-controls { display: flex; gap: 8px; padding: 10px 2px 2px; align-items: center; }
  .ebay-search-inner {
    flex: 1 1 0; min-width: 0; display: flex; align-items: center; gap: 8px;
    background: var(--surface2); border: 1px solid var(--border2);
    border-radius: 999px; padding: 7px 12px;
  }
  .ebay-search-inner svg { flex-shrink: 0; color: var(--muted); }
  .ebay-search-inner:focus-within { border-color: var(--blue); }
  .ebay-ctrl-input {
    flex: 1; min-width: 0; background: transparent; border: none; outline: none; box-shadow: none;
    font-size: 13px; color: var(--text); font-family: var(--font); -webkit-appearance: none; appearance: none;
  }
  .ebay-ctrl-input::placeholder { color: var(--hint); }
  /* Sort = the SAME button+popup component as the Ledger sort (reuses
     .ledger-sort-btn / .ledger-sort-popup / .ledger-sort-option). */
  .ebay-sort-wrap { position: relative; flex: 0 0 auto; }
  .ebay-sort-wrap .ledger-sort-btn { width: auto; white-space: nowrap; }
  .ebay-sort-popup { left: auto; right: 0; min-width: 170px; }
  .ebay-listing {
    display: flex;
    align-items: flex-start;
    gap: 10px;
    padding: 10px 12px;
    border: 0.5px solid var(--border);
    border-radius: 10px;
    background: var(--surface2);
    cursor: pointer;
  }
  .ebay-listing.is-imported { opacity: 0.55; cursor: default; }
  .ebay-listing-cb { margin-top: 1px; }
  .ebay-listing.is-imported .ebay-listing-cb { cursor: default; }

  /* Settings action buttons get a min width that comfortably houses "Confirm?"
     (~83px) so tapping (label → "Confirm?") never changes their width. Buttons
     whose own label is wider (e.g. "Manage / Cancel") keep their natural width.
     eBay's row is excluded — its 4 equal buttons rely on flex:1 + min-width:0. */
  #page-settings .margins-apply:not(.ebay-btn) { min-width: 90px; box-sizing: border-box; }

  /* eBay widget action buttons — equal quarters so they never resize on tap and
     all four fit one row (incl. mobile). Text shrinks/truncates rather than wrap. */
  .ebay-btn {
    flex: 1 1 0;
    min-width: 0;
    padding-left: 4px;
    padding-right: 4px;
    font-size: 11px;
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }

  /* Locked teaser (sub-Trader, eBay launched): hide the working bits, show only
     the upsell. They still see the card title + "?" help, so they know it exists. */
  .ebay-locked .ebay-tip,
  .ebay-locked .ebay-status-block,
  .ebay-locked #ebay-status-note,
  .ebay-locked .ebay-actions { display: none !important; }
  .ebay-lock { margin-top: 12px; }
  .ebay-lock-blurb {
    font-size: 12px;
    line-height: 1.55;
    color: var(--muted);
    margin-bottom: 10px;
  }
  /* Same look as the "Upgrade Plan" button (.margins-apply) — just full-width. */
  .ebay-lock-btn { width: 100%; box-sizing: border-box; }

  /* On-brand checkbox (Surge) — rounded square, blue fill + white tick on check */
  .surge-check {
    -webkit-appearance: none;
    appearance: none;
    flex-shrink: 0;
    width: 18px;
    height: 18px;
    margin: 0;
    border: 1px solid var(--border2);
    border-radius: 5px;
    background: var(--surface);
    cursor: pointer;
    transition: background 0.15s, border-color 0.15s;
    background-repeat: no-repeat;
    background-position: center;
    background-size: 11px;
  }
  .surge-check:hover { border-color: var(--blue); }
  .surge-check:checked {
    background-color: var(--blue);
    border-color: var(--blue);
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='20 6 9 17 4 12'/%3E%3C/svg%3E");
  }
  .surge-check:disabled { opacity: 0.4; cursor: default; }
  .surge-check:disabled:hover { border-color: var(--border2); }
  .ebay-listing-info { flex: 1; min-width: 0; }
  .ebay-listing-meta { font-size: 11px; font-family: var(--mono); color: var(--muted); margin-top: 3px; }
  .ebay-listing-auction {
    display: inline-block;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 1px 5px;
    border-radius: 4px;
    border: 0.5px solid color-mix(in srgb, var(--amber, #f59e0b) 50%, transparent);
    color: var(--amber, #f59e0b);
    background: color-mix(in srgb, var(--amber, #f59e0b) 13%, transparent);
    vertical-align: middle;
    margin-left: 4px;
  }

  /* "Auction" pill on an auction-format ledger row (amber to read as provisional) */
  .ledger-auction-pill {
    display: inline-flex;
    align-items: center;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 1px 5px;
    border-radius: 4px;
    border: 0.5px solid color-mix(in srgb, var(--amber, #f59e0b) 50%, transparent);
    color: var(--amber, #f59e0b);
    background: color-mix(in srgb, var(--amber, #f59e0b) 13%, transparent);
    vertical-align: middle;
    margin-left: 6px;
    line-height: 1.4;
  }

  /* Detail-pane BIN/Auction toggle */
  .ledger-fmt-toggle { display: flex; gap: 6px; }
  .ledger-fmt-opt {
    -webkit-appearance: none; appearance: none;
    border: 0.5px solid var(--border2);
    background: var(--surface);
    color: var(--muted);
    border-radius: 6px;
    padding: 6px 12px;
    font-size: 11px;
    font-family: inherit;
    white-space: nowrap;
    cursor: pointer;
    transition: border-color 0.15s, color 0.15s, box-shadow 0.15s;
  }
  .ledger-fmt-opt.active {
    border-color: var(--blue);
    color: var(--text);
    box-shadow: inset 0 0 0 1px var(--blue);
    font-weight: 600;
  }

  /* "BIN" pill — symmetric with the auction pill, neutral slate tone */
  .ledger-bin-pill {
    display: inline-flex;
    align-items: center;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 1px 5px;
    border-radius: 4px;
    border: 0.5px solid var(--border2);
    color: var(--muted);
    background: color-mix(in srgb, var(--muted) 10%, transparent);
    vertical-align: middle;
    margin-left: 6px;
    line-height: 1.4;
  }

  /* eBay-linked badge on a ledger row (#ebay) — small tag next to the card name */
  .ledger-ebay-badge {
    display: inline-flex;
    align-items: center;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    padding: 1px 5px;
    border-radius: 4px;
    border: 0.5px solid color-mix(in srgb, var(--blue) 45%, transparent);
    color: var(--blue);
    background: color-mix(in srgb, var(--blue) 12%, transparent);
    vertical-align: middle;
    margin-left: 6px;
    line-height: 1.4;
  }

  /* --- Mobile tweaks: bigger tap targets, simpler layout --- */
  @media (max-width: 700px) {
    /* Sort label ellipsis for long text */
    .ledger-sort-btn .ledger-sort-label {
      min-width: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    /* #239: centre the sort label on mobile, matching the date button below
       (base is space-between, which hugged "Purchased" to the left edge). */
    .ledger-sort-btn { justify-content: center; }
    .ledger-date-btn {
      width: 100%;
      justify-content: center;
      padding: 6px 8px;
      min-width: 0;
      overflow: hidden;
    }
    .ledger-date-btn .ledger-date-label {
      min-width: 0;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    /* On mobile, drop the "Sort: " / "Range: " prefixes to fit four
       chips alongside one another. The active value is still shown
       (e.g. "Purchased ▼", "Last 30 days"), which is enough to
       communicate state at a glance. */
    .ledger-sort-btn .ledger-sort-prefix { display: none; }
    .ledger-date-btn .ledger-date-prefix { display: none; }

    /* IO cluster: shrink-to-fit so the icon-only Import/Export buttons
       only consume the space their glyphs need (~32px each), leaving the
       rest of the row to the sort + date chips above. flex: 0 0 auto on
       the cluster, and on each button so they don't try to grow. */
    .ledger-io {
      flex: 0 0 auto;
      min-width: 0;
      gap: 6px;
      flex-wrap: nowrap;
    }
    .ledger-io .ledger-io-btn {
      flex: 0 0 auto;
      padding: 6px 8px;
    }
    .ledger-row-header {
      padding: 12px 12px 12px 14px;
      grid-template-columns: 36px 1fr auto auto;
    }
    .ledger-row-sprite { width: 36px; height: 36px; }
    .ledger-row-title { font-size: 13px; }

    /* Edit panel on mobile: numeric/date rows stay as flex rows (label
       left, value right) — same as desktop. The constraint is just the
       narrower viewport, which we handle by allowing inputs to shrink. */
    .ledger-edit-num { width: 76px; }
    .ledger-date-field {
      /* Cap the field so it never pushes the row off-screen on a phone. */
      width: 124px;
      max-width: 50vw;
    }

    /* Modal: smaller and tighter on phones — the default 360px max-width
       plus 20px padding pushes against the viewport edge on narrow screens. */
    .ledger-modal {
      max-width: 300px;
      padding: 16px;
      gap: 10px;
    }
    .ledger-modal-title { font-size: 13px; }
    .ledger-modal-body { font-size: 12px; }
    .ledger-modal-actions .seller-btn { padding: 7px 14px; min-width: 72px; }
  }

/* ---------------------------------------------------------------------------
   Logo refresh ripple + glow — fires on :active so it triggers on finger-down
   (mobile) and mouse-down (desktop), giving instant feedback before the browser
   begins navigating. Two layered pseudo-elements:
     ::before — a soft radial glow that pulses behind the logo
     ::after  — a circular ripple that expands outward from the logo's center
   Both run on GPU-only properties (transform + opacity). No JS, no reflow.
--------------------------------------------------------------------------- */
.header-logo-link {
  position: relative;
  display: inline-block;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  /* Suppress the iOS long-press callout menu ("Open in New Tab", "Copy Link",
     "Download Image") and disable text/image selection so a long press just
     does nothing instead of interrupting the refresh. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;
  -webkit-user-drag: none;
  /* Padding gives the ripple room to expand beyond the logo's bounds without
     getting clipped by the header. */
  padding: 4px;
}

.header-logo-link img {
  /* Inherit the no-callout / no-drag behavior on the image itself — Safari
     applies the long-press menu to the <img>, not the parent <a>. */
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  -webkit-user-drag: none;
  pointer-events: none;
}

/* Glow layer — sits behind the logo, fades up on tap */
.header-logo-link::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 140%;
  height: 140%;
  transform: translate(-50%, -50%) scale(0.6);
  background: radial-gradient(
    circle at center,
    rgba(96, 165, 250, 0.5) 0%,
    rgba(96, 165, 250, 0.25) 35%,
    transparent 70%
  );
  border-radius: 50%;
  opacity: 0;
  pointer-events: none;
  z-index: -1;
  will-change: transform, opacity;
  filter: blur(8px);
}

/* Ripple layer — circular ring that expands outward */
.header-logo-link::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 40px;
  height: 40px;
  margin-top: -20px;
  margin-left: -20px;
  border-radius: 50%;
  border: 2px solid rgba(96, 165, 250, 0.7);
  transform: scale(0.3);
  opacity: 0;
  pointer-events: none;
  will-change: transform, opacity;
}

/* Mobile-only: Chrome desktop tears down animations the moment the click
   commits, so the effect was either invisible or required holding the mouse.
   Restrict to touch devices (pointer: coarse) where it actually works
   reliably and where the feedback is most needed anyway. */
@media (pointer: coarse) {
  .header-logo-link.is-refreshing::before {
    animation: logo-glow 550ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
  }

  .header-logo-link.is-refreshing::after {
    animation: logo-ripple 550ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
  }
}

/* Front-loaded timing tuned for Chrome's aggressive page-teardown behavior.
   Chrome kills in-flight animations as early as ~50ms after navigation
   begins (Safari is more lenient at ~200-400ms). To stay visible across
   browsers, both animations:
     - Start at non-zero opacity (0.6) so the first paint is already bright
     - Hit visual peak at 8% of the timeline (~44ms) instead of 15%
     - Use larger peak scale values to land harder in that brief window
   The remaining 92% is decorative tail for browsers that allow it. */
@keyframes logo-glow {
  0%   { transform: translate(-50%, -50%) scale(0.7); opacity: 0.6; }
  8%   { transform: translate(-50%, -50%) scale(1.2); opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1.6); opacity: 0; }
}

@keyframes logo-ripple {
  0%   { transform: scale(0.6); opacity: 0.7; border-width: 4px; }
  8%   { transform: scale(2.2); opacity: 1; border-width: 3px; }
  100% { transform: scale(5); opacity: 0; border-width: 1px; }
}

/* Light theme: keep the same blue accent — reads well on light backgrounds. */

/* Respect reduced-motion preferences — fall back to a simple opacity pulse
   so users who disable animations still get refresh feedback. */
@media (prefers-reduced-motion: reduce) {
  .header-logo-link.is-refreshing::before {
    animation: logo-glow-fade 200ms ease-out forwards;
  }
  .header-logo-link.is-refreshing::after {
    animation: none;
  }
  @keyframes logo-glow-fade {
    0%, 100% { opacity: 0; transform: translate(-50%, -50%) scale(1); }
    50%      { opacity: 1; }
  }
}

/* ════════════════════════════════════════════════════════════════════════
   DASHBOARD TAB — ported 1:1 from CardEdge's "Most Scanned" widget styles.
   Terminology becomes "Most Listed" but selectors keep their original
   names for ease of cross-reference.
   ════════════════════════════════════════════════════════════════════════ */

.dashboard-intro {
  margin-bottom: 1.5rem;
}
.dashboard-sub {
  font-size: 13px;
  color: var(--muted);
  margin: 0 0 4px;
  line-height: 1.5;
}
.dashboard-status {
  font-size: 11px;
  color: var(--hint);
  font-family: var(--mono);
  margin: 0;
}

/* ── "Most Listed Today" — grid of 3 columns on desktop, 1 on mobile. ─── */
.pokemon-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
  margin-bottom: 0;
  width: 100%;
  box-sizing: border-box;
}
/* Wrapper gives today-listed same card appearance as alltime-card widgets */
.today-listed-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 8px;
  margin-bottom: 2rem;
  width: 100%;
  box-sizing: border-box;
}
.today-listed-card .pokemon-grid {
  margin-bottom: 0;
  gap: 6px;
}
.today-listed-card .pokemon-card {
  padding: 8px 6px;
  gap: 6px;
  min-width: 0;
  overflow: hidden;
}
.pokemon-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 10px 8px;
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  overflow: hidden;
}
.pokemon-sprite-wrap {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  position: relative;
}
.pokemon-sprite {
  width: 40px;
  height: 40px;
  object-fit: contain;
  /* Pixelated rendering keeps the gen-V animated sprites looking like
     the original BW games. Without it the browser smooths them into a
     blurred mess at this size. */
  image-rendering: pixelated;
}
.pokemon-sprite-fallback {
  /* Sits behind the <img> until the sprite loads. Once we get a real
     URL, JS hides this and reveals the img. On 404 the onerror handler
     reverses the swap so the Pokéball comes back. */
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.pokemon-sprite-fallback .sprite-fallback-svg {
  width: 80%;
  height: 80%;
}
.pokemon-rank {
  font-size: 11px;
  color: var(--hint);
  font-weight: 500;
  min-width: 16px;
}
.pokemon-info {
  flex: 1;
  min-width: 0;
}
.pokemon-name {
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  letter-spacing: -0.01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  text-transform: capitalize;
}
.pokemon-count {
  font-size: 10px;
  color: var(--muted);
  margin-top: 2px;
  font-family: var(--mono);
}
.pokemon-empty {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--hint);
  padding: 1rem 0;
  text-align: center;
  grid-column: 1 / -1;
}

/* ── "Most Listed" — alltime card with period tabs + view toggle ──────── */
.alltime-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 8px;
  margin-bottom: 2rem;
}

/* ── Morning Coffee (#254) — "while you were gone" briefing BAR ──────────────
   Full-width strip above the dashboard widget grid (NOT a customisable widget):
   spans both columns, collapses + dismisses, never moves. Rows lay out on a
   shared grid so Source / Name / Grade / Change / Price line up across rows.
   Theme-aware throughout (all colour from CSS vars → reads on any theme). */
.mc-bar {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 14px;
  margin-bottom: 1.4rem;
  overflow: hidden;
  position: relative;
}
.mc-bar[hidden] { display: none; }
/* A whisper of warmth on the left edge so the coffee reads as special, not loud. */
.mc-bar::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  background: linear-gradient(var(--green), var(--blue)); opacity: 0.5;
}
.mc-bar-head { display: flex; align-items: center; }
.mc-bar-toggle {
  flex: 1; display: flex; align-items: center; gap: 9px;
  background: none; border: none; cursor: pointer; color: var(--text);
  font: inherit; text-align: left; padding: 12px 6px 12px 15px; min-width: 0;
}
/* #323: match the dashboard widget titles ("MOST LISTED TODAY") — uppercase
   section-label style, no emoji — instead of the cheap-looking coffee cup. */
.mc-bar-title { font-size: 11px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; color: var(--muted); white-space: nowrap; }
.mc-bar-count { font-size: 11px; color: var(--hint); white-space: nowrap; letter-spacing: 0.04em; }
.mc-bar-chevron { margin-left: auto; color: var(--muted); font-size: 11px; transition: transform 0.2s ease; }
.mc-bar.mc-collapsed .mc-bar-chevron { transform: rotate(-90deg); }
.mc-bar-dismiss {
  background: none; border: none; color: var(--muted); cursor: pointer;
  font-size: 16px; line-height: 1; padding: 12px 14px;
}
.mc-bar-dismiss:hover { color: var(--text); }
.mc-bar-body { padding: 2px 7px 8px; display: flex; flex-direction: column; gap: 1px; }
.mc-bar.mc-collapsed .mc-bar-body { display: none; }

/* #323: one update per row laid out as clean ALIGNED columns —
   sprite · source · grade · name · change · price — so every row lines up.
   The grade pill sits LEFT of the name; an empty placeholder keeps the column
   when a row has no grade. The market "what's hot" tag spans change+price. */
/* The whole row is ONE horizontal scroller: every cell keeps its natural width and
   the row scrolls (swipe/drag) when it overflows, with an edge fade on whichever
   side is cut off — via .hscroll-more/.hscroll-back (see _HSCROLL_SELECTOR). */
.mc-line {
  display: flex; align-items: center; gap: 11px; width: 100%;
  overflow-x: auto; overflow-y: hidden; white-space: nowrap;
  scrollbar-width: none; -ms-overflow-style: none;
  background: none; border: none; text-align: left; cursor: pointer;
  color: var(--text); font: inherit; padding: 7px 10px; border-radius: 8px;
  transition: background 0.12s ease;
}
.mc-line::-webkit-scrollbar { display: none; }
.mc-line:hover { background: var(--surface2); }
.mc-line > * { flex: 0 0 auto; }
/* Fixed 20px sprite slot: wrapper holds the fallback span, the <img>, and the
   silhouette canvas __applyPokeSprite inserts — all clamped to 20px so a sprite
   can never blow up to the row width and hide the text. */
.mc-sprite { width: 20px; height: 20px; display: inline-flex; align-items: center; justify-content: center; }
.mc-sprite-fb { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; }
.mc-sprite-fb svg, .mc-sprite-fb img { width: 20px; height: 20px; }
.mc-sprite-img, .mc-sprite canvas { width: 20px !important; height: 20px !important; object-fit: contain; }
.mc-src {
  font-size: 9px; text-transform: uppercase; letter-spacing: 0.03em; color: var(--hint);
  white-space: nowrap;
}
.mc-grade {
  font-size: 10px; color: var(--muted); background: var(--surface2);
  padding: 2px 6px; border-radius: 5px; white-space: nowrap;
}
.mc-name { font-size: 13px; font-weight: 600; color: var(--text); white-space: nowrap; }
.mc-chg { font-size: 12.5px; font-weight: 700; white-space: nowrap; }
.mc-chg.up { color: var(--green); }
.mc-chg.down { color: var(--red); }
.mc-amt { font-size: 12.5px; color: var(--muted); white-space: nowrap; }
.mc-line.mc-market .mc-tag { font-size: 12px; color: var(--muted); white-space: nowrap; }
/* The row stays a single horizontal scroller (full name + figures reachable by
   swipe, with the edge fade — see _markHScroll). To still read as neat columns the
   grade pill is a uniform width (empty placeholder reserves the column on ungraded
   rows) so every name starts at the same x; the change/price trail into the
   scrollable area, pushed right when the row has room. */
.mc-grade, .mc-grade-empty { min-width: 52px; box-sizing: border-box; text-align: center; }
.mc-grade-empty { background: none; padding: 0; }
/* Push the figures to the right edge when the row has room (desktop → prices line
   up); when it overflows (mobile) there's no free space, so they just trail the name
   and the row scrolls with the edge fade. */
.mc-line > .mc-chg, .mc-line > .mc-tag { margin-left: auto; }
/* The inline up/down % in a market tag keeps the green/red mover colours. */
.mc-tag .mc-chg { font-size: 12px; }
/* Tap-through highlight on the destination widget. */
.mc-flash { animation: mc-flash 1.2s ease; }
@keyframes mc-flash {
  0%, 100% { box-shadow: 0 0 0 0 transparent; }
  20%      { box-shadow: 0 0 0 2px var(--blue); }
}
.alltime-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
  gap: 8px;
}
.alltime-tabs {
  display: flex;
  gap: 4px;
}
.alltime-tab,
.alltime-view-btn {
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  padding: 4px 10px;
  border-radius: 5px;
  border: 1px solid var(--border2);
  background: transparent;
  color: var(--hint);
  cursor: pointer;
  transition: all 0.15s;
  font-family: var(--mono);
}
.alltime-tab:hover,
.alltime-view-btn:hover { color: var(--text); border-color: var(--hint); }
.alltime-tab.active { background: var(--blue-dim); border-color: var(--blue); color: var(--blue); }
.alltime-view-btn.active { background: var(--surface2); border-color: var(--muted); color: var(--text); }
.alltime-view-btns {
  display: flex;
  gap: 4px;
}

.alltime-list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-height: 420px;
  overflow-y: auto;
}
.alltime-list::-webkit-scrollbar { width: 3px; }
.alltime-list::-webkit-scrollbar-track { background: transparent; }
.alltime-list::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 2px; }

.alltime-row {
  display: grid;
  grid-template-columns: 36px 44px 1fr 80px 36px;
  align-items: center;
  padding: 9px 6px;
  transition: background 0.15s;
}
.alltime-row:hover { background: var(--surface2); border-radius: 6px; }
.alltime-rank {
  font-size: 11px;
  color: var(--hint);
  font-weight: 500;
  text-align: right;
  padding-right: 6px;
}
.alltime-sprite-wrap {
  width: 40px;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
}
.alltime-sprite {
  width: 36px;
  height: 36px;
  object-fit: contain;
  image-rendering: pixelated;
}
.alltime-name {
  font-size: 13px;
  font-weight: 500;
  color: var(--text);
  letter-spacing: -0.01em;
  padding: 0 8px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.alltime-count {
  font-size: 11px;
  color: var(--muted);
  font-family: var(--mono);
  text-align: right;
  white-space: nowrap;
  padding-right: 4px;
}
.alltime-move {
  font-size: 11px;
  font-weight: 700;
  text-align: center;
}
.alltime-move.up   { color: var(--green); }
.alltime-move.down { color: var(--red); }
.alltime-move.new  { color: var(--blue); font-size: 9px; font-weight: 500; }
.alltime-empty {
  font-family: var(--mono);
  font-size: 12px;
  color: var(--hint);
  padding: 1rem 0;
  text-align: center;
}

/* ── Bar view (alternate render) ─────────────────────────────────────── */
.at-bar-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 4px;
  border-bottom: 1px solid var(--border);
  transition: background 0.15s;
}

.at-bar-row:hover { background: var(--surface2); border-radius: 6px; }
.at-bar-rank   { font-size: 11px; color: var(--hint); width: 18px; text-align: right; flex-shrink: 0; }
.at-bar-sprite { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; position: relative; }
.at-bar-sprite img { width: 28px; height: 28px; object-fit: contain; image-rendering: pixelated; }
.at-bar-name   { font-size: 13px; font-weight: 500; color: var(--text); min-width: 90px; text-transform: capitalize; }
.at-bar-track  { flex: 1; height: 5px; background: var(--border2); border-radius: 3px; overflow: hidden; }
.at-bar-fill   { height: 100%; background: var(--blue); border-radius: 3px; transition: width 0.4s ease; }
.at-bar-count  { font-size: 11px; color: var(--muted); font-family: var(--mono); min-width: 70px; text-align: right; }

/* ── Mobile: tabs go full-width; today grid stays 3 columns (only 3 cards) ── */
@media (max-width: 640px) {
  .pokemon-grid { grid-template-columns: repeat(3, 1fr); gap: 6px; }
  .pokemon-card { padding: 8px 6px; gap: 6px; }
  .today-listed-card .pokemon-sprite-wrap { width: 28px; height: 28px; }
  .today-listed-card .pokemon-sprite { width: 28px; height: 28px; }
  .today-listed-card .pokemon-name { font-size: 10px; }
  .today-listed-card .pokemon-count { font-size: 9px; }
  .pokemon-sprite-wrap { width: 30px; height: 30px; }
  .pokemon-sprite { width: 30px; height: 30px; }
  .pokemon-name { font-size: 11px; }
  .pokemon-count { font-size: 9px; }
  .alltime-header { flex-wrap: wrap; }
  .alltime-tabs { width: 100%; order: 1; }
  .alltime-tab  { flex: 1; text-align: center; }
  .alltime-view-btns { order: 2; }
}


/* ════════════════════════════════════════════════════════════════════════
   LAYOUT EDIT SYSTEM — module overlays, hidden tray, drag affordances
   Ported from CardEdge with terminology + selectors kept intact for ease
   of cross-reference. .customise-btn was already defined further up so
   we only add the bits that aren't yet there: module overlays, tray
   panel, mobile pill, drag visuals.
   ════════════════════════════════════════════════════════════════════════ */

/* Hidden state — used by widgets pushed into the tray. */
.module.hidden { display: none; }

/* Drag affordances during a drop. dragging = the source; drag-over =
   the hover target. */
.module.dragging  { opacity: 0.4; }
.module.drag-over { outline: 2px dashed var(--blue); outline-offset: 4px; border-radius: 8px; }

/* ── Edit overlay ── A semi-opaque dim above the widget while edit mode
   is on. Up / label / hide / down buttons sit on top of the dim. The
   widget below stays visible (so the user knows what they're moving). */
.module-overlay {
  display: none;
  position: absolute;
  inset: 0;
  z-index: 50;
  border-radius: 12px;
  background: rgba(0, 0, 0, 0.45);
  align-items: center;
  justify-content: center;
  gap: 10px;
  pointer-events: none;
}
body.customise-mode .module-overlay { display: flex; pointer-events: all; }
body.customise-mode .module         { cursor: grab; }
body.customise-mode .module:active  { cursor: grabbing; }

.module-label {
  font-size: 12px;
  font-weight: 600;
  color: #fff;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  pointer-events: none;
}

.module-hide-btn {
  width: 28px; height: 28px;
  border-radius: 50%;
  border: 1px solid rgba(255,255,255,0.3);
  background: rgba(255,255,255,0.1);
  color: #fff;
  font-size: 14px;
  cursor: pointer;
  display: flex; align-items: center; justify-content: center;
  transition: background 0.15s;
  flex-shrink: 0;
}
.module-hide-btn:hover { background: rgba(248, 113, 113, 0.6); }

.module-arrow-btn {
  width: 28px; height: 28px;
  border-radius: 50%;
  border: 1px solid rgba(255,255,255,0.3);
  background: rgba(255,255,255,0.1);
  color: #fff;
  font-size: 12px;
  cursor: pointer;
  /* Hidden on desktop — drag-to-reorder is the primary interaction.
     Arrows show up on mobile only (touch can't HTML5-drag reliably). */
  display: none;
  align-items: center; justify-content: center;
  transition: background 0.15s;
  flex-shrink: 0;
}
.module-arrow-btn:hover { background: rgba(255,255,255,0.2); }

/* ── Hidden tray ── Desktop: docks below the theme button (fixed,
   top-right). Mobile: bottom-anchored collapsible pill. Only ever
   visible while customise mode is active AND there's something hidden. */
.hidden-tray {
  display: none;
  position: fixed;
  top: 96px;             /* below pencil (16) + gear (56) + 8px gap */
  right: 20px;
  z-index: 99;
  background: var(--surface);
  border: 1px solid var(--border2);
  border-radius: 12px;
  padding: 10px;
  min-width: 180px;
  flex-direction: column;
  gap: 6px;
}
.hidden-tray.visible { display: flex; }
.hidden-tray-section {
  font-size: 8px;
  font-weight: 600;
  letter-spacing: 0.08em;
  color: var(--hint);
  text-transform: uppercase;
  padding: 2px 4px;
}
.hidden-tray-sep {
  height: 1px;
  background: var(--border2);
  margin: 2px 0;
}
.hidden-tray-item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 6px 10px;
  border-radius: 6px;
  background: var(--surface2);
  font-size: 11px;
  color: var(--muted);
  font-weight: 500;
}
.hidden-tray-add {
  font-size: 16px;
  color: var(--green);
  cursor: pointer;
  line-height: 1;
  background: none;
  border: none;
  padding: 0;
}
.hidden-tray-add:hover { color: #fff; }
.tray-reset {
  margin-top: 4px;
  padding: 5px 10px;
  border-radius: 6px;
  border: 1px solid var(--border2);
  background: transparent;
  color: var(--hint);
  font-size: 10px;
  font-weight: 600;
  cursor: pointer;
  letter-spacing: 0.05em;
  font-family: var(--sans);
  width: 100%;
  text-align: center;
}
.tray-reset:hover { border-color: var(--red); color: var(--red); }

/* Desktop hides the mobile pill — the panel is already always-open. */
@media (min-width: 701px) {
  .tray-pill { display: none; }
}

/* ── Mobile (≤ 700px) ──
   On touch screens HTML5 drag-and-drop is unreliable, so we surface
   the up/down arrows on each module overlay and switch the tray from
   a static panel to a collapsible pill. Identical UX to CardEdge. */
@media (max-width: 700px) {
  .module-arrow-btn { display: flex; }
  body.customise-mode .module { cursor: default; }

  .hidden-tray {
    top: auto;
    bottom: 60px;
    right: 12px;
    left: 12px;
    min-width: unset;
    max-height: 60px;
    overflow: hidden;
    /* The padding goes to 0 while collapsed so the pill snaps to the
       same height regardless of how many items the tray would render
       when expanded. */
    transition: max-height 0.25s ease, padding 0.25s ease;
    padding: 0;
  }
  .hidden-tray.tray-expanded {
    max-height: 70vh;
    overflow-y: auto;
    padding: 10px;
  }
  .tray-pill {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 10px 14px;
    cursor: pointer;
    font-size: 11px;
    font-weight: 600;
    color: var(--muted);
    letter-spacing: 0.04em;
    user-select: none;
  }
  .tray-pill-arrow { transition: transform 0.2s; font-size: 10px; }
  .hidden-tray.tray-expanded .tray-pill-arrow { transform: rotate(180deg); }
  .tray-pill-count { color: var(--blue); }
  /* Collapsed state: hide everything except the pill so the tray
     reads as just the pill bar. */
  .hidden-tray:not(.tray-expanded) .hidden-tray-item,
  .hidden-tray:not(.tray-expanded) .hidden-tray-section,
  .hidden-tray:not(.tray-expanded) .hidden-tray-sep,
  .hidden-tray:not(.tray-expanded) .tray-reset { display: none; }
}

/* The .module elements need position:relative so the absolutely-positioned
   .module-overlay sits inside them. The JS sets element.style.position
   on the fly during overlay injection; this rule is a CSS-side belt-and-
   braces in case JS hasn't run yet. */
.page-content .module { position: relative; }

/* ── "Movement since you last viewed X ago" caption ───────────────────────
   Sits above the rendered alltime list, only when a prev snapshot
   exists. Subtle by design — explains the up/down arrows without
   stealing focus. */
.alltime-caption {
  font-family: var(--mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--hint);
  padding: 0 4px 8px;
  text-transform: uppercase;
}


/* ════════════════════════════════════════════════════════════════════════
   TIER BADGES — Pokémon-inspired visual identifiers for each plan.
   Used in: pricing page cards, settings account widget, dashboard header.
   Each badge is self-contained SVG + text, no external assets.
   ════════════════════════════════════════════════════════════════════════ */

/* Shared badge shell */
.tier-badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px 4px 5px;
  border-radius: 6px;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  border: 1.5px solid transparent;
  user-select: none;
}
.tier-badge svg {
  flex-shrink: 0;
  display: block;
  overflow: visible; /* clipPath inside SVG handles clipping */
}

/* ── Starter — green circle, leaf icon ────────────────────────────────── */
/* Default dark theme */
.tier-badge.starter { background: rgba(22,163,74,0.12); border-color: rgba(74,222,128,0.35); color: #4ade80; }
.tier-badge.trader  { background: rgba(37,99,235,0.12);  border-color: rgba(96,165,250,0.35); color: #60a5fa; }
.tier-badge.champion { background: rgba(124,58,237,0.15); border-color: rgba(192,132,252,0.4); color: #c084fc; box-shadow: 0 0 6px rgba(192,132,252,0.2); }

/* Light theme — solid light backgrounds */
html.theme-light .tier-badge.starter, body.theme-light .tier-badge.starter { background: #f0fdf4; border-color: #86efac; color: #15803d; }
html.theme-light .tier-badge.trader,  body.theme-light .tier-badge.trader  { background: #eff6ff; border-color: #93c5fd; color: #1d4ed8; }
html.theme-light .tier-badge.champion,body.theme-light .tier-badge.champion{ background: #faf5ff; border-color: #c084fc; color: #7e22ce; box-shadow: 0 0 0 1px rgba(192,132,252,0.2); }

/* Fun — purple synthwave */
html.theme-default .tier-badge.starter, body.theme-default .tier-badge.starter { background: rgba(45,212,191,0.12); border-color: rgba(45,212,191,0.4); color: #2dd4bf; }
html.theme-default .tier-badge.trader,  body.theme-default .tier-badge.trader  { background: rgba(192,132,252,0.12); border-color: rgba(192,132,252,0.4); color: #c084fc; }
html.theme-default .tier-badge.champion,body.theme-default .tier-badge.champion{ background: rgba(251,191,36,0.12); border-color: rgba(251,191,36,0.45); color: #fbbf24; box-shadow: 0 0 8px rgba(251,191,36,0.2); }

/* Trader theme — terminal green */
html.theme-trader .tier-badge.starter, body.theme-trader .tier-badge.starter { background: rgba(0,255,65,0.08);  border-color: rgba(0,255,65,0.3);  color: #00ff41; }
html.theme-trader .tier-badge.trader,  body.theme-trader .tier-badge.trader  { background: rgba(0,229,255,0.08); border-color: rgba(0,229,255,0.3); color: #00e5ff; }
html.theme-trader .tier-badge.champion,body.theme-trader .tier-badge.champion{ background: rgba(255,224,0,0.08);  border-color: rgba(255,224,0,0.35);  color: #ffe000; box-shadow: 0 0 6px rgba(255,224,0,0.15); }

/* ═══════════════════════════════════════════════════════════════════════════
   ARCADE THEME — 80s coin-op galaxy
   Deep space black · neon cyan · electric yellow · hot magenta
   Pac-Man / Galaga / Space Invaders era
   ═══════════════════════════════════════════════════════════════════════════ */

/* Starfield — animated stars scrolling upward like a space shooter */
html.theme-galaxy body::before,
body.theme-galaxy::before {
  content: "";
  position: fixed;
  top: 0; left: 0; right: 0;
  height: 400%;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(1px 1px at 10% 15%, rgba(255,255,255,0.9) 0%, transparent 100%),
    radial-gradient(1px 1px at 25% 40%, rgba(0,229,255,0.8) 0%, transparent 100%),
    radial-gradient(1.5px 1.5px at 40% 8%, rgba(255,255,255,0.7) 0%, transparent 100%),
    radial-gradient(1px 1px at 55% 60%, rgba(255,230,0,0.7) 0%, transparent 100%),
    radial-gradient(1px 1px at 70% 25%, rgba(255,255,255,0.9) 0%, transparent 100%),
    radial-gradient(1.5px 1.5px at 82% 72%, rgba(0,229,255,0.6) 0%, transparent 100%),
    radial-gradient(1px 1px at 90% 45%, rgba(255,45,107,0.8) 0%, transparent 100%),
    radial-gradient(1px 1px at 15% 80%, rgba(255,255,255,0.6) 0%, transparent 100%),
    radial-gradient(1px 1px at 33% 90%, rgba(57,255,20,0.7) 0%, transparent 100%),
    radial-gradient(1.5px 1.5px at 60% 85%, rgba(255,255,255,0.8) 0%, transparent 100%),
    radial-gradient(1px 1px at 78% 10%, rgba(0,229,255,0.9) 0%, transparent 100%),
    radial-gradient(1px 1px at 92% 88%, rgba(255,230,0,0.6) 0%, transparent 100%),
    radial-gradient(1px 1px at 5% 55%, rgba(255,255,255,0.7) 0%, transparent 100%),
    radial-gradient(1.5px 1.5px at 48% 35%, rgba(255,45,107,0.5) 0%, transparent 100%),
    radial-gradient(1px 1px at 65% 50%, rgba(255,255,255,0.6) 0%, transparent 100%),
    radial-gradient(1px 1px at 20% 20%, rgba(57,255,20,0.5) 0%, transparent 100%),
    radial-gradient(1px 1px at 87% 55%, rgba(255,255,255,0.8) 0%, transparent 100%),
    radial-gradient(1.5px 1.5px at 3% 92%, rgba(0,229,255,0.7) 0%, transparent 100%);
  background-size: 100% 50%;     /* image = 200vh star tile (same as the old
                                    100%/200% box), now repeated down the 400vh
                                    layer so two identical tiles stack. */
  background-repeat: repeat;
  /* Drift via TRANSFORM (compositor-only) rather than background-position, which
     repainted the whole full-viewport layer every frame — the desktop scroll
     lag. Translating up by exactly one tile (-50% of 400vh) loops seamlessly
     with zero per-frame repaint, so scrolling stays smooth. */
  animation: galaxy-starfield 36s linear infinite;
  transform: translateZ(0);
  backface-visibility: hidden;
  will-change: transform;
}
@keyframes galaxy-starfield {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(0, -50%, 0); }
}

/* Scanline overlay — classic CRT horizontal lines */
html.theme-galaxy body::after,
body.theme-galaxy::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: repeating-linear-gradient(
    to bottom,
    transparent 0px,
    transparent 3px,
    rgba(0, 0, 0, 0.18) 3px,
    rgba(0, 0, 0, 0.18) 4px
  );
  /* Own compositor layer → no repaint of this fixed scanline on scroll. */
  transform: translateZ(0);
  backface-visibility: hidden;
}

/* App shell sits above overlays */
html.theme-galaxy .app-shell,
body.theme-galaxy .app-shell {
  position: relative;
  z-index: 2;
}

/* Header — neon glow bottom border */
html.theme-galaxy header,
body.theme-galaxy header {
  border-bottom: none;
  background: transparent;
  box-shadow: none;
}

/* Make header-inner and any direct header backgrounds transparent too */
html.theme-galaxy .header-inner,
body.theme-galaxy .header-inner {
  background: transparent;
}

/* Page tabs — pixel-sharp, neon active state */
html.theme-galaxy .page-tab,
body.theme-galaxy .page-tab {
  color: var(--muted);
}
html.theme-galaxy .page-tab.active,
body.theme-galaxy .page-tab.active {
  border-color: #00e5ff;
  color: #00e5ff;
  box-shadow: 0 0 12px rgba(0,229,255,0.35), inset 0 0 8px rgba(0,229,255,0.08);
}

/* Module cards — neon border glow */
html.theme-galaxy .module,
body.theme-galaxy .module {
  border-color: rgba(0,229,255,0.15);
  box-shadow: 0 0 20px rgba(0,229,255,0.04);
}

/* Section headers — electric yellow, uppercase mono */
html.theme-galaxy .section-label,
html.theme-galaxy .module-header,
body.theme-galaxy .section-label,
body.theme-galaxy .module-header {
  font-family: var(--mono);
  letter-spacing: 0.12em;
  color: #ffe600;
  text-shadow: 0 0 8px rgba(255,230,0,0.5);
}

/* Buttons — neon outline style */
html.theme-galaxy .seller-btn,
body.theme-galaxy .seller-btn {
  font-family: var(--mono);
  letter-spacing: 0.04em;
}
html.theme-galaxy .seller-btn.primary,
body.theme-galaxy .seller-btn.primary {
  background: transparent;
  border-color: #00e5ff;
  color: #00e5ff;
  box-shadow: 0 0 10px rgba(0,229,255,0.3), inset 0 0 6px rgba(0,229,255,0.06);
}
html.theme-galaxy .seller-btn.primary:hover,
html.theme-galaxy .seller-btn.primary:active,
body.theme-galaxy .seller-btn.primary:hover,
body.theme-galaxy .seller-btn.primary:active {
  background: rgba(0,229,255,0.12);
  box-shadow: 0 0 18px rgba(0,229,255,0.5), inset 0 0 10px rgba(0,229,255,0.1);
}

/* Inputs — neon focus ring */
html.theme-galaxy .seller-input:focus,
body.theme-galaxy .seller-input:focus {
  border-color: #00e5ff;
  box-shadow: 0 0 0 2px rgba(0,229,255,0.2), 0 0 12px rgba(0,229,255,0.15);
  outline: none;
}

/* Price display — electric yellow for the main number */
html.theme-galaxy #ps-price,
body.theme-galaxy #ps-price {
  color: #ffe600;
  text-shadow: 0 0 12px rgba(255,230,0,0.5);
  font-family: var(--mono);
}

/* Rank numbers in dashboard widgets — neon magenta top 3 */
html.theme-galaxy .at-lb-num.top,
body.theme-galaxy .at-lb-num.top {
  color: #ff2d6b;
  text-shadow: 0 0 8px rgba(255,45,107,0.6);
}

/* Movement arrows */
html.theme-galaxy .alltime-move.up,
body.theme-galaxy .alltime-move.up  { color: #39ff14; text-shadow: 0 0 3px rgba(57,255,20,0.35); }
html.theme-galaxy .alltime-move.down,
body.theme-galaxy .alltime-move.down { color: #ff2d6b; text-shadow: 0 0 3px rgba(255,45,107,0.35); }
html.theme-galaxy .alltime-move.new,
body.theme-galaxy .alltime-move.new  { color: #ffe600; text-shadow: 0 0 3px rgba(255,230,0,0.35); }

/* Bar fills — cyan */
html.theme-galaxy .at-bar-fill,
html.theme-galaxy .at-lb-bar-inner,
body.theme-galaxy .at-bar-fill,
body.theme-galaxy .at-lb-bar-inner {
  background: linear-gradient(90deg, rgba(0,229,255,0.6), rgba(0,229,255,0.9));
  box-shadow: 0 0 6px rgba(0,229,255,0.4);
}

/* Ledger status badges */
html.theme-galaxy .ledger-status-badge[data-status="active"],
body.theme-galaxy .ledger-status-badge[data-status="active"] {
  color: #39ff14;
  border-color: rgba(57,255,20,0.35);
  background: rgba(57,255,20,0.08);
}
html.theme-galaxy .ledger-status-badge[data-status="sold"],
body.theme-galaxy .ledger-status-badge[data-status="sold"] {
  color: #00e5ff;
  border-color: rgba(0,229,255,0.35);
  background: rgba(0,229,255,0.08);
}

/* Tier badges */
html.theme-galaxy .tier-badge.starter, body.theme-galaxy .tier-badge.starter {
  background: rgba(57,255,20,0.08);
  border-color: rgba(57,255,20,0.35);
  color: #39ff14;
  text-shadow: 0 0 6px rgba(57,255,20,0.4);
}
html.theme-galaxy .tier-badge.trader, body.theme-galaxy .tier-badge.trader {
  background: rgba(0,229,255,0.08);
  border-color: rgba(0,229,255,0.35);
  color: #00e5ff;
  text-shadow: 0 0 6px rgba(0,229,255,0.4);
}
html.theme-galaxy .tier-badge.champion, body.theme-galaxy .tier-badge.champion {
  background: rgba(255,230,0,0.08);
  border-color: rgba(255,230,0,0.4);
  color: #ffe600;
  text-shadow: 0 0 8px rgba(255,230,0,0.6);
  box-shadow: 0 0 10px rgba(255,230,0,0.15);
}

/* ═══════════════════════════════════════════════════════════════════════════
   RETRO THEME — 8/16-bit console era. Original colour palette + animations.
   Inspired generally by the 8/16-bit Nintendo era without using any
   characters, logos, sprites, music, or trademarked artwork. Pure CSS
   animations: drifting clouds + pixelated brick texture.
   ═══════════════════════════════════════════════════════════════════════════ */

/* Drifting clouds layer — soft puffs scrolling slowly across the sky */
/* ── Retro background — subtle dot grid on cream ─────────────────────────────
   Tiny repeating dots evoking aged paper / graph paper / Famicom cartridge
   label texture. Fixed so it shows uniformly behind ALL elements including
   widgets, logos, headers — no rectangles. */
html.theme-retro body,
body.theme-retro {
  background-color: #e8d5a3;
}
html.theme-retro body::before,
body.theme-retro::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  background-image:
    radial-gradient(circle, rgba(180,130,60,0.35) 1px, transparent 1px);
  background-size: 18px 18px;
}

/* No ::after overlay — nothing to create rectangle artifacts */

/* App shell above pattern */
html.theme-retro .app-shell,
body.theme-retro .app-shell {
  position: relative;
  z-index: 2;
}

/* Widgets — semi-transparent so dot pattern shows through uniformly */
html.theme-retro .module,
body.theme-retro .module {
  background: transparent;
  border-color: transparent;
  box-shadow: none;
}
/* Widget inner cards — subtle border matching the cream palette */
html.theme-retro .at-card,
html.theme-retro .pokemon-card,
html.theme-retro .today-listed-card,
html.theme-retro .alltime-card,
html.theme-retro .price-card,
html.theme-retro .ledger-card,
html.theme-retro .ledger-row,
html.theme-retro .wl-row,
body.theme-retro .at-card,
body.theme-retro .pokemon-card,
body.theme-retro .today-listed-card,
body.theme-retro .alltime-card,
body.theme-retro .price-card,
body.theme-retro .ledger-card,
body.theme-retro .ledger-row,
body.theme-retro .wl-row {
  border-color: rgba(180,130,60,0.25);
  background: var(--surface2);
}

/* Header — same cream, transparent so pattern flows through */
html.theme-retro header,
body.theme-retro header {
  background: rgba(232,213,163,0.7);
  border-bottom-color: rgba(180,130,60,0.35);
}

/* Section labels — Famicom dark brown, mono */
html.theme-retro .section-label,
html.theme-retro .module-header,
body.theme-retro .section-label,
body.theme-retro .module-header {
  color: #2c1810;
  font-family: var(--mono);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  font-size: 10px;
  opacity: 0.75;
}

/* Page tabs */
html.theme-retro .page-tab,
body.theme-retro .page-tab {
  border-color: rgba(180,130,60,0.45);
  color: #6b3a2a;
  background: rgba(232,213,163,0.4);
}
html.theme-retro .page-tab.active,
body.theme-retro .page-tab.active {
  background: rgba(212,22,60,0.12);
  border-color: #d4163c;
  color: #d4163c;
  box-shadow: 0 0 0 2px rgba(212,22,60,0.15);
}

/* Buttons */
html.theme-retro .seller-btn,
body.theme-retro .seller-btn {
  border-color: rgba(180,130,60,0.5);
  font-family: var(--mono);
  letter-spacing: 0.5px;
  color: #2c1810;
  background: rgba(232,213,163,0.5);
}
html.theme-retro .seller-btn:hover,
html.theme-retro .seller-btn:active,
body.theme-retro .seller-btn:hover,
body.theme-retro .seller-btn:active {
  background: rgba(232,213,163,0.85);
  border-color: rgba(180,130,60,0.9);
  color: #1a0e06;
}
html.theme-retro .seller-btn.primary,
body.theme-retro .seller-btn.primary {
  background: rgba(212,22,60,0.12);
  border-color: #d4163c;
  color: #d4163c;
}
html.theme-retro .seller-btn.primary:hover,
html.theme-retro .seller-btn.primary:active,
body.theme-retro .seller-btn.primary:hover,
body.theme-retro .seller-btn.primary:active {
  background: rgba(212,22,60,0.2);
}
/* Restore button (seller-btn.primary used in archived rows) should not
   appear red in retro — it's a safe restorative action, not destructive.
   Override to a neutral warm tone matching the retro base palette. */
html.theme-retro .ledger-row-actions .seller-btn.primary,
body.theme-retro .ledger-row-actions .seller-btn.primary {
  background: rgba(232,213,163,0.5);
  border-color: rgba(180,130,60,0.6);
  color: #5a3e1b;
}
html.theme-retro .ledger-row-actions .seller-btn.primary:hover,
html.theme-retro .ledger-row-actions .seller-btn.primary:active,
body.theme-retro .ledger-row-actions .seller-btn.primary:hover,
body.theme-retro .ledger-row-actions .seller-btn.primary:active {
  background: rgba(232,213,163,0.75);
  border-color: rgba(180,130,60,0.9);
}
html.theme-retro .seller-btn.danger,
body.theme-retro .seller-btn.danger {
  background: rgba(180,30,30,0.08);
  border-color: #c0392b;
  color: #c0392b;
}
html.theme-retro .seller-btn.danger:hover,
html.theme-retro .seller-btn.danger:active,
body.theme-retro .seller-btn.danger:hover,
body.theme-retro .seller-btn.danger:active {
  background: rgba(180,30,30,0.18);
}
html.theme-retro .seller-btn.success,
body.theme-retro .seller-btn.success {
  background: rgba(45,122,45,0.1);
  border-color: #2d7a2d;
  color: #2d7a2d;
}
html.theme-retro .seller-btn.success:hover,
html.theme-retro .seller-btn.success:active,
body.theme-retro .seller-btn.success:hover,
body.theme-retro .seller-btn.success:active {
  background: rgba(45,122,45,0.2);
}

/* Inputs */
html.theme-retro input,
html.theme-retro textarea,
html.theme-retro select,
body.theme-retro input,
body.theme-retro textarea,
body.theme-retro select {
  background: rgba(232,213,163,0.6);
  border-color: rgba(180,130,60,0.4);
  color: #2c1810;
  font-family: var(--mono);
}
html.theme-retro input:focus,
html.theme-retro textarea:focus,
html.theme-retro select:focus,
body.theme-retro input:focus,
body.theme-retro textarea:focus,
body.theme-retro select:focus {
  border-color: #d4163c;
  box-shadow: 0 0 0 2px rgba(212,22,60,0.15);
  outline: none;
}

/* Price */
html.theme-retro #ps-price,
body.theme-retro #ps-price {
  color: #2c1810;
  font-family: var(--mono);
}

/* Top rank numbers */
html.theme-retro .at-lb-num.top,
body.theme-retro .at-lb-num.top {
  color: #d4163c;
}

/* Movement arrows */
html.theme-retro .alltime-move.up,
body.theme-retro .alltime-move.up   { color: #2d7a2d; }
html.theme-retro .alltime-move.down,
body.theme-retro .alltime-move.down { color: #d4163c; }
html.theme-retro .alltime-move.new,
body.theme-retro .alltime-move.new  { color: #c8860a; }

/* Bar fills */
html.theme-retro .at-bar-fill,
html.theme-retro .at-lb-bar-inner,
body.theme-retro .at-bar-fill,
body.theme-retro .at-lb-bar-inner {
  background: linear-gradient(90deg, rgba(212,22,60,0.5), rgba(212,22,60,0.85));
}

/* (Removed a retro-only brown filter on .at-sprite-fallback svg — it recoloured
   the bolt/"?" fallbacks to brown ONLY in the all-time leaderboard, while the
   real Pokémon sprite canvas stayed indigo (__getSilhouetteColours has no retro
   case → indigo, shared with dark). That broke the "sprite, bolt and ? all use
   ONE uniform colour" rule. Now the bolt matches the sprites in retro again.) */

/* Tier badges */
/* Tier badges — use global defaults (uniform across all themes) */

@media (prefers-reduced-motion: reduce) {
  html.theme-retro body::before,
  body.theme-retro::before {
    animation: none;
  }
}


/* Large variant for pricing page cards */
.tier-badge.large {
  font-size: 13px;
  padding: 6px 14px 6px 8px;
  gap: 8px;
  border-radius: 7px;
  border-width: 2px;
}
.tier-badge.large svg, .tier-badge.large img { width: 22px; height: 22px; }

/* Settings row — compact inline */
.tier-badge.small {
  font-size: 9px;
  padding: 2px 7px 2px 4px;
  gap: 4px;
  border-radius: 4px;
}
.tier-badge.small svg, .tier-badge.small img { width: 12px; height: 12px; }

.dashboard-header-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 6px;
}


/* ── Scanner Entity sprite canvas ─────────────────────────────────────────
   The hologram canvas replaces each sprite <img> after pixel processing.
   A noise-flicker animation simulates the hologram shimmer — two slightly
   different opacities cycling at random-feeling speed. Static flicker is
   handled by requestAnimationFrame in utils.js, not CSS keyframes — each
   sprite gets its own RAF loop with a seeded LCG for unique rhythm. */

/* The CSS class applied to each processed sprite img. The filter stack
   is applied inline by JS (so it can be adjusted per context if needed).
   This class just marks the element and ensures it renders cleanly. */
.poke-scanner-img {
  image-rendering: pixelated;
}
.poke-scanner-canvas {
  /* kept for back-compat if any old canvas element exists in DOM */
}


/* ── Dashboard badge row ─────────────────────────────────────────────────── */
.dashboard-badge-row { display: none; }

/* ── Tier button — left-side mirror of the gear button ────────────────────
   Identical style to .customise-btn / .theme-btn: fixed 32x32 circle.
   Sits at left:20px top:56px — exact mirror of gear at right:20px top:56px. */
.tier-btn {
  flex-shrink: 0;
  position: relative;
  z-index: 91;
  height: 28px;
  width: 28px;
  border-radius: 50%;
  border: 2px solid var(--border2);
  background: var(--surface2);
  box-shadow:
    inset 0 1px 2px rgba(255,255,255,0.06),
    inset 0 -1px 2px rgba(0,0,0,0.3),
    0 1px 3px rgba(0,0,0,0.4);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  -webkit-tap-highlight-color: transparent;
  transition: box-shadow 0.15s, border-color 0.15s;
}
.tier-btn:active { transform: scale(0.94); }
@media (hover: hover) {
  .tier-btn:hover {
    border-color: var(--blue);
    box-shadow: inset 0 1px 2px rgba(255,255,255,0.06), inset 0 -1px 2px rgba(0,0,0,0.3), 0 0 6px var(--blue-dim);
  }
}
.tier-btn .tier-badge,
html[class] .tier-btn .tier-badge,
body[class] .tier-btn .tier-badge {
  background: none !important;
  border: none !important;
  box-shadow: none !important;
  padding: 0;
  gap: 0;
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.tier-btn .tier-badge svg,
.tier-btn .tier-badge img {
  width: 24px;
  height: 24px;
}

/* ── Sparkle tap animation — three ✦ particles burst out on tap ──────────
   Triggered by JS adding .sparkling class. Three child .sparkle spans
   are injected and removed after the animation. */
@keyframes sparkle-out-0 {
  0%   { transform: translate(0,0) scale(1);   opacity: 1; }
  100% { transform: translate(-18px,-22px) scale(0.3); opacity: 0; }
}
@keyframes sparkle-out-1 {
  0%   { transform: translate(0,0) scale(1);   opacity: 1; }
  100% { transform: translate(14px,-26px) scale(0.2); opacity: 0; }
}
@keyframes sparkle-out-2 {
  0%   { transform: translate(0,0) scale(1);   opacity: 1; }
  100% { transform: translate(20px,-10px) scale(0.4); opacity: 0; }
}
.badge-sparkle {
  position: absolute;
  pointer-events: none;
  font-size: 13px;
  top: 50%;
  left: 50%;
  margin: -7px 0 0 -7px;
  line-height: 1;
  color: #fbbf24;
  animation-duration: 0.7s;
  animation-fill-mode: forwards;
  animation-timing-function: ease-out;
  z-index: 200;
}
.badge-sparkle.s0 { animation-name: sparkle-out-0; }
.badge-sparkle.s1 { animation-name: sparkle-out-1; }
.badge-sparkle.s2 { animation-name: sparkle-out-2; color: #c084fc; }

/* ── Theme option lock state ─────────────────────────────────────────────── */
.theme-option.theme-locked {
  opacity: 0.45;
  cursor: not-allowed;
  pointer-events: none; /* allow display but block tap */
  pointer-events: auto; /* override — we show it but JS blocks action */
}
.theme-lock-badge {
  margin-left: auto;
  font-size: 8px;
  font-weight: 700;
  letter-spacing: 0.05em;
  color: var(--hint);
  font-family: var(--mono);
  text-transform: uppercase;
  white-space: nowrap;
}

/* ── Sprite loading state — three pulsing dots ────────────────────────────
   Shown while the proxy fetch is in progress. Colour is set via --sc CSS
   variable injected inline per-theme by __getLoadingHTML(). Replaced by
   the canvas on success, or by the ? mark on confirmed error. */
@keyframes sprite-dot-pulse {
  0%, 80%, 100% { opacity: 0.15; transform: scale(0.7); }
  40%           { opacity: 0.7;  transform: scale(1.0); }
}
.sprite-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 2px;
  width: 100%;
  height: 100%;
}
.sprite-loading span {
  width: 3px;
  height: 3px;
  border-radius: 50%;
  background: var(--sc, #4a4a55);
  animation: sprite-dot-pulse 1.2s ease-in-out infinite;
}
.sprite-loading span:nth-child(2) { animation-delay: 0.2s; }
.sprite-loading span:nth-child(3) { animation-delay: 0.4s; }

/* ── Countries donut chart widget ───────────────────────────────────────── */
.dashboard-countries-wrap {
  padding: 8px 0 4px;
}
.cc-chart-inner {
  display: flex;
  align-items: flex-start;
  gap: 20px;
}
/* Donut */
.cc-donut-wrap {
  position: relative;
  flex: 0 0 110px;
  width: 110px;
  height: 110px;
}
.cc-donut-svg {
  width: 100%;
  height: 100%;
  transform: rotate(-90deg);
  /* slices are drawn with pathLength=100 so dasharray values = percentages */
}
.cc-donut-slice {
  transition: opacity 0.15s;
}
.cc-donut-centre {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  pointer-events: none;
}
.cc-donut-total {
  font-size: 15px;
  font-weight: 700;
  line-height: 1.1;
  color: var(--text);
  letter-spacing: -0.02em;
}
.cc-donut-label {
  font-size: 9px;
  color: var(--text2);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-top: 1px;
}
/* Bar legend */
.cc-bars {
  flex: 1 1 0;
  display: flex;
  flex-direction: column;
  gap: 7px;
  min-width: 0;
  padding-top: 2px;
}
.cc-bar-row {
  display: flex;
  flex-direction: column;
  gap: 3px;
}
.cc-bar-meta {
  display: flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
}
.cc-bar-dot {
  flex: 0 0 7px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
}
.cc-bar-name {
  flex: 1 1 0;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cc-bar-count {
  font-size: 11px;
  color: var(--text2);
  font-variant-numeric: tabular-nums;
}
.cc-bar-pct {
  font-size: 11px;
  font-weight: 600;
  color: var(--text2);
  font-variant-numeric: tabular-nums;
  min-width: 36px;
  text-align: right;
}
.cc-bar-track {
  height: 3px;
  background: var(--border2);
  border-radius: 99px;
  overflow: hidden;
}
.cc-bar-fill {
  height: 100%;
  border-radius: 99px;
  opacity: 0.85;
  transition: width 0.4s cubic-bezier(0.4,0,0.2,1);
}
.cc-other-note {
  font-size: 10.5px;
  color: var(--text2);
  margin: 2px 0 0;
  opacity: 0.7;
}
@media (max-width: 400px) {
  .cc-donut-wrap { flex: 0 0 88px; width: 88px; height: 88px; }
  .cc-donut-total { font-size: 13px; }
}
.cc-chart-inner {
  display: flex;
  align-items: center;
  gap: 20px;
  flex-wrap: wrap;
}
.cc-pie {
  flex: 0 0 140px;
  width: 140px;
  height: 140px;
  filter: drop-shadow(0 2px 6px rgba(0,0,0,0.25));
}
.cc-legend {
  flex: 1 1 120px;
  display: flex;
  flex-direction: column;
  gap: 6px;
  max-height: 180px;
  overflow-y: auto;
}
.cc-legend::-webkit-scrollbar { width: 3px; }
.cc-legend::-webkit-scrollbar-track { background: transparent; }
.cc-legend::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 2px; }
.cc-legend-row {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12px;
  line-height: 1;
}
.cc-legend-swatch {
  flex: 0 0 10px;
  width: 10px;
  height: 10px;
  border-radius: 2px;
}
.cc-legend-code {
  flex: 0 0 28px;
  font-weight: 600;
  font-size: 11px;
  color: var(--text);
  letter-spacing: 0.04em;
}
.cc-legend-pct {
  flex: 0 0 40px;
  color: var(--text2);
  font-size: 11px;
}
.cc-legend-count {
  color: var(--text2);
  font-size: 11px;
}
@media (max-width: 400px) {
  .cc-pie { flex: 0 0 110px; width: 110px; height: 110px; }
}

/* ── Card row popup ─────────────────────────────────────────────────────────── */
.at-lb-row-tappable {
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  transition: background 0.12s;
  touch-action: manipulation;
}
.at-lb-row-tappable:hover,
.at-lb-row-tappable:active {
  background: var(--surface2);
  border-radius: 8px;
}
/* Popup is appended to <body> and uses fixed positioning via JS so it
   escapes all overflow/clip containers in the widget scroll area. */
.card-action-popup {
  min-width: 190px;
  z-index: 9999;
}
.card-action-popup .ledger-sort-option {
  touch-action: manipulation;
}

/* ── Lookup loading text ─────────────────────────────────────────────────────── */
#lookup-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 10px 0 4px;
}
.loading-phrase {
  font-size: 11.5px;
  color: var(--text2);
  font-family: var(--mono);
  text-align: center;
  height: 30px;
  line-height: 15px;
  overflow: hidden;
  max-width: 300px;
  width: 100%;
  padding: 0 8px;
  transition: opacity 0.4s;
  letter-spacing: 0.01em;
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
}

/* ── Lookup searched / loading phrase line ──────────────────────────────────── */
#ps-searched {
  transition: opacity 0.4s;
  height: 16px;
  line-height: 16px;
  overflow-x: auto;
  overflow-y: hidden;
  display: block !important;
  box-sizing: border-box;
  white-space: nowrap;
  /* Hide scrollbar visually but keep it scrollable */
  scrollbar-width: none;
  -ms-overflow-style: none;
}
#ps-searched::-webkit-scrollbar {
  display: none;
}

/* ═══════════════════════════════════════════════════════════════════════════
   ACHIEVEMENTS — medal button, trophy case modal, dirt overlays, sparkles
   ═══════════════════════════════════════════════════════════════════════════ */

/* ── Medal button — sits in same floating column as pencil/theme ───────── */
/* medal-btn layout defined inside @media (max-width:700px) above */
.medal-btn {
  cursor: pointer;
  transition: opacity 0.3s ease, border-color 0.15s, color 0.15s;
}
@media (hover: hover) {
  .medal-btn:hover { border-color: var(--blue); color: var(--blue); }
}
.medal-btn:focus, .medal-btn:focus-visible { outline: none; }
.medal-btn.active { background: var(--blue-dim); border-color: var(--blue); color: var(--blue); }

/* ── Badges modal ─────────────────────────────────────────────────────── */
.badges-modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0);
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 5vh 5vw;
  -webkit-backdrop-filter: blur(0px);
  backdrop-filter: blur(0px);
  transition: background 0.3s ease, backdrop-filter 0.3s ease;
  /* Prevent touches passing through to ledger behind */
  touch-action: none;
  overscroll-behavior: contain;
}
.badges-modal-overlay.open {
  background: rgba(0,0,0,0.35);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}
.badges-modal-overlay.closing {
  background: rgba(0,0,0,0);
  -webkit-backdrop-filter: blur(0px);
  backdrop-filter: blur(0px);
}
.badges-modal-card {
  width: 100%;
  max-width: 900px;
  height: 90vh;
  max-height: 800px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  box-shadow: 0 20px 60px rgba(0,0,0,0.5);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  transform: translateY(32px) scale(0.97);
  opacity: 0;
  transition: transform 0.32s cubic-bezier(0.34, 1.2, 0.64, 1), opacity 0.28s ease;
  touch-action: pan-y;
  overscroll-behavior: contain;
}
.badges-modal-overlay.open .badges-modal-card {
  transform: translateY(0) scale(1);
  opacity: 1;
}
.badges-modal-overlay.closing .badges-modal-card {
  transform: translateY(24px) scale(0.97);
  opacity: 0;
}
/* #112: the upgrade picker reuses the badges modal chrome, just compact —
   the card hugs its content instead of the badges' full-height layout. */
.upgrade-modal-card { max-width: 420px; height: auto; max-height: 85vh; }
.upgrade-modal-body { padding: 16px 24px 20px; overflow-y: auto; }
.badges-modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 24px;
  border-bottom: 1px solid var(--border2);
  flex-shrink: 0;
}
.badges-modal-title {
  margin: 0;
  font-size: 18px;
  font-weight: 600;
  color: var(--text);
}
.badges-modal-close {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  border: 1px solid var(--border2);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-size: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.badges-modal-close:hover { border-color: var(--red); color: var(--red); }

/* ── Badge case — 2×4 grid of glass-fronted slots ────────────────────── */
.badge-case {
  padding: 22px 24px;
  border-bottom: 1px solid var(--border2);
  flex-shrink: 0;
  background:
    linear-gradient(180deg, rgba(0,0,0,0.18), rgba(0,0,0,0.05));
  touch-action: none;
  overscroll-behavior: contain;
  -webkit-user-select: none;
  user-select: none;
}
.badge-case-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
  max-width: 560px;
  margin: 0 auto;
}
.badge-slot {
  aspect-ratio: 1 / 1;
  border-radius: 12px;
  background:
    linear-gradient(180deg, rgba(255,255,255,0.04), rgba(0,0,0,0.22)),
    var(--bg);
  border: 1px solid var(--border2);
  box-shadow:
    inset 0 2px 6px rgba(0,0,0,0.55),
    inset 0 -1px 2px rgba(255,255,255,0.04),
    0 1px 0 rgba(255,255,255,0.04);
  display: flex;
  align-items: center;
  justify-content: center;
  position: relative;
  overflow: hidden;
}
.badge-slot::before {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(135deg, rgba(255,255,255,0.10) 0%, transparent 45%);
  pointer-events: none;
  z-index: 3;
}
.badge-slot-empty::after {
  content: "";
  position: absolute;
  inset: 14%;
  border: 2px dashed var(--border);
  border-radius: 50%;
  opacity: 0.5;
}
.badge-slot-badge {
  position: relative;
  width: 88%;
  height: 88%;
  z-index: 2;
  cursor: pointer;
  touch-action: none; /* allow free movement for wipe gesture */
  user-select: none;
  -webkit-user-select: none;
}
.badge-slot-badge img,
.badge-slot-badge svg {
  width: 100%;
  height: 100%;
  display: block;
}

/* ── Dirt overlays — three intensity levels via CSS filter + texture ───── */
.badge-slot-badge[data-dirt="1"] img { filter: brightness(0.92) saturate(0.85) sepia(0.15); }
.badge-slot-badge[data-dirt="2"] img { filter: brightness(0.78) saturate(0.6)  sepia(0.3);  }
.badge-slot-badge[data-dirt="3"] img { filter: brightness(0.6)  saturate(0.35) sepia(0.5);  }
.badge-slot-badge[data-dirt="1"]::after,
.badge-slot-badge[data-dirt="2"]::after,
.badge-slot-badge[data-dirt="3"]::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  pointer-events: none;
  background-image:
    radial-gradient(circle at 30% 25%, rgba(60,40,20,0.55) 0%, transparent 20%),
    radial-gradient(circle at 70% 60%, rgba(40,25,10,0.5)  0%, transparent 18%),
    radial-gradient(circle at 20% 75%, rgba(50,30,15,0.45) 0%, transparent 16%),
    radial-gradient(circle at 80% 30%, rgba(45,28,12,0.4)  0%, transparent 14%),
    radial-gradient(circle at 50% 50%, rgba(35,22,10,0.35) 0%, transparent 22%);
  mix-blend-mode: multiply;
}
.badge-slot-badge[data-dirt="1"]::after { opacity: 0.35; }
.badge-slot-badge[data-dirt="2"]::after { opacity: 0.65; }
.badge-slot-badge[data-dirt="3"]::after { opacity: 0.95; }

/* Cleaning sparks — injected by JS, burst radially from badge centre */
.clean-spark {
  position: absolute;
  top: 50%;
  left: 50%;
  font-size: 14px;
  line-height: 1;
  pointer-events: none;
  z-index: 99;
  text-shadow: 0 0 6px currentColor, 0 0 12px currentColor;
  animation: clean-spark-out 0.75s ease-out forwards;
  transform-origin: center center;
}
@keyframes clean-spark-out {
  0%   { opacity: 1; transform: translate(-50%, -50%) scale(1.2); }
  30%  { opacity: 1; }
  100% { opacity: 0; transform: translate(calc(-50% + var(--sx)), calc(-50% + var(--sy))) scale(0.3); }
}

/* ── Badge list — ledger-style rows ───────────────────────────────────── */
.badges-list-section {
  flex: 1;
  overflow-y: auto;
  padding: 16px 24px 24px;
  overscroll-behavior: contain;
  touch-action: pan-y;
}
.badges-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}
.badge-row {
  display: grid;
  /* #99: fixed count column (was auto) so the top row's columns are identical on
     EVERY badge. #110: the progress bar is its own full-width row spanning all
     three columns, so it's perfectly centred in the tile with equal space (the
     14px tile padding) on the left and right — identical for every badge. */
  grid-template-columns: 44px 1fr 118px;
  grid-template-areas:
    "icon body count"
    "bar  bar  bar";
  column-gap: 14px;
  row-gap: 10px;
  align-items: center;
  padding: 12px 14px;
  background: var(--bg);
  border: 1px solid var(--border2);
  border-radius: 10px;
  transition: border-color 0.15s;
}
.badge-row-icon  { grid-area: icon; }
.badge-row-body  { grid-area: body; }
.badge-row-count { grid-area: count; }
.badge-row-bar   { grid-area: bar; }
.badge-row.earned { border-color: rgba(251,191,36,0.4); }
.badge-row-icon {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
}
.badge-row-icon img { width: 100%; height: 100%; display: block; }
.badge-row-icon.locked img { filter: grayscale(1) brightness(0.55); opacity: 0.5; }
.badge-row-body { min-width: 0; }
.badge-row-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 2px;
  /* #241: keep the badge name on one line (it wrapped on mobile) — ellipsis if
     it ever can't fit. The body already has min-width:0 so the grid lets it shrink. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.badge-row-desc {
  font-size: 11px;
  color: var(--muted);
  line-height: 1.35;
}
.badge-row-bar {
  position: relative;
  height: 4px;
  background: var(--border2);
  border-radius: 2px;
  overflow: hidden;
}
.badge-row-bar-fill {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--blue);
  border-radius: 2px;
  transition: width 0.4s;
}
.badge-row.earned .badge-row-bar-fill {
  background: linear-gradient(90deg, #fbbf24, #f59e0b);
}
/* #245: on phones the fixed count column (sized for the widest atlas count
   "1/3 · 2/6 · 5/9") squeezed the name into an ellipsis. Drop the count BELOW
   the body so the name gets the full row width and fits on one line. */
@media (max-width: 560px) {
  .badge-row {
    grid-template-columns: 40px 1fr;
    grid-template-areas:
      "icon body"
      "icon count"
      "bar  bar";
    column-gap: 11px;
    row-gap: 5px;
  }
  .badge-row-count { justify-self: start; text-align: left; }
}
.badge-row-count {
  font-size: 11px;
  color: var(--hint);
  font-family: var(--mono);
  white-space: nowrap;
  align-self: center;
  text-align: right;   /* #99: counts align to a common right edge */
}
.badge-row.earned .badge-row-count {
  color: #fbbf24;
}
/* Retro theme — gold on cream is low contrast, use dark text */
html.theme-retro .badge-row.earned .badge-row-count,
body.theme-retro .badge-row.earned .badge-row-count,
html.theme-light .badge-row.earned .badge-row-count,
body.theme-light .badge-row.earned .badge-row-count {
  color: var(--red);
}

/* ── Badge-earned congrats popup ──────────────────────────────────────── */
.badge-earned-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.75);
  z-index: 1100;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  animation: fade-in 0.3s ease;
}
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }
.badge-earned-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 16px;
  padding: 32px 28px 24px;
  max-width: 360px;
  width: 100%;
  text-align: center;
  position: relative;
  box-shadow: 0 20px 60px rgba(0,0,0,0.5);
  animation: pop-in 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}
@keyframes pop-in {
  0%   { transform: scale(0.5); opacity: 0; }
  100% { transform: scale(1);   opacity: 1; }
}
.badge-earned-spark {
  position: absolute;
  width: 12px;
  height: 12px;
  color: #fbbf24;
  font-size: 20px;
  line-height: 1;
  pointer-events: none;
}
.badge-earned-spark::before { content: "✦"; }
.badge-earned-spark:nth-child(1) { top: 8%;  left: 12%; animation: spark-twinkle 1.8s ease-in-out infinite;       }
.badge-earned-spark:nth-child(2) { top: 18%; right: 10%; animation: spark-twinkle 1.8s ease-in-out 0.6s infinite; }
.badge-earned-spark:nth-child(3) { bottom: 28%; left: 8%; animation: spark-twinkle 1.8s ease-in-out 1.2s infinite; }
@keyframes spark-twinkle {
  0%, 100% { opacity: 0;   transform: scale(0.5) rotate(0deg);  }
  50%      { opacity: 1;   transform: scale(1.2) rotate(180deg); }
}
.badge-earned-title {
  margin: 0 0 16px;
  font-size: 16px;
  font-weight: 600;
  color: #fbbf24;
  letter-spacing: 0.02em;
}
.badge-earned-img-wrap {
  width: 106px;
  height: 106px;
  margin: 0 auto 14px;
  animation: badge-shimmer 2s ease-in-out infinite;
}
.badge-earned-img-wrap img,
.badge-earned-img-wrap svg { width: 100%; height: 100%; display: block; }
@keyframes badge-shimmer {
  0%, 100% { filter: drop-shadow(0 0 6px rgba(251,191,36,0.3));  transform: scale(1);    }
  50%      { filter: drop-shadow(0 0 14px rgba(251,191,36,0.7)); transform: scale(1.04); }
}
.badge-earned-name {
  font-size: 18px;
  font-weight: 700;
  color: var(--text);
  margin-bottom: 4px;
}
.badge-earned-desc {
  font-size: 13px;
  color: var(--muted);
  line-height: 1.4;
}

/* ═══════════════════════════════════════════════════════════════════════════
   PINS — small gold stars in the badges modal header
   ═══════════════════════════════════════════════════════════════════════════ */

.badges-modal-title-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
#modal-pin-stack {
  display: flex;
  align-items: center;
  gap: 4px;
}
.modal-pin {
  width: 18px;
  height: 18px;
  border: none;
  background: transparent;
  padding: 0;
  cursor: pointer;
  -webkit-appearance: none;
  appearance: none;
  flex-shrink: 0;
  filter: drop-shadow(0 1px 2px rgba(0,0,0,0.35));
  transition: transform 0.12s ease;
  -webkit-tap-highlight-color: transparent;
  outline: none;
}
.modal-pin:hover, .modal-pin:active, .modal-pin:focus { transform: scale(1.15); background: transparent; outline: none; }
.modal-pin img { width: 100%; height: 100%; display: block; pointer-events: none; }

/* Pin tooltip */
.tier-pin-tooltip {
  position: fixed;
  z-index: 1500;
  max-width: 240px;
  padding: 10px 14px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.4);
  opacity: 0;
  transform: translateY(-6px);
  transition: opacity 0.2s ease, transform 0.2s ease;
  pointer-events: none;
}
.tier-pin-tooltip.visible {
  opacity: 1;
  transform: translateY(0);
}
.tier-pin-tooltip-item + .tier-pin-tooltip-item {
  margin-top: 8px;
  padding-top: 8px;
  border-top: 1px solid var(--border2);
}
.tier-pin-tooltip-name {
  font-size: 13px;
  font-weight: 600;
  color: var(--text);
  margin-bottom: 2px;
}
.tier-pin-tooltip-desc {
  font-size: 11px;
  color: var(--muted);
  line-height: 1.4;
}


.dashboard-fine-print {
  text-align: center;
  font-size: 10px;
  color: var(--hint);
  margin: 16px 0 8px;
  letter-spacing: 0.03em;
  /* #284: hidden until the dashboard has painted real content, so it never
     flashes at the top of a half-loaded page and then drops to the bottom. */
  display: none;
}
.dashboard-fine-print.ready { display: block; }

/* ── Ledger search bar ────────────────────────────────────────────────── */
.ledger-search-bar {
  order: 4;
  width: 100%;
}
.ledger-search-inner {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 6px 0 2px;
}
.ledger-search-icon { flex-shrink: 0; color: var(--muted); }
.ledger-search-input {
  flex: 1;
  background: transparent;
  border: none;
  outline: none;
  box-shadow: none;
  font-size: 13px;
  color: var(--text);
  font-family: var(--font);
  -webkit-appearance: none;
  appearance: none;
}
.ledger-search-input::placeholder { color: var(--hint); }
.ledger-search-input::-webkit-search-cancel-button { display: none; }
.ledger-search-input:focus { outline: none; box-shadow: none; border: none; }
/* All themes — no outline, border or highlight on focus */
html.theme-retro .ledger-search-input,
body.theme-retro .ledger-search-input,
html.theme-light .ledger-search-input,
body.theme-light .ledger-search-input {
  background: transparent;
  outline: none;
  box-shadow: none;
  border: none;
}
html.theme-retro .ledger-search-input:focus,
body.theme-retro .ledger-search-input:focus {
  outline: none !important;
  box-shadow: none !important;
  border: none !important;
  -webkit-appearance: none;
}
.ledger-search-clear {
  background: transparent;
  border: none;
  color: var(--muted);
  font-size: 12px;
  cursor: pointer;
  padding: 2px 4px;
  display: none;
  line-height: 1;
  -webkit-tap-highlight-color: transparent;
}
.ledger-search-clear.visible { display: block; }

/* ── Watchlist widget — ledger-row style ─────────────────────────────────── */
/* Watchlist rows reuse .ledger-row, .ledger-row-header etc. from ledger CSS. */
/* Only watchlist-specific overrides here. */

/* #262: variant chooser shown in the shared modal when adding a multi-variant card
   to the watchlist. Chips mirror the lookup .ps-variant-chip styling. */
.wl-var-hint { font-size: 12.5px; color: var(--muted); line-height: 1.45; margin-bottom: 12px; }
.wl-var-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.wl-var-chip {
  border: 1px solid var(--border2); background: var(--surface); color: var(--text);
  border-radius: 999px; padding: 7px 14px; font-size: 12.5px; cursor: pointer;
  font-family: var(--mono); white-space: nowrap; transition: all .15s ease;
}
.wl-var-chip:hover { border-color: var(--blue); background: var(--blue-dim); color: var(--blue); }

/* #253: costs & fees editor (Settings). Each fee is a small card — name input on
   top, %/$ toggle + value below, and a × remove button on the top-right corner
   (mirrors the CardEdge GST widget). */
.fees-grid { display: flex; flex-wrap: wrap; gap: 11px; }
.fees-field {
  position: relative;
  /* #281: don't grow — a lone or odd-last box stays HALF width (left-aligned),
     matching how a pair looks, instead of stretching to fill the row. The grid
     gap is 11px, so two per row each take calc(50% - 5.5px). */
  flex: 0 1 calc(50% - 5.5px); min-width: 120px; max-width: calc(50% - 5.5px);
  display: flex; flex-direction: column; gap: 6px;
  background: var(--surface); border: 1px solid var(--border2);
  border-radius: 9px; padding: 9px 10px 10px;
}
.fees-field .fees-name { font-size: 12px; font-weight: 500; }
.fees-field .seller-fees-mode { flex: 0 0 auto; }
.fees-field .fees-value { flex: 1 1 auto; min-width: 0; }
.fees-remove {
  position: absolute; top: -7px; right: -7px;
  width: 18px; height: 18px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--border2); border-radius: 50%;
  background: var(--surface2); color: var(--muted);
  font-size: 13px; line-height: 1; cursor: pointer; transition: all .15s ease;
}
.fees-remove:hover { border-color: var(--red); color: var(--red); }

/* #257B: a paused (over-limit / manually paused) watchlist row reads as dimmed —
   it isn't firing price refreshes. The sprite + price still show their last data. */
.wl-row-paused .ledger-row-sprite,
.wl-row-paused .ledger-row-main,
.wl-row-paused .ledger-row-pnl { opacity: 0.5; }
.wl-row-body {
  padding: 10px 12px 12px;
  display: none;
  flex-direction: column;
  gap: 10px;
}
.wl-row.expanded .wl-row-body { display: flex; }
.wl-row.expanded .wl-row-header { border-bottom: 1px solid var(--border2); }

.wl-sparkline {
  display: block;
  width: 100%;
  height: 80px;   /* compact for the row (mobile); date-spaced X keeps moves sharp without needing the full lookup-chart height */
  border-radius: 6px;
}

/* Watchlist status lines — single-line horizontal scroll for long messages
   (same pattern as #ps-searched: scrollable, scrollbar hidden). */
.wl-status-line {
  white-space: nowrap; overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none; -ms-overflow-style: none;
}
.wl-status-line::-webkit-scrollbar { display: none; }

/* Locked block */
.tier-locked-block {
  display: flex;
  align-items: center;
  justify-content: center;   /* #273: centre the "Upgrade to Trader to unlock Watchlist" row */
  gap: 8px;
  padding: 12px;
  color: var(--hint);
  font-size: 12px;
  font-family: var(--mono);
}
.tier-locked-icon { font-size: 14px; }

#watchlist-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* Fetching price animation on watchlist row */
.wl-fetching {
  animation: wl-pulse 1.2s ease-in-out infinite;
}
@keyframes wl-pulse {
  0%,100% { opacity: 0.3; }
  50%      { opacity: 1; }
}

/* Scrollable card name — same pattern as #ps-searched */
.wl-card-name {
  overflow-x: auto;
  overflow-y: hidden;
  white-space: nowrap;
  text-overflow: unset;
  scrollbar-width: none;
  -ms-overflow-style: none;
}
.wl-card-name::-webkit-scrollbar { display: none; }

/* Lookup history dropdown row — scrollable name, match seller-input font */
.lookup-history-row { font-family: var(--mono) !important; font-size: 12px !important; padding: 5px 8px !important; text-align: left !important; }
.lookup-history-row span:first-child::-webkit-scrollbar { display: none; }

/* ════════════════════════════════════════════════════════════════════════════
   DESKTOP FRAME (Tier 1) — left nav rail + framed column + branded backdrop.
   Everything below is gated behind the desktop breakpoint. Under it the app is
   byte-for-byte the existing mobile layout: the rail is display:none and the
   top tabs + in-shell header render as before. Touch nothing outside the media
   query for mobile parity.
   ════════════════════════════════════════════════════════════════════════════ */
#desktop-rail { display: none; }

@media (min-width: 1024px) {
  /* Branded backdrop + reserve the rail's width on the left so the column
     centres in the space beside it (balanced gaps), with extra top room so the
     content clears the floating pencil now that the in-shell header is hidden.
     Scoped to body.surge-app — this is a GLOBAL selector otherwise and would
     shove every other page that links style.css (e.g. /todo, /login) to the
     right by the rail width.
     ⚠️ Set ALL FOUR paddings here (shorthand). The base `body { padding: 2rem }`
     would otherwise leak its 2rem padding-RIGHT through, making the right gap
     32px wider than the left — the column looked off-centre. Equal breathing
     room each side (rail width + 2rem on the left, 2rem on the right) keeps
     margin:0 auto truly centred beside the rail. */
  body.surge-app {
    padding: 4rem 2rem 2rem calc(222px + 2rem);
    background:
      radial-gradient(115% 70% at 14% -5%,
        color-mix(in srgb, var(--surface) 55%, var(--bg)) 0%,
        var(--bg) 50%);
  }

  /* ── The rail ── */
  #desktop-rail {
    display: flex;
    flex-direction: column;
    gap: 5px;
    position: fixed;
    left: 0; top: 0; bottom: 0;
    width: 222px;
    box-sizing: border-box;
    padding: 22px 14px;
    border-right: 1px solid var(--border);
    background: color-mix(in srgb, var(--bg) 70%, #000 0%);
    z-index: 95;
  }
  /* Shrink the logo link to the logo itself (flex would otherwise stretch it to
     full rail width, making the whole top row — including beside the tier badge
     — a navigation hit-target → accidental refresh). */
  .rail-logo-link { display: block; line-height: 0; align-self: flex-start; }
  .rail-logo { height: 24px; width: auto; margin: 2px 10px 24px; }
  .rail-nav { display: flex; flex-direction: column; gap: 5px; }
  .rail-nav-item {
    display: flex; align-items: center; gap: 12px;
    padding: 10px 12px; border-radius: 10px;
    color: var(--muted); font-family: var(--sans); font-size: 14px; font-weight: 500;
    cursor: pointer; border: 1px solid transparent; background: none;
    text-align: left; width: 100%; transition: all 0.15s;
  }
  .rail-nav-item:hover { color: var(--text); background: color-mix(in srgb, var(--surface) 55%, transparent); }
  .rail-nav-item.active { background: var(--blue-dim); border-color: var(--blue); color: var(--blue); }
  .rail-nav-item[hidden] { display: none; }
  .rail-ico { display: flex; align-items: center; justify-content: center; width: 18px; height: 18px; flex: 0 0 auto; opacity: 0.92; }
  .rail-ico svg { width: 18px; height: 18px; display: block; }
  /* #49: a small keycap on the right of each tab hints its number-key shortcut. */
  .rail-key {
    margin-left: auto; flex: 0 0 auto; font-family: var(--mono); font-size: 10px; font-weight: 600;
    color: var(--hint); background: color-mix(in srgb, var(--surface) 70%, transparent);
    border: 1px solid var(--border2); border-bottom-width: 2px; border-radius: 5px;
    min-width: 17px; height: 17px; display: inline-flex; align-items: center; justify-content: center;
    padding: 0 4px; line-height: 1;
  }
  .rail-nav-item.active .rail-key { color: var(--blue); border-color: var(--blue); }
  /* #86: locked Analytics for lower tiers — dimmed, 🔒 in place of the keycap. */
  .rail-lock { margin-left: auto; font-size: 11px; display: none; }
  .rail-nav-item.rail-locked { opacity: 0.6; }
  .rail-nav-item.rail-locked:hover { opacity: 0.85; }
  .rail-nav-item.rail-locked .rail-key { display: none; }
  .rail-nav-item.rail-locked .rail-lock { display: inline; }
  /* #111: simple fade-in/out notice under the Settings label when a locked
     Analytics is clicked. Same fade mechanic as the lookup status text. */
  #rail-lock-alert {
    font-family: var(--mono); font-size: 10px; color: var(--hint);
    padding: 6px 12px 0; line-height: 1.4;
    opacity: 0; transition: opacity 0.35s; pointer-events: none;
  }

  /* Footer (Theme + Badges) reuses the .rail-nav-item styling for uniformity. */
  .rail-foot { margin-top: auto; padding-top: 14px; border-top: 1px solid var(--border); display: flex; flex-direction: column; gap: 5px; }

  /* The rail replaces the top tabs + the in-shell logo on desktop. The header
     stays rendered (collapsed) so the tier badge — a child of it — can show,
     and is lifted above the rail so the badge is clickable (otherwise the rail's
     logo link, which paints over it, swallows the click → page refresh). The
     theme button carries an inline display:flex, so its hide needs !important. */
  .app-shell > header { padding: 0; margin: 0; min-height: 0; background: none; z-index: 102; }
  .app-shell > header > .header-inner,
  .app-shell > header > .header-spacer { display: none; }
  .page-tabs { display: none; }
  .theme-btn { display: none !important; }
  .gear-btn { display: none !important; }    /* Settings is in the rail on desktop */
  .medal-btn { display: none !important; }   /* badges live in the rail footer now */

  /* Tier badge (the sparkle one) — surfaced top-left, in the rail beside the
     logo. Fixed to the viewport; the header's high z-index keeps it on top. */
  #dashboard-tier-badge { position: fixed; top: 20px; left: 178px; z-index: 102; }

  /* Content column — a touch wider than mobile, centred beside the rail. */
  .app-shell { max-width: 520px; }

  /* Analytics breathes on desktop (within the column): a taller profit chart
     and the stat tiles laid out 4-up instead of 2×2. (#18 Tier 2 — quick win) */
  body.surge-app .la-chart { height: 200px; }
  body.surge-app .la-tiles { grid-template-columns: repeat(4, 1fr); }
  body.surge-app .la-range { justify-content: flex-start; }   /* #236: centred on mobile only — keep desktop left-aligned */
  /* #equity: controls on ONE row on desktop — date pills left, view toggle right. */
  body.surge-app .la-controls { flex-direction: row; align-items: center; justify-content: space-between; }
  body.surge-app .la-controls .alltime-tabs, body.surge-app .la-controls .alltime-view-btns { width: auto; }
  body.surge-app .la-controls .alltime-tab, body.surge-app .la-controls .alltime-view-btn { flex: 0 0 auto; }
  body.surge-app #la-portfolio, body.surge-app #la-equity { min-height: 320px; }
  /* Ledger summary line uses the full width and wraps (no horizontal scroll). */
  /* (ledger summary is a scrolling ticker now — see .lt-viewport) */

  /* Customise pencil: fixed at the page's top-right — the same spot on Dashboard
     and Ledger regardless of each page's column width (they must be identical). */
  .customise-btn { right: 2rem; top: 20px; }

  /* Theme popup flies OUT to the right of the rail (next to its button), rather
     than upward over the button it was opened from. No arrow — it's detached. */
  .theme-popup { top: auto; bottom: 16px; left: 232px; right: auto; }
  .theme-popup::before { display: none; }

  /* The ledger bulk-action sheet spans the full page width to the right of the
     rail (it may extend past the ledger column, but must not slide under the
     left rail). */
  .ledger-action-sheet { left: 222px; right: 0; }

  /* Galaxy & Retro give .app-shell a stacking context (z-index:2, to clear their
     fixed theme overlays). That traps the tier badge's header z-index inside the
     shell, leaving it below the rail (z-index:95) → the rail paints over it and
     it looks greyed. Lift the shell above the rail (but below the pencil at 100
     / popup at 101) so the badge shows. The column never overlaps the rail, so
     nothing else is affected. */
  html.theme-galaxy .app-shell, body.theme-galaxy .app-shell,
  html.theme-retro  .app-shell, body.theme-retro  .app-shell { z-index: 96; }
}

/* ── Lookup sold-comps panel (#18) — the listings behind each price. Stacks
   below the card on mobile; becomes the right column at ≥1200px (below). ── */
/* Card-column centering lives here (was an inline style) so the ≥1200px rule can
   override it — otherwise the inline margin:auto centres the card in its column,
   leaving dead space that reads as an oversized gap. */
#lookup-wrap  { max-width: 560px; margin: 0 auto; }
#lookup-comps { max-width: 560px; margin: 14px auto 0; }
/* Sold-price chart — canvas sizes to its CSS box (the JS sets the bitmap to
   match, DPR-scaled). Detail line mirrors the #ps-searched single-line scroll. */
.comps-chart-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; flex-wrap: wrap; }
.comps-range { display: flex; gap: 4px; }
.comps-range-btn {
  font-family: var(--mono); font-size: 10px; font-weight: 600; letter-spacing: 0.03em;
  padding: 3px 8px; border-radius: 6px; border: 1px solid var(--border2);
  background: transparent; color: var(--hint); cursor: pointer; transition: all 0.15s;
  -webkit-tap-highlight-color: transparent;
}
.comps-range-btn:hover { color: var(--text); border-color: var(--hint); }
.comps-range-btn.active { background: var(--blue-dim); border-color: var(--blue); color: var(--blue); }
/* Watchlist row sparkline period toggle — right-aligned above the chart, with
   the window's price change pinned left (#118: a slow incline can hide a 2×
   move — the delta makes it explicit). */
.wl-chart-range { justify-content: flex-end; margin-bottom: -4px; flex-wrap: wrap; }
.wl-chart-delta {
  margin-right: auto; align-self: center;
  font-family: var(--mono); font-size: 10.5px; font-weight: 600;
}
/* #163/#15: on mobile the period toggles sit on top (their own row) and the
   delta figure drops below them — no collision, toggles easy to reach. */
@media (max-width: 480px) {
  .wl-chart-range { justify-content: center; }
  .wl-chart-delta { order: 2; flex: 1 0 100%; margin: 5px 0 0; text-align: center; }
}
/* Delta + stats line under the chart title (#118 — packed into the title it
   wrapped messily on mobile). */
.comps-chart-meta {
  font-family: var(--mono); font-size: 11px; color: var(--hint);
  margin-top: 4px; line-height: 1.5;
}
/* Shared colours for the window price-change chip (lookup meta + watchlist). */
.chart-delta { font-weight: 600; white-space: nowrap; }
.chart-delta.pos  { color: var(--green); }
.chart-delta.neg  { color: var(--red); }
.chart-delta.flat { color: var(--muted); }
.comps-chart-canvas { display: block; width: 100%; height: 132px; margin-top: 6px; }
/* Real card image in the ledger detail view (#81) — same treatment as the
   lookup result image (190px, contained, soft shadow), above the listing title.
   Shown only for rows captured from a priced lookup; sprites stay the list id. */
/* #92: minimal space under the card — the status badge + fields sit close to it
   (the .ledger-row-body flex gap already provides the small separation). */
.ledger-detail-cardimg-wrap { display: flex; justify-content: center; margin: 0; position: relative; }
/* eBay-imported rows: a link to the live listing in the TOP-RIGHT of the card
   (opposite the bottom "refresh photo" control) so sellers can jump out to edit it. */
.ledger-detail-ebay-link {
  position: absolute; top: 6px; right: 6px; z-index: 2;
  display: inline-flex; align-items: center; justify-content: center;
  width: 27px; height: 27px; border-radius: 50%;
  background: var(--surface); border: 1px solid var(--border2); color: var(--muted);
  cursor: pointer; text-decoration: none; transition: color .12s, border-color .12s;
  -webkit-tap-highlight-color: transparent;
}
.ledger-detail-ebay-link:hover { color: var(--blue); border-color: var(--blue); }
/* Free, local re-resolve of the card photo from the listing title (no credits).
   #108: lives at the right end of the head row — bottom-right of the photo,
   mirroring the status pill on the bottom-left. */
.ledger-row-body-head-right { display: flex; align-items: center; gap: 8px; }
.ledger-img-refresh {
  background: none; border: none; color: var(--hint); cursor: pointer;
  font-family: var(--mono); font-size: 10px; padding: 2px 4px; border-radius: 6px;
  opacity: .65;
}
.ledger-img-refresh:hover { color: var(--blue); opacity: 1; }
.ledger-img-refresh:disabled { opacity: .5; cursor: default; }
.ledger-detail-cardimg {
  height: 190px; width: auto; max-width: 100%;
  border-radius: 10px; object-fit: contain; box-shadow: 0 6px 20px rgba(0,0,0,.35);
}

/* #83: Check-market-price when the card can't be priced (non-EN/JA) — looks
   muted/unavailable but stays tappable so the tap can explain why. */
.seller-btn.ledger-cm-na { opacity: .5; font-size: 11px; }
.seller-btn.ledger-cm-na:hover { border-color: var(--border2); color: var(--muted); }

/* Empty/locked states — uniform treatment for BOTH lookup blocks (#79): centred
   horizontally + vertically. The chart card's keeps the populated-chart height
   (canvas 132px + detail line) so the card doesn't shrink pre-lookup (#69). */
/* #209: variant-filtered comps note */
.comps-variant-note { font-size: 11px; font-family: var(--mono); color: var(--hint); padding: 2px 2px 8px; }
#lookup-comps .comps-empty,
#lookup-comps .comps-locked {
  min-height: 156px; display: flex; align-items: center; justify-content: center;
  text-align: center;
}
.comps-chart-detail {
  font-family: var(--mono); font-size: 12px; color: var(--text);
  margin-top: 8px; min-height: 16px; line-height: 16px;
  white-space: nowrap; overflow-x: auto; overflow-y: hidden; scrollbar-width: none;
}
.comps-chart-detail::-webkit-scrollbar { display: none; }
#comps-list { display: flex; flex-direction: column; }
.comps-more-btn {
  display: block; width: 100%; margin-top: 8px; padding: 8px;
  background: none; border: 1px dashed var(--border2); border-radius: 8px;
  color: var(--blue); font-size: 12px; font-family: var(--mono); cursor: pointer;
}
.comps-more-btn:hover { border-color: var(--blue); }
.comps-row { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; padding: 8px 2px; border-bottom: 0.5px solid var(--border); 
  /* #138: a pooled card can have 500+ comps — offscreen rows skip layout/paint
     entirely, so re-entering the Lookup tab doesn't re-lay-out the lot. */
  content-visibility: auto; contain-intrinsic-size: auto 34px; }
.comps-row:last-child { border-bottom: none; }
/* Title scrolls horizontally (touch/drag) when long — same pattern as the
   ledger card-name + analytics best/worst names — rather than truncating. */
.comps-row-title { display: block; flex: 1; min-width: 0; font-size: 12.5px; color: var(--text); white-space: nowrap; overflow-x: auto; overflow-y: hidden; scrollbar-width: none; -ms-overflow-style: none; text-decoration: none; }
.comps-row-title::-webkit-scrollbar { display: none; }
a.comps-row-title:hover { color: var(--blue); }
.comps-row-meta { display: flex; align-items: baseline; gap: 12px; flex: 0 0 auto; font-family: var(--mono); }
.comps-row-price { font-size: 13px; font-weight: 600; color: var(--text); }
.comps-row-date { font-size: 11px; color: var(--hint); white-space: nowrap; }
/* Admin-only 3-dot flag affordance on each comp. */
.comps-row-flag {
  background: none; border: none; cursor: pointer; color: var(--muted);
  font-size: 15px; line-height: 1; padding: 0 2px; margin-left: -4px;
  border-radius: 5px; flex: 0 0 auto; align-self: center;
}
.comps-row-flag:hover { color: var(--text); background: var(--surface2); }
/* The tiny one-item flag menu (appended to <body>, fixed-positioned). */
.comp-row-menu {
  position: absolute; z-index: 1000; min-width: 168px; max-width: 240px;
  background: var(--surface); border: 1px solid var(--border); border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0,0,0,0.28); padding: 4px; overflow: hidden;
}
.comp-row-menu .crm-head { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); padding: 6px 10px 4px; }
.comp-row-menu .crm-flag {
  width: 100%; text-align: left; background: none; border: none; cursor: pointer;
  color: var(--red); font: inherit; font-size: 13px; padding: 8px 10px; border-radius: 7px;
}
.comp-row-menu .crm-flag:hover { background: var(--surface2); }
.comps-empty, .comps-locked { font-size: 12px; color: var(--hint); padding: 10px 2px; line-height: 1.5; }
.comps-locked { display: flex; align-items: center; gap: 8px; }

/* ── Lookup grade selector + card autocomplete (Scrydex catalog) ── */
.lookup-grade-btn {
  flex: 0 0 auto; white-space: nowrap; align-self: stretch;
  background: var(--surface2); border: 1px solid var(--border2); color: var(--text);
  border-radius: 8px; padding: 0 12px; font-family: var(--mono); font-size: 13px; font-weight: 600;
  cursor: pointer; transition: border-color .15s, color .15s;
}
/* #62: on phones, tighten the chip so even the longest (abbreviated) grade
   leaves the card-name input room to breathe. */
@media (max-width: 480px) {
  .lookup-grade-btn { padding: 0 8px; font-size: 12px; }
}
.lookup-grade-btn:hover { border-color: var(--blue); color: var(--blue); }
.lookup-grade-picker {
  position: absolute; top: calc(100% + 4px); left: 0; z-index: 60;
  background: var(--surface2); border: 1px solid var(--border2); border-radius: 10px;
  padding: 10px; min-width: 230px; box-shadow: 0 8px 24px rgba(0,0,0,.35);
  /* Cap the width so the full grade grid wraps inside the popup and never runs
     off the right edge on mobile (left:0 + shrink-to-fit could overflow). */
  max-width: min(340px, calc(100vw - 28px));
}
.lgp-label { font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); margin: 0 0 4px; }
.lgp-row { display: flex; gap: 6px; flex-wrap: wrap; margin-bottom: 10px; }
.lgp-row:last-child { margin-bottom: 0; }
/* The full grade scale is ~20 chips — let it wrap into a tidy grid and cap the
   height so the popup never runs off-screen on mobile. */
.lgp-grades { max-height: 180px; overflow-y: auto; }
.lgp-grades .lgp-opt { flex: 0 0 auto; min-width: 38px; text-align: center; }
.lgp-opt {
  border: 1px solid var(--border2); background: var(--surface); color: var(--muted);
  border-radius: 6px; padding: 5px 10px; font-size: 12px; cursor: pointer; font-family: var(--mono);
}
.lgp-opt.active { border-color: var(--blue); background: var(--blue-dim); color: var(--blue); }
.lgp-grade-input { width: 100%; box-sizing: border-box; background: var(--surface); border: 1px solid var(--border2); color: var(--text); border-radius: 6px; padding: 6px 9px; font-size: 13px; font-family: var(--mono); }
.lgp-grade-input:focus { outline: none; border-color: var(--blue); }
.lookup-suggest-popup {
  position: absolute; top: calc(100% + 2px); left: 0; right: 0; z-index: 55;
  max-height: 340px; overflow-y: auto;
  background: var(--surface2); border: 1px solid var(--border2); border-radius: 8px; padding: 4px;
  box-shadow: 0 8px 24px rgba(0,0,0,.35);
}
.lsp-item { display: flex; align-items: center; gap: 10px; padding: 6px 8px; border-radius: 6px; cursor: pointer; }
.lsp-item:hover, .lsp-item.active, .lsp-item.ac-active { background: var(--blue-dim); }
.lsp-img { width: 30px; height: 42px; object-fit: contain; flex: 0 0 auto; border-radius: 3px; background: var(--surface); }
.lsp-body { min-width: 0; flex: 1; }
.lsp-name { font-size: 13px; font-weight: 600; color: var(--text); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* Meta line scrolls horizontally (touch/drag) when long — same hidden-scrollbar
   pattern as #ps-searched / the comps-row titles — instead of ellipsis-truncating. */
.lsp-meta {
  font-size: 11px; color: var(--muted); font-family: var(--mono);
  white-space: nowrap; overflow-x: auto; overflow-y: hidden;
  scrollbar-width: none; -ms-overflow-style: none;
}
.lsp-meta::-webkit-scrollbar { display: none; }
.lsp-lang { color: var(--blue); }
.lsp-empty { padding: 10px; font-size: 12px; color: var(--hint); }
/* Recent searches folded into the dropdown (#61) — a small section header + rows
   with a ↺ glyph in place of the card image and a hover-only remove button. */
.lsp-section { padding: 6px 8px 2px; font-size: 10px; text-transform: uppercase; letter-spacing: .05em; color: var(--muted); font-family: var(--mono); }
.lsp-recent-icon { display: flex; align-items: center; justify-content: center; color: var(--hint); font-size: 16px; }
.lsp-recent-del { background: none; border: none; color: var(--hint); cursor: pointer; font-size: 11px; padding: 6px 8px; flex: 0 0 auto; line-height: 1; opacity: 0; transition: opacity .15s; }
.lsp-recent:hover .lsp-recent-del { opacity: 1; }
.lsp-recent-del:hover { color: var(--text); }
/* Touch devices have no hover — keep the remove ✕ always visible there. */
@media (hover: none) {
  .lsp-recent-del { opacity: 1; }
}

/* ════════════════════════════════════════════════════════════════════════════
   DESKTOP SETTINGS (≥1024px, #18) — the page widens and its control-card grids
   (Account · Settings · Danger Zone) flow to two columns instead of the single
   mobile column. body.surge-app scoped + :has() gated to the Settings page, so
   mobile and every other page are byte-for-byte unaffected.
   ════════════════════════════════════════════════════════════════════════════ */
@media (min-width: 1024px) {
  body.surge-app:has(#page-settings.active) .app-shell { max-width: 980px; }  /* #175: match ledger width */
  body.surge-app:has(#page-settings.active) .controls-grid {
    grid-template-columns: 1fr 1fr;
    gap: 16px;
    align-items: start;   /* cards keep their natural height — no stretched empty space */
  }
  /* The lone Danger-Zone card spans both columns rather than sitting half-empty. */
  body.surge-app:has(#page-settings.active) .controls-grid .control-card[data-sid="account"] {
    grid-column: 1 / -1;
  }
}

/* ════════════════════════════════════════════════════════════════════════════
   DESKTOP LOOKUP + COMPS (≥1200px, #18) — the lookup card stays intact on the
   left; the sold-comps panel becomes the right column. The card is untouched;
   the comps panel is the new content that earns the width. Gated to the Lookup
   page; below 1200px both stack (the comps panel sits under the card).
   ════════════════════════════════════════════════════════════════════════════ */
@media (min-width: 1200px) {
  body.surge-app:has(#page-lookup.active) .app-shell { max-width: 980px; }
  body.surge-app #lookup-layout {
    display: grid;
    grid-template-columns: 520px 1fr;
    column-gap: 16px;   /* match the Settings two-column gap */
    align-items: start;
  }
  body.surge-app #lookup-wrap,
  body.surge-app #lookup-comps { max-width: none; margin: 0; min-width: 0; }
  /* The comps list grows naturally, then scrolls within the column once it would
     exceed the lookup card's height. The max-height is set by _syncCompsHeight()
     (the card's height isn't knowable in pure CSS); this just enables the scroll. */
  body.surge-app #lookup-comps #comps-list { overflow-y: auto; }
  /* #69: the column is pinned to the lookup card's height (set by
     _syncCompsHeight) and the sold-comps card flex-fills the remainder, so the
     two blocks always span the full column — even pre-lookup. */
  body.surge-app #lookup-comps { display: flex; flex-direction: column; }
  body.surge-app #lookup-comps > .seller-card:last-child { flex: 1; min-height: 0; box-sizing: border-box; display: flex; flex-direction: column; }
  body.surge-app #lookup-comps > .seller-card:last-child #comps-list { flex: 1 1 auto; }
  /* Empty message centres in the card's full remaining area (#79). */
  body.surge-app #lookup-comps #comps-list .comps-empty,
  body.surge-app #lookup-comps #comps-list .comps-locked { min-height: 100%; box-sizing: border-box; }
}

/* ════════════════════════════════════════════════════════════════════════════
   DESKTOP LEDGER MASTER-DETAIL (≥1200px) — a full-width header (toolbar) above
   two columns: the list (left) and a sticky detail pane (right, top-aligned with
   the rows). On desktop the rows render header-only and the selected card's
   detail/edit panel is rendered into #ledger-detail-pane. Below 1200px the
   ledger is the single Tier-1 column. body.surge-app scoped + gated → mobile and
   narrow desktops unaffected.
   ════════════════════════════════════════════════════════════════════════════ */
@media (min-width: 1200px) {
  /* Ledger page goes wide; the toolbar (a block child of #ledger-list-view)
     naturally spans this full width. Other pages stay 520. */
  body.surge-app:has(#page-ledger.active) .app-shell { max-width: 980px; }

  /* ── #18: DASHBOARD MULTI-COLUMN (≥1200px) ──────────────────────────────
     Two-column masonry via GRID ROW-SPANS (not CSS columns — multicol
     fragments dynamically-loading widgets/canvases in Chrome: vanishing pie
     charts, relocated empty containers). Each module spans
     ceil(height/8px) implicit rows, set by _dashMasonry() in app.js with a
     ResizeObserver so async content growth re-packs automatically. Widgets
     keep ONE linear DOM order (same as mobile, same as the pencil DnD edits,
     same as the server-saved layout); align-items:start keeps measured
     heights honest (no stretch feedback). */
  body.surge-app:has(#page-dashboard.active) .app-shell { max-width: 980px; }  /* #175: match ledger width */
  body.surge-app:has(#page-scout.active) .app-shell { max-width: 980px; }      /* #227: Scout matches the other pages */
  body.surge-app #dashboard-modules-root {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 18px;
    grid-auto-rows: 8px;
    grid-auto-flow: row dense;
    align-items: start;
    /* Until _dashMasonry() measures each module and assigns its row-span, every
       module defaults to a single 8px row and they collapse into a crumpled
       "ball". Stay invisible until the first real pack adds .masonry-ready, then
       fade the correctly-laid-out grid in. (Desktop-only; this whole block is
       inside @media min-width:1200, the same gate _dashMasonry uses. A safety
       timeout in _initDashMasonry reveals it even if packing never reports.) */
    opacity: 0;
    transition: opacity 0.18s ease;
  }
  body.surge-app #dashboard-modules-root.masonry-ready { opacity: 1; }
  /* Widgets carry their own (large, varied) mobile bottom margins — zero the
     trailing one on desktop; the 18px vertical rhythm comes from the span
     math in _dashMasonry(). */
  body.surge-app #dashboard-modules-root > .module > :last-child { margin-bottom: 0 !important; }

  /* Two-column area beneath the full-width toolbar. */
  body.surge-app #ledger-md {
    display: grid;
    grid-template-columns: 440px 1fr;
    column-gap: 26px;
    align-items: start;
  }
  body.surge-app #ledger-md > #ledger-list        { grid-column: 1; min-width: 0; }
  body.surge-app #ledger-md > #ledger-empty       { grid-column: 1 / -1; }
  body.surge-app #ledger-md > #ledger-detail-pane { grid-column: 2; min-width: 0; }

  /* #106: BOTH columns are viewport-pinned with INTERNAL scroll (the email-
     client pattern). The page itself stops scrolling away — arrow-key
     navigation moves the list's own scrollbar while the detail pane sits
     unmoving beside it. Sticky + max-height: the columns ride at top:1rem
     once the toolbar scrolls off, maximising room while keyboard-navigating. */
  body.surge-app #ledger-md > #ledger-list {
    position: sticky;
    top: 1rem;
    align-self: start;
    max-height: calc(100vh - 2rem);
    overflow-y: auto;
    overscroll-behavior: contain;       /* wheel at list edge doesn't yank the page */
    scrollbar-width: thin;
    scrollbar-color: var(--border2) transparent;
    padding-right: 2px;                 /* breathing room beside the thin scrollbar */
  }
  /* .ledger-list is a flex COLUMN — without this, giving it a max-height lets
     flexbox shrink every row to cram the whole list into the viewport
     (42 rows → 5px slivers) instead of overflowing into the scrollbar. */
  body.surge-app #ledger-md > #ledger-list > .ledger-row { flex-shrink: 0; }
  body.surge-app #ledger-md > #ledger-list::-webkit-scrollbar { width: 6px; }
  body.surge-app #ledger-md > #ledger-list::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 3px; }
  body.surge-app #ledger-md > #ledger-list::-webkit-scrollbar-track { background: transparent; }

  /* Detail pane: sticky beside the list, capped to the viewport — a tall card
     detail scrolls INTERNALLY instead of growing the page (#106; the growing
     page was exactly what let the pane scroll out of view). */
  body.surge-app #ledger-detail-pane {
    position: sticky;
    top: 1rem;
    align-self: start;
    max-height: calc(100vh - 2rem);
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
    scrollbar-color: var(--border2) transparent;
  }
  body.surge-app #ledger-detail-pane::-webkit-scrollbar { width: 6px; }
  body.surge-app #ledger-detail-pane::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 3px; }
  body.surge-app #ledger-detail-pane::-webkit-scrollbar-track { background: transparent; }
  body.surge-app #ledger-detail-pane .ledger-row-body {
    display: flex;
    background: color-mix(in srgb, var(--bg) 90%, #fff 0%);
    border: 1px solid var(--border);
    border-radius: 16px;
    padding: 16px 18px;
  }

  /* Selected row highlight in the list. */
  body.surge-app #ledger-md .ledger-row.selected { border-color: var(--blue); background: var(--blue-dim); }
  body.surge-app #ledger-md .ledger-row.selected .ledger-row-chevron { transform: rotate(-90deg); color: var(--blue); }

  /* Analytics sub-view keeps a comfortable centred width inside the wide shell. */
  /* ONE width for the whole page — the old 600px hero island looked broken
     next to the 920px widget grid (#128 follow-up). */
  body.surge-app #ledger-analytics-view { max-width: 980px; margin: 0 auto; }  /* #192: match the other tabs (was 920) */
  body.surge-app .la-grid { grid-template-columns: 1fr 1fr; gap: 12px; }
  body.surge-app .la-grid .la-card:first-child { grid-column: 1 / -1; }  /* monthly bars span */
  body.surge-app .la-grid .la-tiles { grid-template-columns: repeat(2, 1fr); }  /* 2×2 inside a half-width card */

  /* Stable two-row header via grid areas (#34, #36): the P/L figures + SEARCH
     on row 1 (search is the highest-frequency control → prime spot), the
     All/Active/Sold/Archived filters + sort/range/import/export on row 2 (#74).
     Fixed areas mean it never reflows when the figures are empty (no search
     results / archived view). */
  body.surge-app #ledger-list-view .ledger-toolbar {
    display: grid;
    grid-template-columns: 440px 1fr;   /* matches the list/detail columns below */
    column-gap: 26px;                    /* so col2 (search) lines up with the pane */
    row-gap: 10px;
    grid-template-areas:
      "summary search"
      "filters io";
    align-items: center;
  }
  /* #85: P/L · Projected · Tied up stretch across the full left column. */
  body.surge-app #ledger-list-view .ledger-summary { grid-area: summary; width: 100%; justify-self: stretch; justify-content: space-between; gap: 18px; }
  /* #84: Sort / Range / Import / Export, left-aligned at the detail pane's left
     edge. Sort and Range take all the room as TWO EQUAL big buttons (they carry
     text + a value, e.g. "Range: Last 90 days"); Import / Export are compact
     ICON-ONLY squares of equal size. The gate overlay box (#ledger-gated-wrap)
     stays intact. The flex-basis on gated (= the io cluster width) makes
     Range end up exactly as wide as Sort. */
  body.surge-app #ledger-list-view .ledger-io-row {
    grid-area: io; width: 100%; justify-self: stretch; flex: initial;
    display: flex; gap: 8px; align-items: center;
  }
  body.surge-app #ledger-list-view .ledger-sort-wrap { flex: 1 1 0; width: auto; min-width: 0; }
  body.surge-app #ledger-list-view #ledger-gated-wrap {
    flex: 1 1 92px; display: flex; gap: 8px; align-items: center;
    grid-column: auto; grid-template-columns: none;
  }
  body.surge-app #ledger-list-view #ledger-gated-wrap .ledger-date-wrap { flex: 1 1 0; width: auto; min-width: 0; }
  body.surge-app #ledger-list-view .ledger-io { flex: 0 0 auto; display: flex; gap: 8px; flex-wrap: nowrap; }
  body.surge-app #ledger-list-view .ledger-io .ledger-io-btn { flex: 0 0 36px; width: 36px; padding: 0; justify-content: center; }
  body.surge-app #ledger-list-view .ledger-io-label { display: none; }   /* icon-only (#84): frees room for Sort/Range */
  /* Never wrap the Sort/Range labels — they ellipsise only if truly pinched. */
  body.surge-app #ledger-list-view .ledger-sort-btn,
  body.surge-app #ledger-list-view .ledger-date-btn { overflow: hidden; }
  body.surge-app #ledger-list-view .ledger-sort-label,
  body.surge-app #ledger-list-view .ledger-date-label { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; }
  /* Filters fill the left column, split into four equal toggles. */
  body.surge-app #ledger-list-view .ledger-filters { grid-area: filters; width: 100%; }
  body.surge-app #ledger-list-view .ledger-filters .ledger-filter { flex: 1 1 0; min-width: 0; width: auto; justify-content: center; }
  /* Search fills the right column → starts exactly at the detail pane's left edge. */
  body.surge-app #ledger-list-view .ledger-search-bar { grid-area: search; width: 100%; }
  body.surge-app #ledger-list-view .ledger-sort-btn,
  body.surge-app #ledger-list-view .ledger-date-btn  { width: 100%; }
  /* Range dropdown opens left-aligned like Sort (it was right-aligned). */
  body.surge-app #ledger-list-view .ledger-date-popup { left: 0; right: auto; }

  /* #80: uniform control heights so row 2 (filters | sort/range/io) sits on one
     crisp line, and row 1's search aligns with the P/L summary. Every pill gets
     the same fixed height (vertical centring via their inline-flex), and the
     search bar drops its asymmetric mobile padding. */
  body.surge-app #ledger-list-view .ledger-filters .ledger-filter,
  body.surge-app #ledger-list-view .ledger-sort-btn,
  body.surge-app #ledger-list-view .ledger-date-btn,
  body.surge-app #ledger-list-view .ledger-io-btn {
    height: 30px; padding-top: 0; padding-bottom: 0; box-sizing: border-box;
  }
  body.surge-app #ledger-list-view .ledger-io { flex-wrap: nowrap; }
  body.surge-app #ledger-list-view .ledger-summary { align-items: center; min-height: 30px; }
  /* The ticker (lt-viewport) is display:block, so its inline track sat at the TOP
     of the 30px row instead of centred next to the search bar. Flex-centre it. */
  body.surge-app #ledger-list-view .ledger-summary.lt-viewport { display: flex; align-items: center; }
  body.surge-app #ledger-list-view .ledger-search-inner { padding: 0; height: 30px; align-items: center; }

  /* (Date-field calendar button is styled generically — see .ledger-date-field.) */
}

/* ── Card-search helper (#33) — live "ticks off" the parts of a card query ── */
.search-helper {
  margin: 8px 0 2px;
  border: 1px solid var(--border2);
  border-radius: 10px;
  background: color-mix(in srgb, var(--bg) 82%, #000 0%);
  padding: 8px 11px;
}
.search-helper .sh-row { display: flex; align-items: center; gap: 10px; padding: 4px 0; font-size: 13px; }
.search-helper .sh-ico { flex: 0 0 16px; width: 16px; height: 16px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; box-sizing: border-box; line-height: 1; }
.search-helper .sh-met   .sh-ico { background: var(--green-dim); color: var(--green); }
.search-helper .sh-unmet .sh-ico { border: 1.5px solid var(--border2); }
.search-helper .sh-label { flex: 0 0 104px; color: var(--muted); font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.03em; text-transform: uppercase; }
.search-helper .sh-val { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding-right: 4px; color: var(--text); font-weight: 600; }
.search-helper .sh-unmet .sh-val { color: var(--hint); font-weight: 400; font-style: italic; }

/* #223 — Mobile/touch only: kill the iOS/Android long-press text-selection +
   image "save photo / copy" callout that fires on card views (dashboard grid,
   analytics cards, ledger/watchlist sprites). Scoped to the authed app
   (body.surge-app) so marketing/legal pages keep selectable, copyable text;
   gated to touch-primary devices (hover:none + pointer:coarse) so desktop text
   selection is completely untouched. Inputs and the explicitly-selectable detail
   /log areas are whitelisted back to text so editing + copying still work. */
@media (hover: none) and (pointer: coarse) {
  body.surge-app {
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    user-select: none;
  }
  body.surge-app input,
  body.surge-app textarea,
  body.surge-app [contenteditable="true"],
  body.surge-app .ledger-row-body,
  body.surge-app .log-lines,
  body.surge-app .log-line {
    -webkit-user-select: text;
    user-select: text;
  }
}

/* ══ #227 Dashboard sub-views — Scout + Catalogue ══════════════════════════════
   Built on the local mirror; real card art is the hero. --blue is the per-theme
   accent (purple in the default theme). Mobile-first; desktop widens the grids. */
/* #234: Scout/Catalogue sub-nav is retired — mobile uses Scout-tab re-tap (like Ledger↔Analytics), desktop uses separate rail items. Kept in the DOM only for the active-state toggle. */
.dash-subnav { display: none; gap: 4px; background: var(--surface2); border: 1px solid var(--border2); border-radius: 13px; padding: 4px; margin: 0 0 18px; }
.dash-subnav-btn { flex: 1; padding: 9px 10px; border: 0; background: transparent; color: var(--muted); font-family: inherit; font-size: 13px; font-weight: 600; border-radius: 9px; cursor: pointer; transition: background .15s, color .15s; }
.dash-subnav-btn.active { background: var(--blue-dim); color: var(--blue); }

.scout-head { margin: 2px 0 15px; }
.scout-title { font-size: 22px; font-weight: 800; letter-spacing: -.02em; margin: 0 0 4px; color: var(--text); }
.scout-tagline { font-size: 12.5px; line-height: 1.45; color: var(--muted); margin: 0; }

.scout-lenses, .cat-langs { display: flex; gap: 7px; overflow-x: auto; scrollbar-width: none; margin: 0 0 16px; padding-bottom: 2px; -webkit-overflow-scrolling: touch; }
/* #234: centre the chip rows on mobile (safe-center → falls back to start when
   they overflow, so the first chip is never clipped/unreachable). */
@media (max-width: 1023px) { .scout-lenses, .cat-langs { justify-content: safe center; } }
.scout-lenses::-webkit-scrollbar, .cat-langs::-webkit-scrollbar { display: none; }
.scout-lens, .cat-langpill { flex: 0 0 auto; padding: 7px 13px; border: 1px solid var(--border2); background: var(--surface); color: var(--muted); border-radius: 999px; font-family: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; white-space: nowrap; transition: border-color .15s, background .15s, color .15s; }
.scout-lens.active, .cat-langpill.active { border-color: var(--blue); background: var(--blue-dim); color: var(--blue); }
.cat-langpill span { opacity: .55; font-weight: 500; margin-left: 3px; }

.scout-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 9px; }
.scout-card { display: flex; flex-direction: column; min-width: 0; padding: 0; border: 0; background: transparent; text-align: left; cursor: pointer; -webkit-tap-highlight-color: transparent; font-family: inherit; }
.scout-card-art { position: relative; aspect-ratio: 5 / 7; border-radius: 11px; overflow: hidden; background: linear-gradient(135deg, var(--surface2), var(--surface)); box-shadow: 0 2px 9px rgba(0,0,0,.28); }
.scout-card-art img { width: 100%; height: 100%; object-fit: cover; display: block; transition: transform .28s ease; }
.scout-card:hover .scout-card-art img { transform: scale(1.04); }
.scout-card-sig { position: absolute; left: 50%; bottom: 5px; transform: translateX(-50%); max-width: calc(100% - 8px); font-size: 10px; font-weight: 700; letter-spacing: .01em; text-align: center; color: #fff; background: linear-gradient(to top, rgba(6,6,12,.9), rgba(12,12,22,.66)); border: 1px solid rgba(255,255,255,.12); -webkit-backdrop-filter: blur(4px); backdrop-filter: blur(4px); padding: 2.5px 9px; border-radius: 999px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; box-shadow: 0 1px 5px rgba(0,0,0,.32); }
/* Horizontally scrollable when long, with the shared …-fade affordance (it's in
   the _HSCROLL_SELECTOR list) instead of a hard ellipsis. */
.scout-card-name { font-size: 11.5px; font-weight: 600; color: var(--text); margin-top: 5px; line-height: 1.2; white-space: nowrap; overflow-x: auto; overflow-y: hidden; text-overflow: unset; scrollbar-width: none; -ms-overflow-style: none; }
.scout-card-name::-webkit-scrollbar { display: none; }
.scout-card-meta { font-size: 9.5px; color: var(--muted); margin-top: 1px; white-space: nowrap; overflow-x: auto; scrollbar-width: none; }
.scout-card-meta::-webkit-scrollbar { display: none; }
.scout-card-lang { color: var(--blue); font-weight: 700; }
.scout-empty { grid-column: 1 / -1; text-align: center; color: var(--muted); font-size: 13px; line-height: 1.5; padding: 44px 18px; }

/* #235: the sets body cross-fades on a language switch (pill bar stays put). */
.cat-body { transition: opacity .15s ease; }
.cat-body.cat-swapping { opacity: 0; }
.cat-series { margin: 0 0 20px; }
.cat-series-name { font-size: 13px; font-weight: 700; color: var(--text); margin: 0 0 9px; letter-spacing: -.01em; }
.cat-set-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
.cat-set { display: flex; flex-direction: column; min-width: 0; gap: 3px; align-items: flex-start; text-align: left; padding: 11px 13px; border: 1px solid var(--border2); background: var(--surface); border-radius: 11px; cursor: pointer; font-family: inherit; transition: border-color .15s, transform .1s; }
.cat-set:hover { border-color: var(--blue); }
.cat-set:active { transform: scale(.985); }
.cat-set-name { font-size: 13px; font-weight: 600; color: var(--text); line-height: 1.2; }
.cat-set-meta { font-size: 10.5px; color: var(--muted); }

/* #234: inside a set, the name + data line are centred across the full width;
   the ‹ Sets back button floats on the left (absolute, so it doesn't pull the
   title off-centre). The title's side padding keeps it clear of the button. */
.cat-detail-head { position: relative; display: flex; align-items: center; min-height: 34px; margin: 0 0 15px; }
.cat-back { position: absolute; left: 0; top: 50%; transform: translateY(-50%); flex: 0 0 auto; padding: 7px 12px; border: 1px solid var(--border2); background: var(--surface); color: var(--text); border-radius: 9px; font-family: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer; transition: border-color .15s, color .15s; }
.cat-back:hover { border-color: var(--blue); color: var(--blue); }
.cat-detail-title { flex: 1; text-align: center; padding: 0 64px; box-sizing: border-box; }
.cat-detail-name { font-size: 16px; font-weight: 700; color: var(--text); letter-spacing: -.01em; }
.cat-detail-meta { font-size: 11px; color: var(--muted); margin-top: 1px; }
.cat-more { display: block; width: 100%; margin: 15px 0 0; padding: 11px; border: 1px solid var(--border2); background: var(--surface); color: var(--text); border-radius: 10px; font-family: inherit; font-size: 13px; font-weight: 600; cursor: pointer; }
.cat-more:hover { border-color: var(--blue); color: var(--blue); }

/* #227: the chip rows sit on the page background — paint the scroll-fade in --bg
   so the #132 ellipsis affordance dissolves into it (not a surface-coloured box). */
.scout-lenses, .cat-langs, .cat-sort, .scout-card-meta { --hs-fade: var(--bg); }

/* Sort control inside a set's card grid. */
.cat-sort { display: flex; align-items: center; gap: 6px; overflow-x: auto; scrollbar-width: none; margin: -2px 0 14px; }
.cat-sort::-webkit-scrollbar { display: none; }
.cat-sort-label { flex: 0 0 auto; font-size: 11px; font-weight: 600; color: var(--muted); margin-right: 2px; }
.cat-sort-btn { flex: 0 0 auto; padding: 5px 12px; border: 1px solid var(--border2); background: var(--surface); color: var(--muted); border-radius: 999px; font-family: inherit; font-size: 11.5px; font-weight: 600; cursor: pointer; white-space: nowrap; transition: border-color .15s, background .15s, color .15s; }
.cat-sort-btn.active { border-color: var(--blue); background: var(--blue-dim); color: var(--blue); }

@media (min-width: 1024px) {
  body.surge-app .scout-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); }
  body.surge-app .cat-set-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ════════════════════════════════════════════════════════════════════════════
   #232 — "LIQUID GLASS" UI (experimental · iOS-26 water-droplet · body.ui-glass)
   Clear refractive droplets on tabs/toggles/pills/buttons; frosted translucent
   panels on dropdowns. Delicate edge rim, whisper shadow — nothing emanates past
   the element. Accent tint when active. THEME-AWARE via --glass-*.
   ── TO SWITCH BACK: remove "ui-glass" from the <body> classes (app.html,
      switchboard.html, _layout.ejs, early-access.html, login.html) or revert. ──
   ════════════════════════════════════════════════════════════════════════════ */
body.ui-glass {
  --glass-bg:  rgba(255,255,255,0.05);
  --glass-brd: rgba(255,255,255,0.20);
  --glass-blur: blur(8px) saturate(130%);
}
html.theme-light body.ui-glass, body.theme-light.ui-glass {
  --glass-bg:  rgba(255,255,255,0.34);
  --glass-brd: rgba(0,0,0,0.10);
  --glass-blur: blur(8px) saturate(115%);
}

/* ── SURFACE: every tab / toggle / pill / button, on every page ─────────────── */
body.ui-glass .page-tab,
body.ui-glass .scout-lens,
body.ui-glass .cat-langpill,
body.ui-glass .cat-sort-btn,
body.ui-glass .grade-btn,
body.ui-glass .la-range-btn,
body.ui-glass .theme-btn,
body.ui-glass .gear-btn,
body.ui-glass .customise-btn,
body.ui-glass .medal-btn,
body.ui-glass .ledger-sort-btn,
body.ui-glass .ledger-filter,
body.ui-glass .ledger-io-btn,
body.ui-glass .alltime-tab,
body.ui-glass .upgrade-period-btn,
body.ui-glass .lookup-grade-btn,
body.ui-glass .watchlist-grade-btn,
body.ui-glass .pricing-currency-btn,
body.ui-glass.sb .subtab,
body.ui-glass.sb .pill,
body.ui-glass.sb .dd-btn {
  background: var(--glass-bg);
  border: 1px solid var(--glass-brd);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow:
    inset 0 1px 0.5px rgba(255,255,255,.26),
    inset 0 -1px 1px rgba(255,255,255,.07),
    inset 0 0 0 1px rgba(255,255,255,.03),
    0 1px 2px rgba(0,0,0,.10);
}

/* Capsule shape — only the genuine pills (not rail items / icon buttons / tabs). */
body.ui-glass .page-tab,
body.ui-glass .scout-lens,
body.ui-glass .cat-langpill,
body.ui-glass .cat-sort-btn,
body.ui-glass .grade-btn,
body.ui-glass .la-range-btn,
body.ui-glass .ledger-filter,
body.ui-glass .lookup-grade-btn,
body.ui-glass .watchlist-grade-btn,
body.ui-glass.sb .pill,
body.ui-glass.sb .subtab { border-radius: 999px; }

/* ── ACTIVE / SELECTED: clear accent-tinted droplet (contained, no glow bleed) ─ */
body.ui-glass .page-tab.active,
body.ui-glass .scout-lens.active,
body.ui-glass .cat-langpill.active,
body.ui-glass .cat-sort-btn.active,
body.ui-glass .grade-btn.active,
body.ui-glass .la-range-btn.active,
body.ui-glass .theme-btn.active,
body.ui-glass .gear-btn.active,
body.ui-glass .ledger-filter.active,
body.ui-glass .alltime-tab.active,
body.ui-glass .upgrade-period-btn.active,
body.ui-glass.sb .subtab.on,
body.ui-glass.sb .pill.on {
  background: color-mix(in srgb, var(--blue) 20%, var(--glass-bg));
  border-color: color-mix(in srgb, var(--blue) 45%, var(--glass-brd));
  color: var(--blue);
  box-shadow:
    inset 0 1px 0.5px rgba(255,255,255,.32),
    inset 0 0 7px color-mix(in srgb, var(--blue) 16%, transparent),
    0 1px 2px rgba(0,0,0,.10);
}

/* ── PANELS: frosted translucent dropdowns/popups (readable, not see-through) ── */
body.ui-glass .theme-popup,
body.ui-glass .ledger-sort-popup,
body.ui-glass .lookup-suggest-popup,
body.ui-glass .listing-picker-dropdown,
body.ui-glass .widget-reset-popup,
body.ui-glass.sb .dd-pop {
  background: color-mix(in srgb, var(--surface2) 80%, transparent);
  -webkit-backdrop-filter: blur(22px) saturate(150%);
  backdrop-filter: blur(22px) saturate(150%);
  border: 1px solid var(--glass-brd);
  box-shadow:
    inset 0 1px 0.5px rgba(255,255,255,.16),
    0 12px 34px rgba(0,0,0,.42);
}

/* Breathing room so the droplet edge isn't clipped by scrollable chip rows. */
body.ui-glass .scout-lenses,
body.ui-glass .cat-langs { padding-top: 3px; padding-bottom: 5px; }

/* ════ #246 — Lookup card-art "majestic" zoom ════════════════════════════════
   Tap the looked-up card → a quick dislodge wiggle, then it lifts + scales to a
   centred enlarged view (a fixed-position clone does the travelling; the real
   img is hidden but holds its place so the page never jumps). ════════════════ */
.ps-card-backdrop {
  position: fixed; inset: 0; z-index: 4000;
  background: rgba(8,6,14,.74);
  -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px);
  opacity: 0; transition: opacity .42s ease; cursor: zoom-out;
}
.ps-card-backdrop.show { opacity: 1; }
@keyframes psCardDislodge {
  0%   { transform: rotate(0deg)    translateY(0); }
  18%  { transform: rotate(-3.5deg) translateY(-3px); }
  42%  { transform: rotate(2.8deg)  translateY(1px); }
  64%  { transform: rotate(-1.8deg) translateY(0); }
  82%  { transform: rotate(1deg)    translateY(0); }
  100% { transform: rotate(0deg)    translateY(0); }
}
.ps-card-dislodge { animation: psCardDislodge .36s cubic-bezier(.36,.07,.19,.97); }
@media (prefers-reduced-motion: reduce) {
  .ps-card-dislodge { animation: none; }
  .ps-card-zoom { transition-duration: .01s !important; }
}
