/* Holo Night's compatibility layer. New values live in holo-night.css,
 * loaded first by base.html. Legacy selectors keep their names while pages
 * migrate; none may redefine the palette locally. */

/* Native controls inherit the interface face unless their component chooses
   a deliberate display/number style. Keep component sizes and weights. */
:where(button,input,select,textarea) { font-family:inherit; }

:root {
  --ground-0: var(--ground);
  --ground-1: var(--panel);
  --ground-2: var(--raised);
  --violet-lo: var(--violet-ink);
  --violet-dim: color-mix(in srgb, var(--violet) 14%, transparent);
  --glass-bg: var(--panel);
  --glass-edge: var(--line);
  --glass-blur: blur(16px);
  --r-sm: var(--r-control);
  --r-md: var(--r-panel);
  --r-lg: var(--r-panel);
  --s-1: var(--sp-1);
  --s-2: var(--sp-2);
  --s-3: var(--sp-3);
  --s-4: var(--sp-4);
  --s-5: var(--sp-5);
  --s-6: var(--sp-6);
  --s-7: var(--sp-7);
  --s-8: var(--sp-8);
  --s-9: calc(var(--sp-8) + var(--sp-6));
  --gutter: clamp(16px, 4vw, 32px);

  --shadow: 0 1px 2px rgba(0,0,0,.4), 0 8px 32px rgba(0,0,0,.34);

  --font: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, monospace;

  --font-display: "Archivo", "Inter", system-ui, sans-serif;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  font-family: var(--font);
  color: var(--ink);
  background: var(--ground);
  background-attachment: scroll;
  min-height: 100vh;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; text-decoration: none; }

/* ------------------------------------------------------------------ chrome */

.nav {
  position: sticky; top: 0; z-index: 50;
  display: flex; align-items: center; gap: 20px;
  padding: 14px 28px;
  background: rgba(14, 7, 20, 0.72);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border-bottom: 1px solid var(--glass-edge);
}

.brand {
  display: inline-flex; align-items: center; gap: 11px;
  flex: none;
}
/* --mark-w and --mark-h are set inline by _brand.html's mark() macro, derived
   from the artwork's real 1.42:1, so nothing here can disagree with it. */
.brand { position: relative; }
/* Violet rather than white. The nav is already high-contrast, so pure white made
   the wordmark shout louder than the mark beside it; --violet-lo sits it at the
   same visual weight. The wordmark is inlined SVG filled with currentColor
   precisely so this is a token and not a second copy of the artwork. */
.brand .wordmark { display: block; flex: none; color: var(--violet-lo);
                   transition: color .18s ease; }
.brand:hover .wordmark { color: var(--ink); }
/* Below the search field's comfortable minimum the symbol carries the brand
   on its own — a wordmark that wraps is worse than no wordmark. */
@media (max-width: 720px) {
  .brand .wordmark { display: none; }
}

/* A basis of 0 let the search shrink to nothing the moment the nav gained a
   link — it collapsed to a circle rather than getting narrower. */
.searchbar { flex: 1 1 200px; min-width: 170px; max-width: 520px; position: relative; }
.searchbar input {
  width: 100%; padding: 10px 16px;
  border-radius: 999px;
  border: 1px solid var(--glass-edge);
  background: rgba(255,255,255,.045);
  color: var(--ink); font-family: var(--font); font-size: 14px;
  transition: border-color .18s ease, background .18s ease;
}
.searchbar input::placeholder { color: var(--ink-3); }
.searchbar input:focus {
  outline: none;
  border-color: rgba(155,93,229,.6);
  background: rgba(255,255,255,.07);
}

.navlinks {
  display: flex; align-items: center; gap: 18px; flex: none;
  font-size: 13px; color: var(--ink-2);
}
.navlinks a { white-space: nowrap; }
.navlinks a:hover { color: var(--ink); }

/* Explore, Coming soon and Portfolio are the structure; the category links are
   three arbitrary favourites out of sixty-eight. When the bar runs out of room
   the favourites are what should go. */
@media (max-width: 1080px) { .navlinks .navcat { display: none; } }
@media (max-width: 760px)  { .navlinks { gap: 12px; font-size: 12.5px; } }

/* The gutter is fluid: 28px is generous on a phone and mean on a 27-inch
   display, and it is the outermost thing standing between text and the edge of
   the screen. */
.wrap { max-width: 1240px; margin: 0 auto;
        padding: var(--s-6) var(--gutter) var(--s-9); }
/* Decorative light and wide data must not drag the whole phone viewport. */
main.wrap { overflow-x: clip; }

/* ------------------------------------------------------------------- glass */

.glass {
  background: var(--glass-bg);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-edge);
  border-radius: var(--r-md);
  box-shadow: var(--shadow);
}

h1 { font-size: 30px; font-weight: 680; letter-spacing: -.02em; margin: 0 0 6px; }
h2 { font-size: 17px; font-weight: 620; letter-spacing: -.01em; margin: 0 0 14px; }
.sub { color: var(--ink-2); font-size: 14px; margin: 0 0 26px; }
.section { margin-top: 40px; }

/* ------------------------------------------------------------------- stats */

.stats { display: grid; grid-template-columns: repeat(auto-fit, minmax(168px, 1fr)); gap: 14px; }
.stat { padding: 18px 20px; }
.stat .n {
  font-family: var(--mono); font-size: 26px; font-weight: 600;
  letter-spacing: -.02em;
  background: linear-gradient(180deg, var(--ink), var(--violet-lo));
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.stat .k { font-size: 11px; text-transform: uppercase; letter-spacing: .1em; color: var(--ink-3); margin-top: 6px; }

/* -------------------------------------------------------------------- grid */

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

.card-tile { position: relative; display: block; min-width: 0; }
.card-tile-link { display: block; color: inherit; text-decoration: none; }
.card-tile .frame {
  position: relative;
  aspect-ratio: 733 / 1024;
  border-radius: var(--r-sm);
  overflow: hidden;
  background: var(--ground-1);
  border: 1px solid var(--glass-edge);
  transition: transform .22s cubic-bezier(.2,.7,.3,1), box-shadow .22s ease;
}
.card-tile:hover .frame {
  transform: translateY(-5px) scale(1.015);
  box-shadow: 0 14px 42px rgba(0,0,0,.5), 0 0 0 1px var(--tint, var(--violet-dim));
}
.card-tile img { width: 100%; height: 100%; object-fit: cover; display: block; }
.card-tile .noimg {
  display: grid; place-items: center; height: 100%;
  color: var(--ink-3); font-size: 11px; text-align: center; padding: 10px;
}
.card-tile .label { margin-top: 9px; font-size: 12.5px; line-height: 1.35; }
.card-tile .meta { font-size: 11px; color: var(--ink-3); margin-top: 2px; }
.tile-quick-add {
  position: absolute;
  z-index: 4;
  right: var(--sp-2);
  bottom: var(--sp-2);
  width: 40px;
  height: 40px;
  margin: 0;
}
.tile-quick-add button,
a.tile-quick-add {
  display: grid;
  width: 40px;
  height: 40px;
  padding: 0;
  place-items: center;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  background: var(--panel);
  color: var(--action);
  font: 600 var(--fs-title) var(--font);
  line-height: 1;
  text-decoration: none;
  cursor: pointer;
}
.tile-quick-add button:hover,
a.tile-quick-add:hover { border-color: var(--action); color: var(--ink); }
.tile-quick-add button:focus-visible,
a.tile-quick-add:focus-visible { outline: 2px solid var(--violet-ink); outline-offset: 2px; }
.card-tile:has(.tile-quick-add) .label,
.card-tile:has(.tile-quick-add) .meta,
.card-tile:has(.tile-quick-add) .card-floor { padding-right: 46px; }
.quick-add-confirm {
  display: flex;
  min-height: 44px;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
  margin-bottom: var(--sp-4);
  padding: var(--sp-2) var(--sp-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-control);
  background: var(--panel);
  color: var(--ink-2);
  font-size: var(--fs-small);
}
.quick-add-confirm b { margin-right: var(--sp-2); color: var(--up); }
.quick-add-confirm a { color: var(--violet-ink); font-weight: 600; }
.sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip-path:inset(50%); white-space:nowrap; border:0; }
.transient-confirmation { position:fixed; z-index:90; left:16px; bottom:calc(88px + env(safe-area-inset-bottom)); width:max-content; max-width:min(420px,calc(100vw - 104px)); box-sizing:border-box; min-height:44px; padding:0 12px; margin:0; gap:12px; font-size:13px; box-shadow:0 4px 16px #0003; }
.transient-confirmation a { display:inline-flex; align-items:center; min-height:44px; }
[data-recent-changes] { min-width:0; }
[data-recent-changes] > summary { min-height:44px; display:flex; align-items:center; padding:0 12px; cursor:pointer; color:var(--ink-2); background:var(--panel); border-radius:10px; font:13px var(--font); }
[data-recent-changes] > summary:focus-visible { outline:2px solid var(--violet-ink); outline-offset:-2px; }
[data-recent-changes][open] > summary { font-size:12px; }
[data-recent-changes] > form[data-receipt] { display:flex; align-items:center; flex-wrap:wrap; gap:4px; margin:0; padding:0 8px; }
.action-receipts:has([data-recent-changes]:not([open])) { width:auto; }

/* Recovery state is global because a safe back_to may land anywhere. It sits
   above the compact dock instead of borrowing that thumb space. The server
   supplies at most three receipts; this component never becomes an inbox. */
.action-receipts {
  position: fixed;
  left: max(var(--sp-4), env(safe-area-inset-left));
  bottom: max(var(--sp-4), env(safe-area-inset-bottom));
  z-index: 90;
  display: grid;
  width: min(420px, calc(100vw - 2 * var(--sp-4)));
  gap: var(--sp-2);
  max-height: calc(100dvh - 180px);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.action-receipts-dismiss { margin: 0; text-align: right; }
.action-receipts-dismiss button { min-height: 44px; padding: var(--sp-2) var(--sp-3); border: 1px solid var(--line-strong); border-radius: var(--r-control); background: var(--panel); color: var(--ink-2); font: var(--fs-small) var(--font); cursor: pointer; }
.action-receipts-dismiss button:focus-visible { outline: 2px solid var(--violet-ink); outline-offset: -3px; }
.action-receipt {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--sp-3);
  min-width: 0;
  padding: var(--sp-3);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-panel);
  background: var(--raised);
  box-shadow: var(--shadow);
  color: var(--ink-2);
}
.action-receipt-copy { min-width: 0; }
.action-receipt-copy > span {
  display: block;
  margin-bottom: var(--sp-1);
  color: var(--violet-ink);
  font: 600 var(--fs-micro) var(--mono);
  letter-spacing: .08em;
  text-transform: uppercase;
}
.action-receipt-copy p {
  margin: 0;
  overflow-wrap: anywhere;
  color: var(--ink);
  font-size: var(--fs-small);
}
.action-receipt-copy small {
  display: block;
  margin-top: var(--sp-1);
  color: var(--ink-3);
  font-size: var(--fs-micro);
}
.action-receipt form { margin: 0; }
.action-receipt button {
  min-width: 72px;
  min-height: 44px;
  padding: 0 var(--sp-3);
  border: 1px solid var(--action);
  border-radius: var(--r-control);
  background: var(--action);
  color: var(--on-action);
  font: 600 var(--fs-small) var(--font);
  cursor: pointer;
}
.action-receipt button:focus-visible {
  outline: 2px solid var(--violet-ink);
  outline-offset: 2px;
}

