/* Public /market explorer (P4, ahg-macqj). Dedicated stylesheet — same
   convention as dashboard.css/pull.css: a per-page-family CSS file linked
   via asset_path, not an inline <style> block (view-conventions ratchet).
   Vault Industrial palette per DESIGN.md; amber reserved for the primary
   series / key CTA, everything else defers. */

/* Full-bleed shell to match the dashboard app shell and /collect: the page
   spans the viewport (capped at 2000px, the same ceiling as the global
   `.container` and collect's `.qa-app`) so the logo, nav, and content align
   top-left instead of sitting indented inside a narrow 1100px column. The
   32px horizontal padding matches `.container`'s so the logo lands at the
   same left offset as every other signed-in page. */
.mkt-page {
  max-width: 2000px;
  margin: 0 auto;
  padding: 24px 32px 64px;
}

/* Zero the global 32px body padding (application.html.erb sets it) so it does
   NOT stack with `.mkt-page`'s own 32px -- that double gutter pushed the logo
   and content to 64px from the edge, far more inset than the dashboard and
   /collect. Collect does the same reset (`body.qa-collect { padding: 0 }`);
   now `.mkt-page` is the sole gutter, landing content at 32px like the app
   shell. */
body.mkt-body {
  padding: 0;
}

/* The app shell's global `.container` (application.html.erb) wraps every
   page in a padded, rounded, bordered card -- right for the dashboard's
   dense tool chrome, wrong for a public explorer that should read as a page
   on the body's own dark gradient, not a widget floating inside one more
   box. Scoped to `body.mkt-body` (set via content_for :body_class on every
   market/*.html.erb) so nothing outside this page family is affected. */
body.mkt-body .container {
  max-width: none;
  background: transparent;
  border: none;
  box-shadow: none;
  border-radius: 0;
  padding: 0;
  min-height: 0;
}

.mkt-subtitle {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: 600;
  color: var(--text-primary);
  margin: 24px 0 12px;
}

/* Release groups share a column grid regardless of their names or CTA lengths. */
/* Prominent in-page jump from upcoming releases to the recent section. */
.mkt-releases-bar {
  display: flex;
  align-items: center;
  margin: 20px 0 4px;
}
.mkt-releases-jump {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 4px;
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
  white-space: nowrap;
  text-decoration: none;
  border-bottom: 1px dotted var(--text-muted);
}
.mkt-releases-jump:hover { border-bottom-color: var(--text-primary); }

.mkt-releases-period { margin: 32px 0; }
.mkt-releases-period > .mkt-subtitle { font-size: 26px; }
.mkt-release-identity { min-width: 0; }
/* Real link to that game's own /market/:game/releases (ahg) -- was a JS-only
   filter button before the filter bar switched to real navigation. */
.mkt-release-table .mkt-release-game {
  display: block;
  width: fit-content;
  margin-top: 4px;
  color: var(--text-secondary);
  font-size: 14px;
  text-decoration: none;
  text-align: left;
}
.mkt-release-table .mkt-release-game:hover { text-decoration: underline; color: var(--text-primary); }
.mkt-table.mkt-release-table { table-layout: fixed; min-width: 0; }
.mkt-release-col-set { width: 50%; }
.mkt-release-col-date { width: 20%; }
.mkt-release-col-buy { width: 30%; }
/* ahg-0yim1.4: the per-game page adds a State column -- column widths are
   re-split under this modifier class so the hub's plain 3-column table
   above is never touched. */
.mkt-release-table--per-game .mkt-release-col-date { width: 15%; }
.mkt-release-table--per-game .mkt-release-col-set { width: 40%; }
.mkt-release-table--per-game .mkt-release-col-state { width: 15%; }
.mkt-release-table--per-game .mkt-release-col-buy { width: 30%; }
/* Signal via text color only (flat physics rule 3 -- no tinted background,
   no border); amber is the app's one accent, reused from the preorder
   notice on the set page (.mkt-preorder). */
.mkt-state-preorder { color: var(--accent-amber); font-weight: 600; }
.mkt-state-released { color: var(--text-muted); }
.mkt-release-table th {
  font-size: var(--fs-label);
  letter-spacing: var(--tracking-label);
}
.mkt-release-table td { vertical-align: middle; overflow-wrap: anywhere; }
.mkt-release-table .mkt-set-cell a { white-space: normal; }
.mkt-release-table .mkt-buy-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  width: 100%;
  min-height: 44px;
  white-space: normal;
  text-align: center;
  border-bottom: 0;
}
.mkt-release-mobile-label { display: none; }

@media (max-width: 640px) {
  .mkt-release-table,
  .mkt-release-table tbody { display: block; }
  .mkt-release-table colgroup { display: none; }
  .mkt-release-table thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }
  .mkt-release-table tbody tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    padding: 12px;
  }
  /* ahg-0yim1.4 mobile QA fix: this `display: grid` above outranks the
     browser's own `[hidden] { display: none }` UA rule (same specificity
     tier, author sheet wins), so a game-scoped /market/:game/releases page's
     inline hide-the-other-games' -rows script (market/releases.html.erb)
     was setting `hidden` on every non-matching row but they kept rendering
     anyway below 640px -- a Magic page visibly listing Pokemon sets on
     phones. Desktop was never affected (no competing `display` rule there). */
  .mkt-release-table tbody tr[hidden] { display: none; }
  .mkt-release-table td { padding: 4px 0; }
  .mkt-release-table .mkt-release-set-name {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }
  .mkt-release-date { color: var(--text-secondary); }
  .mkt-release-mobile-label { display: inline; }
}

.mkt-empty {
  font-size: 14px;
  color: var(--text-muted);
  padding: 12px 0;
}
/* ahg-0yim1.3: the preorder/new_release prose block above the sealed
   carousel -- fill + text only, no tinted background or border (the old
   .mkt-preorder amber box this replaces broke the flat-physics rule: no
   accent walls, signal lives in text color only). Same treatment as
   .mkt-summary just below. */
.mkt-lifecycle-prose {
  font-size: 15px;
  color: var(--text-secondary);
  line-height: 1.5;
  margin: 4px 0 20px;
}

/* Hardcoded to the DARK-mode hex before -- #4ade80/#f87171 read as illegible
   neon-on-white in light mode (the app's semantic vars already have a
   darker light-mode pair, application_layout.css.erb's [data-theme="light"]
   block: --accent-green:#16a34a / --accent-rose:#dc2626). Route through the
   vars like every other delta color in this file (.panel-pill below)
   already does, so both modes stay readable. */
.mkt-delta-pos { color: var(--accent-green); }
.mkt-delta-neg { color: var(--accent-rose); }
.mkt-delta-flat { color: var(--text-muted); }

/* Fixed-scope drilldown stat strip (/market/:game, /market/:game/:set --
   ahg-85hzm). Server-rendered (Market::SeriesSummary is computed at request
   time, not client-fetched -- no reason to wait on JS for numbers the
   controller already has). A plain label/value row, not a boxed card grid:
   DESIGN.md's "tables are the hero, no widgets competing with data" applies
   here too -- these are headline numbers, not a dashboard tile, so no
   .mkt-card-style box is reintroduced for them. */
