:root{
  /* GNZCollect brand: navy "GNZ" fading to a lighter blue "Collect", white/
     light surfaces, soft gray-blue rules. Replaces the old dark parchment
     theme -- variable NAMES are unchanged from before (app.js's RARC map
     reads --ink-dim, --gold, --u, --down inline), only the color VALUES
     and their meaning flipped from dark-bg/light-text to light-bg/dark-text. */
  --bg:#F4F7FB; --surface:#FFFFFF; --surface-2:#EAF1F9; --rule:#D7E1EC;
  --ink:#1B2430; --ink-dim:#64748B;
  --navy:#1B3E72; --blue:#4A90D2;
  --gold:#B8912F; --u:#3E7A93; --up:#1E8E5A; --down:#C43E3E;
}
/* Dark mode: same token names, same brand hues, re-balanced for a dark
   ground -- --navy/--blue lighten so they still read as text/icons against
   a dark --bg, and --up/--down/--gold brighten a step so they hold their
   own next to the lighter --ink instead of looking muddy. Three ways in:
   no stored choice follows the OS (prefers-color-scheme, guarded so an
   explicit data-theme="light" always wins over a dark OS); an explicit
   choice sets data-theme via the toggle in the header/gate (see .themetoggle
   in app.js) and is remembered per browser in localStorage. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:#0E151E; --surface:#161F2A; --surface-2:#1E2A38; --rule:#2C3B4C;
    --ink:#E7EDF5; --ink-dim:#8FA0B3;
    --navy:#4F91DA; --blue:#7EC1F5;
    --gold:#D8AE55; --u:#6BB4CE; --up:#3ED18C; --down:#F0685E;
  }
}
:root[data-theme="dark"] {
  --bg:#0E151E; --surface:#161F2A; --surface-2:#1E2A38; --rule:#2C3B4C;
  --ink:#E7EDF5; --ink-dim:#8FA0B3;
  --navy:#4F91DA; --blue:#7EC1F5;
  --gold:#D8AE55; --u:#6BB4CE; --up:#3ED18C; --down:#F0685E;
}
/* The hidden attribute must beat class display rules (.gate uses display:flex). */
[hidden]{display:none !important}
*{box-sizing:border-box}
html,body{margin:0;background:var(--bg);color:var(--ink);
  font-family:Montserrat,system-ui,-apple-system,sans-serif;font-size:14px;line-height:1.45}
h1,h2,h3{font-family:Montserrat,system-ui,sans-serif;letter-spacing:0}
.mono,.num{font-family:Montserrat,sans-serif;font-variant-numeric:tabular-nums}
.wrap{max-width:1080px;margin:0 auto;padding:0 16px}
header{border-bottom:1px solid var(--rule);background:var(--surface)}
.brand{display:flex;align-items:center;gap:12px;padding:14px 0 10px;flex-wrap:wrap}
.spacer{flex:1}
.who{font-size:11.5px;color:var(--ink-dim)}
.linkbtn{background:none;border:0;color:var(--blue);cursor:pointer;font-size:12px;padding:0}
/* User-menu dropdown (username button, header). Mirrors .globalsearch's own
   position:relative-parent + position:absolute-panel pattern. */
.usermenu{position:relative}
.whobtn{cursor:pointer}
.whobtn:hover{color:var(--ink)}
.whobtn::after{content:'▾';margin-left:4px;font-size:9px;vertical-align:middle}
.usermenu-panel{position:absolute;top:calc(100% + 8px);right:0;width:240px;z-index:8;
  padding:14px;background:var(--surface);border:1px solid var(--rule);border-radius:8px;
  box-shadow:0 8px 24px rgba(15,26,43,.18)}
.usermenu-head{margin-bottom:4px}
.usermenu-name{font-weight:600;font-size:13px;color:var(--ink)}
.usermenu-sub{font-size:11px;color:var(--ink-dim);margin-top:2px}
.usermenu-divider{height:1px;background:var(--rule);margin:10px 0}
.usermenu-row{display:block;width:100%;text-align:left}
/* Dark/light toggle -- one instance in the gate, one in the app header,
   both static (never re-rendered), so app.js binds them once at module
   scope. Only one of the two icons is visible at a time; which one is
   governed by the exact same prefers-color-scheme/data-theme cascade the
   color tokens use, so the icon always matches the theme actually showing,
   with no JS needed to compute that. */
.themetoggle{display:inline-flex;align-items:center;justify-content:center;flex:none;
  width:30px;height:30px;border-radius:100px;border:1px solid var(--rule);
  background:var(--surface);color:var(--ink-dim);cursor:pointer;padding:0}
.themetoggle:hover{color:var(--ink);border-color:var(--blue)}
.themetoggle svg{width:16px;height:16px}
.themetoggle .icon-moon{display:none}
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .themetoggle .icon-sun{display:none}
  :root:not([data-theme="light"]) .themetoggle .icon-moon{display:block}
}
:root[data-theme="dark"] .themetoggle .icon-sun{display:none}
:root[data-theme="dark"] .themetoggle .icon-moon{display:block}
:root[data-theme="light"] .themetoggle .icon-sun{display:block}
:root[data-theme="light"] .themetoggle .icon-moon{display:none}
#themeToggleGate{position:absolute;top:16px;right:16px}
.cardlink{color:var(--blue);cursor:pointer}
.cardlink:hover{text-decoration:underline}
.globalsearch{position:relative;width:220px}
.globalsearch input{width:100%;font-size:12.5px;padding:6px 10px}
.globalsearch-results{position:absolute;top:calc(100% + 4px);left:0;width:340px;z-index:8;
  max-height:320px;overflow-y:auto;display:flex;flex-direction:column;gap:6px;padding:8px;
  background:var(--surface);border:1px solid var(--rule);border-radius:8px;
  box-shadow:0 8px 24px rgba(15,26,43,.18)}
.globalsearch-row{display:flex;align-items:center;gap:10px;text-align:left;background:none;
  border:1px solid transparent;border-radius:6px;padding:6px 8px;cursor:pointer;width:100%}
.globalsearch-row:hover{border-color:var(--blue);background:var(--surface-2)}
.globalsearch-owned{font-family:Montserrat,sans-serif;font-size:10.5px;color:var(--navy);
  background:var(--surface-2);border-radius:100px;padding:1px 8px;flex:none}
@media(max-width:720px){.globalsearch{display:none}}
nav{display:flex;gap:2px;overflow-x:auto}
nav button{background:none;border:0;border-bottom:2px solid transparent;color:var(--ink-dim);
  font-family:Montserrat,sans-serif;font-weight:600;font-size:12.5px;letter-spacing:.08em;text-transform:uppercase;
  padding:10px 14px;cursor:pointer;white-space:nowrap}
nav button:hover{color:var(--ink)}
nav button[aria-selected=true]{color:var(--navy);border-bottom-color:var(--blue)}
/* Admin-only eye toggle, after the last real tab -- reveals Sealed/Inventory
   inline (see renderTabs()) rather than a separate settings page for two
   tabs. Deliberately not styled like a real tab (no text label, no
   underline-on-select) so it doesn't read as a destination of its own. */
.navicon{margin-left:auto;flex:none;background:none;border:0;color:var(--ink-dim);
  cursor:pointer;padding:10px 12px;display:flex;align-items:center}
