/* Pokémon TCG Tracker — styles */
:root {
  --bg: #0f1220;
  --surface: #191d30;
  --surface-2: #222741;
  --text: #eef0f8;
  --muted: #9aa1b9;
  --accent: #e3350d;
  --accent-soft: #ff5c39;
  --owned: #34c26b;
  --border: #2c3150;
  --radius: 14px;
  --shadow: 0 4px 16px rgba(0, 0, 0, .35);
}

* { box-sizing: border-box; }

/* Hidden means hidden. Any rule here that sets `display` on an element the app
   also hides outranks the browser's own [hidden] rule and leaves the thing on
   screen — that is how "Load more" stayed put with nothing left to load, and
   loaded the same cards over again every time it was pressed. */
[hidden] { display: none !important; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  background: var(--bg);
  color: var(--text);
  min-height: 100dvh;
  padding-bottom: env(safe-area-inset-bottom);
}

/* ---------- fit the phone, whatever phone it is ----------
   Real handsets are narrower than anything a desktop window emulates, and a
   single stubborn child is enough to make the whole page scroll sideways: a
   grid track with a px minimum, an unbreakable set name, a page-flip sheet
   mid-rotation. So: nothing may be wider than the screen, and if something
   still tries, the page clips instead of handing the user a sideways scroll.
   overflow-x: clip (not hidden) on purpose — clip does not create a scroll
   container, so the sticky top bar keeps sticking. */
body { overflow-x: clip; max-width: 100%; }

/* long unbreakable strings (set names with em dashes, IDs, URLs) wrap instead
   of forcing their container wide */
body { overflow-wrap: break-word; }

/* an image that reports a bigger intrinsic size than its slot never widens it */
img, svg, video, canvas { max-width: 100%; }

a { color: var(--accent-soft); text-decoration: none; }

/* ---------- top bar ---------- */
.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  padding-top: calc(10px + env(safe-area-inset-top));
  background: rgba(15, 18, 32, .92);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--border);
}

.brand {
  display: flex;
  align-items: center;
  gap: 8px;
  font-weight: 800;
  font-size: 17px;
  color: var(--text);
  white-space: nowrap;
}

.brand img { border-radius: 8px; }

.searchbar { flex: 1; min-width: 0; }

.searchbar input {
  width: 100%;
  padding: 9px 14px;
  border-radius: 999px;
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 15px;
  outline: none;
}

.searchbar input:focus { border-color: var(--accent-soft); }

.icon-btn {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 17px;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  cursor: pointer;
  flex-shrink: 0;
}

.icon-btn.synced { border-color: var(--owned); }

/* ---------- layout ---------- */
.view { max-width: 1100px; margin: 0 auto; padding: 16px 14px 60px; }

.page-head { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; margin: 6px 0 14px; }

.page-head h1 { font-size: 22px; margin: 0; }

.back-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  color: var(--muted);
  font-size: 14px;
  margin-bottom: 8px;
}

/* ---------- the header that stays ----------
   Everything above the cards is furniture you reach for WHILE reading the
   list: the way out, what you are looking at, and the controls. It used to
   scroll away as a block. Now it sticks under the search bar, and folds down
   to the two things worth the room once you are past the top. */
.page-sticky {
  position: sticky;
  /* measured in JS: the bar above is sticky too, and its height moves with
     the phone's safe-area inset */
  top: var(--topbar-h, 61px);
  z-index: 15;
  background: var(--bg);
  /* bleed through the view's own padding so cards pass behind it, not beside it */
  margin: 0 -14px;
  padding: 12px 14px 0;
}

/* swallow the view's top padding only when nothing is above it — home can put
   a build-progress banner first, and pulling up over that would overlap it */
.page-sticky:first-child { margin-top: -16px; }

.sticky-top { display: flex; align-items: baseline; gap: 10px; min-width: 0; }

/* the name of the thing, for when its heading has just folded away */
.sticky-title {
  display: none;
  font-weight: 700;
  font-size: 15px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.page-sticky .chips { margin-bottom: 0; padding-bottom: 12px; }

/* condensed: keep the way out and the controls, fold the rest */
.page-sticky.condensed { border-bottom: 1px solid var(--border); }

.page-sticky.condensed .page-head,
.page-sticky.condensed .set-filter,
.page-sticky.condensed .stats-banner,
.page-sticky.condensed .signin-banner,
.page-sticky.condensed > p { display: none; }

.page-sticky.condensed .sticky-title { display: block; }

.page-sticky.condensed .back-link { margin-bottom: 0; }

.page-sticky.condensed .sticky-top { padding-bottom: 8px; }

/* The way out, repeated at the end of a page. A long list scrolls its own
   back link off the top, and the fixed bottom bar only offers the four
   top-level destinations — not the one you actually came from. */
.page-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 24px;
  padding-top: 14px;
  border-top: 1px solid var(--border);
}

/* the top copy carries a bottom margin to sit off the heading; down here it
   is one half of a row and wants none */
.page-foot .back-link { margin-bottom: 0; }

/* ---------- stats banner ---------- */
.stats-banner {
  display: grid;
  /* minmax(0, …), not 1fr: a plain 1fr refuses to shrink below its contents,
     which is how three stat tiles push a narrow screen wide */
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 10px;
  margin-bottom: 18px;
}

.stat {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 14px 12px;
  text-align: center;
}

.stat .num { font-size: 22px; font-weight: 800; }

.stat .lbl { font-size: 12px; color: var(--muted); margin-top: 2px; }

.signin-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px 16px;
  margin-bottom: 18px;
}

.signin-banner .btn { flex-shrink: 0; }

/* iOS zooms the whole page in when you focus a field smaller than 16px, and
   never zooms back out — that's how you end up magnified and scrolled sideways.
   Sixteen on touch devices stops it happening in the first place. */
@media (pointer: coarse) {
  input[type=text], input[type=search], input[type=number], input[type=email],
  input[type=password], input:not([type]), textarea, select:not(.chip) { font-size: 16px; }
}

/* ---------- set list ---------- */
.set-filter { margin-bottom: 12px; }

.set-filter input {
  width: 100%;
  padding: 10px 14px;
  border-radius: var(--radius);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--text);
  font-size: 15px;
  outline: none;
}

.set-grid {
  display: grid;
  /* min(260px, 100%) so a screen narrower than one track still gets one column
     that fits, instead of a 260px column hanging off the right edge */
  grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr));
  gap: 12px;
}

.set-card {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 12px;
  cursor: pointer;
  color: var(--text);
  transition: border-color .15s, transform .1s;
}

.set-card:hover { border-color: var(--accent-soft); }

.set-card:active { transform: scale(.98); }

.set-card .logo {
  width: 74px;
  height: 46px;
  object-fit: contain;
  flex-shrink: 0;
  background: transparent;
}