.mkt-page .stats {
  display: flex;
  flex-wrap: wrap;
  gap: 28px;
  margin: 20px 0;
  padding: 16px 0;
  border-top: 1px solid var(--border-subtle);
  border-bottom: 1px solid var(--border-subtle);
}
.mkt-page .stat { min-width: 96px; }
.mkt-page .stat-label {
  font-size: 14px;
  color: var(--text-muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 4px;
}
.mkt-page .stat-value {
  font-family: var(--font-mono);
  font-size: 22px;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
/* ahg-2npqv: "N of M priced" coverage caption under a stat value. */
.mkt-page .stat-sub {
  font-size: 14px;
  color: var(--text-muted);
  margin-top: 2px;
}
/* ahg-e8axv: logged-in seller's "Your position" panel (async), reuses .stats. */
.mkt-page .mkt-your-position { margin-top: 20px; }
.mkt-page .mkt-your-position .mkt-subtitle { margin-bottom: 8px; }

.mkt-table-wrap {
  overflow-x: auto;
  overflow-y: hidden;
}

.mkt-table {
  width: 100%;
  min-width: 560px;
  border-collapse: collapse;
}

.mkt-table th {
  /* 14px horizontal matches td so every header sits directly over its column;
     numeric headers right-align to meet their right-aligned numeric cells
     (they were left-aligned, floating left of the numbers). Everything else
     matches the app-wide table-header convention (application_layout.css.erb
     th, dashboard's .spikes-table/#cards-table-view th) -- 11px uppercase
     muted, not the 14px plain-case market ran before (ahg-o6rs6). */
  padding: 8px 14px;
  text-align: left;
  font-size: 11px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
}
.mkt-table th.mkt-num { text-align: right; }

.mkt-table td {
  padding: 11px 14px;
  font-size: 14px;
}
.mkt-table tbody tr:nth-child(even) { background: var(--zebra); }

.mkt-table td.mkt-num {
  font-family: var(--font-mono);
  text-align: right;
}

/* Market value is the size number the drill-in exists to surface — give it a
   touch more weight than the supporting columns without spending the amber. */
.mkt-table td.mkt-val {
  color: var(--text-primary);
  font-weight: 500;
}

.mkt-table a {
  color: var(--text-primary);
  text-decoration: none;
  border-bottom: 1px dotted var(--text-muted);
}

/* Sets table search (ahg-85hzm follow-up): plain text input, filters
   already-rendered rows client-side via data-set-name -- no round trip. */
.mkt-sets-search { margin-bottom: 10px; }
.mkt-sets-search-input {
  width: 100%;
  max-width: 320px;
  min-height: 44px;
  padding: 0 14px;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  font: inherit;
  font-size: 16px; /* iOS zoom floor */
}
.mkt-sets-search-input::placeholder { color: var(--text-muted); }

/* Set icon + name, one cell -- the icon is decorative (alt="") since the
   link text already names the set. Placeholder is a plain hairline-bordered
   swatch, not a Magic-only guessed URL: set_icon_url is null for every
   game besides Magic/Pokemon today (ahg-85hzm), so a missing icon is the
   COMMON case, not an edge case. */
.mkt-set-cell { display: flex; align-items: center; gap: 8px; }
/* Fixed-width slot prevents layout shift when the icon is absent or fails
   to load (onerror hides the img but the slot holds the space). */
.mkt-set-icon-slot {
  flex-shrink: 0;
  width: 18px;
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.mkt-set-icon {
  width: 18px;
  height: 18px;
  object-fit: contain;
}

.mkt-buy-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 0 16px;
  border-radius: 6px;
  background: var(--accent-amber, #f59e0b);
  color: #0c0a09;
  font-weight: 600;
  font-size: 14px;
  text-decoration: none;
}
/* One loud CTA per page (ahg-5iqwg): the page-foot "shop everything named
   this set" fallback wears this instead of the solid fill whenever a
   sharper, single-product hero CTA already exists above it -- same
   amber-tint treatment as .mkt-sealed-guide-buy, so it still reads as
   "you can buy here" without competing with the one loud button. */
.mkt-buy-btn--quiet {
  background: var(--amber-tint);
  border: 1px solid var(--amber-tint-border);
  color: var(--accent-amber-hover, var(--accent-amber));
  font-weight: 600;
}
.mkt-buy-btn--quiet:hover { background: var(--accent-amber); border-color: var(--accent-amber); color: #0c0a09; }

.mkt-sealed-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px 14px;
  margin: 0 0 20px;
}
.mkt-sealed-cta .mkt-buy-btn {
  max-width: 100%;
  padding: 8px 16px;
  line-height: 1.25;
}
.mkt-affiliate-note {
  flex-basis: 100%;
  margin: 0;
  font-size: var(--fs-label);
  color: var(--text-muted);
}

/* Sealed alternatives: product identity, market reference, and shopping action. */
.mkt-sealed-guide { margin: 0 0 24px; }
.mkt-sealed-guide-title {
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 18px;
  font-weight: 600;
  color: var(--text-primary);
  margin: 0 0 12px;
}
.mkt-sealed-guide-title-logo { height: 28px; max-width: 140px; object-fit: contain; }
.mkt-sealed-guide-table td:first-child {
  max-width: 320px;
  white-space: normal;
}
.mkt-sealed-guide-table td:last-child { text-align: right; }
.mkt-sealed-guide-link {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--text-primary);
  text-decoration: none;
}
/* .mkt-table a's own border-bottom (higher specificity: class + tag) would
   otherwise underline the whole flex row -- thumb included, not just the
   name -- stacking with .mkt-sealed-guide-name's own underline below into a
   double line. Matched specificity + later in the file wins outright. */
.mkt-sealed-guide-table a.mkt-sealed-guide-link { border-bottom: 0; }
.mkt-sealed-guide-name { border-bottom: 1px dotted var(--text-muted); }
.mkt-sealed-guide-link:hover .mkt-sealed-guide-name { border-bottom-color: var(--text-secondary); }
.mkt-sealed-guide-thumb {
  flex: 0 0 auto;
  width: 40px;
  aspect-ratio: 1;
  object-fit: contain;
  background: var(--bg-secondary);
  border-radius: var(--radius-md, 6px);
}
.mkt-sealed-guide-thumb--empty { border: 1px solid var(--border-subtle); }
/* Type badge doubles as the filter control when a set mixes multiple kinds
   (market_explorer.js#initSealedGuideKindFilter) -- a real <button>, so it
   needs its own reset, a visible hover cue, and a pressed/active state.
   Single-kind sets keep the plain, non-interactive <span> version below. */
.mkt-sealed-guide-kind {
  display: inline-block;
  padding: 2px 8px;
  border: 1px solid transparent;
  border-radius: var(--radius-full);
  background: var(--bg-secondary);
  color: var(--text-secondary);
  font: inherit;
  font-size: var(--fs-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
}
button.mkt-sealed-guide-kind {
  cursor: pointer;
  min-height: 28px;
}
button.mkt-sealed-guide-kind:hover { border-color: var(--amber-tint-border); color: var(--accent-amber); }
button.mkt-sealed-guide-kind[aria-pressed="true"] {
  background: var(--amber-tint);
  border-color: var(--amber-tint-border);
  color: var(--accent-amber-hover, var(--accent-amber));
}
.mkt-sealed-guide-filter-hint {
  margin: 0 0 10px;
  font-size: 13px;
  color: var(--text-muted);
}
.mkt-sealed-guide-filter-empty {
  margin: 12px 0 0;
  font-size: 14px;
  color: var(--text-muted);
}
.mkt-sealed-guide-asof {
  display: block;
  font-size: var(--fs-label);
  color: var(--text-muted);
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
}
/* Shop button: amber-tinted fill + border so it reads as the row's action,
   not a third badge indistinguishable from Type -- solid amber (like the
   page's primary .mkt-buy-btn) would be too loud repeated across a whole
   column of rows. */
.mkt-sealed-guide-buy {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: 44px;
  padding: 0 14px;
  border: 1px solid var(--amber-tint-border);
  border-radius: var(--radius-md);
  background: var(--amber-tint);
  color: var(--accent-amber-hover, var(--accent-amber));
  font-size: 14px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
.mkt-sealed-guide-buy:hover { background: var(--accent-amber); border-color: var(--accent-amber); color: #0c0a09; }
.mkt-table .mkt-sealed-guide-buy { border-bottom: 0; }
.mkt-sealed-guide-note {
  margin: 10px 0 0;
  font-size: 14px;
  color: var(--text-muted);
}
.mkt-sealed-guide .mkt-affiliate-note { margin-top: 10px; }
.mkt-sealed-guide-mobile-label { display: none; }

@media (max-width: 640px) {
  .mkt-sealed-guide-table,
  .mkt-sealed-guide-table tbody { display: block; min-width: 0; }
  .mkt-sealed-guide-table thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }
  .mkt-sealed-guide-table tbody tr {
    display: grid;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    padding: 12px;
    margin-bottom: 12px;
    background: var(--zebra);
    border-radius: var(--radius-md);
  }
  .mkt-sealed-guide-table td { padding: 4px 0; }
  .mkt-sealed-guide-table td:first-child,
  .mkt-sealed-guide-table td:last-child { grid-column: 1 / -1; max-width: none; }
  .mkt-sealed-guide-link { display: flex; align-items: center; min-height: 44px; }
  .mkt-sealed-guide-thumb { width: 44px; }
  .mkt-sealed-guide-kind { font-size: 14px; letter-spacing: 0; text-transform: none; }
  .mkt-sealed-guide-asof { font-size: 14px; }
  .mkt-sealed-guide-mobile-label { display: block; font: 14px var(--font-sans); color: var(--text-muted); }
  .mkt-sealed-guide-buy { display: flex; white-space: normal; text-align: center; }
}

/* Above-the-fold hot-sealed-products carousel (ahg): a paged viewport
   (market_explorer.js initHotSealedCarousel) with arrow + dot controls,
   rather than a bare scroll rail -- a full MAX_ENTRIES-sized set dumped
   into one long strip read as overwhelming, not inviting. Scroll-snap
   does the actual paging; JS only drives the arrows and the dot state,
   so the carousel still works (as a plain scroll rail) with JS disabled. */
.mkt-hot-sealed { margin: 0 0 20px; }
.mkt-hot-sealed-viewport {
  overflow-x: auto;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.mkt-hot-sealed-viewport::-webkit-scrollbar { display: none; }
.mkt-hot-sealed-list {
  display: flex;
  gap: 12px;
  list-style: none;
  margin: 0;
  padding: 0 0 2px;
}
.mkt-hot-sealed-item {
  flex: 0 0 auto;
  width: calc(16.666% - 10px);
  min-width: 108px;
  scroll-snap-align: start;
}
.mkt-hot-sealed-link {
  display: flex;
  flex-direction: column;
  gap: 4px;
  height: 100%;
  padding: 8px;
  background: var(--bg-secondary);
  border-radius: var(--radius-lg, 10px);
  text-decoration: none;
  color: inherit;
}
.mkt-hot-sealed-link:hover { background: var(--s2); }
.mkt-hot-sealed-thumb {
  width: 100%;
  aspect-ratio: 1;
  object-fit: contain;
  border-radius: var(--radius-md, 6px);
  background: var(--bg-primary);
}
.mkt-hot-sealed-thumb--empty { border: 1px solid var(--border-subtle); }
.mkt-hot-sealed-name {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.25;
  color: var(--text-primary);
}
.mkt-hot-sealed-kind {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--text-muted);
}
.mkt-hot-sealed-price {
  margin-top: auto;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-secondary);
}
/* One centered control cluster (dots, then arrows) below the whole
   carousel -- not spread edge-to-edge across it, which read as two
   unrelated, barely-clickable floating controls. */
.mkt-hot-sealed-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin-top: 10px;
}
.mkt-hot-sealed-dots { display: flex; align-items: center; gap: 6px; }
.mkt-hot-sealed-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--border-subtle);
  transition: width 150ms ease-in-out, border-radius 150ms ease-in-out, background 150ms ease-in-out;
}
.mkt-hot-sealed-dot.is-active { width: 16px; border-radius: 3px; background: var(--accent-amber); }
.mkt-hot-sealed-arrows { display: flex; gap: 8px; }
.mkt-hot-sealed-arrow {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--border-subtle);
  border-radius: 50%;
  background: var(--bg-secondary);
  color: var(--text-primary);
  cursor: pointer;
}
.mkt-hot-sealed-arrow:hover { border-color: var(--accent-amber); color: var(--accent-amber); background: var(--amber-tint); }
.mkt-hot-sealed-arrow:disabled { opacity: 0.35; cursor: default; }
.mkt-hot-sealed-arrow:disabled:hover { border-color: var(--border-subtle); color: var(--text-primary); background: var(--bg-secondary); }

@media (max-width: 900px) {
  .mkt-hot-sealed-item { width: calc(33.333% - 8px); }
}
@media (max-width: 480px) {
  .mkt-hot-sealed-item { width: 78%; }
  .mkt-hot-sealed-dots { display: none; }
}

/* Above-the-fold chase-card carousel (ahg-mdtjx): same shape as
   .mkt-hot-sealed above (market_explorer.js initChaseCardsCarousel is a
   near-identical wiring function, kept separate rather than shared since
   the two carousels' item aspect ratios differ) -- card art is 5:7
   (matches .mkt-roster-thumb), not the 1:1 box-art ratio sealed product
   thumbnails use. */
.mkt-chase-cards { margin: 0 0 20px; }
.mkt-chase-cards-title {
  font-size: 16px;
  font-weight: 600;
  color: var(--text-primary);
  margin: 0 0 10px;
}
.mkt-chase-cards-viewport {
  overflow-x: auto;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.mkt-chase-cards-viewport::-webkit-scrollbar { display: none; }
.mkt-chase-cards-list {
  display: flex;
  gap: 12px;
  list-style: none;
  margin: 0;
  padding: 0 0 2px;
}
.mkt-chase-cards-item {
  flex: 0 0 auto;
  width: calc(16.666% - 10px);
  min-width: 108px;
  max-width: 150px; /* source art is a 400px-wide rendition (ScryfallCard.display_image_url
                        default) -- past ~150px CSS width a retina display upscales it past
                        its native resolution and it goes soft; cap the box instead of
                        fetching a bigger image nothing else in the app requests. */
  scroll-snap-align: start;
}
.mkt-chase-cards-link {
  display: flex;
  flex-direction: column;
  gap: 4px;
  height: 100%;
  padding: 8px;
  background: var(--bg-secondary);
  border-radius: var(--radius-lg, 10px);
  text-decoration: none;
  color: inherit;
}
.mkt-chase-cards-link:hover { background: var(--s2); }
.mkt-chase-cards-thumb {
  width: 100%;
  aspect-ratio: 5 / 7;
  object-fit: cover;
  border-radius: var(--radius-md, 6px);
  background: var(--bg-primary);
}
.mkt-chase-cards-thumb--empty { border: 1px solid var(--border-subtle); }
.mkt-chase-cards-name {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-size: 12px;
  font-weight: 600;
  line-height: 1.25;
  color: var(--text-primary);
}
.mkt-chase-cards-price {
  margin-top: auto;
  font-family: var(--font-mono);
  font-size: 12px;
  color: var(--text-secondary);
}
.mkt-chase-cards-controls {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 14px;
  margin-top: 10px;
}
.mkt-chase-cards-dots { display: flex; align-items: center; gap: 6px; }
.mkt-chase-cards-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--border-subtle);
  transition: width 150ms ease-in-out, border-radius 150ms ease-in-out, background 150ms ease-in-out;
}
.mkt-chase-cards-dot.is-active { width: 16px; border-radius: 3px; background: var(--accent-amber); }
.mkt-chase-cards-arrows { display: flex; gap: 8px; }
.mkt-chase-cards-arrow {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 1px solid var(--border-subtle);
  border-radius: 50%;
  background: var(--bg-secondary);
  color: var(--text-primary);
  cursor: pointer;
}
.mkt-chase-cards-arrow:hover { border-color: var(--accent-amber); color: var(--accent-amber); background: var(--amber-tint); }
.mkt-chase-cards-arrow:disabled { opacity: 0.35; cursor: default; }
.mkt-chase-cards-arrow:disabled:hover { border-color: var(--border-subtle); color: var(--text-primary); background: var(--bg-secondary); }

