:root {
  --bg: #0f1115;
  --bg-rgb: 15, 17, 21; /* --bg's channels, for rgba() compositing (e.g. the topbar fade) */
  --surface: #171a21;
  --surface-2: #1f232c;
  --line: #2a2f3a;
  --text: #e8eaed;
  --muted: #9aa3b2;
  --accent: #34d399; /* emerald pulse */
  --up: #34d399;    /* under / good / up */
  --down: #f87171;  /* over */
  --stale: #f59e0b; /* stale / estimate: calm amber, not alarm */
  --on-accent: #06281c; /* text/icon color on accent-filled surfaces: buttons, pills, active tabs, toggle knob */
  --stale-surface: #7c5e12; /* amber surface for the offline banner -- "remembered not live" */
  --on-stale: #ffedb0; /* text on --stale-surface */
  --scrim: rgba(0, 0, 0, 0.92); /* full-screen modal backdrop (sheets, the image viewer) */
  --pagegrid-line: rgba(255, 255, 255, 0.55); /* binder crop-cell grid overlay lines */
  /* Missing-art placeholder: the landing page's stylized card look (holo
     gradient + a diagonal sheen streak) instead of a flat blank, so a card
     with no image (and every Riftbound card while its art is hidden) reads as
     a real card back rather than an empty box. Values mirror css/landing.css. */
  --holo-a: #37d6a636;
  --holo-b: #8b7bff33;
  --holo-c: #f2c14e2e;
  --sheen: rgba(255, 255, 255, 0.07);
  --art-missing-bg:
    linear-gradient(115deg, transparent 35%, var(--sheen) 48%, transparent 62%),
    radial-gradient(120% 80% at 20% 0%, var(--holo-b), transparent 55%),
    radial-gradient(130% 90% at 90% 20%, var(--holo-a), transparent 60%),
    conic-gradient(from 210deg at 60% 40%, var(--holo-c), transparent 40%),
    linear-gradient(160deg, var(--surface-2), var(--surface));
  --radius: 8px; /* containers: fields, sheets, buttons */
  --radius-sm: 6px; /* chips, badges, small controls; full-round is reserved for the shutter, toggles and the queue count */
  --tabbar-h: 56px;
  --price-col: 84px; /* the fixed right-hand price column every list shares, so figures align screen to screen */
  --thumb-w: 40px;   /* row thumbnail: a card at 5:7 */
  --thumb-h: 56px;
  /* #251 finish chips, reworked 2026-09-12: one flat dark pill at the art's
     bottom-left, one tint per finish, no gradient (the rainbow sticker on the
     name plate fought the art). Tokens per the palette guard. */
  --chip-bg: rgba(14, 16, 22, 0.84);
  --chip-line: rgba(255, 255, 255, 0.18);
  --chip-holo: #e4dcff;   /* lavender white */
  --chip-rev: #c6d0dc;    /* silver */
  --chip-first: #ffd166;  /* gold */
  --chip-foil: #b9e0ee;   /* steel blue */
}

* { box-sizing: border-box; }
html { height: 100%; overflow-x: hidden; } /* #111: viewport-safety guard -- if any child ever overflows document width, clip it here instead of letting mobile browsers rescale the whole visual viewport (zoom-in, horizontal scrollbar, stuck pinch-zoom). Vertical scroll is untouched. */
body {
  margin: 0;
  min-height: 100%; /* not height:100%: the body must grow past one screen, or overflow rows spill below the padding and the fixed tab bar covers the last item */
  overflow-x: hidden; /* #111: same guard, belt-and-suspenders with html above */
  background: var(--bg);
  color: var(--text);
  font: 15px/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  -webkit-tap-highlight-color: transparent;
}
[hidden] { display: none !important; }
button { font-family: inherit; }

/* Design-system baseline for every text box (#75). Wrapped in :where() so it
   carries ZERO specificity (0,0,0): every bespoke .*-input class still wins
   unchanged, and this only paints controls no bespoke rule reaches (e.g. the
   queue vendor-note field and any future field) so none of them ever render as
   a raw default browser box. */
:where(input[type="text"], input[type="search"], input[type="number"],
       input[type="tel"], input[type="url"], textarea, select) {
  appearance: none; -webkit-appearance: none;
  width: 100%; box-sizing: border-box;
  padding: 12px 14px;
  background: var(--surface); color: var(--text);
  border: 1px solid transparent; border-radius: var(--radius);
  font: 16px/1.3 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  -webkit-tap-highlight-color: transparent;
}
:where(input, textarea)::placeholder { color: var(--muted); }
:where(input[type="text"], input[type="search"], input[type="number"],
       input[type="tel"], input[type="url"], textarea, select):focus {
  outline: none; border-color: var(--line);
}

#offline-banner {
  position: sticky; top: 0; z-index: 20;
  background: var(--stale-surface); color: var(--on-stale); text-align: center;
  padding: 6px 10px; font-size: 13px; font-weight: 600;
}