.set-card .logo.placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 24px;
}

.set-card .info { flex: 1; min-width: 0; }

.set-card .name {
  font-weight: 700;
  font-size: 14.5px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.set-card .count { font-size: 12px; color: var(--muted); margin-top: 2px; }

.set-card.complete { border-color: var(--owned); }

.progress {
  height: 6px;
  border-radius: 3px;
  background: var(--surface-2);
  margin-top: 8px;
  overflow: hidden;
}

.progress > div {
  height: 100%;
  background: linear-gradient(90deg, var(--accent), var(--accent-soft));
  border-radius: 3px;
  transition: width .3s;
}

.progress.done > div { background: var(--owned); }

/* ---------- filter chips ---------- */
.chips { display: flex; gap: 8px; flex-wrap: wrap; margin: 10px 0 14px; }

.chip {
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--muted);
  border-radius: 999px;
  padding: 6px 14px;
  font-size: 13px;
  cursor: pointer;
}

.chip.active { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; }

select.chip { appearance: none; padding-right: 22px; }

/* ---------- card grid ---------- */
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100px, 100%), 1fr));
  gap: 10px;
}

@media (min-width: 700px) {
  .card-grid { grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr)); }
}

/* ---------- the same printings, as a list ----------
   The grid answers "what does this look like"; the list answers "what am I
   missing", which is a question about names and numbers. One row per printing,
   same as one tile per printing, so the two views count the same things. */
.card-list { display: flex; flex-direction: column; }

.card-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 4px 8px 8px;
  border-bottom: 1px solid var(--border);
  cursor: pointer;
  min-height: 44px;               /* a thumb-sized target even with no picture */
}

.card-row:first-child { border-top: 1px solid var(--border); }

.card-row:hover { background: var(--surface); }

.card-row:focus-visible { outline: 2px solid var(--accent-soft); outline-offset: -2px; }

/* Missing is the state worth seeing at a glance, so it is the one that is
   marked — dimmed text and a hollow circle, against a plain owned row. */
.card-row.missing .row-name, .card-row.missing .row-thumb { opacity: .55; }

.row-thumb {
  width: 34px;
  aspect-ratio: 63 / 88;
  flex-shrink: 0;
  border-radius: 3px;
  overflow: hidden;
  background: var(--surface-2);
}

.row-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

.row-noimg { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-size: 15px; }

.row-main { flex: 1; min-width: 0; }

.row-name { display: flex; gap: 8px; align-items: baseline; font-size: 14.5px; font-weight: 600; }

.row-name > span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.row-variant { color: var(--muted); font-size: 12px; font-weight: 500; flex-shrink: 0; }

.row-meta { margin-top: 1px; }

/* the tick column: fixed width so the marks line up down the page rather than
   wandering with the length of each name */
.row-mark {
  flex-shrink: 0;
  min-width: 34px;
  text-align: right;
  font-size: 13px;
  font-weight: 700;
  color: var(--owned);
}

.card-row.missing .row-mark { color: var(--muted); font-weight: 400; }

/* the ⓘ sits at the tile's corner; in a row it is just the last column */
.card-row .info-btn {
  position: static;
  flex-shrink: 0;
  opacity: .75;
}

/* the admin's "add a card" door, sized for a list rather than a grid cell */
.add-card-row { align-self: flex-start; margin-top: 12px; }

/* ---------- a binder read as a checklist ---------- */
/* the book element hosts the list too, so it drops the things that only make
   sense around a drawn page */
.binder-book.as-list { display: block; perspective: none; max-width: 720px; }

.binder-page-label {
  font-size: 12px;
  font-weight: 700;
  color: var(--muted);
  letter-spacing: .04em;
  text-transform: uppercase;
  margin: 16px 0 4px;
}

.binder-page-label:first-child { margin-top: 4px; }

/* which pocket on the sheet — the whole reason this list is worth more than a
   sorted one is that it tells you where to put your hand */
.row-pocket {
  flex-shrink: 0;
  width: 22px;
  text-align: center;
  font-size: 12px;
  color: var(--muted);
  border-right: 1px solid var(--border);
  padding-right: 8px;
  margin-right: 2px;
}

.tcg-card {
  position: relative;
  border-radius: 10px;
  overflow: hidden;
  cursor: pointer;
  background: var(--surface);
  border: 1px solid var(--border);
  aspect-ratio: 63 / 88;
  transition: transform .1s;
}

.tcg-card:active { transform: scale(.96); }

.tcg-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: filter .2s, opacity .2s;
}

.tcg-card.missing img { filter: grayscale(1) brightness(.55); }

.tcg-card .noimg {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
  padding: 8px;
  text-align: center;
  gap: 6px;
  font-size: 12px;
  color: var(--muted);
}

/* prices: the figure a printing goes for, worn in the tile's corner / at the row's end */
.tcg-card .price-pill {
  position: absolute; top: 6px; left: 6px; padding: 2px 7px; border-radius: 999px;
  background: rgba(10, 14, 22, .78); color: #fff; font-size: 11px; font-weight: 700; letter-spacing: .01em;
  font-variant-numeric: tabular-nums; pointer-events: none; backdrop-filter: blur(2px);
}
.tcg-card.missing .price-pill { opacity: .8; }
.card-row .row-price { flex-shrink: 0; font-size: 13px; font-weight: 600; font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--text); }
.card-row.missing .row-price { color: var(--muted); font-weight: 500; }
.price-box { margin-top: 10px; display: flex; flex-direction: column; gap: 4px; }
.price-line { text-align: center; }
.price-now { font-size: 18px; font-weight: 800; font-variant-numeric: tabular-nums; margin-right: 4px; }
.price-chart { margin: 4px 0 0; position: relative; padding-top: 22px; }
.price-spark { width: 100%; height: auto; display: block; -webkit-touch-callout: none; -webkit-user-select: none; user-select: none; }
.price-spark.scrubbing { cursor: ew-resize; }
.spark-grid { stroke: var(--border); stroke-width: 1; stroke-dasharray: 2 3; }
.spark-axis { stroke: var(--border); stroke-width: 1; }
.spark-tick { fill: var(--muted); font-size: 10px; font-variant-numeric: tabular-nums; }
.spark-rule { stroke: var(--muted); stroke-width: 1; pointer-events: none; }
.spark-mark { fill: var(--accent); stroke: var(--surface); stroke-width: 2; pointer-events: none; }
.spark-tip { position: absolute; top: 0; white-space: nowrap; pointer-events: none; font-size: 12px; line-height: 1;
  padding: 4px 8px; border-radius: 6px; background: var(--surface-2); border: 1px solid var(--border); color: var(--text); }