@media (max-width: 1000px) {
  .action-receipts {
    left: max(var(--sp-3), env(safe-area-inset-left));
    bottom: calc(88px + env(safe-area-inset-bottom));
    width: min(420px, calc(100vw - 104px));
  }
  .action-receipt { padding: var(--sp-2) var(--sp-3); }
}

/* -------------------------------------------------------------------- rows */

.rows { display: flex; flex-direction: column; gap: 10px; }
.row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; padding: 14px 18px;
  transition: background .16s ease;
}
.row:hover { background: rgba(255,255,255,.035); }
.row .t { font-size: 14px; }
.row .s { font-size: 12px; color: var(--ink-3); margin-top: 2px; }
.pill {
  font-family: var(--mono); font-size: 11px;
  padding: 4px 10px; border-radius: 999px;
  background: var(--violet-dim); color: var(--violet-lo);
  border: 1px solid rgba(155,93,229,.24);
  white-space: nowrap;
}

/* ------------------------------------------------------------------ card pg */

.card-page { display: grid; grid-template-columns: minmax(0, 320px) minmax(0, 1fr); gap: 24px 30px; align-items: start; }
.card-visual-column { grid-column: 1; grid-row: 1 / span 2; }
.card-page-intro { grid-column: 2; grid-row: 1; }
.card-page-rest { grid-column: 2; grid-row: 2; }
@media (max-width: 900px) {
  .card-page { grid-template-columns: minmax(0, 1fr); }
  .card-page-intro { grid-column: 1; grid-row: 1; }
  .card-visual-column { grid-column: 1; grid-row: 2; }
  .card-page-rest { grid-column: 1; grid-row: 3; }
}

/* The card page's picture frame. Named .card-hero rather than .hero because
   the landing page's hero is a different component entirely, and sharing the
   class meant the landing headline sat 1px inside a border it never asked for
   while overflow:hidden quietly clipped the fan's hover spread. */
.card-hero {
  border-radius: var(--r-md);
  overflow: hidden;
  border: 1px solid var(--glass-edge);
  box-shadow: 0 20px 60px rgba(0,0,0,.55);
  background: var(--ground-1);
}
.card-hero img { width: 100%; display: block; }

.chart-card { padding: 20px 22px; }
.chart-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin-bottom: 4px; }
.chart-head .now { font-family: var(--font); font-size: var(--fs-title); font-variant-numeric: tabular-nums; font-weight: 700; }
.delta { font-family: var(--mono); font-size: 12px; }
.delta.up { color: var(--up); }
.delta.down { color: var(--down); }
.chart-span { font-size: 11px; color: var(--ink-3); }
.chart svg { width: 100%; height: auto; display: block; overflow: visible; }

table.printings { width: 100%; border-collapse: collapse; font-size: 13px; }
table.printings th {
  text-align: left; font-weight: 500; font-size: 11px;
  text-transform: uppercase; letter-spacing: .08em; color: var(--ink-3);
  padding: 0 12px 10px; border-bottom: 1px solid var(--glass-edge);
}
table.printings td { padding: 11px 12px; border-bottom: 1px solid rgba(255,255,255,.04); }
table.printings tr:last-child td { border-bottom: none; }
.num { font-family: var(--mono); text-align: right; }

.evidence { font-size: 11px; color: var(--ink-3); }
.price-proof { display:flex; flex-wrap:wrap; gap:5px 12px; margin-top:8px; color:var(--ink-2); font:10px/1.5 var(--mono); }
.price-proof > span + span { position:relative; }
.price-proof > span + span::before { content:"/"; position:absolute; left:-8px; color:var(--ink-3); }
.price-proof.compact { display:block; margin-top:4px; max-width:260px; white-space:normal; text-align:inherit; font-size:9px; color:var(--ink-3); }
.price-proof.compact > span { display:block; }
.price-proof.compact > span + span::before { content:none; }

.note {
  padding: 13px 16px; border-radius: var(--r-sm);
  background: rgba(155,93,229,.07);
  border: 1px solid rgba(155,93,229,.18);
  font-size: 12.5px; color: var(--ink-2); line-height: 1.5;
}

.empty { padding: 44px; text-align: center; color: var(--ink-3); font-size: 14px; }

/* ------------------------------------------------------- first run
 *
 * An empty portfolio used to open on "Total value $0.00" over an empty chart.
 * Every insight panel below it is {% if %} guarded, so they all vanished and a
 * new account's first impression was a confident zero.
 *
 * Left-aligned rather than centred like .empty: this is three sentences of
 * reading, and centred text is harder to read past one line. It is a panel that
 * replaces content, not a shrug where content should have been.
 */
.firstrun { padding: 30px 32px 28px; }
.firstrun h2 { margin: 0 0 8px; font-size: 21px; letter-spacing: -.02em; }
.fr-lead { color: var(--ink-2); font-size: 15px; margin: 0 0 20px; max-width: 60ch; }

.fr-points { list-style: none; margin: 0 0 24px; padding: 0;
             display: grid; gap: 14px; }
.fr-points li { font-size: 14px; color: var(--ink-2); line-height: 1.6;
                max-width: 66ch; padding-left: 16px; position: relative; }
/* A hairline rather than a bullet glyph: three violet dots down the left of a
   glass panel reads as a list of warnings. */
.fr-points li::before {
  content: ""; position: absolute; left: 0; top: 6px; bottom: 6px;
  width: 2px; border-radius: 2px;
  background: linear-gradient(180deg, var(--violet), transparent);
}
.fr-points strong { color: var(--ink); font-weight: 600; }

.fr-actions { display: flex; gap: 10px; flex-wrap: wrap; }
/* .btn is full-width by default because every other use of it is inside a form
   column. Here two sit side by side. */