.navicon:hover{color:var(--ink)}
.navicon[aria-pressed=true]{color:var(--blue)}
.navicon svg{width:16px;height:16px}
section{padding:20px 0 60px}
.eyebrow{font-size:10.5px;letter-spacing:.16em;text-transform:uppercase;color:var(--ink-dim);margin:0 0 8px}
/* BUILD_PLAN item 07: .eyebrow was doing double duty as both a tiny kicker
   label and a section heading. Genuine kickers ("Position · priced at...")
   keep .eyebrow; anything that introduces a section of its own ("Top
   holdings", "Value over time") gets this instead -- readable at a glance
   as a heading, not a label, with no letter-spacing/uppercase to fight the
   kickers still on the same page. */
.sectionhead{font-size:15px;font-weight:500;color:var(--ink);margin:0 0 8px}
/* A sectionhead that's also a nav trigger (Dashboard's Biggest movers/
   Holdings titles -> their full, unclipped list views). Plain button reset
   plus a hover state so it still reads as clickable. */
.sectionhead-link{background:none;border:0;padding:0;font-family:inherit;text-align:left;cursor:pointer;display:block}
.sectionhead-link:hover{color:var(--blue);text-decoration:underline}
.up{color:var(--up)} .down{color:var(--down)}
.s{color:var(--ink-dim);font-size:12px}
.invgroup-h{font-size:14px;font-weight:600;margin:18px 0 4px;color:var(--navy)}
.invgroup-h:first-child{margin-top:0}
table{width:100%;border-collapse:collapse}
th{font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-dim);
  text-align:left;font-weight:500;padding:6px 8px;border-bottom:1px solid var(--rule)}
td{padding:7px 8px;border-bottom:1px solid var(--rule)}
tr:hover td{background:var(--surface-2)}
td.num,th.num{text-align:right}
.rar{display:inline-block;width:7px;height:7px;border-radius:50%;margin-right:7px;vertical-align:1px;background:#9AA3AE}
.rar.U{background:#3E7A93}.rar.R{background:var(--gold)}.rar.M{background:#C1440E}
.rar.S,.rar.L,.rar.T,.rar.P{background:#6E5E8A}
/* Scryfall set-icon SVG, shown when set_icons has a match (see
   scryfall_set_icons.py) -- roughly a third of TCGplayer's local "sets"
   won't match a real Scryfall set (The List, Secret Lair, prerelease
   buckets), so this is intentionally missing for those rows. */
.setname{display:flex;align-items:center;gap:10px;min-width:0}
.seticon{width:20px;height:20px;flex:none;opacity:.9;color:var(--ink-dim)}
/* Set art grid, shared by the Expansions and Sealed tabs' set pickers: a
   tile per set using the highest-priced card's art_crop as a background
   image, written by scryfall_set_art.py -- see that script's docstring,
   this is a proxy for a "flagship" card, not curated marketing art. */
.setgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(240px,1fr));gap:14px;margin:14px 0}
.yeargroup{border-bottom:1px solid var(--rule)}
.yeargroup>summary{list-style:none;cursor:pointer;display:flex;align-items:baseline;gap:8px;
  padding:14px 2px 6px;font-size:15px;font-weight:600}
.yeargroup>summary::-webkit-details-marker{display:none}
.yeargroup>summary::before{content:'▸';display:inline-block;color:var(--ink-dim);
  font-size:11px;transition:transform .15s}
.yeargroup[open]>summary::before{transform:rotate(90deg)}
.yeargroup .setgrid{margin-top:0}
.setcard{position:relative;aspect-ratio:16/10;border-radius:8px;overflow:hidden;
  border:1px solid var(--rule);background:var(--surface-2);cursor:pointer;padding:0;
  text-align:left;display:block;width:100%;font:inherit;color:inherit;
  box-shadow:0 1px 3px rgba(27,62,114,.08)}
.setcard:hover{border-color:var(--blue)}
.setcard .setart{position:absolute;inset:0;background-size:cover;background-position:center 25%;
  transition:transform .2s;print-color-adjust:exact;-webkit-print-color-adjust:exact}
.setcard .setart.logo-art{background-size:contain;background-position:center;background-repeat:no-repeat}
.setcard.noart .setart{background-image:linear-gradient(135deg,var(--surface-2),var(--rule))}
/* No set art on file -- the game's own card-back photo, zoomed to fill the
   tile like real set art does (cover, not letterboxed -- the small
   letterboxed version read as broken/placeholder-y). Anchored to the top of
   the photo so the crop lands on the card back's own logo/mark rather than
   its plain lower half. Second layer (the gradient) only shows through if
   the photo itself 404s -- see the inline style's own comment for why both
   have to be set together there. */
.setcard .setart.placeholder-art{background-size:cover,cover;
  background-position:top,center;background-repeat:no-repeat,no-repeat}
.setcard:hover .setart{transform:scale(1.05)}
.setcard .pct{position:absolute;top:8px;right:8px;background:rgba(15,26,43,.72);color:#fff;
  padding:3px 9px;border-radius:100px;font-family:Montserrat,sans-serif;font-size:12.5px;
  backdrop-filter:blur(2px)}
.setcard-scrim{position:absolute;left:0;right:0;bottom:0;padding:10px 12px 9px;
  background:linear-gradient(0deg,rgba(10,20,35,.9),rgba(10,20,35,.6) 65%,transparent)}
.setcard-scrim .setname{gap:7px;margin-bottom:2px}
.setcard-scrim .setname .nm{color:#fff;font-weight:600;font-size:13px;line-height:1.25;
  text-shadow:0 1px 3px rgba(0,0,0,.35)}
.setcard-scrim .meta{color:rgba(234,241,249,.85);margin-bottom:6px}
.setcard-scrim .stack{height:6px}
.setcard-scrim .seg{background:rgba(255,255,255,.28);border-color:rgba(255,255,255,.4)}
.setcard-scrim .seg span{display:none}
/* Set-detail banner: same art, shown large above the card grid when you
   open a specific set. */
.setbanner{position:relative;width:100%;aspect-ratio:16/4.5;border-radius:8px;overflow:hidden;
  background-size:cover;background-position:center 25%;background-color:var(--surface-2);
  border:1px solid var(--rule);margin-bottom:8px;
  print-color-adjust:exact;-webkit-print-color-adjust:exact}
.setbanner.logo-art{background-size:contain;background-position:center;background-repeat:no-repeat}
.setbanner.noart{background-image:linear-gradient(135deg,var(--surface-2),var(--rule))}
/* No set art on file -- same zoomed-cover treatment as .setcard's own
   .placeholder-art, top-anchored so the crop favors the card back's own
   logo/mark over its plain lower half. */
.setbanner.placeholder-art{background-size:cover,cover;
  background-position:top,center;background-repeat:no-repeat,no-repeat}
.setbanner-scrim{position:absolute;inset:0;display:flex;align-items:flex-end;
  background:linear-gradient(0deg,rgba(10,20,35,.82),transparent 70%);padding:12px 18px;
  print-color-adjust:exact;-webkit-print-color-adjust:exact}
.setbanner-scrim h2{margin:0;font-size:23px;color:#fff;text-shadow:0 1px 4px rgba(0,0,0,.45)}
.meta,.price{font-size:11.5px;color:var(--ink-dim)}
.stack{display:flex;gap:2px;height:22px}
.seg{position:relative;background:var(--surface-2);border-radius:1px;overflow:hidden;
  border:1px solid var(--rule)}
.seg i{position:absolute;left:0;bottom:0;width:100%;display:block}
.seg span{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  font-size:9px;font-family:Montserrat,sans-serif;color:var(--ink-dim);opacity:.85}
.pct{font-family:Montserrat,sans-serif;font-size:17px;text-align:right;color:var(--navy)}
.toolbar{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin:14px 0}
.rarbox{display:flex;flex-wrap:wrap;gap:6px 14px;align-items:center;border:1px solid var(--rule);
  border-radius:6px;padding:8px 12px 10px;margin:0 0 14px;background:var(--surface)}
.rarbox legend{font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-dim);padding:0 6px}
.rarchk{display:inline-flex;align-items:center;gap:5px;font-size:12px;cursor:pointer;
  user-select:none}
.rarchk input{accent-color:var(--blue);width:14px;height:14px;margin:0;cursor:pointer}
.rarchk:has(input:not(:checked)){opacity:.45}
.rarn{font-family:Montserrat,sans-serif;font-size:10.5px;color:var(--ink-dim)}
input,select{background:var(--surface);border:1px solid var(--rule);color:var(--ink);
  border-radius:6px;padding:7px 10px;font-family:inherit;font-size:13px}
input[type=search]{flex:1;min-width:150px}
input:focus,select:focus,button:focus-visible{outline:2px solid var(--blue);outline-offset:1px}
.chip{background:var(--surface);border:1px solid var(--rule);color:var(--ink-dim);border-radius:100px;
  padding:5px 11px;font-size:11.5px;cursor:pointer}
.chip[aria-pressed=true]{border-color:var(--blue);color:var(--navy);background:var(--surface-2)}
.gameswitch{display:flex;gap:6px;margin-right:14px}
.chip:disabled{opacity:.5;cursor:default}

/* Set detail (BUILD_PLAN item 04b): left filter rail + Bulk Actions menu,
   the latter now living in the toolbar next to the sort dropdown rather
   than overlaid on the banner. Kept as a sibling of .setbanner either way --
   .setbanner has overflow:hidden for the background-art crop, and a
   dropdown nested inside it would get clipped at the banner's bottom edge. */
.setbanner-wrap{margin-bottom:8px}
.setbanner-wrap .setbanner{margin-bottom:0}
.bulkmenu{position:relative}
.bulkmenu summary{list-style:none}
.bulkmenu summary::-webkit-details-marker{display:none}
.bulkmenu summary::before{content:none}
.bulkmenu-list{position:absolute;top:calc(100% + 6px);right:0;min-width:280px;
  background:var(--surface);border:1px solid var(--rule);border-radius:8px;
  box-shadow:0 6px 20px rgba(10,20,35,.18);padding:6px;display:flex;flex-direction:column;gap:1px;z-index:5}
.bulkmenu-item{display:block;width:100%;text-align:left;background:none;border:0;border-radius:6px;
  padding:8px 10px;cursor:pointer;color:var(--ink);font-family:inherit}
.bulkmenu-item:hover{background:var(--surface-2)}
.bulkmenu-item b{display:block;font-size:12.5px;font-weight:600}
.bulkmenu-item span{display:block;font-size:11px;color:var(--ink-dim);margin-top:2px;line-height:1.4}

.setdetail-layout{display:grid;grid-template-columns:220px 1fr;gap:22px;align-items:start}
.setdetail-rail{display:flex;flex-direction:column;gap:16px;position:sticky;top:12px}
.railsec{display:flex;flex-direction:column;gap:6px;margin:0}
fieldset.railsec:not(.rarbox){border:0;padding:0;min-width:0;margin:0}
.raillabel,#showModeSec legend{font-size:10.5px;letter-spacing:.14em;text-transform:uppercase;
  color:var(--ink-dim);padding:0;margin:0}
