/* Easy TCG Lister — Design system
 * Supabase × Anduril aesthetic: 1px borders, no glassmorphism, no shadows,
 * brand green sparingly. Light default, dark + auto follow-OS via [data-theme].
 *
 * Token names are preserved from the previous "glass" system so every template
 * picks up new values automatically without find-and-replace. Tokens like
 * `--glass-*` are now solid colors and crisp borders rather than blurred rgba.
 */

/* ===== Light theme (default) ===== */
:root {
  /* Surfaces — solid, no blur */
  --bg-0: #FAFAFA;          /* outermost canvas / rail bg */
  --bg-1: #FFFFFF;          /* main content surface */
  --bg-grad: none;          /* no painted gradient backdrop */
  --glass-1: #FFFFFF;       /* card surface */
  --glass-2: #FAFAFA;       /* elevated */
  --glass-3: #F5F5F5;       /* hover / modal */
  --glass-border:        #E5E5E5;  /* subtle 1px */
  --glass-border-strong: #D4D4D4;  /* standard 1px */
  --glass-shadow: none;     /* depth from borders only */

  /* Text — bumped darker May 2026 after launch feedback that the light
     theme's secondary text read too thin / washed-out on white. --text is
     now pure black; secondaries closed the gap to it so the hierarchy is
     more legible without sacrificing contrast. */
  --text:    #000000;
  --text-2:  #1F1F1F;
  --muted:   #525252;
  --muted-2: #737373;

  /* Brand — confident indigo (May 2026 swap from #247852 forest-green
     to #4750DD; reads as more distinctive and contemporary). Hover
     shades + tinted backgrounds derived in HSL to stay on-hue. */
  --accent:      #4750DD;
  --accent-2:    #353cbb;        /* darker for hover on light */
  --accent-grad: #4750DD;        /* solid — keep var name for backwards compat */
  --brand-50:    #ECEDFA;        /* near-white indigo tint for selected / featured */
  --rail-active: #ECECEC;        /* neutral grey active fill for sidebar nav (Claude/ChatGPT-style, de-Anduril) */
  --good:        #4750DD;
  --warn:        #F59E0B;
  --bad:         #DC2626;
  --warn-bg:     #FFFBEB;
  --bad-bg:      #FEF2F2;
  --good-bg:     #ECEDFA;

  /* Type */
  --mono: "JetBrains Mono", ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;
  --sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif;

  /* Layout */
  --nav-w: 240px;
  --nav-w-collapsed: 60px;   /* ChatGPT-style icon rail when collapsed */
  --topbar-h: 56px;
  --radius-sm: 4px;
  --radius:    8px;
  --radius-lg: 12px;
}

/* ===== Dark theme — Supabase-exact ===== */
[data-theme="dark"] {
  --bg-0: #0F0F0F;
  --bg-1: #171717;
  --bg-grad: none;
  --glass-1: #1C1C1C;
  --glass-2: #1F1F1F;
  --glass-3: #242424;
  --glass-border:        #242424;
  --glass-border-strong: #2E2E2E;
  --glass-shadow: none;

  --text:    #FAFAFA;
  --text-2:  #D4D4D4;
  /* --muted bumped from #898989 → #A8A8A8 (May 2026 a11y pass) so that
     muted text against --glass-3 (#242424) lifts from 3.8:1 → 4.7:1
     and clears WCAG AA for normal-size body text. */
  --muted:   #A8A8A8;
  --muted-2: #6B6B6B;

  --accent:   #4750DD;
  --accent-2: #6068E5;          /* lighter for hover on dark */
  --accent-grad: #4750DD;       /* matches --accent so gradient buttons stay on-brand */
  --brand-50: #14163D;          /* very dark indigo tint for active sidebar etc. */
  --rail-active: #2A2A2A;        /* neutral grey active fill for sidebar nav (Claude/ChatGPT-style, de-Anduril) */
  --good:     #4750DD;
  --warn:     #F59E0B;
  --bad:      #F87171;
  --warn-bg:  #2A1F09;
  --bad-bg:   #2A0F0F;
  --good-bg:  #14163D;
}

/* =====================================================================
 * Accessibility utilities — native + always-on (NO overlay/widget).
 *
 * Accessibility is built into the markup and design system, not bolted on
 * via a floating toolbar. These rules provide a screen-reader-only utility,
 * a keyboard skip-to-content link, a consistent focus-visible ring, and they
 * honour the user's OS-level "increase contrast" preference natively.
 * Text resizing is handled by native browser zoom; reduced motion is handled
 * by the @media (prefers-reduced-motion) block further down this file.
 * ===================================================================== */

/* Screen-reader-only utility — visible to assistive tech, hidden visually.
   Used for hidden labels (e.g. icon-only buttons) and live-region text. */
.sr-only {
  position: absolute !important;
  width: 1px !important;
  height: 1px !important;
  padding: 0 !important;
  margin: -1px !important;
  overflow: hidden !important;
  clip: rect(0, 0, 0, 0) !important;
  white-space: nowrap !important;
  border: 0 !important;
}

/* Skip-to-content link (WCAG 2.4.1 Bypass Blocks). Visually hidden until it
   receives keyboard focus, then it pins to the top-left so a keyboard or
   screen-reader user can jump past the navigation straight into the page. */
.skip-link {
  position: absolute;
  left: 0.5rem;
  top: -4rem;
  z-index: 1000;
  padding: 0.55rem 0.9rem;
  border-radius: var(--radius-sm);
  background: var(--bg-1);
  color: var(--text);
  border: 1px solid var(--glass-border-strong);
  font-size: 0.9rem;
  font-weight: 600;
  text-decoration: none;
  transition: top 0.15s ease;
}
.skip-link:focus {
  top: 0.5rem;
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Consistent keyboard focus ring across every interactive element. Mouse
   users don't see it (:focus-visible only), keyboard users always do.
   WCAG 2.4.7 Focus Visible. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
summary:focus-visible,
[tabindex]:focus-visible,
[role="button"]:focus-visible,
[role="tab"]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Honour the OS "increase / more contrast" setting natively (this replaces the
   old in-page widget toggle): pure black/white tokens + thicker borders when
   the user requests more contrast at the system level. WCAG 1.4.3 / 1.4.11. */
@media (prefers-contrast: more) {
  [data-theme="light"] {
    --bg-0: #FFFFFF; --bg-1: #FFFFFF;
    --glass-1: #FFFFFF; --glass-2: #FFFFFF; --glass-3: #F0F0F0;
    --glass-border: #000000; --glass-border-strong: #000000;
    --text: #000000; --text-2: #000000; --muted: #1A1A1A; --muted-2: #1A1A1A;
    --accent: #0000A0; --accent-2: #00006B;
  }
  [data-theme="dark"] {
    --bg-0: #000000; --bg-1: #000000;
    --glass-1: #000000; --glass-2: #000000; --glass-3: #1A1A1A;
    --glass-border: #FFFFFF; --glass-border-strong: #FFFFFF;
    --text: #FFFFFF; --text-2: #FFFFFF; --muted: #E0E0E0; --muted-2: #E0E0E0;
    --accent: #FFFF00; --accent-2: #FFD700;
  }
  .card, .listing, input, select, textarea, button, .btn {
    border-width: 2px;
  }
}

/* ===== Base ===== */
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  padding: 0;
  font-family: var(--sans);
  color: var(--text);
  /* Base weight 500 (Inter Medium) — the post-launch readability pass found
     the default 400 too thin on the white surface. Inter 500 reads cleanly
     on dark too (Supabase uses similar) so we apply it globally. Bold
     elements (headings, buttons) keep their own 600+ overrides. */
  font-weight: 500;
  line-height: 1.5;
  letter-spacing: -0.005em;
  background: var(--bg-1);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  transition: background-color 0.15s, color 0.15s;
}

/* ===== Layout grid (dashboard app shell) ===== */
.app {
  display: grid;
  grid-template-columns: var(--nav-w) 1fr;
  /* Desktop: full-height sidebar, no top bar. .topbar is display:none here
     (it's a mobile-only app bar), so the grid is simply rail | main at full
     height — brand at the very top of the rail, account menu pinned to the
     very bottom. Mobile re-introduces a "topbar" row below. */
  grid-template-rows: 1fr;
  grid-template-areas: "rail main";
  height: 100vh;
}

/* ===== Topbar — MOBILE-ONLY app bar (hamburger + brand) =====
   Hidden on desktop (the full-height rail carries the brand + nav there);
   shown as a slim top bar only on phones, where the rail is a drawer. */
.topbar {
  grid-area: topbar;
  display: none;
  align-items: center;
  gap: 0.6rem;
  padding: 0 0.9rem;
  background: var(--bg-1);
  border-bottom: 1px solid var(--glass-border);
}
.topbar-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  text-decoration: none;
  color: var(--text);
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: -0.02em;
}
/* Topbar logo — matches the hero h1 font treatment ("Cards go in.
   Listings come out."): same weight (700) + tight letter-spacing
   (-0.035em). Same Inter family + same size; just heavier + tighter
   to read as a branded mark rather than a header label. */