/* .btn carries margin-top:22px for the form column it was written for. Inside a
   flex row that fights the gap and offsets the two buttons from each other. */
.fr-actions .btn,
.fr-actions .btn-secondary { width: auto; display: inline-block;
                             padding: 11px 22px; margin-top: 0; }

.fr-foot { margin: 20px 0 0; max-width: 62ch; }

@media (max-width: 560px) {
  .firstrun { padding: 24px 20px 22px; }
  .fr-actions .btn, .fr-actions .btn-secondary { width: 100%; text-align: center; }
}

.crumb { font-size: 12px; color: var(--ink-3); margin-bottom: 14px; }
.crumb a:hover { color: var(--violet-lo); }

/* A card reached through a collector's room remains the canonical catalogue
   object. This ticket preserves why somebody opened it without confusing the
   collector's caption (and, later, exhibit responses) with card-wide facts and
   discussion. */
.exhibit-origin {
  position: relative; display: grid; grid-template-columns: 50px minmax(0,1fr) auto;
  align-items: center; gap: 15px; margin: 0 0 20px; padding: 13px 15px;
  overflow: hidden; border: 1px solid rgba(111,233,247,.22); border-left: 3px solid #6fe9f7;
  border-radius: 3px; background: linear-gradient(100deg,rgba(111,233,247,.075),transparent 42%),rgba(12,7,18,.86);
  box-shadow: 0 18px 42px rgba(0,0,0,.2);
}
.exhibit-origin::after { content: "CONTEXT / VERIFIED"; position: absolute; right: 13px; top: 7px; color: rgba(111,233,247,.25); font: 600 5.5px var(--mono); letter-spacing: .16em; }
.exhibit-origin-mark { display: grid; width: 43px; height: 53px; place-items: center; border: 1px solid rgba(111,233,247,.28); background: linear-gradient(145deg,rgba(111,233,247,.12),rgba(155,93,229,.06)); clip-path: polygon(0 0,78% 0,100% 18%,100% 100%,0 100%); }
.exhibit-origin-mark i { color: #6fe9f7; font: normal 9px var(--mono); }
.exhibit-origin-copy > span,.exhibit-origin-copy > strong,.exhibit-origin-copy > q,.exhibit-origin-copy > small { display: block; }
.exhibit-origin-copy > span { color: rgba(111,233,247,.58); font: 600 6.5px var(--mono); letter-spacing: .15em; text-transform: uppercase; }
.exhibit-origin-copy > strong { margin-top: 3px; color: var(--ink-2); font-size: 11.5px; }
.exhibit-origin-copy > q { margin-top: 6px; color: var(--ink); font: 500 11px/1.4 var(--font); quotes: "“" "”"; }
.exhibit-origin-copy > small { margin-top: 5px; color: var(--ink-3); font-size: 8.5px; line-height: 1.45; }
.exhibit-origin > a { margin-top: 8px; padding: 9px 10px; border: 1px solid rgba(195,152,240,.22); border-radius: 2px; color: var(--violet-lo); font-size: 9px; text-decoration: none; white-space: nowrap; }
.exhibit-origin > a i { margin-left: 5px; font-style: normal; }
.exhibit-origin > a:hover { color: #6fe9f7; border-color: rgba(111,233,247,.34); }
@media (max-width: 720px) {
  .exhibit-origin { grid-template-columns: 42px minmax(0,1fr); }
  .exhibit-origin-mark { width: 37px; height: 47px; }
  .exhibit-origin > a { grid-column: 1 / -1; margin-top: 0; text-align: center; }
}

/* View Transitions give the "smooth flows between every part of the site"
 * from D-012 with no client router: the browser cross-fades documents, and
 * the named element morphs between grid thumbnail and card hero. */
@view-transition { navigation: auto; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: .01ms !important; transition-duration: .01ms !important; }
  @view-transition { navigation: none; }
}

/* ------------------------------------------------------------ forms & auth */

.auth-shell { max-width: 420px; margin: 6vh auto; }
.auth-form { padding: 24px; }
.auth-form .finput { min-height: 48px; font-size: 16px; }
.auth-form .btn,.oauth-btn { min-height: 48px; font-size: 16px; }

.flabel {
  display: block; font-size: 11px; text-transform: uppercase;
  letter-spacing: .09em; color: var(--ink-3); margin: 14px 0 6px;
}
.flabel:first-child { margin-top: 0; }

.finput {
  width: 100%; padding: 11px 14px; border-radius: var(--r-sm);
  border: 1px solid var(--glass-edge);
  background: rgba(255,255,255,.045);
  color: var(--ink); font-family: var(--font); font-size: 14px;
  transition: border-color .16s ease, background .16s ease;
}
.finput:focus {
  outline: none; border-color: rgba(155,93,229,.6); background: rgba(255,255,255,.07);
}

.btn {
  width: 100%; margin-top: 22px; padding: 12px 18px;
  border: none; border-radius: var(--r-sm); cursor: pointer;
  font-family: var(--font); font-size: 14px; font-weight: 600; color: #fff;
  background: linear-gradient(180deg, var(--violet), #7B44C9);
  box-shadow: 0 6px 20px rgba(155,93,229,.28);
  transition: transform .14s ease, box-shadow .14s ease, filter .14s ease;
}
.btn:hover { filter: brightness(1.08); box-shadow: 0 8px 26px rgba(155,93,229,.4); }
.btn:active { transform: translateY(1px); }

.btn-sm {
  padding: 7px 14px; border-radius: 999px;
  background: var(--violet-dim); border: 1px solid rgba(155,93,229,.3);
  color: var(--violet-lo); font-size: 12px; font-weight: 600;
}
.btn-sm:hover { background: rgba(155,93,229,.22); color: var(--ink); }

/* The considered second action next to a primary one: "Filter", "Browse the
 * catalogue". Was referenced by three templates and had never been defined, so
 * it rendered as the browser's default grey button on a dark ground. */
.btn-secondary {
  padding: 8px 16px; border-radius: var(--r-sm); cursor: pointer;
  background: var(--violet-dim); border: 1px solid rgba(155,93,229,.3);
  color: var(--violet-lo);
  font-family: var(--font); font-size: 13px; font-weight: 600;
  text-decoration: none; display: inline-flex; align-items: center;
  transition: background .14s ease, color .14s ease, border-color .14s ease;
}
.btn-secondary:hover {
  background: rgba(155,93,229,.22); color: var(--ink);
  border-color: rgba(155,93,229,.5);
}
.btn-secondary:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }

/* The quiet control: add, remove, sign out. Present but never competing with
 * the card art for attention. */
.btn-ghost {
  background: transparent; border: 1px solid var(--glass-edge);
  color: var(--ink-2); border-radius: 8px;
  width: 26px; height: 26px; line-height: 1; font-size: 15px;
  cursor: pointer; font-family: var(--font);
  display: inline-flex; align-items: center; justify-content: center;
  transition: border-color .14s ease, color .14s ease, background .14s ease;
}
.btn-ghost:hover {
  border-color: rgba(155,93,229,.5); color: var(--violet-lo);
  background: var(--violet-dim);
}
.navlinks .btn-ghost { width: auto; padding: 0 10px; height: 24px; font-size: 12px; }

.link { color: var(--violet-lo); }
.link:hover { text-decoration: underline; }

/* --------------------------------------------------------------- portfolio */

.pf-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 20px; }

.bar {
  display: flex; height: 10px; border-radius: 999px; overflow: hidden;
  background: rgba(255,255,255,.05);
}
.bar .seg { display: block; height: 100%; }

.legend { margin-top: 16px; display: flex; flex-direction: column; gap: 9px; }
.legend .lg { display: flex; align-items: center; gap: 9px; font-size: 13px; }
.legend .dot { width: 9px; height: 9px; border-radius: 999px; flex: none; }

/* ----------------------------------------------------------- set completion

   A set page browsed by a signed-in collector is a checklist they can see
   themselves in. Cards they hold come forward — full colour, a check, and a
   ring in the card's own accent; the rest fall back rather than disappear, so
   the gaps in a run read at a glance without hiding what is missing.

   The dimming is deliberately partial. Fully hiding unowned cards would make
   the page useless for deciding what to chase next, which is the whole point. */