.railnote{font-size:11px;color:var(--ink-dim);margin:0;line-height:1.4}
.setdetail-rail .rarbox{flex-direction:column;align-items:flex-start;flex-wrap:nowrap}
.segctl{display:flex;border:1px solid var(--rule);border-radius:6px;overflow:hidden}
.segbtn{flex:1;background:var(--surface);border:0;border-right:1px solid var(--rule);
  color:var(--ink-dim);font-size:11.5px;font-weight:600;padding:7px 2px;cursor:pointer}
.segbtn:last-child{border-right:0}
.segbtn[aria-pressed=true]{background:var(--navy);color:#fff}
.radiolist{display:flex;flex-direction:column;gap:6px}
.pricerow{display:flex;gap:8px}
.pricerow .pxin{width:100%;min-width:0}

.filterpills{display:flex;flex-wrap:wrap;gap:6px;margin:0 0 12px}
.pill{display:inline-flex;align-items:center;gap:6px;background:var(--surface-2);
  border:1px solid var(--rule);color:var(--navy);border-radius:100px;padding:4px 6px 4px 10px;
  font-size:11.5px;cursor:pointer}
.pill:hover{border-color:var(--blue)}
.pill b{font-weight:600;color:var(--ink-dim);font-size:13px;line-height:1}

@media (max-width:760px){.setdetail-layout{grid-template-columns:1fr}
  .setdetail-rail{position:static}}

/* Admin tab: one card per app/admin.py action. */
.admingrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:14px;margin:14px 0}
.admincard{background:var(--surface);border:1px solid var(--rule);border-radius:8px;padding:14px;
  display:flex;flex-direction:column;gap:10px}
.admhead{display:flex;align-items:center;gap:8px;font-size:14px}
.admtip{display:inline-flex;align-items:center;justify-content:center;width:16px;height:16px;
  border-radius:50%;border:1px solid var(--ink-dim);color:var(--ink-dim);font-size:10.5px;
  cursor:help;flex:none;line-height:1}
.admfields{display:flex;flex-direction:column;gap:8px}
.admfield{display:flex;flex-direction:column;gap:3px;font-size:11px;color:var(--ink-dim)}
.admfield input{font-size:12.5px}
.admfieldrow{display:flex;gap:6px}
.admfieldrow input{flex:1;min-width:0}
.admfieldrow .chip{flex:none;white-space:nowrap}
.admchk{display:flex;align-items:center;gap:6px;font-size:12px;cursor:pointer;user-select:none}
.admchk input{accent-color:var(--blue);width:14px;height:14px;margin:0;cursor:pointer}
.admrow{display:flex;align-items:center;gap:10px}
/* Fixed dark terminal colors, not theme tokens -- a console readout should
   look like a console in both themes, not flip to a light background when
   --ink (its bg here) lightens for dark mode. */
.admout{background:#1B2430;color:#D7E1EC;font-family:Montserrat,sans-serif;font-size:11px;
  padding:8px 10px;border-radius:6px;max-height:180px;overflow-y:auto;white-space:pre-wrap;
  word-break:break-word;margin:0}
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(184px,1fr));gap:8px}
.card{background:var(--surface);border:1px solid var(--rule);border-radius:6px;padding:9px 10px;
  display:flex;flex-direction:column;gap:7px;box-shadow:0 1px 2px rgba(27,62,114,.04)}