@media (max-width: 900px) {
  .mkt-chase-cards-item { width: calc(33.333% - 8px); }
}
@media (max-width: 480px) {
  .mkt-chase-cards-item { width: 42%; }
  .mkt-chase-cards-dots { display: none; }
}

.mkt-sealed-cta-ref {
  flex-basis: 100%;
  display: flex;
  align-items: baseline;
  gap: 8px;
  margin: 0;
}
.mkt-sealed-cta-ref-label {
  font-size: var(--fs-label);
  text-transform: uppercase;
  letter-spacing: var(--tracking-label);
  color: var(--text-muted);
}
.mkt-sealed-cta-ref-price {
  font-family: var(--font-mono);
  font-size: 18px;
  color: var(--text-primary);
}
.mkt-sealed-cta-asof {
  font-size: 14px;
  color: var(--text-muted);
}

/* Honest deal frame (ahg-lixsd) -- quiet text, not a second CTA. */
.mkt-cheapest-entry { margin: 0 0 12px; font-size: 14px; color: var(--text-secondary); }
.mkt-cheapest-entry-link { color: var(--accent-amber-hover, var(--accent-amber)); text-decoration: none; border-bottom: 1px dotted currentColor; }
.mkt-cheapest-entry-link:hover { border-bottom-style: solid; }

/* "Is this box worth breaking" (ahg-jgsyg) -- a quiet researcher-facing fact,
   not a second CTA, so it sits as plain text under the hero rather than
   another bordered panel competing with .mkt-sealed-cta. */
.mkt-box-economics {
  margin: 0 0 20px;
  font-size: 14px;
}
.mkt-box-economics-value { color: var(--text-primary); font-weight: 600; }
.mkt-box-economics-note { display: block; margin-top: 2px; color: var(--text-muted); }

/* "Trending in this set" (ahg-1zmvm) -- a short researcher-facing list,
   gainers and losers mixed by |delta| rather than the set-level hub's two
   full gainers/losers columns (market/movers.html.erb), a different scale
   of question than this card-level aside. */
.mkt-in-set-movers { margin: 24px 0; }
.mkt-in-set-movers-list { list-style: none; margin: 12px 0 0; padding: 0; }
.mkt-in-set-movers-item { border-bottom: 1px solid var(--border-subtle); }
.mkt-in-set-movers-item:last-child { border-bottom: 0; }
.mkt-in-set-movers-link {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  padding: 8px 0;
  color: inherit;
  text-decoration: none;
}
.mkt-in-set-movers-link:hover .mkt-in-set-movers-name { color: var(--accent-amber); }
.mkt-in-set-movers-name {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--text-primary);
  font-size: 14px;
}
.mkt-in-set-movers-price {
  flex: 0 0 auto;
  font-family: var(--font-mono);
  font-size: 14px;
  color: var(--text-secondary);
}
.mkt-in-set-movers-item .mkt-num { flex: 0 0 auto; min-width: 60px; text-align: right; font-family: var(--font-mono); font-size: 14px; }

/* /market/:game/:set card roster (ahg-i62pa) -- the forward door into each
   card's own /cards/:product_id page. A price-guide table (ahg-i3k9a),
   same shape as _sealed_buying_guide's .mkt-sealed-guide-table: identity
   cell (thumb + name), a couple of catalog facts, market price, buy. */
.mkt-roster { margin: 24px 0 8px; }
.mkt-roster-hd {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 12px;
}
.mkt-roster-title {
  font-size: 18px;
  font-weight: 600;
  color: var(--text-primary);
  margin: 0;
}
.mkt-roster-note { font-size: 13px; color: var(--text-muted); }
.mkt-roster-table td:first-child { max-width: 320px; white-space: normal; }
.mkt-roster-link {
  display: flex;
  align-items: center;
  gap: 10px;
  min-height: 44px;
  color: var(--text-primary);
  text-decoration: none;
}
.mkt-roster-link:hover .mkt-roster-name { text-decoration: underline; }
.mkt-roster-thumb {
  flex: 0 0 auto;
  width: 32px;
  aspect-ratio: 5 / 7;
  object-fit: cover;
  border-radius: var(--radius-sm, 4px);
  background: var(--bg-secondary);
}
.mkt-roster-thumb--empty { border: 1px solid var(--border-subtle); }
.mkt-roster-name { font-size: 14px; line-height: 1.25; }
.mkt-roster-buy {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 4px;
  min-height: 36px;
  padding: 0 12px;
  border: 1px solid var(--amber-tint-border);
  border-radius: var(--radius-md);
  background: var(--amber-tint);
  color: var(--accent-amber-hover, var(--accent-amber));
  font-size: 13px;
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
}
.mkt-roster-buy:hover { background: var(--accent-amber); border-color: var(--accent-amber); color: #0c0a09; }
.mkt-table .mkt-roster-buy { border-bottom: 0; }
.mkt-roster-mobile-label { display: none; }

@media (max-width: 640px) {
  .mkt-roster-table,
  .mkt-roster-table tbody { display: block; min-width: 0; }
  .mkt-roster-table thead {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
  }
  .mkt-roster-table tbody tr {
    display: grid;
    /* 3 middle fields (#, Rarity, Market Price), unlike the sealed guide
       table's 2 -- a 2-col grid left Market Price wrapping alone with dead
       space beside it, so this gets its own 3-col template. */
    grid-template-columns: repeat(3, minmax(0, 1fr));
    padding: 12px;
    margin-bottom: 12px;
    background: var(--zebra);
    border-radius: var(--radius-md);
  }
  .mkt-roster-table td { padding: 4px 0; }
  .mkt-roster-table td:first-child,
  .mkt-roster-table td:last-child { grid-column: 1 / -1; max-width: none; }
  .mkt-roster-thumb { width: 44px; }
  .mkt-roster-mobile-label { display: block; font: 14px var(--font-sans); color: var(--text-muted); }
  .mkt-roster-buy { display: flex; }
}

/* /market/:game/:set "Compare to" picker (ahg-85hzm follow-up) -- same
   search/dropdown/pick-row shape as /market/compare's rail (.rail-search,
   .mkt-compare-search-input, .mkt-compare-results, .pick/.pick-row are
   already unscoped or reused below), just a smaller inline block instead
   of a full rail column. */
.mkt-compare-picker { margin: 20px 0 16px; max-width: 420px; }
.mkt-compare-label {
  display: block;
  font-size: 14px;
  color: var(--text-secondary);
  margin-bottom: 6px;
}
.mkt-compare-picker .rail-search { position: relative; margin-bottom: 8px; }
/* Same 3-column shape as .mkt-page.mkt-compare .pick-row (name + remove,
   no rank column -- this is "what you're comparing to", not a ranked
   leaderboard) without requiring the whole page to carry a .mkt-compare
   class it doesn't otherwise need. Every override below is qualified
   through the markup's own .pick wrapper (".mkt-compare-picker .pick ...")
   -- one class more than the generic .mkt-page .pick-row/.pick-name rules
   further down this file, so this widget's 3-column shape always wins on
   specificity instead of on source order (a same-specificity tie the
   generic rule was winning, squashing the name into a 22px column and
   leaking that rule's drill-in "›" onto a widget that isn't a leaderboard
   row). */
.mkt-compare-picker .pick { border-top: 0; }
.mkt-compare-picker .pick .pick-row {
  grid-template-columns: 16px 1fr auto;
  cursor: default;
}
.mkt-compare-picker .pick a.pick-name {
  align-self: stretch;
  display: flex;
  align-items: center;
  min-width: 0;
  min-height: 44px;
  color: var(--text-primary);
  text-decoration: none;
  cursor: pointer;
}
.mkt-compare-picker .pick a.pick-name::after { content: none; }
.mkt-compare-picker .pick a.pick-name:hover { text-decoration: underline; }
.mkt-compare-picker .pick .pick-remove {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  color: var(--text-muted);
  text-decoration: none;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.mkt-compare-picker .pick .pick-remove:hover { color: var(--text-primary); }

@media (max-width: 768px) {
  .mkt-page { padding: 20px 16px 48px; }
}

@media (max-width: 480px) {
  .mkt-page .stats { gap: 16px 20px; }
}

/* ---- P2 split-brain overview (ahg-3f3pl): .bar / .seg / .pick / .panel /
   .chart / .legend / .note / .split, lifted from the approved variant-c
   wireframe (~/.gstack/projects/tryhoard-hoard/designs/market-leaderboard-
   20260804/_shared.css + variant-c-splitbrain.html). Token names translated
   to this app's existing :root custom properties (app/views/layouts/
   application.html.erb) instead of redefining duplicate tokens under the
   wireframe's names -- --surface-1/2/3 == --bg-secondary/--s2/--s3,
   --hairline == --border-subtle, --accent == --accent-amber, --success/
   error/info == --accent-green/rose/blue: every one an exact hex/rgba
   match, confirmed against DESIGN.md before porting. ahg-85hzm moved
   /market/:game and /market/:game/:set onto this same .bar/.panel/.chart
   family (they used to run the P4 .mkt-card/.mkt-chart-wrap/.mkt-stats/
   .mkt-grid look with a fixed-viewBox chart) -- only the .mkt-empty/
   .mkt-subtitle/.mkt-table/.mkt-buy-btn/.mkt-compare-form rules above are
   still theirs, none of it a chart. Scoped under .mkt-page so nothing here
   leaks past the /market page family (market.css only loads there, via
   head_extra). */

/* Minimal cross-page header (ahg-lhae1, market/_header partial): logo + a
   way back to the dashboard for signed-in users who land on /market from
   the app. Logged-out visitors see only the logo -- shared/_logo is already
   auth-aware and routes to '/' for them, so it doubles as their way home.
   Sits above .bar so it never competes with the page's own toolbar. */
.mkt-page .mkt-topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px 16px;
  margin-bottom: 24px;
}
.mkt-page .mkt-topbar-left {
  display: flex;
  align-items: center;
  gap: 16px;
}
/* Both the logo anchor (rendered by shared/_logo, no class of its own) and
   the back link are direct children -- one rule gives both a real 44px tap
   target without reaching into the shared partial. */
.mkt-page .mkt-topbar-left > a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.mkt-page .mkt-topbar-back {
  font-size: 14px;
  color: var(--text-secondary);
  text-decoration: none;
  border-bottom: 1px dotted var(--text-muted);
}
.mkt-page .mkt-topbar-back:hover { color: var(--text-primary); }

/* Header game switcher (ahg-ftqi4 follow-up) -- native <details> disclosure. */
.mkt-page .mkt-gameswitch { position: relative; }
.mkt-page .mkt-gameswitch-summary {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 0 12px;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  background: var(--bg-secondary);
  color: var(--text-primary);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  list-style: none;
  user-select: none;
  white-space: nowrap;
}
.mkt-page .mkt-gameswitch-summary::-webkit-details-marker { display: none; }
.mkt-page .mkt-gameswitch-summary:hover { border-color: var(--accent-amber); }
.mkt-page .mkt-gameswitch-summary:focus-visible { outline: 2px solid var(--accent-amber); outline-offset: 1px; }
.mkt-page .mkt-gameswitch-caret { flex-shrink: 0; color: var(--text-muted); transition: transform 120ms ease; }
.mkt-page .mkt-gameswitch[open] .mkt-gameswitch-caret { transform: rotate(180deg); }
.mkt-page .mkt-gameswitch-menu {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 60;
  min-width: 220px;
  padding: 6px;
  background: var(--bg-secondary);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-overlay);
}
/* Type-to-filter field (ahg-frktq): pinned above the list, which owns the
   scroll so the field stays put while the games scroll under it. 16px font
   keeps iOS from zooming on focus. */