.dex-head {
  display: flex; align-items: center; gap: 20px;
  padding: 16px 22px; margin-bottom: 26px;
}
.dex-figures { flex: none; }
.dex-count { font-family: var(--mono); font-size: 22px; font-weight: 600; letter-spacing: -.02em; }
.dex-owned { color: var(--violet-lo); }
.dex-of { color: var(--ink-3); }
.dex-track {
  flex: 1; height: 8px; border-radius: 999px;
  background: rgba(255,255,255,.06); overflow: hidden;
}
.dex-fill {
  display: block; height: 100%; border-radius: 999px;
  background: linear-gradient(90deg, var(--violet), var(--violet-lo));
  transition: width .5s cubic-bezier(.2,.7,.3,1);
}
.dex-fill.full { background: linear-gradient(90deg, var(--up), #A7F3D0); }
.dex-pct { font-family: var(--mono); font-size: 15px; color: var(--ink-2); flex: none; }

.card-tile.unowned .frame { border-color: rgba(255,255,255,.05); }
.card-tile.unowned img {
  filter: grayscale(.85) brightness(.42) contrast(.9);
  transition: filter .22s ease;
}
.card-tile.unowned .label,
.card-tile.unowned .meta { color: var(--ink-3); }
/* Hovering restores it: you are looking at a card you might buy, and judging
   it through a grey filter is not possible. */
.card-tile.unowned:hover img { filter: grayscale(.15) brightness(.85); }

.card-tile.owned .frame {
  border-color: color-mix(in srgb, var(--tint) 55%, transparent);
  box-shadow: 0 0 0 1px color-mix(in srgb, var(--tint) 30%, transparent),
              0 6px 26px color-mix(in srgb, var(--tint) 26%, transparent);
}
.card-tile.owned:hover .frame {
  box-shadow: 0 0 0 1px var(--tint),
              0 14px 42px color-mix(in srgb, var(--tint) 40%, transparent);
}
.card-tile .check {
  position: absolute; top: 7px; right: 7px; z-index: 2;
  width: 20px; height: 20px; border-radius: 999px;
  display: grid; place-items: center;
  font-size: 11px; font-weight: 700; line-height: 1;
  color: #0E0714; background: var(--up);
  box-shadow: 0 2px 8px rgba(0,0,0,.45);
}
.owned-n { color: var(--up) !important; }

/* ------------------------------------------------------------ social login

   Provider buttons keep each brand's own mark and colour rather than being
   flattened into the violet. A Google button that does not look like Google's
   is a button people hesitate over, and hesitation on a login screen is the
   most expensive kind. */

.oauth-stack { display: flex; flex-direction: column; gap: 10px; margin-bottom: 18px; }
.oauth-btn {
  display: flex; align-items: center; justify-content: center; gap: 10px;
  padding: 12px 16px; border-radius: var(--r-sm);
  border: 1px solid var(--glass-edge);
  background: rgba(255,255,255,.055);
  font-size: 14px; font-weight: 500; color: var(--ink);
  transition: background .16s ease, border-color .16s ease, transform .1s ease;
}
.oauth-btn:hover { background: rgba(255,255,255,.09); border-color: rgba(255,255,255,.16); }
.oauth-btn:active { transform: translateY(1px); }
.oauth-btn svg { flex: none; }

@media (max-width: 520px) {
  .auth-shell { margin: var(--sp-5) auto; }
  .auth-form { padding: var(--sp-4); }
}

.or {
  display: flex; align-items: center; gap: 14px;
  margin: 4px 0 18px; color: var(--ink-3); font-size: 11.5px;
  text-transform: uppercase; letter-spacing: .09em;
}
.or::before, .or::after {
  content: ""; flex: 1; height: 1px; background: var(--glass-edge);
}

/* ------------------------------------------------------------------- legal

   Long-form prose, which nothing else on the site is. Narrower measure and
   more line height than the app chrome: these pages are read, not scanned. */

.legal { max-width: 68ch; margin: 0 auto; }
.legal p, .legal li, .legal dd { line-height: 1.75; color: var(--ink-2); font-size: 15px; }
.legal h2 {
  font-size: 16px; margin: 38px 0 12px; color: var(--ink);
  padding-bottom: 8px; border-bottom: 1px solid var(--glass-edge);
}
.legal dl { margin: 0; }
.legal dt {
  font-size: 11px; text-transform: uppercase; letter-spacing: .09em;
  color: var(--violet-lo); margin-top: 20px;
}
.legal dd { margin: 6px 0 0; }
.legal ul { margin: 12px 0 0; }
.legal strong, .legal em { color: var(--ink); }

.footer {
  margin-top: 88px; padding-top: 20px;
  border-top: 1px solid var(--glass-edge);
  display: flex; gap: 12px; align-items: center; flex-wrap: wrap;
  font-size: var(--fs-small); color: var(--ink-3);
}
.footer a {
  display: inline-flex; align-items: center; min-height: 44px;
  padding: 0 4px; color: inherit;
}
.footer a:hover { color: var(--ink); }

/* ------------------------------------------------------- valuation honesty

   A total is only as good as the prices behind it. These say where it is
   soft, in the same place the number is shown, rather than in a footnote
   nobody scrolls to. */

.valuation-caveat {
  margin-top: 14px; padding: 12px 15px;
  border-radius: var(--r-sm);
  background: rgba(232,180,95,.07);
  border: 1px solid rgba(232,180,95,.22);
  font-size: 12.5px; line-height: 1.6; color: var(--ink-2);
}
.valuation-caveat strong { color: #E8B45F; }
.warn-dot {
  display: inline-block; width: 6px; height: 6px; border-radius: 999px;
  background: #E8B45F; margin-right: 7px; vertical-align: middle;
}
.approx {
  color: #E8B45F; font-family: var(--mono); cursor: help; margin-left: 3px;
}

/* ------------------------------------------------------- search set chips */

.setrow { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 16px; }
.setchip {
  display: inline-flex; align-items: baseline; gap: 8px;
  padding: 7px 14px; border-radius: 10px; text-decoration: none;
  color: inherit; font-size: 13.5px;
  border: 1px solid rgba(255, 255, 255, .09); background: rgba(0, 0, 0, .18);
}
.setchip:hover { border-color: rgba(255, 255, 255, .22); }

/* ------------------------------------------------------- valuation modes */

/* A segmented control of links, not buttons: the basis is part of the URL, so
   a mode is shareable and survives the back button like every other view. */
.mode-switch {
  display: inline-flex; gap: 2px; margin: 12px 0 4px; padding: 3px;
  border: 1px solid rgba(255, 255, 255, .09); border-radius: 10px;
  background: rgba(0, 0, 0, .18);
}
.mode-switch a {
  padding: 4px 12px; border-radius: 7px; font-size: 12.5px;
  color: inherit; text-decoration: none; opacity: .72;
}
.mode-switch a:hover { opacity: 1; }
.mode-switch a.on { background: rgba(255, 255, 255, .10); opacity: 1; font-weight: 600; }

/* --------------------------------------------------------- add-with-paid */

/* <details> again, same reasoning as the portfolio editor: no JavaScript, and
   only the open popover carries a form. */
.addpop { position: relative; display: inline-block; }
.addpop > summary { list-style: none; cursor: pointer; }
.addpop > summary::-webkit-details-marker { display: none; }
.addpop .addform {
  position: absolute; right: 0; top: calc(100% + 6px); z-index: 40;
  width: 240px; padding: 14px; display: grid; gap: 9px; text-align: left;
}
.addpop .addform label { display: grid; gap: 4px; font-size: 12px; }

/* ------------------------------------------------------- missing-card box */

.missingbox { padding: 12px 20px; margin-top: 16px; }
.missingbox > summary { cursor: pointer; font-size: 14px; }
.missingform { display: flex; gap: 8px; margin-top: 10px; flex-wrap: wrap; }
.missingform input[type=text] { flex: 1; min-width: 220px; }

/* Search's first visit is a useful route into the catalogue or the camera. */
.search-start { max-width: 850px; padding: var(--s-6); }
.search-start h2 { margin: 0 0 var(--s-2); font-size: clamp(20px, 3vw, 28px); }
.search-start > p { margin: 0 0 var(--s-5); color: var(--ink-2); line-height: 1.5; }
.search-examples { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-3); padding: 0; margin: 0 0 var(--s-5); list-style: none; }
.search-examples li { display: grid; gap: var(--s-2); min-width: 0; }
.search-examples span { color: var(--ink-3); font: 11px var(--mono); }
.search-examples a { display: block; padding: var(--s-3); border: 1px solid var(--glass-edge);
  border-radius: var(--r-sm); color: var(--ink); overflow-wrap: anywhere; }
.search-examples a:hover, .search-examples a:focus-visible { border-color: var(--violet);
  background: var(--violet-dim); outline-offset: 2px; }
.search-scan-path { display: flex; align-items: center; justify-content: space-between;
  gap: var(--s-4); padding-top: var(--s-4); border-top: 1px solid var(--glass-edge); }
.search-scan-path strong, .search-scan-path span { display: block; }
.search-scan-path strong { color: var(--ink); }
.search-scan-path div span { margin-top: var(--s-1); color: var(--ink-2); font-size: 13px; }
.search-scan-path .btn-secondary { flex: none; gap: var(--s-2); }
.search-no-match-scan { max-width: 56ch; margin: var(--s-4) auto 0; line-height: 1.5; }
.search-no-match-scan a { color: var(--violet-lo); text-decoration: underline; }
@media (max-width: 640px) {
  .search-start { padding: var(--s-5); }
  .search-examples { grid-template-columns: 1fr; }
  .search-scan-path { align-items: stretch; flex-direction: column; }
  .search-scan-path .btn-secondary { justify-content: center; min-height: 44px; }
}

/* ------------------------------------------------------------ most valuable */

.top-rail {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 16px;
}
.top-card { display: block; }
.top-frame {
  position: relative; aspect-ratio: 733/1024;
  border-radius: var(--r-sm); overflow: hidden;
  background: var(--ground-1); border: 1px solid var(--glass-edge);
  transition: transform .22s cubic-bezier(.2,.7,.3,1), box-shadow .22s ease;
}
.top-card:hover .top-frame {
  transform: translateY(-4px);
  box-shadow: 0 14px 38px rgba(0,0,0,.5),
              0 0 0 1px color-mix(in srgb, var(--tint) 45%, transparent);
}
.top-frame img { width: 100%; height: 100%; object-fit: cover; display: block; }
.top-name { margin-top: 9px; font-size: 12.5px; line-height: 1.35; }
.top-meta { font-size: 12px; color: var(--ink-3); margin-top: 2px; }
.top-value {
  font-family: var(--mono); font-size: 14px; margin-top: 5px;
  color: var(--violet-lo);
}

.grade-chip {
  position: absolute; top: 7px; left: 7px; z-index: 2;
  font-family: var(--mono); font-size: 9.5px; font-weight: 600;
  letter-spacing: .04em; padding: 3px 7px; border-radius: 5px;
  background: rgba(14,7,20,.86); color: #E8B45F;
  border: 1px solid rgba(232,180,95,.4);
}
.grade-chip.inline {
  position: static; margin-left: 6px; font-size: 9.5px;
  display: inline-block; vertical-align: middle;
}

/* Portfolio filter controls live beside the Collection paging/card styles.
   Sortable headings and filtered-result copy are shared below. */

.filtered-note {
  font-size: 13px;
  color: var(--ink-2);
  margin: -4px 0 12px;
  padding-left: 2px;
}
.filtered-note strong { color: var(--ink); font-weight: 600; }

/* A sortable heading is a link, so it must look like one you can press without
   shouting over the data underneath it. */
.sortcol {
  color: inherit;
  text-decoration: none;
  display: inline-flex;
  align-items: baseline;
  gap: 3px;
  border-radius: 4px;
}
.sortcol:hover { color: var(--ink); }
.sortcol.on { color: var(--violet-lo); }
.sortcol .caret { font-size: 9px; line-height: 1; }
.sortcol:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }

/* Movement columns. Same greens and reds as everywhere else money moves. */
table.holdings td.num.up   { color: var(--up); }
table.holdings td.num.down { color: var(--down); }

/* ------------------------------------------------- portfolio: editing a row
 *
 * Every mistake an import can make — the wrong grade, a missing cost, a
 * quantity typed twice — is fixable here. Until this existed the only remedy
 * was to delete the row and import the file again.
 *
 * A <details> disclosure rather than a modal: no JavaScript, the row keeps its
 * place in the table, and find-in-page still reaches the contents.
 */
.stepper { display: inline-flex; align-items: center; gap: 6px; }
.stepper .n { min-width: 1.6em; text-align: center;
              font-variant-numeric: tabular-nums; }
.step {
  width: 20px; height: 20px; line-height: 1; padding: 0;
  border: 1px solid var(--glass-edge); border-radius: 6px;
  background: transparent; color: var(--ink-2);
  font-family: var(--font); font-size: 13px; cursor: pointer;
  display: inline-flex; align-items: center; justify-content: center;
  transition: border-color .14s ease, color .14s ease, background .14s ease;
}
.step:hover:not(:disabled) {
  border-color: rgba(155,93,229,.5); color: var(--violet-lo);
  background: var(--violet-dim);
}
/* At one, "one fewer" would mean delete — which is a different decision and
   lives behind the edit disclosure where it can say so. */
.step:disabled { opacity: .3; cursor: default; }
.step:focus-visible { outline: 2px solid var(--violet); outline-offset: 1px; }

.edit.open { position: relative; }
.editlink {
  cursor: pointer; font-size: 12px; text-decoration: none;
  color: var(--ink-3); padding: 3px 8px; border-radius: 6px;
  border: 1px solid transparent; display: inline-block;
}
.editlink:hover { color: var(--violet-lo); border-color: var(--glass-edge); }
.editlink:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }
.editlink.on { color: var(--violet-lo); border-color: var(--glass-edge); }
/* The row being edited stays findable while the panel is open over it. */
tr.editing > td { background: var(--violet-dim); }