.topbar h1 {
  margin: 0;
  font-family: var(--sans);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.035em;
  color: var(--text);
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* Brand mark — the stacked-card silhouette that sits left of the
 * "Easy TCG Lister" wordmark in every shell topbar (dashboard,
 * marketing, landing, auth). The source SVG is white; on light
 * theme we invert via filter so it reads as black on white surfaces.
 * Inline-block + height-only keeps the aspect ratio intact, and
 * the parent's flex gap (0.5rem on .topbar h1, etc.) handles
 * the breathing room to the text. */
.brand-logo {
  height: 26px;
  width: auto;
  display: inline-block;
  vertical-align: middle;
  /* The marketing + auth shells aren't always flex containers, so a
   * minimal margin keeps the spacing consistent even when the parent
   * doesn't define `gap`. The dashboard topbar's flex gap dominates. */
  margin-right: 0.15rem;
}
/* Light theme: invert the white SVG so it renders black on the
 * white background. CSS-only theme swap — no JS, no second img tag.
 * `filter: invert(1)` flips white→black + black→white, which is
 * exactly what we want for a single-color silhouette logo. */
[data-theme="light"] .brand-logo,
[data-theme="light"] .rail-open-logo {   /* collapsed-rail toggle logo — same white SVG, needs the light-mode invert too */
  filter: invert(1);
}
.topbar nav { display: flex; gap: 0.15rem; margin-left: 1.5rem; }
.topbar nav a {
  color: var(--muted);
  text-decoration: none;
  font-size: 0.85rem;
  font-weight: 500;
  padding: 0.4rem 0.75rem;
  border-radius: var(--radius-sm);
  transition: background 0.12s, color 0.12s;
}
.topbar nav a:hover { color: var(--text); background: var(--glass-3); }

.status-pill {
  display: inline-block;
  font-family: var(--mono);
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.25rem 0.6rem;
  border-radius: var(--radius-sm);
  background: var(--glass-3);
  color: var(--muted);
  border: 1px solid var(--glass-border);
  white-space: nowrap;
  vertical-align: middle;
}
.topbar .status-pill { margin-left: auto; }
.status-pill.live {
  background: rgba(22,163,74,0.12);
  color: #16A34A;
  border-color: rgba(22,163,74,0.45);
}
[data-theme="dark"] .status-pill.live {
  color: #4ADE80;
  border-color: rgba(74,222,128,0.45);
}
/* Operational status group — pinned right of the slim topbar (the brand, nav,
   and account menu now live in the rail, ChatGPT/Claude-style). Holds the
   eBay-connection indicator + LIVE/DRY-RUN, scanner, and credit pills. */
.topbar-status {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex: 0 0 auto;
}
/* The pills are now grouped inside .topbar-status, so the old
   `.topbar .status-pill { margin-left: auto }` (which right-aligned a lone pill)
   would wrongly split the eBay indicator from the LIVE pill. Neutralize it; the
   wrapper does the right-alignment for the whole group. */
.topbar-status .status-pill { margin-left: 0; }
/* eBay connection indicator (was an inline-styled topbar nav link; now a pill-row
   sibling). Span when connected, link when not. */
.ebay-status {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.82rem;
  color: var(--text);
  text-decoration: none;
  white-space: nowrap;
}
a.ebay-status:hover { color: var(--accent); }
/* Keyboard focus ring for non-native role="button" tiles (variant /
   candidate / catalog-search / suggestion picks on the confirm page). */
[role="button"][tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
/* In-flight form submit feedback (base.html global submit handler). Uses a
   class, not [disabled], so the submitter's name/value still posts. */
.is-busy { pointer-events: none; opacity: 0.75; }
button.is-busy::after,
.btn.is-busy::after {
  content: "";
  display: inline-block;
  width: 0.75em; height: 0.75em;
  margin-left: 0.45em;
  vertical-align: -0.1em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: tcg-btn-spin 0.6s linear infinite;
}
@keyframes tcg-btn-spin { to { transform: rotate(360deg); } }
/* Unified flash banners for settings save/error notices — one look across
   pages (conditions stay per-template). Success uses an explicit green because
   the --good token is repurposed as the indigo accent; errors use --bad. */
.flash {
  margin-bottom: 1rem;
  padding: 0.75rem 1rem;
  border-radius: var(--radius);
  border: 1px solid transparent;
  font-size: 0.9rem;
  line-height: 1.5;
  color: var(--text);
}
.flash-ok  { background: rgba(22,163,74,0.10); border-color: rgba(22,163,74,0.38); }
.flash-err { background: var(--bad-bg, rgba(220,38,38,0.10)); border-color: var(--bad); }

/* eBay connection dot */
.ebay-dot {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 50%;
  flex-shrink: 0;
}
.ebay-dot.connected    { background: #16A34A; }   /* explicit success green — --good is the indigo accent */
.ebay-dot.disconnected { background: var(--bad); }

/* ===== Left rail (sidebar) ===== */
.rail {
  grid-area: rail;
  background: var(--bg-0);
  border-right: 1px solid var(--glass-border);
  padding: 1rem 0.6rem;
  overflow-y: auto;
  /* Flex column so the footer block's margin-top:auto actually pins it (and
     the Sign-out button) to the bottom of the rail (Item 15). The mobile
     rail was already flex column; desktop was a plain grid item, so the
     pin silently did nothing here. */
  display: flex;
  flex-direction: column;
  /* Reserve space so the fixed accessibility launcher (bottom-left, 56px at
     bottom:1.25rem -> occupies the lowest ~4.75rem) never covers the bottom
     nav links or the Sign-out button (Item 16). */
  padding-bottom: 1rem;  /* account menu sits at the true bottom (the floating a11y launcher was removed) */
}
/* Rail header — brand wordmark + a collapse icon at the very top of the rail
   (ChatGPT/Claude shell). The brand links to /dashboard. */
.rail-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.15rem 0.4rem 0.75rem;
  margin-bottom: 0.25rem;
}
/* Override .rail a (which would otherwise space-between the logo + text and add
   nav-link padding/colour) with higher specificity. */
.rail-header .rail-brand {
  display: inline-flex;
  align-items: center;
  justify-content: flex-start;
  gap: 0.5rem;
  padding: 0.25rem 0.35rem;
  margin: 0;
  min-width: 0;
  border-radius: var(--radius-sm);
  color: var(--text);
  text-decoration: none;
  font-family: var(--sans);
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: -0.02em;
}
.rail-header .rail-brand:hover { background: var(--glass-3); }
.rail-header .rail-brand .brand-logo { height: 22px; margin: 0; flex: none; }
.rail-header .rail-brand span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rail-collapse {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 30px;
  height: 30px;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  transition: background 0.12s, color 0.12s;
}
.rail-collapse:hover { background: var(--glass-3); color: var(--text); }
.rail-collapse:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Collapsed-rail top toggle (ChatGPT style). Hidden while expanded; shown by
   the html.rail-collapsed block below. Default = brand logo; on hover the logo
   swaps to an "open sidebar" panel icon inside a grey chip + a tooltip pill. */
.rail-open {
  display: none;
  position: relative;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  margin: 0 auto;
  padding: 0;
  border: none;
  border-radius: var(--radius-sm);
  background: transparent;
  color: var(--text);
  cursor: pointer;
  transition: background 0.12s;
}
.rail-open .rail-open-logo { height: 24px; width: auto; flex: none; }
.rail-open .rail-open-icon { display: none; color: var(--muted); }
.rail-open:hover { background: var(--glass-3); }
.rail-open:hover .rail-open-logo { display: none; }
.rail-open:hover .rail-open-icon { display: inline-flex; color: var(--text); }
.rail-open:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* Reusable collapsed-rail hover tooltip pill (auto-adapts to theme): used for
   the "Open sidebar" tip and the per-icon nav labels. */
.rail-tip,
html.rail-collapsed .rail-queue a .rl {
  position: absolute;
  left: calc(100% + 10px);
  top: 50%;
  transform: translateY(-50%);
  background: var(--text);
  color: var(--bg-1);
  font-size: 0.8rem;
  font-weight: 500;
  line-height: 1.2;
  padding: 0.3rem 0.55rem;
  border-radius: 0.45rem;
  white-space: nowrap;
  overflow: visible;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity 0.12s;
  z-index: 200;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.22);
}
.rail-open:hover .rail-tip,
html.rail-collapsed .rail-queue a:hover .rl { opacity: 1; visibility: visible; }
.rail h3 {
  font-family: var(--mono);
  font-size: 0.66rem;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  color: var(--muted-2);
  margin: 0.5rem 0.75rem 0.25rem;
  font-weight: 500;
}
/* Collapsible rail sections (Settings / Legal): the <summary> looks like an h3
 * section header with a dropdown arrow that flips when open. Collapsed by default. */
.rail .rail-section > summary {
  list-style: none; cursor: pointer;
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  font-family: var(--mono); font-size: 0.66rem; text-transform: uppercase;
  letter-spacing: 0.12em; color: var(--muted-2); font-weight: 500;
  margin: 0.6rem 0 0.25rem; padding: 0.25rem 0.75rem; border-radius: var(--radius-sm);
}
.rail .rail-section > summary::-webkit-details-marker { display: none; }
.rail .rail-section > summary:hover { color: var(--text); background: var(--glass-3); }
.rail .rail-section > summary::after {
  content: "\25BE"; font-size: 0.7rem; transition: transform 0.15s ease;
}
.rail .rail-section[open] > summary::after { transform: rotate(180deg); }
.rail a {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding: 0.45rem 0.75rem;
  border-radius: var(--radius-sm);
  color: var(--text-2);
  text-decoration: none;
  font-size: 0.875rem;
  margin-bottom: 0.05rem;
  transition: background 0.12s, color 0.12s;
  position: relative;
}
.rail a:hover { background: var(--glass-3); color: var(--text); }
/* Active nav item: soft neutral grey fill (Claude/ChatGPT-style, de-Anduril) —
   no indigo left-bar, no indigo count badge. */
.rail a.active {
  background: var(--rail-active);
  color: var(--text);
  font-weight: 500;
}
/* Inline queue icons — muted by default, lift to --text on hover/active so the
   icon tracks the label colour. */
.rail a > svg {
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--muted);
  transition: color 0.12s;
}
.rail a:hover > svg,
.rail a.active > svg { color: var(--text); }
/* Queue label takes the middle, pushing the count to the right edge. */
.rail .rl {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.rail a .count {
  font-family: var(--mono);
  font-size: 0.7rem;
  font-weight: 500;
  background: var(--glass-3);
  color: var(--muted);
  padding: 0.1rem 0.45rem;
  border-radius: var(--radius-sm);
  min-width: 24px;
  text-align: center;
  border: 1px solid var(--glass-border);
}
/* ===== Account menu (rail footer, bottom-left — ChatGPT/Claude style) =====
   Pinned to the bottom of the flex-column rail; holds ALL non-queue navigation
   (Dashboard/Sync/Revenue, settings, legal, Sign out). The panel opens UPWARD.
   Link rules are #id-scoped so they win over the generic `.rail a` nav styling
   (the menu now lives inside .rail, where it didn't in the old topbar layout). */
#account-menu {
  position: relative;
  margin-top: auto;                 /* pin the account block to the rail bottom */
  padding-top: 0.5rem;
  border-top: 1px solid var(--glass-border);
}
#account-menu-trigger {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  width: 100%;
  justify-content: flex-start;
  padding: 0.4rem 0.5rem;
  border-radius: var(--radius-sm);
  background: transparent;
  border: 1px solid transparent;
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--text);
  line-height: 1;
  transition: background 0.12s, border-color 0.12s;
}
#account-menu-trigger:hover {
  background: var(--glass-3);
  border-color: var(--glass-border-strong);
}
#account-menu-trigger .avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: var(--radius-sm);
  background: var(--muted-2);        /* neutral grey, not the brand accent (de-Anduril) */
  color: #FFFFFF;
  font-family: var(--mono);
  font-size: 0.68rem;
  font-weight: 600;
  flex: none;
}
#account-menu-trigger .name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 500;
}
#account-menu-trigger .caret {
  margin-left: auto;
  font-size: 0.65rem;
  line-height: 1;
  flex: none;
}
/* Panel opens UPWARD from the trigger and spans the rail width. */
#account-menu-panel {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(100% + 6px);
  max-height: 70vh;
  overflow-y: auto;
  background: var(--bg-1);
  border: 1px solid var(--glass-border-strong);
  border-radius: var(--radius);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
  padding: 0.35rem;
  z-index: 1000;
}
[data-theme="dark"] #account-menu-panel {
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
}
#account-menu-panel .account-menu-header {
  padding: 0.55rem 0.75rem;
  border-bottom: 1px solid var(--glass-border);
  margin-bottom: 0.25rem;
}
#account-menu-panel .account-menu-header .name {
  font-weight: 600;
  color: var(--text);
  font-size: 0.88rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
#account-menu-panel .account-menu-header .email {
  font-size: 0.72rem;
  color: var(--muted);
  margin-top: 0.15rem;
  font-family: var(--mono);
  word-break: break-all;
}
/* #id-scoped so these beat `.rail a` (which would otherwise impose space-between
   layout, nav padding, and muted colour on these menu links). */
#account-menu-panel .account-menu-link {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 0.6rem;
  padding: 0.5rem 0.75rem;
  margin: 0;
  text-decoration: none;
  color: var(--text);
  font-size: 0.85rem;
  font-weight: 400;
  border-radius: var(--radius-sm);
  transition: background 0.1s;
}
#account-menu-panel .account-menu-link:hover { background: var(--glass-3); }
#account-menu-panel .account-menu-link.is-danger { color: var(--bad); }
#account-menu-panel .account-menu-link > svg {
  flex: none;
  width: 16px;
  height: 16px;
  color: var(--muted);
}
#account-menu-panel .account-menu-link:hover > svg { color: var(--text); }
#account-menu-panel .account-menu-link.is-danger > svg { color: var(--bad); }
#account-menu-panel .account-menu-caption {
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.07em;
  color: var(--muted);
  padding: 0.4rem 0.75rem 0.1rem;
}
#account-menu-panel .account-menu-legal {
  display: flex;
  flex-wrap: wrap;
  gap: 0.1rem 0.85rem;
  padding: 0 0.75rem 0.4rem;
}
#account-menu-panel .account-menu-legal a {
  display: inline;
  padding: 0.12rem 0;
  margin: 0;
  font-size: 0.78rem;
  color: var(--muted);
  text-decoration: none;
}
#account-menu-panel .account-menu-legal a:hover { color: var(--text); background: transparent; }
#account-menu-panel .account-menu-divider {
  border-top: 1px solid var(--glass-border);
  margin: 0.25rem;
}

/* ===== Main content ===== */
.main {
  grid-area: main;
  overflow-y: auto;
  padding: 1.5rem 2rem 4rem;
  background: var(--bg-1);
}
.main h2,
.marketing-content .page-header h2 {
  font-size: 1.35rem;
  font-weight: 600;
  margin: 0;
  letter-spacing: -0.02em;
}
.main h2 .subtitle,
.marketing-content .page-header h2 .subtitle {
  font-weight: 400;
  color: var(--muted);
  font-size: 0.9rem;
  margin-left: 0.5rem;
}
/* Page title promoted to a single <h1> per page (a11y + SEO). Matches the
   .main h2 look in both the app and marketing shells, independent of
   .page-header, so legal/help titles style consistently when logged out too. */
.page-title {
  font-size: 1.35rem;
  font-weight: 600;
  margin: 0;
  letter-spacing: -0.02em;
}
.page-title .subtitle {
  font-weight: 400;
  color: var(--muted);
  font-size: 0.9rem;
  margin-left: 0.5rem;
}
.page-header {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-bottom: 1.5rem;
  flex-wrap: wrap;
}
.page-header .actions { margin-left: auto; display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
/* Every control in the header actions row is pinned to one height so they line
   up as a single even set of pills (user: "all the same size"). box-sizing keeps
   the border inside the height; each control already centers its own content. */
.page-header .actions .view-toggle,
.page-header .actions .btn,
.page-header .actions #scanner-pill,
.page-header .actions #sort-select {
  height: 32px;
  box-sizing: border-box;
}
/* The size/sort controls and the Upload control are <label>s, so they inherit
   the form-label `margin-bottom: 0.25rem`. That 4px inflated the flex line and
   centered the zero-margin pills (Scanner, Connected to eBay, the view-toggle)
   ~2px lower. Spacing here comes from the row's `gap`, so zero every child's
   margin to keep all tops pixel-flush. */
.page-header .actions > * { margin: 0; }

/* The Sort <select>'s native dropdown arrow is invisible against this styling on
   iOS/Safari, so it doesn't read as a dropdown. Suppress the native UI and paint
   our own chevron. The inline style sets background-COLOR (longhand) so this
   background-image holds, and its right padding leaves room for the caret. */
#sort-select {
  -webkit-appearance: none;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23808080' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.7rem center;
  background-size: 0.7rem;
}

/* Desktop scanner-relay status pill — moved out of the old topbar into the
   dashboard page-header actions row. Polled by base.html JS (/relay/status). */
/* Scanner relay status — sized/styled to match the other toolbar controls
   (.btn.secondary): same padding, font, border, radius. The dot + the green
   `.online` state carry the status; it's non-interactive (cursor:default). */
#scanner-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.4rem 0.85rem;
  border-radius: var(--radius-sm);
  background: var(--bg-1);
  border: 1px solid var(--glass-border-strong);
  font-size: 0.85rem;
  font-weight: 500;
  line-height: 1.2;
  color: var(--text);
  white-space: nowrap;
  transition: background 0.15s, border-color 0.15s, color 0.15s;
  cursor: default;
}
#scanner-pill.online { background: rgba(22,163,74,0.10); border-color: #16A34A; color: #16A34A; }  /* online = success green, matches the connected dot */
#scanner-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--muted-2); transition: background 0.3s; }
#scanner-pill.online #scanner-dot { background: #16A34A; }

/* Bulk-action toolbar is a plain inline row — no white card, border, shadow or
   sticky-header mask. It scrolls with the listings and blends into the page. */

/* ===== Cards & panels ===== */
.card {
  background: var(--glass-1);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  padding: 1rem;
  box-shadow: none;
}
.row { display: flex; gap: 1rem; align-items: flex-start; }
.row > * { flex: 1; }

/* ── Reprice opportunities: width-filling rows (card identity left, price +
   Apply right on desktop; stacked on mobile) so the page reads as a real
   desktop tool instead of sparse content stranded in a stretched card. ───── */
.reprice-page .card { margin-bottom: 0.6rem; }
.reprice-row { display: flex; align-items: center; justify-content: space-between; gap: 1.5rem; }
.reprice-id { flex: 1 1 auto; min-width: 0; }
.reprice-name { font-size: 1rem; }
.reprice-meta { font-size: 0.85rem; margin-top: 0.15rem; }
.reprice-reason { font-size: 0.82rem; margin-top: 0.35rem; }
.reprice-sku { font-family: var(--mono); font-size: 0.72rem; margin-top: 0.35rem; opacity: 0.7; }
.reprice-action { flex: 0 0 auto; display: flex; flex-direction: column; align-items: flex-end; gap: 0.5rem; }
.reprice-prices { display: flex; align-items: baseline; gap: 0.5rem; flex-wrap: wrap; }
.reprice-new { font-size: 1.15rem; }
.reprice-trend { font-size: 0.8rem; }
.reprice-form { display: flex; gap: 0.5rem; align-items: center; }
.reprice-form input { width: 6rem; font-weight: 600; }
@media (max-width: 800px) {
  .reprice-row { flex-direction: column; align-items: stretch; gap: 0.7rem; }
  .reprice-action { align-items: stretch; }
  .reprice-prices { justify-content: flex-start; }
  .reprice-form input { flex: 1 1 auto; width: auto; }
}