.card.owned{border-color:var(--blue);background:var(--surface-2)}
.card .top{display:flex;gap:8px;justify-content:space-between;align-items:flex-start}
.card .nm{font-size:12.5px;font-weight:500;line-height:1.25}
.card .no{font-family:Montserrat,sans-serif;font-size:10.5px;color:var(--ink-dim)}
/* Card photo: fixed 5:7 box (Magic's real card ratio) so the tile holds its
   shape whether or not an image exists yet -- cards.image is null for the
   ~10% scryfall_images.py couldn't match, and for anything not downloaded
   yet while a run is still in progress. */
.thumbwrap{width:100%;aspect-ratio:5/7;background:var(--surface-2);border:1px solid var(--rule);
  border-radius:6px;overflow:hidden;cursor:pointer}
.carddetail-image .thumbwrap{cursor:default}
.thumbwrap img{width:100%;height:100%;object-fit:cover;display:block}
.thumbwrap.broken{background:repeating-linear-gradient(135deg,var(--surface-2),var(--surface-2) 6px,var(--rule) 6px,var(--rule) 12px)}
.adds{display:flex;gap:6px}
/* Dense mode: mostly-photo tiles, packed tighter than .grid -- just the
   photo plus one caption line (number + N/F price), not the full price/
   stepper block .grid shows. Click opens the same card detail in
   #cardModal. */
.densegrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(165px,1fr));gap:8px}
.densecard{padding:0;border:2px solid transparent;border-radius:6px;cursor:pointer;
  background:none;display:block;width:100%}
.densecard.owned{border-color:var(--blue)}
.densecard .thumbwrap{border-radius:4px}
.densemeta{font-size:11px;color:var(--ink-dim);
  padding:3px 2px 0;text-align:center;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
/* Card detail popup: one persistent overlay (#cardModal in index.html),
   content swapped per click. Reuses .card's own layout unstyled (no border/
   shadow of its own) since the modal box already provides that framing. */
.modalbg{position:fixed;inset:0;background:rgba(15,26,43,.55);z-index:50;
  display:flex;align-items:center;justify-content:center;padding:16px}
.modalbox{position:relative;background:var(--surface);border-radius:10px;padding:16px;
  max-width:280px;width:100%;box-shadow:0 8px 30px rgba(15,26,43,.35)}
.modalbox .card{border:0;padding:0;box-shadow:none}
.modalclose{position:absolute;top:6px;right:8px;background:none;border:0;cursor:pointer;
  font-size:20px;line-height:1;color:var(--ink-dim);padding:4px}
.modalclose:hover{color:var(--ink)}
/* Big card-detail popup (see openCardDetailModal) -- sized close to the
   full browser window, since the point is admiring the art at real size
   alongside every detail set_cards() knows, not a cramped quick-view.
   Image stacked above the text (not beside it) at 520px wide, up from the
   original 380px side-by-side layout -- same 5:7 box, so area scales with
   width squared: (520/380)^2 = 1.87, i.e. +87% area, comfortably past a
   "25% bigger" bar on either a width or an area reading of it. */
.modalbox.big{max-width:1100px;width:92vw;max-height:88vh;overflow-y:auto;padding:24px}
.carddetail{display:flex;flex-direction:column;align-items:center;text-align:center}
.buyitbtn{display:inline-block;background:var(--up);color:#fff;font-weight:600;text-decoration:none;
  border-radius:100px;padding:9px 20px;font-size:13px;margin-top:16px}
.buyitbtn:hover{filter:brightness(0.85)}
.carddetail-image{width:100%;max-width:520px;margin-bottom:18px}
.carddetail-image .thumbwrap{border-radius:10px;box-shadow:0 4px 18px rgba(15,26,43,.25)}
.carddetail-info{width:100%;max-width:640px}
.carddetail-info h2{margin:0 0 6px;font-size:24px;line-height:1.2}
.carddetail-meta{display:flex;flex-wrap:wrap;justify-content:center;align-items:center;gap:6px 12px;
  color:var(--ink-dim);font-size:13px;margin-bottom:14px}
.carddetail-typeline{display:flex;flex-wrap:wrap;justify-content:center;gap:4px 10px;
  font-size:14px;margin-bottom:10px}
.carddetail-typeline span{color:var(--ink-dim);font-family:Montserrat,sans-serif;font-size:12.5px}
.carddetail-oracle{text-align:left;font-size:14px;line-height:1.5;margin-bottom:10px;
  background:var(--surface-2);border-radius:8px;padding:10px 14px}
.carddetail-pt{font-family:Montserrat,sans-serif;font-weight:600;margin-bottom:10px}
.carddetail-flavor{text-align:left;font-style:italic;color:var(--ink-dim);font-size:13px;
  line-height:1.5;margin-bottom:10px;padding:0 14px}
.carddetail-artist{color:var(--ink-dim);font-size:12px;margin-bottom:14px}
.carddetail-prices{width:100%;margin-bottom:14px;text-align:left}
.carddetail-prices th{text-align:left;font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ink-dim);padding:0 8px 4px 0;font-weight:600}
.carddetail-prices td{padding:3px 8px 3px 0;font-family:Montserrat,sans-serif;font-size:13px}
.carddetail .adds{flex-wrap:wrap;justify-content:center}

/* BUILD_PLAN item 05: position header + "Your copies" table, reusing the
   dashboard hero's own type scale (.hero-figure/.hero-change/.hero-subfig)
   so a card's position reads as the same kind of number as the dashboard's. */
.carddetail-position{margin-bottom:16px;padding-bottom:16px;border-bottom:1px solid var(--rule)}
.carddetail-subfigs{display:flex;gap:24px;justify-content:center;margin-top:14px}
.carddetail-lots{width:100%;margin-bottom:18px;text-align:left}
.carddetail-lots th{font-size:10.5px;letter-spacing:.08em;text-transform:uppercase;
  color:var(--ink-dim);padding:0 8px 4px 0;font-weight:600}
.carddetail-lots td{padding:4px 8px 4px 0;font-size:12.5px;border-bottom:1px solid var(--rule)}
.carddetail-lots td.num{font-family:Montserrat,sans-serif}
.carddetail-sparkline{width:100%;margin:2px 0 14px}
.carddetail-sparkline .chart-svg{height:70px}
@media(max-width:640px){
  .modalbox.big{width:96vw;max-height:92vh;padding:16px}
  .carddetail-image{max-width:280px}
}
/* quantity stepper: [-] label xN [+] */
.qgrp{flex:1;display:flex;align-items:stretch;border:1px solid var(--rule);border-radius:6px;
  overflow:hidden;background:var(--surface-2);font-family:Montserrat,sans-serif;font-size:11px}
.qgrp.f{color:var(--gold)}
.qgrp:hover{border-color:var(--blue)}
.qgrp.hasother .q{box-shadow:inset 0 -2px 0 var(--rule)}
.pxin{width:86px}
.qlabel{flex:1;display:flex;align-items:center;justify-content:center;gap:5px;padding:4px 2px;
  white-space:nowrap}
.qlabel .fin{font-weight:600;letter-spacing:.06em;opacity:.75;font-size:10.5px}
.qlabel .q{background:var(--surface);border-radius:4px;padding:0 5px;min-width:18px;text-align:center;
  color:var(--ink)}
.qbtn{background:none;border:0;color:var(--ink-dim);cursor:pointer;padding:4px 9px;
  font-family:inherit;font-size:13px;line-height:1}
