/* TCIN Manager public site styles. Moved out of index.html (2026-09-11,
   DESIGN_market_public.md Phase C) so market.html shares one copy. */
  @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;600;700&family=Inter:wght@400;500;600&display=swap');

  /* Self-hosted, not Google Fonts (2026-09-02) — swapped in from
     JetBrains Mono at the owner's request. Only the two weights this
     site actually uses (400/500) are shipped; the one 700-weight spot
     (.act-glyph) already fell back to browser-synthesized bold before
     this change too, so nothing regresses. */
  @font-face {
    font-family: 'IBM Plex Mono';
    src: url('/fonts/IBMPlexMono-Regular.ttf') format('truetype');
    font-weight: 400;
    font-style: normal;
    font-display: swap;
  }
  @font-face {
    font-family: 'IBM Plex Mono';
    src: url('/fonts/IBMPlexMono-Medium.ttf') format('truetype');
    font-weight: 500;
    font-style: normal;
    font-display: swap;
  }

  :root{
    color-scheme: dark;
    --accent-ink: 100%;
    --bg: #14161b;
    --surface: #1b1e25;
    --surface-2: #21252e;
    --border: #2a2e39;
    --text: #e9ebf0;
    --text-dim: #8b92a3;
    --text-faint: #858c9e; /* 4.56:1 on --surface-2, was #5c6273 at 2.7:1 (UX audit 2026-09-11) */
    --hold: #fbbf24;
    --either: #38bdf8;
    --rip: #34d399;
    --flip: #fb7185;
    --skip: #94a3b8;
    --danger: #f87171;
    --radius: 10px;

    /* Theme tokens (DESIGN_light_mode.md, 2026-09-11). Every colour that
       has to change between dark and light lives in this block, so the
       light theme is only a second set of values. Hue names, not role
       names: --accent is already taken by the per-card set colour. */
    --border-strong: #444a5a;
    --cyan: #67e8f9;
    --violet: #c4b5fd;
    --sky: #7dd3fc;
    --purple: #c084fc;
    --on-purple: #14161b;
    --up: #34d399;
    --down: #f87171;
    --caveat: #d1954a;
    --caveat-border: #6b5426;
    --holo: linear-gradient(100deg, #67e8f9 0%, #c4b5fd 35%, #fca5f1 55%, #fcd34d 80%, #67e8f9 100%);
    --ov-02: rgba(255,255,255,0.02);
    --ov-03: rgba(255,255,255,0.03);
    --ov-05: rgba(255,255,255,0.05);
    --ov-06: rgba(255,255,255,0.06);
    --scrim: rgba(0,0,0,0.6);
    --shadow-panel: 0 20px 60px rgba(0,0,0,0.5);
    --shadow-toast: 0 8px 40px rgba(0,0,0,0.5);
  }

  /* Light theme (DESIGN_light_mode.md, 2026-09-11). Same tokens, lighter
     values. Applied when the device is in light mode, unless the switch
     pinned dark; or whenever the switch pinned light. The two selectors
     carry an identical list and must stay identical. Grade and accent
     colours are the darker shades of the same hues, picked so chip text
     on its own 14% tint stays above 4.5:1. */
  @media (prefers-color-scheme: light) {
    :root:not([data-theme="dark"]) {
      color-scheme: light;
      --bg: #f4f5f8;
      --surface: #ffffff;
      --surface-2: #eceef3;
      --border: #d7dbe4;
      --border-strong: #b1b7c3;
      --text: #16181d;
      --text-dim: #565d6c;
      --text-faint: #646b7b; /* 4.6:1 on --surface-2, was #7a8192 at 3.6:1 */
      --hold: #9a4a06;
      --either: #03679c;
      --rip: #046c4e;
      --flip: #b8113a;
      --skip: #475569;
      --danger: #b91c1c;
      --cyan: #0b6a83;
      --violet: #6d28d9;
      --sky: #03679c;
      --purple: #7e22ce;
      --on-purple: #ffffff;
      --up: #046c4e;
      --down: #b91c1c;
      --caveat: #8a5212;
      --caveat-border: #d8b88a;
      --holo: linear-gradient(100deg, #0891b2 0%, #7c3aed 35%, #db2777 55%, #b45309 80%, #0891b2 100%);
      --ov-02: rgba(16,18,24,0.025);
      --ov-03: rgba(16,18,24,0.035);
      --ov-05: rgba(16,18,24,0.05);
      --ov-06: rgba(16,18,24,0.06);
      --scrim: rgba(16,18,24,0.45);
      --shadow-panel: 0 20px 60px rgba(16,18,24,0.22);
      --shadow-toast: 0 8px 40px rgba(16,18,24,0.2);
      --accent-ink: 50%;
    }
  }
  :root[data-theme="light"] {
      color-scheme: light;
      --bg: #f4f5f8;
      --surface: #ffffff;
      --surface-2: #eceef3;
      --border: #d7dbe4;
      --border-strong: #b1b7c3;
      --text: #16181d;
      --text-dim: #565d6c;
      --text-faint: #646b7b; /* 4.6:1 on --surface-2, was #7a8192 at 3.6:1 */
      --hold: #9a4a06;
      --either: #03679c;
      --rip: #046c4e;
      --flip: #b8113a;
      --skip: #475569;
      --danger: #b91c1c;
      --cyan: #0b6a83;
      --violet: #6d28d9;
      --sky: #03679c;
      --purple: #7e22ce;
      --on-purple: #ffffff;
      --up: #046c4e;
      --down: #b91c1c;
      --caveat: #8a5212;
      --caveat-border: #d8b88a;
      --holo: linear-gradient(100deg, #0891b2 0%, #7c3aed 35%, #db2777 55%, #b45309 80%, #0891b2 100%);
      --ov-02: rgba(16,18,24,0.025);
      --ov-03: rgba(16,18,24,0.035);
      --ov-05: rgba(16,18,24,0.05);
      --ov-06: rgba(16,18,24,0.06);
      --scrim: rgba(16,18,24,0.45);
      --shadow-panel: 0 20px 60px rgba(16,18,24,0.22);
      --shadow-toast: 0 8px 40px rgba(16,18,24,0.2);
      --accent-ink: 50%;
  }

  * { box-sizing: border-box; }
  [hidden] { display: none !important; }

  html, body {
    background: var(--bg);
    color: var(--text);
    font-family: 'Inter', -apple-system, system-ui, sans-serif;
    margin: 0;
    padding: 0;
    -webkit-font-smoothing: antialiased;
  }

  body { padding: 20px 16px 60px; }

  /* Wide, not full-bleed — the card grid below already reflows to use
     extra width on its own (auto-fill/minmax), this just gives it room
     to actually do that on a real monitor instead of capping out at a
     narrow centered column. */
  .wrap { max-width: 1600px; margin: 0 auto; }

  header { margin-bottom: 22px; }

  .eyebrow {
    font-family: 'IBM Plex Mono', monospace;
    font-size: 11px;
    letter-spacing: 0.12em;
    text-transform: uppercase;
    color: var(--text-faint);
    margin-bottom: 6px;
  }

  h1 {
    font-family: 'Space Grotesk', sans-serif;
    font-weight: 700;
    font-size: 30px;
    margin: 0;
    letter-spacing: -0.01em;
    background: var(--holo);
    background-size: 300% auto;
    -webkit-background-clip: text;
    background-clip: text;
    color: transparent;
    animation: holo 9s linear infinite;
  }

  /* Icon mark (2026-09-02) — pairs with the h1's existing holo gradient
     rather than replacing it; the wordmark treatment already worked, this
     just gives the tab/bookmark something to show that isn't a generic
     globe. decorative alt="" on the <img> itself: the visible "TCIN
     Manager" text right beside it already carries the name. */
  .wordmark { display: flex; align-items: center; gap: 10px; }
  .wordmark-icon { flex: none; }

  @media (prefers-reduced-motion: reduce) {
    h1 { animation: none; }
  }

  @keyframes holo {
    0% { background-position: 0% 50%; }
    100% { background-position: 300% 50%; }
  }

  .sub { color: var(--text-dim); font-size: 14px; margin-top: 6px; }

  /* Sits beside the item count rather than in a nav bar: this site has
     exactly two pages, and a nav for two pages is more chrome than it
     earns. Same pill shape as the count so the pair reads as one row of
     metadata, tinted so it's visibly the clickable one. */
  .header-meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 8px;
    margin-top: 12px;
  }
  .header-meta .count-badge { margin-top: 0; }
  .how-link {
    font-family: 'IBM Plex Mono', monospace;
    font-size: 12px;
    color: var(--sky);
    text-decoration: none;
    border: 1px solid color-mix(in srgb, var(--sky) 35%, transparent);
    border-radius: 999px;
    padding: 4px 12px;
    display: inline-block;
  }
  .how-link:hover { border-color: var(--sky); background: color-mix(in srgb, var(--sky) 8%, transparent); }

  .count-badge {
    font-family: 'IBM Plex Mono', monospace;
    font-size: 12px;
    color: var(--text-dim);
    border: 1px solid var(--border);
    border-radius: 999px;
    padding: 4px 12px;
    display: inline-block;
    margin-top: 12px;
  }
  /* An explicit `display` on an element beats the browser's own
     `[hidden] { display: none }` default (author CSS always outranks
     user-agent CSS) — without this, a hidden count-badge would render as
     an empty pill instead of nothing while its data is still loading. */
  .count-badge[hidden] { display: none; }

  /* The two "fun stat" pills get a small colored dot (same 8px pattern
     as the tier legend's .dot below) so they read as their own thing
     rather than blending into the plain item count next to them. */
  .stat-badge { display: inline-flex; align-items: center; gap: 6px; }
  .stat-badge .dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
  .stat-badge .dot.community { background: var(--sky); }
  .stat-badge .dot.flagged { background: var(--danger); }
  /* Desktop keeps one row of pills; the wrapper only matters on a phone. */
  /* The two or three page links are one quiet row of text under the
     counts, at every width (2026-09-13); as pills they fought the count
     badges on the same row. .how-link keeps its pill look elsewhere. */
  .header-links { display: flex; flex-basis: 100%; flex-wrap: wrap; gap: 4px 18px; margin-top: 2px; }
  .header-links .how-link { border: 0; padding: 0; font-family: inherit; font-size: 13px; text-decoration: underline; text-underline-offset: 3px; }
  .header-links .how-link:hover { background: none; color: var(--text); }

  /* Phone header (2026-09-11, owner: "clean up the top for mobile"). Five
     pills wrapped into three rows of mixed stats and links above the fold.
     On a phone: no eyebrow (the tagline says the same), a smaller
     wordmark, the three stats as one quiet line of text, and the two links
     as plain text on their own line. */
  @media (max-width: 640px) {
    header { margin-bottom: 14px; }
    header .eyebrow { display: none; }
    h1 { font-size: 25px; }
    .wordmark-icon { width: 24px; height: 24px; }
    .sub { font-size: 13.5px; margin-top: 4px; }
    .header-meta { gap: 4px 14px; margin-top: 8px; }
    .header-meta .count-badge { border: 0; padding: 0; font-family: inherit; font-size: 13px; color: var(--text-dim); }
    .header-links { display: flex; flex-basis: 100%; gap: 16px; margin-top: 2px; }
    .header-links .how-link:hover { background: none; }
    /* The strip above already links to the full changes page, so the
       second "Recent changes" box is the same news twice on a phone. */
    #activityDetails { display: none; }
    /* An empty list has nothing to show, clear, copy or send. */
    /* Not ".empty": that class is the page's "nothing matches" block and
       restyles the whole bar. */
    .buy-list-bar.buy-list-empty button { display: none; }
    .buy-list-bar.buy-list-empty { padding: 10px 12px; }
    /* Under the button, not squeezed beside it. Scoped so it outranks the
       base .paste-panel-note rule further down. */
    .paste-panel .paste-panel-note { flex-basis: 100%; }
  }

  /* ---------- My-list bar ---------- */
  .buy-list-bar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 12px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    padding: 12px 14px;
    margin: 18px 0 12px;
  }
  .buy-list-count {
    font-family: 'Space Grotesk', sans-serif;
    font-weight: 700;
    font-size: 16px;
  }
  .buy-list-changes { font-size: 12px; color: var(--text-dim); flex: 1; }
  .buy-list-ship {
    flex-basis: 100%;
    font-size: 12px;
    color: var(--text-dim);
    line-height: 1.35;
  }
  .buy-list-bar .primary { margin-left: auto; }
  .buy-list-bar button + button { margin-left: 8px; }

  /* ---------- Paste-your-list ---------- */
  .paste-import {
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    margin-bottom: 12px;
    padding: 0 14px;
  }
  .paste-import summary {
    cursor: pointer;
    font-size: 13px;
    padding: 10px 0;
    user-select: none;
    color: var(--text-dim);
  }
  .paste-import summary:hover { color: var(--text); }
  .paste-import[open] summary { color: var(--text); }
  .paste-import-body { padding: 0 0 14px; display: flex; flex-direction: column; gap: 8px; }
  .paste-import-hint { font-size: 11px; color: var(--text-faint); }
  .paste-import textarea { width: 100%; min-height: 70px; resize: vertical; }
  .paste-import-actions { display: flex; justify-content: flex-end; }
  .paste-import-result { font-size: 12px; color: var(--text-dim); }
  .paste-import-result .unmatched { color: var(--text-faint); }


  /* The my-list bar is a single flex row, which on a phone crushed the
     title to three lines and the hint text to eight, each in its own
     narrow column (reported from a real device 2026-08-28 — a 375px
     emulation had not made it obvious). Stack it instead: title, then
     hint, then the buttons sharing the width on one row. */
  @media (max-width: 640px) {
    .buy-list-bar { flex-wrap: wrap; gap: 8px; padding: 12px; }
    .buy-list-count { width: 100%; font-size: 15px; }
    .buy-list-changes { width: 100%; flex: none; font-size: 11px; line-height: 1.35; }
    .buy-list-ship { font-size: 11px; }
    .buy-list-bar .primary { margin-left: 0; }
    .buy-list-bar button { flex: 1 1 0; margin-left: 0 !important; white-space: nowrap; }
    .buy-list-bar button + button { margin-left: 0; }
  }

  /* ---------- Controls ---------- */
  .controls {
    display: grid;
    /* 140px, not 150: the Market bar is search (two columns), four selects
       and the button group (two columns), eight in all, and at 150px a
       1300px screen only had seven, so the buttons wrapped (2026-09-16). */
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
    gap: 8px;
    margin-bottom: 14px;
    position: sticky;
    top: 0;
    z-index: 20;
    background: var(--bg);
    padding: 10px 0;
    border-bottom: 1px solid var(--border);
  }
  /* Sticky costs one row on a desktop and pays for itself. On a phone the
     same bar wraps to four rows — 183px measured at 375px wide, most of a
     quarter of the viewport — and it is pinned over the grid it exists to
     help you read. It scrolls away there instead; the filters are a few
     flicks up, and the products get their screen back. */
  @media (max-width: 640px) {
    .controls { position: static; }
  }
  .controls > * { min-width: 0; }
  input, select, textarea {
    background: var(--surface-2);
    border: 1px solid var(--border);
    color: var(--text);
    border-radius: 8px;
    padding: 8px 10px;
    font-size: 13px;
    font-family: 'Inter', sans-serif;
  }
  input:focus, select:focus, textarea:focus {
    outline: 2px solid var(--cyan);
    outline-offset: 1px;
  }
  /* Clear button, inside the field rather than beside it: the field is
     what you're clearing, and a separate control would take a whole grid
     cell for something that's blank most of the time. Hidden until
     there's actually something to clear, so it never sits there as dead
     weight. Escape does the same thing from the keyboard. */
  .search-clear {
    position: absolute;
    right: 6px;
    top: 50%;
    transform: translateY(-50%);
    background: none;
    border: none;
    color: var(--text-faint);
    font-size: 18px;
    line-height: 1;
    padding: 2px 5px;
    cursor: pointer;
    border-radius: 4px;
  }
  .search-clear:hover { color: var(--text); background: var(--surface-2); }

  .search-wrap { grid-column: span 2; position: relative; display: block; }
  .search { width: 100%; padding-right: 30px; }
  /* Below 500px the grid is two columns and .search spans both. */
  @media (max-width: 500px) {
    .search-wrap { grid-column: 1 / -1; }
  }

  button {
    font-family: 'Inter', sans-serif;
    font-weight: 600;
    font-size: 13px;
    border-radius: 8px;
    border: 1px solid var(--border);
    background: var(--surface-2);
    color: var(--text);
    padding: 8px 14px;
    cursor: pointer;
  }
  button:hover { border-color: var(--border-strong); }
  button.primary {
    background: linear-gradient(100deg, #67e8f9, #c4b5fd);
    color: #14161b;
    border: none;
    font-weight: 700;
  }
  button.primary:hover { filter: brightness(1.05); }
  /* Target's brand red — distinguishes the Poke Snipes hand-off button from
     the plain Copy button next to it without competing with .primary's
     cyan/violet gradient (used everywhere else as "the main action"). */
  button.target-red {
    background: #cc0000;
    color: #fff;
    border: none;
    font-weight: 700;
  }
  button.target-red:hover { filter: brightness(1.1); }
  button:focus-visible, select:focus-visible, input:focus-visible {
    outline: 2px solid var(--cyan);
    outline-offset: 2px;
  }

  /* ---------- Legend ---------- */
  .legend {
    display: flex;
    flex-wrap: wrap;
    gap: 6px 14px;
    font-size: 11px;
    color: var(--text-dim);
    margin: 4px 0 18px;
    padding: 10px 12px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius);
  }
  .legend .item { display: flex; align-items: center; gap: 6px; }
  .legend .dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
  .legend .dot.hold { background: var(--hold); }
  .legend .dot.either { background: var(--either); }
  .legend .dot.rip { background: var(--rip); }
  .legend .dot.flip { background: var(--flip); }
  .legend .dot.skip { background: var(--skip); }
  .legend .dot.tier-s { background: var(--violet); }
  .legend .dot.none { background: none; border: 1px dashed var(--text-faint); }
  /* Full-width lead line so the scale is explained before the letters,
     not inferred from them. */
  .legend-lead { flex-basis: 100%; color: var(--text); font-size: 11.5px; margin-bottom: 2px; }


  /* Recent-changes feed. Monospace + one glyph per kind keeps it quiet
     enough to sit above the catalog without competing with it. */
  .activity-list { display: flex; flex-direction: column; gap: 3px; max-height: 260px; overflow-y: auto; }
  .act-line {
    display: grid;
    grid-template-columns: 14px minmax(0,1.4fr) minmax(0,1.6fr) auto;
    gap: 8px; align-items: baseline;
    font-size: 11px; color: var(--text-dim);
    padding: 3px 0; border-bottom: 1px solid var(--ov-03);
  }
  .act-glyph { font-family: 'IBM Plex Mono', monospace; font-weight: 700; text-align: center; }
  .act-glyph.k-added  { color: var(--up); }
  .act-glyph.k-price  { color: var(--cyan); }
  .act-glyph.k-market { color: var(--hold); }
  .act-glyph.k-grade  { color: var(--violet); }
  .act-glyph.k-removed { color: var(--down); }
  .act-glyph.k-restored { color: var(--up); }
  .act-name { color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .act-what { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .act-when { color: var(--text-faint); font-size: 10px; font-family: 'IBM Plex Mono', monospace; }
  @media (max-width: 560px) {
    .act-line { grid-template-columns: 14px 1fr; }
    .act-what, .act-when { grid-column: 2; }
  }

  /* ---------- List ---------- */
  #listRoot {
    display: grid;
    /* 168px, the Market page's width (2026-09-16): 150px cut the read line
       ("21% off hi…") and left no room for a chip beside the tier. */
    grid-template-columns: repeat(auto-fill, minmax(168px, 1fr));
    gap: 12px;
  }
  #listRoot > .empty { grid-column: 1 / -1; }
  .family-heading {
    grid-column: 1 / -1;
    font-family: 'Space Grotesk', sans-serif;
    font-size: 12px;
    text-transform: uppercase;
    letter-spacing: 0.08em;
    color: var(--text-faint);
    margin: 22px 0 8px;
    padding-bottom: 6px;
    border-bottom: 1px solid var(--border);
    display: flex;
    align-items: center;
    gap: 8px;
  }
  .family-heading:first-child { margin-top: 0; }
  .family-heading .fam-count { margin-left: auto; font-weight: 400; text-transform: none; letter-spacing: 0; }
  /* Release year beside the set name. Sets are ordered newest-first, and
     without the year on screen a non-alphabetical list just reads as
     unsorted. Muted and lighter than the set name — it explains the
     order, it isn't information anyone came for. */
  /* Upcoming: the release date, in place of the NEW badge. Amber
     rather than the NEW cyan, and an outline rather than a fill —
     "you can't buy this yet" is worth knowing but less actionable than
     "this just landed", and the styling should say which is which
     before the text is read. */
  .s-soon-badge {
    position: absolute;
    top: 4px;
    left: 4px;
    z-index: 2;
    font-family: 'IBM Plex Mono', monospace;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.06em;
    padding: 2px 5px;
    border-radius: 4px;
    color: #fbbf24;
    background: rgba(20, 22, 27, 0.86);
    border: 1px solid rgba(251, 191, 36, 0.55);
  }
  /* Tonight's drop (2026-09-15): wins over NEW / JUST LISTED while the
     banner is up. DROPPING 100k until the instant, DROPPED for 24h after.
     Not a Target.com fact. */
  .s-drop-badge {
    position: absolute; top: 4px; left: 4px; z-index: 2;
    font-family: 'IBM Plex Mono', monospace;
    font-size: 9px; font-weight: 700; letter-spacing: 0;
    padding: 2px 5px; border-radius: 4px; white-space: nowrap;
    /* The amber is fixed, not the token (2026-09-16). --hold flips to a dark
       burnt orange in light mode, because there it is used as TEXT on white.
       Used as a BACKGROUND under near-black text it read 2.66:1 — worse than
       the gold box art this badge was just fixed to survive. JUST LISTED
       already hard-codes the same amber for the same reason. */
    color: #10202b; background: #fbbf24;
    /* A dark edge, not an amber one (2026-09-16). The fill reads 10:1 against
       its own text, but it sat on gold Pokémon box art and vanished into it;
       the amber border was the same colour as the fill, so it separated the
       tag from nothing. OCT 2 never had the problem because it is dark with
       amber text. This keeps the loud fill and borrows the separation. */
    border: 1px solid rgba(12, 14, 18, 0.85);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
  }
  /* JUST LISTED sits under the date: the same amber as "not out yet",
     filled rather than outlined because it is the fresher news. */
  .s-badge-stack {
    position: absolute; top: 4px; left: 4px; z-index: 2;
    display: flex; flex-direction: column; align-items: flex-start; gap: 3px;
  }
  .s-badge-stack .s-soon-badge { position: static; }
  .s-listed-badge {
    font-family: 'IBM Plex Mono', monospace;
    font-size: 9px; font-weight: 700; letter-spacing: 0.04em;
    padding: 2px 5px; border-radius: 4px; white-space: nowrap;
    color: #1f1603; background: #fbbf24;
    border: 1px solid rgba(12, 14, 18, 0.85);
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.45);
  }

  /* NEW sticker — an item Target put on sale inside the last 30 days
     (server-side `is_new`, main.py NEW_STREET_DATE_MAX_AGE_DAYS). This is now the ONLY thing that says
     "new": the card just glows in its set colour, so the sticker has to
     carry the message on its own, which is why it's a word and not a
     dot.

     Holo, because foil is the native visual language of this hobby — a
     card that catches the light is exactly the thing a collector's eye
     is trained to stop on, and it beats a flat cyan chip at the same
     size. Mechanically it's a wide multi-stop gradient (4x the element
     width) slid across by animating background-position, which is the
     cheap way to do this: no filters, no blend modes, no extra layers,
     and it composites on the GPU. Sits on the thumbnail because that's
     the one part of the card with no competing text. */
  .s-new-badge {
    position: absolute;
    top: 4px;
    left: 4px;
    z-index: 2;
    font-family: 'Space Grotesk', sans-serif;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.09em;
    padding: 2px 5px;
    border-radius: 4px;
    color: #10202b;
    background-image: linear-gradient(
      100deg,
      #a5f3fc 0%, #67e8f9 12%, #c4b5fd 24%, #fca5f1 36%,
      #fde68a 48%, #a3e635 60%, #67e8f9 72%, #c4b5fd 84%, #a5f3fc 100%
    );
    background-size: 400% 100%;
    background-position: 0% 50%;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.45);
    animation: holoShift 4.5s linear infinite;
  }
  @keyframes holoShift {
    to { background-position: -400% 50%; }
  }
  /* Motion is decoration, and for anyone prone to motion sickness it's
     decoration that costs them something. Frozen mid-gradient it still
     reads as foil, and the word NEW was always doing the real work. */
  @media (prefers-reduced-motion: reduce) {
    .s-new-badge { animation: none; background-position: 30% 50%; }
  }
  /* The set-heading counterpart: quieter (outline, not fill) because a
     heading marker is a wayfinding hint, not the thing itself. */
  .family-heading .fam-new {
    font-family: 'Space Grotesk', sans-serif;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.09em;
    padding: 1px 5px;
    border-radius: 4px;
    color: var(--cyan);
    border: 1px solid color-mix(in srgb, var(--cyan) 50%, transparent);
    text-transform: none;
  }
  .family-heading .fam-year {
    font-weight: 400;
    font-size: 0.85em;
    opacity: 0.55;
    text-transform: none;
    letter-spacing: 0;
  }

  /* Game-level heading, one level up from .family-heading — see
     web/static/index.html for why. */
  .game-heading {
    grid-column: 1 / -1;
    font-family: 'Space Grotesk', sans-serif;
    font-size: 16px;
    font-weight: 700;
    color: var(--text);
    margin: 30px 0 2px;
    padding-top: 16px;
    border-top: 2px solid var(--border);
    display: flex;
    align-items: center;
    gap: 10px;
  }
  .game-heading:first-child { margin-top: 0; padding-top: 0; border-top: none; }
  .game-heading .game-count { margin-left: auto; font-size: 12px; font-weight: 400; color: var(--text-faint); }
  .family-heading { cursor: pointer; user-select: none; }
  .family-heading .fam-toggle { font-size: 10px; color: var(--text-faint); width: 10px; display: inline-block; }
  .game-heading { cursor: pointer; user-select: none; }
  .game-heading .fam-toggle { font-size: 11px; color: var(--text-faint); width: 11px; display: inline-block; }


  .grade-detail {
    display: block;
    width: fit-content;
    font-family: 'IBM Plex Mono', monospace;
    font-size: 11px;
    color: var(--text-faint);
    margin-top: 0;
    padding: 5px 0;
    text-decoration: none;
  }
  a.grade-detail:hover { color: var(--cyan); text-decoration: underline; }
  /* "no market data" must NOT read like the confident "2.6x retail" line
     it sits in the same slot as — it means the grade has no resale
     evidence behind it at all. Amber + dashed outline reads as a caveat.
     Deliberately not any of the grade colors (--hold/--either/--rip/
     --flip/--skip) so it can't be misread as a grade chip. */
  .grade-detail.estimated {
    font-style: normal;
    color: var(--caveat);
    border: 1px dashed var(--caveat-border);
    border-radius: 4px;
    padding: 1px 5px;
    font-size: 9px;
    letter-spacing: 0.02em;
  }

  .empty {
    text-align: center;
    color: var(--text-faint);
    padding: 40px 0;
    font-size: 13px;
  }


  /* Auto / Light / Dark (DESIGN_light_mode.md). Auto is the default and
     follows the device; the head script stores a pinned choice under
     localStorage "tcin.theme". Deliberately quiet: most people never
     need it. */
  .theme-switch { display: inline-flex; gap: 2px; padding: 2px; border: 1px solid var(--border); border-radius: 999px; vertical-align: middle; }
  .theme-switch button {
    font-family: 'IBM Plex Mono', monospace; font-size: 10.5px; font-weight: 500; line-height: 1.4;
    color: var(--text-faint); background: transparent; border: 0; border-radius: 999px;
    padding: 3px 9px; margin: 0; cursor: pointer;
  }
  .theme-switch button:hover { color: var(--text); }
  .theme-switch button[aria-pressed="true"] { color: var(--text); background: var(--surface-2); }
  /* In the footer, not the header: the phone header was just cut down to
     the essentials, and Auto already does the right thing for nearly
     everyone. */
  .theme-foot { text-align: center; margin-top: 10px; }

  footer {
    text-align: center;
    color: var(--text-faint);
    font-size: 11px;
    margin-top: 30px;
  }

  .toast {
    position: fixed;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    background: var(--surface-2);
    border: 1px solid var(--border);
    color: var(--text);
    padding: 9px 16px;
    border-radius: 8px;
    font-size: 12px;
    opacity: 0;
    transition: opacity 0.25s;
    pointer-events: none;
    z-index: 80;
    max-width: min(420px, calc(100vw - 24px));
    text-align: center;
  }
  .toast.show { opacity: 1; }
  @media (max-width: 640px) {
    .toast { bottom: calc(20px + env(safe-area-inset-bottom)); }
    body:has(#jumpBar:not([hidden])) .toast { bottom: calc(80px + env(safe-area-inset-bottom)); }
  }

  /* Center-screen, high-visibility variant — used only for the "Copy for
     bot" hand-off. That flow opens a new Discord tab a beat later, which
     steals focus away from this page — the routine bottom-corner toast is
     easy to miss entirely when that happens, so this one is impossible to
     miss instead: dead center, larger, with a beat of scale-in motion. */
  .toast-big {
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%) scale(0.92);
    background: var(--surface-2);
    border: 1.5px solid var(--violet);
    box-shadow: var(--shadow-toast), 0 0 0 4px color-mix(in srgb, var(--violet) 12%, transparent);
    color: var(--text);
    padding: 20px 28px;
    border-radius: 12px;
    font-size: 15px;
    font-weight: 600;
    text-align: center;
    white-space: pre-line;
    max-width: 320px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 0.2s, transform 0.2s;
    z-index: 1000;
  }
  .toast-big.show { opacity: 1; transform: translate(-50%, -50%) scale(1); }
  .toast-big .tb-title { font-family:'Space Grotesk',sans-serif; font-size:17px; font-weight:700; margin-bottom:4px; }
  .toast-big .tb-body { font-size:12.5px; font-weight:400; color: var(--text-dim); margin-bottom:14px; }
  .toast-big .tb-actions { display:flex; gap:8px; justify-content:center; }
  .toast-big .tb-action {
    background:#5865F2; color:#fff; border:none; font-weight:700;
    font-size:12.5px; padding:9px 15px; border-radius:8px; cursor:pointer;
  }
  .toast-big .tb-action:hover { filter:brightness(1.08); }
  .toast-big .tb-dismiss {
    background:none; border:1px solid var(--border); color: var(--text-dim);
    font-size:12.5px; padding:9px 13px; border-radius:8px; cursor:pointer;
  }


  .simple-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
    gap: 8px;
  }

  .s-card {
    background: var(--surface);
    border: 1px solid var(--border);
    border-top-width: 3px;
    border-top-color: var(--accent, var(--border));
    border-radius: var(--radius);
    padding: 10px;
    display: flex;
    flex-direction: column;
    gap: 5px;
    transition: border-color 0.15s;
  }
  .s-card:hover { border-color: var(--border-strong); border-top-color: var(--accent, var(--border-strong)); }

  /* A NEW card keeps its NORMAL trim untouched — set colour on the 3px
     top, default border on the other three sides — and gains a glow in
     the set colour. It adds a signal rather than overwriting one.
     The earlier version painted cyan on three sides and left the set
     colour on top: two colours meaning two different things on one
     card, which read as a half-finished render rather than as two
     signals. One colour per card; the sticker below says "new" in
     actual words, so the border doesn't need to repeat it in a second
     hue.

     The glow is static. All the motion lives in the sticker instead:
     eighteen pulsing outlines is ambient movement that competes with
     itself and directs the eye nowhere, while one small animated badge
     per card is a focal point. `--accent` is the set colour, already
     set inline on every card. */
  /* 2026-09-16: a ring colour means one thing on every card — violet is
     "in my list". NEW, not-out-yet and tonight's drop keep their sticker on
     the photo and no longer colour the ring; with five ring rules nearly
     every card had one, so none of them signalled. */
  /* `.in-list` is a USER STATE and deliberately outranks the set glow —
     "I put this in my list" is the more actionable fact, and its violet
     ring plus the "✓ In list" button should not be muddied by a set
     colour behind them. It wins by source order at equal specificity,
     which is why this comment exists: it's intentional, not the CSS
     accident it replaced (in-list used to silently override the NEW
     border and nobody had decided that it should). The badge still
     shows, so a card never stops advertising that it's new. */
  .s-card.in-list {
    border-color: var(--violet);
    box-shadow: 0 0 0 1px var(--violet) inset;
  }
  /* "Submitted for review" was ambiguous — reviewed by whom, and does
     the visitor need to do something? Nobody reviews these: the private
     app pulls the queue and resolves them on its own. The wording and
     the hourglass say so, and the colour separates "waiting, this is
     fine" from the red of a flagged card, which previously looked like
     the same kind of state. */
  .s-queued {
    font-family: 'IBM Plex Mono', monospace;
    font-size: 11px;
    color: var(--sky);
    margin-top: 2px;
  }
  /* The paste panel: the site's primary action, styled to look like it.
     Accent border on the left ties it to the ticker above; the heading
     states the job in plain words rather than making you infer it from
     a collapsed summary row. */
  .paste-panel {
    background: var(--surface);
    border: 1px solid var(--border);
    border-left: 3px solid var(--violet);
    border-radius: var(--radius);
    padding: 14px 16px 12px;
    margin-bottom: 12px;
  }
  .paste-panel-head { margin-bottom: 8px; }
  .paste-panel h2 {
    font-family: 'Space Grotesk', sans-serif;
    font-size: 16px;
    font-weight: 600;
    margin: 0 0 2px;
    color: var(--text);
  }
  .paste-panel-sub { font-size: 13px; color: var(--text-dim); }
  .paste-panel textarea {
    width: 100%;
    min-height: 74px;
    resize: vertical;
    background: var(--bg);
    color: var(--text);
    border: 1px solid var(--border);
    border-radius: 8px;
    padding: 10px 12px;
    font-family: 'IBM Plex Mono', monospace;
    font-size: 13px;
    line-height: 1.5;
  }
  .paste-panel textarea:focus { outline: none; border-color: var(--violet); }
  .paste-panel-actions {
    display: flex;
    align-items: center;
    gap: 12px;
    margin-top: 9px;
    flex-wrap: wrap;
  }
  /* The caveats live here, small and after the button — they matter
     once you are typing, not before you have decided to. Putting them
     above the box (as a paragraph of hint text) made the panel look
     like reading rather than doing. */
  .paste-panel-note { font-size: 11.5px; color: var(--text-faint); flex: 1 1 220px; }

  /* Deliberately quieter than .how-link's pill-button treatment — this
     signposts a secondary path (browse), it doesn't compete with the
     panel's own primary action (Match my list). Text-level, muted,
     underlined only for the interactive word so it still reads as
     clickable rather than as more disclaimer copy. */
  .paste-panel-browse-hint {
    display: block;
    margin-top: 10px;
    font-size: 12px;
    color: var(--text-dim);
    text-decoration: none;
    border-bottom: 1px dashed transparent;
  }
  .paste-panel-browse-hint:hover {
    color: var(--text);
    border-bottom-color: var(--text-dim);
  }

  /* Recent changes is secondary to the panel below it, so it reads
     quieter — no accent edge, muted summary. */
  .paste-import.quiet { background: transparent; border-color: var(--border); }
  .paste-import.quiet > summary { color: var(--text-dim); font-size: 13px; }

  /* The strip: a held-still fact on the left, a scrolling stream on the
     right. Hidden entirely when neither has anything, because an empty
     strip is worse than no strip. */
  .highlight-strip {
    display: flex;
    align-items: center;
    gap: 14px;
    background: var(--surface);
    border: 1px solid var(--border);
    border-left: 3px solid var(--sky);
    border-radius: var(--radius);
    padding: 0 12px;
    margin-bottom: 12px;
    font-size: 13px;
    color: var(--text-dim);
    min-height: 40px;
    overflow: hidden;
  }
  .highlight-strip .hl-static { flex: none; white-space: nowrap; }
  .highlight-strip .hl-static strong { color: var(--text); font-weight: 600; }
  .highlight-strip .hl-count {
    background: none; border: none; padding: 6px 0; margin: 0; font: inherit; color: inherit; cursor: pointer;
    text-decoration: underline dotted color-mix(in srgb, var(--text-faint) 70%, transparent); text-underline-offset: 3px;
  }
  .highlight-strip .hl-count:hover strong, .highlight-strip .hl-count:focus-visible strong { color: var(--sky); }
  .highlight-strip .hl-sep { color: var(--text-faint); margin: 0 8px; }
  /* Tap a TCIN to copy it (2026-09-11) — kept identical to web/static/app.css. */
  .copy-tcin { background: none; border: none; padding: 0; margin: 0; font: inherit; color: inherit; cursor: copy; text-decoration: underline dotted transparent; text-underline-offset: 3px; }
  .copy-tcin:hover, .copy-tcin:focus-visible { text-decoration-color: currentColor; }
  @media (hover: none) { .copy-tcin { text-decoration-color: color-mix(in srgb, currentColor 45%, transparent); } }
  .highlight-strip .hl-count[aria-pressed="true"] strong,
  .highlight-strip .hl-count[aria-pressed="true"] { color: var(--sky); text-decoration-color: var(--sky); }

  /* Active filters: every filter in force as a removable chip, right under
     the controls. */
  .active-filters {
    display: flex; flex-wrap: wrap; align-items: center; gap: 6px;
    margin: -4px 0 12px; font-size: 13px; color: var(--text-dim);
  }
  .active-filters .af-lead { margin-right: 2px; }
  .active-filters .af-chip {
    display: inline-flex; align-items: center; gap: 6px; min-height: 32px; padding: 0 10px;
    border-radius: 999px; border: 1px solid color-mix(in srgb, var(--sky) 55%, transparent);
    background: color-mix(in srgb, var(--sky) 12%, transparent);
    color: var(--text); font: inherit; cursor: pointer;
  }
  .active-filters .af-chip .af-x { color: var(--text-dim); font-size: 12px; }
  .active-filters .af-chip:hover .af-x, .active-filters .af-chip:focus-visible .af-x { color: var(--text); }
  .active-filters .af-clear-all {
    background: none; border: none; color: var(--sky); font: inherit; cursor: pointer;
    text-decoration: underline; text-underline-offset: 3px; min-height: 32px; padding: 0 6px;
  }
  @media (max-width: 640px) {
    .active-filters { margin-top: 0; }
    .active-filters .af-chip, .active-filters .af-clear-all { min-height: 36px; }
  }
  .highlight-strip .hl-more {
    flex: none;
    background: none;
    border: none;
    color: var(--sky);
    text-decoration: underline;
    font-size: 12px;
    cursor: pointer;
    padding: 0;
    text-decoration: underline;
    white-space: nowrap;
  }

  /* Viewport clips; the track inside is twice the content and slides
     exactly -50%, so the loop has no seam. Masked at both edges so
     entries fade in and out instead of being guillotined by the border. */
  .ticker {
    flex: 1 1 auto;
    overflow: hidden;
    min-width: 0;
    -webkit-mask-image: linear-gradient(90deg, transparent, #000 24px, #000 calc(100% - 24px), transparent);
    mask-image: linear-gradient(90deg, transparent, #000 24px, #000 calc(100% - 24px), transparent);
  }
  .ticker-track { display: flex; width: max-content; }
  .ticker-track.scrolling { animation: tickerScroll linear infinite; }
  /* Hover and keyboard focus slow the ticker to a stop, and pointing at an
     entry opens its details (renderHighlights). The easing lives in JS:
     the CSS pause that used to be here stopped the track dead between
     pixels, and the text visibly jumped as the browser re-snapped it. */
  .ticker-run { display: flex; align-items: center; }
  @keyframes tickerScroll {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
  }

  .tick {
    display: inline-flex;
    align-items: baseline;
    gap: 6px;
    background: none;
    border: none;
    border-right: 1px solid var(--border);
    color: var(--text-dim);
    font: inherit;
    padding: 0 14px;
    cursor: pointer;
    white-space: nowrap;
  }
  .tick:hover .tick-name { color: var(--text); }
  .tick-name { color: var(--text-dim); }
  /* The set code carries the identity, so it gets the emphasis — the
     product type beside it is the qualifier, not the subject. */
  .tick-code {
    font-family: 'IBM Plex Mono', monospace;
    font-size: 11px;
    letter-spacing: 0.04em;
    color: var(--text);
    font-weight: 600;
  }
  .tick-move { font-family: 'IBM Plex Mono', monospace; font-size: 12px; }
  .tick.is-active { background: color-mix(in srgb, var(--sky) 10%, transparent); }
  .tick.is-active .tick-name { color: var(--text); }

  /* Details for the entry under the pointer (2026-09-11). */
  .tick-pop {
    position: fixed; z-index: 60; width: min(310px, calc(100vw - 24px));
    display: grid; grid-template-columns: 52px minmax(0, 1fr); gap: 3px 12px; align-items: start;
    padding: 12px; border-radius: 12px;
    background: var(--surface); border: 1px solid var(--border);
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.35);
    font-size: 12.5px; color: var(--text-dim); line-height: 1.35;
    pointer-events: none; opacity: 0; transform: translateY(-4px);
    transition: opacity 0.12s ease, transform 0.12s ease;
  }
  .tick-pop.show { opacity: 1; transform: none; }
  .tick-pop img, .tick-pop .tp-noimg { grid-row: 1 / span 4; width: 52px; height: 52px; object-fit: contain; background: #fff; border-radius: 8px; }
  .tick-pop .tp-name { color: var(--text); font-weight: 600; font-size: 13px; }
  .tick-pop .tp-move b { color: var(--text); font-weight: 600; }
  .tick-pop .tp-up { color: var(--up); font-weight: 600; }
  .tick-pop .tp-down { color: var(--down); font-weight: 600; }
  .tick-pop .tp-hint { color: var(--text-faint); font-size: 11.5px; }
  @media (prefers-reduced-motion: reduce) { .tick-pop { transition: none; transform: none; } }
  .tick-move.up { color: var(--up); }
  .tick-move.down { color: var(--down); }

  /* No motion: the same entries become a swipeable row. The content is
     identical, only the means of moving through it changes — nobody
     loses information for having motion turned off. */
  @media (prefers-reduced-motion: reduce) {
    .ticker { overflow-x: auto; }
    .ticker-track.scrolling { animation: none; }
    .ticker-run + .ticker-run { display: none; }
  }

  /* On a phone there is no room for a fixed label and a stream side by
     side; the fact goes on its own line above the stream. */
  /* Three stacked rows on a phone: the fact, the stream, then the link.
     Each takes the full width — sharing a row let "see all changes"
     land on top of the scrolling entries, since the ticker is masked at
     its edges and gave no visual edge for the link to sit against. */
  @media (max-width: 640px) {
    .highlight-strip {
      flex-wrap: wrap;
      padding: 8px 12px;
      gap: 4px 0;
      min-height: 0;
    }
    .highlight-strip .hl-static,
    .ticker,
    .highlight-strip .hl-more { flex: 0 0 100%; width: 100%; }
    .highlight-strip .hl-more { text-align: right; margin-left: 0; }
    .tick { padding: 0 10px; }
  }

  /* The spelled-out Target link on an unresolved card — see
     unresolvedCardHtml for why it isn't the bare arrow. Its actions row
     stacks instead of sitting side-by-side with the Add button (fixed
     2026-08-31): "Check on Target ↗" is real text, not the 30px icon
     every other card uses for this link, and on a narrow card sharing
     one row with it squeezed "+ Add" down to nothing — narrow enough
     that the button text wrapped mid-word into "+" over "Add". Full
     width each removes the squeeze instead of trying to out-narrow it. */
  /* :not(.blocked) — a blocked card is ALSO .unresolved, but its
     second action is .s-target-link (the fixed 30px icon every real
     card uses), which never had this problem. Scoping to plain
     unresolved cards only keeps that one exactly as it was. */
  .s-card.unresolved:not(.blocked) .s-actions { flex-direction: column; }
  .s-check-target {
    display: block;
    width: 100%;
    box-sizing: border-box;
    text-align: center;
    font-size: 11px;
    color: var(--text-dim);
    text-decoration: none;
    border: 1px solid var(--border);
    border-radius: 5px;
    padding: 6px 7px;
    white-space: nowrap;
  }
  .s-check-target:hover { color: var(--text); border-color: var(--border-strong); }

  /* Brief outline on the card a ticker entry pointed at. Without it you
     land in a grid of near-identical cards with no idea which one you
     asked for. Fades itself out; nothing to dismiss. */
  .s-card.jump-target {
    outline: 2px solid var(--sky);
    outline-offset: 2px;
    animation: jumpFade 2.2s ease-out forwards;
  }
  @keyframes jumpFade {
    0%, 55% { outline-color: var(--sky); }
    100%    { outline-color: transparent; }
  }
  @media (prefers-reduced-motion: reduce) {
    .s-card.jump-target { animation: none; }
  }

  /* One short line, not the 40-word caution this replaced. The point
     is to prompt a glance at the Target link right beside it, and a
     sentence someone actually reads does that better than a paragraph
     they skip. The full warning still appears in the confirm() gate on
     Add, which is where it has to be read. */
  .s-verify {
    font-size: 11px;
    line-height: 1.35;
    color: var(--text-faint);
    margin-top: 2px;
  }

  /* Placeholder art for a TCIN we have not resolved yet. An empty grey
     rectangle read as a broken image; a sealed-box outline with a "?"
     reads as "we don't know what this is yet", which is the truth. It's
     an inline SVG data URI rather than a file so the page stays
     self-contained and it costs no extra request — and it inherits the
     muted --text-faint colour so it recedes behind real product photos
     instead of competing with them. */
  .s-thumb-empty.unknown-art {
    background-image: url("data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%20fill%3D%22none%22%20stroke%3D%22%235c6273%22%20stroke-width%3D%222.2%22%20stroke-linejoin%3D%22round%22%20stroke-linecap%3D%22round%22%3E%3Cpath%20d%3D%22M32%208%2054%2019v26L32%2056%2010%2045V19z%22%2F%3E%3Cpath%20d%3D%22M10%2019l22%2011%2022-11M32%2030v26%22%20stroke-opacity%3D%22.45%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2241%22%20font-family%3D%22monospace%22%20font-size%3D%2217%22%20fill%3D%22%235c6273%22%20stroke%3D%22none%22%20text-anchor%3D%22middle%22%3E%3F%3C%2Ftext%3E%3C%2Fsvg%3E");
    background-repeat: no-repeat;
    background-position: center;
    background-size: 46%;
    opacity: 0.75;
  }
  /* Resale value on a flagged card. Neutral colour, not the danger red
     of the reason above it — it's information, not part of the warning. */
  .s-blocked-resale {
    font-size: 11.5px;
    color: var(--text-dim);
    margin-top: 2px;
  }
  /* The flag reason on an otherwise normal-looking card. */
  .s-blocked-why {
    font-size: 11.5px;
    line-height: 1.35;
    color: var(--danger);
    margin-top: 2px;
  }
  .s-card.unresolved {
    border-style: dashed;
    border-top-width: 1px;
    opacity: 0.7;
  }
  .s-card.unresolved .s-type { color: var(--text-dim); }
  .s-card.unresolved.blocked {
    border-style: solid;
    border-color: var(--danger);
    opacity: 1;
  }
  .s-card.unresolved.blocked .s-eyebrow { color: var(--danger); }
  .s-card.unresolved.blocked .s-tcin { color: var(--danger); }
  /* Already on the watch queue — a calmer, more confident look than a
     plain unresolved card (solid border, no faded opacity, blue accent
     matching the "community" dot) so it doesn't read as the same
     shrug-emoji unknown as a TCIN nobody has ever seen. */
  .s-card.unresolved.watching {
    border-style: solid;
    border-color: var(--sky);
    opacity: 1;
  }
  .s-card.unresolved.watching .s-eyebrow { color: var(--sky); }
  .s-caution {
    font-size: 0.72rem;
    line-height: 1.4;
    color: var(--hold);
    margin-top: 4px;
  }
  .s-thumb {
    position: relative;
    background: var(--surface-2);
    border-radius: 6px;
  }
  .s-thumb img, .s-thumb-empty {
    width: 100%;
    aspect-ratio: 1;
    object-fit: contain;
    border-radius: 6px;
    background: var(--surface-2);
    border: 1px solid var(--border);
    display: block;
  }
  .s-eyebrow {
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 10px;
    font-weight: 700;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    color: color-mix(in srgb, var(--accent, var(--text-faint)) var(--accent-ink), #000);
    min-height: 12px;
  }
  /* Clamped to exactly 2 lines, short or long — without this, a longer
     title pushes the grade row further down on that one card, so the
     grade chips end up at different heights across a row instead of
     lining up. Full title is still on the element's title="" attribute
     for hover. */
  .s-type {
    font-size: 13px;
    font-weight: 500;
    line-height: 1.3;
    min-height: 2.6em;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
  }
  .s-grade-row { display: flex; align-items: center; gap: 6px; min-height: 22px; }
  /* Context chip (In print, Launch window…) at the right end of the tier
     row, 2026-09-16; it used to have a row of its own that most cards
     left empty. */
  .s-grade-row .s-row-end { margin-left: auto; min-width: 0; overflow: hidden; display: flex; }
  .s-grade-row .s-ctx { height: 20px; font-size: 10.5px; padding: 0 6px; }
  .s-grade-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 18px;
    padding: 0 6px;
    border-radius: 5px;
    font-size: 10px;
    font-weight: 700;
    font-family: 'IBM Plex Mono', monospace;
  }
  .s-grade-chip.hold { background: color-mix(in srgb, var(--hold) 14%, transparent); color: var(--hold); }
  .s-grade-chip.either { background: color-mix(in srgb, var(--either) 14%, transparent); color: var(--either); }
  .s-grade-chip.rip { background: color-mix(in srgb, var(--rip) 14%, transparent); color: var(--rip); }
  .s-grade-chip.flip { background: color-mix(in srgb, var(--flip) 14%, transparent); color: var(--flip); }
  .s-grade-chip.skip { background: color-mix(in srgb, var(--skip) 14%, transparent); color: var(--skip); }
  .s-grade-row .grade-detail { display: inline; margin-top: 0; }

  /* Resale tier chip (2026-08-28). Ramp runs hot -> cold so rank reads at
     a glance without needing the legend: S violet, A amber, B blue,
     C green, D grey. The "none" state is a dash on the faintest possible
     styling — it must never look like a rating, because it isn't one. */
  .s-tier-chip {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 20px;
    height: 18px;
    padding: 0 6px;
    border-radius: 5px;
    font-size: 10px;
    font-weight: 700;
    font-family: 'IBM Plex Mono', monospace;
  }
  /* S is the chase tier, so it gets the holo treatment — same shimmer
     the page title uses, on the chip's own background. Deliberately the
     ONLY animated chip: if every tier shimmered it would be wallpaper
     instead of a signal. Honours prefers-reduced-motion below. */
  .s-tier-chip.tS {
    background: linear-gradient(100deg, #67e8f9 0%, #c4b5fd 30%, #fca5f1 50%, #fcd34d 70%, #67e8f9 100%);
    background-size: 300% auto;
    animation: holo 6s linear infinite;
    color: #14161b;
    text-shadow: 0 1px 0 rgba(255,255,255,0.25);
  }
  @media (prefers-reduced-motion: reduce) {
    .s-tier-chip.tS { animation: none; }
  }
  .s-tier-chip.tA { background: color-mix(in srgb, var(--hold) 14%, transparent);  color: var(--hold); }
  .s-tier-chip.tB { background: color-mix(in srgb, var(--either) 14%, transparent);  color: var(--either); }
  .s-tier-chip.tC { background: color-mix(in srgb, var(--rip) 14%, transparent);  color: var(--rip); }
  .s-tier-chip.tD { background: color-mix(in srgb, var(--skip) 14%, transparent); color: var(--skip); }
  .s-tier-chip.none {
    background: none;
    border: 1px dashed var(--border);
    color: var(--text-faint);
    font-weight: 400;
  }

  /* Price history sparkline + market-context chip + detail panel
     (2026-09-06, DESIGN_public_analytics.md). Byte-identical in
     web/static/index.html and public_site/index.html. */
  .s-spark {
    all: unset; cursor: pointer; display: inline-flex; align-items: center; min-height: 24px;
    color: var(--text-faint); line-height: 0; border-radius: 4px;
  }
  .s-spark svg { display: block; }
  .s-spark.up { color: var(--up); }
  .s-spark.down { color: var(--down); }
  .s-spark:hover, .s-spark:focus-visible { background: var(--ov-06); outline: none; }
  .s-spark.none { cursor: default; opacity: 0.55; }
  /* Names the sparkline's window; the hover text alone left it a guess. */
  /* Fixed label column so every card's line starts level, chart or not. */
  .s-spark-label { font-size: 11px; letter-spacing: 0.04em; color: var(--text-faint); min-width: 24px; }
  .s-read-why { color: var(--text-faint); }
  /* The context, as the first word of the read line (2026-09-16). It was a
     chip on the tier row; see cardReadHtml for why it moved. Same colours
     the chip had, so "Launch window" is still blue and "Reprint wave" still
     red; a word rather than a pill, so it takes a word's width. Clickable,
     like the chip, and the full reading is the tooltip. */
  .s-read-ctx { all: unset; cursor: pointer; font-weight: 600; color: var(--text-dim); }
  .s-read-ctx:hover, .s-read-ctx:focus-visible { text-decoration: underline; text-underline-offset: 2px; }
  .s-read-ctx.ctx-launch_window { color: var(--either); }
  .s-read-ctx.ctx-reprint_wave  { color: var(--danger); }
  .s-read-ctx.ctx-near_high     { color: var(--hold); }
  .s-read-ctx.ctx-recovering    { color: var(--rip); }
  .s-read-ctx.ctx-in_print      { color: var(--skip); }
  .s-read-ctx.ctx-stale         { text-decoration: underline dotted; text-underline-offset: 2px; }
  .s-spark.none:hover { background: none; }
  /* Chip on the left, sparkline on the right. The sparkline used to sit
     in the grade row, where on a phone-width card it squeezed the
     '1.2x retail' / 'no market price' label into two or three lines. */
  /* Chip and sparkline get a row each. They shared one until 2026-09-07,
     when a 94px "Launch window" chip plus the 18px label plus the 56px
     sparkline needed 180px inside a 128px card. Overflow is hidden so
     nothing paints outside a card. Empty chip/read rows collapse
     (2026-09-13 / 2026-09-15) so they don't add a blank band; the grid
     still stretches every card to the row height so sizes match. */
  .s-spark-row { min-height: 24px; display: flex; align-items: center; gap: 6px; margin: 2px 0 0; overflow: hidden; }
  .s-spark-row .s-spark { flex: 1 1 auto; min-width: 0; }
  .s-spark-row .s-spark svg { width: 100%; display: block; }
  .s-ctx {
    all: unset; cursor: pointer; display: inline-flex; align-items: center; height: 22px;
    padding: 0 7px; border-radius: 999px; border: 1px solid var(--border);
    font-size: 11px; font-weight: 600; letter-spacing: 0.02em; color: var(--text-dim);
    white-space: nowrap;
  }
  .s-ctx:hover, .s-ctx:focus-visible { border-color: var(--text-dim); color: var(--text); outline: none; }
  .s-ctx.ctx-launch_window { border-color: color-mix(in srgb, var(--either) 50%, transparent); color: var(--either); }
  .s-ctx.ctx-reprint_wave  { border-color: color-mix(in srgb, var(--danger) 50%, transparent); color: var(--danger); }
  .s-ctx.ctx-near_high     { border-color: color-mix(in srgb, var(--hold) 50%, transparent);  color: var(--hold); }
  .s-ctx.ctx-recovering    { border-color: color-mix(in srgb, var(--rip) 50%, transparent);  color: var(--rip); }
  .s-ctx.ctx-in_print      { border-color: color-mix(in srgb, var(--skip) 50%, transparent); color: var(--skip); }
  .s-ctx.ctx-stale         { border-style: dashed; }

  body.chart-open { overflow: hidden; }
  .chart-modal { position: fixed; inset: 0; z-index: 90; display: flex; align-items: center; justify-content: center; padding: 16px; }
  .chart-backdrop { position: absolute; inset: 0; background: var(--scrim); }
  .chart-panel {
    position: relative; width: min(760px, 100%); max-height: calc(100vh - 32px); overflow: auto;
    background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
    padding: 18px 20px 16px; box-shadow: var(--shadow-panel);
  }
  .chart-close {
    all: unset; cursor: pointer; position: absolute; top: 6px; right: 8px; width: 40px; height: 40px;
    display: inline-flex; align-items: center; justify-content: center; border-radius: 6px;
    color: var(--text-dim); font-size: 20px; line-height: 1;
  }
  .chart-close:hover { background: var(--ov-06); color: var(--text); }
  .chart-loading { padding: 40px 0; text-align: center; color: var(--text-dim); font-size: 13px; }
  .chart-head { padding-right: 44px; }
  /* Name on the left, controls top right, so the chart starts higher
     (2026-09-16, owner's note on the panel). The range buttons and the two
     source links were two full rows stacked above the chart, pushing it down
     the panel and costing height that the chart itself wanted. They are
     controls, not content: the 44px right padding is the close button's
     gutter, so the cluster stops short of it rather than under it.

     A modifier rather than styling .chart-head itself, because the set
     trajectory panel uses the same wrapper for a heading with no controls at
     all, and would otherwise lay its three lines out in a row. */
  .chart-head-split {
    display: flex; align-items: flex-start; justify-content: space-between;
    gap: 6px 18px; flex-wrap: wrap; margin-bottom: 10px;
  }
  .chart-head-split .chart-head-id { flex: 1 1 240px; min-width: 0; }
  .chart-head-split .chart-title { margin-bottom: 0; }
  /* One right-aligned row that wraps, rather than a fixed stack: a product
     with a single source link fits beside the range buttons, and one with
     two links plus Add wraps onto a second line instead of being squeezed. */
  .chart-head-split .chart-head-controls {
    flex: 0 1 auto; display: flex; flex-wrap: wrap; justify-content: flex-end;
    align-items: flex-start; gap: 6px 8px; max-width: 100%;
  }
  .chart-head-split .chart-ranges,
  .chart-head-split .chart-actions { margin: 0; justify-content: flex-end; }
  /* On a narrow screen there is no room beside a product name, so the cluster
     drops under it and reads left-to-right like everything else. */
  @media (max-width: 560px) {
    .chart-head-split .chart-head-controls { width: 100%; align-items: flex-start; }
    .chart-head-split .chart-ranges,
    .chart-head-split .chart-actions { justify-content: flex-start; }
  }
  .chart-eyebrow { font-size: 11px; font-weight: 700; letter-spacing: 0.06em; color: var(--text-faint); }
  .chart-title { font-size: 15px; font-weight: 600; margin: 2px 0 10px; line-height: 1.3; }
  .chart-ranges { display: flex; gap: 6px; margin-bottom: 8px; }
  .chart-range {
    all: unset; cursor: pointer; padding: 7px 12px; border-radius: 6px; border: 1px solid var(--border);
    font-size: 12px; color: var(--text-dim);
  }
  .chart-range.on { color: var(--text); border-color: var(--text-dim); background: var(--ov-05); }
  .chart-svg-wrap { width: 100%; }
  .chart { width: 100%; height: auto; display: block; overflow: visible; }
  .chart .cg { stroke: var(--border); stroke-width: 1; }
  .chart .cg.v { stroke-dasharray: 2 4; }
  .chart .cy, .chart .cx { fill: var(--text-faint); font-size: 10px; font-family: inherit; }
  .chart .cy { text-anchor: end; }
  .chart .cx { text-anchor: middle; }
  .chart .cline { fill: none; stroke: var(--cyan); stroke-width: 1.8; stroke-linejoin: round; stroke-linecap: round; }
  .chart .cband { fill: color-mix(in srgb, var(--cyan) 12%, transparent); stroke: none; }
  .chart .cdot { fill: var(--cyan); }
  .chart .cretail { stroke: var(--hold); stroke-width: 1; stroke-dasharray: 5 4; opacity: 0.8; }
  .chart .cretail-l { fill: var(--hold); font-size: 10px; text-anchor: end; opacity: 0.9; }
  .chart .crel { stroke: var(--text-dim); stroke-width: 1; stroke-dasharray: 3 3; }
  .chart .crel-l { fill: var(--text-dim); font-size: 10px; }
  .chart-stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(96px, 1fr)); gap: 8px; margin: 12px 0 10px; }
  .cs { background: var(--ov-03); border: 1px solid var(--border); border-radius: 8px; padding: 7px 9px; }
  .cs-k { display: block; font-size: 10px; color: var(--text-faint); letter-spacing: 0.04em; text-transform: uppercase; }
  .cs-v { display: block; font-size: 14px; font-weight: 600; margin-top: 2px; }
  .cs-v small { font-size: 10px; font-weight: 400; color: var(--text-dim); }
  .cs-v.up { color: var(--up); }
  .cs-v.down { color: var(--down); }
  /* The signal in a stat tile (2026-09-14): a plain coloured value like the
     tiles beside it. The card pill's dashed border ran past the tile edge. */
  .chart-stats .cs .mk-signal { border: 0; background: none; padding: 0; border-radius: 0; font-size: 14px; line-height: 1.3; white-space: normal; }
  .chart-context { display: flex; gap: 10px; align-items: flex-start; padding: 10px 12px; border: 1px solid var(--border); border-radius: 8px; margin-bottom: 10px; }
  .chart-context .s-ctx { cursor: default; flex: none; margin-top: 1px; }
  .chart-context p { margin: 0; font-size: 13px; color: var(--text); line-height: 1.45; }
  .chart-source { font-size: 11px; color: var(--text-faint); line-height: 1.4; }
  .chart-source a { color: var(--text-dim); }
  /* Section 10: set-trajectory pill on set headings, trajectory chart,
     index overlay. Identical in both index.html files. */
  .fam-traj {
    all: unset; cursor: pointer; display: inline-flex; align-items: center; gap: 4px;
    height: 16px; padding: 0 7px; border-radius: 999px; border: 1px solid var(--border);
    font-size: 10px; font-weight: 600; letter-spacing: 0.02em; text-transform: none;
    color: var(--text-dim); white-space: nowrap; margin-left: 6px;
  }
  .fam-traj small { font-size: 10px; font-weight: 400; color: var(--text-faint); }
  .fam-traj:hover, .fam-traj:focus-visible { border-color: var(--text-dim); color: var(--text); outline: none; }
  .fam-traj.pos-above { border-color: color-mix(in srgb, var(--hold) 50%, transparent); color: var(--hold); }
  .fam-traj.pos-below { border-color: color-mix(in srgb, var(--danger) 50%, transparent); color: var(--danger); }
  .fam-traj.pos-no_curve { color: var(--text-faint); border-style: dashed; }
  .chart-sub { font-size: 12px; color: var(--text-dim); margin: -6px 0 10px; line-height: 1.4; }
  .chart-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin: 2px 0 10px; }
  /* "Only my list" toggle in the buy-list bar. */
  #myListOnlyBtn.on { border-color: var(--either); color: var(--either); }
  /* Reported (unconfirmed) items. Dashed, not solid, and never tier-coloured:
     these must read as "claimed", not as checked catalog products. */
  /* Amber frame (2026-09-11): the grey dashed border barely showed on the dark page. Amber is the site's "not confirmed yet" colour; red stays for flagged listings. Cards inside stay plain. */
  .reported-section { margin: 14px 0 6px; padding: 12px 14px; border: 1px dashed color-mix(in srgb, var(--hold) 55%, transparent); background: color-mix(in srgb, var(--hold) 5%, transparent); border-radius: var(--radius); }
  .reported-head h2 { font-size: 14px; margin: 0 0 4px; color: var(--hold); }
  .reported-head p { font-size: 12px; color: var(--text-dim); margin: 0 0 10px; line-height: 1.45; }
  .reported-count { font-size: 11px; color: var(--text-faint); font-weight: 400; margin-left: 4px; }
  .reported-head p a { color: inherit; text-decoration: underline; }
  .s-card.reported { border-style: dashed; border-top-width: 1px; }
  .s-card.reported .s-eyebrow { color: var(--hold); }
  .s-card.reported .s-type { min-height: 0; }
  .s-reported-meta { font-size: 11.5px; color: var(--text); margin-top: 2px; }
  .s-reported-src { font-size: 10.5px; color: var(--text-faint); margin-top: 2px; line-height: 1.35; }
  /* Quiet secondary, only once a list exists — the primary CTA here
     stays single on purpose (see the onboarding note below). */
  /* Add and Replace are a pair (2026-09-15): one size and shape, Add
     filled, Replace outlined because it swaps the list out. It used to be
     a small underlined link with no way back; now Undo is offered. */
  .paste-panel-actions > button { min-height: 36px; }
  #pasteReplaceBtn { background: transparent; border-color: var(--border-strong); }
  #pasteReplaceBtn:hover { background: var(--surface-2); }
  @media (max-width: 640px) { .paste-panel-actions > button { min-height: 44px; } }
  .paste-only-link {
    all: unset; cursor: pointer; color: var(--either); text-decoration: underline;
    text-underline-offset: 2px; font-size: inherit;
  }
  .paste-only-link:hover { color: var(--sky); }
  #myListOnlyBtn:disabled { opacity: 0.45; cursor: default; }
  /* On a narrow card the Add button used to stretch across a whole row
     while the two links stayed compact; size it to its own text. */
  .chart-actions .track-btn { margin: 0; flex: 0 0 auto; }
  /* Market-event markers + the numbered legend under the chart. */
  .cev { stroke: var(--purple); stroke-width: 1; stroke-dasharray: 3 3; opacity: 0.85; }
  .cev-b { fill: var(--purple); }
  .cev-n { fill: var(--on-purple); font-size: 9px; font-weight: 700; text-anchor: middle; font-family: inherit; }
  .chart-events { list-style: none; margin: 8px 0 0; padding: 0; counter-reset: ev; }
  .chart-events li {
    counter-increment: ev; display: flex; flex-wrap: wrap; gap: 6px; align-items: baseline;
    font-size: 12px; color: var(--text-dim); padding: 3px 0;
  }
  .chart-events li::before {
    content: counter(ev); flex: none; width: 14px; height: 14px; border-radius: 50%;
    background: var(--purple); color: var(--on-purple); font-size: 9px; font-weight: 700;
    display: inline-flex; align-items: center; justify-content: center;
  }
  .chart-events .ev-d { font-family: inherit; color: var(--text-faint); }
  .chart-events .ev-k { color: var(--purple); }
  .chart-events .ev-t { color: var(--text); }
  .chart-events .ev-scope { color: var(--text-faint); font-size: 11px; }
  .chart-actions .track-btn { margin: 0; }
  .chart-link {
    display: inline-flex; align-items: center; height: 34px; padding: 0 10px; border-radius: 6px;
    border: 1px solid var(--border); font-size: 12px; color: var(--text-dim); text-decoration: none;
  }
  .chart-link:hover { color: var(--text); border-color: var(--text-dim); }
  .chart .tband { fill: color-mix(in srgb, var(--skip) 14%, transparent); stroke: none; }
  .chart .tmed { fill: none; stroke: var(--text-dim); stroke-width: 1.5; stroke-linejoin: round; }
  .chart .tset { fill: none; stroke: var(--cyan); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
  .chart .cindex { fill: none; stroke: var(--text-dim); stroke-width: 1.2; stroke-dasharray: 4 3; opacity: 0.8; }
  .chart-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 11px; color: var(--text-dim); margin: 6px 0 10px; }
  .lg::before { content: ''; display: inline-block; width: 14px; height: 0; border-top: 2px solid; vertical-align: middle; margin-right: 6px; }
  .lg-set::before { border-color: var(--cyan); }
  .lg-typ::before { border-color: var(--text-dim); }
  .lg-idx::before { border-color: var(--text-dim); border-top-style: dashed; }
  /* Pre-order asks (2026-09-13): grey and dashed on the card and in the
     chart, so they never read as the market line. */
  .s-spark.pre { color: var(--text-dim); }
  .chart .cpre { fill: none; stroke: var(--text-dim); stroke-width: 1.6; stroke-dasharray: 5 4; stroke-linejoin: round; }
  .chart .cdot.pre { fill: var(--text-dim); }
  .lg-pre::before { border-color: var(--text-dim); border-top-style: dashed; }
  /* What this lot paid, Portfolio only (data.paid). Violet is the user-state
     colour elsewhere, so it never reads as Target, MSRP or the market line. */
  .chart .cretail.cpaid { stroke: var(--violet); stroke-dasharray: 3 3; opacity: 0.95; }
  .chart .cpaid-l { fill: var(--violet); font-size: 10px; text-anchor: end; opacity: 0.95; paint-order: stroke; stroke: var(--surface); stroke-width: 3px; stroke-linejoin: round; }
  .lg-paid::before { border-color: var(--violet); border-top-style: dotted; }
  .chart-traj-title { font-size: 12px; font-weight: 600; color: var(--text-dim); margin: 12px 0 4px; text-transform: uppercase; letter-spacing: 0.04em; }
  /* The SVG scales with the panel, so at phone width its 10px axis
     text shrinks to ~5px. Doubling the size in viewBox units restores
     legibility; the layout itself already fits (checked at 375px). */
  @media (max-width: 600px) {
    .chart .cy, .chart .cx, .chart .cretail-l, .chart .cpaid-l, .chart .crel-l { font-size: 18px; }
    /* Two columns of stat tiles on a phone: three made every key wrap. */
    .chart-stats { grid-template-columns: repeat(2, 1fr); }
    .chart .cline { stroke-width: 2.5; }
    .chart .cdot { r: 5; }
  }

  .s-tcin {
    font-family: 'IBM Plex Mono', monospace;
    font-size: 11px;
    color: var(--text-faint);
  }
  .s-actions {
    margin-top: auto;
    padding-top: 2px;
    display: flex;
    gap: 6px;
  }
  .track-btn {
    background: transparent;
    border: 1px solid var(--border);
    color: var(--text-dim);
    flex: 1;
    font-size: 11px;
    padding: 6px 8px;
  }
  .track-btn:hover { border-color: var(--violet); color: var(--violet); }
  .track-btn.tracked {
    background: color-mix(in srgb, var(--violet) 16%, transparent);
    border-color: var(--violet);
    color: var(--violet);
  }
  .s-target-link {
    flex: 0 0 auto;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 30px;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    color: var(--text-dim);
    text-decoration: none;
    font-size: 13px;
  }
  .s-target-link:hover { border-color: var(--cyan); color: var(--cyan); }

  /* UX audit 2026-09-11. The price is the number people come for, so it no
     longer shares the faint TCIN colour. */
  .s-price { color: var(--text); }
  /* Phones: iOS Safari zooms the whole page when a field under 16px gets
     focus, and the filter dropdowns were 21px tall. !important because
     several fields set their own smaller size with a class. */
  @media (max-width: 640px) {
    input, select, textarea { font-size: 16px !important; }
    /* At 16px the placeholder takes three lines on a phone and was cut off
       in the 74px box. */
    .paste-panel textarea { min-height: 104px; }
    .controls select, .controls input { min-height: 44px; }
    /* A native select ignores min-height in some engines (macOS WebKit drew
       these at 25px). Drawing our own arrow makes the height stick
       everywhere. The grey arrow reads on both themes. */
    .controls select {
      -webkit-appearance: none; appearance: none;
      padding-right: 32px;
      background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%238b92a3' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
      background-repeat: no-repeat;
      background-position: right 12px center;
    }
  }

  /* UX audit 2026-09-11, month items: tap targets of at least 24px (WCAG 2.2),
     11px minimum for card numbers, and a dialog that takes keyboard focus. */
  .highlight-strip .hl-more, .tick { padding-top: 4px; padding-bottom: 4px; }
  .fam-traj { min-height: 24px; box-sizing: border-box; }
  .theme-switch button { min-height: 28px; }
  .chart-close:focus-visible, .chart-range:focus-visible { outline: 2px solid var(--cyan, #67e8f9); outline-offset: 2px; }
  @media (max-width: 640px) {
    .s-card .track-btn { min-height: 36px; }
    .s-card .s-target-link { min-width: 36px; min-height: 36px; }
    .header-links .how-link { padding-top: 6px; padding-bottom: 6px; }
    /* The y-axis labels are drawn larger on a phone and ran past the left edge. */
    .chart-svg-wrap { padding-left: 14px; }
  }
  /* Phone jump bar (2026-09-11, replaces the lone "Filters" pill). Mid-way
     down a ~33,000px catalog there was no quick way back to the top or to
     your list. Once the filter bar has scrolled away, a bar pinned to the
     bottom offers Top / Filters / My list, where a thumb already is. Phone
     only: wider screens keep the filter bar pinned to the top. */
  .jump-bar { display: none; }
  @media (max-width: 640px) {
    body { padding-bottom: 96px; }
    .jump-bar {
      position: fixed; left: 12px; right: 12px; bottom: calc(12px + env(safe-area-inset-bottom)); z-index: 60;
      display: grid; grid-template-columns: repeat(3, 1fr); gap: 4px; padding: 4px;
      border-radius: 14px; border: 1px solid var(--border); background: var(--surface);
      box-shadow: var(--shadow-panel, 0 10px 30px rgba(0,0,0,0.45));
    }
    .jump-bar[hidden], body.chart-open .jump-bar { display: none; }
    .jump-bar button {
      all: unset; box-sizing: border-box; cursor: pointer; min-height: 44px; border-radius: 10px;
      display: inline-flex; align-items: center; justify-content: center; gap: 6px;
      font-family: inherit; font-size: 14px; font-weight: 600; color: var(--text);
    }
    .jump-bar button:active { background: var(--surface-2); }
    .jump-bar button:focus-visible { outline: 2px solid var(--cyan, #67e8f9); outline-offset: -2px; }
    .jump-bar .jump-count {
      min-width: 20px; height: 20px; padding: 0 6px; border-radius: 999px; box-sizing: border-box;
      display: inline-flex; align-items: center; justify-content: center;
      font-size: 12px; font-weight: 700; background: var(--cyan, #67e8f9); color: var(--bg);
    }
    .jump-bar .jump-count:empty { display: none; }
    .jump-bar button.empty { color: var(--text-dim); }
  }

  .empty-clear { all: unset; cursor: pointer; color: var(--cyan, #67e8f9); text-decoration: underline; text-underline-offset: 2px; padding: 6px 2px; }
  .empty-clear:focus-visible { outline: 2px solid var(--cyan, #67e8f9); outline-offset: 2px; }

  /* Phone rows (2026-09-11). On a phone the 2-up cards were 384px tall, so
     229 products ran to about 55,000px, and neither dollar figure was easy
     to read. Below 640px each product is one row: image, name, TCIN,
     "Target $X -> Market $Y", tier and price chart, + Add. About 113px a
     row. Wider screens keep the grid; these elements stay hidden there. */
  .s-tcin-row, .s-money, .lbl-short { display: none; }
  @media (max-width: 640px) {
    .lbl-long { display: none; } .lbl-short { display: inline; }
    #listRoot.simple-grid { grid-template-columns: 1fr; gap: 8px; }
    #listRoot .s-card {
      display: grid; grid-template-columns: 84px minmax(0, 1fr) auto;
      grid-template-areas: "thumb type add" "thumb tcin add" "thumb money money" "thumb grade spark" "thumb read badges" "thumb msrp msrp";
      column-gap: 12px; row-gap: 4px; padding: 10px 12px 10px 10px; align-items: center;
      border-top-width: 1px; border-left: 3px solid var(--accent, var(--border));
    }
    #listRoot .s-card .s-thumb { grid-area: thumb; width: 84px; align-self: start; }
    #listRoot .s-card .s-thumb img, #listRoot .s-card .s-thumb .s-thumb-empty { border-radius: 8px; }
    #listRoot .s-card .s-soon-badge, #listRoot .s-card .s-new-badge, #listRoot .s-card .s-listed-badge, #listRoot .s-card .s-drop-badge { font-size: 9px; padding: 1px 4px; top: 3px; left: 3px; }
    #listRoot .s-card .s-badge-stack { top: 3px; left: 3px; gap: 2px; }
    #listRoot .s-card .s-eyebrow, #listRoot .s-card .s-ctx, #listRoot .s-card .s-tcin { display: none; }
    /* A phone row prints only the dollars it has. */
    #listRoot .s-card .s-money.no-market .m-market, #listRoot .s-card .s-money.no-market .m-sep { display: none; }
    #listRoot .s-card .s-money .m-none b { color: var(--text-faint); font-weight: 500; }
    /* Target's page is one tap away on a phone too — the same square ↗ as the desktop card. */
    #listRoot .s-card .s-actions { display: flex; gap: 6px; }
    #listRoot .s-card .s-target-link { width: 36px; min-width: 36px; min-height: 36px; }
    #listRoot .s-card .s-type { grid-area: type; min-height: 0; font-size: 14px; line-height: 1.3; align-self: start; }
    #listRoot .s-card .s-tcin-row { grid-area: tcin; display: block; font-family: 'IBM Plex Mono', monospace; font-size: 11.5px; color: var(--text-faint); margin-top: -2px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
    #listRoot.by-set .s-card .s-row-set { display: none; }
    #listRoot .s-card .s-money { grid-area: money; display: flex; align-items: baseline; flex-wrap: wrap; gap: 0 6px; font-size: 13px; }
    #listRoot .s-card .s-money b { color: var(--text); font-weight: 600; }
    #listRoot .s-card .s-money .m-lbl { font-size: 11px; color: var(--text-faint); }
    #listRoot .s-card .s-money .m-sep { color: var(--text-faint); }
    #listRoot .s-card .s-money .m-ship { font-size: 11px; font-weight: 600; color: var(--text-dim); }
    #listRoot .s-card .s-grade-row { grid-area: grade; margin: 0; display: flex; align-items: center; gap: 6px; min-width: 0; }
    #listRoot .s-card .s-grade-row .grade-detail { margin: 0; }
    #listRoot .s-card .s-spark-row { grid-area: spark; margin: 0; width: 88px; min-height: 24px; justify-self: end; }
    /* The read line was cut from the phone card for room, and the context
       chip with it, so a phone never learned a product was in a reprint
       wave or a launch window. One line, every card (2026-09-16). */
    #listRoot .s-card .s-read-row { grid-area: read; margin: 0; line-height: 18px; }
    /* The signal chip shares the read line's row on a phone (2026-09-16). */
    #listRoot .s-card .s-badges-row { grid-area: badges; min-height: 0; justify-self: end; }
    #listRoot .s-card .s-spark-row .s-spark-label { display: none; }
    #listRoot .s-card .s-actions { grid-area: add; margin: 0; padding: 0; align-self: start; }
    #listRoot .s-card .s-actions .track-btn { min-height: 36px; padding: 0 12px; white-space: nowrap; }
  }

  /* Spotted early: + Add and Check Target stack, like the other not-yet-live cards. */
  .s-card.reported .s-actions { flex-direction: column; }

/* ---------- Target catalog | Market switcher (2026-09-11, DESIGN_market_public.md 4.3) ---------- */
.site-switch {
  display: inline-flex; gap: 2px; margin-top: 12px; padding: 3px;
  border: 1px solid var(--border); border-radius: 999px; background: var(--surface);
}
.site-switch a {
  display: inline-flex; align-items: center; gap: 6px; min-height: 36px; padding: 0 16px;
  border-radius: 999px; font-size: 13.5px; font-weight: 600;
  color: var(--text-dim); text-decoration: none;
}
.site-switch a:hover { color: var(--text); }
.site-switch a[aria-current="page"] { background: var(--surface-2); color: var(--text); box-shadow: inset 0 0 0 1px var(--border-strong); }
.beta-mini, .beta-badge {
  font-family: 'IBM Plex Mono', monospace; font-weight: 500; letter-spacing: 0.04em; text-transform: uppercase;
  border: 1px solid color-mix(in srgb, var(--violet) 55%, transparent); color: var(--violet); border-radius: 999px;
}
.beta-mini { font-size: 9.5px; padding: 0 5px; line-height: 15px; }
.beta-badge { font-size: 11px; padding: 1px 8px; align-self: center; }

/* ---------- Market page (2026-09-11, DESIGN_market_public.md 4.2) ----------
   With the Market refinements and UI polish of 2026-09-11
   (DESIGN_market_ui_polish.md): signals, Pulse, set scorecards, Compare,
   table view, quiet cards, rows on a phone. IBM Plex Mono and --sky. */
:root { --mk-up: color-mix(in srgb, var(--up) 78%, var(--text-dim)); --mk-down: color-mix(in srgb, var(--down) 78%, var(--text-dim)); }
.mk-intro { font-size: 14px; color: var(--text-dim); line-height: 1.55; margin: 10px 0 0; max-width: 760px; }
.mk-intro a { color: var(--sky); white-space: nowrap; }
.mk-updated { font-size: 12px; color: var(--text-dim); margin: 4px 0 10px; }
.mk-tabs { display: flex; gap: 6px; overflow-x: auto; margin: 14px 0 0; padding-bottom: 2px; scrollbar-width: none; }
.mk-tabs::-webkit-scrollbar { display: none; }
.mk-tab { all: unset; cursor: pointer; white-space: nowrap; display: inline-flex; align-items: center; min-height: 36px; padding: 0 14px; border-radius: 999px; border: 1px solid var(--border); font-size: 13px; color: var(--text-dim); }
.mk-tab:hover { color: var(--text); }
.mk-tab:focus-visible { outline: 2px solid var(--sky); outline-offset: 2px; }
.mk-tab.active { color: var(--text); border-color: var(--sky); background: var(--surface); }
.mk-tab-n { font-family: 'IBM Plex Mono', monospace; font-size: 12px; color: var(--text-faint); margin-left: 6px; }

/* Pulse */
.mk-pulse { margin: 16px 0 8px; padding: 12px 14px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
/* One plain sentence before any number (2026-09-16). Pulse could say how
   many products were rising and never say how the market was doing. */
.mk-brief { margin: 2px 0 10px; font-size: 14px; line-height: 1.5; color: var(--text-dim); }
/* The summary's counts are for a CLOSED Pulse. Open, the brief says the same
   rising/cooling numbers in a sentence and the chips carry the rest, so the
   line would be said twice (2026-09-16). */
.mk-pulse[open] > summary .mk-pulse-sum { display: none; }
.mk-brief:empty { display: none; }
.mk-brief b { color: var(--text); font-weight: 600; }
.mk-brief-idx { border-bottom: 1px dotted var(--text-faint); cursor: help; }
.mk-brief .mk-pct { font-size: 13px; }
/* A set's own chip on its heading, when every product in it carries the
   same one. Same look as the card chip, but it is a statement, not a
   control. */
.family-heading .fam-ctx { cursor: default; }
.mk-pulse-head { display: flex; align-items: center; gap: 4px; margin: -4px 0 8px; }
.mk-pulse-head h2 { font-size: 13px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint); margin: 0; font-weight: 600; }
.mk-info { all: unset; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 999px; color: var(--text-faint); font-size: 16px; }
.mk-info:hover, .mk-info:focus-visible { color: var(--text); background: var(--surface-2); }
.mk-info:focus-visible { outline: 2px solid var(--sky); }
.mk-pulse-signals { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 12px; }
.mk-pulse-signals .mk-signal { box-sizing: border-box; display: inline-flex; align-items: center; gap: 6px; min-height: 32px; padding: 0 12px; cursor: pointer; background: none; font: inherit; font-size: 12px; font-weight: 600; }
.mk-pulse-signals .mk-signal b { font-family: 'IBM Plex Mono', monospace; font-weight: 600; }
.mk-pulse-signals .mk-signal[aria-pressed="true"] { background: color-mix(in srgb, currentColor 16%, transparent); border-color: currentColor; border-style: solid; }
.mk-pulse-signals .mk-signal:focus-visible { outline: 2px solid var(--sky); outline-offset: 2px; }
.mk-pulse-signals .mk-pulse-more { color: var(--text-faint); border: 1px dashed var(--border); font-weight: 500; }
/* Not a signal, so it is not in SIGNAL_ORDER: the count of products sitting
   at their highest market price in a year. Amber, like the near-high chip
   the cards used to carry (2026-09-16). */
.mk-pulse-signals .mk-high { color: var(--hold); border-color: color-mix(in srgb, var(--hold) 50%, transparent); }
.mk-method { font-size: 13px; color: var(--text-dim); line-height: 1.55; margin-top: 8px; }
.mk-method p { margin: 0 0 10px; }
.mk-method b { color: var(--text); font-weight: 600; }
.mk-method a { color: var(--sky); }
.mk-pulse-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 12px 24px; }
/* The movers fold: a heading-sized summary that reads like the list headings
   it hides, with the 7d/30d switch on its own line above the lists. */
.mk-movers-fold { margin-top: 2px; }
.mk-movers-fold > summary { cursor: pointer; list-style: none; display: flex; align-items: center; gap: 8px; min-height: 32px;
  font-size: 12px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint); }
.mk-movers-fold > summary::-webkit-details-marker { display: none; }
.mk-movers-fold > summary::before { content: '▸'; font-size: 11px; }
.mk-movers-fold[open] > summary::before { content: '▾'; }
.mk-movers-fold > summary:hover, .mk-movers-fold > summary:focus-visible { color: var(--text); outline: none; }
.mk-movers-bar { display: flex; justify-content: flex-end; margin: 2px 0 8px; }
.mk-movers h3 { font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint); margin: 0 0 4px; font-weight: 600; display: flex; gap: 8px; align-items: center; }
.mk-movers h3 .mk-win { margin-left: auto; display: inline-flex; gap: 2px; }
.mk-movers h3 .mk-win button { all: unset; box-sizing: border-box; cursor: pointer; display: inline-flex; align-items: center; min-height: 28px; padding: 0 10px; border-radius: 999px; border: 1px solid var(--border); color: var(--text-faint); font-size: 12px; text-transform: none; letter-spacing: 0; }
.mk-movers h3 .mk-win button[aria-pressed="true"] { color: var(--text); border-color: var(--border-strong); background: var(--surface-2); }
.mk-mover { display: grid; grid-template-columns: minmax(0, 1fr) auto auto; gap: 10px; align-items: baseline; width: 100%; text-align: left; padding: 6px 0; border: 0; border-bottom: 1px solid var(--border); background: none; color: inherit; font: inherit; cursor: pointer; font-size: 13px; }
.mk-mover:last-child { border-bottom: 0; }
.mk-mover:hover .mk-mover-name { color: var(--text); text-decoration: underline; }
.mk-mover-name { min-width: 0; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mk-mover-name small { color: var(--text-faint); font-size: 12px; margin-left: 6px; }
.mk-mover-price { font-family: 'IBM Plex Mono', monospace; font-size: 12px; color: var(--text); }

/* Index box: one sentence closed, the chart open. Closed on phones. */
.mk-compare { margin: 8px 0 8px; padding: 12px 14px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
.mk-compare h2 { font-size: 12.5px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint); margin: 0; font-weight: 600; }
.mk-compare summary { list-style: none; cursor: pointer; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; }
.mk-compare summary::-webkit-details-marker { display: none; }
.mk-compare summary h2::before { content: '▸ '; color: var(--text-faint); }
.mk-compare[open] summary h2::before { content: '▾ '; }
.mk-compare-sum { font-size: 13px; color: var(--text-dim); }
.mk-compare-grid { display: grid; grid-template-columns: 1fr; gap: 14px; margin-top: 8px; }
.mk-idx-game { font-size: 12.5px; color: var(--text-dim); margin-bottom: 4px; }
.mk-legend { display: flex; flex-wrap: wrap; gap: 12px; font-size: 12px; color: var(--text-dim); margin-top: 6px; }
.mk-legend[hidden], .mk-compare-help[hidden] { display: none; }
.mk-legend i { display: inline-block; width: 16px; height: 0; border-top: 2px solid currentColor; margin-right: 5px; vertical-align: middle; }
.mk-legend .m { color: var(--cyan); }
.mk-legend .t { color: var(--text-dim); }
.mk-compare-help { font-size: 12.5px; color: var(--text-dim); line-height: 1.5; margin: 8px 0 0; }

/* Controls: the extra buttons take two grid cells, so the row keeps one height. */
.mk-controls { align-items: center; }
.mk-controls .mk-controls-extra { grid-column: span 2; }
.mk-controls-extra { display: flex; flex-wrap: wrap; align-items: center; justify-content: flex-end; gap: 6px 8px; font-size: 13px; }
.mk-controls-extra > button { all: unset; cursor: pointer; color: var(--text-faint); min-height: 32px; }
.mk-controls-extra > button:hover, .mk-controls-extra > button:focus-visible { color: var(--text); }
.mk-view { display: inline-flex; gap: 2px; }
.mk-view button { all: unset; box-sizing: border-box; cursor: pointer; display: inline-flex; align-items: center; min-height: 32px; padding: 0 12px; border-radius: 999px; border: 1px solid var(--border); color: var(--text-faint); font-size: 12.5px; }
.mk-view button[aria-pressed="true"] { color: var(--text); background: var(--surface-2); border-color: var(--border-strong); }
.mk-view button:focus-visible { outline: 2px solid var(--sky); outline-offset: 2px; }

/* Set headings and scorecards */
.family-heading.mk-forced { cursor: default; }
.family-heading[data-mk-set]:focus-visible { outline: 2px solid var(--sky); outline-offset: 2px; }
.mk-list .family-heading { flex-wrap: wrap; row-gap: 4px; }
.mk-list .family-heading .fam-traj, .mk-list .family-heading .fam-traj small { font-size: 12px; }
.mk-list .family-heading .fam-year { font-size: 12px; }
.mk-elsewhere { all: unset; cursor: pointer; color: var(--sky); text-decoration: underline; }
.mk-flip { color: var(--text-faint); font-size: 12px; margin-left: 2px; white-space: nowrap; text-transform: none; letter-spacing: 0; font-family: inherit; }
.mk-score { grid-column: 1 / -1; display: flex; flex-wrap: wrap; gap: 4px 16px; font-size: 12.5px; color: var(--text-dim); margin: -4px 0 6px; padding: 6px 10px; border-left: 3px solid var(--accent, var(--border)); background: var(--ov-02); border-radius: 0 6px 6px 0; }
.mk-score b { color: var(--text); font-weight: 600; }
.mk-score .mk-breadth { display: inline-block; width: 70px; height: 6px; border-radius: 3px; background: var(--border); vertical-align: middle; overflow: hidden; margin: 0 6px 0 2px; }
.mk-score .mk-breadth i { display: block; height: 100%; background: var(--up); }
.mk-score button { all: unset; cursor: pointer; color: var(--text-dim); }
.mk-score button:hover { color: var(--text); text-decoration: underline; }

/* Cards: price, 30d, one read line, signal, a quiet sparkline, footer */
/* Wider than the Target page's 140px cards: the footer holds a shelf price and two buttons. */
.mk-list.simple-grid { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); } /* the catalog's minimum, so the two cards are the same width (2026-09-16) */
.mk-card .s-thumb img { background: #fff; }
.mk-card .s-thumb img.mk-pad { padding: 12%; }
.mk-card .s-thumb[data-chart], .mk-card .s-type[data-chart] { cursor: pointer; }
.mk-card .s-type[data-chart]:focus-visible { outline: 2px solid var(--sky); outline-offset: 2px; border-radius: 3px; }
.mk-card .s-type { min-height: 2.6em; }
/* The Market card wears the catalog card's money line (2026-09-16). The
   base rule hides .s-money and the catalog re-shows it under #listRoot;
   this does the same for Market, same sizes, so the two read as one card. */
.mk-card .s-money { display: flex; flex-direction: column; align-items: flex-start; min-height: 40px; font-size: 13px; }
.mk-card .s-money .m-market { order: -1; font-size: 16px; line-height: 21px; white-space: nowrap; }
.mk-card .s-money .m-market b { color: var(--text); font-weight: 700; }
.mk-card .s-money .m-lbl { font-size: 11px; color: var(--text-faint); }
.mk-card .s-money .m-sep, .mk-card .s-money .mk-pct { display: none; }
.mk-card .s-money .m-target { font-size: 12px; line-height: 17px; white-space: nowrap; }
.mk-card .s-money .m-target b { font-weight: 500; color: var(--text-dim); }
.mk-card .s-money .m-none b { color: var(--text-faint); font-weight: 500; }
.mk-card .s-eyebrow { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.mk-card .s-tcin:empty { min-height: 14px; }
.mk-card .s-actions .s-target-link:empty { visibility: hidden; }
.mk-card .s-actions { margin-top: auto; }
.mk-price-row { display: flex; align-items: baseline; gap: 6px; margin-top: 2px; }
.mk-now { font-family: 'Space Grotesk', sans-serif; font-size: 18px; font-weight: 700; color: var(--text); letter-spacing: -0.01em; }
.mk-now.none { font-size: 12px; font-weight: 500; color: var(--text-faint); font-family: inherit; }
.mk-pct { font-family: 'IBM Plex Mono', monospace; font-size: 12px; font-weight: 600; }
.mk-pct.up { color: var(--mk-up); } .mk-pct.down { color: var(--mk-down); } .mk-pct.flat { color: var(--text-faint); font-weight: 400; }
.mk-pct-lbl { font-size: 12px; color: var(--text-faint); }
/* "asking" beside a Market-card dollar that is a listing, not a sale. Its
   own class on purpose: the gate masks .mk-pct-lbl with the percentages,
   and a visitor must still be told that dollar is an ask (2026-09-16). */
.mk-ask-lbl { font-size: 12px; color: var(--text-faint); font-style: italic; }
/* On a Market card the dollar's own "asking" and the sparkline's "asking"
   land on the same line on a phone, so the sparkline's goes quiet there.
   Hidden, not removed, so the sparkline stays aligned with its neighbours. */
.mk-card:has(.mk-ask-lbl) .s-spark-label { visibility: hidden; }
.mk-read-row { font-size: 12px; line-height: 1.35; color: var(--text-dim); min-height: 2.7em; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
/* With "at its 1-yr high" off the card (2026-09-16) a product with no
   multiple has nothing to read, and the reserved two lines were a blank
   band. The footer is bottom-pinned, so collapsing it keeps the row of
   cards level. */
.mk-read-row:empty { min-height: 0; }
.mk-read-row .grade-detail { display: inline; padding: 0; margin: 0; font-family: inherit; font-size: 12px; color: var(--text-dim); }
.mk-dot { margin: 0 5px; color: var(--text-faint); }
.mk-value { font-size: 12px; color: var(--text-dim); }
/* Same clipping as the footer had (2026-09-16): a signal chip beside a
   context chip did not fit 169px, and nowrap + hidden cut "Too new" down to
   "T…". Chips wrap onto a second line instead of being rendered as one
   letter. */
.mk-badges-row, .s-badges-row { display: flex; flex-wrap: wrap; gap: 4px; min-height: 24px; align-items: center; }
.mk-badges-row .s-ctx { font-size: 12px; }
.mk-card .s-spark polyline { stroke-width: 1.25px; }
.mk-card .s-spark.up { color: color-mix(in srgb, var(--up) 80%, transparent); }
.mk-card .s-spark.down { color: color-mix(in srgb, var(--down) 80%, transparent); }
.mk-card .s-spark.flat { color: var(--text-faint); }
.mk-card .s-spark-label { font-size: 12px; }
/* The shelf price shares this row with three buttons, and on a 169px card
   there was nothing left: it rendered into 24px while needing 80, so every
   card showed "$…" or "N…" (2026-09-16). It gets its own line; the buttons
   keep theirs. */
.mk-foot { display: flex; flex-wrap: wrap; align-items: center; gap: 2px; margin-top: auto; padding-top: 4px; border-top: 1px solid var(--border); min-width: 0; }
.mk-foot .mk-add {
  flex: none; display: inline-flex; align-items: center; justify-content: center;
  min-height: 28px; padding: 0 10px; margin: 0 4px 0 0;
  font: inherit; font-size: 12px; font-weight: 600; line-height: 1; cursor: pointer;
  text-decoration: none; white-space: nowrap; appearance: none;
  color: var(--violet); border: 1px solid color-mix(in srgb, var(--violet) 55%, transparent);
  border-radius: 7px; background: transparent;
}
.mk-foot .mk-add:hover { color: var(--text); }
.mk-foot .mk-add:focus-visible { outline: 2px solid var(--sky); outline-offset: 1px; }
.chart-panel.mk-add-panel { width: min(420px, 100%); }
.mk-add-form { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 12px; align-items: end; margin: 4px 0 0; }
.mk-add-form label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--text-faint); min-width: 0; }
.mk-add-form input { font-size: 13px; height: 40px; width: 100%; box-sizing: border-box; }
.mk-add-form .span2 { grid-column: 1 / -1; }
.mk-add-form button.primary { grid-column: 1 / -1; min-height: 44px; }
.mk-add-help { font-size: 14px; color: var(--text-dim); line-height: 1.45; margin: 0 0 12px; }
.mk-shelf { font-size: 12px; color: var(--text-dim); white-space: nowrap; }
.mk-shelf.none { color: var(--text-faint); }
.mk-foot .mk-shelf { flex: 1 1 100%; min-width: 0; overflow: hidden; text-overflow: ellipsis; margin-bottom: 2px; }
.mk-cmp-btn { all: unset; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 7px; border: 1px solid transparent; color: var(--text-faint); cursor: pointer; flex: none; font-size: 15px; line-height: 1; }
.mk-cmp-btn:hover, .mk-cmp-btn:focus-visible { color: var(--text); border-color: var(--border); }
.mk-cmp-btn:focus-visible, .mk-more summary:focus-visible { outline: 2px solid var(--sky); outline-offset: 1px; }
.mk-cmp-btn[aria-pressed="true"] { color: var(--sky); background: color-mix(in srgb, var(--sky) 16%, transparent); border-color: color-mix(in srgb, var(--sky) 55%, transparent); }
.mk-cmp-btn:disabled { opacity: 0.35; cursor: default; border-color: transparent; }
.mk-more { position: relative; }
.mk-more summary { list-style: none; box-sizing: border-box; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 7px; border: 1px solid transparent; color: var(--text-faint); cursor: pointer; flex: none; font-size: 15px; line-height: 1; }
.mk-more summary::-webkit-details-marker { display: none; }
.mk-more summary:hover, .mk-more[open] summary { color: var(--text); border-color: var(--border); }
.mk-more .mk-actions { position: absolute; right: 0; bottom: 100%; z-index: 5; width: 170px; padding: 8px 10px; margin: 0 0 4px; background: var(--surface-2); border: 1px solid var(--border); border-radius: 8px; box-shadow: 0 8px 24px rgba(0,0,0,0.25); }
.mk-menu-links { display: flex; flex-direction: column; gap: 8px; }
.mk-menu-links a { color: var(--sky); font-size: 13px; text-decoration: none; }
.mk-menu-links a:hover { text-decoration: underline; }
.mk-stubs { grid-column: 1 / -1; display: flex; flex-wrap: wrap; align-items: center; gap: 6px; padding: 8px 10px; border: 1px dashed var(--border); border-radius: var(--radius); }
.mk-stubs-lbl { font-size: 12px; color: var(--text-faint); margin-right: 4px; }
.mk-stub { all: unset; cursor: pointer; font-size: 12px; color: var(--text-dim); padding: 3px 9px; border-radius: 999px; border: 1px solid var(--border); background: var(--surface); }
.mk-stub:hover { color: var(--text); border-color: var(--border-strong); }

/* Signals */
.mk-signal { font-size: 12px; line-height: 1.4; font-weight: 600; padding: 1px 8px; border-radius: 999px; border: 1px solid transparent; letter-spacing: 0.01em; white-space: nowrap; }
.sig-heating_up { color: var(--up); border-color: color-mix(in srgb, var(--up) 60%, transparent); background: color-mix(in srgb, var(--up) 14%, transparent); }
.sig-rising { color: var(--up); border-color: color-mix(in srgb, var(--up) 40%, transparent); }
.sig-bouncing_back { color: var(--up); border-color: color-mix(in srgb, var(--up) 40%, transparent); border-style: dashed; }
.sig-steady { color: var(--text-dim); border-color: var(--border); }
.sig-rolling_over { color: var(--caveat); border-color: color-mix(in srgb, var(--caveat) 50%, transparent); border-style: dashed; }
.sig-cooling { color: var(--caveat); border-color: color-mix(in srgb, var(--caveat) 50%, transparent); }
.sig-falling { color: var(--down); border-color: color-mix(in srgb, var(--down) 60%, transparent); background: color-mix(in srgb, var(--down) 12%, transparent); }
.sig-too_new { color: var(--text-faint); border-color: var(--border); border-style: dashed; }

/* Compare bar and chart */
.mk-cmp-bar { position: fixed; left: 50%; bottom: calc(18px + env(safe-area-inset-bottom)); transform: translateX(-50%); z-index: 40; display: flex; flex-wrap: wrap; align-items: center; gap: 8px; max-width: calc(100vw - 24px); box-sizing: border-box; padding: 8px 12px; background: var(--surface-2); border: 1px solid var(--border-strong); border-radius: 12px; box-shadow: 0 8px 30px rgba(0,0,0,0.3); font-size: 13px; }
.mk-cmp-bar[hidden] { display: none; }
.mk-cmp-bar .chip { display: inline-flex; align-items: center; gap: 6px; padding: 3px 8px; border-radius: 999px; border: 1px solid var(--border); background: var(--surface); }
.mk-cmp-bar .chip button { all: unset; cursor: pointer; color: var(--text-faint); min-width: 20px; text-align: center; }
.mk-cmp-bar > button { min-height: 36px; padding: 0 14px; border-radius: 999px; border: 1px solid var(--border); background: var(--surface); color: var(--text); font: inherit; cursor: pointer; }
.mk-cmp-bar > button.primary { background: var(--sky); border-color: var(--sky); color: var(--on-purple, #14161b); font-weight: 600; }
.mk-cmp-bar > button:disabled { opacity: 0.5; cursor: default; }
body.chart-open .mk-cmp-bar { display: none; }
.cmp-legend { display: flex; flex-wrap: wrap; gap: 6px 14px; font-size: 12px; margin: 6px 0 10px; }
.cmp-legend .lg::before { border-color: currentColor; }
.cmp-table-wrap { overflow-x: auto; }
.cmp-table { width: 100%; font-size: 13px; border-collapse: collapse; margin-top: 6px; }
.cmp-table th, .cmp-table td { text-align: right; padding: 5px 8px; border-bottom: 1px solid var(--border); white-space: nowrap; }
.cmp-table th:first-child, .cmp-table td:first-child { text-align: left; }
.cmp-table th { color: var(--text-faint); font-size: 12px; text-transform: uppercase; letter-spacing: 0.04em; font-weight: 600; }
.chart .cma { fill: none; stroke: var(--violet); stroke-width: 1.2; opacity: 0.85; }
.lg-ma::before { border-color: var(--violet); }
.cs-range { display: inline-block; position: relative; width: 64px; height: 6px; border-radius: 3px; background: linear-gradient(90deg, var(--down), var(--text-faint) 50%, var(--up)); vertical-align: middle; opacity: 0.85; }
.cs-range i { position: absolute; top: -3px; width: 3px; height: 12px; margin-left: -1.5px; border-radius: 2px; background: var(--text); }
.chart .chi { fill: var(--surface); stroke: var(--text); stroke-width: 1.5; }
.chart .chi-l { fill: var(--text-dim); font-size: 10px; }

/* Table view */
.mk-table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); }
.mk-table-wrap[hidden] { display: none; }
.mk-table { width: 100%; border-collapse: collapse; font-size: 13px; }
.mk-table th, .mk-table td { padding: 7px 10px; border-bottom: 1px solid var(--border); white-space: nowrap; text-align: right; }
.mk-table th:first-child, .mk-table td:first-child { text-align: left; }
.mk-table th { position: sticky; top: 0; background: var(--surface-2); color: var(--text-faint); font-weight: 600; font-size: 12px; letter-spacing: 0.04em; text-transform: uppercase; cursor: pointer; user-select: none; }
.mk-table th[aria-sort="ascending"]::after { content: ' ▲'; } .mk-table th[aria-sort="descending"]::after { content: ' ▼'; }
.mk-table td.name { max-width: 340px; overflow: hidden; text-overflow: ellipsis; color: var(--text); }
.mk-table td.name small { display: block; color: var(--text-faint); font-size: 12px; }
.mk-table td.name button { all: unset; cursor: pointer; }
.mk-table td.name button:hover { text-decoration: underline; }
.mk-table td.num { font-family: 'IBM Plex Mono', monospace; }
.mk-table td.num small { font-size: 12px; font-family: inherit; color: var(--text-faint); }
.mk-table tr:hover td { background: var(--ov-03); }
.mk-table-cap { padding: 8px 10px; font-size: 12px; color: var(--text-faint); border-bottom: 1px solid var(--border); position: sticky; left: 0; }
.mk-table td.cmp, .mk-table th.cmp { width: 1%; padding: 2px 8px; cursor: default; }
.mk-table .mk-pct.flat { font-weight: 400; }

@media (max-width: 640px) {
  .site-switch { display: flex; width: fit-content; }
  /* Each product is one row: photo; name; price, 30d and sparkline; the read
     line; signal left and shelf price with + Add, compare and ⋯ right. */
  .mk-list.simple-grid { grid-template-columns: 1fr; gap: 8px; }
  .mk-list .mk-card {
    display: grid; grid-template-columns: 72px minmax(0, 1fr) auto;
    grid-template-areas: "thumb name name" "thumb money spark" "thumb grade badges" "thumb read read" "thumb foot foot";
    column-gap: 12px; row-gap: 3px; padding: 10px 10px 8px; align-items: center;
    border-top-width: 1px; border-top-color: var(--border); border-left: 3px solid var(--accent, var(--border));
  }
  .mk-list .mk-card:hover { border-color: var(--border-strong); border-left-color: var(--accent, var(--border)); }
  .mk-list .mk-card .s-thumb { grid-area: thumb; width: 72px; align-self: start; }
  .mk-list .mk-card .s-type { grid-area: name; min-height: 0; font-size: 14px; }
  .mk-list .mk-card .s-money { grid-area: money; min-height: 0; flex-direction: row; flex-wrap: wrap; align-items: baseline; gap: 0 6px; }
  .mk-list .mk-card .s-money .m-market { order: 0; }
  .mk-list .mk-card .s-money .m-sep { display: inline; color: var(--text-faint); }
  .mk-list .mk-card .s-grade-row { grid-area: grade; min-height: 0; }
  .mk-list .mk-card .s-eyebrow, .mk-list .mk-card .s-tcin { display: none; }
  .mk-list .mk-card .s-spark-row { grid-area: spark; width: 84px; margin: 0; justify-self: end; }
  .mk-list .mk-card .s-read-row { grid-area: read; min-height: 0; }
  .mk-list .mk-card .s-badges-row { grid-area: badges; min-height: 36px; min-width: 0; justify-self: end; }
  .mk-list .mk-card .s-actions { grid-area: foot; margin: 0; padding: 0; border-top: 0; justify-self: end; }
  .mk-list .mk-card .mk-foot .mk-shelf { flex: none; margin-right: 2px; }
  .mk-card .s-spark { min-height: 32px; }
  .mk-cmp-btn, .mk-more summary { width: 36px; height: 36px; }
  .mk-foot .mk-add { min-height: 36px; }
  .mk-menu-links a { padding: 9px 0; }
  .mk-view button, .mk-movers h3 .mk-win button { min-height: 36px; }
  /* The signal chips wrapped to six rows, 222px, and pushed the first real
     price below the fold on a phone (measured 2026-09-16: gainers began at
     861px in an 844px viewport). One row that swipes, like the Portfolio
     stack. */
  .mk-pulse-signals {
    flex-wrap: nowrap; overflow-x: auto; scroll-snap-type: x proximity;
    -webkit-overflow-scrolling: touch; scrollbar-width: none; padding-bottom: 2px;
  }
  .mk-pulse-signals::-webkit-scrollbar { display: none; }
  .mk-pulse-signals .mk-signal { flex: none; scroll-snap-align: start; }
  .mk-brief { font-size: 13.5px; }
  .mk-controls-extra > button { min-height: 36px; }
  .mk-list .family-heading .fam-traj { min-height: 32px; }
  .mk-mover { align-items: center; padding: 8px 0; }
  .mk-mover-name { white-space: normal; }
  .mk-mover-name small { display: block; margin-left: 0; }
  .mk-score { gap: 2px 14px; }
  .mk-score > span:nth-child(n+2) { flex-basis: 100%; display: flex; align-items: center; gap: 6px; min-height: 32px; }
  .mk-score button { min-height: 32px; display: inline-flex; align-items: center; }
  .mk-stub { padding: 8px 12px; }
  .mk-table td.name { max-width: 180px; }
  .mk-table .col-7d { display: none; }
  .mk-table td.name button, .mk-table td.name .nm { display: block; box-sizing: border-box; min-height: 32px; padding: 8px 0 4px; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .mk-table td.name small { margin-top: -2px; }
  .theme-foot .theme-switch button { min-height: 36px; }
  .mk-cmp-bar .chip button { min-width: 32px; min-height: 36px; }
}

/* --- QA review fixes (2026-09-11): Pulse as a fold, status line, compare on
   phones, ⋯ menus, table header buttons, focus rings, tap targets. --- */
.mk-pulse summary { list-style: none; cursor: pointer; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; min-height: 32px; margin: -2px 0; }
.mk-pulse summary::-webkit-details-marker { display: none; }
.mk-pulse summary h2 { font-size: 13px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint); margin: 0; font-weight: 600; }
.mk-pulse summary h2::before { content: '▸ '; color: var(--text-faint); }
.mk-pulse[open] summary h2::before { content: '▾ '; }
.mk-pulse summary:focus-visible { outline: 2px solid var(--sky); outline-offset: 2px; border-radius: 4px; }
.mk-pulse-sum { font-size: 13px; color: var(--text-dim); }
.mk-pulse-head { display: flex; align-items: flex-start; gap: 6px; margin: 10px 0 0; }
.mk-pulse-head .mk-pulse-signals { flex: 1 1 auto; }
.mk-pulse-head .mk-info { flex: none; }
.mk-controls { margin-top: 14px; }
.mk-status { display: flex; flex-wrap: wrap; align-items: center; gap: 4px 12px; font-size: 13px; color: var(--text-dim); padding: 6px 2px 2px; }
.mk-status[hidden] { display: none; }
.mk-status .empty-clear, #marketRoot .empty-clear, #tableRoot .empty-clear { display: inline-flex; align-items: center; min-height: 32px; color: var(--sky); }
.mk-elsewhere { display: inline-flex; align-items: center; min-height: 32px; padding: 0 4px; }
.mk-table th .mk-th-btn { all: unset; cursor: pointer; display: inline-block; padding: 7px 0; font: inherit; color: inherit; text-transform: inherit; letter-spacing: inherit; }
.mk-table th { padding-top: 0; padding-bottom: 0; }
.mk-table th .mk-th-btn:focus-visible { outline: 2px solid var(--sky); outline-offset: -2px; border-radius: 3px; }
.mk-badges-row .mk-signal, .s-badges-row .mk-signal { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.cmp-table-wrap { margin-bottom: 12px; }
.cmp-cards { display: grid; gap: 8px; margin: 6px 0 12px; }
.cmp-card { border: 1px solid var(--border); border-left: 3px solid var(--border); border-radius: 8px; padding: 8px 10px; background: var(--ov-02); }
.cmp-card b { display: block; font-size: 13px; }
.cmp-card small { display: block; color: var(--text-faint); font-size: 12px; }
.cmp-card dl { display: grid; grid-template-columns: auto 1fr; gap: 3px 12px; margin: 6px 0 0; font-size: 12.5px; }
.cmp-card dt { color: var(--text-faint); }
.cmp-card dd { margin: 0; text-align: right; color: var(--text); }
.chart-stats-note { font-size: 12px; color: var(--text-faint); line-height: 1.45; margin: -4px 0 10px; }
.mk-cmp-bar .chip-name { max-width: 28ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* Every control that reset the browser outline gets a visible keyboard ring. */
:is(.mk-elsewhere, .mk-stub, .mk-score button, .mk-cmp-bar .chip button, .mk-cmp-bar > button, .mk-movers h3 .mk-win button, .mk-mover, .mk-menu-links a, .site-switch a, .empty-clear):focus-visible { outline: 2px solid var(--sky); outline-offset: 2px; border-radius: 4px; }
.beta-mini { font-size: 12px; }
.theme-switch button { font-size: 12px; }
@media (max-width: 640px) {
  .mk-list .family-heading[data-mk-set] { min-height: 36px; box-sizing: border-box; align-items: center; }
  /* Compare bar: a bottom sheet, chips on one scrolling line. */
  .mk-cmp-bar { left: 0; right: 0; bottom: 0; transform: none; max-width: none; border-radius: 12px 12px 0 0; border-bottom: 0; flex-wrap: nowrap; overflow-x: auto; gap: 6px; padding: 8px 10px calc(8px + env(safe-area-inset-bottom)); }
  .mk-cmp-bar .mk-cmp-lbl { display: none; }
  .mk-cmp-bar .chip { flex: none; }
  .mk-cmp-bar .chip-name { max-width: 30vw; }
  .mk-cmp-bar > button { flex: none; padding: 0 12px; }
  /* ⋯ menu: a sheet at the bottom of the screen instead of a popover that
     could open above the top edge. */
  .mk-more .mk-actions { position: fixed; left: 12px; right: 12px; bottom: calc(12px + env(safe-area-inset-bottom)); top: auto; width: auto; margin: 0; z-index: 45; padding: 10px 14px; box-shadow: 0 -8px 30px rgba(0,0,0,0.35); }
}

/* --- Target page, carried from main's public_site/index.html on the rebase of
   2026-09-11: phone tap targets (option B), the Poke Snipes icon button and
   the sized big toast. --- */
  /* Poke Snipes hand-off (2026-09-11): their Discord server icon, a copy
     hosted here so the page makes no request to Discord and a new icon on
     their side cannot break the image. Cropped to its circle with a thin
     white ring so it reads against Target red; the negative margin keeps
     the button the same height as Copy beside it. On a phone the button
     already has its own row and gets 44px. */
  button.target-red.send-snipes { display: inline-flex; align-items: center; justify-content: center; gap: 8px; }
  .send-snipes .snipes-icon { width: 22px; height: 22px; margin: -3px 0; flex: none; border-radius: 50%; background: #fff; box-shadow: 0 0 0 1.5px #fff; }
  @media (max-width: 640px) {
    .buy-list-bar .send-snipes { min-height: 44px; }
    /* The display above outranked the empty-list rule that hides the bar's
       buttons on a phone; hide this one again while the list is empty. */
    .buy-list-bar.buy-list-empty button.send-snipes { display: none; }
  }

  /* Option B (QA review 2026-09-11): 32px only where it costs no row height
     (set pill, ticker, header links, highlight counts); the TCIN copy link
     rises from 15px to the 24px WCAG 2.2 minimum. */
  @media (max-width: 640px) {
    #listRoot .s-card .s-tcin-row .copy-tcin { display: inline-flex; align-items: center; min-height: 24px; }
    #listRoot .s-card .s-tcin-row { margin-top: -6px; margin-bottom: -4px; }
    .family-heading .fam-traj { min-height: 32px; }
    .tick { min-height: 32px; }
    .header-links .how-link { display: inline-flex; align-items: center; min-height: 32px; }
    .highlight-strip .hl-count { display: inline-flex; align-items: center; min-height: 32px; }
  }

  /* The ↗ moved from "Send to Poke Snipes" to the toast's Open button: the
     first only copies and shows the toast, the second opens Discord. */
  .toast-big .tb-icon { display: block; width: 44px; height: 44px; margin: 0 auto 8px; border-radius: 50%; }
  /* The toast sat at left:50%, so it could only shrink to half the screen:
     187px wide on a phone, with its button wrapped onto four lines. Sized to
     its content up to 320px (or the screen), buttons stacked full width. */
  .toast-big { width: max-content; max-width: min(320px, calc(100vw - 32px)); box-sizing: border-box; padding: 20px 22px; }
  .toast-big .tb-actions { flex-direction: column; align-items: stretch; }
  .toast-big .tb-action, .toast-big .tb-dismiss { min-height: 40px; justify-content: center; white-space: nowrap; }

  /* Who Poke Snipes is, under the Copied! toast's buttons (2026-09-11). The
     link's padding gives it a 35px tap area without changing the line height. */
  .toast-big .tb-note { font-size: 12px; font-weight: 400; line-height: 1.45; color: var(--text-dim); margin: 12px 0 0; white-space: normal; }
  .toast-big .tb-note a { color: var(--sky); text-decoration: underline; text-underline-offset: 2px; white-space: nowrap; display: inline-block; padding: 9px 2px; margin: -9px 0; }

/* Market reads on catalog cards, Pulse and compare (2026-09-13). */
.s-read-row { margin: 2px 0 0; font-size: 12px; line-height: 20px; color: var(--text-dim); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.s-actions .mk-cmp-btn { width: 32px; height: 32px; }
.s-money .mk-pct { margin-left: 2px; }
/* Desktop cards (2026-09-14). Market is the large figure, Target.com
   small under it, both named (2026-09-15). The chart pins to the bottom
   of the tile so every card in the row is the same height; empty chip
   rows still collapse, so the leftover space is only what a neighbour
   actually printed. */
@media (min-width: 641px) {
  #listRoot .s-card .s-money { display: flex; flex-direction: column; align-items: flex-start; min-height: 40px; }
  #listRoot .s-card .s-money .m-market { order: -1; font-size: 16px; line-height: 21px; white-space: nowrap; }
  #listRoot .s-card .s-money .m-market b { color: var(--text); font-weight: 700; }
  #listRoot .s-card .s-money .m-lbl { font-size: 11px; color: var(--text-faint); }
  #listRoot .s-card .s-money .m-sep, #listRoot .s-card .s-money .mk-pct { display: none; }
  #listRoot .s-card .s-money .m-target { font-size: 12px; line-height: 17px; white-space: nowrap; }
  #listRoot .s-card .s-money .m-target b { font-weight: 500; color: var(--text-dim); }
  /* + ship is a small tag, not bold text run into the price (2026-09-16). */
  #listRoot .s-card .s-money .m-ship { font-size: 10px; font-weight: 600; color: var(--text-faint); border: 1px solid var(--border); border-radius: 4px; padding: 0 4px; margin-left: 2px; }
  #listRoot .s-card .s-money .m-none b { color: var(--text-faint); font-weight: 500; }
  #listRoot .s-card .s-spark-row { margin-top: auto; }
  #listRoot .s-card .s-actions { margin-top: 0; min-height: 34px; box-sizing: border-box; }
  #listRoot .s-card:not(.unresolved):not(.reported) .s-eyebrow { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
}
.act-community { margin-left: 6px; font-size: 11.5px; font-weight: 600; color: var(--sky); white-space: nowrap; }
.act-community::before { content: ''; display: inline-block; width: 7px; height: 7px; margin-right: 4px; border-radius: 50%; background: var(--sky); vertical-align: 1px; }
@media (max-width: 640px) {
  #listRoot .s-card .mk-cmp-btn { display: none; }
}
#communityAddedBadge { cursor: pointer; }
#communityAddedBadge:hover, #communityAddedBadge:focus-visible { color: var(--text); }

/* The site's name on the pinned filter bar (2026-09-13). */
.controls-brand { all: unset; box-sizing: border-box; cursor: pointer; display: inline-flex; align-items: center; gap: 8px; min-height: 36px; padding: 0 4px; font-family: 'Space Grotesk', sans-serif; font-size: 16px; font-weight: 700; letter-spacing: -0.01em; color: var(--text); white-space: nowrap; }
.controls-brand[hidden] { display: none; }
.controls-brand img { width: 20px; height: 20px; flex: none; }
.controls-brand:focus-visible { outline: 2px solid var(--cyan, #67e8f9); outline-offset: 2px; border-radius: 6px; }
/* No legend toggle any more: the legend shows under the filters on a wide
   screen and stays off a phone, where it cost ~300px before the first product. */
@media (max-width: 640px) { .legend { display: none; } }
/* Spotted early cards credit Poke Snipes with their icon (owner, 2026-09-13),
   the same copy the Send to Poke Snipes button uses. */
.s-reported-src { display: flex; align-items: center; gap: 5px; }
.s-reported-src .snipes-mini, .drop-tonight-via .snipes-mini { width: 16px; height: 16px; flex: none; border-radius: 50%; background: #fff; }
/* Site tour (2026-09-13, tour.js): a spotlight with a card beside it, and a
   one-time offer in the corner. */
.tour { position: fixed; inset: 0; z-index: 200; }
.tour-block { position: absolute; inset: 0; }
.tour-spot { position: fixed; border-radius: 12px; box-shadow: 0 0 0 9999px rgba(8, 10, 14, 0.62); outline: 2px solid var(--cyan); pointer-events: none; }
.tour-card { position: fixed; width: min(340px, calc(100vw - 32px)); box-sizing: border-box; padding: 16px 18px 14px; background: var(--surface); color: var(--text); border: 1px solid var(--border-strong); border-radius: 12px; box-shadow: var(--shadow-panel); }
.tour-card:focus { outline: none; }
.tour-count { font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--text-faint); }
.tour-title { margin: 4px 0 6px; font-family: 'Space Grotesk', sans-serif; font-size: 17px; font-weight: 700; color: var(--text); }
.tour-text { margin: 0 0 14px; font-size: 13.5px; line-height: 1.5; color: var(--text-dim); }
.tour-actions { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.tour-nav { display: flex; gap: 8px; }
.tour-nav button { min-height: 36px; padding: 0 14px; }
.tour-nav button[hidden] { display: none; }
.tour-skip { all: unset; cursor: pointer; font-size: 12.5px; color: var(--text-faint); text-decoration: underline; text-underline-offset: 3px; padding: 8px 2px; }
.tour-skip:hover, .tour-skip:focus-visible { color: var(--text); }
.tour-offer { position: fixed; right: 18px; bottom: calc(18px + env(safe-area-inset-bottom)); z-index: 70; display: flex; align-items: center; gap: 10px; max-width: min(460px, calc(100vw - 24px)); box-sizing: border-box; padding: 10px 10px 10px 16px; background: var(--surface); border: 1px solid var(--border-strong); border-radius: 12px; box-shadow: var(--shadow-panel); font-size: 13px; line-height: 1.4; color: var(--text-dim); }
.tour-offer b { color: var(--text); }
.tour-offer .primary { white-space: nowrap; min-height: 36px; padding: 0 14px; }
.tour-offer-x { all: unset; cursor: pointer; width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center; border-radius: 8px; color: var(--text-faint); flex: none; }
.tour-offer-x:hover, .tour-offer-x:focus-visible { color: var(--text); background: var(--ov-06); }
body.chart-open .tour-offer { display: none; }
@media (max-width: 640px) {
  .tour-offer { left: 12px; right: 12px; bottom: calc(12px + env(safe-area-inset-bottom)); max-width: none; display: grid; grid-template-columns: 1fr auto; padding: 12px 8px 12px 14px; }
  .tour-offer .primary { grid-column: 1 / -1; grid-row: 2; min-height: 44px; margin: 4px 6px 0 0; }
  .tour-offer-x { grid-column: 2; grid-row: 1; align-self: start; }
  body:has(#jumpBar:not([hidden])) .tour-offer { bottom: calc(80px + env(safe-area-inset-bottom)); }
  .tour-card { left: 12px; right: 12px; bottom: calc(12px + env(safe-area-inset-bottom)); width: auto; }
  .tour-nav button { min-height: 44px; }
}

/* MSRP beside Target's price (2026-09-13): the marked price tile in the
   panel and the second reference line on the chart. */
.chart-stats .cs.cs-basis { border-color: color-mix(in srgb, var(--cyan) 55%, transparent); }
.chart .cretail.cref2 { stroke: var(--text-dim); stroke-dasharray: 2 4; opacity: 0.7; }
.chart .cref2-l { fill: var(--text-dim); }
.mk-shelf.mk-target { color: var(--text-faint); }

/* Polish (2026-09-13). Rows with nothing in them take no space: a card
   with no badge and no read line used to show blank bands
   between the tier and the chart. The actions row pins to the bottom, so
   cards in a grid row still line up. */
/* 2026-09-16: rows no longer collapse. Every card has the same slots in the
   same order and the read line always has text, so tiles line up. */
/* The set pill says what its numbers are on a wide screen; a phone keeps
   the short form. */
.fam-traj .traj-short { display: none; }
@media (max-width: 640px) {
  .fam-traj .traj-long { display: none; }
  .fam-traj .traj-short { display: inline; }
}
/* The panel's stat explanations fold away; they were three lines of grey
   text under every chart. */
.chart-stats-note summary { cursor: pointer; color: var(--text-dim); list-style: none; }
.chart-stats-note summary::-webkit-details-marker { display: none; }
.chart-stats-note summary::before { content: '▸ '; }
.chart-stats-note[open] summary::before { content: '▾ '; }
.chart-stats-note p { margin: 4px 0 0; }
/* Chart labels keep a halo of the panel colour so they read over the line (2026-09-13). */
.chart .cretail-l, .chart .chi-l, .chart .crel-l { paint-order: stroke; stroke: var(--surface); stroke-width: 3px; stroke-linejoin: round; }

/* Sign in with Discord (2026-09-15, DESIGN_accounts.md). Hidden unless the
   account functions answer; the signed-in menu is a <details> popover. Top
   right of the header on every width, where people look for it. */
header { position: relative; }
.account { position: absolute; top: 0; right: 0; z-index: 20; display: inline-flex; align-items: center; }
.account[hidden] { display: none; }
.discord-btn .db-short { display: none; }
.discord-btn { display: inline-flex; align-items: center; gap: 7px; min-height: 32px; padding: 0 12px; border-radius: 999px; background: #5865F2; color: #fff; font-size: 13px; font-weight: 600; text-decoration: none; white-space: nowrap; }
.discord-btn:hover { background: #4752C4; }
.discord-btn:focus-visible, .account-menu summary:focus-visible { outline: 2px solid var(--sky); outline-offset: 2px; }
.account-menu { position: relative; }
.account-menu summary { list-style: none; display: inline-flex; align-items: center; gap: 7px; min-height: 32px; padding: 0 10px 0 4px; border: 1px solid var(--border); border-radius: 999px; background: var(--surface); cursor: pointer; font-size: 13px; font-weight: 600; color: var(--text); }
.account-menu summary::-webkit-details-marker { display: none; }
.account-menu summary::after { content: '\25BE'; font-size: 10px; color: var(--text-faint); }
.account-avatar { width: 22px; height: 22px; border-radius: 50%; flex: none; object-fit: cover; }
.account-initial { display: inline-flex; align-items: center; justify-content: center; background: #5865F2; color: #fff; font-size: 12px; }
.account-name { max-width: 16ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.account-pop { position: absolute; right: 0; top: calc(100% + 6px); z-index: 30; width: 260px; padding: 12px; border: 1px solid var(--border); border-radius: var(--radius); background: var(--surface); box-shadow: 0 8px 24px rgb(0 0 0 / 0.14); display: grid; gap: 8px; }
.account-note { margin: 0; font-size: 12.5px; line-height: 1.4; color: var(--text-dim); }
.account-pop button { min-height: 36px; text-align: left; }
.account-pop .account-danger { color: var(--danger); }
/* A phone keeps the header's first line free: "Sign in", and just the
   avatar once signed in (the menu says who). */
@media (max-width: 640px) {
  .discord-btn { min-height: 34px; padding: 0 11px; }
  .discord-btn .db-long, .account-name { display: none; }
  .discord-btn .db-short { display: inline; }
  .account-menu summary { padding: 0 8px 0 4px; min-height: 34px; }
  .account-pop { width: min(280px, calc(100vw - 32px)); }
}

/* Three tabs on a phone (2026-09-15): Target catalog, Market and Portfolio fit
   on one line at 375px with narrower padding. */
@media (max-width: 640px) {
  .site-switch a { padding: 0 10px; white-space: nowrap; }
}

/* --- UI consistency pass (2026-09-15, owner's comparison shots and a sweep
   of every page). --- */
/* One wordmark on every page: the gradient holds still, so the catalog and
   Market no longer show different colours depending on when they loaded. */
h1 { animation: none; background-position: 0% 50%; }
/* One field height in every filter bar, and a drawn arrow at every width:
   Safari drew native dropdowns at 25px beside a 34px search box. */
.controls input, .controls select { height: 36px; box-sizing: border-box; }
.controls select {
  -webkit-appearance: none; appearance: none;
  padding-right: 32px;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%238b92a3' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
}
/* Market's Expand all / Collapse all read as the same kind of control as
   Cards / Table beside them, at the fields' height. */
.mk-controls-extra > button { box-sizing: border-box; display: inline-flex; align-items: center; min-height: 36px; padding: 0 12px; border: 1px solid var(--border); border-radius: 999px; font-size: 12.5px; }
.mk-controls-extra { gap: 6px; }
.mk-view button { min-height: 36px; }
/* Pulse: the 7d / 30d toggle sits in the head row with the signal chips, so
   both movers headings are one height and their rows line up. */
.mk-pulse-head .mk-win { flex: none; display: inline-flex; gap: 2px; }
.mk-pulse-head .mk-win:empty { display: none; }
.mk-pulse-head .mk-win button { all: unset; box-sizing: border-box; cursor: pointer; display: inline-flex; align-items: center; min-height: 32px; padding: 0 12px; border-radius: 999px; border: 1px solid var(--border); color: var(--text-faint); font-size: 12.5px; }
.mk-pulse-head .mk-win button[aria-pressed="true"] { color: var(--text); background: var(--surface-2); border-color: var(--border-strong); }
.mk-pulse-head .mk-win button:focus-visible { outline: 2px solid var(--sky); outline-offset: 2px; }
/* Pinned Market bar keeps one row: the site name takes a cell, so Expand all /
   Collapse all step aside while pinned (they act on the list, which is what
   you are scrolling), leaving Cards / Table. Measured 57px either way at
   1280, not 57 then 101. */
.mk-controls.is-stuck .mk-controls-extra { grid-column: span 1; }
.mk-controls.is-stuck .mk-controls-extra > [data-mk-expand] { display: none; }
/* Panel titles share one style: "Spotted early" matched the small labels. */
.reported-head h2 { font-family: 'Space Grotesk', sans-serif; font-size: 16px; }
/* The Market freshness line sits under the intro, like the catalog's meta. */
header .mk-updated { margin: 8px 0 0; }
@media (max-width: 640px) {
  .mk-controls-extra > button, .mk-view button, .mk-pulse-head .mk-win button { min-height: 40px; }
}

/* --- Changes on your list (2026-09-15, DESIGN_list_alerts.md). --- */
.list-changes { margin: 0 0 14px; padding: 12px 14px; border: 1px solid var(--border); border-left: 3px solid var(--sky); border-radius: var(--radius); background: var(--surface); }
.list-changes[hidden] { display: none; }
.list-changes-head { display: flex; align-items: center; gap: 10px; }
.list-changes-head h2 { margin: 0; font-family: 'Space Grotesk', sans-serif; font-size: 16px; font-weight: 700; }
.list-changes-count { font-family: 'IBM Plex Mono', monospace; font-size: 12px; font-weight: 500; color: var(--text-faint); margin-left: 4px; }
.list-changes-head button { margin-left: auto; min-height: 36px; }
.list-changes-rows { list-style: none; margin: 8px 0 0; padding: 0; }
.lc-row { display: flex; align-items: center; gap: 10px; padding: 8px 0; border-top: 1px solid var(--border); }
.lc-thumb { flex: none; width: 40px; height: 40px; border-radius: 6px; background: var(--surface-2); overflow: hidden; }
.lc-thumb img { width: 100%; height: 100%; object-fit: contain; display: block; }
.lc-main { flex: 1; min-width: 0; font-size: 13px; line-height: 1.35; }
.lc-name { color: var(--text); font-weight: 600; }
.lc-tcin { font-family: 'IBM Plex Mono', monospace; font-size: 11.5px; color: var(--text-faint); margin-left: 6px; }
.lc-what { display: block; color: var(--text-dim); }
.lc-bad .lc-what { color: var(--down); }
.lc-good .lc-what { color: var(--up); }
.lc-remove { flex: none; min-height: 32px; font-size: 12.5px; }
.lc-more { padding: 8px 0 0; border-top: 1px solid var(--border); }
.lc-more button { min-height: 36px; }
/* A phone puts Remove under the text, which keeps its full width. */
@media (max-width: 640px) {
  .lc-row { flex-wrap: wrap; row-gap: 6px; }
  .lc-main { flex: 1 1 calc(100% - 50px); }
  .lc-remove { min-height: 40px; margin-left: 50px; }
  .lc-more button { min-height: 44px; }
}
/* A toast carrying an Undo button takes clicks while it shows. */
.toast.has-action { pointer-events: auto; display: flex; align-items: center; gap: 14px; }
.toast .toast-action { all: unset; cursor: pointer; font-weight: 700; color: var(--sky); padding: 4px 2px; }
.toast .toast-action:focus-visible { outline: 2px solid var(--sky); outline-offset: 2px; border-radius: 4px; }
/* A catalog card whose product page Target removed. Sits where the NEW badge
   would, so no row of the card moves. */
.s-status-badge { position: absolute; top: 4px; left: 4px; z-index: 3; font-family: 'Space Grotesk', sans-serif; font-size: 9px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; padding: 2px 5px; border-radius: 4px; color: #fff; background: var(--down); }

/* Tonight's Target drop (2026-09-15, DESIGN_drop_tonight.md). Amber edge
   matches Spotted early: expected stock, not Target fact. */
.drop-tonight {
  margin: 0 0 14px;
  padding: 12px 14px;
  border: 1px solid var(--border);
  border-left: 3px solid var(--hold);
  border-radius: var(--radius);
  background: var(--surface);
}
.drop-tonight[hidden] { display: none; }
.drop-tonight-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 8px 14px; }
.drop-tonight-head h2 {
  margin: 0;
  font-family: 'Space Grotesk', sans-serif;
  font-size: 16px;
  font-weight: 700;
}
.drop-tonight-when { margin: 0; font-size: 13px; color: var(--text-dim); }
.drop-tonight-when strong { color: var(--text); font-weight: 600; }
.drop-tonight-via { margin: 4px 0 0; font-size: 12px; color: var(--text-faint); display: flex; align-items: center; gap: 6px; line-height: 1.45; }
.drop-tonight-via a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
.drop-tonight-via a:hover { color: var(--sky); }
.drop-tonight-list { list-style: none; margin: 8px 0 0; padding: 0; }
.drop-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 7px 0;
  border-top: 1px solid var(--border);
  font-size: 13px;
  line-height: 1.35;
}
.drop-name { flex: 1; min-width: 0; font-weight: 600; color: var(--text); }
.drop-name a { color: inherit; text-decoration: none; }
.drop-name a:hover { color: var(--sky); }
.drop-stock {
  flex: none;
  font-family: 'IBM Plex Mono', monospace;
  font-size: 12px;
  font-weight: 500;
  color: var(--hold);
}
@media (min-width: 900px) {
  .drop-tonight-list {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 28px;
  }
}
@media (max-width: 640px) {
  .drop-tonight { padding: 12px; }
  .drop-tonight-head h2 { font-size: 15px; }
  .drop-row { padding: 8px 0; }
}

/* Catalog first screen (2026-09-16). The paste panel is a disclosure; its
   heading is the summary. The empty list bar drops its buttons, which do
   nothing with nothing on the list, and keeps its one-line hint. */
.paste-panel > summary.paste-panel-head { cursor: pointer; list-style: none; display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 12px; }
.paste-panel > summary.paste-panel-head::-webkit-details-marker { display: none; }
.paste-panel > summary.paste-panel-head h2::before { content: '\25B8'; display: inline-block; width: 1em; font-size: 0.8em; color: var(--text-faint); }
.paste-panel[open] > summary.paste-panel-head h2::before { content: '\25BE'; }
.paste-panel:not([open]) { padding-bottom: 12px; }
.paste-panel:not([open]) > summary .paste-panel-sub { color: var(--text-faint); }
.buy-list-bar.buy-list-empty > button, .buy-list-bar.buy-list-empty .buy-list-ship { display: none; }
.buy-list-bar.buy-list-empty { padding-top: 10px; padding-bottom: 10px; }

/* My list's TCINs: one line until tapped (2026-09-17). */
.buy-list-changes.is-list { min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer; font-variant-numeric: tabular-nums; }
/* Open, the numbers take a row of their own under the buttons, full width. */
.buy-list-bar { flex-wrap: wrap; }
.buy-list-changes.is-list.open { white-space: normal; overflow: visible; order: 9; flex: 0 0 100%; line-height: 1.6; }
.buy-list-bar:has(.buy-list-changes.open) .buy-list-count { margin-right: auto; }
.buy-list-bar:has(.buy-list-changes.open) .primary { margin-left: 0; }

/* Tonight's drop: act on it where you see it (2026-09-17). */
.drop-row .drop-add { flex: none; margin: 0; min-height: 30px; padding: 0 10px; font-size: 12px; }
.drop-tonight-actions { display: flex; justify-content: flex-end; margin-top: 8px; }
.drop-tonight-actions .track-btn { margin: 0; min-height: 34px; }
.drop-tonight-actions .track-btn[disabled] { cursor: default; opacity: .8; }
@media (max-width: 640px) {
  .drop-row .drop-add { min-height: 34px; }
  .drop-tonight-actions .track-btn { width: 100%; min-height: 40px; }
}