/* ── Offers to watchers: selectable width-filling rows + sticky send bar ──── */
.offers-head { display: flex; align-items: center; justify-content: space-between; gap: 0.5rem; flex-wrap: wrap; }
.offers-row { display: flex; align-items: center; gap: 1rem; margin-bottom: 0.5rem; cursor: pointer; }
.offers-check { flex: 0 0 auto; width: 18px; height: 18px; }
.offers-id { flex: 1 1 auto; min-width: 0; }
.offers-name { font-size: 1rem; }
.offers-meta { font-size: 0.85rem; margin-top: 0.15rem; }
.offers-sku { font-family: var(--mono); font-size: 0.72rem; margin-top: 0.3rem; opacity: 0.7; }
.offers-price { flex: 0 0 auto; display: flex; align-items: baseline; gap: 0.5rem; }
.offers-new { font-size: 1.1rem; }
.offers-bar {
  position: sticky; bottom: 0.5rem; z-index: 20;
  display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap;
  margin-top: 1rem; padding: 0.7rem 0.85rem;
  background: var(--glass-1); border: 1px solid var(--glass-border-strong);
  border-radius: var(--radius); box-shadow: 0 4px 16px rgba(0,0,0,0.10);
}
.offers-pct { display: flex; align-items: center; gap: 0.4rem; font-size: 0.85rem; font-weight: 600; }
.offers-pct input { width: 5rem; font-weight: 600; }
.offers-msg { flex: 1 1 12rem; min-width: 0; }
.offers-toast { padding: 0.7rem 0.9rem; margin-bottom: 0.6rem; }
.offers-toast.ok { border-color: var(--good, #16A34A); }
.offers-toast.bad { border-color: var(--bad, #e11d48); }
.offers-hist { display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; padding: 0.6rem 0.9rem; margin-bottom: 0.4rem; }
@media (max-width: 800px) {
  .offers-row { flex-wrap: wrap; }
  .offers-price { width: 100%; }
  .offers-bar { flex-direction: column; align-items: stretch; }
  .offers-msg { flex: 1 1 auto; }
}

/* ===== Badges (status pills) ===== */
.badge {
  display: inline-block;
  font-family: var(--mono);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.15rem 0.55rem;
  border-radius: var(--radius-sm);
  border: 1px solid;
}
.b-pending {
  background: var(--glass-3); color: var(--muted-2); border-color: var(--glass-border-strong);
}
.b-confirm {
  background: var(--warn-bg); color: var(--warn); border-color: var(--warn);
}
.b-ready {
  background: var(--good-bg); color: var(--good); border-color: var(--good);
}
.b-ready::before { content: "✓"; margin-right: 0.3em; font-weight: 700; }
.b-pub {
  background: var(--brand-50); color: var(--accent-2); border-color: var(--accent);
}
.b-flag {
  /* Amber, NOT red. FLAGGED means "pricing engine recommends a
     sanity-check before you publish" — the listing is fully usable.
     Red is reserved for .b-error (true pipeline failures, listing
     unusable). Sibling visual to .b-confirm (PENDING_CONFIRM), which
     is also a "needs eyes" state. */
  background: var(--warn-bg); color: var(--warn); border-color: var(--warn);
}
.b-error {
  background: var(--bad-bg); color: var(--bad); border-color: var(--bad);
}
[data-theme="dark"] .b-pub  { color: var(--accent); }
[data-theme="dark"] .b-confirm { color: var(--warn); }

/* ===== Per-destination publish pills (Phase 4 + 6 dispatcher) =====
   Rendered by destPills(L) in ui/index.html into each listing card's
   stats-row alongside the status badge. One pill per marketplace this
   card was attempted on (eBay / Shopify / TikTok). The ok variant is
   a clickable link to the buyer-facing listing URL; the fail variant
   shows the eBay/Shopify/TikTok error text on hover. */
.dest-pill {
  display: inline-block;
  font-family: var(--mono);
  font-size: 0.64rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  padding: 0.12rem 0.45rem;
  margin-left: 0.35rem;
  border-radius: var(--radius-sm);
  border: 1px solid;
  text-decoration: none;
  white-space: nowrap;
  vertical-align: middle;
}
.dest-pill.ok {
  background: var(--brand-50);
  color: var(--accent-2);
  border-color: var(--accent);
}
.dest-pill.ok:hover {
  background: var(--accent);
  color: #fff;
}
.dest-pill.fail {
  background: var(--bad-bg);
  color: var(--bad);
  border-color: var(--bad);
  cursor: help;
}
[data-theme="dark"] .dest-pill.ok { color: var(--accent); }

/* ===== Buttons (Supabase-clean) ===== */
button, .btn {
  background: var(--bg-1);
  color: var(--text);
  border: 1px solid var(--glass-border-strong);
  padding: 0.4rem 0.85rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 500;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
  box-shadow: none;
  line-height: 1.2;
}
button:hover, .btn:hover {
  background: var(--glass-3);
  border-color: var(--muted-2);
  filter: none;
}
button:active, .btn:active { transform: none; filter: none; }
button:focus-visible, .btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 1px;
}
button:disabled, .btn:disabled, button.disabled, .btn.disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

button.primary, .btn.primary {
  background: var(--accent);
  color: #FFFFFF;
  border-color: var(--accent);
}
button.primary:hover, .btn.primary:hover {
  background: var(--accent-2);
  border-color: var(--accent-2);
  color: #FFFFFF;
  transform: none;
  box-shadow: none;
}

button.secondary, .btn.secondary {
  background: var(--bg-1);
  color: var(--text);
  border: 1px solid var(--glass-border-strong);
}
button.secondary:hover, .btn.secondary:hover {
  background: var(--glass-3);
  border-color: var(--muted-2);
}

button.ghost, .btn.ghost {
  background: transparent;
  color: var(--muted);
  border: 1px solid transparent;
  box-shadow: none;
}
button.ghost:hover, .btn.ghost:hover {
  color: var(--text);
  background: var(--glass-3);
  filter: none;
}

button.danger, .btn.danger {
  background: var(--bad);
  color: #FFFFFF;
  border-color: var(--bad);
}
button.danger:hover, .btn.danger:hover {
  background: var(--bad);
  filter: brightness(1.08);
  border-color: var(--bad);
}

/* ===== Form controls ===== */
input, select, textarea {
  background: var(--bg-1);
  color: var(--text);
  border: 1px solid var(--glass-border-strong);
  padding: 0.45rem 0.65rem;
  border-radius: var(--radius-sm);
  font-size: 0.875rem;
  font-family: inherit;
  transition: border-color 0.12s, box-shadow 0.12s, background 0.12s;
}
input:focus, select:focus, textarea:focus {
  outline: 0;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--brand-50);
}
input::placeholder, textarea::placeholder { color: var(--muted-2); }
input[type=number] { width: 8rem; }

/* Styled <select>: turn off the OS-native control and draw our own chevron so
 * dropdowns match the text inputs instead of rendering as the glossy native one. */
select {
  -webkit-appearance: none; appearance: none;
  background-color: var(--bg-1);
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' fill='none'%3E%3Cpath d='M1 1.5 6 6.5l5-5' stroke='%23737373' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 0.65rem center;
  padding-right: 2rem;
}
select::-ms-expand { display: none; }

input[type=range] {
  -webkit-appearance: none; appearance: none;
  background: transparent; padding: 0;
  width: 110px; height: 22px;
  border: none; box-shadow: none;
}
input[type=range]::-webkit-slider-runnable-track {
  height: 4px;
  background: var(--glass-border-strong);
  border-radius: 999px;
}
input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: var(--accent);
  margin-top: -5px;
  box-shadow: 0 0 0 2px var(--bg-1);
  cursor: ew-resize;
}
input[type=range]::-moz-range-track {
  height: 4px;
  background: var(--glass-border-strong);
  border-radius: 999px;
}
input[type=range]::-moz-range-thumb {
  width: 14px; height: 14px;
  border-radius: 50%;
  background: var(--accent);
  border: 2px solid var(--bg-1);
  cursor: ew-resize;
}

label {
  font-size: 0.78rem;
  color: var(--muted);
  display: block;
  margin-bottom: 0.25rem;
  letter-spacing: 0;
  font-weight: 500;
}
.field { margin-bottom: 0.75rem; }

img.scan {
  /* User feedback: the 280px cap made the confirm-page scan too small
     to verify card details, so it was widened to fill the column minus
     40px. Later reduced a further 20% per user request: the displayed
     scan is now 80% of (column - 40px). The inline `margin: 0 auto`
     still centers it; the auto-margins simply widen to absorb the
     reclaimed space. */
  width: calc((100% - 40px) * 0.65);
  max-width: calc((100% - 40px) * 0.65);
  height: auto;
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
}

table { width: 100%; border-collapse: collapse; font-size: 0.9rem; }
th, td {
  text-align: left;
  padding: 0.55rem 0.6rem;
  border-bottom: 1px solid var(--glass-border);
}
th {
  font-family: var(--mono);
  color: var(--muted);
  font-weight: 500;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
}

code, .mono { font-family: var(--mono); font-size: 0.85rem; }
.muted {
  color: var(--muted);
  /* Match the bumped body weight so .muted text doesn't look feathery
     next to surrounding 500-weight body copy. */
  font-weight: 500;
}

.warn-box {
  background: var(--warn-bg);
  border: 1px solid var(--warn);
  color: var(--warn);
  padding: 0.85rem 1.1rem;
  border-radius: var(--radius);
  margin-bottom: 1rem;
  font-size: 0.875rem;
}
[data-theme="dark"] .warn-box { color: var(--warn); }

/* ===== Listings ===== */
.grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));   /* fixed 5/row; 0-min => cards shrink, never overflow */
  gap: 0.85rem;
}
/* Responsive column ladder (replaces the old size slider — no user resize).
   minmax(0,1fr) tracks shrink instead of overflowing, so cards never overlap. */
@media (max-width: 1200px) { .grid:not(.list-mode) { grid-template-columns: repeat(4, minmax(0, 1fr)); } }
@media (max-width: 920px)  { .grid:not(.list-mode) { grid-template-columns: repeat(3, minmax(0, 1fr)); } }
@media (max-width: 680px)  { .grid:not(.list-mode) { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 480px)  { .grid:not(.list-mode) { grid-template-columns: 1fr; } }
.listing {
  background: var(--glass-1);
  border: 1px solid var(--glass-border);
  border-radius: 16px;           /* render-style soft corners (was var(--radius)) */
  padding: 1.1rem;               /* more breathing room (was 0.9rem) */
  /* Card view = single-column grid; named areas lock the visual order
     (image → title → status → price → actions → footer) regardless of DOM
     order or per-status action differences. List mode overrides to flex-row. */
  display: grid;
  grid-template-columns: minmax(0, 1fr);   /* 0-min so a wide child can't stretch the card past its track */
  grid-template-areas: "thumb" "info" "stats" "price" "primary" "footer";
  gap: 0.6rem;
  /* Soft elevation + a slight indigo edge-glow so each card lifts off the
     near-white canvas. List mode resets this back to flat rows. */
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05), 0 12px 30px -10px rgba(71, 80, 221, 0.16);
  transition: border-color 0.14s, box-shadow 0.16s, transform 0.16s;
}
.listing > .stats-row { grid-area: stats; }
.listing > .price-slot { grid-area: price; }
.listing > .card-primary-row { grid-area: primary; }
.listing > .card-footer-row { grid-area: footer; }
.listing:hover {
  border-color: var(--glass-border-strong);
  transform: translateY(-2px);
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06), 0 18px 42px -12px rgba(71, 80, 221, 0.26);
}
/* .top wraps thumb + the list-view checkbox in the DOM, but in card view it must
   NOT occupy one grid cell — dissolve it so its children land in their own areas.
   List mode restores it to a flex group. */
.listing .top { display: contents; }
.listing > .top > .row-cb { display: none; }   /* list-view checkbox copy */
.listing > .top > .thumb { grid-area: thumb; }
/* Thumbnail click-through: the <img>/.placeholder is wrapped in an <a> that
   navigates to /confirm/{sku} — users instinctively tap the picture. The
   link takes over the grid slot the bare thumb occupied (the > selector
   above no longer matches once wrapped). */
.listing > .top > .thumb-link { grid-area: thumb; }
.thumb-link {
  display: block;
  line-height: 0;          /* no phantom inline gap under the img */
  width: 100%;
  cursor: pointer;
  border-radius: 10px;
}
.thumb-link:focus-visible { outline: 2px solid var(--accent, #7aa2ff); outline-offset: 2px; }
.thumb-link:hover .thumb { opacity: 0.9; }
.listing > .top > .info { grid-area: info; }
.listing .thumb {
  /* Card view: hero card image at the top (full card width). List mode
     overrides to a small 40×56 thumbnail. */
  width: 100%;
  height: auto;
  aspect-ratio: 5 / 7;
  /* No max-height cap: a cap breaks the 5:7 box, and object-fit:cover then
     crops the card top/bottom (the reported clipping, worst at 4-per-row /
     wider cards). The hero now always renders at the full 5:7 card shape. */
  justify-self: center;
  border-radius: 10px;
  object-fit: cover;
  /* Default centered crop; .is-wide/.is-stitched show the LEFT HALF (front of a
     stitched front+back scan) instead of the white gap between the two sides. */
  object-position: center center;
  border: 1px solid var(--glass-border);
  background: var(--bg-0);
  flex-shrink: 0;
}
.listing .thumb.is-stitched,
.listing .thumb.is-wide {
  /* Center the crop on 25% of the image's width — the midpoint of the
     LEFT HALF. For a 2400×900 source: pixel 600 is centered. The 60-px
     container then displays roughly pixels 570-630 (the right edge of
     the front card art), which is visually the front side. */
  object-position: 25% center;
}
.listing .thumb.placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--muted-2);
  font-size: 2.4rem;
}
.listing .info { flex: 1; min-width: 0; }
.listing .info .name {
  font-weight: 600;
  font-size: 0.92rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.listing .info .meta {
  /* Inter (not mono) — reads as a proper subtitle, matches the rest of
     the site. Set/number/rarity is text first, code second. */
  font-family: var(--sans);
  font-size: 0.72rem;
  color: var(--muted);
  margin-top: 0.2rem;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.listing .stats-row {
  display: flex;
  gap: 0.4rem;
  align-items: center;
  flex-wrap: wrap;
  font-size: 0.8rem;
}
/* NM condition — neutral bordered chip (sibling to the status .badge, but
   neutral-colored so it reads as condition, not status). */
.listing .stats-row .cond {
  font-family: var(--mono);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted-2);
  padding: 0.15rem 0.5rem;
  border: 1px solid var(--glass-border-strong);
  border-radius: var(--radius-sm);
}
.listing .price-edit {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}
.listing .price-edit input {
  width: 5.5rem;
  padding: 0.22rem 0.4rem;
  font-size: 0.85rem;
  font-family: var(--sans);
  /* tabular-nums keeps the dollar-amount columns visually aligned even
     though we left mono behind — digits still line up across rows. */
  font-feature-settings: 'tnum' 1;
  font-weight: 600;
}
.listing .price-edit .save-flash {
  color: var(--good);
  font-size: 0.75rem;
  opacity: 0;
  transition: opacity 0.2s;
}
.listing .price-edit .save-flash.show { opacity: 1; }

/* ===== Card-view price = full-width "input-group" (render style) =====
   A light-grey rounded container: divided $ prefix, big number filling the
   width, native stepper on the right. Scoped to card view (.grid:not(.list-mode))
   so the compact list-view price is untouched. */
.grid:not(.list-mode) .listing .price-edit {
  width: 100%;
  gap: 0;
  background: var(--glass-3);
  border: 1px solid var(--glass-border-strong);
  border-radius: var(--radius);
  overflow: hidden;
  min-height: 2.6rem;
  position: relative;
}
.grid:not(.list-mode) .listing .price-edit:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--brand-50);
}
.grid:not(.list-mode) .listing .price-edit .price-currency {   /* divided $ prefix */
  display: flex;
  align-items: center;
  align-self: stretch;
  padding: 0 0.7rem;
  font-size: 1rem;
  font-weight: 600;
  color: var(--muted);
  border-right: 1px solid var(--glass-border-strong);
}
.grid:not(.list-mode) .listing .price-edit input {             /* number fills width */
  flex: 1 1 auto;
  width: auto;
  min-width: 0;
  background: transparent;
  border: 0;
  border-radius: 0;
  padding: 0 0.7rem;
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--text);
}
.grid:not(.list-mode) .listing .price-edit input:focus { outline: 0; box-shadow: none; }
.grid:not(.list-mode) .listing .price-edit .save-flash {       /* overlay, no layout width */
  position: absolute;
  right: 2.1rem;
  top: 50%;
  transform: translateY(-50%);
  font-size: 0.9rem;
  pointer-events: none;
}
/* Read-only price (PUBLISHED/etc.) gets a matching flat bar so non-editable
   cards keep the same rhythm. Child combinator keeps it off other .mono spans. */