.editform, .editremove {
  position: absolute; right: 0; z-index: 5;
  width: min(420px, 82vw);
  background: var(--ground-1); border: 1px solid var(--glass-edge);
  box-shadow: var(--shadow);
}
.editform {
  top: calc(100% + 6px);
  border-radius: var(--r-sm) var(--r-sm) 0 0; border-bottom: none;
  padding: 14px; display: grid; grid-template-columns: 1fr 1fr; gap: 10px;
  text-align: left;
}
.editremove {
  top: calc(100% + 6px); margin-top: 176px;
  border-radius: 0 0 var(--r-sm) var(--r-sm);
  padding: 0 14px 12px;
}
.editform label, .editremove {
  font-size: 11px; text-transform: uppercase; letter-spacing: .07em;
  color: var(--ink-3);
}
.editform label { display: flex; flex-direction: column; gap: 4px; }
.editform label.wide, .editform .editactions { grid-column: 1 / -1; }
.editform input, .editform select {
  background: var(--ground-0); border: 1px solid var(--glass-edge);
  border-radius: 7px; color: var(--ink);
  font-family: var(--font); font-size: 13px; text-transform: none;
  letter-spacing: normal; padding: 7px 9px; width: 100%;
}
.editform input:focus-visible, .editform select:focus-visible {
  outline: 2px solid var(--violet); outline-offset: 1px;
}
.editform .editactions { display: flex; justify-content: flex-end; }
.btn-ghost.wide {
  width: 100%; height: auto; padding: 7px 10px; font-size: 12px;
  text-transform: none; letter-spacing: normal;
}
.btn-ghost.wide:hover { border-color: var(--down); color: var(--down); background: transparent; }

@media (max-width: 640px) {
  .editform { grid-template-columns: 1fr; }
  .editremove { margin-top: 0; position: static; width: auto; border: none;
                padding: 8px 0 0; box-shadow: none; background: transparent; }
  .editform { position: static; width: auto; border-radius: var(--r-sm);
              border-bottom: 1px solid var(--glass-edge); }
}

/* ------------------------------------------------------------ coming soon
 *
 * TCGCSV lists a set the moment it is announced, so we have held these dates
 * since the first ingest with nothing showing them. Knowing what drops next
 * week is half of why a collector opens an app.
 */
.soonrail { display: flex; gap: 10px; overflow-x: auto; padding-bottom: 4px; }
.soonitem {
  flex: 0 0 auto; width: 190px; text-decoration: none; color: inherit;
  background: var(--glass-bg); border: 1px solid var(--glass-edge);
  border-radius: var(--r-sm); padding: 12px 14px;
  transition: border-color .14s ease, transform .14s ease;
}
.soonitem:hover { border-color: rgba(94,231,247,.4); transform: translateY(-1px); }
.soonitem:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }
/* The live cyan, not the price green: this is "happening soon", not "worth
   more than yesterday", and the palette already draws that line. */
.soonitem .when {
  font-family: var(--mono, "JetBrains Mono", monospace); font-size: 11px;
  letter-spacing: .08em; text-transform: uppercase; color: var(--live);
}
.soonitem .what {
  font-size: 13.5px; font-weight: 600; margin: 5px 0 3px; line-height: 1.25;
}

.dropday {
  display: grid; grid-template-columns: 86px 1fr; gap: 18px;
  padding: 18px 0; border-bottom: 1px solid var(--glass-edge);
}
.dropday:last-child { border-bottom: none; }
.dropdate { text-align: center; padding-top: 4px; }
.dropdate .d { font-size: 30px; font-weight: 700; line-height: 1;
               letter-spacing: -.03em; font-variant-numeric: tabular-nums; }
