:root {
  --bg: #0c0e14;
  --bg-elev: #14171f;
  --bg-elev-2: #1b1f2a;
  --border: #262b38;
  --text: #eef0f5;
  --text-dim: #8b91a3;
  --accent: #4fd1c5;
  --accent-2: #a78bfa;
  --danger: #f87171;
  --warn: #fbbf24;
  --good: #4ade80;
  --radius: 12px;
  --shadow: 0 8px 24px rgba(0,0,0,0.35);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  /* Requires viewport-fit=cover on the <meta viewport> tag (see index.html)
     — without it these env() values are always 0, even on notched
     devices. Bottom inset lives here so ordinary scrolled content never
     sits flush under Safari's floating bottom bar; fixed-position sheets
     add their own bottom inset on top of their normal padding, since this
     body padding doesn't help an element that isn't in normal flow. */
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px 28px;
  background: var(--bg-elev);
  border-bottom: 1px solid var(--border);
  position: sticky;
  top: 0;
  z-index: 10;
  flex-wrap: wrap;
  gap: 12px;
  transition: padding .18s ease;
}
/* Desktop/tablet version of the mobile-only collapse further down (see
   its own comment) — topbar+workspace-nav+toolbar are all sticky here
   with no shrink at all above 600px, which on a modest browser height
   left almost the whole viewport as permanent chrome before a single
   card was visible. Lighter touch than the mobile version (that one's
   brand is oversized on purpose; this one's already close to its
   resting size), just enough to give the content back some room.
   body.header-scrolled (not .topbar.scrolled) so the same toggle can
   also compact workspace-nav/toolbar below without three separate
   classList calls in app.js. */
body.header-scrolled .topbar { padding: 9px 28px; }
body.header-scrolled .topbar .brand-mark { width: 20px; height: 20px; }

.brand { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 20px; background: none; border: none; padding: 0; color: inherit; font-family: inherit; cursor: pointer; }
.brand-mark {
  width: 22px;
  height: 22px;
  display: inline-block;
  border-radius: 5px;
}
.workspace-nav {
  display: flex;
  align-items: center;
  gap: 4px;
  width: 100%;
  max-width: 100%;
  margin-top: -30px; /* cancels .page's top padding so this sits flush with the sticky topbar — otherwise there's a gap between their natural and stuck positions and it visibly slides into place on the first bit of scroll */
  /* Spacing below is padding, not margin — margin sits outside the sticky
     box and paints nothing, so scrolled content shows through that strip;
     padding is part of the box the background actually covers. */
  padding: 12px 2px 18px;
  background: var(--bg);
  border-bottom: 1px solid rgba(132,147,177,.24);
  transition: padding .18s ease;
}
body.header-scrolled .workspace-nav { padding: 6px 2px 10px; }
body.header-scrolled .workspace-nav-btn { padding: 8px 14px 7px; }
.workspace-nav-btn { padding: 11px 14px 10px; border: 0; border-bottom: 2px solid transparent; border-radius: 0; background: transparent; color: var(--text-dim); font: inherit; font-size: 14px; font-weight: 700; cursor: pointer; }
.workspace-nav-btn:hover { color: var(--text); background: rgba(89,216,204,.06); }
.workspace-nav-btn.active { background: transparent; border-bottom-color: var(--accent); color: var(--text); box-shadow: none; }
.header-wrapped-btn {
  margin-left: 20px;
  padding: 8px 12px;
  border: 1px solid rgba(167,139,250,.48);
  border-radius: 9px;
  background: rgba(167,139,250,.1);
  color: #d8caff;
  font: inherit;
  font-size: 12px;
  font-weight: 700;
  cursor: pointer;
}
.header-wrapped-btn:hover { background: rgba(167,139,250,.18); border-color: #c4b5fd; }
a.header-wrapped-btn { text-decoration: none; display: inline-block; }
.theme-toggle-btn {
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--bg-elev-2);
  color: var(--text-dim);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.theme-toggle-btn:hover { color: var(--text); border-color: var(--accent); }
.theme-icon { width: 17px; height: 17px; }
.theme-icon-moon { display: none; }
:root[data-theme="light"] .theme-icon-sun { display: none; }
:root[data-theme="light"] .theme-icon-moon { display: block; }

.header-settings-btn {
  margin-left: 12px;
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--bg-elev-2);
  color: var(--text-dim);
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease;
}
.header-settings-btn:hover { color: var(--text); border-color: var(--accent); }
.header-settings-btn svg { width: 17px; height: 17px; }

.header-greeting {
  margin-left: 20px;
  font-size: 13px;
  font-weight: 600;
  color: var(--text-dim);
  white-space: nowrap;
}

.account-theme-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 4px;
}
.account-theme-row > span { font-size: 14px; color: var(--text-dim); }

.summary-bar {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
  padding: 14px 28px;
  background: var(--bg-elev);
  border-bottom: 1px solid var(--border);
}
.header-menu-btn {
  display: none;
  align-items: center;
  justify-content: center;
  width: 38px;
  height: 38px;
  border-radius: 8px;
  border: 1px solid var(--border);
  background: transparent;
  color: var(--text);
  font-size: 18px;
  cursor: pointer;
}
.header-menu-panel { display: contents; }
/* Mobile-only surfaces — hidden by default so they don't render as an
   unstyled block full of stray buttons/links on desktop. */
.mobile-bottom-nav, .mobile-more-panel { display: none; }
/* Mobile-only label swaps (Add Card's camera-first wording, the
   shortened "Confirm" stat) — real default is the desktop wording, the
   mobile media query below flips which span is visible. */
.btn-add-card-mobile-label, .pill-label-mobile { display: none; }

.pill {
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 8px 16px;
  display: flex;
  flex-direction: column;
  min-width: 110px;
}
.pill-label { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.04em; }
.pill-value { font-size: 18px; font-weight: 700; }
.pill-warn .pill-value { color: var(--warn); }
.pill-favorite .pill-value { color: var(--accent-2); }
.pill-filtered { border-color: rgba(79,209,197,.5); background: rgba(79,209,197,.06); }
.pill-filtered .pill-value { color: var(--accent); }
.pill-clickable { cursor: pointer; font: inherit; text-align: left; }
.pill-clickable:hover { border-color: rgba(251,191,36,.6); background: rgba(251,191,36,.08); }
.pill-clickable.active { border-color: rgba(251,191,36,.7); background: rgba(251,191,36,.14); box-shadow: 0 0 0 2px rgba(251,191,36,.18); }

.page { padding: 24px 28px 60px; max-width: 1400px; margin: 0 auto; }

.collection-layout { display: grid; grid-template-columns: 310px minmax(0, 1fr); gap: 18px; align-items: start; }
.dashboard-insights { display: grid; grid-column: 1; grid-row: 1; grid-template-columns: 1fr; gap: 12px; align-content: start; padding-right: 3px; }

/* Keep the top bars and sidebar in place while only the card grid scrolls.
   Offsets are computed in JS (syncStickyOffsets) rather than hardcoded,
   since the topbar can wrap to two lines and would silently break a fixed
   pixel stack. */
.workspace-nav, .toolbar { position: sticky; background: var(--bg); }
.workspace-nav { top: var(--nav-sticky-top, 90px); z-index: 9; }
.toolbar { top: var(--toolbar-sticky-top, 130px); z-index: 8; padding-top: 4px; }
.dashboard-insights {
  position: sticky;
  top: var(--sidebar-sticky-top, 240px);
  max-height: calc(100vh - var(--sidebar-sticky-top, 240px) - 16px);
  overflow-y: auto;
  scrollbar-width: thin;
}
.collection-layout > #grid-view, .collection-layout > #table-view, .collection-layout > .empty-state { grid-column: 2; grid-row: 1; }
.collection-layout > #table-view, .collection-layout > .empty-state { grid-row: 1; }
.dashboard-insights .insight-wide { grid-row: auto; }
.insight-card { min-width: 0; padding: 17px 18px; background: linear-gradient(145deg, #171c27, #12161f); border: 1px solid var(--border); border-radius: 12px; box-shadow: 0 10px 24px rgba(0,0,0,0.14); }
.confirmation-queue-modal { width: min(560px, calc(100vw - 32px)); max-height: min(640px, calc(100vh - 32px)); overflow-y: auto; }
.duplicates-modal { width: min(640px, calc(100vw - 32px)); max-height: min(680px, calc(100vh - 32px)); overflow-y: auto; }
/* Above every other modal (z-index 100) — this can be triggered while the
   Add New Card modal is already open (duplicate-link check), and as a tie
   in DOM order it was losing to whichever modal-backdrop comes later in
   the HTML and rendering invisibly behind it. */
#confirm-dialog-backdrop { z-index: 200; align-items: center; }
/* .modal.confirm-dialog (two classes) outranks the plain .modal rule
   further down the sheet regardless of source order, so this can't lose
   the width/padding tie-break the way a single-class override would. */
.modal.confirm-dialog { width: min(400px, calc(100vw - 32px)); padding: 22px; }
.confirm-dialog #confirm-dialog-message, .confirm-dialog #prompt-dialog-message { margin: 0 0 18px; font-size: 14px; line-height: 1.5; }
.confirm-dialog-actions { display: flex; justify-content: flex-end; gap: 10px; }
.prompt-dialog-input {
  display: block;
  width: 100%;
  margin: -8px 0 18px;
  padding: 8px 10px;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text);
  font: inherit;
  font-size: 13.5px;
  box-sizing: border-box;
}
.prompt-dialog-input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(89,216,204,0.1); }
.text-dim { color: var(--text-dim); }
.duplicate-groups { display: grid; gap: 16px; margin-top: 16px; }
.duplicate-groups > * { min-width: 0; }
.duplicate-group { padding: 12px; background: var(--panel-inset); border: 1px solid var(--border); border-radius: 10px; }
.duplicate-group-head { font-weight: 700; margin-bottom: 8px; }
.duplicate-row { display: flex; align-items: center; gap: 10px; padding: 8px; border: 1px solid rgba(132,147,177,.16); border-radius: 8px; margin-top: 6px; cursor: pointer; transition: background .12s ease, border-color .12s ease; }
.duplicate-row:hover, .duplicate-row:focus-visible { background: rgba(132,147,177,.08); border-color: rgba(132,147,177,.32); outline: none; }
.duplicate-row-info { flex: 1; min-width: 0; display: grid; gap: 2px; font-size: 12px; }
.duplicate-row-info a { color: var(--accent); }
.duplicate-row-value { flex: 0 0 auto; font-weight: 700; }
.duplicate-not-dupe, .duplicate-delete { flex: 0 0 auto; padding: 6px 10px; font-size: 12px; }

.sold-badge {
  display: inline-flex;
  align-items: center;
  padding: 3px 9px;
  border-radius: 999px;
  background: rgba(132,147,177,.16);
  border: 1px solid rgba(132,147,177,.32);
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 800;
  letter-spacing: .06em;
}
.sold-section { margin-top: 16px; }
.sold-fields { margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--border); }
.sold-fields.hidden { display: none; }
.sold-profit-note:empty { display: none; }
.sold-profit-note .profit-summary { margin-top: 10px; }

.sold-summary { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin: 18px 0; }
.sold-summary-tile { display: grid; gap: 4px; padding: 14px; text-align: center; background: var(--panel-inset); border: 1px solid rgba(132,147,177,.16); border-radius: 10px; }
.sold-summary-tile strong { font-size: 20px; }
.sold-summary-tile span { color: var(--text-dim); font-size: 11px; text-transform: uppercase; letter-spacing: .05em; }

.sold-rows { display: grid; gap: 8px; }
.sold-rows > * { min-width: 0; }
.sold-row { display: flex; align-items: center; gap: 12px; padding: 10px; border: 1px solid rgba(132,147,177,.16); border-radius: 10px; cursor: pointer; transition: background .12s ease, border-color .12s ease; }
.sold-row:hover, .sold-row:focus-visible { background: rgba(132,147,177,.08); border-color: rgba(132,147,177,.32); outline: none; }
.sold-row-info { flex: 1; min-width: 0; display: grid; gap: 2px; }
.sold-row-value { flex: 0 0 auto; text-align: right; font-weight: 700; }
.sold-restore { flex: 0 0 auto; padding: 6px 10px; font-size: 12px; }

/* 168px (not 150px) so the four action buttons below have room — at 150px
   the three icon buttons plus "+ Add" text used to overflow past the
   card's own right edge instead of fitting inside it. */
