*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }

/* Ensure HTML hidden attribute always wins over display rules */
[hidden] { display: none !important; }

/* ═══════════════════════════════════════════════════════════════════════
   ING Listing Engine — design tokens
   One source of truth for colour, space, type, radius, elevation and
   motion. Component rules below consume these; they should not invent
   raw hex values or one-off pixel gaps.
   ═══════════════════════════════════════════════════════════════════════ */
:root {
  /* ── Type families ────────────────────────────────────────────────────
     No web fonts: the app is served from localhost and must render the
     same with no network at all. These stacks resolve to the best face
     already on the machine and degrade in order, so the UI never falls
     back to Times. */
  --font-sans:
    "Segoe UI Variable Text", "Segoe UI", Inter, -apple-system,
    "SF Pro Text", system-ui, Roboto, "Helvetica Neue", Arial, sans-serif;
  /* Display face for headings. On Windows 11 this is the optical size cut
     for large text — tighter spacing, finer joints — which is what makes a
     heading read as typeset rather than as scaled-up body copy. */
  --font-display:
    "Segoe UI Variable Display", "Segoe UI Semibold", "Segoe UI", Inter,
    -apple-system, "SF Pro Display", system-ui, "Helvetica Neue", Arial, sans-serif;
  --font-mono:
    ui-monospace, "Cascadia Mono", "Cascadia Code", Consolas,
    SFMono-Regular, Menlo, monospace;

  /* ── Type scale ───────────────────────────────────────────────────────
     Integer sizes only. Fractional px sizes get rasterised between
     hinting steps, which is the difference between text that looks crisp
     and text that looks slightly out of focus. */
  --fs-3xs: 10px;
  --fs-2xs: 11px;
  --fs-xs: 12px;
  --fs-sm: 13px;
  --fs-base: 14px;
  --fs-md: 15px;
  --fs-lg: 17px;
  --fs-xl: 20px;
  --fs-2xl: 24px;
  --fs-3xl: 28px;
  --fs-4xl: 34px;
  --fs-5xl: 42px;
  --fs-display: 56px;

  /* Weights. 550/650 are real weights on a variable face and land between
     the stock steps, which is where UI text wants to sit. */
  --fw-normal: 400;
  --fw-medium: 550;
  --fw-semibold: 650;
  --fw-bold: 700;
  --fw-heavy: 800;

  /* Line heights — tighter as text gets larger, the way type is set. */
  --lh-tight: 1.08;
  --lh-snug: 1.25;
  --lh-normal: 1.5;
  --lh-relaxed: 1.62;

  /* Tracking. Large type needs negative tracking to stop it looking loose;
     small caps need positive tracking to stay readable. */
  --tracking-display: -.03em;
  --tracking-tight: -.02em;
  --tracking-snug: -.01em;
  --tracking-normal: 0;
  --tracking-caps: .08em;
  --tracking-caps-wide: .13em;

  /* ── Brand ────────────────────────────────────────────────────────────
     Deep teal is the structure; gold is the single accent. Nothing else in
     the app gets to be the loudest colour on a screen. */
  --teal-950: #04171a;
  --teal-900: #072429;
  --teal-850: #0a2c31;
  --teal-800: #0c3438;
  --teal-700: #115057;
  --teal-600: #1a6b73;
  --teal-500: #2a8b93;
  --teal-200: #c3dde0;
  --teal-100: #e3eeef;
  --teal-50: #f1f7f7;
  --gold-dark: #946d1f;
  --gold-deep: #7d5a15;
  --gold: #c79a36;
  --gold-light: #e0bb63;
  --gold-pale: #f0d38b;
  --gold-soft: #fff4d7;
  --gold-tint: #fffaf0;
  --on-gold: #231904;

  /* ── Signature gradients ──────────────────────────────────────────────
     The identity of the product in four declarations. Every gold surface,
     dark panel and accent hairline in the app pulls from these rather than
     writing its own angle and stops, so they all catch the light the same
     way. */
  --grad-gold: linear-gradient(160deg, var(--gold-light), var(--gold) 58%, var(--gold-dark));
  --grad-gold-flat: linear-gradient(180deg, var(--gold-light), var(--gold));
  --grad-gold-hot: linear-gradient(180deg, #e9c877, #cba238);
  --grad-brand: linear-gradient(135deg, var(--teal-900), var(--teal-700));
  --grad-brand-deep:
    radial-gradient(90% 120% at 100% 0%, rgba(199, 154, 54, .20), transparent 62%),
    linear-gradient(135deg, rgba(7, 36, 41, .99), rgba(17, 80, 87, .94));
  /* A gold hairline that fades out at both ends — the app's signature edge,
     used on the top of every dark panel. */
  --grad-hairline: linear-gradient(90deg, transparent, rgba(224, 187, 99, .7), transparent);
  --grad-sheen: linear-gradient(180deg, rgba(255, 255, 255, .55), rgba(255, 255, 255, 0));

  /* ── Text tiers — every piece of copy picks one of these three ─────── */
  --ink: #131c20;
  --ink-2: #465359;
  /* Measured, not eyeballed. The previous steps (#6c7a80 / #96a3a8) came out
     at 4.44:1 and 2.59:1 on white and 3.99:1 and 2.33:1 on --soft-2 — so the
     app's most-used secondary colour (266 rules) and every placeholder in it
     were under AA on the surfaces they actually sit on. These clear 4.5:1 on
     white, --soft, --soft-2 and --page, and keep the two tiers a visible step
     apart so the hierarchy still reads. */
  --muted: #606e74;
  --faint: #6b797f;

  /* ── Surfaces ─────────────────────────────────────────────────────── */
  --page: #eef2f3;
  --card: #ffffff;
  --soft: #f7fafa;
  --soft-2: #eef4f5;
  --line: #dde5e7;
  --line-soft: #e9eff0;
  --line-strong: #c8d5d8;

  /* Dark surfaces. The photo editor and other full-bleed tools were built
     on neutral greys, which read as a different application dropped inside
     this one. These are the same greys pulled toward the brand teal, so a
     dark tool still looks like it belongs to the product. */
  --dark-0: #08181b;
  --dark-1: #0d2226;
  --dark-2: #122c31;
  --dark-3: #17373c;
  --dark-line: #1f4249;
  --dark-line-strong: #2b565e;
  --on-dark: #f6fbfb;
  --on-dark-2: rgba(246, 251, 251, .72);
  --on-dark-3: rgba(246, 251, 251, .48);

  /* ── Semantic families ────────────────────────────────────────────────
     Each is a five-step ramp: text, hover/solid, hairline, fill, wash.
     Feature screens had been reaching for generic web reds and greens that
     sit outside the palette; these are the same signals tuned to the brand
     so a profit figure and a warning banner belong to the same product. */
  --success: #10714f;
  --success-600: #1a8a60;
  --success-line: #b5e3cd;
  --success-soft: #e6f6ee;
  --success-tint: #f2faf6;
  --danger: #b93a34;
  --danger-600: #cf4b42;
  --danger-line: #f3c3b9;
  --danger-hover: #fbdfd8;
  --danger-soft: #fdeeeb;
  --danger-tint: #fdf5f3;
  --warning: #9a6410;
  --warning-600: #b4771a;
  --warning-line: #ecd39a;
  --warning-soft: #fdf3dd;
  --warning-tint: #fffaf0;
  --info: #14606b;
  --info-600: #1a7c89;
  --info-line: #b9d9de;
  --info-soft: #e6f2f4;
  --info-tint: #f2f8f9;
  /* Accent — the one hue outside teal/gold, kept for AI-authored and
     assistant surfaces so they are legible as "the machine did this". */
  --accent: #6b3f86;
  --accent-600: #7f4c9e;
  --accent-line: #d9c7e4;
  --accent-soft: #f4ecf8;
  --accent-tint: #faf6fc;
  /* Wine — deadline-bearing rows (liquidation auctions) only. */
  --wine: #8a1c3d;
  --wine-soft: #fbe9ef;

  /* Source marks. Third-party badges keep their own brand colour, but they
     are declared here so they read as a deliberate set rather than as
     stray hex values in the middle of a component. */
  --src-ebay: #0064d2;
  --src-facebook: #1877f2;
  --src-craigslist: #4a2f83;
  --src-freebies: var(--success);
  --src-dealfeeds: #b4530a;

  /* ── Space scale — 4px base ───────────────────────────────────────── */
  --s1: 4px;
  --s2: 8px;
  --s3: 12px;
  --s4: 16px;
  --s5: 20px;
  --s6: 24px;
  --s7: 32px;
  --s8: 40px;
  --s9: 56px;
  --s10: 72px;

  /* ── Shell ────────────────────────────────────────────────────────────
     The sidebar width lives here rather than being typed into each rule
     that has to sit beside it. The full-page overlays (.opportunity-overlay,
     .new-listing-overlay) are position:fixed, so they only stay clear of the
     nav by measuring against the same number the shell grid uses. Two copies
     of "280px" is how the nav ends up buried again. */
  --sidebar-w: 280px;

  /* ── Radius ───────────────────────────────────────────────────────────
     Corner radius is a strong identity signal and the app had been mixing
     nine of them. These are the nine rungs everything now rounds to. */
  --r-3xs: 3px;
  --r-2xs: 4px;
  --r-xs: 6px;
  --r-sm: 8px;
  --r-md: 12px;
  --r-lg: 16px;
  --r-xl: 20px;
  --r-2xl: 28px;
  --r-pill: 999px;

  /* Shared width for the Sold Price Research block, so the stat cards and the list of sales
     below them line up on the same left and right edges instead of being two different widths. */
  --sold-comps-w: none;  /* fill the strip: stats + list share the heading's left/right edges.
                          A centred, capped block leaves a wedge of dead space on the left and
                          lines up with nothing. Set a px value only for a reading-width cap. */
  --radius: 12px;

  /* ── Elevation ────────────────────────────────────────────────────────
     Layered and low-contrast; premium reads as soft, not heavy. Every
     shadow is tinted with the brand teal instead of black, so cards look
     lit rather than dirty. */
  --e1: 0 1px 2px rgba(6, 27, 29, .05), 0 1px 3px rgba(6, 27, 29, .04);
  --e2: 0 1px 2px rgba(6, 27, 29, .05), 0 4px 12px rgba(6, 27, 29, .06);
  --e3: 0 2px 4px rgba(6, 27, 29, .05), 0 12px 28px rgba(6, 27, 29, .09);
  --e4: 0 4px 8px rgba(6, 27, 29, .06), 0 24px 60px rgba(6, 27, 29, .16);
  --e5: 0 8px 16px rgba(6, 27, 29, .08), 0 40px 90px rgba(6, 27, 29, .22);
  --shadow: var(--e3);
  /* The same elevation thrown upward, for a bar pinned to the bottom of a
     scroll. A downward shadow under a bottom-pinned bar lights it from the
     wrong side and the content scrolling beneath it looks pasted on top. */
  --e3-up: 0 -2px 4px rgba(6, 27, 29, .05), 0 -12px 28px rgba(6, 27, 29, .10);
  /* Gold glow for the primary action, and the inner highlight that makes a
     gold surface look like a lit edge rather than a flat swatch. */
  --glow-gold: 0 6px 18px rgba(199, 154, 54, .30);
  --glow-gold-lg: 0 10px 30px rgba(199, 154, 54, .34);
  --inset-hi: inset 0 1px 0 rgba(255, 255, 255, .38);
  --inset-hi-strong: inset 0 1px 0 rgba(255, 255, 255, .45);

  /* ── Motion ───────────────────────────────────────────────────────────
     --ease is the house curve: fast out of the gate, long settle. Use
     --ease-out for anything entering, --ease-spring for anything the user
     directly grabbed. */
  --ease: cubic-bezier(.32, .72, 0, 1);
  --ease-out: cubic-bezier(.22, .61, .36, 1);
  --ease-in-out: cubic-bezier(.65, 0, .35, 1);
  --ease-spring: cubic-bezier(.34, 1.4, .64, 1);
  --dur-0: .08s;
  --dur-1: .12s;
  --dur-2: .2s;
  --dur-3: .32s;
  --dur-4: .5s;

  /* Focus — one ring, used everywhere, never removed without replacement */
  --focus-ring: 0 0 0 2px #ffffff, 0 0 0 4px rgba(199, 154, 54, .85);
  --focus-ring-dark: 0 0 0 2px rgba(4, 23, 26, .9), 0 0 0 4px rgba(224, 187, 99, .9);

  /* Aliases. Later features were written against these generic names, which
     were never declared — so every rule using one was being dropped by the
     parser (an unresolved var() invalidates the whole declaration). Mapping
     them onto the real tokens repairs those rules instead of hunting each
     one down, and keeps both vocabularies pointing at one palette. */
  --bg: var(--page);
  --surface: var(--card);
  --card-alt: var(--soft);
  --border: var(--line);
  --text: var(--ink);
  --text-muted: var(--muted);
  --text-strong: var(--teal-850);
  --warn: var(--warning);
  --ok: var(--success);
  --bad: var(--danger);

  /* ── Roles ────────────────────────────────────────────────────────────
     A role names the JOB a colour does, not the colour it is. Everything
     below has a different value in light and in dark, and the component
     rules reach for the role so neither theme has to be written twice.
     The dark values live in one block at the end of this file. */

  /* The label that sits ON a filled semantic swatch. In light the fills
     are dark and the label is white; in dark the fills are the bright end
     of the same hue and the label flips to near-black. One token, so a
     "BEST" pill is legible in both without the rule knowing which. */
  --on-solid: #ffffff;

  /* Gold as TEXT, which is not the same colour as gold as a SURFACE.
     --gold-dark is a gradient stop and has to stay put; this is the step
     that clears 4.5:1 on white AND on the gold-tinted callouts (it was
     landing at 4.29 on --gold-soft, which is a fail nobody would have
     seen). In dark it becomes a pale gold instead of a darker one. */
  --gold-ink: #805d15;

  /* Brand teal as text. Same split, same reason: --teal-700/800 are
     surfaces as often as they are ink, and only the ink flips. */
  --brand-ink: var(--teal-800);
  --brand-ink-2: var(--teal-700);
  --brand-ink-strong: var(--teal-900);

  /* Pale marks that live on a PERMANENTLY dark surface — the hero bands,
     the drawer headers, the sold-comps strip. Those panels are deep teal
     in both themes, so their type must not follow the theme at all. */
  --gold-note: #fff4d7;
  --pale-pos: #b5e3cd;
  --pale-neg: #f3c3b9;
  --pale-warn: #ecd39a;
  --pale-accent: #d9c7e4;

  /* Chrome that sits on the dark masthead band in both themes, but has to
     stop being a white slab once the page behind it goes dark. */
  --search-bg: rgba(255, 255, 255, .94);
  --search-bg-focus: #ffffff;
  --search-line: rgba(255, 255, 255, .22);
  --ghost-bg: rgba(255, 255, 255, .80);
  --ghost-bg-hover: #ffffff;
  --ghost-line: rgba(10, 50, 54, .10);
  --ghost-line-hover: rgba(10, 50, 54, .20);

  /* The transparent end of a fade-to-surface mask. It must be the surface
     colour at zero alpha, not `transparent` — `transparent` is black at
     zero alpha, which greys the middle of the fade. */
  --card-fade: rgba(255, 255, 255, 0);
  --skeleton-sheen: rgba(255, 255, 255, .78);
  --scroll-thumb: rgba(12, 52, 56, .22);
  --scroll-thumb-hover: rgba(12, 52, 56, .38);

  /* Native widgets — date pickers, the spinner on a number field, the
     default scrollbar — are painted by the OS, not by this file. This is
     the only thing that tells it which set to paint. */
  color-scheme: light;

  /* The masthead band. It is the one dark surface the light theme keeps,
     and the dark theme re-mixes rather than removes. */
  --band:
    radial-gradient(80rem 22rem at 8% -6rem, rgba(199, 154, 54, .14), transparent 70%),
    linear-gradient(180deg, var(--teal-950) 0, var(--teal-900) 74px, rgba(7, 36, 41, 0) 250px);
}

body {
  min-height: 100vh;
  /* A fixed-height dark band rather than a 135° gradient across the whole
     document. The diagonal was sized by page length, so a long page dragged
     the dark wedge down past the content and left a black void beside short
     panels; this is the same header band at any scroll depth. */
  background-color: var(--page);
  background-image: var(--band);
  background-repeat: no-repeat;
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--fs-base);
  line-height: var(--lh-normal);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  font-variant-numeric: tabular-nums;
  /* Contextual alternates and standard ligatures on; discretionary
     ligatures explicitly off, so "fi" sets properly but nothing gets
     clever with a product title full of model numbers. */
  font-feature-settings: "calt" 1, "liga" 1, "dlig" 0;
}

/* ── Type hierarchy ─────────────────────────────────────────────────────
   Headings are set on the display face with tightening tracking as they
   grow. Sizes come from the scale; a component may re-set the size, but it
   should not re-invent the weight, tracking and colour of a heading. */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-display);
  color: var(--ink);
  line-height: var(--lh-snug);
  letter-spacing: var(--tracking-tight);
  text-wrap: balance;
}

h1 {
  font-size: var(--fs-3xl);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-display);
}

h2 {
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
}

h3 {
  font-size: var(--fs-lg);
  font-weight: var(--fw-semibold);
}

h4, h5, h6 {
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-snug);
}

/* Prose blocks read at a longer measure and looser leading than UI chrome.
   Paragraphs inside a panel are the only place in the app with body copy,
   and they were previously set at the same rhythm as button labels. */
p { text-wrap: pretty; }

/* Monospace anywhere in the app resolves to one stack, at a size corrected
   for the fact that monospace faces render optically larger than the sans
   at the same nominal size. */
code, kbd, pre, samp, .mono { font-family: var(--font-mono); font-size: .92em; }

button, input, select, textarea { font: inherit; }
button { cursor: pointer; }

/* Numbers that sit in a column or get compared must not jitter between
   renders, so money/metrics lock to tabular figures. */
.tnum, .stat-card strong, .num, td.num, th.num { font-variant-numeric: tabular-nums; }

/* One focus treatment for the whole app. Mouse users never see it;
   keyboard users always do. */
:where(button, a, input, select, textarea, [tabindex]):focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--r-sm);
}
.sidebar :where(button, a):focus-visible,
.hero-panel :where(button, a):focus-visible,
.topbar :where(button):focus-visible { box-shadow: var(--focus-ring-dark); }

::selection { background: rgba(199, 154, 54, .28); }

/* Scrollbars — the default Windows bar is the loudest thing on the page. */
* { scrollbar-color: var(--scroll-thumb) transparent; scrollbar-width: thin; }
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: var(--scroll-thumb);
  border: 3px solid transparent;
  border-radius: var(--r-pill);
  background-clip: content-box;
}
::-webkit-scrollbar-thumb:hover { background: var(--scroll-thumb-hover); background-clip: content-box; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}

.app-shell {
  display: grid;
  grid-template-columns: var(--sidebar-w) minmax(0, 1fr);
  min-height: 100vh;
}

.sidebar {
  position: sticky;
  top: 0;
  height: 100vh;
  display: flex;
  flex-direction: column;
  padding: var(--s6) var(--s4) var(--s4);
  color: var(--on-dark);
  background:
    radial-gradient(120% 60% at 0% 0%, rgba(199, 154, 54, .10), transparent 70%),
    linear-gradient(180deg, rgba(4, 23, 26, .97), rgba(7, 36, 41, .94));
  border-right: 1px solid rgba(255, 255, 255, .07);
  overflow-y: auto;
}

.sidebar-footer {
  margin-top: auto;
  padding: var(--s3) var(--s4) 0;
  border-top: 1px solid rgba(255, 255, 255, .09);
  text-align: center;
}
.sidebar-footer-brand {
  font-size: var(--fs-2xs);
  font-weight: 800;
  color: var(--gold-light);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}
.sidebar-footer-copy {
  font-size: var(--fs-2xs);
  color: rgba(246, 251, 251, .40);
  margin-top: 3px;
}

.brand-lockup {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s1) var(--s2) var(--s6);
}

.brand-mark {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: var(--r-md);
  background: var(--grad-gold);
  color: var(--on-gold);
  font-weight: 900;
  font-size: var(--fs-sm);
  letter-spacing: .04em;
  box-shadow: var(--glow-gold), var(--inset-hi-strong);
}

.brand-name {
  font-size: var(--fs-md);
  font-weight: 700;
  letter-spacing: -.01em;
}

.brand-sub {
  color: rgba(246, 251, 251, .55);
  font-size: var(--fs-xs);
  margin-top: 2px;
  letter-spacing: .01em;
}

.nav-list {
  display: grid;
  gap: 2px;
  margin-top: 0;
  flex: 1;
  align-content: start;
}

/* Ten destinations in one undifferentiated list reads as a dump. The group
   labels split it into sell / grow / account so a new user can find their
   way without reading all ten. */
.nav-group-label {
  padding: var(--s4) var(--s3) var(--s2);
  color: rgba(246, 251, 251, .38);
  font-size: var(--fs-2xs);
  font-weight: 800;
  letter-spacing: .13em;
  text-transform: uppercase;
}
.nav-group-label:first-child { padding-top: 0; }

.nav-item {
  position: relative;
  width: 100%;
  border: 0;
  border-radius: var(--r-sm);
  padding: 9px var(--s3);
  background: transparent;
  color: rgba(246, 251, 251, .72);
  text-align: left;
  font-weight: 550;
  font-size: var(--fs-sm);
  display: flex;
  align-items: center;
  gap: 11px;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}

.nav-item .nav-icon {
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: .68;
  transition: opacity var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}

.nav-item:hover {
  background: rgba(255, 255, 255, .06);
  color: #ffffff;
}
.nav-item:hover .nav-icon { opacity: .9; }

.nav-item.active {
  background: rgba(255, 255, 255, .10);
  color: #ffffff;
  font-weight: 650;
}

/* Gold rail on the active row — the one place the eye needs to land first. */
.nav-item.active::before {
  content: "";
  position: absolute;
  left: calc(var(--s4) * -1);
  top: 50%;
  transform: translateY(-50%);
  width: 3px;
  height: 20px;
  border-radius: 0 var(--r-pill) var(--r-pill) 0;
  background: linear-gradient(180deg, var(--gold-light), var(--gold));
  box-shadow: 0 0 12px rgba(199, 154, 54, .45);
}

.nav-item.active .nav-icon { opacity: 1; color: var(--gold-light); }

.nav-license { position: relative; }
.nav-license-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  margin-left: auto;
  flex-shrink: 0;
  box-shadow: 0 0 0 3px rgba(255, 255, 255, .06);
}
.dot-off  { background: var(--danger-600); }
.dot-on   { background: var(--success-600); }
.dot-warn { background: var(--gold-light); }

.sidebar-card {
  margin-top: 28px;
  padding: 16px;
  border: 1px solid rgba(255, 255, 255, .12);
  border-radius: var(--r-md);
  background: rgba(255, 255, 255, .08);
}

.sidebar-card-label {
  display: block;
  color: var(--gold-note);
  font-size: var(--fs-2xs);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .08em;
}

.sidebar-card strong {
  display: block;
  margin-top: 5px;
  color: #ffffff;
}

.sidebar-card p {
  margin-top: 6px;
  color: rgba(246, 251, 251, .68);
  font-size: var(--fs-xs);
}

.main {
  min-width: 0;
  padding: var(--s5) var(--s6) var(--s7);
}

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  margin-bottom: var(--s5);
}

.search-wrap {
  display: flex;
  align-items: center;
  gap: var(--s2);
  width: min(560px, 100%);
  padding: 9px var(--s4);
  border: 1px solid var(--search-line);
  border-radius: var(--r-pill);
  background: var(--search-bg);
  box-shadow: var(--e2);
  transition: box-shadow var(--dur-2) var(--ease), background var(--dur-2) var(--ease);
}

.search-wrap:focus-within {
  background: var(--search-bg-focus);
  box-shadow: var(--e2), 0 0 0 3px rgba(199, 154, 54, .32);
}

.search-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  stroke: var(--muted);
  fill: none;
  stroke-width: 1.8;
  stroke-linecap: round;
}

.search-wrap input {
  width: 100%;
  border: 0;
  outline: 0;
  background: transparent;
  color: var(--ink);
  font-size: var(--fs-sm);
}
.search-wrap input::placeholder { color: var(--muted); }
.search-wrap input:focus-visible { box-shadow: none; }

/* Discoverability for a shortcut people otherwise never find. */
.search-kbd {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
  padding: 3px 7px;
  border: 1px solid var(--line);
  border-bottom-width: 2px;
  border-radius: var(--r-xs);
  background: var(--soft);
  color: var(--muted);
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: .04em;
}

.topbar-actions,
.hero-actions,
.panel-actions,
.form-actions {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  min-height: 36px;
  padding: 8px var(--s4);
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  font-weight: 650;
  letter-spacing: -.005em;
  white-space: nowrap;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease),
              box-shadow var(--dur-1) var(--ease), color var(--dur-1) var(--ease), filter var(--dur-1) var(--ease);
}

/* A button that lifts on hover shifts the layout under the cursor. Depth and
   brightness read as responsive without moving anything. */
.btn:active:not(:disabled) { transform: translateY(.5px); }

.btn:disabled {
  opacity: .5;
  cursor: not-allowed;
  box-shadow: none;
}

.btn svg { width: 15px; height: 15px; flex-shrink: 0; }

/* The icon symbols are stroke drawings. Without this they inherit the SVG
   default of black fill and no stroke, which paints each one as a blob. */
.btn svg, .state-icon svg, .icon {
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.btn-primary {
  background: var(--grad-gold-flat);
  border-color: var(--gold-dark);
  color: var(--on-gold);
  box-shadow: var(--e1), var(--inset-hi);
}

/* The one button on any screen that glows. Reserving the gold light for the
   primary action is what makes it findable without making it shout. */
.btn-primary:hover:not(:disabled) {
  background: var(--grad-gold-hot);
  box-shadow: var(--e2), var(--glow-gold), var(--inset-hi-strong);
}

.btn-secondary {
  background: var(--card);
  border-color: var(--line);
  color: var(--brand-ink);
  box-shadow: var(--e1);
}

.btn-secondary:hover:not(:disabled) {
  border-color: var(--line-strong);
  background: var(--soft);
}

.btn-danger {
  background: var(--danger-soft);
  border-color: var(--danger-line);
  color: var(--danger);
}

.btn-danger:hover:not(:disabled) {
  background: var(--danger-hover);
  border-color: var(--danger-line);
}

.btn-ghost {
  background: var(--ghost-bg);
  border-color: var(--ghost-line);
  color: var(--brand-ink);
}

.btn-ghost:hover:not(:disabled) {
  background: var(--ghost-bg-hover);
  border-color: var(--ghost-line-hover);
}

.btn-ghost.light {
  background: rgba(255, 255, 255, .10);
  border-color: rgba(255, 255, 255, .22);
  color: #ffffff;
}

.btn-ghost.light:hover:not(:disabled) {
  background: rgba(255, 255, 255, .18);
  border-color: rgba(255, 255, 255, .34);
}

.btn.small {
  min-height: 30px;
  padding: 5px var(--s3);
  font-size: var(--fs-xs);
  border-radius: var(--r-xs);
}

.btn-large {
  min-height: 44px;
  padding-inline: var(--s6);
  font-size: var(--fs-base);
  border-radius: var(--r-md);
}

/* Square icon-only button — keeps the topbar from filling with word buttons. */
.btn-icon {
  min-height: 36px;
  width: 36px;
  padding: 0;
}
.btn-icon svg { width: 16px; height: 16px; }

/* Busy state: any button can show it, without markup changes. */
.btn.is-busy { position: relative; color: transparent !important; pointer-events: none; }
.btn.is-busy::after {
  content: "";
  position: absolute;
  width: 14px; height: 14px;
  border-radius: 50%;
  border: 2px solid currentColor;
  border-top-color: transparent;
  color: var(--brand-ink);
  animation: spin .7s linear infinite;
}
.btn-primary.is-busy::after { color: var(--on-gold); }

@keyframes spin { to { transform: rotate(360deg); } }

.badge {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 28px;
  padding: 4px 11px;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-size: var(--fs-sm);
  font-weight: 700;
  white-space: nowrap;
}

.badge-off {
  background: var(--danger-soft);
  border-color: var(--danger-line);
  color: var(--danger);
}

.badge-on {
  background: var(--success-soft);
  border-color: var(--success-line);
  color: var(--success);
}

.badge-ai {
  background: var(--gold-soft);
  border-color: rgba(159, 118, 36, .22);
  color: var(--gold-ink);
}

.badge-safe {
  background: var(--gold-soft);
  color: var(--gold-ink);
  border: 1px solid rgba(159, 118, 36, .25);
}

.hero-panel {
  position: relative;
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: var(--s6);
  margin-bottom: var(--s4);
  padding: var(--s7) var(--s7) var(--s6);
  border-radius: var(--r-xl);
  background: var(--grad-brand-deep), var(--teal-900);
  color: #ffffff;
  box-shadow: var(--e4);
  overflow: hidden;
}

/* Hairline of gold along the top edge — the whole panel's accent, done once.
   Every dark surface in the app wears the same edge, which is most of what
   makes them read as one family rather than as separate dark boxes. */
.hero-panel::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: var(--grad-hairline);
}

.hero-panel h1 {
  margin-top: var(--s2);
  font-size: clamp(var(--fs-3xl), 3.4vw, var(--fs-5xl));
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-display);
  color: #ffffff;
}

.hero-copy {
  max-width: 620px;
  margin-top: var(--s3);
  color: rgba(246, 251, 251, .70);
  font-size: var(--fs-md);
  line-height: 1.55;
}

.draft-safety-note {
  display: inline-flex;
  margin-top: 14px;
  padding: 8px 11px;
  border: 1px solid rgba(255, 244, 215, .32);
  border-radius: var(--r-pill);
  background: rgba(255, 244, 215, .10);
  color: var(--gold-note);
  font-size: var(--fs-xs);
  font-weight: 800;
}

.eyebrow {
  color: var(--gold-light);
  font-size: var(--fs-xs);
  font-weight: var(--fw-heavy);
  letter-spacing: var(--tracking-caps-wide);
  text-transform: uppercase;
}

.eyebrow.dark {
  color: var(--gold-ink);
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s3);
  margin-bottom: var(--s4);
}

.stat-card,
.panel,
.modal {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--e2);
}

.stat-card {
  position: relative;
  padding: var(--s5);
  overflow: hidden;
  transition: box-shadow var(--dur-2) var(--ease), border-color var(--dur-2) var(--ease);
}

.stat-card:hover {
  border-color: var(--line-strong);
  box-shadow: var(--e3);
}

/* A tinted top rail per tile: four identical white boxes give the eye nothing
   to sort by, and these are the numbers the seller checks first. The rail is
   a gradient rather than a flat bar so it reads as a lit edge and stays a
   detail — a solid 3px band across four tiles is stripes, not accent. */
.stat-card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 3px;
  background: linear-gradient(90deg, var(--teal-600), var(--teal-500));
  opacity: .85;
}
.stat-card:nth-child(2)::before { background: linear-gradient(90deg, var(--teal-700), var(--teal-600)); }
.stat-card:nth-child(3)::before { background: linear-gradient(90deg, var(--gold-dark), var(--gold-light)); }
.stat-card:nth-child(4)::before { background: linear-gradient(90deg, var(--warning), var(--warning-600)); }

.stat-label,
.stat-note {
  display: block;
  color: var(--muted);
  font-size: var(--fs-sm);
}

.stat-label {
  font-weight: var(--fw-heavy);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--ink-2);
}

.stat-card strong {
  display: block;
  margin: var(--s3) 0 var(--s1);
  color: var(--brand-ink-strong);
  font-family: var(--font-display);
  font-size: var(--fs-3xl);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-display);
}

.content-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 330px;
  gap: var(--s4);
  align-items: start;
}

/* The listings panel is hundreds of cards tall and the activity rail is six
   items; pinning it keeps the newest events readable while scrolling the
   catalogue, instead of stranding an empty column. */
.activity-panel {
  position: sticky;
  top: var(--s5);
  max-height: calc(100vh - var(--s7));
  overflow-y: auto;
}

.panel {
  padding: var(--s5);
}

.panel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  margin-bottom: var(--s4);
  padding-bottom: var(--s4);
  border-bottom: 1px solid var(--line-soft);
}

.panel-header h2 {
  margin-top: var(--s1);
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-tight);
}

.panel-header.compact {
  align-items: flex-start;
}

.view-toggle {
  display: inline-flex;
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--soft-2);
}

.toggle-btn {
  border: 0;
  border-radius: var(--r-xs);
  padding: 6px 12px;
  background: transparent;
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 700;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}

.toggle-btn:hover:not(.active) { color: var(--brand-ink); }

.toggle-btn.active {
  background: var(--card);
  color: var(--brand-ink-strong);
  box-shadow: var(--e1);
}

.listings-feedback {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin-bottom: var(--s3);
  padding: 9px var(--s3);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  background: var(--soft);
  color: var(--ink-2);
  font-size: var(--fs-sm);
  font-weight: 550;
}

/* The bar reports counts most of the time and failures some of the time; the
   failure needs to look different from "12 listings shown". */
.listings-feedback.is-error {
  border-color: var(--danger-line);
  background: var(--danger-soft);
  color: var(--danger);
}
.listings-feedback.is-busy { color: var(--muted); }
.listings-feedback.is-busy::before {
  content: "";
  width: 12px; height: 12px;
  flex-shrink: 0;
  border-radius: 50%;
  border: 2px solid var(--line-strong);
  border-top-color: var(--teal-700);
  animation: spin .7s linear infinite;
}

.listings-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
  gap: var(--s3);
}

.listing-card {
  display: grid;
  gap: var(--s3);
  padding: var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
  cursor: pointer;
  transition: border-color var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease);
}

.listing-card:hover,
.listing-card.active {
  border-color: rgba(199, 154, 54, .7);
  box-shadow: var(--e3);
}

.listing-card.active { box-shadow: var(--e3), 0 0 0 3px rgba(199, 154, 54, .18); }

.listing-media,
.listing-card img {
  width: 100%;
  aspect-ratio: 4 / 3;
  border-radius: var(--r-sm);
  object-fit: contain;
  background: var(--soft-2);
}

.listing-media {
  display: grid;
  place-items: center;
  color: var(--muted);
  font-weight: 700;
}

.listing-media strong {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border-radius: var(--r-pill);
  background: var(--teal-800);
  color: var(--gold-note);
  font-size: var(--fs-sm);
}

.listing-media span {
  margin-top: var(--s2);
  font-size: var(--fs-sm);
}

/* Two lines, then ellipsis — ragged 1–4 line titles were what made the card
   grid look uneven. */
.listing-title {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: 38px;
  color: var(--ink);
  font-weight: 650;
  font-size: var(--fs-sm);
  line-height: 1.35;
  letter-spacing: -.01em;
}

.listing-meta {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s2) var(--s3);
  color: var(--muted);
  font-size: var(--fs-xs);
  letter-spacing: .02em;
}

.listing-meta > span { overflow: hidden; }

.listing-meta strong {
  display: block;
  margin-top: 1px;
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 650;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.listing-thumb { position: relative; }

.watch-badge {
  position: absolute;
  top: var(--s2);
  right: var(--s2);
  padding: 3px 8px;
  border: 0;
  border-radius: var(--r-pill);
  background: rgba(7, 36, 41, .78);
  color: #ffffff;
  font-size: var(--fs-2xs);
  font-weight: 700;
  backdrop-filter: blur(4px);
}

.listing-footer {
  display: flex;
  justify-content: space-between;
  gap: var(--s2);
  align-items: center;
  padding-top: var(--s3);
  border-top: 1px solid var(--line-soft);
}

.listing-footer-actions {
  display: inline-flex;
  align-items: center;
  gap: var(--s3);
  flex-shrink: 0;
}

.view-ebay-link {
  white-space: nowrap;
  text-decoration: none;
  font-size: var(--fs-sm);
}
.view-ebay-link:hover { text-decoration: underline; }

.status-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  border-radius: var(--r-pill);
  padding: 3px 9px;
  background: var(--soft-2);
  color: var(--brand-ink);
  font-size: var(--fs-2xs);
  font-weight: 750;
  letter-spacing: .04em;
  text-transform: uppercase;
}

/* A status dot carries the state at a glance; the word confirms it. */
.status-chip::before {
  content: "";
  width: 5px; height: 5px;
  border-radius: 50%;
  background: currentColor;
  flex-shrink: 0;
}

.status-chip.live {
  background: var(--success-soft);
  color: var(--success);
}

.status-chip.review {
  background: var(--warning-soft);
  color: var(--warning);
}

.status-chip.sample {
  background: var(--soft-2);
  color: var(--brand-ink-2);
}

.status-chip.local {
  background: var(--warning-soft);
  color: var(--warning);
}

.table-wrap {
  display: none;
  overflow: auto;
  max-height: 70vh;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}

.listings-panel.table-mode .listings-grid {
  display: none;
}

.listings-panel.table-mode .table-wrap {
  display: block;
}

.listings-table {
  width: 100%;
  min-width: 980px;
  border-collapse: collapse;
}

.listings-table th,
.listings-table td {
  padding: var(--s3);
  border-bottom: 1px solid var(--line-soft);
  text-align: left;
  vertical-align: middle;
}

.listings-table td { font-size: var(--fs-sm); }
.listings-table tbody tr:last-child td { border-bottom: 0; }

/* Sticky header so the column meaning survives a scroll through 200 rows. */
.listings-table th {
  position: sticky;
  top: 0;
  z-index: 2;
  background: var(--soft);
  border-bottom: 1px solid var(--line);
  color: var(--muted);
  font-size: var(--fs-2xs);
  font-weight: 750;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  white-space: nowrap;
}

.listings-table tbody tr {
  cursor: pointer;
  transition: background var(--dur-1) var(--ease);
}

/* Hover and the active row are painted by the shared data-table grammar at
   the foot of this file — on the row, not on each cell, so the money band
   in the price column survives being hovered. */

/* Money and counts read as columns only when they share a right edge. */
.listings-table th:nth-child(3), .listings-table td:nth-child(3),
.listings-table th:nth-child(4), .listings-table td:nth-child(4),
.listings-table th:nth-child(10), .listings-table td:nth-child(10) {
  text-align: right;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.listings-table td:nth-child(3) { font-weight: 650; color: var(--brand-ink-strong); }

/* Two lines of title, then ellipsis. Unclamped, a long miner title wrapped to
   five lines and set the height of the whole row. */
.listings-table td:nth-child(2) { max-width: 340px; }
.listings-table td:nth-child(2) strong {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  font-weight: 600;
  line-height: 1.35;
}

.listings-table td:nth-child(5),
.listings-table td:nth-child(6),
.listings-table td:nth-child(8),
.listings-table td:nth-child(9) { color: var(--ink-2); white-space: nowrap; }

.content-grid.table-mode { grid-template-columns: minmax(0, 1fr); }
.content-grid.table-mode .activity-panel { position: static; max-height: none; }

.table-img {
  width: 44px;
  height: 44px;
  border-radius: var(--r-xs);
  object-fit: contain;
  background: var(--soft-2);
}

.activity-list {
  display: grid;
  gap: var(--s2);
}

/* Timeline rail — turns a stack of boxes into a sequence you read downward. */
.activity-item {
  position: relative;
  padding: var(--s3) var(--s3) var(--s3) var(--s5);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  background: var(--soft);
}

.activity-item::before {
  content: "";
  position: absolute;
  left: var(--s3);
  top: 17px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--teal-600);
  box-shadow: 0 0 0 3px rgba(26, 107, 115, .12);
}

.activity-item:first-child::before { background: var(--gold); box-shadow: 0 0 0 3px rgba(199, 154, 54, .18); }

.activity-item strong {
  display: block;
  color: var(--brand-ink-strong);
  font-size: var(--fs-sm);
  font-weight: 650;
}

.activity-item span {
  display: block;
  margin-top: 3px;
  color: var(--muted);
  font-size: var(--fs-sm);
  line-height: 1.45;
}

.upload-panel,
.settings-panel,
.logs-panel,
.form-section,
.license-page {
  max-width: 1040px;
}

/* ── License Page ──────────────────────────────────────────────────── */
.lp-status-banner {
  display: flex;
  align-items: center;
  gap: 16px;
  padding: 20px 24px;
  border-radius: var(--radius);
  margin-bottom: 24px;
  border: 1.5px solid transparent;
}
.lp-unlicensed  { background: var(--danger-tint); border-color: var(--danger-line); }
.lp-licensed    { background: var(--success-soft); border-color: var(--success-line); }
.lp-unverified  { background: var(--gold-soft); border-color: var(--warning-line); }

.lp-status-icon { font-size: var(--fs-4xl); line-height: 1; }

.lp-tier-label  { font-size: var(--fs-xl); font-weight: 700; color: var(--ink); }
.lp-status-msg  { font-size: var(--fs-sm); color: var(--muted); margin-top: 2px; }

.lp-card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 24px;
  margin-bottom: 24px;
}
.lp-card-title  { font-size: var(--fs-md); font-weight: 700; margin-bottom: 14px; }

.lp-activate-row {
  display: flex;
  gap: 10px;
}
.lp-key-input {
  flex: 1;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 10px 14px;
  font-size: var(--fs-base);
}
.lp-hint { font-size: var(--fs-xs); color: var(--muted); margin-top: 10px; }

.lp-tiers {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 20px;
  margin-bottom: 24px;
}
@media (max-width: 680px) { .lp-tiers { grid-template-columns: 1fr; } }

.lp-tier-card {
  background: var(--card);
  border: 1.5px solid var(--line);
  border-radius: var(--radius);
  padding: 24px;
  display: flex;
  flex-direction: column;
  gap: 16px;
  position: relative;
}
.lp-tier-pro {
  border-color: var(--gold);
  background: linear-gradient(160deg, var(--warning-tint) 0%, var(--card) 60%);
}

.lp-tier-badge {
  position: absolute;
  top: -1px; right: 20px;
  background: var(--gold);
  color: var(--on-gold);
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: .04em;
  padding: 3px 10px;
  border-radius: 0 0 8px 8px;
  text-transform: uppercase;
}

.lp-tier-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
  padding-top: 4px;
}
.lp-tier-name  { font-size: var(--fs-xl); font-weight: 700; }
.lp-tier-price { font-size: var(--fs-xl); font-weight: 800; color: var(--gold-ink); }

.lp-feature-list {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 8px;
  flex: 1;
}
.lp-feature-list li {
  font-size: var(--fs-sm);
  color: var(--ink);
  padding-left: 20px;
  position: relative;
}
.lp-feature-list li::before {
  content: '✓';
  position: absolute;
  left: 0;
  color: var(--success);
  font-weight: 700;
}

.lp-tier-key-hint {
  font-size: var(--fs-xs);
  color: var(--muted);
  background: var(--soft);
  border-radius: var(--r-xs);
  padding: 8px 12px;
}
.lp-tier-key-hint code {
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  color: var(--ink);
  font-weight: 600;
}

.lp-beta-note {
  background: var(--gold-soft);
  border: 1px solid var(--warning-line);
  border-radius: var(--radius);
  padding: 16px 20px;
  font-size: var(--fs-sm);
  color: var(--ink);
  line-height: 1.6;
}
.lp-beta-note code {
  font-family: var(--font-mono);
  font-weight: 700;
  background: var(--soft-2);
  padding: 1px 5px;
  border-radius: var(--r-2xs);
}

.badge-warn { background: var(--gold-soft); color: var(--warning); border: 1px solid var(--warning-line); }
.link-btn { background: none; border: none; padding: 0; color: var(--gold-ink); text-decoration: underline; cursor: pointer; font: inherit; }

.settings-list,
.logs-list {
  display: grid;
  gap: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  overflow: hidden;
}

/* A state block inside the list frame keeps its own padding and needs no
   border of its own. */
.logs-list > .state { border: 0; border-radius: 0; }

/* One bordered container, rows split on a rule — not a bordered rounded box
   inside a bordered rounded box, four times over. `.settings-list` already
   draws the frame and clips to it; each row drawing its own frame again is the
   double-boxing the log list was fixed of and this one was not. */
.settings-row {
  display: grid;
  grid-template-columns: minmax(120px, 180px) 1fr;
  gap: 12px;
  align-items: start;
  padding: 12px 14px;
  border-bottom: 1px solid var(--line-soft);
  background: var(--soft);
}

.settings-row:last-child { border-bottom: 0; }

.settings-row span {
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 800;
}

.settings-row strong {
  color: var(--ink);
  font-size: var(--fs-sm);
  overflow-wrap: anywhere;
}

/* A log is a dense list, not a stack of cards. Each entry was a bordered box
   with a 190px title column that wrapped every title to three lines and left
   the detail column half empty — nine entries filled the screen. Rows now
   share one bordered container, split on a rule, and give the title the room
   it actually needs. */
.log-row {
  display: grid;
  grid-template-columns: 72px minmax(240px, 340px) 1fr;
  gap: var(--s4);
  align-items: baseline;
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--line-soft);
  background: var(--card);
  transition: background var(--dur-1) var(--ease);
}

.log-row:hover { background: var(--soft); }
.log-row:last-child { border-bottom: 0; }

.log-level {
  display: inline-flex;
  justify-content: center;
  width: 100%;
  padding: 3px var(--s2);
  border-radius: var(--r-xs);
  background: var(--success-soft);
  color: var(--success);
  font-size: var(--fs-2xs);
  font-weight: 750;
  text-transform: uppercase;
  letter-spacing: .06em;
}

.log-level.warning {
  background: var(--warning-soft);
  color: var(--warning);
}

.log-level.error {
  background: var(--danger-soft);
  color: var(--danger);
}

.log-title {
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 650;
  line-height: 1.4;
}

/* The timestamp is metadata on the entry, not part of its name. */
.log-title small {
  display: block;
  margin-top: 2px;
  color: var(--muted);
  font-size: var(--fs-2xs);
  font-weight: 550;
  font-variant-numeric: tabular-nums;
}

.log-detail {
  color: var(--ink-2);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  line-height: 1.55;
  overflow-wrap: anywhere;
}

.drop-zone {
  border: 2px dashed var(--line-strong);
  border-radius: var(--r-lg);
  background: var(--teal-50);
  padding: 46px 24px;
  text-align: center;
  cursor: pointer;
  transition: border-color var(--dur-1), background var(--dur-1);
}

.drop-zone:hover,
.drop-zone.drag-over {
  border-color: var(--gold);
  background: var(--warning-tint);
}

.drop-icon {
  display: grid;
  place-items: center;
  width: 58px;
  height: 58px;
  margin: 0 auto 14px;
  border-radius: var(--r-lg);
  background: var(--teal-800);
  color: #ffffff;
  font-weight: 900;
}

.drop-zone p {
  color: var(--muted);
}

.drop-zone .hint {
  margin-top: 4px;
  font-size: var(--fs-xs);
}

.link {
  color: var(--brand-ink-2);
  cursor: pointer;
  font-weight: 800;
  text-decoration: underline;
}

.preview-wrap {
  position: relative;
  display: inline-block;
  margin-top: 16px;
}

#preview-img {
  max-width: min(100%, 620px);
  max-height: 360px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
}

#btn-clear {
  position: absolute;
  top: 10px;
  right: 10px;
}

.loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
  padding: 44px;
  color: var(--muted);
}

.spinner {
  width: 38px;
  height: 38px;
  border: 4px solid var(--line);
  border-top-color: var(--gold);
  border-radius: 50%;
  animation: spin .75s linear infinite;
}

@keyframes spin { to { transform: rotate(360deg); } }

.form-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
}

.form-panel {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  margin-bottom: 11px;
  overflow: visible;
  background: var(--card);
}

.form-panel > summary {
  display: flex;
  align-items: center;
  padding: 11px 16px;
  border-radius: 12px 12px 0 0;
  background: var(--soft);
  color: var(--brand-ink-strong);
  font-weight: 700;
  font-size: var(--fs-sm);
  cursor: pointer;
  list-style: none;
  border-bottom: 1px solid transparent;
  user-select: none;
  letter-spacing: .01em;
}

.form-panel > summary::after {
  content: '›';
  margin-left: auto;
  font-size: var(--fs-xl);
  line-height: 1;
  color: var(--muted);
  transition: transform var(--dur-1);
  font-weight: 400;
}

.form-panel[open] > summary::after {
  transform: rotate(90deg);
}

.form-panel[open] > summary {
  border-bottom-color: var(--line);
  background: var(--soft-2);
  color: var(--brand-ink);
}

.form-panel > summary::-webkit-details-marker { display: none; }

.form-panel > :not(summary) {
  padding: 16px;
}

.form-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.field.full-width {
  grid-column: 1 / -1;
}

label {
  display: flex;
  justify-content: space-between;
  gap: var(--s3);
  color: var(--ink-2);
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: .01em;
}

.hint-inline,
.char-count {
  color: var(--muted);
  font-weight: 550;
}

/* url and date were missing from this list, so every photo URL field in the
   listing editor and all three date fields were being drawn by the browser
   rather than by the app — white boxes with a default border, in the middle of
   a form of tinted ones. */
input[type="text"],
input[type="number"],
input[type="search"],
input[type="password"],
input[type="url"],
input[type="date"],
select,
textarea {
  width: 100%;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-sm);
  padding: 9px 11px;
  background: var(--soft);
  color: var(--ink);
  outline: none;
  transition: border-color var(--dur-1) var(--ease), background var(--dur-1) var(--ease), box-shadow var(--dur-1) var(--ease);
}

input::placeholder, textarea::placeholder { color: var(--faint); }

input[type="text"]:hover,
input[type="number"]:hover,
input[type="url"]:hover,
input[type="date"]:hover,
select:hover,
textarea:hover { border-color: var(--line-strong); }

input:focus,
select:focus,
textarea:focus {
  border-color: var(--gold);
  background: var(--card);
  box-shadow: 0 0 0 3px rgba(199, 154, 54, .16);
}

/* The generic focus-visible ring would double up on fields that already
   draw their own; theirs is the more precise one. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
  box-shadow: 0 0 0 3px rgba(199, 154, 54, .16);
  border-radius: var(--r-sm);
}

input:disabled, select:disabled, textarea:disabled {
  background: var(--soft-2);
  color: var(--muted);
  cursor: not-allowed;
}

/* Field-level error state, so a failed save can point at the field. */
.field.has-error input,
.field.has-error select,
.field.has-error textarea,
input.has-error, select.has-error, textarea.has-error {
  border-color: var(--danger);
  background: var(--danger-soft);
}
.field-error {
  color: var(--danger);
  font-size: var(--fs-sm);
  font-weight: 600;
}

textarea {
  resize: vertical;
}

.toggle-label {
  align-items: center;
  justify-content: flex-start;
  color: var(--ink);
  cursor: pointer;
}

.toggle-label input[type="checkbox"] {
  width: 16px;
  height: 16px;
  accent-color: var(--gold);
}

#specifics-list {
  display: grid;
  gap: 8px;
  margin-bottom: 10px;
}

.draft-preview {
  margin-top: 14px;
  padding: 14px;
  border: 1px solid rgba(199, 154, 54, .42);
  border-radius: var(--radius);
  background: var(--warning-tint);
}

.draft-preview h3 {
  margin: 0 0 10px;
  color: var(--brand-ink-strong);
  font-size: var(--fs-md);
}

.draft-preview-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 10px;
}

.draft-preview-item {
  display: grid;
  gap: 3px;
  padding: 10px;
  border: 1px solid rgba(199, 154, 54, .24);
  border-radius: var(--r-md);
  background: var(--card);
}

.draft-preview-item span {
  color: var(--muted);
  font-size: var(--fs-2xs);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .05em;
}

.draft-preview-item strong {
  color: var(--ink);
  font-size: var(--fs-sm);
}

.specific-row {
  display: grid;
  grid-template-columns: 1fr 1fr auto;
  gap: 8px;
}

/* One photo per line. .photo-url-row is display:contents, so its input and its
   remove button are the grid's own items and fall into these two columns — a
   wrapping flex row instead packed four URLs across and truncated every one of
   them to "https://i.ebayimg.com/p", which is the same string for all six. */
.photo-url-list {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: var(--s2);
  margin-bottom: var(--s2);
}

.photo-url-row {
  display: contents;
}

.photo-thumb-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  width: 130px;
}

.photo-thumb-img-wrap {
  position: relative;
  width: 130px;
  height: 130px;
  border: 2px solid var(--line);
  border-radius: var(--r-sm);
  overflow: hidden;
  background: var(--soft);
}

.photo-thumb-img-wrap img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.photo-thumb-remove {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: rgba(0,0,0,.55);
  color: #fff;
  border: none;
  cursor: pointer;
  font-size: var(--fs-2xs);
  display: flex;
  align-items: center;
  justify-content: center;
  opacity: 0;
  transition: opacity var(--dur-1);
}

.photo-thumb-img-wrap:hover .photo-thumb-remove { opacity: 1; }

.photo-thumb-num {
  font-size: var(--fs-2xs);
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .04em;
}

.photo-thumb-url {
  width: 100%;
  font-size: var(--fs-3xs);
  padding: 4px 6px;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  color: var(--muted);
  background: var(--bg);
}

.specific-row button,
.photo-url-row button {
  width: 36px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--danger);
  font-weight: 900;
}

#result {
  margin-top: var(--s4);
  padding: var(--s3) var(--s4);
  border-radius: var(--r-md);
  font-weight: 600;
  line-height: 1.5;
}

#result.success {
  background: var(--success-soft);
  border: 1px solid var(--success-line);
  color: var(--success);
}

#result.error {
  background: var(--danger-soft);
  border: 1px solid var(--danger-line);
  color: var(--danger);
}

/* Waiting on eBay: the seller's consent screen is open in another tab and this page is holding
   its place. Deliberately neither green nor red — nothing has succeeded or failed yet, and the
   whole point of the new tab is that this page did not go anywhere. */
#result.waiting {
  background: var(--accent-soft);
  border: 1px solid var(--accent-line);
  color: var(--accent);
}

#result.waiting a,
#result.error a {
  color: inherit;
  text-decoration: underline;
}

.modal-overlay {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: var(--s8) var(--s4);
  overflow-y: auto;
  background: rgba(3, 19, 22, .58);
  backdrop-filter: blur(3px) saturate(.9);
  animation: overlay-in var(--dur-2) var(--ease);
}

@keyframes overlay-in { from { opacity: 0; } }

.modal {
  width: min(620px, 100%);
  padding-bottom: var(--s1);
  border-radius: var(--r-xl);
  box-shadow: var(--e4);
  animation: modal-in var(--dur-3) var(--ease);
}

@keyframes modal-in {
  from { opacity: 0; transform: translateY(10px) scale(.985); }
}

.modal-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s5) var(--s6) var(--s3);
  border-bottom: 1px solid var(--line-soft);
}

.modal-header h2 { font-size: var(--fs-lg); font-weight: 700; }

.modal-close {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--muted);
  font-weight: 700;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}

.modal-close:hover {
  background: var(--soft-2);
  border-color: var(--line-strong);
  color: var(--ink);
}

.modal-desc {
  padding: var(--s3) var(--s6) 0;
  color: var(--ink-2);
  line-height: 1.55;
}

.modal-note {
  margin-top: 8px;
  color: var(--muted);
  font-size: var(--fs-xs);
}

.modal-note code {
  padding: 2px 5px;
  border-radius: var(--r-xs);
  background: var(--soft-2);
  color: var(--brand-ink);
}

.setup-section {
  padding: 18px 22px 0;
  margin-top: 16px;
  border-top: 1px solid var(--line);
}

.setup-section:first-of-type {
  border-top: 0;
  margin-top: 0;
}

.setup-section h3 {
  margin-bottom: 12px;
}

.setup-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
}

.setup-section .field {
  margin-bottom: 12px;
}

.link-ext {
  color: var(--brand-ink-2);
  font-size: var(--fs-xs);
}

.modal-actions {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  align-items: center;
  margin-top: 18px;
  padding: 18px 22px 22px;
  border-top: 1px solid var(--line);
}

.modal-actions.compact-actions {
  margin: -2px 0 14px;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--soft);
}

#setup-status-msg.ok { color: var(--success); }
#setup-status-msg.error { color: var(--danger); }
#oauth-redirect-msg.ok { color: var(--success); }
#oauth-redirect-msg.error { color: var(--danger); }

.hidden {
  display: none !important;
}

@media (max-width: 1120px) {
  .app-shell {
    grid-template-columns: 1fr;
  }

  .sidebar {
    position: static;
    height: auto;
    display: grid;
    grid-template-columns: 1fr;
  }

  .nav-list {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }

  .sidebar-card {
    display: none;
  }

  .content-grid,
  .stats-grid {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (max-width: 760px) {
  .main {
    padding: 16px;
  }

  .topbar,
  .hero-panel,
  .panel-header,
  .form-header {
    align-items: stretch;
    flex-direction: column;
  }

  .topbar-actions,
  .hero-actions,
  .panel-actions {
    width: 100%;
  }

  .topbar-actions .btn,
  .hero-actions .btn,
  .panel-actions .btn {
    flex: 1;
  }

  .content-grid,
  .stats-grid,
  .form-grid,
  .setup-grid {
    grid-template-columns: 1fr;
  }

  .field.full-width {
    grid-column: 1;
  }

  .nav-list {
    grid-template-columns: 1fr 1fr;
  }

  .specific-row,
  .photo-url-row,
  .settings-row,
  .log-row {
    grid-template-columns: 1fr;
  }

  .new-listing-body {
    grid-template-columns: 1fr;
  }

  .nl-image-panel {
    border-right: none;
    border-bottom: 1px solid var(--line);
  }
}

/* ── New AI Listing modal ──────────────────────────────────── */

/* Starts where the sidebar ends. This is a page, not a dialog — there is no
   scrim and nothing behind it to dismiss, so covering the nav only meant the
   seller had to close it to go anywhere. See --sidebar-w. */
.new-listing-overlay {
  position: fixed;
  inset: 0 0 0 var(--sidebar-w);
  z-index: 900;
  display: flex;
}

.new-listing-modal {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  background: var(--card);
}

.new-listing-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 16px 28px;
  background: linear-gradient(90deg, var(--teal-900), var(--teal-800));
  color: #fff;
  flex-shrink: 0;
  position: relative;
}

.new-listing-header .eyebrow {
  color: rgba(255, 255, 255, .56);
  font-size: var(--fs-2xs);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .08em;
  margin-bottom: 2px;
}

.new-listing-header h2 {
  color: #fff;
  font-size: var(--fs-xl);
  font-weight: 800;
}

/* ── Chrome-style Draft Tabs (embedded in header) ───────────── */
.new-listing-header {
  flex-direction: column;
  padding: 10px 16px 0 !important;
  gap: 0 !important;
  align-items: stretch !important;
}

.nl-header-brand {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding-bottom: 8px;
}

.new-listing-header .nl-close-btn {
  position: absolute;
  top: 12px;
  right: 16px;
}

.new-listing-header .nl-home-btn {
  right: 60px;
}

.nl-tab-bar {
  display: flex;
  align-items: flex-end;
  gap: 2px;
  flex-wrap: wrap;
  overflow: visible;
  flex-shrink: 0;
  padding: 0 40px 0 0;
}

.nl-tab {
  position: relative;
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 8px 0 10px;
  height: 36px;
  min-width: 70px;
  max-width: 180px;
  flex: 1 1 120px;
  background: rgba(0,0,0,.25);
  border-radius: 8px 8px 0 0;
  color: rgba(255,255,255,.6);
  font-size: var(--fs-xs);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  border: none;
  z-index: 0;
  transition: background var(--dur-1), color var(--dur-1);
}
.nl-tab:hover {
  background: rgba(0,0,0,.15);
  color: rgba(255,255,255,.9);
}
.nl-tab.active {
  background: var(--card);
  color: var(--ink);
  z-index: 2;
  font-weight: 600;
}

/* Curved shoulder connectors */
.nl-tab.active::before,
.nl-tab.active::after {
  content: '';
  position: absolute;
  bottom: 0;
  width: 14px;
  height: 14px;
  background: transparent;
  pointer-events: none;
  z-index: 3;
}
.nl-tab.active::before {
  left: -14px;
  border-bottom-right-radius: 10px;
  box-shadow: 6px 5px 0 5px var(--card);
}
.nl-tab.active::after {
  right: -14px;
  border-bottom-left-radius: 10px;
  box-shadow: -6px 5px 0 5px var(--card);
}

.nl-tab-favicon {
  font-size: var(--fs-xs);
  flex-shrink: 0;
  opacity: .7;
}
.nl-tab.active .nl-tab-favicon { opacity: 1; }

.nl-tab-title {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
}

.nl-tab-close {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 18px;
  height: 18px;
  border-radius: 50%;
  font-size: var(--fs-3xs);
  flex-shrink: 0;
  opacity: 0;
  transition: opacity var(--dur-1), background var(--dur-1);
}
.nl-tab:hover .nl-tab-close,
.nl-tab.active .nl-tab-close { opacity: .55; }
.nl-tab-close:hover { opacity: 1 !important; background: rgba(0,0,0,.18); }

.nl-tab-saved-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--success-600);
  flex-shrink: 0;
}

.nl-tab-new-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  margin-bottom: 4px;
  background: transparent;
  color: rgba(255,255,255,.5);
  border: none;
  cursor: pointer;
  font-size: var(--fs-xl);
  font-weight: 300;
  border-radius: 50%;
  flex-shrink: 0;
  transition: background var(--dur-1), color var(--dur-1);
  align-self: center;
}
.nl-load-all-drafts-btn {
  padding: 3px 10px;
  font-size: var(--fs-2xs);
  font-weight: 600;
  background: var(--gold-light);
  color: var(--on-gold);
  border: none;
  border-radius: var(--r-xs);
  cursor: pointer;
  align-self: center;
  flex-shrink: 0;
  white-space: nowrap;
}
.nl-load-all-drafts-btn:hover { background: var(--gold); }
.nl-load-all-drafts-btn:disabled { opacity: .6; cursor: default; }

.nl-clear-drafts-btn {
  padding: 3px 10px;
  font-size: var(--fs-2xs);
  font-weight: 600;
  background: var(--danger);
  color: var(--on-solid);
  border: none;
  border-radius: var(--r-xs);
  cursor: pointer;
  align-self: center;
  flex-shrink: 0;
  white-space: nowrap;
}
.nl-clear-drafts-btn:hover { background: var(--danger); }
.nl-clear-drafts-btn:disabled { opacity: .6; cursor: default; }

.nl-tab-new-btn:hover {
  background: rgba(255,255,255,.15);
  color: #fff;
}

.nl-close-btn {
  width: 36px;
  height: 36px;
  border: 1px solid rgba(255, 255, 255, .25);
  border-radius: var(--r-sm);
  background: rgba(255, 255, 255, .1);
  color: #fff;
  cursor: pointer;
  font-size: var(--fs-xl);
  display: grid;
  place-items: center;
  flex-shrink: 0;
  transition: background var(--dur-1);
}

.nl-close-btn:hover { background: rgba(255, 255, 255, .22); }

/* ── Category search widget ──────────────────────────── */
.cat-search-wrap {
  position: relative;
}

.cat-search-wrap input {
  width: 100%;
}

.cat-dropdown {
  position: absolute;
  top: calc(100% + 4px);
  left: 0; right: 0;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  box-shadow: 0 8px 24px rgba(0,0,0,.14);
  list-style: none;
  margin: 0; padding: 4px 0;
  z-index: 200;
  max-height: 260px;
  overflow-y: auto;
}

.cat-dropdown li {
  padding: 9px 14px;
  cursor: pointer;
  font-size: var(--fs-sm);
  line-height: 1.4;
  border-bottom: 1px solid var(--line);
}

.cat-dropdown li:last-child { border-bottom: none; }
.cat-dropdown li:hover, .cat-dropdown li.active { background: rgba(201,168,76,.12); }

.cat-dropdown .cat-name   { font-weight: 700; color: var(--text); display: block; }
.cat-dropdown .cat-path   { font-size: var(--fs-2xs); color: var(--muted); display: block; margin-top: 1px; }
.cat-dropdown .cat-id-tag { font-size: var(--fs-3xs); color: var(--muted); float: right; margin-top: 2px; }

.cat-dropdown .cat-loading,
.cat-dropdown .cat-no-results {
  padding: 12px 14px;
  color: var(--muted);
  font-size: var(--fs-xs);
  text-align: center;
}

.cat-browse-btn {
  position: absolute;
  right: 6px;
  top: 50%;
  transform: translateY(-50%);
  padding: 3px 10px;
  font-size: var(--fs-2xs);
  font-weight: 600;
  background: var(--teal-700);
  color: #fff;
  border: none;
  border-radius: var(--r-2xs);
  cursor: pointer;
}
.cat-search-wrap input { padding-right: 74px; }

.cat-browser {
  border: 1px solid var(--teal-700);
  border-radius: var(--r-sm);
  margin-top: 4px;
  background: var(--card);
  box-shadow: 0 4px 16px rgba(0,0,0,.12);
  max-height: 340px;
  overflow: hidden;
  display: flex;
  flex-direction: column;
}
.cat-browser-trail {
  padding: 8px 12px;
  background: var(--teal-700);
  color: #fff;
  font-size: var(--fs-2xs);
  border-radius: 7px 7px 0 0;
}
.cat-browser-trail .trail-crumb { opacity: .7; }
.cat-browser-list {
  list-style: none;
  margin: 0;
  padding: 0;
  overflow-y: auto;
  flex: 1;
}
.cat-browser-list li {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 9px 14px;
  cursor: pointer;
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-sm);
}
.cat-browser-list li:hover { background: var(--dt-hover); }
.cat-browser-list .cat-name { flex: 1; font-weight: 600; }
.cat-browser-list .cat-id-tag { font-size: var(--fs-3xs); color: var(--muted); }
.cat-browser-list .cat-arrow { color: var(--brand-ink-2); font-weight: 700; }
.cat-browser-list .cat-browser-back {
  color: var(--brand-ink-2);
  font-weight: 700;
  font-size: var(--fs-xs);
  background: var(--dt-zebra);
}
.cat-browser-list .cat-loading,
.cat-browser-list .cat-no-results {
  padding: 16px;
  color: var(--muted);
  font-size: var(--fs-xs);
  text-align: center;
  cursor: default;
}

.cat-selected {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 6px;
  padding: 6px 10px;
  background: rgba(201,168,76,.1);
  border: 1px solid rgba(201,168,76,.35);
  border-radius: var(--r-xs);
  font-size: var(--fs-xs);
}

#nl-cat-selected-name { font-weight: 700; flex: 1; }

.cat-id-badge {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-2xs);
  padding: 1px 6px;
  font-size: var(--fs-2xs);
  font-family: var(--font-mono);
  color: var(--muted);
  flex-shrink: 0;
}

.cat-clear {
  background: none; border: none; cursor: pointer;
  color: var(--muted); font-size: var(--fs-sm); padding: 0 2px;
  flex-shrink: 0;
}
.cat-clear:hover { color: var(--danger); }

/* ── Header title ──────────────────────────────────────────── */
.nl-header-title {
  font-size: var(--fs-base);
  font-weight: 700;
  color: rgba(255,255,255,.92);
  letter-spacing: .01em;
  white-space: nowrap;
}

/* ── URL section wrapper ────────────────────────────────────── */
.nl-url-section {
  flex-shrink: 0;
  border-bottom: 1px solid var(--line);
  background: var(--card);
  padding: 18px 16px 14px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.nl-action-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
@media (max-width: 980px) {
  .nl-action-grid { grid-template-columns: 1fr; }
}

.nl-action-card {
  position: relative;
  border: 1.5px solid var(--line);
  border-radius: var(--r-lg);
  padding: 13px 16px;
  background: var(--soft);
  display: flex;
  flex-direction: column;
  gap: 3px;
}

.nl-action-head   { display: flex; align-items: center; gap: 8px; }
.nl-action-icon   { font-size: var(--fs-xl); line-height: 1; }
.nl-action-title  { font-size: var(--fs-base); font-weight: 800; color: var(--ink); letter-spacing: -.01em; }
.nl-action-how    { font-size: var(--fs-xs); color: var(--muted); margin: 0; }
.nl-action-why    { font-size: var(--fs-sm); font-weight: 700; margin: 2px 0 8px; color: var(--brand-ink-2); }
.nl-action-row    { display: flex; align-items: center; gap: 8px; margin-top: auto; }

.nl-action-card--assistant .nl-action-why { color: var(--accent-600); }
.nl-action-card--hero      .nl-action-why { color: var(--gold-ink); }

/* Hero card (AI Product Research) — the headline feature, made impossible to miss */
.nl-action-card--hero {
  background: linear-gradient(135deg, var(--warning-tint) 0%, var(--warning-tint) 100%);
  border: 2px solid var(--gold);
  box-shadow: 0 0 0 4px rgba(199,154,54,.12), 0 10px 26px rgba(199,154,54,.2);
  padding: 20px 22px 18px;
}
.nl-action-card--hero .nl-action-icon  { font-size: var(--fs-3xl); }
.nl-action-card--hero .nl-action-title { font-size: var(--fs-xl); }
.nl-action-card--hero .nl-action-how   { font-size: var(--fs-base); color: var(--ink); font-weight: 600; margin-top: 2px; }
.nl-action-card--hero .nl-action-why   { font-size: var(--fs-md); margin-bottom: 12px; }
.nl-action-card--hero .nl-url-input    { height: 44px; font-size: var(--fs-md); border-width: 2px; }
.nl-action-card--hero .nl-url-go       { height: 44px; padding: 0 26px; font-size: var(--fs-base); }

.nl-action-badge {
  position: absolute;
  top: -11px;
  left: 20px;
  background: var(--gold);
  color: #fff;
  font-size: var(--fs-2xs);
  font-weight: 800;
  letter-spacing: .05em;
  text-transform: uppercase;
  padding: 3px 11px;
  border-radius: var(--r-xl);
  box-shadow: 0 2px 6px rgba(0,0,0,.18);
}

.nl-url-input {
  flex: 1;
  height: 34px;
  border: 1.5px solid var(--line);
  border-radius: var(--r-xl);
  padding: 0 14px;
  font-size: var(--fs-sm);
  background: var(--surface);
  color: var(--text);
  outline: none;
  transition: border-color var(--dur-1);
}

.nl-url-input:focus { border-color: var(--gold); }
.nl-url-input.loading { border-color: var(--gold); background: rgba(199, 154, 54, .08); }

.nl-url-go {
  height: 34px;
  padding: 0 18px;
  background: var(--gold);
  color: var(--on-gold);
  border: none;
  border-radius: var(--r-xl);
  font-size: var(--fs-sm);
  font-weight: 700;
  cursor: pointer;
  flex-shrink: 0;
  transition: opacity var(--dur-1);
}

.nl-url-go:hover    { opacity: .85; }
.nl-url-go:disabled { opacity: .5; cursor: default; }

.nl-bulk-go  { background: var(--teal-700) !important; }

.nl-quickfill-go   { background: var(--gold) !important; color: var(--on-gold) !important; }
.nl-quickfill-go:hover   { opacity: .88; }
.nl-quickfill-go.loading { opacity: .6; cursor: wait; }

.nl-sold-comps-strip {
  position: relative;
  border-top: 1px solid rgba(199,154,54,.35);
  background:
    radial-gradient(120% 140% at 85% -20%, rgba(199,154,54,.18) 0%, transparent 55%),
    linear-gradient(135deg, var(--teal-900) 0%, var(--teal-700) 55%, var(--teal-700) 100%);
  padding: 22px 40px 26px 40px;
  animation: nlSoldCompsIn var(--dur-2) ease-out;
}
@keyframes nlSoldCompsIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }

.nl-sold-comps-strip.loading .nl-sold-comps-glyph { animation: nlSpinPulse 1s ease-in-out infinite; }
@keyframes nlSpinPulse { 0%, 100% { opacity: 1; } 50% { opacity: .35; } }

.nl-sold-comps-close {
  position: absolute;
  top: 10px;
  right: 12px;
  width: 22px;
  height: 22px;
  border: none;
  background: transparent;
  color: rgba(255,255,255,.6);
  font-size: var(--fs-sm);
  cursor: pointer;
  border-radius: 50%;
  line-height: 1;
}
.nl-sold-comps-close:hover { background: rgba(255,255,255,.12); color: #fff; }

.nl-sold-comps-head {
  display: flex;
  align-items: center;
  gap: 24px;
  margin-bottom: 14px;
  font-size: var(--fs-md);
}
.nl-sold-comps-glyph { font-size: var(--fs-2xl); margin-right: 2px; }
.nl-sold-comps-title-text { font-weight: 800; color: #fff; font-size: var(--fs-lg); letter-spacing: .01em; }
.nl-sold-comps-summary-text { font-weight: 700; color: #fff; letter-spacing: .01em; margin-left: auto; }

/* Connect prompt — a self-contained card shown until a session exists. */
.nl-sold-comps-connect {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
}
.nl-sold-comps-connect.hidden { display: none; }

.nl-sold-comps-connect-card {
  display: flex;
  align-items: center;
  gap: 18px;
  max-width: 720px;
  width: 100%;
  background: rgba(255,255,255,.06);
  border: 1px solid rgba(255,255,255,.14);
  border-radius: var(--r-lg);
  padding: 16px 20px;
  backdrop-filter: blur(2px);
}
.nl-sold-comps-connect-icon {
  flex-shrink: 0;
  width: 46px;
  height: 46px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-2xl);
  background: rgba(199,154,54,.16);
  border: 1px solid rgba(199,154,54,.4);
  border-radius: var(--r-md);
}
.nl-sold-comps-connect-copy { flex: 1; display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.nl-sold-comps-connect-copy strong { color: #fff; font-size: var(--fs-md); font-weight: 800; letter-spacing: .01em; }
.nl-sold-comps-connect-text { color: rgba(255,255,255,.72); font-size: var(--fs-sm); line-height: 1.45; }
.nl-sold-comps-connect-btn {
  flex-shrink: 0;
  background: linear-gradient(180deg, var(--gold) 0%, var(--gold-dark) 100%);
  color: var(--on-gold);
  border: none;
  border-radius: var(--r-md);
  padding: 12px 22px;
  font-size: var(--fs-base);
  font-weight: 800;
  cursor: pointer;
  white-space: nowrap;
  box-shadow: 0 6px 16px rgba(199,154,54,.3);
  transition: transform var(--dur-1), box-shadow var(--dur-1), filter var(--dur-1);
}
.nl-sold-comps-connect-btn:hover { transform: translateY(-1px); filter: brightness(1.06); box-shadow: 0 9px 22px rgba(199,154,54,.4); }
.nl-sold-comps-connect-btn:active { transform: translateY(0); }
.nl-sold-comps-connect-btn:disabled { opacity: .6; cursor: not-allowed; transform: none; box-shadow: none; }
.nl-sold-comps-connect-msg { color: var(--gold-note); font-size: var(--fs-sm); text-align: center; }

@media (max-width: 640px) {
  .nl-sold-comps-connect-card { flex-direction: column; text-align: center; gap: 12px; }
  .nl-sold-comps-connect-copy { align-items: center; }
}
.nl-sold-comps-link {
  color: var(--gold-note);
  font-weight: 700;
  font-size: var(--fs-md);
  text-decoration: none;
  white-space: nowrap;
  padding: 4px 0;
}
.nl-sold-comps-link:hover { text-decoration: underline; color: #fff; }
.nl-sold-comps-link.hidden { display: none; }
.nl-sold-comps-link-secondary { color: rgba(255,255,255,.7); font-weight: 600; padding-left: 4px; border-left: 1px solid rgba(255,255,255,.2); margin-left: -4px; }

/* Stats row and the comps list below it share one width and one centre line. They used to
   disagree — the stats stretched edge to edge while the list was capped at 780px, so on a wide
   window the numbers ran the full panel and the sales sat in a narrow column underneath with
   dead space either side. Same max-width on both makes it read as one block. */
.nl-sold-comps-stats {
  display: flex;
  gap: 10px;
  margin-bottom: 10px;
  max-width: var(--sold-comps-w);
  margin-left: auto;
  margin-right: auto;
  width: 100%;
}
.nl-sold-comps-stat {
  flex: 1;
  background: rgba(255,255,255,.08);
  border: 1px solid rgba(255,255,255,.12);
  border-radius: var(--r-md);
  padding: 8px 10px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  text-align: center;
}
.nl-sold-comps-stat-val   { font-size: var(--fs-2xl); font-weight: 800; color: var(--gold-note); letter-spacing: -.01em; }
.nl-sold-comps-stat-label { font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: .07em; color: rgba(255,255,255,.65); }
.nl-sold-comps-stat-count .nl-sold-comps-stat-val { color: #fff; }

.nl-sold-comp-table {
  max-height: 420px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 7px;
  padding-right: 4px;
  max-width: var(--sold-comps-w);
  margin: 0 auto;
  width: 100%;
}
.nl-sold-comp-table:empty { display: none; }
/* Fixed tracks for date and price rather than auto: with auto, every row sized its own columns
   from its own text, so the dates and the money zig-zagged down the list instead of forming two
   straight edges. Price is right-aligned so the figures line up on the decimal side, which is
   how you actually scan a column of prices. */
.nl-sold-comp-row {
  display: grid;
  grid-template-columns: 32px minmax(0, 1fr) 62px 104px;
  align-items: center;
  gap: 10px;
  padding: 6px 10px;
  border-radius: var(--r-sm);
  background: rgba(255,255,255,.05);
  text-decoration: none;
  transition: background var(--dur-1);
}
.nl-sold-comp-row:hover { background: rgba(255,255,255,.13); }
.nl-sold-comp-thumb {
  width: 28px;
  height: 28px;
  border-radius: var(--r-xs);
  object-fit: cover;
  background: rgba(255,255,255,.1);
  flex-shrink: 0;
}
.nl-sold-comp-thumb-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-sm);
}
.nl-sold-comp-title {
  color: #fff;
  font-size: var(--fs-base);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.nl-sold-comp-date  { color: rgba(255,255,255,.6); font-size: var(--fs-sm); white-space: nowrap; text-align: right; }
.nl-sold-comp-price {
  color: var(--gold-note);
  font-weight: 700;
  font-size: var(--fs-md);
  white-space: nowrap;
  text-align: right;
  /* Tabular figures: proportional digits make a 1 narrower than a 0, so a column of prices
     never quite lines up. This locks every digit to the same advance width. */
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1;
}

/* Narrow windows: drop the fixed money tracks before they squeeze the title to nothing. */
@media (max-width: 720px) {
  .nl-sold-comp-row { grid-template-columns: 28px minmax(0, 1fr) auto; }
  .nl-sold-comp-date { display: none; }
}

.nl-ai-modify-input:focus { border-color: var(--accent-600) !important; }
.nl-ai-modify-input::placeholder { color: var(--pale-accent); }
.nl-ai-modify-go {
  background: var(--accent-600) !important;
  color: var(--on-solid) !important;
}
.nl-ai-modify-go:hover   { opacity: .88; }
.nl-ai-modify-go.loading { opacity: .6; cursor: wait; }

/* The modal's one scroller: everything between the pinned header and the pinned footer.
   flex:1 + min-height:0 is what lets it actually shrink inside the flex column — without the
   min-height a flex item refuses to go below its content height and the scrollbar never appears. */
.nl-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
}

.new-listing-body {
  display: grid;
  grid-template-columns: 360px 1fr;
  /* Sizes to its content and scrolls with the page above it. It used to be flex:1 with its own
     hidden overflow, which handed the form whatever height the blocks above hadn't already taken —
     about 115px on a normal window — and trapped 864px of fields inside it. */
  align-items: start;
}

.nl-image-panel {
  border-right: 1px solid var(--line);
  padding: 24px;
  background: var(--soft);
  display: flex;
  flex-direction: column;
  gap: 14px;
  flex-shrink: 0;
  /* Deliberately not sticky and not its own scroller. Sticky reads well until the photo column is
     itself taller than the window, at which point its lower half is unreachable — the same trap
     this change removes, moved to a different element. Both columns just flow. */
}

.nl-panel-label {
  font-size: var(--fs-3xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .1em;
  color: var(--muted);
}

/* ── Column section headings — full-bleed teal header band ──── */
.nl-section-heading {
  font-size: var(--fs-2xs);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .1em;
  color: rgba(255,255,255,.9);
  background: var(--teal-800);
  padding: 9px 24px;
  margin: -24px -24px 18px;
}

.nl-form-panel .nl-section-heading {
  margin: -20px -28px 18px;
  padding: 9px 28px;
}

/* The SKU a draft arrived with. Quiet — it is a fact about this listing, not an instruction — but
   it is about money already spent, so it reads before the first field rather than after the last. */
.nl-sku-note {
  margin: 0 0 14px;
  padding: 8px 12px;
  border-left: 3px solid var(--teal-600);
  border-radius: var(--r-sm);
  background: var(--card);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--muted);
  line-height: 1.5;
}

.nl-drop-zone {
  border: 2px dashed var(--line-strong);
  border-radius: var(--r-lg);
  padding: 32px 20px;
  text-align: center;
  cursor: pointer;
  background: var(--card);
  transition: border-color var(--dur-1), background var(--dur-1);
  /* contenteditable (needed so the browser offers a right-click "Paste") but not a real text field */
  caret-color: transparent;
  outline: none;
  user-select: none;
}

.nl-drop-zone:hover,
.nl-drop-zone.drag-over {
  border-color: var(--gold);
  background: var(--gold-soft);
}

.nl-drop-icon {
  width: 48px;
  height: 48px;
  margin: 0 auto 14px;
  border: 2.5px solid var(--line-strong);
  border-radius: 50%;
  position: relative;
}
/* Upload arrow via CSS — no emoji needed */
.nl-drop-icon::before {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -54%);
  width: 0;
  height: 0;
  border-left: 8px solid transparent;
  border-right: 8px solid transparent;
  border-bottom: 10px solid var(--faint);
}
.nl-drop-icon::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, 10%);
  width: 4px;
  height: 10px;
  background: var(--faint);
  border-radius: 0 0 2px 2px;
}
.nl-drop-title {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--brand-ink-strong);
  margin-bottom: 5px;
}
.nl-drop-sub {
  font-size: var(--fs-xs);
  color: var(--muted);
  margin-bottom: 4px;
}
.nl-drop-hint {
  font-size: var(--fs-2xs);
  color: var(--faint);
  font-style: italic;
}
.nl-drop-zone:hover .nl-drop-icon,
.nl-drop-zone.drag-over .nl-drop-icon {
  border-color: var(--gold);
}
.nl-drop-zone:hover .nl-drop-icon::before { border-bottom-color: var(--gold); }
.nl-drop-zone:hover .nl-drop-icon::after  { background: var(--gold); }

.nl-drop-zone p { font-size: var(--fs-sm); }
.nl-drop-zone .hint { margin-top: 4px; font-size: var(--fs-2xs); }
.nl-drop-zone .link { color: var(--brand-ink-2); font-weight: 700; cursor: pointer; text-decoration: underline; }

.nl-preview-wrap {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.nl-preview-wrap img {
  width: 100%;
  border-radius: var(--r-md);
  border: 1px solid var(--line);
  object-fit: contain;
  max-height: 280px;
  background: var(--soft-2);
}

.nl-cutout-wrap { display: flex; flex-direction: column; gap: 6px; }
.nl-cutout-img-wrap {
  position: relative;
  border-radius: var(--r-md);
  border: 2px solid var(--teal-700);
  overflow: hidden;
  background: repeating-conic-gradient(var(--line) 0% 25%, #fff 0% 50%) 0 0 / 16px 16px;
}
.nl-cutout-img-wrap img {
  width: 100%;
  max-height: 260px;
  object-fit: contain;
  display: block;
}
.nl-cutout-spinner {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center; gap: 8px;
  background: rgba(255,255,255,.75);
  font-size: var(--fs-sm); color: var(--brand-ink-2);
}

.nl-ai-status {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border-radius: var(--r-md);
  background: var(--info-tint);
  border: 1px solid var(--info-line);
  color: var(--info);
  font-size: var(--fs-sm);
}

.nl-ai-spinner {
  width: 16px;
  height: 16px;
  border: 2px solid var(--info-line);
  border-top-color: var(--info);
  border-radius: 50%;
  animation: spin .7s linear infinite;
  flex-shrink: 0;
}

.nl-ai-done {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 12px 14px;
  border-radius: var(--r-md);
  background: var(--success-tint);
  border: 1px solid var(--success-line);
  color: var(--success);
  font-size: var(--fs-sm);
  flex-wrap: wrap;
}

.nl-ai-check { font-size: var(--fs-lg); font-weight: 800; }

.nl-ai-error {
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 12px 14px;
  border-radius: var(--r-md);
  background: var(--danger-tint);
  border: 1px solid var(--danger-line);
  color: var(--danger);
  font-size: var(--fs-sm);
  flex-wrap: wrap;
}

.nl-form-panel {
  /* No scroller of its own — .nl-scroll above carries the whole screen now. */
  padding: 20px 28px 40px;
}

#nl-specifics-list {
  display: grid;
  gap: 8px;
  margin-bottom: 10px;
}

/* 6-slot photo grid — 3 columns in form, auto-fits in image panel */
.nl-photo-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 6px;
  margin-bottom: 8px;
}

/* In the left image panel the grid is narrower — use 3 cols still but smaller slots */
.nl-image-panel .nl-photo-grid {
  grid-template-columns: repeat(3, 1fr);
}

.nl-photo-slot {
  aspect-ratio: 1;
  border: 2px dashed var(--line);
  border-radius: var(--r-sm);
  position: relative;
  cursor: pointer;
  overflow: hidden;
  background: var(--surface);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  transition: border-color 0.15s, background 0.15s;
  min-height: 88px;
}

.nl-photo-slot:hover { border-color: var(--gold); }
.nl-photo-slot.drag-over { border-color: var(--gold); background: rgba(255,195,0,0.06); }
.nl-photo-slot.has-image { border-style: solid; border-color: var(--line); cursor: pointer; }
.nl-photo-slot.has-image:hover { border-color: var(--gold); }

.nl-photo-slot img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: var(--r-xs);
}

.slot-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
  color: var(--muted);
  user-select: none;
  pointer-events: none;
}

.slot-plus {
  font-size: var(--fs-2xl);
  line-height: 1;
  color: var(--line);
}

.slot-num {
  font-size: var(--fs-3xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
}

.slot-label {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  background: rgba(0,0,0,0.55);
  color: #fff;
  font-size: var(--fs-3xs);
  font-weight: 700;
  text-align: center;
  padding: 3px 4px;
  border-radius: 0 0 6px 6px;
  pointer-events: none;
}

.slot-remove {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 20px;
  height: 20px;
  background: rgba(0,0,0,0.6);
  color: #fff;
  border: none;
  border-radius: 50%;
  font-size: var(--fs-3xs);
  cursor: pointer;
  display: none;
  align-items: center;
  justify-content: center;
  z-index: 2;
  padding: 0;
  line-height: 1;
  opacity: 0;
  transition: opacity var(--dur-1);
}

.nl-photo-slot.has-image:hover .slot-remove { display: flex; opacity: 1; }
.nl-photo-slot.has-image .slot-placeholder  { display: none; }

.slot-rembg {
  position: absolute;
  bottom: 22px;
  left: 4px;
  background: rgba(0,0,0,0.55);
  color: #fff;
  border: none;
  border-radius: var(--r-2xs);
  font-size: var(--fs-3xs);
  font-weight: 700;
  padding: 2px 5px;
  cursor: pointer;
  display: none;
  z-index: 2;
  opacity: 0;
  transition: opacity var(--dur-1);
}

.nl-photo-slot.has-image:hover .slot-rembg { display: block; opacity: 1; }

.slot-ebay-badge {
  position: absolute;
  top: 4px;
  left: 4px;
  background: var(--src-ebay);
  color: #fff;
  font-size: 8px;
  font-weight: 900;
  padding: 1px 4px;
  border-radius: var(--r-3xs);
  z-index: 2;
  pointer-events: none;
}

.nl-photo-upload-status {
  font-size: var(--fs-xs);
  color: var(--muted);
  font-weight: 700;
  padding: 4px 0;
  display: flex;
  align-items: center;
  gap: 6px;
}

.nl-photo-upload-status::before {
  content: "";
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--gold);
  display: inline-block;
  flex-shrink: 0;
}

.nl-photo-upload-status.done::before { background: var(--success); }

/* Two stacked bands: the readiness check, then the result message and the actions.
   This used to be one flex row of [message | buttons]. Adding the readiness bar as a
   third item in that row made it a very tall row that overlapped the scrollable form
   body above it and swallowed clicks on the bottom of the form — caught in a browser
   pass, not by reading the CSS. The row it always was now lives in .nl-footer-row. */
.new-listing-footer {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--s2);
  padding: 14px 28px;
  border-top: 1px solid var(--line);
  background: var(--card);
  flex-shrink: 0;
  /* A long fix list plus a blocker gate can otherwise grow the footer until the form
     above it is a sliver. The footer scrolls before the form loses its room. */
  max-height: 52vh;
  overflow-y: auto;
}

.nl-footer-row {
  display: flex;
  align-items: center;
  gap: 14px;
}

.nl-result-msg {
  flex: 1;
  font-size: var(--fs-sm);
  border-radius: var(--r-sm);
  padding: 8px 12px;
  min-height: 36px;
  display: flex;
  align-items: center;
}

.nl-result-msg:empty { background: none; }

.nl-result-msg.success {
  background: var(--success-tint);
  border: 1px solid var(--success-line);
  color: var(--success);
}

.nl-result-msg.error {
  background: var(--danger-tint);
  border: 1px solid var(--danger-line);
  color: var(--danger);
}

.powered-by {
  font-size: var(--fs-2xs);
  font-weight: 600;
  color: var(--gold-light);
  letter-spacing: .03em;
  margin-right: auto;
  white-space: nowrap;
}

.app-footer {
  padding: 10px 28px;
  border-top: 1px solid var(--line);
  background: var(--surface);
  font-size: var(--fs-2xs);
  color: var(--muted);
  display: flex;
  gap: 10px;
  align-items: center;
}
.app-footer strong { color: var(--brand-ink-2); }
.app-footer-divider { opacity: .4; }

.nl-footer-actions {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-shrink: 0;
}

.sd-test-msg {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--muted);
  flex: 1;
}
.sd-test-msg.ok    { color: var(--success); }
.sd-test-msg.error { color: var(--danger); }

/* AI generated photos section */
.nl-photos-section {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.nl-photos-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.nl-photos-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 8px;
}

.nl-photo-thumb-wrap {
  position: relative;
  border-radius: var(--r-md);
  overflow: hidden;
  border: 2px solid var(--line);
  background: var(--soft-2);
  aspect-ratio: 1;
  cursor: pointer;
  transition: border-color var(--dur-1), transform var(--dur-1);
}

.nl-photo-thumb-wrap:hover { border-color: var(--gold); transform: scale(1.02); }
.nl-photo-thumb-wrap.selected { border-color: var(--success); }

.nl-photo-thumb-wrap img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.nl-photo-thumb-badge {
  position: absolute;
  top: 4px;
  right: 4px;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--success);
  color: var(--on-solid);
  font-size: var(--fs-2xs);
  font-weight: 800;
  display: grid;
  place-items: center;
  opacity: 0;
  transition: opacity var(--dur-1);
}

.nl-photo-thumb-wrap.selected .nl-photo-thumb-badge { opacity: 1; }

.nl-photo-thumb-label {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  background: rgba(0,0,0,.55);
  color: #fff;
  font-size: var(--fs-3xs);
  font-weight: 700;
  text-align: center;
  padding: 3px;
}

/* Watch count badge on listing cards */
.watch-badge {
  font-size: var(--fs-2xs);
  font-weight: 700;
  color: var(--brand-ink-2);
  background: var(--teal-100);
  border: 1px solid var(--teal-200);
  border-radius: var(--r-xs);
  padding: 2px 7px;
}

/* View on eBay link on listing cards */
.view-ebay-link {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--gold-ink);
  text-decoration: underline;
}

/* ── Business Policy loader ──────────────────────────────────── */
.policy-load-row {
  display: flex;
  align-items: center;
  gap: 10px;
  margin-bottom: 14px;
  flex-wrap: wrap;
}
.policy-hub-link {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--gold-ink);
  text-decoration: underline;
  margin-left: auto;
}

/* ── Description tab bar + preview ──────────────────────────── */
.desc-tab-bar {
  display: flex;
  align-items: center;
  gap: 2px;
  margin-bottom: 6px;
}
.desc-tab {
  background: none;
  border: 1px solid var(--line);
  border-bottom: none;
  border-radius: 6px 6px 0 0;
  padding: 5px 14px;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--muted);
  cursor: pointer;
  transition: background var(--dur-1), color var(--dur-1);
}
.desc-tab.active {
  background: var(--soft);
  color: var(--brand-ink-2);
  border-color: var(--teal-700);
}
.desc-tab-hint {
  margin-left: auto;
  font-size: var(--fs-2xs);
  color: var(--muted);
  opacity: .7;
}
.nl-desc-preview {
  border: 1px solid var(--line);
  border-radius: 0 6px 6px 6px;
  min-height: 200px;
  max-height: 520px;
  overflow-y: auto;
  background: var(--card);
  padding: 0;
}

/* SEO improvement spinner inside the ai-done bar */
#nl-btn-improve-seo:disabled { opacity: .6; cursor: default; }

/* ── Image Generator Setup Modal ─────────────────────────────── */
.imggen-setup-modal {
  max-width: 700px;
  width: 96%;
  max-height: 90vh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
}

/* Detection banner */
.imggen-detect-banner {
  padding: 10px 16px;
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  font-weight: 600;
  margin: 0 0 16px;
  border: 1px solid transparent;
}
.imggen-detect-banner.detecting {
  background: var(--soft-2);
  border-color: var(--line-strong);
  color: var(--muted);
}
.imggen-detect-banner.detected {
  background: var(--success-soft);
  border-color: var(--success-line);
  color: var(--success);
}
.imggen-detect-banner.not-detected {
  background: var(--warning-tint);
  border-color: var(--gold-pale);
  color: var(--gold-deep);
}

/* Tabs */
.imggen-tabs {
  display: flex;
  gap: 4px;
  margin-bottom: 16px;
  border-bottom: 2px solid var(--line);
  padding-bottom: 0;
}
.imggen-tab {
  background: none;
  border: none;
  border-bottom: 3px solid transparent;
  margin-bottom: -2px;
  padding: 8px 14px;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
  border-radius: 6px 6px 0 0;
  transition: color var(--dur-1), border-color var(--dur-1);
}
.imggen-tab:hover { color: var(--brand-ink-2); }
.imggen-tab.active {
  color: var(--brand-ink-2);
  border-bottom-color: var(--teal-700);
  background: var(--soft);
}
.imggen-recommended-tag {
  background: var(--gold);
  color: var(--brand-ink-strong);
  font-size: var(--fs-3xs);
  font-weight: 800;
  padding: 2px 5px;
  border-radius: var(--r-2xs);
  vertical-align: middle;
  margin-left: 4px;
  text-transform: uppercase;
}

/* Instruction panels */
.imggen-panel { padding: 4px 0 16px; }
.imggen-desc {
  font-size: var(--fs-sm);
  color: var(--muted);
  margin: 0 0 14px;
  line-height: 1.5;
}
.imggen-steps {
  margin: 0 0 16px;
  padding-left: 20px;
  font-size: var(--fs-sm);
  line-height: 1.8;
  color: var(--text);
}
.imggen-steps li { margin-bottom: 4px; }
.imggen-steps code {
  background: var(--soft);
  border: 1px solid var(--line);
  border-radius: var(--r-2xs);
  padding: 1px 5px;
  font-size: var(--fs-xs);
  font-family: var(--font-mono);
}
.imggen-download-btn {
  display: inline-block;
  text-decoration: none;
  margin-top: 4px;
}

/* Connection settings */
.imggen-connection-section {
  border-top: 1px solid var(--line);
  padding-top: 16px;
  margin-top: 4px;
}
.imggen-connection-section h4 {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--brand-ink-2);
  margin: 0 0 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
}
.imggen-connection-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  margin-bottom: 12px;
}
.imggen-connection-grid .field { margin: 0; }
.imggen-test-row {
  display: flex;
  align-items: center;
  gap: 12px;
}

/* Status bar inside new-listing photos section */
.nl-imggen-status-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--muted);
  padding: 6px 0 8px;
  flex-wrap: wrap;
}
.nl-imggen-status-bar .btn { margin: 0; padding: 3px 9px; font-size: var(--fs-2xs); }

.view-ebay-link:hover { color: var(--gold); }

/* ── Settings page cards + optional connect strips ────────────
   Required setup, then the optional strips, in that order and looking different
   from each other: the required card carries the brand's gold edge, an optional
   one is a plain card with a state chip. Nothing optional should be able to look
   like something you are behind on. */
.settings-card { padding-bottom: 18px; }

#pg-required-card {
  border-top: 3px solid var(--gold);
  border-radius: var(--r-lg);
  margin-top: 18px;
  padding: 18px 22px;
  background: var(--card);
}

.settings-card-copy {
  margin-bottom: 14px;
  color: var(--muted);
  font-size: var(--fs-sm);
  line-height: 1.55;
}
.settings-card-copy strong { color: var(--ink); font-weight: 650; }

.settings-card-note {
  margin-top: 12px;
  color: var(--muted);
  font-size: var(--fs-xs);
}

.pg-required-list {
  display: grid;
  gap: 8px;
  margin: 0 0 14px;
  padding: 0;
  list-style: none;
}

.pg-required-item {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 10px 12px;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--soft);
}
.pg-required-item.is-done { opacity: .7; }

.pg-required-text {
  color: var(--muted);
  font-size: var(--fs-sm);
  line-height: 1.5;
}
.pg-required-text strong { color: var(--ink); font-weight: 650; }

.pg-required-state,
.setup-req-state,
.connect-state {
  flex-shrink: 0;
  padding: 2px 9px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--soft-2);
  color: var(--muted);
  font-size: var(--fs-2xs);
  font-weight: 750;
  text-transform: uppercase;
  letter-spacing: .06em;
  white-space: nowrap;
}
.pg-required-state.is-done,
.setup-req-state.is-done,
.connect-state.is-on {
  border-color: var(--success-line);
  background: var(--success-tint);
  color: var(--success);
}

.settings-eyebrow,
.settings-optional-eyebrow {
  margin: 26px 0 -4px;
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 750;
  text-transform: uppercase;
  letter-spacing: .13em;
}

.connect-card-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  margin-bottom: 10px;
}
.connect-card-head h3 { margin-bottom: 0; }

.connect-tag {
  margin-left: 6px;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: var(--soft-2);
  color: var(--muted);
  font-size: var(--fs-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
}

/* ── The Settings screen ───────────────────────────────────────────────────
   Six subjects, about three screens of scrolling, and until this pass the only
   way to reach one of them was to scroll past the other five. Three things
   change and nothing about what any control does:

   1. A rail, so the page has a map. Sticky, so the map does not scroll away.
   2. Every card is one subject. Fees and Listing Defaults shared a card with
      two Save buttons in it, which is a trap: press the first one and you have
      saved neither half of what you just typed.
   3. Units live on the field, not in a parenthesis at the end of the label.
      "13.25" is a fee, a dollar amount or a percentage depending on a bracket
      four words away — and getting that wrong writes a wrong number into every
      profit figure the app shows. */
.settings-panel { max-width: 1240px; }

.settings-layout {
  display: grid;
  grid-template-columns: 196px minmax(0, 1fr);
  gap: var(--s6);
  align-items: start;
}

/* Pinned below the workspace tab bar, not under it. `.ws-tab-bar` is
   position:fixed at z-index 950 and is only ever 0px tall until a second tab is
   opened — so a rail pinned at a flat `top` looks right for exactly as long as
   the seller has one tab, and loses its first entry behind the bar the moment
   they have two. The sidebar already offsets by this token; so does this. */
.settings-nav {
  position: sticky;
  top: calc(var(--ws-tabbar-h) + var(--s5));
  display: grid;
  gap: 2px;
  padding: var(--s2);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--card);
  box-shadow: var(--e1);
}

.settings-nav-link {
  position: relative;
  display: block;
  width: 100%;
  padding: 8px 10px 8px 14px;
  border: 0;
  border-radius: var(--r-sm);
  background: none;
  color: var(--muted);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 650;
  text-align: left;
  cursor: pointer;
  transition: color var(--dur-1) var(--ease), background-color var(--dur-1) var(--ease);
}

/* One marker that never leaves the DOM, so reading down the page grows and
   shrinks a gold bar rather than blinking six of them on and off. */
.settings-nav-link::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 50%;
  width: 3px;
  height: 0;
  border-radius: var(--r-pill);
  background: var(--gold);
  transform: translateY(-50%);
  transition: height var(--dur-2) var(--ease-out);
}

.settings-nav-link:hover { color: var(--ink); background: var(--soft); }
.settings-nav-link:focus-visible { outline: none; box-shadow: var(--focus-ring); }

.settings-nav-link[aria-current="true"] {
  color: var(--ink);
  background: var(--soft-2);
  font-weight: 750;
}
.settings-nav-link[aria-current="true"]::before { height: 18px; }

.settings-body { min-width: 0; }

/* A scroll target has to clear the top of the viewport rather than land under
   it — otherwise pressing a rail item puts its own heading off-screen. */
.settings-body .settings-card,
.settings-group { scroll-margin-top: calc(var(--ws-tabbar-h) + var(--s5)); }

/* Real cards, not rule-separated bands. `.setup-section` gives every one of
   these a hairline and no frame, which is right inside the setup modal where
   they are steps of one thing, and wrong on a page where they are six separate
   subjects. `#pg-required-card` keeps its own gold-edged treatment — it is the
   only one of them you are behind on. */
.settings-body .settings-card {
  margin-top: var(--s4);
  padding: var(--s5) var(--s5) var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--card);
  box-shadow: var(--e1);
}

.settings-fieldset { margin-top: var(--s5); }

.settings-subhead {
  margin: 0;
  padding-bottom: var(--s2);
  border-bottom: 1px solid var(--line-soft);
  color: var(--ink);
  font-size: var(--fs-xs);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
}

.settings-subnote {
  margin: var(--s2) 0 0;
  max-width: 72ch;
  color: var(--muted);
  font-size: var(--fs-xs);
  line-height: 1.55;
}

.settings-fieldset .form-grid,
.settings-fieldset .measure-grid { margin-top: var(--s3); }

/* Five short numbers that belong together read as five short numbers, not as
   two-and-a-half rows of a two-column form. */
.measure-grid {
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 14px;
}

/* The action row is the bottom of a card, so it is drawn as one. */
.settings-card-foot {
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--line-soft);
}

.field-hint {
  color: var(--muted);
  font-size: var(--fs-2xs);
  line-height: 1.45;
}

.field-inline {
  display: flex;
  align-items: center;
  gap: 6px;
}
.field-inline > select,
.field-inline > input { flex: 1; min-width: 0; }

/* ── Units on the field ────────────────────────────────────────────────
   The mark is a second <label for> on the same input, not a decorative span:
   an input may carry any number of labels and their text concatenates, so the
   unit is part of the accessible name rather than a symbol only sighted users
   get — and clicking it focuses the field, like the label it is. */
/* A percentage is two characters wide. Letting it stretch to a third of a
   1240px page is the difference between a form that was laid out and a form
   that was merely filled with controls. */
.unit-field {
  position: relative;
  display: block;
  max-width: 240px;
}

.unit-field > input { padding-right: 38px; }
.unit-field.is-prefix > input { padding-right: 11px; padding-left: 26px; }

/* Spinner arrows would sit exactly where the suffix does. Keyboard stepping
   still works — only the two-pixel mouse targets go. */
.unit-field > input[type="number"] { appearance: textfield; -moz-appearance: textfield; }
.unit-field > input[type="number"]::-webkit-outer-spin-button,
.unit-field > input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }

.unit-mark {
  position: absolute;
  top: 1px;
  right: 1px;
  bottom: 1px;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 30px;
  padding: 0 10px 0 4px;
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: 0;
  cursor: text;
  user-select: none;
}

.unit-field.is-prefix > .unit-mark {
  right: auto;
  left: 1px;
  min-width: 0;
  padding: 0 4px 0 11px;
  border-radius: var(--r-sm) 0 0 var(--r-sm);
}

/* Below the two-column width the rail becomes a scroller across the top: still
   the same six names in the same order, still sticky, still marking where you
   are — the marker just moves from the left edge to the underline.

   The breakpoint is 1280 and not something narrower because the fixed sidebar
   has already taken 320px before this page gets a say: on a 1280-wide laptop a
   side rail leaves the form 672px, and the same page with the rail overhead
   gets 896. A rail is worth having; it is not worth two-thirds of the form. */
@media (max-width: 1280px) {
  .settings-layout {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--s4);
  }

  .settings-nav {
    position: sticky;
    top: var(--ws-tabbar-h);
    z-index: 5;
    display: flex;
    gap: var(--s1);
    overflow-x: auto;
    border-radius: var(--r-md);
  }

  /* `width: auto` is load-bearing: the stacked rail sets width:100%, and a flex
     item keeping it is one viewport wide each — six of them in a row that then
     scrolls, showing exactly one name at a time. */
  .settings-nav-link {
    flex: 0 0 auto;
    width: auto;
    padding: 7px 12px;
    white-space: nowrap;
  }

  .settings-nav-link::before {
    left: 10px;
    right: 10px;
    top: auto;
    bottom: 3px;
    width: auto;
    transform: none;
  }
  .settings-nav-link[aria-current="true"]::before { height: 2px; }

  /* The rail is overhead now, so a card scrolled to has to clear it as well as
     the tab bar it is itself sitting under. */
  .settings-body .settings-card,
  .settings-group { scroll-margin-top: calc(var(--ws-tabbar-h) + 62px); }
}

@media (max-width: 900px) {
  .measure-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (max-width: 640px) {
  .measure-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .settings-body .settings-card { padding: var(--s4) var(--s4) var(--s3); }
}

/* ── Connections: the connection doctor ─────────────────────────────
   One row per connection, in the same card frame and the same chip as the
   connect strips above — it answers the same question, it just answers it
   with a live test rather than a saved flag. The tones are the app's
   semantic families and nothing else, and green is spent on exactly one
   thing: the doctor said Connected. */
.cd-head-actions {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-shrink: 0;
}

.cd-rows {
  display: grid;
  gap: var(--s2);
}

.cd-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--line);
  /* The tone lives on one edge rather than in the fill. Four rows of solid
     red is a screen nobody reads twice, and three of these connections are
     optional — "not set up" must not look like a fire. */
  border-left: 3px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--soft);
}

.cd-row--ok      { border-left-color: var(--success); background: var(--success-tint); }
.cd-row--warn    { border-left-color: var(--warning); background: var(--warning-tint); }
.cd-row--bad     { border-left-color: var(--danger);  background: var(--danger-tint); }
.cd-row--unknown { border-left-color: var(--line-strong); background: var(--soft); }

.cd-row-body {
  display: grid;
  gap: var(--s1);
  flex: 1 1 auto;
  min-width: 0;
}

.cd-row-head {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
}

.cd-row-name {
  font-family: var(--font-display);
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-snug);
  color: var(--ink);
}

/* The verdict, in the same pill as .connect-state on the strips above, tinted
   by the row's tone so the word and the colour can never disagree. */
.cd-chip {
  flex-shrink: 0;
  padding: 2px 9px;
  border-radius: var(--r-pill);
  border: 1px solid var(--line);
  background: var(--soft-2);
  color: var(--muted);
  font-size: var(--fs-2xs);
  font-weight: 750;
  text-transform: uppercase;
  letter-spacing: .06em;
  white-space: nowrap;
}
.cd-row--ok   .cd-chip { border-color: var(--success-line); background: var(--success-soft); color: var(--success); }
.cd-row--warn .cd-chip { border-color: var(--warning-line); background: var(--warning-soft); color: var(--warning); }
.cd-row--bad  .cd-chip { border-color: var(--danger-line);  background: var(--danger-soft);  color: var(--danger); }

/* Why it is broken — the sentence that used to not exist anywhere. */
.cd-reason {
  color: var(--ink-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
}

/* The line that says a person has to do something themselves. It gets a
   callout of its own because "click Connect, then go and type your password
   into the window that opens" is the instruction people miss, and a login
   window waiting behind the app looks identical to a hung button. */
.cd-human {
  margin-top: var(--s1);
  padding: 6px 10px;
  border: 1px solid var(--warning-line);
  border-radius: var(--r-xs);
  background: var(--warning-soft);
  color: var(--warning);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
}
.cd-human strong { font-weight: var(--fw-heavy); }

.cd-next {
  color: var(--ink-2);
  font-size: var(--fs-sm);
  font-weight: 600;
  line-height: var(--lh-normal);
}

/* Corroboration: status codes, expiry dates, row counts. Monospaced because
   it is machine output, muted because it is for the one time in ten that the
   sentence above it isn't enough. Never a token, cookie or key — see the
   Detail contract on ConnectionCheck. */
.cd-detail {
  color: var(--muted);
  font-size: var(--fs-xs);
  font-family: var(--font-mono);
  overflow-wrap: anywhere;
}

.cd-row-actions { flex-shrink: 0; }

/* Narrow: the fix button goes under the reason rather than squeezing it into
   a column two words wide. */
@media (max-width: 720px) {
  .cd-row { flex-direction: column; align-items: stretch; }
}

/* Arriving from the dashboard or the setup modal: say which card was meant. */
.connect-card.is-flagged,
.settings-card.is-flagged {
  outline: 2px solid var(--gold);
  outline-offset: 4px;
  border-radius: var(--r-md);
}

/* ── Setup modal: the two required steps ─────────────────────── */
.setup-notice {
  margin: 14px 22px 0;
  padding: 12px 14px;
  border: 1px solid var(--success-line);
  border-radius: var(--r-sm);
  background: var(--success-tint);
  color: var(--success);
  font-size: var(--fs-sm);
}

.setup-req-eyebrow {
  margin: 18px 22px 0;
  color: var(--gold-ink);
  font-size: var(--fs-xs);
  font-weight: 750;
  text-transform: uppercase;
  letter-spacing: .13em;
}

.setup-req {
  margin: 12px 22px 0;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--card);
}
.setup-req.is-done { border-color: var(--success-line); }
/* Saved, and refused by Anthropic. The card that holds the field is where the fix happens, so it
   is the card that carries the warning — a red line on the dashboard and a neutral settings box
   sends the seller looking for the problem in the wrong place. */
.setup-req.is-problem { border-color: var(--danger-line); background: var(--danger-tint); }
.setup-req.is-problem .setup-req-num { background: var(--danger); color: var(--danger-tint); }

.setup-req-head {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  margin-bottom: 14px;
}

.setup-req-num {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  border-radius: var(--r-pill);
  background: var(--teal-800);
  color: var(--gold-note);
  font-size: var(--fs-sm);
  font-weight: 700;
}
.setup-req.is-done .setup-req-num { background: var(--success); color: var(--success-tint); }

.setup-req-heading { flex: 1; min-width: 0; }
.setup-req-heading h3 { margin-bottom: 3px; }

.setup-req-sub {
  color: var(--muted);
  font-size: var(--fs-sm);
  line-height: 1.55;
}

/* The key field is the single most important input in the app; it gets the room. */
.setup-key-row {
  display: flex;
  align-items: center;
  gap: 8px;
}
.setup-key-row input {
  flex: 1;
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
}
.setup-key-row .btn { flex-shrink: 0; }

.setup-req-hint {
  margin-top: 8px;
  color: var(--muted);
  font-size: var(--fs-xs);
  line-height: 1.6;
}
.setup-req-hint strong { color: var(--ink); }
.setup-req-hint code {
  padding: 1px 5px;
  border-radius: var(--r-xs);
  background: var(--soft-2);
  color: var(--brand-ink);
}

/* "Test key" and whatever Anthropic said about it, on one line under the field it is about —
   and "Test connection" with what eBay said, which is the same row doing the same job one
   required step along. Deliberately one rule: the two verdicts are read the same way, so they
   must not drift into looking like different kinds of thing. */
.ai-key-check,
.ebay-link-check {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 12px;
}
.ai-key-check .btn,
.ebay-link-check .btn { flex-shrink: 0; }
.ai-key-check .sd-test-msg,
.ebay-link-check .sd-test-msg { flex: 1; min-width: 180px; }

.setup-policy-manual {
  margin-top: 12px;
}
.setup-policy-manual > summary {
  cursor: pointer;
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 700;
}
.setup-policy-manual[open] > summary { margin-bottom: 10px; }

.setup-optional-note {
  margin: 18px 22px 0;
  padding: 12px 14px;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
  color: var(--muted);
  font-size: var(--fs-sm);
  line-height: 1.6;
}

.setup-advanced { margin: 16px 22px 0; }
.setup-advanced > summary {
  cursor: pointer;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--soft);
  color: var(--ink-2);
  font-size: var(--fs-sm);
  font-weight: 700;
}
.setup-advanced > summary:hover { border-color: var(--line-strong); }
.setup-advanced[open] > summary { margin-bottom: 6px; }
.setup-advanced .setup-section { padding-left: 0; padding-right: 0; }

/* ── License nag banner ──────────────────────────────────────── */
.license-nag {
  position: sticky;
  top: 0;
  z-index: 200;
  padding: 10px 24px;
  background: var(--grad-brand-deep), var(--teal-950);
  border-bottom: 1px solid rgba(224, 187, 99, .28);
  box-shadow: var(--e2);
}

/* The house hairline, so the banner belongs to the same family as the hero
   panel and the sidebar rather than being a separate coloured strip. */
.license-nag::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 1px;
  background: var(--grad-hairline);
}
.license-nag-inner {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  font-size: var(--fs-sm);
  color: var(--on-dark-2);
}
.license-nag-inner strong { color: var(--gold-light); font-weight: var(--fw-bold); }
.license-nag-inner a { color: var(--teal-200); text-underline-offset: 2px; }
.license-nag-inner .btn { flex-shrink: 0; }

/* ── Photo Editor Modal ──────────────────────────────────────── */
#photo-editor-modal {
  position: fixed;
  inset: 0;
  z-index: 10000;
  background: var(--dark-0);
  display: flex;
  flex-direction: column;
  font-family: inherit;
}
#photo-editor-modal.hidden { display: none; }

.pe-header {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 8px 12px;
  background: var(--dark-1);
  border-bottom: 1px solid var(--dark-line);
  flex-shrink: 0;
  flex-wrap: wrap;
  position: relative;
}

.pe-header::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 1px;
  background: var(--grad-hairline);
}

.pe-tab {
  padding: 5px 13px;
  border: 1px solid transparent;
  border-radius: var(--r-xs);
  background: transparent;
  color: var(--on-dark-3);
  cursor: pointer;
  font-size: var(--fs-sm);
  font-weight: 600;
  white-space: nowrap;
  transition: color var(--dur-1), background var(--dur-1);
}
.pe-tab:hover { color: var(--on-dark); background: var(--dark-2); }
.pe-tab.active {
  color: var(--on-dark);
  background: var(--dark-3);
  border-color: rgba(199, 154, 54, .55);
  box-shadow: inset 0 0 0 1px rgba(199, 154, 54, .12);
}

.pe-spacer { flex: 1; min-width: 8px; }

.pe-btn-save {
  background: var(--gold);
  color: var(--on-gold);
  border: none;
  border-radius: var(--r-xs);
  padding: 6px 18px;
  font-weight: 700;
  cursor: pointer;
  font-size: var(--fs-sm);
  margin-left: 4px;
}
.pe-btn-save:hover { background: var(--gold-dark); }
.pe-btn-save:disabled { opacity: .5; cursor: default; }

.pe-btn-cancel {
  background: var(--dark-2);
  color: var(--on-dark-2);
  border: 1px solid var(--dark-line);
  border-radius: var(--r-xs);
  padding: 6px 16px;
  font-weight: 700;
  cursor: pointer;
  font-size: var(--fs-sm);
}
.pe-btn-cancel:hover { color: #fff; }

.pe-tool-panel {
  padding: 8px 14px;
  background: var(--dark-1);
  border-bottom: 1px solid var(--dark-line);
  flex-shrink: 0;
  min-height: 46px;
  display: flex;
  align-items: center;
  gap: 14px;
  flex-wrap: wrap;
}

.pe-hint { color: var(--on-dark-3); font-size: var(--fs-xs); }

.pe-btn-apply {
  background: var(--gold);
  color: var(--on-gold);
  border: none;
  border-radius: var(--r-xs);
  padding: 4px 14px;
  font-weight: 700;
  cursor: pointer;
  font-size: var(--fs-xs);
}
.pe-btn-apply:disabled { opacity: .35; cursor: default; }
.pe-btn-apply:not(:disabled):hover { background: var(--gold-dark); }

.pe-btn-reset {
  background: var(--dark-2);
  color: var(--on-dark-2);
  border: 1px solid var(--dark-line);
  border-radius: var(--r-xs);
  padding: 4px 12px;
  font-weight: 700;
  cursor: pointer;
  font-size: var(--fs-xs);
}
.pe-btn-reset:hover { color: #fff; }

.pe-adj-label {
  display: flex;
  align-items: center;
  gap: 6px;
  color: var(--on-dark-2);
  font-size: var(--fs-xs);
  font-weight: 600;
  white-space: nowrap;
}
.pe-adj-label input[type=range] { width: 90px; cursor: pointer; accent-color: var(--gold); }
.pe-adj-label input[type=color] {
  width: 28px; height: 22px;
  border: 1px solid var(--dark-line-strong);
  border-radius: var(--r-3xs);
  cursor: pointer;
  padding: 1px;
  background: none;
}

.pe-filter-list { display: flex; gap: 6px; flex-wrap: wrap; }

.pe-filter-btn {
  padding: 4px 12px;
  border: 1px solid var(--dark-line);
  border-radius: var(--r-xs);
  background: var(--dark-2);
  color: var(--on-dark-2);
  cursor: pointer;
  font-size: var(--fs-xs);
  font-weight: 600;
}
.pe-filter-btn:hover { color: #fff; border-color: var(--dark-line-strong); }
.pe-filter-btn.active { color: var(--on-gold); background: var(--gold); border-color: var(--gold); }

.pe-canvas-area {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  background: var(--dark-0);
  padding: 16px;
}

.pe-canvas-wrap {
  position: relative;
  display: inline-flex;
  line-height: 0;
}

#pe-canvas {
  display: block;
  max-width: calc(100vw - 48px);
  max-height: calc(100vh - 220px);
  width: auto;
  height: auto;
  box-shadow: 0 4px 32px rgba(0,0,0,.6);
  /* checkerboard pattern to show transparent areas */
  background-color: var(--dark-2);
  background-image:
    linear-gradient(45deg, var(--dark-line) 25%, transparent 25%),
    linear-gradient(-45deg, var(--dark-line) 25%, transparent 25%),
    linear-gradient(45deg, transparent 75%, var(--dark-line) 75%),
    linear-gradient(-45deg, transparent 75%, var(--dark-line) 75%);
  background-size: 16px 16px;
  background-position: 0 0, 0 8px, 8px -8px, -8px 0;
}

#pe-overlay {
  position: absolute;
  top: 0; left: 0;
  width: 100%;
  height: 100%;
  display: block;
  pointer-events: none;
}

/* ── Opportunity Finder (full-page overlay, like New Listing) ─────────────
   Eighteen of the app's twenty-four views render through this one class, so
   `inset: 0` here is what put an opaque panel over the sidebar on nearly every
   screen. It is a page, not a dialog: it has no scrim, nothing behind it is
   dismissable, and the nav underneath stayed alive but unreachable. Leaving the
   sidebar column clear is the whole fix — see --sidebar-w. */
.opportunity-overlay {
  position: fixed;
  inset: 0 0 0 var(--sidebar-w);
  z-index: 900;
  display: flex;
  flex-direction: column;
  background: var(--card);
}

.opportunity-overlay-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s4) var(--s7);
  background:
    radial-gradient(70% 200% at 100% 0%, rgba(199, 154, 54, .18), transparent 70%),
    linear-gradient(90deg, var(--teal-900), var(--teal-800));
  color: #fff;
  flex-shrink: 0;
  position: relative;
  box-shadow: var(--e2);
  z-index: 1;
}

.opportunity-overlay-header::after {
  content: "";
  position: absolute;
  inset: auto 0 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, rgba(224, 187, 99, .55), transparent);
}

.opportunity-overlay-header-left {
  display: flex;
  align-items: center;
  gap: var(--s3);
}

.opportunity-overlay-header .eyebrow {
  color: rgba(255, 255, 255, .50);
  font-size: var(--fs-2xs);
  font-weight: 750;
  text-transform: uppercase;
  letter-spacing: .13em;
  margin-bottom: 3px;
}

.opportunity-overlay-header h2 {
  color: #fff;
  font-size: var(--fs-xl);
  font-weight: 700;
  letter-spacing: -.02em;
}

.opportunity-overlay-header-actions {
  display: flex;
  gap: var(--s2);
  flex-shrink: 0;
  align-items: center;
}

/* Where the "Import eBay Sales" button used to be. Deliberately not styled as a button and not
   placed like one: there is nothing to press, and a report that looks pressable is a worse lie
   than no report. Sits quiet when everything is current and only gains colour when it doesn't. */
.er-auto-status {
  font-size: var(--fs-xs);
  color: rgba(255, 255, 255, .72);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  /* Wide enough for the longest thing it says — "✓ Sales updated 12 minutes ago — this keeps
     itself current" — because a status ellipsised mid-sentence reads as a layout bug. */
  max-width: 56ch;
}
.er-auto-status.is-ok      { color: var(--success-tint); }
.er-auto-status.is-waiting { color: var(--gold-note); }
.er-auto-status.is-problem { color: var(--gold-note); font-weight: 650; }

.opportunity-overlay-body {
  flex: 1;
  overflow-y: auto;
  padding: var(--s6) var(--s7) var(--s8);
  background: var(--page);
}

/* Every overlay page shares one content column so the three of them line up
   with each other and with the dashboard. */
.opportunity-overlay-body > * { max-width: 1320px; margin-inline: auto; }

/* Below the shell breakpoint the sidebar stops being a column and becomes a
   nav strip stacked above the content (see the max-width:1120px block near
   .app-shell), so there is no column to stay clear of — the overlays take the
   full width again. This has to sit after both overlay rules to win. */
@media (max-width: 1120px) {
  .opportunity-overlay,
  .new-listing-overlay { left: 0; }
}

.opp-connect-banner {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  margin-bottom: var(--s5);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
  box-shadow: var(--e1);
  font-size: var(--fs-sm);
  font-weight: 600;
}

.opp-connect-icon { font-size: var(--fs-md); }

.opp-connect-status {
  flex: 1;
  color: var(--text-muted, var(--muted));
}

.opp-search-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
  margin-bottom: 20px;
}

.opp-field {
  display: flex;
  flex-direction: column;
  gap: 3px;
  flex: 0 0 auto;
}

.opp-field label {
  font-size: var(--fs-3xs);
  font-weight: 700;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: .04em;
}

.opp-field input,
.opp-field select {
  padding: 9px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
  color: var(--ink);
  font-size: var(--fs-base);
  outline: 0;
}

.opp-field input:focus,
.opp-field select:focus {
  border-color: var(--gold);
}

.opp-field-keyword {
  flex: 1 1 220px;
  min-width: 180px;
}

.opp-field-keyword input {
  width: 100%;
}

.opp-field-price input {
  width: 100px;
}

.opp-search-submit {
  flex: 0 0 auto;
}

.opp-hint {
  font-size: var(--fs-xs);
  color: var(--muted);
  margin: -10px 0 20px;
}

.opp-stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 14px;
  margin-bottom: 18px;
}

.opp-stats-grid .stat-card strong.good { color: var(--success); }
.opp-stats-grid .stat-card strong.bad  { color: var(--danger); }

.opp-stats-grid a.stat-card {
  display: block;
  text-decoration: none;
  transition: box-shadow var(--dur-1) ease, transform var(--dur-1) ease;
}

.opp-stats-grid a.stat-card:hover {
  box-shadow: 0 14px 40px rgba(6, 27, 29, .14);
  transform: translateY(-1px);
}

.opp-stats-grid .stat-note {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.opp-results {
  margin-bottom: 28px;
}

.opp-results-layout {
  display: grid;
  grid-template-columns: 200px 1fr;
  gap: 20px;
  align-items: start;
}

.opp-filters-panel {
  display: flex;
  flex-direction: column;
  gap: 2px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 14px;
  position: sticky;
  top: 0;
}

.opp-filters-panel h4 {
  font-size: var(--fs-2xs);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--muted);
  margin-bottom: 8px;
}

.opp-filter-chip {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  padding: 7px 6px;
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  font-weight: 500;
  color: var(--ink);
  cursor: pointer;
}

.opp-filter-chip input {
  flex-shrink: 0;
  accent-color: var(--gold);
}

.opp-filter-chip:hover {
  background: var(--teal-50);
}

.opp-filter-chip.opp-filter-disabled {
  color: var(--muted);
  cursor: default;
}

.opp-filter-chip.opp-filter-disabled:hover {
  background: none;
}

.opp-results-main {
  min-width: 0;
}

@media (max-width: 900px) {
  .opp-results-layout {
    grid-template-columns: 1fr;
  }

  .opp-filters-panel {
    position: static;
    flex-direction: row;
    flex-wrap: wrap;
    gap: 4px;
  }
}

.opp-results-summary {
  font-size: var(--fs-sm);
  color: var(--muted);
  margin-bottom: 14px;
  padding: 10px 14px;
  background: var(--soft);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
}

.opp-results-summary strong { color: var(--ink); }

.opp-results-list {
  display: grid;
  gap: 10px;
}

.opp-result-row {
  display: grid;
  grid-template-columns: 56px minmax(160px, 1fr) 150px 190px 60px;
  align-items: center;
  gap: 14px;
  padding: 12px 16px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
}

.opp-result-thumb {
  width: 56px;
  height: 56px;
  border-radius: var(--r-sm);
  object-fit: cover;
  background: var(--info-tint);
}

.opp-result-info { min-width: 0; }

.opp-result-title {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.opp-result-title a { color: inherit; }
.opp-result-title a:hover { color: var(--gold-ink); }

.opp-result-meta {
  font-size: var(--fs-2xs);
  color: var(--muted);
  margin-top: 3px;
}

.opp-result-listing-type {
  display: inline-block;
  font-size: var(--fs-3xs);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--gold-ink);
  background: rgba(199, 154, 54, .12);
  border-radius: var(--r-2xs);
  padding: 1px 6px;
  margin-right: 5px;
}

.opp-result-costs,
.opp-result-value {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: var(--fs-2xs);
  color: var(--muted);
}

.opp-cost-line {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  white-space: nowrap;
}

.opp-cost-line strong {
  font-weight: 700;
  color: var(--ink);
}

.opp-cost-line.total strong { font-weight: 800; }

.opp-cost-line.profit.good strong { color: var(--success); }
.opp-cost-line.profit.bad  strong { color: var(--danger); }
.opp-cost-line.profit.flat strong { color: var(--muted); }

.opp-verified-tag {
  font-size: var(--fs-3xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .03em;
  white-space: nowrap;
  margin-top: 2px;
  align-self: flex-end;
}

.opp-verified-tag.opp-verified  { color: var(--success); }
.opp-verified-tag.opp-estimate  { color: var(--muted); }
/* Unverifiable sell-through — deliberately reads as a warning, never as the green "matched" tag. */
.opp-verified-tag.opp-thin-data { color: var(--warning); cursor: help; }

.opp-result-score {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 3px;
}

.opp-score-badge {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: var(--fs-base);
  font-weight: 800;
  background: var(--info-tint);
  color: var(--muted);
}

.opp-score-badge.good { background: rgba(34,197,94,.14); color: var(--success); }
.opp-score-badge.mid  { background: rgba(199,154,54,.16); color: var(--gold-ink); }
.opp-score-badge.bad  { background: rgba(239,68,68,.1); color: var(--danger); }

.opp-result-score-label {
  font-size: var(--fs-3xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--muted);
}

.opp-results-empty {
  font-size: var(--fs-sm);
  color: var(--muted);
  padding: 24px;
  text-align: center;
}

.opp-results-filtered-note {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--muted);
  margin-bottom: 8px;
}

/* ── Range filters (min ROI / net profit / confidence / sell-through, max days to sell) ──────── */
.opp-range-filter {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 6px;
  font-size: var(--fs-2xs);
  font-weight: 600;
  color: var(--muted);
}

.opp-range-filter input {
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  padding: 5px 8px;
  font-size: var(--fs-xs);
  color: var(--ink);
}

/* ── Sort toolbar above the results list ──────────────────────────────────────────────────── */
.opp-results-toolbar {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 10px;
  font-size: var(--fs-xs);
  color: var(--muted);
}

.opp-results-toolbar select {
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  padding: 5px 8px;
  font-size: var(--fs-xs);
  color: var(--ink);
}

/* ── Confidence badge (next to the Opportunity Score badge) ──────────────────────────────────── */
.opp-confidence-badge {
  width: 32px;
  height: 32px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: var(--fs-xs);
  font-weight: 800;
  background: var(--info-tint);
  color: var(--muted);
}

.opp-confidence-badge.good     { background: rgba(34,197,94,.14); color: var(--success); }
.opp-confidence-badge.mid-good { background: rgba(199,154,54,.16); color: var(--gold-ink); }
.opp-confidence-badge.mid      { background: rgba(199,154,54,.10); color: var(--muted); }
.opp-confidence-badge.bad      { background: rgba(239,68,68,.1); color: var(--danger); }

/* ── Expandable "how this was scored" panel ───────────────────────────────────────────────────── */
.opp-details-toggle {
  background: none;
  border: none;
  padding: 4px 0 0;
  font-size: var(--fs-2xs);
  font-weight: 700;
  color: var(--gold-ink);
  cursor: pointer;
}

.opp-details-toggle:hover { text-decoration: underline; }

.opp-result-details {
  grid-column: 1 / -1;
  border-top: 1px solid var(--line);
  margin-top: 10px;
  padding-top: 10px;
}

.opp-details-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 8px 16px;
  font-size: var(--fs-2xs);
  color: var(--muted);
}

.opp-details-grid > div {
  display: flex;
  justify-content: space-between;
  gap: 8px;
}

.opp-details-grid strong {
  font-weight: 700;
  color: var(--ink);
}

.opp-disagreement {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--gold-ink);
  background: rgba(199,154,54,.10);
  border-radius: var(--r-sm);
  padding: 8px 10px;
  margin-bottom: 10px;
}

.opp-score-explanation {
  list-style: none;
  margin-top: 10px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
  font-size: var(--fs-xs);
}

.opp-score-explanation .opp-reason { color: var(--success); }
.opp-score-explanation .opp-warning { color: var(--danger); }

/* Flags Title/Description as the likely source of an eBay "improper words / policy violation"
   rejection — see nlHighlightPolicyIssues in app.js. Applies to inputs, textareas, and the
   rendered description preview alike since any of them may be the visible one at the time. */
.field-flagged {
  border: 2px solid var(--danger, var(--danger-600)) !important;
  background: rgba(220, 38, 38, .06) !important;
  animation: field-flagged-pulse 1.2s ease-in-out 2;
}
@keyframes field-flagged-pulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(220, 38, 38, .35); }
  50%      { box-shadow: 0 0 0 6px rgba(220, 38, 38, 0); }
}

.opp-supplier-panel {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--soft);
  padding: 20px 22px;
  margin-bottom: 20px;
}

.opp-supplier-heading {
  font-size: var(--fs-lg);
  color: var(--ink);
  margin-bottom: 4px;
}

.opp-supplier-panel .nl-drop-zone { margin-top: 12px; }

/* ── Local sourcing: pluggable sources (Craigslist, Facebook Marketplace) ──── */
/* The picker is rendered from /api/local/sources, so these styles have to work for a source
   that doesn't exist yet — nothing here keys off a specific site except the badge colours. */
.local-sources {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 12px;
  font-size: var(--fs-xs);
  color: var(--muted);
}
.local-sources-label { font-weight: 600; }
.local-source-picker { display: flex; gap: 8px; flex-wrap: wrap; }

.local-source {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--card);
  cursor: pointer;
}
.local-source input { margin: 0; }
.local-source-name { font-size: var(--fs-xs); color: var(--ink); font-weight: 600; }
/* A source needing a login is dimmed but never hidden — hiding it makes the feature look absent. */
.local-source-off { opacity: 0.75; }
.local-source-badge {
  font-size: var(--fs-3xs);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
  background: var(--soft-2);
  border-radius: var(--r-2xs);
  padding: 1px 5px;
}

/* Craigslist's metro override. Only shown while Craigslist is ticked. */
.cl-site-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 10px;
  font-size: var(--fs-xs);
  color: var(--muted);
}
/* Width is pinned for the same reason the sort control's is: the panel's form styling would
   otherwise stretch this select to the full row and push the hint onto its own line. */
.cl-site-row select {
  width: auto;
  flex: 0 0 auto;
  max-width: 280px;
  padding: 4px 8px;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
}
.cl-site-hint { font-style: italic; }

/* Per-source outcome chips: with several sites in one table, "24 results" alone would hide
   that one of them failed. */
.local-source-status {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 8px;
}
.local-source-chip {
  font-size: var(--fs-2xs);
  color: var(--muted);
  background: var(--teal-50);
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  padding: 4px 8px;
}
.local-source-chip strong { color: var(--ink); }
/* The money half of a chip on the goldmine board — what that site is actually worth, as opposed to
   how many rows it returned. Carries the same colour as the board's own headline figures so the two
   read as one claim rather than as two different kinds of number. */
.local-source-chip strong.local-chip-money { color: var(--gold-ink); }
.local-source-chip-off { background: var(--warning-tint); }
.local-source-chip-err { display: block; color: var(--warning-600); margin-top: 2px; }

/* A chip that describes a fixable state carries the button that fixes it — Connect for a source
   that needs a login, Retry for one that's blocked. Sized down to sit inside the chip's line
   rather than setting the chip's height. */
.local-chip-btn {
  font-size: var(--fs-3xs);
  padding: 1px 7px;
  margin-left: 4px;
  vertical-align: baseline;
  line-height: 1.6;
}
/* The panel's status line doubles as the "that didn't work" state, so its retry sits inline with
   the sentence explaining what didn't. */
.local-retry-btn,
.local-sources-retry {
  font-size: var(--fs-2xs);
  padding: 2px 9px;
  margin-left: 6px;
  vertical-align: baseline;
}

/* Which site to go and buy on — carried on every card and every ranked row. */
.local-badge {
  display: inline-block;
  font-size: var(--fs-3xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border-radius: var(--r-2xs);
  padding: 2px 6px;
  color: var(--on-solid);
  background: var(--muted);
  vertical-align: middle;
}
.local-badge-craigslist { background: var(--src-craigslist); }
.local-badge-facebook   { background: var(--src-facebook); }
/* Retail deal feeds. One colour for the whole source even though the badge text names the
   aggregator (Slickdeals / DealNews / TechBargains) — the distinction that matters at a glance is
   "this is a shop, not a person". */
.local-badge-dealfeeds  { background: var(--src-dealfeeds); }
/* Liquidation auctions. Its own colour because the row behaves differently from every other one on
   the board: it has a deadline on it. */
.local-badge-liquidation { background: var(--wine); }
/* Free and free-after-coupon. Green, and the only badge on this board that is: nothing was spent,
   so there is nothing on the row that can lose money. */
.local-badge-freebies   { background: var(--src-freebies); }

/* The free extras. Every one of these is either "what free still costs" or "how long you have",
   which are the only two things about a free row a seller can get wrong. */
.free-kind {
  display: inline-block;
  font-weight: 700;
  border: 1px solid rgba(31, 122, 69, 0.4);
  border-radius: var(--r-2xs);
  padding: 0 5px;
  color: var(--success);
}

.free-price {
  font-weight: 700;
  color: var(--success);
}

.free-clock { white-space: nowrap; }
.free-clock-soon { color: var(--muted); }

/* The deadline that is actually short. A free classified post is claimed the same day, which is a
   tighter clock than most stated expiry dates. */
.free-clock-now {
  font-weight: 700;
  color: var(--warning-600);
}

/* A cost the app couldn't see — unstated delivery on a $0 item, or freight-sized stock priced by
   parcel comps. Deliberately loud: it is the one case where "free" overstates the deal. */
.free-caveat {
  font-weight: 700;
  color: var(--warning-600);
  cursor: help;
}

/* The liquidation extras. Everything here is about a row that expires or contains more than one
   thing — neither of which any other source on this board can do. */
.liq-event {
  font-weight: 700;
  color: var(--wine);
}

.liq-units {
  display: inline-block;
  font-weight: 700;
  border: 1px solid rgba(138, 28, 61, 0.35);
  border-radius: var(--r-2xs);
  padding: 0 5px;
  color: var(--wine);
}

.liq-closing { color: var(--muted); }

/* The one piece of urgency on this board that is real: after this it is gone. */
.liq-closing-soon {
  font-weight: 700;
  color: var(--warning-600);
}

.liq-per-unit {
  display: inline-block;
  font-size: var(--fs-3xs);
  color: var(--muted);
  white-space: nowrap;
}

/* The liquidation sites this app deliberately doesn't read, offered as prefilled searches. */
.manual-sites-row {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin: 6px 0 2px;
  font-size: var(--fs-xs);
}

.manual-sites-label { color: var(--muted); }

.manual-sites-links {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.manual-site-link {
  color: var(--wine);
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px dashed rgba(138, 28, 61, 0.45);
}

.manual-site-link:hover { border-bottom-style: solid; }

/* The retail extras that only some rows have: what the register adds, the code you have to type,
   and the store you're buying from. */
.retail-tax {
  display: inline-block;
  font-size: var(--fs-3xs);
  color: var(--muted);
  white-space: nowrap;
}

.retail-code {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--fs-3xs);
  font-weight: 700;
  letter-spacing: 0.03em;
  border: 1px dashed var(--warning-600);
  border-radius: var(--r-2xs);
  padding: 0 4px;
  color: var(--warning-600);
}

/* ── Remaining warranty ─────────────────────────────────────────────────────
   Used stock that still carries cover. Three visual weights, and the difference between them is the
   whole feature: cover that transfers is money already inside the profit column, cover that doesn't
   still protects the buy, and a stated absence of cover is the loudest thing on the row. */
.warranty-chip {
  display: inline-block;
  font-size: var(--fs-3xs);
  font-weight: 700;
  border-radius: var(--r-2xs);
  padding: 0 5px;
  cursor: help;
  white-space: nowrap;
}

/* Stated, transferable, still running — the case the resale premium is paid on. */
.warranty-chip-ok {
  border: 1px solid rgba(31, 122, 69, 0.45);
  color: var(--success);
}

/* Real cover that is worth nothing on resale: the seller's own guarantee, or a brand whose terms
   name the original purchaser. Quieter, because it protects the buy and moves no price. */
.warranty-chip-partial {
  border: 1px solid var(--border);
  color: var(--muted);
}

/* Worked out from the item's age rather than claimed by the listing. Dashed on purpose: nothing
   here was stated, and it is worth exactly $0 on the board. */
.warranty-chip-guess {
  border: 1px dashed var(--border);
  color: var(--muted);
  font-weight: 600;
}

/* The listing says there is no cover. The only warranty chip that is a warning. */
.warranty-chip-risk {
  border: 1px solid rgba(180, 83, 10, 0.55);
  color: var(--warning-600);
}

/* Sold as-is with no returns, at a price where that IS the loss. Loud, and it holds the row's
   verdict down as well — see WarrantyPricer.JudgeWarranty. */
.warranty-risk {
  font-weight: 700;
  color: var(--warning-600);
  cursor: help;
}

.warranty-proof {
  font-size: var(--fs-3xs);
  color: var(--muted);
  white-space: nowrap;
}

/* The premium, under the resale price it is already part of. Same weight as the coupon line beside
   it: a figure that came from a listing's prose never outshouts one that came from sold comps. */
.warranty-extra {
  display: inline-block;
  font-size: var(--fs-3xs);
  font-weight: 600;
  color: var(--success);
  white-space: nowrap;
  cursor: help;
}

/* ── Categories, vehicles and where a price came from ───────────────────────
   Three related things, and the weighting between them is the message. What KIND of thing a row is
   is a quiet label. What the app READ off a vehicle title — the year, make, model and odometer — is
   the loudest thing in the row's meta line, because it is the whole basis on which somebody decides
   to drive across town. And what VALUED the row sits with the evidence, where a source and a
   confidence always travel together: a source with no confidence beside it invites the number to be
   read as a fact, and a confidence with no source invites it to be read as eBay's. */
.cat-chip {
  display: inline-block;
  font-size: var(--fs-3xs);
  font-weight: 700;
  border: 1px solid var(--border);
  border-radius: var(--r-2xs);
  padding: 0 5px;
  color: var(--muted);
  white-space: nowrap;
}

/* "2011 Toyota Tundra" — the identity, pulled out of the ad copy it was buried in. */
.veh-chip {
  display: inline-block;
  font-weight: 700;
  border: 1px solid rgba(19, 78, 116, 0.35);
  border-radius: var(--r-2xs);
  padding: 0 5px;
  color: var(--ink);
  white-space: nowrap;
}

.veh-wear {
  font-size: var(--fs-3xs);
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
}

/* The row is costed with no shipping and, on a vehicle, no percentage fee. Stated rather than
   implied: a profit computed under different rules from the row above it has to say so. */
.cat-pickup {
  font-size: var(--fs-3xs);
  color: var(--muted);
  cursor: help;
  white-space: nowrap;
}

/* Title and transport, under the fees rather than inside them — eBay doesn't charge you to
   register a truck, and merging the two would make the stated fee basis uncheckable. */
.cat-extra {
  display: inline-block;
  font-size: var(--fs-3xs);
  font-weight: 600;
  color: var(--muted);
  white-space: nowrap;
  cursor: help;
}

/* Which source put the price on this row, graded by how far to believe it. */
.val-src {
  display: inline-block;
  font-size: var(--fs-3xs);
  font-weight: 700;
  border-radius: var(--r-2xs);
  padding: 0 5px;
  cursor: help;
  white-space: nowrap;
}

.val-src-ok {
  border: 1px solid rgba(31, 122, 69, 0.45);
  color: var(--success);
}

.val-src-weak {
  border: 1px dashed rgba(180, 83, 10, 0.5);
  color: var(--warning-600);
}

.val-src-none {
  border: 1px dashed var(--border);
  color: var(--muted);
}

/* A row the app refused to price. Deliberately not styled as a failure — it is an answer, and the
   link beside it is what the row has instead of a number. */
.val-manual {
  display: inline-block;
  font-size: var(--fs-3xs);
  font-weight: 700;
  border: 1px dashed var(--border);
  border-radius: var(--r-2xs);
  padding: 0 5px;
  color: var(--muted);
  cursor: help;
  white-space: nowrap;
}

.val-manual-price {
  display: inline-block;
  font-size: var(--fs-3xs);
  font-weight: 600;
  line-height: 1.15;
  color: var(--muted);
  cursor: help;
}

.val-lookup { font-size: var(--fs-3xs); }

/* What the chosen category is about to do, said before the two-minute scan rather than after it. */
.fb-category-note {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 6px 0 0;
  font-size: var(--fs-2xs);
  color: var(--muted);
}

.fb-category-scope,
.fb-category-manual { font-size: var(--fs-3xs); }

/* The one sentence in this block a seller can act on: this app cannot price these, and here is
   what it will show instead. */
.fb-category-manual { color: var(--warning-600); }

/* ── Coupons, promo codes and cashback ──────────────────────────────────────
   The buy side of a retail row. Everything here is deliberately quieter than the money columns it
   sits beside: a public promo code is a claim rather than a price, and the visual weight has to say
   so before the seller reads a word. */
.coupon-chip {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: var(--fs-3xs);
  font-weight: 700;
  border-radius: var(--r-2xs);
  padding: 0 5px;
  cursor: help;
}

/* Fresh, dated, no conditions attached — the only grade that gets a solid border. */
.coupon-chip-ok {
  border: 1px solid rgba(31, 122, 69, 0.45);
  color: var(--success);
}

.coupon-chip-maybe {
  border: 1px dashed rgba(180, 83, 10, 0.5);
  color: var(--warning-600);
}

/* A lead, not a price: an old code, a range, or one with conditions this app can't check. */
.coupon-chip-weak {
  border: 1px dashed var(--border);
  color: var(--muted);
}

.coupon-cashback,
.coupon-cost {
  display: inline-block;
  font-size: var(--fs-3xs);
  color: var(--muted);
  white-space: nowrap;
}

/* The row that only exists because of the code. Loud on purpose — at the shelf price the board is
   telling the seller to walk away from it. */
.coupon-rescue {
  font-weight: 700;
  color: var(--warning-600);
  cursor: help;
}

/* Extra profit if the code works, under the profit that doesn't depend on one. Never the same
   weight as the figure above it. */
.coupon-extra {
  display: inline-block;
  font-size: var(--fs-3xs);
  font-weight: 600;
  color: var(--success);
  white-space: nowrap;
  cursor: help;
}

/* Which stores were checked, under the scan summary. */
.coupon-stores {
  margin: 6px 0 10px;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--r-xs);
  font-size: var(--fs-xs);
}

.coupon-stores-head {
  font-weight: 700;
  margin-bottom: 4px;
}

.coupon-store {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  padding: 2px 0;
}

.coupon-store-name { font-weight: 600; }
.coupon-store-count,
.coupon-store-note { color: var(--muted); }

.coupon-store-links {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.coupon-store-links a {
  color: var(--warning-600);
  font-weight: 600;
  text-decoration: none;
  border-bottom: 1px dashed rgba(180, 83, 10, 0.45);
}

.coupon-store-links a:hover { border-bottom-style: solid; }

/* The standalone lookup: the same check for something being bought outside this app. */
.coupon-check {
  margin: 10px 0 0;
  padding: 8px 10px;
  border: 1px solid var(--border);
  border-radius: var(--r-xs);
}

.coupon-check-summary {
  font-weight: 700;
  cursor: pointer;
}

.coupon-check-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px;
  margin: 6px 0;
}

.coupon-check-price input { width: 120px; }

.coupon-check-status {
  font-size: var(--fs-xs);
  color: var(--muted);
  min-height: 16px;
}

.coupon-verdict {
  padding: 8px 10px;
  border-radius: var(--r-xs);
  border: 1px solid var(--border);
  margin-bottom: 8px;
}

.coupon-verdict-ok { border-color: rgba(31, 122, 69, 0.45); }
.coupon-verdict-maybe { border-color: rgba(180, 83, 10, 0.45); }
.coupon-verdict-weak { border-color: var(--border); }

.coupon-verdict strong { font-size: var(--fs-xl); }

.coupon-verdict-sub {
  font-size: var(--fs-2xs);
  color: var(--muted);
  margin-left: 6px;
}

.coupon-verdict-note,
.coupon-merchant-note {
  font-size: var(--fs-xs);
  color: var(--muted);
  margin-top: 4px;
}

.coupon-offers {
  list-style: none;
  margin: 6px 0;
  padding: 0;
  font-size: var(--fs-xs);
}

.coupon-offer {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 8px;
  padding: 4px 0;
  border-top: 1px solid var(--border, var(--line-soft));
}

.coupon-code {
  font-family: var(--font-mono);
  font-weight: 700;
  letter-spacing: 0.03em;
  border: 1px dashed var(--warning-600);
  border-radius: var(--r-2xs);
  padding: 0 5px;
  color: var(--warning-600);
  user-select: all;   /* one double-click copies it into the checkout */
}

.coupon-code-none {
  border-style: dotted;
  border-color: var(--border);
  color: var(--muted);
}

.coupon-offer-what { font-weight: 600; }

.coupon-offer-min,
.coupon-offer-exp,
.coupon-offer-excl,
.coupon-offer-scope { color: var(--muted); }

.coupon-offer-grade {
  border-radius: var(--r-2xs);
  padding: 0 5px;
  font-size: var(--fs-3xs);
  cursor: help;
}

.coupon-offer-src {
  margin-left: auto;
  color: var(--muted);
  text-decoration: none;
}

/* Sales tax field: only meaningful while a retail source is ticked, so it is hidden otherwise. */
.retail-tax-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin: 8px 0 0;
  font-size: var(--fs-xs);
}
.retail-tax-row input { width: 72px; }
.retail-tax-hint { color: var(--muted); }

/* Shown only while the freebie board is ticked, because it is the only source where an empty
   keyword box is a better search than a full one. */
.fb-blank-query-hint {
  display: block;
  margin-top: 4px;
  font-size: var(--fs-2xs);
  color: var(--success);
}

/* ── Facebook Marketplace local sourcing ──────────────────────────────────── */
.fb-connect-banner { margin: 12px 0 4px; }

.fb-search-row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  align-items: flex-end;
  margin-top: 12px;
}
.fb-field-keyword  { flex: 2 1 260px; }
/* Wider than the zip and radius beside it: the option labels are words rather than numbers, and a
   truncated "Motorcycles & powers…" is the one control on this row a seller has to read. */
.fb-field-category { flex: 1 1 190px; }
.fb-field-zip      { flex: 0 1 130px; }
.fb-field-radius   { flex: 0 1 150px; }
.fb-search-row .opp-search-submit { flex: 0 0 auto; }

/* Cards, not table rows: a Marketplace result is carried by its photo and its price —
   a used local item is judged on condition at a glance the way a spec column can't convey. */
.fb-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  gap: 12px;
  margin-top: 12px;
}

.fb-card {
  display: flex;
  gap: 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
  padding: 10px;
}

.fb-card-img {
  width: 92px;
  height: 92px;
  flex: 0 0 92px;
  object-fit: cover;
  border-radius: var(--r-sm);
  background: var(--page);
}
.fb-card-img-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-3xl);
}

.fb-card-body { min-width: 0; flex: 1; }

.fb-card-price {
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--ink);
}
/* A struck-through original price means the seller already cut it — worth spotting. */
.fb-drop {
  font-size: var(--fs-xs);
  font-weight: 500;
  color: var(--danger);
  text-decoration: line-through;
  margin-left: 6px;
}

.fb-card-title {
  font-size: var(--fs-sm);
  color: var(--ink);
  margin-top: 2px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.fb-card-meta {
  font-size: var(--fs-2xs);
  color: var(--muted);
  margin-top: 4px;
}

.fb-card-actions {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 8px;
}

.fb-card-comp {
  font-size: var(--fs-xs);
  margin-top: 6px;
  color: var(--muted);
}
.fb-comp-val  { font-weight: 600; color: var(--ink); }
.fb-comp-good { font-weight: 600; color: var(--success); }
.fb-comp-bad  { font-weight: 600; color: var(--danger); }
.fb-comp-none { color: var(--muted); }

/* ── Local arbitrage ranking ──────────────────────────────────────────────────
   A table here, unlike the card grid above, because this view is a comparison:
   the question is which row wins on profit, and money columns only line up in
   a table. */
.fb-arb-warning {
  margin-top: 10px;
  padding: 8px 12px;
  border-left: 3px solid var(--warning);
  background: var(--warning-tint);
  border-radius: var(--r-xs);
  font-size: var(--fs-xs);
  color: var(--ink);
}

.fb-arb-sources {
  font-size: var(--fs-2xs);
  color: var(--muted);
  font-weight: 400;
  margin-top: 3px;
}

.fb-arb-hit { color: var(--gold-ink); }

.fb-arb-controls {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin: 12px 0 8px;
  font-size: var(--fs-xs);
  color: var(--muted);
}
/* width:auto beats the panel-wide "form selects fill their row" rule — these are inline
   controls in a toolbar, not stacked form fields. */
.fb-arb-controls select {
  width: auto;
  flex: 0 0 auto;
  padding: 4px 8px;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
}
.fb-arb-check { display: flex; align-items: center; gap: 5px; cursor: pointer; }
.fb-arb-shown { margin-left: auto; }

.fb-arb-table-wrap {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
}

.fb-arb-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-xs);
}

/* The budget basket is the one table still built for a modal's width rather than the window's:
   eight fixed columns in a narrow box, where scrolling the box is the right answer. The deals
   board's rules are the opposite and live in their own section — see .fb-arb-board. */
.bud-table { min-width: 860px; }
.fb-arb-table th {
  text-align: left;
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--muted);
  padding: 9px 10px;
  border-bottom: 1px solid var(--line);
  background: var(--soft);
  white-space: nowrap;
}
.fb-arb-table td {
  padding: 9px 10px;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}
.fb-arb-table tr:last-child td { border-bottom: none; }
.fb-arb-table .num { text-align: right; white-space: nowrap; }
.fb-arb-th-rank { width: 34px; color: var(--muted); }

/* The single most valuable row on the page earns a gold edge; everything else stays
   neutral so the badge, not the styling, carries the claim. */
.fb-arb-row-goldmine { background: var(--warning-tint); box-shadow: inset 3px 0 0 var(--gold); }
.fb-arb-row-pass, .fb-arb-row-nodata { color: var(--muted); }

.fb-arb-item { display: flex; gap: 10px; min-width: 280px; }
.fb-arb-thumb {
  width: 46px;
  height: 46px;
  flex: 0 0 46px;
  object-fit: cover;
  border-radius: var(--r-xs);
  background: var(--page);
}
.fb-arb-thumb-empty { display: flex; align-items: center; justify-content: center; font-size: var(--fs-xl); }
.fb-arb-item-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.fb-arb-title {
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  /* A 60-character unbroken model number is a real listing title, and without
     this it sets the column's floor and pushes the table past its frame. */
  overflow-wrap: anywhere;
}
.fb-arb-title:hover { text-decoration: underline; }
.fb-arb-meta, .fb-arb-note { font-size: var(--fs-2xs); color: var(--muted); }

/* Verdicts are colour-coded by how much the data supports them, not by how big the
   number is — a thin-evidence row must never read as a green light. */
.fb-verdict {
  align-self: flex-start;
  font-size: var(--fs-3xs);
  font-weight: 700;
  padding: 1px 7px;
  border-radius: var(--r-pill);
  margin-top: 2px;
  white-space: nowrap;
}
.fb-verdict-goldmine { background: var(--gold-soft); color: var(--gold-ink); }
.fb-verdict-solid    { background: var(--success-soft); color: var(--success); }
.fb-verdict-thin     { background: var(--warning-soft); color: var(--warning); }
.fb-verdict-pass     { background: var(--soft-2); color: var(--muted); }
.fb-verdict-nodata   { background: var(--soft-2); color: var(--muted); }

.fb-arb-profit { font-weight: 700; }
.fb-arb-profit.good { color: var(--success); }
.fb-arb-profit.bad  { color: var(--danger); }

/* ── Days to cash ──────────────────────────────────────────────────────────
   Coloured by how long the seller's money is tied up, never by how big the profit is:
   a large margin that parks the cash for four months has to LOOK slow next to a small
   one that clears in a fortnight. Tiers come from the server (DaysToCashEstimator), so
   the colour here and the sentence in the tooltip always agree. */
.fb-arb-speed { white-space: nowrap; }
.speed-days {
  display: inline-block;
  font-weight: 700;
  font-size: var(--fs-2xs);
  padding: 1px 7px;
  border-radius: var(--r-pill);
  cursor: help;
}
.speed-rate { display: block; font-size: var(--fs-3xs); color: var(--muted); margin-top: 2px; }
.speed-badge {
  display: inline-block;
  font-size: var(--fs-2xs);
  font-weight: 600;
  padding: 1px 7px;
  border-radius: var(--r-pill);
  cursor: help;
}
.speed-fast    { background: var(--success-soft); color: var(--success); }
.speed-steady  { background: var(--info-soft); color: var(--info); }
.speed-slow    { background: var(--warning-soft); color: var(--warning); }
.speed-dead    { background: var(--danger-soft); color: var(--danger); }
.speed-unknown { background: var(--soft-2); color: var(--muted); }
.fb-arb-cost { color: var(--muted); }
.fb-arb-evidence { font-size: var(--fs-2xs); color: var(--muted); max-width: 220px; }
.fb-arb-flag { color: var(--warning); cursor: help; }
.fb-arb-muted { color: var(--muted); font-style: italic; }
.fb-arb-empty { text-align: center; color: var(--muted); padding: 18px 10px; }

/* A price, ROI or margin that rests on too few matching sold comps. Dimmed and italicised so it
   reads at a glance as a working figure rather than a rate — the same number in the same column,
   demoted rather than hidden, because the lead is still worth chasing by hand. */
.fb-arb-estimate { color: var(--muted); font-style: italic; cursor: help; }
.fb-arb-estimate-flag {
  font-size: var(--fs-3xs);
  font-style: normal;
  font-weight: 600;
  color: var(--warning);
  text-transform: uppercase;
  letter-spacing: 0.02em;
  cursor: help;
}

.fb-arb-footnote {
  font-size: var(--fs-2xs);
  color: var(--muted);
  margin-top: 8px;
  line-height: 1.5;
}

/* Supplier rows have no thumbnail (unlike live-listing opp-result-row), so the info
   column reclaims that space instead of leaving a blank 56px gutter. */
.opp-supplier-row {
  grid-template-columns: minmax(220px, 1.4fr) 150px 190px 110px !important;
}
.opp-supplier-row .opp-result-score {
  flex-direction: column;
  align-items: stretch;
}

.opp-comp-toggle { margin-top: 8px; }

.opp-liquidity-unknown { color: var(--muted); font-weight: 500; font-size: var(--fs-2xs); }

.opp-comp-panel {
  margin-top: 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  overflow-x: auto;
  max-width: 100%;
}
.opp-comp-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-xs);
}
.opp-comp-table th, .opp-comp-table td {
  padding: 6px 10px;
  text-align: left;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}
.opp-comp-table th {
  color: var(--muted);
  font-weight: 600;
  background: var(--card-alt, rgba(0,0,0,.02));
}
.opp-comp-table td:first-child {
  white-space: normal;
  max-width: 320px;
}
.opp-comp-table tr:last-child td { border-bottom: none; }

.opportunity-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: 16px;
}

.opportunity-card {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--soft);
  padding: 20px 22px;
}

.opportunity-card h3 {
  font-size: var(--fs-md);
  color: var(--ink);
  margin-bottom: 8px;
}

.opportunity-empty {
  font-size: var(--fs-sm);
  color: var(--muted);
  line-height: 1.6;
}

.opportunity-subnote {
  font-size: var(--fs-2xs);
  color: var(--muted);
  font-style: italic;
  line-height: 1.5;
  margin-bottom: 10px;
}

.opportunity-insight-list {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.opportunity-insight-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  font-size: var(--fs-xs);
  padding-bottom: 8px;
  border-bottom: 1px solid var(--line);
}

.opportunity-insight-row:last-child { border-bottom: none; padding-bottom: 0; }

.opportunity-insight-row a {
  color: var(--ink);
  font-weight: 600;
  text-decoration: none;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.opportunity-insight-row a:hover { color: var(--gold-ink); text-decoration: underline; }

.opportunity-insight-label {
  color: var(--ink);
  font-weight: 600;
  text-transform: capitalize;
}

.opportunity-insight-value {
  font-weight: 800;
  white-space: nowrap;
  flex-shrink: 0;
}

.opportunity-insight-value.good { color: var(--success); }
.opportunity-insight-value.bad  { color: var(--danger); }

/* ── Edit Listing drawer ─────────────────────────────────────────────────────
   Hosts the existing #form-section in a right-side sliding panel so the
   listings stay visible behind it. The form markup itself is unchanged — app.js
   relocates the node into .edit-drawer-body at init (see initEditDrawer). */

.edit-drawer-overlay {
  position: fixed;
  inset: 0;
  background: rgba(6, 27, 29, .55);
  backdrop-filter: blur(2px);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-2) ease, visibility var(--dur-2) ease;
  z-index: 900;
}

.edit-drawer-overlay.open {
  opacity: 1;
  visibility: visible;
}

.edit-drawer {
  position: fixed;
  top: 0;
  right: 0;
  height: 100%;
  width: min(980px, 92vw);
  max-width: 100%;
  background: var(--page);
  box-shadow: -24px 0 60px rgba(5, 35, 38, .28);
  transform: translateX(100%);
  transition: transform var(--dur-2) var(--ease-out);
  z-index: 901;
  display: flex;
  flex-direction: column;
  visibility: hidden;
}

.edit-drawer.open {
  transform: translateX(0);
  visibility: visible;
}

.edit-drawer-header {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 16px 22px;
  background: var(--teal-900);
  color: #fff;
  border-bottom: 3px solid var(--gold);
}

.edit-drawer-heading {
  min-width: 0;
  flex: 1;
}

.edit-drawer-title {
  font-size: var(--fs-lg);
  font-weight: 700;
  letter-spacing: .01em;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.edit-drawer-sub {
  font-size: var(--fs-xs);
  color: var(--on-dark-3);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  margin-top: 2px;
}

/* Whether the live listing was actually read. Wraps rather than truncates: the failure message
   names what is missing and what happens if it stays missing, and a truncated warning is a
   warning that didn't work. */
.edit-drawer-detail-status {
  margin-top: 4px;
  font-size: var(--fs-2xs);
  line-height: 1.45;
  color: var(--on-dark-3);
}
.edit-drawer-detail-status.is-ok    { color: var(--success-tint); }
.edit-drawer-detail-status.is-busy  { color: var(--on-dark-3); }
.edit-drawer-detail-status.is-warn  { color: var(--gold-note); font-weight: 650; }

.edit-drawer-dirty {
  display: none;
  flex-shrink: 0;
  font-size: var(--fs-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--brand-ink-strong);
  background: var(--gold);
  border-radius: var(--r-pill);
  padding: 4px 10px;
}

.edit-drawer.dirty .edit-drawer-dirty { display: inline-block; }

.edit-drawer-close {
  flex-shrink: 0;
  width: 34px;
  height: 34px;
  border-radius: var(--r-sm);
  border: 1px solid rgba(255, 255, 255, .25);
  background: transparent;
  color: #fff;
  font-size: var(--fs-xl);
  line-height: 1;
  cursor: pointer;
  transition: background var(--dur-1) ease, border-color var(--dur-1) ease;
}

.edit-drawer-close:hover {
  background: rgba(255, 255, 255, .12);
  border-color: var(--gold);
}

.edit-drawer-close:focus-visible {
  outline: 2px solid var(--gold);
  outline-offset: 2px;
}

.edit-drawer-body {
  flex: 1;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 18px 22px 40px;
}

/* The hosted form is already a .panel; inside the drawer it should not
   double up on card chrome or its own max-width. */
.edit-drawer-body > .form-section {
  box-shadow: none;
  border: none;
  border-radius: 0;
  margin: 0;
  padding: 0;
  background: transparent;
  max-width: none;
}

body.drawer-open {
  overflow: hidden;
}

@media (max-width: 720px) {
  .edit-drawer { width: 100vw; }
  .edit-drawer-body { padding: 14px 14px 32px; }
  .edit-drawer-header { padding: 13px 15px; }
}

@media (prefers-reduced-motion: reduce) {
  .edit-drawer,
  .edit-drawer-overlay { transition: none; }
}

/* ── Listing editor: closed panels that still say something ──────────────────
   The editor is ten collapsible panels. Collapsed, they used to be ten
   identical grey bars — the seller had to open each one to find out whether
   there were photos in it. Each summary now carries a chip with the state of
   the panel, and the four things eBay refuses a listing without (title,
   category, price, photo) mark their panel when they are empty.

   Three chip states, and the difference between them is the whole point:
   neutral is information, .is-thin is the app's opinion about what sells,
   .is-empty is eBay's rule. Only .is-empty marks the panel, because a mark
   that also fires on an opinion is a mark people learn to ignore. */

/* .fp-sum only — the New Listing overlay shares .form-panel and its plain
   summaries still need the caret pushed right by its own auto margin. */
.form-panel > summary.fp-sum::after { margin-left: var(--s3); }

.fp-name {
  flex: 1 1 auto;
  min-width: 0;
}

.fp-chip {
  flex: 0 0 auto;
  max-width: 52%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  padding: 2px var(--s2);
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--card);
  color: var(--muted);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-medium);
  letter-spacing: var(--tracking-normal);
  font-variant-numeric: tabular-nums;
}

/* An empty chip is a panel whose state hasn't been computed yet — no pill. */
.fp-chip:empty { display: none; }

.fp-chip.is-thin {
  border-color: var(--warning-line);
  background: var(--warning-tint);
  color: var(--gold-ink);
}

.fp-chip.is-empty {
  border-color: var(--warning-line);
  background: var(--warning-soft);
  color: var(--warning);
  font-weight: var(--fw-semibold);
}

/* The panel itself, when eBay would refuse the listing over what's inside it.
   An inset rule rather than a border swap, so the panel does not change size
   as the seller fills it in and shift everything below by a pixel. */
.form-panel.is-incomplete,
.form-panel.is-incomplete > summary {
  box-shadow: inset 3px 0 0 var(--gold);
}

.form-panel > summary:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.form-panel > summary:hover { background: var(--soft-2); }
.form-panel[open] > summary:hover { background: var(--line-soft); }

/* ── Listing editor: the action bar that stays on screen ─────────────────────
   Pinned to the bottom of whichever pane scrolls it — the edit drawer, or the
   page when the form is inline. Floating card rather than a full-bleed strip:
   the drawer's own padding stays visible down both sides, so the bar reads as
   part of the form rather than as a second window chrome. */
.form-actions-sticky {
  position: sticky;
  bottom: var(--s3);
  z-index: 5;
  margin-top: var(--s5);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
  box-shadow: var(--e3-up);
  gap: var(--s4);
}

.fa-state {
  flex: 1 1 220px;
  min-width: 0;
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s1) var(--s2);
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: var(--lh-snug);
}

.fa-state strong {
  color: var(--ink);
  font-weight: var(--fw-semibold);
}

.fa-dot {
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  border-radius: var(--r-pill);
  background: var(--success-600);
}

.fa-dot.is-empty { background: var(--gold); }

/* Each thing eBay is missing is a button, not a word: the panel it lives in
   may be four screens away and closed. */
.fa-jump {
  padding: 2px var(--s2);
  border: 1px solid var(--warning-line);
  border-radius: var(--r-pill);
  background: var(--warning-soft);
  color: var(--warning);
  font-family: inherit;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: background var(--dur-1) ease, border-color var(--dur-1) ease;
}

.fa-jump:hover {
  background: var(--gold-soft);
  border-color: var(--gold);
}

.fa-jump:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
}

.fa-buttons {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
  margin-left: auto;
}

/* The same fact as the drawer header's badge, at the other end of the form —
   the header answers "is it safe to close this", the bar answers "is there
   anything to press Save for". Quieter than the header's filled pill on
   purpose: two identical gold pills on one screen is one of them shouting for
   no reason. Driven entirely by the drawer's .dirty class, so there is no
   second source of truth about whether the listing has been edited. */
.fa-unsaved {
  display: none;
  flex: 0 0 auto;
  align-items: center;
  gap: var(--s1);
  color: var(--gold-ink);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
}

.fa-unsaved::before {
  content: '';
  width: 6px;
  height: 6px;
  border-radius: var(--r-pill);
  background: var(--gold);
}

.edit-drawer.dirty .fa-unsaved { display: inline-flex; }

@media (max-width: 720px) {
  .form-actions-sticky { padding: var(--s2) var(--s3); }
  .fa-buttons { width: 100%; }
  .fa-buttons .btn { flex: 1 1 auto; }
}

/* ── Market Research panel ───────────────────────────────────────────────── */

.mr-wrap { display: flex; flex-direction: column; gap: 12px; }

.mr-actions { display: flex; flex-wrap: wrap; gap: 8px; }

.mr-query {
  font-size: var(--fs-xs);
  color: var(--muted);
  word-break: break-word;
}

.mr-query strong { color: var(--ink); }

.mr-status { font-size: var(--fs-sm); }
.mr-status.error   { color: var(--danger); }
.mr-status.working { color: var(--muted); }
.mr-status.empty   { color: var(--warning); }

/* Live eBay lookup progress. Fetching one model's sold history takes ~40-60s; the bar exists so
   that minute reads as work in progress rather than a frozen screen. */
.mr-live {
  background: var(--soft);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.mr-live-head {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  font-size: var(--fs-sm);
}

.mr-live-stage { color: var(--ink); }

/* The seconds counter does as much work as the bar: a number that ticks every second is
   unambiguous proof the app is alive, even on the long flat stretch of a single page load. */
.mr-live-elapsed {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

.mr-live-track {
  height: 6px;
  background: var(--line);
  border-radius: 999px;
  overflow: hidden;
}

.mr-live-bar {
  height: 100%;
  width: 0;
  border-radius: 999px;
  background: var(--accent);
  /* Eased rather than stepped: progress arrives in a handful of poll-sized jumps, and an
     un-transitioned bar snapping 15% -> 88% looks like a glitch instead of loading. */
  transition: width 420ms ease-out;
}

.mr-live.done .mr-live-bar { background: var(--success); }
.mr-live.blocked .mr-live-bar { background: var(--warning); }

.mr-live-note { font-size: var(--fs-xs); color: var(--muted); }
.mr-live-note:empty { display: none; }
.mr-live.blocked .mr-live-note { color: var(--warning); }

@media (prefers-reduced-motion: reduce) {
  .mr-live-bar { transition: none; }
}

.mr-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
}

.mr-stat {
  background: var(--soft);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 10px 12px;
  display: flex;
  flex-direction: column;
  gap: 3px;
  min-width: 0;
}

.mr-stat span {
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--muted);
}

.mr-stat strong {
  font-size: var(--fs-lg);
  color: var(--ink);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mr-reco {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  background: var(--teal-900);
  border-radius: var(--r-md);
  padding: 12px 14px;
}

.mr-reco-main { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.mr-reco-main span {
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--on-dark-3);
}

.mr-reco-main strong { font-size: var(--fs-2xl); color: var(--gold); }
.mr-reco-main em { font-size: var(--fs-2xs); color: var(--on-dark-3); font-style: normal; }

.mr-reco-actions { display: flex; gap: 8px; flex-wrap: wrap; }

.mr-comps { display: flex; flex-direction: column; gap: 6px; }

.mr-comp {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--card);
  font-size: var(--fs-sm);
}

.mr-comp-title {
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.mr-comp-price { font-weight: 700; white-space: nowrap; }

.mr-comp.outlier { border-color: var(--warning); }
.mr-comp.outlier .mr-comp-price { color: var(--warning); }

.mr-outlier-note { font-size: var(--fs-xs); color: var(--warning); }

/* ── Cross-List panel ─────────────────────────────────────────────────────
   Marketplace tabs, then a per-site card: price parity, copyable title and
   description, required fields, warnings, CSV. */

.xl-wrap { display: flex; flex-direction: column; gap: 12px; }

.xl-intro { font-size: var(--fs-xs); color: var(--muted); margin: 0; line-height: 1.55; }

.xl-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }

.xl-target {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-xs);
  color: var(--ink);
  cursor: pointer;
  user-select: none;
}

.xl-target input { margin: 0; }

.xl-status { font-size: var(--fs-sm); }
.xl-status.error   { color: var(--danger); }
.xl-status.working { color: var(--muted); }
.xl-status.empty   { color: var(--warning); }

.xl-results { display: flex; flex-direction: column; gap: 12px; }

.xl-tabs { display: flex; flex-wrap: wrap; gap: 6px; }

.xl-tab {
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--muted);
  border-radius: var(--r-pill);
  padding: 6px 14px;
  font-size: var(--fs-xs);
  font-weight: 600;
  cursor: pointer;
}

.xl-tab:hover { border-color: var(--teal-900); color: var(--ink); }

.xl-tab.active {
  background: var(--teal-900);
  border-color: var(--teal-900);
  color: #fff;
}

/* Count of blocking problems for that marketplace, so the seller sees which tab
   needs attention before clicking into it. */
.xl-tab-flag {
  display: inline-block;
  margin-left: 6px;
  background: var(--warning);
  color: var(--on-gold);
  border-radius: var(--r-pill);
  padding: 0 6px;
  font-size: var(--fs-3xs);
  font-weight: 700;
}

.xl-card { display: flex; flex-direction: column; gap: 12px; }

.xl-card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.xl-card-head h4 { margin: 0; font-size: var(--fs-md); color: var(--ink); }

.xl-price {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px;
  background: var(--teal-900);
  border-radius: var(--r-md);
  padding: 12px 14px;
}

.xl-price-cell { display: flex; flex-direction: column; gap: 2px; min-width: 0; }

.xl-price-cell span {
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--on-dark-3);
}

.xl-price-cell strong { font-size: var(--fs-xl); color: #fff; white-space: nowrap; }
.xl-price-cell.headline strong { color: var(--gold); }

.xl-price-note {
  grid-column: 1 / -1;
  font-size: var(--fs-xs);
  color: var(--on-dark-3);
  line-height: 1.5;
}

.xl-block { display: flex; flex-direction: column; gap: 6px; }

.xl-block-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 8px;
}

.xl-block-head label {
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--muted);
}

.xl-count { font-size: var(--fs-2xs); color: var(--muted); }
.xl-count.over { color: var(--warning); font-weight: 700; }

.xl-block textarea {
  width: 100%;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 9px 12px;
  font-size: var(--fs-sm);
  font-family: inherit;
  background: var(--soft);
  color: var(--ink);
  resize: vertical;
}

.xl-fields {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 8px;
}

.xl-field {
  background: var(--soft);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 8px 10px;
  min-width: 0;
}

.xl-field span {
  display: block;
  font-size: var(--fs-3xs);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--muted);
}

.xl-field strong {
  display: block;
  font-size: var(--fs-sm);
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.xl-field.missing { border-color: var(--warning); }
.xl-field.missing strong { color: var(--warning); }

.xl-warnings { display: flex; flex-direction: column; gap: 6px; }

.xl-warning {
  display: flex;
  gap: 8px;
  font-size: var(--fs-xs);
  line-height: 1.5;
  color: var(--ink);
  background: var(--soft);
  border: 1px solid var(--warning);
  border-left-width: 3px;
  border-radius: var(--r-sm);
  padding: 8px 10px;
}

.xl-import-note { font-size: var(--fs-xs); color: var(--muted); line-height: 1.5; }

.xl-card-actions { display: flex; flex-wrap: wrap; gap: 8px; }

@media (max-width: 720px) {
  .xl-price-cell strong { font-size: var(--fs-lg); }
}

/* ── Representative Photo Library ─────────────────────────────────────────
   Folder rail on the left, the selected model's photos on the right. */
.pl-create-row {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin-bottom: 0;
}

.pl-create-row input {
  flex: 0 1 340px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 9px 12px;
  font-size: var(--fs-sm);
  background: var(--card);
}

.pl-layout {
  display: grid;
  grid-template-columns: 260px minmax(0, 1fr);
  gap: 18px;
  align-items: start;
}

.pl-folders {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--soft);
  overflow: hidden;
}

.pl-folders-head {
  padding: 10px 14px;
  font-size: var(--fs-2xs);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .08em;
  color: var(--muted);
  border-bottom: 1px solid var(--line);
}

.pl-folder-list {
  max-height: 60vh;
  overflow-y: auto;
  padding: 6px;
}

.pl-folder-list .opportunity-empty { padding: 10px 8px; }

.pl-folder {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  width: 100%;
  padding: 9px 10px;
  border: 1px solid transparent;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink);
  font-size: var(--fs-sm);
  text-align: left;
}

.pl-folder:hover { background: var(--dt-hover); }

.pl-folder.active {
  background: var(--card);
  border-color: var(--teal-700);
  font-weight: 700;
}

.pl-folder-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pl-folder-count {
  flex-shrink: 0;
  min-width: 24px;
  padding: 1px 7px;
  border-radius: var(--r-pill);
  background: var(--teal-700);
  color: #fff;
  font-size: var(--fs-2xs);
  font-weight: 700;
  text-align: center;
}

.pl-folder-count.empty {
  background: var(--line);
  color: var(--muted);
}

.pl-detail {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
  padding: 18px 20px 20px;
}

.pl-detail-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}

.pl-detail-head h3 {
  font-size: var(--fs-lg);
  color: var(--ink);
  word-break: break-all;
}

.pl-rembg-toggle {
  display: flex;
  align-items: center;
  gap: 7px;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--muted);
  cursor: pointer;
}

.pl-detail .nl-drop-zone { margin-bottom: 12px; }

.pl-photo-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 12px;
  margin-top: 12px;
}

.pl-photo {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--soft);
  overflow: hidden;
}

.pl-photo img {
  display: block;
  width: 100%;
  aspect-ratio: 1;
  object-fit: contain;
  background: #fff;
  cursor: zoom-in;
}

.pl-photo-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  padding: 6px 8px;
  border-top: 1px solid var(--line);
}

.pl-photo-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-2xs);
  color: var(--muted);
}

.pl-photo-delete { color: var(--danger); }

@media (max-width: 900px) {
  .pl-layout { grid-template-columns: minmax(0, 1fr); }
  .pl-folder-list { max-height: 260px; }
}

/* ── Inventory Health ─────────────────────────────────────────────────────
   The portfolio view over the seller's own live listings. Colour here tracks
   how much money is at stake, not how large a percentage is — a red tile means
   capital is stuck, not that a number is big. */

.inv-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 12px;
  margin: 14px 0 10px;
}

/* The overlay body styles form controls full-width; these are toolbar selects
   and have to stay narrow or they push the row onto three lines. */
.inv-field-small { flex: 0 0 auto; min-width: 0; }
.inv-field-small select { width: auto; min-width: 150px; }

.inv-status {
  flex: 1 1 240px;
  min-width: 0;
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.45;
}

.inv-warning {
  margin: 10px 0;
  padding: 10px 12px;
  border: 1px solid var(--gold);
  border-left-width: 4px;
  border-radius: var(--r-sm);
  background: var(--gold-soft);
  font-size: var(--fs-sm);
  color: var(--ink);
}

.inv-summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 10px;
  margin: 14px 0;
}

.inv-tile {
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-left: 4px solid var(--teal-700);
  border-radius: var(--r-md);
  background: var(--card);
}

.inv-tile-good { border-left-color: var(--success); }
.inv-tile-warn { border-left-color: var(--warning); }
.inv-tile-bad  { border-left-color: var(--danger); }

.inv-tile-label {
  font-size: var(--fs-2xs);
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
}

.inv-tile-value {
  margin-top: 4px;
  font-size: var(--fs-2xl);
  font-weight: 700;
  color: var(--ink);
}

.inv-tile-sub {
  margin-top: 3px;
  font-size: var(--fs-2xs);
  color: var(--muted);
  line-height: 1.4;
}

.inv-bulk-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  margin: 12px 0;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--soft);
}

.inv-select-all {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink);
}

.inv-selection-note {
  flex: 1 1 auto;
  font-size: var(--fs-xs);
  color: var(--muted);
}

.inv-results { overflow-x: auto; }

.inv-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}

.inv-table th {
  position: sticky;
  top: 0;
  padding: 8px 10px;
  border-bottom: 2px solid var(--line);
  background: var(--card);
  text-align: left;
  font-size: var(--fs-2xs);
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

.inv-table td {
  padding: 10px;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
}

.inv-table th.num, .inv-table td.num { text-align: right; white-space: nowrap; }
.inv-col-check { width: 30px; }

/* Dead capital is the only row state loud enough to earn a background — it is
   the one thing on this screen that is costing money every day it stays. */
.inv-row-dead { background: rgba(194, 65, 59, .05); }

.inv-cell-title { min-width: 260px; }

.inv-title-row { display: flex; gap: 10px; align-items: flex-start; }

.inv-thumb {
  width: 40px;
  height: 40px;
  flex: 0 0 auto;
  object-fit: contain;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  background: #fff;
}

.inv-sub {
  margin-top: 2px;
  font-size: var(--fs-2xs);
  color: var(--muted);
}

.inv-dash { color: var(--muted); }

.inv-gap-bad  { color: var(--danger);  font-weight: 700; }
.inv-gap-good { color: var(--success); font-weight: 700; }
.inv-gap-ok   { color: var(--muted); }

.inv-cost-input, .inv-price-input {
  width: 88px;
  padding: 5px 6px;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  font-size: var(--fs-xs);
  text-align: right;
  background: var(--card);
}

.inv-cost-input:placeholder-shown { border-style: dashed; color: var(--muted); }
.inv-price-input { font-weight: 700; border-color: var(--teal-700); }

.inv-change { margin-top: 3px; font-size: var(--fs-2xs); font-weight: 600; }
.inv-change.down { color: var(--danger); }
.inv-change.up   { color: var(--success); }

.inv-cell-verdict { max-width: 300px; }

.inv-verdict {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  font-size: var(--fs-2xs);
  font-weight: 700;
  white-space: nowrap;
}

.inv-v-underwater { background: var(--accent-soft); color: var(--accent); }
.inv-v-dead       { background: var(--danger-soft); color: var(--danger); }
.inv-v-stale      { background: var(--gold-soft); color: var(--gold-ink); }
.inv-v-over       { background: var(--warning-soft); color: var(--warning); }
.inv-v-under      { background: var(--success-soft); color: var(--success); }
.inv-v-ok         { background: var(--soft); color: var(--muted); }
.inv-v-nodata     { background: var(--soft); color: var(--muted); }

.inv-note { margin-top: 4px; font-size: var(--fs-2xs); color: var(--ink); line-height: 1.4; }
.inv-signals { margin-top: 3px; font-size: var(--fs-2xs); color: var(--muted); line-height: 1.4; }

.inv-footnote {
  margin-top: 16px;
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.55;
}

/* Confirmation gate for the live-price write. .modal supplies no inner padding
   of its own (its children own it), so this box lays out its own. */
.inv-confirm-box { width: min(560px, 100%); padding: 22px; }
.inv-confirm-sub { margin: 6px 0 14px; font-size: var(--fs-sm); color: var(--muted); line-height: 1.5; }

.inv-confirm-list {
  max-height: 260px;
  overflow-y: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}

.inv-confirm-row {
  display: flex;
  gap: 10px;
  align-items: baseline;
  justify-content: space-between;
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
  font-size: var(--fs-xs);
}

.inv-confirm-row:last-child { border-bottom: 0; }

.inv-confirm-title {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.inv-confirm-prices { white-space: nowrap; color: var(--ink); }

.inv-confirm-override {
  display: flex;
  align-items: center;
  justify-content: flex-start;
  gap: 8px;
  margin: 14px 0;
  font-size: var(--fs-xs);
  text-align: left;
  color: var(--muted);
}

.inv-confirm-override input { flex: 0 0 auto; margin: 0; }

.inv-confirm-actions {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
}

@media (max-width: 900px) {
  .inv-summary { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
  .inv-table { font-size: var(--fs-xs); }
  .inv-cell-title { min-width: 200px; }
}

/* A gap whose market comparison failed — shown as "n/a", never coloured as a
   finding, because it is not one. */
.inv-gap-void { color: var(--muted); font-style: italic; }

/* A listing that has actually sold units — the one verdict that means "do nothing". */
.inv-v-selling { background: var(--success-soft); color: var(--success); }

/* ── Aging-Inventory Rescue ───────────────────────────────────────────────
   Reuses the inventory-health tiles, bulk bar and confirm gate wholesale. What
   it adds is the card: a plan is a listing plus a dated ladder, which is more
   than a table row can hold without becoming unreadable. */
.rsc-card {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: 14px 16px;
  margin-bottom: 12px;
  background: var(--card);
  box-shadow: var(--e1);
}

.rsc-card-head { display: flex; gap: 10px; align-items: flex-start; }
.rsc-check { flex: 0 0 auto; margin-top: 3px; }
.rsc-check-spacer { flex: 0 0 13px; }
.rsc-card-title { min-width: 0; flex: 1; }

.rsc-card-title a {
  font-weight: 700;
  font-size: var(--fs-sm);
  color: var(--ink);
  text-decoration: none;
}

.rsc-card-title a:hover { text-decoration: underline; }

.rsc-card-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  align-items: center;
  margin-top: 5px;
  font-size: var(--fs-xs);
  color: var(--muted);
}

/* The one sentence the seller acts on, so it outranks everything else on the card. */
.rsc-headline {
  margin: 10px 0 4px;
  font-size: var(--fs-sm);
  font-weight: 650;
  color: var(--ink);
  line-height: 1.45;
}

.rsc-why { margin: 0 0 10px; font-size: var(--fs-xs); color: var(--muted); line-height: 1.5; }

.rsc-ladder { margin: 0 0 8px; }
.rsc-ladder th, .rsc-ladder td { padding: 5px 8px; font-size: var(--fs-xs); }

/* The only row that is a decision today; the rest are a schedule. */
.rsc-step-now { background: var(--gold-soft); }

.rsc-floor-tag {
  display: inline-block;
  padding: 1px 6px;
  border-radius: var(--r-pill);
  background: var(--accent-soft);
  color: var(--accent);
  font-size: var(--fs-3xs);
  font-weight: 700;
}

.rsc-signals {
  margin: 8px 0 0;
  padding-left: 16px;
  font-size: var(--fs-2xs);
  color: var(--muted);
  line-height: 1.5;
}

.rsc-bundles { margin-top: 26px; }

.rsc-bundle-heading {
  margin: 0 0 6px;
  font-size: var(--fs-md);
  font-weight: 700;
  color: var(--ink);
}

.rsc-bundle-card { border-left: 3px solid var(--success); }

/* Slow + fast, side by side, because the pairing IS the idea — reading it as two
   stacked list items loses the point entirely. */
.rsc-bundle-pair {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  gap: 12px;
  align-items: center;
}

.rsc-bundle-half {
  min-width: 0;
  padding: 10px 12px;
  border-radius: var(--r-md);
  background: var(--soft);
}

.rsc-bundle-slow { border-left: 3px solid var(--warning); }
.rsc-bundle-fast { border-left: 3px solid var(--success); }

.rsc-bundle-tag { font-size: var(--fs-2xs); font-weight: 700; color: var(--muted); }
.rsc-bundle-name { margin-top: 3px; font-size: var(--fs-sm); font-weight: 650; color: var(--ink); }
.rsc-bundle-price { margin-top: 3px; font-size: var(--fs-xs); color: var(--muted); }
.rsc-bundle-plus { font-size: var(--fs-xl); font-weight: 700; color: var(--muted); }

.rsc-bundle-money {
  display: flex;
  flex-wrap: wrap;
  gap: 6px 18px;
  margin-top: 12px;
  font-size: var(--fs-xs);
  color: var(--muted);
}

.rsc-bundle-money strong { color: var(--ink); }

.rsc-bundle-title { margin-top: 10px; font-size: var(--fs-xs); color: var(--muted); }
.rsc-bundle-title code {
  padding: 2px 6px;
  border-radius: var(--r-xs);
  background: var(--soft);
  color: var(--ink);
  font-size: var(--fs-2xs);
}

@media (max-width: 900px) {
  .rsc-bundle-pair { grid-template-columns: 1fr; }
  .rsc-bundle-plus { text-align: center; }
}

/* ── Offers to Watchers ───────────────────────────────────────────────────
   Shares the inventory-health table, tiles and bulk bar wholesale — it is the
   same inventory seen from a different angle, and it should look like it.
   Only what is genuinely new to this screen lives here. */
.wo-v-ready { background: var(--success-soft); color: var(--success); }

/* The audience count, which is the whole reason a row is on this screen. */
.wo-watchers { font-weight: 700; white-space: nowrap; }
.wo-watchers.hot { color: var(--success); }

/* Rows that can't carry an offer stay readable but stop competing for attention
   with the ones that can. */
.wo-row-muted { opacity: .72; }

.wo-discount-input {
  width: 68px;
  padding: 5px 6px;
  border: 1px solid var(--teal-700);
  border-radius: var(--r-xs);
  font-size: var(--fs-xs);
  font-weight: 700;
  text-align: right;
  background: var(--card);
  color: var(--ink);
}

.wo-profit-input {
  width: 110px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  background: var(--card);
  color: var(--ink);
}

.wo-confirm-total {
  margin: 0 0 10px;
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink);
}

.wo-message-label {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  margin: 14px 0 6px;
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--ink);
}

.wo-message-count { font-weight: 500; color: var(--muted); }

.wo-message {
  width: 100%;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  font-family: inherit;
  line-height: 1.5;
  resize: vertical;
  background: var(--card);
  color: var(--ink);
}

/* ── Recover Lost Sales ────────────────────────────────────────────────
   Reuses the inventory table wholesale (.inv-*) — this is the third view of
   the same inventory, so it should not look like a different application.
   Only the two things this screen has and the others don't get styles of
   their own: the verdicts specific to an ended listing, and the block of
   losing bidders that lives inside a row. */

/* An ended auction with reachable bidders is the most valuable row on the
   board, so it is the only verdict here rendered as a full-strength badge. */
.rl-v-second {
  background: var(--success-soft);
  color: var(--success);
  border: 1px solid var(--success-line);
}

/* "Put it back up unchanged" is a real recommendation, not a fallback, but it
   is a quieter one than a repriced relist — the money comes from the relist
   itself rather than from a decision about the price. */
.rl-v-asis { background: var(--warning-soft); color: var(--warning); }

.rl-bids { font-weight: 700; color: var(--warning); }

.rl-duration {
  width: 100%;
  margin-bottom: 4px;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  background: var(--card);
  color: var(--ink);
}

.rl-price-input {
  width: 88px;
  padding: 5px 6px;
  border: 1px solid var(--teal-700);
  border-radius: var(--r-xs);
  font-size: var(--fs-xs);
  font-weight: 700;
  text-align: right;
  background: var(--card);
  color: var(--ink);
}

/* The bidders sit inside the listing's own row on purpose: a person who bid on
   this item is a fact about this item, and putting them on a separate screen is
   how a seller relists something they could simply have sold. */
.rl-bidders {
  margin-top: 8px;
  padding: 8px;
  border: 1px solid var(--success-line);
  border-radius: var(--r-md, 8px);
  background: var(--success-soft);
}

.rl-bidders-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  margin-bottom: 6px;
  font-size: var(--fs-xs);
  color: var(--success);
}

.rl-bidder {
  display: grid;
  grid-template-columns: 18px auto auto 1fr;
  align-items: baseline;
  gap: 8px;
  padding: 3px 0;
  font-size: var(--fs-xs);
  cursor: pointer;
}

/* A bidder who can't be offered to is still shown — the reason is the useful
   part — but stops competing with the ones who can. */
.rl-bidder-muted { opacity: .68; cursor: default; }
.rl-bidder-spacer { display: inline-block; width: 18px; }
.rl-bidder-id { font-weight: 700; color: var(--ink); }
.rl-bidder-price { font-weight: 700; white-space: nowrap; color: var(--success); }
.rl-bidder-note { color: var(--muted); }

@media (max-width: 720px) {
  .rl-bidder { grid-template-columns: 18px 1fr auto; }
  .rl-bidder-note { grid-column: 1 / -1; }
}

/* ═══════════════════════════════════════════════════════════════════════
   Design system — states, skeletons and shared surfaces
   Added in the premium design pass. Everything below is generic: no screen
   owns these classes, and any new screen gets the same empty / loading /
   error treatment for free.
   ═══════════════════════════════════════════════════════════════════════ */

/* ── State blocks ─────────────────────────────────────────────────────
   Three moments every data view has, and which this app was rendering as a
   bare sentence: nothing here yet, working on it, it broke. Each gets an
   icon, a headline, one line of plain explanation, and — where there is
   one — the action that resolves it. */
.state {
  display: grid;
  justify-items: center;
  gap: var(--s3);
  padding: var(--s8) var(--s6);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-lg);
  background:
    radial-gradient(120% 100% at 50% 0%, rgba(199, 154, 54, .05), transparent 60%),
    var(--soft);
  text-align: center;
}

.state-icon {
  display: grid;
  place-items: center;
  width: 52px;
  height: 52px;
  border-radius: var(--r-pill);
  background: var(--card);
  border: 1px solid var(--line);
  box-shadow: var(--e1);
  color: var(--brand-ink-2);
  font-size: var(--fs-2xl);
  line-height: 1;
}

.state-icon svg {
  width: 22px; height: 22px;
  stroke: currentColor; fill: none;
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}

.state-title {
  color: var(--ink);
  font-size: var(--fs-md);
  font-weight: 700;
  letter-spacing: -.015em;
}

.state-body {
  max-width: 46ch;
  color: var(--muted);
  font-size: var(--fs-sm);
  line-height: 1.55;
}

.state-actions {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--s2);
  margin-top: var(--s1);
}

.state-hint { color: var(--muted); font-size: var(--fs-sm); }

.state--error {
  border-style: solid;
  border-color: var(--danger-line);
  background: var(--danger-soft);
}
.state--error .state-icon { color: var(--danger); border-color: var(--danger-line); }
.state--error .state-title { color: var(--danger); }
.state--error .state-body { color: var(--danger); }

/* The raw API text belongs in the error state rather than being swallowed —
   but it is evidence, not the headline, so it reads as evidence. */
.state-detail {
  max-width: 72ch;
  margin-top: var(--s1);
  padding: var(--s2) var(--s3);
  border: 1px solid var(--danger-line);
  border-radius: var(--r-sm);
  background: var(--danger-tint);
  color: var(--danger);
  font-family: var(--font-mono);
  font-size: var(--fs-xs);
  line-height: 1.5;
  text-align: left;
  overflow-wrap: anywhere;
}

.state--success {
  border-style: solid;
  border-color: var(--success-line);
  background: var(--success-soft);
}
.state--success .state-icon { color: var(--success); border-color: var(--success-line); }

.state--compact { padding: var(--s6) var(--s4); }
.state--inline { border: 0; background: none; padding: var(--s6) var(--s4); }

/* States sit outside the card grid so they stay full width and survive the
   switch to table view, where the grid itself is hidden. */
.listings-state:not(:empty) { margin-bottom: var(--s3); }

/* Any state dropped into a card grid spans it, rather than being squeezed
   into one 160px column. */
.listings-grid > .state,
.pl-photo-grid > .state,
.opp-results-list > .state,
.fb-list > .state { grid-column: 1 / -1; }

/* Skeleton rows stand in for table rows, so the cell holding them keeps none
   of a cell's padding. */
td.skeleton-cell { padding: 0 !important; }

/* ── Skeletons ────────────────────────────────────────────────────────
   A spinner says "wait"; a skeleton says "wait, and here is the shape of
   what is coming" — which makes the same wait feel shorter and stops the
   page jumping when the data lands. */
.skeleton {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-xs);
  background: var(--soft-2);
}

.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(90deg, transparent, var(--skeleton-sheen), transparent);
  animation: shimmer 1.4s var(--ease) infinite;
}

@keyframes shimmer { to { transform: translateX(100%); } }

@media (prefers-reduced-motion: reduce) {
  .skeleton::after { animation: none; opacity: .4; }
}

.skeleton-card {
  display: grid;
  gap: var(--s3);
  padding: var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
}

.skeleton-media { width: 100%; aspect-ratio: 4 / 3; border-radius: var(--r-sm); }
.skeleton-line { height: 11px; }
.skeleton-line.tall { height: 15px; }
.skeleton-line.w-90 { width: 90%; }
.skeleton-line.w-70 { width: 70%; }
.skeleton-line.w-50 { width: 50%; }
.skeleton-line.w-40 { width: 40%; }

.skeleton-meta {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s2);
}

.skeleton-row {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3);
  border-bottom: 1px solid var(--line-soft);
}
.skeleton-row .skeleton-avatar { width: 44px; height: 44px; border-radius: var(--r-xs); flex-shrink: 0; }
.skeleton-row .skeleton-fill { flex: 1; }

/* ── Setup checklist ──────────────────────────────────────────────────
   Was a stack of inline styles in a blue that belonged to no other part of
   this product. Same three steps, now on the brand's teal and gold. */
/* Light, not dark. It sits on the dark top band of the page background and
   directly above the dark hero, and a third dark slab there disappeared into
   both. A white card is also the correct weight for the first thing a new
   user is asked to do. */
.setup-checklist {
  position: relative;
  margin-bottom: var(--s5);
  padding: var(--s6);
  border: 1px solid var(--line);
  border-top: 3px solid var(--gold);
  border-radius: var(--r-lg);
  background: var(--card);
  color: var(--ink);
  box-shadow: var(--e3);
}

.setup-dismiss {
  position: absolute;
  top: var(--s3);
  right: var(--s3);
  width: 28px;
  height: 28px;
  border: 0;
  border-radius: var(--r-xs);
  background: transparent;
  color: var(--muted);
  font-size: var(--fs-xl);
  line-height: 1;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.setup-dismiss:hover { background: var(--soft-2); color: var(--ink); }

/* The eyebrow carries the label the title used to. The title itself is now a sentence written by
   the server — "Start here", then "Two logins, then the part that makes money", then "That's the
   whole loop" — and a sentence set in 11px uppercase tracking reads as a legal notice. */
.setup-eyebrow {
  color: var(--gold-ink);
  font-size: var(--fs-xs);
  font-weight: 750;
  text-transform: uppercase;
  letter-spacing: .13em;
}

.setup-title {
  margin-top: var(--s1);
  color: var(--ink);
  font-size: var(--fs-lg);
  font-weight: 700;
  letter-spacing: -.01em;
}

.setup-sub {
  margin-top: var(--s2);
  margin-bottom: var(--s4);
  color: var(--muted);
  font-size: var(--fs-sm);
}

.setup-steps { display: grid; gap: var(--s2); }

.setup-step {
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--soft);
  transition: border-color var(--dur-2) var(--ease), background var(--dur-2) var(--ease);
}
.setup-step:hover { border-color: var(--line-strong); background: var(--card); }
.setup-step--optional { background: transparent; }

.setup-step-num {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  border-radius: var(--r-pill);
  background: var(--teal-800);
  color: var(--gold-note);
  font-size: var(--fs-sm);
  font-weight: 700;
}
.setup-step--optional .setup-step-num { background: var(--soft-2); color: var(--muted); }

/* Done is the state worth styling — it is the reward for finishing a step. */
.setup-step.is-done .setup-step-num { background: var(--success); color: var(--success-tint); }
.setup-step.is-done { opacity: .62; }

.setup-step-body { flex: 1; min-width: 0; }

.setup-step-title {
  color: var(--ink);
  font-weight: 650;
  font-size: var(--fs-sm);
  margin-bottom: 2px;
}

.setup-step-copy {
  color: var(--muted);
  font-size: var(--fs-sm);
  line-height: 1.5;
}
.setup-step-copy strong { color: var(--ink); font-weight: 650; }
.setup-step-copy a { color: var(--brand-ink-2); font-weight: 600; }

.setup-tag {
  display: inline-block;
  margin-left: var(--s2);
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: var(--soft-2);
  color: var(--muted);
  font-size: var(--fs-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .06em;
}

.setup-step .btn { flex-shrink: 0; }

/* The optional extras: one line, visibly not a step. Dashed rather than filled, so it reads as
   an aside to the two numbered rows above it rather than as the one still outstanding. */
.setup-extras {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
  margin-top: var(--s2);
  padding: var(--s3) var(--s4);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
}

.setup-extras-label {
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 650;
}

.setup-extras-copy {
  flex: 1;
  min-width: 200px;
  color: var(--muted);
  font-size: var(--fs-sm);
}

.setup-extras .btn { flex-shrink: 0; }

/* ── Getting-started progress ─────────────────────────────────────────
   Five steps, two of which are setup and three of which are the flip. The bar
   exists because the panel got longer: six rows with no sense of how far
   through them you are is a form, and the whole point of the three new rows is
   that they are not a form. The count beside it is the honest version of the
   same fact — a bar alone can be read as "nearly there" from any width. */
.setup-progress {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin-bottom: var(--s4);
}

.setup-progress-track {
  flex: 1;
  height: 6px;
  border-radius: var(--r-pill);
  background: var(--soft-2);
  overflow: hidden;
}

.setup-progress-fill {
  height: 100%;
  border-radius: var(--r-pill);
  background: linear-gradient(90deg, var(--teal-800), var(--gold));
  transition: width var(--dur-3) var(--ease);
}

.setup-progress-count {
  flex-shrink: 0;
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

/* The one row to do now. Everything below the fold of a six-row panel looks
   equally optional; without this, so does everything above it. */
.setup-step.is-next {
  border-color: var(--gold);
  background: var(--card);
  box-shadow: 0 0 0 3px rgba(199, 154, 54, .14);
}
.setup-step.is-next .setup-step-num { background: var(--gold); color: var(--teal-800); }

/* A step that looks finished and is not. Only step 1 can be in this state, and only when
   Anthropic has told the app in so many words that the saved key will not work — so it outranks
   the gold "do this next" ring: this is not the next thing to do, it is the thing already done
   wrong. The row keeps its full opacity (unlike .is-done) because it still needs reading. */
.setup-step.is-problem {
  border-color: var(--danger-line);
  background: var(--danger-tint);
  opacity: 1;
}
.setup-step.is-problem .setup-step-num { background: var(--danger); color: var(--danger-tint); }

.setup-step-problem {
  margin-top: 6px;
  color: var(--danger);
  font-size: var(--fs-sm);
  line-height: 1.5;
  font-weight: 600;
}
.setup-step-problem .link-ext { margin-left: 6px; font-weight: 650; }

/* The three steps past setup. Not a lighter weight than the two logins — they
   are the ones that show what the app is for — but visibly a different kind of
   thing: a login is a fact, these are an errand. */
.setup-step--flip { background: transparent; }

/* All five done. The panel has finished its job and says so once, in the gold
   it opened in, and then waits to be closed rather than vanishing on its own. */
.setup-checklist.is-complete { border-top-color: var(--success); }
.setup-checklist.is-complete .setup-eyebrow { color: var(--success); }

/* ── The getting-started rail ────────────────────────────────────────
   The same panel, carried onto the screen the step actually happens on. Pinned to
   the bottom of the main column rather than the whole viewport, so it never covers
   the sidebar the seller navigates with, and never covers the header of the screen
   it is talking about. */
.onboard-coach {
  position: fixed;
  left: var(--sidebar-w);
  right: 0;
  bottom: 0;
  /* Above the feature screens and below the modals. Eighteen of the app's views render
     through .opportunity-overlay, which is fixed, opaque and z-index 900 — a rail below
     that is invisible on exactly the screens it exists for. Above 1000 and it would sit
     over the settings modal its own "Enter Key →" button opens. */
  z-index: 950;
  padding: 0 var(--s6) var(--s4);
  /* The gutter around the card is not a target — a full-width invisible strip along
     the bottom of the app would swallow clicks meant for the screen behind it. */
  pointer-events: none;
  animation: coach-in var(--dur-2) var(--ease);
}
.onboard-coach.hidden { display: none; }

.onboard-coach-inner {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--s4);
  padding: var(--s4) var(--s6) var(--s4) var(--s4);
  pointer-events: auto;
  border: 1px solid var(--line);
  border-top: 3px solid var(--gold);
  border-radius: var(--r-lg);
  background: var(--card);
  color: var(--ink);
  box-shadow: var(--e4);
}

/* The same numbered bead as the checklist row this line came from, so the two read
   as one thing in two places rather than as two features saying similar words. */
.onboard-coach-bead {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--gold);
  color: var(--teal-800);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  line-height: 1;
}

.onboard-coach-body { flex: 1; min-width: 0; }

.onboard-coach-eyebrow {
  margin: 0;
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
}

.onboard-coach-title {
  margin: 2px 0 0;
  color: var(--ink);
  font-size: var(--fs-md);
  font-weight: 700;
}

.onboard-coach-copy {
  margin: 4px 0 0;
  color: var(--muted);
  font-size: var(--fs-sm);
  line-height: 1.55;
}
.onboard-coach-copy:empty { display: none; }

/* What earns the tick, in the smallest type on the card. It is reassurance rather
   than instruction — the seller only goes looking for it when the row has not
   ticked and they think it should have. */
.onboard-coach-proof {
  margin: 4px 0 0;
  color: var(--muted);
  font-size: var(--fs-xs);
  line-height: 1.5;
  opacity: .85;
}
.onboard-coach-proof:empty { display: none; }

.onboard-coach-actions {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding-top: 2px;
}

.onboard-coach-dismiss {
  position: absolute;
  top: 6px;
  right: 8px;
  width: 24px;
  height: 24px;
  border: 0;
  border-radius: var(--r-xs);
  background: transparent;
  color: var(--muted);
  font-size: var(--fs-lg);
  line-height: 1;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease);
}
.onboard-coach-dismiss:hover { background: var(--soft-2); color: var(--ink); }

/* A step just ticked. The one moment the app has proof it did something for the
   seller, so it gets the success colour and the bead the checklist gives a done row. */
.onboard-coach.is-win .onboard-coach-inner { border-top-color: var(--success); }
.onboard-coach.is-win .onboard-coach-bead { background: var(--success); color: var(--success-tint); }
.onboard-coach.is-win .onboard-coach-eyebrow { color: var(--success); }

@keyframes coach-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* The sidebar stops being a column here and the feature screens take the full width
   with it, so the rail has to let go of the same gutter they do — otherwise it sits
   three hundred pixels in from a left edge that no longer belongs to anything. */
@media (max-width: 1120px) {
  .onboard-coach { left: 0; }
}

@media (max-width: 900px) {
  .onboard-coach { padding: 0 var(--s4) var(--s3); }
  .onboard-coach-inner { flex-wrap: wrap; }
  .onboard-coach-actions { width: 100%; justify-content: flex-end; }
}

@media (prefers-reduced-motion: reduce) {
  .onboard-coach { animation: none; }
}

/* ── The first run ───────────────────────────────────────────────────
   Wider than the settings modal and set in prose, because it is the one screen
   in the app that is read rather than used. */
.welcome-modal {
  width: min(680px, 100%);
  padding: var(--s7) var(--s7) var(--s6);
  border-top: 3px solid var(--gold);
}

.welcome-modal h2 {
  margin-top: var(--s2);
  color: var(--ink);
  font-size: var(--fs-3xl);
  font-weight: 750;
  letter-spacing: -.02em;
}

.welcome-promise {
  margin-top: var(--s3);
  color: var(--ink);
  font-size: var(--fs-md);
  line-height: 1.6;
}

.welcome-points {
  display: grid;
  gap: var(--s3);
  margin: var(--s5) 0;
  padding: 0;
  list-style: none;
}

.welcome-points li {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  color: var(--muted);
  font-size: var(--fs-sm);
  line-height: 1.55;
}
.welcome-points strong { color: var(--ink); font-weight: 650; }

.welcome-point-icon {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border-radius: var(--r-md);
  background: var(--teal-800);
  color: var(--gold-note);
}
.welcome-point-icon svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* What it will cost and how long, said before the seller is asked for anything.
   Leaving this to be discovered on the settings screen is how a free beta ends
   up feeling like a paywall. */
.welcome-needs {
  padding: var(--s4);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
  color: var(--muted);
  font-size: var(--fs-sm);
  line-height: 1.6;
}
.welcome-needs strong { color: var(--ink); font-weight: 650; }

.welcome-actions {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
  margin-top: var(--s5);
}

/* ── Shared surface polish ────────────────────────────────────────── */
.opp-supplier-panel {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--card);
  padding: var(--s6);
  margin-bottom: var(--s5);
  box-shadow: var(--e1);
}

.opp-supplier-heading {
  margin-bottom: var(--s2);
  font-size: var(--fs-md);
  font-weight: 700;
  letter-spacing: -.015em;
  color: var(--brand-ink-strong);
}

/* Was pulled up 10px, which sat it on top of the heading above it. The
   measure cap matters more here than anywhere else in the app: these panels
   run 1,300px wide and an uncapped line that long is genuinely hard to
   track back to the start of the next one. */
.opp-hint {
  max-width: 105ch;
  margin: 0 0 var(--s5);
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: var(--muted);
}

.inv-footnote,
.fb-arb-footnote { max-width: 105ch; }

/* Overlay chrome buttons hold icons now, not emoji. */
.nl-close-btn svg {
  width: 17px;
  height: 17px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.inv-tile {
  padding: var(--s4);
  border: 1px solid var(--line);
  border-left: 3px solid var(--teal-700);
  border-radius: var(--r-md);
  background: var(--card);
  box-shadow: var(--e1);
  transition: box-shadow var(--dur-2) var(--ease);
}
.inv-tile:hover { box-shadow: var(--e2); }

/* ── Page section rhythm ──────────────────────────────────────────── */
.section-intro {
  max-width: 70ch;
  margin: calc(var(--s2) * -1) 0 var(--s5);
  color: var(--muted);
  font-size: var(--fs-sm);
  line-height: 1.6;
}

.divider {
  height: 1px;
  margin: var(--s6) 0;
  background: var(--line-soft);
  border: 0;
}

/* ── Responsive ───────────────────────────────────────────────────────
   The window is resizable and the installed app opens smaller than a design
   mock, so the two fixed grids need a way down. */
@media (max-width: 1180px) {
  .content-grid { grid-template-columns: minmax(0, 1fr); }
  .stats-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
  .app-shell { grid-template-columns: 232px minmax(0, 1fr); }
  .main { padding: var(--s4); }
  .hero-panel { flex-direction: column; align-items: flex-start; padding: var(--s6); }
  .opportunity-overlay-body { padding: var(--s4) var(--s4) var(--s7); }
  .opportunity-overlay-header { padding: var(--s3) var(--s4); }
}

@media (max-width: 640px) {
  .stats-grid { grid-template-columns: minmax(0, 1fr); }
  .topbar { flex-wrap: wrap; }
}

/* ── Item Specifics, driven by eBay's own category requirements ────────────
   The panel used to be blank name/value rows. It is now the category's real
   aspect list, so the visual job changed: a required-but-empty field has to
   be impossible to scroll past, and a value the app is merely *offering* has
   to look different from one the seller entered. Hence the coloured left rail
   on required rows and the dashed, clickable suggestion chip. ------------- */

/* --- Keyword gap: the words the competing listings use, under the title box. Every chip is a
   button because every one of them is a claim the seller has to agree to; the counts beside the
   word are what makes agreeing possible. ---------------------------------------------------- */

.kw-wrap {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
  margin-top: var(--s2);
}

.kw-free { font-size: var(--fs-xs); color: var(--muted); }

.kw-panel {
  margin-top: var(--s3);
  padding: var(--s3);
  border: 1px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--soft);
}

.kw-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s3);
}

.kw-headline { font-size: var(--fs-sm); font-weight: 700; color: var(--ink-2); }
.kw-source   { font-size: var(--fs-xs); color: var(--muted); margin-top: 2px; }

.kw-close {
  border: 0;
  background: transparent;
  color: var(--muted);
  cursor: pointer;
  font-size: var(--fs-sm);
  line-height: 1;
  padding: var(--s1);
  flex-shrink: 0;
}
.kw-close:hover { color: var(--ink-2); }

.kw-message {
  margin-top: var(--s2);
  font-size: var(--fs-xs);
  color: var(--ink-2);
  line-height: 1.5;
}

.kw-label {
  margin: var(--s3) 0 var(--s2);
  font-size: var(--fs-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--ink-2);
}
.kw-hint  { font-weight: 400; text-transform: none; letter-spacing: 0; color: var(--muted); }
.kw-count { font-weight: 400; color: var(--muted); }

.kw-chips { display: flex; flex-wrap: wrap; gap: var(--s2); }

.kw-chip {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  padding: 4px 9px;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  background: var(--card);
  font: inherit;
  text-align: left;
}
.kw-chip.is-add  { cursor: pointer; border-style: dashed; border-color: var(--info-line); }
.kw-chip.is-add:hover:not(:disabled) { background: var(--info-soft); border-style: solid; }
.kw-chip.is-have { border-color: var(--success-line); background: var(--success-soft); }
.kw-chip.is-added,
.kw-chip:disabled {
  cursor: default;
  opacity: 0.55;
  border-style: solid;
}

.kw-chip-term { font-size: var(--fs-sm); font-weight: 600; color: var(--ink); }
.kw-chip-why  { font-size: var(--fs-2xs); color: var(--muted); white-space: nowrap; }

.kw-suggest {
  margin-top: var(--s3);
  padding-top: var(--s3);
  border-top: 1px solid var(--line-soft);
}

.kw-suggest-title {
  padding: var(--s2);
  border-radius: var(--r-sm);
  background: var(--card);
  border: 1px solid var(--line-soft);
  font-size: var(--fs-sm);
  line-height: 1.5;
  word-break: break-word;
}

.kw-suggest-row {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
  margin-top: var(--s2);
}
.kw-suggest-note { font-size: var(--fs-xs); color: var(--muted); }

.kw-specifics { display: flex; flex-direction: column; gap: var(--s2); margin-bottom: var(--s2); }

.kw-spec {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s2);
  border-radius: var(--r-sm);
  border: 1px solid var(--line-soft);
  background: var(--card);
}
.kw-spec.is-required { border-left: 3px solid var(--danger); }

.kw-spec-main { min-width: 0; }
.kw-spec-name { font-size: var(--fs-sm); font-weight: 600; color: var(--ink); }
.kw-spec-why  { font-size: var(--fs-2xs); color: var(--muted); margin-top: 2px; }
.kw-spec-use  { flex-shrink: 0; }

.kw-spec-pill {
  display: inline-block;
  margin-left: var(--s1);
  padding: 1px 7px;
  border-radius: var(--r-pill);
  font-size: var(--fs-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  white-space: nowrap;
}
.kw-spec-pill.is-required { background: var(--danger-soft); color: var(--danger); border: 1px solid var(--danger-line); }
.kw-spec-pill.is-rec      { background: var(--info-soft);   color: var(--info);   border: 1px solid var(--info-line); }

.kw-shared { margin-top: var(--s3); }
.kw-shared > summary {
  cursor: pointer;
  font-size: var(--fs-xs);
  color: var(--muted);
  margin-bottom: var(--s2);
}

.kw-caveat {
  margin: var(--s3) 0 0;
  font-size: var(--fs-2xs);
  color: var(--muted);
  line-height: 1.5;
}

.asp-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
  margin-bottom: var(--s3);
}

.asp-head-actions { display: flex; gap: var(--s2); flex-shrink: 0; }

.asp-status {
  font-size: var(--fs-sm);
  color: var(--ink-2);
  line-height: 1.5;
  flex: 1 1 260px;
}
.asp-status.is-blocker { color: var(--danger); font-weight: 600; }
.asp-status.is-warn    { color: var(--warning); font-weight: 600; }
.asp-status.is-ok      { color: var(--success); font-weight: 600; }
.asp-status.is-hint    { color: var(--muted); }

.asp-summary-badge {
  display: inline-block;
  margin-left: var(--s2);
  padding: 2px 9px;
  border-radius: var(--r-pill);
  font-size: var(--fs-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  vertical-align: middle;
}
.asp-summary-badge.is-blocker { background: var(--danger-soft);  color: var(--danger);  border: 1px solid var(--danger-line); }
.asp-summary-badge.is-warn    { background: var(--warning-soft); color: var(--warning); border: 1px solid var(--warning-line); }
.asp-summary-badge.is-ok      { background: var(--success-soft); color: var(--success); border: 1px solid var(--success-line); }

.asp-group { display: flex; flex-direction: column; gap: var(--s3); }
.asp-group:not(:empty) { margin-bottom: var(--s3); }

.asp-row {
  display: grid;
  grid-template-columns: minmax(150px, 34%) 1fr;
  grid-template-areas: "label control" ". under";
  align-items: center;
  gap: var(--s1) var(--s3);
  padding: var(--s2) var(--s2) var(--s2) var(--s3);
  border-radius: var(--r-sm);
  border-left: 3px solid transparent;
}
.asp-row.is-required    { background: var(--soft); border-left-color: var(--line-strong); }
.asp-row.is-blocker     { background: var(--danger-soft); border-left-color: var(--danger); }
.asp-row.is-recommended { background: transparent; border-left-color: var(--line-soft); }

.asp-label {
  grid-area: label;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink);
  line-height: 1.4;
}

.asp-pill {
  display: inline-block;
  padding: 1px 7px;
  border-radius: var(--r-pill);
  font-size: var(--fs-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  white-space: nowrap;
}
.asp-pill.is-required { background: var(--danger-soft); color: var(--danger); border: 1px solid var(--danger-line); }
.asp-pill.is-rec      { background: var(--info-soft);   color: var(--info);   border: 1px solid var(--info-line); }

.asp-renamed {
  font-size: var(--fs-2xs);
  font-weight: 500;
  color: var(--muted);
  font-style: italic;
}

.asp-control { grid-area: control; width: 100%; }
.asp-row.is-blocker .asp-control { border-color: var(--danger-line); }

/* Confirms an applied suggestion landed in the right box, then gets out of the
   way — a permanent highlight would leave every filled field looking unresolved. */
.asp-just-filled { animation: asp-flash 1.4s var(--ease); }
@keyframes asp-flash {
  0%   { background: var(--gold-soft); box-shadow: 0 0 0 3px rgba(199, 154, 54, .35); }
  100% { background: transparent;      box-shadow: none; }
}

.asp-under {
  grid-area: under;
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
}
.asp-under:empty { display: none; }

.asp-note { font-size: var(--fs-2xs); color: var(--muted); }

.asp-suggest {
  border: 1px dashed var(--gold);
  background: var(--gold-soft);
  color: var(--gold-ink);
  border-radius: var(--r-pill);
  padding: 3px 10px;
  font-size: var(--fs-2xs);
  font-weight: 700;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease);
}
.asp-suggest:hover { background: var(--gold-soft); border-color: var(--gold-dark); }
.asp-suggest.is-low { border-color: var(--line-strong); background: var(--soft); color: var(--ink-2); }
.asp-suggest-src { font-weight: 500; opacity: .8; }

.asp-optional > summary {
  cursor: pointer;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink-2);
  padding: var(--s2) 0;
}
.asp-count { color: var(--muted); font-weight: 500; }

.asp-custom { margin-top: var(--s4); padding-top: var(--s3); border-top: 1px solid var(--line-soft); }
.asp-custom-label { font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2); margin: 0 0 var(--s2); }
.asp-custom-hint  { font-weight: 400; color: var(--muted); }

@media (max-width: 720px) {
  .asp-row {
    grid-template-columns: 1fr;
    grid-template-areas: "label" "control" "under";
  }
}

/* ── Readiness bar ─────────────────────────────────────────────────────────
   Sits directly above Publish, because that is the moment it exists for. The
   score is the affordance; the fix list underneath is collapsed by default, so
   a finished listing shows one line and an unfinished one is one click from
   the reasons. ---------------------------------------------------------- */

.rd-bar {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
  margin-bottom: var(--s3);
  overflow: hidden;
}
.rd-bar.is-blocker { border-color: var(--danger-line);  background: var(--danger-soft); }
.rd-bar.is-warn    { border-color: var(--warning-line); background: var(--warning-soft); }
.rd-bar.is-good    { border-color: var(--info-line);    background: var(--info-soft); }
.rd-bar.is-ok      { border-color: var(--success-line); background: var(--success-soft); }

.rd-toggle {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  padding: var(--s3) var(--s4);
  border: 0;
  background: transparent;
  cursor: pointer;
  text-align: left;
  font: inherit;
  color: var(--ink);
}

.rd-score {
  flex-shrink: 0;
  min-width: 46px;
  height: 46px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--r-pill);
  font-size: var(--fs-lg);
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  background: var(--card);
  border: 2px solid var(--line-strong);
  color: var(--ink);
}
.rd-bar.is-blocker .rd-score { border-color: var(--danger);  color: var(--danger); }
.rd-bar.is-warn    .rd-score { border-color: var(--warning); color: var(--warning); }
.rd-bar.is-good    .rd-score { border-color: var(--info);    color: var(--info); }
.rd-bar.is-ok      .rd-score { border-color: var(--success); color: var(--success); }

.rd-text { display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; min-width: 0; }
.rd-grade { font-size: var(--fs-md); font-weight: 700; }
.rd-headline { font-size: var(--fs-sm); color: var(--ink-2); }

.rd-counts {
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--ink-2);
  white-space: nowrap;
  flex-shrink: 0;
}

.rd-chevron { flex-shrink: 0; color: var(--muted); transition: transform var(--dur-2) var(--ease); }
.rd-toggle.is-open .rd-chevron { transform: rotate(180deg); }

/* The one-click fill. Sits between the score and the fix list, above the fold of the
   disclosure, because it is the shortest route from "not ready" to "ready" on the screen. */
.rd-fill-row {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
  padding: 0 var(--s4) var(--s3);
}
.rd-fill-note { font-size: var(--fs-xs); color: var(--ink-2); }

/* A value the app is offering for a listing field. Same dashed-gold treatment as an Item
   Specific suggestion — it is the same promise: nothing is written until this is clicked. */
.rd-fix-fill {
  flex-shrink: 0;
  align-self: center;
  border: 1px dashed var(--gold);
  background: var(--gold-soft);
  color: var(--gold-ink);
  border-radius: var(--r-pill);
  padding: 4px 12px;
  font-size: var(--fs-2xs);
  font-weight: 700;
  cursor: pointer;
  max-width: 240px;
  text-align: left;
  transition: border-color var(--dur-1) var(--ease);
}
.rd-fix-fill:hover { border-color: var(--gold-dark); }
.rd-fix-fill.is-low { border-color: var(--line-strong); background: var(--soft); color: var(--ink-2); }

/* Where the value came from, and — for an inferred one — that it was inferred. The estimate
   mark is not decoration: this number prices a shipping label. */
.rd-fix-src { display: block; font-weight: 500; opacity: .8; margin-top: 1px; }
.rd-fix-est { font-style: italic; }

.rd-list {
  border-top: 1px solid var(--line-soft);
  background: var(--card);
  max-height: min(320px, 26vh);
  overflow-y: auto;
}

.rd-fix {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--line-soft);
}
.rd-fix:last-child { border-bottom: 0; }

.rd-dot {
  flex-shrink: 0;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-top: 6px;
}
.rd-fix.is-blocker .rd-dot { background: var(--danger); }
.rd-fix.is-warning .rd-dot { background: var(--warning); }
.rd-fix.is-tip     .rd-dot { background: var(--info); }

.rd-fix-text { display: flex; flex-direction: column; gap: 2px; flex: 1 1 auto; min-width: 0; }
.rd-fix-label { font-size: var(--fs-sm); font-weight: 600; color: var(--ink); }
.rd-fix-why   { font-size: var(--fs-xs); color: var(--ink-2); line-height: 1.5; }
.rd-fix-go    { flex-shrink: 0; align-self: center; }

.rd-empty { padding: var(--s4); margin: 0; font-size: var(--fs-sm); color: var(--muted); }

.rd-block-list { margin: var(--s2) 0; padding-left: var(--s5); font-size: var(--fs-sm); }
.rd-block-list li { margin-bottom: 2px; }

@media (prefers-reduced-motion: reduce) {
  .asp-just-filled { animation: none; background: var(--gold-soft); }
  .rd-chevron { transition: none; }
}

/* ─────────────────────────────────────────────────────────────────────────────
   Reliability: failure panels, recovered work, photo notices

   These three carry the app's worst moments, and the previous treatment gave the
   worst moment the smallest box: a one-line red bar holding either a raw API
   string or a fragment of an HTML error page. A failure needs room for what
   happened, what to do about it, and the button that does it — which is the
   difference between a seller who fixes it in ten seconds and one who concludes
   the app is broken.
   ───────────────────────────────────────────────────────────────────────────── */

.failure-panel {
  margin: var(--s3) 0;
  padding: var(--s4);
  border: 1px solid var(--danger-line);
  border-left: 3px solid var(--danger);
  border-radius: var(--r-md);
  background: var(--danger-soft);
}

.failure-head {
  font-size: var(--fs-md);
  font-weight: 800;
  color: var(--danger);
  margin-bottom: var(--s2);
  letter-spacing: -0.01em;
}

.failure-what,
.failure-todo,
.failure-preserved,
.failure-attempts {
  margin: 0 0 var(--s2);
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--text-strong, var(--teal-850));
}

/* What to do next is the line the seller acts on, so it gets the weight. */
.failure-todo { font-weight: 700; }

/* Reassurance, not an alarm — deliberately quieter than everything above it. */
.failure-preserved,
.failure-attempts {
  font-size: var(--fs-xs);
  color: var(--muted);
  font-weight: 600;
}

.failure-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-top: var(--s3);
}

/* Evidence, folded away. Sellers forward this when asking for help, so it is always
   present — but it is never the first thing they read. */
.failure-detail {
  margin-top: var(--s3);
  font-size: var(--fs-xs);
}

.failure-detail summary {
  cursor: pointer;
  color: var(--muted);
  font-weight: 700;
  user-select: none;
}

.failure-detail summary:focus-visible {
  outline: none;
  box-shadow: var(--focus-ring);
  border-radius: var(--r-xs);
}

.failure-detail pre {
  margin: var(--s2) 0 0;
  padding: var(--s3);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  font-family: var(--font-mono);
  font-size: var(--fs-2xs);
  line-height: 1.5;
  color: var(--muted);
  white-space: pre-wrap;
  word-break: break-word;
  max-height: 180px;
  overflow: auto;
}

/* Recovered work. Teal rather than red: finding a listing again is good news, and
   dressing it as an error is how a seller dismisses the banner without reading it. */
.recovery-banner {
  margin-bottom: var(--s4);
  padding: var(--s4);
  border: 1px solid var(--info-line);
  border-left: 3px solid var(--info);
  border-radius: var(--r-md);
  background: var(--info-soft);
}

.recovery-head {
  font-size: var(--fs-md);
  font-weight: 800;
  color: var(--info);
  letter-spacing: -0.01em;
}

.recovery-sub {
  margin: var(--s1) 0 var(--s3);
  font-size: var(--fs-xs);
  color: var(--muted);
  font-weight: 600;
}

.recovery-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}

.recovery-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
  padding: var(--s3);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
}

.recovery-meta {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.recovery-label {
  font-size: var(--fs-sm);
  font-weight: 700;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 46ch;
}

.recovery-when {
  font-size: var(--fs-2xs);
  color: var(--muted);
  font-weight: 600;
}

/* An unresolved publish is the one row that needs a second look, so it is marked
   rather than left to read like an ordinary unfinished draft. */
.recovery-flag {
  align-self: flex-start;
  margin-top: 2px;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: var(--warning-soft);
  border: 1px solid var(--warning-line);
  color: var(--warning);
  font-size: var(--fs-3xs);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.recovery-error {
  font-size: var(--fs-2xs);
  color: var(--danger);
  font-weight: 600;
  max-width: 60ch;
}

.recovery-row-actions {
  display: flex;
  gap: var(--s2);
  flex-wrap: wrap;
  margin-left: auto;
}

/* The way back to an unfinished listing, in the listing screen's own header. Styled off
   .nl-load-all-drafts-btn beside it so the header keeps one kind of button, and tinted
   teal for the same reason the banner is: recovering work is good news. */
.nl-recover-btn {
  border-color: var(--info-line);
  color: var(--info);
}

/* Sits above the action cards, inside the modal's own scroller. Full width, because it is
   a list of listings and a narrow one would truncate every title it exists to show. */
.nl-recover-panel {
  margin: var(--s3) var(--s5) 0;
}

/* Whether the listing on screen is actually stored, said in the header where it is visible
   without being in the way. Autosave used to be entirely invisible, which is fine right up
   until it stops working — a seller whose saves have been failing for ten minutes saw the
   same screen as one whose saves were all landing. Muted by default: this is a status, not
   an announcement, and it only raises its voice when the answer is no.

   Colours are literal rather than themed. This line sits on the header's teal gradient,
   which is the same in light and dark, and the themed --success is a dark green that
   disappears against it. */
.nl-save-state {
  flex: none;
  margin-left: auto;
  align-self: center;
  padding: 0 var(--s3) 6px;
  font-size: var(--fs-2xs);
  font-weight: 700;
  color: rgba(255, 255, 255, .6);
  white-space: nowrap;
}

.nl-save-state--ok { color: #7fe3bd; }

.nl-save-state--warn {
  color: #ffcf7a;
  cursor: help;
}

/* Below the list and right-aligned, away from the per-row Restore buttons: it clears every
   draft at once, so it must not be the button a hurried click lands on. */
.recovery-bulk {
  display: flex;
  justify-content: flex-end;
  margin-top: var(--s3);
  padding-top: var(--s2);
  border-top: 1px solid var(--info-line);
}

/* A photo problem that does not stop the listing. Amber, and it stays put — the old
   version auto-hid after four seconds, which is how a listing reached eBay with no
   picture and nobody remembered being told. */
.nl-photo-upload-status.warn {
  color: var(--warning);
  background: var(--warning-soft);
  border: 1px solid var(--warning-line);
  border-radius: var(--r-sm);
  padding: var(--s2) var(--s3);
  font-weight: 700;
  line-height: 1.5;
  align-items: flex-start;
}

.nl-photo-upload-status.warn::before {
  background: var(--warning);
  margin-top: 5px;
}

/* Follows a publish result, where "already live" and "just published" have to read
   differently — a seller told the wrong one either hunts for a listing that does not
   exist or duplicates one that does. */
.nl-result-note {
  display: block;
  margin-top: var(--s2);
  font-size: var(--fs-xs);
  font-weight: 600;
  color: var(--muted);
  line-height: 1.55;
}

@media (max-width: 640px) {
  .recovery-row { flex-direction: column; align-items: stretch; }
  .recovery-row-actions { margin-left: 0; }
  .recovery-label { max-width: none; white-space: normal; }
}

/* Full-width inside the AI listing modal: it sits above the two-column body, so it needs the
   modal's own horizontal padding rather than a column's. */
#new-listing-overlay .failure-panel {
  margin: 0 40px var(--s3);
  border-radius: var(--r-md);
}

@media (max-width: 900px) {
  #new-listing-overlay .failure-panel { margin: 0 16px var(--s3); }
}

/* ── Roll the Dice ─────────────────────────────────────────────────────────────
   The one feature a seller with no idea what to sell can use, so it gets the loudest
   surface in the app: a card in the dashboard's quick actions (.dash-act--dice) and a
   gold-lit board in the Opportunity Finder. Everything below still reads money in the
   same colours the rest of the app uses — success/danger for profit, gold for a
   jackpot — because the point is that these numbers are the same numbers, not a
   louder set of them.

   The full-width .dice-band that used to carry this on the dashboard is gone: it was
   one of six bands competing there, and it is now one of the three action cards. */

/* The one button in the app that should look like a lever worth pulling. */
.btn-dice {
  background: linear-gradient(180deg, var(--gold-light), var(--gold-dark));
  border-color: var(--gold-deep);
  color: var(--on-gold);
  font-size: var(--fs-md);
  font-weight: 800;
  letter-spacing: .01em;
  padding: 12px 22px;
  box-shadow: var(--e2), inset 0 1px 0 rgba(255, 255, 255, .4);
}

.btn-dice:hover:not(:disabled) {
  background: linear-gradient(180deg, var(--gold-pale), var(--gold-dark));
  box-shadow: var(--e3), 0 8px 22px rgba(199, 154, 54, .35), inset 0 1px 0 rgba(255, 255, 255, .5);
}

.dice-panel { border-color: var(--gold); }

.dice-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--s3);
  margin: var(--s4) 0 var(--s2);
}

.dice-field-zip { max-width: 130px; }

.dice-optional {
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0;
  color: var(--muted);
}

/* Where the sweep actually looked. Present even when a category gave up nothing — a roll
   that reports "no plays" without saying where it dug can't be judged. */
.dice-niche-chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin: var(--s3) 0;
}

.dice-chip {
  display: inline-flex;
  flex-direction: column;
  gap: 1px;
  padding: 5px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--soft);
  font-size: var(--fs-xs);
  color: var(--ink-2);
}

.dice-chip-hit {
  border-color: var(--gold);
  background: var(--gold-soft);
  color: var(--gold-ink);
}

.dice-chip-probe { color: var(--muted); font-size: var(--fs-2xs); }

.dice-board-controls {
  display: flex;
  align-items: center;
  gap: var(--s4);
  flex-wrap: wrap;
  margin: var(--s3) 0 var(--s2);
  font-size: var(--fs-sm);
  color: var(--ink-2);
}
/* Same reason as .fb-arb-controls select: a toolbar control, not a stacked form field. */
.dice-board-controls select {
  width: auto;
  flex: 0 0 auto;
  padding: 4px 8px;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
}

.dice-board { display: flex; flex-direction: column; gap: var(--s3); }

.dice-play {
  display: flex;
  gap: var(--s4);
  padding: var(--s4);
  border: 1px solid var(--line);
  border-left: 4px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--card);
  box-shadow: var(--e1);
}

.dice-play-jackpot { border-left-color: var(--gold); background: linear-gradient(90deg, var(--gold-soft), var(--card) 55%); }
.dice-play-strong  { border-left-color: var(--success); }
.dice-play-target  { border-left-color: var(--info); }
.dice-play-thin    { border-left-color: var(--warning); }
.dice-play-watch   { border-left-color: var(--line-strong); }
.dice-play-pass    { border-left-color: var(--line-strong); opacity: .72; }

.dice-play-rank {
  flex: 0 0 24px;
  font-size: var(--fs-lg);
  font-weight: 800;
  color: var(--muted);
  text-align: right;
}

.dice-play-thumb {
  flex: 0 0 84px;
  width: 84px;
  height: 84px;
  object-fit: contain;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  background: var(--soft);
}

.dice-play-thumb-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--fs-3xl);
}

.dice-play-body { flex: 1 1 auto; min-width: 0; }

.dice-play-head {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
}

.dice-tier {
  font-size: var(--fs-2xs);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  padding: 2px 8px;
  border-radius: var(--r-pill);
  background: var(--soft-2);
  color: var(--muted);
}

.dice-tier-jackpot { background: var(--gold); color: var(--on-gold); }
.dice-tier-strong  { background: var(--success-soft); color: var(--success); border: 1px solid var(--success-line); }
.dice-tier-target  { background: var(--info-soft); color: var(--info); border: 1px solid var(--info-line); }
.dice-tier-thin    { background: var(--warning-soft); color: var(--warning); border: 1px solid var(--warning-line); }

.dice-play-niche { font-size: var(--fs-xs); color: var(--muted); }

.dice-play-title {
  margin: var(--s2) 0 var(--s1);
  font-size: var(--fs-md);
  line-height: 1.35;
  color: var(--ink);
}

.dice-play-money {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  flex-wrap: wrap;
}

.dice-play-profit { font-size: var(--fs-2xl); font-weight: 800; letter-spacing: -.01em; }
.dice-play-profit.good { color: var(--success); }
.dice-play-profit.bad  { color: var(--danger); }
.dice-play-profit-note { font-size: var(--fs-sm); color: var(--ink-2); }

.dice-play-facts,
.dice-play-note,
.dice-play-evidence,
.dice-play-where {
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--ink-2);
}

.dice-play-facts { margin-top: var(--s1); }
.dice-play-note { color: var(--ink); }
.dice-play-evidence { color: var(--muted); }
.dice-play-where { margin-top: var(--s1); font-weight: 600; color: var(--ink-2); }

.dice-sources {
  display: flex;
  flex-direction: column;
  gap: var(--s1);
  margin-top: var(--s2);
  padding-top: var(--s2);
  border-top: 1px solid var(--line-soft);
}

.dice-source {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex-wrap: wrap;
  font-size: var(--fs-sm);
}

.dice-source-title {
  flex: 1 1 220px;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dice-source-buy { font-weight: 700; color: var(--ink); }
.dice-source-net { font-weight: 700; }
.dice-source-net.good { color: var(--success); }
.dice-source-net.bad  { color: var(--danger); }
.dice-source-meta { color: var(--muted); font-size: var(--fs-xs); }

.dice-play-actions {
  display: flex;
  gap: var(--s2);
  flex-wrap: wrap;
  margin-top: var(--s3);
  align-items: center;
}

/* Already handed to Deal Radar. Reads as a state, not as a button that failed — it is the only
   thing on this board that survives the next roll. */
.dice-watch-btn.is-watching {
  opacity: 1;
  cursor: default;
  color: var(--ok);
  border-color: currentColor;
}

/* Why a row can't be kept, shown only where keeping it was the one thing left to do. */
.dice-watch-refusal {
  font-size: 12px;
  color: var(--muted);
  align-self: center;
}

/* eBay joins Craigslist and Facebook as a place supply comes from, so it needs the badge
   colour the other two already have. */
.local-badge-ebay { background: var(--src-ebay); }

@media (max-width: 900px) {
  .dice-play { flex-wrap: wrap; }
  .dice-play-thumb { flex-basis: 64px; width: 64px; height: 64px; }
}

/* ── Liquidation Lot / Manifest Analyzer ──────────────────────────────────
   Reuses the inventory-health tiles, table, warning bar and footnote
   wholesale — it is the same money model pointed at a pallet instead of a
   shelf, and it should read like it. What lives here is only what this
   screen genuinely adds: the manifest input, the cost/recovery controls,
   the verdict banner and the value-concentration callout. */

.lot-input-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.35fr) minmax(280px, 1fr);
  gap: var(--s5);
  margin-top: var(--s4);
}

.lot-manifest-col { display: flex; flex-direction: column; gap: var(--s3); min-width: 0; }
.lot-cost-col { display: flex; flex-direction: column; gap: var(--s4); min-width: 0; }

.lot-label {
  font-size: var(--fs-2xs);
  letter-spacing: .04em;
  text-transform: uppercase;
  font-weight: 800;
  color: var(--muted);
}

.lot-manifest {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--card);
  color: var(--ink);
  /* Manifests are columns. A monospace box makes a misread column obvious
     to the eye before the analysis has to report it. */
  font-family: var(--font-mono);
  font-size: var(--fs-sm);
  line-height: 1.5;
  resize: vertical;
}

.lot-manifest:focus { outline: none; border-color: var(--teal-600); box-shadow: 0 0 0 3px rgba(26,107,115,0.12); }

.lot-manifest-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
}

.lot-source-note { font-size: var(--fs-xs); color: var(--muted); }

.lot-drop-zone { padding: 18px 16px; }

.lot-fieldset {
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  padding: var(--s4);
  background: var(--soft);
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}

.lot-fieldset-title {
  font-size: var(--fs-2xs);
  letter-spacing: .05em;
  text-transform: uppercase;
  font-weight: 800;
  color: var(--brand-ink-2);
}

.lot-assumption-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s3); }

.lot-grade-note { font-size: var(--fs-2xs); color: var(--muted); line-height: 1.45; }

.lot-cost-line {
  margin-top: 2px;
  padding-top: var(--s3);
  border-top: 1px dashed var(--line-strong);
  font-size: var(--fs-sm);
  color: var(--ink);
}
.lot-cost-parts { color: var(--muted); font-size: var(--fs-xs); }

.lot-run-row { margin-top: var(--s4); }

/* The verdict. One line, unmissable, with the two prices worth walking into
   a negotiation with sitting under it. */
.lot-verdict {
  margin: var(--s4) 0;
  padding: var(--s4) var(--s5);
  border: 1px solid var(--line);
  border-left-width: 5px;
  border-radius: var(--r-md);
  background: var(--card);
}

.lot-v-buy    { border-left-color: var(--success); background: var(--success-tint); }
.lot-v-below  { border-left-color: var(--gold); background: var(--gold-soft); }
.lot-v-thin   { border-left-color: var(--warning); background: var(--warning-tint); }
.lot-v-skip   { border-left-color: var(--danger); background: var(--danger-tint); }
.lot-v-nodata { border-left-color: var(--line-strong); background: var(--soft); }

.lot-verdict-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
}

.lot-verdict-badge { font-size: var(--fs-xl); font-weight: 900; letter-spacing: .01em; color: var(--ink); }
.lot-verdict-net { font-size: var(--fs-xl); font-weight: 800; color: var(--ink-2); }
.lot-verdict-note { margin-top: 6px; font-size: var(--fs-sm); line-height: 1.55; color: var(--ink); }

.lot-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: var(--s3); }

.lot-chip {
  font-size: var(--fs-xs);
  padding: 3px 10px;
  border-radius: var(--r-pill);
  background: var(--card);
  border: 1px solid var(--line-strong);
  color: var(--ink-2);
  white-space: nowrap;
}
.lot-chip-strong { border-color: var(--gold); background: var(--gold-soft); color: var(--gold-ink); font-weight: 700; }

/* Which few lines carry the lot — the items to physically check before paying. */
.lot-concentration {
  margin: var(--s4) 0;
  padding: var(--s4) var(--s5);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--soft);
}

.lot-conc-head { font-size: var(--fs-base); font-weight: 800; color: var(--ink); }
.lot-conc-warn { margin-top: 6px; font-size: var(--fs-sm); line-height: 1.5; color: var(--gold-ink); font-weight: 600; }

.lot-conc-list { margin: var(--s3) 0 0; padding-left: 20px; display: flex; flex-direction: column; gap: 4px; }
.lot-conc-list li { font-size: var(--fs-sm); color: var(--ink); display: flex; justify-content: space-between; gap: var(--s3); }
.lot-conc-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.lot-conc-share { color: var(--muted); white-space: nowrap; font-variant-numeric: tabular-nums; }
.lot-conc-foot { margin-top: var(--s3); font-size: var(--fs-xs); color: var(--muted); line-height: 1.45; }

/* Table: the value-carrying rows get a gold edge, because those are the ones
   a buyer has to inspect. */
.lot-table td { vertical-align: top; }
.lot-row-key { background: rgba(199,154,54,0.05); }
.lot-row-key td:first-child { box-shadow: inset 3px 0 0 var(--gold); }

.lot-key-flag {
  font-size: var(--fs-3xs);
  font-weight: 800;
  letter-spacing: .04em;
  text-transform: uppercase;
  padding: 1px 6px;
  border-radius: var(--r-pill);
  background: var(--gold-soft);
  color: var(--gold-ink);
  white-space: nowrap;
}

.lot-s-priced   { background: var(--success-soft); color: var(--success); }
.lot-s-thin     { background: var(--warning-soft); color: var(--warning); }
.lot-s-excluded { background: var(--accent-soft); color: var(--accent); }
.lot-s-nodata   { background: var(--soft); color: var(--muted); }

@media (max-width: 1000px) {
  .lot-input-grid { grid-template-columns: 1fr; }
}

/* ── Take-home panel ──────────────────────────────────────────────────────
   Sits directly under the price field in both editors. Deliberately the
   heaviest thing in the Pricing panel: the asking price is an input, but the
   take-home is the answer, and the old form gave the input all the weight. */
.th-panel {
  margin-top: var(--s3);
  padding: var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--soft);
}

.th-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  margin-bottom: var(--s3);
}

.th-title {
  font-size: var(--fs-sm);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink-2);
}

.th-fees-btn {
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink-2);
  font-size: var(--fs-xs);
  font-weight: 600;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  cursor: pointer;
}
.th-fees-btn:hover { border-color: var(--line-strong); color: var(--ink); }

.th-inputs {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s3);
  margin-bottom: var(--s3);
}

.th-input { display: flex; flex-direction: column; gap: 3px; }
.th-input > span { font-size: var(--fs-xs); color: var(--muted); }
.th-input > input {
  width: 100%;
  padding: 6px 8px;
  border: 1px solid var(--line);
  border-radius: var(--r-xs);
  font-variant-numeric: tabular-nums;
}

/* The verdict carries the colour, so a price that loses money can never be
   rendered in the same style as one that makes money. */
.th-verdict {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: var(--s3);
  border: 1px solid var(--line);
  border-left-width: 3px;
  border-radius: var(--r-sm);
  background: var(--card);
}

.th-headline { font-size: var(--fs-lg); font-weight: 700; color: var(--ink); }
.th-note { font-size: var(--fs-sm); color: var(--muted); line-height: 1.5; }

.th-profitable    { border-left-color: var(--success); background: var(--success-soft); }
.th-profitable .th-headline { color: var(--success); }
.th-thin          { border-left-color: var(--warning); background: var(--warning-soft); }
.th-thin .th-headline { color: var(--warning); }
.th-below_floor   { border-left-color: var(--warning); background: var(--warning-soft); }
.th-below_floor .th-headline { color: var(--warning); }
.th-loss          { border-left-color: var(--danger); background: var(--danger-soft); }
.th-loss .th-headline { color: var(--danger); }
.th-error         { border-left-color: var(--danger); }
.th-error .th-headline { color: var(--danger); }
.th-no_cost_basis { border-left-color: var(--info); background: var(--info-soft); }
.th-no_cost_basis .th-headline { color: var(--info); }
.th-idle, .th-no_price { border-left-color: var(--line-strong); }
.th-idle .th-headline, .th-no_price .th-headline { color: var(--muted); }

.th-floors {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s2);
  margin-top: var(--s2);
}

.th-floor {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--card);
}
.th-floor > span { font-size: var(--fs-xs); color: var(--muted); text-transform: uppercase; letter-spacing: .04em; font-weight: 700; }
.th-floor > strong { font-size: var(--fs-lg); font-variant-numeric: tabular-nums; color: var(--ink); }
.th-floor > em { font-size: var(--fs-xs); color: var(--muted); font-style: normal; line-height: 1.45; }

/* The floor is the actionable one of the two — it is what a Best Offer gets
   judged against, so it gets the gold edge and the button. */
.th-floor-min { border-color: var(--gold-light); background: var(--gold-soft); }
.th-floor-min > strong { color: var(--gold-ink); }

.th-floor-btn {
  align-self: flex-start;
  margin-top: var(--s1);
  border: 1px solid var(--gold-light);
  background: var(--card);
  color: var(--gold-ink);
  font-size: var(--fs-xs);
  font-weight: 700;
  padding: 3px 10px;
  border-radius: var(--r-pill);
  cursor: pointer;
}
.th-floor-btn:hover { background: var(--gold-soft); }

.th-total {
  margin-top: var(--s2);
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}

.th-lines { margin-top: var(--s2); }
.th-lines > summary {
  cursor: pointer;
  font-size: var(--fs-sm);
  color: var(--ink-2);
  padding: var(--s1) 0;
}
.th-lines > summary:hover { color: var(--ink); }

.th-line {
  display: flex;
  justify-content: space-between;
  gap: var(--s3);
  padding: 3px 0;
  font-size: var(--fs-sm);
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
/* A fee the seller has not configured still gets a row — a missing line reads
   as "handled", a $0.00 line reads as "a knob you haven't turned". */
.th-line-zero { color: var(--muted); opacity: .6; }
.th-line-gross { font-weight: 700; color: var(--ink); border-bottom: 1px solid var(--line-soft); padding-bottom: var(--s2); margin-bottom: var(--s1); }
.th-line-total { font-weight: 700; color: var(--ink); border-top: 1px solid var(--line-soft); padding-top: var(--s2); margin-top: var(--s1); }
.th-line-cost { color: var(--ink-2); }
.th-line-net { font-weight: 800; color: var(--ink); border-top: 1px solid var(--line); padding-top: var(--s2); margin-top: var(--s1); }

/* Fees & Costs settings summary — restates the numbers as a sentence, because
   eleven form fields do not add up to a feeling about what they cost. */
.fees-floor-head {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink);
  margin-top: var(--s2);
}

.fees-summary {
  margin-top: var(--s3);
  padding: var(--s3);
  border: 1px solid var(--info-line);
  border-radius: var(--r-sm);
  background: var(--info-soft);
  font-size: var(--fs-sm);
  color: var(--ink-2);
  line-height: 1.55;
}
.fees-summary strong { color: var(--ink); font-variant-numeric: tabular-nums; }

/* Inventory Health: the floor under the break-even in the same cell. */
.inv-th-sub { font-size: var(--fs-2xs); font-weight: 600; color: var(--muted); text-transform: none; letter-spacing: 0; }
.inv-floor { color: var(--gold-ink); font-weight: 700; }

@media (max-width: 900px) {
  .th-inputs, .th-floors { grid-template-columns: 1fr; }
}

/* ── Ad Rate Advisor ──────────────────────────────────────────────────────
   Reuses the Inventory Health table, tiles and verdict pills wholesale — this
   is the same board seen from a different angle, and it should read as the
   same screen. Only the things that are genuinely new to promoted listings
   get their own rules: the rate verdicts, the tradeoff ladder, and the strip
   under the price field in the editor. */

.ad-v-under { background: var(--gold-soft); color: var(--gold-ink); }
.ad-v-over  { background: var(--danger-soft); color: var(--danger); }
.ad-v-ok    { background: var(--success-soft); color: var(--success); }
.ad-v-none  { background: var(--soft); color: var(--ink-2); }
.ad-v-price { background: var(--warning-soft); color: var(--warning); }

/* A rate coming down is money kept, not a loss — so it reads as the gold
   "act on this" colour rather than the red one used for a shrinking margin. */
.ad-rate-down { color: var(--gold-ink); }
.ad-lift-bad  { color: var(--danger); font-weight: 700; }

/* ── The tradeoff ladder ───────────────────────────────────────────────── */
.ad-ladder-box { width: min(860px, 100%); padding: 22px; }

.ad-ladder-table { margin-top: 4px; font-size: var(--fs-sm); }
.ad-ladder-table td, .ad-ladder-table th { padding: 6px 10px; }

/* The winning rung carries the emphasis; the seller's current rung carries an
   outline, so "where you are" and "where to go" are never the same signal. */
.ad-rung-best { background: var(--success-soft); }
.ad-rung-best td { font-weight: 700; }
.ad-rung-current td:first-child { box-shadow: inset 3px 0 0 var(--gold); }
.ad-rung-over td { color: var(--muted); }

.ad-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: var(--r-pill);
  background: var(--soft-2);
  color: var(--ink-2);
  font-size: var(--fs-3xs);
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
}
.ad-tag-best { background: var(--success); color: var(--on-solid); }

.ad-assumptions {
  margin: 14px 0 0;
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.6;
}

/* ── Buy-side negotiation ──────────────────────────────────────────────────
   The cell on the local board, and the modal behind it. Colour carries one
   meaning only: gold is "act on this number", red is "do not pay this". A row
   we would walk away from is deliberately the dullest thing on the table —
   the point of the walk badge is that nobody clicks it. */
.fb-arb-offer { white-space: nowrap; }

.neg-offer {
  display: block;
  font-weight: 700;
  color: var(--gold-ink);
}
.neg-upside { display: block; font-size: var(--fs-3xs); color: var(--success); }

.neg-badge {
  display: inline-block;
  margin-top: 3px;
  padding: 1px 7px;
  border-radius: var(--r-pill);
  font-size: var(--fs-3xs);
  font-weight: 700;
  letter-spacing: .02em;
  text-transform: uppercase;
  cursor: help;
}
.neg-buynow   { background: var(--success-soft); color: var(--success); }
.neg-haggle   { background: var(--gold-soft); color: var(--gold-ink); }
.neg-must     { background: var(--warning-soft); color: var(--warning); }
.neg-longshot { background: var(--soft-2); color: var(--ink-2); }
.neg-walk     { background: var(--danger-soft); color: var(--danger); }
.neg-nodata   { background: var(--soft-2); color: var(--muted); }

.fb-arb-neg-btn { display: block; margin-top: 5px; }

/* ── The modal ─────────────────────────────────────────────────────────── */
.neg-box {
  width: min(760px, 100%);
  padding: 22px;
  max-height: 88vh;
  overflow-y: auto;
}

.neg-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
}
.neg-head h3 { margin: 2px 0 0; }

.neg-headline {
  margin: 12px 0 16px;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  border-left: 3px solid var(--line-strong);
  background: var(--soft-2);
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: var(--ink-2);
}
.neg-headline-buynow { border-left-color: var(--success); background: var(--success-soft); }
.neg-headline-haggle { border-left-color: var(--gold); }
.neg-headline-must   { border-left-color: var(--warning); background: var(--warning-soft); }
.neg-headline-walk   { border-left-color: var(--danger); background: var(--danger-soft); }

/* The four numbers in the order they get used: open here, stop here, never past here. */
.neg-numbers {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
  gap: 10px;
  margin-bottom: 16px;
}
.neg-tile {
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--surface);
}
.neg-tile-open { border-color: var(--gold); box-shadow: inset 0 0 0 1px var(--gold); }
.neg-tile-stop { border-style: dashed; }
.neg-tile-label {
  display: block;
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--muted);
}
.neg-tile-value { display: block; font-size: var(--fs-xl); line-height: 1.3; }
.neg-tile-open .neg-tile-value { color: var(--gold-ink); }
.neg-tile-stop .neg-tile-value { color: var(--ink-2); }
.neg-tile-sub { display: block; font-size: var(--fs-2xs); color: var(--muted); line-height: 1.4; }

.neg-signals-head {
  font-size: var(--fs-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--muted);
  margin: 16px 0 6px;
}
.neg-signals ul { margin: 0; padding-left: 18px; }
.neg-signals li { font-size: var(--fs-xs); line-height: 1.6; color: var(--ink-2); }

.neg-ladder-table { font-size: var(--fs-sm); }
.neg-ladder-table td, .neg-ladder-table th { padding: 6px 10px; }
.neg-rung-great td { background: var(--success-soft); }
.neg-rung-loss td  { color: var(--muted); background: var(--danger-soft); }
.neg-rung-net { font-weight: 700; }
.neg-rung-great .neg-rung-net { color: var(--success); }
.neg-rung-loss .neg-rung-net  { color: var(--danger); }
.neg-rung-label { font-size: var(--fs-2xs); color: var(--muted); }

.neg-tone {
  display: inline-block;
  padding: 1px 6px;
  border-radius: var(--r-pill);
  font-size: var(--fs-3xs);
  font-weight: 700;
  text-transform: uppercase;
}
.neg-tone-great { background: var(--success); color: var(--on-solid); }
.neg-tone-good  { background: var(--success-soft); color: var(--success); }
.neg-tone-thin  { background: var(--warning-soft); color: var(--warning); }
.neg-tone-loss  { background: var(--danger); color: var(--on-solid); }

.neg-tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border-radius: var(--r-pill);
  background: var(--soft-2);
  color: var(--ink-2);
  font-size: var(--fs-3xs);
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
}

/* Drafts are editable in place: a message that reads like a form letter gets
   treated like one, so the box invites a rewrite rather than presenting a
   finished artefact. Copy takes whatever is in the box at the time. */
.neg-message {
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 10px 12px;
  margin-bottom: 10px;
  background: var(--surface);
}
.neg-message-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-bottom: 6px;
}
.neg-message-when { display: block; font-size: var(--fs-2xs); color: var(--muted); }
.neg-message-text {
  width: 100%;
  font: inherit;
  font-size: var(--fs-sm);
  line-height: 1.6;
  padding: 8px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--ink);
  resize: vertical;
}

.neg-nomessage {
  margin: 8px 0 0;
  padding: 10px 12px;
  border-radius: var(--r-sm);
  background: var(--danger-soft);
  color: var(--danger);
  font-size: var(--fs-sm);
  line-height: 1.6;
}

.neg-evidence {
  margin: 14px 0 0;
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.6;
}

/* ── The strip under the price field in both editors ───────────────────── */
.th-ads { margin-top: var(--s2); }

.th-ads-idle {
  padding: var(--s3);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-sm);
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.5;
}

.th-ads-row {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s3);
  flex-wrap: wrap;
  padding: var(--s3);
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--card);
}

.th-ads-on  { border-left-color: var(--gold); background: var(--gold-soft); }
.th-ads-off { border-left-color: var(--line-strong); }

.th-ads-headline { display: block; font-size: var(--fs-sm); color: var(--ink); }
.th-ads-on .th-ads-headline { color: var(--gold-ink); }
.th-ads-note { display: block; margin-top: 2px; font-size: var(--fs-xs); color: var(--ink-2); line-height: 1.5; }

.th-ads-meta {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 2px;
  font-size: var(--fs-xs);
  color: var(--muted);
  white-space: nowrap;
}
.th-ads-ceiling { font-weight: 700; color: var(--danger); }

@media (max-width: 900px) {
  .th-ads-meta { align-items: flex-start; }
}

/* ── Rising-Demand / Price-Trend Radar ────────────────────────────────────────
   Colour carries meaning here and nothing else: green is a measured rise, gold is
   an early or squeezed one, grey is flat. The tentative pill is deliberately plain
   rather than alarming — it qualifies a number, it doesn't condemn it. */

.tr-table { table-layout: auto; }

.tr-row-buy { background: linear-gradient(90deg, var(--success-soft) 0%, transparent 55%); }

.tr-product { max-width: 340px; }

.tr-name {
  font-weight: 700;
  font-size: var(--fs-sm);
  color: var(--ink);
  line-height: 1.35;
}

.tr-sub {
  margin-top: 2px;
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.45;
}

.tr-note {
  margin-top: 5px;
  font-size: var(--fs-xs);
  color: var(--ink-2);
  line-height: 1.5;
}

/* The raw measurement sentence. Present on every row, quieter than the verdict — it
   is the working, and the working should be available without competing with the answer. */
.tr-note-quiet { color: var(--muted); }

.tr-strong { font-weight: 700; color: var(--ink); }
.tr-up     { color: var(--success); }

.tr-trend { min-width: 150px; }

.tr-signal {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  font-size: var(--fs-2xs);
  font-weight: 700;
  white-space: nowrap;
}

.tr-s-up      { background: var(--success-soft); color: var(--success); }
.tr-s-early   { background: var(--gold-soft);    color: var(--gold-ink); }
.tr-s-squeeze { background: var(--warning-soft);             color: var(--warning); }
.tr-s-flat    { background: var(--soft);         color: var(--muted); }
.tr-s-down    { background: var(--danger-soft);  color: var(--danger); }

.tr-tentative {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 6px;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-pill);
  font-size: var(--fs-3xs);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--muted);
  cursor: help;
}

/* Marks, tones and the gap logic now come from the shared .viz-spark component
   at the bottom of this file — this only places it in the cell. The tone
   classes are .viz-spark-up / -early / -squeeze / -down / -flat, still named
   to match the signal badges above so the line and the label beside it can
   never disagree about which way this product is going. */
.tr-spark {
  display: block;
  margin: 6px 0 2px;
  overflow: visible;
}

.tr-spark-empty {
  margin: 6px 0 2px;
  font-size: var(--fs-xs);
  color: var(--muted);
  font-style: italic;
}

.tr-verdict {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  font-size: var(--fs-2xs);
  font-weight: 700;
  white-space: nowrap;
}

.tr-v-buy   { background: var(--success-soft); color: var(--success); }
.tr-v-early { background: var(--gold-soft);    color: var(--gold-ink); }
.tr-v-watch { background: var(--soft);         color: var(--ink-2); }
.tr-v-thin  { background: var(--warning-soft);             color: var(--warning); }
.tr-v-pass  { background: var(--soft);         color: var(--muted); }

.tr-hunt-btn { display: block; margin-top: 6px; }

@media (max-width: 1100px) {
  .tr-product { max-width: none; }
  .tr-note-quiet { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   Money Made — the earnings tracker
   The only screen that reports money already banked, so it is the only one
   that gets a hero figure. Everything below it is context for that number,
   and sized accordingly.
   ═══════════════════════════════════════════════════════════════════════ */

.er-hero {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s7);
  flex-wrap: wrap;
  margin-bottom: var(--s5);
  padding: var(--s6) var(--s7);
  border-radius: var(--r-lg);
  border: 1px solid var(--teal-700);
  background:
    radial-gradient(1000px 220px at 90% -50%, rgba(224, 187, 99, .30), transparent 62%),
    linear-gradient(135deg, var(--teal-900), var(--teal-700));
  box-shadow: var(--e3);
  color: #fff;
}

.er-hero-label {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--gold-light);
}

/* The hero figure. Proportional figures, not tabular — a standalone number at
   this size looks loose when every digit is forced to the width of a zero. */
.er-hero-figure {
  margin: var(--s2) 0 var(--s1);
  font-size: var(--fs-display);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.02em;
  color: #fff;
}

.er-hero-figure.er-negative { color: var(--pale-neg); }

.er-hero-sub {
  margin: 0;
  font-size: var(--fs-base);
  color: rgba(255, 255, 255, .84);
}

.er-hero-side {
  display: flex;
  gap: var(--s6);
  flex-wrap: wrap;
}

.er-hero-stat { display: flex; flex-direction: column; gap: var(--s1); }

.er-hero-stat-label {
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: rgba(255, 255, 255, .62);
}

.er-hero-stat-value {
  font-size: var(--fs-lg);
  font-weight: 700;
  color: #fff;
}

/* ── Stat tiles ─────────────────────────────────────────────────────────── */

.er-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
  gap: var(--s3);
  margin-bottom: var(--s5);
}

.er-stat {
  display: flex;
  flex-direction: column;
  gap: var(--s1);
  padding: var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
}

.er-stat-label {
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--muted);
}

.er-stat-value { font-size: var(--fs-xl); font-weight: 700; color: var(--ink); }
.er-stat-note  { font-size: var(--fs-xs); color: var(--ink-2); line-height: 1.45; }

/* Loss is red wherever it appears — but it is always beside a signed number
   carrying a minus sign, never colour alone. Spelled out per component rather
   than left to source order: .er-row-value and .er-table .num are both defined
   further down and would otherwise repaint a loss green. */
.er-negative,
.er-stat-value.er-negative,
.er-row-value.er-negative,
.er-log-preview.er-negative,
.er-table .num.er-negative { color: var(--danger); }

/* ── Money waiting on a cost ────────────────────────────────────────────── */
/* Gold, not red: this is money the seller has already earned and hasn't proved,
   which is an opportunity, not an error. */

.er-awaiting {
  margin-bottom: var(--s5);
  padding: var(--s5);
  border: 1px solid var(--gold);
  border-left: 4px solid var(--gold);
  border-radius: var(--r-md);
  background: var(--gold-soft);
}

.er-awaiting-title {
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--ink);
}

.er-awaiting-sub {
  margin: var(--s1) 0 0;
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: var(--ink-2);
}

.er-awaiting-list {
  margin-top: var(--s4);
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  max-height: 340px;
  overflow-y: auto;
}

.er-awaiting-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
  padding: var(--s3);
  border: 1px solid var(--gold-light);
  border-radius: var(--r-sm);
  background: var(--card);
}

.er-cost-entry {
  display: flex;
  align-items: center;
  gap: var(--s2);
}

.er-cost-label {
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

.er-cost-input {
  width: 110px;
  padding: 6px var(--s2);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-xs);
  font-size: var(--fs-base);
  font-variant-numeric: tabular-nums;
}

/* ── Chart ──────────────────────────────────────────────────────────────── */

.er-chart-card {
  margin-bottom: var(--s5);
  padding: var(--s5);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
}

.er-chart-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s4);
  margin-bottom: var(--s4);
}

.er-chart-head h3 { margin: var(--s1) 0 0; font-size: var(--fs-lg); color: var(--ink); }

.er-chart-svg { width: 100%; height: auto; display: block; }

/* Recessive: hairline, solid, one step off the surface. Gridlines are scaffolding. */
.er-grid { stroke: var(--line-soft); stroke-width: 1; }
.er-zero { stroke: var(--line-strong); stroke-width: 1; }

.er-bar      { fill: var(--success); }
.er-bar-loss { fill: var(--danger); }

/* The hit target is the whole band, not the 24px bar — a thin column is far too
   small to hover reliably. */
.er-bar-hit { fill: transparent; }
.er-bar-group:hover .er-bar-hit { fill: var(--soft-2); }

.er-axis {
  font-size: var(--fs-2xs);
  fill: var(--muted);
  font-variant-numeric: tabular-nums;
}

.er-axis-x    { text-anchor: middle; }
.er-axis-y    { text-anchor: end; }
.er-axis-year { fill: var(--line-strong); }

/* Text never wears the data colour — the bar beside it carries the identity. */
.er-bar-label {
  font-size: var(--fs-xs);
  font-weight: 700;
  fill: var(--ink);
  text-anchor: middle;
}

.er-chart-table { margin-top: var(--s4); }

.er-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-sm);
}

.er-table th,
.er-table td {
  padding: var(--s2) var(--s3);
  border-bottom: 1px solid var(--line-soft);
  text-align: left;
}

.er-table th {
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--muted);
}

/* Tabular figures here and only here: columns of numbers have to line up. */
.er-table .num { text-align: right; font-variant-numeric: tabular-nums; }

.er-chip {
  display: inline-block;
  padding: 1px 6px;
  border-radius: var(--r-pill);
  background: var(--soft-2);
  font-size: var(--fs-2xs);
  color: var(--muted);
}

/* ── Ledger ─────────────────────────────────────────────────────────────── */

.er-ledger { max-height: 520px; overflow: auto; }

.er-ledger thead th {
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--card);
}

.er-cell-item {
  max-width: 320px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.er-muted { color: var(--muted); font-weight: 400; }

/* A refunded or cancelled sale stays visible — dropping it would leave a gap
   the seller can't reconcile — but reads as struck from the total. */
.er-row-void td { opacity: .55; }

.er-flip-delete {
  border: 0;
  background: none;
  cursor: pointer;
  padding: 2px 6px;
  border-radius: var(--r-xs);
  color: var(--muted);
  font-size: var(--fs-sm);
}

.er-flip-delete:hover { background: var(--danger-soft); color: var(--danger); }

/* ── Leaderboards ───────────────────────────────────────────────────────── */

.er-columns {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s4);
  margin-bottom: var(--s5);
}

.er-column {
  padding: var(--s5);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
}

.er-col-title { margin: 0 0 var(--s3); font-size: var(--fs-md); color: var(--ink); }

.er-list { display: flex; flex-direction: column; gap: var(--s1); }

.er-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  min-width: 0;
  padding: var(--s2) 0;
  border-bottom: 1px solid var(--line-soft);
}

.er-row:last-child { border-bottom: 0; }

.er-row-main {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  flex: 1;
}

.er-row-title {
  font-size: var(--fs-base);
  font-weight: 600;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.er-row-meta { font-size: var(--fs-xs); color: var(--muted); }

.er-row-value {
  font-size: var(--fs-md);
  font-weight: 700;
  color: var(--success);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

.er-empty { margin: 0; font-size: var(--fs-sm); color: var(--muted); line-height: 1.55; }

.er-notice-good {
  border-color: var(--success-line);
  background: var(--success-soft);
  color: var(--success);
}

/* ── Log-a-flip modal ───────────────────────────────────────────────────── */

.er-modal {
  position: fixed;
  inset: 0;
  z-index: 900;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--s5);
  background: rgba(4, 23, 26, .55);
  backdrop-filter: blur(3px);
}

.er-modal-card {
  width: min(720px, 100%);
  max-height: 90vh;
  display: flex;
  flex-direction: column;
  border-radius: var(--r-lg);
  background: var(--card);
  box-shadow: var(--e4);
  overflow: hidden;
}

.er-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  padding: var(--s5) var(--s6);
  border-bottom: 1px solid var(--line);
}

.er-modal-head h3 { margin: 0; font-size: var(--fs-lg); color: var(--ink); }

.er-modal-body { padding: var(--s5) var(--s6); overflow-y: auto; }

.er-modal-hint {
  margin: 0 0 var(--s4);
  font-size: var(--fs-sm);
  line-height: 1.55;
  color: var(--ink-2);
}

.er-form-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--s3);
}

.er-field-wide { grid-column: 1 / -1; }

.er-log-preview {
  margin: var(--s4) 0 0;
  padding: var(--s3) var(--s4);
  border-radius: var(--r-sm);
  background: var(--soft);
  font-size: var(--fs-base);
  font-weight: 600;
  color: var(--ink);
}

.er-log-error {
  margin: var(--s3) 0 0;
  padding: var(--s3) var(--s4);
  border: 1px solid var(--danger-line);
  border-radius: var(--r-sm);
  background: var(--danger-soft);
  font-size: var(--fs-sm);
  color: var(--danger);
}

.er-modal-foot {
  display: flex;
  justify-content: flex-end;
  gap: var(--s2);
  padding: var(--s4) var(--s6);
  border-top: 1px solid var(--line);
  background: var(--soft);
}

/* ── Dashboard band ─────────────────────────────────────────────────────── */

.dash-earnings {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s6);
  flex-wrap: wrap;
  margin-bottom: var(--s5);
  padding: var(--s5) var(--s7);
  border-radius: var(--r-lg);
  border: 1px solid var(--success-line);
  background: linear-gradient(135deg, var(--success-soft), var(--card));
  box-shadow: var(--e2);
}

.dash-earnings-figure {
  margin: var(--s1) 0;
  font-size: var(--fs-5xl);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.02em;
  color: var(--success);
}

.dash-earnings-figure.er-negative { color: var(--danger); }

.dash-earnings-sub { margin: 0; font-size: var(--fs-sm); color: var(--ink-2); }

.dash-earnings-side {
  display: flex;
  align-items: center;
  gap: var(--s5);
}

.dash-earnings-spark { width: 180px; }
.er-spark-svg { width: 100%; height: auto; display: block; }

/* De-emphasised history, the current month in the accent — the stat-tile idiom. */
.er-spark-bar  { fill: var(--success-line); }
.er-spark-now  { fill: var(--success); }
.er-spark-loss { fill: var(--danger-line); }

@media (max-width: 900px) {
  .er-columns { grid-template-columns: 1fr; }
  .er-hero-figure { font-size: var(--fs-5xl); }
  .er-hero-side { gap: var(--s5); }
  .dash-earnings-spark { display: none; }
  .er-cost-entry { width: 100%; }
  .er-cost-input { flex: 1; width: auto; }
}

/* ═══════════════════════════════════════════════════════════════════════
   Deal Pipeline — Sourced → Bought → Listed → Sold
   The colour system carries the one distinction the whole feature rests on:
   PROJECTED money is gold (a forecast, warm, unproven) and REALIZED money is
   green (banked — the same green Money Made uses). They never share a colour,
   never share a row, and are never summed. Urgency is amber/red and belongs
   to actions only: a large number is not an emergency, a stuck one is.
   ═══════════════════════════════════════════════════════════════════════ */

.dp-hero {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s7);
  flex-wrap: wrap;
  margin-bottom: var(--s5);
  padding: var(--s6) var(--s7);
  border-radius: var(--r-lg);
  border: 1px solid var(--teal-700);
  background:
    radial-gradient(1000px 220px at 90% -50%, rgba(224, 187, 99, .30), transparent 62%),
    linear-gradient(135deg, var(--teal-900), var(--teal-700));
  box-shadow: var(--e3);
  color: #fff;
}

.dp-hero-label {
  margin: 0;
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--gold-light);
}

.dp-hero-figure {
  margin: var(--s2) 0 var(--s1);
  font-size: var(--fs-display);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.02em;
  color: #fff;
}

.dp-hero-sub { margin: 0; font-size: var(--fs-base); color: rgba(255, 255, 255, .84); }

.dp-hero-side { display: flex; gap: var(--s6); flex-wrap: wrap; }
.dp-hero-stat { display: flex; flex-direction: column; gap: 2px; max-width: 210px; }

.dp-hero-stat-label {
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: rgba(255, 255, 255, .62);
}

.dp-hero-stat-value { font-size: var(--fs-lg); font-weight: 700; color: #fff; }
.dp-hero-stat-note  { font-size: var(--fs-2xs); color: rgba(255, 255, 255, .55); line-height: 1.4; }

/* On the dark hero both money colours have to lift enough to read, but they
   stay recognisably gold and green so the meaning survives the surface. */
.dp-hero .dp-projected { color: var(--gold-light); }
.dp-hero .dp-realized  { color: var(--pale-pos); }
.dp-hero .dp-under     { color: var(--pale-warn); }

/* ── Do this next ───────────────────────────────────────────────────────── */

.dp-actions-card {
  margin-bottom: var(--s5);
  padding: var(--s5) var(--s6);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--card);
  box-shadow: var(--e1);
}

.dp-actions { display: flex; flex-direction: column; gap: var(--s2); margin-top: var(--s4); }

.dp-action {
  display: grid;
  grid-template-columns: 92px 1fr auto;
  align-items: center;
  gap: var(--s4);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--line);
  border-left-width: 3px;
  border-radius: var(--r-md);
  background: var(--soft);
}

/* Urgency is the left edge. A whole row washed in red makes every row shout,
   and then none of them do. */
.dp-action-normal { border-left-color: var(--line-strong); }
.dp-action-warn   { border-left-color: var(--warning); background: var(--warning-soft); }
.dp-action-urgent { border-left-color: var(--danger);  background: var(--danger-soft); }

.dp-action-money {
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  text-align: right;
}

.dp-action-text { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.dp-action-label { font-size: var(--fs-sm); font-weight: 700; color: var(--ink); }
.dp-action-detail { font-size: var(--fs-xs); color: var(--ink-2); line-height: 1.45; }
.dp-action-go { white-space: nowrap; }

/* ── The board ──────────────────────────────────────────────────────────── */

.dp-board {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--s3);
  margin-bottom: var(--s5);
  align-items: start;
}

.dp-col {
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--soft);
  padding: var(--s3);
  min-width: 0;
}

/* Only the last column is tinted — that is where money actually came back,
   and it is the direction every other column is heading. */
.dp-col-sold { border-color: var(--success-line); background: var(--success-soft); }

.dp-col-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s2);
}

.dp-col-title {
  font-size: var(--fs-xs);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--ink-2);
}

.dp-col-count {
  min-width: 22px;
  padding: 1px 7px;
  border-radius: var(--r-pill);
  background: var(--card);
  border: 1px solid var(--line);
  font-size: var(--fs-2xs);
  font-weight: 700;
  color: var(--ink);
  text-align: center;
}

.dp-col-money {
  margin: var(--s1) 0 var(--s3);
  font-size: var(--fs-xs);
  color: var(--muted);
}

.dp-col-body { display: flex; flex-direction: column; gap: var(--s2); }

.dp-col-empty {
  margin: 0;
  padding: var(--s4) var(--s3);
  font-size: var(--fs-xs);
  color: var(--muted);
  text-align: center;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
}

/* ── One card ───────────────────────────────────────────────────────────── */

.dp-card {
  padding: var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
  box-shadow: var(--e1);
}

.dp-card-warn   { box-shadow: inset 3px 0 0 var(--warning), var(--e1); }
.dp-card-urgent { box-shadow: inset 3px 0 0 var(--danger),  var(--e1); }

.dp-card-head { display: flex; align-items: flex-start; gap: var(--s2); }

.dp-card-title {
  flex: 1;
  min-width: 0;
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
  overflow-wrap: anywhere;
}

a.dp-card-title:hover { color: var(--brand-ink-2); text-decoration: underline; }

.dp-card-meta {
  margin: var(--s1) 0 var(--s2);
  font-size: var(--fs-2xs);
  color: var(--muted);
}

.dp-auto { color: var(--info); font-weight: 600; }

.dp-card-money {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--s2) 0;
  border-top: 1px solid var(--line-soft);
  border-bottom: 1px solid var(--line-soft);
}

.dp-money-row {
  display: flex;
  justify-content: space-between;
  gap: var(--s2);
  font-size: var(--fs-xs);
  color: var(--ink-2);
}

.dp-money-row b { font-variant-numeric: tabular-nums; font-weight: 700; }
.dp-money-quiet { color: var(--muted); }
.dp-money-quiet b { color: var(--muted); font-weight: 600; }

.dp-projected { color: var(--gold-ink); }
.dp-realized  { color: var(--success); }
.dp-loss      { color: var(--danger); }
.dp-under     { color: var(--warning); }

.dp-card-flag {
  margin: var(--s2) 0 0;
  font-size: var(--fs-2xs);
  color: var(--ink-2);
  line-height: 1.45;
}

.dp-card-basis {
  margin: var(--s1) 0 0;
  font-size: var(--fs-2xs);
  color: var(--muted);
  font-style: italic;
}

.dp-card-foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1);
  margin-top: var(--s3);
}

/* ── The dashboard band ─────────────────────────────────────────────────── */
/* Teal, not green: this is money OUT, and wearing the earnings band's green
   would read as profit at a glance. */
.dash-pipeline {
  border-color: var(--info-line);
  background: linear-gradient(135deg, var(--info-soft), var(--card));
}

.dash-pipeline .dash-earnings-figure { color: var(--brand-ink-2); }

.dash-pipeline-next {
  display: flex;
  flex-direction: column;
  gap: 2px;
  max-width: 260px;
}

.dash-pipeline-next-label {
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--muted);
}

.dash-pipeline-next-text { font-size: var(--fs-sm); color: var(--ink); font-weight: 600; }

/* ── The Track cell in the goldmine table ───────────────────────────────── */

.fb-arb-th-track { width: 92px; }
.fb-arb-track { white-space: nowrap; }

.fb-arb-tracked,
.fb-arb-tracked-flag {
  font-size: var(--fs-2xs);
  font-weight: 700;
  color: var(--success);
}

@media (max-width: 1200px) {
  .dp-board { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 900px) {
  .dp-board { grid-template-columns: 1fr; }
  .dp-hero-figure { font-size: var(--fs-5xl); }
  .dp-hero-side { gap: var(--s5); }
  .dp-action { grid-template-columns: 1fr; gap: var(--s2); }
  .dp-action-money { text-align: left; }
  .dash-pipeline-next { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   The Undervalued-Auction Sniper
   One thing on this board moves while you look at it, and it is the only
   thing that decides whether any of the money is still available: the
   clock. So the clock is the only element allowed to shout — it goes red
   inside the hour and grey the moment it runs out. Everything else stays
   quiet, including the profit, because a profit figure is worth nothing
   on a listing that closed forty seconds ago.
   ═══════════════════════════════════════════════════════════════════════ */

.sn-field-wide { flex: 1 1 320px; min-width: 240px; }

.sn-table { table-layout: auto; min-width: 1100px; }

.sn-row-snipe { background: linear-gradient(90deg, var(--success-soft) 0%, transparent 55%); }

.sn-item { max-width: 380px; }

.sn-name {
  font-weight: 700;
  color: var(--ink);
  text-decoration: none;
  line-height: 1.35;
  display: block;
}

.sn-name:hover { text-decoration: underline; }

.sn-sub {
  margin-top: 2px;
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.45;
}

.sn-note {
  margin-top: 5px;
  font-size: var(--fs-xs);
  color: var(--ink-2);
  line-height: 1.5;
}

.sn-strong { font-weight: 700; color: var(--ink); }
.sn-max    { color: var(--ink); }
.sn-profit { color: var(--success); }
.sn-over   { color: var(--danger); }

.sn-tag {
  display: inline-block;
  padding: 1px 6px;
  border-radius: var(--r-pill);
  background: var(--soft);
  color: var(--ink-2);
  font-size: var(--fs-2xs);
  font-weight: 700;
}

/* Why it might be cheap. Rendered in full on every row rather than folded into
   a score — a score hides exactly the thing the bidder needs to go and look at. */
.sn-risks {
  margin: 6px 0 0;
  padding-left: 16px;
  list-style: disc;
}

.sn-risks li {
  font-size: var(--fs-xs);
  color: var(--warning);
  line-height: 1.5;
  margin-top: 2px;
}

/* ── The clock ───────────────────────────────────────────────────────────── */

.sn-time { min-width: 120px; }

.sn-clock {
  font-variant-numeric: tabular-nums;
  font-weight: 700;
  font-size: var(--fs-sm);
  color: var(--ink);
}

.sn-clock-closing { color: var(--danger); }
.sn-clock-today   { color: var(--warning); }
.sn-clock-open    { color: var(--ink-2); }
.sn-clock-none,
.sn-clock-ended   { color: var(--muted); font-weight: 600; }

.sn-snipe-at { color: var(--ink-2); font-weight: 600; }

/* ── Verdicts ────────────────────────────────────────────────────────────── */

.sn-verdict {
  display: inline-block;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  font-size: var(--fs-2xs);
  font-weight: 700;
  white-space: nowrap;
}

.sn-v-snipe { background: var(--success-soft); color: var(--success); }
.sn-v-early { background: var(--gold-soft);    color: var(--gold-ink); }
.sn-v-watch { background: var(--soft);         color: var(--ink-2); }
.sn-v-thin  { background: var(--warning-soft);             color: var(--warning); }
.sn-v-pass  { background: var(--soft);         color: var(--muted); }

/* ── What was searched, and why ──────────────────────────────────────────── */

.sn-terms-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
  margin: var(--s3) 0;
}

.sn-terms-label {
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}

.sn-chip {
  display: inline-flex;
  flex-direction: column;
  gap: 1px;
  padding: 4px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--surface);
  font-size: var(--fs-xs);
  color: var(--ink);
}

.sn-chip-quiet { opacity: .6; }
.sn-chip-sub { color: var(--muted); font-size: var(--fs-2xs); }

/* ── The caveats, rendered rather than buried ────────────────────────────── */

.sn-honesty {
  margin: var(--s4) 0 0;
  padding-left: 18px;
  list-style: disc;
}

.sn-honesty li {
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.6;
  margin-top: 4px;
}

@media (max-width: 900px) {
  .sn-item { max-width: none; }
  .sn-risks { display: none; }
  .dash-snipe .dash-pipeline-next { display: none; }
}

/* ── Where to Sell Highest ────────────────────────────────────────────────── */
/* One card per venue, ranked left to right by what the seller actually banks. The take-home is the
   biggest thing on every card and the asking price is deliberately not — this screen exists because
   those two numbers are different. */

.wts-field-wide { flex: 1 1 260px; min-width: 200px; }
.wts-field-wide input { width: 100%; }

.wts-banner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s5);
  margin: var(--s4) 0 0;
  padding: var(--s5) var(--s6);
  border: 1px solid var(--line);
  border-left-width: 4px;
  border-radius: var(--r-md);
  background: var(--card);
  box-shadow: var(--e1);
}

.wts-banner-move  { border-left-color: var(--success); background: var(--success-soft); }
.wts-banner-stay  { border-left-color: var(--info);    background: var(--info-soft); }
.wts-banner-close { border-left-color: var(--gold);    background: var(--gold-soft); }
.wts-banner-none  { border-left-color: var(--line-strong); }

.wts-banner-head {
  font-size: var(--fs-lg);
  font-weight: 800;
  color: var(--ink);
  line-height: 1.3;
}

.wts-banner-sub {
  margin-top: 6px;
  font-size: var(--fs-sm);
  color: var(--ink-2);
  line-height: 1.6;
}

.wts-banner-extra { text-align: right; flex: 0 0 auto; }

.wts-extra-value {
  display: block;
  font-size: var(--fs-3xl);
  font-weight: 800;
  color: var(--success);
  letter-spacing: -.02em;
}

.wts-extra-label {
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: .06em;
  color: var(--muted);
}

.wts-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  gap: var(--s4);
  margin-top: var(--s4);
}

.wts-card {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  padding: var(--s5);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
  box-shadow: var(--e1);
}

.wts-card-best { border-color: var(--success-line); box-shadow: var(--e2); }
.wts-card-quiet { background: var(--soft); }

.wts-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s2);
}

.wts-venue { font-size: var(--fs-md); font-weight: 700; color: var(--ink); }
.wts-mode { font-size: var(--fs-2xs); color: var(--muted); margin-top: 2px; }

.wts-verdict {
  flex: 0 0 auto;
  padding: 3px 8px;
  border-radius: var(--r-pill);
  font-size: var(--fs-2xs);
  font-weight: 700;
  white-space: nowrap;
}

.wts-v-best  { background: var(--success-soft); color: var(--success); }
.wts-v-close { background: var(--gold-soft);    color: var(--gold-ink); }
.wts-v-lower { background: var(--soft-2);       color: var(--muted); }
.wts-v-thin  { background: var(--warning-soft); color: var(--warning); }
.wts-v-none  { background: var(--soft-2);       color: var(--muted); }

.wts-take {
  font-size: var(--fs-3xl);
  font-weight: 800;
  color: var(--ink);
  letter-spacing: -.02em;
  line-height: 1.1;
}

.wts-take-none { color: var(--muted); }

.wts-take-label { font-size: var(--fs-2xs); color: var(--muted); margin-top: 2px; }

.wts-gap { margin-top: 6px; font-size: var(--fs-xs); font-weight: 700; }
.wts-gap-up { color: var(--success); }
.wts-gap-down { color: var(--muted); }

/* The word that stops the comparison lying: a sold price and an asking price are not the same
   evidence, and the badge saying which is on every card. */
.wts-evidence { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

.wts-ev {
  padding: 2px 7px;
  border-radius: var(--r-pill);
  font-size: var(--fs-2xs);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
}

.wts-e-sold { background: var(--success-soft); color: var(--success); }
.wts-e-ask  { background: var(--warning-soft); color: var(--warning); }
.wts-e-none { background: var(--soft-2);       color: var(--muted); }

.wts-ev-sub { font-size: var(--fs-2xs); color: var(--muted); }

.wts-lines { display: flex; flex-direction: column; gap: 3px; }

.wts-line {
  display: flex;
  justify-content: space-between;
  gap: var(--s3);
  font-size: var(--fs-xs);
  color: var(--ink-2);
}

.wts-line-cost span:last-child { color: var(--danger); }
.wts-line-quiet, .wts-line-quiet span { color: var(--muted); }
.wts-line-free strong { color: var(--success); }

.wts-beat {
  padding: 7px 10px;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--soft);
  font-size: var(--fs-xs);
  color: var(--ink-2);
}

.wts-note { font-size: var(--fs-xs); color: var(--muted); line-height: 1.55; }
.wts-speed { font-size: var(--fs-2xs); font-weight: 700; color: var(--ink-2); }
.wts-speed-quiet { font-weight: 600; color: var(--muted); }
.wts-fee-note { font-size: var(--fs-2xs); color: var(--muted); line-height: 1.5; }
.wts-go { align-self: flex-start; margin-top: auto; }

@media (max-width: 900px) {
  .wts-banner { flex-direction: column; align-items: flex-start; }
  .wts-banner-extra { text-align: left; }
}

/* ── Spend My Budget — the sourcing basket ────────────────────────────────────────────────────
   The only screen that spends money rather than ranking it. Tiles, the results table and the
   speed pills are deliberately the ones Inventory Health and the arbitrage board already use —
   a basket costed by the same rules should look like it was. Everything below is only the parts
   that are new here: the lift callout, the basket rows, the alternatives and the left-out list. */

.bud-pool {
  margin: 8px 0 0;
  font-size: var(--fs-xs);
  color: var(--muted);
}

/* The claim the feature stands on, in dollars. Green when it won, quiet when the obvious way
   would have done just as well — which it says rather than hides. */
.bud-lift {
  margin: 14px 0;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-left: 4px solid var(--line-strong);
  border-radius: var(--r-md);
  background: var(--card);
}

.bud-lift-won {
  border-left-color: var(--success);
  background: var(--success-soft);
  border-color: var(--success-line);
}

.bud-lift-head {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
}

.bud-lift-amount {
  font-size: var(--fs-3xl);
  font-weight: 800;
  letter-spacing: -.02em;
  line-height: 1.05;
  color: var(--success);
}

.bud-lift-label {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink);
}

.bud-lift-sub {
  margin: 6px 0 0;
  font-size: var(--fs-xs);
  color: var(--ink-2);
  line-height: 1.55;
}

.bud-headline {
  margin: 4px 0 2px;
  font-size: var(--fs-md);
  font-weight: 700;
  color: var(--ink);
}

.bud-note {
  margin: 0 0 10px;
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.55;
}

.bud-table tfoot td {
  border-top: 2px solid var(--line-strong);
  font-weight: 700;
  color: var(--ink);
}

.bud-pick-title { font-weight: 700; }
.bud-qty { font-weight: 700; color: var(--brand-ink-2); }

/* Why this one earned its place, on the row that spends the money on it. */
.bud-why {
  margin-top: 3px;
  font-size: var(--fs-2xs);
  color: var(--ink-2);
  line-height: 1.5;
}

.bud-sub { font-size: var(--fs-2xs); color: var(--muted); }

/* A forecast frozen weeks ago is a weaker claim than a scan run five minutes ago. */
.bud-origin {
  padding: 1px 6px;
  border-radius: var(--r-pill);
  background: var(--warning-soft);
  color: var(--warning);
  font-weight: 700;
  font-size: var(--fs-2xs);
}

.bud-section-title {
  margin: 18px 0 8px;
  font-size: var(--fs-sm);
  font-weight: 800;
  text-transform: uppercase;
  letter-spacing: .05em;
  color: var(--muted);
}

.bud-alt-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 10px;
}

.bud-alt {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
}

.bud-alt-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
}

.bud-alt-label { font-size: var(--fs-sm); font-weight: 800; color: var(--ink); }

.bud-alt-money { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; }

.bud-alt-profit {
  font-size: var(--fs-2xl);
  font-weight: 800;
  letter-spacing: -.02em;
  color: var(--ink);
}

.bud-alt-sub { font-size: var(--fs-2xs); color: var(--muted); }

.bud-alt-note {
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--ink-2);
  line-height: 1.55;
}

.bud-leftout-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.bud-leftout-item {
  display: grid;
  grid-template-columns: minmax(160px, 1.4fr) minmax(120px, .6fr) minmax(200px, 2fr);
  gap: var(--s3);
  align-items: baseline;
  padding: 8px 12px;
  border: 1px solid var(--line);
  border-left: 3px solid var(--line-strong);
  border-radius: var(--r-sm);
  background: var(--soft);
  font-size: var(--fs-xs);
}

/* The most actionable line on the page: a real deal the budget was a few dollars short of. */
.bud-reason-not_enough_left { border-left-color: var(--warning); background: var(--warning-soft); }
.bud-reason-crowded_out,
.bud-reason-objective_excluded { border-left-color: var(--teal-700); }

.bud-leftout-title { font-weight: 700; color: var(--ink); }
.bud-leftout-money { font-weight: 700; color: var(--ink-2); }
.bud-leftout-reason { color: var(--muted); line-height: 1.5; }

.bud-footnote {
  margin-top: 16px;
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.6;
}

@media (max-width: 900px) {
  .bud-leftout-item { grid-template-columns: 1fr; gap: 2px; }
}

/* ── Ship Smart — the Shipping Profit Engine ──────────────────────────────────────────────────
   Reuses the inventory tiles and verdict pills wholesale; what is new here is the two-tab split,
   the package strip (whose whole job is to look different when a number was guessed rather than
   weighed), the mode comparison table, and the leak row. */

.ship-tabs {
  display: flex;
  gap: 2px;
  margin-top: var(--s4);
  padding: 3px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--soft-2);
  width: fit-content;
}

.ship-tab {
  padding: 7px 16px;
  border: 0;
  border-radius: var(--r-sm);
  background: transparent;
  color: var(--ink-2);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
}

.ship-tab:hover { color: var(--ink); }

.ship-tab.active {
  background: var(--card);
  color: var(--brand-ink);
  box-shadow: var(--e1);
}

.ship-controls { align-items: flex-end; }
.ship-field-wide { flex: 1 1 260px; min-width: 200px; }

/* The verdict, in the one place the eye lands first. */
.ship-headline {
  margin-bottom: var(--s4);
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-left: 4px solid var(--teal-700);
  border-radius: var(--r-md);
  background: var(--card);
}

.ship-headline-good { border-left-color: var(--success); background: var(--success-soft); }
.ship-headline-warn { border-left-color: var(--warning); background: var(--warning-soft); }
.ship-headline-bad  { border-left-color: var(--danger);  background: var(--danger-soft); }

.ship-headline-main { font-size: var(--fs-xl); font-weight: 700; color: var(--ink); }
.ship-headline-sub  { margin-top: 5px; font-size: var(--fs-sm); color: var(--ink-2); line-height: 1.5; }

/* An estimated package and a weighed one produce identical numbers downstream and deserve very
   different confidence, so the distinction is a visual one and not a footnote. */
.ship-package {
  margin-bottom: var(--s4);
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--soft);
}

.ship-package-head {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
}

.ship-package-figures {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s4);
  margin-top: 6px;
  font-size: var(--fs-base);
  color: var(--ink);
}

.ship-package-basis { margin-top: 5px; font-size: var(--fs-xs); color: var(--muted); line-height: 1.45; }
.ship-dim-flag { color: var(--warning); font-weight: 600; }

.ship-chip {
  padding: 2px 8px;
  border-radius: var(--r-pill);
  font-size: var(--fs-3xs);
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
}

.ship-chip-measured  { background: var(--success-soft); color: var(--success); }
.ship-chip-estimated { background: var(--gold-soft);    color: var(--gold-ink); }

.ship-block { margin-bottom: var(--s6); }

.ship-block-title { margin: 0 0 4px; font-size: var(--fs-md); font-weight: 700; color: var(--ink); }
.ship-block-sub   { margin: 0 0 var(--s3); font-size: var(--fs-xs); color: var(--muted); line-height: 1.5; max-width: 78ch; }

/* Not the listings .table-wrap: that one is display:none until a listings panel opts into table
   mode, which would silently hide every table on this page. */
.ship-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
}

.ship-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }

.ship-table th {
  padding: 8px 10px;
  border-bottom: 1px solid var(--line-strong);
  font-size: var(--fs-2xs);
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--muted);
  text-align: left;
  white-space: nowrap;
}

.ship-table td {
  padding: 10px;
  border-bottom: 1px solid var(--line-soft);
  vertical-align: top;
}

.ship-table .ship-num { text-align: right; white-space: nowrap; }
.ship-table th.ship-num { text-align: right; }

.ship-num-bad { color: var(--danger); font-weight: 600; }
.ship-num-ok  { color: var(--success); }

.ship-mode-pick { background: var(--success-soft); }
.ship-mode-name { font-weight: 700; color: var(--ink); }
.ship-mode-desc { margin-top: 3px; font-size: var(--fs-xs); color: var(--muted); line-height: 1.45; max-width: 52ch; }
.ship-mode-verdict { margin-top: 5px; font-size: var(--fs-xs); color: var(--ink-2); line-height: 1.45; max-width: 52ch; }

.ship-pick-tag {
  margin-left: var(--s2);
  padding: 2px 7px;
  border-radius: var(--r-pill);
  background: var(--success);
  color: var(--on-solid);
  font-size: var(--fs-3xs);
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
}

.ship-sub { margin-top: 2px; font-size: var(--fs-2xs); color: var(--muted); font-weight: 400; }

/* Services that cannot carry the package stay on the page — "two inches from a flat-rate box" is
   worth knowing — but recede so they never compete with the ones that can. */
.ship-service-out { opacity: .62; }
.ship-out-reason  { font-size: var(--fs-xs); color: var(--muted); font-style: italic; }

.ship-modes-empty {
  padding: 14px;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
  background: var(--soft);
  font-size: var(--fs-sm);
  color: var(--muted);
}

.ship-tips { display: flex; flex-direction: column; gap: var(--s2); }

.ship-tip {
  display: flex;
  gap: var(--s4);
  align-items: flex-start;
  padding: 12px 14px;
  border: 1px solid var(--success-line);
  border-radius: var(--r-md);
  background: var(--success-soft);
}

.ship-tip-save {
  min-width: 84px;
  font-size: var(--fs-xl);
  font-weight: 700;
  color: var(--success);
  line-height: 1.1;
}

.ship-tip-save span { display: block; font-size: var(--fs-3xs); font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.ship-tip-head   { font-weight: 700; color: var(--ink); }
.ship-tip-detail { margin-top: 3px; font-size: var(--fs-xs); color: var(--ink-2); line-height: 1.5; }

.ship-zones {
  margin-top: var(--s4);
  padding: 12px 14px;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--soft);
}

.ship-zones summary { font-size: var(--fs-sm); font-weight: 600; color: var(--ink-2); cursor: pointer; }
.ship-zone-table { margin-top: var(--s3); }
.ship-zone-regions { font-size: var(--fs-xs); color: var(--muted); }
.ship-zones-note { margin: var(--s3) 0 0; font-size: var(--fs-2xs); color: var(--muted); line-height: 1.5; }

/* One leak: what is wrong on the left, what it costs on the right. */
.ship-leak {
  display: flex;
  gap: var(--s5);
  align-items: flex-start;
  padding: 14px 16px;
  margin-bottom: var(--s2);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
}

.ship-leak:hover { box-shadow: var(--e2); }

.ship-leak-main { flex: 1; min-width: 0; }
.ship-leak-top { display: flex; align-items: center; gap: var(--s2); flex-wrap: wrap; }

.ship-leak-title { margin-top: 6px; font-weight: 600; color: var(--ink); }
.ship-leak-title a { color: var(--brand-ink-2); text-decoration: none; }
.ship-leak-title a:hover { text-decoration: underline; }

.ship-leak-head   { margin-top: 5px; font-size: var(--fs-base); font-weight: 700; color: var(--ink); }
.ship-leak-detail { margin-top: 4px; font-size: var(--fs-xs); color: var(--ink-2); line-height: 1.5; }
.ship-leak-fix    { margin-top: 6px; font-size: var(--fs-xs); font-weight: 600; color: var(--brand-ink-2); line-height: 1.45; }

.ship-leak-money { text-align: right; min-width: 132px; }
.ship-leak-impact { font-size: var(--fs-2xl); font-weight: 700; color: var(--danger); line-height: 1.1; }
.ship-leak-impact-label { font-size: var(--fs-3xs); letter-spacing: .04em; text-transform: uppercase; color: var(--muted); }
.ship-leak-atrisk { margin-top: 5px; font-size: var(--fs-xs); font-weight: 600; color: var(--warning); }
.ship-leak-price  { margin-top: 4px; font-size: var(--fs-2xs); color: var(--muted); }

@media (max-width: 900px) {
  .ship-leak { flex-direction: column; gap: var(--s3); }
  .ship-leak-money { text-align: left; min-width: 0; }
  .ship-tip { flex-direction: column; gap: var(--s2); }
}

/* ═══════════════════════════════════════════════════════════════════════
   Workspace tabs
   One tab per open feature. Every screen in this app is a fixed, full-viewport
   overlay (.opportunity-overlay, .new-listing-overlay), so the strip that
   switches between them has to sit ABOVE them — fixed to the top of the window
   at a z-index over theirs, with everything else pushed down by its height.
   That height is one token: 0 until there is more than one tab open, so a
   seller who only ever uses the Dashboard never pays for chrome they don't use.
   Modals stay above the strip (z-index 1000+), which is right — a confirmation
   must not be switchable-away-from.
   ═══════════════════════════════════════════════════════════════════════ */
:root { --ws-tabbar-h: 0px; }
body.ws-tabs-open { --ws-tabbar-h: 38px; }

.ws-tab-bar {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: var(--ws-tabbar-h);
  z-index: 950;
  display: none;
  align-items: flex-end;
  gap: 2px;
  padding: 6px 10px 0;
  background: linear-gradient(180deg, var(--teal-950), var(--teal-900));
  border-bottom: 1px solid rgba(255, 255, 255, .08);
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
}
body.ws-tabs-open .ws-tab-bar { display: flex; }

/* Everything the strip covers moves down by exactly its height. */
body.ws-tabs-open .app-shell { padding-top: var(--ws-tabbar-h); }
body.ws-tabs-open .sidebar {
  top: var(--ws-tabbar-h);
  height: calc(100vh - var(--ws-tabbar-h));
}
body.ws-tabs-open .opportunity-overlay,
body.ws-tabs-open .new-listing-overlay { top: var(--ws-tabbar-h); }

.ws-tab {
  display: flex;
  align-items: center;
  height: 32px;
  min-width: 96px;
  max-width: 210px;
  flex: 0 1 auto;
  padding-right: 4px;
  background: rgba(255, 255, 255, .07);
  border-radius: 8px 8px 0 0;
  color: rgba(246, 251, 251, .62);
  transition: background var(--dur-2) var(--ease), color var(--dur-2) var(--ease);
}
.ws-tab:hover { background: rgba(255, 255, 255, .14); color: var(--on-dark); }

.ws-tab.active {
  background: var(--page);
  color: var(--ink);
}
.ws-tab.active .ws-tab-icon { color: var(--brand-ink-2); opacity: 1; }

.ws-tab-main {
  display: flex;
  align-items: center;
  gap: var(--s2);
  flex: 1;
  min-width: 0;
  height: 100%;
  padding: 0 var(--s2) 0 var(--s3);
  background: none;
  border: none;
  border-radius: 8px 8px 0 0;
  color: inherit;
  font: inherit;
  font-size: var(--fs-xs);
  font-weight: 600;
  cursor: pointer;
  white-space: nowrap;
}
.ws-tab-main:focus-visible { box-shadow: var(--focus-ring-dark); outline: none; }
.ws-tab.active .ws-tab-main:focus-visible { box-shadow: var(--focus-ring); }

.ws-tab-icon {
  width: 15px;
  height: 15px;
  flex-shrink: 0;
  stroke: currentColor;
  fill: none;
  stroke-width: 1.6;
  stroke-linecap: round;
  stroke-linejoin: round;
  opacity: .72;
}

.ws-tab-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ws-tab-close {
  display: grid;
  place-items: center;
  width: 18px;
  height: 18px;
  flex-shrink: 0;
  background: none;
  border: none;
  border-radius: 50%;
  color: inherit;
  font-size: var(--fs-3xs);
  line-height: 1;
  cursor: pointer;
  opacity: 0;
  transition: opacity var(--dur-2) var(--ease), background var(--dur-2) var(--ease);
}
.ws-tab:hover .ws-tab-close,
.ws-tab.active .ws-tab-close,
.ws-tab-close:focus-visible { opacity: .6; }
.ws-tab-close:hover { opacity: 1; background: rgba(0, 0, 0, .18); }

@media (max-width: 900px) {
  .ws-tab { min-width: 44px; }
  .ws-tab-title { display: none; }
}

/* The "+" and its feature menu — the only way to a second feature while a full-screen one is
   open, since those overlays sit on top of the sidebar. */
.ws-tab-new-btn {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  margin: 0 0 3px 4px;
  flex-shrink: 0;
  background: none;
  border: none;
  border-radius: 50%;
  color: rgba(246, 251, 251, .55);
  font-size: var(--fs-xl);
  font-weight: 300;
  line-height: 1;
  cursor: pointer;
  transition: background var(--dur-2) var(--ease), color var(--dur-2) var(--ease);
}
.ws-tab-new-btn:hover { background: rgba(255, 255, 255, .15); color: #fff; }
.ws-tab-new-btn:focus-visible { box-shadow: var(--focus-ring-dark); outline: none; }

.ws-open-menu {
  position: fixed;
  z-index: 960;
  width: 264px;
  max-height: min(70vh, 620px);
  overflow-y: auto;
  padding: var(--s2);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  box-shadow: var(--e3);
}

.ws-open-menu-group {
  padding: var(--s2) var(--s2) 4px;
  font-size: var(--fs-2xs);
  font-weight: 800;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--muted);
}

.ws-open-menu-item {
  display: flex;
  align-items: center;
  gap: var(--s2);
  width: 100%;
  padding: 7px var(--s2);
  background: none;
  border: none;
  border-radius: var(--r-sm);
  color: var(--ink);
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.ws-open-menu-item:hover { background: var(--soft-2); }
.ws-open-menu-item:focus-visible { background: var(--soft-2); box-shadow: var(--focus-ring); outline: none; }
.ws-open-menu-item .ws-tab-icon { color: var(--brand-ink-2); opacity: .85; }

.ws-open-menu-label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.ws-open-menu-open {
  flex-shrink: 0;
  padding: 1px 7px;
  border-radius: var(--r-pill);
  background: var(--teal-100);
  color: var(--brand-ink-2);
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: .03em;
  text-transform: uppercase;
}

/* ═══════════════════════════════════════════════════════════════════════
   Motion & micro-interactions
   ───────────────────────────────────────────────────────────────────────
   The app already had the right static surfaces; what it did not have was
   any sense of cause and effect between a click and what happened next.
   Screens appeared, modals vanished, numbers teleported, and a failure
   arrived as a browser alert() with the whole app frozen behind it.

   Three rules for everything in this section:

     1. 150-250ms. Long enough to be seen, short enough that nobody waits
        on it. Only the number count-up runs longer, because watching a
        figure change IS the point of that one.
     2. Compositor-only. opacity and transform. Nothing here animates a
        property that forces layout, so none of it can drop a frame on a
        page that is also rendering 200 listing cards.
     3. Every rule here is decoration. The global prefers-reduced-motion
        block near the top of this file already flattens durations to
        ~0ms; the extra rules at the end of this section cover the few
        cases where "instant" is not the same as "off".
   ═══════════════════════════════════════════════════════════════════════ */

/* ── Screen transitions ──────────────────────────────────────────────────
   Switching workspace tabs used to be a hard cut: one section display:none,
   the next display:block, in the same frame. The eye reads that as a page
   reload. A 6px rise reads as the same surface moving. */
@keyframes section-in {
  from { opacity: 0; transform: translateY(6px); }
}

.section-enter { animation: section-in var(--dur-2) var(--ease-out); }

/* ── Overlay exit ────────────────────────────────────────────────────────
   Modals animated in and then vanished on close, which is the most visible
   asymmetry in the app. Closing is driven from ~30 call sites that all do
   the same thing — add .hidden — so rather than touch every one,
   initOverlayMotion() adds .closing and these rules hold the overlay on
   screen for exactly one exit animation.

   .hidden stays on the element throughout, so any code asking "is this
   modal open?" still gets the truth. That is why these selectors have to
   out-specify `.hidden { display: none !important }`. */
.modal-overlay.hidden.closing {
  display: flex !important;
  pointer-events: none;
  animation: overlay-out var(--dur-2) var(--ease) forwards;
}

.modal-overlay.closing .modal { animation: modal-out var(--dur-2) var(--ease) forwards; }

@keyframes overlay-out { to { opacity: 0; } }
@keyframes modal-out   { to { opacity: 0; transform: translateY(6px) scale(.985); } }

/* ── Buttons ─────────────────────────────────────────────────────────────
   The existing note on .btn is right: a button that lifts on hover shifts
   the layout under the cursor. Press is the opposite case — the pointer is
   already down and committed, and a small recede is the only feedback that
   survives being underneath a fingertip or a cursor. */
.btn { transition-property: background, border-color, box-shadow, color, filter, transform; }

.btn:active:not(:disabled) { transform: translateY(1px) scale(.988); }

/* The primary action is the one button in the app that glows. On hover a
   single light sweep crosses it — the same read as tilting a foil-stamped
   card toward a lamp. It runs once per hover and never loops: an idle
   animation on the main call to action is a distraction, not a delight. */
.btn-primary {
  position: relative;
  overflow: hidden;
}

/* ::before, not ::after: `.btn.is-busy::after` is already the spinner, and a
   second rule on the same pseudo-element would merge into it — the sheen's
   `opacity: 0` would have made every busy button spin invisibly. */
.btn-primary::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(105deg, transparent 34%, rgba(255, 255, 255, .30) 48%, transparent 62%);
  opacity: 0;
}

.btn-primary:hover:not(:disabled)::before { animation: btn-sheen .55s var(--ease-out); }

@keyframes btn-sheen {
  from { opacity: 1; transform: translateX(-115%); }
  to   { opacity: 1; transform: translateX(115%); }
}

/* Everything else that is a click target but is not a .btn. These already
   had colour transitions; none had a press state, so a fast click on a nav
   item or a view toggle gave back nothing at all. */
.nav-item,
.toggle-btn,
.modal-close,
.ws-tab { transition-property: background, border-color, box-shadow, color, opacity, transform; }

.nav-item:active,
.toggle-btn:active,
.modal-close:active,
.ws-tab:active { transform: scale(.97); }

/* ── Numbers ─────────────────────────────────────────────────────────────
   countUp() walks a figure to its new value instead of swapping it.
   Tabular figures are what make that legible: with proportional digits
   every frame is a different width, so the number jitters sideways as it
   counts and drags the tile's layout with it. */
.stat-card strong,
.dash-earnings-figure,
.num-anim { font-variant-numeric: tabular-nums; }

/* The settle at the end of a count. Scale only — a figure that flashes a
   colour on every refresh trains sellers to stop seeing the colour. */
.num-settled { animation: num-settle var(--dur-3) var(--ease-spring); }

@keyframes num-settle {
  from { transform: scale(1.035); }
}

/* ── Skeletons ───────────────────────────────────────────────────────────
   The card grid and the table already show skeletons while listings load.
   The four stat tiles above them did not: they sat holding the previous
   run's numbers, which is worse than blank, because a stale number looks
   exactly like a fresh one. This greys the figure into the same shimmer
   the grid below it is already showing. */
.stat-card.is-loading strong {
  color: transparent;
  border-radius: var(--r-2xs);
  background: var(--soft-2);
  background-image: linear-gradient(90deg, transparent, var(--skeleton-sheen), transparent);
  background-size: 220% 100%;
  animation: skeleton-sweep 1.4s var(--ease) infinite;
  user-select: none;
}

.stat-card.is-loading strong { width: 60%; }

@keyframes skeleton-sweep {
  from { background-position: 120% 0; }
  to   { background-position: -120% 0; }
}

/* ── Toasts ──────────────────────────────────────────────────────────────
   Replaces alert(). An alert is a modal dialog: it stops the page, steals
   focus, cannot carry an action, and on Windows it renders as OS chrome
   with the page origin in the title bar — which is the least premium thing
   the app could possibly put on screen. These stack bottom-right, dismiss
   themselves, and pause while the pointer is over them, so a long error
   message can actually be finished.

   The stack itself is inert; only the toasts inside it take pointer
   events, so it never swallows a click on the page behind it. */
.toast-stack {
  position: fixed;
  right: var(--s5);
  bottom: var(--s5);
  z-index: 3000;
  display: flex;
  flex-direction: column;
  width: min(390px, calc(100vw - var(--s6)));
  pointer-events: none;
}

/* The collapsing wrapper. When a toast leaves, the ones below it have to
   move up — animating the slot's row track from 1fr to 0fr does that with
   nothing measured in JS, so it stays correct at any toast height. */
.toast-slot {
  display: grid;
  grid-template-rows: 1fr;
  padding-top: var(--s2);
  transition: grid-template-rows var(--dur-2) var(--ease), padding-top var(--dur-2) var(--ease);
}

.toast-slot.leaving { grid-template-rows: 0fr; padding-top: 0; }

.toast {
  position: relative;
  min-height: 0;
  display: grid;
  grid-template-columns: 20px minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--s3);
  overflow: hidden;
  padding: var(--s3) var(--s3) var(--s4) var(--s4);
  border: 1px solid var(--line);
  border-left: 3px solid var(--teal-600);
  border-radius: var(--r-md);
  background: var(--card);
  box-shadow: var(--e4);
  pointer-events: auto;
  animation: toast-in var(--dur-2) var(--ease-out);
}

@keyframes toast-in {
  from { opacity: 0; transform: translateX(20px) scale(.97); }
}

.toast-slot.leaving > .toast {
  opacity: 0;
  transform: translateX(20px);
  transition: opacity var(--dur-2) var(--ease), transform var(--dur-2) var(--ease);
}

.toast-icon {
  display: flex;
  width: 20px;
  height: 20px;
  margin-top: 1px;
  color: var(--brand-ink-2);
}

.toast-icon svg {
  width: 20px;
  height: 20px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.9;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.toast-title {
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-snug);
}

.toast-msg {
  margin-top: 2px;
  color: var(--ink-2);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  overflow-wrap: anywhere;
}

.toast-action {
  margin-top: var(--s2);
  padding: 5px 11px;
  border: 1px solid var(--line-strong);
  border-radius: var(--r-xs);
  background: var(--soft);
  color: var(--ink);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), border-color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}

.toast-action:hover { background: var(--soft-2); border-color: var(--teal-600); }
.toast-action:active { transform: scale(.97); }

.toast-close {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  border: none;
  border-radius: var(--r-xs);
  background: none;
  color: var(--faint);
  font-size: var(--fs-lg);
  line-height: 1;
  cursor: pointer;
  transition: background var(--dur-1) var(--ease), color var(--dur-1) var(--ease), transform var(--dur-1) var(--ease);
}

.toast-close:hover { background: var(--soft-2); color: var(--ink); }
.toast-close:active { transform: scale(.9); }

/* The remaining-time hairline. Driven entirely by CSS animation, so hover
   can pause it with animation-play-state and JS never has to track a
   deadline or re-derive one on resume. */
.toast-life {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 2px;
  transform-origin: left center;
  background: currentColor;
  opacity: .3;
  animation: toast-life linear forwards;
}

@keyframes toast-life { from { transform: scaleX(1); } to { transform: scaleX(0); } }

.toast:hover .toast-life,
.toast:focus-within .toast-life { animation-play-state: paused; }

.toast--success { border-left-color: var(--success-600); color: var(--success); }
.toast--success .toast-icon { color: var(--success-600); }
.toast--error   { border-left-color: var(--danger-600);  color: var(--danger); }
.toast--error   .toast-icon { color: var(--danger-600); }
.toast--warning { border-left-color: var(--warning-600); color: var(--warning); }
.toast--warning .toast-icon { color: var(--warning-600); }
.toast--info    { border-left-color: var(--teal-600);    color: var(--brand-ink-2); }

@media (max-width: 620px) {
  .toast-stack {
    right: var(--s3);
    left: var(--s3);
    bottom: var(--s3);
    width: auto;
  }
}

/* ── Reduced motion ──────────────────────────────────────────────────────
   The global block at the top of this file collapses every duration to
   ~0ms, which correctly handles the transitions and one-shot animations
   above. These are the cases where near-zero is the wrong answer rather
   than the fast one: a countdown hairline that empties instantly is a lie
   about how long the toast will stay, and a shimmer that finishes in a
   microsecond is a flash. JS reads the same media query and skips
   count-ups and screen enters outright. */
@media (prefers-reduced-motion: reduce) {
  .toast-life { display: none; }
  .btn-primary::before { display: none; }
  .stat-card.is-loading strong { background-image: none; animation: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   Data visualisation
   ═══════════════════════════════════════════════════════════════════════

   Every chart in this app draws from the tokens in this block and nothing
   else. Before it, each money screen invented its own greens and bar
   widths, so the profit chart, the trend sparkline and the sell-through
   number read as three products' worth of charting bolted together.

   The colours below are NOT eyeballed. They were run through the
   colour-blindness / lightness / chroma / contrast checks and adjusted
   until every one passed, in both modes:

     light (on #ffffff)   #00789c  #946d1f  #7f4c9e  #157a55
     dark  (on --dark-1)  #2f9cbc  #b58c3c  #a274c6  #2fa878

   Slot 4 is a step darker than the app's --success-600. It carries a white
   value label inside the fill on the composition bar, and at --success-600
   that label came out at 4.3:1 — under the 4.5 small-text line. Darkening
   the fill fixes the label without moving the hue, and the palette still
   passes every check at the new step.

   The brand teal (--teal-600) was the first choice for slot 1 and FAILED:
   at OKLCH C .075 it is under the .10 chroma floor, which means a reader
   sees it as grey and the hue stops carrying identity at all. #00789c is
   the same teal pushed just over the floor — close enough to sit beside
   the brand, saturated enough to be a colour.

   The categorical slots are assigned in fixed order and never cycled. A
   fifth series does not get a generated fifth hue; it folds into the
   neutral "not accounted for" slot or the view stops being a chart. */
:root {
  /* Chart chrome. Grid and axis are deliberately one step off the surface:
     scaffolding, never ink that competes with the data. */
  --viz-surface: var(--card);
  --viz-grid: var(--line-soft);
  --viz-axis: var(--line-strong);
  --viz-label: var(--muted);
  --viz-ink: var(--ink);
  --viz-ink-2: var(--ink-2);
  --viz-hover: var(--soft-2);

  /* Categorical — fixed order, validated. */
  --viz-1: #00789c;
  --viz-2: #946d1f;
  --viz-3: #7f4c9e;
  --viz-4: #157a55;
  /* The "no data" slot. Grey on purpose: an unmeasured share must not look
     like a measured one, and grey is the only honest colour for it. */
  --viz-none: #9aa8ac;
  --viz-none-soft: #e4eaec;

  /* One-series default, and the de-emphasis step history is drawn in. */
  --viz-solo: #00789c;
  --viz-solo-soft: #cfe6ee;

  /* Polarity. These are status, not identity — they always ship beside a
     word ("Rising demand", "+12.4%") and a position (above or below the
     zero line), never as the only channel. */
  --viz-up: var(--success-600);
  --viz-up-soft: var(--success-line);
  --viz-down: var(--danger-600);
  --viz-down-soft: var(--danger-line);
  --viz-flat: var(--muted);
  --viz-flat-soft: var(--line);
}

/* Dark chart surfaces. Re-stepped from the same hues rather than flipped:
   an automatic inversion puts a light-mode mid-tone on a near-black panel,
   where it either glows or disappears. Applied by container so a chart
   dropped into the hero or an overlay header is correct without the
   caller knowing what it is sitting on. */
/* The dark theme takes the same set at the root, so an ordinary .viz-panel
   sitting on a dark page is drawn with the palette that was validated
   against a dark surface — not the light-mode one on a dark background. */
:root[data-theme="dark"],
.viz-dark,
.hero-panel,
.opportunity-overlay-header {
  --viz-surface: var(--dark-1);
  --viz-grid: rgba(246, 251, 251, .13);
  --viz-axis: rgba(246, 251, 251, .30);
  --viz-label: var(--on-dark-2);
  --viz-ink: var(--on-dark);
  --viz-ink-2: var(--on-dark-2);
  --viz-hover: rgba(246, 251, 251, .08);

  --viz-1: #2f9cbc;
  --viz-2: #b58c3c;
  --viz-3: #a274c6;
  --viz-4: #2fa878;
  --viz-none: #6c8288;
  --viz-none-soft: rgba(246, 251, 251, .14);

  --viz-solo: #2f9cbc;
  --viz-solo-soft: rgba(47, 156, 188, .30);

  --viz-up: #3fbd8b;
  --viz-up-soft: rgba(63, 189, 139, .32);
  --viz-down: #e0796a;
  --viz-down-soft: rgba(224, 121, 106, .32);
  --viz-flat: var(--on-dark-3);
  --viz-flat-soft: rgba(246, 251, 251, .16);
}

/* ── The panel a chart lives in ──────────────────────────────────────── */
.viz-panel {
  position: relative;
  margin-bottom: var(--s5);
  padding: var(--s5);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
  overflow: hidden;
}

/* The dark variant is the same panel wearing the product's own dark
   surface — the gold hairline and the deep teal wash it uses everywhere
   else, so a chart band reads as this app and not as an embed. */
.viz-panel.viz-dark {
  border-color: var(--dark-line);
  background: var(--grad-brand-deep), var(--dark-1);
  box-shadow: var(--e3);
}

.viz-panel.viz-dark::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 1px;
  background: var(--grad-hairline);
}

.viz-panel-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s4);
  margin-bottom: var(--s4);
}

.viz-panel-head h3 {
  margin: var(--s1) 0 0;
  font-size: var(--fs-lg);
  color: var(--viz-ink);
  letter-spacing: var(--tracking-snug);
}

.viz-panel-sub {
  margin: var(--s2) 0 0;
  max-width: 68ch;
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--viz-ink-2);
}

.viz-panel.viz-dark .eyebrow { color: var(--gold-pale); }

/* A ghost button on a dark panel needs its own steps — the light one
   disappears against the teal. */
.viz-panel.viz-dark .btn-ghost {
  color: var(--on-dark-2);
  border-color: var(--dark-line-strong);
  background: rgba(246, 251, 251, .05);
}

.viz-panel.viz-dark .btn-ghost:hover {
  color: var(--on-dark);
  background: rgba(246, 251, 251, .12);
}

.viz-svg { display: block; width: 100%; height: auto; }

/* ── Shared chart chrome ─────────────────────────────────────────────── */
.viz-grid { stroke: var(--viz-grid); stroke-width: 1; }
.viz-zero { stroke: var(--viz-axis); stroke-width: 1; }

.viz-axis {
  font-size: var(--fs-2xs);
  fill: var(--viz-label);
  font-variant-numeric: tabular-nums;
}

.viz-axis-y { text-anchor: end; }
.viz-axis-x { text-anchor: middle; }

/* Text never wears the data colour. The mark beside it carries identity;
   a light categorical hue used as type is simply unreadable. */
.viz-value-label {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  fill: var(--viz-ink);
  text-anchor: middle;
}

/* The hit target is the whole band, never the 12px column — a thin mark
   is far too small to land on, with a mouse or a caret. The band carries
   the tooltip and the tab stop; the rect inside it is only the wash that
   shows which band you are on, because the column paints over its own
   rect and a hover on the column itself has to count. */
.viz-hit { fill: transparent; cursor: default; }
.viz-band:hover .viz-hit,
.viz-band:focus-visible .viz-hit { fill: var(--viz-hover); }
.viz-band { outline: none; }
.viz-band:focus-visible .viz-hit { stroke: var(--gold); stroke-width: 2; }

/* ── Meter — one share of one whole ──────────────────────────────────── */
/* The unfilled track is a lighter step of the fill's own ramp, so the
   state reads across the whole bar rather than only across the fill. */
.viz-meter {
  display: flex;
  align-items: center;
  gap: var(--s2);
  width: 100%;
}

.viz-meter-track {
  position: relative;
  flex: 1;
  height: 6px;
  min-width: 40px;
  border-radius: var(--r-pill);
  background: var(--viz-flat-soft);
  overflow: hidden;
}

/* display:block is load-bearing, not tidiness. vizMeter() emits the fill as a
   <span> and sets its width as an inline style; the track around it is a flex
   ITEM but is not itself a flex container, so the fill stayed inline, ignored
   both width and height, and every meter in the app drew an empty track. */
.viz-meter-fill {
  display: block;
  height: 100%;
  border-radius: var(--r-pill);
  background: var(--viz-flat);
  transition: width var(--dur-2) var(--ease-out);
}

.viz-meter-value {
  flex-shrink: 0;
  min-width: 3.2em;
  text-align: right;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--viz-ink-2);
}

.viz-tone-good .viz-meter-track { background: var(--success-line); }
.viz-tone-good .viz-meter-fill  { background: var(--success-600); }
.viz-tone-mid  .viz-meter-track { background: var(--warning-line); }
.viz-tone-mid  .viz-meter-fill  { background: var(--warning-600); }
.viz-tone-low  .viz-meter-track { background: var(--danger-line); }
.viz-tone-low  .viz-meter-fill  { background: var(--danger-600); }
.viz-tone-solo .viz-meter-track { background: var(--viz-solo-soft); }
.viz-tone-solo .viz-meter-fill  { background: var(--viz-solo); }

/* ── Ranked bars — the shape a top-five list should have been ────────── */
.viz-rank { display: flex; flex-direction: column; gap: 2px; }

.viz-rank-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 82px auto;
  align-items: center;
  gap: var(--s3);
  padding: var(--s2) var(--s2);
  border-radius: var(--r-sm);
  transition: background var(--dur-1) var(--ease-out);
}

.viz-rank-row:hover { background: var(--viz-hover); }

.viz-rank-label {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--fs-sm);
  color: var(--viz-ink);
}

.viz-rank-label a { color: inherit; text-decoration: none; }
.viz-rank-label a:hover { text-decoration: underline; }

.viz-rank-track {
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--viz-flat-soft);
  overflow: hidden;
}

/* Grows from a single baseline at the left; the rounded end is the data
   end, and the end at the baseline stays square. */
.viz-rank-bar {
  display: block;
  height: 100%;
  min-width: 3px;
  border-top-right-radius: var(--r-pill);
  border-bottom-right-radius: var(--r-pill);
  background: var(--viz-solo);
}

.viz-tone-good .viz-rank-track { background: var(--success-line); }
.viz-tone-good .viz-rank-bar   { background: var(--success-600); }
.viz-tone-mid  .viz-rank-track { background: var(--warning-line); }
.viz-tone-mid  .viz-rank-bar   { background: var(--warning-600); }
.viz-tone-low  .viz-rank-track { background: var(--danger-line); }
.viz-tone-low  .viz-rank-bar   { background: var(--danger-600); }

.viz-rank-value {
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
  color: var(--viz-ink-2);
  white-space: nowrap;
}

.viz-rank-note {
  margin: var(--s2) 0 0;
  font-size: var(--fs-2xs);
  line-height: var(--lh-normal);
  color: var(--muted);
}

/* ── Diverging chip — a signed change, drawn from a shared centre ────── */
/* Two half-tracks meeting at a centre rule: a "+8%" and a "−8%" are the
   same length in opposite directions, which is the only way the eye can
   compare them. A single left-anchored bar cannot show sign at all. */
.viz-delta {
  display: inline-grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  width: 72px;
  height: 8px;
  border-radius: var(--r-pill);
  background: var(--viz-flat-soft);
  overflow: hidden;
  vertical-align: middle;
}

.viz-delta-neg { justify-self: end; height: 100%; background: var(--viz-down); border-radius: var(--r-pill) 0 0 var(--r-pill); }
.viz-delta-pos { justify-self: start; height: 100%; background: var(--viz-up); border-radius: 0 var(--r-pill) var(--r-pill) 0; }

/* ── Composition bar — where one whole was spent ─────────────────────── */
.viz-stack {
  display: flex;
  width: 100%;
  height: 34px;
  /* White does the separating. A stroke around each segment would add ink
     that isn't data; a 2px gap in the surface colour separates two
     neighbouring steps just as well and stays quiet. */
  gap: 2px;
  border-radius: var(--r-sm);
  overflow: hidden;
}

.viz-seg {
  position: relative;
  display: flex;
  align-items: center;
  min-width: 3px;
  padding: 0 var(--s2);
  overflow: hidden;
  cursor: default;
}

.viz-seg:first-child { border-radius: var(--r-sm) 0 0 var(--r-sm); }
.viz-seg:last-child  { border-radius: 0 var(--r-sm) var(--r-sm) 0; }

.viz-seg-1 { background: var(--viz-1); }
.viz-seg-2 { background: var(--viz-2); }
.viz-seg-3 { background: var(--viz-3); }
.viz-seg-4 { background: var(--viz-4); }
.viz-seg-none { background: var(--viz-none); }

/* An in-segment label is rendered only when JS has measured that it fits
   with padding on both sides — a clipped "$1,2…" is worse than no label,
   and the legend below carries every value anyway. */
.viz-seg-label {
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  color: #fff;
  opacity: 0;
  transition: opacity var(--dur-1) var(--ease-out);
}

.viz-seg.viz-fits .viz-seg-label { opacity: 1; }

/* A label set inside a filled mark is the one place text does sit on the
   data colour, so it picks white or ink by the fill's own luminance rather
   than always being white. The grey "not measured" slot is light enough
   that white on it lands at 2.5:1 — unreadable, and on the one segment a
   seller most needs to notice. */
.viz-seg-none .viz-seg-label { color: var(--ink); }
.viz-dark .viz-seg-label { color: var(--dark-0); }

.viz-legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3) var(--s6);
  margin-top: var(--s4);
}

.viz-legend-item {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  font-size: var(--fs-sm);
}

.viz-swatch {
  flex-shrink: 0;
  width: 10px;
  height: 10px;
  border-radius: 3px;
  transform: translateY(1px);
}

.viz-swatch-1 { background: var(--viz-1); }
.viz-swatch-2 { background: var(--viz-2); }
.viz-swatch-3 { background: var(--viz-3); }
.viz-swatch-4 { background: var(--viz-4); }
.viz-swatch-none { background: var(--viz-none); }

.viz-legend-name  { color: var(--viz-ink-2); }
.viz-legend-value { font-weight: var(--fw-semibold); font-variant-numeric: tabular-nums; color: var(--viz-ink); }
.viz-legend-share { font-size: var(--fs-2xs); color: var(--viz-label); font-variant-numeric: tabular-nums; }

/* ── Sparkline ───────────────────────────────────────────────────────── */
.viz-spark { display: block; overflow: visible; }
.viz-spark-line {
  fill: none;
  stroke-width: 2;
  stroke-linejoin: round;
  stroke-linecap: round;
  stroke: var(--viz-solo);
}

/* A wash, never a saturated block — it says "under the line", it is not a
   second thing to read. */
.viz-spark-area { fill: var(--viz-solo); opacity: .10; stroke: none; }

/* The end dot carries a ring in the surface colour so it stays legible
   where it crosses the line or another point. */
.viz-spark-end {
  fill: var(--viz-solo);
  stroke: var(--viz-surface);
  stroke-width: 2;
}

.viz-spark-up      { --viz-solo: var(--success-600); }
.viz-spark-early   { --viz-solo: var(--gold-dark); }
.viz-spark-squeeze { --viz-solo: var(--warning-600); }
.viz-spark-down    { --viz-solo: var(--danger-600); }
.viz-spark-flat    { --viz-solo: var(--muted); }

/* ── Market pulse columns ────────────────────────────────────────────── */
/* Polarity, not identity — so these are the status steps rather than a
   categorical slot. Colour is the second channel here, never the first:
   the column's side of the zero line already says which way it went, which
   is what keeps the chart readable to someone who cannot separate the two
   hues at all. */
.tr-pulse-up   { fill: var(--viz-up); }
.tr-pulse-down { fill: var(--viz-down); }

/* ── Tooltip ─────────────────────────────────────────────────────────── */
/* One tooltip element for the whole app, driven off data-viz-tip. It
   enhances and never gates: every value it shows is also in a direct
   label, the legend, or the table view under the chart. Keyboard focus
   raises exactly what hover raises. */
.viz-tip {
  position: fixed;
  z-index: 3000;
  max-width: 280px;
  padding: var(--s2) var(--s3);
  border-radius: var(--r-sm);
  background: var(--dark-0);
  color: var(--on-dark);
  font-size: var(--fs-xs);
  line-height: 1.45;
  box-shadow: var(--e4);
  pointer-events: none;
  opacity: 0;
  transform: translateY(3px);
  transition: opacity var(--dur-1) var(--ease-out),
              transform var(--dur-1) var(--ease-out);
}

.viz-tip.is-open { opacity: 1; transform: translateY(0); }
.viz-tip-title { display: block; font-weight: var(--fw-semibold); }
.viz-tip-line  { display: block; color: var(--on-dark-2); font-variant-numeric: tabular-nums; }

/* ── Chart table twin ────────────────────────────────────────────────── */
.viz-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }

.viz-table th,
.viz-table td {
  padding: var(--s2) var(--s3);
  border-bottom: 1px solid var(--viz-grid);
  text-align: left;
  color: var(--viz-ink-2);
}

.viz-table th { color: var(--viz-label); font-weight: var(--fw-semibold); }
.viz-table td.num,
.viz-table th.num { text-align: right; font-variant-numeric: tabular-nums; }
.viz-table td strong { color: var(--viz-ink); }

/* ── Sell-through meter where it sits in a result row ────────────────── */
.opp-sth-meter { margin: -2px 0 var(--s2); }

@media (max-width: 720px) {
  .viz-rank-row { grid-template-columns: minmax(0, 1fr) 56px auto; }
  .viz-legend { gap: var(--s2) var(--s4); }
}

/* Where hue cannot be trusted — forced-colours mode strips background
   images and paints everything from the system palette — the marks fall
   back to system colours and keep their outlines, so a bar is still a bar
   and the legend swatches are still visible. */
@media (forced-colors: active) {
  .viz-seg,
  .viz-rank-bar,
  .viz-meter-fill,
  .viz-swatch { forced-color-adjust: none; border: 1px solid CanvasText; }
  .viz-tip { border: 1px solid CanvasText; }
}

@media (prefers-reduced-motion: reduce) {
  .viz-meter-fill,
  .viz-seg-label,
  .viz-tip { transition: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   Data tables — one grammar for every dense grid in the app

   Local Arbitrage, Listings, Inventory Health, Snipe, Shipping, Earnings
   and the lot and ladder tables had each grown their own padding, their
   own header treatment and their own idea of what a number column looks
   like. Nine tables, nine rhythms. This is the one reading grammar all of
   them now share:

     · the header is a fixed rail — it sticks, it never scrolls away, and
       it is the quietest type on the screen
     · every figure is right-aligned on tabular figures, so a column of
       money is a column and not a ragged list
     · the row is the unit: a stripe to hold the eye on the line, a hover
       to confirm which line, a left rail when the row means something
     · the columns that answer "what does this make?" carry a gold band,
       because that is the only question the seller opened the table to
       answer

   None of this changes what a table says. It changes how long it takes to
   find the one row worth acting on.
   ═══════════════════════════════════════════════════════════════════════ */
:root {
  --dt-pad-y: 10px;
  --dt-pad-x: 12px;

  /* Stripes and hovers are the brand teal at single-digit alpha: enough to
     hold the eye on one line across fourteen columns, never enough to read
     as a state. A zebra you can name the colour of is too strong. */
  --dt-zebra: rgba(17, 80, 87, .028);
  --dt-hover: rgba(17, 80, 87, .062);
  --dt-rule: var(--line-soft);

  /* The money band. Gold is the app's one accent, so the columns carrying
     the answer — net profit, ROI, price — are the only columns tinted with
     it, and the eye lands there before it reads anything else. */
  --dt-money: rgba(199, 154, 54, .07);
  --dt-money-edge: rgba(199, 154, 54, .3);

  /* The header rail: a hair of light at the top edge so it reads as a
     surface sitting above the rows, not as the first row painted grey. */
  --dt-head: linear-gradient(180deg, var(--card), var(--soft) 70%, var(--soft-2));
  --dt-head-lift: 0 8px 12px -10px rgba(6, 27, 29, .55);
}

.listings-table,
.fb-arb-table,
.inv-table,
.ship-table,
.er-table,
.opp-comp-table {
  /* separate, not collapse. Collapsed borders drop out from under a sticky
     header the moment it starts scrolling, and a collapsed table renders no
     box-shadow on a row at all — which is where the state rail lives. The
     spacing is zero, so the grid itself is unchanged. */
  border-collapse: separate;
  border-spacing: 0;
  font-variant-numeric: tabular-nums;
}

/* ── The header rail ──────────────────────────────────────────────────
   Sticky on every table now, not just the listings one. Column meaning is
   the first thing lost in a long scroll and the most expensive to go back
   for. */
.listings-table thead th,
.fb-arb-table thead th,
.inv-table thead th,
.ship-table thead th,
.er-table thead th,
.opp-comp-table thead th {
  position: sticky;
  top: 0;
  z-index: 3;
  padding: 9px var(--dt-pad-x) 8px;
  border: 0;
  background-color: var(--soft);
  background-image: var(--dt-head);
  box-shadow: inset 0 -1px 0 var(--line-strong), var(--dt-head-lift);
  color: var(--muted);
  font-family: var(--font-sans);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-caps);
  line-height: 1.3;
  text-align: left;
  text-transform: uppercase;
  vertical-align: bottom;
  white-space: nowrap;
}

/* ── The cells ────────────────────────────────────────────────────────── */
.listings-table tbody td,
.fb-arb-table tbody td,
.inv-table tbody td,
.ship-table tbody td,
.er-table tbody td,
.opp-comp-table tbody td {
  padding: var(--dt-pad-y) var(--dt-pad-x);
  border-bottom: 1px solid var(--dt-rule);
}

/* A totals row keeps the body's rhythm, so the figures in it stay in the
   same columns they are summing. */
.fb-arb-table tfoot td,
.inv-table tfoot td,
.er-table tfoot td { padding: var(--dt-pad-y) var(--dt-pad-x); }

/* The last hairline is the one the shell's own border already draws. */
.listings-table tbody tr:last-child > td,
.fb-arb-table tbody tr:last-child > td,
.inv-table tbody tr:last-child > td,
.ship-table tbody tr:last-child > td,
.er-table tbody tr:last-child > td,
.opp-comp-table tbody tr:last-child > td { border-bottom: 0; }

/* Ladders and comp lists are reference, not ranking — they stay compact
   rather than inheriting the scanning rhythm of a results table. Written
   at thead/tbody depth deliberately: at plain `th, td` they would lose the
   specificity contest with the rule above and quietly get the tall one. */
.opp-comp-table thead th, .opp-comp-table tbody td,
.ad-ladder-table thead th, .ad-ladder-table tbody td,
.neg-ladder-table thead th, .neg-ladder-table tbody td { padding: 7px 10px; }
.rsc-ladder thead th, .rsc-ladder tbody td { padding: 5px 8px; }

/* ── Figures ──────────────────────────────────────────────────────────
   A shared right edge, tabular figures, and contextual alternates off so a
   digit is never reshaped by the digit beside it. This is the whole reason
   a comparison lives in a table instead of a card. */
.listings-table .num,
.fb-arb-table .num,
.inv-table .num,
.ship-table .num,
.er-table .num,
.opp-comp-table .num,
.ship-table .ship-num,
.listings-table tbody td:nth-child(3),
.listings-table tbody td:nth-child(4),
.listings-table tbody td:nth-child(10) {
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "calt" 0;
}

/* ── The row ──────────────────────────────────────────────────────────
   Zebra first, because it is the floor every state paints over. */
.listings-table tbody tr,
.fb-arb-table tbody tr,
.inv-table tbody tr,
.ship-table tbody tr,
.er-table tbody tr,
.opp-comp-table tbody tr { transition: background-color var(--dur-1) var(--ease); }

/* The deals board stripes on a class, not on nth-child: each of its deals is two rows now (the
   row and its folded-away detail), so every second ELEMENT is a detail panel rather than every
   second deal. renderArbitrageRows() sets .is-alt. Every other table still counts rows. */
.listings-table tbody tr:nth-child(even),
.bud-table tbody tr:nth-child(even),
.fb-arb-table tbody tr.is-alt,
.inv-table tbody tr:nth-child(even),
.ship-table tbody tr:nth-child(even),
.er-table tbody tr:nth-child(even) { background-color: var(--dt-zebra); }

/* A row that means something outranks the stripe: dead capital, or the one
   goldmine on the board, must never be striped into the background. Each
   takes a 3px rail as well as a wash, so the state survives the hover that
   washes over it. */
.fb-arb-table tbody tr.fb-arb-row-goldmine {
  background-color: var(--gold-tint);
  box-shadow: inset 3px 0 0 var(--gold);
}
.inv-table tbody tr.inv-row-dead {
  background-color: var(--danger-tint);
  box-shadow: inset 3px 0 0 var(--danger-600);
}
.ship-table tbody tr.ship-mode-pick {
  background-color: var(--success-tint);
  box-shadow: inset 3px 0 0 var(--success-600);
}
.listings-table tbody tr.active {
  background-color: var(--warning-tint);
  box-shadow: inset 3px 0 0 var(--gold);
}

/* Hover confirms which line the eye is on. It comes after the states so it
   answers on every row, and the stateful rows deepen their own colour
   rather than losing it. */
.listings-table tbody tr:hover,
.fb-arb-table tbody tr:hover,
.inv-table tbody tr:hover,
.ship-table tbody tr:hover,
.er-table tbody tr:hover,
.opp-comp-table tbody tr:hover { background-color: var(--dt-hover); }

.fb-arb-table tbody tr.fb-arb-row-goldmine:hover { background-color: var(--gold-soft); }
.inv-table tbody tr.inv-row-dead:hover           { background-color: var(--danger-soft); }
.ship-table tbody tr.ship-mode-pick:hover        { background-color: var(--success-soft); }
.listings-table tbody tr.active:hover            { background-color: var(--warning-soft); }

/* An empty-state cell is a message, not a row — it takes neither. */
.fb-arb-table tbody tr:hover > td.fb-arb-empty { background-color: var(--card); }

/* ── The money columns ────────────────────────────────────────────────
   The band is a background-image, not a background, so it layers over
   whatever the row is doing — stripe, state or hover — instead of fighting
   it. The two hairlines make it a column rather than a smudge. */
.dt-money {
  background-image: linear-gradient(var(--dt-money), var(--dt-money));
  box-shadow: inset 1px 0 0 var(--dt-money-edge), inset -1px 0 0 var(--dt-money-edge);
}

.listings-table thead th.dt-money,
.fb-arb-table thead th.dt-money {
  background-image: linear-gradient(var(--dt-money), var(--dt-money)), var(--dt-head);
  box-shadow:
    inset 0 -1px 0 var(--line-strong),
    inset 1px 0 0 var(--dt-money-edge),
    inset -1px 0 0 var(--dt-money-edge),
    var(--dt-head-lift);
  color: var(--gold-ink);
}

/* One step up in size and weight from the columns around it. On a
   fourteen-column board that single step is the difference between reading
   the table and scanning it.

   Size and weight only — deliberately no colour. This selector outranks
   .fb-arb-profit.good/.bad, so a colour here would repaint every net-profit
   figure a flat ink and take the green/red read off the one column the
   whole board exists to answer. */
.fb-arb-table tbody td.dt-money {
  font-size: var(--fs-sm);
  font-weight: var(--fw-bold);
}
.listings-table tbody td.dt-money { font-size: var(--fs-base); }

/* ── Rank ─────────────────────────────────────────────────────────────
   The board is sorted, so the first three rows are the answer. The numeral
   says so on its own, without a badge. */
.fb-arb-table thead th.fb-arb-th-rank { text-align: center; }
.fb-arb-table tbody td.fb-arb-th-rank {
  text-align: center;
  color: var(--faint);
  font-weight: var(--fw-semibold);
  font-variant-numeric: tabular-nums;
}
/* .is-top3 for the same reason .is-alt exists: with a detail row between deals, "the first three
   rows" and "the first three deals" stopped being the same thing. */
.fb-arb-table tbody tr.is-top3 > td.fb-arb-th-rank,
.bud-table tbody tr:nth-child(-n+3) > td.fb-arb-th-rank {
  color: var(--gold-ink);
  font-weight: var(--fw-heavy);
}

/* ── The shell ────────────────────────────────────────────────────────
   Every dense table now sits in the same frame: a hairline card that
   scrolls in both axes, bounded in height so the sticky header has
   something to stick to and the controls above it stay on screen.

   The edge shadows are pure CSS: two white covers pinned to the content
   (background-attachment: local) sit on top of two shadows pinned to the
   frame, so a shadow only shows on a side that has more table to reach. No
   scroll handler, nothing to keep in sync. */
.table-wrap,
.fb-arb-table-wrap,
.ship-table-wrap,
.opp-comp-panel,
.er-ledger,
.inv-results:has(> table) {
  overflow: auto;
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background-color: var(--card);
  background-image:
    linear-gradient(to right, var(--card) 40%, var(--card-fade)),
    linear-gradient(to left,  var(--card) 40%, var(--card-fade)),
    linear-gradient(to right, rgba(6, 27, 29, .13), rgba(6, 27, 29, 0)),
    linear-gradient(to left,  rgba(6, 27, 29, .13), rgba(6, 27, 29, 0));
  background-position: 0 0, 100% 0, 0 0, 100% 0;
  background-repeat: no-repeat;
  background-size: 44px 100%, 44px 100%, 16px 100%, 16px 100%;
  background-attachment: local, local, scroll, scroll;
  box-shadow: var(--e1);
}

/* Bounded, so the header rail has something to stick to. :has() keeps this
   off the empty and resting states, which are messages and must not
   scroll; a browser without it simply keeps today's unbounded table. */
.inv-results:has(> table),
.fb-arb-table-wrap { max-height: min(76vh, 900px); }

/* ═══════════════════════════════════════════════════════════════════════
   The deals board
   The screen a seller stares at while deciding where their money goes, and
   the one they said "this window sucks" about. It carried fourteen columns,
   which fitted no window: the wrapper grew a horizontal scrollbar under the
   table, the table grew a 1040px floor to force it, the results block was
   capped at 1320px on a monitor with room to spare, and the box scrolled
   vertically inside a page that was already scrolling. The right-hand
   columns arrived cut off mid-word — a "What to say" button rendered as
   "What…" — and a number nobody can see answers nothing.

   Three rules here, in the order they matter:
     1. no scrollbar in either axis, ever — the page scrolls, the board does
        not;
     2. the board gets the width the window actually has;
     3. eight columns, being the buying decision and nothing else. The rest
        folds under the row that owns it.
   ═══════════════════════════════════════════════════════════════════════ */

/* Room. The overlay's shared 1320px column is right for prose and forms and
   wrong for a ranking. Measured off the overlay body — the board's own
   panel is capped, so the block breaks out of it with a negative margin and
   lands centred on the body's content edges.

   The cap used to be 2200px, which on a wide monitor was the whole window:
   the board became a white slab 880px wider than the card that had just
   produced it, sticking out on both sides of everything else on the screen,
   with the item name at one end and the money at the other and half a
   window of nothing in between. Wide enough for nine columns is the goal;
   as wide as the glass is not. Past this the columns stop getting denser
   and just spread, and a table stops being scannable and becomes a runway. */
#opportunity-section > .opportunity-overlay-body { container-type: inline-size; }

#fb-arb-results {
  --board-w: min(100cqw, 1560px);
  width: var(--board-w);
  max-width: none;
  margin-inline: calc((100% - var(--board-w)) / 2);
}

/* The count and the budget button are the right-hand end of the controls
   row. Only the count carried the auto margin, so on the narrower board the
   button wrapped to a second line and landed back at the left, under the
   sort dropdown, reading as one of the filters. Auto margins resolve per
   flex line, so giving the button its own keeps it at the right-hand end
   whichever line it ends up on. */
.fb-arb-controls #fb-arb-budget-btn { margin-left: auto; }

/* The bars are holding rows back and this says which and how many, between
   the switches that did it and the board that looks empty because of it. */
.fb-arb-cut {
  display: flex;
  align-items: center;
  gap: var(--s3);
  flex-wrap: wrap;
  margin: var(--s3) 0 0;
  padding: var(--s3) var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--soft);
  font-size: var(--fs-xs);
  color: var(--muted);
}
.fb-arb-cut-count { margin-right: auto; }

/* Small print under a board that is wider than any paragraph should be. Left
   at one measure it ended a third of the way across the frame and left the
   rest of the board blank, which is most of what "off centre" looked like.
   Flowed into columns it spans the same edges as the table it explains, and
   collapses back to one column on a narrow window by itself. */
#fb-arb-results .fb-arb-footnote {
  max-width: none;
  columns: 48ch;
  column-gap: var(--s7);
}

/* The frame. `clip` rather than `auto` or `hidden`: it never scrolls, it
   still rounds the corners off the header rail, and — unlike `hidden` — it
   is not a scroll container, so the sticky header keeps tracking the page
   the way it did when the box scrolled itself. */
.fb-arb-table-wrap.fb-arb-board-wrap {
  overflow: clip;
  max-height: none;
  background-image: none;
  /* The board answers to its own width, not the window's: between the
     sidebar, the overlay padding and this cap, viewport breakpoints are the
     wrong measure of whether nine columns fit. */
  container-type: inline-size;
  container-name: dealboard;
}

/* Column widths as shares, not pixels, so a wider window widens the whole
   board instead of pouring every spare pixel into one enormous Deal column
   with a hole in it. Hints to an auto layout rather than a fixed one: a long
   title reflows, and nothing is ever cut off to keep a track. */
.fb-arb-board { width: 100%; }
.fb-arb-board .fb-arb-th-rank   { width: 3%;  }
.fb-arb-board .fb-arb-th-deal   { width: 28%; }
.fb-arb-board .fb-arb-th-source { width: 8%;  }
.fb-arb-board .fb-arb-th-pay    { width: 10%; }
.fb-arb-board .fb-arb-th-net    { width: 11%; }
.fb-arb-board .fb-arb-th-days   { width: 9%;  }
.fb-arb-board .fb-arb-th-max    { width: 9%;  }
.fb-arb-board .fb-arb-th-offer  { width: 12%; }
.fb-arb-board .fb-arb-th-track  { width: 10%; }

/* The buy-side cell stacks four things — the number, what it saves, the
   verdict on saying it, and the draft. Right-aligned as a column so they
   share the edge the figures beside them use, rather than the button
   drifting a space to the left of everything above it. On a span inside the
   cell, not on the cell: a flex <td> stops stretching to the row height. */
.fb-arb-offer-stack {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
}
.fb-arb-offer-stack .btn { margin-top: 2px; }

/* Room to breathe now that there is room to spare. */
.fb-arb-board thead th   { padding: 11px var(--dt-pad-x) 10px; }
.fb-arb-board tbody td   { padding: 14px var(--dt-pad-x); }
.fb-arb-board .fb-arb-item  { min-width: 220px; gap: 12px; }
.fb-arb-board .fb-arb-thumb { width: 52px; height: 52px; flex: 0 0 52px; }
.fb-arb-board .fb-arb-source { white-space: nowrap; }

/* The verdict, the estimate warning and the way into the detail on one line:
   three short things that would otherwise take three lines each, on every
   row of thirty. Wraps rather than pushing the title column wider. */
.fb-arb-badges {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 10px;
  margin: 3px 0 1px;
}
.fb-arb-badges > .fb-verdict { margin-top: 0; }

/* ROI kept its place without keeping a column: the board is ranked by it,
   and a ranking key nobody can see is a ranking nobody can check. It sits
   under the net profit it comes from, the way $/day sits under the wait. */
.fb-arb-roi-line {
  display: block;
  margin-top: 3px;
  font-size: var(--fs-3xs);
  font-weight: var(--fw-semibold);
  color: var(--muted);
  letter-spacing: .01em;
}

/* ── The fold ─────────────────────────────────────────────────────────
   One click, on the row that raised the question. */
.fb-arb-more {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 0;
  border: 0;
  background: none;
  color: var(--brand-ink-2);
  font-family: inherit;
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  cursor: pointer;
}
.fb-arb-more:hover { text-decoration: underline; }
.fb-arb-more-chev {
  display: inline-block;
  font-size: var(--fs-xs);
  line-height: 1;
  transition: transform var(--dur-1) var(--ease);
}
.fb-arb-more.is-open .fb-arb-more-chev { transform: rotate(90deg); }

.fb-arb-detail-row { display: none; }
.fb-arb-detail-row.is-open { display: table-row; }
/* A panel, not a row: it takes neither the stripe nor the hover, because it
   is part of the deal above it rather than another line to compare. */
.fb-arb-table tbody tr.fb-arb-detail-row,
.fb-arb-table tbody tr.fb-arb-detail-row:hover { background-color: var(--soft); }

.fb-arb-board tbody td.fb-arb-detail {
  padding: 14px 18px 16px;
  border-bottom: 1px solid var(--dt-rule);
  box-shadow: inset 0 1px 0 var(--line-soft);
}

.fb-arb-detail-figs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
  gap: 12px 28px;
  max-width: 820px;
}
.fb-arb-fig { display: flex; flex-direction: column; gap: 3px; min-width: 0; }
.fb-arb-fig-label {
  font-size: var(--fs-3xs);
  font-weight: var(--fw-bold);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--faint);
}
.fb-arb-fig-value {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "calt" 0;
}
.fb-arb-fig-evidence { margin-top: 14px; }
/* Prose, which is why it was never a column: it wraps to a readable measure
   here instead of stacking four words deep in a 220px cell. */
.fb-arb-detail .fb-arb-evidence {
  max-width: 96ch;
  font-size: var(--fs-2xs);
  line-height: 1.5;
}

/* ── Narrow ───────────────────────────────────────────────────────────
   Below the width eight columns need, the row stacks. Never a sideways
   scroll: that is the thing this whole section exists to remove. */
@container dealboard (max-width: 1080px) {
  .fb-arb-board,
  .fb-arb-board > tbody { display: block; }
  .fb-arb-board > thead { display: none; }

  .fb-arb-board > tbody > tr.fb-arb-row {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
    align-items: start;
    gap: 12px 22px;
    padding: 16px;
    border-bottom: 1px solid var(--dt-rule);
  }

  /* Each cell becomes the same labelled figure the row detail uses: the
     column heading above the number rather than beside it. Left-aligned,
     because a card has no shared right edge for figures to line up on. */
  .fb-arb-board > tbody > tr.fb-arb-row > td {
    display: block;
    padding: 0;
    border: 0;
    text-align: left;
  }
  .fb-arb-board > tbody > tr.fb-arb-row > td::before {
    content: attr(data-label);
    display: block;
    margin-bottom: 3px;
    font-size: var(--fs-3xs);
    font-weight: var(--fw-bold);
    text-transform: uppercase;
    letter-spacing: var(--tracking-caps);
    color: var(--faint);
    white-space: nowrap;
  }
  .fb-arb-board > tbody > tr > td.fb-arb-offer .fb-arb-offer-stack { align-items: flex-start; }

  .fb-arb-board > tbody > tr > td.fb-arb-deal {
    grid-column: 1 / -1;
    margin-bottom: 2px;
  }
  .fb-arb-board > tbody > tr > td.fb-arb-deal::before { content: none; }

  .fb-arb-board > tbody > tr > td.fb-arb-th-rank {
    grid-column: 1 / -1;
    text-align: left;
    color: var(--faint);
  }
  .fb-arb-board > tbody > tr > td.fb-arb-th-rank::before {
    content: "#";
    display: inline;
    margin: 0;
    color: inherit;
    font-size: inherit;
    letter-spacing: 0;
  }

  /* A band down a column means nothing once there are no columns; the size
     and weight that make it the answer stay. */
  .fb-arb-board > tbody > tr > td.dt-money {
    background-image: none;
    box-shadow: none;
  }

  .fb-arb-board > tbody > tr > td.fb-arb-empty { display: block; }
  .fb-arb-board > tbody > tr.fb-arb-detail-row.is-open { display: block; }
  .fb-arb-board > tbody > tr.fb-arb-detail-row > td { display: block; }
}

@media (prefers-reduced-motion: reduce) {
  .fb-arb-more-chev { transition: none; }
}

/* ── Status chips inside a table ──────────────────────────────────────
   Five components had invented five pill shapes at four sizes. One shape,
   one scale, and a hairline ring so a soft fill still has an edge against
   a striped row. */
.fb-verdict,
.inv-verdict,
.speed-days,
.speed-badge,
.er-chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 2px 8px;
  border-radius: var(--r-pill);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  line-height: 1.4;
  letter-spacing: .005em;
  white-space: nowrap;
  box-shadow: inset 0 0 0 1px rgba(6, 27, 29, .08);
}

/* ── Opportunity Finder rows ──────────────────────────────────────────
   Not a table — a scored result is carried by its photo — but it is read
   the same way, so it takes the same grammar: figures on a shared right
   edge in tabular numerals, a rule under the line that totals, and the
   profit line set as the answer it is. */
.opp-result-row {
  transition:
    border-color var(--dur-1) var(--ease),
    box-shadow var(--dur-2) var(--ease);
}
.opp-result-row:hover {
  border-color: var(--line-strong);
  box-shadow: var(--e2);
}

.opp-cost-line { padding: 1px 0; }
.opp-cost-line > span:first-child { color: var(--faint); }
.opp-cost-line strong {
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum" 1, "calt" 0;
}

/* The two lines that are conclusions rather than inputs sit below a rule,
   the way a total does on a receipt. */
.opp-cost-line.total,
.opp-cost-line.profit {
  margin-top: 3px;
  padding-top: 4px;
  border-top: 1px solid var(--line-soft);
}
.opp-cost-line.profit strong {
  font-size: var(--fs-xs);
  font-weight: var(--fw-heavy);
}

@media (prefers-reduced-motion: reduce) {
  .listings-table tbody tr,
  .fb-arb-table tbody tr,
  .inv-table tbody tr,
  .ship-table tbody tr,
  .er-table tbody tr,
  .opp-comp-table tbody tr,
  .opp-result-row { transition: none; }
}

/* Where hue and shadow cannot be trusted, the grammar falls back to
   structure: the header keeps a real border and the money columns keep
   real edges, so the hierarchy survives with no colour at all. */
@media (forced-colors: active) {
  .listings-table thead th,
  .fb-arb-table thead th,
  .inv-table thead th,
  .ship-table thead th,
  .er-table thead th,
  .opp-comp-table thead th { border-bottom: 2px solid CanvasText; }
  .dt-money { border-left: 1px solid CanvasText; border-right: 1px solid CanvasText; }
}

/* ═══════════════════════════════════════════════════════════════════════
   The Dashboard
   The front page had become a stack of equal bands — hero, three money
   bands, a dice band, then the tiles — each the full width of the page and
   each about as loud as the one above it. Nothing was first. This gives it
   one order to read in: masthead, the three things worth doing, the money,
   the counts, then the market.

   Everything here is scoped to the dashboard's own classes. .stat-card and
   .dash-earnings are used on other screens and keep their base rules.
   ═══════════════════════════════════════════════════════════════════════ */

.dash-hero {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: start;
  gap: var(--s5) var(--s7);
  /* The bottom padding is the landing pad for .dash-quick, which sits up over
     this edge by --s9. What is left of it is the air under the status chips,
     so the two have to move together. */
  padding: var(--s8) var(--s8) calc(var(--s10) + var(--s6));
  margin-bottom: 0;
  border-radius: var(--r-2xl);
  box-shadow: var(--e5);
}

/* Two lit corners and a fine grid. The panel was a flat gradient, which at
   this size reads as a coloured rectangle; the light has to come from
   somewhere for it to read as a surface. The grid is 1px lines at under 3%
   — invisible as lines, but it stops the large dark area looking empty. */
.dash-hero::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(38rem 24rem at 88% -20%, rgba(224, 187, 99, .22), transparent 68%),
    radial-gradient(30rem 20rem at 6% 120%, rgba(42, 139, 147, .30), transparent 70%),
    linear-gradient(rgba(246, 251, 251, .028) 1px, transparent 1px) 0 0 / 100% 34px,
    linear-gradient(90deg, rgba(246, 251, 251, .028) 1px, transparent 1px) 0 0 / 34px 100%;
  mask-image: linear-gradient(180deg, #000 40%, transparent);
}

/* Everything the hero holds sits above that wash. */
.dash-hero > * { position: relative; z-index: 1; }

.dash-hero-copy { max-width: 62ch; }

.dash-hero h1 {
  margin-top: var(--s3);
  font-size: clamp(var(--fs-4xl), 4.4vw, var(--fs-display));
  font-weight: var(--fw-bold);
  /* The lockup catches the light the way every other gold surface in the
     app does, rather than being white text on a dark panel. */
  background: linear-gradient(96deg, #ffffff 22%, var(--gold-pale) 62%, var(--gold-light));
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* background-clip:text paints the gradient only where the element's own
     background box is. h1 carries --lh-tight (1.08), so that box stops just
     below the baseline and the descenders of the two "g"s in Listing/Engine
     fell outside it — the gradient never reached them and the tails rendered
     as nothing. Pad the box down past the descenders and take the same amount
     back off the layout, so the lockup is whole without moving anything. */
  padding-bottom: .26em;
  margin-bottom: -.26em;
}

/* The trademark is a mark, not a word — set at the size a mark is set. */
.dash-hero-tm {
  font-size: .34em;
  font-weight: var(--fw-semibold);
  vertical-align: super;
  letter-spacing: 0;
}

.dash-hero .hero-copy {
  max-width: 54ch;
  margin-top: var(--s4);
  font-size: var(--fs-lg);
  line-height: 1.5;
  color: rgba(246, 251, 251, .78);
}

/* Top-right, level with the eyebrow. These are the housekeeping actions — an
   import and a refresh — and putting them anywhere lower puts them in
   competition with the three cards below, which are the actual offer. */
.dash-hero-actions {
  grid-row: 1;
  grid-column: 2;
  display: flex;
  align-items: center;
  gap: var(--s2);
  justify-self: end;
}

/* ── Status chips ──────────────────────────────────────────────────────
   Three facts about the account, on the front page: connected or not, key
   or no key, and what this costs. The dot carries the state and the word
   repeats it, so a chip is never colour alone. */
.dash-status {
  grid-column: 1 / -1;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-top: var(--s2);
}

.dash-chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: 5px var(--s3) 5px var(--s2);
  border: 1px solid rgba(246, 251, 251, .16);
  border-radius: var(--r-pill);
  background: rgba(246, 251, 251, .07);
  color: var(--on-dark-2);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  letter-spacing: var(--tracking-snug);
}

.dash-chip-dot {
  width: 7px;
  height: 7px;
  margin-left: var(--s1);
  border-radius: var(--r-pill);
  background: var(--on-dark-3);
  box-shadow: 0 0 0 3px rgba(246, 251, 251, .07);
}

.dash-chip.is-on { color: var(--on-dark); border-color: rgba(63, 189, 139, .38); }
.dash-chip.is-on .dash-chip-dot { background: #3fbd8b; box-shadow: 0 0 0 3px rgba(63, 189, 139, .18); }
.dash-chip.is-off { border-color: rgba(224, 187, 99, .38); }
.dash-chip.is-off .dash-chip-dot { background: var(--gold-light); box-shadow: 0 0 0 3px rgba(224, 187, 99, .16); }

.dash-chip--beta {
  padding-left: var(--s3);
  border-color: rgba(224, 187, 99, .30);
  background: rgba(224, 187, 99, .12);
  color: var(--gold-pale);
}

/* ── Quick actions ─────────────────────────────────────────────────────
   Lifted over the hero's bottom edge, which is what makes them read as the
   point of the masthead rather than as the next band down.

   This overlap is deliberate and wanted — it was taken out once and put back
   on request, so it is not a layout bug to be tidied away. The --s9 lift and
   the hero's matching bottom padding are one unit: change either and the
   cards either float off the panel or crash into the status chips. */
.dash-quick {
  position: relative;
  z-index: 2;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s4);
  margin: calc(var(--s9) * -1) 0 var(--s6);
  padding: 0 var(--s5);
}

.dash-act {
  position: relative;
  display: grid;
  grid-template-rows: auto auto 1fr auto;
  gap: var(--s3);
  overflow: hidden;
  padding: var(--s5) var(--s5) var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--card);
  box-shadow: var(--e3);
  text-align: left;
  cursor: pointer;
  transition: transform var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease);
}

/* These lift on hover where the rest of the app does not: they are targets,
   not content, and there is nothing beneath them for the movement to shift. */
.dash-act:hover { transform: translateY(-3px); box-shadow: var(--e4); border-color: var(--line-strong); }
.dash-act:active { transform: translateY(-1px); }
.dash-act:disabled { opacity: .55; cursor: progress; transform: none; box-shadow: var(--e2); }

.dash-act-icon {
  display: grid;
  place-items: center;
  width: 42px;
  height: 42px;
  border-radius: var(--r-md);
  border: 1px solid var(--teal-100);
  background: var(--teal-50);
  color: var(--brand-ink-2);
  font-size: var(--fs-xl);
  line-height: 1;
}

.dash-act-icon svg {
  width: 20px; height: 20px;
  fill: none; stroke: currentColor;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}

.dash-act-title {
  font-family: var(--font-display);
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-tight);
  color: var(--brand-ink-strong);
}

.dash-act-sub {
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--ink-2);
}

.dash-act-go {
  display: flex;
  align-items: center;
  gap: var(--s2);
  padding-top: var(--s3);
  border-top: 1px solid var(--line-soft);
  font-size: var(--fs-xs);
  font-weight: var(--fw-heavy);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--brand-ink-2);
}

.dash-act-arrow { transition: transform var(--dur-2) var(--ease-spring); }
.dash-act:hover .dash-act-arrow { transform: translateX(4px); }

/* The primary of the three. Gold ink and a gold rail rather than a gold
   fill: a fully gold card beside two white ones stops being a card and
   becomes a banner, and the app already spends its gold on buttons. */
.dash-act--gold {
  border-color: rgba(199, 154, 54, .40);
  background: linear-gradient(168deg, var(--gold-tint), var(--card) 46%);
  box-shadow: var(--e3), inset 0 1px 0 rgba(255, 255, 255, .6);
}

.dash-act--gold::before {
  content: "";
  position: absolute;
  inset: 0 0 auto;
  height: 3px;
  background: var(--grad-gold-flat);
}

.dash-act--gold:hover { border-color: var(--gold); box-shadow: var(--e4), var(--glow-gold); }
.dash-act--gold .dash-act-icon { border-color: rgba(199, 154, 54, .28); background: var(--gold-soft); color: var(--gold-ink); }
.dash-act--gold .dash-act-go { border-top-color: rgba(199, 154, 54, .22); color: var(--gold-ink); }

.dash-act--ai .dash-act-icon { border-color: var(--accent-line); background: var(--accent-soft); color: var(--accent); }
.dash-act--ai .dash-act-go { color: var(--accent); }
.dash-act--dice .dash-act-icon { border-color: var(--info-line); background: var(--info-soft); }

/* ── The money row ─────────────────────────────────────────────────────
   Three bands stacked full-width was three headline figures each claiming
   the page. Side by side they read as one answer with three parts, and the
   row costs nothing on a fresh install because every card in it stays
   hidden until a real figure exists. */
.dash-money-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  gap: var(--s4);
  align-items: stretch;
}

.dash-money-row:has(> :not(.hidden)) { margin-bottom: var(--s6); }

.dash-money-row .dash-earnings {
  flex-direction: column;
  align-items: stretch;
  justify-content: space-between;
  gap: var(--s4);
  margin-bottom: 0;
  padding: var(--s5);
  border-radius: var(--r-lg);
  border-color: var(--line);
  background:
    radial-gradient(120% 90% at 0% 0%, var(--success-tint), transparent 62%),
    var(--card);
}

.dash-money-row .dash-earnings-figure {
  font-family: var(--font-display);
  font-size: var(--fs-4xl);
  letter-spacing: var(--tracking-display);
}

.dash-money-row .dash-earnings-side {
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s4);
}

.dash-money-row .dash-earnings-spark { width: 130px; }
.dash-money-row .dash-earnings .btn { flex-shrink: 0; }

/* Each card keeps the colour its own screen uses, so the front page and the
   page behind it agree about what kind of money this is. */
.dash-money-row .dash-pipeline {
  background: radial-gradient(120% 90% at 0% 0%, var(--info-tint), transparent 62%), var(--card);
}
.dash-money-row .dash-snipe {
  background: radial-gradient(120% 90% at 0% 0%, var(--warning-tint), transparent 62%), var(--card);
}

/* ── Stat tiles ────────────────────────────────────────────────────────
   The same four numbers, with an icon to sort by, room to breathe, and a
   figure set at the size of the thing being reported. */
.dash-stats {
  gap: var(--s4);
  margin-bottom: var(--s6);
}

.dash-stats .stat-card {
  padding: var(--s5) var(--s5) var(--s4);
  border-radius: var(--r-lg);
  transition: transform var(--dur-2) var(--ease), box-shadow var(--dur-2) var(--ease),
              border-color var(--dur-2) var(--ease);
}

.dash-stats .stat-card:hover { transform: translateY(-2px); }

.dash-stats .stat-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s3);
}

.dash-stats .stat-label { font-size: var(--fs-xs); }

.dash-stats .stat-icon {
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border-radius: var(--r-sm);
  background: var(--soft-2);
  color: var(--brand-ink-2);
}

.dash-stats .stat-icon svg {
  width: 16px; height: 16px;
  fill: none; stroke: currentColor;
  stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round;
}

/* One tinted tile per measure, matched to the rail already on its top edge. */
.dash-stats .stat-card:nth-child(3) .stat-icon { background: var(--gold-soft); color: var(--gold-ink); }
.dash-stats .stat-card:nth-child(4) .stat-icon { background: var(--warning-soft); color: var(--warning); }

.dash-stats .stat-card strong {
  margin: var(--s4) 0 var(--s2);
  font-size: var(--fs-4xl);
}

.dash-stats .stat-note { font-size: var(--fs-xs); color: var(--muted); }

/* ── Market pulse ──────────────────────────────────────────────────────
   The same measurement Rising Now draws at full size, on the front page. */
.dash-pulse {
  margin-bottom: var(--s6);
  padding: var(--s6);
  border-radius: var(--r-xl);
}

.dash-pulse .viz-panel-head { margin-bottom: var(--s5); }
.dash-pulse .viz-panel-head h3 { font-size: var(--fs-xl); }

/* The resting state. The art behind it is a watermark, at an opacity where
   it cannot be read as a measurement of anything. */
.dash-pulse-rest {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 380px);
  align-items: center;
  gap: var(--s7);
  padding: var(--s2) 0 var(--s3);
}

.dash-pulse-art {
  display: flex;
  align-items: flex-end;
  gap: var(--s2);
  height: 132px;
  opacity: .16;
  /* Fades out upward: it is a texture where the chart will be, not a chart. */
  mask-image: linear-gradient(180deg, transparent, #000 72%);
  pointer-events: none;
}

.dash-pulse-art span {
  flex: 1;
  border-radius: var(--r-3xs) var(--r-3xs) 0 0;
  background: var(--on-dark);
}

.dash-pulse-art span:nth-child(1)  { height: 22%; }
.dash-pulse-art span:nth-child(2)  { height: 46%; }
.dash-pulse-art span:nth-child(3)  { height: 34%; }
.dash-pulse-art span:nth-child(4)  { height: 62%; }
.dash-pulse-art span:nth-child(5)  { height: 40%; }
.dash-pulse-art span:nth-child(6)  { height: 78%; }
.dash-pulse-art span:nth-child(7)  { height: 52%; }
.dash-pulse-art span:nth-child(8)  { height: 30%; }
.dash-pulse-art span:nth-child(9)  { height: 58%; }
.dash-pulse-art span:nth-child(10) { height: 36%; }
.dash-pulse-art span:nth-child(11) { height: 68%; }
.dash-pulse-art span:nth-child(12) { height: 26%; }

.dash-pulse-rest-copy {
  display: grid;
  justify-items: start;
  gap: var(--s3);
  max-width: 56ch;
}

.dash-pulse-rest-title {
  font-family: var(--font-display);
  font-size: var(--fs-2xl);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-tight);
  color: var(--on-dark);
}

.dash-pulse-rest-body {
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--on-dark-2);
}

.dash-pulse-rest-note { font-size: var(--fs-xs); color: var(--on-dark-3); }

/* The read, once a scan exists: the headline on the left, the distribution
   on the right. The sentence is the answer; the columns are why. */
.dash-pulse-live {
  display: grid;
  grid-template-columns: 264px minmax(0, 1fr);
  gap: var(--s6);
  align-items: center;
}

.dash-pulse-figure {
  font-family: var(--font-display);
  font-size: var(--fs-5xl);
  font-weight: var(--fw-bold);
  line-height: var(--lh-tight);
  letter-spacing: var(--tracking-display);
  color: var(--on-dark);
}

.dash-pulse-figure-of { font-size: .46em; font-weight: var(--fw-semibold); color: var(--on-dark-3); }

/* The share climbing is polarity, not a score — 30% climbing is a market, not
   a failure — so the meter takes the app's up-colour rather than the
   good/mid/low ramp. The ramp is also a light-mode one: on this panel its
   track and fill land two steps apart and the fill disappears. */
.dash-pulse .viz-meter-track { background: var(--viz-up-soft); }
.dash-pulse .viz-meter-fill  { background: var(--viz-up); }
.dash-pulse .viz-meter-value { color: var(--on-dark-2); }

.dash-pulse-label {
  margin: var(--s2) 0 var(--s4);
  font-size: var(--fs-sm);
  line-height: var(--lh-normal);
  color: var(--on-dark-2);
}

.dash-pulse-movers {
  display: grid;
  gap: var(--s2);
  margin-top: var(--s4);
  padding-top: var(--s4);
  border-top: 1px solid var(--dark-line);
}

.dash-pulse-mover {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--s3);
  font-size: var(--fs-xs);
  color: var(--on-dark-2);
}

.dash-pulse-mover-name {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.dash-pulse-mover-pct {
  flex-shrink: 0;
  font-weight: var(--fw-bold);
  font-variant-numeric: tabular-nums;
}

.dash-pulse-mover-pct.up { color: var(--viz-up); }
.dash-pulse-mover-pct.down { color: var(--viz-down); }

/* ── Entrance ──────────────────────────────────────────────────────────
   The blocks arrive in the order they are meant to be read, ~40-60ms
   apart. Short enough that it never delays a click; long enough that the
   page assembles rather than appearing. */
.dashboard-section > .dash-hero,
.dashboard-section > .dash-quick,
.dashboard-section > .dash-money-row,
.dashboard-section > .dash-stats,
.dashboard-section > .dash-pulse,
.dashboard-section > .content-grid { animation: dash-rise var(--dur-4) var(--ease-out) both; }

.dashboard-section > .dash-quick     { animation-delay: .06s; }
.dashboard-section > .dash-money-row { animation-delay: .12s; }
.dashboard-section > .dash-stats     { animation-delay: .16s; }
.dashboard-section > .dash-pulse     { animation-delay: .20s; }
.dashboard-section > .content-grid   { animation-delay: .24s; }

@keyframes dash-rise {
  from { opacity: 0; transform: translateY(10px); }
  to   { opacity: 1; transform: none; }
}

/* ── Narrower windows ────────────────────────────────────────────────── */
@media (max-width: 1240px) {
  .dash-quick { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .dash-pulse-live { grid-template-columns: minmax(0, 1fr); }
  /* The watermark is the first thing to go: it is texture, and the copy beside
     it is the whole point of the resting state. */
  .dash-pulse-rest { grid-template-columns: minmax(0, 1fr); }
  .dash-pulse-art { display: none; }
}

@media (max-width: 1024px) {
  .dash-hero { grid-template-columns: minmax(0, 1fr); padding: var(--s7) var(--s6) var(--s9); }
  /* Back into the single column with everything else. Left at `grid-column: 2`
     it would open an implicit second track and sit off to the side of a hero
     that no longer has a right-hand side. */
  .dash-hero-actions { grid-row: auto; grid-column: 1; justify-self: start; }
  .dash-quick { margin-top: calc(var(--s8) * -1); padding: 0 var(--s4); }
  .dash-stats { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* One per row only once two of them would be narrower than their own copy. */
@media (max-width: 760px) {
  .dash-quick { grid-template-columns: minmax(0, 1fr); }
}

/* The shared .stats-grid rule drops to one column here; .dash-stats is later in
   the sheet, so it has to say so itself or the two-up above would outrank it. */
@media (max-width: 640px) {
  .dash-stats { grid-template-columns: minmax(0, 1fr); }
}

@media (prefers-reduced-motion: reduce) {
  .dashboard-section > .dash-hero,
  .dashboard-section > .dash-quick,
  .dashboard-section > .dash-money-row,
  .dashboard-section > .dash-stats,
  .dashboard-section > .dash-pulse,
  .dashboard-section > .content-grid { animation: none; }
  .dash-act:hover,
  .dash-stats .stat-card:hover { transform: none; }
  .dash-act:hover .dash-act-arrow { transform: none; }
}

/* With hue and shadow untrustworthy, the cards still need edges and the
   primary action still has to be the one that looks different. */
@media (forced-colors: active) {
  .dash-act { border: 1px solid CanvasText; }
  .dash-act--gold { border-width: 2px; }
  .dash-chip { border: 1px solid CanvasText; }
  .dash-pulse-art { display: none; }
}

/* ═══════════════════════════════════════════════════════════════════════
   Themes — one design system, two lightings

   The app was built light, with dark chrome: a deep-teal masthead band, a
   deep-teal sidebar, deep-teal hero panels. That contrast IS the product's
   look, so the dark theme keeps it rather than flattening everything to
   one grey. What changes is the paper the content is printed on.

   Three rules this block obeys:

     1. Nothing here is an inversion. Every value was re-stepped from the
        same hue at a lightness that works on a dark surface, then checked.
        An inverted palette puts a light-mode mid-tone on near-black, where
        it either glows or vanishes.

     2. Gold stays gold. The one accent does not flip. A gold button is the
        same gold button in both themes — that is the thing a seller
        recognises the app by.

     3. The permanently-dark surfaces do not move. The sidebar, the
        masthead band, the hero panels and the drawer headers are deep teal
        in both themes. Their type is on the constant --gold-note and
        --pale-* roles precisely so it cannot follow the theme off them.

   Contrast was measured, not judged. Every text role below clears 4.5:1 on
   --card, on --soft and on --soft-2 (the surfaces text actually lands on),
   every filled semantic swatch clears 4.5:1 against --on-solid, and the
   two neutral tiers stay a visible step apart so hierarchy survives.

   Applied by data-theme on <html>, set by an inline script in the head
   before first paint — so a seller who chose dark never sees a white
   flash. prefers-color-scheme is the default when nothing was chosen; the
   choice, once made, wins and persists.
   ═══════════════════════════════════════════════════════════════════════ */
:root[data-theme="dark"] {
  color-scheme: dark;

  /* ── Surfaces. Teal-tinted, never neutral grey: the same pull toward the
     brand the photo editor's greys already had. Four steps, each a
     readable jump from the last, so a card on the page and a well inside
     the card are still telling you which is which. */
  --page: #061518;
  --card: #0e2327;
  --soft: #12292e;
  --soft-2: #173338;
  --line: #22474e;
  --line-soft: #1a3a40;
  --line-strong: #31606a;

  /* ── Text. Not pure white — #ffffff on a dark panel vibrates and reads
     thinner than it is. This is the app's off-white pulled a shade cooler,
     with the tiers spaced so "secondary" still looks secondary rather than
     just dimmer. */
  --ink: #ecf5f5;
  --ink-2: #b6c9cd;
  --muted: #93a9ae;
  --faint: #7b9197;
  --text-strong: #f4fcfc;

  /* The label on a filled swatch flips with the fill. In dark the fills
     are the bright end of each hue, so their label is near-black. */
  --on-solid: #06181a;

  /* Gold and brand teal AS INK — the surfaces they name keep their own
     values above; only the type steps up into the light. */
  --gold-ink: #e7c47e;
  --brand-ink: #a6d2d8;
  --brand-ink-2: #8fc4cb;
  --brand-ink-strong: #d7ecee;

  /* Gold as a SURFACE. The pale gold callout is the one light-theme
     surface that could not survive: #fff4d7 on a dark page is a lamp.
     This is the same hue taken to a wash, and --gold-ink reads 9.4:1 on
     it — a better figure than the light theme manages. */
  --gold-soft: #302614;
  --gold-tint: #241c0f;

  /* ── Semantic families. Same five rungs, re-stepped: the text end goes
     up, the fill and wash ends go down, the hairline meets in the middle.
     Each text step clears 4.5:1 on --card AND on --soft-2, because a
     status word in a zebra-striped table lands on both. */
  --success: #43c08f;
  --success-600: #57cf9f;
  --success-line: #2a6b52;
  --success-soft: #11362c;
  --success-tint: #0d2a24;
  --danger: #f28a79;
  --danger-600: #ff9d8c;
  --danger-line: #7a3a32;
  --danger-hover: #4b2925;
  --danger-soft: #3d211d;
  --danger-tint: #2c1917;
  --warning: #e5b96a;
  --warning-600: #f2ca7d;
  --warning-line: #77592a;
  --warning-soft: #382b14;
  --warning-tint: #2a2110;
  --info: #63c6d8;
  --info-600: #7ad5e6;
  --info-line: #2d616c;
  --info-soft: #103138;
  --info-tint: #0d262c;
  --accent: #c3a4dc;
  --accent-600: #d2b9e8;
  --accent-line: #55406a;
  --accent-soft: #2a2137;
  --accent-tint: #21192b;
  --wine: #ec8fa9;
  --wine-soft: #3d1e2a;

  /* Third-party marks. These are the only colours in the app the app does
     not own, so they move as little as the surface allows — but eBay's
     blue at #0064d2 reads 2.9:1 on --card, which is a badge nobody can
     read. Same hue, lifted to the first step that clears 4.5:1. */
  --src-ebay: #4f9ceb;
  --src-facebook: #5f9df6;
  --src-craigslist: #ab8dd7;
  --src-dealfeeds: #e08d42;

  /* ── Elevation. On a dark page a shadow cannot say "above" on its own —
     there is no light for it to block. So the shadows go blacker and the
     surface itself does the lifting: --card sits above --page, and the
     inner highlight draws the lit top edge that sells the effect. */
  --e1: 0 1px 2px rgba(0, 0, 0, .40), 0 1px 3px rgba(0, 0, 0, .28);
  --e2: 0 1px 2px rgba(0, 0, 0, .44), 0 4px 12px rgba(0, 0, 0, .34);
  --e3: 0 2px 4px rgba(0, 0, 0, .44), 0 12px 28px rgba(0, 0, 0, .44);
  --e4: 0 4px 8px rgba(0, 0, 0, .48), 0 24px 60px rgba(0, 0, 0, .56);
  --e5: 0 8px 16px rgba(0, 0, 0, .52), 0 40px 90px rgba(0, 0, 0, .66);
  --e3-up: 0 -2px 4px rgba(0, 0, 0, .44), 0 -12px 28px rgba(0, 0, 0, .44);
  --inset-hi: inset 0 1px 0 rgba(246, 251, 251, .06);
  --inset-hi-strong: inset 0 1px 0 rgba(246, 251, 251, .10);
  --grad-sheen: linear-gradient(180deg, rgba(255, 255, 255, .14), rgba(255, 255, 255, 0));

  /* The white halo in the light ring would be a hole punched in a dark
     page; the ring separates from the page colour instead. */
  --focus-ring: var(--focus-ring-dark);

  /* Chrome on the masthead band. The band is dark in both themes, so this
     stops being a white slab and becomes glass. */
  --search-bg: rgba(246, 251, 251, .09);
  --search-bg-focus: rgba(246, 251, 251, .15);
  --search-line: rgba(246, 251, 251, .17);
  --ghost-bg: rgba(246, 251, 251, .07);
  --ghost-bg-hover: rgba(246, 251, 251, .14);
  --ghost-line: rgba(246, 251, 251, .15);
  --ghost-line-hover: rgba(246, 251, 251, .28);

  --card-fade: rgba(14, 35, 39, 0);
  --skeleton-sheen: rgba(246, 251, 251, .09);
  --scroll-thumb: rgba(246, 251, 251, .16);
  --scroll-thumb-hover: rgba(246, 251, 251, .30);

  /* ── Data tables. Same grammar: a stripe you cannot name the colour of,
     a hover that confirms the line, a gold band on the columns that answer
     the question. On dark the stripe has to come from light rather than
     from teal, or it disappears into the surface. */
  --dt-zebra: rgba(246, 251, 251, .028);
  --dt-hover: rgba(246, 251, 251, .07);
  --dt-money: rgba(224, 187, 99, .07);
  --dt-money-edge: rgba(224, 187, 99, .34);
  --dt-head: linear-gradient(180deg, #1c3d44, var(--soft-2) 70%, var(--soft));
  --dt-head-lift: 0 8px 12px -10px rgba(0, 0, 0, .8);

  /* The masthead band. Light needs a hard teal-950 ramp because it has to
     separate from a near-white page. Here the page is already deep, so the
     band is a longer, softer lift with the gold bloom carried further —
     the same light, one stop down. */
  --band:
    radial-gradient(72rem 26rem at 6% -8rem, rgba(199, 154, 54, .16), transparent 68%),
    linear-gradient(180deg, #0a2429 0, #082024 90px, rgba(6, 21, 24, 0) 320px);
}

/* ── Dark-theme corrections ─────────────────────────────────────────────
   Rules whose light-theme value was right for the reason it was written,
   and wrong once the paper goes dark. Kept to a handful on purpose: if a
   component needs more than this, the fix belongs in its own rule reaching
   for a role, not in a pile of overrides down here. */

/* A scrim has to be darker than what it dims, and on a dark page the
   light-theme scrim barely registers as a layer at all. */
:root[data-theme="dark"] .modal-overlay,
:root[data-theme="dark"] .opportunity-overlay,
:root[data-theme="dark"] .edit-drawer-overlay { background: rgba(2, 12, 14, .74); }

/* An empty-state medallion is a lit disc on the surface behind it. In
   light that is white on off-white; in dark, --card on --card is nothing. */
:root[data-theme="dark"] .state-icon { background: var(--soft-2); }

/* Product photography is shot on white and stays on white — a matte that
   followed the theme would show every JPEG's own white background as a
   bright rectangle inside a dark tile. These are the app's only permanent
   whites, and they are correct. */
:root[data-theme="dark"] .pl-photo img,
:root[data-theme="dark"] .inv-thumb { background: #ffffff; }

/* ── The theme control ──────────────────────────────────────────────────
   Three states, not two: Light, Dark and Auto — because "follow the OS" is
   a real answer and a two-way switch cannot express it. A segmented
   control rather than a cycling button, so all three are one click away
   and the current one is visible without being read.

   It sits on the masthead band, which is deep teal in BOTH themes, so this
   component is styled once and looks identical in each. */
.theme-switch {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
  padding: 3px;
  border: 1px solid var(--search-line);
  border-radius: var(--r-pill);
  background: rgba(4, 23, 26, .38);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, .34);
}

/* The gold pill is one element that slides, not three that fade. A moving
   object tells you where the selection went; three crossfades do not. */
.theme-switch-thumb {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 30px;
  height: 30px;
  border-radius: var(--r-pill);
  background: var(--grad-gold-flat);
  box-shadow: var(--glow-gold), var(--inset-hi-strong);
  transform: translateX(calc(var(--theme-i, 0) * 30px));
  transition: transform var(--dur-3) var(--ease-spring);
  pointer-events: none;
}

.theme-switch-opt {
  position: relative;
  z-index: 1;
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--on-dark-3);
  transition: color var(--dur-2) var(--ease);
}

.theme-switch-opt svg {
  width: 15px;
  height: 15px;
  fill: none;
  stroke: currentColor;
  stroke-width: 1.7;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.theme-switch-opt:hover { color: var(--on-dark); }
/* The selected one is read off the gold, so it takes the gold's own ink. */
.theme-switch-opt[aria-checked="true"] { color: var(--on-gold); }
.theme-switch-opt[aria-checked="true"] svg { stroke-width: 2; }
.theme-switch-opt:focus-visible { box-shadow: var(--focus-ring-dark); }

/* ── Changing theme ─────────────────────────────────────────────────────
   A hard cut between two full-page palettes reads as a glitch. One short
   cross-fade on colour only — never on layout, never on transform — makes
   it read as a light being turned. It is switched on for the length of the
   change and switched off again, so nothing in the app carries a global
   transition around with it for the rest of the session. */
@media (prefers-reduced-motion: no-preference) {
  html.theme-switching,
  html.theme-switching *,
  html.theme-switching *::before,
  html.theme-switching *::after {
    transition:
      background-color var(--dur-3) var(--ease),
      background-image var(--dur-3) var(--ease),
      border-color var(--dur-3) var(--ease),
      color var(--dur-3) var(--ease),
      fill var(--dur-3) var(--ease),
      stroke var(--dur-3) var(--ease),
      box-shadow var(--dur-3) var(--ease) !important;
  }
}

/* With colour untrustworthy, the selected segment needs an edge to say so. */
@media (forced-colors: active) {
  .theme-switch-thumb { display: none; }
  .theme-switch-opt[aria-checked="true"] { border: 2px solid CanvasText; }
}

/* ═══════════════════════════════════════════════════════════════════════
   Deal Radar
   The one screen whose subject is a machine working while nobody watches,
   so its visual job differs from every other board here: not "rank these
   rows" but "here is what is running, and here is what it caught". Two
   stacks, deliberately unalike — watch cards read as instruments (state
   pill, terms, last reading), alert cards read as finds (image, one
   sentence, the money, and the two things to do about it).
   ═══════════════════════════════════════════════════════════════════════ */

/* The sidebar's unread count. Drawn from data-count rather than as text, so
   the workspace tab bar keeps taking its title from the button's
   textContent without picking the number up with it. */
.nav-badge {
  margin-left: auto;
  flex-shrink: 0;
  min-width: 18px;
  padding: 0 5px;
  border-radius: var(--r-pill);
  background: var(--grad-gold-flat);
  color: var(--on-gold);
  font-size: var(--fs-3xs);
  font-weight: var(--fw-bold);
  line-height: 18px;
  text-align: center;
  box-shadow: 0 0 0 3px rgba(255, 255, 255, .06);
}
.nav-badge::after { content: attr(data-count); }

/* ── The master strip ──────────────────────────────────────────────── */
.rad-master {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s3) var(--s4);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--line);
  border-left: 4px solid var(--gold);
  border-radius: var(--r-md);
  background: var(--card);
  box-shadow: var(--e1);
}

.rad-switch {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  cursor: pointer;
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--ink);
}
.rad-switch input { position: absolute; opacity: 0; width: 0; height: 0; }

.rad-switch-track {
  position: relative;
  width: 40px;
  height: 22px;
  border-radius: var(--r-pill);
  background: var(--line-strong);
  transition: background-color .18s ease;
}
.rad-switch-track::after {
  content: '';
  position: absolute;
  top: 3px;
  left: 3px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: #fff;
  box-shadow: var(--e1);
  transition: transform .18s ease;
}
.rad-switch input:checked + .rad-switch-track { background: var(--success-600); }
.rad-switch input:checked + .rad-switch-track::after { transform: translateX(18px); }
.rad-switch input:focus-visible + .rad-switch-track { box-shadow: 0 0 0 3px rgba(199, 154, 54, .45); }

.rad-state { font-size: var(--fs-sm); color: var(--muted); }
.rad-master-actions { display: flex; gap: var(--s2); margin-left: auto; }

.rad-settings {
  margin-top: var(--s3);
  padding: var(--s3) var(--s4);
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-md);
  background: var(--soft);
}

.rad-note {
  margin-top: var(--s2);
  max-width: 90ch;
  font-size: var(--fs-xs);
  line-height: 1.55;
  color: var(--muted);
}

.rad-check {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--fs-sm);
  color: var(--ink-2);
  cursor: pointer;
}

/* Whether a notification can physically reach this desktop. Green when it
   can, gold when it cannot — never hidden, because a seller who believes
   they will be told and will not be is the one failure this cannot afford. */
.rad-channel {
  margin-top: var(--s3);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--line);
  border-left-width: 4px;
  border-radius: var(--r-sm);
  font-size: var(--fs-sm);
  line-height: 1.55;
}
.rad-channel--ok   { border-color: var(--success-line); border-left-color: var(--success-600); background: var(--success-tint); color: var(--ink); }
.rad-channel--warn { border-color: var(--warning-line); border-left-color: var(--warning-600); background: var(--warning-tint); color: var(--ink); }

/* ── The watch editor ──────────────────────────────────────────────── */
.rad-form {
  /* margin-block, not `margin: x 0`. The overlay centres its direct children
     with `margin-inline: auto` (.opportunity-overlay-body > *); the shorthand
     would reset that to 0 and this panel would sit hard against the left of
     the column while the panels above and below it stayed centred — which is
     invisible until the window is wider than 1320 + the column padding. */
  margin-block: var(--s5);
  padding: var(--s5);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  background: var(--card);
  box-shadow: var(--e2);
}
.rad-form-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  margin-bottom: var(--s3);
}
.rad-form-head h3 { font-family: var(--font-display); font-size: var(--fs-lg); color: var(--ink); }

/* Four short legends instead of one long form: what, where, what is worth
   waking you for, how often. The third one is the whole feature. */
.rad-form-legend {
  margin: var(--s4) 0 var(--s2);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--muted);
}

.rad-sources { display: flex; flex-wrap: wrap; gap: var(--s2) var(--s4); padding-top: var(--s1); }
/* These sit inside an .opp-field, whose label rule sets small caps and wide
   tracking — right for "SITES TO READ" above them, wrong for a site name and
   badly wrong for the sentence explaining why one of them is unavailable. */
.rad-sources .rad-check {
  text-transform: none;
  letter-spacing: var(--tracking-normal);
  font-size: var(--fs-sm);
  font-weight: var(--fw-normal);
  color: var(--ink-2);
}
.rad-source-note { font-size: var(--fs-xs); color: var(--muted); }

.rad-form-actions {
  display: flex;
  align-items: center;
  gap: var(--s3);
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--line-soft);
}
.rad-form-actions .btn-primary { margin-left: auto; }
.rad-form-error { flex: 1; font-size: var(--fs-sm); color: var(--danger); }

/* ── The watch cards ───────────────────────────────────────────────── */
.rad-watches {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
  gap: var(--s4);
  /* margin-block for the same reason as .rad-form above — the inline shorthand
     would knock this grid out of the column's centre line. */
  margin-block: var(--s5);
}

.rad-watch {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  padding: var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
  box-shadow: var(--e1);
}
.rad-watch.is-paused { background: var(--soft); opacity: .82; }

.rad-watch-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s3);
}
.rad-watch-head h4 {
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--ink);
}

.rad-watch-terms { font-size: var(--fs-sm); color: var(--ink-2); }
.rad-watch-bar,
.rad-watch-last { font-size: var(--fs-xs); color: var(--muted); line-height: 1.5; }
.rad-watch-found { font-size: var(--fs-xs); color: var(--success); font-weight: var(--fw-medium); }

.rad-watch-actions { display: flex; gap: var(--s2); margin-top: auto; padding-top: var(--s3); }

.rad-pill {
  flex-shrink: 0;
  padding: 2px 9px;
  border-radius: var(--r-pill);
  font-size: var(--fs-3xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  white-space: nowrap;
}
.rad-pill--ok     { background: var(--success-soft); color: var(--success); }
.rad-pill--quiet  { background: var(--soft-2);       color: var(--muted); }
.rad-pill--paused { background: var(--line-soft);    color: var(--faint); }
.rad-pill--error  { background: var(--danger-soft);  color: var(--danger); }
.rad-pill--scan   { background: var(--info-soft);    color: var(--info); }
.rad-pill--new    { background: var(--grad-gold-flat); color: var(--on-gold); }

/* ── The feed ──────────────────────────────────────────────────────── */
.rad-feed-head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s3);
  margin: var(--s6) 0 var(--s3);
  padding-bottom: var(--s2);
  border-bottom: 1px solid var(--line-soft);
}
.rad-feed-head h3 { font-family: var(--font-display); font-size: var(--fs-lg); color: var(--ink); }
.rad-feed-actions { display: flex; gap: var(--s2); }

.rad-alerts { display: flex; flex-direction: column; gap: var(--s3); }

.rad-alert {
  display: grid;
  grid-template-columns: 96px 1fr;
  gap: var(--s4);
  padding: var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
  box-shadow: var(--e1);
}
/* An unread find gets the app's one accent, and nothing else does. */
.rad-alert.is-new { border-left: 4px solid var(--gold); background: var(--gold-tint); }

.rad-alert-img {
  width: 96px;
  height: 96px;
  object-fit: cover;
  border-radius: var(--r-sm);
  background: var(--soft-2);
}
.rad-alert-img--none { border: 1px dashed var(--line-strong); }

.rad-alert-body { display: flex; flex-direction: column; gap: var(--s2); min-width: 0; }
.rad-alert-top { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s3); }

/* The sentence the whole feature exists to write. Sized like a heading
   because on this screen it IS the heading — the item's own title is a
   detail inside it, not the other way round. */
.rad-alert-headline {
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  line-height: var(--lh-snug);
  color: var(--ink);
}

.rad-alert-meta { font-size: var(--fs-xs); color: var(--muted); }
.rad-alert-evidence { font-size: var(--fs-xs); color: var(--muted); font-style: italic; }
.rad-alert-nolink { font-size: var(--fs-xs); color: var(--muted); align-self: center; }

.rad-alert-figures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
  gap: var(--s2) var(--s4);
  padding: var(--s2) 0;
}
.rad-figure { display: flex; flex-direction: column; gap: 1px; }
.rad-figure-label {
  font-size: var(--fs-3xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--muted);
}
.rad-figure-value {
  font-size: var(--fs-base);
  font-weight: var(--fw-semibold);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.rad-alert-actions { display: flex; flex-wrap: wrap; gap: var(--s2); margin-top: var(--s1); }

@media (max-width: 720px) {
  .rad-alert { grid-template-columns: 1fr; }
  .rad-alert-img { width: 100%; height: 140px; }
  .rad-master-actions { margin-left: 0; }
}

/* The gold tint on an unread alert is a light-mode surface; the same rule
   on the dark palette would paint a near-white card into a dark stack. */
:root[data-theme="dark"] .rad-alert.is-new { background: rgba(199, 154, 54, .10); }
:root[data-theme="dark"] .rad-switch-track::after { background: var(--on-dark); }

/* ── Snap & Source ──────────────────────────────────────────────────────
   The only screen in this app designed for a phone held one-handed in
   somebody's driveway, so it is the only one written mobile-first: the
   base rules below are the phone layout, and the desktop widening happens
   in a min-width query. Everything else in this file narrows down; this
   widens up, because the small screen is the real one for this feature.

   Tap targets are 44px or larger throughout — the seller is standing up,
   possibly in gloves, and a mis-tap costs a re-run of a paid AI call. */

.snap-body { max-width: 780px; }

.snap-input-card {
  display: flex;
  flex-direction: column;
  gap: var(--s3);
  padding: var(--s4);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--e1);
}

/* The camera. Big and first, because on a phone this is the primary input
   and a file-input-shaped control would read as an afterthought. */
.snap-drop {
  position: relative;
  display: grid;
  place-items: center;
  min-height: 132px;
  padding: var(--s4);
  border: 2px dashed var(--line);
  border-radius: var(--r-md);
  background: var(--info-tint);
  cursor: pointer;
  overflow: hidden;
  transition: border-color .15s ease, background .15s ease;
}
.snap-drop:hover,
.snap-drop:focus-visible,
.snap-drop-over { border-color: var(--info-600); background: var(--info-soft); }

.snap-drop-prompt {
  display: grid;
  justify-items: center;
  gap: var(--s1);
  text-align: center;
  pointer-events: none;
}
.snap-drop-icon { font-size: var(--fs-3xl); line-height: 1; }
.snap-drop-label { font-size: var(--fs-md); font-weight: var(--fw-semibold); color: var(--ink); }
.snap-drop-hint { font-size: var(--fs-xs); color: var(--muted); }

.snap-thumb {
  width: 100%;
  max-height: 240px;
  object-fit: contain;
  border-radius: var(--r-sm);
}
.snap-clear-photo {
  position: absolute;
  top: var(--s2);
  right: var(--s2);
  width: 34px;
  height: 34px;
  border: 1px solid var(--line);
  border-radius: var(--r-pill);
  background: var(--card);
  color: var(--ink);
  font-size: var(--fs-sm);
  cursor: pointer;
}

.snap-fields { display: flex; flex-direction: column; gap: var(--s3); }
.snap-fields input { width: 100%; min-height: 48px; font-size: var(--fs-md); }

/* Full-width on the phone. The header's own button is the desktop route and
   is hidden below, because a control off the top of a scrolled page is not a
   control at all when the answer is the reason you scrolled. */
.snap-go { width: 100%; min-height: 52px; font-size: var(--fs-md); }

.snap-status { margin: 0; font-size: var(--fs-sm); color: var(--muted); min-height: 1.2em; }
.snap-hint   { margin: 0; font-size: var(--fs-xs); color: var(--muted); }

.snap-result { margin-top: var(--s4); }

.snap-working {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s5);
  font-size: var(--fs-md);
  color: var(--muted);
}
.snap-spinner {
  width: 18px; height: 18px;
  border: 2px solid var(--line);
  border-top-color: var(--info-600);
  border-radius: var(--r-pill);
  animation: snap-spin .8s linear infinite;
}
@keyframes snap-spin { to { transform: rotate(360deg); } }

.snap-error {
  padding: var(--s4);
  border: 1px solid var(--danger-line);
  border-radius: var(--r-md);
  background: var(--danger-soft);
  color: var(--danger);
  font-size: var(--fs-base);
}

.snap-card {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  padding: var(--s4);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--e2);
}

/* The verdict. Readable at arm's length — the only thing on the screen that
   has to survive being glanced at while somebody is talking to you. */
.snap-verdict {
  padding: var(--s5) var(--s4);
  border-radius: var(--r-md);
  text-align: center;
  border: 1px solid transparent;
}
.snap-call {
  font-family: var(--font-display);
  font-size: var(--fs-4xl);
  font-weight: var(--fw-heavy);
  letter-spacing: var(--tracking-display);
  line-height: var(--lh-tight);
}
.snap-call-sub { margin-top: var(--s1); font-size: var(--fs-sm); opacity: .85; }

.snap-call-buy     { background: var(--success-soft); border-color: var(--success-line); color: var(--success); }
.snap-call-close   { background: var(--warning-soft); border-color: var(--warning-line); color: var(--warning); }
.snap-call-pass    { background: var(--danger-soft);  border-color: var(--danger-line);  color: var(--danger); }
.snap-call-unknown { background: var(--info-soft);    border-color: var(--info-line);    color: var(--info); }

.snap-reason { font-size: var(--fs-md); line-height: var(--lh-normal); color: var(--ink); }

.snap-item { display: flex; gap: var(--s3); align-items: flex-start; }
.snap-item-img {
  width: 76px; height: 76px;
  object-fit: cover;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  flex: 0 0 auto;
}
.snap-item-body { flex: 1 1 auto; min-width: 0; }
.snap-item-label {
  display: block;
  font-size: var(--fs-3xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: var(--s1);
}
.snap-item-name {
  width: 100%;
  min-height: 44px;
  font-size: var(--fs-base);
  font-weight: var(--fw-medium);
}
.snap-item-meta { margin-top: var(--s1); font-size: var(--fs-xs); color: var(--muted); }
.snap-id-note   { margin-top: var(--s1); font-size: var(--fs-xs); color: var(--accent); }

/* Two across on a phone, auto-fit above. "Pay up to" spans the row because it
   is the number the seller acts on, and the grid must not be able to bury it
   beside a figure that is merely interesting. */
.snap-tiles {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--s2);
}
.snap-tile {
  padding: var(--s3);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--info-tint);
}
.snap-tile-label {
  font-size: var(--fs-3xs);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--muted);
}
.snap-tile-value {
  font-size: var(--fs-xl);
  font-weight: var(--fw-bold);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  line-height: var(--lh-tight);
}
.snap-tile-foot { margin-top: 2px; font-size: var(--fs-2xs); color: var(--muted); }

.snap-tile-hero {
  grid-column: 1 / -1;
  background: var(--info-soft);
  border-color: var(--info-line);
}
.snap-tile-hero .snap-tile-value { font-size: var(--fs-3xl); color: var(--info); }
.snap-tile-good .snap-tile-value { color: var(--success); }
.snap-tile-bad  .snap-tile-value { color: var(--danger); }

.snap-evidence {
  display: flex;
  gap: var(--s2);
  align-items: flex-start;
  padding: var(--s3);
  border-radius: var(--r-sm);
  font-size: var(--fs-xs);
  line-height: var(--lh-normal);
}
.snap-ev-pill {
  flex: 0 0 auto;
  padding: 2px var(--s2);
  border-radius: var(--r-pill);
  font-size: var(--fs-3xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}
.snap-ev-confident { background: var(--success-tint); color: var(--success); }
.snap-ev-confident .snap-ev-pill { background: var(--success-soft); }
.snap-ev-low  { background: var(--warning-tint); color: var(--warning); }
.snap-ev-low  .snap-ev-pill { background: var(--warning-soft); }
.snap-ev-none { background: var(--danger-tint); color: var(--danger); }
.snap-ev-none .snap-ev-pill { background: var(--danger-soft); }

.snap-warnings {
  margin: 0;
  padding-left: var(--s5);
  font-size: var(--fs-xs);
  line-height: var(--lh-normal);
  color: var(--warning);
}
.snap-warnings li + li { margin-top: var(--s1); }

.snap-actions { display: flex; flex-wrap: wrap; gap: var(--s2); }
.snap-actions .btn { flex: 1 1 auto; min-height: 46px; justify-content: center; }

/* Desktop: the fields go back on one line and the header button takes over,
   so the in-card button doesn't sit under a duplicate of itself. */
@media (min-width: 720px) {
  .snap-fields { flex-direction: row; align-items: flex-end; }
  /* Only once the container is a ROW. On the phone the same declarations sit on a column
     flex container, where flex-basis is read as a height — which forced the price field to
     130px tall and left a dead band above the button on the one layout that can least
     afford it. Found in a 390px browser pass, not in review. */
  .snap-field-wide { flex: 1 1 auto; }
  .snap-field-price { flex: 0 0 130px; }
  .snap-go { display: none; }
  .snap-tiles { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
  .snap-actions .btn { flex: 0 0 auto; }
}

@media (prefers-reduced-motion: reduce) {
  .snap-spinner { animation-duration: 2.4s; }
}

/* The semantic soft fills are light-mode surfaces; on the dark palette the
   same tints paint near-white blocks into a dark stack, so the verdict card
   and the tiles are re-derived from the role colours at low alpha instead. */
:root[data-theme="dark"] .snap-call-buy     { background: rgba(26, 138, 96, .16); color: var(--success-600); }
:root[data-theme="dark"] .snap-call-close   { background: rgba(180, 119, 26, .16); color: var(--warning-600); }
:root[data-theme="dark"] .snap-call-pass    { background: rgba(207, 75, 66, .16);  color: var(--danger-600); }
:root[data-theme="dark"] .snap-call-unknown { background: rgba(26, 124, 137, .16); color: var(--info-600); }
:root[data-theme="dark"] .snap-tile         { background: rgba(26, 124, 137, .07); }
:root[data-theme="dark"] .snap-tile-hero    { background: rgba(26, 124, 137, .14); }
:root[data-theme="dark"] .snap-tile-hero .snap-tile-value { color: var(--info-600); }
:root[data-theme="dark"] .snap-drop         { background: rgba(26, 124, 137, .08); }
:root[data-theme="dark"] .snap-error        { background: rgba(207, 75, 66, .14);  color: var(--danger-600); }
:root[data-theme="dark"] .snap-ev-confident { background: rgba(26, 138, 96, .12); color: var(--success-600); }
:root[data-theme="dark"] .snap-ev-low       { background: rgba(180, 119, 26, .12); color: var(--warning-600); }
:root[data-theme="dark"] .snap-ev-none      { background: rgba(207, 75, 66, .12);  color: var(--danger-600); }

/* ── eBay scanner panel ─────────────────────────────────────────────────────
   Its own panel above the local board because it is the one source that needs
   no zip, no radius, no login and no explanation — folding it in with the
   local sources buried a one-field search behind five chips and four fields
   that mean nothing to it. */
.ebay-scan-row {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 12px;
  margin-top: 10px;
}
.ebay-scan-field {
  flex: 1 1 320px;
  min-width: 0;
}

/* ── Dropship split on the awaiting-cost rows ───────────────────────────────
   A dropshipper has no unit cost to type: they keep a share of the sale and
   the supplier keeps the rest. The percentage sits under the dollar box and
   fills it, so Save still writes the one cost field everything else reads. */
.er-pct-label {
  grid-column: 1 / -1;
  margin-top: 6px;
  opacity: .85;
}
.er-cost-pct {
  width: 100%;
  max-width: 120px;
}
.er-pct-hint {
  grid-column: 1 / -1;
  font-size: 12px;
  color: #9fb3ac;
  min-height: 1em;
}

/* Filters that only apply to eBay. A wrapping grid rather than another flex row: five controls
   on one line squeezes each to unusable width on a laptop. */
.ebay-scan-filters {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 10px 12px;
  margin-top: 10px;
}
.ebay-scan-narrow { max-width: 170px; }

/* ── Today's picks: Marketplace's own front page ────────────────────────────
   A picture grid rather than the ranked table, because these rows are not a
   priced answer to a question — they are what is for sale near you right now,
   and the photo is the whole reason one is worth a second look. */
.fb-picks-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 14px;
}
/* Says what the grid is sorted by. Hidden until the prices land, because until then the cards
   really are still in Facebook's order and the line would be a lie. */
.fb-picks-order {
  margin-top: 4px;
  font-weight: 600;
  color: var(--accent);
}
.fb-picks-order[hidden] { display: none; }
.fb-picks-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 12px;
  margin-top: 12px;
}
.fb-pick-card {
  display: flex;
  flex-direction: column;
  gap: 3px;
  text-decoration: none;
  color: inherit;
  border: 1px solid rgba(255, 255, 255, .08);
  border-radius: 10px;
  overflow: hidden;
  padding-bottom: 8px;
  background: rgba(0, 0, 0, .18);
  transition: border-color .15s ease, transform .15s ease;
}
.fb-pick-card:hover { border-color: rgba(232, 201, 122, .5); transform: translateY(-2px); }
.fb-pick-img {
  width: 100%;
  aspect-ratio: 1 / 1;
  object-fit: cover;
  display: block;
  background: rgba(0, 0, 0, .3);
}
.fb-pick-img-empty {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 26px;
}
.fb-pick-price { font-weight: 700; padding: 6px 9px 0; }
.fb-pick-title {
  padding: 0 9px;
  font-size: 13px;
  line-height: 1.3;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.fb-pick-loc { padding: 0 9px; font-size: 12px; color: #9fb3ac; }

/* Placeholder tiles while the Marketplace feed loads. The call drives a real page load and takes
   about thirty seconds; an empty strip for that long reads as broken rather than as busy. */
.fb-pick-skeleton {
  pointer-events: none;
  border-color: rgba(255, 255, 255, .05);
}
.fb-pick-skeleton .fb-pick-img {
  animation: fbPickPulse 1.4s ease-in-out infinite;
}
@keyframes fbPickPulse {
  0%, 100% { opacity: .35; }
  50%      { opacity: .7; }
}
@media (prefers-reduced-motion: reduce) {
  .fb-pick-skeleton .fb-pick-img { animation: none; }
}

/* Marketplace's own filters, rebuilt as controls. Facebook serves X-Frame-Options: DENY on every
   Marketplace URL, so their page cannot be embedded here by any browser — these reach the same
   results through the query-string parameters their own filters use. */
.fb-picks-search {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 10px 12px;
  margin-top: 12px;
}
.fb-picks-q { flex: 1 1 260px; min-width: 0; }
.fb-picks-filters {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 10px 12px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid rgba(255, 255, 255, .07);
}
.fb-picks-narrow { max-width: 150px; }

/* ── Listing Copilot ────────────────────────────────────────────────────────
   Proposed changes, shown before anything is applied. The was/now pair is the
   whole point of the screen, so it reads as one line rather than two columns. */
.copilot-summary {
  padding: 12px 14px;
  margin: 12px 0;
  border-radius: 10px;
  background: var(--card, #fff);
  border: 1px solid var(--border, rgba(128,128,128,.28));
  font-size: var(--fs-sm, .9rem);
}
.copilot-error {
  padding: 12px 14px;
  margin: 12px 0;
  border-radius: 10px;
  background: rgba(220, 60, 60, .12);
  border: 1px solid rgba(220, 60, 60, .35);
  font-size: var(--fs-sm, .9rem);
}
.copilot-actions {
  display: grid;
  gap: 16px;
  grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
  margin-top: 16px;
}
.copilot-card {
  display: flex;
  flex-direction: column;
  padding: 16px;
  border-radius: 12px;
  background: var(--card, #fff);
  border: 1px solid var(--border, rgba(128,128,128,.28));
}
.copilot-card-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 10px;
}
.copilot-card-head h3 { margin: 0; font-size: var(--fs-md, 1rem); }
.copilot-count {
  flex: none;
  font-size: var(--fs-xs, .78rem);
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--surface-2, rgba(128,128,128,.10));
  white-space: nowrap;
}
.copilot-why {
  font-size: var(--fs-xs, .78rem);
  color: var(--text-muted, #606e74);
  margin: 8px 0 10px;
  line-height: 1.5;
}
.copilot-result { flex: 1 1 auto; margin-bottom: 12px; }
.copilot-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-height: 260px;
  overflow-y: auto;
}
.copilot-list li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
  padding: 6px 0;
  border-bottom: 1px solid var(--border, rgba(128,128,128,.22));
  font-size: var(--fs-xs, .78rem);
}
.copilot-list li:last-child { border-bottom: 0; }
.copilot-was  { text-decoration: line-through; color: var(--text-muted, #606e74); }
.copilot-arrow{ opacity: .5; }
.copilot-now  { font-weight: 600; }
.copilot-title{ font-weight: 600; }
.copilot-issue{ width: 100%; color: var(--text-muted, #606e74); }
.copilot-clean{ font-size: var(--fs-xs, .78rem); color: var(--text-muted, #606e74); margin: 0; }
.copilot-more { font-size: var(--fs-xs, .78rem); color: var(--text-muted, #606e74); margin: 6px 0 0; }
.copilot-apply { align-self: flex-start; }
.copilot-apply[disabled] { opacity: .45; cursor: not-allowed; }

/* The way back to a rewrite the seller just paid for. On its own row under the title diff, so a
   long title cannot push it off the edge of the card. */
.copilot-open-draft {
  width: 100%;
  margin-top: 2px;
  align-self: flex-start;
  font-size: var(--fs-2xs, .72rem);
  text-align: left;
}
a.copilot-open-draft { color: var(--accent, #2f9c3b); text-decoration: none; }
a.copilot-open-draft:hover { text-decoration: underline; }
.copilot-foot { margin-top: 16px; }

/* Progress for the whole-account rewrite. A run takes minutes; a page with no visible
   movement reads as frozen, and a frozen page gets reloaded mid-run. */
.copilot-bar {
  height: 6px;
  border-radius: 999px;
  background: var(--surface-2, rgba(128,128,128,.10));
  overflow: hidden;
  margin: 10px 0 12px;
}
.copilot-bar > span {
  display: block;
  height: 100%;
  border-radius: 999px;
  background: linear-gradient(90deg, #0f766e, #14b8a6);
  transition: width .4s ease;
}

/* Listing picker: rewrite one at a time, or take the lot. */
.copilot-picker { margin: 4px 0 12px; }
.copilot-pick-all {
  display: flex; align-items: center; gap: 8px;
  font-size: var(--fs-xs, .78rem); font-weight: 600;
  padding-bottom: 8px; margin-bottom: 6px;
  border-bottom: 1px solid var(--border, rgba(128,128,128,.28));
}
.copilot-picklist { max-height: 240px; overflow-y: auto; }
.copilot-pick {
  display: flex; align-items: baseline; gap: 8px;
  padding: 5px 0; font-size: var(--fs-xs, .78rem); cursor: pointer;
}
.copilot-pick:hover { background: var(--surface-2, rgba(128,128,128,.10)); }
.copilot-pick-title {
  flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.copilot-pick-flag {
  flex: none; font-size: 11px; padding: 1px 7px; border-radius: 999px;
  background: rgba(245,158,11,.15); color: #f59e0b;
}
.copilot-apply-row { display: flex; flex-wrap: wrap; gap: 8px; }

/* What the rewrite did to the rest of the listing. One line per listing until it is opened: a
   rewrite fills twenty item specifics, and eighty of those expanded at once is a page nobody
   reads — but a seller who is never shown them has no idea the description changed either. */
.copilot-changes { width: 100%; margin-top: 4px; }
.copilot-changes > summary {
  cursor: pointer;
  font-size: var(--fs-2xs, .72rem);
  color: var(--text-muted, #606e74);
  padding: 2px 0;
}
.copilot-changes > summary:hover { color: var(--text, inherit); }
.copilot-changes ul {
  list-style: none;
  margin: 4px 0 2px;
  padding: 6px 0 2px 10px;
  border-left: 2px solid var(--border, rgba(128,128,128,.22));
  max-height: 220px;
  overflow-y: auto;
}
.copilot-changes li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 6px;
  padding: 3px 0;
  font-size: var(--fs-2xs, .72rem);
}
.copilot-chg-field { flex: none; min-width: 110px; font-weight: 600; }
.copilot-chg-tag { flex: none; font-size: 11px; padding: 0 6px; border-radius: 999px; }
.copilot-chg-filled  { background: rgba(47,156,59,.15); color: var(--accent, #2f9c3b); }
.copilot-chg-removed { background: rgba(245,158,11,.15); color: #f59e0b; }

/* Money on a Today's Picks card. The grid answers "what is near me"; these answer
   "is it a buy", which is the only reason to look at the grid. */
.fb-pick-money { display: block; margin-top: 4px; line-height: 1.35; }
.fb-pick-profit { display: block; font-weight: 800; font-size: var(--fs-sm, .9rem); }
.fb-pick-profit.is-good { color: #16a34a; }
.fb-pick-profit.is-bad  { color: var(--text-muted, #606e74); }
.fb-pick-comp {
  display: block; font-size: 11px; color: var(--text-muted, #606e74);
}
.fb-pick-nodata { display: block; font-size: 11px; color: var(--text-muted, #606e74); font-style: italic; }

/* A figure the server graded "low": the arithmetic is right and the price under it is a guess.
   Dimmed rather than hidden — the seller can still use it as a starting point — but it must not
   read like the confident number on the card next to it. Same treatment the deals board gives
   its estimate rows, and for the same reason: an undimmed -93% off a mismatched comp is a lie
   told in a confident voice. */
.fb-pick-profit.is-guess { color: var(--text-muted, #606e74); font-weight: 700; opacity: .85; }
.fb-pick-guess {
  display: block;
  font-size: 10.5px;
  line-height: 1.3;
  margin-top: 1px;
  color: #b45309;
  font-style: italic;
  cursor: help;
}

/* Update banner. Deliberately calm: an update is worth knowing about, not worth alarming
   someone mid-listing. Sits with the recovery banner at the top of the dashboard. */
.update-banner {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  padding: 12px 16px; margin: 0 0 14px;
  border-radius: 10px;
  background: var(--card, #fff);
  border: 1px solid var(--border, rgba(128,128,128,.28));
  border-left: 4px solid #0f766e;
  font-size: var(--fs-sm, .9rem);
}
.update-banner a { font-weight: 700; text-decoration: underline; }
.update-banner .btn { margin-left: auto; }

/* ─────────────────────────────────────────────────────────────────────────────
   The app is not running
   ─────────────────────────────────────────────────────────────────────────────
   Pinned to the bottom of the window rather than dropped into whichever panel
   noticed first, because this is not that panel's problem: with the backend gone,
   nothing on any screen can do anything. Measured from the bug that produced it —
   a seller clicking "Scan My Account" against a closed app was told "Failed to
   fetch" by one panel and left to guess about the rest of the page.

   It clears itself. The page keeps knocking on /api/app/instance, and the moment
   the app answers this says so and goes.
   ───────────────────────────────────────────────────────────────────────────── */

.app-offline-banner {
  position: fixed;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  z-index: 9000;
  max-width: min(680px, calc(100vw - 32px));
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 13px 18px;
  border-radius: 12px;
  background: var(--danger-soft, #fdeeeb);
  border: 1px solid var(--danger-line, #f3c3b9);
  border-left: 4px solid var(--danger, #c0392b);
  box-shadow: 0 10px 30px rgba(0, 0, 0, .18);
  font-size: var(--fs-sm, .9rem);
  line-height: 1.55;
  color: var(--text-strong, #17302f);
}

/* Reconnected: same box, opposite news. Green rather than a second red bar, so a
   seller glancing at the corner reads the outcome without reading the words. */
.app-offline-banner.app-offline-back {
  background: #e8f6f0;
  border-color: #a8ddc7;
  border-left-color: #0f766e;
}

@media (prefers-reduced-motion: no-preference) {
  .app-offline-banner { transition: background .25s ease, border-color .25s ease; }
}

/* ── Reviewing a rewritten draft ───────────────────────────────────────────
   Same screen, different job. Opening a finished rewrite put the seller at the
   top of the AI Listing page — three cards offering to start something NEW —
   with the listing they had just paid to have rewritten below the fold. It read
   as "it opened the wrong window". In review mode the intake block is out of the
   way and the listing itself is the first thing on screen. Cleared again the
   moment the screen is opened to start a listing (openNewListingModal). */
.nl-review-draft .nl-url-section { display: none; }

/* "SELLS FOR: $X" — the one number a reseller reads first. Everything else on the row is
   arithmetic derived from it, so it does not sit in the same weight as the rest of the facts. */
.sells-for {
  font-size: var(--fs-sm, .95rem);
  letter-spacing: .02em;
  color: var(--text, #131c20);
}
.sells-for strong {
  font-weight: 800;
  font-size: 1.15em;
  color: #16a34a;
}

/* ── The Tax Pack ──────────────────────────────────────────────────────────────────────────
   Deliberately built out of the Money Made furniture — the same hero, the same card, the same
   footnote — because the two screens are two halves of one sentence and a seller should not have
   to relearn a layout to read the second half. Only the pieces that have no equivalent upstairs
   get new rules: the cost-basis gap, the quarter cards, and the Schedule C itself. */

.tx-year-label {
  font-size: var(--fs-xs);
  color: var(--muted);
  white-space: nowrap;
}

.tx-year {
  padding: 6px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 600;
}

/* An export with nothing to export is worse than no button: it hands back an error for a click
   that looked legitimate. It stays visible so the screen doesn't reflow when sales arrive. */
.is-disabled {
  opacity: .45;
  pointer-events: none;
}

/* The hero is the same component as Money Made's, one notch cooler, so a glance can tell the two
   screens apart without reading the heading. */
.tx-hero {
  background:
    radial-gradient(1000px 220px at 90% -50%, rgba(224, 187, 99, .22), transparent 62%),
    linear-gradient(135deg, var(--teal-900), var(--teal-800, var(--teal-700)));
}

/* ── The money at stake ──────────────────────────────────────────────────────────────────── */
/* Gold, not red. This is the block that makes the bill smaller, and dressing an opportunity as an
   error teaches the seller to close it. */
.tx-gap {
  display: flex;
  align-items: center;
  gap: var(--s5);
  flex-wrap: wrap;
  margin-bottom: var(--s5);
  padding: var(--s5);
  border: 1px solid var(--gold);
  border-left: 4px solid var(--gold);
  border-radius: var(--r-md);
  background: var(--gold-soft);
}

.tx-gap-figure {
  font-size: clamp(1.9rem, 4vw, 2.6rem);
  font-weight: 800;
  line-height: 1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.tx-gap-body { flex: 1 1 320px; min-width: 0; }

.tx-gap-lead {
  margin: 0;
  font-size: var(--fs-lg);
  font-weight: 700;
  color: var(--ink);
}

.tx-gap-detail {
  margin: var(--s2) 0 var(--s4);
  font-size: var(--fs-sm);
  color: var(--muted);
  line-height: 1.55;
}

/* ── When to pay it ──────────────────────────────────────────────────────────────────────── */
.tx-quarters {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--s3);
}

.tx-quarter {
  padding: var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--card);
}

/* A window whose due date has gone is not an error — the seller may well have paid it — so it
   recedes rather than alarming. The one being earned into now is the one to look at. */
.tx-quarter.is-past { opacity: .62; }

.tx-quarter.is-current {
  border-color: var(--teal-700);
  box-shadow: inset 0 0 0 1px var(--teal-700);
  opacity: 1;
}

.tx-q-head {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  flex-wrap: wrap;
  margin-bottom: var(--s2);
}

.tx-q-name {
  font-size: var(--fs-sm);
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}

.tx-q-tag {
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--teal-900);
  color: #fff;
  font-size: .68rem;
  font-weight: 700;
}

.tx-q-tag-past {
  background: transparent;
  border: 1px solid var(--line);
  color: var(--muted);
}

.tx-q-amount {
  font-size: 1.5rem;
  font-weight: 800;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.tx-q-due {
  margin-top: 2px;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--ink);
}

.tx-q-meta {
  margin-top: var(--s2);
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.5;
}

.tx-quarters-note {
  margin: var(--s4) 0 0;
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.55;
}

/* ── The form ────────────────────────────────────────────────────────────────────────────── */
/* Laid out as the paper form is, because that is what the figures are being copied into. */
.tx-line {
  display: grid;
  grid-template-columns: 3.2rem 1fr auto;
  align-items: baseline;
  gap: var(--s3);
  padding: var(--s2) 0;
}

.tx-line.is-subtotal {
  border-top: 1px solid var(--line);
  font-weight: 700;
}

.tx-line-no {
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.tx-line-label {
  font-size: var(--fs-sm);
  color: var(--ink);
  min-width: 0;
}

.tx-line-amount {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Said on the line it applies to, not gathered into a footnote nobody reads. A figure marked
   estimated that the seller can make exact is a piece of work worth doing. */
.tx-line-chip {
  margin-left: var(--s2);
  padding: 1px 6px;
  border-radius: 999px;
  border: 1px solid var(--warning-line);
  background: var(--warning-soft);
  color: var(--warning);
  font-size: .66rem;
  font-weight: 700;
  white-space: nowrap;
}

.tx-line-basis {
  margin: 0 0 var(--s3);
  padding-left: calc(3.2rem + var(--s3));
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.55;
}

/* The workings are the reason an accountant will trust this, and clutter for the seller who
   already does. One toggle, and the numbers keep their places either way. */
.tx-schedule-bare .tx-line-basis { display: none; }
.tx-schedule-bare .tx-line { padding: var(--s2) 0; }

.tx-estimate {
  margin-top: var(--s4);
  padding-top: var(--s4);
  border-top: 2px solid var(--line);
}

.tx-estimate .tx-line-basis { padding-left: calc(3.2rem + var(--s3)); margin-top: var(--s2); }

/* ── The bracket, and the 1099-K notes ───────────────────────────────────────────────────── */
.tx-rate-card {
  display: flex;
  align-items: center;
  gap: var(--s5);
  flex-wrap: wrap;
  margin-bottom: var(--s5);
  padding: var(--s4) var(--s5);
  border: 1px solid var(--line);
  border-radius: var(--r-md);
  background: var(--card);
}

.tx-rate-field label {
  display: block;
  margin-bottom: var(--s2);
  font-size: var(--fs-xs);
  font-weight: 700;
  color: var(--muted);
}

.tx-rate-input {
  display: flex;
  align-items: center;
  gap: var(--s2);
}

.tx-rate-input input {
  width: 5.5rem;
  padding: 7px 10px;
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--card);
  color: var(--ink);
  font-size: var(--fs-sm);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

.tx-rate-suffix {
  font-size: var(--fs-sm);
  font-weight: 700;
  color: var(--muted);
}

.tx-rate-note {
  flex: 1 1 320px;
  margin: 0;
  font-size: var(--fs-xs);
  color: var(--muted);
  line-height: 1.55;
}

.tx-rate-note-error { color: var(--danger); font-weight: 600; }

.tx-notes p {
  margin: 0 0 var(--s3);
  font-size: var(--fs-sm);
  color: var(--muted);
  line-height: 1.6;
}

.tx-notes p:last-child { margin-bottom: 0; }

@media (max-width: 640px) {
  .tx-line { grid-template-columns: 2.6rem 1fr auto; }
  .tx-line-basis { padding-left: 0; }
}

/* ═══════════════════════════════════════════════════════════════════════
   How this price was worked out — the price/confidence trail
   Rendered by priceBasisHtml() on the deals board's row detail and on the
   Opportunity Finder's scoring details. One panel, one set of rules, so the
   two screens can never explain the same pipeline's numbers differently.
   ═══════════════════════════════════════════════════════════════════════ */

.pb-basis {
  margin-top: var(--s4);
  max-width: 96ch;
  border: 1px solid var(--line-soft);
  border-radius: var(--r-sm);
  background: var(--card);
}

.pb-summary {
  cursor: pointer;
  padding: var(--s2) var(--s3);
  font-size: var(--fs-2xs);
  font-weight: var(--fw-semibold);
  color: var(--muted);
  list-style: none;
}

/* The marker is drawn here rather than left to the browser: the default
   triangle sits on a different baseline in each engine and pushed the label a
   pixel out of line with every other label in the panel. */
.pb-summary::-webkit-details-marker { display: none; }
.pb-summary::before {
  content: "›";
  display: inline-block;
  margin-right: var(--s2);
  transition: transform var(--dur-1) var(--ease);
}
.pb-basis[open] > .pb-summary::before { transform: rotate(90deg); }
.pb-summary:hover { color: var(--ink); }

.pb-body {
  padding: 0 var(--s3) var(--s3);
  border-top: 1px solid var(--line-soft);
}

/* The blend in one line. Tabular figures because the whole point of the
   sentence is that the reader can check the multiplication in it. */
.pb-arith {
  margin: var(--s3) 0 var(--s2);
  font-size: var(--fs-xs);
  line-height: 1.6;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.pb-disagree {
  margin: var(--s3) 0 0;
  padding: var(--s2);
  border-radius: var(--r-sm);
  background: var(--warning-soft);
  font-size: var(--fs-2xs);
  line-height: 1.5;
  color: var(--ink);
}

.pb-srcs { display: flex; flex-direction: column; gap: 2px; }

/* Name · count · figure · share. The share is last and right-aligned because
   it is the column a reader scans down to check the two add to 100. */
.pb-src {
  display: grid;
  grid-template-columns: minmax(9rem, 1fr) auto minmax(8rem, auto) 3.2rem;
  gap: var(--s3);
  align-items: baseline;
  padding: 3px 0;
  font-size: var(--fs-2xs);
  border-bottom: 1px solid var(--line-soft);
}
.pb-src:last-child { border-bottom: 0; }
.pb-src-name { font-weight: var(--fw-semibold); color: var(--ink); }
.pb-src-count { color: var(--muted); }
.pb-src-value { color: var(--ink); font-variant-numeric: tabular-nums; }
.pb-src-weight {
  text-align: right;
  font-weight: var(--fw-semibold);
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

/* When this source saw the market, under its own row rather than squeezed into
   a fifth column — it is a caveat on the line above, not a figure to scan down. */
.pb-src-age {
  grid-column: 1 / -1;
  margin-top: 1px;
  font-size: var(--fs-3xs);
  line-height: 1.4;
  color: var(--faint);
}

.pb-also {
  margin: var(--s2) 0 0;
  font-size: var(--fs-2xs);
  color: var(--muted);
  font-variant-numeric: tabular-nums;
}

.pb-conf-head {
  margin: var(--s4) 0 var(--s2);
  padding-top: var(--s3);
  border-top: 1px solid var(--line-soft);
  font-size: var(--fs-2xs);
  text-transform: uppercase;
  letter-spacing: var(--tracking-caps);
  color: var(--faint);
}
.pb-conf-head strong { color: var(--ink); font-size: var(--fs-sm); }

.pb-factors { display: flex; flex-direction: column; gap: 3px; }

/* Label, bar, sentence. The bar is the shared viz meter rather than a second
   bar drawn to its own scale — a seven-term score is a chart, and this is the
   only place on either board where the terms can be compared at a glance. */
.pb-factor {
  display: grid;
  grid-template-columns: minmax(8rem, 11rem) minmax(90px, 130px) 1fr;
  gap: var(--s3);
  align-items: center;
  padding: 2px 0;
  font-size: var(--fs-2xs);
}
.pb-factor-label { color: var(--ink); font-weight: var(--fw-semibold); }
.pb-factor-detail { color: var(--muted); line-height: 1.45; }
.pb-factor .viz-meter-value { min-width: 3.6em; }

.pb-gap {
  margin: var(--s3) 0 0;
  padding-top: var(--s2);
  border-top: 1px solid var(--line-soft);
  font-size: var(--fs-2xs);
  line-height: 1.55;
  color: var(--muted);
}
.pb-gap strong { color: var(--ink); }

/* Below this the three-column factor row is two words wide per column, so it
   stacks: the sentence under its own bar rather than beside it. */
@media (max-width: 720px) {
  .pb-src { grid-template-columns: 1fr auto; }
  .pb-factor { grid-template-columns: 1fr; gap: 2px; padding: var(--s2) 0; }
}

/* ═══════════════════════════════════════════════════════════════════════
   The Store Plan — which eBay Store subscription to be on
   Cards rather than a table, for the same reason the Restock List is: every
   tier carries its own arithmetic and the band of listing counts it wins
   over, and neither is the shape of a table cell. Six tiers is few enough
   that a grid reads at a glance and no row needs to be scrolled to.
   ═══════════════════════════════════════════════════════════════════════ */

/* The hero only goes gold when there is money on the table. A screen that
   always looks urgent is one the seller stops reading. */
.sp-hero-win { box-shadow: inset 0 0 0 1px var(--gold); }

.sp-controls {
  display: flex;
  align-items: flex-end;
  gap: var(--s3);
  flex-wrap: wrap;
  margin-bottom: var(--s2);
  padding: var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--card);
}

.sp-field { display: flex; flex-direction: column; gap: 4px; min-width: 170px; }

.sp-field label {
  font-size: var(--fs-2xs);
  font-weight: 700;
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--muted);
}

.sp-note {
  margin: 0 0 var(--s5);
  font-size: var(--fs-xs);
  line-height: 1.55;
  color: var(--muted);
}

.sp-note-error { color: var(--danger); }

/* The arithmetic of both sides, in one paragraph, before any table. A seller
   who reads only this line has enough to act on. */
.sp-detail {
  margin: 0 0 var(--s4);
  padding: var(--s4);
  border-left: 4px solid var(--teal-700);
  border-radius: var(--r-sm);
  background: var(--surface);
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--ink);
}

/* Gold, like every other block in this app that is money the seller has not
   collected yet: the same plan, billed annually, for less. */
.sp-billing-win {
  margin: 0 0 var(--s5);
  padding: var(--s4);
  border-left: 4px solid var(--gold);
  border-radius: var(--r-sm);
  background: var(--gold-soft);
  font-size: var(--fs-sm);
  line-height: 1.6;
  color: var(--ink);
}

.sp-plans {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: var(--s3);
}

.sp-plan {
  padding: var(--s4);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  background: var(--card);
}

/* A tier that is neither the seller's nor the answer is context, not noise —
   it recedes rather than disappearing, because the bands between them are
   what tell the seller where the next change lands. */
.sp-plan { opacity: .74; }
.sp-plan.is-current, .sp-plan.is-best { opacity: 1; }

.sp-plan.is-best {
  border-color: var(--gold);
  box-shadow: inset 0 0 0 1px var(--gold);
}

.sp-plan.is-current { border-color: var(--teal-700); }

.sp-plan-head {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  flex-wrap: wrap;
  margin-bottom: var(--s2);
}

.sp-plan-name {
  font-size: var(--fs-sm);
  font-weight: 800;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--muted);
}

.sp-tag {
  padding: 1px 7px;
  border-radius: 999px;
  font-size: .68rem;
  font-weight: 700;
}

.sp-tag-current { background: var(--teal-900); color: #fff; }
.sp-tag-best { background: var(--gold); color: #1b1405; }
.sp-tag-note { border: 1px solid var(--line); color: var(--muted); }

.sp-plan-cost {
  font-size: 1.5rem;
  font-weight: 800;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.sp-plan-per { margin-left: 2px; font-size: var(--fs-sm); font-weight: 600; color: var(--muted); }

.sp-plan-year { font-size: var(--fs-xs); color: var(--muted); }

/* Signed and stated in words. A bare figure leaves the seller to work out
   which direction it goes, which is the one thing this screen is for. */
.sp-delta {
  display: inline-block;
  margin-top: var(--s2);
  font-size: var(--fs-xs);
  font-weight: 700;
}

.sp-delta-down { color: var(--e3-up); }
.sp-delta-up { color: var(--danger); }
.sp-delta-flat { color: var(--muted); font-weight: 600; }

.sp-basis, .sp-band, .sp-unlocks {
  margin: var(--s2) 0 0;
  font-size: var(--fs-2xs);
  line-height: 1.55;
  color: var(--muted);
}

.sp-band { color: var(--ink); font-weight: 600; }

.sp-next {
  margin: var(--s4) 0 0;
  font-size: var(--fs-xs);
  line-height: 1.55;
  color: var(--muted);
}

/* The published card on its own, with nothing computed from it, so it can be
   checked against eBay's fee page line by line. */
.sp-rates { display: flex; flex-direction: column; gap: var(--s2); }

.sp-rate {
  display: grid;
  grid-template-columns: minmax(140px, 1fr) repeat(4, minmax(120px, 1fr));
  gap: var(--s2);
  align-items: baseline;
  padding: var(--s2) 0;
  border-bottom: 1px solid var(--line-soft);
  font-size: var(--fs-2xs);
  color: var(--muted);
}

.sp-rate-name { font-weight: 700; color: var(--ink); font-size: var(--fs-xs); }
.sp-rate-cell { font-variant-numeric: tabular-nums; }

.sp-rates-note {
  margin: var(--s3) 0 0;
  font-size: var(--fs-2xs);
  line-height: 1.55;
  color: var(--muted);
}

/* Below this the rate card's five columns are two words wide each, so it
   stacks into a list per tier rather than shredding every line. */
@media (max-width: 860px) {
  .sp-rate { grid-template-columns: 1fr; gap: 2px; }
  .sp-controls { align-items: stretch; }
  .sp-field { min-width: 100%; }
}

/* ═══════════════════════════════════════════════════════════════════════
   The Restock List — what to go and buy again
   Cards rather than a table: every row carries a headline sentence and a
   list of cautions, and a table cell is the wrong shape for either. The
   whole board is one column so the ranking reads top to bottom — this is
   a shopping list, and a grid invites the eye to shop it sideways.
   ═══════════════════════════════════════════════════════════════════════ */

.rs-notice {
  margin: 0 0 var(--s5);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--warning-line);
  border-radius: var(--r-md);
  background: var(--warning-soft);
  color: var(--warning);
  font-size: var(--fs-sm);
  line-height: 1.55;
}

.rs-notice-error {
  border-color: var(--danger-line);
  background: var(--danger-soft);
  color: var(--danger);
}

.rs-board-head { align-items: flex-end; }

.rs-sort { display: flex; gap: var(--s1); flex-wrap: wrap; }

.rs-sort-btn {
  padding: 6px var(--s3);
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  background: var(--card);
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  cursor: pointer;
}

.rs-sort-btn:hover { border-color: var(--gold); color: var(--ink); }

.rs-sort-btn.active {
  border-color: var(--teal-700);
  background: var(--teal-700);
  color: #fff;
}

.rs-list { display: flex; flex-direction: column; gap: var(--s3); }

.rs-card {
  padding: var(--s4) var(--s5);
  border: 1px solid var(--line-soft);
  border-left: 3px solid var(--teal-700);
  border-radius: var(--r-md);
  background: var(--card);
  box-shadow: var(--e1);
}

/* The stop list earns a red edge. It is the only list here that is telling
   the seller to do less of something, and it should not look like the
   shopping list with different words in it. */
.rs-card-stop { border-left-color: var(--danger); }
.rs-card-needs_cost { border-left-color: var(--warning); }
.rs-card-watch { border-left-color: var(--line-soft); }

.rs-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
}

.rs-card-title { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }

.rs-card-title h4 {
  margin: 0;
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--ink);
}

.rs-card-actions { display: flex; gap: var(--s2); flex-shrink: 0; }

.rs-badges { display: flex; gap: var(--s1); flex-wrap: wrap; }

.rs-badge {
  padding: 2px var(--s2);
  border-radius: 999px;
  font-size: var(--fs-3xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}

/* "None listed" is the one badge that is an instruction, so it is the one
   that is loud. Everything else on a card states; this one asks. */
.rs-badge-out   { background: var(--gold-soft);    color: var(--gold-deep); }
.rs-badge-live  { background: var(--success-soft); color: var(--success); }
.rs-badge-stop  { background: var(--danger-soft);  color: var(--danger); }
.rs-badge-cost  { background: var(--warning-soft); color: var(--warning); }

.rs-headline {
  margin: var(--s2) 0 0;
  font-size: var(--fs-base);
  line-height: 1.55;
  color: var(--ink-2);
}

.rs-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(104px, 1fr));
  gap: var(--s3);
  margin-top: var(--s3);
  padding-top: var(--s3);
  border-top: 1px solid var(--line-soft);
}

.rs-stat { display: flex; flex-direction: column; gap: 2px; }

.rs-stat-label {
  font-size: var(--fs-3xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--muted);
}

.rs-stat-value {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--ink);
}

/* Cautions are never folded away behind a "more" link. The seller reading
   this card is about to spend money on what it recommends. */
.rs-cautions {
  margin: var(--s3) 0 0;
  padding-left: var(--s4);
  font-size: var(--fs-xs);
  line-height: 1.6;
  color: var(--muted);
}

.rs-cautions li { margin-top: 2px; }

/* The two quiet lists — one sale each, or no cost recorded. They are
   reference, not recommendations, so they sit back a shade. */
.rs-list-quiet .rs-card { box-shadow: none; background: var(--card-alt); }

@media (max-width: 720px) {
  .rs-card-actions { width: 100%; }
  .rs-card-actions .btn { flex: 1; }
}

/* ═══════════════════════════════════════════════════════════════════════
   Price Position — the shelf the buyer actually sees
   Cards, one column, same shape as the Restock List: every row carries a
   sentence and a list of cautions, and neither fits in a table cell.
   The left edge is the BLOCKER rather than the rank — a listing 7th of 9
   because nobody can find it is a different problem from one 7th of 9
   because it is dearer, and the two should never look the same.
   ═══════════════════════════════════════════════════════════════════════ */

.pp-board-head { align-items: flex-end; }

.pp-filter { display: flex; gap: var(--s1); flex-wrap: wrap; }

.pp-filter-btn {
  padding: 6px var(--s3);
  border: 1px solid var(--line-soft);
  border-radius: 999px;
  background: var(--card);
  color: var(--muted);
  font-size: var(--fs-xs);
  font-weight: var(--fw-semibold);
  cursor: pointer;
}

.pp-filter-btn:hover { border-color: var(--gold); color: var(--ink); }

.pp-filter-btn.active {
  border-color: var(--teal-700);
  background: var(--teal-700);
  color: #fff;
}

.pp-list { display: flex; flex-direction: column; gap: var(--s3); }

.pp-empty {
  margin: 0;
  padding: var(--s5);
  border: 1px dashed var(--line-soft);
  border-radius: var(--r-md);
  color: var(--muted);
  font-size: var(--fs-sm);
  text-align: center;
}

.pp-card {
  padding: var(--s4) var(--s5);
  border: 1px solid var(--line-soft);
  border-left: 3px solid var(--line-soft);
  border-radius: var(--r-md);
  background: var(--card);
  box-shadow: var(--e1);
}

/* Price is fixable this afternoon and gets the loud edge. Supply is red
   because "cut to match them" is a loss on those rows, not a markdown.
   Visibility is gold: the money is there, the listing is just invisible. */
.pp-card-price      { border-left-color: var(--warning); }
.pp-card-supply     { border-left-color: var(--danger); }
.pp-card-visibility { border-left-color: var(--gold); }
.pp-card-none       { border-left-color: var(--success); }

/* A row with no position is not a row that passed. Too few rivals to compare,
   nothing on the shelf that was the same item, a search that failed — all
   three arrive with no blocker, and green on any of them reads as "this one
   is fine", which is a claim the board did not make. */
.pp-card-unjudged { border-left-color: var(--line-soft); }

.pp-card-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
}

.pp-card-title { display: flex; align-items: flex-start; gap: var(--s3); }

.pp-thumb {
  width: 46px;
  height: 46px;
  object-fit: cover;
  border-radius: var(--r-sm);
  border: 1px solid var(--line-soft);
  flex-shrink: 0;
}

.pp-card-title h4 {
  margin: 0 0 var(--s1);
  font-family: var(--font-display);
  font-size: var(--fs-md);
  font-weight: var(--fw-semibold);
  color: var(--ink);
}

.pp-card-title h4 a { color: inherit; text-decoration: none; }
.pp-card-title h4 a:hover { text-decoration: underline; }

.pp-card-actions { display: flex; gap: var(--s2); flex-shrink: 0; align-items: center; }

.pp-badges { display: flex; gap: var(--s1); flex-wrap: wrap; }

.pp-badge {
  padding: 2px var(--s2);
  border-radius: 999px;
  font-size: var(--fs-3xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
}

.pp-badge-rank       { background: var(--card-alt);     color: var(--ink-2); }
.pp-badge-alone      { background: var(--card-alt);     color: var(--muted); }
.pp-badge-price      { background: var(--warning-soft); color: var(--warning); }
.pp-badge-supply     { background: var(--danger-soft);  color: var(--danger); }
.pp-badge-visibility { background: var(--gold-soft);    color: var(--gold-deep); }

.pp-headline {
  margin: var(--s2) 0 0;
  font-size: var(--fs-base);
  line-height: 1.55;
  color: var(--ink-2);
}

/* The one number on the card the seller might actually type. It is set
   apart from the statistics because it is the only thing here that is a
   suggestion — and the take-home beside it is what stops it being read as
   a race to the bottom. */
.pp-target {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  flex-wrap: wrap;
  margin-top: var(--s3);
  padding: var(--s3) var(--s4);
  border: 1px solid var(--gold);
  border-radius: var(--r-md);
  background: var(--gold-soft);
}

.pp-target-main { display: flex; align-items: baseline; gap: var(--s2); flex-wrap: wrap; }

.pp-target-label {
  font-size: var(--fs-3xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--gold-deep);
}

.pp-target-price {
  font-family: var(--font-display);
  font-size: var(--fs-lg);
  font-weight: var(--fw-bold);
  color: var(--ink);
}

.pp-target-note { font-size: var(--fs-xs); color: var(--muted); }

.pp-target-side { display: flex; align-items: center; gap: var(--s3); flex-wrap: wrap; }

.pp-target-profit { font-size: var(--fs-sm); font-weight: var(--fw-semibold); color: var(--success); }
.pp-target-profit-thin { color: var(--muted); }
.pp-target-floor { font-size: var(--fs-xs); color: var(--muted); }

.pp-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(112px, 1fr));
  gap: var(--s3);
  margin-top: var(--s3);
  padding-top: var(--s3);
  border-top: 1px solid var(--line-soft);
}

.pp-stat { display: flex; flex-direction: column; gap: 2px; }

.pp-stat-label {
  font-size: var(--fs-3xs);
  font-weight: var(--fw-bold);
  letter-spacing: var(--tracking-caps);
  text-transform: uppercase;
  color: var(--muted);
}

.pp-stat-value {
  font-size: var(--fs-sm);
  font-weight: var(--fw-semibold);
  color: var(--ink);
}

/* The shelf itself, cheapest first — the evidence behind the verdict. It
   is on the card rather than behind a link because "you are 7th" is a
   claim, and a seller should be able to check a claim about their own
   money without leaving the screen. */
.pp-rivals {
  list-style: none;
  margin: var(--s3) 0 0;
  padding: 0;
  border-top: 1px solid var(--line-soft);
}

.pp-rival {
  display: grid;
  grid-template-columns: 1fr auto auto;
  align-items: baseline;
  gap: var(--s3);
  padding: var(--s2) 0;
  font-size: var(--fs-xs);
  border-bottom: 1px solid var(--line-soft);
}

.pp-rival:last-child { border-bottom: 0; }

.pp-rival a {
  color: var(--ink-2);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pp-rival a:hover { text-decoration: underline; color: var(--teal-700); }
.pp-rival-price { font-weight: var(--fw-semibold); color: var(--ink); }
.pp-rival-seller { color: var(--muted); }

/* Showing the working. Every listing left out of the ranking was left out
   for a reason, and a board that silently drops half a shelf is a board
   whose position nobody should believe. */
.pp-skipped {
  margin: var(--s2) 0 0;
  font-size: var(--fs-xs);
  line-height: 1.6;
  color: var(--muted);
}

/* Never folded away, same rule as the Restock List: the seller reading
   this card is about to change a price on the strength of it. */
.pp-cautions {
  margin: var(--s3) 0 0;
  padding-left: var(--s4);
  font-size: var(--fs-xs);
  line-height: 1.6;
  color: var(--muted);
}

.pp-cautions li { margin-top: 2px; }

@media (max-width: 720px) {
  .pp-card-actions { width: 100%; }
  .pp-card-actions .btn { flex: 1; }
  .pp-rival { grid-template-columns: 1fr auto; }
  .pp-rival-seller { grid-column: 1 / -1; }
}

/* The every-sale table's editable cost cell. Sized and aligned like the plain number it
   replaced, so a table of 60 rows still reads as figures rather than as a form. It only
   announces itself as editable on hover/focus — the column is scanned far more often than
   it is typed into. */
.er-cost-cell {
  width: 6.5rem;
  padding: 2px 6px;
  text-align: right;
  font: inherit;
  font-variant-numeric: tabular-nums;
  color: inherit;
  background: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
}
.er-cost-cell::placeholder { color: var(--muted, #7e8b98); }
.er-cost-cell:hover { border-color: var(--line, #242c34); }
.er-cost-cell:focus {
  outline: none;
  background: var(--raised, #171d23);
  border-color: var(--accent, #63b39b);
}

/* Marks a row in the waiting-on-a-cost list that IS already counted in the totals — recorded at
   $0.00, so it inflates profit rather than being missing from it. The two cases sit in one list
   and must not look identical. */
.er-row-flag {
  color: var(--warn, #e0a94a);
  font-weight: 600;
}

/* ── WhatsNot: embedded live-feed browser ──────────────────────────────────────
   The frame fills the panel so a live stream has room to be watched. The address
   bar is a plain flex row; the frame sits on black so a blocked (blank) embed reads
   as "the site refused" rather than a broken white box. */
.wn-bar {
  display: flex;
  gap: 8px;
  margin-bottom: 10px;
}
.wn-url {
  flex: 1;
  min-width: 0;
}
.wn-nav {
  display: flex;
  gap: 4px;
  flex: 0 0 auto;
}
.wn-nav-btn {
  min-width: 34px;
  padding: 0 9px;
  font-size: 15px;
  line-height: 1;
}
.wn-nav-btn:disabled {
  opacity: .35;
  cursor: default;
}
.wn-hint {
  margin-bottom: 10px;
}

/* The status strip. A refused embed is the ordinary case here, not an error state, so
   it reads as information — amber, not red — while still being impossible to miss. */
.wn-status {
  margin: 0 0 10px;
  padding: 7px 10px;
  border-radius: 8px;
  font-size: 12.5px;
  border: 1px solid var(--border, #2a2f3a);
  color: var(--text-dim, #8b93a7);
  background: var(--panel-2, rgba(255, 255, 255, .03));
}
.wn-status-ok {
  border-color: rgba(90, 190, 130, .35);
  color: var(--success, #6fcf97);
}
.wn-status-refused {
  border-color: rgba(224, 169, 74, .45);
  color: var(--warn, #e0a94a);
  font-weight: 600;
}
.wn-status-unknown {
  border-color: rgba(224, 169, 74, .28);
}
.wn-frame-wrap {
  position: relative;
  flex: 1 1 auto;
  min-height: 70vh;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 10px;
  overflow: hidden;
  background: #000;
}

/* Sits over the blank rectangle a refused embed leaves behind, because a black box
   with nothing in it reads as a broken app rather than as somebody else's decision. */
.wn-blocked {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 24px;
  text-align: center;
  background: linear-gradient(180deg, rgba(18, 20, 26, .96), rgba(10, 11, 15, .98));
}
.wn-blocked-title {
  font-size: 17px;
  font-weight: 700;
  color: var(--warn, #e0a94a);
}
.wn-blocked-detail {
  margin: 0;
  max-width: 56ch;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-dim, #8b93a7);
}
.wn-blocked-header {
  margin: 0;
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 11.5px;
  color: var(--text-dim, #8b93a7);
  opacity: .8;
}
/* How old the verdict is. Quieter than the refusal itself — it qualifies the claim above
   it, it is not a second claim. */
.wn-blocked-note {
  margin: 0;
  max-width: 56ch;
  font-size: 12px;
  font-style: italic;
  color: var(--text-dim, #8b93a7);
  opacity: .85;
}
/* Wraps rather than squeezing: the frame gets narrow on a laptop beside a live stream and
   three buttons on one line would push the way in off the edge. */
.wn-blocked-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 4px;
}
.wn-frame {
  width: 100%;
  height: 100%;
  min-height: 70vh;
  border: 0;
  display: block;
}

/* ── WhatsNot: the live-auction arbitrage card ─────────────────────────────────
   Read at a glance, during a live sale, by somebody who has seconds. So: one
   badge, one ladder of five numbers, five stat tiles, and the caveats under
   them rather than folded into a score. The frame keeps its space below — the
   card sits above it because the decision is the point and the stream is the
   context, not the other way round. */
/* ── Read the lot off the show ─────────────────────────────────────────────────
   Sits above the ask because it FILLS the ask. Quieter than the card below it:
   this is where the item's name comes from, not what the item is worth, and the
   only coloured thing on a successful read is the strip down its left edge. */
.wn-read {
  margin-bottom: 10px;
}
.wn-read-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: flex-end;
}
.wn-read-btn {
  white-space: nowrap;
}
/* The one control on this screen that reaches the network by itself, so it reads
   as a switch rather than as another button in the row. */
.wn-watch {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 10px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 8px;
  font-size: 12px;
  color: var(--text-dim, #8b93a7);
  cursor: pointer;
  white-space: nowrap;
}
.wn-watch:hover {
  border-color: var(--accent, #d4a24a);
  color: var(--text, #e8ecf4);
}
.wn-watch > input {
  margin: 0;
  cursor: pointer;
}
.wn-read-hint {
  margin: 6px 0 0;
}

.wn-read-out {
  margin-top: 8px;
  padding: 9px 12px;
  border: 1px solid var(--border, #2a2f3a);
  border-left: 3px solid var(--border, #2a2f3a);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
  font-size: 12.5px;
}
/* Found it, read the page and there was no lot on it, and couldn't read it at all.
   Three states, three edges — the middle one is the normal state of a live show
   between lots and is deliberately not a failure colour. The fourth, while the
   request is out, keeps the neutral edge: every read passes through it, including
   the ones that work. */
.wn-read-busy {
  border-left-color: var(--border, #2a2f3a);
}
.wn-read-ok {
  border-left-color: var(--ok, #4ea87a);
}
.wn-read-none {
  border-left-color: var(--text-dim, #8b93a7);
}
.wn-read-bad {
  border-left-color: var(--warn, #d4a24a);
}
.wn-read-main {
  display: flex;
  gap: 10px;
  align-items: flex-start;
}
.wn-read-text {
  flex: 1 1 auto;
  min-width: 0;
}
/* The lot's photo answers the one question its title can't — is this the thing on
   screen. Nothing is priced off it, so it is thumbnail-sized and stays that way. */
.wn-read-shot {
  flex: 0 0 auto;
  width: 64px;
  height: 64px;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid var(--border, #2a2f3a);
  background: var(--panel, rgba(255, 255, 255, .02));
}
.wn-read-head {
  font-weight: 600;
  color: var(--text, #e8ecf4);
  word-break: break-word;
}
.wn-read-detail,
.wn-read-do,
.wn-read-warn {
  margin: 4px 0 0;
  color: var(--text-dim, #8b93a7);
}
.wn-read-warn {
  color: var(--warn, #d4a24a);
}
.wn-read-do {
  color: var(--text, #e8ecf4);
}
/* Folded, because it is the answer to "why did it read that?" and not part of the
   read itself. Open it and every field says which key in the page it came from. */
.wn-read-ev {
  margin-top: 6px;
  font-size: 11.5px;
  color: var(--text-dim, #8b93a7);
}
.wn-read-ev > summary {
  cursor: pointer;
}
.wn-read-ev ul {
  margin: 4px 0 0;
  padding-left: 18px;
  word-break: break-word;
}

/* ── What the photo says about the name ────────────────────────────────────────
   The check on everything below it: the ceiling is a sold search on a NAME, and
   the lot's photograph is the only thing on this screen that can contradict it.
   Deliberately built to the read panel's shape — same edge, same thumbnail, same
   folded evidence — because it answers the same kind of question about the same
   lot, and a second visual language for that would read as a second feature.
   Four edges rather than the read's three: agreeing and sharpening are both good
   news and only one of them is worth looking up for. */
.wn-photo-btn {
  white-space: nowrap;
}
.wn-photo-out {
  margin-top: 8px;
  padding: 9px 12px;
  border: 1px solid var(--border, #2a2f3a);
  border-left: 3px solid var(--border, #2a2f3a);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
  font-size: 12.5px;
}
/* In flight keeps the neutral edge: every look passes through it, including the
   ones that agree. */
.wn-photo-busy {
  border-left-color: var(--border, #2a2f3a);
}
.wn-photo-ok {
  border-left-color: var(--ok, #4ea87a);
}
/* The photo found something the name didn't. Not a warning — this is the case the
   whole check exists for, and it wears the colour of an opportunity. */
.wn-photo-new {
  border-left-color: var(--accent, #d4a24a);
}
/* The photo and the name are about different products, which means the ceiling
   below belongs to something that is not on screen. The loudest thing this
   screen can say short of the badge itself. */
.wn-photo-bad {
  border-left-color: var(--danger, #d05a5a);
}
.wn-photo-none {
  border-left-color: var(--text-dim, #8b93a7);
}
.wn-photo-main {
  display: flex;
  gap: 10px;
  align-items: flex-start;
}
.wn-photo-text {
  flex: 1 1 auto;
  min-width: 0;
}
.wn-photo-shot {
  flex: 0 0 auto;
  width: 64px;
  height: 64px;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid var(--border, #2a2f3a);
  background: var(--panel, rgba(255, 255, 255, .02));
}
.wn-photo-head {
  font-weight: 600;
  color: var(--text, #e8ecf4);
  word-break: break-word;
}
.wn-photo-detail,
.wn-photo-do,
.wn-photo-warn,
.wn-photo-ask {
  margin: 4px 0 0;
  color: var(--text-dim, #8b93a7);
}
.wn-photo-warn {
  color: var(--warn, #d4a24a);
}
.wn-photo-do {
  color: var(--text, #e8ecf4);
}
/* The one line on this panel that is a question rather than a fact. On a live show
   the seller can't pick the thing up — but the chat is open and the host is holding
   it, which makes this the most actionable sentence here. */
.wn-photo-ask {
  color: var(--text, #e8ecf4);
  font-weight: 600;
}
/* The offer, above the caveats and sized like something you press while a hammer is
   coming down. It is the only way a photo ever reaches the item box. */
.wn-photo-use {
  margin-top: 8px;
}
.wn-photo-use-btn {
  max-width: 100%;
  text-align: left;
  white-space: normal;
  word-break: break-word;
}
.wn-photo-ev {
  margin-top: 6px;
  font-size: 11.5px;
  color: var(--text-dim, #8b93a7);
}
.wn-photo-ev > summary {
  cursor: pointer;
}
.wn-photo-ev ul {
  margin: 4px 0 0;
  padding-left: 18px;
  word-break: break-word;
}

.wn-ask {
  margin-bottom: 12px;
}
.wn-ask-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: flex-end;
}
.wn-field {
  display: flex;
  flex-direction: column;
  gap: 3px;
  width: 108px;
}
.wn-field-wide {
  flex: 1 1 260px;
  width: auto;
  min-width: 200px;
}
.wn-field > span {
  font-size: 11px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text-dim, #8b93a7);
}
.wn-field > input,
.wn-field > select {
  width: 100%;
  min-width: 0;
}
/* The bid is the field that moves during a lot, so it gets hands: − and + either
   side of it, sized to be hit without looking away from the stream. */
.wn-stepper {
  display: flex;
  align-items: stretch;
  gap: 4px;
}
.wn-stepper > input {
  flex: 1 1 auto;
  min-width: 0;
  text-align: center;
}
.wn-step {
  flex: 0 0 auto;
  width: 30px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
  color: var(--text, #e8ecf4);
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
}
.wn-step:hover {
  border-color: var(--accent, #d4a24a);
  color: var(--accent, #d4a24a);
}
.wn-field-bid {
  width: 168px;
}
/* One or two digits, never more — anything past this screen's cap is a spec. Narrow
   on purpose: it is beside the bid, and the bid is the field the eye goes to. */
.wn-field-qty {
  width: 74px;
}
/* Narrow for the same reason, and next to the bid because it is a fact about the
   bid: how far one press of the button moves it. Usually blank. */
.wn-field-inc {
  width: 86px;
}
/* Who is selling. Wider than the money boxes because it holds a handle or a pasted
   address, and it sits immediately before the two shipping rates because the three
   of them are one fact together: which box this lot is arriving in. */
.wn-field-show {
  width: 150px;
}
/* The extra-item rate. Beside Shipping, and no wider — the pair is meant to read as
   "first one / each one after". */
.wn-field-shipadd {
  width: 108px;
}
/* The seller's own combined rate, beside the premium and no wider — the two things
   the platform bills on top of the hammer read as one pair. */
.wn-field-tax {
  width: 108px;
}
/* And the two of them wrap together or not at all. Split across two lines by the
   row's own flex-wrap they stop being a pair, and "Each extra" on its own is a
   figure with nothing to be extra to. */
.wn-field-pair {
  display: flex;
  gap: 8px;
  align-items: flex-end;
}
/* The one tick on this row. It is not a figure, so it does not get a figure's
   column: the label sits beside the box rather than above it, and the whole
   thing is only as wide as the two words it holds. Bottom-aligned so it sits on
   the same baseline as the inputs it qualifies rather than floating above them. */
.wn-field-exempt {
  flex-direction: row;
  align-items: center;
  gap: 6px;
  width: auto;
  align-self: flex-end;
  padding-bottom: 7px;
  white-space: nowrap;
}
.wn-field-exempt > input {
  width: auto;
  margin: 0;
  cursor: pointer;
}
/* Tonight's money. Wider than the rate boxes because it holds a night's spend
   rather than two digits, and last on the row because it is the only field here
   that is not about the item on screen. */
.wn-field-budget {
  width: 132px;
}

.wn-price-btn {
  white-space: nowrap;
}
.wn-ask-hint {
  margin: 6px 0 0;
}

/* ── The one line ─────────────────────────────────────────────────────────────
   The card below is replaced whole every time the bid moves, so the eye has to
   re-find whatever it was reading. This does not move: same place, same length,
   one sentence. It is also the screen's only live region — see index.html.
   Its words are the server's (Services/LiveBidSpeech.cs); the stripe is the only
   thing decided here, and it uses the badge's own four colours. */
.wn-say {
  margin: 0 0 8px;
  padding: 9px 12px;
  border: 1px solid var(--border, #2a2f3a);
  border-left: 3px solid var(--border, #2a2f3a);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
  font-size: 14px;
  font-weight: 600;
  line-height: 1.45;
  font-variant-numeric: tabular-nums;
}
.wn-say-bid {
  border-left-color: var(--success, #5ac882);
}
.wn-say-risky {
  border-left-color: var(--warn, #e0a94a);
}
.wn-say-stop,
.wn-say-no_data {
  border-left-color: var(--danger, #e0796a);
}

.wn-card {
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 12px;
  padding: 14px;
  margin-bottom: 12px;
  background: var(--panel-2, rgba(255, 255, 255, .02));
}
/* Opening a lot from the list moves the keyboard here. That is a programmatic
   focus, not a tab stop, so it gets no ring — but a seller who tabbed here on
   purpose still does. */
.wn-card:focus {
  outline: none;
}
.wn-card:focus-visible {
  outline: 2px solid var(--accent, #d4a24a);
  outline-offset: 2px;
}
.wn-empty {
  margin: 0;
  color: var(--text-dim, #8b93a7);
}
.wn-empty-bad {
  color: var(--danger, #e0796a);
}
/* The half of a failure that says what to do about it. Dimmer than the headline
   and directly under it, because it is the sentence you act on. */
.wn-empty-do {
  margin: 4px 0 0;
}

/* The badge carries the answer, so it carries the colour. Four states, and the
   two that are not "go" have to look different from each other: thin evidence is
   not the same warning as "the bidding has passed your ceiling". */
.wn-call {
  display: flex;
  gap: 12px;
  align-items: center;
  flex-wrap: wrap;
}
.wn-call-badge {
  font-size: 17px;
  font-weight: 800;
  letter-spacing: .02em;
  padding: 8px 14px;
  border-radius: 10px;
  white-space: nowrap;
  border: 1px solid transparent;
}
.wn-call-bid .wn-call-badge {
  background: rgba(90, 200, 130, .14);
  border-color: rgba(90, 200, 130, .45);
  color: var(--success, #5ac882);
}
.wn-call-risky .wn-call-badge {
  background: rgba(224, 169, 74, .14);
  border-color: rgba(224, 169, 74, .45);
  color: var(--warn, #e0a94a);
}
.wn-call-stop .wn-call-badge,
.wn-call-no_data .wn-call-badge {
  background: rgba(224, 121, 106, .12);
  border-color: rgba(224, 121, 106, .4);
  color: var(--danger, #e0796a);
}
.wn-call-body {
  flex: 1 1 260px;
  min-width: 0;
}
.wn-call-item {
  font-weight: 600;
}
.wn-call-reason {
  margin: 2px 0 0;
  color: var(--text-dim, #8b93a7);
}

/* ── What was actually asked ──────────────────────────────────────────────────
   One quiet line, first, because everything under it is the answer to it: the sold
   lookup is a boolean AND, and a live lot's name is half auction talk. Quiet by
   default (it is on every card), and it grows an edge only when the search had to
   be widened to find any sold history at all — which is the case where the comps
   are for a broader question than the lot on screen. */
.wn-search {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 10px 0 0;
  padding: 7px 10px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
  font-size: 13px;
}
.wn-search-wide {
  border-left: 3px solid var(--warn, #e0b44a);
}
.wn-search-exact-on {
  border-left: 3px solid var(--accent, #d4a24a);
}
.wn-search-line {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  align-items: baseline;
  min-width: 0;
}
.wn-search-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-search-query {
  min-width: 0;
  overflow-wrap: anywhere;
}
.wn-search-tag {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(224, 180, 74, .16);
  color: var(--warn, #e0b44a);
}
/* What was left out, struck through, with the reason on the chip. The seller has
   seconds and one glance has to answer "did it drop something I meant?". */
.wn-search-drops {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
}
.wn-search-drop {
  font-size: 12px;
  padding: 1px 7px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 999px;
  color: var(--text-dim, #8b93a7);
  cursor: help;
}
.wn-search-drop-count {
  border-color: rgba(212, 162, 74, .45);
}
.wn-search-drop-widened {
  border-color: rgba(224, 180, 74, .45);
  color: var(--warn, #e0b44a);
}
.wn-search-note {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-dim, #8b93a7);
}
.wn-search-undo {
  align-self: flex-start;
  padding: 0;
  border: 0;
  background: none;
  font: inherit;
  font-size: 12.5px;
  color: var(--accent, #d4a24a);
  text-decoration: underline;
  cursor: pointer;
}

/* ── The press, not the price ─────────────────────────────────────────────────
   First under the badge, above everything else on the card, because it is the only
   block about the thing the hand is about to do — and because it is the one block
   that can contradict the room figure printed further down. The headline is an
   integer where the rest of the card is dollars, which is what makes it readable in
   the half-second a live sale gives you.

   Coloured by what to do rather than by how the lot scored: green while there are
   presses in hand, amber on the last one, red the moment there is no press left
   that stays under the ceiling. */
.wn-next {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 10px 0 0;
  padding: 7px 10px;
  border: 1px solid var(--border, #2a2f3a);
  border-left: 3px solid var(--border, #2a2f3a);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
  font-size: 13px;
}
.wn-next-press {
  border-left-color: var(--success, #43c08f);
}
/* One press left. Amber rather than red: this is a bid the seller may still make,
   and it is the last one. */
.wn-next-last {
  border-left-color: var(--warn, #e0b44a);
}
/* The gap this block exists for — room above the bid, and no press that stays
   inside it — and the case where the bidding has already gone past. */
.wn-next-stop,
.wn-next-over {
  border-left-color: var(--danger, #d46a6a);
}
.wn-next-line {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  align-items: baseline;
  min-width: 0;
}
.wn-next-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-next-headline {
  min-width: 0;
  overflow-wrap: anywhere;
  font-size: 15px;
  font-variant-numeric: tabular-nums;
}
.wn-next-press .wn-next-headline {
  color: var(--success, #43c08f);
}
.wn-next-last .wn-next-headline {
  color: var(--warn, #e0b44a);
}
.wn-next-stop .wn-next-headline,
.wn-next-over .wn-next-headline {
  color: var(--danger, #d46a6a);
}
/* What pressing costs. Beside the headline rather than inside it, so the count and
   the dollar figure are never mistaken for one another. */
.wn-next-amount {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text, #d8dce6);
}
.wn-next-tag {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(139, 147, 167, .16);
  color: var(--text-dim, #8b93a7);
}
.wn-next-note {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text, #d8dce6);
}
/* The increment itself, and how to overrule it. Dimmest line on the strip: it is
   an assumption on most cards, and an assumption nobody can see is one nobody can
   correct. */
.wn-next-assumed {
  margin: 0;
  font-size: 12px;
  line-height: 1.5;
  color: var(--text-dim, #8b93a7);
}

/* ── Which way the sold price has been going ──────────────────────────────────
   Directly under the search strip, and for the same reason it is there: both are
   facts about what the numbers below MEAN rather than facts about the money.

   Quiet by default. A steady item and an unreadable one are the common cases and
   neither is worth a colour, so the only edges that appear are the two that change
   the decision: a fall that took money off the ceiling, and sales that stopped. */
.wn-trend {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 10px 0 0;
  padding: 7px 10px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
  font-size: 13px;
}
/* A slide that moved the money. The one state on this strip the seller has to see
   without reading it, because the ceiling above it is already lower for it. */
.wn-trend-cut {
  border-left: 3px solid var(--danger, #d46a6a);
}
/* Falling, but not by enough or not firmly enough to have cut anything. Worth a
   glance, deliberately not worth an alarm — the ceiling below it is untouched. */
.wn-trend-falling {
  border-left: 3px solid var(--warn, #e0b44a);
}
.wn-trend-line {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  align-items: baseline;
  min-width: 0;
}
.wn-trend-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-trend-headline {
  min-width: 0;
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}
.wn-trend-falling .wn-trend-headline {
  color: var(--warn, #e0b44a);
}
.wn-trend-cut .wn-trend-headline {
  color: var(--danger, #d46a6a);
}
.wn-trend-rising .wn-trend-headline {
  color: var(--success, #43c08f);
}
.wn-trend-tag {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(212, 106, 106, .16);
  color: var(--danger, #d46a6a);
}
/* "Tentative" is a hedge on a reading, not a warning about money — it never earns
   the red the cut wears. */
.wn-trend-tag-soft {
  background: rgba(139, 147, 167, .16);
  color: var(--text-dim, #8b93a7);
}
/* What the read did, or did not do, to the ceiling. Said on every readable card,
   so this is the line that is normally the whole block. */
.wn-trend-money {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text, #d8dce6);
}
/* The measurement's own sentence, caveats and all. Dimmer than the money line
   above it: it is the working, and the working is for the seller who doubts. */
.wn-trend-note {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-dim, #8b93a7);
}

/* ── What kind of one it is, against what kind the comps were ─────────────────
   Directly under the trend strip and for the same reason: both are facts about what
   the money below MEANS rather than facts about the money. A ceiling built on a
   median of sealed boxes is a real ceiling for a sealed box, and the thing on screen
   has been out of its box since 2019.

   Quiet by default. Mixed comps under an unstated lot are the ordinary case and get
   no colour at all — the bars carry it. The two edges that appear are the ones that
   change the decision: a condition gap that took money off the ceiling, and one that
   could not be priced away and left the badge knowingly optimistic. */
.wn-cond-strip {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 10px 0 0;
  padding: 7px 10px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
  font-size: 13px;
}
/* Something the seller has to act on: either the ceiling is already cut for the
   condition, or it is a better-condition price and nothing could correct it. */
.wn-cond-warn {
  border-left: 3px solid var(--warn, #e0b44a);
}
.wn-cond-cut {
  border-left: 3px solid var(--danger, #d46a6a);
}
.wn-cond-line {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  align-items: baseline;
  min-width: 0;
}
.wn-cond-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-cond-headline {
  min-width: 0;
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}
.wn-cond-cut .wn-cond-headline {
  color: var(--danger, #d46a6a);
}
.wn-cond-tag {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(212, 106, 106, .16);
  color: var(--danger, #d46a6a);
}
/* Where the band came from — the seller's own eyes, or a word in the lot's name.
   Never louder than the headline: it is provenance, not news. */
.wn-cond-src {
  font-size: 11.5px;
  color: var(--text-dim, #8b93a7);
}
/* The bars. "9 new at $120 · 3 used at $55" is the whole feature in one glance —
   the sentence that decides the bid, in the time there is to read it. */
.wn-cond-bands {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
  min-width: 0;
}
.wn-cond-band {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 3px 8px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 6px;
  background: var(--panel, rgba(255, 255, 255, .02));
  min-width: 0;
}
/* The band the lot is actually in — the rows the ceiling should be standing on.
   The only one that gets a colour, because it is the only one being asked a
   question. */
.wn-cond-band-mine {
  border-color: var(--accent, #6ea8fe);
  background: rgba(110, 168, 254, .1);
}
.wn-cond-band-name {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-cond-band-mine .wn-cond-band-name {
  color: var(--accent, #6ea8fe);
}
.wn-cond-band-fig {
  font-size: 12.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* What the read did, or did not do, to the ceiling. Said on every priced card, so
   this is the line that is normally the whole block. */
.wn-cond-money {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text, #d8dce6);
}
/* The picker itself, sized like the fixed-width boxes beside it. Wider than the
   quantity because its answers are words rather than digits, and narrow enough that
   it never takes width off the item name. */
.wn-field-cond {
  width: 148px;
}

/* ── How many things is this ──────────────────────────────────────────────────
   Between the badge and the ladder, because it is what every number under it is a
   number ABOUT: a ceiling for three miners looks exactly like a ceiling for one.
   Three edges, and they mean three different things — a lot that was counted, a
   lot whose size nobody stated (priced as ONE, and the seller can fix it), and a
   count that was found and deliberately not used. */
.wn-units {
  margin: 12px 0 0;
  padding: 9px 12px;
  border: 1px solid var(--border, #2a2f3a);
  border-left: 3px solid var(--accent, #d4a24a);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
}
.wn-units-ask {
  border-left-color: var(--warn, #e0b44a);
}
.wn-units-refused {
  border-left-color: var(--border, #2a2f3a);
}
.wn-units-head {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: baseline;
  font-size: 14px;
}
.wn-units-src {
  font-size: 12px;
  color: var(--text-dim, #8b93a7);
}
.wn-units-note {
  margin: 4px 0 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--text-dim, #8b93a7);
}
p.wn-units-refused {
  margin: 4px 0 0;
  padding: 0;
  border: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--warn, #e0b44a);
}
/* What one of them is worth — the figure the seller carries between lots, while
   everything above it is the figure the hammer is measured against. */
.wn-units-each {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 16px;
  margin-top: 6px;
  font-size: 13px;
  font-variant-numeric: tabular-nums;
  color: var(--text-dim, #8b93a7);
}

/* ── How many of these you'd then own ─────────────────────────────────────────
   Directly under the unit count, because it is the same count carried one step
   further: how many are in this lot, and then how many that makes altogether.
   Sits above the ladder for the same reason the strips above it do — everything
   below is money, and this is the only block on the card that is about a calendar.

   Four edges, and only two of them are news. A single item and a stack the market
   clears comfortably get no colour at all: this strip is on every card, and one
   that shouted on all of them would train the eye to skip the two that matter. */
.wn-stock {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 10px 0 0;
  padding: 7px 10px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
  font-size: 13px;
}
/* Months of stock. Real money, parked — an amber sentence, not a red one: every
   unit still makes its profit, it just makes it later. */
.wn-stock-deep {
  border-left: 3px solid var(--warn, #e0b44a);
}
/* Long enough that the last one will be sold into a market nothing on this card
   has any evidence about. */
.wn-stock-flooded {
  border-left: 3px solid var(--danger, #d46a6a);
}
/* A pile with no dated history to measure it against — unknown rather than bad,
   and the strip says so in words rather than in colour. */
.wn-stock-blind,
.wn-stock-none {
  border-left: 3px solid var(--border, #2a2f3a);
}
.wn-stock-line {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  align-items: baseline;
  min-width: 0;
}
.wn-stock-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-stock-headline {
  min-width: 0;
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}
.wn-stock-deep .wn-stock-headline {
  color: var(--warn, #e0b44a);
}
.wn-stock-flooded .wn-stock-headline {
  color: var(--danger, #d46a6a);
}
.wn-stock-tag {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(224, 180, 74, .16);
  color: var(--warn, #e0b44a);
}
.wn-stock-flooded .wn-stock-tag {
  background: rgba(212, 106, 106, .16);
  color: var(--danger, #d46a6a);
}
/* That the pile may be somebody else's product. Provenance, never news. */
.wn-stock-src {
  font-size: 11.5px;
  color: var(--text-dim, #8b93a7);
}
/* The bars — the whole feature in one glance. "2 on the shelf · 1 won tonight ·
   2 in this lot" is the sentence that decides whether to press bid on the fourth
   one, in the time a live lot leaves to read it. */
.wn-stock-bars {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
  min-width: 0;
}
.wn-stock-bar {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 3px 8px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 6px;
  background: var(--panel, rgba(255, 255, 255, .02));
  min-width: 0;
}
/* The two that are already committed money get the colour; the lot on screen is
   not owned yet and is drawn as the plain one. Tonight's is the loud one because
   it is the count nothing else on this card can see. */
.wn-stock-bar-shelf {
  border-color: rgba(224, 180, 74, .5);
  background: rgba(224, 180, 74, .08);
}
.wn-stock-bar-tonight {
  border-color: var(--accent, #6ea8fe);
  background: rgba(110, 168, 254, .1);
}
.wn-stock-bar-name {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-stock-bar-tonight .wn-stock-bar-name {
  color: var(--accent, #6ea8fe);
}
.wn-stock-bar-fig {
  font-size: 12.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
/* The arithmetic behind the headline. Said on every card, so this is normally the
   whole block. */
.wn-stock-note {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-dim, #8b93a7);
}

/* ── What the queue in front of it costs ─────────────────────────────────────
   The stock strip above counts the pile and deliberately prices nothing, because
   "you already have three" is not a measured reason to pay less. This prices the
   other half of that sentence — the fourth one sells in April, and the trend line
   on this same card says in dollars a month what April costs.

   So a deep pile of a FLAT product is drawn as an ordinary strip with no colour
   and no cells: nothing was charged, and the seller has to be able to see that at
   a glance. Only the state that actually moved money gets an edge. */
.wn-hold {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 10px 0 0;
  padding: 7px 10px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
  font-size: 13px;
}
/* A measured slide across a real wait, taken off the ceiling. The one state here
   that is a cut, so the one state that is coloured like one. */
.wn-hold-priced {
  border-left: 3px solid var(--danger, #d46a6a);
}
/* A slide was seen and judged too thin to price. The ceiling is the full one, and
   the amber says the seller is the one holding that risk. */
.wn-hold-unsure {
  border-left: 3px solid var(--warn, #e0b44a);
}
/* A real wait with no dated history to say what happens across it. */
.wn-hold-blind {
  border-left: 3px solid var(--warn, #e0b44a);
}
/* Nothing was charged: no queue, a short one, or a price that is not falling. */
.wn-hold-solo,
.wn-hold-quick,
.wn-hold-steady,
.wn-hold-none {
  border-left: 3px solid var(--border, #2a2f3a);
}
.wn-hold-line {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  align-items: baseline;
  min-width: 0;
}
.wn-hold-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-hold-headline {
  min-width: 0;
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}
.wn-hold-priced .wn-hold-headline {
  color: var(--danger, #d46a6a);
}
.wn-hold-unsure .wn-hold-headline,
.wn-hold-blind .wn-hold-headline {
  color: var(--warn, #e0b44a);
}
/* What the wait took off, as a percentage of the ceiling. Drawn as a cost, which
   is the opposite of the shipping strip's badge and deliberately so — the two are
   the only tags on this card that move money, in opposite directions. */
.wn-hold-tag {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(212, 106, 106, .16);
  color: var(--danger, #d46a6a);
}
/* That the wait ran past what the slide's evidence can be projected across, so
   the figure beside it is knowingly the gentle one. Provenance, never news. */
.wn-hold-src {
  font-size: 11.5px;
  overflow-wrap: anywhere;
  color: var(--text-dim, #8b93a7);
}
/* The whole argument in three figures: how long yours waits, how fast these are
   falling, and what that is worth a unit. The last one is what came off the
   ceiling, so it is the one drawn loud. */
.wn-hold-box {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
  min-width: 0;
}
.wn-hold-cell {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 3px 8px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 6px;
  background: var(--panel, rgba(255, 255, 255, .02));
  min-width: 0;
}
.wn-hold-cell-this {
  border-color: var(--danger, #d46a6a);
  background: rgba(212, 106, 106, .1);
}
.wn-hold-cell-name {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-hold-cell-this .wn-hold-cell-name {
  color: var(--danger, #d46a6a);
}
.wn-hold-cell-fig {
  font-size: 12.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.wn-hold-note,
.wn-hold-money {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-dim, #8b93a7);
}
/* What the ceiling below was priced at, and why. Said in every state, including
   the ones that took nothing off — so it is set apart from the arithmetic above
   it the same way the trend and condition strips set theirs apart. */
.wn-hold-money {
  padding-top: 4px;
  border-top: 1px solid var(--border, #2a2f3a);
}

/* ── What this lot costs to get delivered ────────────────────────────────────
   A live seller posts one box per show, not one per lot. This is the only strip
   on the card that can raise a ceiling — everything else here either describes
   the market or takes money off — so it is the only one drawn in the positive
   colour, and only in the state where money really moved. */
.wn-ship {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 10px 0 0;
  padding: 7px 10px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
  font-size: 13px;
}
/* The one good edge on this card: the lot rides in a box already going out, so
   the ceiling above is genuinely higher than the one the first lot got. */
.wn-ship-combined {
  border-left: 3px solid var(--ok, #5fbf7f);
}
/* Repeated wins from one show with no extra-item rate entered — the ceiling is
   knowingly charging full freight for a box already on its way, and only the
   seller can fix it. */
.wn-ship-unstated {
  border-left: 3px solid var(--warn, #e0b44a);
}
/* Nothing entered at all, so the ceiling is costing the lot as though it arrived
   free. Real money, missing. */
.wn-ship-none {
  border-left: 3px solid var(--danger, #d46a6a);
}
.wn-ship-alone,
.wn-ship-first {
  border-left: 3px solid var(--border, #2a2f3a);
}
.wn-ship-line {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  align-items: baseline;
  min-width: 0;
}
.wn-ship-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-ship-headline {
  min-width: 0;
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}
.wn-ship-combined .wn-ship-headline {
  color: var(--ok, #5fbf7f);
}
.wn-ship-unstated .wn-ship-headline {
  color: var(--warn, #e0b44a);
}
.wn-ship-none .wn-ship-headline {
  color: var(--danger, #d46a6a);
}
/* What combining is worth on this lot, in dollars of extra room under the
   ceiling. The one badge on this card that is good news. */
.wn-ship-tag {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(95, 191, 127, .16);
  color: var(--ok, #5fbf7f);
}
/* Which show the box belongs to. Provenance, never news. */
.wn-ship-src {
  font-size: 11.5px;
  overflow-wrap: anywhere;
  color: var(--text-dim, #8b93a7);
}
/* The box, in three figures: what tonight's shipment already costs, what this lot
   adds to it, and what it becomes. The middle one is the number the ceiling above
   was actually costed at, so it is the one drawn loud. */
.wn-ship-box {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
  min-width: 0;
}
.wn-ship-cell {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 3px 8px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 6px;
  background: var(--panel, rgba(255, 255, 255, .02));
  min-width: 0;
}
.wn-ship-cell-this {
  border-color: var(--accent, #6ea8fe);
  background: rgba(110, 168, 254, .1);
}
.wn-ship-cell-name {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-ship-cell-this .wn-ship-cell-name {
  color: var(--accent, #6ea8fe);
}
.wn-ship-cell-fig {
  font-size: 12.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.wn-ship-note {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-dim, #8b93a7);
}

/* ── What the marketplace bills on top ───────────────────────────────────────
   The fourth part of the landed cost. A live platform is a marketplace
   facilitator and collects the buyer's sales tax at checkout, so this is money
   that leaves the wallet on every win — and for fifteen versions of this card it
   was in none of them. Drawn immediately under the shipping strip because the
   two are the same kind of fact: what winning costs beyond the hammer. */
.wn-tax {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 10px 0 0;
  padding: 7px 10px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
  font-size: 13px;
}
/* Nothing entered, so the ceiling above is costing the lot as though the
   platform collected nothing. Real money, missing — the same red the shipping
   strip uses for the same silence. */
.wn-tax-none {
  border-left: 3px solid var(--danger, #d46a6a);
}
/* A rate is charged and the ceiling came down for it. Not an error and not good
   news: it is the card being right, which is what the neutral warn edge says. */
.wn-tax-charged {
  border-left: 3px solid var(--warn, #e0b44a);
}
/* Charged nothing, correctly — a certificate on file, or a state that levies
   none. Both are ordinary states of an accurate card and get the ordinary edge. */
.wn-tax-exempt,
.wn-tax-free {
  border-left: 3px solid var(--border, #2a2f3a);
}
.wn-tax-line {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  align-items: baseline;
  min-width: 0;
}
.wn-tax-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-tax-headline {
  min-width: 0;
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}
.wn-tax-none .wn-tax-headline {
  color: var(--danger, #d46a6a);
}
.wn-tax-charged .wn-tax-headline {
  color: var(--warn, #e0b44a);
}
/* How much lower the ceiling is for the tax — which is not the rate, because the
   tax multiplies the landed cost and the ceiling divides by it. */
.wn-tax-tag {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(224, 180, 74, .16);
  color: var(--warn, #e0b44a);
}
/* The size of the silence, in the one state that has one. Dim, because it is a
   figure nothing was charged at — it exists to say whether filling the box in is
   worth the four seconds. */
.wn-tax-src {
  font-size: 11.5px;
  overflow-wrap: anywhere;
  color: var(--text-dim, #8b93a7);
}
/* The arithmetic in three figures: what it is charged on, at what rate, and what
   that adds. The last one is the money the ceiling above was costed with, so it
   is the one drawn loud. */
.wn-tax-box {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
  min-width: 0;
}
.wn-tax-cell {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 3px 8px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 6px;
  background: var(--panel, rgba(255, 255, 255, .02));
  min-width: 0;
}
.wn-tax-cell-this {
  border-color: var(--accent, #6ea8fe);
  background: rgba(110, 168, 254, .1);
}
.wn-tax-cell-name {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-tax-cell-this .wn-tax-cell-name {
  color: var(--accent, #6ea8fe);
}
.wn-tax-cell-fig {
  font-size: 12.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.wn-tax-note {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-dim, #8b93a7);
}

/* ── And whether the money is there ──────────────────────────────────────────
   The last strip before the ladder, and the only one on this card that is not
   about the item. Everything above it says what the thing is worth; this says
   what is left to pay for it. Drawn immediately over the ceiling it can lower,
   because on the two states that lower it the number in the ladder is the
   wallet's answer and not the market's. */
.wn-budget {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 10px 0 0;
  padding: 7px 10px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
  font-size: 13px;
}
/* No budget set. Not an error — the card is right, and the ceiling above is the
   market's — so this is the quietest edge on the strip even when there is real
   money on tonight's sheet. Nothing here is being got wrong; it is simply not
   being counted, and the seller is the one who decides whether to. */
.wn-budget-none {
  border-left: 3px solid var(--border, #2a2f3a);
}
/* Room to spare. The ordinary state of a good night. */
.wn-budget-clear {
  border-left: 3px solid var(--border, #2a2f3a);
}
/* The ceiling above is the cash and not the comps. Not an error and not good
   news: it is the card being right about a limit the seller set, which is what
   the neutral warn edge says everywhere else on this screen. */
.wn-budget-capped {
  border-left: 3px solid var(--warn, #e0b44a);
}
/* Nothing left to bid with. The one state that stops the card, and the only one
   drawn as a refusal. */
.wn-budget-spent {
  border-left: 3px solid var(--danger, #d46a6a);
}
.wn-budget-line {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  align-items: baseline;
  min-width: 0;
}
.wn-budget-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-budget-headline {
  min-width: 0;
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}
.wn-budget-capped .wn-budget-headline {
  color: var(--warn, #e0b44a);
}
.wn-budget-spent .wn-budget-headline {
  color: var(--danger, #d46a6a);
}
/* How much lower the ceiling is because of the cash. The same shape as the tax
   tag beside it, and for the same reason: a number that moved the ceiling has to
   say by how much, or the seller cannot check it. */
.wn-budget-tag {
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: .04em;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(224, 180, 74, .16);
  color: var(--warn, #e0b44a);
}
.wn-budget-src {
  font-size: 11.5px;
  overflow-wrap: anywhere;
  color: var(--text-dim, #8b93a7);
}
/* The night, as one bar. It is a width and nothing else — every figure in it is
   the server's, and the point of drawing it is that "68% gone" is read in the
   half-second a live card gets and three dollar figures are not. */
.wn-budget-bar {
  position: relative;
  height: 6px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--panel, rgba(255, 255, 255, .06));
}
.wn-budget-bar-spent {
  height: 100%;
  border-radius: 999px;
  background: var(--accent, #6ea8fe);
  transition: width .18s ease-out;
}
.wn-budget-capped .wn-budget-bar-spent {
  background: var(--warn, #e0b44a);
}
.wn-budget-spent .wn-budget-bar-spent {
  background: var(--danger, #d46a6a);
}
/* The arithmetic in three figures: what was set aside, what has gone, what is
   left. The last is the one the ceiling above was capped by, so it is the one
   drawn loud. */
.wn-budget-box {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
  min-width: 0;
}
.wn-budget-cell {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 3px 8px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 6px;
  background: var(--panel, rgba(255, 255, 255, .02));
  min-width: 0;
}
.wn-budget-cell-this {
  border-color: var(--accent, #6ea8fe);
  background: rgba(110, 168, 254, .1);
}
.wn-budget-cell-name {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-budget-cell-this .wn-budget-cell-name {
  color: var(--accent, #6ea8fe);
}
.wn-budget-cell-fig {
  font-size: 12.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.wn-budget-note {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-dim, #8b93a7);
}

.wn-ladder {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin: 12px 0 0;
}
.wn-rung {
  flex: 1 1 110px;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 8px 10px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 8px;
}
.wn-rung > span {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-rung > strong {
  font-size: 17px;
  font-variant-numeric: tabular-nums;
}
.wn-rung-stop > strong {
  color: var(--success, #5ac882);
}
.wn-neg {
  color: var(--danger, #e0796a);
}

/* The meter. One track from nothing to the walk-away line: green up to the ceiling,
   the ceiling marked, and the bid sitting somewhere along it. This is the part read
   in the two seconds a live lot gives you, so it carries no text of its own beyond
   three labels — the numbers are in the ladder directly above it. */
.wn-meter {
  margin: 10px 0 0;
}
.wn-meter-track {
  position: relative;
  height: 10px;
  border-radius: 999px;
  background: var(--danger-soft, rgba(224, 121, 106, .22));
  overflow: hidden;
}
.wn-meter-good {
  position: absolute;
  inset: 0 auto 0 0;
  background: var(--success, #5ac882);
  opacity: .55;
}
/* The ceiling itself, drawn as a line rather than a colour change, because the
   colour change is the thing being explained. */
.wn-meter-edge {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 2px;
  margin-left: -1px;
  background: var(--success, #5ac882);
}
.wn-meter-bid {
  position: absolute;
  top: -3px;
  bottom: -3px;
  width: 3px;
  margin-left: -1px;
  border-radius: 2px;
  background: var(--text, #e8ecf4);
  box-shadow: 0 0 0 2px var(--panel-2, rgba(0, 0, 0, .35));
}
.wn-meter-bid-past {
  background: var(--danger, #e0796a);
}
.wn-meter-legend {
  display: flex;
  justify-content: space-between;
  gap: 8px;
  margin-top: 4px;
  font-size: 11px;
  color: var(--text-dim, #8b93a7);
  font-variant-numeric: tabular-nums;
}
.wn-meter-mid {
  color: var(--success, #5ac882);
}

/* ── And how much of the evidence agrees ─────────────────────────────────────
   Drawn directly under the meter, because it is the answer to the question the
   meter raises: the ceiling on that track is the bid that keeps the MIDDLE of the
   sold prices clearing a target, and half the sales came in under the middle.
   This is the count of the ones that would actually have paid for the win.

   It is the only block on the card that moves with the bidding rather than with
   the item, so the bar is the part that matters: a seller watching it shorten as
   somebody else raises is watching the arbitrage close. */
.wn-odds {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 10px 0 0;
  padding: 7px 10px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
  font-size: 13px;
}
/* Most of the evidence is behind the bid. The only good-news edge on this strip,
   and the one state that needs no explaining. */
.wn-odds-strong {
  border-left: 3px solid var(--success, #5ac882);
}
/* A coin flip, and drawn as one. Deliberately NOT on the card's warning list —
   a count the seller can see is a decision, and an interruption for it would be
   an interruption on the ordinary lot. */
.wn-odds-even {
  border-left: 3px solid var(--border, #2a2f3a);
}
/* Most of the past sales came in under what winning costs. The ceiling above is
   right and is still the wrong thing to act on alone, which is exactly what the
   neutral warn edge means everywhere else on this screen. */
.wn-odds-long {
  border-left: 3px solid var(--warn, #e0b44a);
}
/* Too few sales for the count to be odds. The count is still true and still
   shown; what is withheld is the percentage, so the edge is the quiet one. */
.wn-odds-thin {
  border-left: 3px solid var(--border, #2a2f3a);
}
.wn-odds-line {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  align-items: baseline;
  min-width: 0;
}
.wn-odds-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-odds-headline {
  min-width: 0;
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}
.wn-odds-strong .wn-odds-headline {
  color: var(--success, #5ac882);
}
.wn-odds-long .wn-odds-headline {
  color: var(--warn, #e0b44a);
}
/* The share, as a number. Absent on a thin count rather than rounded up out of
   four rows — the strip says the fraction there and refuses the percentage. */
.wn-odds-tag {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .04em;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--panel, rgba(255, 255, 255, .06));
  color: var(--text-dim, #8b93a7);
  font-variant-numeric: tabular-nums;
}
.wn-odds-strong .wn-odds-tag {
  background: rgba(90, 200, 130, .16);
  color: var(--success, #5ac882);
}
.wn-odds-long .wn-odds-tag {
  background: rgba(224, 180, 74, .16);
  color: var(--warn, #e0b44a);
}
.wn-odds-src {
  font-size: 11.5px;
  overflow-wrap: anywhere;
  color: var(--text-dim, #8b93a7);
}
/* The count, as one width. This is the part read in the half-second a climbing
   bid leaves, and it is the only thing on the card that shortens while the seller
   is looking at it. */
.wn-odds-bar {
  position: relative;
  height: 6px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--danger-soft, rgba(224, 121, 106, .22));
}
.wn-odds-bar-covered {
  height: 100%;
  border-radius: 999px;
  background: var(--accent, #6ea8fe);
  transition: width .18s ease-out;
}
.wn-odds-strong .wn-odds-bar-covered {
  background: var(--success, #5ac882);
}
.wn-odds-long .wn-odds-bar-covered {
  background: var(--warn, #e0b44a);
}
/* The three figures the count came out of: the line each sale had to clear, the
   middle one, and the whole range. The first is the one the count was made
   against, so it is the one drawn loud. */
.wn-odds-box {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
  min-width: 0;
}
.wn-odds-cell {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 3px 8px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 6px;
  background: var(--panel, rgba(255, 255, 255, .02));
  min-width: 0;
}
.wn-odds-cell-this {
  border-color: var(--accent, #6ea8fe);
  background: rgba(110, 168, 254, .1);
}
.wn-odds-cell-name {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-odds-cell-this .wn-odds-cell-name {
  color: var(--accent, #6ea8fe);
}
.wn-odds-cell-fig {
  font-size: 12.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.wn-odds-note {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-dim, #8b93a7);
}

/* ── The room ─────────────────────────────────────────────────────────────────
   Directly under the odds, because the two are the "will this actually work"
   pair and they fail in opposite directions: the odds say the item may not fetch
   what the middle says, and this says the item may never be bought at all.

   It is the only strip on the card that is about the other bidders rather than
   about the object, and the colour follows the money rather than the mood: a
   room clearing under the ceilings is the good edge, a room clearing above them
   is the warn edge — never the danger one, because nothing is wrong with the lot
   or its price, and a seller who learns to read this as "bad item" has learned
   the wrong thing. */
.wn-room {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin: 10px 0 0;
  padding: 7px 10px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
  font-size: 13px;
}
/* Daylight between the hammer and the ceiling. The one state on this strip that
   is a reason to stay in the show. */
.wn-room-cheap {
  border-left: 3px solid var(--success, #5ac882);
}
/* Clearing right at the ceiling — the ordinary shape of a live auction, and
   deliberately not warned about. */
.wn-room-tight {
  border-left: 3px solid var(--border, #2a2f3a);
}
/* The room outbids the arithmetic. The warn edge and not the danger one: the
   card is right, the price is right, and the thing being spent is the evening. */
.wn-room-hot {
  border-left: 3px solid var(--warn, #e0b44a);
}
/* Some lots watched, too few to be a rate. The count is still true; what is
   withheld is the claim, so the edge is the quiet one. */
.wn-room-thin,
.wn-room-unread {
  border-left: 3px solid var(--border, #2a2f3a);
}
.wn-room-line {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  align-items: baseline;
  min-width: 0;
}
.wn-room-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-room-headline {
  min-width: 0;
  overflow-wrap: anywhere;
  font-variant-numeric: tabular-nums;
}
.wn-room-cheap .wn-room-headline {
  color: var(--success, #5ac882);
}
.wn-room-hot .wn-room-headline {
  color: var(--warn, #e0b44a);
}
/* The rate, as a number. Absent below three rated lots rather than computed off
   two — the strip says the count there and refuses the share. */
.wn-room-tag {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .04em;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--panel, rgba(255, 255, 255, .06));
  color: var(--text-dim, #8b93a7);
  font-variant-numeric: tabular-nums;
}
.wn-room-cheap .wn-room-tag {
  background: rgba(90, 200, 130, .16);
  color: var(--success, #5ac882);
}
.wn-room-hot .wn-room-tag {
  background: rgba(224, 180, 74, .16);
  color: var(--warn, #e0b44a);
}
.wn-room-src {
  font-size: 11.5px;
  overflow-wrap: anywhere;
  color: var(--text-dim, #8b93a7);
}
/* The rate as one width, with the ceiling marked at the far end. A fill that
   reaches the edge is a room buying at everything the lot is worth; past that
   the fill is capped, because a bar drawn to 140% needs a scale and this is the
   part read in the half-second a climbing bid leaves. */
.wn-room-bar {
  position: relative;
  height: 6px;
  border-radius: 999px;
  overflow: hidden;
  background: var(--panel, rgba(255, 255, 255, .06));
}
.wn-room-bar-fill {
  height: 100%;
  border-radius: 999px;
  background: var(--accent, #6ea8fe);
  transition: width .18s ease-out;
}
.wn-room-cheap .wn-room-bar-fill {
  background: var(--success, #5ac882);
}
.wn-room-hot .wn-room-bar-fill {
  background: var(--warn, #e0b44a);
}
/* Where the ceiling is on that track. Always at the right-hand end, because the
   bar is a share OF the ceiling — the mark is what makes a full bar mean
   something rather than just look finished. */
.wn-room-bar-edge {
  position: absolute;
  top: -2px;
  bottom: -2px;
  right: 0;
  width: 2px;
  background: var(--text-dim, #8b93a7);
}
.wn-room-box {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 6px;
  min-width: 0;
}
.wn-room-cell {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 3px 8px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 6px;
  background: var(--panel, rgba(255, 255, 255, .02));
  min-width: 0;
}
.wn-room-cell-this {
  border-color: var(--accent, #6ea8fe);
  background: rgba(110, 168, 254, .1);
}
.wn-room-cell-name {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-room-cell-this .wn-room-cell-name {
  color: var(--accent, #6ea8fe);
}
.wn-room-cell-fig {
  font-size: 12.5px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.wn-room-note {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-dim, #8b93a7);
}

/* The second button under the card. Quieter than Won it on purpose — it records
   an observation rather than a spend — but the same size, because a button that
   is fiddly to hit mid-show is a button whose data never gets collected. */
.wn-passed-btn {
  font-size: 13px;
}

/* ── The room book ────────────────────────────────────────────────────────────
   The panel under the buy sheet: one line per room, then every lot watched to
   the hammer. It borrows the sheet's own layout wholesale, because it is the
   same kind of object — a list of rows with a figure and a remove — and two
   panels on one screen that look different for no reason cost a glance each. */
.wn-room-shows {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-bottom: 8px;
}
.wn-room-show {
  display: flex;
  flex-wrap: wrap;
  gap: 2px 8px;
  align-items: baseline;
  padding: 5px 8px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 6px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
  font-size: 12.5px;
}
.wn-room-show-name {
  font-weight: 600;
  overflow-wrap: anywhere;
}
.wn-room-show-say {
  flex: 1 1 200px;
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--text-dim, #8b93a7);
}
.wn-room-show-count {
  font-size: 11.5px;
  color: var(--text-dim, #8b93a7);
  font-variant-numeric: tabular-nums;
}

/* ── Whether eBay will take the listing at all ────────────────────────────────
   First on the card, directly under the badge, because every number below it is
   the price of an eBay listing and none of them is a price of anything if the
   listing is not allowed to exist.

   Four edges for four answers, and only one of them is loud. The blocked one is
   the single place on this whole screen that gets the danger colour on the strip
   AND in the headline: it is not "this is a poor deal", it is "the thing you are
   about to buy cannot be sold where every figure here was priced". */
.wn-gate {
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin: 10px 0 0;
  padding: 7px 10px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 8px;
  background: var(--panel-2, rgba(255, 255, 255, .03));
  font-size: 13px;
}
/* eBay refuses it. The only stop on this card that is not about a price. */
.wn-gate-blocked {
  border-left: 3px solid var(--danger, #b93a34);
  background: var(--danger-soft, rgba(224, 121, 106, .1));
}
/* eBay allows it on a condition only the seller can check. */
.wn-gate-restricted {
  border-left: 3px solid var(--warn, #e0b44a);
}
/* Allowed, and it goes to eBay's authenticator first — later money and a fake is
   a refund. A real cost, and not a refusal, so it gets the neutral attention
   colour rather than the warning one. */
.wn-gate-authenticated {
  border-left: 3px solid var(--accent, #6ea8fe);
}
/* Nothing matched. Present and quiet, because a block that only appeared when
   something was wrong would be a block whose silence meant both "eBay is fine
   with this" and "nothing ever looked". */
.wn-gate-clear {
  border-left: 3px solid var(--border, #2a2f3a);
}
.wn-gate-line {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 8px;
  align-items: baseline;
  min-width: 0;
}
.wn-gate-label {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-gate-headline {
  min-width: 0;
  overflow-wrap: anywhere;
}
.wn-gate-blocked .wn-gate-headline {
  color: var(--danger, #b93a34);
}
.wn-gate-restricted .wn-gate-headline {
  color: var(--warn, #e0b44a);
}
.wn-gate-authenticated .wn-gate-headline {
  color: var(--accent, #6ea8fe);
}
/* The state in two or three words, for the glance. Absent on a clear lot — there
   is nothing to flag, and a badge reading "OK" on every ordinary card is how a
   seller learns to stop reading this strip. */
.wn-gate-tag {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: .04em;
  padding: 1px 6px;
  border-radius: 999px;
  background: var(--panel, rgba(255, 255, 255, .06));
  color: var(--text-dim, #8b93a7);
  white-space: nowrap;
}
.wn-gate-blocked .wn-gate-tag {
  background: var(--danger, #b93a34);
  color: #fff;
}
.wn-gate-restricted .wn-gate-tag {
  background: rgba(224, 180, 74, .16);
  color: var(--warn, #e0b44a);
}
.wn-gate-authenticated .wn-gate-tag {
  background: rgba(110, 168, 254, .16);
  color: var(--accent, #6ea8fe);
}
/* The words in the lot's own name that fired the rule. The seller has to be able
   to see WHY in a glance, because a wrong match is fixed by retyping the name —
   which is the whole reason this is shown rather than just the verdict. */
.wn-gate-src {
  font-size: 11.5px;
  overflow-wrap: anywhere;
  color: var(--text-dim, #8b93a7);
}
.wn-gate-note {
  margin: 0;
  font-size: 12.5px;
  line-height: 1.5;
  color: var(--text-dim, #8b93a7);
}

/* Where the answer came from. Quiet on a fresh card, and it has to stay quiet —
   it is a note about the evidence, not a warning about the number. */
.wn-held {
  margin: 8px 0 0;
  font-size: 12px;
  color: var(--text-dim, #8b93a7);
}
.wn-held-stale {
  color: var(--warn, #e0b155);
}

.wn-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
}
.wn-stat {
  flex: 1 1 130px;
  padding: 8px 10px;
  border-radius: 8px;
  background: var(--panel, rgba(255, 255, 255, .03));
}
.wn-stat-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-stat-value {
  font-size: 16px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.wn-stat-note {
  font-size: 11px;
  color: var(--text-dim, #8b93a7);
}

.wn-evidence,
.wn-fresh,
.wn-ceiling-note,
.wn-foot {
  margin: 8px 0 0;
  font-size: 12px;
  color: var(--text-dim, #8b93a7);
}
.wn-evidence-confident {
  color: var(--success, #5ac882);
}
.wn-evidence-low {
  color: var(--warn, #e0a94a);
}
.wn-warnings {
  margin: 8px 0 0;
  padding-left: 18px;
  font-size: 12px;
  color: var(--warn, #e0a94a);
}
.wn-warnings > li {
  margin-bottom: 3px;
}

.wn-comps {
  margin-top: 10px;
  font-size: 12px;
}
.wn-comps > summary {
  cursor: pointer;
  color: var(--text-dim, #8b93a7);
}
.wn-comps-table {
  width: 100%;
  margin-top: 6px;
  border-collapse: collapse;
}
.wn-comps-table td {
  padding: 3px 6px;
  border-top: 1px solid var(--border, #2a2f3a);
  vertical-align: top;
}
.wn-comp-title {
  word-break: break-word;
}
.wn-comp-price,
.wn-comp-age {
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* ── Your own record with this thing ───────────────────────────────────────────
   Deliberately set apart from the market statistics below it: a left border in the
   app's gold, so the eye can tell in one glance whether it is reading what strangers
   got for these or what THIS seller got. They are different claims and they are
   allowed to disagree, which is most of the value. */
.wn-own {
  margin-top: 10px;
  padding: 10px 12px;
  border-radius: 10px;
  border-left: 3px solid var(--accent-2, #d4a24a);
  background: var(--panel-2, rgba(255, 255, 255, .02));
}
.wn-own-proven {
  border-left-color: var(--success, #5ac882);
}
.wn-own-holding {
  border-left-color: var(--warn, #e0a94a);
}
.wn-own-head {
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-own-headline {
  text-transform: none;
  letter-spacing: 0;
  font-size: 13px;
  color: var(--text, #e7ebf3);
}
.wn-own-tiles {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 8px;
}
.wn-own-tiles > .wn-stat {
  background: var(--panel, rgba(255, 255, 255, .03));
}
/* The second ceiling. Quiet when it agrees with the badge — a sentence styled as a
   warning every time would train the seller to skip the times it is one. */
.wn-own-ceiling {
  margin: 8px 0 0;
  font-size: 12px;
  color: var(--text-dim, #8b93a7);
}
.wn-own-ceiling > strong {
  font-variant-numeric: tabular-nums;
}
.wn-own-ceiling-low {
  color: var(--warn, #e0a94a);
}
.wn-own-ceiling-only {
  color: var(--accent-2, #d4a24a);
}
.wn-own-notes {
  margin: 6px 0 0;
  padding-left: 18px;
  font-size: 12px;
  color: var(--text-dim, #8b93a7);
}
.wn-own-notes > li {
  margin-bottom: 3px;
}
/* Never sold one, never bought one: one line, and it stays a line. */
.wn-own-empty {
  margin: 10px 0 0;
  font-size: 12px;
  color: var(--text-dim, #8b93a7);
}
.wn-own-sales {
  margin-top: 8px;
  font-size: 12px;
}
.wn-own-sales > summary {
  cursor: pointer;
  color: var(--text-dim, #8b93a7);
}
.wn-own-table {
  width: 100%;
  margin-top: 6px;
  border-collapse: collapse;
}
.wn-own-table td {
  padding: 3px 6px;
  border-top: 1px solid var(--border, #2a2f3a);
  vertical-align: top;
}
.wn-own-cell-title {
  word-break: break-word;
}
.wn-own-cell-price,
.wn-own-cell-net,
.wn-own-cell-age {
  text-align: right;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}

/* ── The show's lot list ───────────────────────────────────────────────────────
   Rows are read at a glance while something else is on screen, so the call is a
   fixed-width block on the left and the ceiling a fixed-width block on the right:
   the eye lands in the same place on every row regardless of how long the title is. */
.wn-queue {
  margin: 10px 0 14px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 10px;
  /* Translucent, like every other panel on this screen. A solid colour here reads as
     the wrong theme's panel the moment the app is not in the one it was picked in. */
  background: var(--panel-2, rgba(255, 255, 255, .02));
}
.wn-queue-summary {
  cursor: pointer;
  padding: 9px 12px;
  font-weight: 600;
  font-size: 13px;
  list-style: none;
}
.wn-queue-summary::-webkit-details-marker { display: none; }
.wn-queue-summary::before {
  content: '▸ ';
  opacity: 0.7;
}
.wn-queue[open] > .wn-queue-summary::before { content: '▾ '; }
.wn-queue-body {
  padding: 0 12px 12px;
}
.wn-lots-label {
  display: block;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  opacity: 0.7;
  margin-bottom: 4px;
}
/* Border, background and colour come from the app's own textarea rule, so the box is
   whatever a textarea is in whichever theme is on. Only the size is set here. */
.wn-lots {
  font-size: 13px;
  min-height: 78px;
}
.wn-lots-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 8px;
}
.wn-lots-note {
  font-size: 12px;
  opacity: 0.85;
  flex: 1 1 260px;
}
.wn-lots-hint {
  margin-top: 6px;
}
.wn-lot-rows {
  display: flex;
  flex-direction: column;
  gap: 5px;
  margin-top: 10px;
}
/* A priced row is a real <button>, so this has to undo what a button brings with
   it — the centred text, the browser's own font, its own colour — while a queued
   row is still a plain div. Both are laid out by the same rule so a row does not
   change shape at the moment it gets an answer. */
.wn-lot-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border-radius: 8px;
  border: 1px solid var(--border, #2a2f3a);
  background: var(--panel-2, rgba(255, 255, 255, .02));
  width: 100%;
  text-align: left;
  font: inherit;
  color: inherit;
}
.wn-lot-row.wn-lot-priced {
  cursor: pointer;
}
.wn-lot-row.wn-lot-priced:hover {
  border-color: var(--accent, #d4a24a);
}
/* A focus indicator that only changes a border colour is one you cannot find on a
   screen holding twelve bordered rows. Keyboard focus gets an outline outside the
   row, which is the one mark nothing else on this list wears. */
.wn-lot-row.wn-lot-priced:focus-visible,
.wn-step:focus-visible,
.wn-queue-summary:focus-visible,
.wn-nav-btn:focus-visible {
  outline: 2px solid var(--accent, #d4a24a);
  outline-offset: 2px;
}
.wn-lots-empty {
  margin: 10px 0 0;
  font-size: 12px;
  color: var(--text-dim, #8b93a7);
}
.wn-lot-pricing {
  opacity: 0.75;
}
/* The same three colours the card's own badge uses, so a row and the card it opens
   never disagree about what colour the answer is. */
.wn-lot-call {
  flex: 0 0 66px;
  text-align: center;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.04em;
  padding: 3px 0;
  border-radius: 5px;
  border: 1px solid transparent;
}
.wn-lot-call-bid > .wn-lot-call {
  background: rgba(90, 200, 130, .14);
  border-color: rgba(90, 200, 130, .45);
  color: var(--success, #5ac882);
}
.wn-lot-call-risky > .wn-lot-call {
  background: rgba(224, 169, 74, .14);
  border-color: rgba(224, 169, 74, .45);
  color: var(--warn, #e0a94a);
}
.wn-lot-call-stop > .wn-lot-call,
.wn-lot-call-no_data > .wn-lot-call {
  background: rgba(224, 121, 106, .12);
  border-color: rgba(224, 121, 106, .4);
  color: var(--danger, #e0796a);
}
.wn-lot-call-no_data > .wn-lot-call {
  font-size: 9px;
  opacity: 0.8;
}
.wn-lot-main {
  flex: 1 1 auto;
  min-width: 0;
}
/* display:block because these are spans inside a <button> — a button may only hold
   phrasing content, and an inline box cannot be given an ellipsis. */
.wn-lot-title {
  display: block;
  font-size: 13px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wn-lot-sub {
  display: block;
  font-size: 11px;
  opacity: 0.75;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wn-lot-max {
  flex: 0 0 auto;
  text-align: right;
  font-variant-numeric: tabular-nums;
}
.wn-lot-max > span {
  display: block;
  font-size: 10px;
  opacity: 0.6;
}
.wn-lot-max > strong {
  font-size: 15px;
}

/* ── Won it, and the buy sheet ───────────────────────────────────────────────
   The button sits inside the card, under the meter, where the decision was just
   made — the hammer falls two seconds after the last bid and there is no time to
   go looking for it. It is deliberately not the card's loudest thing: the badge
   and the ceiling are what get read while the bidding runs, and this is what gets
   pressed after. */
.wn-won {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 10px;
}
.wn-won-btn {
  font-weight: 700;
  border: 1px solid var(--border, #2a2f3a);
  background: var(--panel, rgba(255, 255, 255, .04));
  color: inherit;
}
.wn-won-btn:hover {
  border-color: var(--accent, #d4a24a);
}
/* Won above the ceiling the app gave. Recorded either way — the sheet's whole
   discipline half depends on that — but the button says so before it is pressed. */
.wn-won-btn-over {
  border-color: rgba(224, 121, 106, .55);
  color: var(--danger, #e0796a);
}
.wn-won-note {
  font-size: 11px;
  color: var(--text-dim, #8b93a7);
  flex: 1 1 200px;
}

.wn-sheet {
  margin: 0 0 14px;
  border: 1px solid var(--border, #2a2f3a);
  border-radius: 10px;
  background: var(--panel-2, rgba(255, 255, 255, .02));
}
/* The night's sentence sits in the summary, so the answer is readable with the
   panel shut. It wraps rather than truncating: half a sentence about money is a
   different claim from the whole of it. */
.wn-sheet-summary {
  cursor: pointer;
  padding: 9px 12px;
  font-weight: 600;
  font-size: 13px;
  line-height: 1.45;
  list-style: none;
}
.wn-sheet-summary::-webkit-details-marker { display: none; }
.wn-sheet-summary::before {
  content: '▸ ';
  opacity: 0.7;
}
.wn-sheet[open] > .wn-sheet-summary::before { content: '▾ '; }
.wn-sheet-summary:focus-visible,
.wn-sheet-remove:focus-visible {
  outline: 2px solid var(--accent, #d4a24a);
  outline-offset: 2px;
}
.wn-sheet-body {
  padding: 0 12px 12px;
}
.wn-sheet-totals {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.wn-sheet-tile {
  flex: 1 1 130px;
  padding: 8px 10px;
  border-radius: 8px;
  border: 1px solid transparent;
  background: var(--panel, rgba(255, 255, 255, .03));
}
/* Colour is the second thing these say, never the only one — the note under every
   tile carries the same fact in words. */
.wn-sheet-tile-good {
  border-color: rgba(90, 200, 130, .35);
}
.wn-sheet-tile-bad {
  border-color: rgba(224, 121, 106, .45);
}
.wn-sheet-tile-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: .04em;
  color: var(--text-dim, #8b93a7);
}
.wn-sheet-tile-value {
  font-size: 16px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.wn-sheet-tile-note {
  font-size: 11px;
  color: var(--text-dim, #8b93a7);
}
.wn-sheet-list {
  list-style: none;
  margin: 10px 0 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 5px;
}
.wn-sheet-row {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 7px 10px;
  border-radius: 8px;
  border: 1px solid var(--border, #2a2f3a);
  background: var(--panel-2, rgba(255, 255, 255, .02));
}
.wn-sheet-row-over {
  border-color: rgba(224, 121, 106, .45);
}
.wn-sheet-row-main {
  flex: 1 1 auto;
  min-width: 0;
}
.wn-sheet-row-title {
  display: block;
  font-size: 13px;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wn-sheet-row-sub {
  display: block;
  font-size: 11px;
  opacity: 0.75;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.wn-sheet-row-profit {
  flex: 0 0 auto;
  font-size: 15px;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}
/* The button that turns a won lot into money coming back. Quiet enough not to compete with
   the profit figure beside it, loud enough to be the obvious next thing on the row — because
   an unlisted lot is the one state on this sheet that costs the seller something. */
.wn-sheet-draft {
  flex: 0 0 auto;
  border: 1px solid var(--accent, #4a8fe7);
  background: rgba(74, 143, 231, .12);
  color: var(--text, #e6e9ef);
  cursor: pointer;
  font-size: 11px;
  font-weight: 600;
  line-height: 1;
  padding: 6px 9px;
  border-radius: 6px;
  white-space: nowrap;
}
.wn-sheet-draft:hover {
  background: rgba(74, 143, 231, .22);
}
.wn-sheet-draft[disabled] {
  opacity: .55;
  cursor: default;
}
/* Already a draft: it opens the listing instead of making a second one, and says so by
   stepping back out of the accent colour. */
.wn-sheet-draft-done {
  border-color: var(--border, #2a2f3a);
  background: none;
  color: var(--text-dim, #8b93a7);
  font-weight: 500;
}
.wn-sheet-draft-done:hover {
  background: var(--panel-2, rgba(255, 255, 255, .04));
  color: var(--text, #e6e9ef);
}
.wn-sheet-row-listed {
  border-color: rgba(93, 176, 117, .35);
}
.wn-sheet-draft:focus-visible {
  outline: 2px solid var(--accent, #4a8fe7);
  outline-offset: 2px;
}
.wn-sheet-remove {
  flex: 0 0 auto;
  border: 1px solid transparent;
  background: none;
  color: var(--text-dim, #8b93a7);
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
  padding: 4px 6px;
  border-radius: 6px;
}
.wn-sheet-remove:hover {
  color: var(--danger, #e0796a);
  border-color: rgba(224, 121, 106, .45);
}
.wn-sheet-empty {
  margin: 10px 0 0;
  font-size: 12px;
  color: var(--text-dim, #8b93a7);
}
.wn-sheet-actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 10px;
}
.wn-sheet-hint {
  margin-top: 6px;
}

@media (max-width: 860px) {
  .wn-field {
    width: calc(50% - 4px);
  }
  .wn-price-btn {
    width: 100%;
  }
}

/* Narrower than this the app is a window down the side of a live stream, which is
   how this screen is actually used. Everything that was trying to sit five across
   drops to two, the ceiling moves under the lot rather than being squeezed beside
   it, and the frame gives back the height the card needs — the decision is the
   point and the stream is the context. */
@media (max-width: 620px) {
  .wn-field,
  .wn-field-bid,
  .wn-field-inc,
  .wn-field-qty,
  .wn-field-wide {
    width: 100%;
    flex: 1 1 100%;
    min-width: 0;
  }
  /* Three per-unit figures on one line is three half-lines at this width. */
  .wn-units-each {
    flex-direction: column;
    gap: 2px;
  }
  /* "SEARCHED EBAY FOR" and a long query share a line until there is no line to
     share. Below that the label sits above the query rather than pushing it into a
     one-word column. */
  .wn-search-line {
    flex-direction: column;
    gap: 1px;
  }
  /* Same trade on the trend strip: "LAST 30 DAYS" and "$180 → $140 median" do not
     both fit, and the headline is the half worth the width. */
  .wn-trend-line {
    flex-direction: column;
    gap: 1px;
  }
  /* And on the next-bid strip, where the headline is the count and the width is
     wanted by "NEXT BID", the amount and the tag as well. The count keeps the
     line. */
  .wn-next-line {
    flex-direction: column;
    gap: 1px;
  }
  /* Same again on the condition strip. The band bars below it stay side by side
     as long as they fit and wrap when they do not — two or three of them are the
     one thing on this block worth comparing at a glance. */
  .wn-cond-line {
    flex-direction: column;
    gap: 1px;
  }
  /* And once more on the stock strip, where "YOUR STOCK" and "You'd have 5 — about
     2.5 months of stock" want the same line and only one of them is the sentence.
     The bars below wrap on their own; three of them side by side is the comparison
     the block exists for and is kept as long as it fits. */
  .wn-stock-line {
    flex-direction: column;
    gap: 1px;
  }
  /* And on the shipping strip, where "SHIPPING" and "Ships with your other 3 —
     $1, not $12" want the same line and only one of them is the sentence. The
     three box figures below wrap on their own; side by side they are the whole
     comparison, and they are kept that way as long as they fit. */
  .wn-ship-line {
    flex-direction: column;
    gap: 1px;
  }
  /* And on the shelf-time strip, where "SHELF TIME" and "Yours sells about 2
     months out — about $18 a unit lower by then" want the same line and only one
     of them is the sentence. The three cells below wrap on their own; side by
     side they are the argument for the cut, and are kept so while they fit. */
  .wn-hold-line {
    flex-direction: column;
    gap: 1px;
  }
  /* And on the tax strip, where "SALES TAX" and "$8.64 tax at 8%" want the same
     line and only one of them is the sentence. The three cells below wrap on
     their own; side by side they are the arithmetic, kept while they fit. */
  .wn-tax-line {
    flex-direction: column;
    gap: 1px;
  }
  /* And on the budget strip, where "TONIGHT" and "$62 left — that's the ceiling"
     want the same line and only one of them is the sentence. The bar and the
     three cells below it wrap on their own. */
  .wn-budget-line {
    flex-direction: column;
    gap: 1px;
  }
  /* And on the odds strip, where "ODDS" and "only 4 of 12 past sales cover a $46
     win" want the same line and only one of them is the sentence. The bar keeps
     its full width — it is the part read at a glance — and the three cells below
     wrap on their own. */
  .wn-odds-line {
    flex-direction: column;
    gap: 1px;
  }
  /* And on the room strip, where "ROOM" and "This room clears at 62% of your
     ceilings" want the same line and only one of them is the sentence. The bar
     keeps its full width — it is the part read at a glance — and the three cells
     below wrap on their own. */
  .wn-room-line {
    flex-direction: column;
    gap: 1px;
  }
  /* And on the eBay-policy strip, where "EBAY" and "eBay won't let you list this
     — replicas and counterfeits" want the same line and only one of them is the
     sentence. The tag and the matched words wrap under it on their own. */
  .wn-gate-line {
    flex-direction: column;
    gap: 1px;
  }
  .wn-field-cond {
    width: 100%;
  }
  /* Tonight's money gets the whole line. Its label is two words and its figure is
     a night's spend, and squeezed into a 132px column beside a rate box the two
     of them wrap into something unreadable. */
  .wn-field-budget {
    width: 100%;
  }
  /* The certificate is a tick, not a figure, and its label is two words. Given
     the whole line so the box and its words stay on one row rather than the
     label wrapping under a checkbox. */
  .wn-field-exempt {
    width: 100%;
  }
  /* The show is a name, not a figure, and it is the one box on this row that
     needs room to be read back. The two rates split the line under it — still a
     pair, still side by side, and each still wide enough for a price. */
  .wn-field-show {
    width: 100%;
  }
  .wn-field-pair {
    width: 100%;
  }
  .wn-field-pair > .wn-field {
    flex: 1 1 0;
    width: auto;
    min-width: 0;
  }
  .wn-rung,
  .wn-stat,
  .wn-sheet-tile {
    flex: 1 1 calc(50% - 4px);
  }
  /* The profit, List it and the remove button move under the lot rather than being
     squeezed beside a title that has nowhere left to go. */
  .wn-sheet-row {
    flex-wrap: wrap;
  }
  .wn-sheet-row-main {
    flex: 1 1 100%;
  }
  /* List it takes the width the title gave back, so the one action that turns tonight's
     spend into money is still a full-sized target down the side of a stream. */
  .wn-sheet-draft {
    flex: 1 1 auto;
    text-align: center;
    padding: 7px 9px;
  }
  .wn-say {
    font-size: 13px;
  }
  /* The read row's two buttons and its switch go full width under the address
     rather than being squeezed beside it — this is the row you press first. */
  .wn-read-btn,
  .wn-photo-btn,
  .wn-watch {
    flex: 1 1 auto;
    justify-content: center;
  }
  /* The photo gives its width back to the sentences, which are the part that says
     what the app decided the page meant. */
  .wn-read-shot,
  .wn-photo-shot {
    width: 48px;
    height: 48px;
  }
  /* The offer is the one thing on the look panel you press, so at this width it is
     the full row rather than a button the thumbnail has squeezed. */
  .wn-photo-use-btn {
    display: block;
    width: 100%;
    text-align: center;
  }
  .wn-meter-legend {
    font-size: 10px;
  }
  /* Four columns of the seller's own sales is one more than the comp table has, and
     the last of them carries two facts. It wraps rather than pushing the title out. */
  .wn-own-table {
    font-size: 11px;
  }
  .wn-own-cell-age {
    white-space: normal;
  }
  .wn-lot-row {
    flex-wrap: wrap;
  }
  .wn-lot-max {
    flex: 1 1 100%;
    text-align: left;
    padding-left: 76px;
  }
  .wn-lot-max > span {
    display: inline;
  }
  .wn-frame {
    min-height: 50vh;
  }
}

/* ── The live screen with the colour taken away ────────────────────────────────
   Windows High Contrast overrides every background and every border colour the
   app chose. On most screens here that costs decoration. On this one it costs the
   answer: the whole point of the card is that a bidding seller reads it in the two
   seconds a lot gives them, and half of what they read at that speed is colour —
   the fill along the meter, the length of the odds bar, which condition band is
   the one this lot is in, which of tonight's counts is money already committed.
   All of it flattens to one CanvasText outline on one Canvas fill.

   Two rules, applied consistently:

   1. Where the colour IS the datum — a fill whose LENGTH is the number — the
      author's colours are kept (forced-color-adjust: none) and the track around
      them is outlined so the extent is still visible against any background. Same
      treatment the charts get; see .viz-* above.
   2. Where the colour was only emphasis — "this is the band/cell/count that
      matters" — the emphasis moves to a border WIDTH, which is the one thing
      forced colours do not override.

   The four calls need nothing: BID UP TO $90, DON'T BID, NO DATA and CAN'T LIST
   IT are words, on the badge, in the one line above the card and on every lot row.
   The colour was never carrying that on its own. */
@media (forced-colors: active) {
  /* The ceiling meter: green track up to the walk-away line, the line itself, and
     the bid sitting somewhere along it. Three overlapping absolutely-positioned
     fills with no text anywhere on them. */
  .wn-meter-track,
  .wn-meter-good,
  .wn-meter-edge,
  .wn-meter-bid,
  .wn-budget-bar,
  .wn-budget-bar-spent,
  .wn-room-bar,
  .wn-room-bar-fill,
  .wn-room-bar-edge,
  .wn-odds-bar,
  .wn-odds-bar-covered {
    forced-color-adjust: none;
  }
  /* The tracks get the outline, not the fills — a border on a fill inside an
     overflow:hidden track would change the width, and the width is the number. */
  .wn-meter-track,
  .wn-budget-bar,
  .wn-room-bar,
  .wn-odds-bar {
    border: 1px solid CanvasText;
  }
  /* The one this lot is in, the money already committed, the cell the ceiling was
     cut by. Colour said which; a second pixel of border says it just as fast. */
  .wn-cond-band-mine,
  .wn-stock-bar-shelf,
  .wn-stock-bar-tonight,
  .wn-hold-cell-this,
  .wn-ship-cell-this,
  .wn-tax-cell-this,
  .wn-budget-cell-this,
  .wn-odds-cell-this {
    border-width: 2px;
  }
  /* The badge and each lot row's call keep a visible edge of their own, so the
     answer still reads as a separate object rather than as another line of text. */
  .wn-call-badge,
  .wn-lot-call,
  .wn-status,
  .wn-gate,
  .wn-say {
    border: 1px solid CanvasText;
  }
  /* The say line's stripe is a coloured left border, which is the one part of it
     that does not survive. Widened so it is still a stripe. */
  .wn-say {
    border-left-width: 4px;
  }
}

/* Three widths animate on this screen, and every one of them animates BECAUSE the
   number moved: how much of tonight's budget has gone, how many past sales still
   cover the bid, and what this room has been clearing at. During a live sale that
   is a bar sliding every few seconds in the corner of the eye of somebody who
   asked the system not to do that. */
@media (prefers-reduced-motion: reduce) {
  .wn-budget-bar-spent,
  .wn-odds-bar-covered,
  .wn-room-bar-fill {
    transition: none;
  }
}