/* --- top bar: the screen's name, large, and the one utility --- */
.topbar {
  position: sticky; top: 0; z-index: 10;
  display: flex; align-items: flex-end; justify-content: space-between; gap: 12px;
  padding: calc(env(safe-area-inset-top) + 14px) 16px 6px;
  background: var(--bg);
  max-width: 720px; margin: 0 auto;
}
.brand { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.wordmark { margin: 0; font-size: 17px; font-weight: 700; letter-spacing: -0.02em; }
.wordmark span { color: var(--accent); }
.screen-title { margin: 0; font-size: 28px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.15; }

.game-switch { display: flex; gap: 4px; background: var(--surface-2); padding: 4px; border-radius: var(--radius-sm); }
.game-switch button {
  border: 0; background: transparent; color: var(--muted);
  padding: 6px 12px; border-radius: var(--radius-sm); font-weight: 600; font-size: 13px; cursor: pointer;
}
.game-switch button[aria-selected="true"] { background: var(--surface-2); color: var(--text); }

#view { padding: 8px 16px 24px; max-width: 720px; margin: 0 auto; }

.placeholder { color: var(--muted); text-align: center; padding: 48px 12px; }

/* --- search --- */
.search { margin-bottom: 12px; }
.search-label {
  /* screen-reader only: the field's placeholder already names it */
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}
.search-input {
  width: 100%; appearance: none; -webkit-appearance: none; line-height: 1.3;
  background: var(--surface); color: var(--text);
  border: 1px solid transparent; border-radius: var(--radius);
  padding: 13px 36px 13px 14px; font-size: 16px; /* 16px: avoids iOS zoom-on-focus */
}
.search-input::placeholder { color: var(--muted); }
.search-input:focus { outline: none; border-color: var(--line); }
.search-input::-webkit-search-cancel-button {
  -webkit-appearance: none; appearance: none; margin-right: 0;
  width: 14px; height: 14px; cursor: pointer; background: var(--muted);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M2 2l10 10M12 2L2 12' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M2 2l10 10M12 2L2 12' stroke='black' stroke-width='2' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat;
}

/* The in-field scan icon (#80) is retired: the Scan tab is the scanner's home. */
.scan-icon { display: none; }

/* --- as-you-type completions (#22): a sideways row of chips UNDER the field,
   in normal flow. The result list keeps updating as the vendor types and is
   never covered. --- */
.search-inputwrap { position: relative; }
.search-hint { margin: 8px 2px 0; font-size: 12px; line-height: 1.35; color: var(--muted); }
.suggest-row {
  display: flex; gap: 8px; margin-top: 8px; padding-bottom: 2px;
  overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none;
}
.suggest-row::-webkit-scrollbar { display: none; }
.suggest-item {
  flex: 0 0 auto; max-width: 70vw; min-height: 36px; padding: 6px 12px;
  display: inline-flex; align-items: center;
  border: 0; border-radius: var(--radius-sm); background: var(--surface-2); color: var(--text);
  font-size: 14px; font-weight: 400; cursor: pointer;
}
.suggest-item:active { background: var(--line); }
.suggest-text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.suggest-match { font-weight: 600; }

/* "understood as" hint (card #27): a quiet line naming the set / number the
   parser locked onto. */
.understood {
  margin: 0 2px 4px; color: var(--muted); font-size: 12px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.relax-note { margin: 0 2px 6px; color: var(--stale); font-size: 12px; }

.status { color: var(--muted); font-size: 13px; margin: 0 2px 10px; min-height: 1em; }
.status:empty { min-height: 0; margin: 0; }
.status-retry {
  display: inline-flex; align-items: center; min-height: 44px;
  margin-left: 8px; padding: 4px 14px; border-radius: var(--radius-sm);
  border: 1px solid var(--line); background: transparent;
  color: var(--text); font-size: 13px; font-weight: 600;
  cursor: pointer; vertical-align: middle;
}
.status-retry:active { background: var(--surface-2); }

/* --- search home: examples on a first run, recently viewed cards after --- */
.empty-state { padding: 4px 0 16px; }
.empty-title { margin: 0; font-size: 17px; font-weight: 600; letter-spacing: 0; }
.empty-sub { margin: 6px 0 0; color: var(--muted); font-size: 14px; line-height: 1.4; max-width: 34ch; }

.chip-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
/* The combined filter row stays on ONE line: the game strip swipes, the EN/JP
   toggle is pinned beside it. */
.filter-row { flex-wrap: nowrap; align-items: center; }
/* The swipeable game strip: a horizontal scroll-snap track. It only scrolls when
   the chips overflow (few games -> reads as a plain row); .is-scrollable (set in
   JS on overflow) adds the edge fade so a peeking chip signals there's more. */
.game-strip {
  flex: 1 1 auto; min-width: 0; display: flex; gap: 8px;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x proximity; -webkit-overflow-scrolling: touch;
  scrollbar-width: none; padding-bottom: 2px;
}
.game-strip::-webkit-scrollbar { display: none; }
.game-strip > .chip { flex: 0 0 auto; scroll-snap-align: center; }
.game-strip.is-scrollable {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 14px, #000 calc(100% - 14px), transparent 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 14px, #000 calc(100% - 14px), transparent 100%);
}
/* The full-bar game DIAL: a horizontal centre-snap scroller. Half-width end
   spacers let any game (first/last included) reach the centre; the centred game
   is emphasized and the rest dim, so the middle reads as the selection (a lock
   landing on a number). Edge fade signals more to spin to. */
.filter-row--dial { flex-wrap: nowrap; }
.game-dial {
  flex: 1 1 auto; min-width: 0; display: flex; gap: 8px;
  overflow-x: auto; overflow-y: hidden; touch-action: pan-x;
  scroll-snap-type: x mandatory; -webkit-overflow-scrolling: touch; scrollbar-width: none;
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 22%, #000 78%, transparent 100%);
          mask-image: linear-gradient(to right, transparent 0, #000 22%, #000 78%, transparent 100%);
}
.game-dial::-webkit-scrollbar { display: none; }
.game-dial-spacer { flex: 0 0 50%; }
.game-dial > .game-dial-item { flex: 0 0 auto; scroll-snap-align: center; opacity: 0.5; transition: opacity 0.15s ease, transform 0.15s ease; }
.game-dial > .game-dial-item.is-centered { opacity: 1; transform: scale(1.06); border-color: var(--accent); color: var(--accent); background: var(--surface-2); }
/* Collapsed game chip (the dial's resting state), a normal active chip. */
.game-collapsed { flex: 0 0 auto; }
/* Divider between the game strip and the EN/JP toggle in the combined filter row. */
.filter-sep { flex: 0 0 auto; align-self: center; width: 1px; height: 22px; margin: 0 2px; background: var(--line); }
.chip {
  min-height: 40px; display: inline-flex; align-items: center;
  padding: 8px 14px; border: 1px solid transparent; border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--text);
  font-weight: 500; font-size: 14px; cursor: pointer; max-width: 100%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.chip:active { background: var(--line); }
.chip.is-active { border-color: var(--muted); color: var(--text); background: var(--surface-2); }

/* The per-game filter (#108/#205): a wrapping row of pill chips under the
   field, hidden with 0-1 enabled games (the Settings toggle gates which games
   appear). #205 replaced the underline text-tabs + horizontal scroll (which
   clipped and showed a stray scrollbar) with pills that wrap and fit the theme. */
.game-filter-row {
  margin-top: 6px; margin-bottom: 10px; gap: 6px; flex-wrap: wrap; display: flex;
  max-width: 100%;
}
.game-filter-row .chip {
  min-height: 32px; padding: 5px 12px; font-size: 13px; font-weight: 600;
  border: 1px solid var(--line); border-radius: 999px;
  background: transparent; color: var(--muted);
}
.game-filter-row .chip.is-active { background: var(--surface-2); color: var(--text); border-color: var(--muted); }

.chip-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; margin-top: 16px;
}
.chip-head--first { margin-top: 4px; }
/* #162: recently viewed cards on the search home share the results row grammar. */
.results--viewed { margin-top: 4px; }
.empty-action {
  margin-top: 16px; min-height: 44px; padding: 10px 18px; width: auto;
  border: 1px solid var(--line); border-radius: var(--radius); background: transparent; color: var(--text);
  font-weight: 500; font-size: 15px; cursor: pointer;
}
.empty-action:active { background: var(--surface); }
.empty-actions { display: flex; gap: 10px; justify-content: center; }
.empty-actions .empty-action { margin-top: 16px; }
.chip-head-label {
  color: var(--muted); font-size: 11px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.06em;
}
.chip-clear {
  min-height: 44px; padding: 0 4px; border: 0; background: transparent;
  color: var(--muted); font-size: 13px; font-weight: 400; cursor: pointer;
}
.chip-clear:active { color: var(--text); }

/* --- result rows: the one row grammar every list in the app shares.
   Thumbnail, name over set and number, and the price in a fixed right-hand
   column (--price-col) so figures line up down the list and screen to
   screen. Hairlines, no boxes. --- */
.results { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 0; border-top: 1px solid var(--line); }
.results > li { border-bottom: 1px solid var(--line); }
.result {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  background: transparent; color: var(--text);
  border: 0; border-radius: 0;
  padding: 8px 0; cursor: pointer;
}
.result:active { background: var(--surface); }
.result .thumb {
  width: var(--thumb-w); height: var(--thumb-h); flex: 0 0 var(--thumb-w); object-fit: contain;
  background: var(--surface-2); border-radius: 4px;
}
/* No image from any source (js/art.js): the stylized card placeholder (see
   --art-missing-bg), never a flat box or the browser's glyph. */
.thumb.is-missing, .inv-thumb.is-missing, .detail-img.is-missing { background: var(--art-missing-bg); border: 1px solid var(--line); }
.result .text { flex: 1 1 auto; min-width: 0; }
.result .name { font-weight: 500; font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.result .meta-row { display: flex; align-items: center; gap: 6px; margin-top: 1px; min-width: 0; }
.result .meta { color: var(--muted); font-size: 13px; overflow: hidden; min-width: 0; white-space: normal; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; line-height: 1.3; }
.result-game-badge { display: none; } /* the art says which game; the filter above says the rest */
/* #235 final: a Japanese print is marked (English is the unmarked default) now that search spans both. */
.result-lang-badge {
  display: inline-block; font-size: 11px; font-weight: 700; line-height: 1; letter-spacing: 0.02em;
  padding: 2px 5px; border-radius: 4px; margin-right: 6px;
  background: var(--surface-2); color: var(--muted);
}
.result .price { flex: 0 0 var(--price-col); min-width: var(--price-col); display: flex; flex-direction: column; align-items: flex-end; gap: 2px; font-weight: 600; font-size: 16px; color: var(--text); font-variant-numeric: tabular-nums; text-align: right; }
.result .price-finish { display: none; } /* the finish reads on the meta line (.meta-finish) */
.meta-finish { color: var(--muted); }
.result .price-amount { order: 1; line-height: 1.1; }
/* #207: a multi-print price range ("$0.13–$2.90") is wider than one figure, so
   size it down to fit the price column without wrapping. */
.result .price.price-multi { font-size: 13px; }
.result .price.price-multi .price-amount { white-space: nowrap; }

/* offline cache hint: dim the row and flag the price as remembered, not live */
.result--cached { opacity: 0.82; }
.result-cached-badge {
  flex: 0 0 auto; align-self: center;
  color: var(--muted); background: var(--surface-2); border: 1px solid var(--line);
  font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em;
  padding: 2px 6px; border-radius: var(--radius-sm);
}

/* Quick add mode: the row or tile is the add; an added card wears an emerald
   check (with its count after the first copy) over the art. */
.results li { display: flex; align-items: stretch; gap: 0; position: relative; }
.results li .result { flex: 1 1 auto; min-width: 0; position: relative; }
.result-added {
  position: absolute; left: 0; top: 8px; min-width: 22px; height: 22px; padding: 0 6px;
  display: inline-flex; align-items: center; justify-content: center; gap: 3px;
  border-radius: 11px; background: var(--accent); color: var(--on-accent);
  font-size: 12px; font-weight: 700; line-height: 1; font-variant-numeric: tabular-nums;
  transform: translate(-6px, -6px);
}
.result-added:empty { display: none; }
/* #205: per-card decrement in quick add mode -- a small circle just below the
   added badge (top-left over the thumb), so "you added N" and "remove one" sit
   together. Hidden until the row has a copy. */
.results li { position: relative; }
.result-dec {
  position: absolute; left: 0; top: 8px; transform: translate(-6px, 24px);
  width: 24px; height: 24px; border-radius: 50%; padding: 0;
  border: 1px solid var(--line); background: var(--bg); color: var(--text);
  font-size: 17px; line-height: 1; display: inline-flex; align-items: center; justify-content: center;
  z-index: 3; cursor: pointer;
}
.result-dec[hidden] { display: none; }
.results--grid .result-dec { left: auto; right: 6px; top: 6px; transform: translateY(30px); }
.results li.is-added .thumb { box-shadow: 0 0 0 2px var(--accent); }
.results.is-quickadd .result:active { background: var(--surface-2); }
.results-quickadd-toggle.is-on, .results-toolbar .chip.results-quickadd-toggle.is-on {
  background: var(--accent); color: var(--on-accent); padding: 5px 10px; border-radius: var(--radius-sm);
}

/* #137: results toolbar. #217: the count is now a real member of this row
   (leftmost, own margin-right:auto) instead of a separate .status line faked
   onto it with a negative margin -- see .results-count below. */
.results-toolbar { display: flex; flex-wrap: nowrap; align-items: center; justify-content: flex-end; gap: 16px; margin: 0 0 10px; position: relative; z-index: 1; }
.results-count { font-size: 13px; color: var(--muted); margin-right: auto; }
.results-toolbar .add-target-chip { width: auto; min-height: 34px; border: 0; font-size: 13px; color: var(--muted); gap: 4px; }
.results-toolbar .add-target-chip .add-target-label { display: none; }
.results-toolbar .add-target-chip .add-target-value { color: var(--text); }
.results-toolbar .chip { min-height: 34px; padding: 5px 0; font-size: 13px; background: transparent; color: var(--muted); }
.results-toolbar .chip.is-active { background: transparent; color: var(--text); border-color: transparent; }
/* #205: icon control for view so nothing clips. Quick add keeps text. #217:
   sort dropped its icon too -- the fixed-width tag is the whole chip now. */
.results-toolbar .results-toggle, .results-toolbar .results-sort-field { display: inline-flex; align-items: center; gap: 5px; }
.results-toolbar svg { display: block; width: 18px; height: 18px; }
.results-quickadd-toggle.is-on svg { color: inherit; }
/* #215: fixed width so the chip (and the whole toolbar) never resizes across
   the 7 sort states -- "↕" and "<arrow>Aa/$/#" are all <=2 chars, but a
   proportional font renders them at slightly different widths; min-width
   plus centering pins the box, tabular-nums keeps any digit-like glyph from
   drifting. This was the layout-shift bug: a separate direction chip used to
   appear/disappear instead. #217: relevance's tag is now the combined arrow
   "↕" (was the word "Rel"), still comfortably inside the same 3.5ch slot. */
/* The sort tag inherits the toolbar chip's muted color, which made the default
   (relevance) glyph nearly invisible; pin it to --text and enlarge so the
   control reads as tappable and the arrows are legible. */
.results-sort-tag { display: inline-block; min-width: 3.5ch; text-align: center; font-size: 16px; line-height: 1; font-weight: 700; color: var(--text); font-variant-numeric: tabular-nums; }

.result-finish-badge { display: none; }

/* Stack mode's printing-count badge: a SIBLING of .result, tap to expand. */
.result-variant-badge {
  flex: 0 0 auto; min-height: 44px; padding: 0 10px;
  border: 0; border-radius: 0;
  background: transparent; color: var(--muted);
  font-size: 13px; font-weight: 600; cursor: pointer;
}
.result-variant-badge:active { color: var(--text); }
.results li.is-expanded .result-variant-badge { color: var(--text); }
.results li.result-sub { margin-left: 16px; }

/* #137 Grid mode: image-only tiles (the owner's call: the art is the browse,
   a tap opens the card for its name and price); the same row markup, only
   the layout changes. */
/* #248: tighter, like the Search list's density -- narrower minimum tile and
   smaller gutters put more cards on a screen; a tile stays well over the 44px
   tap minimum. */
.results--grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 8px; border-top: 0;
}
.results--grid li { position: relative; display: block; border-bottom: 0; }
/* Infinite-scroll trigger: a 1px row the observer watches; spans the full grid
   width so it never claims a card cell, and takes no pointer input. */
.results-sentinel { list-style: none; height: 1px; margin: 0; padding: 0; border: 0; pointer-events: none; }
.results--grid .results-sentinel { grid-column: 1 / -1; }
.results--grid .result-added { left: auto; right: 6px; top: 6px; transform: none; min-width: 26px; height: 26px; border-radius: 13px; font-size: 13px; }
.results--grid li.is-added .thumb { box-shadow: 0 0 0 3px var(--accent); }
.results--grid .result {
  flex-direction: column; align-items: stretch; gap: 8px; padding: 0 0 6px; height: 100%;
}
.results--grid .result .thumb { width: 100%; height: auto; aspect-ratio: 5 / 7; flex: none; border-radius: var(--radius-sm); }
.results--grid .result .text, .results--grid .result .price, .results--grid .result-cached-badge { display: none; }
.results--grid .result { padding: 0; gap: 0; }
.results--grid .result-variant-badge {
  position: absolute; top: 6px; right: 6px; z-index: 1;
  min-height: 22px; padding: 2px 8px; font-size: 11px; background: var(--surface);
}
.results--grid li.result-sub { grid-column: 1 / -1; margin-left: 0; }
.results--grid .result-sub .result { flex-direction: row; align-items: center; }
.results--grid .result-sub .result .thumb { width: var(--thumb-w); height: var(--thumb-h); aspect-ratio: auto; }

/* --- card detail: the art, the name, and the one number this screen is for --- */
.is-detail .topbar { display: none; }
.detail-ref { color: var(--muted); font-size: 13px; margin: 0 2px 10px; }
.estimate-tag {
  display: inline-block; color: var(--stale); font-size: 10px; font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.04em; vertical-align: 1px;
}
.detail-est-note { color: var(--stale); font-size: 12px; font-weight: 600; margin: -6px 2px 12px; }
.detail-pc-attrib { color: var(--muted); font-size: 11px; margin: 8px 2px 0; }
.detail-pc-attrib a { color: var(--muted); text-decoration: none; }

.detail-ask { display: flex; align-items: center; gap: 12px; }
.detail-ask-label { flex: 0 0 auto; font-weight: 600; font-size: 14px; }
.detail-ask-field {
  flex: 1 1 auto; display: flex; align-items: center; gap: 6px;
  background: var(--surface); border: 1px solid transparent; border-radius: var(--radius);
  padding: 10px 14px;
}
.detail-ask-field:focus-within { border-color: var(--line); }
.detail-ask-prefix { color: var(--muted); font-weight: 600; }
.detail-ask-input {
  flex: 1 1 auto; min-width: 0; appearance: none; -webkit-appearance: none;
  background: transparent; color: var(--text); border: 0; outline: none;
  font-size: 16px; font-variant-numeric: tabular-nums;
}

.detail-verdict { margin: 10px 2px 0; font-weight: 600; font-size: 16px; }
.detail-verdict[data-tone="over"] { color: var(--down); }
.detail-verdict[data-tone="under"] { color: var(--up); }
.detail-verdict[data-tone="at"] { color: var(--text); }
.detail-verdict[data-tone="none"] { color: var(--muted); font-weight: 500; }

/* --- tab bar: Search, Scan, Collection. The Scan tab carries the queued-scan
   count. --- */
.tabbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  height: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  padding-bottom: env(safe-area-inset-bottom);
  display: grid; grid-template-columns: repeat(3, 1fr);
  background: var(--bg); border-top: 1px solid var(--line);
}
.tab {
  position: relative;
  border: 0; background: transparent; color: var(--muted);
  min-height: 44px;
  font-weight: 500; font-size: 12px; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
}
.tab svg { display: block; width: 24px; height: 24px; }
.tab[aria-current="page"] { color: var(--accent); }
.tab .queue-badge-n {
  position: absolute; top: 4px; left: calc(50% + 8px); min-width: 16px; height: 16px; padding: 0 4px;
  border-radius: 8px; background: var(--accent); color: var(--on-accent);
  font-size: 10px; font-weight: 700; line-height: 16px; text-align: center; font-variant-numeric: tabular-nums;
}

/* --- install affordance (js/install.js) --- */
/* Slim, dismissible bar under the topbar. One line of copy, a right-aligned
   action (Install on Android/Chrome; none on iOS), and a dismiss ×. */
.install-bar {
  position: sticky; top: 0; z-index: 9;
  display: flex; align-items: center; gap: 10px;
  padding: 8px 16px calc(8px);
  padding-left: max(16px, env(safe-area-inset-left));
  padding-right: max(16px, env(safe-area-inset-right));
  background: var(--surface-2); border-bottom: 1px solid var(--line);
}
.install-text {
  flex: 1 1 auto; min-width: 0;
  color: var(--text); font-size: 13px; line-height: 1.35;
}
.install-action {
  flex: 0 0 auto;
  min-height: 44px; padding: 6px 16px;
  border: 0; border-radius: var(--radius);
  background: var(--accent); color: var(--on-accent);
  font-weight: 700; font-size: 14px; cursor: pointer;
}
.install-action:active { filter: brightness(0.94); }
.install-dismiss {
  flex: 0 0 auto;
  min-width: 44px; min-height: 44px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; background: transparent; color: var(--muted);
  font-size: 22px; line-height: 1; cursor: pointer;
}
.install-dismiss:active { color: var(--text); }

/* --- add to inventory (js/detail.js): the one primary action, docked above
   the tab bar until its form opens --- */
.add-inv-graded-attrib a { color: var(--muted); text-decoration: underline; }
.add-inv-cert-link {
  display: block; margin: 4px 0 0; color: var(--muted); font-size: 12px;
  font-weight: 600; text-decoration: underline;
}

/* --- Collection tab (js/inventoryView.js, #188): Personal | Buy | Sell on
   top, the list name as the title, one hero figure, the rows, one primary
   pinned above the tab bar. The ledger overview and the list menu are
   sheets. --- */
.is-collection .topbar { display: none; }
.is-collection #view { padding-top: calc(env(safe-area-inset-top) + 8px); padding-bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom) + 92px); }
.inventory { display: flex; flex-direction: column; gap: 0; }
.inventory .empty-state { min-height: 40vh; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; }
.inventory .empty-sub { max-width: 30ch; }

/* --- unified Collection facet bar + totals line (js/holdingsview.js, #266):
   text search, then a stack of chip rows (reusing the app's shared .chip /
   .chip-row / .chip-count), then the one adaptive totals line under it. --- */
.coll-facetbar { display: flex; flex-direction: column; gap: 10px; padding-top: 10px; }
.coll-facet-search { display: flex; align-items: center; gap: 10px; }
.coll-facet-input {
  flex: 1 1 auto; min-width: 0; border: 1px solid var(--line); background: var(--surface);
  color: var(--text); border-radius: var(--radius-sm); padding: 9px 12px; font-size: 14px;
}
.coll-facet-input::placeholder { color: var(--muted); }
.coll-facet-clear {
  flex: 0 0 auto; border: 0; background: transparent; color: var(--muted);
  font-size: 13px; font-weight: 600; padding: 4px 2px; cursor: pointer;
}
.coll-facet-clear:active { color: var(--text); }
.coll-facet-clear[hidden] { display: none; }
.coll-facet-chips { display: flex; flex-direction: column; gap: 8px; }
.coll-facet-chips .chip-count { margin-left: 5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.coll-facet-chips .chip.is-active .chip-count { color: inherit; }
.coll-facet-totals { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 14px; padding: 2px 0 4px; }
.coll-facet-totals[hidden] { display: none; }
.coll-facet-total { display: inline-flex; align-items: baseline; gap: 4px; font-size: 13px; color: var(--muted); }
.coll-facet-total-label { color: var(--muted); }
.coll-facet-total-amt { color: var(--text); font-weight: 600; font-variant-numeric: tabular-nums; }
.coll-facet-total[data-tone="under"] .coll-facet-total-amt { color: var(--up); }
.coll-facet-total[data-tone="over"] .coll-facet-total-amt { color: var(--down); }
.coll-facet-total--extra { font-size: 12px; }

/* --- tag editor on the detail holding path (js/detail.js, #266) --- */
.detail-tags { display: flex; flex-direction: column; gap: 8px; margin-top: 8px; }
.detail-tags[hidden] { display: none; }
.detail-tags-chips { display: flex; flex-wrap: wrap; gap: 8px; }
.detail-tags-chips:empty { display: none; }
.detail-tag-chip {
  display: inline-flex; align-items: center; gap: 6px; border: 1px solid var(--line);
  background: var(--surface-2); color: var(--text); border-radius: 999px; padding: 5px 6px 5px 12px; font-size: 13px;
}
.detail-tag-remove {
  border: 0; background: transparent; color: var(--muted); width: 18px; height: 18px;
  border-radius: 50%; font-size: 14px; line-height: 1; cursor: pointer; display: inline-flex; align-items: center; justify-content: center;
}
.detail-tag-remove:active { color: var(--text); background: var(--line); }
.detail-tags-add { display: flex; align-items: center; gap: 8px; }
.detail-tags-input {
  flex: 1 1 auto; min-width: 0; border: 1px solid var(--line); background: var(--surface);
  color: var(--text); border-radius: var(--radius-sm); padding: 8px 10px; font-size: 13px;
}
.detail-tags-addbtn {
  flex: 0 0 auto; min-height: 36px; padding: 0 14px; border: 0; border-radius: var(--radius-sm);
  background: var(--surface-2); color: var(--text); font-size: 13px; font-weight: 600; cursor: pointer;
}
.detail-tags-addbtn:active { background: var(--line); }
.detail-tags-suggest { display: flex; flex-wrap: wrap; gap: 6px; }
.detail-tags-suggest:empty { display: none; }
.detail-tag-suggest {
  border: 1px dashed var(--line); background: transparent; color: var(--muted);
  border-radius: 999px; padding: 4px 10px; font-size: 12px; cursor: pointer;
}
.detail-tag-suggest:active { color: var(--text); border-color: var(--muted); }

/* the segmented control: selection is --text on --surface-2, never emerald */
.coll-seg { display: flex; gap: 4px; padding: 3px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm); }
.coll-seg-btn {
  flex: 1 1 0; border: 0; background: transparent; color: var(--muted);
  padding: 8px 0; border-radius: var(--radius-sm); font-weight: 500; font-size: 14px; cursor: pointer;
}
.coll-seg-btn[aria-selected="true"] { background: var(--surface-2); color: var(--text); font-weight: 600; box-shadow: inset 0 0 0 1px var(--line); }

/* the title row: the list name is the switcher (its chevron says so), then
   find and the ellipsis */
.coll-titlerow { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 0 0; }
.coll-title {
  display: flex; align-items: center; gap: 6px; min-width: 0;
  border: 0; background: transparent; color: var(--text); padding: 0; cursor: pointer; text-align: left;
  font-size: 28px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.1;
}
.coll-title-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.coll-title-chev { flex: 0 0 auto; color: var(--muted); width: 16px; height: 16px; margin-top: 4px; }
.coll-icons { display: flex; align-items: center; gap: 4px; flex: 0 0 auto; }
.coll-iconbtn {
  width: 44px; height: 44px; display: inline-flex; align-items: center; justify-content: center;
  border: 0; background: transparent; color: var(--muted); border-radius: var(--radius-sm); cursor: pointer;
  margin-right: -10px;
}
.coll-iconbtn:active { color: var(--text); background: var(--surface-2); }
.coll-iconbtn svg { width: 24px; height: 24px; }

/* the hero: one number, its delta on the same baseline, one caption */
.coll-hero { display: flex; align-items: baseline; flex-wrap: wrap; gap: 4px 10px; padding: 10px 0 0; }
.coll-hero-big { font-size: 28px; font-weight: 700; letter-spacing: -0.02em; line-height: 1; font-variant-numeric: tabular-nums; }
.coll-hero-delta { font-size: 15px; font-weight: 600; line-height: 1; font-variant-numeric: tabular-nums; color: var(--muted); }
.coll-hero-delta[data-tone="over"] { color: var(--down); }
.coll-hero-delta[data-tone="under"] { color: var(--up); }
.coll-cap { margin: 6px 0 0; color: var(--muted); font-size: 13px; line-height: 1.4; font-variant-numeric: tabular-nums; }
.coll-cap:empty { display: none; }
.coll-kv { display: flex; gap: 22px; padding: 12px 0 0; }
.coll-kv > div { display: flex; flex-direction: column; gap: 3px; }
.coll-kv-k { font-size: 11px; font-weight: 500; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); line-height: 1; }
.coll-kv-v { font-size: 15px; font-weight: 700; line-height: 1.2; font-variant-numeric: tabular-nums; }
.coll-kv-v[data-tone="under"] { color: var(--up); }
.coll-kv-v[data-tone="over"] { color: var(--down); }
.coll-kv--gains[hidden] { display: none; }
.sold-row { cursor: default; }