.dropdate .m { font-size: 12px; text-transform: uppercase; letter-spacing: .12em;
               color: var(--live); margin-top: 3px; }
.dropdate .y { font-size: 11px; color: var(--ink-3); margin-top: 1px; }
.dropdate .away { font-size: 10.5px; color: var(--ink-3); margin-top: 7px;
                  text-transform: uppercase; letter-spacing: .06em; }

.droplist { display: flex; flex-direction: column; gap: 8px; min-width: 0; }
.drop {
  display: flex; align-items: center; justify-content: space-between; gap: 14px;
  text-decoration: none; color: inherit;
  background: var(--glass-bg); border: 1px solid var(--glass-edge);
  border-radius: var(--r-sm); padding: 12px 15px;
  transition: border-color .14s ease;
}
.drop:hover { border-color: rgba(155,93,229,.45); }
.drop:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }
.dropmain { min-width: 0; }
.dropname { font-size: 14.5px; font-weight: 600; }
.dropcards { font-size: 12px; color: var(--ink-2); text-align: right;
             white-space: nowrap; }

@media (max-width: 560px) {
  .dropday { grid-template-columns: 62px 1fr; gap: 12px; }
  .dropdate .d { font-size: 24px; }
  .drop { flex-direction: column; align-items: flex-start; gap: 6px; }
  .dropcards { text-align: left; }
}

/* --------------------------------------------------------- set completion
 *
 * The most repeated feature across the twenty-four apps surveyed — nine ship it
 * and several exist only for it. A bar rather than a number because "nearly
 * done" is the thing being read, and a percentage alone does not say it.
 */
.setgrid { display: grid; gap: 10px;
           grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); }
.setcard {
  text-decoration: none; color: inherit;
  background: var(--glass-bg); border: 1px solid var(--glass-edge);
  border-radius: var(--r-sm); padding: 14px 16px;
  display: flex; flex-direction: column; gap: 4px;
  transition: border-color .14s ease, transform .14s ease;
}
.setcard:hover { border-color: rgba(155,93,229,.45); transform: translateY(-1px); }
.setcard:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }
.setname { font-size: 14px; font-weight: 600; line-height: 1.25; }
.setbar { height: 6px; border-radius: 3px; background: rgba(255,255,255,.07);
          overflow: hidden; margin: 8px 0 6px; }
.setbar i { display: block; height: 100%; border-radius: 3px;
            background: linear-gradient(90deg, var(--violet), var(--violet-lo)); }
.setfoot { display: flex; align-items: baseline; justify-content: space-between;
           gap: 8px; font-size: 12px; color: var(--ink-2); }
.setpct { font-family: var(--mono, "JetBrains Mono", monospace);
          font-variant-numeric: tabular-nums; color: var(--ink); font-weight: 600; }
.setpct .of { color: var(--ink-3); font-weight: 400; }
.setcost { text-align: right; }

/* ═══════════════════════════════════════════════════════ material
 *
 * The palette and the blur could belong to any dashboard. Nothing in them came
 * from cards, which is the piece that was missing.
 *
 * Cards are physical objects and three facts about them are worth borrowing:
 * foil catches light along an angle, card stock has tooth rather than gloss,
 * and every card already carries its own colour. We store all three — the
 * printing variant, and accent_rgb captured during hashing — so the treatment
 * below is driven by what a card *is*. A card that shimmers is a foil card.
 * That is the whole discipline here: this is information, not decoration, and
 * the moment it stops being true it should come out.
 */

/* ---------------------------------------------------------------- 1. grain
 *
 * A flat #0E0714 reads as a screen. Real dark surfaces have noise in them, and
 * two percent of it is the difference between "dark UI" and "unlit room". An
 * inline SVG turbulence rather than an image: no request, no asset to lose.
 */
body::before {
  content: ""; position: fixed; inset: 0; z-index: 0; pointer-events: none;
  opacity: .035; mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}
/* Everything the page draws sits above the grain. */
.nav, .wrap, .landing-shell { position: relative; z-index: 1; }

/* ------------------------------------------------------ 2. the treatments
 *
 * A card's surface, by what the card actually is. The ladder is the hobby's own
 * — one-of-one, numbered, autograph, relic, rainbow, holo, reverse, foil, base.
 *
 * The rule that shapes all of it, and it is the opposite of the obvious one:
 * **the rarer the card, the quieter and slower.** Cheap things flash; expensive
 * things glow. A one-of-one that strobed would look like a slot machine, and a
 * base common doing nothing at all beside it is the contrast that makes the
 * ladder legible at a glance.
 */