.qbtn:hover:not(:disabled){background:var(--surface);color:var(--ink)}
.qbtn:disabled{opacity:.3;cursor:default}
.qbtn.plus.has{color:var(--blue)}
.qbtn.minus{border-right:1px solid var(--rule)}
.qbtn.plus{border-left:1px solid var(--rule)}
td .qgrp{display:inline-flex;width:auto;min-width:96px}
/* Proxies: a visual builder that mirrors the PDF layout it produces -- one
   3x3 grid "page" shown at a time, each cell a card or a "+" to fill it,
   flipped through with the arrows on the left like a binder. */
.proxybinder{display:flex;align-items:center;justify-content:center;gap:16px;margin-top:14px}
.proxybinder-arrows{display:flex;flex-direction:column;align-items:center;gap:6px}
.proxybinder-arrow{width:34px;height:30px;border-radius:6px;border:1px solid var(--rule);
  background:var(--surface);color:var(--ink-dim);font-size:12px;cursor:pointer}
.proxybinder-arrow:hover:not(:disabled){border-color:var(--blue);color:var(--blue)}
.proxybinder-arrow:disabled{opacity:.3;cursor:default}
.proxybinder-pagenum{font-size:11px;color:var(--ink-dim);font-family:Montserrat,sans-serif;
  white-space:nowrap}
.proxybinder-list{display:flex;flex-wrap:wrap;gap:8px;margin:8px 0}
.proxybinder-chip{display:flex;align-items:stretch;background:var(--surface);
  border:1px solid var(--rule);border-radius:6px;overflow:hidden}
.proxybinder-chip.active{border-color:var(--blue)}
.proxybinder-chip button:first-child{padding:6px 10px;background:none;border:0;cursor:pointer;
  font-size:12px;color:var(--ink)}
.proxybinder-chip button:first-child:hover{background:var(--surface-2)}
.proxybinder-chip .linkbtn{padding:6px 9px;font-size:14px}
.proxygrid3x3{display:grid;grid-template-columns:repeat(3,1fr);gap:6px;width:100%;max-width:420px}
.proxycell{position:relative;aspect-ratio:63/88;border-radius:6px;overflow:hidden;
  margin:0;padding:0;box-sizing:border-box;font-family:inherit}
.proxycell.empty{background:var(--surface-2);border:1.5px dashed var(--rule);
  display:flex;align-items:center;justify-content:center;color:var(--ink-dim);
  font-size:26px;line-height:1;cursor:pointer;transition:border-color .12s,color .12s}
.proxycell.empty span{display:block;transform:translateY(-2px)}   /* optical centering: the glyph's ink sits above its box-metrics center */
.proxycell.empty:hover{border-color:var(--blue);color:var(--blue)}
.proxycell.filled{border:1px solid var(--rule);background:var(--surface-2)}
.proxycell.filled img{width:100%;height:100%;object-fit:cover;display:block}
.proxycell-noimg{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;
  padding:6px;text-align:center;font-size:10px;color:var(--ink-dim)}
.proxycell-graded{position:absolute;top:0;left:0;right:0;background:#fff;color:#111;
  font-size:8px;line-height:1.25;text-align:center;padding:2px 3px;
  border-bottom:1px solid var(--rule);font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.proxycell-remove{position:absolute;top:3px;right:3px;width:20px;height:20px;border-radius:50%;
  border:0;background:rgba(15,26,43,.65);color:#fff;font-size:14px;line-height:1;cursor:pointer;
  display:flex;align-items:center;justify-content:center;padding:0}
.proxycell-remove:hover{background:var(--down)}
/* Card-picker popup: reuses #cardModal/#modalBox (see openCardModal), just
   wider than the card-detail popup it's normally sized for. */
.modalbox.wide{max-width:440px}
.proxypicker input[type=search],.proxypicker input[type=text]{width:100%;margin-bottom:8px}
.proxypicker > label{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--ink-dim)}
.proxypicker-qtyrow{display:flex;align-items:center;gap:8px;font-size:12px;
  color:var(--ink-dim);margin-bottom:10px}
.proxypicker-qtyrow input{width:56px}
.proxypicker-results{max-height:260px;overflow-y:auto;display:flex;flex-direction:column;gap:6px}
.proxypicker-row{display:flex;align-items:center;gap:10px;text-align:left;background:var(--surface);
  border:1px solid var(--rule);border-radius:6px;padding:6px 8px;cursor:pointer;width:100%}