/* the flip rule as one tappable settings row */
.coll-rulerow { padding: 10px 0 0; }
.coll-rulerow[hidden] { display: none; }
.coll-rule {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 8px;
  border: 0; border-bottom: 1px solid var(--line); background: transparent; color: var(--text);
  padding: 10px 0; cursor: pointer; text-align: left; font-size: 14px; font-weight: 500; font-variant-numeric: tabular-nums;
}
.coll-rule-chev { color: var(--muted); width: 16px; height: 16px; transform: rotate(-90deg); flex: 0 0 auto; }
.coll-rule-form { display: flex; flex-direction: column; gap: 10px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.coll-rule-field { display: flex; align-items: center; justify-content: space-between; gap: 12px; font-size: 14px; color: var(--muted); }
.coll-rule-field input { width: 96px; text-align: right; font-variant-numeric: tabular-nums; padding: 8px 10px; }
.coll-rule-actions { display: flex; gap: 8px; flex-wrap: wrap; }

/* find, shown by the magnifier */
.inv-filter { display: flex; align-items: center; gap: 8px; padding: 12px 0 0; }
.inv-filter .inv-filter-input { padding: 9px 12px; font-size: 16px; }
.inv-filter[hidden] { display: none; }
.inv-filter .inv-filter-input { flex: 1 1 auto; min-width: 0; }
.inv-filter-input { width: 100%; font-size: 16px; font-weight: 500; color: var(--text); }
.inv-filter-input::-webkit-search-cancel-button { -webkit-appearance: none; appearance: none; width: 14px; height: 14px; margin-right: 2px; cursor: pointer; background: var(--muted); -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M2 2l10 10M12 2L2 12' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat; mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 14'%3E%3Cpath d='M2 2l10 10M12 2L2 12' stroke='black' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E") center / contain no-repeat; }
.inv-nomatch { display: flex; flex-direction: column; align-items: flex-start; gap: 12px; padding: 24px 0; }
.inv-nomatch-title { margin: 0; color: var(--text); font-size: 15px; font-weight: 500; }
.inv-nomatch-sub { margin: -6px 0 0; color: var(--muted); font-size: 13px; }
.inv-nomatch-clear {
  border: 1px solid var(--line); background: transparent; color: var(--text);
  font-size: 13px; font-weight: 600; padding: 8px 14px; border-radius: var(--radius-sm); cursor: pointer;
}
.inv-nomatch-clear:active { background: var(--surface-2); }

/* the sync row: only speaks up while a run is on or the phone cannot update */
.inv-syncrow { display: flex; align-items: center; flex-wrap: wrap; gap: 4px 12px; padding: 4px 0 0; }
.inv-syncrow:has(> :empty:only-child), .inv-syncrow .inv-sync:empty, .inv-syncrow .inv-sync-note:empty, .inv-syncrow .inv-freshness:empty { display: none; }
.inv-syncrow .inv-freshness, .inv-syncrow .inv-sync { margin-top: 0; }
.inv-tools { display: flex; flex-direction: column; gap: 4px; width: 100%; }

/* sections: 13px semibold sentence case, the count beside, list / grid as
   one glyph pair on the first */
.inv-groups { display: flex; flex-direction: column; gap: 8px; padding-top: 14px; }
.coll-sect { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 8px 0 4px; }
.coll-sect-title { margin: 0; color: var(--muted); font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; }
.coll-viewtoggle { display: flex; gap: 2px; margin-right: -8px; }
.coll-viewbtn {
  width: 32px; height: 32px; display: inline-flex; align-items: center; justify-content: center;
  border: 0; background: transparent; color: var(--muted); border-radius: var(--radius-sm); cursor: pointer;
}
.coll-viewbtn[aria-pressed="true"] { color: var(--text); }
.coll-viewbtn svg { width: 16px; height: 16px; }
.inv-rows { margin-top: 0; }
.inv-rows li { position: relative; }
.inv-rows .result { padding: 10px 0; }
/* one line of meta per row; one fixed price column so every figure shares an
   axis ("max $210.00" needs 96px); dividers inset to the text edge */
.inv-rows .result .meta { white-space: nowrap; display: block; text-overflow: ellipsis; }
.inv-rows .result .price { flex: 0 0 96px; min-width: 96px; white-space: nowrap; font-size: 15px; font-weight: 700; }
.inv-rows > li { border-bottom: 0; }
.inv-rows > li .result::after { content: ""; position: absolute; left: calc(var(--thumb-w) + 12px); right: 0; bottom: 0; height: 1px; background: var(--line); }
.inv-rows > li:last-child .result::after { display: none; }
/* grid: the art, then the figure and its delta captioned beneath */
.inv-rows.results--grid { gap: 8px 6px; } /* #248: closer together, like Search */
.inv-rows.results--grid > li { border-bottom: 0; }
.inv-rows.results--grid .result { display: flex; flex-direction: column; align-items: stretch; gap: 4px; }
.inv-rows.results--grid .result::after { display: none; }
.inv-rows.results--grid .result .thumb { border-radius: 4px; }
.inv-rows.results--grid .result .price { display: flex; flex-direction: row; align-items: baseline; justify-content: flex-start; gap: 8px; flex: none; min-width: 0; font-size: 13px; padding: 0 2px; }
/* #248: the key-fields caption under a tile's price (grade or finish ·
   condition, set code + number, ×qty). Grid-only: the list row's meta line
   already carries these; one line, ellipsised, never wrapping the tile. */
.tile-cap { display: none; }
.results--grid .tile-cap {
  display: block; padding: 0 2px; font-size: 11px; line-height: 1.25; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0;
}
/* #251: the finish chip on a grid tile, at the ART'S BOTTOM-LEFT (the least
   identifying corner of a card; the top-right sat on the name plate). The
   art is an <img> (no nesting), a fixed 5:7 of the tile width, so the chip
   is placed from the tile's top with a width-relative margin: 140% is the
   art's height, minus the chip's 16px and a 6px inset. A flat dark pill, one
   tint per finish, no gradient. Grid-only: the list row's meta line already
   names the finish. A Normal tile wears no chip at all. */
.finish-chip { display: none; }
.results--grid .finish-chip {
  display: inline-flex; align-items: center; position: absolute; top: 0; left: 6px; z-index: 1;
  margin-top: calc(140% - 22px);
  height: 16px; padding: 0 6px; border-radius: 4px;
  font-size: 9px; font-weight: 800; letter-spacing: 0.06em; line-height: 1;
  background: var(--chip-bg); box-shadow: inset 0 0 0 1px var(--chip-line);
  pointer-events: none;
}
.finish-chip--holo { color: var(--chip-holo); }
.finish-chip--reverse { color: var(--chip-rev); }
.finish-chip--first { color: var(--chip-first); }
.finish-chip--foil { color: var(--chip-foil); }
/* #251: the Finish filter row above the grid (the app's .chip-row / .chip
   idiom); a chip carries its count; chips for finishes the list does not
   hold are not rendered. */
.finish-filter-row { margin: 2px 0 8px; }
.finish-filter-row .chip-count { margin-left: 5px; color: var(--muted); font-variant-numeric: tabular-nums; }
.finish-filter-row .chip.is-active .chip-count { color: var(--text); }
.inv-rows.results--grid .result .price-trail { order: 2; font-size: 12px; }
.inv-cert-link {
  display: inline-block; margin: 0 0 8px 52px; color: var(--muted); font-size: 12px; font-weight: 600; text-decoration: underline;
}
.results--grid .inv-cert-link { display: none; }
.result .price-trail { font-size: 13px; font-weight: 500; color: var(--muted); order: 2; font-variant-numeric: tabular-nums; }
.result .price-trail[data-tone="over"] { color: var(--down); }
.result .price-trail[data-tone="under"] { color: var(--up); }

/* the one primary, pinned above the tab bar */
.coll-addbar {
  position: fixed; left: 0; right: 0; z-index: 30;
  bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  margin: 0 auto; max-width: 720px; padding: 10px 16px 12px; background: var(--bg); border-top: 1px solid var(--line);
}
.coll-addbar[hidden] { display: none; }
.coll-add {
  width: 100%; height: 44px; border: 0; border-radius: var(--radius);
  background: var(--accent); color: var(--on-accent); font-size: 15px; font-weight: 600; cursor: pointer;
}
.coll-add:active { opacity: 0.85; }

/* the ledger overview: the sheet's title is the screen name at 28px, then
   the hero, the label/value row, and the groups */
.ledger-sheet { gap: 0; }
.ledger-sheet .coll-sheet-head { align-items: flex-start; }
.ledger-title { margin: 0; font-size: 28px; font-weight: 700; letter-spacing: -0.02em; line-height: 1.1; }
.ledger-sheet .coll-sheet-head .coll-icons { margin-left: auto; }
.ledger-sheet .coll-sheet-close { padding: 10px 0 10px 10px; }
.ledger-list { list-style: none; margin: 14px 0 0; padding: 0; max-width: 640px; width: 100%; }
.ledger-group-btn, .ledger-row-btn {
  width: 100%; display: flex; align-items: center; gap: 12px; padding: 10px 0; min-height: 56px;
  border: 0; border-bottom: 1px solid var(--line); background: transparent; color: var(--text); cursor: pointer; text-align: left;
}
.ledger-group-btn { padding: 14px 0 10px; }
.ledger-row-btn:active, .ledger-group-btn:active { background: var(--surface); }
.ledger-row-btn[aria-current="true"] .ledger-nm { font-weight: 600; }
.ledger-group-btn .ledger-chev { order: -1; margin-right: -2px; }
.ledger-th { width: var(--thumb-w); height: var(--thumb-h); flex: 0 0 var(--thumb-w); object-fit: contain; background: var(--surface-2); border-radius: 4px; }
.ledger-th.is-missing { border: 1px solid var(--line); }
.ledger-tx { flex: 1 1 auto; min-width: 0; }
.ledger-nm { font-size: 15px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ledger-group-btn .ledger-nm { font-size: 17px; font-weight: 600; }
.ledger-mt { color: var(--muted); font-size: 12px; margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-variant-numeric: tabular-nums; }
.ledger-pr { flex: 0 0 var(--price-col); min-width: var(--price-col); text-align: right; }
.ledger-pr b { display: block; font-weight: 700; font-size: 15px; font-variant-numeric: tabular-nums; }
.ledger-group-btn .ledger-pr b { font-size: 17px; }
.ledger-pr i { display: block; font-style: normal; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
.ledger-pr i[data-tone="over"] { color: var(--down); }
.ledger-pr i[data-tone="under"] { color: var(--up); }
.ledger-chev { flex: 0 0 auto; width: 16px; height: 16px; color: var(--muted); transition: transform 120ms; }
.ledger-group-btn[aria-expanded="false"] .ledger-chev { transform: rotate(-90deg); }
.ledger-body[hidden] { display: none; }
.ledger-lists { list-style: none; margin: 0; padding: 0; }
.ledger-new { padding: 0; }
.ledger-new-btn {
  width: 100%; display: flex; align-items: center; gap: 12px; min-height: 48px;
  border: 0; border-bottom: 1px solid var(--line); background: transparent; color: var(--text); padding: 10px 0; font-size: 15px; font-weight: 500; cursor: pointer; text-align: left;
}
.ledger-new-btn::before { content: "+"; width: var(--thumb-w); text-align: center; color: var(--muted); font-size: 20px; font-weight: 400; line-height: 1; flex: 0 0 var(--thumb-w); }
.ledger-new-btn[hidden] { display: none; }

/* the list menu */
.menu-list { list-style: none; margin: 0 auto; padding: 0; max-width: 640px; width: 100%; }
.menu-row { border-bottom: 1px solid var(--line); }
.menu-btn {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 52px;
  border: 0; background: transparent; color: var(--text); padding: 12px 0; cursor: pointer; text-align: left; font-size: 15px; font-weight: 500;
}
.menu-btn:disabled { color: var(--muted); cursor: not-allowed; }
.menu-note { color: var(--muted); font-size: 13px; font-weight: 400; text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 55%; }
.menu-row--kind { display: flex; flex-direction: column; gap: 0; padding-bottom: 14px; }
.menu-row--kind .menu-btn { min-height: 44px; padding-bottom: 6px; cursor: default; }
.coll-seg--small .coll-seg-btn { padding: 7px 0; font-size: 13px; }
.menu-row--tools { padding: 0; border-bottom: 0; }
.menu-head { margin: 24px 0 0; padding-bottom: 4px; color: var(--muted); font-size: 13px; font-weight: 600; }
.menu-row--tools + .menu-row--tools .menu-head { margin-top: 20px; }
.menu-row--delete { margin-top: 28px; }
/* the tools read as rows here, never a wall of outlined buttons */
.menu-sheet .inv-prep, .menu-sheet .inv-export, .menu-sheet .inv-import { display: flex; flex-direction: column; align-items: stretch; gap: 0; margin: 0; }
.menu-sheet .inv-prep-btn, .menu-sheet .inv-export-btn, .menu-sheet .inv-import-btn {
  width: 100%; display: flex; align-items: center; min-height: 52px; padding: 12px 0; margin: 0;
  border: 0; border-bottom: 1px solid var(--line); border-radius: 0; background: transparent; color: var(--text);
  font-size: 15px; font-weight: 500; text-align: left; cursor: pointer;
}
.menu-sheet .inv-prep-btn:disabled, .menu-sheet .inv-export-btn:disabled, .menu-sheet .inv-import-btn:disabled { color: var(--muted); }
.menu-sheet .inv-prep { border-bottom: 1px solid var(--line); }
.menu-sheet .inv-prep-btn { border-bottom: 0; padding-bottom: 2px; }
.menu-sheet .inv-prep-note { padding: 0 0 12px; font-size: 13px; font-weight: 400; }
.menu-sheet .inv-export-note:empty, .menu-sheet .inv-import-note:empty { display: none; }
.menu-sheet .inv-export-note, .menu-sheet .inv-import-note { padding: 8px 0; font-size: 13px; }

/* Price freshness (card #26): an honest "synced X ago" the seller can trust
   offline, plus a calm amber (--stale) nudge when the numbers have gone stale.
   Reads as a signal, never an alarm. */
.inv-freshness {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px 12px;
  margin-top: 12px;
}
.inv-fresh-line {
  color: var(--muted); font-size: 13px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.inv-freshness[data-stale] .inv-fresh-line { color: var(--text); }
.inv-fresh-nudge {
  display: inline-flex; align-items: center; gap: 6px;
  color: var(--stale); font-size: 13px; font-weight: 700;
}
.inv-fresh-nudge::before {
  content: ""; width: 7px; height: 7px; border-radius: 50%;
  background: var(--stale); flex: 0 0 auto;
}

/* Price sync (card #9): the "refresh at home" control + progress + freshness. */
.inv-sync {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-top: 12px;
}
.inv-sync-btn {
  flex: 0 0 auto; min-height: 44px; padding: 8px 20px; border-radius: var(--radius);
  border: 1px solid var(--line); background: transparent; color: var(--text);
  font-size: 14px; font-weight: 700; cursor: pointer;
}
.inv-sync-btn:active { background: var(--surface-2); }
.inv-sync-btn:disabled { border-color: var(--line); color: var(--muted); cursor: not-allowed; }
.inv-sync-note {
  color: var(--muted); font-size: 13px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.inv-sync-note[data-tone="running"] { color: var(--accent); font-weight: 700; }
.inv-sync-note[data-tone="err"] { color: var(--down); font-weight: 700; }

/* Prep for show (#115, Buy-kind only): preload a Buy collection's images +
   prices for offline. Same shape as the sync control above -- one quiet
   pill button + a status note that turns into a progress readout while
   running. */
.inv-prep {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-top: 12px;
}
.inv-prep-btn {
  flex: 0 0 auto; min-height: 44px; padding: 8px 20px; border-radius: var(--radius);
  border: 1px solid var(--line); background: transparent; color: var(--text);
  font-size: 14px; font-weight: 700; cursor: pointer;
}
.inv-prep-btn:active { background: var(--surface-2); }
.inv-prep-btn:disabled { border-color: var(--line); color: var(--muted); cursor: not-allowed; }
.inv-prep-note {
  color: var(--muted); font-size: 13px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.inv-prep-note[data-tone="running"] { color: var(--accent); font-weight: 700; }
.inv-prep-note[data-tone="err"] { color: var(--down); font-weight: 700; }

/* Export (card #10): get the priced inventory off the phone as a CSV. A quiet
   secondary control — the numbers are the product; this just carries them out. */
.inv-export {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-top: 10px;
}
.inv-export-btn {
  flex: 0 0 auto; min-height: 44px; padding: 8px 20px; border-radius: var(--radius);
  border: 1px solid var(--line); background: transparent; color: var(--text);
  font-size: 14px; font-weight: 700; cursor: pointer;
}
.inv-export-btn:active { background: var(--surface-2); }
.inv-export-btn:disabled { border-color: var(--line); color: var(--muted); cursor: not-allowed; }
.inv-export-note {
  color: var(--muted); font-size: 13px; font-weight: 600; min-height: 1em;
}
.inv-export-note[data-tone="err"] { color: var(--down); font-weight: 700; }

/* Import (card #62): the inverse of Export — get a vendor's priced inventory
   BACK into the phone from a CSV. Styled to match .inv-export-btn exactly, so
   the pair reads as one control group. */
.inv-import {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-top: 10px;
}
.inv-import-btn {
  flex: 0 0 auto; min-height: 44px; padding: 8px 20px; border-radius: var(--radius);
  border: 1px solid var(--line); background: transparent; color: var(--text);
  font-size: 14px; font-weight: 700; cursor: pointer;
}
.inv-import-btn:active { background: var(--surface-2); }
.inv-import-btn:disabled { border-color: var(--line); color: var(--muted); cursor: not-allowed; }
.inv-import-note {
  color: var(--muted); font-size: 13px; font-weight: 600; min-height: 1em;
}
.inv-import-note[data-tone="err"] { color: var(--down); font-weight: 700; }

/* Import preview/confirm panel: EVERY row, its status, and why (for errors),
   before anything is written. */
.inv-import-preview {
  margin-top: 12px; padding: 14px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface);
}
.inv-import-summary {
  margin: 0 0 10px; color: var(--muted); font-size: 13px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.inv-import-error { margin: 0 0 10px; color: var(--down); font-size: 13px; font-weight: 700; }
.inv-import-rows {
  display: flex; flex-direction: column; gap: 6px;
  max-height: 320px; overflow-y: auto; margin-bottom: 12px;
}
.inv-import-row {
  display: flex; align-items: center; flex-wrap: wrap; gap: 8px;
  padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-2); font-size: 13px;
}
.inv-import-row[data-status="error"] { border-color: var(--down); }
.inv-import-chip {
  flex: 0 0 auto; padding: 2px 8px; border-radius: var(--radius);
  font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em;
  color: var(--muted); border: 1px solid var(--line);
}
.inv-import-chip[data-status="new"] { color: var(--up); border-color: var(--up); }
.inv-import-chip[data-status="update"] { color: var(--stale); border-color: var(--stale); }
.inv-import-chip[data-status="unchanged"] { color: var(--muted); }
.inv-import-chip[data-status="error"] { color: var(--down); border-color: var(--down); }
.inv-import-text { flex: 1 1 160px; min-width: 0; color: var(--text); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.inv-import-offcat {
  flex: 0 0 auto; color: var(--stale); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.03em;
}
.inv-import-reason { flex: 1 1 100%; color: var(--down); font-size: 12px; }
.inv-import-actions { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }
.inv-import-confirm {
  flex: 0 0 auto; min-height: 44px; padding: 8px 20px; border-radius: var(--radius);
  border: 0; background: var(--accent); color: var(--on-accent);
  font-size: 14px; font-weight: 700; cursor: pointer;
}
.inv-import-confirm:disabled { background: transparent; border-color: var(--line); color: var(--muted); cursor: not-allowed; }
.inv-import-cancel {
  flex: 0 0 auto; min-height: 44px; padding: 8px 20px; border-radius: var(--radius);
  border: 1px solid var(--line); background: transparent; color: var(--muted);
  font-size: 14px; font-weight: 700; cursor: pointer;
}
.inv-import-cancel:active { border-color: var(--down); color: var(--down); }

/* --- Export/Import QR (#82): offline phone-to-phone inventory handoff --- */
/* Export QR sheet: same overlay idiom as .collection-sheet (a .scan-result
   variant) -- centered content this time, since it's just a status line + a
   square image, not a scrollable list. */
.qr-export-sheet { justify-content: center; align-items: center; text-align: center; }
.qr-export-status {
  max-width: 420px; margin: 0; padding: 0 16px;
  font-size: 14px; line-height: 1.5; color: var(--text);
}
.qr-export-status[data-tone="err"] { color: var(--down); font-weight: 700; }
.qr-export-image {
  margin-top: 16px; padding: 12px; border-radius: var(--radius);
  background: #fff; /* the QR needs a solid light quiet zone regardless of theme */
  max-width: min(88vw, 360px);
}
.qr-export-image svg { display: block; width: 100%; height: auto; }

/* Present mode (#254): a full-screen QR of a list's share link for a table.
   Big code, the destination URL printed under it (trust), centered. */
.present-sheet {
  justify-content: center; align-items: center; text-align: center;
  gap: 14px; padding: 24px 16px;
}
.present-head { display: flex; flex-direction: column; gap: 4px; }
.present-title { margin: 0; font-size: 20px; font-weight: 800; color: var(--text); }
.present-meta { margin: 0; font-size: 14px; color: var(--muted); font-variant-numeric: tabular-nums; }
.present-qr {
  padding: 16px; border-radius: var(--radius);
  background: #fff; /* the QR needs a solid light quiet zone regardless of theme */
  width: min(78vw, 340px);
}
.present-qr svg { display: block; width: 100%; height: auto; }
.present-caption { margin: 0; font-size: 15px; font-weight: 600; color: var(--text); }
.present-url { margin: 0; font-size: 12px; color: var(--muted); word-break: break-all; max-width: 88vw; }
.present-done { margin-top: 6px; }

/* Import QR sheet: reuses .capture/.capture-head/.capture-title/
   .capture-close/.capture-stage/.capture-msg (card #29's capture surface)
   unchanged -- js/qrscan.js mounts a bare <video> into .capture-stage with
   no class of its own, so it needs the same fill treatment .capture-video
   gets. */
/* #195: the QR camera is opened FROM the Collection's List options sheet
   (.scan-result.collection-sheet, z-index 50), so it must stack above that
   sheet, not at .capture's own 40 (the camera opened behind the sheet and
   only showed once the sheet was closed). Under the added toast's 60. */
.capture.qr-scan { z-index: 51; }
.qr-scan .capture-stage video { width: 100%; height: 100%; object-fit: cover; }
.qr-scan .capture-msg { position: absolute; left: 0; right: 0; bottom: 12px; margin: 0 auto; }

/* --- Animated multi-frame QR transport (#116): the full-binder fountain-
   coded handoff. Reuses the #82 sheet idioms above unchanged (same overlay,
   same .qr-export-image quiet zone, same .capture-stage camera surface) --
   .qr-transport-sheet/.qr-transport-frame/.qr-transport-counter/
   .qr-transport-progress only add the frame-counter and recovered-blocks
   readouts neither #82 mode needed. */
.qr-transport-frame svg { display: block; width: 100%; height: auto; }
.qr-transport-counter {
  margin: 12px 0 0; max-width: 420px; padding: 0 16px;
  font-size: 13px; color: var(--muted);
}
.qr-transport-progress {
  position: absolute; left: 0; right: 0; bottom: 44px; margin: 0 auto;
  max-width: 420px; padding: 0 16px; text-align: center;
  font-size: 13px; font-weight: 700; color: var(--text);
}

/* --- Share link (#101): the anonymous read-only collection share sheet
   (reuses .qr-export-sheet/.qr-export-status/.qr-export-image above
   unchanged) and the standalone /share/<id> preview+import screen a vendor's
   own link opens into. */
.share-link-row {
  display: flex; align-items: center; gap: 8px; width: 100%;
  background: transparent; padding: 0;
}
.share-link-input {
  flex: 1 1 auto; min-width: 0; min-height: 44px; padding: 8px 12px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-2); color: var(--text); font-size: 13px;
}
/* Hide-value privacy toggle (#101): centered in the share sheet, above the
   link row it rebuilds. */
.share-priv {
  display: inline-flex; align-items: center; gap: 8px; margin: 4px 0 12px;
  color: var(--text); font-size: 13px; font-weight: 600; cursor: pointer;
}
.share-priv input { width: 18px; height: 18px; accent-color: var(--up); cursor: pointer; }
.share-result { display: flex; justify-content: center; width: 100%; }

.share-preview { max-width: 640px; margin: 0 auto; padding: 4px 4px 32px; }
.share-preview-title { margin: 0 0 4px; font-size: 20px; font-weight: 800; }
.share-preview-name { margin: 0 0 2px; font-size: 15px; font-weight: 700; color: var(--text); }
.share-preview-sub { margin: 0 0 14px; color: var(--muted); font-size: 13px; font-weight: 600; }
.share-total { color: var(--text); font-weight: 800; font-variant-numeric: tabular-nums; }
.share-preview-status { margin: 24px 0; color: var(--muted); font-size: 14px; }
.share-preview-status[data-tone="err"] { color: var(--down); font-weight: 700; }
.share-preview-rows {
  display: flex; flex-direction: column; gap: 6px; margin-bottom: 16px;
  max-height: 40vh; overflow-y: auto;
}
.share-preview-row {
  padding: 8px 10px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-2); font-size: 13px; color: var(--text);
}
.share-preview-actions { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
.share-preview-note { color: var(--muted); font-size: 13px; font-weight: 600; }
.share-preview-note[data-tone="err"] { color: var(--down); font-weight: 700; }
.share-preview-confirm {
  margin-top: 12px; padding: 14px; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--surface);
}
.share-import-dest { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; }
.share-import-dest-label { color: var(--muted); font-size: 13px; font-weight: 700; }
.share-import-dest-select {
  min-height: 40px; padding: 6px 10px; border: 1px solid var(--line);
  border-radius: var(--radius); background: var(--surface-2); color: var(--text); font-size: 13px;
}
.share-import-newlist { flex: 1 1 160px; }

/* #231: the binder-style grid of card ART tiles -- the screenshot surface a
   /share/<id> link exists to be. Fixed 63/88 aspect boxes (same ratio as
   every other card-art surface in the app, see .detail-art) so the grid
   never reflows as art loads or 404s into the placeholder. */
.share-preview-cta {
  margin: 0 0 16px; font-size: 12px; font-weight: 600;
}
.share-preview-cta a { color: var(--muted); text-decoration: underline; text-underline-offset: 2px; }
.share-preview-cta a:hover { color: var(--text); }
.share-grid {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px;
  margin-bottom: 20px;
}
@media (min-width: 480px) { .share-grid { grid-template-columns: repeat(4, 1fr); } }
@media (min-width: 720px) { .share-grid { grid-template-columns: repeat(6, 1fr); } }
.share-tile { margin: 0; display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.share-tile-art {
  aspect-ratio: 63 / 88; border-radius: var(--radius-sm); overflow: hidden;
  background: var(--surface-2); border: 1px solid var(--line);
}
.share-tile-art.is-missing { display: flex; align-items: center; justify-content: center; padding: 6px; background: var(--art-missing-bg); }
.share-tile-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.share-tile-fallback {
  color: var(--muted); font-size: 11px; font-weight: 700; text-align: center; line-height: 1.3;
}
.share-tile-cap { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.share-tile-name {
  font-size: 11px; font-weight: 700; color: var(--text);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.share-tile-price { font-size: 11px; font-weight: 600; color: var(--muted); font-variant-numeric: tabular-nums; min-height: 13px; }
/* #252: the whole tile is a button (tap -> the read-only detail sheet). */
.share-tile-btn {
  all: unset; display: flex; flex-direction: column; gap: 4px;
  width: 100%; min-width: 0; box-sizing: border-box; cursor: pointer;
  border-radius: var(--radius-sm);
}
.share-tile-btn:focus-visible { outline: 2px solid var(--text); outline-offset: 2px; }
.share-tile-btn:active .share-tile-art { transform: scale(0.97); }
.share-detail { justify-content: flex-start; text-align: center; }
.share-detail .coll-sheet-head { width: 100%; max-width: 420px; }
.share-detail-art {
  width: min(64vw, 260px); aspect-ratio: 63 / 88; border-radius: var(--radius-sm); overflow: hidden;
  background: var(--surface-2); border: 1px solid var(--line);
}
.share-detail-art.is-missing { display: flex; align-items: center; justify-content: center; padding: 12px; background: var(--art-missing-bg); }
.share-detail-img { width: 100%; height: 100%; object-fit: cover; display: block; }
.share-detail-meta { margin: 0; color: var(--muted); font-size: 14px; font-weight: 600; }
.share-detail-facts { margin: 0; color: var(--muted); font-size: 13px; font-weight: 600; }
.share-detail-price { margin: 0; font-size: 18px; font-weight: 800; color: var(--text); font-variant-numeric: tabular-nums; }
.share-detail-link { color: var(--text); font-weight: 700; text-decoration: underline; text-underline-offset: 3px; }
.share-create-btn { align-self: center; }

.inv-groups { display: flex; flex-direction: column; gap: 20px; }
.inv-group-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; margin-bottom: 8px;
}
.inv-group-title {
  margin: 0; color: var(--muted); font-size: 12px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.04em;
}
.inv-subtotal {
  color: var(--muted); font-size: 12px; font-weight: 700;
  font-variant-numeric: tabular-nums; white-space: nowrap;
}
/* #173: Collection rows are search rows (.results / .results--grid from the
   Search section); only the cert link and the price trail are the Collection's own. */
.inv-rows { margin-top: 4px; }
.inv-rows li { position: relative; }
.inv-cert-link {
  display: inline-block; margin: 0 0 8px 52px; color: var(--muted); font-size: 12px; font-weight: 600; text-decoration: underline;
}
.results--grid .inv-cert-link { display: none; }
.result .price-trail { font-size: 11px; font-weight: 700; color: var(--muted); order: 2; }
.result .price-trail[data-tone="over"] { color: var(--down); }
.result .price-trail[data-tone="under"] { color: var(--up); }
.inv-filter { display: flex; align-items: center; gap: 8px; }
.inv-filter .inv-filter-input { flex: 1 1 auto; min-width: 0; }
.inv-view-toggle { flex: 0 0 auto; }

/* --- fast-add target bar + inline feedback (js/blitz.js + js/search.js, #109) ---
   Replaces the old ambient-session HUD: a target chip is ALWAYS visible (no
   "session" gate to start it), and the inline "N added to <target> · Undo"
   line appears once this visit's tally has an add. No End any more — every
   add already persisted the instant it committed. */
.add-bar {
  position: sticky; top: 0; z-index: 6;
  display: flex; flex-direction: column; gap: 8px;
  margin-bottom: 12px;
}
.add-target-chip {
  width: 100%; max-width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 12px;
  min-height: 44px; padding: 0; border-radius: 0;
  border: 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  background: transparent; color: var(--text);
  font-weight: 400; font-size: 14px; cursor: pointer; text-align: left;
}
.add-target-label { color: var(--muted); }
.add-target-value { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.add-target-value::after { content: ' \203A'; color: var(--muted); }
.add-target-chip:active { background: var(--surface); }
/* #209: a FIXED toast, not an in-flow bar -- appearing must never push the
   results down mid-add. Sits above the tab bar, centered, out of the layout so
   nothing shifts under the finger; auto-dismisses (js/search.js updateFeedback). */
.add-feedback {
  position: fixed; left: 12px; right: 12px; margin: 0 auto;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 76px);
  max-width: 560px; z-index: 40;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 14px;
  background: var(--surface-2); border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 8px 28px rgba(0, 0, 0, 0.5);
}
.add-feedback-text { font-weight: 700; font-size: 15px; font-variant-numeric: tabular-nums; letter-spacing: -0.01em; }
.add-feedback-undo {
  flex: 0 0 auto; min-height: 44px; padding: 6px 16px; border-radius: var(--radius);
  border: 1px solid var(--line); background: var(--surface-2); color: var(--text);
  font-weight: 700; font-size: 14px; cursor: pointer;
}
.add-feedback-undo:active { border-color: var(--accent); }
.add-feedback-undo:disabled { opacity: 0.45; cursor: default; }

/* Quick-add sheet: a compact, near-instant add pinned to the bottom. #86: this
   sheet is appended to document.body (not the search view's `root`) and sits
   ABOVE the review overlay's z-index:41 (see .scan-result below) so a
   candidate accept opens on top of the review, not trapped behind its opaque
   backdrop — the root cause of the accept-flash. */
.blitz-quickadd {
  position: fixed; left: 0; right: 0; z-index: 60;
  bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  max-width: 720px; margin: 0 auto;
  display: flex; flex-direction: column; gap: 12px;
  padding: 14px 16px calc(14px + env(safe-area-inset-bottom));
  background: var(--surface); border-top: 1px solid var(--line);
  border-radius: var(--radius) var(--radius) 0 0;
  box-shadow: 0 -12px 32px rgba(0, 0, 0, 0.5);
  max-height: 78vh; overflow-y: auto;
}
.blitz-qa-head { display: flex; align-items: center; gap: 12px; }
.blitz-qa-thumb {
  width: 40px; height: 56px; flex: 0 0 40px; object-fit: contain;
  background: var(--surface-2); border-radius: 6px;
}
.blitz-qa-headtext { flex: 1 1 auto; min-width: 0; }
.blitz-qa-name { font-weight: 700; font-size: 16px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.blitz-qa-meta { color: var(--muted); font-size: 12px; margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.blitz-qa-close {
  flex: 0 0 auto; min-width: 44px; min-height: 44px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; background: transparent; color: var(--muted);
  font-size: 26px; line-height: 1; cursor: pointer;
}
.blitz-qa-close:active { color: var(--text); }

.blitz-qa-target {
  align-self: flex-start; max-width: 100%;
  min-height: 44px; padding: 8px 16px; border-radius: var(--radius);
  border: 1px solid var(--line); background: var(--surface-2); color: var(--text);
  font-weight: 700; font-size: 14px; cursor: pointer;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.blitz-qa-target:active { border-color: var(--accent); }
.blitz-config { display: flex; flex-direction: column; gap: 8px; }
.blitz-config-select, .blitz-config-input {
  width: 100%; min-height: 44px; box-sizing: border-box;
  appearance: none; -webkit-appearance: none;
  padding: 10px 14px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-2); color: var(--text); font-size: 16px;
}
.blitz-config-select:focus, .blitz-config-input:focus { outline: none; border-color: var(--accent); }

/* #117: one-tap grade picker for a barcode-scanned slab (grader known, grade not yet). */
.blitz-grade-picker { margin-top: -2px; }
.grade-picker { display: flex; flex-wrap: wrap; gap: 6px; }
.grade-picker-btn {
  min-width: 44px; min-height: 40px; padding: 6px 10px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-2); color: var(--text); font-weight: 600; font-size: 14px; cursor: pointer;
}
.grade-picker-btn[aria-selected="true"] { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }

.blitz-finishes, .blitz-conditions { flex-wrap: wrap; }
.blitz-finishes button:disabled { cursor: default; opacity: 1; }
.blitz-finishes button, .blitz-conditions button { min-height: 44px; }

.blitz-qa-controls { display: flex; gap: 12px; align-items: flex-end; }
.blitz-qa-qty { flex: 0 0 88px; display: flex; flex-direction: column; gap: 4px; }
.blitz-qa-askfield { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 4px; }
.blitz-qa-fieldlabel {
  color: var(--muted); font-size: 11px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.04em;
}
.blitz-qa-input {
  width: 100%; min-height: 44px; box-sizing: border-box;
  appearance: none; -webkit-appearance: none;
  padding: 10px 14px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-2); color: var(--text);
  font-size: 16px; font-variant-numeric: tabular-nums;
}
.blitz-qa-input:focus { outline: none; border-color: var(--accent); }
.blitz-qa-money {
  display: flex; align-items: center; gap: 6px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-2); padding-left: 14px;
}
.blitz-qa-money:focus-within { border-color: var(--accent); }
.blitz-qa-prefix { color: var(--muted); font-weight: 700; }
.blitz-qa-money .blitz-qa-input { border: 0; background: transparent; padding-left: 0; }

.blitz-qa-actions { display: flex; align-items: center; gap: 12px; }
.blitz-qa-add {
  flex: 1 1 auto; min-height: 48px; padding: 12px 22px;
  border: 0; border-radius: var(--radius); background: var(--accent); color: var(--on-accent);
  font-weight: 700; font-size: 16px; cursor: pointer;
}
.blitz-qa-add:active { filter: brightness(0.94); }
.blitz-qa-add:disabled { opacity: 0.6; cursor: default; }
.blitz-qa-err { margin: 0; color: var(--down); font-weight: 700; font-size: 13px; }

/* --- tap-target polish (>=44px on interactive controls) --- */
.detail-finishes button,
.detail-conditions button { min-height: 44px; }

/* --- scan entry point (#29) --- */
.scan-launch {
  margin-top: 10px; width: 100%; min-height: 44px;
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 10px 14px; border: 1px solid var(--line); border-radius: var(--radius);
  background: transparent; color: var(--text); font-weight: 500; font-size: 14px; cursor: pointer;
}
/* "N cards in Tray": a quiet text row under the field, not a button. */
.scan-launch--tray { border: 0; justify-content: flex-start; padding: 6px 0; margin-top: 4px; color: var(--muted); font-size: 13px; }
.scan-launch:active { background: var(--surface-2); }

/* --- camera capture surface (#29): full-screen overlay --- */
.capture {
  position: fixed; inset: 0; z-index: 40;
  display: flex; flex-direction: column;
  background: #000; color: var(--text);
  padding-bottom: env(safe-area-inset-bottom);
}
.capture-head {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: calc(env(safe-area-inset-top) + 12px) 16px 12px;
  background: var(--surface);
}
.capture-title { margin: 0; font-size: 17px; font-weight: 600; }
.capture-close {
  min-height: 44px; padding: 8px 14px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-2); color: var(--text); font-weight: 700; font-size: 14px; cursor: pointer;
}
.capture-close:active { background: var(--line); }
.capture-stage {
  position: relative; flex: 1 1 auto; min-height: 0;
  display: flex; align-items: center; justify-content: center;
  overflow: hidden; background: #000;
}
.capture-video { width: 100%; height: 100%; object-fit: cover; }
/* Binder (page) mode is WYSIWYG: show the WHOLE captured frame (letterboxed)
   instead of the cover-cropped zoom, so framing matches the crop. */
.capture-stage.is-page .capture-video { object-fit: contain; background: var(--bg); }
/* The crop-cell grid overlay: a box with the video's own aspect ratio, centred
   in the stage so it lines up with the contained (letterboxed) video, split into
   rows x cols equal cells. Non-interactive; drawn only in page mode. */
/* Outer: tracks the video's rendered (object-fit: contain) frame -- aspect set
   from the live video by js/camera.js. A positioning box only. */
.capture-pagegrid {
  position: absolute; inset: 0; margin: auto;
  max-width: 100%; max-height: 100%; aspect-ratio: 3 / 4;
  pointer-events: none;
}
/* Inner: the card-aspect page region centred within the frame (aspect set from
   (cols/rows)*CARD_ASPECT by js/camera.js), holding the crop-cell grid. Matches
   binderpage.js pageRegion, so the cells sit exactly where each pocket is cropped. */
.capture-pagegrid-inner {
  position: absolute; inset: 0; margin: auto;
  max-width: 100%; max-height: 100%; aspect-ratio: 0.716;
  display: grid;
  grid-template-columns: repeat(var(--grid-cols, 3), 1fr);
  grid-template-rows: repeat(var(--grid-rows, 3), 1fr);
}
.capture-pagegrid-inner > span { border: 1px solid var(--pagegrid-line); box-sizing: border-box; }
/* #111: the capture scratch canvas is a pure pixel buffer (drawImage ->
   toDataURL -> .capture-preview shows the result) -- it must never lay out.
   canvas.width/height get set to the camera's native capture resolution
   (~1080-1920px) with no CSS size rule, so an unhidden canvas would render
   at full bitmap width and blow out the document past device-width, which
   rescales the whole mobile viewport. The element already carries the
   `hidden` attribute (caught by the global [hidden] rule above), but a
   class-scoped rule here is the direct, explicit fix and survives even if
   that attribute is ever dropped. */
.capture-canvas { display: none; }
/* card-shaped framing guide (~63x88mm, aspect 0.716) with a dim surround --
   the AUTO guide-crop the live camera draws at capture time. #296 deleted
   the manual editor's own .scan-editor-guide, which used to share this rule. */
.capture-guide {
  position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
  width: min(74vw, 320px); aspect-ratio: 63 / 88;
  border: 0; border-radius: 6px;
  /* corner brackets: two bars per corner, drawn as gradients so the four corners
     stay one element (js/camera.js measures this box for the crop) */
  --guide-bar: rgba(255, 255, 255, 0.9);
  background:
    linear-gradient(var(--guide-bar), var(--guide-bar)) top left / 28px 3px no-repeat,
    linear-gradient(var(--guide-bar), var(--guide-bar)) top left / 3px 28px no-repeat,
    linear-gradient(var(--guide-bar), var(--guide-bar)) top right / 28px 3px no-repeat,
    linear-gradient(var(--guide-bar), var(--guide-bar)) top right / 3px 28px no-repeat,
    linear-gradient(var(--guide-bar), var(--guide-bar)) bottom left / 28px 3px no-repeat,
    linear-gradient(var(--guide-bar), var(--guide-bar)) bottom left / 3px 28px no-repeat,
    linear-gradient(var(--guide-bar), var(--guide-bar)) bottom right / 28px 3px no-repeat,
    linear-gradient(var(--guide-bar), var(--guide-bar)) bottom right / 3px 28px no-repeat;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.55);
  display: flex; align-items: flex-end; justify-content: center;
  pointer-events: none;
}
.capture-msg {
  max-width: 420px; margin: 0; padding: 24px;
  text-align: center; font-size: 15px; line-height: 1.5; color: var(--text);
}
/* Capture coaching hint (#85): a brief tip banner pinned to the TOP of the
   stage, deliberately away from the centered .capture-guide, so it never
   blocks or overlaps the live view it's coaching. Shown at most once per
   stage mount (js/camera.js showHintOnce), auto-fades, and a tap dismisses
   it for good. The fade is opacity-only and gated off under
   prefers-reduced-motion below, so it disappears instantly there instead
   of animating. */
/* Transient import acknowledgement over the stage (js/camera.js showFlashNote):
   immediate feedback that a chosen photo registered, before its queue row lands. */
.capture-flashnote {
  position: absolute; top: 12px; left: 50%; transform: translateX(-50%); z-index: 3;
  padding: 7px 14px; border-radius: 999px;
  background: rgba(0, 0, 0, 0.62); color: #fff;
  font-size: 13px; font-weight: 600; text-align: center; max-width: 80%;
  pointer-events: none;
}
.capture-hint {
  position: absolute; top: 12px; left: 12px; right: 12px; z-index: 1;
  margin: 0 auto; max-width: 420px;
  display: flex; align-items: center; justify-content: center; gap: 4px;
  padding: 6px 6px 6px 14px; border-radius: var(--radius);
  background: rgba(0, 0, 0, 0.62); color: #fff;
  opacity: 1; transition: opacity 400ms ease;
}
.capture-hint[data-fade] { opacity: 0; }
@media (prefers-reduced-motion: reduce) {
  .capture-hint { transition: none; }
}
.capture-hint-text { flex: 1 1 auto; font-size: 13px; line-height: 1.4; text-align: center; }
.capture-hint-dismiss {
  flex: 0 0 auto; min-width: 44px; min-height: 44px; padding: 0;
  display: flex; align-items: center; justify-content: center;
  border: 0; border-radius: var(--radius); background: transparent;
  color: #fff; font-size: 18px; line-height: 1; cursor: pointer;
}
.capture-hint-dismiss:active { background: rgba(255, 255, 255, 0.18); }
.capture-preview { max-width: 100%; max-height: 100%; object-fit: contain; }
/* #286: the camera-off placeholder -- covers the whole stage (same black as
   .capture-stage itself) whenever there's no live stream, so the raw <video>
   is never left showing the browser's own default poster/play-button. */
.capture-placeholder {
  position: absolute; inset: 0; z-index: 2; /* above the guide/hint, below .capture-flashnote (3) */
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
  padding: 24px; text-align: center; background: #000;
}
/* #fff (not a lighter alpha) throughout, matching .capture-hint's own choice
   of a fully-opaque white over this same camera-chrome black -- see
   BRAND.md's "Sanctioned non-token exceptions" for why camera chrome is
   exempt from the token-only rule, and test/palette-tokens.test.mjs's
   ALLOWED_LITERALS for the exact sanctioned set (kept short on purpose). */
.capture-placeholder-icon { color: #fff; }
.capture-placeholder-text { margin: 0; font-size: 14px; line-height: 1.4; color: #fff; max-width: 260px; }
.capture-placeholder-start { min-width: 200px; }
.capture-placeholder-photo {
  background: transparent; border: 0; color: #fff; text-decoration: underline; min-height: 32px; padding: 4px;
}
.capture-foot {
  flex: 0 0 auto;
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 14px 16px calc(14px + env(safe-area-inset-bottom));
  background: var(--surface);
}
.capture-btn {
  min-height: 48px; flex: 1 1 auto; max-width: 220px;
  display: inline-flex; align-items: center; justify-content: center;
  padding: 12px 16px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-2); color: var(--text); font-weight: 700; font-size: 15px; cursor: pointer;
  /* #283: a quick tap can otherwise be held for double-tap-zoom/scroll
     detection before the browser commits to it as a tap -- that delay is
     part of why a fast tap can miss. manipulation drops that hold, so both
     the pointerup fast path and the click fallback fire promptly. */
  touch-action: manipulation;
}
.capture-btn:active { background: var(--line); }
.capture-btn-primary { border-color: var(--accent); background: var(--accent); color: var(--on-accent); }
.capture-btn-primary:active { filter: brightness(0.94); }

/* --- the scanner (#93): the live camera on top, the scan queue below, the
   tab bar still visible underneath. One screen: a capture lands as a row,
   the row resolves to a name and a price as recognition finishes, and a tap
   opens it in place. Layering inside .scanner's own stacking context:
   camera 0 < queue 1 < toast 7. --- */
.scanner {
  position: fixed; left: 0; right: 0; top: 0; z-index: 30;
  bottom: calc(var(--tabbar-h) + env(safe-area-inset-bottom));
  display: flex; flex-direction: column;
  background: var(--bg); color: var(--text);
  overflow: hidden;
}
.scanner-camera {
  position: relative; flex: 0 0 auto; z-index: 0;
  /* FIXED height in both states so the camera preview and its framing guide
     never resize when the queue ("match window") appears. It stays at the large
     size to maximize the card's share of the captured pixels; the queue below
     takes the remaining space (a smaller, fixed band that scrolls internally),
     rather than the camera shrinking to make room for it. */
  height: 62vh; min-height: 300px;
  background: #000; overflow: hidden;
  /* The controls live in solid black chrome bars ABOVE and BELOW the preview,
     never over it -- the live image between them stays fully unobstructed. */
  --scan-top-bar: calc(env(safe-area-inset-top, 0px) + 48px);
  --scan-bot-bar: 88px;
}
/* The scanning surface (video + framing guide + crop grid) is inset to leave the
   two chrome bars; object-fit: cover fills this box, so the buttons sit off it. */
.scanner-camera .capture-stage { position: absolute; inset: var(--scan-top-bar) 0 var(--scan-bot-bar) 0; height: auto; }
/* The guide sits centered in the room above the shutter row, sized from the
   region's height (the same object-fit: cover crop math in js/camera.js
   guideCropBox maps it back to source pixels wherever it lands). */
.scanner-camera .capture-guide {
  top: 50%;
  width: auto; aspect-ratio: 63 / 88;
  height: min(calc(100% - 40px), 420px);
  max-width: 78vw;
  box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.7);
}
.scanner-camera .capture-hint {
  top: calc(env(safe-area-inset-top, 0px) + 12px); bottom: auto;
  max-width: max-content; background: rgba(0, 0, 0, 0.62); padding: 6px 14px; justify-content: center;
  font-weight: 500;
}
.scanner-camera .capture-hint-dismiss { display: none; }
.scanner-camera .capture-msg {
  position: absolute; left: 16px; right: 16px;
  top: calc(env(safe-area-inset-top, 0px) + 48px);
  margin: 0 auto;
}
.scanner-camera .capture-foot {
  position: absolute; left: 0; right: 0; bottom: 0; z-index: 2;
  height: var(--scan-bot-bar);
  padding: 0 16px; gap: 0;
  display: grid; grid-template-columns: 1fr auto 1fr; align-items: center;
  background: #000; /* the bottom chrome bar, below the preview */
}
.scanner-camera .capture-foot .capture-btn { max-width: none; flex: none; grid-row: 1; }
.scanner-camera .capture-foot .capture-btn:not(.capture-btn-shutter) {
  min-height: 44px; min-width: 44px; padding: 8px 10px; border-radius: var(--radius-sm);
  background: transparent; border-color: transparent;
  color: #fff; font-size: 13px; font-weight: 500; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.62);
}
/* The gallery pick is an icon beside the shutter (its label stays for screen readers). */
.scanner-camera .capture-foot > .capture-btn:not(.capture-btn-shutter):not(.capture-btn-torch):not(.capture-btn-primary) {
  font-size: 0; position: relative;
}
.scanner-camera .capture-foot > .capture-btn:not(.capture-btn-shutter):not(.capture-btn-torch):not(.capture-btn-primary)::before {
  content: ''; display: block; width: 26px; height: 26px; margin: 0 auto; background: #fff;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='5' width='18' height='14' rx='2'/%3E%3Ccircle cx='9' cy='10' r='1.6'/%3E%3Cpath d='M21 16l-5-5-7 7'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Crect x='3' y='5' width='18' height='14' rx='2'/%3E%3Ccircle cx='9' cy='10' r='1.6'/%3E%3Cpath d='M21 16l-5-5-7 7'/%3E%3C/svg%3E") center / contain no-repeat;
}
.scanner-camera .capture-foot .capture-btn:not(.capture-btn-shutter):active { background: rgba(255, 255, 255, 0.18); }
.scanner-camera .capture-foot > .capture-btn:not(.capture-btn-shutter) { grid-column: 1; justify-self: start; }
.scanner-camera .capture-foot > .capture-btn-shutter { grid-column: 2; justify-self: center; }
.scanner-camera .capture-foot > .capture-btn.capture-btn-primary:not(.capture-btn-shutter) { grid-column: 2; justify-self: center; }
.scanner-camera .capture-foot > .capture-btn.capture-btn-torch { grid-column: 3; justify-self: end; }
.scanner-camera .capture-foot > .capture-btn.capture-btn-torch[aria-pressed="true"] {
  background: var(--accent); border-color: var(--accent); color: var(--on-accent);
}
/* The shutter: a round white button with an inner ring, the label kept for
   screen readers only. A tap commits straight to the queue. */
.scanner-camera .capture-btn-shutter {
  width: 68px; height: 68px; min-height: 0; padding: 0;
  border-radius: 999px; border: 4px solid rgba(255, 255, 255, 0.9);
  background: transparent; box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.55);
  position: relative;
}
.scanner-camera .capture-btn-shutter::after {
  content: ''; position: absolute; inset: 5px; border-radius: 999px; background: #fff;
  transition: transform 90ms ease;
}
.scanner-camera .capture-btn-shutter:active::after { transform: scale(0.86); }
.scanner-camera .capture-btn-shutter .capture-btn-label {
  position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* The queue: a summary line with "Add N matched" beside it, then the rows. */
.scanner-queue {
  position: relative; z-index: 1; flex: 1 1 auto; min-height: 0;
  display: flex; flex-direction: column;
  max-width: 720px; width: 100%; margin: 0 auto;
}
.scanner-queue-head {
  flex: 0 0 auto; min-height: 48px; padding: 8px 16px;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  border-bottom: 1px solid var(--line);
}
.scanner-queue-count { color: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums; margin-right: auto; }
.scanner-queue-target {
  flex: 0 1 auto; min-height: 34px; padding: 6px 12px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: transparent; color: var(--text);
  font-size: 13px; font-weight: 600; cursor: pointer;
  max-width: 48%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.scanner-queue-addn {
  flex: 0 0 auto; min-height: 34px; padding: 6px 14px;
  border: 0; border-radius: var(--radius-sm);
  background: var(--accent); color: var(--on-accent);
  font-size: 13px; font-weight: 600; cursor: pointer;
}
.scanner-queue-addn:disabled { opacity: 0.6; cursor: default; }
.scan-queue-empty { margin: 0; padding: 28px 16px; color: var(--muted); font-size: 14px; text-align: center; }
.scanner-queue-list {
  flex: 1 1 auto; min-height: 0; overflow-y: auto; -webkit-overflow-scrolling: touch;
  padding: 0 16px 16px;
}
.scan-row { border-bottom: 1px solid var(--line); animation: scan-row-in 200ms ease-out; }
/* Swipe-to-delete: the head slides inside a clipped wrap over a red Delete
   backdrop. The head carries a solid --bg so the backdrop only shows once slid,
   and touch-action: pan-y keeps vertical list scrolling while JS reads the
   horizontal drag. */
.scan-row-swipewrap { position: relative; overflow: hidden; }
.scan-row-swipe {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: flex-end;
  padding-right: 20px; background: var(--down); color: #fff; font-weight: 700; font-size: 14px; letter-spacing: 0.02em;
}
.scan-row-swipewrap .scan-row-head { position: relative; z-index: 1; background: var(--bg); touch-action: pan-y; will-change: transform; }
/* Swipe a collection row left to remove it (with an Undo toast) -- the same
   gesture and look as the scan queue. */
.coll-row-swipewrap { position: relative; overflow: hidden; }
.coll-row-swipe {
  position: absolute; inset: 0; display: flex; align-items: center; justify-content: flex-end;
  padding-right: 20px; background: var(--down); color: #fff; font-weight: 700; font-size: 14px; letter-spacing: 0.02em;
}
/* The row `<li>` is display:flex, so the sliding head must GROW to fill it
   (flex:1) -- otherwise it shrinks to its content and the red Remove background
   shows past it at rest. flex-column keeps the slab "Verify" link stacked below
   the row like before. */
.coll-row-swipewrap .coll-row-head {
  position: relative; z-index: 1; flex: 1 1 auto; min-width: 0;
  display: flex; flex-direction: column;
  background: var(--bg); touch-action: pan-y; will-change: transform;
}
.coll-row-swipewrap .coll-row-head > .result { flex: 1 1 auto; }
/* Shared removal Undo toast (js/undotoast.js): fixed above the tab bar, never
   reflows the list. */
.undo-toast {
  position: fixed; left: 50%; transform: translateX(-50%); z-index: 1200;
  /* Above the tab bar AND the full-width primary action button (the collection
     reserves ~92px above the tab bar for it, see .is-collection #view), so the
     toast never lands on top of Add card / Save. Bottom-centred, one place. */
  bottom: calc(var(--tabbar-h, 56px) + env(safe-area-inset-bottom, 0px) + 104px);
  display: flex; align-items: center; gap: 14px; max-width: calc(100% - 24px);
  padding: 10px 12px 10px 16px; background: var(--surface-2); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius);
  box-shadow: 0 8px 28px rgba(var(--bg-rgb), 0.6); font-size: 14px; font-weight: 600;
}
.undo-toast-text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.undo-toast-undo { flex: 0 0 auto; border: 0; background: transparent; color: var(--accent); font-weight: 800; font-size: 14px; padding: 4px 8px; cursor: pointer; }
@keyframes scan-row-in {
  from { transform: translateY(6px); opacity: 0; }
  to { transform: none; opacity: 1; }
}
/* Two tap targets per row (#176): the photo opens the full-screen editor,
   the name and price open the candidates in place. */
/* Inline padding so the thumb's match/weak/fail box-shadow RING (0 0 0 2px)
   isn't clipped by the swipe wrap's overflow:hidden -- flush against the row
   edge the 2px ring was cut off (#swipe-border). */
.scan-row-head { display: flex; align-items: center; gap: 12px; min-height: 64px; padding: 8px 6px; }
.scan-row-thumbbtn {
  display: block; flex: 0 0 var(--thumb-w); min-height: 44px; padding: 0; border: 0;
  background: transparent; cursor: pointer;
}
.scan-row-thumbbtn:active .scan-row-thumb { opacity: 0.8; }
.scan-row-main {
  display: flex; align-items: center; gap: 12px; flex: 1 1 auto; min-width: 0; min-height: 44px;
  padding: 0; text-align: left; border: 0; background: transparent; color: var(--text); cursor: pointer;
}
.scan-row-main:active { background: var(--surface); }
/* The scan's own crop, ringed by its recognition state: emerald matched,
   amber low confidence, coral no match, dimmed while reading. */
.scan-row-thumb {
  display: block; width: var(--thumb-w); height: var(--thumb-h); flex: 0 0 var(--thumb-w); object-fit: cover;
  border-radius: 4px; background: var(--surface-2); box-shadow: 0 0 0 2px transparent;
}
.scan-row.is-matched .scan-row-thumb { box-shadow: 0 0 0 2px var(--accent); }
.scan-row.is-weak .scan-row-thumb { box-shadow: 0 0 0 2px var(--stale); }
.scan-row.is-failed .scan-row-thumb { box-shadow: 0 0 0 2px var(--down); opacity: 0.7; }
.scan-row.is-pending .scan-row-thumb { opacity: 0.55; animation: scan-row-pulse 1.2s ease-in-out infinite; }
/* #181: a re-check keeps the fresh crop fully visible (it just landed) and only pulses. */
.scan-row.is-rechecking .scan-row-thumb { animation: scan-row-pulse 1.2s ease-in-out infinite; }
@keyframes scan-row-pulse { 50% { opacity: 0.85; } }
.scan-row.is-viewing .scan-row-thumb { outline: 2px solid var(--accent); outline-offset: 2px; }
.scan-row-text { flex: 1 1 auto; min-width: 0; }
.scan-row-name { font-size: 15px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.scan-row-set { margin-top: 1px; color: var(--muted); font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.scan-row.is-pending .scan-row-name, .scan-row.is-failed .scan-row-name { color: var(--muted); font-weight: 400; }
.scan-row.is-weak .scan-row-name { color: var(--stale); }
.scan-row-price { flex: 0 0 var(--price-col); min-width: var(--price-col); text-align: right; font-size: 16px; font-weight: 600; font-variant-numeric: tabular-nums; }
.scan-row-detail { padding: 0 0 12px; }
.scan-row-note { margin: 0 0 4px; color: var(--muted); font-size: 13px; }
.scan-row-note.is-weak { color: var(--stale); font-weight: 600; }
.scan-row-cands { gap: 0; }
.scan-row-actions { display: flex; align-items: center; gap: 20px; padding-top: 10px; }
.scan-row-action {
  min-height: 40px; padding: 0; border: 0; background: transparent;
  color: var(--text); font-size: 14px; font-weight: 500; cursor: pointer;
}
.scan-row-action:last-child { color: var(--muted); margin-left: auto; }
.scan-row-action:active { color: var(--muted); }
@media (prefers-reduced-motion: reduce) {
  .scan-row, .scan-row.is-pending .scan-row-thumb { animation: none; }
  .scanner-camera .capture-btn-shutter::after { transition: none; }
}

/* The "Added · Undo" toast floats over the bottom of the queue. */
.scanner > .scan-added-toast {
  position: absolute; left: 16px; right: 16px; z-index: 7;
  bottom: 12px;
  width: auto; max-width: none; margin: 0 auto;
}

/* #68 binder-page scan: the mode toggle sits over the camera. #230 folded
   its result into the ordinary single-card scan-strip list below (each
   detected cell is cropped and enqueued as its own scan) instead of its own
   bespoke card-grid block, so only the toggle button's own styling remains.
   #232: top is pinned clear of the framing guide's top-right corner bracket
   (.scanner-camera .capture-guide below), not just the safe-area inset. That
   guide's own top offset (calc(50% - 30px), height clamped to
   `.scanner-camera` height minus 140px) puts its top edge, and so the top of
   its corner bracket, at a CONSTANT 40px down from the region's top for
   every camera height this layout actually reaches (the min-height: 300px
   floor through the tallest is-empty 62vh room) -- the bracket itself is
   28px tall, so 68px down is its bottom edge. Anchoring the button at 80px
   clears that with an 12px margin regardless of phone width (this offset
   has no width dependency at all) or button label length ("Binder page" vs
   "Binder page: on" only changes width, never height). */
/* All scan toggles in ONE side-swappable cluster (the vendor moves it under
   their thumb; the side is remembered per device). The group is absolutely
   positioned; the buttons inside are plain flex children (no longer pinned to
   opposite corners). #fff + the rgba's are allowlisted camera-chrome literals. */
.scan-toggle-cluster {
  position: absolute; z-index: 3;
  top: calc(env(safe-area-inset-top, 0px) + 6px); right: 12px;
  display: flex; flex-direction: row; gap: 8px; align-items: center;
}
.scan-toggle-cluster--left { right: auto; left: 12px; align-items: flex-start; }
/* Swap-sides button: pinned alone on the side OPPOSITE the action cluster
   (cluster right -> swap left), so a one-handed vendor reaches it with the
   other thumb; --right flips it when the cluster moves left. */
.scan-hand-toggle {
  position: absolute; z-index: 3;
  top: calc(env(safe-area-inset-top, 0px) + 6px); left: 12px;
}
.scan-hand-toggle--right { left: auto; right: 12px; }
.capture-guide.is-slab { aspect-ratio: 2.6 / 1; width: min(88vw, 420px); } /* #245: a slab label, not a card */
/* The scanner sizes its card guide by HEIGHT (width: auto + max-width), which
   turned the label plate into a clamped square; the label plate is sized by
   width instead, so the height follows the 2.6:1 aspect. */
.scanner-camera .capture-guide.is-slab { aspect-ratio: 2.6 / 1; height: auto; width: min(90vw, 460px); max-width: 92vw; }
.scan-hand-toggle, .scan-pagemode-toggle, .scan-lang-toggle {
  min-height: 36px; padding: 7px 12px; border: 1px solid rgba(255, 255, 255, 0.9); border-radius: var(--radius-sm);
  background: rgba(0, 0, 0, 0.62); color: #fff; font-size: 13px; font-weight: 600; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center; gap: 6px;
}
.scan-hand-toggle { padding: 7px; }
.scan-pagemode-badge { font-size: 12px; font-weight: 700; line-height: 1; }
.scan-pagemode-badge:empty { display: none; }
.scan-pagemode-toggle.is-active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }
.scan-lang-toggle.is-active { background: var(--accent); border-color: var(--accent); color: var(--on-accent); }

/* --- scan to match + confirm (#31): OCR result -> ranked confirm picker --- */
/* #86: top-aligned, not centered. Centered flex content taller than the
   viewport overflows equally above and below the visible box, and the
   overflow ABOVE a centered flex item is unreachable by scrolling — the
   review queue's tall content (a mode-switch + a full candidate list, or
   dozens of grid squares) got its own top clipped off with no way back to
   it. Top-aligning means content always starts flush at the scroll origin,
   so everything — including the very first pixel — stays reachable by
   scrolling, in every mode. The 24px padding still gives the first/last item
   breathing room off the edges. */
.scan-result {
  position: fixed; inset: 0; z-index: 50;
  display: flex; flex-direction: column; align-items: center; justify-content: flex-start; gap: 16px;
  padding: 24px; background: var(--scrim);
  overflow-y: auto;
}
.scan-result-note {
  margin: 0; max-width: 420px; text-align: center;
  font-size: 14px; line-height: 1.5; color: var(--muted);
}
.scan-result .scan-launch { max-width: 220px; }
/* the ranked candidate list: the same .result rows as the search list */
.scan-candidates {
  list-style: none; margin: 0; padding: 0;
  width: 100%;
  display: flex; flex-direction: column; gap: 0;
  border-top: 1px solid var(--line);
  position: relative; /* #87: anchors .scan-spotlight, appended as a child on a match tap */
}
.scan-candidates li { position: relative; border-bottom: 1px solid var(--line); }

/* #84: honest low-confidence framing lives in .scan-row-note.is-weak (css
   above). .scan-weak-note/-actions/-action/-demoted-note styled the
   pre-v110 Carousel/Split/Grid-sheet builder (js/search.js
   buildWeakMatchHead); deleted by #222 as dead code once v110 left it with
   no caller. The candidate list below stays fully visible and tappable: a
   weak match can still be right, just visually demoted under the honest
   header above it. */
.scan-candidates--weak { opacity: 0.6; }
.scan-candidates--weak:focus-within,
.scan-candidates--weak:hover { opacity: 0.85; }

/* #87 SPOTLIGHT: shared "tapping a match focuses it" commit feedback (all
   three review modes — Carousel/Split's own .scan-candidates and the Grid
   choice sheet's .scan-grid-candidates variant of the same list). An opaque
   wall covers the whole list, dimming every row; the tapped row's OWN
   z-index sits above it (rung with the accent ring below), so it visually
   pops through while everything else goes dark behind the wall — the commit
   feedback that replaces the old confirm sheet. Purely additive DOM (see
   spotlightThenCommit in js/search.js): never toggled off in place, just
   discarded on the next repaint, so no teardown transition is needed. */
.scan-spotlight {
  position: absolute; inset: -4px;
  z-index: 3; border-radius: var(--radius);
  background: rgba(0, 0, 0, 0.82);
  pointer-events: none;
}
.scan-candidates.is-spotlighting li.is-chosen {
  position: relative; z-index: 4;
}
.scan-candidates.is-spotlighting li.is-chosen .result {
  box-shadow: 0 0 0 2px var(--accent);
  border-radius: var(--radius-sm); background: var(--surface);
}

/* #87 "Added · Undo" toast: a brief, non-blocking affordance after a one-tap
   review add. A sibling of the mode body (never wiped by its repaints — see
   openReview in js/search.js), pinned just above the overlay's own Close
   button so it never competes with either the mode switch or a mode's own
   nav row for space. */
.scan-added-toast {
  width: 100%; max-width: 420px;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 10px 14px; border-radius: var(--radius);
  background: var(--surface-2); border: 1px solid var(--line);
}
.scan-added-toast-text {
  font-size: 13px; font-weight: 700; color: var(--text);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.scan-added-toast-undo {
  flex: 0 0 auto; min-height: 32px; padding: 0 12px;
  border: 1px solid var(--line); border-radius: var(--radius); background: transparent;
  color: var(--accent); font-size: 13px; font-weight: 700; cursor: pointer;
}
.scan-added-toast-undo:active { background: var(--surface); transform: scale(0.96); }
.scan-added-toast-undo:disabled { opacity: 0.5; pointer-events: none; }

.scan-preview {
  position: fixed; inset: 0; z-index: 1000;
  display: flex; align-items: center; justify-content: center;
  padding: max(16px, env(safe-area-inset-top, 0px)) 16px max(16px, env(safe-area-inset-bottom, 0px));
  background: var(--scrim); border: 0;
  touch-action: none; /* #296: a two-finger gesture starting a few px outside the stage must never fall through to the browser's own pinch-zoom */
}

/* --- #296: the full-res image viewer ---------------------------------------
   Reached from a tap on a scan row's thumbnail. Read-only: pinch, double-tap
   and drag pan/zoom the photo (js/search.js's pointer handlers set
   .scan-viewer-img's transform directly); the only control is Close. No
   guide, no crop, no rotate -- those belonged to the deleted manual editor. */
.scan-viewer-close {
  position: absolute; top: max(12px, env(safe-area-inset-top, 0px)); right: 12px; z-index: 1;
  width: 36px; height: 36px; border-radius: 999px; border: 0;
  background: rgba(var(--bg-rgb), 0.6); color: var(--text); font-size: 20px; line-height: 1;
  display: flex; align-items: center; justify-content: center; cursor: pointer;
}
.scan-viewer-stage {
  position: relative; width: 100%; height: 100%; max-width: 720px;
  touch-action: none; /* a gesture on the stage must never scroll/pinch-zoom the page */
  overflow: hidden;
}
.scan-viewer-img {
  position: absolute; left: 50%; top: 50%; transform-origin: center center; pointer-events: none;
  max-width: none; /* js/search.js sets width/height explicitly to the fit box, then scales it */
}

/* The manual card search for a scan the recognizer could not place, in the
   review row's own body (js/search.js paintQueueDetail). An ordinary in-flow
   block under the note, not an overlay: it flows with the row, so the on-screen
   keyboard scrolls it into view like any list field, no visualViewport lift. */
.scan-row-find { display: flex; flex-direction: column; gap: 6px; margin: 8px 0 4px; }
.scan-row-find-input {
  width: 100%; min-height: 40px; padding: 0 12px;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--bg); color: var(--text); font-size: 15px;
}
.scan-row-find-input::placeholder { color: var(--muted); }
.scan-row-find-input:focus { outline: none; border-color: var(--accent); }
.scan-row-find-status { margin: 0; color: var(--muted); font-size: 12px; font-weight: 600; }
.scan-row-find-status:empty { display: none; }

.scan-secondary {
  min-height: 44px; padding: 10px 14px;
  font-size: 15px; color: var(--text);
  background: transparent; border: 1px solid var(--line); border-radius: var(--radius);
}
.scan-secondary:active { background: var(--surface-2); border-color: var(--accent); transform: scale(0.97); }
.scan-launch:disabled { opacity: 0.5; pointer-events: none; }

/* --- Settings entry point (#100) ------------------------------------------- */
/* Icon-only gear, unobtrusive per BRAND.md. Opens the Settings sheet, which is
   now the home for the offline-image download manager (moved out of its own
   topbar slot, #98/#99). 44px tap target matches .queue-badge alongside it. */
.settings-btn {
  margin-left: 0; display: inline-flex; align-items: center; justify-content: center;
  min-height: 44px; min-width: 44px; padding: 0;
  border: 0; background: transparent;
  color: var(--muted); border-radius: var(--radius-sm); cursor: pointer;
}
.settings-btn:hover, .settings-btn:active { color: var(--text); background: var(--surface-2); }
.settings-btn svg { display: block; }

/* --- Settings sheet (#100, js/settings.js openSettingsSheet) --------------
   Same overlay idiom as the offline panel / collection sheet above
   (.scan-result), laid out as a top-aligned stack of sections instead of a
   list. One thing per section, generous space (BRAND.md: uncluttered). */
.settings-sheet { justify-content: flex-start; align-items: stretch; padding: calc(env(safe-area-inset-top) + 12px) 16px 24px; gap: 8px; background: var(--bg); }
/* The dialog itself is the initial focus landing spot (see openSettingsSheet), not a control the user operates -- so it never shows a focus ring of its own. */
.settings-sheet:focus, .settings-sheet:focus-visible { outline: none; }
.settings-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; max-width: 640px; width: 100%; margin: 0 auto; }
.settings-head h2 { margin: 0; font-size: 28px; font-weight: 700; letter-spacing: -0.02em; color: var(--text); }
.settings-head { padding-bottom: 8px; }
.settings-close {
  border: 0; background: transparent; color: var(--text);
  font-weight: 600; font-size: 15px; padding: 8px 0; cursor: pointer;
}
/* A reload into Settings programmatically focuses Done (so keyboard users land
   inside the dialog); that must NOT paint a focus ring on touch/reload. The
   ring still shows for real keyboard focus via :focus-visible. (v200 reopen) */
.settings-close:focus:not(:focus-visible) { outline: none; }
.settings-section {
  max-width: 640px; width: 100%; margin: 0 auto;
  background: transparent; border: 0; border-top: 1px solid var(--line);
  border-radius: 0; padding: 16px 0 8px;
  display: flex; flex-direction: column; gap: 8px;
}
#settings-about .settings-section-title { display: none; }
#settings-about { margin-top: auto; border-top: 0; }
#settings-about .settings-desc { color: var(--muted); font-size: 12px; }
/* The version links to the public /releases notes. */
.settings-version-link { color: var(--accent); font-weight: 600; text-decoration: none; }
.settings-version-link:hover { text-decoration: underline; }
/* The unofficial/trademark disclaimer: a quiet legal footnote under About. */
.settings-legal { font-size: 11px; line-height: 1.5; color: var(--muted); margin-top: 10px; }
.settings-section-title { margin: 0; font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.settings-desc { margin: 8px 0 0; color: var(--muted); font-size: 13px; order: 10; }
/* Community social icon row: filled brand marks, quiet until hovered. Sits
   below the section's description (order 11 > the desc's 10). */
.settings-socials { display: flex; gap: 10px; margin-top: 12px; order: 11; }
.settings-social {
  display: inline-flex; align-items: center; justify-content: center;
  width: 40px; height: 40px; flex: 0 0 auto;
  border: 1px solid var(--line); background: var(--surface-2); border-radius: var(--radius-sm);
  color: var(--muted); text-decoration: none;
  transition: color 0.12s ease, border-color 0.12s ease, background 0.12s ease;
}
.settings-social:hover, .settings-social:active { color: var(--text); border-color: var(--text); background: var(--bg); }
.settings-social:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.settings-social svg { display: block; }
.settings-row-btn {
  align-self: stretch; display: flex; align-items: center; justify-content: space-between;
  border: 0; background: transparent; color: var(--text); text-align: left;
  font-weight: 400; font-size: 15px; padding: 12px 0; margin-top: 0; border-radius: 0; cursor: pointer;
}
.settings-row-btn::after { content: '\203A'; color: var(--muted); }
.settings-badge {
  align-self: flex-start; border: 1px solid var(--line); background: var(--surface-2); color: var(--muted);
  font-weight: 600; font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase;
  padding: 3px 8px; border-radius: var(--radius-sm);
}

/* --- Games section (#108): one toggle-switch row per registered provider -- */
.settings-toggle-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  min-height: 52px;
}
.settings-toggle-row + .settings-toggle-row { border-top: 1px solid var(--line); }
.settings-toggle-label { font-size: 15px; font-weight: 400; color: var(--text); }
/* A pill switch built from one <button role="switch">: the knob is a
   ::before pseudo-element that slides on the .is-on class, which js/settings.js
   always sets in the same paint() call as the aria-checked attribute -- one
   read of the enabled-map, two in-sync outputs, never a class the visual
   state could drift from the accessible one. */
.settings-toggle {
  position: relative; flex: 0 0 auto; width: 44px; height: 26px;
  border: 1px solid var(--line); border-radius: 999px; background: var(--surface-2);
  cursor: pointer; padding: 0;
}
.settings-toggle::before {
  content: ''; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px;
  border-radius: 50%; background: var(--muted); transition: transform 0.15s ease, background 0.15s ease;
}
.settings-toggle.is-on { border-color: var(--accent); background: var(--accent); }
.settings-toggle.is-on::before { transform: translateX(18px); background: var(--on-accent); }
.settings-games-note { color: var(--stale); font-size: 12px; }

/* Filter input (#98): sits between the head and the set list, styled by the
   #75 input baseline (:where() above) — no bespoke box rules needed here. */
.offline-filterwrap { max-width: 640px; width: 100%; margin: 0 auto; }
.offline-filter::-webkit-search-cancel-button { margin-right: 2px; }

/* Reuse the scan overlay base (.scan-result) but lay out as a top-aligned,
   scrollable stack of labelled groups (#200). */
.offline-panel { justify-content: flex-start; align-items: stretch; padding: calc(env(safe-area-inset-top) + 20px) 16px 32px; gap: 22px; background: var(--bg); overflow-y: auto; }
.offline-head { display: grid; grid-template-columns: 1fr auto; align-items: baseline; gap: 4px 12px; max-width: 640px; width: 100%; margin: 0 auto; }
.offline-head h2 { grid-column: 1; grid-row: 1; margin: 0; font-size: 26px; font-weight: 700; letter-spacing: -0.02em; color: var(--text); }
.offline-sub { grid-column: 1 / 2; grid-row: 2; margin: 0; color: var(--muted); font-size: 13px; line-height: 1.45; }
.offline-close {
  grid-column: 2; grid-row: 1; justify-self: end; align-self: baseline; border: 0;
  background: transparent; color: var(--muted); font-weight: 600; font-size: 15px;
  padding: 4px 0; cursor: pointer;
}

/* #200: each kind of offline data is its own labelled group -- a small
   uppercase heading + hint over a single surface card that holds the rows. */
.offline-group { max-width: 640px; width: 100%; margin: 0 auto; display: flex; flex-direction: column; gap: 10px; }
.offline-group-title { margin: 0; font-size: 11px; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); }
.offline-group .offline-sub { margin: -2px 0 2px; }

/* The rows of a group sit on one rounded surface card, hairline-divided --
   works for a multi-row <ul.offline-list> and for a lone direct-child .offline-row
   (the scanning + Magic rows). */
.offline-list { list-style: none; margin: 0; padding: 0; width: 100%; display: flex; flex-direction: column; }
.offline-group .offline-list,
.offline-group > .offline-row {
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius-sm);
}
.offline-empty { color: var(--muted); text-align: center; padding: 22px; font-size: 13px; }
.offline-row {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  background: transparent; border: 0; border-bottom: 1px solid var(--line);
  border-radius: 0; padding: 13px 14px;
}
.offline-list .offline-row:last-child { border-bottom: 0; }
.offline-info { min-width: 0; }
.offline-name { color: var(--text); font-weight: 600; font-size: 15px; display: flex; align-items: center; gap: 8px; min-width: 0; }
.offline-name > :not(.offline-status-dot):not(.offline-game-badge) { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.offline-meta { color: var(--muted); font-size: 12px; margin-top: 3px; }

/* #200: a game's offline readiness at a glance -- muted when it is not on the
   device, emerald when it is (semantic ready, not the brand pulse elsewhere). */
.offline-status-dot { flex: 0 0 auto; width: 8px; height: 8px; border-radius: 50%; background: var(--muted); opacity: 0.6; }
.offline-status-dot.is-ready { background: var(--accent); opacity: 1; }
/* Automatic games carry no button -- a quiet tag stands in for the action. */
.offline-auto-tag { flex: 0 0 auto; color: var(--muted); font-size: 12px; font-weight: 600; }

/* #108: only shown when more than one downloadable game is on -- tells two
   games' sets apart in one merged, newest-first list. */
.offline-game-badge {
  display: inline-block; flex: 0 0 auto;
  color: var(--muted); background: var(--surface-2); border: 1px solid var(--line);
  font-size: 9px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em;
  padding: 1px 6px; border-radius: var(--radius); vertical-align: middle;
}
.offline-dl {
  flex: 0 0 auto;
  border: 1px solid var(--line); background: transparent; color: var(--text);
  font-weight: 600; font-size: 13px; padding: 8px 14px; border-radius: var(--radius);
  cursor: pointer; min-width: 112px; text-align: center;
}
.offline-dl[disabled] { opacity: 0.7; cursor: default; }
.offline-dl.is-done { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }

/* Filter input (#98): sits inside the Set-images group, above its list. */
.offline-filterwrap { width: 100%; }

/* #114/#200: on-device scanning is now a first-class group like the others. */
.ondevice-section { margin: 0 auto; }
.ondevice-sub { font-size: 13px; }
/* Download progress bar: only shown while the recognizer is downloading, so the
   count is never a lone static number that reads as stuck. */
.ondevice-progress { height: 6px; margin-top: 10px; border-radius: 999px; background: var(--surface-2, var(--line)); overflow: hidden; }
.ondevice-progress-fill { height: 100%; width: 0; border-radius: 999px; background: var(--accent); transition: width 0.3s ease; }
@media (prefers-reduced-motion: reduce) { .ondevice-progress-fill { transition: none; } }

/* --- collection switcher/manage sheet (#95, js/inventoryView.js openCollectionSheet) ---
   Same overlay idiom + list layout as .offline-panel above (a full-screen
   .scan-result variant), so it reads as the same "sheet" language, not a
   bespoke dialog. --- */
.collection-sheet { justify-content: flex-start; align-items: stretch; padding: calc(env(safe-area-inset-top) + 16px) 16px 24px; gap: 12px; background: var(--bg); }
.coll-sheet-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; max-width: 640px; width: 100%; margin: 0 auto; }
.coll-sheet-head h2 { margin: 0; font-size: 24px; font-weight: 700; letter-spacing: -0.02em; color: var(--text); }
.coll-sheet-close {
  border: 0; background: transparent; color: var(--text);
  font-weight: 600; font-size: 15px; padding: 8px 0; cursor: pointer;
}
.coll-sheet-list { list-style: none; margin: 0 auto; padding: 0; max-width: 640px; width: 100%; display: flex; flex-direction: column; gap: 8px; }
.coll-sheet-row {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 4px;
}
.coll-sheet-row[aria-current="true"] { border-color: var(--accent); }
.coll-sheet-body {
  width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 12px;
  border: none; background: transparent; padding: 10px 10px 4px; cursor: pointer; text-align: left;
}
.coll-sheet-name { color: var(--text); font-weight: 700; font-size: 15px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* #15: the current list is marked by the green row border alone (was a trailing
   "· current" whose kerning read awkwardly). */
.coll-sheet-kind { flex: 0 0 auto; color: var(--muted); font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; }
.coll-sheet-count { flex: 0 0 auto; color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
.coll-sheet-actions { display: flex; gap: 8px; padding: 0 10px 8px; }
/* Target picker (Scan into / Add to): kind segments + create-in-place. */
.coll-sheet-kinds { display: flex; gap: 8px; max-width: 640px; width: 100%; margin: 0 auto 12px; }
.coll-sheet-kindbtn {
  flex: 1 1 0; min-height: 40px; padding: 8px 6px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: transparent; color: var(--muted);
  font-size: 14px; font-weight: 700; cursor: pointer;
}
.coll-sheet-kindbtn.is-active { color: var(--on-accent); background: var(--accent); border-color: var(--accent); }
/* A picker row holds a single line (name only), so its body is vertically
   centered -- symmetric padding, unlike the management row that stacks actions. */
.coll-sheet-row--pick .coll-sheet-body { padding: 15px 14px; justify-content: flex-start; }
.coll-sheet-row--new { background: transparent; border-style: dashed; }
.coll-sheet-newbtn { color: var(--muted); font-weight: 600; padding: 14px; justify-content: flex-start; }
.coll-sheet-newform { display: flex; gap: 8px; width: 100%; padding: 8px; }
.coll-sheet-newinput {
  flex: 1 1 auto; min-width: 0; min-height: 40px; padding: 8px 12px;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface-2, var(--surface)); color: var(--text); font-size: 15px;
}
.coll-sheet-newinput:focus { outline: none; border-color: var(--accent); }
.coll-sheet-rename, .coll-sheet-delete {
  flex: 1 1 0; border: 1px solid var(--line); background: transparent; color: var(--text);
  font-weight: 600; font-size: 12px; padding: 8px 10px; border-radius: 8px; cursor: pointer;
}
/* --down (coral) is reserved for pricing "over market" meaning (BRAND.md) —
   kept OUT of this row's resting state, same convention as .inv-remove:
   neutral until pressed, so it never reads as a price signal. */
.coll-sheet-delete:active { border-color: var(--down); color: var(--down); }
.coll-sheet-delete[disabled] { color: var(--muted); border-color: var(--line); opacity: 0.6; cursor: not-allowed; }
.coll-sheet-form {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px; padding: 10px;
}
.coll-sheet-input {
  flex: 1 1 160px; appearance: none; -webkit-appearance: none;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 10px 12px; font-size: 15px; /* 16px would avoid iOS zoom but breaks this row's fit — acceptable, matches the rest of the sheet's compact controls */
}
.coll-sheet-input:focus { outline: none; border-color: var(--accent); }
/* Collection intent kind (#107): Buy/Sell/Personal, same look as
   .coll-sheet-input, sized to its short option labels rather than growing to
   fill the row. */
.coll-sheet-kind-select {
  flex: 0 0 auto; appearance: none; -webkit-appearance: none;
  background: var(--surface-2); color: var(--text);
  border: 1px solid var(--line); border-radius: var(--radius);
  padding: 10px 12px; font-size: 15px;
}
.coll-sheet-kind-select:focus { outline: none; border-color: var(--accent); }
.coll-sheet-kind-select[disabled] { color: var(--muted); opacity: 0.6; }
.coll-sheet-save, .coll-sheet-create-btn {
  border: 0; background: var(--accent); color: var(--on-accent);
  font-weight: 700; font-size: 13px; padding: 9px 14px; border-radius: var(--radius); cursor: pointer;
}
.coll-sheet-cancel {
  border: 1px solid var(--line); background: transparent; color: var(--muted);
  font-weight: 600; font-size: 13px; padding: 9px 14px; border-radius: var(--radius); cursor: pointer;
}
.coll-sheet-delete-confirm {
  border: 1px solid var(--line); background: var(--surface-2); color: var(--down);
  font-weight: 700; font-size: 13px; padding: 9px 14px; border-radius: var(--radius); cursor: pointer;
}
.coll-sheet-delete-confirm:active { border-color: var(--down); }
.coll-sheet-confirm-msg { flex: 1 1 100%; color: var(--text); font-size: 13px; }
.coll-sheet-err { flex: 1 1 100%; color: var(--down); font-size: 12px; font-weight: 600; }
.coll-sheet-create {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  max-width: 640px; width: 100%; margin: 4px auto 0;
  background: var(--surface); border: 1px dashed var(--line);
  border-radius: var(--radius); padding: 12px;
}

/* --- add-sealed sheet (#96, js/inventoryView.js openAddSealedSheet) ---
   Same overlay/sheet language as the collection sheet above (.scan-result +
   .collection-sheet), with its own search box + result list. --- */
.sealed-sheet-search { max-width: 640px; width: 100%; margin: 0 auto; }
.sealed-sheet-status {
  max-width: 640px; width: 100%; margin: 0 auto; color: var(--muted);
  font-size: 13px; min-height: 1em;
}
.sealed-sheet-list {
  list-style: none; margin: 0 auto; padding: 0; max-width: 640px; width: 100%;
  display: flex; flex-direction: column; gap: 8px; overflow-y: auto;
}
.sealed-sheet-row {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 8px;
  display: flex; align-items: center; gap: 10px;
}
.sealed-sheet-thumb {
  flex: 0 0 auto; width: 44px; height: 44px; border-radius: 8px;
  object-fit: cover; background: var(--surface-2);
}
.sealed-sheet-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.sealed-sheet-name {
  color: var(--text); font-weight: 700; font-size: 14px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sealed-sheet-price { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }
.sealed-sheet-add {
  flex: 0 0 auto; border: 1px solid var(--accent); background: var(--accent);
  color: var(--on-accent); font-weight: 700; font-size: 13px;
  padding: 8px 14px; border-radius: var(--radius); cursor: pointer;
}

/* --- explore: browse by set / by Pokemon + master-set checklist (#112) ---
   Same list/row/sheet language as everywhere else in the app (.results,
   .coll-sheet-*), so a third tab reads as one more screen, not a bespoke
   feature bolted on. --- */
.explore-mode-row {
  margin-bottom: 12px; gap: 18px; display: flex;
  border-bottom: 1px solid var(--line);
}
.explore-mode-row .chip {
  min-height: 36px; padding: 6px 0; font-size: 14px; border: 0; border-radius: 0;
  border-bottom: 2px solid transparent; background: transparent; color: var(--muted); margin-bottom: -1px;
}
.explore-mode-row .chip.is-active { background: transparent; color: var(--text); border-bottom-color: var(--text); }
.explore-list { list-style: none; margin: 0 0 16px; padding: 0; display: flex; flex-direction: column; gap: 0; }
.explore-row {
  background: transparent; border: 0; border-bottom: 1px solid var(--line);
  border-radius: 0; display: flex; align-items: center; gap: 4px;
}
.explore-row-btn {
  flex: 1 1 auto; min-width: 0; display: flex; flex-direction: row; align-items: baseline; justify-content: space-between; gap: 12px;
  border: none; background: transparent; color: var(--text);
  padding: 11px 0; cursor: pointer; text-align: left; font: inherit;
}
.explore-check-row { flex-direction: row; align-items: center; gap: 10px; }
.explore-check-mark {
  flex: 0 0 auto; width: 22px; height: 22px; border-radius: 999px;
  display: flex; align-items: center; justify-content: center;
  font-size: 13px; font-weight: 700; color: var(--muted); border: 1px solid var(--line);
}
.explore-check-row[data-owned="true"] .explore-check-mark { color: var(--accent); border-color: var(--accent); }
.explore-row-name { color: var(--text); font-weight: 400; font-size: 15px; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.explore-row-meta { color: var(--muted); font-size: 13px; font-variant-numeric: tabular-nums; flex: 0 0 auto; }
.explore-empty { color: var(--muted); text-align: center; padding: 20px; }
.explore-group {
  position: sticky; top: 0; z-index: 2; background: var(--bg);
  padding: 18px 0 6px; color: var(--muted); font-size: 11px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase;
}

.explore-meter { display: flex; flex-direction: column; gap: 6px; width: 100%; margin: 4px 0 16px; }
.explore-meter-bar { height: 6px; border-radius: 999px; background: var(--surface-2); overflow: hidden; }
.explore-meter-fill { height: 100%; background: var(--accent); border-radius: 999px; }
.explore-meter-label { color: var(--muted); font-size: 12px; font-variant-numeric: tabular-nums; }

/* #99: contextual "Download this set for offline" row in the Explore-by-set
   checklist sheet. Same shape as .inv-prep above -- one quiet pill button + a
   status note that turns into a progress readout while running. */
.explore-download {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin: 4px 0 16px;
}
.explore-download-btn {
  flex: 0 0 auto; min-height: 44px; padding: 8px 20px; border-radius: var(--radius);
  border: 1px solid var(--line); background: transparent; color: var(--text);
  font-size: 14px; font-weight: 700; cursor: pointer;
}
.explore-download-btn:active { background: var(--surface-2); }
.explore-download-btn:disabled { border-color: var(--line); color: var(--muted); cursor: not-allowed; }
.explore-download-btn.is-done { background: var(--accent); color: var(--on-accent); border-color: var(--accent); }
.explore-download-note {
  color: var(--muted); font-size: 13px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.explore-download-note[data-tone="running"] { color: var(--accent); font-weight: 700; }
.explore-download-note[data-tone="err"] { color: var(--down); font-weight: 700; }

.explore-sheet { justify-content: flex-start; align-items: stretch; padding: calc(env(safe-area-inset-top) + 16px) 16px 24px; gap: 4px; background: var(--bg); }
.explore-note { color: var(--muted); font-size: 13px; line-height: 1.4; margin: 0 0 12px; }
.explore-add-row { margin-bottom: 16px; }
.explore-add-results { margin-top: 8px; }
.explore-checklist { max-height: 60vh; overflow-y: auto; }
.sealed-sheet-add[disabled] { opacity: 0.6; cursor: not-allowed; }

/* --- Card detail (#172): one screen, no scrolling. Art left; Market, finish
   and trend right, flush with the art; a kind-aware price block, one fields
   row, and the pinned action. The trend card is the flexible element. --- */
.is-detail #view { padding: 0 16px; }
.detail {
  display: flex; flex-direction: column; gap: 10px;
  box-sizing: border-box; min-height: calc(100dvh - var(--tabbar-h) - env(safe-area-inset-bottom));
  max-height: calc(100dvh - var(--tabbar-h) - env(safe-area-inset-bottom));
  padding: calc(env(safe-area-inset-top) + 10px) 0 12px; overflow: hidden;
}
/* #194: while a field is focused under the keyboard, js/detail.js lifts the
   one-screen height cap and pads the detail by the keyboard inset, so the
   document gains that much scroll room and the focused row can ride above
   the keyboard; cleared on blur. */
.detail--kbd { max-height: none; }
.detail-top { display: flex; align-items: flex-start; gap: 10px; }
.detail-back {
  flex: 0 0 auto; width: 40px; height: 40px; border-radius: 999px; border: 1px solid var(--line);
  background: var(--surface); color: var(--text); padding: 0; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
}
.detail-back svg { width: 20px; height: 20px; display: block; }
.detail-back:active { background: var(--surface-2); }
.detail-heading { flex: 1 1 auto; min-width: 0; }
.detail-name { margin: 0; font-size: 19px; font-weight: 700; line-height: 1.2; letter-spacing: -0.01em; overflow-wrap: anywhere; }
.detail-meta { margin: 2px 0 0; color: var(--muted); font-size: 13px; overflow-wrap: anywhere; }
.detail-kind {
  flex: 0 0 auto; padding: 5px 10px; border-radius: var(--radius-sm); font-size: 11px; font-weight: 700;
  border: 1px solid var(--accent); color: var(--accent); background: transparent; max-width: 40%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  cursor: pointer; font-family: inherit;
}
.detail-kind::after { content: ' \203A'; opacity: 0.8; }
.detail-kind:active { background: var(--surface-2); }
.coll-sheet-group { list-style: none; margin: 10px 0 2px; color: var(--muted); font-size: 13px; font-weight: 600; }
.detail-kind[data-kind="buy"] { border-color: var(--stale); color: var(--stale); }
.detail-kind[data-kind="sell"] { border-color: var(--text); color: var(--text); }
.detail-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; align-items: stretch; }
/* The art box stretches to the column's height so both columns end flush; the image sits centred inside at its own aspect. */
/* align-self: start (not stretch): the art's height follows from its own width
   via aspect-ratio. Under stretch, a tall right column (a long graded ladder)
   would force the art taller, and aspect-ratio would balloon its WIDTH to match,
   spilling the card over the market panel (#234 ask-est overflow). */
.detail-art { aspect-ratio: 63 / 88; border-radius: var(--radius); background: var(--surface); overflow: hidden; align-self: start; min-height: 0; }
.detail-art--missing { border: 1px solid var(--line); background: var(--art-missing-bg); }
.detail-img { display: block; width: 100%; height: 100%; object-fit: contain; }
.detail-img.is-missing { background: var(--art-missing-bg); }
.detail-col { display: flex; flex-direction: column; gap: 8px; min-width: 0; min-height: 0; }
/* No price history yet: the market card absorbs the column height so there is no
   empty gap where the Trend card would sit (it only appears once history exists). */
/* No Trend card: the market card keeps its NATURAL height (graded rows sit right
   under the price, as when Trend is present) rather than stretching to fill the
   column, stretching pushed the graded block to the bottom and left a gap
   between the price and the grades (the Venusaur "deadspace"). The column simply
   ends after the market card; the art defines the row height. */
.detail-col--flat .detail-market { flex: 0 0 auto; }
.detail-market {
  background: var(--surface); border: 1px solid var(--line); border-radius: 14px; padding: 8px 10px;
  display: flex; flex-direction: column; gap: 4px; flex: 0 0 auto;
}
.detail-market-main { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.detail-market-label { color: var(--muted); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.04em; }
.detail-hero-price { font-size: 24px; font-weight: 800; line-height: 1.05; font-variant-numeric: tabular-nums; letter-spacing: -0.02em; }
.detail-market-src, .detail-graded-src { color: var(--muted); font-size: 10px; text-decoration: underline; }
.detail-prices-status { color: var(--muted); font-size: 11px; margin: 2px 0 0; }
/* max-height + scroll: the graded ladder can carry many eBay ask-est rows on
   top of the licensed comps (#234); bound it so the market panel never grows
   tall enough to push the card art out of shape, the overflow scrolls in place. */
.detail-graded { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex: 0 0 auto; border-top: 1px solid var(--line); padding-top: 4px; max-height: 132px; overflow-y: auto; overscroll-behavior: contain; }
.detail-graded-rows { display: grid; grid-template-columns: auto auto 1fr; align-items: baseline; gap: 1px 6px; margin: 0; width: 100%; }
.detail-graded-price { margin: 0; font-size: 11px; font-weight: 700; font-variant-numeric: tabular-nums; text-align: left; }
.detail-graded-grade { margin: 0; color: var(--muted); font-size: 9px; font-weight: 700; white-space: nowrap; display: contents; }
.detail-graded-grade > .detail-graded-name { white-space: nowrap; }
.detail-ladder-mult { font-size: 9px; color: var(--muted); font-variant-numeric: tabular-nums; font-weight: 500; text-align: right; }
.detail-finish {
  flex: 0 0 auto; height: 36px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface-2);
  color: var(--text); font-weight: 600; font-size: 13px; padding: 0 10px; width: 100%;
}
.detail-finish:disabled { color: var(--muted); }
.detail-trend {
  background: var(--surface); border: 1px solid var(--line); border-radius: 14px; padding: 8px 10px;
  display: flex; flex-direction: column; gap: 4px; flex: 1 1 auto; min-height: 48px;
}
.detail-trend-head { display: flex; align-items: baseline; justify-content: space-between; flex: 0 0 auto; }
.detail-trend-label { color: var(--muted); font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.03em; }
.detail-trend-delta { font-weight: 700; font-size: 11px; }
.detail-trend-delta[data-tone="up"] { color: var(--up); }
.detail-trend-delta[data-tone="down"] { color: var(--down); }
.detail-trend-spark { display: block; width: 100%; flex: 1 1 auto; min-height: 0; }
.detail-trend-path { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.detail-trend-span, .detail-trend-note { margin: 0; color: var(--muted); font-size: 10px; }
.detail-rule { height: 1px; background: var(--line); flex: 0 0 auto; }
.detail-bottom { display: flex; flex-direction: column; gap: 8px; flex: 1 1 auto; min-height: 0; }
.detail-kind-block { display: flex; flex-direction: column; gap: 7px; }
.detail-price-row {
  border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface-2);
  display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 12px; min-height: 52px; box-sizing: border-box;
}
.detail-price-row--edit { border-color: var(--accent); }
.detail-price-left { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.detail-price-label { color: var(--muted); font-size: 11px; font-weight: 600; }
.detail-price-value { font-weight: 800; font-size: 17px; font-variant-numeric: tabular-nums; }
.detail-money { display: flex; align-items: baseline; gap: 2px; font-weight: 800; font-size: 17px; }
.detail-money-prefix { color: var(--muted); }
.detail-money-input {
  border: 0; background: transparent; color: var(--text); font-weight: 800; font-size: 17px; padding: 0; width: 7ch;
  font-variant-numeric: tabular-nums; border-radius: 0;
}
.detail-money-input:focus { outline: none; }
.detail-price-pct { color: var(--muted); font-size: 11px; font-weight: 700; text-align: right; white-space: pre-line; flex: 0 0 auto; }
.detail-price-pct[data-tone="under"] { color: var(--up); }
.detail-price-pct[data-tone="over"] { color: var(--down); }
.detail-fields { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; }
.detail-field { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.detail-field-label { color: var(--muted); font-size: 11px; font-weight: 600; }
.detail-select, .detail-input {
  height: 38px; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface-2);
  color: var(--text); font-weight: 600; font-size: 13px; padding: 0 10px; width: 100%; box-sizing: border-box;
}
.detail-select:disabled, .detail-input:disabled { color: var(--muted); }
.detail-qty-stepper { display: flex; align-items: stretch; gap: 4px; }
.detail-step {
  flex: 0 0 30px; height: 38px; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-2); color: var(--text); font-size: 18px; font-weight: 600; line-height: 1;
  padding: 0; cursor: pointer; -webkit-tap-highlight-color: transparent;
  display: flex; align-items: center; justify-content: center;
}
.detail-step:active { opacity: 0.6; }
.detail-qty-input { flex: 1 1 auto; min-width: 0; text-align: center; padding: 0 4px; }
.detail-qty-input::-webkit-inner-spin-button, .detail-qty-input::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
.detail-grade { display: flex; gap: 6px; }
.detail-grade-company { flex: 1 1 auto; min-width: 0; padding: 0 6px; }
.detail-grade-value { flex: 0 0 52px; width: 52px; text-align: center; padding: 0; }
.detail-notes { display: flex; flex-direction: column; gap: 4px; }
.detail-notes:empty { display: none; }
.detail-note { margin: 0; color: var(--muted); font-size: 12px; line-height: 1.35; }
.detail-note a { color: var(--muted); }
.detail-spacer { flex: 1 1 auto; min-height: 4px; }
.detail-actions { display: flex; flex-direction: column; gap: 6px; flex: 0 0 auto; }
.detail-submit {
  height: 52px; border: 0; border-radius: 14px; background: var(--accent); color: var(--on-accent);
  font-weight: 800; font-size: 16px; cursor: pointer;
}
.detail-submit:active { filter: brightness(0.94); }
.detail-submit:disabled { opacity: 0.6; cursor: default; }
.detail-remove { border: 0; background: transparent; color: var(--muted); font-size: 13px; font-weight: 600; padding: 4px; cursor: pointer; }
/* #191: Bought / Sold, the secondary verb above the primary */
.detail-verb { min-height: 44px; border: 1px solid var(--line); border-radius: var(--radius); background: transparent; color: var(--text); font-size: 15px; font-weight: 600; cursor: pointer; }
.detail-verb:active { background: var(--surface-2); }
.detail-verbform { display: flex; align-items: flex-end; gap: 8px; padding: 4px 0 6px; }
.detail-verbform[hidden] { display: none; }
.detail-verbform-label { flex: 1 1 auto; display: flex; flex-direction: column; gap: 4px; color: var(--muted); font-size: 12px; font-weight: 600; }
.detail-verbform-label input { width: 100%; }
.detail-verbform-go { min-height: 44px; padding: 0 16px; border: 0; border-radius: var(--radius); background: var(--accent); color: var(--on-accent); font-size: 15px; font-weight: 600; cursor: pointer; }
.detail-verbform-cancel { min-height: 44px; padding: 0 12px; border: 1px solid var(--line); border-radius: var(--radius); background: transparent; color: var(--text); font-size: 14px; font-weight: 500; cursor: pointer; }
.detail-confirm { margin: 0; font-weight: 600; font-size: 13px; text-align: center; }
.detail-confirm[data-tone="ok"] { color: var(--accent); }
.detail-confirm[data-tone="err"] { color: var(--down); }
@media (max-height: 700px) {
  .detail { gap: 6px; }
  .detail-trend { min-height: 40px; }
  .detail-price-row { min-height: 44px; padding: 6px 12px; }
}