.grid:not(.list-mode) .listing .price-slot > .mono {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 2.6rem;
  padding: 0 0.7rem;
  background: var(--glass-2);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--text);
  font-feature-settings: 'tnum' 1;
}

.listing .actions { display: flex; gap: 0.35rem; flex-wrap: wrap; }
.listing .actions form { display: inline; }
.listing .actions .btn,
.listing .actions button {
  font-size: 0.75rem;
  padding: 0.3rem 0.6rem;
}

/* Card-view PRIMARY action row — equal columns: 1 action = full width
   (Confirm/Review/View Details), 2 actions (Edit|Publish, Auto-price|Edit) =
   equal halves. */
.listing .card-primary-row {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(0, 1fr);   /* 0-min so nowrap buttons shrink instead of widening the card */
  gap: 0.4rem;
  margin-top: 0.1rem;
}
.listing .card-primary-row > a,
.listing .card-primary-row > button,
.listing .card-primary-row .publish-action {
  width: 100%;
  text-align: center;
  justify-content: center;
  white-space: nowrap;            /* keep "Auto-price" on one line */
  min-width: 0;                   /* shrink to fit the half-card — fixes card overlap */
  overflow: hidden;               /* clip rather than spill if a label is still too long */
  font-size: 0.85rem;
  padding: 0.7rem 0.6rem;         /* taller but not wider — fits side-by-side at 5/row */
  border-radius: var(--radius);   /* 8px corners to match the price container */
}
/* The Publish CTA is a <form> wrapping the button. As a grid cell it must be a
   flex box, NOT block — a block <form> adds a tall line-box that stretched the
   sibling Edit cell to 46px while the button stayed 32px. flex + the button
   filling it makes both halves the same height. */
.listing .card-primary-row > .publish-form { display: flex; width: 100%; }
.listing .card-primary-row .publish-action { flex: 1 1 auto; }
/* Publish = the filled brand-indigo CTA (render style); Edit & friends stay
   outline. Use --good (#4750DD), NOT --accent: .app neutralises --accent to
   grey, but the brand indigo lives in --good (same token the READY badge uses). */
.listing .card-primary-row .publish-action {
  background: var(--good);
  color: #fff;
  border-color: var(--good);
}
.listing .card-primary-row .publish-action:hover {
  background: #353cbb;             /* darker indigo */
  border-color: #353cbb;
  color: #fff;
}
/* Card-view FOOTER — select checkbox (left) + trash (right) at the very bottom. */
.listing .card-footer-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-top: 0.15rem;
  padding-top: 0.55rem;
  border-top: 1px solid var(--glass-border);
}
.listing .card-footer-row .row-cb,
.listing .card-footer-row .delete-btn { flex: 0 0 auto; }
/* In CARD view the inline list-action row is redundant — hide it. */
.listing .actions.list-only { display: none; }

/* Selected listing row */
.listing.is-selected {
  border-color: var(--accent) !important;
  background: var(--brand-50) !important;
}

/* Custom checkbox — clean square with brand-green fill */
.select-cb {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  cursor: pointer;
  user-select: none;
  font-size: 0.85rem;
  color: var(--text);
  position: relative;
}
.select-cb input[type="checkbox"] {
  position: absolute;
  opacity: 0;
  width: 0; height: 0;
  pointer-events: none;
}
.select-cb .checkmark {
  display: inline-block;
  width: 1.15rem;
  height: 1.15rem;
  border-radius: var(--radius-sm);
  background: var(--bg-1);
  border: 1px solid var(--glass-border-strong);
  position: relative;
  transition: background 0.12s, border-color 0.12s;
  flex-shrink: 0;
}
.select-cb:hover .checkmark { border-color: var(--accent); }
.select-cb input:focus-visible + .checkmark {
  box-shadow: 0 0 0 3px var(--brand-50);
  border-color: var(--accent);
}
.select-cb input:checked + .checkmark {
  background: var(--accent);
  border-color: var(--accent);
}
.select-cb input:checked + .checkmark::after {
  content: "";
  position: absolute;
  left: 0.32rem;
  top: 0.12rem;
  width: 0.35rem;
  height: 0.62rem;
  border: solid #FFFFFF;
  border-width: 0 2px 2px 0;
  transform: rotate(45deg);
}
.select-cb input:indeterminate + .checkmark {
  background: var(--brand-50);
  border-color: var(--accent);
}
.select-cb input:indeterminate + .checkmark::after {
  content: "";
  position: absolute;
  left: 0.18rem;
  top: 0.5rem;
  width: 0.75rem;
  height: 2px;
  background: var(--accent);
  border-radius: 1px;
}
.listing .select-cb.row-cb {
  gap: 0;
  align-self: center;
  flex-shrink: 0;
}

/* Bulk action toolbar buttons */
.bulk-btn {
  padding: 0.4rem 0.85rem;
  font-size: 0.82rem;
  font-weight: 500;
  /* Inter (not mono) — the toolbar reads as part of the same UI as the
     cards below it; mono was inconsistent with the rest of the dashboard. */
  font-family: var(--sans);
  border-radius: var(--radius-sm);
  cursor: pointer;
  border: 1px solid var(--glass-border-strong);
  background: var(--bg-1);
  color: var(--text);
  transition: background 0.12s, border-color 0.12s, opacity 0.12s;
  white-space: nowrap;
}
.bulk-btn:hover:not(:disabled) {
  background: var(--glass-3);
  border-color: var(--muted-2);
}
.bulk-btn:active:not(:disabled) { transform: none; }
.bulk-btn:disabled { opacity: 0.5; cursor: not-allowed; }
/* Primary bulk actions (Confirm, Publish) follow the app-wide primary style
   (.app .btn.primary): a neutral that inverts with the theme — light fill +
   dark text on dark, dark fill + light text on light. The old --accent fill
   was greyed inside .app, so white text on light grey read as low-contrast /
   "inverted"; --text-2 + --bg-1 text fixes that and reads brighter. */
.bulk-btn-primary {
  background: var(--text-2);
  border-color: var(--text-2);
  color: var(--bg-1);
}
.bulk-btn-primary:hover:not(:disabled) {
  background: var(--text);
  border-color: var(--text);
  color: var(--bg-1);
}
.bulk-btn-danger {
  background: var(--bad-bg);
  border-color: var(--bad);
  color: var(--bad);
}
.bulk-btn-danger:hover:not(:disabled) {
  background: var(--bad);
  color: #FFFFFF;
}
.bulk-btn-secondary {
  background: var(--bg-1);
  border-color: var(--glass-border-strong);
}

/* Delete button on listing row */
.listing .delete-btn {
  padding: 0.3rem 0.5rem;
  border-radius: var(--radius-sm);
  border: 1px solid var(--glass-border-strong);
  background: var(--bg-1);
  color: var(--bad);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: background 0.12s, border-color 0.12s, color 0.12s;
  flex-shrink: 0;
  box-shadow: none;
}
.listing .delete-btn:hover {
  background: var(--bad-bg);
  border-color: var(--bad);
}
.listing .delete-btn:active { transform: none; }
.listing .delete-btn:disabled { cursor: wait; opacity: 0.5; }
.listing .delete-btn svg { display: block; }

/* List view (table-like rows) */
.grid.list-mode {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}
.grid.list-mode .listing {
  /* Override the card-view grid back to a horizontal row. Flat rows — the
     card-view float shadow + hover lift are reset here. */
  display: flex;
  grid-template-areas: none;
  flex-direction: row;
  align-items: center;
  padding: 0.6rem 0.9rem;
  gap: 1rem;
  box-shadow: none;
  border-radius: var(--radius);
}
.grid.list-mode .listing:hover { transform: none; box-shadow: none; }
/* Reinstate .top as a real flex group (card view dissolves it via
   display:contents). min-width:0 is what lets the name ellipsis instead of
   forcing the row wider. */
.grid.list-mode .listing .top {
  display: flex;
  flex: 1 1 auto;
  min-width: 0;
  gap: 0.75rem;
  align-items: center;
}
.grid.list-mode .listing > .top > .row-cb { display: inline-flex; }  /* list-view checkbox */
.grid.list-mode .listing .thumb {
  width: 40px; height: 56px; aspect-ratio: auto; max-height: none; flex: 0 0 auto;
}
.grid.list-mode .listing .info { flex: 1 1 auto; min-width: 0; }
.grid.list-mode .listing .stats-row { flex: 0 1 auto; min-width: 0; gap: 0.5rem; }
.grid.list-mode .listing .price-slot { flex: 0 0 auto; display: flex; align-items: center; }
.grid.list-mode .listing .price-edit .price-currency { font-size: 0.8rem; }  /* list keeps the small $ */
/* Card-view-only rows hidden in list view; the inline .actions row carries
   Edit/Publish/Trash, and the .top checkbox handles selection. */
.grid.list-mode .listing .card-primary-row,
.grid.list-mode .listing .card-footer-row { display: none; }
/* nowrap: keep Edit/Publish/Delete on one horizontal line — never let them
   stack vertically and balloon the row height in the single-row band. */
.grid.list-mode .listing .actions.list-only { display: flex; flex-wrap: nowrap; }

/* ===== Stats strip ===== */
.stats {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 1.5rem;
}
.stat {
  background: var(--glass-1);
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  padding: 0.75rem 1rem;
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--muted);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  min-width: 110px;
  box-shadow: none;
}
.stat .n {
  font-family: var(--mono);
  font-weight: 600;
  font-size: 1.5rem;
  color: var(--text);
  display: block;
  line-height: 1.1;
  letter-spacing: -0.02em;
  text-transform: none;
}

/* ===== View toggle + size control ===== */
.view-toggle {
  display: inline-flex;
  padding: 2px;
  gap: 0;
  border-radius: var(--radius-sm);
  background: var(--glass-3);
  border: 1px solid var(--glass-border-strong);
}
.view-toggle button {
  background: transparent;
  box-shadow: none;
  color: var(--muted);
  padding: 0.3rem 0.6rem;
  font-size: 0.75rem;
  font-weight: 500;
  border: 0;
  border-radius: calc(var(--radius-sm) - 1px);
}
.view-toggle button:hover {
  background: var(--bg-1);
  color: var(--text);
  filter: none;
  border: 0;
}
.view-toggle button.active {
  background: var(--bg-1);
  color: var(--text);
  border: 0;
  box-shadow: 0 0 0 1px var(--glass-border-strong);
}

/* ===== Upload zone ===== */
.upload-zone {
  border: 1.5px dashed var(--glass-border-strong);
  border-radius: var(--radius);
  padding: 1.5rem;
  text-align: center;
  color: var(--muted);
  background: var(--bg-1);
  cursor: pointer;
  transition: border-color 0.12s, background 0.12s, color 0.12s;
}
.upload-zone:hover, .upload-zone.dragover {
  /* Neutral bright highlight (not indigo): the dashed border lights up toward
     white/black with the theme and the fill lifts to the neutral hover surface. */
  border-color: var(--text);
  background: var(--glass-3);
  color: var(--text);
}
.upload-zone input { display: none; }

/* Mobile-only camera entry, sat directly under the drag-drop add-photos zone.
   Hidden on desktop (flatbed / drag-drop there); a full-width tap target on
   phones, tucked up close beneath the zone. */
.take-photos-btn { display: none; }
@media (max-width: 560px) {
  .take-photos-btn {
    display: flex;
    width: 100%;
    box-sizing: border-box;
    justify-content: center;
    margin: -1rem 0 1.5rem;
    padding: 0.7rem;
    font-size: 0.95rem;
  }
}

/* ===== HUD utilities (Anduril-style spec callouts) ===== */
.hud-label {
  font-family: var(--mono);
  font-size: 0.68rem;
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
  display: inline-block;
}
.hud-stat {
  font-family: var(--mono);
  font-size: 2rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--text);
  line-height: 1;
  display: inline-block;
}
.hud-stat .unit {
  font-size: 0.6em;
  color: var(--muted);
  margin-left: 0.2rem;
  letter-spacing: 0;
}
.hud-rule {
  border: none;
  border-top: 1px solid var(--accent);
  width: 48px;
  margin: 0 0 1rem 0;
  opacity: 0.6;
}
.dot-grid {
  background-image: radial-gradient(circle, var(--glass-border) 1px, transparent 1px);
  background-size: 24px 24px;
  background-position: 0 0;
}
[data-theme="dark"] .dot-grid {
  background-image: radial-gradient(circle, var(--glass-border-strong) 1px, transparent 1px);
}