.spark-tip span { color: var(--muted); }
.spark-area { fill: var(--accent); opacity: .15; }
.spark-line { fill: none; stroke: var(--accent); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.spark-dot { fill: var(--accent); }
.set-card .set-value { font-variant-numeric: tabular-nums; }

.tcg-card .badge {
  position: absolute;
  top: 6px;
  right: 6px;
  background: var(--owned);
  color: #fff;
  font-size: 12px;
  font-weight: 800;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--shadow);
}




.tcg-card .qty-badge {
  /* beside the tick, top-right — the top-left corner belongs to the price */
  position: absolute;
  top: 6px;
  right: 32px;
  background: rgba(15, 18, 32, .85);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 11px;
  font-weight: 700;
  padding: 2px 7px;
  border-radius: 999px;
}

.tcg-card .info-btn {
  position: absolute;
  bottom: 6px;
  right: 6px;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  border: none;
  background: rgba(15, 18, 32, .8);
  color: var(--text);
  font-size: 13px;
  cursor: pointer;
  opacity: .85;
}

.load-more { display: block; margin: 20px auto; }

/* ---------- buttons ---------- */
.btn {
  background: var(--accent);
  border: none;
  color: #fff;
  font-weight: 700;
  font-size: 14px;
  padding: 10px 18px;
  border-radius: 10px;
  cursor: pointer;
}

.btn:hover { background: var(--accent-soft); }

.btn.ghost {
  background: transparent;
  border: 1px solid var(--border);
  color: var(--text);
}

.btn.small { padding: 6px 12px; font-size: 13px; }

.row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }

/* ---------- modal ---------- */
.modal {
  border: 1px solid var(--border);
  border-radius: 16px;
  background: var(--surface);
  color: var(--text);
  padding: 0;
  max-width: min(440px, 92vw);
  width: 100%;
  box-shadow: var(--shadow);
}

.modal::backdrop { background: rgba(0, 0, 0, .6); backdrop-filter: blur(3px); }

/* An installed app draws under the phone's status bar (viewport-fit=cover), and
   a dialog centers itself in the WHOLE screen — so a tall one put its top, and
   the way out, behind the clock. It lives between the safe-area insets now. */
.modal {
  top: calc(env(safe-area-inset-top) + 8px);
  bottom: calc(env(safe-area-inset-bottom) + 8px);
  max-height: calc(100vh - env(safe-area-inset-top) - env(safe-area-inset-bottom) - 16px);
  max-height: calc(100dvh - env(safe-area-inset-top) - env(safe-area-inset-bottom) - 16px);
  overscroll-behavior: contain;
}
.modal-body { padding: 20px; position: relative; }
/* Card details on a wide screen: the dialog grows and the long single column
   becomes picture | everything else — two columns at most, however wide the
   monitor (three were tried and he preferred two). A phone keeps the one column. */
@media (min-width: 860px) {
  #card-modal { max-width: min(880px, 94vw); }
  #card-modal .cm-grid {
    display: grid; grid-template-columns: minmax(0, 380px) minmax(0, 1fr); column-gap: 24px;
    grid-template-areas: "pic facts" "pic price" "pic actions"; grid-template-rows: auto auto 1fr; align-items: start;
  }
  #card-modal .cm-pic { grid-area: pic; position: sticky; top: 0; }
  #card-modal .cm-facts { grid-area: facts; }
  #card-modal .cm-price { grid-area: price; }
  #card-modal .cm-actions { grid-area: actions; }
  #card-modal .cm-pic .card-img-wrap, #card-modal .cm-pic .card-img { margin-bottom: 0; }
}

/* The way out rides along: sticky in the corner, so it is on screen however far
   the details have been scrolled, and big enough for a thumb. */
.modal-close {
  position: sticky; top: 10px; z-index: 6; float: right; margin: -10px -10px 0 8px;
  width: 40px; height: 40px; border-radius: 50%;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text);
  box-shadow: 0 2px 8px rgba(0, 0, 0, .35);
  font-size: 24px; line-height: 1; display: flex; align-items: center; justify-content: center; cursor: pointer; padding: 0;
}
.modal-close:hover { color: var(--text); border-color: var(--text); }

.modal h2 { margin: 0 0 12px; font-size: 19px; }

.modal h3 { font-size: 15px; margin: 14px 0 6px; }

.modal hr { border: none; border-top: 1px solid var(--border); margin: 16px 0; }

.modal .card-img { width: 100%; border-radius: 12px; display: block; margin-bottom: 12px; }

.modal .kv { display: flex; justify-content: space-between; gap: 12px; padding: 6px 0; border-bottom: 1px solid var(--border); font-size: 14px; }

.modal .kv span:first-child { color: var(--muted); }

.qty-row { display: flex; align-items: center; justify-content: center; gap: 16px; margin: 16px 0 6px; }

.qty-row .qty { font-size: 22px; font-weight: 800; min-width: 40px; text-align: center; }

.qty-row button {
  width: 44px;
  height: 44px;
  font-size: 20px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
}

.field { margin-bottom: 10px; }

.field input {
  width: 100%;
  padding: 10px 12px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--text);
  font-size: 15px;
  outline: none;
}

.field input:focus { border-color: var(--accent-soft); }

.error-msg { color: #ff7b6b; font-size: 13px; margin: 8px 0; }

.ok-msg { color: var(--owned); font-size: 13px; margin: 8px 0; }

.muted { color: var(--muted); font-size: 14px; }

.small { font-size: 12px; }

.tabs { display: flex; gap: 6px; margin-bottom: 14px; }

/* two kinds of tab share this strip: the sign-in form's, which switch what a
   form is for, and the settings pages', which are links to addresses */
.tabs button, .tabs a {
  flex: 1;
  padding: 8px;
  border-radius: 10px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-weight: 600;
}

.tabs a { text-align: center; text-decoration: none; font-size: 14px; }

.tabs button.active, .tabs a.active { background: var(--surface-2); color: var(--text); border-color: var(--accent-soft); }

/* a narrow phone cannot fit four tabs across; let them wrap rather than
   squeeze the labels into initials */
@media (max-width: 420px) {
  .tabs { flex-wrap: wrap; }
  .tabs a { flex: 1 1 40%; }
}

/* ---------- settings pages (#/account, #/admin) ---------- */
.settings-page { max-width: 640px; margin: 0 auto; }

.settings-card {
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--surface);
  padding: 14px 16px;
  margin-bottom: 12px;
}

.settings-card:empty { display: none; }

.settings-card h3 { font-size: 15px; margin: 0 0 6px; }

.settings-card hr { border: none; border-top: 1px solid var(--border); margin: 14px 0; }

/* every labelled field gets the same air above and below it, so a card of
   twelve of them reads as a list rather than a wall */
.settings-card .ce-field { margin: 10px 0 6px; }

.settings-card .row { margin-top: 10px; }