.proxypicker-row:hover{border-color:var(--blue)}
.proxypicker-info{display:flex;flex-direction:column;gap:2px;min-width:0}
.proxypicker-info .nm{font-size:12px;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.proxypicker-info .price{font-size:10.5px;color:var(--ink-dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.proxythumb{width:36px;aspect-ratio:5/7;object-fit:cover;border-radius:3px;
  border:1px solid var(--rule);background:var(--surface-2);display:block;flex-shrink:0}
/* legacy single-action button, still used for inline actions */
.add{flex:1;display:flex;align-items:center;justify-content:space-between;gap:6px;
  background:var(--surface-2);border:1px solid var(--rule);color:var(--ink);border-radius:6px;
  padding:5px 8px;font-size:11px;cursor:pointer;font-family:Montserrat,sans-serif}
.add:hover{border-color:var(--blue)}
.note{border-left:3px solid var(--blue);background:var(--surface-2);padding:9px 12px;font-size:12px;
  color:var(--ink-dim);margin:14px 0;border-radius:0 6px 6px 0}
.note b{color:var(--ink);font-weight:600}
/* Empty-state rows (no lots yet, no movers this snapshot, no cards match the
   filters) get a faint G-mark stamp so blank space still reads as branded
   rather than broken -- purely decorative, ::after so it never affects the
   message's layout or hit area. */
.empty{position:relative;color:var(--ink-dim);padding:22px 60px 22px 8px;font-size:13px;overflow:hidden}
.empty::after{content:"";position:absolute;right:8px;top:50%;transform:translateY(-50%);
  width:42px;height:42px;background:url('/static/images/brand/g-mark.svg') center/contain no-repeat;
  opacity:.07;pointer-events:none}
.backlink{background:none;border:0;color:var(--blue);cursor:pointer;font-size:12px;padding:0;margin-bottom:6px}
.primary{background:var(--navy);border:0;color:#fff;font-weight:600;border-radius:6px;
  padding:10px 14px;cursor:pointer;font-size:13px;width:100%;margin-top:6px}
.primary:hover{filter:brightness(1.15)}
/* brand wordmark: G-mark icon + two-tone "GNZ"/"Collect" text, used in the
   header and (larger) on the sign-in gate. */
.wordmark{display:flex;align-items:center;gap:8px;font-family:'Baloo 2',Montserrat,sans-serif;
  font-weight:800;font-size:20px;line-height:1;white-space:nowrap}
.wordmark .wm-icon{width:26px;height:26px;border-radius:6px;flex:none;display:block}
.wordmark .wm-a{color:var(--navy)}
.wordmark .wm-b{color:var(--blue)}
.wordmark-lg{justify-content:center;font-size:32px;margin-bottom:4px}
.wordmark-lg .wm-icon{width:38px;height:38px}
.wordmark-link{background:none;border:0;padding:0;margin:0;cursor:pointer;text-align:left}
.wordmark-link:hover .wm-a{color:var(--blue)}
.wordmark-link:hover .wm-b{color:var(--navy)}
.betabadge{font-family:Montserrat,sans-serif;font-size:10px;font-weight:600;letter-spacing:.06em;
  text-transform:uppercase;color:var(--gold);border:1px solid var(--gold);border-radius:4px;
  padding:2px 6px;line-height:1.3;align-self:center}
/* Version tag next to the wordmark, so a shared build can be told apart at
   a glance -- muted/neutral on purpose, distinct from .betabadge's gold so
   the two don't compete for attention. Bump the "0.2" in index.html (two
   spots: gate + app header) and the <title> tag when the version changes. */
.versionbadge{font-family:Montserrat,sans-serif;font-size:10px;font-weight:600;letter-spacing:.04em;
  color:var(--ink-dim);border:1px solid var(--rule);border-radius:4px;
  padding:2px 6px;line-height:1.3;align-self:center}
/* auth gate (BUILD_PLAN item 07): two columns -- .gate-intro explains what
   the product is and what happens after signing up; .gate-form is the
   original single-column form, untouched in behavior, just narrower. */
.gate{min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px;background:var(--bg)}
.gatebox{position:relative;width:100%;max-width:760px;background:var(--surface);border:1px solid var(--rule);
  border-radius:10px;padding:32px;box-shadow:0 4px 18px rgba(27,62,114,.08);
  display:grid;grid-template-columns:1.15fr 1fr;gap:32px}
.gate-intro{border-right:1px solid var(--rule);padding-right:32px;display:flex;flex-direction:column}
.gate-intro .wordmark-lg{justify-content:flex-start}
.gate-headline{font-size:25px;font-weight:700;color:var(--navy);line-height:1.28;margin:18px 0 10px;
  text-wrap:balance}
.gate-lede{font-size:13.5px;color:var(--ink-dim);line-height:1.55;margin:0 0 18px}
.gate-steps{margin:0 0 20px;padding-left:20px;display:flex;flex-direction:column;gap:9px;
  font-size:12.5px;line-height:1.5;color:var(--ink-dim)}
.gate-steps li::marker{color:var(--blue);font-weight:600}
.gate-steps b{color:var(--ink);font-weight:600}
.gate-privacy{font-size:11px;color:var(--ink-dim);margin:auto 0 0;padding-top:14px;
  border-top:1px solid var(--rule)}
.gate-form{display:flex;flex-direction:column;justify-content:center}
.gatebox label{display:block;font-size:11px;letter-spacing:.1em;text-transform:uppercase;
  color:var(--ink-dim);margin:12px 0 0}
.gatebox input{width:100%;margin-top:5px}
#inviteRow .hint{text-transform:none;letter-spacing:0;font-size:11px;margin:5px 0 0}
.tabsm{display:flex;gap:6px;margin-bottom:14px;justify-content:center}
@media (max-width:680px){
  .gatebox{grid-template-columns:1fr;padding:24px}
  .gate-intro{border-right:0;border-bottom:1px solid var(--rule);padding-right:0;padding-bottom:20px;margin-bottom:20px}
  .gate-privacy{margin-top:0}
}
.hint{font-size:11.5px;color:var(--ink-dim);margin:8px 0 0}
.err{color:var(--down);font-size:12.5px;min-height:18px;margin:8px 0 0}
.toast{position:fixed;left:50%;transform:translateX(-50%);bottom:22px;background:var(--navy);
  border:1px solid var(--navy);color:#fff;padding:8px 14px;border-radius:6px;font-size:12.5px;z-index:9;
  box-shadow:0 4px 14px rgba(27,62,114,.25)}
.pager{display:flex;gap:8px;align-items:center;justify-content:center;margin:18px 0}

/* Dashboard: hero position figure, top-holdings carousel, value charts,
   sortable tables. */
.hero-figure{font-family:Montserrat,sans-serif;font-variant-numeric:tabular-nums;
  font-size:42px;font-weight:600;color:var(--navy);line-height:1.1}
.hero-change{font-size:14px;margin-top:4px}
.hero-change .s{color:var(--ink-dim);font-size:12px;margin-left:4px}
.hero-subfigs{display:flex;gap:28px;margin-top:18px}
.hero-subfig .k{font-size:10.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--ink-dim)}
.hero-subfig .v{font-family:Montserrat,sans-serif;font-size:19px;
  font-variant-numeric:tabular-nums;margin-top:2px}
.datanote{font-size:11.5px;color:var(--ink-dim);margin:0 0 20px}
.dashrow{display:grid;grid-template-columns:2fr 1fr;gap:16px;margin:20px 0}
@media(max-width:720px){.dashrow{grid-template-columns:1fr}}

/* Collector Card hero: rank badge + tier progress (left) beside a featured
   "trophy" card for the account's single highest-value holding (right).
   Replaced the old Position Price / Cards Owned toggle+chart. */
.combo-grid{display:grid;grid-template-columns:1fr 1fr;gap:20px;align-items:stretch;margin-bottom:14px}
.rank-hero{display:grid;grid-template-columns:auto 1fr;gap:18px;align-items:center;margin-bottom:16px}
.rank-badge{width:84px;height:84px;border-radius:50%;
  background:conic-gradient(var(--gold) calc(var(--pct,0)*1%), var(--rule) 0);
  display:flex;align-items:center;justify-content:center;flex:none}
.rank-badge-inner{width:66px;height:66px;border-radius:50%;background:var(--surface);
  display:flex;flex-direction:column;align-items:center;justify-content:center}
.rank-badge-tier{font-size:9.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-dim)}
.rank-badge-score{font-family:Montserrat,sans-serif;font-size:15px;font-weight:700;color:var(--ink)}
.rank-next{font-size:12.5px;color:var(--ink-dim)}
.rank-next b{color:var(--ink)}
.rank-track{height:7px;background:var(--rule);border-radius:4px;overflow:hidden;margin:8px 0 0}
.rank-fill{height:100%;background:var(--gold)}
.combo-rank{display:flex;flex-direction:column;justify-content:space-between;
  background:var(--surface);border:1px solid var(--rule);border-radius:10px;padding:18px}
.rank-stats{display:flex;gap:20px;padding-top:14px;border-top:1px solid var(--rule)}
.rank-stat-label{font-size:10.5px;letter-spacing:.06em;text-transform:uppercase;color:var(--ink-dim);margin-bottom:3px}
.rank-stat-value{font-family:Montserrat,sans-serif;font-size:17px;font-weight:600;color:var(--ink)}
.rank-stat-sub{font-size:10.5px;margin-top:2px}
.rank-stat-sub.up{color:var(--up)}
.rank-stat-sub.down{color:var(--down)}
.rank-stat-value .s{font-size:12px;font-weight:400;color:var(--ink-dim)}
.trophy-card{background:linear-gradient(155deg, color-mix(in srgb, var(--gold) 14%, var(--surface)), var(--surface));
  border:1px solid color-mix(in srgb, var(--gold) 40%, var(--rule));border-radius:10px;padding:18px;
  display:flex;flex-direction:column;justify-content:space-between;cursor:pointer}
.trophy-card.trophy-empty{cursor:default;align-items:center;justify-content:center}
.trophy-kicker{font-size:10px;letter-spacing:.12em;text-transform:uppercase;color:var(--gold);font-weight:700}
.trophy-name{font-size:17px;font-weight:700;color:var(--ink);margin:8px 0 2px}
.trophy-sub{font-size:11.5px;color:var(--ink-dim)}
.trophy-value{font-family:Montserrat,sans-serif;font-size:26px;font-weight:600;color:var(--ink);margin-top:10px}
.trophy-badge{display:inline-block;margin-top:8px;font-size:10.5px;font-weight:600;color:var(--surface);
  background:var(--gold);padding:3px 9px;border-radius:100px;width:fit-content}
.spark-wrap{margin-top:12px}
.mini-label{font-size:11px;color:var(--ink-dim);margin-bottom:3px}
.trophy-spark .chart-svg{height:36px}
.trophy-spark .chart-area{fill:color-mix(in srgb, var(--gold) 20%, transparent)}
.trophy-spark .chart-line{stroke:var(--gold)}
.trophy-spark .chart-dot{fill:var(--gold)}
.trophy-spark .chart-axis{display:none}
@media(max-width:720px){.combo-grid{grid-template-columns:1fr}}
.carousel-wrap{display:flex;align-items:center;gap:8px;margin-bottom:20px}
/* min-width:0 overrides the flex-item default of min-width:auto -- without
   it, .carousel's own minimum width is inflated by its flex-shrink:0
   children's basis (see .carousel-card below), which throws off the % math
   those children's own flex-basis uses. Classic nested-flex-with-overflow
   gotcha; caught live, not from theory -- the cards rendered ~277px instead
   of the intended ~199px at a 796px-wide carousel until this was added. */
.carousel{display:flex;gap:14px;overflow-x:auto;scroll-behavior:smooth;padding:2px 0 6px;flex:1;min-width:0}
/* Four cards fill the visible width exactly (3 gaps between them); anything
   past the 4th is still there, just scrolled to -- same 5:7 card-image ratio
   used everywhere else in this app (see openCardDetailModal's own note on
   that box), scaled up via aspect-ratio instead of a fixed height so it
   holds at this bigger size. */
.carousel-card{flex:0 0 calc((100% - 3*14px) / 4);min-width:0;cursor:pointer}
.carousel-card img{transition:transform .12s}
.carousel-card:hover img{transform:scale(1.03)}
.carousel-card img{width:100%;aspect-ratio:5/7;object-fit:cover;border-radius:8px;
  border:1px solid var(--rule);background:var(--surface-2);display:block}
.carousel-cap{padding:7px 2px 0;font-size:13px;line-height:1.35}
.carousel-cap b{display:block;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.carousel-cap span{color:var(--ink-dim);font-family:Montserrat,sans-serif;font-size:12px}
.carousel-arrow{flex:none}
@media(max-width:640px){.carousel-card{flex:0 0 calc((100% - 14px) / 2)}}
.dashchart{background:var(--surface);border:1px solid var(--rule);border-radius:6px;padding:12px 14px;
  box-shadow:0 1px 2px rgba(27,62,114,.05)}
.chart-svg{width:100%;height:120px;display:block}
.chart-area{fill:rgba(74,144,210,.15);stroke:none}
.chart-line{fill:none;stroke:var(--blue);stroke-width:2}
.chart-dot{fill:var(--navy)}
.chart-axis{display:flex;justify-content:space-between;font-size:10.5px;color:var(--ink-dim);
  font-family:Montserrat,sans-serif;margin-top:4px}
.rarity-bars{display:flex;flex-direction:column;gap:8px}
.rarity-bar-row{display:grid;grid-template-columns:90px 1fr 72px;align-items:center;gap:8px}
.rarity-bar-label{font-size:11.5px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
.rarity-bar-track{background:var(--surface-2);border-radius:3px;height:10px;overflow:hidden}
.rarity-bar-fill{height:100%;border-radius:3px}
.rarity-bar-value{font-family:Montserrat,sans-serif;font-size:11px;text-align:right}
.tablewrap{overflow-x:auto}
th.sortable{cursor:pointer;user-select:none}
th.sortable:hover{color:var(--ink)}
@media(max-width:560px){.setgrid{grid-template-columns:1fr}.stack{height:18px}
  .carousel-card,.carousel-card img{width:96px}.carousel-card img{height:134px}}
@media(prefers-reduced-motion:no-preference){.card,.setcard,.add{transition:background .12s,border-color .12s}}
/* Trims page-specific chrome when printing / saving as PDF, regardless of
   which view mode triggered it -- PDF mode already omits the filter
   toolbar and rarity box from the DOM. The app header (logo, tabs, signed-
   in email) stays visible on purpose so a printed sheet still shows what
   it's from and who it belongs to. */
@media print{
  .backlink,.pager,.note,.noprint,#toast,#cardModal{display:none !important}
  .setdetail-rail,.filterpills{display:none !important}
  .setdetail-layout{display:block}
  body,.wrap{background:#fff;margin:0;padding:0}
  .densegrid{gap:4px}
}

/* Needs list (0.2 stage 3). A wrapping flex row, deliberately not an
   auto-fit grid with a spanning child -- that strands an empty column at
   mid widths (a real bug in the design prototype, per the handoff doc).
   Below ~760px the two panels stack instead of narrowing. */
.needslayout{display:flex;flex-wrap:wrap;gap:22px}
/* No shared "surface card" utility class existed yet (the closest, .admincard,
   is named for a different tab) -- these two declare the same background/
   border/radius/padding directly rather than repurpose that name. */
.needslist,.needsexport{background:var(--surface);border:1px solid var(--rule);border-radius:8px;padding:14px}
.needslist{flex:3 1 470px}
.needsexport{flex:1 1 268px}
.needslist-head{display:flex;justify-content:space-between;align-items:baseline;gap:12px;flex-wrap:wrap}
.needsfilters{display:flex;flex-wrap:wrap;gap:6px;margin:10px 0}
.needsfilters .chip[disabled]{opacity:.45;cursor:default}
/* 24x34 -- much smaller than .thumbwrap's full card-tile size, this is a
   list-row thumbnail. Dashed frame (not the .thumbwrap card-back image
   placeholder used in the grid views) when card_images has no match for
   this product -- mirrors .proxycell.empty's existing dashed-slot look,
   the closest thing this codebase already had to "this slot is empty." */
.needsthumb{width:24px;height:34px;flex:none;border-radius:3px;overflow:hidden;background:var(--surface-2)}
.needsthumb.noimg{border:1.5px dashed var(--rule);background:transparent}
.needsthumb img{width:100%;height:100%;object-fit:cover;display:block}
.needsrow-name{font-size:12.5px}
.needsrow-set{font-size:10px;color:var(--ink-dim)}
.needsrow-chase{color:var(--ink-dim);font-size:9.5px;text-transform:uppercase;letter-spacing:.08em}
.exportcard{border:1px solid var(--rule);border-radius:8px;padding:10px 12px;cursor:pointer;margin-bottom:10px}
.exportcard[aria-pressed=true]{border-color:var(--blue)}
.exportcard-head{display:flex;justify-content:space-between;align-items:baseline;gap:8px}
.exportcard-desc{font-size:11px;color:var(--ink-dim);margin-top:2px}
.exportopts{margin-top:14px;border-top:1px solid var(--rule);padding-top:10px}
.exportopt{display:flex;justify-content:space-between;padding:5px 0;border-bottom:1px solid var(--rule);font-size:12.5px}
.exportopt span:last-child{color:var(--ink-dim)}
@media(max-width:720px){.needsthumb{display:none}}

/* Achievements (0.2 stage 7). .achscore/.needsexport in viewAchievements()
   deliberately reuse .needslist/.needsexport's card/flex shell (same
   "surface card, wrapping flex row" pattern already established for the
   needs list panels) rather than a second near-identical panel class. */
.achscore{flex:1 1 264px}
.achscore-head{display:flex;align-items:baseline;gap:10px;margin:4px 0 8px}
.achscore-num{font-size:40px;font-weight:600;line-height:1}
.achbar{height:4px;background:var(--rule);border-radius:2px;overflow:hidden;margin:6px 0 4px}
.achbar-fill{height:100%;background:var(--blue)}
.achcat-row{display:flex;align-items:center;gap:8px;font-size:12px;padding:3px 0}
.achcat-row>span:first-child{width:78px;flex:none;color:var(--ink-dim)}
.achcat-row>span:last-child{width:48px;flex:none;text-align:right}
.achcat-track{flex:1;height:5px;background:var(--rule);border-radius:3px;overflow:hidden}
.achcat-fill{height:100%;background:var(--blue)}
.achgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(158px,1fr));gap:11px;margin-top:10px}
.achtile{text-align:left;background:var(--surface-2);border:1px solid var(--rule);border-radius:8px;
  padding:10px;cursor:pointer;display:flex;flex-direction:column;gap:4px;font-family:inherit;color:var(--ink)}
.achtile.selected{border-color:var(--blue)}
.achtile.unavailable{opacity:.55;cursor:default}
.achtile-head{display:flex;justify-content:space-between;align-items:center}
.achglyph{font-size:16px;color:var(--ink-dim)}
.achtile.earned .achglyph{color:var(--blue)}
.achglyph.big{font-size:26px}
.achtile-name{font-size:13px;font-weight:600}
.achtile-sub{font-size:11px;color:var(--ink-dim);min-height:2.6em}
.achtile-unavail{font-size:10.5px;color:var(--ink-dim);text-transform:uppercase;letter-spacing:.06em;margin-top:2px}
.achtile-foot{display:flex;justify-content:space-between;font-family:Montserrat,sans-serif;
  font-size:9.5px;color:var(--ink-dim);text-transform:uppercase;letter-spacing:.04em}
.achanatomy{background:var(--surface);border:1px solid var(--rule);border-radius:8px;padding:14px;width:100%}
.achanatomy-head{display:flex;align-items:center;gap:10px;margin-bottom:8px}
.achanatomy-name{font-size:16px;font-weight:600}

/* Share sheet (0.2 stage 8) -- lives inside #cardModal/#modalBox, same
   shell every other popup in this app already uses. */
.sharesheet{display:flex;flex-wrap:wrap;gap:20px}
.sharesheet-col{flex:1 1 240px;min-width:240px}
.sharerow{display:flex;justify-content:space-between;align-items:center;gap:10px;
  padding:8px 0;border-bottom:1px solid var(--rule);font-size:12.5px}
.sharerow .price{display:block;font-size:10.5px;color:var(--ink-dim);margin-top:1px}
.sharerow.expired{opacity:.5;text-decoration:line-through}
.sharetoggle{width:34px;height:19px;flex:none;border-radius:999px;border:1px solid var(--rule);
  background:var(--surface-2);position:relative;cursor:pointer;padding:0}
.sharetoggle::after{content:'';position:absolute;top:2px;left:2px;width:13px;height:13px;
  border-radius:50%;background:var(--ink-dim);transition:transform .12s}
.sharetoggle[aria-pressed=true]{background:var(--blue);border-color:var(--blue)}
.sharetoggle[aria-pressed=true]::after{transform:translateX(15px);background:#fff}
.sharetoggle:disabled{opacity:.4;cursor:default}
.sharelink{background:color-mix(in srgb, var(--blue) 10%, var(--surface));
  border:1px solid var(--blue);border-radius:8px;padding:12px}
.sharelink-url{font-size:11.5px;word-break:break-all;margin:4px 0 10px}

/* Social handles form (user-menu dropdown) -- plain label/input rows inside
   #cardModal/#modalBox, same shell as the share sheet above. */
.fieldrow{display:block;margin-bottom:12px;font-size:11px;letter-spacing:.05em;
  text-transform:uppercase;color:var(--ink-dim)}
.fieldrow input{width:100%;margin-top:5px;text-transform:none;letter-spacing:normal;font-size:13px}
.modalactions{display:flex;justify-content:flex-end;margin-top:6px}

/* Ad placeholders (icy-veins.com-style scaffolding -- real ad tech comes
   later, these just reserve and demonstrate the layout). Left/right rails
   are `position:fixed`, not `sticky` -- the header itself scrolls away, but
   the rails hold a fixed distance from the viewport top regardless, so they
   read as "static while everything scrolls" the same way a real ad
   network's fixed skyscraper unit does. Positioned off .wrap's own
   half-width (1080px/2 = 540px) plus a gap, mirrored left/right from
   viewport center -- same technique, not a coincidence of matching pixels.
   Hidden below ~1500px: that's the narrowest viewport where both a 160px
   rail on each side plus the 24px gap actually clears the 1080px content
   column without overlapping it. */
.ad-rail{display:none;position:fixed;top:96px;width:160px;
  height:min(600px, calc(100vh - 120px));z-index:5;
  border:1px dashed var(--rule);border-radius:10px;background:var(--surface-2);
  align-items:center;justify-content:center;text-align:center}
.ad-rail-left{right:calc(50% + 540px + 24px)}
.ad-rail-right{left:calc(50% + 540px + 24px)}
@media (min-width:1500px){.ad-rail{display:flex}}
.ad-rail-inner{display:flex;flex-direction:column;gap:6px;color:var(--ink-dim)}
.ad-label{font-size:10.5px;letter-spacing:.08em;text-transform:uppercase}
.ad-dims{font-size:10px;opacity:.7}

/* Corner video ad -- fixed bottom-right, dismissible like a real one. The
   "video" is a CSS shimmer sweep behind a play glyph, not a real file: this
   is a placeholder for where a video creative will actually play. */
.ad-corner-video{position:fixed;right:16px;bottom:16px;width:280px;z-index:20;
  background:var(--surface);border:1px solid var(--rule);border-radius:12px;
  box-shadow:0 8px 24px rgba(0,0,0,.18);overflow:hidden}
.ad-corner-video-stage{position:relative;width:100%;aspect-ratio:16/9;
  background:linear-gradient(120deg, var(--navy), var(--blue));overflow:hidden;
  display:flex;align-items:center;justify-content:center}
.ad-corner-video-shimmer{position:absolute;inset:0;
  background:linear-gradient(100deg, transparent 30%, rgba(255,255,255,.25) 50%, transparent 70%);
  background-size:220% 100%;animation:ad-shimmer 2.6s linear infinite}
@keyframes ad-shimmer{from{background-position:120% 0}to{background-position:-20% 0}}
.ad-corner-play{width:40px;height:40px;color:#fff;opacity:.9;filter:drop-shadow(0 2px 6px rgba(0,0,0,.35))}
.ad-label-video{display:block;padding:8px 12px;color:var(--ink-dim)}
.ad-corner-close{position:absolute;top:8px;right:8px;z-index:1;width:22px;height:22px;
  border-radius:50%;border:0;background:rgba(0,0,0,.55);color:#fff;font-size:15px;
  line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center}
.ad-corner-close:hover{background:rgba(0,0,0,.75)}
@media (max-width:480px){.ad-corner-video{width:200px;right:10px;bottom:10px}}