/* ===== Animations ===== */
@keyframes flash-good {
  0%   { background: transparent; }
  30%  { background: var(--good-bg); }
  100% { background: transparent; }
}
@keyframes fade-up {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* ===== Scrollbars ===== */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb {
  background: var(--glass-border-strong);
  border-radius: 999px;
}
::-webkit-scrollbar-thumb:hover { background: var(--muted-2); }
::-webkit-scrollbar-track { background: transparent; }

/* =====================================================================
 * Mobile nav (hamburger drawer) — replaces the previous
 * `.rail { display: none }` so users can still navigate on phone.
 *
 * At ≤800px:
 *   - .app collapses to a single column
 *   - Hamburger button (#mobile-menu-btn) appears in the topbar
 *   - .rail becomes a fixed-position drawer that slides in from the
 *     left when the user taps the hamburger
 *   - .rail-backdrop dims the rest of the screen while the drawer is open
 * ===================================================================== */

/* Hamburger button hidden on desktop; revealed at narrow widths. */
#mobile-menu-btn {
  display: none;
  background: transparent;
  border: none;                 /* borderless, like the desktop rail-collapse control */
  border-radius: var(--radius-sm);
  width: 40px;
  height: 40px;
  padding: 0;
  cursor: pointer;
  color: var(--muted);
  flex: 0 0 auto;
  align-items: center;
  justify-content: center;
  transition: background 0.12s, color 0.12s;
}
#mobile-menu-btn:hover { background: var(--glass-3); color: var(--text); }
#mobile-menu-btn:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}
#mobile-menu-btn svg { width: 22px; height: 22px; }

#rail-backdrop {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.45);
  z-index: 199;
  opacity: 0;
  transition: opacity 0.2s ease;
}
#rail-backdrop.is-open {
  display: block;
  opacity: 1;
}

@media (max-width: 800px) {
  .app {
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr;
    grid-template-areas: "topbar" "main";
    /* Never let the page scroll sideways on a phone. */
    max-width: 100vw;
    overflow-x: hidden;
  }
  .main {
    padding: 1rem 1rem 2rem;
    grid-area: main;
    overflow-x: hidden;
    min-width: 0;            /* let flex/grid children shrink instead of overflowing */
  }

  /* The topbar becomes the mobile app bar (hamburger + brand). */
  .topbar {
    display: flex;
    padding: 0 0.75rem;
    gap: 0.5rem;
  }

  /* Show the hamburger trigger. */
  #mobile-menu-btn { display: inline-flex; }

  /* Rail becomes a slide-in drawer. */
  .rail {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    width: 280px;
    max-width: 85vw;
    z-index: 200;
    transform: translateX(-100%);
    transition: transform 0.25s ease;
    box-shadow: 4px 0 20px rgba(0, 0, 0, 0.2);
    overflow-y: auto;
    grid-area: unset;
    display: flex;            /* preserve flex column for the nav list */
    flex-direction: column;
  }
  .rail.is-mobile-open { transform: translateX(0); }

  /* When the drawer is open, lock body scroll so the page behind
     doesn't drift under the user's thumb. */
  body.rail-drawer-open { overflow: hidden; }

  /* Condense the dashboard rows so nothing overflows the viewport: the action
     cluster wraps full-width under the title, the bulk toolbar wraps, the
     card-size slider hides, and the pairing toggle + rows stay in-bounds. */
  .page-header .actions { width: 100%; margin-left: 0; }
  #bulk-toolbar { flex-wrap: wrap; }
  #pair-mode-toggle { flex-wrap: wrap; max-width: 100%; }
  .listing { min-width: 0; }
  /* Final backstop: never allow the page itself to scroll sideways on a phone. */
  body { overflow-x: hidden; }
}

/* Phones: the in-app camera is THE mobile action, so promote it to a
   full-width primary CTA at the top of the wrapped toolbar instead of
   letting it get lost among the secondary controls. */
@media (max-width: 560px) {
  /* Even 2-up grid so the chips are equal width, equally gapped, and aligned:
     Row 1  [ Cards|List ] [ Sort      ]
     Row 2  [ Upload     ] [ Scanner   ]
     Row 3  [ Connected to eBay        ]   (full width) */
  .page-header .actions {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 0.5rem;
  }
  .page-header .actions > * { width: 100%; box-sizing: border-box; }
  .page-header .actions .ebay-status { grid-column: 1 / -1; }
  /* Inventory search gets its own full-width row so the even 2-col grid
     stays pixel-perfect (its 220px desktop width would break the columns). */
  .page-header .actions .listing-search { grid-column: 1 / -1; width: 100%; }
  /* center each chip's content inside its stretched cell */
  .page-header .actions .btn,
  .page-header .actions #scanner-pill { justify-content: center; }
  /* segmented toggle splits Cards|List evenly across its cell */
  .page-header .actions .view-toggle { display: flex; }
  .page-header .actions .view-toggle button { flex: 1; justify-content: center; }
  /* sort: pin height to match the other chips (it was never in the 32px rule),
     drop the tiny "Sort" label so the select fills the whole cell */
  .page-header .actions .sort-control { display: flex; align-items: center; height: 32px; }
  .page-header .actions .sort-control > span { display: none; }
  .page-header .actions #sort-select { flex: 1; min-width: 0; width: 100%; }
}

/* =====================================================================
 * Tablet band — fills the 720→1100px blind spot the audit identified.
 * Layout stays grid (no drawer) but main padding tightens + grid gap
 * compresses so the desktop layout reads cleanly on iPad portrait
 * (768×1024) and landscape (1024×768).
 * ===================================================================== */
@media (min-width: 801px) and (max-width: 1100px) {
  .main { padding: 1.25rem 1.5rem; }
  .topbar { padding: 0 1rem; gap: 0.75rem; }
  .topbar nav { gap: 0.2rem; }
  .topbar nav a { padding: 0.4rem 0.55rem; font-size: 0.82rem; }
}

/* =====================================================================
 * Touch-target sizing — WCAG 2.5.5 requires ≥44×44px for touch input.
 * Applied via @media (hover: none) so we don't fatten controls on
 * desktop where the cursor is precise.
 * ===================================================================== */
@media (hover: none) and (pointer: coarse) {
  button, .btn, .listing .delete-btn,
  input[type="checkbox"], input[type="radio"],
  .select-cb, .topbar nav a, .rail a {
    min-height: 44px;
    min-width: 44px;
  }
  .listing .delete-btn { padding: 0.6rem 0.85rem; }
  .select-cb .checkmark {
    width: 1.5rem !important;
    height: 1.5rem !important;
  }
  /* Larger thumb on the range slider for finger-precision sliding. */
  input[type="range"]::-webkit-slider-thumb {
    width: 24px !important;
    height: 24px !important;
  }
  input[type="range"]::-moz-range-thumb {
    width: 24px !important;
    height: 24px !important;
  }
  /* Avatar trigger needs to be a real touch target. */
  #account-menu-trigger { min-width: 44px; min-height: 44px; }
}

/* =====================================================================
 * Modal overflow fix — `.modal-card` had a hard 440/480px max-width that
 * overflowed a 390px iPhone viewport by ~50px. Cap to viewport-aware width.
 * ===================================================================== */
.modal-card,
.upgrade-modal-card,
#sync-result-panel > div {
  max-width: min(440px, calc(100vw - 2rem)) !important;
  width: auto;
}

/* =====================================================================
 * Confirm page — at ≤720px the two-column "scan on left, form on right"
 * layout becomes unreadable (each column shrinks to ~185px). Stack them.
 * ===================================================================== */
@media (max-width: 720px) {
  .confirm-grid {
    grid-template-columns: 1fr !important;
    gap: 1rem !important;
  }
  /* The variant picker grid drops from 4-up to 2-up. */
  .variants-grid {
    grid-template-columns: repeat(2, 1fr) !important;
  }
}

/* =====================================================================
 * Dashboard mobile pass:
 *   - Bulk toolbar buttons wrap to multiple lines (no horizontal scroll)
 *   - Listing card thumb shrinks at ≤480px for more room on the meta line
 * ===================================================================== */
/* ===== List-view responsive degradation (desktop → tablet → mobile) =====
   Priority: as width shrinks, drop the lowest-value signals FIRST so the card
   NAME keeps its space and the row never overflows. */
@media (max-width: 1100px) {
  /* Single-row band (961–1100px): still roomy — just trim secondary noise and
     tighten the buttons so the card NAME keeps its width. */
  .grid.list-mode .listing { gap: 0.8rem; }
  .grid.list-mode .listing .stats-row { gap: 0.45rem; }
  .grid.list-mode .listing .actions.list-only { gap: 0.5rem; padding-left: 0.6rem; }
  .grid.list-mode .listing .actions.list-only .btn,
  .grid.list-mode .listing .actions.list-only button { padding: 0.35rem 0.6rem; font-size: 0.78rem; }
  .grid.list-mode .listing .stats-row .dest-pill { display: none; }   /* operator-only noise */
}

/* Tablet + mobile (≤1000px): the single row gets cramped here — the name would
   truncate while the controls fight for width (the reported bug). STACK to two
   clean lines instead: thumb + title on line 1; status + price + the
   Edit/Publish/Delete cluster on line 2. The full name returns; nothing
   overflows; meta + NM come back since line 2 has room. */
@media (max-width: 1000px) {
  .grid.list-mode .listing { flex-wrap: wrap; row-gap: 0.6rem; }
  .grid.list-mode .listing .top { flex: 1 1 100%; min-width: 0; }
  .grid.list-mode .listing .stats-row,
  .grid.list-mode .listing .price-slot,
  .grid.list-mode .listing .actions.list-only { flex: 0 1 auto; }
  .grid.list-mode .listing .actions.list-only { margin-left: auto; padding-left: 0; }
  .grid.list-mode .listing .info .meta { display: block; }
  .grid.list-mode .listing .stats-row .cond,
  .grid.list-mode .listing .stats-row .dest-pill { display: inline-block; }
}

@media (max-width: 600px) {
  #bulk-toolbar {
    flex-wrap: wrap !important;
    gap: 0.4rem !important;
  }
  #bulk-toolbar > * { flex: 0 1 auto; }
}

@media (max-width: 480px) {
  /* List view only — keep the small thumb; don't shrink the card-view hero. */
  .grid.list-mode .listing .thumb { width: 40px !important; height: 56px !important; }
  /* Price input shrinks so the row / bulk-set-price form fits. */
  .listing .price-edit input { width: 4rem !important; }
  /* Very narrow: let the action cluster wrap under status/price if it must,
     rather than force horizontal overflow. */
  .grid.list-mode .listing .actions.list-only { flex-wrap: wrap; }
}

/* Single-row list view (≥1001px): fixed-width columns so status / price /
   actions line up vertically across every row — a table, not a ragged flex
   flow. The name (.top, flex:1) is the only flexible column; it absorbs slack
   and pushes these three into aligned columns. Scoped ≥1001px so it never
   touches the ≤1000px two-line stack (which keeps its margin-left:auto flow).
   11.5rem fits the widest status cluster (badge + NM ≈ 180px). */
@media (min-width: 1001px) {
  .grid.list-mode .listing .stats-row { flex: 0 0 11.5rem; }
  .grid.list-mode .listing .price-slot { flex: 0 0 8rem; justify-content: flex-start; }
  .grid.list-mode .listing .actions.list-only {
    flex: 0 0 14rem; margin-left: 0; justify-content: flex-end; padding-left: 0;
  }
}

/* =====================================================================
 * Settings pages mobile — single-column cards, snug padding.
 * ===================================================================== */
@media (max-width: 600px) {
  .card { padding: 0.85rem 1rem !important; }
  .card h3, .card h2 { font-size: 1rem !important; }
}

/* =============================================================
 * /pricing page — Supabase-style 1px borders, brand-green featured
 * Scoped via .pricing-page on the outermost container.
 * ============================================================= */

.pricing-page { --halo: var(--brand-50); }

/* Segmented toggle buttons (billing period, scan pair-mode) */
.period-btn {
  background: transparent !important;
  box-shadow: none !important;
  border: 0 !important;
  color: var(--muted);
  cursor: pointer;
  font-size: 0.82rem;
  font-weight: 500;
  padding: 0.4rem 0.85rem;
  border-radius: var(--radius-sm);
  transition: background 0.12s, color 0.12s;
}
.period-btn:hover { color: var(--text); background: var(--glass-3); filter: none; }
.period-btn.active {
  background: var(--bg-1) !important;
  color: var(--text) !important;
  box-shadow: 0 0 0 1px var(--glass-border-strong) !important;
}

/* Eyebrow tag */
.pricing-page .eyebrow {
  display: inline-block;
  font-family: var(--mono);
  font-size: 0.66rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  padding: 0.25rem 0.7rem;
  border-radius: var(--radius-sm);
  background: var(--brand-50);
  border: 1px solid var(--accent);
  color: var(--accent-2);
  margin-bottom: 0.75rem;
}
[data-theme="dark"] .pricing-page .eyebrow { color: var(--accent); }

/* Hero */
.pricing-page .pricing-hero h1 {
  font-size: clamp(1.8rem, 4vw, 2.8rem);
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.05;
  margin: 0 0 0.5rem;
  color: var(--text);
}
.pricing-page .pricing-hero p.lead {
  font-size: 1rem;
  color: var(--muted);
  margin: 0;
  max-width: 540px;
  line-height: 1.55;
}

.pricing-page .grad-text {
  color: var(--accent-2);
  font-weight: inherit;
}
[data-theme="dark"] .pricing-page .grad-text { color: var(--accent); }

/* Slim rollover pill */
.pricing-page .rollover-pill {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.4rem 0.85rem;
  border-radius: var(--radius-sm);
  background: var(--good-bg);
  border: 1px solid var(--good);
  font-size: 0.82rem;
  color: var(--text-2);
  margin-bottom: 1.5rem;
}
.pricing-page .rollover-pill strong { color: var(--text); }

/* Section divider — thin solid line */
.pricing-page .section-divider {
  height: 1px;
  margin: 2.5rem 0 2rem;
  background: var(--glass-border);
  border: 0;
}

/* Plan tile prices — solid (no gradient text) */
.pricing-page .plan-card .price-display .price-number {
  font-family: var(--mono);
  font-size: 2.4rem;
  font-weight: 600;
  letter-spacing: -0.04em;
  line-height: 1;
  color: var(--text);
}
.pricing-page .plan-card .price-display .price-suffix {
  color: var(--muted);
  font-size: 0.82rem;
  font-weight: 500;
  margin-left: 0.2rem;
}