.mkt-page .mkt-gameswitch-search {
  width: 100%;
  box-sizing: border-box;
  margin-bottom: 6px;
  padding: 8px 10px;
  font-size: 16px;
  font-family: var(--font-sans);
  color: var(--text-primary);
  background: var(--bg-primary);
  border: 1px solid var(--border-subtle);
  border-radius: 6px;
}
.mkt-page .mkt-gameswitch-search:focus { outline: none; border-color: var(--accent-amber); }
.mkt-page .mkt-gameswitch-list { max-height: 52vh; overflow-y: auto; }
.mkt-page .mkt-gameswitch-empty { padding: 9px 10px; font-size: 13px; color: var(--text-muted); }
.mkt-page .mkt-gameswitch-item {
  display: block;
  padding: 9px 10px;
  border-radius: 6px;
  font-size: 14px;
  color: var(--text-primary);
  text-decoration: none;
  white-space: nowrap;
}
.mkt-page .mkt-gameswitch-item:hover { background: var(--bg-glass, rgba(255,255,255,0.04)); }
.mkt-page .mkt-gameswitch-item.is-current { color: var(--accent-amber); font-weight: 700; }

/* Right-hand cluster (Condition guide / Compare sets / theme toggle):
   pushed to the far edge by the parent's justify-content: space-between --
   one visual cluster ("utility nav") distinct from the "way back" links on
   the left, instead of 5 items packed into one row. */
.mkt-page .mkt-topbar-nav {
  display: flex;
  align-items: center;
  gap: 20px;
}
.mkt-page .mkt-topbar-link {
  font-size: 14px;
  font-weight: 600;
  color: var(--accent-amber);
  text-decoration: none;
}
.mkt-page .mkt-topbar-link:hover { color: #fbbf24; }
.mkt-page .mkt-topbar-nav .theme-toggle,
.mkt-page .mkt-topbar-nav #mkt-search-trigger { flex-shrink: 0; }

@media (max-width: 768px) {
  .mkt-page .mkt-topbar { margin-bottom: 14px; row-gap: 10px; }
}

.mkt-page .bar {
  display: flex;
  align-items: center;
  gap: 14px 24px;
  flex-wrap: wrap;
  min-height: 44px;
  padding-bottom: 14px;
  margin-bottom: 20px;
  border-bottom: 1px solid var(--border);
}
/* Set shortcut bar (ahg-ftqi4) -- ports the /collect set command bar
   (collect/index.html.erb .qa-selected-set-bar + .qa-set-neighbors) into the
   market page family so set-hopping looks the same in both contexts. Neighbor
   set icons, the current set, a "Change set" search picker, and a card-name
   filter over the roster. Filled by market_explorer.js#initSetNav. */
.mkt-page .mkt-setnav {
  display: flex;
  align-items: center;
  gap: 12px 16px;
  flex-wrap: wrap;
  margin: 0 0 20px;
}
.mkt-page .mkt-setnav-strip {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1 1 auto;
  min-width: 0;
  position: relative;
}
.mkt-page .mkt-setnav-neighbors {
  display: flex;
  align-items: center;
  gap: 4px;
  flex-shrink: 0;
}
.mkt-page .mkt-setnav-neighbors:empty { display: none; }
.mkt-page .mkt-setnav-neighbor {
  height: 30px;
  width: 30px;
  border-radius: 6px;
  border: 1.5px solid var(--border-subtle);
  background: transparent;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 2px;
  flex-shrink: 0;
  transition: border-color 100ms, transform 80ms;
}
.mkt-page .mkt-setnav-neighbor:hover { border-color: var(--accent-amber); transform: scale(1.1); }
.mkt-page .mkt-setnav-neighbor:focus-visible { outline: 2px solid var(--accent-amber); outline-offset: 1px; }
.mkt-page .mkt-setnav-neighbor img { width: 20px; height: 20px; object-fit: contain; }

/* data-tooltip: a fast, styled replacement for the native title= tooltip on
   the neighbor set icons -- same flat/bordered look as .chart-tip, since
   both are transient overlay layers (physics rule 1 allows shadow there).
   Generic on purpose (any .mkt-page element can opt in with data-tooltip),
   but only the setnav neighbors use it today. */
.mkt-page [data-tooltip] { position: relative; }
.mkt-page [data-tooltip]::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  max-width: 200px;
  padding: 6px 10px;
  background: var(--bg-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.28);
  color: var(--text-primary);
  font-size: 14px;
  font-family: var(--font-sans);
  white-space: normal;
  text-align: center;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 100ms;
  z-index: 20;
}
/* Centered on the button, a wide label overflows the viewport off the
   leftmost "earlier set" icon (it sits right at the page's 32px gutter,
   with no room to its left) -- pin that one to its left edge instead of
   centering. The native title= tooltip this replaces let the browser
   handle edge collision; this is the CSS-only equivalent for the one
   button close enough to the edge to matter. */
.mkt-page [data-setnav-before] .mkt-setnav-neighbor:first-child[data-tooltip]::after {
  left: 0;
  transform: none;
  text-align: left;
}
.mkt-page [data-tooltip]:hover::after,
.mkt-page [data-tooltip]:focus-visible::after {
  opacity: 1;
  visibility: visible;
}
.mkt-page .mkt-setnav-initials {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--text-secondary);
  line-height: 1;
}
[data-theme="dark"] .mkt-page .mkt-setnav-neighbor img:not(.mkt-setnav--card) { filter: invert(0.7); }
.mkt-page .mkt-setnav-logo { height: 30px; max-width: 160px; object-fit: contain; flex-shrink: 0; }
.mkt-page .mkt-setnav-icon { width: 30px; height: 30px; object-fit: contain; flex-shrink: 0; }
[data-theme="dark"] .mkt-page .mkt-setnav-icon { filter: invert(1); }
.mkt-page .mkt-setnav-icon--placeholder {
  border-radius: 6px;
  background: var(--surface-2, var(--bg-secondary));
  border: 1px solid var(--border-subtle);
  filter: none;
}
.mkt-page .mkt-setnav-name {
  font-family: var(--font-display);
  font-size: 16px;
  font-weight: 700;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
}
.mkt-page .mkt-setnav-change {
  padding: 5px 12px;
  border: 1px solid var(--border-subtle);
  border-radius: 6px;
  background: transparent;
  color: var(--text-muted);
  font-size: 13px;
  font-family: var(--font-sans);
  cursor: pointer;
  flex-shrink: 0;
  min-height: 32px;
  transition: border-color 100ms, color 100ms;
}
.mkt-page .mkt-setnav-change:hover { border-color: var(--accent-amber); color: var(--text-primary); }
/* Change-set picker: an absolutely-positioned popover under the strip, same
   fetch-once/filter-client-side autocomplete as .mkt-compare-picker. */
.mkt-page .mkt-setnav-picker {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 40;
  width: min(340px, 90vw);
  background: var(--bg-secondary);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-overlay);
  padding: 8px;
}
.mkt-page .mkt-setnav-picker-input {
  width: 100%;
  box-sizing: border-box;
  background: var(--bg-primary);
  border: 1px solid var(--border-subtle);
  border-radius: 6px;
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: 14px;
  padding: 8px 10px;
  min-height: 40px;
}
.mkt-page .mkt-setnav-results {
  list-style: none;
  margin: 6px 0 0;
  padding: 0;
  max-height: 260px;
  overflow-y: auto;
}
.mkt-page .mkt-setnav-result {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: 0;
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: 14px;
  padding: 9px 8px;
  border-radius: 6px;
  cursor: pointer;
}
.mkt-page .mkt-setnav-result:hover { background: var(--bg-glass, rgba(255,255,255,0.04)); color: var(--accent-amber); }
.mkt-page .mkt-setnav-noresults { color: var(--text-muted); font-size: 13px; padding: 9px 8px; }
.mkt-page .mkt-setnav-filter {
  flex: 0 1 300px;
  min-width: 180px;
  background: var(--bg-primary);
  border: 1px solid var(--border-subtle);
  border-radius: 6px;
  color: var(--text-primary);
  font-family: var(--font-sans);
  font-size: 16px;
  padding: 8px 12px;
  min-height: 40px;
}
.mkt-page .mkt-setnav-filter:focus { outline: none; border-color: var(--accent-amber); }
@media (max-width: 768px) {
  .mkt-page .mkt-setnav { gap: 10px; margin-bottom: 14px; }
  .mkt-page .mkt-setnav-filter { flex-basis: 100%; min-height: 44px; }
  /* Hard 44px touch targets on the interactive controls (mobile minimum). */
  .mkt-page .mkt-setnav-change { min-height: 44px; padding: 5px 14px; }
  .mkt-page .mkt-setnav-neighbor { height: 38px; width: 38px; }
  .mkt-page .mkt-setnav-picker-input { min-height: 44px; }
}
@media (max-width: 560px) {
  /* Neighbour icons are a desktop quick-hop nicety; on a phone six of them plus
     the set name and "Change set" overflow the row. Hide them and let "Change
     set" carry set switching (the picker lists every set in the game). */
  .mkt-page .mkt-setnav-neighbors { display: none; }
}

.mkt-page .bar-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 21px;
  line-height: 1.1;
  color: var(--text-primary);
  margin: 0;
  letter-spacing: -0.01em;
  /* No nowrap here (ahg-85hzm): overview/compare's short "Market" / "Market
     / Compare" titles never wrapped anyway, but the game/set drilldown
     breadcrumb ("Market / <game> / <set>") can run long -- a set name alone
     can exceed a 375px viewport, and nowrap would force horizontal overflow
     on the whole page instead of just wrapping the heading. */
  overflow-wrap: break-word;
}
/* ahg-0yim1.3: preorder/new_release descriptor suffix inside the H1 -- plain
   muted text, not bold like the heading itself (signal is text color only,
   never a badge/pill/chrome around it). */