.wantlist-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(168px, 1fr)); gap: 14px; }
.wantlist-card { display: flex; flex-direction: column; gap: 8px; padding: 10px; border: 1px solid rgba(132,147,177,.16); border-radius: 10px; background: var(--panel-inset); }
.wantlist-card-img { position: relative; width: 100%; aspect-ratio: 5 / 7; border-radius: 7px; overflow: hidden; background: var(--bg-elev-2); border: 1px solid var(--border); display: flex; align-items: center; justify-content: center; }
.wantlist-card-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
.wantlist-card-info { min-width: 0; }
.wantlist-card-name { font-weight: 700; font-size: 13px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.wantlist-card-actions { display: flex; gap: 6px; }
.wantlist-card-actions .btn-secondary { padding: 6px 8px; font-size: 12px; text-align: center; text-decoration: none; }
.wantlist-add-owned { flex: 1; min-width: 0; }
/* Fixed-size squares instead of sizing to their (icon) content — that's
   what let three content-sized icon buttons plus "+ Add" add up to wider
   than the card at its minimum width in the first place. */
.wantlist-icon-btn { flex: 0 0 26px; width: 26px; height: 26px; padding: 0; display: inline-flex; align-items: center; justify-content: center; }
.wantlist-icon { width: 13px; height: 13px; flex-shrink: 0; vertical-align: -2px; }
.wantlist-target-line { margin-top: 2px; font-size: 11px; color: var(--text-dim); display: flex; align-items: center; gap: 3px; }
.wantlist-card.wantlist-alert { border-color: var(--accent-2); box-shadow: 0 0 0 1px var(--accent-2) inset; position: relative; }
.wantlist-alert-badge {
  align-self: flex-start;
  display: flex;
  align-items: center;
  gap: 3px;
  font-size: 10px;
  font-weight: 700;
  padding: 3px 8px;
  border-radius: 999px;
  background: var(--accent-2);
  color: #2b1a00;
}
.wantlist-alert .wantlist-target-line { color: var(--accent-2); font-weight: 600; }
.wantlist-header-actions { display: flex; gap: 8px; }

@media (max-width: 640px) {
  .sold-summary { grid-template-columns: 1fr; }
}
.btn-review-queue {
  flex: 0 0 auto;
  padding: 9px 14px;
  border: 1px solid rgba(242,201,76,.45);
  border-radius: 10px;
  background: rgba(242,201,76,.1);
  color: #f2c94c;
  font: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease;
}
.btn-review-queue:hover { background: rgba(242,201,76,.18); border-color: #f2c94c; }
.confirmation-queue-head { display: flex; align-items: start; justify-content: space-between; gap: 12px; }
.confirmation-queue-head h3 { margin: 3px 0 0; font-size: 15px; }
.confirmation-queue-list { display: grid; gap: 6px; margin-top: 12px; }
.confirmation-queue-list > * { min-width: 0; }
.confirmation-queue-row {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  min-height: 44px;
  padding: 9px 12px;
  border: 1px solid rgba(132,147,177,.16);
  border-radius: 8px;
  background: var(--panel);
  color: var(--text);
  text-align: left;
  font: inherit;
  cursor: pointer;
  box-sizing: border-box;
}
.confirmation-queue-row:hover { border-color: #f2c94c; background: rgba(242,201,76,.08); }
.confirmation-queue-set { flex: 1; min-width: 0; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.confirmation-queue-count { flex: 0 0 auto; padding: 2px 9px; border-radius: 999px; background: rgba(242,201,76,.14); color: #f2c94c; font-size: 12px; font-weight: 700; }
.confirmation-queue-action { flex: 0 0 auto; color: var(--text-dim); font-size: 12px; }

/* ---------- Incoming/to-confirm queue ---------- */
.incoming-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 12px;
  margin: 0 0 16px;
  padding: 14px 16px;
  border: 1px solid rgba(242,201,76,.35);
  border-radius: 12px;
  background: rgba(242,201,76,.07);
}
.incoming-banner-title { display: block; font-size: 15px; font-weight: 800; color: #f2c94c; }
.incoming-banner-count { display: block; margin-top: 2px; font-size: 12.5px; color: var(--text-dim); }
.incoming-banner-actions { display: flex; gap: 8px; flex-wrap: wrap; }
/* Quiet on purpose — "mark everything" is the exception here, not the
   thing this banner should be pushing; the checklist bar below is the
   actual primary action. */
.incoming-banner-mark-all {
  background: none;
  border: none;
  padding: 6px 2px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-dim);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
}
.incoming-banner-mark-all:hover, .incoming-banner-mark-all:focus-visible { color: var(--text); }

/* The delivery checklist bar — deliberately not styled like .bulk-toolbar
   (this app's generic admin-y multi-select bar): no divider-separated row
   of buttons, no danger-red action, just a count and the one thing you'd
   actually do with a ticked pile of incoming cards. On desktop it's a
   plain compact bar in normal flow, right under the incoming banner; see
   the mobile override below for why it becomes a fixed bar instead. */
.incoming-checklist-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin: 0 0 16px;
  padding: 10px 16px;
  border: 1px solid rgba(79,209,197,.35);
  border-radius: 12px;
  background: rgba(79,209,197,.07);
}
.incoming-checklist-count { font-size: 13px; font-weight: 700; color: var(--accent); }
.incoming-checklist-actions { display: flex; gap: 8px; }

/* Compact, list-style incoming cards — a card that hasn't arrived yet
   usually has no photo, so the normal grid's full-height "Add a photo"
   block ends up dominating the tile and reading as a broken/empty card
   rather than "this one just hasn't shipped." A queue row (small photo
   slot, info beside it, the arrival action right there) reads as "waiting
   on delivery" instead. Only used while the Incoming filter is active —
   see els.grid.classList.toggle('incoming-mode', ...) in app.js. */
.card-grid.incoming-mode { grid-template-columns: 1fr; gap: 10px; }
.card-tile-incoming { display: flex; flex-direction: column; gap: 10px; padding: 12px; cursor: pointer; }
.card-tile-incoming:hover { transform: none; border-color: rgba(79,209,197,.4); }
/* A tasteful selected state — a tint plus the accent border/ring the
   generic .card-tile.selected rule already gives every card, not just a
   thin outline that's easy to miss on a plain list row like this. */
.card-tile-incoming.selected { background: rgba(79,209,197,.07); }
.incoming-card-row { display: flex; align-items: center; gap: 12px; }
/* The arrival check's actual tap target (44px, comfortable on mobile) is
   bigger than the box it visually draws (26px) — the label is sized to
   the target, the input inside is sized to the visible box. */
.incoming-check-wrap {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: -9px;
  cursor: pointer;
}
/* Styled directly on the real checkbox input (appearance:none + a
   :checked background), not paired with a decorative sibling icon — see
   incomingCardTileHTML's comment on why a sibling node would double the
   toggle on tap. */
.incoming-check-input {
  appearance: none;
  -webkit-appearance: none;
  width: 26px;
  height: 26px;
  margin: 0;
  border-radius: 8px;
  border: 2px solid var(--border);
  background: var(--bg-elev-2);
  cursor: pointer;
  transition: background .15s ease, border-color .15s ease;
}
.incoming-check-input:hover { border-color: var(--accent); }
.incoming-check-input:checked {
  border-color: var(--accent);
  background-color: var(--accent);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M3.5 8.5l3 3 6-6' fill='none' stroke='%2306231f' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
  background-size: 15px 15px;
}
.incoming-check-input:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Sized to the card's own 5:7 aspect ratio (not a square) so a real card
   photo fills the box instead of letterboxing inside it — bumped up from
   52x52 since a thumbnail that small couldn't show enough of the card to
   be worth including at all. */
.incoming-card-thumb {
  flex: 0 0 auto;
  width: 64px;
  height: 90px;
  border-radius: 8px;
  background: var(--bg-elev-2);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}
.incoming-card-thumb img { width: 100%; height: 100%; object-fit: cover; }
.incoming-card-thumb .placeholder-icon { width: 46%; height: auto; color: rgba(132,147,177,0.55); }
.incoming-card-info { flex: 1; min-width: 0; }
.incoming-card-value { margin-top: 2px; font-size: 13px; font-weight: 700; color: var(--accent); }
.incoming-card-value.card-value-unpriced { font-size: 11.5px; font-weight: 600; }
.incoming-card-badge {
  display: inline-block;
  margin-bottom: 3px;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--warn);
  color: #241c02;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: .05em;
}
.incoming-card-name { font-weight: 700; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.incoming-card-set { color: var(--text-dim); font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.incoming-card-note { margin-top: 3px; color: var(--text-dim); font-size: 11.5px; font-style: italic; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The one remaining per-row button — a quiet secondary link, not a
   button competing with the checklist's own primary action. */
.incoming-card-details {
  flex: 0 0 auto;
  background: none;
  border: none;
  padding: 8px 2px 8px 8px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-dim);
  cursor: pointer;
}
.incoming-card-details:hover, .incoming-card-details:focus-visible { color: var(--accent); }
.insight-wide { grid-row: span 2; }
.insight-heading { display: flex; align-items: start; justify-content: space-between; gap: 12px; }
.insight-heading h2 { margin: 3px 0 0; font-size: 16px; }
.eyebrow { color: var(--accent); font-size: 10px; font-weight: 700; letter-spacing: .09em; text-transform: uppercase; }
.insight-updated { color: var(--text-dim); font-size: 11px; }
.insight-heading-toggle {
  cursor: pointer;
  margin: -4px -4px 0;
  padding: 4px;
  border-radius: 8px;
  transition: background .15s ease;
}
.insight-heading-toggle:hover { background: rgba(89,216,204,.06); }
.insight-heading-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
/* Grid-rows animates cleanly between 0 and the content's natural height
   without ever needing to know that height in px (unlike max-height, which
   needs a guessed ceiling, or display:none, which can't transition at all). */
.insight-card-body {
  display: grid;
  grid-template-rows: 1fr;
  transition: grid-template-rows .22s ease, opacity .18s ease;
  opacity: 1;
}
.insight-card-body-inner { min-height: 0; overflow: hidden; }
.insight-card.collapsed .insight-card-body { grid-template-rows: 0fr; opacity: 0; }
.insight-metrics { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; margin: 20px 0 14px; }
.insight-metrics div { display: grid; gap: 3px; }
.insight-metrics span { color: var(--text-dim); font-size: 11px; }
.insight-metrics strong { font-size: 17px; }
.metric-invested { color: var(--accent-2); }
.metric-accent { color: var(--accent); }
.metric-profit { color: var(--good); }
.metric-profit.metric-negative, .metric-negative { color: var(--danger); }
.value-chart { height: 112px; color: var(--accent); position: relative; }
.value-chart svg { width: 100%; height: 92px; overflow: visible; }
.chart-labels { display: flex; justify-content: space-between; color: var(--text-dim); font-size: 10px; }
.insight-list { display: grid; gap: 8px; margin-top: 12px; }
.insight-list > * { min-width: 0; }
/* A dedicated track below the name/value line, not a bar blended into the
   row's own background behind the text — the previous version had the bar
   and the text sharing one line and fighting for the same width, which is
   what forced ugly mid-word truncation ("Team Rock...") on anything but
   the shortest names. Splitting them onto two lines gives the name the
   entire row width, and makes the bar a real filled shape (a track you can
   see is only 15% full) instead of a subtle background tint. */
.insight-row {
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
  padding: 8px 10px;
  border: 1px solid transparent;
  border-radius: 8px;
  background: var(--bg-elev-2);
  color: var(--text);
  text-align: left;
  font: inherit;
}
.insight-row-top { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; min-width: 0; }
.insight-row-name { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12.5px; }
.insight-row strong { flex: 0 0 auto; color: var(--accent); font-weight: 800; font-size: 12.5px; white-space: nowrap; }
.insight-row-track { height: 5px; border-radius: 999px; background: rgba(132,147,177,.16); overflow: hidden; }
.insight-row-bar { display: block; height: 100%; width: 0; border-radius: 999px; background: var(--accent); }
.insight-link { width: 100%; cursor: pointer; }
.insight-link:hover { border-color: rgba(89,216,204,.3); }
.insight-link:hover .insight-row-name { color: var(--accent); }
.active-insight-filter { border-color: var(--accent); background: rgba(89,216,204,.08); }
.active-insight-filter .insight-row-name { color: var(--accent); }
.insight-empty { color: var(--text-dim); font-size: 11px; }

.breakdown-tabs { display: flex; gap: 6px; margin-top: 14px; flex-wrap: wrap; }
.breakdown-tab {
  padding: 5px 12px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: transparent;
  color: var(--text-dim);
  font: inherit;
  font-size: 11.5px;
  font-weight: 700;
  cursor: pointer;
  transition: border-color .15s ease, color .15s ease, background .15s ease;
}
.breakdown-tab:hover { border-color: var(--accent); color: var(--text); }
.breakdown-tab.active { border-color: var(--accent); background: rgba(89,216,204,.14); color: var(--accent); }
.value-tracker-view { display: grid; gap: 16px; }
/* Implicit single-column grid (no grid-template-columns) — its auto track
   still sizes to a grid item's content-based min-width: auto by default,
   so a wide child (the refresh controls row, in particular) blew the
   whole section past the page edge instead of wrapping/shrinking inside it. */
.value-tracker-view > * { min-width: 0; }
.value-tracker-heading { display: flex; align-items: flex-start; justify-content: space-between; }
.value-tracker-heading .eyebrow { display: block; margin-top: 22px; }
.value-tracker-heading h2 { margin: 4px 0; font-size: 26px; }
.value-tracker-heading p { margin: 0; color: var(--text-dim); font-size: 13px; }
.value-refresh-action { position: relative; margin-top: 22px; flex: 0 0 auto; }
.refresh-menu-trigger { white-space: nowrap; }
/* A single trigger opening one panel — the scope, the extra filters, and
   the action button all live together instead of being spread across a
   pill row + a separately-floating date popover + a second line of
   differently-styled chips (the previous layout: confusing wording side by
   side with no grouping, feedback from 2026-09-04/05). */
.refresh-panel {
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 40;
  width: min(300px, calc(100vw - 32px));
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px 16px;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 14px 34px rgba(0,0,0,0.32);
}
.refresh-panel-label { margin: 0; color: var(--text); font-size: 12.5px; font-weight: 600; }
.refresh-panel-filters { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; padding-top: 10px; border-top: 1px solid var(--border); }
.refresh-panel-action { width: 100%; }
.refresh-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-elev-2);
  color: var(--text-dim);
  font-size: 12.5px;
  cursor: pointer;
}
.refresh-filter-chip:has(input:checked) { border-color: var(--accent); color: var(--text); }
.refresh-filter-price {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-elev-2);
  color: var(--text-dim);
  font-size: 12.5px;
}
.refresh-filter-price input {
  width: 70px;
  background: transparent;
  border: none;
  color: var(--text);
  font: inherit;
  padding: 2px 0;
}
.refresh-filter-price input:focus { outline: none; }
/* A plain <select>'s open option list is OS-native chrome no CSS can
   reach — same reason the refresh-cutoff date field below is a hand-built
   popover rather than <input type="date">. This is that same fix applied
   to a tag picker: a button trigger + a dark listbox popover instead of a
   native select. */
.refresh-filter-select-custom { position: relative; }
.refresh-filter-select {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--bg-elev-2);
  color: var(--text-dim);
  font: inherit;
  font-size: 12.5px;
  cursor: pointer;
}
.refresh-filter-select:hover { border-color: var(--accent); }
.refresh-filter-select span:last-child { color: var(--text); max-width: 110px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.refresh-tag-panel { width: 180px; max-height: 220px; overflow-y: auto; padding: 6px; display: flex; flex-direction: column; gap: 2px; }
.refresh-tag-option {
  display: block;
  width: 100%;
  text-align: left;
  background: transparent;
  border: none;
  border-radius: 6px;
  padding: 7px 10px;
  font-size: 12.5px;
  color: var(--text-dim);
  cursor: pointer;
  font-family: inherit;
}
.refresh-tag-option:hover { background: var(--bg-elev); color: var(--text); }
.refresh-tag-option.active { background: var(--bg-elev); color: var(--accent); font-weight: 600; }
.refresh-filter-count { color: var(--text-dim); font-size: 12px; }
/* Vertical radio-style list for refresh scope, inside .refresh-panel — one
   option per row instead of a segmented pill row, so the four choices read
   as one group instead of competing for space with everything else that
   used to sit alongside them. Overrides .refresh-scope-btn's shared pill
   styling (also used, unmodified, by #movers-direction-toggle) by scoping
   every rule under .refresh-scope-list. The "Custom date" row keeps its
   own popover — position: relative here is what anchors it. */
.refresh-scope-list { display: flex; flex-direction: column; gap: 2px; position: relative; }
.refresh-scope-list .refresh-scope-btn,
.refresh-scope-list .refresh-scope-custom-trigger {
  display: flex;
  align-items: center;
  width: 100%;
  background: transparent;
  border: none;
  border-radius: 6px;
  padding: 7px 10px 7px 30px;
  position: relative;
  text-align: left;
  color: var(--text-dim);
}
.refresh-scope-list .refresh-scope-btn::before,
.refresh-scope-list .refresh-scope-custom-trigger::before {
  content: '';
  position: absolute;
  left: 10px;
  top: 50%;
  transform: translateY(-50%);
  width: 12px;
  height: 12px;
  border-radius: 50%;
  border: 1.5px solid var(--border);
  background: transparent;
}
.refresh-scope-list .refresh-scope-btn:hover,
.refresh-scope-list .refresh-scope-custom-trigger:hover { color: var(--text); }
.refresh-scope-list .refresh-scope-btn.active,
.refresh-scope-list .refresh-scope-custom-trigger.active {
  background: var(--bg-elev);
  color: var(--text);
  font-weight: 600;
}
.refresh-scope-list .refresh-scope-btn.active::before,
.refresh-scope-list .refresh-scope-custom-trigger.active::before {
  border-color: var(--accent);
  background: var(--accent);
  box-shadow: inset 0 0 0 2px var(--bg-elev-2);
}
.refresh-scope-list .refresh-scope-btn:first-of-type { border-radius: 6px; }
/* Segmented pill toggle — still used by #movers-direction-toggle. */
.refresh-scope-toggle { display: flex; border: 1px solid var(--border); border-radius: 8px; }
.refresh-scope-btn {
  background: var(--bg-elev-2);
  color: var(--text-dim);
  border: none;
  border-right: 1px solid var(--border);
  padding: 9px 14px;
  cursor: pointer;
  font-size: 12.5px;
  font-family: inherit;
  white-space: nowrap;
}
.refresh-scope-btn:first-of-type { border-radius: 7px 0 0 7px; }
.movers-filters { display: flex; align-items: center; gap: 10px; margin: 10px 0 14px; flex-wrap: wrap; }
.movers-direction-toggle .refresh-scope-btn:last-of-type { border-right: none; border-radius: 0 7px 7px 0; }
#movers-grade-filter {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 9px 12px;
  border-radius: 8px;
  font-size: 12.5px;
  font-family: inherit;
}
#movers-grade-filter:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(89,216,204,0.1); }
.refresh-scope-btn:hover { color: var(--text); }
.refresh-scope-btn.active { background: var(--accent); color: #06201e; font-weight: 600; }
.refresh-scope-btn.refresh-scope-custom { position: relative; padding: 0; background: transparent; border-right: none; }
.refresh-scope-custom-trigger {
  background: transparent;
  color: inherit;
  border: none;
  border-radius: 0 7px 7px 0;
  padding: 9px 14px;
  cursor: pointer;
  font: inherit;
  font-size: 12.5px;
  white-space: nowrap;
}
.refresh-scope-custom-trigger:hover { color: var(--text); }
.refresh-scope-custom-trigger.active { background: var(--accent); color: #06201e; font-weight: 600; }
.date-picker-panel {
  position: absolute;
  top: calc(100% + 6px);
  left: 0;
  z-index: 30;
  width: 240px;
  max-height: min(248px, calc(100vh - 24px));
  overflow-y: auto;
  padding: 10px;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 14px 34px rgba(0,0,0,0.32);
}
.date-picker-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.date-picker-month-label { font-size: 13px; font-weight: 700; color: var(--text); }
.date-picker-nav {
  width: 26px;
  height: 26px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--text-dim);
  cursor: pointer;
  font-size: 14px;
}
.date-picker-nav:hover { border-color: var(--accent); color: var(--accent); }
.date-picker-weekdays { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; color: var(--text-dim); font-size: 10px; text-align: center; margin-bottom: 4px; }
.date-picker-days { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.date-picker-day {
  aspect-ratio: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  border-radius: 6px;
  color: var(--text);
  font-size: 12px;
  cursor: pointer;
}
.date-picker-day:hover { background: rgba(89,216,204,0.14); }
.date-picker-day.other-month { color: var(--text-dim); opacity: .45; }
.date-picker-day.selected { background: var(--accent); color: #06201e; font-weight: 700; }
.date-picker-day.today:not(.selected) { border: 1px solid var(--accent); }
.refresh-progress-status { color: var(--text-dim); font-size: 10px; }
.refresh-panel-estimate { color: var(--text-dim); font-size: 11px; text-align: center; }
.refresh-job-modal { width: min(620px, calc(100vw - 32px)); padding: 24px; }
.refresh-job-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.refresh-job-header h2 { margin: 5px 0 4px; }
.refresh-job-header p { margin: 0; color: var(--text-dim); font-size: 12px; }
.refresh-job-progress { height: 8px; margin: 22px 0 8px; overflow: hidden; background: var(--bg-elev-2); border-radius: 999px; }
.refresh-job-progress span { display: block; width: 0; height: 100%; background: var(--accent); border-radius: inherit; transition: width .25s ease; }
.refresh-job-count { color: var(--text-dim); font-size: 11px; }
.refresh-job-summary { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; margin-top: 12px; }
.refresh-job-stat { display: grid; gap: 2px; padding: 10px 8px; text-align: center; background: var(--panel-inset); border: 1px solid rgba(132,147,177,.16); border-radius: 9px; }
.refresh-job-stat strong { font-size: 18px; }
.refresh-job-stat span { color: var(--text-dim); font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em; }
.refresh-job-stat.up strong { color: var(--good); }
.refresh-job-stat.down strong, .refresh-job-stat.error strong { color: var(--danger); }
.refresh-job-stat.flat strong { color: #f2c94c; }
.refresh-job-events { display: grid; gap: 5px; max-height: 230px; margin-top: 16px; overflow-y: auto; padding: 10px 7px 10px 10px; background: var(--panel-inset); border: 1px solid rgba(132,147,177,.16); border-radius: 9px; scrollbar-width: thin; scrollbar-color: rgba(89,216,204,.72) rgba(35,42,58,.78); }
.refresh-job-events > * { min-width: 0; }
.refresh-job-events::-webkit-scrollbar { width: 9px; }
.refresh-job-events::-webkit-scrollbar-track { margin: 7px 2px 7px 0; border-radius: 999px; background: rgba(35,42,58,.78); }
.refresh-job-events::-webkit-scrollbar-thumb { border: 2px solid var(--panel-inset); border-radius: 999px; background: rgba(89,216,204,.72); }
.refresh-job-events::-webkit-scrollbar-thumb:hover { background: var(--accent); }
.refresh-job-event { display: flex; align-items: center; gap: 8px; color: var(--text-dim); font-size: 11px; }
.refresh-job-event > span:nth-child(2) { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.refresh-job-event-dot { width: 6px; height: 6px; flex: 0 0 auto; border-radius: 50%; background: var(--accent); }
.refresh-job-event-dot.flat { background: #f2c94c; }
.refresh-job-event-dot.up { background: var(--good); }
.refresh-job-event-dot.down, .refresh-job-event-dot.error { background: var(--danger); }
.refresh-job-outcome { margin-left: auto; flex: 0 0 auto; font-size: 10px; font-weight: 800; }
.refresh-job-outcome.flat { color: #f2c94c; }
.refresh-job-outcome.up { color: var(--good); }
.refresh-job-outcome.down, .refresh-job-outcome.error { color: var(--danger); }
.refresh-job-modal { width: min(620px, calc(100vw - 32px)); max-height: min(680px, calc(100vh - 32px)); overflow: hidden; }
.refresh-job-events { max-height: 230px; overflow-y: auto; }
.refresh-job-dock { position: fixed; right: 22px; bottom: 22px; z-index: 90; padding: 10px 14px; border: 1px solid rgba(89,216,204,.58); border-radius: 10px; background: #152927; color: var(--accent); box-shadow: 0 12px 28px rgba(0,0,0,.34); font: inherit; font-size: 12px; font-weight: 750; cursor: pointer; }
.refresh-job-dock:hover { background: #1b3633; border-color: var(--accent); transform: translateY(-1px); }
.value-tracker-layout { display: grid; grid-template-columns: minmax(0, 2.2fr) minmax(220px, 1fr); gap: 16px; align-items: start; }
.value-tracker-summary { display: flex; flex-direction: column; gap: 12px; }
.value-tracker-summary > div { display: grid; gap: 5px; padding: 16px 18px; background: var(--bg-elev); border: 1px solid var(--border); border-radius: 11px; }
.value-tracker-summary span { color: var(--text-dim); font-size: 11px; text-transform: uppercase; letter-spacing: .06em; }
.value-tracker-summary strong { font-size: 21px; }
.value-tracker-chart-card, .value-history-card, .value-movers-card { padding: 20px; background: var(--bg-elev); border: 1px solid var(--border); border-radius: 12px; }
.report-view { display: flex; flex-direction: column; gap: 18px; }
.report-controls { display: flex; align-items: center; gap: 14px; flex-wrap: wrap; margin-top: 4px; }
.report-month-nav { display: flex; align-items: center; gap: 10px; color: var(--text); font-size: 14px; font-weight: 600; }
.report-month-nav-btn {
  width: 30px;
  height: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--text-dim);
  cursor: pointer;
  font-size: 15px;
}
.report-month-nav-btn:hover { border-color: var(--accent); color: var(--accent); }
.report-custom-range { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.report-custom-range label { display: flex; align-items: center; gap: 6px; color: var(--text-dim); font-size: 12.5px; }
.report-custom-range input[type="date"] {
  color-scheme: dark;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 7px 10px;
  border-radius: 8px;
  font: inherit;
  font-size: 13px;
}
.report-summary-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; }
.report-stat { display: grid; gap: 5px; padding: 16px 18px; background: var(--bg-elev); border: 1px solid var(--border); border-radius: 11px; }
.report-stat-label { color: var(--text-dim); font-size: 11px; text-transform: uppercase; letter-spacing: .06em; }
.report-stat-value { font-size: 24px; }
.report-stat-sub { color: var(--text-dim); font-size: 12px; }
.report-lists { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; }
@media (max-width: 700px) {
  .report-controls { flex-direction: column; align-items: stretch; }
}
.movers-list { display: grid; gap: 8px; margin-top: 14px; }
.movers-list > * { min-width: 0; }
.mover-row {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px;
  background: transparent;
  border: 1px solid var(--border);
  border-radius: 10px;
  color: var(--text);
  text-align: left;
  cursor: pointer;
}
.mover-row:hover { border-color: var(--accent); background: var(--bg-elev-2); }
.mover-thumb { width: 34px; height: 48px; object-fit: cover; border-radius: 5px; background: var(--bg-elev-2); flex-shrink: 0; }
.mover-info { flex: 1; min-width: 0; }
.mover-name { font-size: 13px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mover-set { font-size: 11.5px; color: var(--text-dim); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mover-updated { font-size: 10.5px; color: var(--text-dim); opacity: .75; margin-top: 1px; }
.mover-before-after { flex-shrink: 0; color: var(--text-dim); font-size: 12px; white-space: nowrap; }
.mover-before-after span { margin: 0 2px; }
.mover-figures { text-align: right; flex-shrink: 0; min-width: 68px; }
.mover-delta { margin-bottom: 2px; font-size: 15px; font-weight: 800; color: var(--good); }
.mover-pct { font-size: 11px; color: var(--text-dim); }
.mover-row.down .mover-delta { color: var(--danger); }
@media (max-width: 780px) {
  .value-tracker-layout { grid-template-columns: minmax(0, 1fr); }
  .value-tracker-summary { flex-direction: row; flex-wrap: wrap; }
  .value-tracker-summary > div { flex: 1 1 140px; }
}
.portfolio-chart { margin-top: 24px; color: var(--accent); }
.chart-plot { position: relative; height: 320px; }
.chart-plot svg { width: 100%; height: 100%; display: block; overflow: visible; }
.chart-y-labels { position: absolute; inset: 0; pointer-events: none; }
.chart-y-labels span { position: absolute; left: 0; transform: translateY(-50%); font-size: 10px; color: var(--text-dim); background: var(--bg-elev); padding: 1px 6px; border-radius: 4px; }
.chart-dots { position: absolute; inset: 0; }
.chart-dot {
  position: absolute;
  width: 10px;
  height: 10px;
  margin: -5px 0 0 -5px;
  padding: 0;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg-elev);
  cursor: pointer;
  transition: transform .12s ease, background .12s ease;
}
.chart-dot:hover, .chart-dot:focus-visible { background: var(--text); transform: scale(1.35); outline: none; z-index: 5; }
.chart-dot-tooltip {
  position: absolute;
  bottom: calc(100% + 9px);
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 1px;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 6px 10px;
  white-space: nowrap;
  opacity: 0;
  pointer-events: none;
  transition: opacity .12s ease;
  z-index: 10;
  box-shadow: 0 8px 20px rgba(0,0,0,.3);
}
.chart-dot:hover .chart-dot-tooltip, .chart-dot:focus-visible .chart-dot-tooltip { opacity: 1; }
.chart-dot-tooltip-date { color: var(--text-dim); font-size: 10px; }
.chart-dot-tooltip-value { color: var(--text); font-size: 13px; font-weight: 700; }
.value-history-table-wrap { overflow-x: auto; margin-top: 10px; }
.value-history-table { width: 100%; border-collapse: collapse; }
.value-history-table th, .value-history-table td { padding: 11px 8px; border-bottom: 1px solid var(--border); text-align: left; }
.value-history-table th { color: var(--text-dim); font-size: 11px; text-transform: uppercase; letter-spacing: .06em; }
.value-history-table td:last-child { color: var(--accent); font-weight: 700; text-align: right; }
.value-history-table th:last-child { text-align: right; }
.value-delta-positive { color: var(--good); font-weight: 700; }
.value-delta-negative { color: var(--danger); font-weight: 700; }
.value-history-row { cursor: pointer; }
.value-history-row:hover td { background: var(--bg-elev-2); }
.report-activity-table tbody tr[data-id] { cursor: pointer; }
.report-activity-table tbody tr[data-id]:hover td { background: var(--bg-elev-2); }
.report-activity-name { display: flex; align-items: center; gap: 10px; }
/* Market Price is informational, not the row's headline number (that's
   Price/Profit, styled via .metric-invested/.metric-profit instead) — this
   overrides the generic td:last-child accent/bold treatment which would
   otherwise land on whichever column happens to be last. */
.report-activity-table tbody td.report-market-price { color: var(--text-dim); font-weight: 400; text-align: right; }
/* Profit is now the last column in both tables — override td:last-child's
   generic accent color so green/red (.metric-profit/.metric-negative)
   actually wins instead. */
.report-activity-table tbody td:last-child.metric-profit { color: var(--good); }
.report-activity-table tbody td:last-child.metric-negative { color: var(--danger); }
/* Financials' Added/Sold tables run 5 columns wide — on a phone that
   clipped the header labels and the empty-state text off the right edge
   (new-user audit screenshot 31). Stack each row into a small card
   instead of shrinking a wide table into a narrow one. Cell labels come
   from data-label attributes set in renderReport(). */
@media (max-width: 700px) {
  .report-activity-table thead { display: none; }
  .report-activity-table, .report-activity-table tbody, .report-activity-table tr, .report-activity-table td {
    display: block;
    width: 100%;
    box-sizing: border-box;
  }
  .report-activity-table tbody tr[data-id] {
    padding: 10px 2px;
    border-bottom: 1px solid var(--border);
  }
  /* A <td> with no other display/width override (the Card-name cell)
     kept collapsing to a min-content sliver instead of the 100% set
     above — browsers still carry some of a table cell's intrinsic
     sizing into "display: block" here unless it's forced explicitly. */
  .report-activity-table td {
    border-bottom: none;
    padding: 3px 2px;
    text-align: left !important;
    width: 100% !important;
    box-sizing: border-box;
  }
  .report-activity-table td[data-label] {
    display: flex;
    justify-content: space-between;
    gap: 10px;
  }
  .report-activity-table td[data-label]::before {
    content: attr(data-label);
    color: var(--text-dim);
    font-size: 11px;
    text-transform: uppercase;
    letter-spacing: .04em;
    flex: 0 0 auto;
  }
  .report-activity-table tr:not([data-id]) td {
    text-align: center !important;
    padding: 14px 2px;
    white-space: normal;
    width: 100% !important;
    box-sizing: border-box;
  }
}
.modal.value-breakdown-modal { width: min(560px, calc(100vw - 28px)); max-width: 560px; }
.value-breakdown { display: grid; gap: 20px; }
.value-breakdown > * { min-width: 0; }
.breakdown-section-heading { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 8px; }
.breakdown-section-heading h3 { margin: 0; font-size: 12px; color: var(--text-dim); text-transform: uppercase; letter-spacing: .06em; }
.breakdown-section-total { font-size: 13px; font-weight: 800; }
.breakdown-section-total.up { color: var(--good); }
.breakdown-section-total.down { color: var(--danger); }
.mover-row-static { cursor: default; }
.mover-row-static:hover { border-color: var(--border); background: transparent; }

.toolbar {
  display: flex;
  gap: 14px;
  align-items: stretch;
  flex-wrap: wrap;
  /* padding, not margin — margin sits outside the sticky box and paints
     nothing, so scrolled content shows through that strip. */
  padding-bottom: 20px;
  transition: padding-bottom .18s ease;
}
body.header-scrolled .toolbar { padding-bottom: 10px; }

.toolbar-filters {
  flex: 1 1 100%;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.toolbar-actions {
  display: flex;
  align-items: stretch;
  gap: 10px;
  flex: 1 1 100%;
  flex-wrap: wrap;
}
/* "Select Cards" swaps this row's own content into the bulk-action controls
   in place, rather than opening a separate toolbar elsewhere on the page —
   the trigger and the result are the same spot. */
.toolbar-actions-default { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; width: 100%; }
.selection-active .toolbar-actions-default { display: none; }
.card-tag-legend {
  display: flex;
  align-items: center;
  gap: 6px 10px;
  flex: 1 1 100%;
  flex-wrap: wrap;
  padding: 2px 4px;
  color: var(--text-dim);
  font-size: 11px;
}
.card-tag-legend-label {
  color: var(--text);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}
.legend-group {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.legend-group-label {
  color: var(--text);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.legend-divider {
  width: 1px;
  height: 16px;
  background: var(--border);
}
.legend-item {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  white-space: nowrap;
}
.legend-item b {
  color: var(--accent);
  font-size: 10px;
  font-weight: 800;
}
.catalog-field small {
  display: block;
  margin-top: 4px;
  color: var(--text-dim);
  font-size: 10px;
}
.edition-picker-field { grid-column: span 2; }
.edition-picker {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
}
.edition-choice {
  flex: 1 1 150px;
  min-height: 38px;
  padding: 8px 12px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 7px;
  color: var(--text-dim);
  font: inherit;
  font-size: 12px;
  cursor: pointer;
}
.edition-choice:hover { border-color: var(--accent); color: var(--text); }
.edition-choice.active { background: rgba(79, 209, 197, .14); border-color: var(--accent); color: var(--text); }
.edition-choice-rare { color: var(--warn); }
.edition-choice-rare.active { background: rgba(255, 190, 70, .12); border-color: var(--warn); color: var(--warn); }
.rare-edition-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
  color: var(--text-dim);
  font-size: 11px;
}
.rare-edition-row select { min-width: 150px; padding: 7px 9px; }
.edition-help { display: block; margin-top: 6px; color: var(--text-dim); font-size: 10px; }
.legend-rarity-group .legend-item { gap: 3px; }
.legend-rarity-group .rarity-symbol {
  width: 14px;
  height: 14px;
  font-size: 9px;
}
.legend-rarity-group .rarity-symbol.rarity-double-rare,
.legend-rarity-group .rarity-symbol.rarity-special-illustration-rare { font-size: 6px; }

.bulk-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  width: 100%;
  padding: 10px 14px;
  background: linear-gradient(100deg, rgba(79,209,197,.12), rgba(20,23,31,.95) 58%);
  border: 1px solid rgba(79,209,197,.45);
  border-radius: 12px;
  box-shadow: 0 10px 24px rgba(0,0,0,.14);
}
/* "Select Cards" toggled this in with an instant display:none/flex flip,
   which read as an abrupt pop rather than a deliberate reveal. Animating it
   in (same easing/feel as the modal open animation elsewhere) makes it a
   response to the click rather than something that just appears. */
.bulk-toolbar:not(.hidden) { animation: bulk-toolbar-in .2s ease-out both; }
@keyframes bulk-toolbar-in { from { opacity: 0; transform: translateY(-8px); } to { opacity: 1; transform: translateY(0); } }
.bulk-toolbar-left { display: flex; align-items: center; gap: 8px; flex: 0 0 auto; }
/* A bordered "✕ Exit" pill button read as dated chrome — this is now a
   plain dismiss glyph in the same visual language as .modal-close, just
   inline instead of corner-absolute, sitting right after the count it
   dismisses ("4 selected ×") rather than announcing itself as a separate
   labeled action. Esc does the same thing (see the keydown handler). */
.bulk-exit-button {
  flex: 0 0 auto;
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 20px;
  line-height: 1;
  padding: 2px 4px;
  cursor: pointer;
}
.bulk-exit-button:hover { color: var(--text); }
.bulk-selection-count {
  padding: 5px 10px;
  background: rgba(79,209,197,.14);
  border-radius: 999px;
  font-size: 12px;
  font-weight: 700;
  color: var(--accent);
  white-space: nowrap;
}
.bulk-toolbar-actions { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
/* One checkbox drives select-all/none/partial for both Grid and Table view
   (previously a one-way "Select Visible" button in Grid, a proper
   tri-state checkbox in the Table header — two different mechanisms for
   the same action). */
.bulk-select-all { display: inline-flex; align-items: center; gap: 7px; color: var(--text); font-size: 13px; cursor: pointer; padding: 9px 4px; }
.bulk-toolbar-divider { width: 1px; height: 18px; background: var(--border); flex: 0 0 auto; }
/* All four actions read as equal weight until something is selected — a
   solid-fill .btn-primary here used to stay visually louder than the
   sibling buttons even while disabled (42% opacity teal still outshines
   full-opacity grey outlines), which made the one button you couldn't
   click the most eye-catching thing in the row. */
.bulk-toolbar-actions button {
  padding: 9px 14px;
  font-size: 13px;
  font-weight: 400;
  min-height: 35px;
}
.bulk-toolbar button:disabled { opacity: .42; cursor: not-allowed; transform: none; }
.selection-active .toolbar-actions { align-items: center; }
.selection-active .bulk-toolbar {
  background: linear-gradient(100deg, rgba(79,209,197,.16), rgba(17,21,30,.96) 48%);
  border-color: rgba(79,209,197,.62);
}

.toolbar-menu { position: relative; flex-shrink: 0; }
.toolbar-menu-btn { font-size: 16px; font-weight: 700; padding: 9px 13px; line-height: 1; }
.toolbar-menu-btn[aria-expanded="true"] { border-color: var(--accent); color: var(--accent); }
.toolbar-menu-panel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 20;
  min-width: 250px;
  padding: 6px;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: 0 14px 34px rgba(0,0,0,0.32);
  display: flex;
  flex-direction: column;
}
/* Only shown at all on mobile (see .mobile-sheet-head's own base rule) —
   the desktop popover already sits right under its own trigger button, so
   a repeated "More actions" title there would be redundant chrome. */
.toolbar-menu-title { display: none; }
.toolbar-menu-label {
  padding: 8px 10px 4px;
  color: var(--text-dim);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.toolbar-menu-label:not(:first-child) { margin-top: 4px; border-top: 1px solid var(--border); padding-top: 10px; }
.toolbar-menu-item {
  display: flex;
  align-items: center;
  gap: 9px;
  width: 100%;
  padding: 9px 10px;
  background: transparent;
  border: none;
  border-radius: 7px;
  color: var(--text);
  text-align: left;
  font: inherit;
  font-size: 13px;
  cursor: pointer;
}
.toolbar-menu-item:hover { background: rgba(89,216,204,0.1); }
.toolbar-menu-icon { flex: 0 0 auto; width: 16px; height: 16px; color: var(--text-dim); }
.toolbar-menu-item:hover .toolbar-menu-icon, .toolbar-menu-item-danger .toolbar-menu-icon { color: currentColor; }
.toolbar-menu-item-danger { color: var(--danger); }
.toolbar-menu-item-danger:hover { background: rgba(224,80,80,0.12); }
/* A quiet highlight, not a loud one — this is a convenience shortcut
   (skip scrolling to "Incoming" further down), not an alert. */
.toolbar-menu-contextual {
  margin-bottom: 4px;
  padding-bottom: 6px;
  border-bottom: 1px solid var(--border);
  background: rgba(79,209,197,.05);
  border-radius: 8px;
}
.toolbar-menu-contextual .toolbar-menu-label { padding-top: 8px; }
/* Restore Backup is the one action in this menu that can actually destroy
   real data — a visible boundary (border + tinted background) around it
   is what "keep it red but also make it look separate, not just another
   red-text row" actually means; color alone reads as "careful," not
   "different category of action entirely." */
.toolbar-menu-danger-zone {
  margin-top: 4px;
  padding: 4px;
  border: 1px solid rgba(224,80,80,.3);
  border-radius: 9px;
  background: rgba(224,80,80,.05);
}
.toolbar-menu-danger-zone .toolbar-menu-label { padding-top: 4px; }

.toolbar input[type="search"] {
  flex: 1 1 220px;
  min-width: 220px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 14px;
}

.toolbar select {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 10px 12px;
  border-radius: 8px;
  font-size: 14px;
}

.checkbox-label {
  display: flex;
  align-items: center;
  gap: 7px;
  min-height: 38px;
  padding: 7px 11px 7px 9px;
  border: 1px solid rgba(132,147,177,.24);
  border-radius: 10px;
  background: var(--panel);
  color: var(--text-dim);
  font-size: 11px;
  font-weight: 650;
  cursor: pointer;
  transition: border-color .16s ease, background .16s ease, color .16s ease;
}
.checkbox-label:hover { border-color: rgba(89,216,204,.48); color: var(--text); }
.checkbox-label:has(input:checked) {
  border-color: rgba(89,216,204,.72);
  background: rgba(89,216,204,.12);
  color: var(--text);
}
.checkbox-label input {
  appearance: none;
  width: 15px;
  height: 15px;
  margin: 0;
  border: 1px solid #59657a;
  border-radius: 5px;
  background: var(--bg-elev);
  display: grid;
  place-content: center;
  cursor: pointer;
}
.checkbox-label input::after {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 2px;
  background: transparent;
}
.checkbox-label input:checked { border-color: var(--accent); background: var(--accent); }
.checkbox-label input:checked::after { background: #071312; }

.filter-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
}
.filter-toggle[aria-expanded="true"] {
  border-color: var(--accent);
  color: var(--accent);
}
.filter-toggle-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  border-radius: 999px;
  background: var(--accent);
  color: #06201e;
  font-size: 10px;
  font-weight: 800;
}
.filter-controls-panel {
  position: absolute;
  top: calc(100% - 8px);
  right: 0;
  z-index: 25;
  display: grid;
  grid-template-columns: repeat(2, minmax(210px, 1fr));
  gap: 9px;
  width: min(720px, calc(100vw - 36px));
  padding: 14px;
  background: var(--bg-elev);
  border: 1px solid rgba(132,147,177,.34);
  border-radius: 12px;
  box-shadow: 0 18px 42px rgba(0,0,0,.38);
}
.filter-menu-head { display: grid; grid-column: 1 / -1; gap: 2px; padding: 0 2px 7px; border-bottom: 1px solid rgba(132,147,177,.18); }
.filter-menu-head strong { color: var(--text); font-size: 12px; }
.filter-menu-head span { color: var(--text-dim); font-size: 10px; }
.filter-control {
  display: flex;
  flex-direction: column;
  align-items: center;
  align-items: stretch;
  gap: 6px;
  font-size: 13px;
  color: var(--text-dim);
  white-space: nowrap;
}
/* Same reasoning as the refresh panel's Tag/Exclude/Franchise pickers — a
   native <select>'s open option list is unstyleable OS chrome that breaks
   the dark theme, so these are hand-built dropdowns (trigger button + dark
   listbox popover) instead. */
.filter-select-custom { position: relative; }
.filter-select-trigger {
  width: 100%;
  text-align: left;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 10px 12px;
  border-radius: 8px;
  font-size: 13px;
  font-family: inherit;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.filter-select-panel { width: 100%; min-width: 180px; max-height: 240px; overflow-y: auto; padding: 6px; display: flex; flex-direction: column; gap: 2px; }
.filter-controls-panel .checkbox-label { width: 100%; justify-content: flex-start; }
/* Franchise/Rarity/Condition + the two checkboxes are the common case;
   Holo/Edition/Language/Tags/Location Bought are narrower facets tucked
   behind this disclosure instead of all ten filters competing for
   attention at once (audit: "powerful but dense... a bit heavy for
   casual browsing"). */
.filter-more { grid-column: 1 / -1; }
.filter-more summary {
  cursor: pointer;
  list-style: none;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-dim);
  padding: 2px 0;
}
.filter-more summary::-webkit-details-marker { display: none; }
.filter-more summary::before { content: '›'; display: inline-block; margin-right: 5px; transition: transform .15s ease; }
.filter-more[open] summary::before { transform: rotate(90deg); }
.filter-more summary:hover { color: var(--accent); }
.filter-more-body { display: grid; grid-template-columns: repeat(2, minmax(210px, 1fr)); gap: 9px; margin-top: 9px; }
.filter-control.active-filter > span { color: var(--accent); font-weight: 700; }
.filter-control.active-filter > span { max-width: 150px; overflow: hidden; text-overflow: ellipsis; }
.filter-control.active-filter .filter-select-trigger { border-color: var(--accent); box-shadow: 0 0 0 1px rgba(79, 209, 197, .18); }
.filter-status {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 3px 4px 3px 10px;
  border: 1px solid rgba(79, 209, 197, .45);
  border-radius: 8px;
  color: var(--accent);
  font-size: 12px;
  background: rgba(79, 209, 197, .07);
}
/* JS toggles the native `hidden` attribute (not a `.hidden` class) on this
   element to show/hide it — but `.filter-status`'s own `display: inline-flex`
   above ties in specificity with the UA stylesheet's `[hidden]{display:none}`
   rule (both are a single class/attribute selector), and later source order
   wins that tie. Without this, "Clear filters" stayed visible even with no
   filters active. */
.filter-status[hidden] { display: none; }
/* The button sat inside .filter-status's own teal pill with its own
   separate white/dark background and border, reading as a button nested
   inside a button. Stripping its chrome here makes the whole thing read
   as one cohesive pill instead — just the label text plus an inline
   "Clear filters" action colored to match. */
.filter-status .btn-secondary { padding: 6px 9px; font-size: 11px; background: transparent; border: none; color: inherit; }
.filter-status .btn-secondary:hover { background: rgba(79,209,197,.16); border-radius: 6px; }

.view-toggle { display: flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
.view-btn {
  background: var(--bg-elev);
  color: var(--text-dim);
  border: none;
  padding: 9px 16px;
  cursor: pointer;
  font-size: 13px;
}
.view-btn.active { background: var(--accent); color: #06201e; font-weight: 600; }

.btn-primary {
  background: var(--accent);
  color: #06201e;
  border: none;
  border-radius: 8px;
  padding: 10px 18px;
  font-weight: 600;
  cursor: pointer;
  font-size: 14px;
  white-space: nowrap;
}
.btn-primary:hover { filter: brightness(1.08); }

.btn-secondary {
  background: transparent;
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 9px 16px;
  cursor: pointer;
  font-size: 13px;
}
.btn-secondary:hover { border-color: var(--accent); }

.btn-danger {
  background: transparent;
  color: var(--danger);
  border: 1px solid var(--danger);
  border-radius: 8px;
  padding: 9px 16px;
  cursor: pointer;
  font-size: 13px;
}

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

.card-tile {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
  transition: transform 0.15s ease, border-color 0.15s ease;
  position: relative;
}
.card-select-wrap {
  position: absolute;
  top: 9px;
  left: 9px;
  z-index: 2;
  display: flex;
  padding: 4px;
  background: rgba(12,14,20,0.82);
  border-radius: 5px;
  cursor: pointer;
}
.card-select { accent-color: var(--accent); cursor: pointer; }
.card-tile:hover { transform: translateY(-3px); border-color: var(--accent); box-shadow: var(--shadow); }
.card-tile.selected { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(79,209,197,.4); }
.filter-transition .card-tile { animation: filter-card-in .3s cubic-bezier(.16,.84,.44,1) both; }
@keyframes filter-card-in {
  from { opacity: 0; transform: translate3d(0, 8px, 0) scale(.97); }
  to { opacity: 1; transform: translate3d(0, 0, 0) scale(1); }
}

/* Task 7: loading skeletons — .card-skeleton reuses .card-tile/.card-thumb
   as-is (same aspect-ratio, same padding) so the real cards that replace
   it land at an identical size, nothing reflows. Only the inner content
   differs: plain pulsing bars instead of an image/name/value. */
.card-skeleton { cursor: default; }
.card-skeleton .card-thumb { background: var(--bg-elev-2); }
.skeleton-line {
  height: 12px;
  margin: 6px 0;
  border-radius: 4px;
  background: linear-gradient(90deg, var(--bg-elev-2) 25%, var(--border) 50%, var(--bg-elev-2) 75%);
  background-size: 200% 100%;
  animation: skeleton-pulse 1.4s ease-in-out infinite;
}
.skeleton-line-name { width: 78%; height: 14px; }
.skeleton-line-set { width: 55%; }
.skeleton-line-value { width: 40%; height: 16px; }
@keyframes skeleton-pulse {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}
@media (prefers-reduced-motion: reduce) {
  .skeleton-line { animation: none; }
}

.card-thumb {
  width: 100%;
  aspect-ratio: 3 / 4.15;
  background: var(--bg-elev-2);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  position: relative;
}
.card-thumb img { width: 100%; height: 100%; object-fit: contain; }

.card-flag {
  position: absolute;
  top: 8px;
  right: 8px;
  background: var(--warn);
  color: #241c02;
  font-size: 10px;
  font-weight: 700;
  padding: 3px 7px;
  border-radius: 999px;
  letter-spacing: 0.02em;
  z-index: 2;
}

.card-copies-badge {
  position: absolute;
  bottom: 8px;
  left: 8px;
  background: rgba(9,11,16,0.82);
  border: 1px solid var(--border);
  color: var(--text);
  font-size: 11px;
  font-weight: 800;
  padding: 3px 7px;
  border-radius: 999px;
  z-index: 1;
}
.table-copy-badge { color: var(--text-dim); font-size: 11px; font-weight: 700; }
.card-favorite-badge {
  position: absolute;
  bottom: 8px;
  right: 8px;
  background: rgba(9,11,16,0.82);
  border: 1px solid rgba(242,198,109,0.55);
  /* Fixed bright gold, not var(--accent-2) — this badge always sits on
     its own dark circle regardless of theme (an overlay on the card art,
     same idea as the copies/rarity corner badges), but accent-2 itself
     gets deepened for light mode so it stays readable as page text on a
     white background. That deepened, browner gold read as muddy/low
     contrast against this badge's dark circle. */
  color: #f2c66d;
  font-size: 12px;
  width: 22px;
  height: 22px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 999px;
  z-index: 1;
}
.table-favorite-badge { color: var(--accent-2); font-size: 12px; }

.card-info { padding: 10px 12px 14px; }
.card-info-main { min-width: 0; }
.card-name { font-weight: 700; font-size: 14px; margin-bottom: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card-set { font-size: 11.5px; color: var(--text-dim); margin-bottom: 8px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.card-value { font-size: 16px; font-weight: 700; color: var(--accent); }
/* An unpriced card (no value fetched yet, still flagged needs_confirmation
   — see cardNeedsPricing) shouldn't lead with "€0.00" as if that's a real,
   confirmed value (new-user audit screenshot 25). Calm and secondary, not
   an alarm color — tapping the tile opens the editor's existing "Fetch
   Card Info" action. */
.card-value-unpriced { font-size: 12.5px; font-weight: 600; color: var(--text-dim); }
.card-name-row { display: flex; align-items: baseline; justify-content: space-between; gap: 6px; }
.card-name-row .card-name { min-width: 0; flex: 1 1 auto; }
.card-name-row .profit-chip { flex: 0 0 auto; }

/* Metadata badges (set logo, edition, rarity, holo, condition) move onto the
   thumbnail as a compact overlay instead of taking their own row in the info
   panel, so the tile reads as image + name + value first. */
.card-tags {
  position: absolute;
  right: 6px;
  bottom: 6px;
  display: flex;
  align-items: center;
  gap: 3px;
  flex-wrap: wrap;
  justify-content: flex-end;
  max-width: calc(100% - 12px);
  z-index: 1;
}
/* .card-tags-prefixed (not just .card-set-icon) so this outranks the
   generic ".card-thumb img { width/height: 100% }" rule — .card-tags now
   lives inside .card-thumb (an actual overlay on the photo, matching its
   own design intent) rather than floating in .card-info where it used to
   collide with the price on any card with enough badges/a long name. */
.card-tags .card-set-icon {
  width: 20px;
  height: 20px;
  object-fit: contain;
  background: rgba(9,11,16,0.72);
  border-radius: 5px;
  padding: 2px;
}
.card-tag {
  min-width: 20px;
  height: 20px;
  padding: 2px 4px;
  border: 1px solid var(--border);
  border-radius: 5px;
  font-size: 9.5px;
  font-weight: 700;
  line-height: 1;
  text-align: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: rgba(9,11,16,0.72);
  backdrop-filter: blur(2px);
}
.card-tag-holo { color: var(--accent); border-color: rgba(79,209,197,0.45); }
.card-tag-condition { color: var(--text-dim); }
.card-tag-edition { color: var(--warn); border-color: rgba(255,190,70,0.5); padding: 2px 3px; height: 24px; }
/* The actual 1st Edition stamp, used as an alpha mask so it renders as a
   solid amber glyph on the same dark badge as its neighbours. */
.card-tags .card-tag-edition .edition-1st-stamp {
  display: block;
  width: 28px;
  height: 21px;
  background: var(--warn);
  -webkit-mask: url("/assets/edition-1st-stamp.png") center / contain no-repeat;
  mask: url("/assets/edition-1st-stamp.png") center / contain no-repeat;
}
.card-tag-rarity { color: #d7a7ff; border-color: rgba(180,120,240,0.5); padding: 2px; }
.card-tag.card-tag-psa { min-width: auto; width: auto; padding: 2px 6px; color: #6fb3ff; border-color: rgba(111,179,255,0.5); background: rgba(20,35,60,0.82); letter-spacing: .02em; }
/* Franchise badges. Real logo files (user-sourced, see
   public/assets/franchise-logos/) for the named franchises; "Sports
   Cards" and "Other" aren't one single franchise with a logo, so those
   two stay a short color-coded text abbreviation. */
.franchise-logo { display: inline-block; height: 14px; width: auto; max-width: 46px; object-fit: contain; vertical-align: middle; }
.franchise-logo.franchise-logo-plain { height: 12px; max-width: 40px; margin-right: 2px; }
/* Re-declared at higher specificity for the same reason as .card-tags
   .card-set-icon above — .card-tags now overlays .card-thumb, whose own
   "img { width/height: 100% }" rule would otherwise win and blow this
   logo up to fill the whole thumbnail. */
.card-tags .franchise-logo { height: 14px; width: auto; max-width: 46px; }
.card-tag.card-tag-franchise { min-width: auto; width: auto; padding: 2px 5px; letter-spacing: .02em; }
.card-tag-franchise-sports { color: #8fe3a0; border-color: rgba(143,227,160,0.5); }
.card-tag-franchise-other { color: var(--text-dim); }
.franchise-badge-plain { display: inline-flex; align-items: center; font-size: 10px; font-weight: 700; vertical-align: middle; }
.rarity-symbol {
  display: inline-flex;
  width: 18px;
  height: 18px;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: #f4f5f7;
  color: #11131a;
  font-size: 12px;
  line-height: 1;
}
.rarity-symbol.rarity-double-rare,
.rarity-symbol.rarity-special-illustration-rare,
.rarity-symbol.rarity-shiny-ultra-rare { font-size: 8px; }
.rarity-symbol.rarity-ultra-rare { background: #151823; color: #fff; }
.rarity-symbol.rarity-illustration-rare,
.rarity-symbol.rarity-special-illustration-rare,
.rarity-symbol.rarity-hyper-rare,
.rarity-symbol.rarity-mega-hyper-rare { color: #f0c84b; }
.rarity-symbol.rarity-ace-spec-rare { color: #d629a8; }
.rarity-symbol.rarity-radiant-rare,
.rarity-symbol.rarity-shiny-rare,
.rarity-symbol.rarity-shiny-ultra-rare { color: #e1bf42; }

/* A card slab shape, not just a small icon floating on the tile's own
   dark background — the gradient here was previously so close to the
   surrounding card-tile color that it read as an empty box with a tiny
   icon in it rather than an intentional placeholder (new-user audit).
   The dashed inset rect gives it a "photo goes here" outline even before
   you notice the icon or text. */
.placeholder-card {
  width: 100%;
  height: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 8px;
  position: relative;
  background: linear-gradient(160deg, #232a3a, #12151f);
  color: var(--text-dim);
  text-align: center;
  padding: 14px;
}
.placeholder-card::before {
  content: '';
  position: absolute;
  inset: 10%;
  border: 1.5px dashed rgba(132,147,177,0.28);
  border-radius: 10px;
}
.placeholder-card .placeholder-icon { width: 40%; max-width: 46px; height: auto; color: rgba(132,147,177,0.55); position: relative; }
.placeholder-card .txt { font-size: 11px; font-weight: 700; letter-spacing: .02em; position: relative; }

/* Small table/report row thumbnails — icon only, no "Add a photo" copy,
   the row already has the card's name right next to it. */
.table-thumb-fallback, .scan-candidate-thumb-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  color: rgba(132,147,177,0.5);
}
.table-thumb-fallback .placeholder-icon { width: 60%; height: auto; }
.scan-candidate-thumb-fallback .placeholder-icon { width: 55%; height: auto; }

.table-wrap { overflow-x: auto; border: 1px solid var(--border); border-radius: var(--radius); }
table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
thead th {
  text-align: left;
  padding: 12px 14px;
  background: var(--bg-elev-2);
  color: var(--text-dim);
  font-weight: 600;
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  cursor: pointer;
  white-space: nowrap;
}
tbody td { padding: 10px 14px; border-top: 1px solid var(--border); }
thead th:first-child, tbody td:first-child { width: 42px; padding-left: 12px; padding-right: 4px; }
tbody tr { cursor: pointer; }
tbody tr:hover { background: var(--bg-elev-2); }
tbody tr.selected { background: rgba(79,209,197,.08); box-shadow: inset 3px 0 0 var(--accent); }
.table-thumb { width: 34px; height: 46px; border-radius: 4px; object-fit: cover; background: var(--bg-elev-2); }

.hidden { display: none !important; }
/* Not .hidden (display:none) — iOS Safari can fail or behave
   inconsistently when a <label for="..."> targets a display:none file
   input, especially one with `capture` set for the camera. Kept in
   normal layout flow (so it stays a real, clickable, focusable element)
   but visually and practically invisible via the standard
   clip/position technique instead. */
.visually-hidden-file-input {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}
.empty-state { display: flex; flex-direction: column; align-items: center; gap: 12px; text-align: center; color: var(--text-dim); padding: 40px 0; }
.empty-state-mark { width: 40px; height: 40px; border-radius: 9px; opacity: .55; }
.empty-state-heading { font-size: 16px; color: var(--text); }
.empty-state-text { margin: 0; }
.empty-state-onboarding { display: flex; flex-direction: column; align-items: center; gap: 12px; max-width: 380px; }
.empty-state-onboarding .empty-state-text { max-width: 320px; line-height: 1.5; }
.empty-state-onboarding-actions { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin-top: 6px; }

.tracker-heading {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 20px;
  margin-bottom: 20px;
}
.tracker-heading h2 { margin: 18px 0 4px; font-size: 24px; }
.tracker-heading p { margin: 0; color: var(--text-dim); font-size: 13px; }
.tracker-heading input {
  width: min(320px, 100%);
  background: var(--bg-elev);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 14px;
}
.tracker-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px;
  margin-bottom: 18px;
}
.tracker-stat {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 12px 14px;
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 10px;
}
.tracker-stat strong { font-size: 20px; }
.tracker-stat span { color: var(--text-dim); font-size: 11px; text-transform: uppercase; letter-spacing: .05em; }
.tracker-stat-highlight { border-color: rgba(79,209,197,.5); background: rgba(79,209,197,.06); }
.tracker-stat-highlight strong { color: var(--accent); }
.tracker-percent { color: var(--accent); font-weight: 800; margin-left: 4px; }

.tracker-groups { display: grid; grid-template-columns: repeat(auto-fill, minmax(290px, 1fr)); gap: 12px; align-items: start; }
.tracker-group {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: 10px;
  overflow: hidden;
}
.tracker-group-header {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 10px;
  padding: 14px 16px 16px;
  background: transparent;
  border: none;
  color: var(--text);
  text-align: left;
  cursor: pointer;
}
.tracker-group-header:hover { background: var(--bg-elev-2); }
.tracker-group-top { display: flex; align-items: center; gap: 10px; }
.tracker-group-name { flex: 1; min-width: 0; font-size: 15px; font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tracker-group-meta { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.tracker-group-count { color: var(--text-dim); font-size: 12px; white-space: nowrap; }
.tracker-group-count.no-total { font-style: italic; opacity: .75; }
.tracker-group-chevron { color: var(--accent); font-size: 18px; flex-shrink: 0; line-height: 1; }
.tracker-set-icon-wrap {
  width: 54px;
  height: 32px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
}
.tracker-set-icon { max-width: 54px; max-height: 32px; object-fit: contain; }
.tracker-set-icon-fallback { color: var(--accent); font-size: 24px; }
.japanese-set-icon { width: 54px; height: 32px; }
.japanese-set-icon .tracker-set-icon-fallback { display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 24px; border: 1px solid rgba(79,209,197,.7); border-radius: 7px; color: var(--accent); font-size: 12px; font-weight: 800; letter-spacing: .08em; }
.tracker-progress {
  height: 5px;
  background: var(--border);
  border-radius: 999px;
  overflow: hidden;
}
.tracker-progress span { display: block; height: 100%; background: var(--accent); border-radius: inherit; transition: width .3s ease; }
.tracker-progress.tier-low span { background: var(--warn); }
.tracker-progress.tier-mid span { background: var(--accent); }
.tracker-progress.tier-high span { background: var(--good); }
.tracker-progress.no-total { background: repeating-linear-gradient(90deg, var(--border), var(--border) 6px, transparent 6px, transparent 10px); opacity: .65; }
.tracker-card-list-toolbar { display: flex; justify-content: flex-end; padding: 0 2px 8px; }
.tracker-card-list-toolbar label { display: flex; align-items: center; gap: 8px; color: var(--text-dim); font-size: 12px; }
.tracker-card-list-toolbar select { background: var(--bg-elev-2); border: 1px solid var(--border); border-radius: 6px; color: var(--text); padding: 6px 8px; }
.tracker-card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(64px, 1fr)); gap: 8px; }
.tracker-card-thumb {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4px;
  padding: 0;
  background: none;
  border: none;
  color: var(--text-dim);
  cursor: pointer;
}
.tracker-card-thumb-img {
  position: relative;
  width: 100%;
  aspect-ratio: 5 / 7;
  border-radius: 7px;
  overflow: hidden;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  display: flex;
  align-items: center;
  justify-content: center;
}
.tracker-card-thumb-img img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* :not([hidden]) matters here — without it this rule's two-class
   specificity beats the browser's own [hidden]{display:none}, so a
   fallback marked hidden (an image that loaded fine) still rendered as
   a flex box taking up real layout space next to the <img>, squeezing
   it sideways and leaving a visible gap (every loaded thumbnail sat
   shifted left in its tile). */
.tracker-card-thumb-img .tracker-card-thumb-fallback:not([hidden]) { display: flex; align-items: center; justify-content: center; height: 100%; }
.tracker-card-thumb-fallback { font-size: 20px; opacity: .5; }
.tracker-card-thumb-label { font-size: 10px; text-align: center; line-height: 1.2; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tracker-card-thumb-rarity { position: absolute; bottom: 3px; left: 3px; width: 16px; height: 16px; display: flex; align-items: center; justify-content: center; background: rgba(11,15,22,.75); border-radius: 4px; }
.tracker-card-thumb-copies { position: absolute; top: 3px; left: 3px; background: rgba(11,15,22,.82); border: 1px solid var(--border); color: var(--text); font-size: 9px; font-weight: 800; padding: 1px 4px; border-radius: 999px; line-height: 1.4; }
.tracker-card-thumb.owned .tracker-card-thumb-img { border-color: var(--good); box-shadow: 0 0 0 1px var(--good) inset; }
.tracker-card-thumb.owned::after {
  content: '✓';
  position: absolute;
  top: -3px;
  right: -3px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--good);
  color: #04150f;
  font-size: 10px;
  font-weight: 800;
  display: flex;
  align-items: center;
  justify-content: center;
}
.tracker-card-thumb.owned:hover .tracker-card-thumb-img { border-color: var(--accent); box-shadow: 0 0 0 1px var(--accent) inset; }
.tracker-card-thumb.owned:hover .tracker-card-thumb-label { color: var(--accent); }
.tracker-card-thumb.missing .tracker-card-thumb-img { filter: grayscale(1); opacity: .45; transition: opacity .15s, filter .15s; }
.tracker-card-thumb.missing .tracker-card-thumb-label { opacity: .55; }
.tracker-card-thumb.missing:hover .tracker-card-thumb-img { filter: grayscale(.3); opacity: .7; border-color: var(--accent-2); box-shadow: 0 0 0 1px var(--accent-2) inset; }
.tracker-card-thumb-want-hint {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 10px;
  font-weight: 700;
  color: #04150f;
  background: var(--accent-2);
  border-radius: 7px;
  opacity: 0;
  transition: opacity .15s;
  pointer-events: none;
}
.tracker-card-thumb.missing:hover .tracker-card-thumb-want-hint { opacity: .92; }
.tracker-card-thumb.missing:disabled { opacity: .5; pointer-events: none; }
.tracker-card-thumb.missing.wanted .tracker-card-thumb-img { filter: grayscale(.35) sepia(.3); opacity: .8; border-color: var(--accent-2); box-shadow: 0 0 0 1px var(--accent-2) inset; }
.tracker-card-thumb.missing.wanted .tracker-card-thumb-label { opacity: .85; color: var(--accent-2); }
.tracker-card-thumb.missing.wanted:hover .tracker-card-thumb-img { filter: grayscale(0) sepia(0); opacity: .55; border-color: var(--danger); box-shadow: 0 0 0 1px var(--danger) inset; }
.tracker-card-thumb.missing.wanted:hover .tracker-card-thumb-want-hint { opacity: .92; background: var(--danger); color: #2b0505; }
.tracker-card-thumb-wanted-badge {
  position: absolute;
  top: -3px;
  right: -3px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--accent-2);
  color: #2b1a00;
  font-size: 9px;
  display: flex;
  align-items: center;
  justify-content: center;
}
@media (max-width: 600px) {
  .toolbar-filters, .toolbar-actions { width: 100%; }
  .tracker-heading { align-items: stretch; flex-direction: column; }
  .tracker-heading input { width: 100%; }
}

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(4,5,9,0.7);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 40px 16px;
  overflow-y: auto;
  z-index: 100;
}
.modal-backdrop:not(.hidden) { animation: modal-fade-in 0.16s ease-out both; }
.modal-backdrop.is-closing { animation: modal-fade-out 0.16s ease-in both; pointer-events: none; }
/* confirmDialog()/promptDialog() are global replacements for
   confirm()/prompt() — they can be triggered from inside an already-open
   modal (e.g. Admin panel's "Reset password"), so they need to always
   paint above every other .modal-backdrop regardless of DOM order, not
   just win ties by being later in the document. */
#confirm-dialog-backdrop, #prompt-dialog-backdrop { z-index: 200; }
.modal {
  background: var(--bg-elev);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  max-width: 880px;
  width: 100%;
  position: relative;
  box-shadow: var(--shadow);
}
.modal-backdrop:not(.hidden) .modal { animation: modal-rise-in 0.2s ease-out both; }
.modal-backdrop.is-closing .modal { animation: modal-rise-out 0.16s ease-in both; }
.image-lightbox { align-items: center; padding: 24px; }
.image-lightbox-content {
  position: relative;
  display: flex;
  max-width: min(760px, 92vw);
  max-height: 92vh;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  animation: lightbox-in 0.2s ease-out both;
}
.image-lightbox.is-closing .image-lightbox-content { animation: lightbox-out 0.16s ease-in both; }
.image-lightbox-content img {
  display: block;
  max-width: 88vw;
  max-height: 84vh;
  width: auto;
  height: auto;
  object-fit: contain;
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: var(--shadow);
}
.image-lightbox-content .modal-close { top: -34px; right: -34px; color: var(--text); }
.image-lightbox-content div { color: var(--text); font-size: 13px; font-weight: 600; }
@keyframes lightbox-in { from { opacity: 0; transform: scale(0.96); } to { opacity: 1; transform: scale(1); } }
@keyframes lightbox-out { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.97); } }
.bulk-add-modal { width: min(680px, calc(100vw - 28px)); }
.bulk-add-modal textarea { min-height: 190px; font-family: ui-monospace, SFMono-Regular, Consolas, monospace; font-size: 12px; line-height: 1.5; }

.sell-ebay-modal { width: min(640px, calc(100vw - 28px)); max-height: min(720px, calc(100vh - 32px)); overflow-y: auto; }
.sell-ebay-header-row { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; flex-wrap: wrap; }
.sell-ebay-queue-nav { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text-dim); white-space: nowrap; }
.sell-ebay-skipped-note { margin-top: 10px; padding: 10px 12px; border-radius: 10px; background: rgba(230,168,68,0.12); border: 1px solid rgba(230,168,68,0.3); color: #e6a844; font-size: 12.5px; }
.sell-ebay-not-ready { padding: 24px 0; color: var(--text-dim); font-size: 14px; text-align: center; }
.sell-ebay-photos { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin: 16px 0; }
.sell-ebay-photos figure { margin: 0; }
.sell-ebay-photos img { width: 100%; aspect-ratio: 5/7; object-fit: cover; border-radius: 10px; background: var(--bg-elev-2); }
.sell-ebay-photos figcaption { display: flex; justify-content: space-between; align-items: center; margin-top: 6px; font-size: 12px; color: var(--text-dim); }
.sell-ebay-char-count { font-weight: 400; color: var(--text-dim); font-size: 11px; }
.value-edit-row-top { align-items: flex-start; }
.value-edit-row-top textarea { flex: 1; font-family: inherit; resize: vertical; }

.admin-modal { width: min(920px, calc(100vw - 28px)); max-height: min(640px, calc(100vh - 32px)); overflow-y: auto; }
.admin-status { margin-bottom: 14px; color: var(--danger); font-size: 13px; min-height: 16px; }
.admin-table-wrap { border-radius: 14px; background: var(--panel); box-shadow: var(--shadow); overflow-x: auto; }
#admin-users-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
#admin-users-table thead th { padding: 12px 14px; background: var(--bg-elev-2); text-align: left; white-space: nowrap; color: var(--text-dim); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; }
#admin-users-table thead th.admin-sortable { cursor: pointer; user-select: none; }
#admin-users-table thead th.admin-sortable:hover { color: var(--text); }
#admin-users-table thead th.admin-sortable .admin-sort-arrow { margin-left: 4px; opacity: .5; }
#admin-users-table thead th.admin-sort-active { color: var(--accent); }
#admin-users-table thead th.admin-sort-active .admin-sort-arrow { opacity: 1; }
.report-activity-table thead th.report-sortable { cursor: pointer; user-select: none; }
.report-activity-table thead th.report-sortable:hover { color: var(--text); }
.report-activity-table thead th.report-sortable .report-sort-arrow { margin-left: 4px; opacity: .5; }
.report-activity-table thead th.report-sort-active { color: var(--accent); }
.report-activity-table thead th.report-sort-active .report-sort-arrow { opacity: 1; }
#admin-users-table tbody td { padding: 11px 14px; border-top: 1px solid rgba(132,147,177,0.14); vertical-align: middle; }
#admin-users-table tbody tr:hover { background: rgba(89,216,204,0.05); }
.role-badge, .status-badge { display: inline-block; padding: 3px 9px; border-radius: 999px; font-size: 11px; font-weight: 700; letter-spacing: .02em; }
.role-badge.admin { background: rgba(242,198,109,0.14); color: var(--accent-2); border: 1px solid rgba(242,198,109,0.32); }
.role-badge.member { background: rgba(132,147,177,0.14); color: var(--text-dim); border: 1px solid rgba(132,147,177,0.24); }
.status-badge.active { background: rgba(89,216,204,0.1); color: var(--accent); border: 1px solid rgba(89,216,204,0.3); }
.status-badge.inactive { background: rgba(248,113,113,0.1); color: var(--danger); border: 1px solid rgba(248,113,113,0.3); }
.admin-row-actions { display: flex; gap: 6px; flex-wrap: wrap; }
.admin-row-actions button {
  padding: 6px 10px; border-radius: 8px; border: 1px solid var(--border);
  background: var(--panel); color: var(--text); font: inherit; font-size: 12px; cursor: pointer;
}
.admin-row-actions button:hover { background: rgba(89,216,204,0.08); border-color: rgba(89,216,204,0.4); }
.admin-row-actions button.danger:hover { background: rgba(248,113,113,0.08); border-color: rgba(248,113,113,0.4); }
.admin-row-actions button:disabled { opacity: .5; cursor: not-allowed; }
.admin-you-tag { color: var(--text-dim); font-size: 11px; margin-left: 6px; }
.admin-loading { padding: 30px; text-align: center; color: var(--text-dim); }
.bulk-add-status { min-height: 20px; margin-top: 12px; color: var(--text-dim); font-size: 12px; white-space: pre-line; }
.bulk-add-status.error { color: var(--danger); }
.bulk-add-results-summary { display: flex; justify-content: space-between; align-items: baseline; padding-bottom: 10px; margin-bottom: 10px; border-bottom: 1px solid var(--border); }
.bulk-add-results-summary strong { font-size: 15px; }
.bulk-add-results-summary span { color: var(--text-dim); font-size: 13px; }
.bulk-add-results-note { margin-top: 2px; color: var(--text-dim); font-size: 12px; }
.bulk-add-result-list { max-height: 320px; overflow-y: auto; display: flex; flex-direction: column; gap: 2px; }
.bulk-add-result-row { display: flex; justify-content: space-between; gap: 12px; padding: 7px 4px; font-size: 13.5px; border-bottom: 1px solid rgba(132,147,177,0.1); }
.bulk-add-result-row:last-child { border-bottom: none; }
.bulk-add-result-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.bulk-add-result-value { color: var(--text-dim); white-space: nowrap; }
.bulk-add-result-empty { color: var(--text-dim); justify-content: center; }
.bulk-add-results-failed { margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border); color: var(--danger); font-size: 12.5px; }
.bulk-add-results-failed ul { margin: 6px 0 0; padding-left: 18px; }
.set-detail-modal { width: min(1080px, calc(100vw - 28px)); }
.set-detail-heading { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; }
.set-detail-heading h2 { margin: 0 0 4px; font-size: 20px; }
.set-detail-heading p { margin: 0; color: var(--text-dim); font-size: 13px; }
.set-detail-heading .tracker-set-icon-wrap { width: 64px; height: 38px; }
.set-detail-heading .tracker-set-icon { max-width: 64px; max-height: 38px; }
.set-detail-modal .tracker-progress { margin-bottom: 14px; }
.set-detail-grid { grid-template-columns: repeat(auto-fill, minmax(84px, 1fr)); gap: 10px; }

.wrapped-modal { width: min(420px, calc(100vw - 28px)); }
.wrapped-modal .modal-body { padding: 20px 20px 22px; }
#wrapped-card-wrap { display: flex; justify-content: center; margin-top: 16px; }
/* A dark-on-dark flat card read as a wireframe, not something worth
   sharing — this leans into an actual "wrapped" feel: a gradient
   background, a warm hero number, and the prized card's own art shown
   at real size. downloadWrappedImage() redraws this same layout on a
   <canvas> for the PNG export, so any layout change here should be
   mirrored there too. */
.wrapped-card {
  width: 100%;
  max-width: 320px;
  border-radius: 20px;
  overflow: hidden;
  position: relative;
  background: linear-gradient(160deg, #1a0b3d 0%, #4a1a6b 45%, #0d5c52 100%);
  box-shadow: 0 12px 32px rgba(0,0,0,0.4);
}
.wrapped-glow {
  position: absolute;
  top: -40px;
  right: -40px;
  width: 140px;
  height: 140px;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(255,209,102,0.35), transparent 70%);
  pointer-events: none;
}
.wrapped-header { padding: 22px 20px 0; position: relative; }
.wrapped-brand-label { color: rgba(255,255,255,0.7); font-size: 10px; letter-spacing: .1em; text-transform: uppercase; font-weight: 600; }
.wrapped-title { color: #fff; font-size: 20px; font-weight: 700; margin-top: 10px; line-height: 1.25; }
.wrapped-value-block { padding: 20px 20px 0; position: relative; }
.wrapped-value-label { color: rgba(255,255,255,0.65); font-size: 10px; text-transform: uppercase; letter-spacing: .05em; }
.wrapped-value { color: #ffd166; font-size: 38px; font-weight: 800; line-height: 1.15; }
.wrapped-value-sub { color: #8ff0c8; font-size: 12px; font-weight: 600; margin-top: 2px; }
.wrapped-value-sub.metric-negative { color: #ff9b8a; }
.wrapped-franchise-line { color: rgba(255,255,255,0.55); font-size: 10.5px; margin-top: 6px; }
.wrapped-stats-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 8px; padding: 18px 20px 0; position: relative; }
.wrapped-stat { background: rgba(255,255,255,0.08); border-radius: 12px; padding: 10px 8px; text-align: center; min-width: 0; }
.wrapped-stat-value { color: #fff; font-size: 16px; font-weight: 800; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.wrapped-stat-value-sm { font-size: 12px; }
.wrapped-stat-label { color: rgba(255,255,255,0.6); font-size: 8px; text-transform: uppercase; letter-spacing: .04em; margin-top: 2px; }
.wrapped-prized { margin: 18px 20px 0; background: rgba(0,0,0,0.28); border-radius: 16px; padding: 14px; display: flex; gap: 14px; align-items: center; position: relative; }
.wrapped-prized-img { width: 56px; height: 78px; object-fit: cover; border-radius: 6px; flex-shrink: 0; box-shadow: 0 6px 16px rgba(0,0,0,0.4); background: rgba(0,0,0,0.3); }
.wrapped-prized-label { color: rgba(255,255,255,0.6); font-size: 9px; text-transform: uppercase; letter-spacing: .05em; }
.wrapped-prized-name { color: #fff; font-size: 17px; font-weight: 700; }
.wrapped-prized-value { color: #ffd166; font-size: 14px; font-weight: 700; margin-top: 2px; }
.wrapped-watermark { margin-top: 20px; padding: 12px 20px; background: rgba(0,0,0,0.35); display: flex; align-items: center; justify-content: center; gap: 6px; position: relative; }
.wrapped-watermark-dot { width: 6px; height: 6px; border-radius: 50%; background: #8ff0c8; }
.wrapped-watermark span:last-child { color: #fff; font-size: 11px; font-weight: 700; }
.bulk-progress-label { display: flex; align-items: center; gap: 8px; color: var(--text); font-weight: 600; }
.bulk-spinner {
  width: 13px;
  height: 13px;
  border: 2px solid var(--border);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: bulk-spin 0.7s linear infinite;
}
.bulk-progress-track { height: 5px; margin-top: 9px; overflow: hidden; background: var(--bg-elev-2); border-radius: 999px; }
.bulk-progress-track span { display: block; height: 100%; background: var(--accent); border-radius: inherit; transition: width 0.2s ease; }
@keyframes bulk-spin { to { transform: rotate(360deg); } }
@keyframes modal-fade-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes modal-fade-out { from { opacity: 1; } to { opacity: 0; } }
@keyframes modal-rise-in { from { opacity: 0; transform: translateY(10px) scale(0.985); } to { opacity: 1; transform: translateY(0) scale(1); } }
@keyframes modal-rise-out { from { opacity: 1; transform: translateY(0) scale(1); } to { opacity: 0; transform: translateY(6px) scale(0.99); } }

/* Portfolio refresh: denser information hierarchy with softer surfaces and clearer actions. */
:root {
  --bg: #090b10;
  --bg-elev: #11151e;
  --bg-elev-2: #191e29;
  --border: #2b3342;
  --text: #f4f6fb;
  --text-dim: #8f99ad;
  --accent: #59d8cc;
  --accent-2: #f2c66d;
  --radius: 14px;
  --shadow: 0 16px 34px rgba(0, 0, 0, 0.28);
  /* Translucent page-level surfaces (topbar, toolbar, table wraps) and the
     darker inset "well" panels nested inside modals (stat tiles, grouped
     rows) — consolidated from several near-duplicate one-off rgba() values
     so a theme only has to redefine these two instead of a dozen. */
  --panel: rgba(17,21,30,0.88);
  --panel-inset: rgba(9,11,16,0.5);
}

body {
  background: var(--bg);
  background-image: linear-gradient(rgba(255,255,255,0.018) 1px, transparent 1px);
  background-size: 100% 44px;
}

.topbar {
  padding: 18px clamp(18px, 3vw, 42px);
  background: rgba(17, 21, 30, 0.92);
  border-bottom-color: rgba(132, 147, 177, 0.18);
  box-shadow: 0 8px 28px rgba(0,0,0,0.18);
  backdrop-filter: blur(14px);
}
.brand { gap: 12px; font-size: 19px; letter-spacing: -0.01em; }
.brand-mark { width: 26px; height: 26px; }
.summary-bar { gap: 8px; padding: 12px 28px; }
.pill { min-width: 102px; padding: 8px 13px; border-radius: 10px; background: var(--panel); }
.pill-label { font-size: 10px; letter-spacing: 0.08em; }
.pill-value { font-size: 17px; line-height: 1.25; }

.page { max-width: 1480px; padding: 30px clamp(18px, 3vw, 42px) 72px; }
.toolbar { gap: 12px; padding-bottom: 24px; }
.toolbar-actions { order: 1; }
.toolbar-filters { order: 2; }
.card-tag-legend { order: 3; }
.toolbar-filters {
  gap: 8px;
  padding: 10px;
  background: var(--panel);
  border: 1px solid rgba(132,147,177,0.2);
  border-radius: 15px;
  box-shadow: 0 10px 26px rgba(0,0,0,0.16);
}
.toolbar-filters input[type="search"] { min-width: 260px; flex-basis: 300px; padding: 11px 14px; border-radius: 10px; background: var(--bg-elev-2); }
.toolbar select, .filter-control .filter-select-trigger { padding: 10px 12px; border-radius: 10px; background: var(--bg-elev-2); }
.filter-control { gap: 5px; }
.filter-control span { font-size: 11px; color: var(--text-dim); }
.view-toggle { border-radius: 10px; }
.view-btn { padding: 10px 15px; background: var(--bg-elev-2); }
.toolbar-actions { gap: 8px; }
.toolbar-actions { flex-wrap: nowrap; align-items: center; }
.btn-primary, .btn-secondary, .btn-danger { border-radius: 10px; }
.btn-primary { padding: 10px 17px; box-shadow: 0 6px 16px rgba(89,216,204,0.12); }
.btn-secondary { padding: 9px 14px; background: var(--panel); }
.btn-secondary:hover { background: rgba(89,216,204,0.08); }
.card-tag-legend { gap: 7px 12px; padding: 10px 12px; background: var(--panel); border: 1px solid rgba(132,147,177,0.14); border-radius: 12px; }
.legend-group { gap: 7px; }

.bulk-toolbar { border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.18); }

.card-grid { grid-template-columns: repeat(auto-fill, minmax(214px, 1fr)); gap: 20px; }
.card-tile { background: var(--bg-elev); border-color: rgba(132,147,177,0.24); border-radius: 14px; box-shadow: 0 8px 20px rgba(0,0,0,0.14); }
.card-tile:hover { transform: translateY(-5px); border-color: rgba(89,216,204,0.72); box-shadow: 0 18px 34px rgba(0,0,0,0.3); }
.card-thumb { background: var(--bg-elev-2); border-bottom: 1px solid rgba(132,147,177,0.14); }
.card-thumb img { padding: 8px; }
.card-flag { top: 10px; right: 10px; padding: 4px 8px; font-size: 9px; letter-spacing: 0.08em; box-shadow: 0 3px 10px rgba(0,0,0,0.22); }
.card-info { min-height: 92px; padding: 13px 14px 15px; }
.card-name { font-size: 15px; letter-spacing: -0.01em; }
.card-set { margin-bottom: 10px; color: var(--text-dim); }
.card-value { font-size: 17px; letter-spacing: -0.01em; }
.card-custom-tags { display: flex; flex-wrap: wrap; gap: 4px; margin: -3px 0 9px; }
.custom-tag {
  max-width: 92px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 3px 6px;
  border: 1px solid rgba(167,139,250,0.38);
  border-radius: 999px;
  background: rgba(167,139,250,0.1);
  color: #c9b9ff;
  font-size: 9px;
  font-weight: 700;
}
.card-tags { gap: 4px; }
.card-set-icon { width: 22px; height: 22px; }
.card-tag { min-width: 22px; height: 22px; border-radius: 6px; }

.table-wrap { border-radius: 14px; background: var(--panel); box-shadow: var(--shadow); }
thead th { padding: 13px 15px; background: var(--bg-elev-2); }
tbody td { padding: 12px 15px; }
tbody tr:hover { background: rgba(89,216,204,0.06); }

@media (max-width: 900px) {
  .toolbar-filters input[type="search"] { flex-basis: 100%; }
  .toolbar-actions { flex-wrap: wrap; }
}
/* Shared bottom-sheet chrome for .header-menu-panel and
   .filter-controls-panel on mobile (see the max-width:600px block below,
   which is the only place either actually becomes a sheet) — a drag
   handle plus an explicit close button, since a sheet with no visible
   affordance to dismiss it (beyond an undiscoverable tap-outside) reads
   as broken chrome on a phone. Hidden by default: on desktop
   .header-menu-panel is `display: contents` (an invisible wrapper), and
   without this rule the handle/close button would render as a stray
   inline element in the middle of the header row instead of vanishing
   with the rest of that wrapper's own styling. */
.mobile-sheet-head { display: none; }
.mobile-sheet-handle {
  width: 36px;
  height: 4px;
  border-radius: 999px;
  background: var(--border);
  margin: 0 auto;
}
.mobile-sheet-close {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
}
.mobile-sheet-close:hover { color: var(--text); }

@media (max-width: 600px) {
  /* ---- Task 1: app shell — a compact, app-like header, with the status
     bar/notch inset added as extra top padding rather than fixed height,
     since the inset itself varies by device. ---- */
  .topbar {
    align-items: center;
    justify-content: flex-start;
    padding: 14px 16px;
    padding-top: calc(14px + env(safe-area-inset-top, 0px));
    min-height: 60px;
    gap: 10px;
    /* .topbar's own backdrop-filter (base rule, unconditional) creates a
       containing block for any position:fixed descendant per spec — same
       as `filter`/`perspective` would. .header-menu-panel below relies on
       position:fixed being viewport-relative to work as a bottom sheet;
       left in place, it silently anchors to the header's own ~50px box
       instead, so the "sheet" renders squashed into the header itself.
       Dropping the blur here (mobile only) is a minor visual trade, not a
       functional one — the header still has a solid-enough background. */
    backdrop-filter: none;
    transition: padding .18s ease, min-height .18s ease;
  }
  /* Brand upgrade: on mobile this header IS the app's identity — the
     previous 20px mark / 16px wordmark read as a dashboard label, not a
     product. Sized and weighted to hold its own the way a collector app
     competing with polished market tools needs to, not just function as
     a nav bar. */
  .brand { gap: 11px; }
  .brand-name { font-size: 30px; font-weight: 850; letter-spacing: 0; }
  .brand-mark { width: 36px; height: 36px; border-radius: 8px; }
  .header-menu-btn { display: none; }

  /* Scroll-collapse: once you've scrolled past the header's own height,
     the full-size brand has already made its impression — shrink back
     down to reclaim vertical space for the content, same idea as a
     native app's large-title-to-compact-title transition. Toggled by a
     scroll listener in app.js (syncStickyOffsets); CSS-only (no
     IntersectionObserver) since the topbar is a plain sticky element
     already tracked for other reasons. */
  .topbar.scrolled { padding: 8px 16px; padding-top: calc(8px + env(safe-area-inset-top, 0px)); min-height: 44px; }
  .topbar.scrolled .brand-name { font-size: 17px; }
  .topbar.scrolled .brand-mark { width: 22px; height: 22px; }

  /* Installed PWA (display: standalone/fullscreen — no browser chrome of
     its own around it) — the header is the only titlebar-like surface
     the user ever sees, so it earns a touch more presence than it would
     sitting inside a browser tab that already has its own chrome above
     it. */
  @media (display-mode: standalone), (display-mode: fullscreen) {
    .topbar { padding-top: calc(18px + env(safe-area-inset-top, 0px)); }
  }

  .header-menu-panel { display: none; }
  .mobile-more-panel .mobile-sheet-head { display: block; position: relative; padding: 4px 0 10px; }
  /* The mobile More sheet owns secondary navigation plus account-level
     controls, so the header can stay brand-first instead of carrying a
     redundant hamburger. */
  .mobile-more-panel {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 8px;
    background: var(--bg-elev);
    border: 1px solid var(--border);
    border-bottom: none;
    border-radius: 16px 16px 0 0;
    padding: 6px 16px calc(16px + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -12px 32px rgba(0,0,0,.4);
    z-index: 30;
    max-height: 80vh;
    overflow-y: auto;
    transform: translateY(100%);
    transition: transform .22s ease;
    visibility: hidden;
  }
  .mobile-more-panel.open { transform: translateY(0); visibility: visible; }
  .mobile-more-panel .header-wrapped-btn {
    margin-left: 0;
    width: 100%;
    min-height: 44px;
    padding: 12px 14px;
    border-color: var(--border);
    border-radius: 12px;
    background: var(--bg-elev-2);
    color: var(--text);
    font-size: 14px;
    text-align: left;
  }
  .mobile-more-panel .header-wrapped-btn:hover { border-color: var(--accent); background: rgba(79,209,197,.1); }
  .mobile-more-grid .header-wrapped-btn { text-align: center; }
  #mnav-more-logout { color: var(--danger); }
  .mobile-more-account {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 2px 2px 6px;
  }
  .mobile-more-kicker {
    color: var(--text-dim);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
  }
  .mobile-more-greeting {
    color: var(--text);
    font-size: 18px;
    font-weight: 800;
  }
  .mobile-more-grid {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 8px;
  }
  .mobile-more-currency {
    width: 100%;
    min-height: 44px;
    margin: 2px 0 6px;
  }

  /* ---- Task 6: mobile-only bottom nav for the core sections, since 7
     top tabs don't fit (or scan well) as a scrolling row on a phone.
     Replaces the top workspace-nav on mobile entirely rather than
     running both at once. ---- */
  .workspace-nav { display: none; }
  .mobile-bottom-nav {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 25;
    background: var(--bg-elev);
    border-top: 1px solid var(--border);
    padding: 6px 4px calc(6px + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -6px 20px rgba(0,0,0,.25);
  }
  .mobile-bottom-nav-btn {
    flex: 1 1 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 2px;
    padding: 6px 2px;
    min-height: 48px;
    background: none;
    border: none;
    border-radius: 10px;
    color: var(--text-dim);
    font: inherit;
    font-size: 10px;
    cursor: pointer;
  }
  .mobile-bottom-nav-btn svg { width: 20px; height: 20px; }
  .mobile-bottom-nav-btn.active { color: var(--accent); background: rgba(79,209,197,.1); }
  /* The bottom nav sits fixed over the last inch of scrollable content —
     without this, the final row of cards (or the bottom of any page)
     ends up permanently hidden behind the bar. */
  body { padding-bottom: calc(60px + env(safe-area-inset-bottom, 0px)); }
  /* Sits *above* the bottom nav (not over/instead of it, unlike the old
     generic .bulk-toolbar) — you're still mid-browsing the incoming list,
     not in some separate admin mode that needs primary navigation out of
     the way. --mobile-bottom-nav-height is measured in
     syncStickyOffsets() (app.js) rather than hardcoded, for the same
     reason the sticky topbar/toolbar offsets are: real device chrome
     (safe-area insets) makes a fixed pixel guess unreliable. */
  .incoming-checklist-bar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: var(--mobile-bottom-nav-height, 60px);
    z-index: 24;
    margin: 0;
    border-radius: 14px 14px 0 0;
    border-left: none;
    border-right: none;
    border-bottom: none;
    box-shadow: 0 -8px 20px rgba(0,0,0,.28);
    background: var(--bg-elev);
  }
  /* Extra breathing room while the fixed checklist bar is up (see
     body.incoming-checklist-open in render()), so the last card row in
     the list isn't sitting behind it. */
  body.incoming-checklist-open { padding-bottom: calc(60px + 64px + env(safe-area-inset-bottom, 0px)); }
  /* ---- Task 5: filter details become the same kind of bottom sheet as
     the header menu above, instead of a dropdown pinned to the toggle
     button that could run off either edge of a narrow screen. ---- */
  .filter-controls-panel .mobile-sheet-head { display: block; position: relative; padding: 4px 0 10px; }
  .filter-controls-panel {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    width: 100%;
    grid-template-columns: 1fr 1fr;
    max-height: 80vh;
    overflow-y: auto;
    border-radius: 16px 16px 0 0;
    border-bottom: none;
    padding: 6px 16px calc(16px + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -12px 32px rgba(0,0,0,.4);
    z-index: 30;
  }

  /* The "⋯" More actions menu: a tiny dropdown pinned to its trigger
     button on desktop makes sense; on a narrow screen that same anchoring
     risks running off the top/side edge, and a small popover for a menu
     this long (five groups) just feels like a cramped shrunk-down desktop
     control. A real bottom sheet instead — same chrome as the filter
     panel above and the account "More" sheet. Toggling is still the
     existing .hidden class (see closeToolbarMenu/the click handler in
     app.js) — nothing about the show/hide mechanism changed, only how it
     looks at this width. */
  .toolbar-menu-panel .mobile-sheet-head { display: block; position: relative; padding: 4px 0 10px; }
  .toolbar-menu-title { display: block; padding: 0 4px 10px; font-size: 17px; font-weight: 800; color: var(--text); }
  .toolbar-menu-panel {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    width: 100%;
    min-width: 0;
    max-height: 80vh;
    overflow-y: auto;
    border-radius: 16px 16px 0 0;
    border-bottom: none;
    padding: 6px 16px calc(16px + env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -12px 32px rgba(0,0,0,.4);
    z-index: 30;
  }
  .toolbar-menu-item { min-height: 44px; font-size: 14px; }

  /* .value-tracker-heading is a nowrap flex row by default — its second
     child (.value-refresh-action) was staying on the same line as the
     heading text no matter how narrow the screen got, forcing the whole
     row wider than the viewport instead of dropping to its own row
     underneath. The refresh panel itself is position: absolute (out of
     flow) and already clamps to the viewport width, so it needs no
     mobile-specific sizing of its own. */
  .value-tracker-heading { flex-wrap: wrap; }
  .value-refresh-action { margin-top: 10px; }
  .refresh-filter-chip, .refresh-filter-price { min-width: 0; }
  .mover-before-after { display: none; }

  /* ---- Tasks 8 & 9: every modal (the scan/add-card chooser, the scan
     camera flow, and the full card detail/edit form) goes full-screen on
     mobile instead of staying a centered desktop-style dialog with
     margins on all sides — a floating card with 40px of backdrop around
     it just wastes space and feels like a shrunk desktop popup, not a
     native mobile screen. The close button becomes a sticky bar pinned
     to the top so it's always reachable without scrolling back up,
     which is the "sticky top bar" part of Task 9 — the save/submit
     button itself stays where each form already puts it (bottom of the
     form) rather than being pulled out into the bar, since several of
     these forms don't have a single obvious "save" action to promote
     (the card form autosaves per-field in places, others submit via a
     bottom button already sized for touch). */
  /* The backdrop's usual rgba(4,5,9,.7) is a dimming layer meant to be
     seen through to a smaller floating dialog — on mobile the modal
     fills the whole screen instead, so there's no page left behind it to
     dim, and that translucency instead let other fixed-position elements
     (the bottom nav, sitting at a lower z-index but still painted) show
     through wherever the modal's own content didn't happen to cover that
     exact spot. Fully opaque here removes the possibility outright. */
  .modal-backdrop { padding: 0; align-items: stretch; background: var(--bg-elev); }
  .modal { max-width: 100%; width: 100%; min-height: 100%; border-radius: 0; margin: 0; }
  /* Fixed (not the original absolute-within-.modal) so it stays put at
     the top-right of the screen regardless of how far the modal's own
     content scrolls — an absolutely-positioned close button scrolls
     away with the rest of the modal body the moment you scroll past it. */
  .modal-close {
    position: fixed;
    top: calc(10px + env(safe-area-inset-top, 0px));
    right: 10px;
    background: var(--bg-elev);
    border: 1px solid var(--border);
    border-radius: 8px;
    width: 34px;
    height: 34px;
    z-index: 110;
  }

  /* ---- Task 3: portfolio stats become a compact 2x2 grid. Horizontal
     scrolling hid the last pill on phone-width screens; wrapping keeps
     every stat visible without pretending the clipped edge is polish. ---- */
  .summary-bar {
    width: 100%;
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    overflow: visible;
    gap: 8px;
    padding: 10px 14px;
  }
  .pill {
    flex-direction: row;
    align-items: baseline;
    justify-content: space-between;
    gap: 6px;
    min-width: 0;
    padding: 8px 12px;
    white-space: nowrap;
  }
  .pill-label { font-size: 10px; }
  .pill-value { font-size: 14px; }

  .toolbar-filters, .toolbar-actions { width: 100%; }
  /* ---- Task 5: search stays its own full-width row; sort, the filter
     toggle and the grid/table switch share one compact row underneath. ---- */
  .toolbar-filters { flex-wrap: wrap; gap: 8px; }
  .toolbar-filters input[type="search"] { min-height: 44px; }
  .toolbar select, .filter-toggle, .view-toggle { min-height: 44px; }
  .toolbar select { flex: 1 1 160px; min-width: 150px; }
  .filter-toggle { flex: 0 0 auto; }
  .view-toggle { flex: 1 1 180px; }
  .view-toggle button { flex: 1 1 0; }
  .btn-review-queue { flex: 1 1 100%; }

  /* Brand/polish pass: the first screen should read brand -> value ->
     card grid, not brand -> controls -> banner -> (eventually) grid — a
     shorter toolbar and a slimmer verify-email nag both get the grid
     on-screen sooner without removing anything. */
  .page { padding-top: 12px; }
  .toolbar { gap: 10px; padding-bottom: 12px; }
  .verify-banner {
    margin: 10px 12px 0;
    max-width: calc(100% - 24px);
    padding: 4px 6px 4px 10px;
    font-size: 11.5px;
  }

  /* + Add Card leads with the camera on mobile, since scanning a card is
     the primary way anyone adds one from a phone — pasting a link or
     filling a form by hand are still one tap away inside the same
     chooser, just no longer what the button itself advertises first. */
  .btn-add-card-mobile-label { display: inline; }
  .btn-add-card-desktop-label { display: none; }
  /* "Needs Confirmation" -> "Review": same stat, shorter label — the
     full phrase was the widest thing in an already-cramped horizontal
     strip (see .summary-bar above). */
  .pill-label-desktop { display: none; }
  .pill-label-mobile { display: inline; }
  body[data-page="collection"] .dashboard-insights { display: none; }
  body[data-page="collection"] .collection-layout > #grid-view,
  body[data-page="collection"] .collection-layout > #table-view,
  body[data-page="collection"] .collection-layout > .empty-state {
    grid-column: 1;
    grid-row: 1;
  }

  /* ---- Task 4: "+ Add Card" is the dominant action on its own full-width
     row; "Select Cards" and the "⋯" overflow menu share a compact second
     row instead of all three wrapping unpredictably against each other. ---- */
  .toolbar-actions-default { display: flex; flex-wrap: wrap; gap: 8px; }
  #btn-add-card { flex: 1 1 100%; min-height: 48px; font-size: 15px; order: 1; }
  #btn-selection-mode { flex: 1 1 0; min-height: 44px; order: 2; }
  #toolbar-menu { flex: 0 0 auto; order: 3; }
  #toolbar-menu, .toolbar-menu-btn { min-height: 44px; }
  /* ---- Task 10: bulk select mode gets its own sticky bottom bar on
     mobile, replacing the regular bottom nav for as long as selection
     mode is active (rather than sitting inline in the toolbar, where it
     scrolls out of view along with everything else — the whole point of
     a bulk action bar is that it stays reachable no matter how far
     you've scrolled through the grid to select things). The selected
     count and cancel (×) stay in their own row up top so they're never
     pushed off by however many action buttons wrap onto extra lines. */
  body.selection-active .mobile-bottom-nav { display: none; }
  body.selection-active .bulk-toolbar {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 26;
    flex-direction: column;
    align-items: stretch;
    border-radius: 16px 16px 0 0;
    border-left: none;
    border-right: none;
    border-bottom: none;
    padding: 10px 12px calc(10px + env(safe-area-inset-bottom, 0px));
    margin: 0;
    /* The default translucent gradient (partly ~12% opacity) is meant for
       an inline row sitting over plain page background — pinned fixed at
       the bottom over the dashboard sidebar's own cards, that
       translucency let their text visibly bleed through underneath this
       bar's own "N selected"/action labels. Opaque here removes it. */
    background: var(--bg-elev);
  }
  body.selection-active .bulk-toolbar-left { order: -1; justify-content: space-between; width: 100%; }
  .bulk-toolbar { width: 100%; }
  .bulk-toolbar-actions { width: 100%; }
  .bulk-toolbar-divider { display: none; }
  .bulk-select-all { min-height: 44px; flex: 1 1 100%; }
  .bulk-toolbar-actions button { min-height: 44px; flex: 1 1 calc(50% - 4px); }

  /* ---- Task 7: card grid polish carried over from the previous pass —
     a stable 2-column layout rather than however many columns happen to
     fit, so thumbnails stay a consistent, predictable size. ---- */
  .card-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; }
  .card-info { padding: 10px; }
  .card-name { font-size: 13px; }
  .card-set { font-size: 10px; }
  .card-value { font-size: 15px; }
}
.modal-close {
  position: absolute;
  top: 12px;
  right: 14px;
  background: none;
  border: none;
  color: var(--text-dim);
  font-size: 24px;
  cursor: pointer;
  line-height: 1;
  z-index: 2;
}
.modal-close:hover { color: var(--text); }
.modal-body { padding: 28px; }

/* Form fields (identity, value, grade) come first in the grid — both in
   the DOM and visually — with the photo gallery as a second column, not
   a big "REFERENCE PHOTO" drop zone leading the whole editor (new-user
   audit screenshot 23: photos were the very first thing a new user saw,
   before they'd even named the card). */
.modal-grid { display: grid; grid-template-columns: minmax(0, 1fr) 200px; gap: 28px; }
/* The mobile override for this lives in the later @media(max-width:700px)
   block further down, not here — a later unconditional ".modal-grid"
   rule (same specificity) always wins over a media-scoped rule this
   early in the cascade regardless of which media condition matches, so
   a rule placed here would be dead code. */

.photo-col { display: flex; flex-direction: column; gap: 14px; }
.photo-box { text-align: center; }
.photo-box-label { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.03em; margin-bottom: 6px; }
.photo-frame {
  width: 100%;
  aspect-ratio: 3 / 4.15;
  border-radius: 8px;
  border: 1px dashed var(--border);
  background: var(--bg-elev-2);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  position: relative;
}
.photo-frame img { width: 100%; height: 100%; object-fit: cover; }
.photo-frame.reference img { cursor: zoom-in; }
.photo-frame.reference { border-style: solid; }
.photo-upload-input { display: none; }
.photo-upload-btn {
  margin-top: 6px;
  font-size: 11.5px;
  padding: 6px 10px;
}
.photo-remove-btn {
  font-size: 11px;
  color: var(--danger);
  background: none;
  border: none;
  cursor: pointer;
  margin-top: 4px;
}

/* minmax(0, 1fr), not plain 1fr — same fix already applied to
   .modal-grid (see its own comment further down): a field with a long
   placeholder (e.g. "e.g. eBay, Card Show Las Vegas 2026") has an
   intrinsic min-content width, and a plain "1fr" track's own auto
   minimum defers to that, so at some in-between window widths the
   track — and the input inside it — gets forced wider than the space
   actually available, overflowing past the column's real boundary. */
.form-grid { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 14px; }
.form-grid.full { grid-template-columns: minmax(0, 1fr); }
.field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.field label { font-size: 11px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.03em; }
.field input, .field select, .field textarea {
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 8px 10px;
  border-radius: 6px;
  font-size: 13.5px;
  font-family: inherit;
}
.field textarea { resize: vertical; min-height: 60px; }
/* A price field's spin buttons eat into the input's own width — with a
   placeholder like "Unknown"/"0.00" that's real overlap, not just visual
   clutter nobody uses (reported: placeholder text got cut off behind the
   up/down arrows on Price Bought For). */
.field input[type="number"] { -moz-appearance: textfield; }
.field input[type="number"]::-webkit-outer-spin-button,
.field input[type="number"]::-webkit-inner-spin-button {
  -webkit-appearance: none;
  margin: 0;
}
.field.span-2 { grid-column: span 2; }

.tag-input {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  padding: 6px 8px;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 6px;
  min-width: 0;
}
.tag-input:focus-within { border-color: var(--accent); box-shadow: 0 0 0 3px rgba(89,216,204,0.1); }
.tag-chip-list { display: flex; flex-wrap: wrap; gap: 6px; }
.tag-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 3px 4px 3px 9px;
  border: 1px solid rgba(167,139,250,0.38);
  border-radius: 999px;
  background: rgba(167,139,250,0.12);
  color: #c9b9ff;
  font-size: 12px;
  font-weight: 600;
  white-space: nowrap;
}
.tag-chip-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  border: none;
  border-radius: 999px;
  background: transparent;
  color: #c9b9ff;
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
  opacity: 0.75;
}
.tag-chip-remove:hover { opacity: 1; background: rgba(167,139,250,0.28); }
#tag-input-field, #bulk-add-tag-input-field {
  flex: 1 1 120px;
  min-width: 120px;
  border: none;
  background: transparent;
  padding: 4px 2px;
  font-size: 13.5px;
  color: var(--text);
  font-family: inherit;
}
#tag-input-field:focus, #bulk-add-tag-input-field:focus { outline: none; }
/* One scrollable line rather than wrapping — a handful of tags/locations
   wrapped onto an awkward extra line even when nearly everything fit on
   one, and this list is genuinely unbounded (as many distinct tags or
   locations as the user has actually used), so wrapping doesn't even
   have a natural cap the way a fixed set of filter chips would. */
/* A single scrollable row (nowrap + overflow-x) rather than wrapping to
   multiple lines on purpose — a long tag/location list would otherwise
   push the rest of the form down unpredictably. But with no scroll
   affordance, a chip getting cut off at the edge read as broken/clipped
   rather than "scroll for more" — the fade mask below is that affordance. */
.tag-suggestions { display: flex; flex-wrap: nowrap; overflow-x: auto; align-items: center; gap: 6px; margin-top: 8px; padding-bottom: 2px; scrollbar-width: none; -ms-overflow-style: none; mask-image: linear-gradient(90deg, black calc(100% - 28px), transparent 100%); -webkit-mask-image: linear-gradient(90deg, black calc(100% - 28px), transparent 100%); }
.tag-suggestions::-webkit-scrollbar { display: none; }
.tag-suggestions-label { flex: 0 0 auto; }
.tag-suggestions .tag-suggestion-chip { flex: 0 0 auto; }
.tag-suggestions-label { color: var(--text-dim); font-size: 11px; text-transform: uppercase; letter-spacing: .05em; margin-right: 2px; }
.tag-suggestion-chip {
  padding: 4px 10px;
  border: 1px solid var(--border);
  border-radius: 999px;
  background: var(--bg-elev-2);
  color: var(--text-dim);
  font-size: 12px;
  cursor: pointer;
}
.tag-suggestion-chip:hover { color: var(--accent); border-color: var(--accent); }
.tag-suggestion-chip[disabled] { opacity: .4; cursor: default; }
.tag-suggestion-chip.active { color: var(--accent); border-color: var(--accent); background: rgba(89,216,204,0.1); }
.location-suggestion-count { color: var(--text-dim); font-size: 10.5px; margin-left: 3px; }
@media (max-width: 700px) { .field.span-2 { grid-column: span 1; } }

.card-detail-set-logo {
  grid-column: span 2;
  min-height: 62px;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 14px;
  padding: 5px 0 8px;
}
.card-detail-set-logo-art {
  width: 42px;
  min-height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.card-detail-set-logo-art img {
  max-width: 34px;
  max-height: 34px;
  object-fit: contain;
}
.card-detail-set-logo-fallback { color: var(--accent); font-size: 30px; }
.card-detail-set-name { font-size: 16px; font-weight: 700; color: var(--text); }
@media (max-width: 700px) { .card-detail-set-logo { grid-column: span 1; } }

.psa-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; margin-top: 4px; }
.psa-grid .field { min-width: 0; }
.psa-grid input { min-width: 0; width: 100%; box-sizing: border-box; }
@media (max-width: 500px) { .psa-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.modal-title { font-size: 20px; font-weight: 700; margin: 0 0 4px; }
.modal-subtitle { font-size: 12.5px; color: var(--text-dim); margin: 0 0 18px; }

.section-title { font-size: 13px; font-weight: 700; margin: 20px 0 8px; color: var(--text-dim); text-transform: uppercase; letter-spacing: 0.04em; }

/* Quick Add vs. everything else — a first-time "Enter manually" card only
   needs a name/set/value to be useful; rarity, edition, condition, tags,
   purchase info, product link and notes are real but can wait. Collapsed
   by default for a new card (`open` omitted), always expanded when editing
   an existing card so nothing already filled in looks like it vanished. */
.card-more-details { margin: 22px 0 0; border: 1px solid var(--border); border-radius: 12px; background: var(--bg-elev); }
.card-more-details summary {
  cursor: pointer;
  list-style: none;
  padding: 12px 14px;
  font-size: 13px;
  font-weight: 700;
  color: var(--text);
  display: flex;
  align-items: baseline;
  gap: 8px;
}
.card-more-details summary::-webkit-details-marker { display: none; }
.card-more-details summary::before { content: '›'; display: inline-block; font-size: 16px; transition: transform .15s ease; color: var(--text-dim); }
.card-more-details[open] summary::before { transform: rotate(90deg); }
.card-more-details summary:hover { color: var(--accent); }
.card-more-details-hint { font-size: 11.5px; font-weight: 400; color: var(--text-dim); }
.card-more-details-body { padding: 2px 14px 14px; }
.card-more-details-body .section-title:first-child { margin-top: 4px; }

.app-legal { margin: 30px 0 90px; text-align: center; font-size: 12px; color: var(--text-dim); }
.app-legal a { color: var(--text-dim); }
.app-legal a:hover { color: var(--accent); }
.modal-actions { display: flex; justify-content: space-between; margin-top: 24px; gap: 10px; flex-wrap: wrap; }
.modal-actions-right { display: flex; gap: 10px; flex-wrap: wrap; }

.flag-toggle { display: flex; align-items: center; gap: 8px; font-size: 13px; }

.field input[disabled] {
  color: var(--text-dim);
  cursor: not-allowed;
  opacity: 0.85;
}

.value-edit-row { display: flex; gap: 6px; }
.value-edit-row input { flex: 1; }
.currency-mini-select { flex: 0 0 auto; width: 88px; padding: 8px 6px; border: 1px solid var(--border); border-radius: 9px; background: var(--bg-elev-2); color: var(--text); font-size: 13px; }
.btn-edit-value {
  padding: 0 12px;
  font-size: 14px;
  flex-shrink: 0;
}

.pricecharting-link {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-size: 12.5px;
  color: var(--accent);
  text-decoration: none;
  padding: 8px 0;
}
.pricecharting-link:hover { text-decoration: underline; }
.pricecharting-link-icon { flex: 0 0 auto; width: 13px; height: 13px; }
.pricecharting-url-input { margin-top: 8px; width: 100%; }

.ai-check-row {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 12px;
  flex-wrap: wrap;
}
.ai-panel {
  margin-top: 14px;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px;
}
.ai-panel-error { color: var(--danger); }

.toast {
  position: fixed;
  bottom: 24px;
  left: 50%;
  transform: translateX(-50%);
  background: var(--bg-elev-2);
  border: 1px solid var(--accent);
  color: var(--text);
  padding: 10px 18px;
  border-radius: 8px;
  font-size: 13px;
  z-index: 200;
  box-shadow: var(--shadow);
  display: inline-flex;
  align-items: center;
  gap: 14px;
}
.toast-action {
  border: 0;
  border-radius: 7px;
  padding: 5px 9px;
  background: var(--accent);
  color: #06201e;
  font: inherit;
  font-size: 12px;
  font-weight: 800;
  cursor: pointer;
}
.toast-action:disabled { opacity: 0.6; cursor: wait; }

.currency-control {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  color: var(--text-dim);
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  margin-left: auto;
  flex: 0 0 auto;
}
.currency-control select {
  min-width: 92px;
  padding: 8px 9px;
  border: 1px solid var(--border);
  border-radius: 9px;
  background: var(--bg-elev-2);
  color: var(--text);
  font: inherit;
  font-size: 12px;
  letter-spacing: 0;
  text-transform: none;
}

/* Card editor refresh: gallery-style photos and clearer form rhythm. */
.modal {
  max-width: 1080px;
  border-radius: 18px;
  background: var(--bg-elev);
  border-color: rgba(132,147,177,0.26);
  box-shadow: 0 28px 70px rgba(0,0,0,0.48);
}
.modal-body { padding: 30px 34px 32px; }
.modal-title { font-size: 22px; letter-spacing: -0.02em; }
.modal-subtitle { margin-bottom: 26px; }
.modal-grid { grid-template-columns: minmax(0, 1fr) 220px; gap: 38px; }
.photo-col {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-content: start;
  gap: 18px 10px;
}
.photo-box:first-child { grid-column: 1 / -1; }
.photo-box-label { margin-bottom: 8px; font-size: 10px; letter-spacing: 0.08em; }
.photo-frame { border-radius: 11px; background: var(--bg-elev-2); border-color: rgba(132,147,177,0.24); }
.photo-frame.reference { box-shadow: 0 10px 24px rgba(0,0,0,0.2); }
.photo-frame img { object-fit: contain; padding: 5px; }
.photo-upload-btn { margin-top: 8px; border-radius: 8px; }
.photo-remove-btn { margin-top: 7px; }
.photo-link-section {
  grid-column: 1 / -1;
  margin-top: 2px;
  padding-top: 16px;
  border-top: 1px solid rgba(132,147,177,0.2);
}
.photo-link-field { min-width: 0; }
.photo-link-field .value-edit-row { align-items: center; }
.photo-link-field .pricecharting-link { padding: 4px 0 2px; font-size: 12px; }
.photo-link-field .pricecharting-url-input { margin-top: 6px; }
.photo-link-section .ai-check-row { margin-top: 12px; }
.photo-link-section .ai-panel { margin-top: 12px; }
.card-value-history-section { margin-top: 18px; padding-top: 16px; border-top: 1px solid rgba(132,147,177,0.2); }
.section-title-row { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; margin: 20px 0 8px; }
.section-title-row .section-title { margin: 0; }
.card-value-chart { margin-top: 10px; color: var(--accent); }
.card-value-chart .insight-empty { display: block; font-size: 12px; line-height: 1.5; }
.card-chart-plot { position: relative; height: 110px; }
.card-chart-dot { width: 8px; height: 8px; margin: -4px 0 0 -4px; border-width: 1.5px; }
.profit-summary {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto auto;
  align-items: center;
  gap: 10px;
  margin-top: 12px;
  padding: 10px 12px;
  border: 1px solid rgba(89,216,204,0.24);
  border-radius: 10px;
  background: rgba(89,216,204,0.06);
  color: var(--text-dim);
  font-size: 11px;
}
.profit-summary-label { font-weight: 700; color: var(--text); }
.profit-summary strong { font-size: 14px; }
.profit-summary.up strong { color: var(--good); }
.profit-summary.down { border-color: rgba(248,113,113,0.3); background: rgba(248,113,113,0.06); }
.profit-summary.down strong { color: var(--danger); }
.profit-summary.unavailable { display: block; color: var(--text-dim); }
.profit-summary.unavailable .profit-summary-label { margin-right: 8px; }
/* Compact up/down badge next to a card's value in Grid and Table view —
   same purchase-vs-current comparison as .profit-summary, just small
   enough to sit inline instead of taking its own section. */
.profit-chip { display: inline-flex; align-items: center; gap: 2px; font-size: 10px; font-weight: 700; padding: 1px 5px; border-radius: 999px; white-space: nowrap; vertical-align: middle; }
.profit-chip.up { color: var(--good); background: rgba(74, 222, 128, 0.12); }
.profit-chip.down { color: var(--danger); background: rgba(248, 113, 113, 0.12); }

.fetch-loading {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 13px;
  border: 1px solid rgba(89,216,204,0.24);
  border-radius: 10px;
  background: rgba(89,216,204,0.06);
  color: var(--text-dim);
  font-size: 12px;
}
.fetch-spinner {
  width: 15px;
  height: 15px;
  flex: 0 0 15px;
  border: 2px solid rgba(89,216,204,0.22);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: fetch-spin 0.75s linear infinite;
}
.loading-dots { display: inline-block; width: 18px; overflow: hidden; animation: loading-dots 1.15s steps(4, end) infinite; }
@keyframes fetch-spin { to { transform: rotate(360deg); } }
@keyframes loading-dots { 0%, 20% { width: 0; } 50% { width: 6px; } 75% { width: 12px; } 100% { width: 18px; } }

/* Add-card chooser: three clearly-delineated option blocks (scan / paste
   a link / enter manually) instead of a lone scan button + link field +
   tiny skip/add pair with dead space beneath — see new-user audit
   screenshot 21. Single column on mobile, three across on wider screens
   where a single narrow column would look sparse. */
.add-option-grid { display: grid; grid-template-columns: 1fr; gap: 10px; }
.add-option {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 14px 16px;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 12px;
}
.add-option-head { display: flex; align-items: center; gap: 8px; }
.add-option-icon { display: inline-flex; flex: 0 0 auto; color: var(--accent); }
.add-option-icon svg { width: 18px; height: 18px; display: block; }
.add-option-title { font-weight: 700; font-size: 14px; }
.add-option-desc { margin: 0; color: var(--text-dim); font-size: 12px; line-height: 1.4; }
.add-option-form { display: flex; flex-direction: column; gap: 8px; }
.add-option-form input { width: 100%; box-sizing: border-box; min-height: 42px; padding: 10px 12px; border: 1px solid rgba(132,147,177,0.24); border-radius: 9px; background: var(--bg-elev-2); color: var(--text); font: inherit; font-size: 14px; }
.add-option-form input:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(89,216,204,0.1); }
.add-option-btn { width: 100%; box-sizing: border-box; text-align: center; padding: 10px; font-size: 13.5px; font-weight: 700; margin-top: auto; }
@media (min-width: 640px) {
  .add-option-grid { grid-template-columns: repeat(3, 1fr); align-items: stretch; }
}
/* ---------- Scan flow: 1. Capture 2. Confirm match 3. Add ---------- */
.scan-capture-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  width: 100%;
  box-sizing: border-box;
  padding: 36px 14px;
  border: 1.5px solid var(--border);
  border-radius: 14px;
  background: var(--bg-elev-2);
  cursor: pointer;
  transition: border-color .15s ease, background .15s ease;
}
.scan-capture-tile:hover, .scan-capture-tile:focus-visible { border-color: var(--accent); background: var(--bg-elev); }
.scan-capture-icon { width: 34px; height: 34px; color: var(--accent); }
.scan-capture-label { font-size: 16px; font-weight: 800; color: var(--text); }
.scan-capture-hint { margin: 10px 0 0; text-align: center; font-size: 12.5px; color: var(--text-dim); }
.scan-capture-hint:first-of-type { margin-top: 14px; }
.scan-capture-hint-dim { opacity: .75; }
.scan-capture-secondary { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; row-gap: 4px; column-gap: 8px; margin-top: 18px; }
.scan-camera-wrap {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 4;
  border-radius: 14px;
  overflow: hidden;
  background: #000;
}
.scan-camera-wrap video { display: block; width: 100%; height: 100%; object-fit: cover; }
/* Card ratio (63mm x 88mm) at 74% of the frame width, dead center — the
   dark mask outside it (a huge box-shadow spread, cheaper than a second
   overlay element) is what actually reads as "line the card up here." */
.scan-camera-guide {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 74%;
  aspect-ratio: 63 / 88;
  border: 2px solid rgba(255, 255, 255, .9);
  border-radius: 10px;
  box-shadow: 0 0 0 999px rgba(0, 0, 0, .5);
  pointer-events: none;
  transition: opacity .2s ease;
}
/* Once live edge tracking (see startEdgeDetection in app.js) is actually
   drawing a quadrilateral on scan-camera-overlay, the static frame just
   competes with it visually — dimmed, not removed, so there's still a
   reference box during the moment OpenCV.js is loading/initializing or on
   a frame where nothing was confidently detected. */
.scan-camera-wrap.has-live-guide .scan-camera-guide { opacity: .25; box-shadow: none; }
.scan-camera-overlay { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; }
.scan-camera-shutter {
  display: block;
  width: 62px;
  height: 62px;
  margin: 18px auto 0;
  padding: 0;
  border-radius: 50%;
  border: 3px solid var(--text);
  background: transparent;
  cursor: pointer;
}
.scan-camera-shutter-ring { display: block; width: 100%; height: 100%; border-radius: 50%; background: var(--text); transform: scale(.78); transition: transform .1s ease; }
.scan-camera-shutter:hover .scan-camera-shutter-ring, .scan-camera-shutter:focus-visible .scan-camera-shutter-ring { transform: scale(.85); }
.scan-camera-shutter:active .scan-camera-shutter-ring { transform: scale(.66); }
.scan-secondary-link {
  background: none;
  border: none;
  padding: 4px 2px;
  font: inherit;
  font-size: 12.5px;
  font-weight: 700;
  color: var(--text-dim);
  cursor: pointer;
}
.scan-secondary-link:hover, .scan-secondary-link:focus-visible { color: var(--accent); }
.scan-secondary-sep { color: var(--border); font-size: 12px; }
/* "Paste a link" is a real alternate path (not just a fallback like
   "Enter manually"), so it gets its own accent color + icon instead of
   blending into the same muted row. */
.scan-secondary-link-emphasis { display: inline-flex; align-items: center; gap: 4px; color: var(--accent); }
.scan-secondary-link-emphasis:hover, .scan-secondary-link-emphasis:focus-visible { color: var(--accent); text-decoration: underline; }
.scan-secondary-link-icon { flex: 0 0 auto; width: 13px; height: 13px; }

.scan-card-preview { display: block; width: 100%; max-height: 220px; object-fit: contain; border-radius: 10px; background: var(--bg-elev-2); margin-bottom: 12px; }

/* "Reading card" scanning animation: a beam sweeps down over the user's
   own captured photo (instead of a small generic spinner sitting below
   it, disconnected from what's actually happening) so the motion reads
   as "analyzing this exact photo," with a soft pulsing glow around the
   frame reinforcing it. Respects reduced-motion (see below) by keeping
   the glow but dropping the sweep and continuous pulse. */
.scan-preview-wrap { position: relative; margin-bottom: 12px; border-radius: 10px; overflow: hidden; }
.scan-preview-wrap .scan-card-preview { margin-bottom: 0; }
.scan-sweep {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  box-shadow: inset 0 0 0 1px rgba(89,216,204,0.3);
  animation: scan-sweep-glow 2s ease-in-out infinite;
}
.scan-sweep::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  height: 45%;
  top: -45%;
  background: linear-gradient(180deg, transparent, rgba(89,216,204,0.16) 35%, rgba(89,216,204,0.55) 50%, rgba(89,216,204,0.16) 65%, transparent);
  animation: scan-sweep-move 1.6s cubic-bezier(.45,0,.55,1) infinite alternate;
}
.scan-sweep::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  height: 2px;
  top: -2px;
  background: var(--accent);
  box-shadow: 0 0 14px 2px rgba(89,216,204,0.8), 0 0 40px 6px rgba(89,216,204,0.25);
  animation: scan-sweep-line 1.6s cubic-bezier(.45,0,.55,1) infinite alternate;
}
@keyframes scan-sweep-move { 0% { top: -45%; } 100% { top: 100%; } }

/* Faint drifting scanline texture over the whole photo — reads as "the
   scanner is working on this image", not just a line passing over it. */
.scan-noise {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  pointer-events: none;
  background: repeating-linear-gradient(180deg, rgba(89,216,204,0.06) 0 1px, transparent 1px 5px);
  mix-blend-mode: screen;
  opacity: .5;
  animation: scan-noise-drift 1s linear infinite;
}
@keyframes scan-noise-drift { to { transform: translateY(5px); } }

/* Viewfinder corner brackets, breathing softly — the classic "locked on"
   scanner framing. */
.scan-corner {
  position: absolute;
  width: 24px;
  height: 24px;
  border: 2.5px solid var(--accent);
  pointer-events: none;
  animation: scan-corner-pulse 1.6s ease-in-out infinite;
}
.scan-corner-tl { top: 7px; left: 7px; border-right: 0; border-bottom: 0; border-top-left-radius: 8px; }
.scan-corner-tr { top: 7px; right: 7px; border-left: 0; border-bottom: 0; border-top-right-radius: 8px; }
.scan-corner-br { bottom: 7px; right: 7px; border-left: 0; border-top: 0; border-bottom-right-radius: 8px; }
.scan-corner-bl { bottom: 7px; left: 7px; border-right: 0; border-top: 0; border-bottom-left-radius: 8px; }
@keyframes scan-corner-pulse { 0%, 100% { opacity: .45; } 50% { opacity: 1; } }

/* Stage progress: advanced by app.js at each real pipeline phase. */
.scan-progress-track {
  height: 4px;
  border-radius: 2px;
  background: var(--bg-elev-2);
  overflow: hidden;
  margin: 0 0 10px;
}
.scan-progress-fill {
  height: 100%;
  width: 6%;
  border-radius: 2px;
  background: linear-gradient(90deg, var(--accent), #8ceadd);
  box-shadow: 0 0 10px rgba(89,216,204,0.55);
  transition: width .7s ease;
}
.scan-stage-label { justify-content: center; font-size: 13px; }
@keyframes scan-sweep-line { 0% { top: -1px; } 100% { top: 100%; } }
@keyframes scan-sweep-glow {
  0%, 100% { box-shadow: inset 0 0 0 1px rgba(89,216,204,0.22), inset 0 0 16px rgba(89,216,204,0.06); }
  50% { box-shadow: inset 0 0 0 1px rgba(89,216,204,0.5), inset 0 0 26px rgba(89,216,204,0.16); }
}
@media (prefers-reduced-motion: reduce) {
  .scan-sweep, .scan-sweep::before, .scan-sweep::after { animation: none; }
  .scan-sweep { box-shadow: inset 0 0 0 1px rgba(89,216,204,0.35), inset 0 0 20px rgba(89,216,204,0.1); }
}

/* The user's own photo, shown small at the top of the confirm-match step
   so it's clear what's being matched, without it competing for space
   with the actual result below. */
.scan-confirm-photo { display: block; width: 64px; height: 64px; object-fit: cover; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-elev-2); margin-bottom: 12px; }

/* The top candidate reads as THE result, not just the first row in a
   list — a bigger thumbnail, its own card, and the one obvious primary
   action, with everything else demoted to "other matches" below. */
.scan-hero {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 14px;
  border: 1.5px solid var(--accent);
  border-radius: 14px;
  background: var(--bg-elev-2);
  margin-bottom: 14px;
}
.scan-hero-tap {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  padding: 0;
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
  color: inherit;
  font: inherit;
}
.scan-hero-thumb {
  position: relative;
  flex: 0 0 auto;
  width: 68px;
  height: 94px;
  border-radius: 8px;
  overflow: hidden;
  background: var(--bg-elev);
}
/* A candidate image is real (fetched from the same TCGdex URL the server
   just hashed to rank it), not broken — it just hasn't finished loading
   yet the instant the results screen appears, which read as a blank/dead
   box rather than an obviously-loading one. Shared with .card-skeleton's
   own shimmer so it reads the same way loading already does elsewhere in
   the app. Removed (see armScanThumbFallback in app.js) the moment the
   image actually finishes, successfully or not — it never lingers behind
   a loaded picture. */
.scan-hero-thumb.scan-thumb-loading, .scan-candidate-thumb.scan-thumb-loading {
  background: linear-gradient(90deg, var(--bg-elev) 25%, var(--border) 50%, var(--bg-elev) 75%);
  background-size: 200% 100%;
  animation: skeleton-pulse 1.4s ease-in-out infinite;
}
@media (prefers-reduced-motion: reduce) {
  .scan-hero-thumb.scan-thumb-loading, .scan-candidate-thumb.scan-thumb-loading { animation: none; }
}
.scan-hero-thumb img { display: block; width: 100%; height: 100%; object-fit: contain; }
.scan-hero-thumb-fallback { align-items: center; justify-content: center; width: 100%; height: 100%; color: var(--text-dim); }
.scan-hero-thumb-fallback .placeholder-icon { width: 55%; height: auto; }
.scan-hero-info { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.scan-hero-label { font-size: 11px; font-weight: 800; letter-spacing: .04em; text-transform: uppercase; color: var(--accent); }
.scan-hero-name { font-size: 17px; font-weight: 800; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.scan-hero-set { font-size: 13px; color: var(--text-dim); }
.scan-hero-note { margin-top: 2px; font-size: 11.5px; font-weight: 700; color: var(--warn); }
.scan-hero-use { width: 100%; box-sizing: border-box; }
.scan-hero-tcgdex {
  display: block;
  margin: -6px 0 14px;
  font-size: 12.5px;
  color: var(--accent);
  text-decoration: none;
  text-align: center;
}
.scan-hero-tcgdex:hover { text-decoration: underline; }
.scan-hero-edition-note {
  margin: -4px 0 14px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--text-dim);
  text-align: center;
}
.scan-hero-edition-note.is-detected { color: var(--accent); font-weight: 700; }
.tcgdex-entry-link { display: block; margin-top: 4px; padding: 4px 0; }

/* OCR text and the detected-crop preview are reasoning, not results —
   collapsed by default behind a native <details> disclosure instead of
   sitting in the main flow as visible debug output. */
.scan-why-match { margin: 0 0 14px; font-size: 12.5px; }
.scan-why-match summary {
  cursor: pointer;
  color: var(--text-dim);
  font-weight: 700;
  list-style: none;
}
.scan-why-match summary::-webkit-details-marker { display: none; }
.scan-why-match summary::before { content: '›'; display: inline-block; margin-right: 5px; transition: transform .15s ease; }
.scan-why-match[open] summary::before { transform: rotate(90deg); }
.scan-why-match summary:hover { color: var(--accent); }
.scan-why-match-body { padding-top: 10px; }

.scan-other-matches-label { margin: 0 0 8px; font-size: 12px; font-weight: 700; color: var(--text-dim); text-transform: uppercase; letter-spacing: .03em; }
.scan-candidate-list { display: flex; flex-direction: column; gap: 8px; margin: 0 0 12px; max-height: 40vh; overflow-y: auto; }
.scan-candidate {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 10px;
  cursor: pointer;
  text-align: left;
  width: 100%;
  color: var(--text);
  font: inherit;
}
.scan-candidate:hover, .scan-candidate:focus-visible { border-color: var(--accent); background: var(--bg-elev); }
.scan-candidate-thumb { width: 44px; height: 61px; object-fit: contain; border-radius: 5px; background: var(--bg-elev); flex: 0 0 auto; }
/* A candidate with no image, or whose image URL fails to load, showed as
   a plain dark box with nothing in it (new-user audit screenshot 41) —
   a clean generic card icon reads as "no picture available" instead of
   looking broken. */
.scan-candidate-thumb-fallback { display: flex; align-items: center; justify-content: center; border: 1px dashed var(--border); color: var(--text-dim); }
.scan-candidate-thumb-fallback .placeholder-icon { width: 55%; height: auto; }
.scan-candidate-info { flex: 1 1 auto; min-width: 0; }
.scan-candidate-name { font-weight: 700; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.scan-candidate-set { font-size: 12px; color: var(--text-dim); }
.scan-candidate-ambiguous { font-size: 10px; color: var(--warn); margin-top: 2px; }
.scan-ocr-debug { line-height: 1.5; opacity: .8; }
.scan-crop-preview-wrap { display: flex; align-items: center; gap: 10px; margin-bottom: 10px; }
.scan-crop-preview { width: 56px; height: 56px; object-fit: cover; border-radius: 8px; border: 1px solid var(--border); background: var(--bg-elev-2); }
.scan-no-match-title { font-size: 15px; font-weight: 800; margin: 0 0 4px; }
.scan-candidate-confidence {
  flex: 0 0 auto;
  font-size: 10.5px;
  font-weight: 800;
  padding: 4px 9px;
  border-radius: 999px;
  white-space: nowrap;
}
.scan-candidate-confidence.high { background: rgba(74,222,128,.16); color: var(--good); }
.scan-candidate-confidence.medium { background: rgba(251,191,36,.16); color: var(--warn); }
.scan-candidate-confidence.low { background: rgba(139,145,163,.16); color: var(--text-dim); }
@media (max-width: 600px) {
  .scan-candidate { min-height: 44px; }
  .scan-candidate-thumb { width: 52px; height: 72px; }
  .scan-hero-thumb { width: 60px; height: 84px; }
  .scan-hero-name { font-size: 16px; }
}
@media (max-width: 600px) {
  /* Task 5 (scan flow): Retake/Add Card stack full-width and large on a
     phone instead of the compact right-aligned pair that works fine on
     desktop's much wider modal. */
  .scan-card-confirm-actions { flex-direction: column-reverse; gap: 8px; }
  .scan-card-confirm-actions button { width: 100%; min-height: 48px; font-size: 15px; }
  .scan-capture-tile { padding: 44px 14px; }
  .scan-card-preview { max-height: 320px; }
}
#scan-card-reading .fetch-loading { justify-content: center; }
.modal-title-row { display: flex; align-items: center; gap: 10px; padding-right: 36px; flex-wrap: wrap; }
.modal-title-row .modal-title { flex: 1; }
.btn-mark-sold { padding: 6px 12px; font-size: 12.5px; white-space: nowrap; border-color: rgba(242,198,109,0.4); color: var(--accent-2); }
.btn-mark-sold:hover { border-color: rgba(242,198,109,0.7); background: rgba(242,198,109,0.1); }
.favorite-toggle {
  width: 34px;
  height: 34px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: 10px;
  background: var(--bg-elev-2);
  color: var(--text-dim);
  font-size: 21px;
  line-height: 1;
  cursor: pointer;
  transition: color 0.15s ease, border-color 0.15s ease, background 0.15s ease, transform 0.15s ease;
}
.favorite-toggle:hover { color: var(--accent-2); border-color: rgba(242,198,109,0.55); transform: translateY(-1px); }
.favorite-toggle.active { color: var(--accent-2); background: rgba(242,198,109,0.12); border-color: rgba(242,198,109,0.55); }
.form-grid { gap: 18px 16px; }
.field { gap: 6px; }
.field label { font-size: 10px; letter-spacing: 0.08em; color: var(--text-dim); }
.field input, .field select, .field textarea { padding: 10px 11px; border-radius: 9px; background: var(--bg-elev-2); border-color: rgba(132,147,177,0.24); }
.field input:focus, .field select:focus, .field textarea:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px rgba(89,216,204,0.1); }
.card-detail-set-logo {
  min-height: 66px;
  padding: 10px 14px;
  border: 1px solid rgba(132,147,177,0.18);
  border-radius: 12px;
  background: var(--bg-elev);
}
.card-detail-set-logo-art { width: 44px; min-height: 44px; }
.card-detail-set-logo-art img { max-width: 38px; max-height: 38px; }
.card-detail-set-name { font-size: 15px; }
.section-title {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 28px 0 12px;
  color: var(--text-dim);
  font-size: 12px;
  letter-spacing: 0.08em;
}
.section-title::after { content: ''; height: 1px; flex: 1; background: rgba(132,147,177,0.2); }
.psa-grid { gap: 12px; }
.bulk-edit-grid { gap: 14px 16px; }
.bulk-edit-grid .field span { font-size: 10px; color: var(--text-dim); letter-spacing: .08em; text-transform: uppercase; }
.ai-check-row { margin-top: 18px; }
.flag-toggle { margin-top: 18px !important; padding: 10px 12px; border: 1px solid rgba(132,147,177,0.18); border-radius: 10px; background: var(--bg-elev); color: var(--text-dim); transition: border-color .16s ease, background .16s ease, color .16s ease; }
.flag-toggle:hover { border-color: rgba(89,216,204,.48); color: var(--text); }
.flag-toggle:has(input:checked) { border-color: rgba(89,216,204,.72); background: rgba(89,216,204,.12); color: var(--text); }
/* Same hand-built checkbox as .checkbox-label in the Filters panel — a
   native checkbox's default rendering (tiny, browser-chrome grey square)
   looked out of place next to everything else's dark theme. */
.flag-toggle input[type="checkbox"],
.refresh-filter-chip input[type="checkbox"] {
  appearance: none;
  width: 15px;
  height: 15px;
  margin: 0;
  flex-shrink: 0;
  border: 1px solid #59657a;
  border-radius: 5px;
  background: var(--bg-elev);
  display: grid;
  place-content: center;
  cursor: pointer;
}
.flag-toggle input[type="checkbox"]::after,
.refresh-filter-chip input[type="checkbox"]::after {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 2px;
  background: transparent;
}
.flag-toggle input[type="checkbox"]:checked,
.refresh-filter-chip input[type="checkbox"]:checked { border-color: var(--accent); background: var(--accent); }
.flag-toggle input[type="checkbox"]:checked::after,
.refresh-filter-chip input[type="checkbox"]:checked::after { background: #071312; }
.modal-actions { margin-top: 28px; padding-top: 18px; border-top: 1px solid rgba(132,147,177,0.18); }
@media (max-width: 700px) {
  .collection-layout { grid-template-columns: minmax(0, 1fr); }
  /* On a phone, the point of opening this app is almost always "see my
     cards" — the analytics sidebar (Collection value / Breakdown /
     Collection health / Collection organization) runs to ~900px tall on
     its own, so putting it first the way the desktop two-column layout
     naturally does pushed every card below the fold. Cards go first here;
     the sidebar still follows right after for anyone scrolling further. */
  .dashboard-insights { grid-column: 1; grid-row: 2; position: static; max-height: none; overflow: visible; }
  .workspace-nav, .toolbar { position: static; }
  .workspace-nav { margin-top: 0; }
  .collection-layout > #grid-view, .collection-layout > #table-view, .collection-layout > .empty-state { grid-column: 1; grid-row: 1; }
  .insight-wide { grid-row: auto; }
  .modal-body { padding: 24px 18px 22px; }
  /* grid-template-columns specifically (not just gap, below) has to be
     repeated here rather than relying on the earlier
     @media(max-width:700px) rule further up this file — a later,
     unconditional ".modal-grid { grid-template-columns: 220px
     minmax(0,1fr) }" rule (same specificity, later in the cascade) was
     winning over that regardless of screen size, so the photo column
     never actually collapsed: every form field ended up squeezed into
     a ~59px second column on a phone. */
  .modal-grid { grid-template-columns: minmax(0, 1fr); gap: 26px; }
  /* An ID selector's own base rule (font-size: 13.5px, further up this
     file) outranks the earlier ".modal input" mobile rule regardless of
     media query nesting — specificity, not source order, decides between
     an ID and a class selector. Caught by the mobile Playwright sweep:
     every other modal field was fixed to 16px, but this one, being
     ID-selected, silently wasn't. */
  #tag-input-field, #bulk-add-tag-input-field { min-width: 60px; font-size: 16px; }
  .modal-actions-right { width: 100%; justify-content: flex-end; }
  .modal-grid > div { min-width: 0; }
  .photo-col { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Plain "1fr" is minmax(auto, 1fr) — a track's auto minimum is set by its
     content's intrinsic width (a <select>, a date input, a long value —
     whichever is widest), so even a single column doesn't stop that content
     forcing the grid wider than the modal. This was overflowing the whole
     card-edit form on phones, not just the two-column Card Name/Set Number
     pairs it was designed for. minmax(0, 1fr) removes the track's own
     floor; .field (a flex container) and its input/select each default to
     min-width: auto too, so every level needs min-width: 0 or the widest
     field's intrinsic width still blows out the whole chain. */
  .form-grid { grid-template-columns: minmax(0, 1fr); }
  .form-grid .field { min-width: 0; }
  .form-grid input, .form-grid select { min-width: 0; width: 100%; box-sizing: border-box; }
  .modal-backdrop { overflow: hidden; }
  .modal {
    box-sizing: border-box;
    height: 100dvh;
    min-height: 100dvh;
    overflow-y: auto;
  }
  /* The add-card chooser is short by design (three compact option blocks,
     nothing to scroll to) — forcing it to full viewport height like the
     card editor just left a big empty dark area below the content (audit
     screenshot 21). Size it to its content instead. */
  .modal.add-link-modal {
    height: auto;
    min-height: 0;
  }
  .modal-backdrop:not(.hidden) .modal,
  .modal-backdrop.is-closing .modal {
    animation: none;
  }
  .modal input:not([type="checkbox"]):not([type="radio"]),
  .modal select,
  .modal textarea,
  .refresh-panel input:not([type="checkbox"]):not([type="radio"]),
  .refresh-panel select,
  .refresh-panel button {
    font-size: 16px;
  }
}
@media (max-width: 600px) {
  .currency-control { width: 100%; justify-content: space-between; }
  .currency-control select { flex: 1; max-width: 150px; }
  .chart-plot { height: 220px; }
  .refresh-panel {
    position: fixed;
    left: 12px;
    right: 12px;
    bottom: calc(72px + env(safe-area-inset-bottom, 0px));
    top: auto;
    width: auto;
    max-height: min(560px, calc(100dvh - 112px - env(safe-area-inset-bottom, 0px)));
    overflow-y: auto;
    z-index: 120;
  }
  .date-picker-panel {
    position: static;
    width: 100%;
    max-height: none;
    margin-top: 8px;
    box-sizing: border-box;
  }
}

/* Light theme — an explicit opt-in via the toggle in the topbar (not tied
   to prefers-color-scheme), persisted in localStorage and applied as
   data-theme="light" on <html>. This has to be the LAST :root block in the
   cascade: the app's actual active dark palette is the *second* :root
   block above (the first one near the top of this file is superseded by
   it entirely), so this one needs to win over both. Most of the app reads
   colors through the --bg/--text/etc tokens already, so redefining just
   these covers the large majority of the UI; the handful of selectors
   below that hardcode a color instead of using a token (the topbar's own
   background, the purple tag-chip accent, and the profit-chip's own
   accent colors, mainly) get their own explicit override underneath. */
:root[data-theme="light"] {
  --bg: #f3f4f8;
  --bg-elev: #ffffff;
  --bg-elev-2: #eef0f4;
  --border: #d8dce4;
  --text: #191c24;
  --text-dim: #666f80;
  --accent: #0d8f83;
  --accent-2: #a87406;
  --radius: 14px;
  --shadow: 0 10px 26px rgba(30,41,59,0.1);
  --panel: rgba(255,255,255,0.9);
  --panel-inset: rgba(15,23,42,0.035);
}
:root[data-theme="light"] body { background-image: none; }
:root[data-theme="light"] .topbar { background: rgba(255,255,255,0.92); border-bottom-color: var(--border); }
/* Light mode's own brand treatment — a plain dark wordmark on a pale
   surface reads flat where the dark theme's near-white text already had
   natural contrast/presence. A soft gradient fill (the app's own teal ->
   purple accent pair) and a light halo behind the mark give it the same
   "this is a brand, not just a label" weight without introducing a new
   color or going loud. */
:root[data-theme="light"] .brand-name {
  background: linear-gradient(100deg, var(--accent) 0%, var(--accent-2) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}
:root[data-theme="light"] .brand-mark { box-shadow: 0 0 0 3px rgba(79,209,197,.14); }
:root[data-theme="light"] .modal-backdrop { background: rgba(15,18,26,0.45); }
/* On mobile every .modal-backdrop is forced opaque and full-screen (see the
   max-width:600px rule earlier in this file) because there's no page left
   behind it to dim — but this light-theme rule shares its exact selector
   specificity (three class-level selectors either way) and comes later in
   the file, so on a narrow *light-theme* viewport it silently won that tie
   and put the translucent desktop dimming color back, which is exactly the
   "modal looks broken/translucent on mobile" bug. Same selector, scoped to
   the same breakpoint, declared after: wins the tie back. */
@media (max-width: 600px) {
  :root[data-theme="light"] .modal-backdrop { background: var(--bg-elev); }
}
:root[data-theme="light"] .tag-chip,
:root[data-theme="light"] .tag-suggestion-chip,
:root[data-theme="light"] .card-tag-edition {
  color: #7c3aed;
  border-color: rgba(124,58,237,0.35);
  background: rgba(124,58,237,0.08);
}
:root[data-theme="light"] .tag-chip-remove { color: #7c3aed; }
:root[data-theme="light"] .header-wrapped-btn { color: #6d28d9; background: rgba(124,58,237,0.08); border-color: rgba(124,58,237,0.35); }
:root[data-theme="light"] .header-wrapped-btn:hover { background: rgba(124,58,237,0.14); border-color: #7c3aed; }
:root[data-theme="light"] .placeholder-card { background: linear-gradient(160deg, #eef0f4, #e2e5eb); }
:root[data-theme="light"] .insight-card { background: linear-gradient(145deg, #ffffff, #f6f7fa); box-shadow: 0 6px 18px rgba(30,41,59,0.06); }
/* .card-tag's dark translucent chip look was designed to sit as an
   overlay on top of a card's own art — but .card-tags is actually
   positioned against the whole tile, not just the image, so on many
   cards it lands over the plain info-panel background instead. That
   read fine when the panel itself was dark; on the light panel it now
   is, a dark floating chip sticks out instead of reading as a normal
   badge, so it gets the same light-surface treatment as everything else
   in the panel. */
/* Background/shadow only — not border-color, which would clobber the
   holo/edition/rarity/psa/franchise-sports variants' own accent-colored
   borders (each sets border-color individually at lower specificity than
   this rule, and border-color isn't the problem here). */
:root[data-theme="light"] .card-tag,
:root[data-theme="light"] .card-set-icon { background: #ffffff; backdrop-filter: none; box-shadow: 0 1px 3px rgba(30,41,59,0.08); }
:root[data-theme="light"] .card-tag.card-tag-psa { background: #eef4ff; }
:root[data-theme="light"] .profit-chip.up { color: #0f7a3d; background: rgba(15,122,61,0.12); }
:root[data-theme="light"] .profit-chip.down { color: #c0362c; background: rgba(192,54,44,0.1); }
:root[data-theme="light"] .checkbox-label input,
:root[data-theme="light"] .flag-toggle input[type="checkbox"],
:root[data-theme="light"] .refresh-filter-chip input[type="checkbox"] {
  border-color: #b7bfcc;
}
/* Card thumbnails and their overlay badges (favorite/copies/rarity/PSA
   corner chips) are designed to sit on top of full-color card artwork —
   they stay dark-translucent in light mode too, same as a caption bubble
   on a photo, rather than following the page's own light/dark swap. */

/* Account settings modal — these classes used to live only in account.html's
   own inline <style> (duplicated again in login.html/register.html for
   their own forms), so the modal version of this UI had no styling at all
   until now. Pulled in here once so every place that needs them (the
   modal, and still login/register's own pages) can share one definition. */
.account-identity {
  padding: 12px 14px;
  border-radius: 9px;
  background: rgba(89,216,204,0.06);
  border: 1px solid rgba(89,216,204,0.24);
  font-size: 13.5px;
  margin-bottom: 4px;
}
/* A slim inline pill, not a dominant full-width warning bar — verifying
   email is a nice-to-have nudge, not something that should own the first
   screen someone sees after logging in. Sits inline near the toolbar
   rather than stretching edge to edge. */
/* Neutral, not warning-amber, on purpose — an unverified email isn't an
   error state the moment you sign up, so it shouldn't read like one next
   to a freshly added first card. It only needs to look urgent if the
   user runs into something that actually requires verification. */
.verify-banner {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  width: fit-content;
  max-width: calc(100% - 2 * clamp(18px, 3vw, 42px));
  margin: 14px 0 0 clamp(18px, 3vw, 42px);
  padding: 5px 8px 5px 12px;
  background: var(--bg-elev-2);
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--text-dim);
  font-size: 12px;
}
.verify-banner-text { white-space: nowrap; }
.verify-banner-resend {
  flex: 0 0 auto;
  padding: 3px 10px;
  font-size: 11.5px;
  font-weight: 700;
  white-space: nowrap;
  background: none;
  border: 1px solid var(--border);
  border-radius: 999px;
  color: var(--accent);
  cursor: pointer;
}
.verify-banner-resend:hover { background: var(--bg-elev); }
.verify-banner-dismiss {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: none;
  border: none;
  color: inherit;
  opacity: .7;
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
  border-radius: 6px;
}
.verify-banner-dismiss:hover { opacity: 1; background: var(--bg-elev); }
.account-identity .label { color: var(--text-dim); font-size: 11px; text-transform: uppercase; letter-spacing: .04em; display: block; margin-bottom: 3px; }
.account-oauth-note { color: var(--text-dim); font-size: 12.5px; margin-top: 14px; line-height: 1.5; }
.account-section-title { font-size: 13px; font-weight: 700; margin: 20px 0 4px; }
.account-danger-title { color: var(--danger); }
/* Scoped to this one button, not the shared .btn-danger class — it used
   to be a bare `.btn-danger` rule, which meant "Delete my account" here
   quietly forced display:block/width:100% onto every other danger button
   in the app too (Delete Selected in the bulk toolbar, Delete Card in the
   card editor, the duplicates modal's per-row delete), most visibly as
   the bulk toolbar's "Delete Selected" stretching to fill its own line on
   desktop. This button wants the same full-width treatment as its
   siblings above it (#account-export-data etc, styled the same way
   inline) — nothing else does. */
#account-delete-open {
  display: block;
  width: 100%;
  box-sizing: border-box;
  text-align: center;
  padding: 9px 14px;
  border-radius: 9px;
  border: 1px solid var(--danger);
  background: rgba(248,113,113,0.08);
  color: var(--danger);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
}
.btn-danger:hover { background: rgba(248,113,113,0.16); }
.btn-danger:disabled { opacity: .5; cursor: default; }
#account-delete-confirm { margin-top: 10px; }
.account-delete-actions { display: flex; gap: 8px; margin-top: 12px; }
.account-delete-actions .btn-secondary { flex: 1; }
.account-delete-actions .btn-danger { flex: 1; }
.field-hint { font-size: 11px; color: var(--text-dim); margin-top: 5px; }
.login-error { margin-top: 12px; color: var(--danger); font-size: 13px; min-height: 16px; }
.login-success { margin-top: 12px; color: var(--accent); font-size: 13px; min-height: 16px; }
.login-error:empty, .login-success:empty { margin-top: 0; min-height: 0; }
.password-field { position: relative; }
.password-field input { padding-right: 40px; width: 100%; box-sizing: border-box; }
.password-field .password-toggle {
  position: absolute;
  right: 4px;
  top: 50%;
  transform: translateY(-50%);
  width: 30px;
  height: 30px;
  margin-top: 0;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--text-dim);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 6px;
}
.password-field .password-toggle:hover { color: var(--text); background: rgba(255,255,255,0.06); }
.password-strength { margin-top: 6px; }
.password-strength-bar { height: 4px; border-radius: 2px; background: rgba(255,255,255,0.1); overflow: hidden; }
.password-strength-bar-fill { height: 100%; width: 0; border-radius: 2px; transition: width .15s, background-color .15s; }
.password-strength-label { margin-top: 4px; font-size: 11px; color: var(--text-dim); }
.password-strength-warnings { margin: 2px 0 0; padding: 0; list-style: none; font-size: 11px; color: var(--warn); }
.account-modal { width: min(440px, calc(100vw - 32px)); max-height: min(720px, calc(100vh - 32px)); overflow-y: auto; }
:root[data-theme="light"] .password-strength-bar { background: rgba(15,23,42,0.08); }

@media (max-width: 600px) {
  :root[data-theme="light"] .mobile-more-panel .header-wrapped-btn {
    color: var(--text);
    background: var(--bg-elev-2);
    border-color: var(--border);
  }
  :root[data-theme="light"] .mobile-more-panel .header-wrapped-btn:hover {
    border-color: var(--accent);
    background: rgba(13,143,131,0.08);
  }
  :root[data-theme="light"] #mnav-more-logout { color: var(--danger); }
}