/* Featured plan tile — solid brand-green border, tinted bg */
.pricing-page .plan-card.is-featured {
  position: relative;
  border: 2px solid var(--accent) !important;
  background: var(--brand-50);
  box-shadow: none;
}
[data-theme="dark"] .pricing-page .plan-card.is-featured {
  background: var(--glass-1);
}

/* Highlights tile — same brand ring */
.pricing-page .highlight-feature {
  background: var(--brand-50);
  border: 1px solid var(--accent);
  box-shadow: none;
}
[data-theme="dark"] .pricing-page .highlight-feature {
  background: var(--glass-1);
}

/* Feature matrix */
.pricing-page .feature-matrix {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}
.pricing-page .feature-matrix thead th {
  font-family: var(--mono);
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-weight: 500;
  text-align: center;
  padding: 0.85rem 0.75rem;
  border-bottom: 1px solid var(--glass-border-strong);
  white-space: nowrap;
}
.pricing-page .feature-matrix thead th:first-child {
  text-align: left;
  text-transform: none;
  letter-spacing: 0;
  font-size: 0.82rem;
  color: var(--text);
  font-family: var(--sans);
}
.pricing-page .feature-matrix thead th.is-featured-col { color: var(--accent); }
.pricing-page .feature-matrix tbody td {
  padding: 0.65rem 0.75rem;
  border-bottom: 1px solid var(--glass-border);
  text-align: center;
  color: var(--text);
  font-size: 0.88rem;
}
.pricing-page .feature-matrix tbody td:first-child {
  text-align: left;
  color: var(--text-2);
}
/* Plan-cards grid: 5 tiles in a row on desktop (Free / Starter /
   Standard / Pro / Enterprise). Collapses gracefully below 1100px. */
.pricing-page .pricing-cards-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); }
@media (max-width: 1100px) {
  .pricing-page .pricing-cards-grid { grid-template-columns: repeat(3, minmax(0, 1fr)) !important; }
}
@media (max-width: 720px) {
  .pricing-page .pricing-cards-grid { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }
}
@media (max-width: 480px) {
  .pricing-page .pricing-cards-grid { grid-template-columns: 1fr !important; }
}

/* Highlight the Standard column with a clearly-blue tint (was a
   legacy Supabase-green leftover that read as washed-out next to the
   indigo brand). Brighter blue + higher opacity makes the column
   instantly findable in both themes. */
.pricing-page .feature-matrix tbody td.is-featured-col {
  background: rgba(91, 156, 255, 0.12);
}
[data-theme="dark"] .pricing-page .feature-matrix tbody td.is-featured-col {
  background: rgba(91, 156, 255, 0.16);
}
.pricing-page .feature-matrix tr.group-row td {
  padding: 1.5rem 0 0.6rem;
  border-bottom: 0;
  text-align: left;
  text-transform: uppercase;
  letter-spacing: 0.12em;
  font-size: 0.78rem;
  font-family: var(--mono);
  color: var(--muted);
  font-weight: 500;
}
.pricing-page .feature-matrix tr.group-row + tr td {
  border-top: 1px solid var(--glass-border-strong);
}
.pricing-page .feature-matrix tr.group-row:first-child td { padding-top: 0.6rem; }
.pricing-page .feature-matrix .check {
  display: inline-block;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--good-bg);
  color: var(--good);
  font-size: 0.7rem;
  line-height: 18px;
  font-weight: 600;
  border: 1px solid var(--good);
}
.pricing-page .feature-matrix .dash {
  color: var(--muted-2);
  font-size: 0.9rem;
}
.pricing-page .feature-matrix .num {
  font-family: var(--mono);
  font-weight: 500;
}
.pricing-page .feature-matrix .num.is-featured-col { color: var(--accent); }

/* FAQ accordion */
.pricing-page .faq-accordion {
  display: flex;
  flex-direction: column;
  gap: 0;
}
.pricing-page .faq-item {
  padding: 0;
  overflow: hidden;
  border-bottom: 1px solid var(--glass-border);
  background: transparent;
  border-radius: 0;
  transition: background 0.12s;
}
.pricing-page .faq-item:first-child { border-top: 1px solid var(--glass-border); }
.pricing-page .faq-item > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  list-style: none;
  list-style-type: none;
  cursor: pointer;
  padding: 1.05rem 1.25rem;
  font-size: 1rem;
  font-weight: 500;
  color: var(--text);
  transition: color 0.12s;
  gap: 1rem;
}
.pricing-page .faq-item > summary::-webkit-details-marker { display: none !important; }
.pricing-page .faq-item > summary::marker { content: "" !important; display: none !important; }
.pricing-page .faq-item > summary::after {
  content: "+";
  font-family: var(--mono);
  font-size: 1.3rem;
  font-weight: 300;
  color: var(--muted);
  line-height: 1;
  width: 1.2rem;
  text-align: center;
  flex-shrink: 0;
  transition: color 0.12s;
}
.pricing-page .faq-item[open] > summary::after {
  content: "−";
  color: var(--accent);
}
.pricing-page .faq-item:hover { background: var(--glass-3); }
.pricing-page .faq-item > summary:hover { color: var(--accent); }
.pricing-page .faq-item .faq-a {
  padding: 0 1.25rem 1.15rem;
  margin: 0;
  font-size: 0.92rem;
  line-height: 1.65;
  color: var(--text-2);
}

/* Custom-pricing band — bordered, no gradient */
.pricing-page .custom-pricing-band {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 1rem;
  padding: 1.75rem 1.5rem;
  margin-bottom: 0.75rem;
  border-radius: var(--radius-lg);
  background: var(--glass-1);
  border: 1px solid var(--glass-border);
}
.pricing-page .custom-pricing-band .custom-pricing-text { max-width: 540px; }
.pricing-page .custom-pricing-band h3 {
  margin: 0 0 0.25rem;
  font-size: 1.15rem;
  font-weight: 600;
  color: var(--text);
  letter-spacing: -0.01em;
}
.pricing-page .custom-pricing-band p {
  margin: 0;
  font-size: 0.9rem;
  color: var(--muted);
  line-height: 1.55;
}
.pricing-page .custom-pricing-band .btn {
  padding: 0.6rem 1.4rem;
  font-size: 0.92rem;
  white-space: nowrap;
}

/* Final CTA — clean centered section. Previously used a heavy
   tinted-box treatment that felt boxed-in. New design: borderless
   centered column with generous vertical rhythm. */
.pricing-page .final-cta {
  max-width: 640px;
  margin: 5rem auto 2rem;
  padding: 2.5rem 1.5rem;
  text-align: center;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0;
  background: transparent;
  border: 0;
  box-shadow: none;
}
.pricing-page .final-cta-headline {
  font-size: clamp(2rem, 4.5vw, 3rem);
  font-weight: 700;
  letter-spacing: -0.035em;
  line-height: 1.1;
  margin: 0 0 0.85rem;
  color: var(--text);
  text-align: center;
}
.pricing-page .final-cta-sub {
  color: var(--text-2);
  margin: 0 auto 2rem;
  font-size: clamp(0.95rem, 1.8vw, 1.05rem);
  line-height: 1.65;
  max-width: 460px;
  text-align: center;
}
.pricing-page .final-cta .cta-row {
  display: flex;
  gap: 0.75rem;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  width: 100%;
}
.pricing-page .final-cta-btn {
  padding: 0.7rem 1.5rem !important;
  font-size: 0.95rem !important;
  font-weight: 500;
}

/* Mobile / tablet: keep the desktop table but make it horizontally scrollable
   with the feature-name column pinned, rather than the old stacked-card reflow
   (which read as a long, repetitive run of all-✓ rows). These rules are no-ops
   on wide screens where the table already fits, so no media query is needed —
   the table only scrolls once the viewport is narrower than its min-width. */
.matrix-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.pricing-page .feature-matrix {
  min-width: 640px;
}
/* Pin the feature-name (first) column so labels stay visible while the plan
   columns scroll; the opaque card-surface background masks the cells sliding
   underneath it. */
.pricing-page .feature-matrix thead th:first-child,
.pricing-page .feature-matrix tbody td:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--glass-1);
}
/* Group-header rows span every column (single colspan cell) — keep them static
   so the pinned-column rule doesn't clip their full-width labels. */
.pricing-page .feature-matrix tr.group-row td {
  position: static;
}

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

/* Upload → AI-identify progress bar (dashboard drop-zone). Slim determinate
 * track + accent fill; an indeterminate shimmer covers the brief moment before
 * the first /api/stats count lands. The universal reduced-motion rule above
 * already neutralizes the transition + shimmer; the targeted rule below keeps
 * the indeterminate state legible (a static, dimmed full bar) in that case. */