.sheen { position: relative; isolation: isolate; }
.sheen::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  border-radius: inherit; z-index: 2;
  opacity: 0; transition: opacity .3s ease;
  background-size: 260% 260%; background-position: 100% 0;
  background: linear-gradient(105deg, transparent 32%,
    rgba(255,255,255,.16) 45%,
    color-mix(in srgb, var(--tint, #9B5DE5) 55%, white) 50%,
    rgba(255,255,255,.16) 55%, transparent 68%);
}
.sheen:hover::after, .sheen:focus-visible::after { opacity: .85; }
.sheen:hover::after, .sheen:focus-visible::after { animation: sheen-sweep 1.15s ease-out; }
@keyframes sheen-sweep {
  from { background-position: 100% 0; }
  to   { background-position: 0 100%; }
}

/* Rainbow foil earns the widest spectrum of the print treatments. */
.tier-rainbow::after {
  background: linear-gradient(105deg, transparent 24%,
    rgba(94,231,247,.40) 36%, rgba(155,93,229,.40) 44%,
    rgba(255,120,200,.40) 52%, rgba(255,214,120,.36) 60%,
    rgba(122,255,190,.34) 66%, transparent 78%);
  background-size: 280% 280%; background-position: 100% 0;
}
/* Holo: the spectrum, with the card's own colour through the middle so a red
   card flashes warm. */
.tier-holo::after {
  background: linear-gradient(105deg, transparent 30%,
    rgba(94,231,247,.34) 40%,
    color-mix(in srgb, var(--tint, #9B5DE5) 60%, white) 48%,
    rgba(255,120,200,.34) 56%, rgba(255,214,120,.28) 62%, transparent 72%);
  background-size: 260% 260%; background-position: 100% 0;
}
/* A reverse holo shimmers on the border rather than the art: softer, wider. */
.tier-reverse::after {
  background: linear-gradient(100deg, transparent 26%, rgba(255,255,255,.13) 44%,
    color-mix(in srgb, var(--tint, #9B5DE5) 40%, white) 50%,
    rgba(255,255,255,.13) 56%, transparent 74%);
  background-size: 300% 300%; background-position: 100% 0;
}

/* ---- above the print treatments: what makes a card scarce, not shiny ---- */

/* A signature is ink on a surface — one warm stroke travelling slowly, not a
   spectrum. It is a different thing from foil and should not borrow its look. */
.tier-auto::after {
  background: linear-gradient(115deg, transparent 34%,
    rgba(255,232,180,.22) 46%, rgba(255,246,224,.55) 50%,
    rgba(255,232,180,.22) 54%, transparent 66%);
  background-size: 240% 240%; background-position: 100% 0;
}
.tier-auto:hover::after, .tier-auto:focus-visible::after { animation-duration: 1.7s; }

/* A relic is cloth or a shard behind a window: matte, no rainbow, barely a
   highlight. The restraint is the point — it reads as material, not light. */
.tier-relic::after {
  background: linear-gradient(100deg, transparent 38%,
    rgba(255,255,255,.10) 48%, rgba(255,255,255,.16) 52%,
    rgba(255,255,255,.10) 56%, transparent 64%);
  background-size: 220% 220%; background-position: 100% 0;
}
.tier-relic:hover::after, .tier-relic:focus-visible::after {
  animation-duration: 2s; opacity: .6;
}

/* Numbered: an edge that holds light rather than a sweep across the face. The
   scarcer band gets a warmer, brighter edge and nothing else — scarcity is a
   fact about the print run, so it belongs on the boundary of the object. */
.tier-numbered { box-shadow: 0 0 0 1px rgba(94,231,247,.30) inset; }
.tier-numbered::after {
  background: linear-gradient(105deg, transparent 36%,
    rgba(94,231,247,.20) 48%, rgba(214,247,255,.42) 52%,
    rgba(94,231,247,.20) 56%, transparent 66%);
  background-size: 250% 250%; background-position: 100% 0;
}
.tier-numbered.scarce { box-shadow: 0 0 0 1px rgba(255,214,120,.45) inset; }
.tier-numbered.scarce::after {
  background: linear-gradient(105deg, transparent 36%,
    rgba(255,214,120,.24) 48%, rgba(255,246,224,.5) 52%,
    rgba(255,214,120,.24) 56%, transparent 66%);
  background-size: 250% 250%; background-position: 100% 0;
}
.tier-numbered:hover::after, .tier-numbered:focus-visible::after { animation-duration: 1.6s; }

/* One of one. The quietest treatment in the system, and the only one that is
   visible without being touched — a slow breath rather than a sweep, because
   the whole claim is that it does not need to compete for attention. */
.tier-oneofone { box-shadow: 0 0 0 1px rgba(255,246,224,.55) inset,
                             0 0 22px -6px rgba(255,214,120,.5); }
.tier-oneofone::after {
  opacity: .30;
  background: radial-gradient(120% 90% at 50% 0%,
    rgba(255,246,224,.22) 0%, transparent 62%);
  background-size: auto; background-position: center;
  animation: oneofone-breathe 5.5s ease-in-out infinite;
}
.tier-oneofone:hover::after, .tier-oneofone:focus-visible::after {
  opacity: .5; animation: oneofone-breathe 5.5s ease-in-out infinite;
}
@keyframes oneofone-breathe {
  0%, 100% { opacity: .22; }
  50%      { opacity: .44; }
}

/* Motion is the decoration; the treatment is the information. When motion is
   unwelcome the surface stays and stops travelling. */
@media (prefers-reduced-motion: reduce) {
  .sheen::after { background-position: 42% 30%; }
  .sheen:hover::after, .sheen:focus-visible::after { animation: none; opacity: .5; }
  .tier-oneofone::after { animation: none; opacity: .3; }
}

/* ------------------------------------------------- 3. the card's own colour
 *
 * accent_rgb is computed for 569,329 cards during hashing and has only ever
 * been a tile tint. On a card page it can do more: the page takes its ambient
 * light from the card being looked at, so a Charizard page burns warm and a
 * Blastoise page runs cold. Dynamic branding in the literal sense — the
 * identity is the artwork's, and the violet is only the fallback.
 */
.card-page { position: relative; }
.card-page::before {
  content: ""; position: absolute; z-index: -1; pointer-events: none;
  left: 50%; top: -140px; transform: translateX(-50%);
  width: min(1100px, 100%); height: 520px;
  background: radial-gradient(
    ellipse 60% 50% at 50% 40%,
    color-mix(in srgb, var(--tint, #9B5DE5) 26%, transparent) 0%,
    transparent 70%);
  filter: blur(28px);
}

/* The hero picks up the same light along its top edge, the way a card under a
   lamp does. */
.card-hero { position: relative; }
.card-hero::before {
  content: ""; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  border-radius: inherit;
  background: linear-gradient(
    180deg,
    color-mix(in srgb, var(--tint, #9B5DE5) 16%, transparent) 0%,
    transparent 28%);
}

/* ------------------------------------------------------------- 4. the slab
 *
 * A graded card is a different physical object: a card inside a sealed case
 * with a label across the top. Rendering it as a bare rectangle throws away the
 * one thing its owner paid for.
 */
.slab {
  position: relative; border-radius: 10px;
  padding: 6px; background: linear-gradient(160deg,
    rgba(255,255,255,.09), rgba(255,255,255,.02) 40%, rgba(255,255,255,.06));
  border: 1px solid rgba(255,255,255,.14);
  box-shadow: 0 1px 0 rgba(255,255,255,.12) inset, 0 10px 30px rgba(0,0,0,.5);
}
.slab > * { border-radius: 6px; overflow: hidden; display: block; }
.slab .slab-label {
  font-family: var(--mono); font-size: 9px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-2);
  padding: 3px 6px 5px; text-align: center; border-radius: 0;
}

/* The slab label sits above the card, the way a real one does, so the frame
   stacks rather than overlaying a chip on the art. */
.top-frame.slab { display: flex; flex-direction: column; gap: 4px; }
.top-frame.slab .slab-label {
  font-family: var(--mono); font-size: 9px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-2);
  text-align: center; padding: 2px 0 3px;
  border-bottom: 1px solid rgba(255,255,255,.09);
}
.top-frame.slab img { border-radius: 5px; }

/* ---------------------------------------------------------------- wishlist */
.rowactions { display: flex; gap: 5px; justify-content: flex-end; }
.rowactions form { display: inline; }
/* A filled star is on the list. The colour is the live cyan rather than the
   price green — wanting a card is not the same as it being worth more. */
.btn-ghost.wished { color: var(--live); border-color: rgba(94,231,247,.4); }
.btn-ghost.wished:hover { color: var(--down); border-color: rgba(248,113,113,.5); }

tr.at-target > td { background: rgba(74,222,128,.07); }
tr.at-target > td:first-child { box-shadow: inset 2px 0 0 var(--up); }

/* ------------------------------------------------------------ grade ladder
 *
 * A card's whole graded market in one grid: grade down the side, grading company
 * across the top. One card in the catalogue carries 28 of these — PSA 4 through
 * BGS 10 across six companies — and the page used to show a single ungraded
 * figure and discard the rest.
 *
 * Each cell carries a bar as well as a number. The bar is scaled to the dearest
 * grade on the card, so the shape of the ladder is readable before any figure is
 * — and the jump from a 9 to a 10 is the shape collectors are looking for.
 */
.laddwrap { overflow-x: auto; margin: 4px -4px 0; padding: 0 4px; }
table.ladder { border-collapse: collapse; width: 100%; font-size: 12.5px; }
table.ladder th {
  font-family: var(--mono); font-size: 10px; font-weight: 500;
  text-transform: uppercase; letter-spacing: .09em; color: var(--ink-3);
  padding: 4px 8px; text-align: center; white-space: nowrap;
}
table.ladder th.gv {
  text-align: right; font-size: 13px; color: var(--ink);
  letter-spacing: 0; text-transform: none; width: 2.6em;
  font-variant-numeric: tabular-nums;
}
.gcell {
  position: relative; padding: 5px 8px; text-align: right;
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  border-top: 1px solid rgba(255,255,255,.05); min-width: 74px;
}
.gcell .bar {
  position: absolute; left: 3px; right: 3px; bottom: 2px; height: 2px;
  border-radius: 2px; background: var(--violet-dim); overflow: hidden;
}
.gcell .bar::after {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: var(--w);
  background: linear-gradient(90deg, var(--violet), var(--violet-lo));
  border-radius: 2px;
}
.gcell .v { position: relative; }
/* A blank is "nobody sold one recently", not "worthless" — so it is a mark
   rather than a zero. */
.gcell.empty { color: var(--ink-3); opacity: .45; text-align: center; }

.ladderfoot {
  display: flex; align-items: baseline; gap: 10px;
  margin-top: 12px; padding-top: 10px;
  border-top: 1px solid var(--glass-edge); font-size: 13px; color: var(--ink-2);
}
.ladderfoot strong { color: var(--ink); font-family: var(--mono); font-size: 15px; }

/* ═══════════════════════════════════════════════════════════════ binders
 *
 * A collection rendered as the object collectors actually keep it in. The
 * correction that makes it work: a binder is a *curated list*, not the whole
 * collection — 5,000 cards at nine a page is 555 pages, and nobody puts their
 * bulk in a binder in real life either.
 *
 * Covers are drawn from four values rather than loaded as images, the same
 * trick as the slab labels: any size, always crisp, no storage, and unlocking
 * a cover is unlocking a row in a table.
 */
.binder-obj {
  position: relative; display: block; aspect-ratio: .78;
  border-radius: 4px 9px 9px 4px;
  background:
    linear-gradient(100deg, rgba(255,255,255,.10) 0 6%, transparent 6%),
    linear-gradient(145deg, var(--base), var(--accent));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.18),
              inset -1px 0 0 rgba(0,0,0,.28),
              0 10px 26px rgba(0,0,0,.34);
  transition: transform .32s cubic-bezier(.2,.8,.3,1), box-shadow .32s ease;
}
.binder-obj::before,
.binder-obj::after {
  content: ""; position: absolute; z-index: 1; pointer-events: none;
}
.binder-obj::before {
  inset: 5%; border: 1px solid color-mix(in srgb, var(--accent) 46%, var(--ink));
  border-radius: inherit; opacity: .42;
}
.binder-obj::after {
  left: 10%; right: 8%; bottom: 8%; height: 1px;
  background: color-mix(in srgb, var(--accent) 42%, transparent);
  box-shadow: 0 -3px color-mix(in srgb, var(--ground-0) 34%, transparent);
}
.binder-obj.lg { width: 100%; }
.binder-obj.sm { width: 42px; }
.binder-obj .spine {
  position: absolute; z-index: 2; left: 6%; top: 7%; bottom: 7%; width: 2px;
  background: rgba(0,0,0,.30); border-radius: 2px;
}
/* Texture is a spec value, so a canvas binder reads differently from leather
   without either of them being a picture. */
.binder-obj.tex-canvas {
  background-image:
    repeating-linear-gradient(45deg, rgba(255,255,255,.045) 0 2px, transparent 2px 4px),
    linear-gradient(145deg, var(--base), var(--accent));
}
.binder-obj.tex-smooth {
  background-image:
    linear-gradient(160deg, rgba(255,255,255,.16), transparent 45%),
    linear-gradient(145deg, var(--base), var(--accent));
}
.binder-obj .emblem {
  position: absolute; z-index: 2; left: 50%; top: 35%; translate: -50% -50%;
  width: 46%; aspect-ratio: 1;
}
.binder-cover-identity {
  position: absolute; z-index: 3; left: 13%; right: 9%; bottom: 13%;
  display: grid; gap: 4px; min-width: 0; padding: 7% 6%;
  border: 1px solid color-mix(in srgb, var(--accent) 36%, transparent);
  border-radius: 3px; background: color-mix(in srgb, var(--ground-0) 78%, transparent);
  box-shadow: inset 0 0 0 2px color-mix(in srgb, var(--ink) 5%, transparent);
}
.binder-cover-identity small {
  color: color-mix(in srgb, var(--accent) 68%, var(--ink));
  font: 650 clamp(5px,.55vw,8px)/1 var(--mono); letter-spacing: .12em; text-transform: uppercase;
}
.binder-cover-identity strong {
  overflow: hidden; color: var(--ink); font: 700 clamp(8px,1.05vw,15px)/1.05 var(--font-display);
  text-overflow: ellipsis; white-space: nowrap;
}
.binder-cover-identity img { display: block; width: 100%; height: clamp(24px,4vw,58px); object-fit: contain; }
.binder-obj.sm .binder-cover-identity { display: none; }
.binder-obj .emblem.orb {
  border-radius: 50%; border: 3px solid rgba(255,255,255,.42);
  background: linear-gradient(180deg, rgba(255,255,255,.28) 0 50%, transparent 50%);
  box-shadow: inset 0 0 0 3px rgba(0,0,0,.14);
}
.binder-obj .emblem.diamond {
  rotate: 45deg; border: 3px solid rgba(255,255,255,.42);
  background: linear-gradient(135deg, rgba(255,255,255,.26), transparent 60%);
}

.shelf { display: grid; gap: 18px;
         grid-template-columns: repeat(auto-fill, minmax(176px, 1fr)); }
.shelf-card { min-width:0; }
.shelf-item { text-decoration: none; color: inherit; display: block; }
.shelf-item:hover .binder-obj {
  transform: rotateY(-13deg) translateX(-3px) scale(1.02);
  box-shadow: inset 0 1px 0 rgba(255,255,255,.2), 0 18px 40px rgba(0,0,0,.44);
}
.shelf-item:focus-visible { outline: 2px solid var(--violet); outline-offset: 4px;
                            border-radius: var(--r-sm); }
.shelf-name { font-size: 14px; font-weight: 600; margin-top: 10px; line-height: 1.25; }
.shelf-tags { display: flex; flex-wrap: wrap; gap: 5px; margin-top: 6px; }
.shelf-card-actions { display:grid; grid-template-columns:.72fr 1.28fr; gap:6px; margin-top:10px; }
.shelf-card-actions a { min-height:38px; display:flex; align-items:center; justify-content:center; padding:7px 8px; border:1px solid var(--glass-edge); border-radius:8px; color:var(--ink-2); font-size:10.5px; font-weight:650; }
.shelf-card-actions a:last-child { border-color:color-mix(in srgb,var(--live) 34%,transparent); color:var(--live); }
.shelf-card-actions a:hover,.shelf-card-actions a:focus-visible { border-color:var(--live); background:color-mix(in srgb,var(--live) 7%,transparent); color:var(--ink); }
.tagline {
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .07em;
  text-transform: uppercase; padding: 2px 7px; border-radius: 999px;
  background: rgba(255,255,255,.06); color: var(--ink-3);
}
.tagline.pub  { color: var(--up); background: rgba(74,222,128,.12); }
.tagline.link { color: var(--live); background: rgba(94,231,247,.12); }
.tagline.paid { color: var(--violet-lo); background: var(--violet-dim); }

/* ---- the open binder ------------------------------------------------- */
.binder-page-wrap { position: relative; }
.binder-page {
  display: grid; grid-template-columns: repeat(var(--cols), 1fr); gap: 10px;
  background: var(--ground-1); border: 1px solid var(--glass-edge);
  border-radius: var(--r-md); padding: 16px;
  box-shadow: inset 0 1px 0 rgba(255,255,255,.05), var(--shadow);
}
.binder-page-no {
  font-family: var(--mono); font-size: 10px; color: var(--ink-3);
  text-align: center; margin-top: 6px; letter-spacing: .1em;
}
.pocket {
  position: relative; aspect-ratio: .715; border-radius: 7px;
  overflow: hidden; display: block; text-decoration: none;
}
/* An empty pocket is drawn rather than skipped: on a ruled binder the holes are
   the information — they are the cards still missing. */
.pocket.empty {
  border: 1px dashed var(--glass-edge);
  background: rgba(255,255,255,.02);
}
.pocket.filled { border: 1px solid rgba(255,255,255,.09);
                 background: var(--ground-0); }
.pocket img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pocket:hover { border-color: rgba(155,93,229,.5); }
.pocket:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }
.pocket-noimg {
  display: flex; align-items: center; justify-content: center; height: 100%;
  padding: 6px; font-size: 10px; color: var(--ink-3); text-align: center;
}
/* A slot holding a printing rather than a holding is a card wanted, not owned —
   dimmed, and it says so, in the same language the explore grid already uses. */
.pocket.wanted img { filter: grayscale(1); opacity: .38; }
.pocket-want, .pocket-grade, .pocket-qty {
  position: absolute; font-family: var(--mono); font-size: 8.5px;
  letter-spacing: .07em; text-transform: uppercase;
  padding: 2px 5px; border-radius: 4px; background: rgba(14,7,20,.82);
}
.pocket-want  { left: 4px; bottom: 4px; color: var(--live); }
.pocket-grade { left: 4px; top: 4px;    color: var(--violet-lo); }
.pocket-qty   { right: 4px; bottom: 4px; color: var(--ink-2); }

/* ---- forms ------------------------------------------------------------ */
.newbinder {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px; padding: 18px 20px;
}
.newbinder label { display: flex; flex-direction: column; gap: 5px;
                   font-size: 11px; text-transform: uppercase;
                   letter-spacing: .07em; color: var(--ink-3); }
.newbinder label.wide, .newbinder .wide { grid-column: 1 / -1; }
.newbinder input[type="text"], .newbinder input[type="search"], .newbinder select {
  background: var(--ground-0); border: 1px solid var(--glass-edge);
  border-radius: 8px; color: var(--ink); font-family: var(--font);
  font-size: 13px; padding: 8px 10px; text-transform: none; letter-spacing: normal;
}
.newbinder input:focus-visible, .newbinder select:focus-visible {
  outline: 2px solid var(--violet); outline-offset: 1px;
}

.coverpick { display: flex; flex-wrap: wrap; gap: 10px; }
.coveropt {
  display: flex; flex-direction: column; align-items: center; gap: 5px;
  width: 64px; cursor: pointer; text-transform: none; letter-spacing: normal;
  padding: 7px 5px; border-radius: 9px; border: 1px solid transparent;
}
.coveropt:hover { border-color: var(--glass-edge); }
.coveropt input { accent-color: var(--violet); }
.coveropt:has(input:checked) {
  border-color: rgba(155,93,229,.55); background: var(--violet-dim);
}
.covername { font-size: 9.5px; color: var(--ink-2); text-align: center; line-height: 1.2; }
/* Locked covers are shown rather than hidden: seeing what a subscription buys is
   the point, and hiding them would only make the picker look sparse. */
.coveropt.locked .binder-obj { opacity: .55; }

.sharelink {
  margin-top: 6px; width: 260px; max-width: 100%;
  background: var(--ground-0); border: 1px solid var(--glass-edge);
  border-radius: 8px; color: var(--ink-2);
  font-family: var(--mono); font-size: 11px; padding: 6px 9px;
}

@media (prefers-reduced-motion: reduce) {
  .binder-obj { transition: none; }
  .shelf-item:hover .binder-obj { transform: none; }
}