.settings-card .ce-field + .row { margin-top: 4px; }

/* ---------- toast ---------- */
.toast {
  position: fixed;
  bottom: calc(20px + env(safe-area-inset-bottom));
  left: 50%;
  transform: translateX(-50%);
  background: var(--surface-2);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 10px 18px;
  border-radius: 999px;
  font-size: 14px;
  z-index: 50;
  box-shadow: var(--shadow);
  max-width: 90vw;
  text-align: center;
}

/* ---------- misc ---------- */
.center { text-align: center; padding: 40px 0; color: var(--muted); }

.spinner {
  width: 34px;
  height: 34px;
  margin: 40px auto;
  border: 3px solid var(--surface-2);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: spin .8s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

/* --- spinning-pokeball loader, shown while a card picture loads --- */
.img-loading::after {
  content: '';
  position: absolute;
  inset: 0;
  margin: auto;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  border: 2px solid #16181f;
  /* pokeball: red top half, black band, white bottom half, center button */
  background:
    radial-gradient(circle, #fff 0 5px, #16181f 5px 8px, transparent 8px),
    linear-gradient(to bottom, #e3350d 0 44%, #16181f 44% 56%, #f2f2f2 56% 100%);
  animation: pokeball-spin .9s linear infinite;
  box-shadow: 0 3px 10px rgba(0, 0, 0, .4);
  z-index: 3;
  pointer-events: none;
}
/* the modal image has no height until it loads — reserve a card-shaped area */
.card-img-wrap.img-loading { aspect-ratio: 63 / 88; }
.card-img-wrap.img-loading::after {
  width: 64px;
  height: 64px;
  border-width: 3px;
  background:
    radial-gradient(circle, #fff 0 9px, #16181f 9px 14px, transparent 14px),
    linear-gradient(to bottom, #e3350d 0 44%, #16181f 44% 56%, #f2f2f2 56% 100%);
}
@keyframes pokeball-spin { to { transform: rotate(360deg); } }

/* ---------- bottom navigation ---------- */
.bottomnav {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 20;
  display: flex;
  background: rgba(15, 18, 32, .95);
  backdrop-filter: blur(10px);
  border-top: 1px solid var(--border);
  padding-bottom: env(safe-area-inset-bottom);
}

.bottomnav a {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 8px 0 6px;
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}

.bottomnav a.active { color: var(--accent-soft); }

.bottomnav .nav-icon { font-size: 18px; line-height: 1; }

body { padding-bottom: calc(64px + env(safe-area-inset-bottom)); }

/* the collapse control belongs to the sidebar; across the bottom there is
   nothing to collapse and no room to say so */
.nav-toggle { display: none; }

/* ---------- …and as a left sidebar, on a window wide enough to spare it ----------
   A bar across the bottom is right where a thumb is: on a phone. On a laptop
   the bottom of the window is the furthest point from what you are reading,
   and there is width going spare at the sides. Same markup, turned ninety
   degrees. 1000px, because the app's other breakpoint (700px) also covers
   half-screen windows and tablets, where 200px of chrome is a big fraction of
   what is left. */
@media (min-width: 1000px) {
  :root { --sidenav-w: 200px; }

  body {
    padding-bottom: 0;
    /* the sidebar is fixed, so the page has to be told to stop short of it */
    padding-left: var(--sidenav-w);
  }

  /* …except the top bar, which owns the full width of the window and the
     sidebar hangs below it. Search and the account button belong to the whole
     app, not to the part of it right of the menu. The negative margin cancels
     the body's gutter; the width then follows from being a block. */
  .topbar { margin-left: calc(-1 * var(--sidenav-w)); }

  .bottomnav {
    /* parked under the bar above, exactly like the page header is */
    top: var(--topbar-h, 61px);
    bottom: 0;
    right: auto;
    width: var(--sidenav-w);
    flex-direction: column;
    align-items: stretch;
    gap: 2px;
    border-top: none;
    border-right: 1px solid var(--border);
    padding: 10px 8px 12px;
  }

  .bottomnav a, .nav-toggle {
    flex: 0 0 auto;
    flex-direction: row;
    justify-content: flex-start;
    align-items: center;
    gap: 12px;
    padding: 11px 12px;
    border-radius: 10px;
    font-size: 14px;
    white-space: nowrap;
  }

  .bottomnav a:hover { background: var(--surface); color: var(--text); }

  .bottomnav a.active { background: var(--surface-2); }

  .bottomnav .nav-icon { font-size: 19px; width: 22px; text-align: center; flex-shrink: 0; }

  .nav-toggle {
    display: flex;
    border: none;
    background: transparent;
    color: var(--muted);
    font-weight: 600;
    font-family: inherit;
    cursor: pointer;
    margin-bottom: 6px;
  }

  .nav-toggle:hover { background: var(--surface); color: var(--text); }

  /* collapsed: the four ways to anywhere are still one click, they just stop
     spelling themselves out */
  body.nav-rail { --sidenav-w: 64px; }

  body.nav-rail .nav-label { display: none; }

  body.nav-rail .bottomnav a, body.nav-rail .nav-toggle { justify-content: center; padding: 11px 0; gap: 0; }

  /* a toast centred on the viewport reads as off-centre against the page */
  .toast { left: calc(50% + var(--sidenav-w) / 2); }
}

/* ---------- variant tracking ---------- */
.variant-list { display: flex; flex-direction: column; gap: 6px; margin-top: 6px; }

.variant-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg);
}

.variant-row.owned { border-color: var(--owned); }

.variant-row .v-name { font-size: 14px; }

.variant-row.owned .v-name::before { content: '✓ '; color: var(--owned); font-weight: 700; }

.v-controls { display: flex; align-items: center; gap: 10px; }

.v-controls button {
  width: 34px;
  height: 34px;
  font-size: 17px;
  border-radius: 50%;
  border: 1px solid var(--border);
  background: var(--surface-2);
  color: var(--text);
  cursor: pointer;
}

.v-controls .v-qty { min-width: 26px; text-align: center; font-weight: 800; font-size: 16px; }

/* ---------- printing looks (used when no dedicated scan exists) ---------- */
.fx { position: absolute; inset: 0; pointer-events: none; border-radius: inherit; }

/* the printing's name written across the closest (base) card image */
/* the banner's center sits 75% down the card (his preference), not at 50% */
.fx-label span {
  position: absolute; top: 75%; left: 50%;
  transform: translate(-50%, -50%) rotate(-24deg);
  background: rgba(10, 12, 22, .74);
  border: 1px solid rgba(255, 255, 255, .35);
  color: #fff;
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .5px;
  font-size: 10.5px;
  line-height: 1.2;
  text-align: center;
  padding: 4px 10px;
  border-radius: 6px;
  max-width: 94%;
  box-shadow: 0 1px 5px rgba(0, 0, 0, .45);
}

.modal .fx-label span { font-size: 21px; padding: 8px 18px; }

.tcg-card.missing .fx-label span, .pocket.filled:not(.have) .fx-label span { opacity: .55; }

/* modal image wrapper so overlays can position over the big picture */
.card-img-wrap { position: relative; border-radius: 12px; overflow: hidden; margin-bottom: 12px; }

.card-img-wrap .card-img { margin-bottom: 0; }

/* ---------- pokémon list ---------- */
.poke-thumb { object-fit: cover; border-radius: 6px; width: 46px; height: 64px; }

/* ---------- scanner ---------- */
.scan-stage {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  background: #000;
  aspect-ratio: 3 / 4;
  max-height: 60vh;
}

.scan-video { width: 100%; height: 100%; object-fit: cover; display: block; }

.scan-guide {
  position: absolute;
  top: 50%;
  left: 50%;
  height: 70%;
  aspect-ratio: 63 / 88;
  transform: translate(-50%, -50%);
  border: 3px solid rgba(255, 255, 255, .85);
  border-radius: 12px;
  box-shadow: 0 0 0 2000px rgba(0, 0, 0, .45);
  pointer-events: none;
}

.scan-results { display: flex; flex-direction: column; gap: 10px; }

.scan-result {
  display: flex;
  align-items: center;
  gap: 12px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  padding: 10px;
  cursor: pointer;
}

.scan-result.have { border-color: var(--owned); }

.scan-result img { width: 56px; border-radius: 6px; }

.scan-result .info { flex: 1; min-width: 0; }

.scan-result .name { font-weight: 700; font-size: 14.5px; }

.scan-result .count { font-size: 12px; color: var(--muted); margin: 2px 0; }

.scan-result .own-status { font-size: 12.5px; font-weight: 600; }

.scan-result.have .own-status { color: var(--owned); }

.scan-result:not(.have) .own-status { color: var(--accent-soft); }

.match-score {
  font-size: 13px;
  font-weight: 800;
  color: var(--muted);
  background: var(--surface-2);
  border-radius: 999px;
  padding: 4px 9px;
  flex-shrink: 0;
}

@media (max-width: 480px) {
  .brand span { display: none; }
  .stats-banner { gap: 8px; }
  .stat .num { font-size: 18px; }
}

/* ---------- binders ---------- */
.binder-list { display: grid; grid-template-columns: repeat(auto-fill, minmax(min(150px, 100%), 1fr)); gap: 12px; margin-bottom: 20px; }
.binder-cover {
  aspect-ratio: 3 / 4; border-radius: 10px 14px 14px 10px; padding: 14px 12px 12px 20px;
  display: flex; flex-direction: column; justify-content: flex-end; gap: 4px;
  color: #fff; position: relative; border: 1px solid rgba(255,255,255,.14);
  box-shadow: inset 10px 0 14px -10px rgba(0,0,0,.65);
}
.binder-cover::before { /* spine rings */
  content: ''; position: absolute; left: 6px; top: 12%; bottom: 12%; width: 3px;
  border-radius: 3px; background: repeating-linear-gradient(rgba(255,255,255,.55) 0 6px, transparent 6px 18px);
}
.binder-cover:hover { filter: brightness(1.12); }
.binder-name { font-weight: 700; font-size: 15px; line-height: 1.2; word-break: break-word; }
.binder-meta { font-size: 12px; opacity: .85; }
/* ---------- "move to page": a page you can see without turning to it ---------- */
/* narrow on purpose: nine card-shaped cells at full panel width make a grid
   taller than the panel, and a destination you have to scroll to find is a
   worse map of the page than the page itself */
.move-grid { display: grid; gap: 6px; max-width: 264px; margin: 2px auto; width: 100%; }
/* a 5×5 binder is 25 cells — the panel scrolls rather than overflowing */
.picker-panel.move-panel { overflow-y: auto; }
.move-cell {
  aspect-ratio: 63 / 88; border-radius: 6px; cursor: pointer;
  border: 1px dashed var(--border); background: var(--surface-2); color: var(--muted);
  font-size: 11px; line-height: 1.15; padding: 4px; overflow: hidden;
  display: flex; align-items: center; justify-content: center; text-align: center;
}
.move-cell.filled { border-style: solid; border-color: var(--accent-soft); color: var(--text); }
.move-cell:not(:disabled):hover { border-color: var(--accent); background: var(--surface); }
/* the pocket it is already in, and pockets a picture is lying across: both are
   places this card cannot go, and they should look it rather than just fail */
.move-cell:disabled { cursor: default; opacity: .45; }
.move-cell.self { border-style: solid; }

/* a binder that is out in the world, marked from the shelf */
.binder-shared {
  position: absolute; top: 8px; right: 8px; font-size: 13px; line-height: 1;
  background: rgba(0,0,0,.45); border-radius: 999px; padding: 4px 6px;
}
.b-red    { background: linear-gradient(145deg, #b3271e, #7c1712); }
.b-blue   { background: linear-gradient(145deg, #2456b3, #16336e); }
.b-green  { background: linear-gradient(145deg, #1e8a4c, #135631); }
.b-purple { background: linear-gradient(145deg, #6d3fb3, #45266f); }
.b-black  { background: linear-gradient(145deg, #3a3f47, #17191d); }
.binder-dot { display: inline-block; width: 14px; height: 14px; border-radius: 4px; vertical-align: baseline; }
.swatch { display: inline-block; width: 30px; height: 30px; border-radius: 8px; border: 2px solid transparent; cursor: pointer; }
/* "no color" is a choice, not an absence, so it has to look deliberate: an
   empty shell with a line through it rather than a swatch that failed to load. */
.b-none { background: var(--card, #1c2333); }
.binder-cover.b-none:not(.has-art), .binder-cover-page.b-none:not(.has-art) { border: 1px dashed rgba(255,255,255,.3); }
.b-none::before { opacity: .4; }
.swatch.b-none, .binder-dot.b-none {
  border: 2px solid rgba(255,255,255,.35);
  background-image: linear-gradient(45deg, transparent 43%, #e05a5a 43%, #e05a5a 57%, transparent 57%);
}
.swatch.active, .swatch.b-none.active { border-color: #fff; outline: 2px solid var(--accent-soft); }
/* the color choices inside the cover picker: swatch above its own name, since
   a bare square is a guess and this row now includes one that means "none" */
.swatch-pick { display: flex; flex-direction: column; align-items: center; gap: 5px;
  background: none; border: 0; padding: 2px; cursor: pointer; font: inherit; }
.swatch-pick:hover .swatch { filter: brightness(1.15); }
/* nothing behind a picture any more, so it shows at full strength — and gets a
   scrim at the foot instead, which is what the name actually needs to stay legible */
.binder-cover.b-none .binder-cover-img { opacity: 1; }
.binder-cover-page.b-none img.cover-photo { opacity: 1; }
.binder-cover.has-art::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  background: linear-gradient(transparent 40%, rgba(0,0,0,.8));
}
.binder-create { border: 1px solid var(--line, #333); border-radius: 12px; padding: 14px; max-width: 560px; }
.binder-create input[type=text], .binder-create select {
  background: var(--card, #1c2333); color: inherit; border: 1px solid var(--line, #333);
  border-radius: 8px; padding: 8px 10px; font-size: 14px;
}
.binder-grid { display: grid; gap: 8px; max-width: 720px; margin: 0 auto; }
.pocket {
  aspect-ratio: 63 / 88; border: 2px dashed var(--line, #3a4157); border-radius: 8px;
  position: relative; overflow: hidden; cursor: pointer;
  display: flex; align-items: center; justify-content: center; background: rgba(255,255,255,.02);
}
.pocket:hover { border-color: var(--accent-soft); }
.pocket.filled { border-style: solid; }
.pocket.filled img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pocket.filled:not(.have) img { filter: grayscale(.85) brightness(.55); }
.pocket.have { border-color: var(--owned); }
.pocket.moving { outline: 3px solid var(--accent-soft); }
.pocket-plus { font-size: 26px; color: var(--line, #3a4157); }
.pocket-name { font-size: 11px; padding: 4px; text-align: center; }
/* a picture that never came down after several tries: tap to ask again */
.stalled { cursor: pointer; }
.stalled::after { content: '\21bb'; display: block; opacity: .6; font-size: 15px; }
.pocket-badge {
  position: absolute; top: 4px; left: 4px; background: var(--owned); color: #fff;
  border-radius: 50%; width: 20px; height: 20px; font-size: 12px;
  display: flex; align-items: center; justify-content: center;
}
.pocket-qty {
  position: absolute; bottom: 4px; right: 4px; background: rgba(0,0,0,.7); color: #fff;
  border-radius: 8px; padding: 1px 6px; font-size: 11px; font-weight: 700; pointer-events: none;
}
/* set name + card number along the bottom edge, in every binder mode */
.pocket-cap, .card-cap {
  position: absolute; left: 0; right: 0; bottom: 0; pointer-events: none;
  display: flex; align-items: baseline; gap: 4px; justify-content: space-between;
  background: rgba(0, 0, 0, .72); color: #fff; padding: 1px 4px;
  font-size: 9px; line-height: 1.35; letter-spacing: .01em;
}
.pocket-cap .cap-set, .card-cap .cap-set { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.pocket-cap .cap-no, .card-cap .cap-no { flex: 0 0 auto; font-weight: 700; }
/* badges step up so the caption never sits on top of them */
.pocket.capped .pocket-qty, .pocket.capped .pick-badge { bottom: 19px; }
/* the tile's ⓘ button clears the strip too, and the strip clears the rounded corner */
.tcg-card.capped .info-btn { bottom: 22px; }
.tcg-card .card-cap { border-radius: 0 0 9px 9px; }
@media (min-width: 700px) {
  .pocket-cap, .card-cap { font-size: 10px; padding: 2px 5px; }
  .tcg-card.capped .info-btn { bottom: 24px; }
}
/* view mode: empty pockets are just empty — not an "add here" affordance */
.pocket.plain { cursor: default; }
.pocket.plain:hover { border-color: var(--line, #3a4157); }
.pocket-edit {
  position: absolute; top: 2px; right: 2px; background: rgba(0,0,0,.55); color: #fff;
  border: 0; border-radius: 6px; padding: 2px 7px; font-size: 13px; cursor: pointer;
}
.pocket-actions { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; justify-content: center; margin: 12px 0; }
/* "select to print": pockets act as checkboxes, the bar keeps the running count */
.pick-bar {
  display: flex; gap: 8px; flex-wrap: wrap; align-items: center; justify-content: center;
  border: 1px solid var(--accent-soft, #ff5c39); border-radius: 10px;
  background: rgba(227, 53, 13, .12); padding: 8px 10px; margin: 10px 0 0;
}
.pocket.picked { outline: 3px solid var(--accent, #e3350d); outline-offset: -1px; border-color: var(--accent, #e3350d); }
.pocket.picked.filled:not(.have) img { filter: grayscale(.4) brightness(.8); }
.pick-badge {
  position: absolute; bottom: 4px; left: 4px; background: var(--accent, #e3350d); color: #fff;
  border-radius: 50%; width: 20px; height: 20px; font-size: 11px;
  display: flex; align-items: center; justify-content: center; pointer-events: none;
}
.picker-overlay {
  position: fixed; inset: 0; background: rgba(0,0,0,.65); z-index: 60;
  display: flex; align-items: flex-start; justify-content: center; padding: 30px 12px;
}
.picker-panel {
  background: var(--card, #1c2333); border: 1px solid var(--line, #333); border-radius: 12px;
  padding: 14px; width: 100%; max-width: 480px; max-height: 80vh; display: flex; flex-direction: column; gap: 10px;
}
.picker-panel input { flex: 1; background: transparent; color: inherit; border: 1px solid var(--line, #333); border-radius: 8px; padding: 8px 10px; }
.picker-results { overflow-y: auto; display: flex; flex-direction: column; gap: 8px; }
.picker-row { display: flex; gap: 10px; align-items: flex-start; border-bottom: 1px solid var(--line, #2a3040); padding-bottom: 8px; }
.picker-row img, .picker-thumb { width: 46px; aspect-ratio: 63/88; object-fit: cover; border-radius: 4px; flex-shrink: 0; }
.picker-thumb { display: flex; align-items: center; justify-content: center; background: rgba(255,255,255,.05); }
.picker-info { flex: 1; min-width: 0; }

/* ---------- proxy printing ---------- */
#print-area { display: none; }
body.printing-proxies #print-area {
  /* no gap by default — butted cards share one cut line, so you cut once per
     seam instead of twice and fit more per sheet. printProxies overrides this
     with whatever spacing was chosen in the dialog. */
  display: flex; flex-wrap: wrap; gap: 0; align-content: flex-start;
  background: #fff; padding: 0;
}
@media print {
  body.printing-proxies > :not(#print-area) { display: none !important; }
  /* padding, not just margin: on a wide window the body carries the sidebar's
     gutter, and a proxy sheet indented by 200mm-worth of nothing wastes paper */
  body.printing-proxies { background: #fff !important; margin: 0; padding: 0 !important; }
  body.printing-proxies #print-area { padding: 0; }
}
.print-cell {
  width: 63mm; height: 88mm; box-sizing: border-box; position: relative; overflow: hidden;
  break-inside: avoid; page-break-inside: avoid;
  outline: 0.3mm dashed #999;               /* cut guide */
  background: #fff; border-radius: 0;
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.print-cell img { width: 100%; height: 100%; object-fit: cover; display: block; }
.print-fallback {
  width: 100%; height: 100%; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 2mm; border: 0.5mm solid #444; color: #111; text-align: center; padding: 3mm;
}
.pf-name { font-weight: 700; font-size: 12pt; }
.pf-meta { font-size: 8pt; color: #555; }
/* same pill as the web app's .fx-label: dark rounded box at the 75% line */
.print-fx {
  position: absolute; top: 75%; left: 50%; max-width: 94%; box-sizing: border-box;
  transform: translate(-50%, -50%) rotate(-24deg);
  background: rgba(10, 12, 22, .74); color: #fff;
  border: 0.3mm solid rgba(255, 255, 255, .35); border-radius: 2mm;
  font-weight: 800; font-size: 9pt; letter-spacing: 0.04em; text-transform: uppercase;
  text-align: center; line-height: 1.2; padding: 1.5mm 3mm;
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
/* same caption the binder shows, so a cut proxy still says what it stands in for */
.print-cap {
  position: absolute; left: 0; right: 0; bottom: 0; box-sizing: border-box;
  display: flex; align-items: baseline; gap: 2mm; justify-content: space-between;
  background: rgba(10, 12, 22, .8); color: #fff; padding: 0.8mm 2mm;
  font-size: 6.5pt; line-height: 1.25;
  -webkit-print-color-adjust: exact; print-color-adjust: exact;
}
.print-cap .cap-set { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.print-cap .cap-no { flex: 0 0 auto; font-weight: 700; }
.print-cell.print-art { outline: 0.3mm dashed #999; }

/* ---------- art placement editor ---------- */
.art-editor { max-width: 500px; }
.art-board {
  position: relative; overflow: hidden; margin: 0 auto;
  background: #0b0e14; border: 1px solid var(--line, #333); border-radius: 8px;
  touch-action: none; cursor: grab; user-select: none;
}
.art-board:active { cursor: grabbing; }
.art-board > img { position: absolute; max-width: none; pointer-events: none; }
.art-cells { position: absolute; inset: 0; }
.art-cell { position: absolute; border: 1px dashed rgba(255,255,255,.45); border-radius: 4px; box-sizing: border-box; }
.art-cell.sel { border: 2px solid var(--owned); box-shadow: inset 0 0 0 1px rgba(52,194,107,.4); }
.art-cell.blocked { background: rgba(227,53,13,.18); border-color: rgba(227,53,13,.6); cursor: not-allowed; }
.art-editor input[type=range] { flex: 1; }
.pocket.art { background-color: #000; }

/* ---------- binder book: cover, spreads, page flips ---------- */
.binder-book { position: relative; max-width: 900px; margin: 0 auto; perspective: 1800px; }
.binder-book.spread { display: flex; gap: 0; }
.book-page { flex: 1 1 50%; min-width: 0; padding: 10px; border: 1px solid var(--line, #2a3040); background: rgba(255,255,255,.02); }
.book-page.left { border-radius: 12px 2px 2px 12px; border-right: 0; box-shadow: inset -14px 0 18px -14px rgba(0,0,0,.7); }
.book-page.right { border-radius: 2px 12px 12px 2px; box-shadow: inset 14px 0 18px -14px rgba(0,0,0,.7); }
.book-page.single { border-radius: 12px; max-width: 480px; margin: 0 auto; }
.book-page .binder-grid { max-width: none; }
/* edit mode: each page carries a remove-sheet button that says so in words,
   sitting under the page rather than floating over its corner — in a spread,
   a corner button is genuinely ambiguous about which page it belongs to. */
.page-wrap { position: relative; }
.page-wrap .binder-grid { max-width: none; }
.page-remove {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; margin: 8px 0 0; padding: 7px 10px;
  border: 1px solid rgba(224, 90, 90, .4); border-radius: 9px;
  background: rgba(224, 90, 90, .1); color: #ff9b9b;
  font: inherit; font-size: 12px; line-height: 1.2; cursor: pointer;
}
.page-remove-icon { font-size: 13px; }
.page-remove:hover { background: rgba(224, 90, 90, .2); border-color: #e05a5a; }
.page-remove:active { transform: translateY(1px); }

/* the sheet's own handle. dragging the page itself would fight the pockets for
   the same gesture, so the grab gets a bar of its own above the page — and the
   bar doubles as the phone path, where HTML5 drag does not exist at all. */
.page-move {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; margin: 0 0 8px; padding: 7px 10px;
  border: 1px solid var(--border); border-radius: 9px;
  background: rgba(255, 255, 255, .04); color: var(--muted);
  font: inherit; font-size: 12px; line-height: 1.2; cursor: grab;
  -webkit-user-select: none; user-select: none;
}
.page-move-icon { font-size: 13px; }
.page-move:hover { background: rgba(255, 255, 255, .08); color: var(--text); }
.page-move:active { cursor: grabbing; }
/* the sheet in hand, and every sheet it could be put down on */
.page-move.carrying { border-color: var(--accent); background: rgba(227, 53, 13, .16); color: var(--text); }
.page-move.target { border-style: dashed; border-color: var(--accent); color: var(--text); }
.page-wrap.carrying .binder-grid { opacity: .55; }
.page-wrap.drop-target { outline: 2px dashed var(--accent); outline-offset: 4px; border-radius: 12px; }

/* opening a gap is not destruction — it reads as an ordinary add, not a warning */
.page-insert {
  display: flex; align-items: center; justify-content: center; gap: 6px;
  width: 100%; margin: 8px 0 0; padding: 7px 10px;
  border: 1px solid var(--border); border-radius: 9px;
  background: rgba(255, 255, 255, .04); color: var(--muted);
  font: inherit; font-size: 12px; line-height: 1.2; cursor: pointer;
}
.page-insert-icon { font-size: 13px; }
.page-insert:hover { background: rgba(255, 255, 255, .08); color: var(--text); }
.page-insert:active { transform: translateY(1px); }

/* the one dialog people should actually read before answering */
.confirm-overlay { align-items: center; z-index: 90; }
.confirm-panel { max-width: 420px; gap: 8px; }
.confirm-panel h3 { margin: 0; }
.btn.danger { background: #b3352f; color: #fff; }
.btn.danger:hover { background: #c93c35; }
.book-blank { min-height: 200px; height: 100%; opacity: .35; background: repeating-linear-gradient(45deg, transparent 0 12px, rgba(255,255,255,.03) 12px 24px); border-radius: 8px; }
.binder-cover-page {
  position: relative; max-width: 440px; margin: 0 auto; aspect-ratio: 3 / 4;
  border-radius: 10px 16px 16px 10px; cursor: pointer; overflow: hidden;
  display: flex; flex-direction: column; justify-content: flex-end; padding: 18px 18px 16px 26px;
  color: #fff; border: 1px solid rgba(255,255,255,.16);
  box-shadow: inset 12px 0 16px -12px rgba(0,0,0,.7), 0 10px 30px rgba(0,0,0,.45);
}
.binder-cover-page::before { /* spine rings */
  content: ''; position: absolute; left: 8px; top: 10%; bottom: 10%; width: 4px; z-index: 2;
  border-radius: 4px; background: repeating-linear-gradient(rgba(255,255,255,.55) 0 8px, transparent 8px 24px);
}
.binder-cover-page img.cover-photo { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: .9; }
.binder-cover-page img.cover-logo { position: absolute; inset: 0; margin: auto; max-width: 70%; max-height: 45%; object-fit: contain; filter: drop-shadow(0 4px 12px rgba(0,0,0,.6)); }
.binder-cover-page .cover-name { position: relative; z-index: 1; font-weight: 800; font-size: 20px; text-shadow: 0 2px 8px rgba(0,0,0,.8); }
.binder-cover-page .cover-hint { position: relative; z-index: 1; text-shadow: 0 1px 4px rgba(0,0,0,.8); }
.binder-cover { position: relative; overflow: hidden; }
.binder-cover .binder-cover-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; opacity: .55; }
.binder-cover .binder-cover-img.logo { object-fit: contain; padding: 18%; opacity: .9; }
.binder-cover .binder-name, .binder-cover .binder-meta { position: relative; z-index: 1; text-shadow: 0 1px 4px rgba(0,0,0,.85); }
/* the turning sheet */
.flip-sheet {
  position: absolute; top: 0; bottom: 0; z-index: 5;
  transform-style: preserve-3d; transition: transform .5s cubic-bezier(.35,.1,.25,1);
}
.flip-sheet.right { left: 50%; width: 50%; transform-origin: left center; }
.flip-sheet.left { left: 0; width: 50%; transform-origin: right center; }
.flip-sheet.single { left: 0; width: 100%; transform-origin: left center; }
.flip-sheet.right.go, .flip-sheet.single.go { transform: rotateY(-180deg); }
.flip-sheet.left.go { transform: rotateY(180deg); }
.flip-face {
  position: absolute; inset: 0; backface-visibility: hidden; -webkit-backface-visibility: hidden;
  background: var(--bg, #10141f); border: 1px solid var(--line, #2a3040); border-radius: 8px;
  overflow: hidden; padding: 10px; box-shadow: 0 8px 30px rgba(0,0,0,.5);
}
.flip-face.back { transform: rotateY(180deg); }
.flip-face .binder-cover-page { max-width: none; height: 100%; }

/* ---------- admin: whole-card editor + new-set / add-card entry points ---------- */
.add-card-tile {
  aspect-ratio: 63 / 88; border: 2px dashed var(--line, #3a4157); border-radius: 10px;
  background: rgba(255,255,255,.02); color: inherit; cursor: pointer;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 4px;
}
.add-card-tile:hover { border-color: var(--accent-soft); }
.add-card-tile .act-plus { font-size: 30px; color: var(--line, #3a4157); line-height: 1; }
.add-card-tile:hover .act-plus { color: var(--accent-soft); }
.ce-panel { max-width: 560px; }
.ce-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 8px 10px; }
@media (max-width: 480px) { .ce-grid { grid-template-columns: 1fr; } }
.ce-field { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.ce-field input, .ce-field select, .ce-field textarea {
  background: var(--card, #1c2333); color: inherit; border: 1px solid var(--line, #333);
  border-radius: 8px; padding: 7px 9px; font-size: 14px; width: 100%; box-sizing: border-box;
}
.ce-field textarea { font-family: inherit; resize: vertical; min-height: 64px; }
/* reports: what collectors say the catalog is missing */
.report-panel { max-width: 460px; overflow-y: auto; }
.report-panel .ce-field input { flex: none; }
.report-list { display: flex; flex-direction: column; gap: 8px; }
.report-row { border: 1px solid var(--line, #2a3040); border-radius: 8px; padding: 8px 10px; }
.report-row .report-head { display: flex; flex-wrap: wrap; gap: 6px 10px; align-items: baseline; }
.report-row .report-status { font-size: 12px; padding: 1px 8px; border-radius: 999px; border: 1px solid var(--line, #333); }
.report-row .report-status.done { border-color: var(--owned); color: var(--owned); }
.report-row .report-status.dismissed { opacity: .6; }
/* ---------- accounts (Administration) ---------- */
.acct-row { border: 1px solid var(--line, #2a3040); border-radius: 8px; padding: 8px 10px; margin: 6px 0; }
.acct-row-off { opacity: .65; }
.acct-name { display: flex; flex-wrap: wrap; gap: 4px 6px; align-items: baseline; }
.acct-tag { font-size: 11px; padding: 1px 7px; border-radius: 999px; border: 1px solid var(--line, #333); color: var(--muted); }
.acct-tag.acct-admin { border-color: var(--accent); color: var(--accent); }
.acct-tag.acct-collab { border-color: var(--owned); color: var(--owned); }
.acct-tag.acct-off { border-color: #c44; color: #c44; }
.acct-ok { color: var(--owned); }
.acct-warn { color: var(--accent); font-size: 12px; }
.acct-actions { margin-top: 4px; }
.acct-actions summary { cursor: pointer; font-size: 13px; color: var(--muted); }
.confirm-banner { margin: 0 0 12px; border-color: var(--accent); }
#acct-filter { flex: 1; min-width: 160px; }

.ce-vars { display: flex; flex-wrap: wrap; gap: 4px 14px; }
.ce-var { display: inline-flex; align-items: center; gap: 5px; font-size: 14px; cursor: pointer; }

/* ---------- master update review (choose additions before pulling) ---------- */
.pr-row { display: flex; align-items: center; gap: 7px; padding: 3px 0; cursor: pointer; }
.pr-head { margin: 12px 0 4px; }

/* inline ✕ inside label chips (editor's custom printings, picture source) */
.chip-x { border: 0; background: transparent; color: inherit; cursor: pointer; font-size: 11px; padding: 0 2px; }
span.chip { display: inline-flex; align-items: center; gap: 2px; }

/* stacked progress bars (cards + printings) on set & Pokémon pages */
.prog-stack { flex: 1; min-width: min(200px, 100%); display: flex; flex-direction: column; gap: 4px; }
.prog-row { display: flex; align-items: center; gap: 8px; }
.prog-row .muted { white-space: nowrap; font-size: 13px; }
.prog-row .progress { margin-top: 0; }

/* mirrored art renders on a child layer so buttons/labels never flip with it */
.art-bg { position: absolute; inset: 0; }