.upload-progress {
  display: block;
  width: 100%;
  height: 7px;
  border-radius: 999px;
  background: var(--glass-2, #1F1F1F);
  border: 1px solid var(--glass-border, #242424);
  overflow: hidden;
}
.upload-progress > .bar {
  display: block;
  height: 100%;
  width: 0;
  border-radius: inherit;
  background: var(--accent, #4750DD);
  transition: width 0.5s ease;
}
.upload-progress.is-indeterminate > .bar {
  width: 40%;
  background: linear-gradient(90deg, transparent, var(--accent, #4750DD), transparent);
  animation: upload-indeterminate 1.1s ease-in-out infinite;
}
@keyframes upload-indeterminate {
  0%   { transform: translateX(-120%); }
  100% { transform: translateX(320%); }
}
@media (prefers-reduced-motion: reduce) {
  .upload-progress.is-indeterminate > .bar { width: 100% !important; opacity: 0.55; }
}

/* ── Confirm page redesign ─────────────────────────────────────────────────
 * A subtle gray working canvas (scoped — does NOT change the global --bg-1, so
 * the topbar / modals / other pages stay untouched) holding a LIGHT zoomable
 * card viewer, a prominent AI-match card, labeled sections, a card-position
 * progress bar, and a sticky action bar. */
.confirm-page {
  background: var(--glass-2);
  border-radius: var(--radius-lg);
  padding: 1.25rem 1.5rem;
}
[data-theme="dark"] .confirm-page { background: #161618; }
/* ~40% card viewer / ~60% info+widgets (2:3 flex) — scoped to the MAIN row so it
 * doesn't leak into the form's own two-column rows (Set/Card#, Sub-Type/Condition…). */
.confirm-page .confirm-main-row { gap: 1.25rem; align-items: flex-start; }
/* Left column = scanned card, pinned in place: as the user scrolls the right
 * column (card details + price + comps) the image stays in view the whole
 * time. align-self:flex-start keeps it content-height so sticky engages;
 * max-height + overflow guards the rare case of an image taller than the
 * viewport. Disabled on mobile (the app-shell handles scrolling there). */
.confirm-page .confirm-left {
  flex: 2 1 0; min-width: 0; display: flex; flex-direction: column; gap: 1rem;
}
.confirm-page .confirm-main-row > div:last-child { flex: 3 1 0; min-width: 0; }

/* Header row — ONE line: Back (left), the card-position progress filling the
 * middle, and the page title on the right. Reclaims the two header rows the
 * full-width Back bar + standalone progress used to take (the app-shell's
 * flex-column scroll was stretching the inline Back link to full width).
 * Wraps on narrow screens. */
.confirm-page .confirm-header {
  display: flex; align-items: center;
  gap: 1.25rem; flex-wrap: wrap; margin-bottom: 0.5rem;
}
.confirm-page .confirm-back { flex: 0 0 auto; }
.confirm-page .confirm-header .confirm-progress {
  flex: 1 1 auto; min-width: 140px; max-width: none; margin: 0;
}

/* Card-position progress indicator */
.confirm-progress { display: flex; align-items: center; gap: 0.75rem; margin: 0.3rem 0 1.25rem; max-width: 520px; }
.confirm-progress-label { font-size: 0.82rem; color: var(--muted); white-space: nowrap; }
.confirm-progress-track { flex: 1 1 0; height: 6px; border-radius: 999px; background: var(--glass-3); overflow: hidden; }
.confirm-progress-bar { display: block; height: 100%; border-radius: inherit; background: var(--accent); transition: width 0.3s ease; }

/* Light card viewer (left column) */
.confirm-page .card-viewer {
  background: var(--glass-1); border: 1px solid var(--glass-border-strong);
  border-radius: var(--radius-lg); padding: 0.85rem;
  display: flex; flex-direction: column; gap: 0.75rem;
}
.confirm-page .card-viewer-head { display: flex; align-items: center; justify-content: space-between; }
.confirm-page .viewer-title { font-weight: 600; font-size: 0.9rem; }
/* Scan-quality badge — Red (bad) / Yellow (ok) / Green (good). NOTE: --good is
 * the indigo accent in this theme, so green is explicit here. */
.confirm-page .scan-badge {
  font-size: 0.7rem; font-weight: 600; border: 1px solid;
  border-radius: 999px; padding: 0.12rem 0.55rem;
}
.confirm-page .scan-good { color: #16A34A; border-color: #16A34A; background: rgba(22,163,74,0.12); }
.confirm-page .scan-ok   { color: var(--warn); border-color: var(--warn); background: var(--warn-bg); }
.confirm-page .scan-bad  { color: var(--bad); border-color: var(--bad); background: var(--bad-bg); }
.confirm-page .card-viewer-stage { display: flex; justify-content: center; }
.confirm-page .card-viewer .scan {
  width: 100%; max-width: 100%; margin: 0 auto;
  border: 1px solid var(--glass-border); border-radius: var(--radius); cursor: zoom-in;
}
.confirm-page .card-viewer-toolbar {
  display: flex; align-items: center; justify-content: space-between; gap: 0.5rem;
  border-top: 1px solid var(--glass-border); padding-top: 0.6rem;
}
.confirm-page .viewer-tool {
  width: 30px; height: 30px; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--glass-border); border-radius: var(--radius-sm);
  background: var(--glass-2); color: var(--text); cursor: pointer;
}
.confirm-page .viewer-tool:hover { background: var(--glass-3); }
.confirm-page .seg-toggle { display: inline-flex; border: 1px solid var(--glass-border); border-radius: 999px; padding: 0.15rem; background: var(--glass-2); }
.confirm-page .seg-btn {
  border: 0; background: transparent; color: var(--muted); cursor: pointer;
  font-size: 0.8rem; padding: 0.2rem 0.7rem; border-radius: 999px;
}
.confirm-page .seg-btn.active { background: var(--glass-1); color: var(--text); border: 1px solid var(--glass-border); }

/* Vision notes / detected attributes — consolidated INTO the scanned-card card
 * as a section under the viewer (hairline divider), so the left column is one
 * tidy card that fits the viewport without its own scrollbar. */
.confirm-page .card-viewer-notes {
  border-top: 1px solid var(--glass-border);
  padding-top: 0.7rem;
  display: flex; flex-direction: column; gap: 0.45rem;
}
.confirm-page .attr-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; }
.confirm-page .attr-chip {
  background: var(--glass-2); color: var(--text-2); border: 1px solid var(--glass-border);
  border-radius: 999px; padding: 0.2rem 0.6rem; font-size: 0.76rem; font-weight: 500;
}

/* ── Pricing stack: Price Decision / Recently Sold / eBay active listings live
 *    in the RIGHT column, under the card-details form, stacked top-to-bottom.
 *    The scanned image is pinned on the left (.confirm-left is sticky), so the
 *    user scrolls the right column to review price + comps with the card in
 *    view the whole time. Comps render in full — reviewing them is just
 *    scrolling. */
.confirm-page .pricing-stack { margin-top: 1rem; }
.confirm-page .pricing-stack > div { margin-bottom: 1rem; }
.confirm-page .pricing-stack > div:last-child { margin-bottom: 0; }
.confirm-page .pricing-stack > div > h3 { margin: 0 0 0.5rem; font-size: 1rem; }

.confirm-page .confirm-form-card { margin-bottom: 1rem; }
.confirm-page .confirm-form-card h3 { margin-top: 0; }
/* Form controls fill their column (border-box so width:100% never overflows the
 * card); number (Product ID) + checkbox (Foil) keep their natural size. */
.confirm-page .confirm-form-card .field { min-width: 0; }
.confirm-page .confirm-form-card .field select,
.confirm-page .confirm-form-card .field input:not([type="number"]):not([type="checkbox"]) {
  width: 100%; box-sizing: border-box;
}
.confirm-actionbar .btn.ghost { background: transparent; border: 1px solid var(--glass-border); color: var(--text); }
.confirm-actionbar .btn.ghost:hover { background: var(--glass-2); }
.confirm-actionbar .confirm-continue { border: 1px solid var(--accent); color: var(--accent); background: transparent; }
.confirm-actionbar .confirm-continue:hover { background: var(--good-bg); }

/* Enter-key default submit: present (so Enter = Confirm & Price) but invisible */
.confirm-page .enter-default {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  border: 0; opacity: 0; overflow: hidden; pointer-events: none;
}

/* Sticky floating action bar (mirrors the dashboard toolbar pattern) */
.confirm-actionbar {
  position: sticky; bottom: 0.5rem; z-index: 30;
  display: flex; align-items: center; justify-content: space-between;
  gap: 0.75rem; flex-wrap: wrap; margin-top: 1.25rem;
  padding: 0.6rem 0.85rem;
  background: var(--glass-1); border: 1px solid var(--glass-border-strong);
  border-radius: var(--radius); box-shadow: 0 4px 16px rgba(0,0,0,0.10);
}
.confirm-actionbar-nav, .confirm-actionbar-actions { display: flex; gap: 0.5rem; align-items: center; flex-wrap: wrap; }
.confirm-actionbar .btn.is-disabled { opacity: 0.4; pointer-events: none; }
/* Keyboard-dismiss button: mobile-only (revealed in the confirm @media). */
.kb-done { display: none; }

/* Zoom lightbox */
.zoom-overlay {
  position: fixed; inset: 0; z-index: 1000; background: rgba(0,0,0,0.85);
  display: flex; align-items: center; justify-content: center; padding: 2rem; cursor: zoom-out;
}
.zoom-overlay[hidden] { display: none; }
.zoom-overlay img { max-width: 95vw; max-height: 95vh; border-radius: var(--radius); box-shadow: 0 8px 40px rgba(0,0,0,0.5); }

/* ── Desktop app-shell (≥801px): the confirm page fills the viewport and owns
 *    its own scroll. The scanned card is a STATIC pane on the left (image
 *    scales to fit — no inner scrollbar); ONLY the right column scrolls (card
 *    details → price → recently sold → eBay comps); and Confirm/Price is a
 *    solid toolbar pinned flush at the bottom (not a floating pill). Everything
 *    fits on one screen. :has() bounds the shell's main area — no base.html. */
@media (min-width: 801px) {
  .main:has(.confirm-page) { overflow: hidden; padding: 0; }
  .confirm-page {
    height: 100vh; max-height: 100vh; overflow: hidden;
    display: flex; flex-direction: column;
    border-radius: 0; padding: 0;
  }
  .confirm-page .confirm-scroll {
    flex: 1 1 auto; min-height: 0;
    display: flex; flex-direction: column; overflow: hidden;
    padding: 1.1rem 1.6rem 0;
  }
  .confirm-page .confirm-main-row {
    flex: 1 1 auto; min-height: 0; overflow: hidden;
    align-items: stretch;
  }
  /* Left pane: static, never scrolls; the image scales to fit the space. */
  .confirm-page .confirm-left { min-height: 0; max-height: 100%; overflow: hidden; }
  .confirm-page .confirm-left .card-viewer { flex: 1 1 auto; min-height: 0; }
  .confirm-page .card-viewer-stage { flex: 1 1 auto; min-height: 0; align-items: center; }
  .confirm-page .card-viewer .scan {
    width: auto; max-width: 100%; max-height: 100%; object-fit: contain;
  }
  /* Right pane: the only scroller. */
  .confirm-page .confirm-main-row > div:last-child {
    min-height: 0; max-height: 100%; overflow-y: auto;
    padding-right: 0.4rem; padding-bottom: 1rem;
  }
  /* Action bar: solid toolbar flush at the bottom. */
  .confirm-page .confirm-actionbar {
    position: static; flex: 0 0 auto;
    margin: 0; border-radius: 0; box-shadow: none;
    border: none; border-top: 1px solid var(--glass-border-strong);
    padding: 0.6rem 1.6rem;
  }
}

@media (max-width: 800px) {
  .confirm-page .row { flex-direction: column; }
  .confirm-page .confirm-main-row { flex-direction: column; }
  .confirm-page .card-viewer { flex: 1 1 auto; max-width: 100%; }

  /* Cap the scanned card so the override fields + prices are a short scroll
     away, not a full screen down. Tap-to-zoom still gives the close look. */
  .confirm-page .card-viewer .scan { width: auto; max-width: 100%; max-height: 46vh; }

  /* Tighter cards + legible tables on a narrow screen — less dead space,
     less scrolling, prices readable at a glance. */
  .confirm-page .card,
  .confirm-page .confirm-form-card,
  .confirm-page .card-viewer { padding: 0.85rem; }
  /* Comps tables (active eBay listings): smaller + tighter so each row reads
     as one glanceable line, not a towering multi-line block. */
  .confirm-page table { font-size: 0.78rem; }
  .confirm-page th, .confirm-page td { padding: 0.35rem 0.4rem; line-height: 1.3; }
  .confirm-page table a { white-space: nowrap; }

  /* Floating action bar: a COMPACT grid, never the stacked full-width giants
     that were burying the content. Nav (Prev/Next/Flag) on one row; Save Draft
     + Confirm & Continue share a row; Confirm & Price is the full-width primary. */
  /* App-shell layout: .confirm-page fills the viewport as a flex column; the
     content scrolls inside .confirm-scroll (a bounded region), and this bar is a
     constant, non-shrinking row pinned below it. The scroll area ENDS at the
     bar's top, so the last button can never hide behind it — no position:fixed,
     so no iOS WKWebView mis-alignment. */
  .confirm-actionbar {
    position: static; flex: 0 0 auto; z-index: 40;
    flex-direction: column; align-items: stretch;
    gap: 0.4rem; margin: 0; border-radius: 0;
    padding: 0.5rem 0.7rem calc(0.5rem + env(safe-area-inset-bottom));
    border: none; border-top: 1px solid var(--glass-border-strong);
    background: var(--bg-1);
  }
  .confirm-actionbar-nav { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 0.35rem; }
  .confirm-actionbar-actions { display: grid; grid-template-columns: 1fr 1fr; gap: 0.35rem; }
  .confirm-actionbar .btn {
    width: 100%; font-size: 0.82rem; padding: 0.5rem 0.3rem; min-height: 40px;
    justify-content: center; white-space: nowrap;
  }
  .confirm-actionbar .btn.primary {
    grid-column: 1 / -1; font-size: 0.95rem; min-height: 46px; padding: 0.7rem;
  }

  /* .confirm-page is the app-shell: a full-viewport flex column that does NOT
     scroll itself (overflow hidden). Its child .confirm-scroll holds the content
     and scrolls; the action bar is the constant row beneath it. */
  .confirm-page {
    display: flex; flex-direction: column;
    height: 100dvh; max-height: 100dvh; overflow: hidden;
    padding: 0;
  }
  .confirm-scroll {
    flex: 1 1 auto; min-height: 0; overflow-y: auto;
    -webkit-overflow-scrolling: touch;
    padding: 0.8rem 0.55rem 0.8rem;
  }
  /* Keep the shell from double-scrolling — SCOPED to the confirm page only via
     :has(), so every other embedded page (dashboard, revenue, settings) still
     scrolls normally. Zero the vertical padding so the page sits flush. */
  body.embedded-app:has(.confirm-page),
  body.embedded-app .main:has(.confirm-page) {
    height: 100dvh; overflow: hidden; padding-top: 0; padding-bottom: 0;
  }
  body.embedded-app .main { padding-left: 0.45rem; padding-right: 0.45rem; }

  /* Plain mobile web (Safari/Chrome — NO embedded-app cookie): same app-shell,
     but here the mobile .topbar occupies the first grid row, so the full-viewport
     `.confirm-page { height: 100dvh }` overflows the .main row that sits BELOW
     the topbar — .main then scrolls the Confirm/Price action bar down under
     Safari's bottom toolbar (the reported bug). Bound the shell to the VISIBLE
     viewport (dvh) and let the confirm-page fill the .main row (height: 100%, so
     it accounts for the topbar) — the static, safe-area-padded action bar then
     always sits on screen. Scoped via :has() so only the confirm page changes;
     every other mobile page keeps its normal 100vh scroll. */
  .app:has(.confirm-page) { height: 100dvh; }
  .main:has(.confirm-page) { overflow: hidden; padding: 0; }
  .main:has(.confirm-page) .confirm-page { height: 100%; max-height: 100%; }

  /* ── Polish pass: tighter, cleaner, more intentional on a phone ──────────── */
  .confirm-page .card,
  .confirm-page .card-viewer { padding: 0.8rem; margin-bottom: 0.5rem; border-radius: 14px; box-shadow: none; }
  /* Mobile: the left image is NOT pinned (the app-shell's .confirm-scroll
     handles scrolling), so the whole page flows normally and the pricing
     cards stack under the form as usual. */
  .confirm-page .confirm-left { position: static; max-height: none; overflow: visible; }
  .confirm-page .card h3 { font-size: 1rem; margin: 0 0 0.7rem; letter-spacing: -0.01em; }
  .confirm-page .card-viewer { padding: 0.7rem; }
  /* Cap the scanned card so it informs without eating the screen. */
  .confirm-page .card-viewer-stage { max-height: 30vh; align-items: center; }
  .confirm-page .card-viewer .scan { max-height: 30vh; width: auto; }
  /* Quiet, uppercase field labels read as "designed", not "web form". */
  .confirm-page label {
    font-size: 0.7rem; font-weight: 600; letter-spacing: 0.04em;
    text-transform: uppercase; color: var(--muted-2);
  }
  .confirm-page .field { margin-bottom: 0.6rem; }
  .confirm-page input, .confirm-page select, .confirm-page textarea {
    padding: 0.65rem 0.7rem; font-size: 0.95rem; border-radius: 10px;
  }
  /* The touch-target rule fattens EVERY checkbox to 44×44 — on the dense
     confirm form that turns FOIL + the publish boxes into giant tiles. Restore
     a real checkbox size; the label text carries the tap area. */
  .confirm-page input[type="checkbox"],
  .confirm-page input[type="radio"] {
    min-width: 22px; min-height: 22px; width: 22px; height: 22px;
    flex: 0 0 auto; margin: 0; padding: 0;
  }
  /* Tighten the 'Recent sold price' explainer + other card body copy. */
  .confirm-page .card p { font-size: 0.88rem; line-height: 1.45; }
  /* Mobile-only "Done" button to dismiss the iOS number keyboard. */
  .confirm-page .kb-done { display: inline-flex !important; align-items: center; }
  /* (bar position/padding/border handled above; just the inner button size) */
  .confirm-actionbar .btn { min-height: 38px; }
  /* Row 1: icon nav (‹ › flag) + Save Draft filling the rest of the row. */
  .confirm-actionbar-nav { display: flex; gap: 0.4rem; }
  .confirm-actionbar-nav .btn.ghost { flex: 0 0 auto; width: auto; min-width: 48px; padding: 0.5rem 0.4rem; font-size: 1.1rem; }
  .confirm-actionbar-nav .btn.secondary { flex: 1; font-size: 0.82rem; }
  /* Row 2: Confirm | Price side by side (Price stays the prominent primary). */
  .confirm-actionbar-actions .btn { min-height: 46px; }
  .confirm-actionbar-actions .btn.primary { grid-column: auto; }
}

/* ── Global collapsible sidebar ────────────────────────────────────────────
 * Topbar toggle collapses the rail for full-width work; remembered in
 * localStorage with a pre-paint boot (base.html) to avoid a flash. Desktop only
 * (mobile uses the translateX drawer). */
.rail-toggle {
  display: inline-flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: var(--radius-sm);
  border: 1px solid var(--glass-border); background: var(--glass-2);
  color: var(--text); cursor: pointer; font-size: 1rem; line-height: 1;
  transition: background 0.12s, border-color 0.12s;
}
.rail-toggle:hover { background: var(--glass-3); }
/* The topbar re-expand button stays hidden while the rail is open; it only
   appears once the rail is collapsed (the in-rail .rail-collapse does the
   collapsing, so we never show two toggles at once). */
.rail-expand {
  display: none;
  position: fixed;
  top: 0.6rem;
  left: 0.6rem;
  z-index: 60;
}
@media (min-width: 801px) {
  /* Collapsed = a ~60px ChatGPT-style ICON RAIL (kept visible, not width 0). */
  html.rail-collapsed .app { grid-template-columns: var(--nav-w-collapsed) 1fr; }
  html.rail-collapsed .rail {
    padding: 1rem 0.5rem;
    overflow: visible;          /* let hover tooltip pills escape the narrow rail */
  }
  /* Top: hide the expanded brand + collapse button; show the hover-swap toggle. */
  html.rail-collapsed .rail-header { justify-content: center; padding: 0.15rem 0 0.75rem; }
  html.rail-collapsed .rail-brand,
  html.rail-collapsed .rail-collapse { display: none; }
  html.rail-collapsed .rail-open { display: inline-flex; }
  /* Queue items: centered icon only; the label shows as a hover pill (above),
     the count badge is hidden. */
  html.rail-collapsed .rail-queue a { justify-content: center; padding: 0.5rem 0; gap: 0; }
  html.rail-collapsed .rail-queue a .count { display: none; }
  /* Account footer: avatar only; hide the name + caret; widen the popup menu so
     the upward-opening panel is usable from the narrow rail. */
  html.rail-collapsed #account-menu-trigger { justify-content: center; padding: 0.4rem 0; }
  html.rail-collapsed #account-menu-trigger .name,
  html.rail-collapsed #account-menu-trigger .caret { display: none; }
  html.rail-collapsed #account-menu-panel { left: 0; right: auto; width: 230px; }
}
@media (max-width: 800px) { .rail-toggle { display: none; } }

/* =====================================================================
 * Wide-viewport breathing room. The existing CSS only has down-breakpoints
 * (≤1100, ≤800, ≤720, ≤480). On Mac Safari at 1920×1080 the topbar nav
 * + stats strip + listings layout all read tight. Two breakpoints:
 *   ≥1200px — looser topbar/stats/page-header gaps
 *   ≥1600px — center the main column with a max-width so it doesn't sprawl
 * ===================================================================== */
@media (min-width: 1200px) {
  .topbar { padding-left: 1.5rem; padding-right: 1.5rem; }
  .topbar nav { gap: 0.5rem; }      /* was 0.15rem */
  .stats { gap: 1rem; }              /* was 0.75rem */
  .page-header { padding-left: 0.5rem; padding-right: 0.5rem; }
}

/* Wide-viewport scaling (24"-27" iMacs at 2000-2300px logical width).
 * The MacBook (1500ish viewport) looks fine because the main grid track
 * fills naturally — 4 listing cards per row at min-width 280 + comfortable
 * gaps. On a 24" iMac the same rules produce 7+ cards per row, each
 * proportionally smaller, with the same 0.85rem gap that feels generous
 * at 1500px but cramped at 2200px. The fix scales card sizes, gaps,
 * and outer padding with the viewport so the layout breathes
 * proportionally. */
@media (min-width: 1600px) {
  :root, [data-theme="dark"] {
    --nav-w: 256px;
  }
  .main {
    padding: 2rem 3rem 4rem;
    /* Wider cap than before (1800 vs 1600) so wide-iMac users don't see
       300px+ empty gutters on each side of the main column. */
    max-width: 1800px;
    margin-left: auto;
    margin-right: auto;
  }
  .grid { gap: 1.1rem; }              /* was 0.85rem */
  .listing { padding: 1.05rem; }      /* was 0.9rem */
  .stats { gap: 1.1rem; margin-bottom: 1.75rem; }
  .page-header { gap: 1.25rem; margin-bottom: 1.75rem; }

  /* Topbar pill spacing — user reported the LIVE / SCANNER / credit /
     account pills touching at this viewport. The .topbar's `gap: 1rem`
     was too tight; bump so the right-side group has visible breathing. */
  .topbar { gap: 1.5rem; }
  .topbar nav { gap: 0.4rem; }
  .topbar .status-pill,
  .credit-pill {
    padding: 0.3rem 0.75rem;
  }
}

/* Very-wide viewports (27"+ iMacs / Studio Displays / external 4K at
 * 2400-3000px logical width). Same shape as the 1600 block but with
 * more headroom — keeps the 1:1 visual density consistent with what
 * the MacBook user sees. */
@media (min-width: 2000px) {
  :root, [data-theme="dark"] {
    --nav-w: 272px;
  }
  .main {
    padding: 2.5rem 4.5rem 4rem;
    max-width: 2200px;
  }
  .grid { gap: 1.4rem; }
  .listing { padding: 1.35rem; gap: 0.95rem; }
  .stats { gap: 1.6rem; margin-bottom: 2rem; }
  .stat {
    padding: 1rem 1.4rem;            /* was 0.75rem 1rem */
    font-size: 0.82rem;              /* was 0.72rem */
  }
  .page-header { gap: 1.5rem; margin-bottom: 2rem; }

  /* Topbar at very-wide: bump font-sizes proportionally + larger pill
     paddings so the right-side group reads at the same physical size
     as it does on a smaller MacBook. */
  .topbar { gap: 1.75rem; }
  .topbar h1 { font-size: 1.05rem; }     /* was 1rem */
  .topbar nav { gap: 0.6rem; }
  .topbar nav a { font-size: 0.92rem; padding: 0.5rem 0.9rem; }
  .topbar .status-pill,
  .credit-pill {
    padding: 0.35rem 0.85rem;
    font-size: 0.74rem;
  }
  .credit-pill { font-size: 0.82rem; }   /* keep credit count slightly bigger */
}

/* Ultra-wide (32"+ Pro Display XDR or external ultrawide). Cap the
 * content so it doesn't sprawl into one giant row across 3000+ pixels. */
@media (min-width: 2600px) {
  .main { max-width: 2400px; }
}

/* =====================================================================
 * List-mode row density. The Item 8 (audit) layout was too tight per
 * user feedback: badge, condition, price, edit, publish, trash all
 * crowded the right edge with only 0.35rem between buttons. Bump:
 *   - Row vertical padding 0.6 → 0.75
 *   - Row horizontal padding 0.9 → 1.25
 *   - Row inter-element gap 1 → 1.25
 *   - Actions group gap 0.35 → 0.65, plus a left-pad to separate from
 *     the price input
 *   - Action buttons larger (0.4×0.8 padding instead of 0.3×0.6,
 *     0.82rem font instead of 0.75rem)
 * ===================================================================== */
.grid.list-mode .listing {
  padding: 0.75rem 1.25rem;
  gap: 1.25rem;
}
.grid.list-mode .listing .actions.list-only {
  gap: 0.5rem;
  margin-left: auto;
  padding-left: 1rem;
}
.grid.list-mode .listing .actions.list-only button,
.grid.list-mode .listing .actions.list-only .btn {
  padding: 0.4rem 0.8rem;
  font-size: 0.82rem;
}
/* Stats row (badge + condition + price) in list mode — bump from
   0.65rem inter-item to 0.9rem so the price input separates clearly
   from the badge. */
.grid.list-mode .listing .stats-row {
  gap: 0.9rem;
}

/* ============================================================
   /setup — dedicated onboarding wizard (chrome-free, focused)
   Standalone page (ui/setup.html), not the dashboard shell.
   Reuses .pricing-page .eyebrow / .grad-text, .btn, .card,
   .warn-box, and the tcg-btn-spin keyframe.
   ============================================================ */
body.setup-page {
  min-height: 100vh;
  margin: 0;
  background: var(--bg-0);
  color: var(--text);
  display: flex;
  flex-direction: column;
}
/* The wizard toggles steps/spinner/panels via the [hidden] attribute. The
   custom display rules below (e.g. .setup-spinner{display:inline-flex}) would
   otherwise out-specify the UA [hidden]{display:none}, so re-assert it. */
body.setup-page [hidden] { display: none !important; }

/* slim top bar: brand left, "skip for now" right */
.setup-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1rem 1.5rem;
  border-bottom: 1px solid var(--glass-border);
}
.setup-brand {
  display: inline-flex;
  align-items: center;
  gap: 0.55rem;
  text-decoration: none;
  color: var(--text);
  font-weight: 700;
  font-size: 0.95rem;
  letter-spacing: -0.02em;
}
.setup-brand .brand-logo { height: 22px; width: auto; margin: 0; flex: none; }
.setup-skip { text-decoration: none; font-size: 0.85rem; white-space: nowrap; }
.setup-skip:hover { color: var(--text); }

/* centered focus column */
.setup-wrap {
  flex: 1;
  width: 100%;
  max-width: 640px;
  margin: 0 auto;
  padding: 2.75rem 1.5rem 4rem;
  text-align: center;
}

/* numbered progress stepper */
.setup-steps {
  list-style: none;
  display: flex;
  justify-content: center;
  gap: 0;
  padding: 0;
  margin: 0 auto 2.75rem;
  max-width: 460px;
}
.setup-steps li {
  position: relative;
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.45rem;
  color: var(--muted-2);
  font-size: 0.72rem;
  font-weight: 500;
  letter-spacing: 0.01em;
}
.setup-steps li .num {
  position: relative;
  z-index: 1;
  width: 2rem;
  height: 2rem;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  border: 2px solid var(--glass-border-strong);
  background: var(--bg-0);
  color: var(--muted-2);
  font-family: var(--mono);
  font-size: 0.85rem;
  font-weight: 600;
  transition: border-color .2s, background .2s, color .2s;
}
.setup-steps li .lbl { position: relative; z-index: 1; }
/* connector line between circles */
.setup-steps li::before {
  content: "";
  position: absolute;
  top: 1rem;
  right: 50%;
  width: 100%;
  height: 2px;
  background: var(--glass-border);
  z-index: 0;
}
.setup-steps li:first-child::before { display: none; }
.setup-steps li.is-active { color: var(--text); }
.setup-steps li.is-active .num { border-color: var(--accent); color: var(--accent); }
.setup-steps li.is-done { color: var(--muted); }
.setup-steps li.is-done .num { border-color: var(--accent); background: var(--accent); color: #fff; }
.setup-steps li.is-done::before { background: var(--accent); }

/* step content */
.setup-step { animation: fade-up .35s ease both; }
.setup-title {
  font-size: clamp(1.85rem, 4.6vw, 2.6rem);
  font-weight: 700;
  letter-spacing: -0.025em;
  line-height: 1.1;
  margin: 0.5rem 0 0.65rem;
}
.setup-step .lead {
  font-size: 1.02rem;
  color: var(--muted);
  line-height: 1.55;
  max-width: 480px;
  margin: 0 auto;
}
.setup-actions { margin-top: 1.6rem; }
.setup-cta { font-size: 1rem; padding: 0.72rem 1.6rem; }
.setup-fine { font-size: 0.8rem; margin-top: 1.1rem; }

/* in-progress spinner row */
.setup-spinner {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  margin-top: 1.6rem;
  color: var(--muted);
  font-size: 0.95rem;
}
.setup-spinner .spin {
  width: 1.1rem;
  height: 1.1rem;
  border: 2px solid var(--glass-border-strong);
  border-top-color: var(--accent);
  border-radius: 50%;
  animation: tcg-btn-spin .7s linear infinite;
}

/* ZIP + guidance panels sit left-aligned inside the centered column */
.setup-panel { margin: 1.5rem auto 0; max-width: 460px; text-align: left; }
.setup-field { display: block; font-size: 0.9rem; color: var(--text); }
.setup-field input {
  display: block;
  width: 100%;
  margin-top: 0.45rem;
  box-sizing: border-box;
}

/* collapsed "customize" disclosure */
.setup-customize { margin: 2rem auto 0; max-width: 480px; text-align: left; }
.setup-customize > summary {
  cursor: pointer;
  font-size: 0.88rem;
  padding: 0.5rem 0;
  user-select: none;
}
.setup-customize > summary:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.setup-custom-grid {
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
  margin-top: 0.9rem;
  padding: 1rem 1.1rem;
  border: 1px solid var(--glass-border);
  border-radius: var(--radius);
  background: var(--bg-1);
}
.setup-field-inline {
  display: block;
  font-size: 0.88rem;
  color: var(--text-2);
  line-height: 1.6;
}
.setup-field-inline input[type="text"] { width: 5rem; display: inline-block; }
.setup-field-inline select { display: inline-block; width: auto; }
/* calculated-shipping info note (not an input — reads as guidance) */
.setup-calc-note {
  margin: 0;
  padding: 0.6rem 0.75rem;
  border-radius: var(--radius-sm);
  background: var(--bg-0);
  border: 1px dashed var(--glass-border-strong);
  color: var(--muted);
}

@media (max-width: 560px) {
  .setup-wrap { padding: 2rem 1.1rem 3rem; }
  .setup-steps { margin-bottom: 2.25rem; }
  .setup-steps li { font-size: 0.66rem; }
  .setup-header { padding: 0.85rem 1.1rem; }
}

/* ============================================================
   App (dashboard) shell — neutral / monochrome accent.
   The signed-in app drops the indigo brand for grey, ChatGPT/Claude
   style: primary ("Publish") buttons become a high-contrast neutral
   and accent links/focus rings go grey. Marketing, landing, and auth
   shells are NOT inside .app, so they keep the indigo brand.
   ============================================================ */
.app {
  --accent: #3A3A3A;          /* neutral accent: links, focus rings, highlights */
  --accent-2: #1F1F1F;
  --accent-grad: #3A3A3A;
}
[data-theme="dark"] .app {
  --accent: #B5B5B5;
  --accent-2: #D4D4D4;
  --accent-grad: #B5B5B5;
}
/* Respect the forced high-contrast accent for users who need it — re-assert
   inside .app, since the .app vars above would otherwise win by inheritance
   proximity over the :root prefers-contrast override. */
@media (prefers-contrast: more) {
  [data-theme="light"] .app { --accent: #0000A0; --accent-2: #00006B; --accent-grad: #0000A0; }
  [data-theme="dark"]  .app { --accent: #FFFF00; --accent-2: #FFD700; --accent-grad: #FFFF00; }
}
/* Primary buttons: a neutral that inverts with the theme — near-black + white
   text on light, light grey + dark text on dark (ChatGPT/Claude style). */
.app button.primary, .app .btn.primary {
  background: var(--text-2);
  color: var(--bg-1);
  border-color: var(--text-2);
}
.app button.primary:hover, .app .btn.primary:hover {
  background: var(--text);
  border-color: var(--text);
  color: var(--bg-1);
}

/* =====================================================================
 * Embedded-app mode — pages opened inside the native iOS app's web sheets
 * (marked by the `etcg_app=1` cookie -> body.embedded-app). The app brings
 * its own chrome, so the site's rail/topbar/PWA hint disappear and content
 * renders edge-to-edge like a window-within-window, not "the website".
 * ===================================================================== */
body.embedded-app .rail,
body.embedded-app .topbar,
body.embedded-app .a2hs-hint { display: none !important; }
body.embedded-app .app {
  grid-template-columns: 1fr;
  grid-template-rows: 1fr;
  grid-template-areas: "main";
  max-width: 100vw;
}
body.embedded-app .main { padding: 1rem 1rem 2rem; }

.nav-badge { display:inline-block; min-width:1.1rem; padding:0 0.35rem; border-radius:999px; background:var(--accent); color:#fff; font-size:0.68rem; line-height:1.15rem; text-align:center; vertical-align:middle; }