.mkt-page .bar-lifecycle {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 15px;
  color: var(--text-secondary);
}
/* Breadcrumb drill-up links ("Market / Pokemon / <set>" -- Market and
   Pokemon are real links, the final segment is the page you're on and
   stays plain). Without this they inherited .bar-title's bold heading
   color with no hover cue, so a clickable segment looked identical to the
   inert one. Same dotted-underline/muted-to-primary treatment as
   .mkt-topbar-back and .bar-link, so every "step up a level" affordance in
   this page family reads the same way. */
.mkt-page .bar-up {
  color: var(--text-secondary);
  text-decoration: none;
  border-bottom: 1px dotted var(--text-muted);
}
.mkt-page .bar-up:hover { color: var(--text-primary); border-bottom-color: var(--text-secondary); }
.mkt-page .bar-asof {
  /* Prose, not tabular data -- no font-family override (was --font-mono,
     which put this "As of ..." sentence in a different font than every
     other "as of <date>" instance on the page, e.g. .mkt-sealed-cta-asof). */
  order: 9;
  margin-left: auto;
  font-size: 14px;
  color: var(--text-muted);
  white-space: nowrap;
}
/* ahg-pebhh: set release-date indicator, sits just after "As of ...". Layout
   (margin, not a "·" glyph) separates it -- middot separators are banned. */
.mkt-page .bar-release {
  order: 10;
  margin-left: 8px;
  font-size: 14px;
  color: var(--text-muted);
  white-space: nowrap;
}
.mkt-page .bar-break { display: none; }

/* Discoverability link from the overview to /market/compare (ahg-eqatd) --
   same minimal underline treatment as .mkt-breadcrumb a, but sized as a
   real 44px tap target for mobile (project rule: no sub-44px targets). */
.mkt-page .bar-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: 14px;
  color: var(--text-secondary);
  text-decoration: none;
  border-bottom: 1px dotted var(--text-muted);
}
.mkt-page .bar-link:hover { color: var(--text-primary); }

/* segmented controls -- shared by the toolbar's track switch (server-round-
   trip links) and the chart panel's range switch (buttons, static in P2). */
.mkt-page .seg {
  display: inline-flex;
  background: var(--bg-secondary);
  border-radius: var(--radius-md);
  padding: 3px;
  gap: 2px;
}
.mkt-page .seg a,
.mkt-page .seg button {
  appearance: none;
  border: 0;
  background: transparent;
  color: var(--text-secondary);
  font: inherit;
  font-size: 14px;
  padding: 0 16px;
  min-height: 44px; /* DESIGN.md tap-target floor, not 38 */
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-sm);
  cursor: pointer;
  text-decoration: none;
  transition: background 100ms ease-in-out, color 100ms ease-in-out;
}
.mkt-page .seg a:hover,
.mkt-page .seg button:hover { background: var(--s2); }
.mkt-page .seg a[aria-pressed='true'],
.mkt-page .seg button[aria-pressed='true'] {
  background: var(--accent-amber);
  color: #1c1310;
  font-weight: 600;
}
/* Range control (30d/90d/1y): an amber-TINT active state -- a deliberate brand
   touch on the chart panel (DESIGN.md: segmented controls carry amber), kept
   quieter than the primary track control's full amber fill so amber stays
   rationed. Zero amber on the panel was the design-review deviation. */
.mkt-page .seg--quiet a[aria-pressed='true'],
.mkt-page .seg--quiet button[aria-pressed='true'] {
  background: var(--amber-tint, rgba(245, 158, 11, 0.14));
  color: var(--accent-amber);
  font-weight: 600;
}
.mkt-page .bar .seg { padding: 2px; }
.mkt-page .bar .seg a,
.mkt-page .bar .seg button { min-height: 40px; padding: 0 14px; }

/* master-detail split: leaderboard rail + chart panel */
.mkt-page .split {
  display: grid;
  grid-template-columns: 380px 1fr;
  gap: 24px;
  align-items: start;
  margin-bottom: 24px;
}

/* left rail: leaderboard doubles as the chart series picker (toggle wiring
   is Phase 3 -- P2 renders the correct static aria-pressed/swatch state). */
/* Frame the rail as a card matching the chart .panel, so the leaderboard and
   the chart read as one matched pair instead of two unrelated components
   (design review: rail had no container, just floating hairlines). */
.mkt-page .pick {
  background: var(--bg-secondary);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.mkt-page .pick-row {
  display: grid;
  /* minmax(0,1fr) on the name lets it shrink so the value/delta column always
     keeps its width -- a plain 1fr has a min-content floor that pushed a long
     "$398,995.34" past the framed rail's clipped edge on a narrow column. */
  grid-template-columns: 44px 22px minmax(0, 1fr) auto;
  align-items: center;
  column-gap: 10px;
  width: 100%;
  min-height: 52px;
  padding: 8px 10px;
  background: transparent;
  border: 0;
  color: inherit;
  font: inherit;
  text-align: left;
  transition: background 100ms ease-in-out;
}
.mkt-page .pick-row:nth-child(even) { background: var(--zebra); }
.mkt-page .pick-row:hover { background: var(--bg-secondary); }
.mkt-page .pick-row[aria-pressed='true'] { background: var(--s2); }
.mkt-page .pick-row--empty { color: var(--text-muted); }

/* Chart on/off toggle -- the checkbox-style control (the swatch reads as a
   checked/unchecked box). 44px hit target per DESIGN.md; the 12px swatch
   stays centered inside it. */
.mkt-page .pick-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  min-height: 44px;
  padding: 0;
  background: transparent;
  border: 0;
  cursor: pointer;
  color: inherit;
}
.mkt-page .pick-toggle:disabled { cursor: default; }
.mkt-page .pick-toggle:disabled .swatch { opacity: 0.5; }

/* Drill-in name link: goes to the game's set list (/market/:game). The
   trailing chevron is a persistent "opens a page" affordance (not hover-only)
   -- mirrors the icon-presence-signals-navigation rule in DESIGN.md. */
/* The name is the drill-in tap target -- stretch it to the full row height so
   the hit area clears the 44px minimum (the row is ~52px; a baseline inline
   <a> was only ~24px tall, failing the mobile tap-target rule). */
.mkt-page a.pick-name {
  display: flex;
  align-items: center;
  align-self: stretch;
  min-width: 0; /* allow the name column to shrink so the value never clips */
  min-height: 44px;
  text-decoration: none;
  cursor: pointer;
}
.mkt-page a.pick-name::after {
  content: "\203A"; /* › -- single right angle quote */
  margin-left: 5px;
  color: var(--text-muted);
  font-weight: 400;
}
.mkt-page a.pick-name:hover { text-decoration: underline; }
.mkt-page .pick-row--empty a.pick-name { color: var(--text-muted); }
/* Keyboard focus lands on the two real controls (toggle + drill-in name), not
   the row -- give each its own amber focus ring (was outline:none territory). */
.mkt-page .pick-toggle:focus-visible,
.mkt-page a.pick-name:focus-visible {
  outline: 2px solid var(--accent-amber);
  outline-offset: -2px;
  border-radius: var(--radius-sm);
}
.mkt-page .swatch {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  border: 1.5px solid var(--text-muted);
  background: transparent;
}
/* Categorical series palette -- the dataviz reference order, validated for
   adjacent-pair CVD + normal-vision separation on BOTH --bg-secondary
   surfaces (#1c1917 dark / #f5f5f4 light) in both modes. Dark steps are the
   default (dark is the app default); [data-theme="light"] swaps in the light
   steps. Kept as custom properties so the chart lines (.mkt-s0..7 -> stroke
   currentColor), legend dots, crosshair dots, and rail swatches all resolve
   ONE source and theme-swap together. App chrome (range tabs, buttons) stays
   amber; only the data series wear these hues. The light-mode <3:1 contrast
   WARN on some steps is relieved by the always-present legend labels +
   crosshair tooltip -- identity is never color-alone. */
.mkt-page {
  --series-0: #3987e5; --series-1: #d95926; --series-2: #199e70; --series-3: #c98500;
  --series-4: #d55181; --series-5: #008300; --series-6: #9085e9; --series-7: #e66767;
}
[data-theme="light"] .mkt-page {
  --series-0: #2a78d6; --series-1: #eb6834; --series-2: #1baf7a; --series-3: #eda100;
  --series-4: #e87ba4; --series-5: #008300; --series-6: #4a3aa7; --series-7: #e34948;
}
.mkt-page .mkt-s0 { color: var(--series-0); }
.mkt-page .mkt-s1 { color: var(--series-1); }
.mkt-page .mkt-s2 { color: var(--series-2); }
.mkt-page .mkt-s3 { color: var(--series-3); }
.mkt-page .mkt-s4 { color: var(--series-4); }
.mkt-page .mkt-s5 { color: var(--series-5); }
.mkt-page .mkt-s6 { color: var(--series-6); }
.mkt-page .mkt-s7 { color: var(--series-7); }
.mkt-page .swatch--0 { background: var(--series-0); border-color: transparent; }
.mkt-page .swatch--1 { background: var(--series-1); border-color: transparent; }
.mkt-page .swatch--2 { background: var(--series-2); border-color: transparent; }
.mkt-page .swatch--3 { background: var(--series-3); border-color: transparent; }
.mkt-page .swatch--4 { background: var(--series-4); border-color: transparent; }
.mkt-page .swatch--5 { background: var(--series-5); border-color: transparent; }
.mkt-page .swatch--6 { background: var(--series-6); border-color: transparent; }
.mkt-page .swatch--7 { background: var(--series-7); border-color: transparent; }
.mkt-page .pick-rank,
.mkt-page .pick-val,
.mkt-page .pick-d30 {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums; /* DESIGN.md: numeric cells align */
}
.mkt-page .pick-rank { color: var(--text-muted); font-size: 14px; }
.mkt-page .pick-name { color: var(--text-primary); font-size: 15px; }
/* min-height reserves room for the 2-line case (value + 30d change) so an
   insufficient-data row's single "Not enough market data" line is
   vertically centered to match instead of coming out shorter than every
   row around it (ahg-nbi70). */
.mkt-page .pick-right {
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 44px;
  text-align: right;
}
.mkt-page .pick-val,
.mkt-page .pick-d30 { display: block; font-size: 14px; }
.mkt-page .pick-empty { font-size: 14px; color: var(--text-muted); }
/* ahg-nbi70: reverted ahg-2npqv's "N% of products priced" from visible
   .pick-cov text back to hover-only (the row's title= attribute) -- an
   unexplained percentage only some rows had read as clutter, not a useful
   caveat, and was the root cause of the jagged rail this bead's min-height
   fix above addresses more directly. .pick-cov itself is gone; the game/set
   drill-down's "Tracked products... of M priced" caption (_tracked_products
   _stat.html.erb) is a separate, more contextual disclosure and is untouched. */

/* right: the chart is the point */
.mkt-page .panel {
  background: var(--bg-secondary);
  border-radius: var(--radius-lg);
  padding: 20px;
  position: relative; /* offset anchor for the absolutely-positioned .chart-tip */
}
.mkt-page .panel-hd { display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; flex-wrap: wrap; }
.mkt-page .panel-title { color: var(--text-primary); font-size: 17px; font-weight: 500; margin: 0; }
.mkt-page .panel-note { color: var(--text-muted); font-size: 14px; font-weight: 400; margin-left: 8px; }
.mkt-page .panel-sub { color: var(--text-muted); font-size: 14px; margin: 4px 0 0; }

/* Drilldown headline (/market/:game, /market/:game/:set): eyebrow + the ONE
   number that matters (index level) + its change pill, promoted into the
   card header -- amber is rationed to the active range tab below, never
   spent on the eyebrow label too. */
.mkt-page .panel-headline { min-width: 0; }
.mkt-page .panel-eyebrow {
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--text-muted);
  margin-bottom: 6px;
}
.mkt-page .panel-value-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.mkt-page .panel-value {
  /* Matches the app's one canonical "big number" spec (dashboard's
     .today-stat-card .today-stat-value) instead of running its own bigger,
     bolder size -- ahg-o6rs6. */
  font-family: var(--font-mono);
  font-size: 26px;
  font-weight: 600;
  line-height: 1.15;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.mkt-page .panel-pill {
  display: inline-flex;
  align-items: center;
  padding: 3px 10px;
  border-radius: var(--radius-lg);
  border: 1px solid transparent;
  font-family: var(--font-mono);
  font-size: 13px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.mkt-page .panel-pill.mkt-delta-pos { background: var(--green-tint); border-color: rgba(74, 222, 128, 0.28); color: var(--accent-green); }
.mkt-page .panel-pill.mkt-delta-neg { background: var(--rose-tint); border-color: rgba(248, 113, 113, 0.28); color: var(--accent-rose); }
.mkt-page .panel-pill.mkt-delta-flat { background: var(--s2); border-color: var(--border-subtle); color: var(--text-muted); }

/* Secondary metrics: one hairline-divided strip inside the card, never a
   wrapping grid of equal-weight boxes (no card chrome per item). */
.mkt-page .panel-stats {
  display: flex;
  flex-wrap: wrap;
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--border-subtle);
}
.mkt-page .panel-stat {
  flex: 1 1 0;
  min-width: 110px;
  padding: 0 18px;
}
.mkt-page .panel-stat:first-child { padding-left: 0; }
.mkt-page .panel-stat-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-muted);
  margin-bottom: 4px;
}
.mkt-page .panel-stat-value {
  font-family: var(--font-mono);
  font-size: 17px;
  font-weight: 600;
  color: var(--text-primary);
  font-variant-numeric: tabular-nums;
}
.mkt-page .panel-stat-sub { font-size: 12px; color: var(--text-muted); margin-top: 2px; }

/* ahg-ghtg0 -- Index/$ mode toggle + market/low basis option. Its own row
   below .panel-hd (not a third child crowded into .panel-hd's flex row --
   that row is pinned nowrap at the 768px breakpoint below so the headline
   fits next to the range control; a third control there would overflow at
   375px), reusing the same .seg/.seg--quiet look as Range. */
.mkt-page .panel-mode-row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }
/* Same fix as svg.chart[hidden] above (ahg-iyofn): .seg's own
   `display: inline-flex` has equal specificity to the UA `[hidden]` rule,
   and wins on source order alone -- without this, hiding the basis toggle
   in index mode is a no-op; JS sets `hidden` but the control stays visible. */
.mkt-page [data-mkt-basis-toggle][hidden] { display: none; }

/* Explanatory prose is a footnote, not a subtitle wedged between the title
   and the chart -- lives OUTSIDE the card, muted and small. */
.mkt-page .panel-footnote {
  margin: 12px 0 0;
  font-size: 13px;
  color: var(--text-muted);
  max-width: 78ch;
}
/* ahg: chart reads as a thin strip on a normal desktop viewport at a fixed
   300px -- scale with the viewport instead so it actually reads as the
   page's main event, floored at the old 300px and capped so it doesn't
   dominate an ultra-tall monitor. JS redraws off the element's own
   clientWidth/clientHeight (market_explorer.js), so no coordinate math here
   needs to change to match. Mobile keeps its own tuned heights below (the
   sticky-pinned-chart-over-scrolling-rail layout, ahg-q0r0r). */
.mkt-page svg.chart { width: 100%; height: clamp(300px, 50vh, 640px); margin-top: 12px; display: block; }

/* Drilldown index panel (/market/:game, /market/:game/:set, market report):
   headline, range control and stats stack in a left rail beside a fixed-
   height chart, so the panel stops filling the whole first screen and the
   sets / cards underneath start above the fold. Below the .split breakpoint
   it falls back to the stacked layout above (and the pinned mobile panel). */
@media (min-width: 961px) {
  .mkt-page .panel--index {
    display: grid;
    grid-template-columns: 250px minmax(0, 1fr);
    grid-template-rows: auto auto auto;
    column-gap: 32px;
    row-gap: 16px;
    padding: 20px 24px;
  }
  .mkt-page .panel--index .panel-hd {
    grid-column: 1;
    grid-row: 1;
    flex-direction: column;
    align-items: flex-start;
    gap: 14px;
  }
  .mkt-page .panel--index .panel-stats {
    grid-column: 1;
    grid-row: 2;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 12px 16px;
    margin: 0;
    padding: 0;
    border: 0;
  }
  .mkt-page .panel--index .panel-stat { min-width: 0; padding: 0; }
  .mkt-page .panel--index svg.chart {
    grid-column: 2;
    grid-row: 1 / span 2;
    align-self: center;
    height: 300px;
    margin: 0;
  }
  .mkt-page .panel--index .legend {
    grid-column: 2;
    grid-row: 3;
    margin: 0;
    padding: 0;
    border: 0;
  }
  /* ahg-ghtg0: explicit placement (not DOM-order auto-flow) -- the left
     rail's third row, below panel-stats, same column as the headline. */
  .mkt-page .panel--index .panel-mode-row {
    grid-column: 1;
    grid-row: 3;
    margin: 0;
  }
  .mkt-page .panel--index .panel-empty,
  .mkt-page .panel--index .panel-error { grid-column: 2; }
}
.mkt-page svg.chart .grid-line { stroke: var(--border-subtle); stroke-width: 1; }
/* The 100 = flat index reference -- stronger than the faint gridlines so
   "above = rising, below = falling" reads without reading the axis. */
.mkt-page svg.chart .grid-baseline { stroke: var(--text-muted); stroke-width: 1; stroke-opacity: 0.55; }
/* 14px floor applies to axis labels too -- the uppercase-label exception in
   DESIGN.md covers letterspaced caps, not chart scales (GATE FIX). */
.mkt-page svg.chart .axis-lbl {
  fill: var(--text-muted);
  font-family: var(--font-mono);
  font-size: 14px;
  font-variant-numeric: tabular-nums;
}
/* The 100 axis label rides the emphasized baseline -- lift it out of the faint
   muted tier so the break-even reference is legible. */
.mkt-page svg.chart .axis-lbl--base { fill: var(--text-secondary); }
.mkt-page .legend { display: flex; gap: 20px; flex-wrap: wrap; margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--border-subtle); }
.mkt-page .legend-item { display: flex; align-items: center; gap: 8px; font-size: 14px; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
.mkt-page .legend-dot { width: 10px; height: 10px; border-radius: 2px; background: currentColor; }

/* Crosshair layer (market_explorer.js bindChartInteraction): a vertical rule
   + a focus dot per series, both hidden until the pointer enters the chart.
   The dot inherits its series color via .mkt-sN (color -> currentColor); a
   surface-colored ring lifts it off an overlapping line (dataviz marks-and-
   anatomy: 2px surface ring on overlapping marks). */
.mkt-page svg.chart .chart-cursor-line { stroke: var(--text-muted); stroke-width: 1; stroke-dasharray: 3 3; pointer-events: none; }
/* ahg-pebhh: set-release markers on the index chart's time axis. */
.mkt-page svg.chart .mkt-rel-line { stroke: var(--border-strong, #cbd5e1); stroke-width: 1; stroke-dasharray: 2 3; opacity: 0.5; pointer-events: none; }
.mkt-page svg.chart .mkt-rel-icon { opacity: 0.85; }
.mkt-page svg.chart .mkt-rel-dot { fill: var(--text-muted); }
/* Scryfall/most set symbols are black glyphs -- invert so they read on the dark canvas. */
[data-theme="dark"] .mkt-page svg.chart .mkt-rel-icon { filter: invert(1); }
.mkt-page svg.chart .chart-cursor-dot { stroke: var(--bg-secondary); stroke-width: 2; pointer-events: none; }

/* Crosshair tooltip: absolutely positioned within .panel (position:relative),
   follows the nearest date column, lists every series present there. */
.mkt-page .chart-tip {
  position: absolute;
  z-index: 5;
  min-width: 132px;
  max-width: 240px;
  padding: 8px 10px;
  background: var(--bg-primary);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.28);
  pointer-events: none;
  font-size: 14px; /* DESIGN.md 14px floor -- hierarchy via color, not size */
}
.mkt-page .chart-tip[hidden] { display: none; }
.mkt-page .chart-tip-date { color: var(--text-muted); margin-bottom: 6px; font-variant-numeric: tabular-nums; }
.mkt-page .chart-tip-row { display: flex; align-items: center; gap: 8px; line-height: 1.7; }
.mkt-page .chart-tip-dot { width: 9px; height: 9px; border-radius: 2px; flex: none; background: currentColor; }
.mkt-page .chart-tip-name { color: var(--text-secondary); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mkt-page .chart-tip-val { color: var(--text-primary); margin-left: auto; }

/* Visually-hidden but readable by screen readers -- same pattern as
   dashboard.css.erb's .sr-only (that stylesheet isn't loaded on /market). */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Client empty-state (findings 4 & 6, ahg-iyofn): the `hidden` attribute
   already yields display:none via the UA stylesheet, but svg.chart's own
   `display: block` rule below has equal specificity on the element itself
   -- the attribute selector here wins deterministically instead of relying
   on source order. */
.mkt-page svg.chart[hidden] { display: none; }
.mkt-page .panel-empty { margin: 12px 0 0; }

/* Fetch-failure banner (finding 2): aria-pressed/URL already flipped
   optimistically on tap, so a failed request needs a visible, retryable
   contradiction-breaker rather than staying silent. */
/* Same specificity trap as svg.chart[hidden] above: this `display: flex`
   is author-origin and would otherwise beat the UA `[hidden]{display:none}`,
   leaving the banner permanently visible. Explicit override keeps it hidden
   until showFetchError() clears the attribute (QA finding, ahg-iyofn). */
.mkt-page .panel-error[hidden] { display: none; }
.mkt-page .panel-error {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 12px;
  padding: 10px 12px;
  border-radius: var(--radius-sm);
  background: var(--s2);
  font-size: 14px;
  color: var(--text-secondary);
}
.mkt-page .panel-error-retry {
  appearance: none;
  border: 1px solid var(--border-subtle);
  background: transparent;
  color: var(--text-primary);
  font: inherit;
  font-weight: 600;
  padding: 0 14px;
  min-height: 44px;
  border-radius: var(--radius-sm);
  cursor: pointer;
}
.mkt-page .panel-error-retry:hover { background: var(--s3); }

/* CARRIED FROM P2 REVIEW (ahg-pnypl notes): legend numbers render in
   <span class="num"> but nothing set the mono face -- they fell back to
   body Geist instead of Geist Mono, a typographic drift from the approved
   wireframe. */
.mkt-page .num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* P3 (ahg-pnypl): a toggle/range change refetches before redrawing -- the
   PRIOR chart line stays on screen (never blanks) while the request is in
   flight; this just dims it slightly so the update doesn't look inert. */
.mkt-page svg.chart { transition: opacity 150ms ease-in-out; }
.mkt-page .panel--loading svg.chart { opacity: 0.55; }

/* Cap-at-3 nudge: clicking a 4th rail row while 3 are already charted must
   never fire the request that 422s (API MAX_SCOPES=3) -- this is the "brief
   visual nudge" that tells the tap something happened without changing
   selection or state. */
@keyframes mkt-pick-nudge {
  0%, 100% { transform: translateX(0); }
  25% { transform: translateX(-4px); }
  75% { transform: translateX(4px); }
}
.mkt-page .pick-row--nudge { animation: mkt-pick-nudge 220ms ease-in-out; }
@media (prefers-reduced-motion: reduce) {
  .mkt-page .pick-row--nudge { animation: none; }
}

.mkt-page .note {
  margin-top: 24px;
  font-size: 14px;
  color: var(--text-muted);
  border-top: 1px solid var(--border-subtle);
  padding-top: 16px;
  max-width: 78ch;
}
.mkt-page .note b { color: var(--text-secondary); font-weight: 500; }

/* Public-explorer footer (ahg-o9isl, ahg-pgvq2). The market attribution
   sentence sits above the shared layouts/_footer, whose column/link styles
   live inline in the landing and features layouts -- those layouts are not
   used here, so .mkt-page restates them against the app shell's tokens. */
.mkt-page .mkt-footer {
  margin-top: 40px;
  padding-top: 16px;
  border-top: 1px solid var(--border-subtle);
}
.mkt-page .mkt-footer-attrib {
  margin: 0;
  font-size: 14px;
  color: var(--text-muted);
  max-width: 78ch;
}
.mkt-page .site-footer {
  border-top: none;
  padding: 0;
  margin: 0;
  max-width: none;
}
.mkt-page .footer-inner {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 32px;
  margin: 28px 0 24px;
}
.mkt-page .footer-heading {
  font-family: var(--font-mono);
  font-size: 13px;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 10px;
}
.mkt-page .footer-link {
  display: block;
  font-size: 14px;
  color: var(--text-secondary);
  text-decoration: none;
  padding: 5px 0;
}
.mkt-page .footer-link:hover { color: var(--text-primary); }
.mkt-page .footer-bottom {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 24px;
  border-top: 1px solid var(--border-subtle);
  padding-top: 16px;
}
.mkt-page .footer-bottom span { font-size: 13px; color: var(--text-muted); }

@media (max-width: 960px) {
  .mkt-page .footer-inner { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 24px; }
}

@media (max-width: 768px) {
  .mkt-page .footer-link { display: flex; align-items: center; min-height: 44px; padding: 0; }
}

@media (max-width: 480px) {
  .mkt-page .footer-inner { gap: 16px 20px; }
}

@media (max-width: 960px) {
  .mkt-page .split { grid-template-columns: 1fr; }
  .mkt-page svg.chart { height: 240px; }
}

@media (max-width: 768px) {
  .mkt-page .bar { gap: 8px 10px; min-height: 0; padding-bottom: 10px; margin-bottom: 12px; }
  .mkt-page .bar-title { font-size: 19px; flex: 1 1 auto; width: auto; }
  .mkt-page .bar-asof { order: 0; margin-left: auto; }
  .mkt-page .bar-break { display: block; flex-basis: 100%; height: 0; margin: 0; }
  .mkt-page .bar .seg { flex: 1; }
  .mkt-page .bar .seg a,
  .mkt-page .bar .seg button { min-height: 44px; padding: 0 10px; flex: 1 1 0; min-width: 0; }

  /* Tapping a rail row must show its effect. With the chart below a
     nine-row list it lands off-screen and the tap reads as broken -- pin
     the chart to the top and let the rail scroll under it (ahg-q0r0r
     MOBILE, non-negotiable). No gap under the pinned panel: a transparent
     gap lets rows scroll through it and reads as a rendering glitch. */
  .mkt-page .split { display: flex; flex-direction: column; gap: 0; }
  .mkt-page .split > .panel { order: 1; position: sticky; top: 0; z-index: 2; }
  .mkt-page .split > div:not(.panel) { order: 2; }
  .mkt-page .panel {
    padding: 12px 14px;
    border-radius: 0 0 var(--radius-lg) var(--radius-lg);
    border-top: 0;
    margin: 0 -16px; /* full-bleed against .mkt-page's 16px mobile padding */
    background: var(--bg-secondary);
  }
  .mkt-page .panel-title { font-size: 18px; }
  .mkt-page .panel-note,
  .mkt-page .panel-sub { display: none; }
  .mkt-page svg.chart { height: 170px; margin-top: 10px; }
  .mkt-page .legend { margin-top: 10px; padding-top: 10px; gap: 6px 16px; }
  .mkt-page .pick-row { min-height: 56px; }

  /* On the 170px phone chart a floating tooltip blankets the whole plot it
     annotates. Dock it below the chart as a compact wrapping strip instead
     (the JS-set absolute left/top are ignored under position:static). */
  .mkt-page .chart-tip {
    position: static;
    min-width: 0;
    max-width: none;
    margin: 10px 0 0;
    padding: 10px 0 0;
    background: transparent;
    border: 0;
    border-top: 1px solid var(--border-subtle);
    border-radius: 0;
    box-shadow: none;
  }
  .mkt-page .chart-tip-date { display: inline-block; margin: 0 12px 6px 0; }
  .mkt-page .chart-tip-row { display: inline-flex; width: auto; margin: 0 14px 6px 0; }
  .mkt-page .chart-tip-val { margin-left: 6px; }

  /* The range control belongs to the chart, not the page -- the base
     mobile rule above gives every .seg width:100%, which would make it
     claim its own row. Scope it back to content width. */
  .mkt-page .panel-hd { flex-wrap: nowrap; align-items: center; gap: 10px; }
  .mkt-page .panel-hd .panel-title { min-width: 0; }
  .mkt-page .panel-hd .seg { width: auto; flex: none; }
  .mkt-page .panel-hd .seg button { min-height: 44px; padding: 0 10px; flex: none; }

  /* Drilldown headline shrinks to fit next to the range tabs on one row. */
  .mkt-page .panel-headline { min-width: 0; }
  .mkt-page .panel-eyebrow { font-size: 11px; margin-bottom: 4px; }
  .mkt-page .panel-value { font-size: 24px; }
  .mkt-page .panel-pill { font-size: 12px; padding: 2px 8px; }
  .mkt-page .panel-stats { margin-top: 12px; padding-top: 10px; }
}

@media (max-width: 560px) {
  /* Below ~3 items per row a hairline-divided single strip wraps into a
     ragged half-row -- a clean 2-column grid (vertical divider down the
     middle, horizontal hairlines between rows) reads better than that. */
  .mkt-page .panel-stats { flex-wrap: wrap; }
  .mkt-page .panel-stat {
    flex: 1 1 50%;
    min-width: 0;
    border-left: 0;
    padding: 10px 0 0;
    margin-top: 10px;
    border-top: 1px solid var(--border-subtle);
  }
  .mkt-page .panel-stat:nth-child(-n+2) { margin-top: 0; padding-top: 0; border-top: 0; }
  .mkt-page .panel-stat:nth-child(odd) { padding-right: 12px; }
  .mkt-page .panel-stat:nth-child(even) { padding-left: 12px; border-left: 1px solid var(--border-subtle); }
}

/* ---- /market/compare (ahg-eqatd) -- reuses every .bar/.seg/.split/.pick/
   .pick-row/.swatch/.panel/svg.chart/.legend/.panel-empty/.panel-error rule
   above VERBATIM. Only new here: the game selector + search + results
   dropdown (the autocomplete), and dropping the leaderboard-only rank
   column from .pick-row. */

/* No .pick-rank on this surface -- the rail here is "what you picked", not a
   ranked leaderboard, so the column disappears rather than going blank. */
.mkt-page.mkt-compare .pick-row { grid-template-columns: 16px 1fr auto; cursor: default; }
/* The name is a drill-in link filling its cell so the whole set label is a
   44px tap target into the set's own page. */
.mkt-page.mkt-compare a.pick-name {
  align-self: stretch;
  display: flex;
  align-items: center;
  min-height: 44px;
  color: var(--text-primary);
  text-decoration: none;
  cursor: pointer;
}
.mkt-page.mkt-compare a.pick-name:hover { text-decoration: underline; }
/* Real <button>, not a decorative span -- reset its chrome and give it a
   44px square hit area for the remove action. */
.mkt-page.mkt-compare .pick-remove {
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  padding: 0;
  background: transparent;
  border: 0;
  color: var(--text-muted);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}
.mkt-page.mkt-compare .pick-remove:hover { color: var(--text-primary); }

.mkt-compare-toolbar { margin-bottom: 10px; }
.mkt-compare-game-select {
  width: 100%;
  min-height: 44px;
  padding: 0 12px;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  font: inherit;
  font-size: 16px; /* iOS zoom floor -- applies to selects too */
}

.mkt-compare .rail-search { position: relative; margin-bottom: 12px; }
.mkt-compare-search-input {
  width: 100%;
  min-height: 48px;
  padding: 0 14px;
  background: var(--bg-secondary);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  color: var(--text-primary);
  font: inherit;
  font-size: 16px; /* iOS zoom floor */
}
.mkt-compare-search-input::placeholder { color: var(--text-muted); }
.mkt-compare-search-input:focus-visible { outline: 2px solid var(--accent-amber); outline-offset: -1px; }

.mkt-compare-results {
  position: absolute;
  z-index: 4;
  top: calc(100% + 4px);
  left: 0;
  right: 0;
  margin: 0;
  padding: 4px;
  list-style: none;
  background: var(--s2);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
  max-height: 320px;
  overflow-y: auto;
}
.mkt-compare-result {
  display: block;
  width: 100%;
  min-height: 44px;
  padding: 0 12px;
  background: transparent;
  border: 0;
  border-radius: var(--radius-sm);
  color: var(--text-primary);
  font: inherit;
  font-size: 15px;
  text-align: left;
  cursor: pointer;
}
.mkt-compare-result:hover { background: var(--s3); }
.mkt-compare-noresults {
  padding: 10px 12px;
  color: var(--text-muted);
  font-size: 14px;
}

@media (max-width: 768px) {
  .mkt-page.mkt-compare .pick-row { min-height: 52px; }
}

/* Hoard attribution line (ahg-99x5k, shared/_hoard_attribution partial) --
   small factual "priced and tracked with Hoard" strip on public /cards and
   /market pages. 14px meets the DESIGN.md text-size floor exactly; the
   dotted-underline / arrow-suffix split follows DESIGN.md's Links
   convention (internal action = dotted underline, external destination =
   trailing arrow, both text-primary, no separate color register). */
.hoard-attribution {
  font-size: 14px;
  color: var(--text-muted);
  line-height: 1.5;
  margin: 20px 0;
}
.hoard-attribution a {
  color: var(--text-primary);
  text-decoration: none;
  border-bottom: 1px dotted var(--text-muted);
}
.hoard-attribution a:hover { border-bottom-color: var(--text-secondary); }
.hoard-attribution a.hoard-attribution-external {
  border-bottom: none;
}
.hoard-attribution a.hoard-attribution-external::after {
  content: " \2197";
}

/* Collector bridge (ahg-8f5ld, shared/_collect_bridge partial) -- points
   logged-out SEO landers on /cards and /market at the /collect Quick Add
   tracker. Same treatment as .hoard-attribution directly above (14px meets
   the DESIGN.md text-size floor; dotted-underline link, no new colors). */
.collect-bridge {
  font-size: 14px;
  color: var(--text-muted);
  line-height: 1.5;
  margin: 4px 0 20px;
}
.collect-bridge a {
  color: var(--text-primary);
  text-decoration: none;
  border-bottom: 1px dotted var(--text-muted);
}
.collect-bridge a:hover { border-bottom-color: var(--text-secondary); }

/* One-sentence factual summary on the market index/game/set pages -- the
   exact same string embedded in that page's JSON-LD `description`, so the
   visible copy and the structured data can never drift apart. */
.mkt-summary {
  font-size: 15px;
  color: var(--text-secondary);
  line-height: 1.5;
  margin: 8px 0 20px;
}

/* Page tab bar linking a game's movers/market-report/most-valuable pages
   (ahg-o6rs6). Ported from the dashboard's .subtab-bar/.subtab-btn pattern
   (dashboard.css.erb, the Today tab's Overview/Suggestions/Sync Log/Rollback
   strip) rather than reusing .seg -- .seg is what the rest of /market uses
   for a SECONDARY in-panel toggle (product track, chart range); this is
   primary page-level navigation, so it gets the same underline-tab
   treatment as the app's other page-level tab strip. market.css doesn't
   load dashboard.css.erb (each page-family stylesheet is self-contained),
   so the values are copied here rather than shared. */
.mkt-subnav {
  display: flex;
  gap: 4px;
  flex-wrap: wrap;
  margin: 4px 0 20px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border-subtle);
}
.mkt-subnav-tab {
  position: relative;
  padding: 6px 14px;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-muted);
  text-decoration: none;
  border-radius: var(--radius-md);
  transition: color 100ms ease-out, background 100ms ease-out;
}
.mkt-subnav-tab:hover {
  color: var(--text-secondary);
  background: rgba(255, 255, 255, 0.04);
}
.mkt-subnav-tab[aria-current="page"] {
  color: var(--accent-amber);
  font-weight: 600;
}
.mkt-subnav-tab[aria-current="page"]::after {
  content: "";
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: -9px;
  height: 2px;
  background: var(--accent-amber);
  border-radius: 999px;
}
/* Dashboard's mobile .subtab-btn shrinks below the 44px tap-target floor
   (an accepted tradeoff for an authenticated, mostly-desktop surface) --
   /market is public and skews mobile, so it keeps a tap-safe height here
   instead of copying that shrink. */
@media (max-width: 768px) {
  .mkt-subnav-tab {
    min-height: 44px;
    display: inline-flex;
    align-items: center;
    padding: 0 12px;
  }
}

/* "See all X" link under a market-report excerpt table (ahg-o6rs6). */
.mkt-excerpt-more {
  font-size: 14px;
  margin: 8px 0 28px;
}

/* Boxes the "Common questions" FAQ on the movers/market-report/most-valuable
   pages so it reads as a section of the app, not a wall of prose dropped
   below the tables (ahg-o6rs6 follow-up). Deliberately its own class, not
   .panel -- .panel carries sticky/mobile drill-down overrides (the chart
   panel) that don't apply to a plain FAQ box. guides/_common_questions
   itself is unchanged (still the shared h2/h3/p + FAQPage JSON-LD partial),
   this only wraps it. */
.mkt-faq {
  background: var(--bg-secondary);
  border-radius: var(--radius-lg);
  padding: 20px 24px;
  margin: 8px 0 28px;
}
.mkt-faq h2 { margin-top: 0; }
.mkt-faq h3 { font-size: 15px; }

/* Quick clarifying caption on /market/:game/movers (ahg-o6rs6 follow-up) --
   states up front that ranking is set-level, not per-card, instead of
   leaving it to be inferred from the table rows. */
.mkt-note {
  font-size: 14px;
  color: var(--text-muted);
  margin: 0 0 16px;
}

/* Gainers/losers side by side (ahg-o6rs6 follow-up) -- stacked full tables
   put losers a full scroll below gainers, easy to miss entirely. Matches
   the dashboard's own gainers/losers layout (.movers-columns in
   dashboard.css.erb); market.css doesn't load that file, so the values are
   copied here rather than shared. */
.mkt-movers-columns {
  display: flex;
  gap: 24px;
  align-items: flex-start;
}
.mkt-movers-col { flex: 1 1 0; min-width: 0; }
@media (max-width: 960px) {
  /* align-items: flex-start (base rule) sizes a row's cross-axis items to
     their own content -- fine side by side, but once stacked (flex-direction:
     column) that same "flex-start" governs the now-HORIZONTAL cross axis, so
     each column shrank to its table's min-content width (560px) instead of
     the viewport. stretch forces full width so .mkt-table-wrap's own
     overflow-x:auto contains the table, not the page. */
  .mkt-movers-columns { flex-direction: column; gap: 0; align-items: stretch; }
}

/* Quick card search overlay (ahg) -- same component as the signed-in
   dashboard's global search (.global-search-overlay / .gs-*,
   dashboard.css.erb ahg-z2cmt); market.css doesn't load that file, so the
   values are copied here rather than shared (same precedent as
   .mkt-movers-columns above). Wiring: market_search_overlay.js,
   market/_card_search_overlay. */
.global-search-overlay {
  position: fixed;
  inset: 0;
  z-index: 1200;
  background: rgba(12, 10, 9, 0.72);
}
.global-search-overlay[hidden] { display: none; }
body.gs-open { overflow: hidden; }
.global-search-panel {
  display: flex;
  flex-direction: column;
  max-width: 640px;
  max-height: 70vh;
  margin: 8vh auto 0;
  background: var(--bg-secondary);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-xl);
  overflow: hidden;
}
.global-search-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--border-subtle);
}
.global-search-bar-icon { width: 18px; height: 18px; flex-shrink: 0; color: var(--text-muted); }
.global-search-input {
  flex: 1;
  min-width: 0;
  padding: 8px 0;
  background: none;
  border: none;
  outline: none;
  font-family: var(--font-sans);
  font-size: 16px; /* iOS: < 16px would auto-zoom on focus */
  color: var(--text-primary);
}
.global-search-input::-webkit-search-cancel-button { display: none; }
.global-search-cancel {
  min-height: 44px;
  padding: 0 10px;
  background: none;
  border: none;
  font-size: 14px;
  color: var(--text-secondary);
  cursor: pointer;
}
.global-search-body {
  flex: 1;
  min-height: 80px;
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  padding: 8px;
}
.global-search-hint {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  height: 100%;
  min-height: 200px;
  box-sizing: border-box;
  padding: 28px 16px;
  font-size: 14px;
  color: var(--text-muted);
  text-align: center;
}
.gs-hint-mark {
  width: 120px;
  height: 120px;
  background-color: var(--text-primary);
  opacity: 0.06;
  -webkit-mask: var(--hoard-mark-svg) center / contain no-repeat;
  mask: var(--hoard-mark-svg) center / contain no-repeat;
}
.gs-result {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  min-height: 56px;
  padding: 8px 10px;
  background: none;
  border: none;
  border-radius: var(--radius-md);
  text-align: left;
  text-decoration: none;
  cursor: pointer;
}
.gs-result:hover { background: rgba(255, 255, 255, 0.05); }
.gs-thumb {
  width: 34px;
  height: 48px;
  flex-shrink: 0;
  object-fit: cover;
  border-radius: 3px;
  background: var(--bg-primary);
}
.gs-info { display: flex; flex-direction: column; gap: 2px; flex: 1; min-width: 0; }
.gs-name { font-size: 15px; color: var(--text-primary); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gs-set { font-size: 14px; color: var(--text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.gs-right { display: flex; flex-direction: column; align-items: flex-end; gap: 2px; flex-shrink: 0; }
.gs-price { font-family: var(--font-mono); font-variant-numeric: tabular-nums; font-size: 14px; color: var(--text-primary); }
.gs-owned {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  font-size: 14px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--accent-amber);
}
.gs-sealed-tag {
  display: inline-block;
  margin-right: 6px;
  padding: 1px 7px;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-full);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--text-secondary);
  vertical-align: middle;
}
.gs-load-more {
  display: block;
  width: 100%;
  padding: 12px;
  background: none;
  border: none;
  border-top: 1px solid var(--border-subtle);
  font-size: 14px;
  color: var(--accent-amber);
  cursor: pointer;
  text-align: center;
}
.gs-load-more:hover { color: var(--text-primary); }

@media (max-width: 768px) {
  .global-search-panel { max-width: none; max-height: none; height: 100%; margin: 0; border: none; border-radius: 0; }
}

/* Exact product references and seller-entered resale estimates. */
.mkt-sealed-market, .mkt-resale { margin: 24px 0 32px; }
.mkt-sealed-product-name { display: block; margin-top: 8px; font-size: 14px; color: var(--text-secondary); }
.mkt-resale { padding: 24px; background: var(--bg-secondary); }
.mkt-resale-fields { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 16px; }
.mkt-resale-fields label { display: block; font-size: 14px; margin-bottom: 8px; }
.mkt-resale-fields input { width: 100%; min-height: 44px; font-size: 16px; box-sizing: border-box; }
.mkt-resale-result { display: block; font: 15px var(--font-mono); line-height: 1.7; }
.mkt-sealed-market tr[hidden] { display: none; }
@media (max-width: 800px) { .mkt-resale-fields { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 480px) { .mkt-resale { padding: 16px; } .mkt-resale-fields { grid-template-columns: 1fr; } }
.mkt-track-comparison { margin: 32px 0; }
.mkt-track-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 16px; }
.mkt-track-item { padding: 16px; background: var(--bg-secondary); }
.mkt-track-item h3 { margin: 0 0 8px; font: 700 20px var(--font-display); letter-spacing: var(--tracking-display); }
.mkt-track-item p { font-size: 14px; line-height: 1.6; margin: 8px 0; }
.mkt-track-item a { display: inline-flex; align-items: center; min-height: 44px; }
.mkt-track-item .mkt-track-value { font-family: var(--font-mono); }
@media (max-width: 700px) { .mkt-track-grid { grid-template-columns: 1fr; } }
.mkt-track-item a { color: var(--text-primary); text-decoration-color: var(--text-muted); text-underline-offset: 3px; }
.mkt-track-item a:hover { color: var(--accent); }
.mkt-resale-fields input { color: var(--text-primary); background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 8px 12px; }
.mkt-resale-fields input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
[data-sealed-show-all][hidden] { display: none; }
