/* UI tokens + accessibility defaults (Wise-inspired, not copied) */
/* This file is intentionally plain CSS (served by Propshaft) so it is easy to revert. */

:root {
  /* Tell the browser we support both schemes; actual colors come from tokens. */
  color-scheme: light;

  /* Global chrome */
  --app-header-height: 64px;

  /* Typography */
  --ui-font-sans: Inter, ui-sans-serif, system-ui, -apple-system, "Segoe UI",
    Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
  --ui-font-display: "Archivo Black", var(--ui-font-sans);
  --ui-font-interface: var(--ui-font-sans); /* Inter — UI/body voice (alias used by SuccessModal etc.) */

  /* Surfaces (light) */
  --bg: #f5f5f0;
  --surface: #e8ebe4; /* sage/gray: Wise-inspired container background */
  --surface2: #ecefec; /* cards float on surface */
  --surface2-hover: #e1e5de; /* card hover state */
  --border: #d4d9ce;

  /* Text (light) */
  --text: #0b1220;
  --muted: #44506a;
  --subtle: #6b7280;

  /* Brand (core blue) */
  --primary: var(--core-ocean-blue);
  --accent: var(--primary); /* legacy alias used across payment_links + shared button/focus rings */
  --primary_hover: color-mix(in oklab, var(--primary) 85%, black);
  --primary_active: color-mix(in oklab, var(--primary) 70%, black);
  --primary_fg: var(--core-bright-blue);
  --link: var(--core-ocean-blue);
  --button-secondary-text: var(--core-ocean-blue);

  /* Mono base aliases — resolvable in every scope (legacy :root + .app-mono).
     Defined here so the retired state tokens below resolve even on pages that
     have not yet opted into the .app-mono scope. */
  --white: #ffffff;
  --ink: #0e0e0c;
  --gray: #6b6b66;
  --line: #e4e4e0;
  --brick: #993d33;
  --stone: #999998;

  /* States — retired to Mono (ink + brick only): success/warning = ink,
     danger = brick, lifecycle ramp mirrors the canonical .app-mono block. */
  --success: var(--ink);
  --success_fg: #ffffff;
  --warning: var(--ink);
  --warning_fg: #ffffff;
  --danger: var(--brick);
  --danger_fg: #ffffff;
  --error: var(--danger);
  --status-in-stock: color-mix(in oklab, var(--ink) 16%, var(--white));
  --status-reserved: var(--stone);
  --status-listed: var(--gray);
  --status-sold: var(--ink);
  --status-defective: var(--brick);

  /* Bright accent colors for status bars (light mode) */
  --bright-orange: #FFC091;
  --bright-yellow: #FFEB69;
  --bright-blue: #A0E1E1;
  --bright-pink: #FFD7EF;

  /* Focus: must stay visible on both light and dark surfaces */
  --focus_ring: #3080ff; /* close to Tailwind blue-500 */
  --focus_ring_offset: #ffffff;

  /* Padding tokens */
  --padding-x-small: 8px;
  --padding-small: 16px;
  --padding-medium: 24px;
  --padding-large: 32px;

  /* Size tokens */
  --size-x-small: 24px;
  --size-medium: 40px;
  --size-large: 48px;
  --size-x-large: 56px;
  --size-2x-large: 72px;

  /* Radius tokens (desktop scale) */
  --radius-small: 16px;
  --radius-medium: 20px;
  --radius-large: 30px;
  --radius-x-large: 40px;
  --radius-2x-large: 60px;

  /* Shadow tokens */
  --shadow-card: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);

  /* Foundational spacing tokens */
  --size-4: 4px;
  --size-8: 8px;
  --size-12: 12px;
  --size-16: 16px;
  --size-24: 24px;
  --size-32: 32px;
  --size-40: 40px;
  --size-48: 48px;
  --size-56: 56px;
  --size-64: 64px;
  --size-72: 72px;
  --size-80: 80px;
  --size-88: 88px;
  --size-96: 96px;
  --size-104: 104px;
  --size-112: 112px;
  --size-120: 120px;
  --size-128: 128px;
}

html[data-theme="dark"] {
  color-scheme: dark;

  /* Surfaces (dark) */
  --bg: #121511;
  --surface: #1a1d18; /* darker container */
  --surface2: #292d29; /* cards */
  --surface2-hover: #404441; /* card hover state */
  --border: #2e332b;

  /* Text (dark) */
  --text: #e6eaf2;
  --muted: #b8c0d4;
  --subtle: #95a0b8;

  /* Brand (core blue) */
  --primary: var(--core-bright-blue);
  --accent: var(--primary);
  --primary_fg: var(--core-ocean-blue);
  --link: var(--core-bright-blue);
  --button-secondary-text: var(--core-bright-blue);

  /* States — Mono retirement (dark): success/warning read as dark-scope ink
     (--text, the light foreground), danger = brick, status blues → neutral.
     Surface/background tokens above are intentionally left intact. */
  --success: var(--text);
  --success_fg: #071215;
  --warning: var(--text);
  --warning_fg: #141106;
  --danger: var(--brick);
  --danger_fg: #ffffff;
  --status-in-stock: var(--subtle);
  --status-listed: var(--subtle);
  --status-sold: var(--success);
  --status-defective: var(--danger);

  /* Bright accent colors for status bars (dark mode)
     Use same bright colors as light mode for visibility */
  --bright-orange: #FFC091;
  --bright-yellow: #FFEB69;
  --bright-blue: #A0E1E1;
  --bright-pink: #FFD7EF;

  --focus_ring: #60a5fa;
  --focus_ring_offset: #070b12;

  /* Shadow tokens (dark mode - slightly more visible) */
  --shadow-card: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.16);
}

@media (max-width: 767px) {
  :root {
    /* Radius tokens (mobile scale) */
    --radius-small: 10px;
    --radius-medium: 16px;
    --radius-large: 24px;
    --radius-x-large: 32px;
    --radius-2x-large: 48px;
  }
}

html {
  font-family: var(--ui-font-sans);
}

/* Grid container (Wise-like margins) */
.ds-grid-container {
  max-width: 1440px;
  margin: 0 auto;
  padding-inline: 20px;
}

@media (min-width: 480px) {
  .ds-grid-container {
    padding-inline: 32px;
  }
}

@media (min-width: 768px) {
  .ds-grid-container {
    padding-inline: 40px;
  }
}

@media (min-width: 992px) {
  .ds-grid-container {
    padding-inline: 80px;
  }
}

@media (min-width: 1200px) {
  .ds-grid-container {
    padding-inline: 100px;
  }
}

/* Typography foundations helpers */
.design-typography__display {
  font-family: var(--ui-font-display);
  letter-spacing: 0.01em;
}

/* Title Screen: Main page title (30px, semibold, -2.5% tracking, 34px line-height) */
.ds-title-screen {
  font-size: 30px;
  font-weight: 600;
  line-height: 34px;
  letter-spacing: -0.025em;
  color: var(--text);
}

/* Title Section: Section headings (26px, semibold, -1.5% tracking) */
.ds-title-section {
  font-size: 26px;
  font-weight: 600;
  line-height: 32px;
  letter-spacing: -0.015em;
  color: var(--text);
}

/* Title Subsection: Subsection headings (22px, semibold, -1.5% tracking) */
.ds-title-subsection {
  font-size: 22px;
  font-weight: 600;
  line-height: 28px;
  letter-spacing: -0.015em;
  color: var(--text);
}

/* Title Body: Body headings (18px, semibold, -1% tracking) */
.ds-title-body {
  font-size: 18px;
  font-weight: 600;
  line-height: 24px;
  letter-spacing: -0.01em;
  color: var(--text);
}

/* Padding tokens helpers */
.ui-padding-x-small {
  padding: var(--padding-x-small);
}

.ui-padding-small {
  padding: var(--padding-small);
}

.ui-padding-medium {
  padding: var(--padding-medium);
}

.ui-padding-large {
  padding: var(--padding-large);
}

/* Size tokens helpers */
.ui-size-x-small {
  width: var(--size-x-small);
  height: var(--size-x-small);
}

.ui-size-medium {
  width: var(--size-medium);
  height: var(--size-medium);
}

.ui-size-large {
  width: var(--size-large);
  height: var(--size-large);
}

.ui-size-x-large {
  width: var(--size-x-large);
  height: var(--size-x-large);
}

.ui-size-2x-large {
  width: var(--size-2x-large);
  height: var(--size-2x-large);
}

.ui-size-circle {
  border-radius: 9999px;
  background: color-mix(in oklab, var(--primary) 18%, transparent);
  border: 1px solid color-mix(in oklab, var(--primary) 35%, transparent);
}

/* Radius helpers */
.ui-radius-small {
  border-radius: var(--radius-small);
}

.ui-radius-medium {
  border-radius: var(--radius-medium);
}

.ui-radius-large {
  border-radius: var(--radius-large);
}

.ui-radius-x-large {
  border-radius: var(--radius-x-large);
}

.ui-radius-2x-large {
  border-radius: var(--radius-2x-large);
}

/* Focus ring helper */
.ui-focus-ring:focus-visible {
  outline: 2px solid var(--focus_ring);
  outline-offset: 2px;
}

/* Form controls */
.ui-input {
  display: flex;
  align-items: center;
  gap: var(--size-8);
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: var(--radius-medium);
  padding: var(--size-8) var(--padding-small);
  transition: border-color 150ms ease, box-shadow 150ms ease;
}

/* Mono Input focus = solid 2px ink edge (1px border + 1px inset), NOT a glow. Ink (not
   --focus_ring) so it matches .ds-field on every surface, incl. root/landing where focus_ring is blue. */
.ui-input:focus-within {
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
}

.ui-input__field {
  flex: 1;
  min-width: 0;
  background: transparent;
  color: var(--text);
}

.ui-input__field:focus-visible {
  outline: none;
}

.ui-input__field::placeholder {
  color: var(--subtle);
}

.ui-control {
  width: 100%;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-medium);
  padding: var(--size-8) var(--padding-small);
  color: var(--text);
  transition: border-color 150ms ease, box-shadow 150ms ease;
}

.ui-control:focus-visible {
  outline: 2px solid var(--focus_ring);
  outline-offset: 2px;
  border-color: var(--focus_ring);
}

.ui-button-danger {
  background: var(--danger);
  color: var(--danger_fg);
  border: 1px solid color-mix(in oklab, var(--danger) 80%, transparent);
  transition: background-color 150ms ease, border-color 150ms ease;
}

.ui-button-danger:hover {
  background: color-mix(in oklab, var(--danger) 85%, black);
  border-color: color-mix(in oklab, var(--danger) 85%, black);
}

body {
  background: var(--bg);
  color: var(--text);
}

/* Account menu (avatar dropdown) */
.account-menu__panel {
  /* Ensure the dropdown never becomes transparent, even if utility classes change */
  background-color: var(--surface2) !important;
}

.account-menu__panel a.account-menu__item,
.account-menu__panel a.account-menu__item:hover,
.account-menu__panel a.account-menu__item:focus,
.account-menu__panel a.account-menu__item:active {
  /* Prevent global link styling (blue + underline) inside dropdown rows */
  color: inherit;
  text-decoration: none;
}

.account-menu__item {
  /* Make anchors look like the other dropdown "buttons" */
  color: var(--text);
  text-decoration: none;
}

/* Physicality: the avatar trigger presses with the one Mono spring (transform only —
   a quiet control, no tint), reduced-motion-safe. Standalone (never combined with
   ds-button). */
.account-menu__trigger {
  transition: background-color 0.15s ease, transform var(--mono-dur-fast) var(--mono-spring);
}
.account-menu__trigger:hover,
.account-menu__trigger:focus-visible,
.account-menu__trigger[aria-expanded="true"] {
  /* Match the desktop sidebar active highlight */
  background-color: color-mix(in oklab, var(--primary) 10%, transparent) !important;
}
@media (prefers-reduced-motion: no-preference) {
  .account-menu__trigger:active:not(:disabled) {
    transform: scale(var(--mono-press-scale));
  }
}

.account-menu__item:hover,
.account-menu__item:focus-visible {
  /* Same hover behavior as sidebar items */
  background-color: color-mix(in oklab, var(--primary) 10%, transparent);
}

.account-menu__icon-wrap {
  width: 36px;
  height: 36px;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  line-height: 0;
}

.account-menu__icon {
  display: block;
  color: var(--muted);
  align-self: center;
}

.account-menu__item:hover .account-menu__icon,
.account-menu__item:focus-visible .account-menu__icon {
  color: var(--primary);
}

.inventory-status__modal-overlay {
  background-color: rgba(0, 0, 0, 0.6);
}

.inventory-status__modal-panel {
  background-color: var(--surface2) !important;
}

.inventory-table__grid {
  display: grid;
  grid-template-columns: 36px minmax(200px, 2.8fr) minmax(80px, 0.7fr) minmax(110px, 0.85fr) minmax(90px, 0.7fr) 28px;
  gap: 10px;
  align-items: center;
}

.inventory-table__frame {
  background: var(--surface2);
  box-shadow: var(--shadow-card);
  padding: 8px;
  border-radius: 24px;
}

.inventory-table__card {
  background: var(--surface2);
  border-radius: 18px;
  overflow: hidden;
}

.inventory-table__header {
  background-color: transparent;
}

.inventory-table__row {
  position: relative;
}

.inventory-table__separator {
  height: 1px;
  margin: 0 22px;
  background-color: color-mix(in oklab, var(--text) 12%, transparent);
}

.inventory-table__card > turbo-frame:last-child .inventory-table__separator {
  display: none;
}

.inventory-table__row::after {
  content: "";
  position: absolute;
  left: 12px;
  right: 12px;
  top: 6px;
  bottom: 6px;
  border-radius: 16px;
  background-color: transparent;
  transition: background-color 150ms ease;
  pointer-events: none;
  z-index: 0;
}

.inventory-table__row > .inventory-table__grid {
  position: relative;
  z-index: 1;
}

.inventory-table__row:hover::after,
.inventory-table__row:focus-visible::after {
  background-color: color-mix(in oklab, var(--primary) 10%, transparent);
}

.status-bar__fill {
  background-color: var(--status-in-stock);
}

.status-bar__fill--in_stock {
  background-color: var(--status-in-stock);
}

.status-bar__fill--listed {
  background-color: var(--status-listed);
}

.status-bar__fill--sold {
  background-color: var(--status-sold);
}

.status-bar__fill--defective {
  background-color: var(--status-defective);
}

.peer:checked + .status-pill--in_stock {
  background-color: var(--status-in-stock);
  border-color: transparent;
  color: var(--text);
}

.peer:checked + .status-pill--listed {
  background-color: var(--status-listed);
  border-color: transparent;
  color: var(--text);
}

.peer:checked + .status-pill--sold {
  background-color: var(--status-sold);
  border-color: transparent;
  color: var(--text);
}

.peer:checked + .status-pill--defective {
  background-color: var(--status-defective);
  border-color: transparent;
  color: var(--text);
}

.status-badge {
  color: var(--text);
}

.status-badge--in_stock {
  background-color: var(--status-in-stock);
  border-color: transparent;
  color: var(--text);
}

.status-badge--listed {
  background-color: var(--status-listed);
  border-color: transparent;
  color: var(--text);
}

.status-badge--sold {
  background-color: var(--status-sold);
  border-color: transparent;
  color: var(--text);
}

.status-badge--defective {
  background-color: var(--status-defective);
  border-color: transparent;
  color: var(--text);
}

.account-menu__segment[aria-checked="true"] .account-menu__icon {
  color: var(--text);
}

.account-menu__segment:hover .account-menu__icon,
.account-menu__segment:focus-visible .account-menu__icon {
  color: var(--primary);
}

/* Segmented control (Wise-ish) used inside account menu */
.account-menu__segmented {
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  align-items: center;
  height: 40px;
  min-width: 170px;
  padding: 4px;
  border-radius: 9999px;
  background: color-mix(in oklab, var(--text) 8%, transparent);
  overflow: hidden;
}

.account-menu__theme-row:hover .account-menu__segmented,
.account-menu__theme-row:focus-within .account-menu__segmented {
  /* Hover highlight should be on the pill itself (Wise-like), not on a square row */
  background: color-mix(in oklab, var(--text) 12%, transparent);
}

.account-menu__segmented::before {
  content: "";
  position: absolute;
  top: 4px;
  left: 4px;
  width: calc(50% - 4px);
  height: calc(100% - 8px);
  border-radius: 9999px;
  background: var(--surface2);
  box-shadow: 0 8px 20px rgba(0, 0, 0, 0.18);
  transition: transform 160ms cubic-bezier(0.2, 0.8, 0.2, 1);
}

.account-menu__segmented[data-active="dark"]::before,
html[data-theme="dark"] .account-menu__segmented::before {
  transform: translateX(100%);
}

.account-menu__segment {
  position: relative;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  height: 100%;
  border-radius: 9999px;
  font-size: 12px;
  font-weight: 700;
  color: var(--muted);
  background: transparent;
  padding: 0;
  border: none;
  outline: none;
}

.account-menu__segmented--menu {
  width: 100%;
  min-width: 0;
  /* Base segmented has 4px padding, so make the content box 36px tall:
     44px total height - 8px padding = 36px (matches .account-menu__icon-wrap) */
  height: 44px;
}

.account-menu__segmented[data-active="light"] .account-menu__segment:first-child,
html[data-theme="light"] .account-menu__segment:first-child {
  color: var(--text);
}

.account-menu__segmented[data-active="dark"] .account-menu__segment:last-child,
html[data-theme="dark"] .account-menu__segment:last-child {
  color: var(--text);
}

.account-menu__segment:focus-visible {
  outline: 2px solid var(--focus_ring);
  outline-offset: 2px;
}

/* Links — kept in @layer base so Tailwind's layered text-color / text-decoration
   utilities (in @layer utilities, which tailwind.css orders AFTER base and loads
   before this sheet) win on <a> elements. An UNLAYERED `a {}` outranks layered
   utilities regardless of specificity, which silently forced e.g. `text-[var(--bg)]`
   on an anchor back to the link colour (the invisible active-pill bug, PR #290).
   Links without a colour utility still get var(--link) — behaviour unchanged. */
@layer base {
  a {
    color: var(--link);
    text-decoration-thickness: 1px;
    text-underline-offset: 2px;
  }

  a:hover {
    text-decoration: underline;
  }
}

/* Sidebar should NOT look like a regular "link list" (no underline, no link-blue). */
.desktop-sidebar a,
.desktop-sidebar a:hover,
.desktop-sidebar a:focus,
.desktop-sidebar a:active,
.mobile-bottom-nav a,
.mobile-bottom-nav a:hover,
.mobile-bottom-nav a:focus,
.mobile-bottom-nav a:active {
  text-decoration: none;
}

/* Theme toggle icon swapping (works without JS too) */
.theme-icon-sun {
  display: none;
}

html[data-theme="dark"] .theme-icon-sun {
  display: block;
}

html[data-theme="dark"] .theme-icon-moon {
  display: none;
}

/* Header logo swapping */
.logo-dark {
  display: none;
}

html[data-theme="dark"] .logo-dark {
  display: block;
}

html[data-theme="dark"] .logo-light {
  display: none;
}

/* Token-driven action buttons — ink primary / brick danger under .app-mono.
   Plain classes, no Tailwind freeze. Identical block also in the other Mono PRs;
   keep one copy on merge. */
.btn-token-primary {
  background: var(--primary);
  color: var(--primary_fg);
  border: 1px solid var(--primary);
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease, transform var(--mono-dur-fast) var(--mono-spring);
}
.btn-token-primary:hover {
  background: var(--primary_hover);
}
.btn-token-danger {
  background: var(--danger);
  color: var(--danger_fg);
  border: 1px solid var(--danger);
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease, transform var(--mono-dur-fast) var(--mono-spring);
}
.btn-token-danger:hover {
  background: color-mix(in oklab, var(--danger) 88%, #000);
}
/* Token outline secondary — the quiet sibling of btn-token-primary. A visible, one-tap
   button with a lighter visual weight (filled surface + ink border + ink text), used where
   a screen needs a clear hierarchy between a single filled primary and a still-present
   secondary (e.g. the Sell screen's "Paid in cash" beside the Every Pay primary). Mono:
   ink + surface only, no colour. */
.btn-token-secondary {
  background: var(--surface);
  color: var(--text);
  /* Resting boundary clears WCAG 1.4.11 non-text contrast (>=3:1) against the bare
     paper page (the Sell payment grid sits on --bg, not a card) — touch has no :hover,
     so the affordance must hold at rest. A 55% ink mix reads visibly (>=3:1 on white)
     yet stays the quiet sibling of the filled primary; :hover still raises to full ink. */
  border: 1.5px solid color-mix(in oklab, var(--text) 55%, var(--paper));
  transition: background-color 0.15s ease, border-color 0.15s ease, color 0.15s ease, box-shadow 0.15s ease, transform var(--mono-dur-fast) var(--mono-spring);
}
.btn-token-secondary:hover {
  background: var(--surface2-hover);
  border-color: var(--text);
}

/* Physicality: the token buttons press with the one Mono spring — same treatment
   ds-button got (#327), reduced-motion-safe (the :active scale only applies under
   no-preference; the base color/border transitions stay unconditional, non-vestibular).
   Covers btn-token-primary / -secondary / -danger. Never combined with ds-button. A few
   sell/index buttons also wear .mono-press: that is a BENIGN redundancy — .mono-press:active
   sets the IDENTICAL transform: scale(var(--mono-press-scale)); the higher-specificity rule
   here wins but with the same value, so the press is unchanged (no dead-rule conflict). The
   redundant .mono-press class on those buttons is now removable (the base class presses). */
@media (prefers-reduced-motion: no-preference) {
  .btn-token-primary:active:not(:disabled),
  .btn-token-secondary:active:not(:disabled),
  .btn-token-danger:active:not(:disabled) {
    transform: scale(var(--mono-press-scale));
  }
}

/* Wizard AI-describe status (photo→describe flow) — Mono token classes that replace the
   old inline color-mix style= attributes (which were off-brand amber/green/red and tripped
   the design gates). "Ink does everything; brick is the only colour, reserved for losses."
   The designed WAIT itself (labelled spinner + role/aria-live + retry) lives in the partial;
   these classes carry ONLY the colour/brand layer.

   pending → INK / neutral: a process is NOT a warning. Quiet surface card, ink text, muted
   spinner. done → INK: a calm ✓ confirmation, no green. Both share the neutral process card.
   Text colour lives here (not Tailwind arbitrary utilities) so no new utility needs a freeze. */
.wizard-ai-status--process {
  background: var(--surface2);
  border: 1px solid var(--border);
  color: var(--text);
}
.wizard-ai-status--process .wizard-ai-spinner {
  color: var(--muted);
}

/* failed → BRICK: a recoverable loss, framed CALM not hostile. Mirrors the Ui::AlertComponent
   danger idiom — quiet surface card, hairline border, a 2px left brick rule, brick text — the
   one colour moment the palette allows. Retry stays (styled .btn-token-danger in the partial). */
.wizard-ai-status--failed {
  background: var(--surface2);
  border: 1px solid var(--border);
  border-left: 2px solid var(--danger);
}
.wizard-ai-status--failed .wizard-ai-failed-text {
  color: var(--danger);
}
.wizard-ai-status--failed .wizard-ai-error-ref {
  color: var(--muted);
}

/* WhatsApp share button — the one sanctioned off-token colour (WhatsApp's brand-locked
   logo green). A real CSS :hover, NOT inline onmouseover JS (which the nonce-based CSP
   blocks). Keep the brand-green pair in this single place. */
.whatsapp-btn {
  background-color: #25d366;
  color: #fff;
  transition: background-color 0.15s ease, transform var(--mono-dur-fast) var(--mono-spring);
}
.whatsapp-btn:hover {
  background-color: #128c7e;
}
/* Physicality: the WhatsApp share button presses with the one Mono spring,
   reduced-motion-safe. Standalone (never combined with ds-button). */
@media (prefers-reduced-motion: no-preference) {
  .whatsapp-btn:active:not(:disabled) {
    transform: scale(var(--mono-press-scale));
  }
}

/* Lucro wordmark — Mono: Fraunces + ink on EVERY in-app page (not only inside
   .app-mono). Base --ui-font-display is Archivo Black and --core-ocean-blue is
   off-brand, so pin Fraunces + ink unconditionally for the partial Mono rollout. */
.lucro-logo-text {
  font-family: "Fraunces", Georgia, "Times New Roman", serif;
  font-style: normal;
  font-weight: 700;
  font-size: 31px;
  line-height: 1;
  height: auto;
  display: block;
  letter-spacing: -0.02em;
  color: var(--ink);
}

html[data-theme="dark"] .lucro-logo-text {
  color: var(--core-bright-blue);
}

/* Mono Edition: the wordmark is ink (brand-correct), not ocean-blue.
   The dark-theme selector above is (0,2,1) and would otherwise win on a
   dark-theme user's Mono page, resolving --core-bright-blue to paper (white)
   on a white header — an invisible wordmark. Qualifying the dark case here
   (0,3,1) keeps Mono ink-on-paper regardless of the persisted theme. */
.app-mono .lucro-logo-text,
html[data-theme="dark"] .app-mono .lucro-logo-text {
  color: var(--ink);
}

/* Avatar (Wise-inspired) */
.ui-avatar {
  position: relative;
  border-radius: 9999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--surface2);
  border: 1px solid var(--border);
  color: var(--muted);
  overflow: hidden;
}

.ui-avatar--interactive {
  cursor: pointer;
}

.ui-avatar--borderless {
  border: none;
}

.ui-avatar__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
  transform: scale(1.2);
  transform-origin: center;
  border-radius: 9999px;
  display: block;
}

.ui-avatar__text {
  font-size: var(--avatar-text-size, 12px);
  line-height: 1;
  font-weight: 700;
  letter-spacing: 0.02em;
}

/* Mono Avatar: hairline ink ring on white, serif (Fraunces) ink initial. */
.app-mono .ui-avatar {
  border-color: var(--ink);
  background: var(--paper);
  color: var(--ink);
}

.app-mono .ui-avatar__text {
  font-family: var(--ui-font-display);
  font-weight: 400;
  letter-spacing: normal;
  color: var(--ink);
}

.ui-avatar__badge {
  position: absolute;
  bottom: -2px;
  right: -2px;
  width: var(--badge-size, 14px);
  height: var(--badge-size, 14px);
  border-radius: 9999px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
}

.ui-avatar__badge-text {
  font-size: 9px;
  font-weight: 700;
  color: var(--text);
}

.ui-avatar__badge-icon {
  width: 10px;
  height: 10px;
  color: var(--text);
}

.ui-avatar__badge-image {
  width: 100%;
  height: 100%;
  border-radius: 9999px;
  object-fit: cover;
}

.ui-avatar__dot {
  position: absolute;
  top: -2px;
  right: -2px;
  width: 10px;
  height: 10px;
  border-radius: 9999px;
  background: var(--danger);
  border: 2px solid var(--bg);
}

/* Avatar groups */
.ui-avatar-group {
  display: inline-flex;
  align-items: center;
}

.ui-avatar-group__item + .ui-avatar-group__item {
  margin-left: calc(var(--avatar-overlap, 10px) * -1);
}

.ui-avatar-group--diagonal .ui-avatar-group__item + .ui-avatar-group__item {
  transform: translateY(var(--avatar-offset, 6px));
}

/* <details> accordion polish (used for list-item expanders) */
details > summary {
  list-style: none;
}
details > summary::-webkit-details-marker {
  display: none;
}

/* Global focus ring (keyboard users) */
:focus-visible {
  outline: 2px solid var(--focus_ring);
  outline-offset: 2px;
}

/* Prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition: none !important;
    animation: none !important;
    scroll-behavior: auto !important;
  }
}

/* OrderItem page: enforce 2-column layout at md+ without relying on Tailwind rebuilds */
.order-item-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 24px;
}

@media (min-width: 768px) {
  .order-item-grid {
    grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
    align-items: start;
  }
}

/* ============================================================
   Landing / marketing pages — Mono Edition ("Ink does everything")
   Warm monochrome: ink on paper, hairline rules over boxes,
   emphasis by weight + scale, Fraunces display + Plex Mono ledger.
   The ONLY color is brick (#993D33), reserved for losses.
   No dark mode on marketing pages: .landing pins its own tokens,
   so every var()-based component inside re-skins automatically.
   ============================================================ */
.landing {
  color-scheme: light;

  /* Mono palette */
  --ink: #0E0E0C;
  --paper: #FFFFFF;
  --mist: #F4F4F2;
  --gray: #6B6B66;
  --line: #E4E4E0;
  --brick: #993D33;

  /* Re-skin the shared token vocabulary inside the marketing scope */
  --bg: var(--paper);
  --surface: var(--mist);
  --surface2: var(--paper);
  --surface2-hover: var(--mist);
  --border: var(--line);
  --text: var(--ink);
  --muted: var(--gray);
  --subtle: var(--gray);
  --primary: var(--ink);
  --accent: var(--primary);
  --primary_hover: #2A2A26;
  --primary_active: #000000;
  --primary_fg: var(--paper);
  --link: var(--ink);
  --focus_ring: var(--ink);
  --focus_ring_offset: var(--paper);
  --button-secondary-text: var(--ink);

  /* Legacy palette aliases: old "ocean/mist" landing classes resolve to ink/mist */
  --core-ocean-blue: var(--ink);
  --core-mist-blue: var(--mist);
  --core-bright-blue: var(--paper);

  /* Type voices */
  --ui-font-display: "Fraunces", Georgia, "Times New Roman", serif;
  --ui-font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Hairline aesthetic: data sits on 2px corners, never bubbles */
  --radius-small: 2px;
  --radius-medium: 2px;
  --radius-large: 2px;
  --radius-x-large: 2px;

  background: var(--bg);
  color: var(--text);
}

/* ----- Marketing chrome (shared header/footer) ----- */
.landing-nav {
  background: var(--paper);
  border-bottom: 1px solid var(--line);
}

.landing-wordmark {
  font-family: var(--ui-font-display);
  font-weight: 700;
  font-size: 31px;
  line-height: 1;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.landing-wordmark--footer {
  font-size: 26px;
}

.landing-nav__link {
  font-size: 14px;
  color: var(--gray);
  transition: color 150ms ease;
}

.landing-nav__link:hover {
  color: var(--ink);
  text-decoration: underline;
  text-underline-offset: 4px;
}

.landing-footer {
  background: var(--paper);
  border-top: 1px solid var(--line);
}

/* ----- Voice helpers ----- */
.landing-kicker {
  font-family: var(--ui-font-mono);
  font-size: 11px;
  font-weight: 400;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--gray);
}

.landing-kicker--ink {
  color: var(--ink);
}

.landing-mono {
  font-family: var(--ui-font-mono);
}

.landing-money {
  font-family: var(--ui-font-mono);
  font-weight: 500;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.landing-money--quiet {
  font-weight: 400;
  color: var(--gray);
}

.landing-money--loss {
  color: var(--brick);
}

/* ----- Display typography (Fraunces, maison voice) ----- */
.landing-display-xl {
  font-family: var(--ui-font-display);
  font-weight: 340;
  font-size: clamp(2.75rem, 6.5vw, 5.75rem);
  line-height: 1.04;
  letter-spacing: -0.015em;
  color: var(--ink);
}

.landing-display-lg {
  font-family: var(--ui-font-display);
  font-weight: 340;
  font-size: clamp(2rem, 4vw, 3.25rem);
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.landing-display-md {
  font-family: var(--ui-font-display);
  font-weight: 340;
  font-size: clamp(1.625rem, 3vw, 2.5rem);
  line-height: 1.12;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.landing-display--light {
  color: var(--paper);
}

/* ----- Buttons: pill is reserved for the primary action ----- */
.landing-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--ink);
  color: var(--paper);
  border: 1px solid var(--ink);
  border-radius: 9999px;
  padding: 10px 22px;
  font-weight: 600;
  transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease, transform var(--mono-dur-fast) var(--mono-spring);
}

.landing-button:hover {
  background: var(--primary_hover);
  border-color: var(--primary_hover);
}

/* Ghost = quiet text link (class names are spec-asserted; keep both) */
.landing-button--ghost {
  background: transparent;
  color: var(--ink);
  border-color: transparent;
  padding-left: 12px;
  padding-right: 12px;
}

.landing-button--ghost:hover {
  background: transparent;
  border-color: transparent;
  text-decoration: underline;
  text-underline-offset: 4px;
}

.landing-button-lg {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  background: var(--ink);
  color: var(--paper);
  border: 1px solid var(--ink);
  border-radius: 9999px;
  padding: 14px 32px;
  font-size: 16px;
  font-weight: 600;
  transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease, transform var(--mono-dur-fast) var(--mono-spring);
}

.landing-button-lg:hover {
  background: var(--primary_hover);
  border-color: var(--primary_hover);
  transform: translateY(-1px);
}

/* Physicality: the landing CTAs press with the one Mono spring, reduced-motion-safe.
   On landing-button-lg the :active scale overrides the hover translateY(-1px) on press
   (depresses on touch) — intentional, a press always wins over the lift. Both standalone
   (never combined with ds-button), so no higher-specificity :active conflict. */
@media (prefers-reduced-motion: no-preference) {
  .landing-button:active:not(:disabled),
  .landing-button-lg:active:not(:disabled) {
    transform: scale(var(--mono-press-scale));
  }
}

/* Inverted = paper pill on the ink CTA band */
.landing-button--inverted {
  background: var(--paper);
  color: var(--ink);
  border-color: var(--paper);
}

.landing-button--inverted:hover {
  background: var(--mist);
  border-color: var(--mist);
}

/* Quiet arrow text-link (secondary CTA: "Ver demo →") */
.landing-textlink {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-weight: 500;
  color: var(--ink);
  text-underline-offset: 4px;
}

.landing-textlink:hover {
  text-decoration: underline;
}

.landing-textlink--light {
  color: var(--paper);
}

/* ----- Sections ----- */
.landing-section {
  background: var(--paper);
}

.landing-section-mist {
  background: var(--mist);
}

/* Ink band (final CTA) */
.landing-section-ocean {
  background: var(--ink);
  color: var(--paper);
}

.landing-section-ocean .landing-kicker {
  color: color-mix(in oklab, var(--paper) 70%, transparent);
}

.landing-hairline {
  border: 0;
  border-top: 1px solid var(--line);
}

/* ----- Generic card: hairline over box-shadow ----- */
.landing-card {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: var(--padding-medium);
}

/* ----- Select (locale switch) ----- */
.landing-select {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: 8px 32px 8px 12px;
  color: var(--ink);
  font-family: var(--ui-font-mono);
  font-size: 12px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  -webkit-appearance: none;
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230E0E0C' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  background-size: 12px;
}

/* ----- Auth form field (Mono): tokenized so the 2px radius + ink focus ring
   match the card/buttons. One class instead of a repeated 9-utility string. */
.landing .landing-input {
  width: 100%;
  background: var(--surface);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: 2px;
  padding: 10px 12px;
  transition: border-color 150ms ease, box-shadow 150ms ease;
}

.landing .landing-input::placeholder {
  color: var(--subtle);
}

/* Focus = the same 2px ink edge as .ds-field (1px border + 1px inset ink). Was a blue
   `0 0 0 2px var(--focus_ring)` outer ring — unified to the Mono ink frame app-wide. */
.landing .landing-input:focus {
  outline: none;
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
}

/* ----- OTP / verification code field (auth): ONE input styled as cells -----
   Kept a single input (never six boxes) so autocomplete="one-time-code"
   autofill keeps working; the cell look is letter-spacing, not separate fields.
   Symmetric horizontal padding reserves room for the paste icon without
   pulling the centered code off-center. */
.landing .landing-otp {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 2px;
  padding: 14px 44px;
  color: var(--ink);
  font-family: var(--ui-font-mono);
  font-size: 1.5rem;
  text-align: center;
  letter-spacing: 0.6em;
  text-indent: 0.6em; /* re-center the trailing letter-spacing gap */
}

.landing .landing-otp:focus {
  outline: none;
  border-color: var(--ink);
  box-shadow: 0 0 0 2px var(--focus_ring);
}

/* ----- Flash toast inside the auth (.landing) scope: ink/brick, 2px corners.
   App pages are unaffected — they fall back to the inline hex in the partial. */
.landing {
  --flash-success-bg: var(--ink);
  --flash-error-bg: var(--brick);
}

/* Mono Toast — ink card, white text, one leading status icon. MUST use literal Mono hex,
   NOT var(--ink)/var(--paper): those resolve only inside .landing/.app-mono, so a
   var()-based rule would render transparent on a not-yet-Mono page (PR#184 bug). */
.flash-toast {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  padding: 12px 18px;
  background: #0E0E0C;
  color: #FFFFFF;
  border-radius: 6px;
  box-shadow: 0 24px 60px -12px rgba(14, 14, 12, 0.22);
}

.flash-toast__text {
  color: #FFFFFF;
}

.flash-toast__icon {
  width: 18px;
  height: 18px;
}

/* Error/expired: the icon is the only brick moment; text stays white. */
.flash-toast--error .flash-toast__icon {
  color: #993D33;
}

.flash-toast__close {
  color: #FFFFFF;
}

.flash-toast__close:hover {
  background: rgba(255, 255, 255, 0.10);
}

.landing .flash-toast {
  border-radius: 2px;
  width: 100%;
}

/* ----- Bullets (legacy name, used by guide/faq bodies) ----- */
.landing-bullet {
  width: 14px;
  height: 1.5px;
  border-radius: 0;
  flex-shrink: 0;
  margin-top: 10px;
  background: var(--ink);
}

.landing-bullet--blue {
  background: var(--ink);
}

.landing-bullet--muted {
  background: var(--gray);
}

/* ----- Screenshot frame (guide pages keep using it) ----- */
.landing-screenshot-frame {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 2px;
  overflow: hidden;
}

.landing-screenshot-frame__header {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 10px 16px;
  border-bottom: 1px solid var(--line);
  background: var(--mist);
}

.landing-screenshot-frame__dots {
  display: flex;
  gap: 6px;
}

.landing-screenshot-frame__dot {
  width: 8px;
  height: 8px;
  border-radius: 9999px;
  border: 1px solid var(--gray);
  background: transparent;
}

.landing-screenshot-frame__dot--red,
.landing-screenshot-frame__dot--yellow,
.landing-screenshot-frame__dot--green {
  background: transparent;
}

.landing-screenshot-frame__url {
  font-family: var(--ui-font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--gray);
}

.landing-screenshot-frame__content {
  background: var(--paper);
}

.landing-screenshot-frame__content img {
  display: block;
  width: 100%;
  height: auto;
}

/* ----- Lifecycle dots: the point darkens as the piece advances ----- */
.landing-dot {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 9999px;
  flex-shrink: 0;
}

.landing-dot--stock {
  background: transparent;
  border: 1.5px solid var(--ink);
}

.landing-dot--listed {
  background: var(--gray);
}

.landing-dot--sold {
  background: var(--ink);
}

.landing-dot--defective {
  background: var(--brick);
}

/* ----- Hero ledger card (the product, hand-drawn in HTML) ----- */
.landing-ledger {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 2px;
  overflow: hidden; /* clip the reel's corners to the card radius */
}

/* ----- Hero reel: the live app in motion (rendered in reels/) ----- */
/* The autoplaying, muted, looping reel is the DEFAULT hero visual; it fills the
   ledger card frame. Autoplay is best-effort — iOS Low Power Mode / Data Saver
   refuse it, so the poster frame is designed to carry the hero on its own. */
.landing-reel {
  display: block;
  width: 100%;
  height: auto;
  background: var(--paper);
}

/* The static ledger is the fallback: hidden by default (so it never
   double-renders under the reel), shown when motion is reduced. It stays in the
   DOM regardless, so no-JS / SEO always read the server-rendered money figure. */
.landing-ledger__fallback {
  display: none;
}

@media (prefers-reduced-motion: reduce) {
  .landing-reel { display: none; }
  .landing-ledger__fallback { display: block; }
}

/* ===== Cinematic hero: golden-ratio video panel + copy overlay =====
   The demo film fills a φ-width panel flush to the right + bottom, header→bottom.
   Paper copy is lifted onto its scrimmed left portion (Mono light-on-dark); the
   left ~38.2% stays paper as editorial negative space. Below lg it collapses to a
   safe stacked layout (video banner + ink copy on paper) so the phone read always
   clears the sunlight/WCAG floor. */
:root {
  --marketing-header-h: 75px; /* measured rendered height of .landing-nav */
  --hero-seam-x: 38.2vw;      /* video's left edge = 100% − φ (1/1.618, viewport units) */
  /* The site grid's left inset — matches .ds-grid-container (max-width 1440, centred,
     stepped padding) so the hero copy shares ONE left edge with the logo, the proof
     strip and every section. Fed to the gradient offset too, so the ink→paper seam
     stays pinned to the video edge as the copy shifts onto the grid. */
  --grid-pad: 20px;
  --grid-inset: calc(max((100vw - 1440px) / 2, 0px) + var(--grid-pad));
}
@media (min-width: 480px)  { :root { --grid-pad: 32px; } }
@media (min-width: 768px)  { :root { --grid-pad: 40px; } }
@media (min-width: 992px)  { :root { --grid-pad: 80px; } }
@media (min-width: 1200px) { :root { --grid-pad: 100px; } }

.landing-hero {
  position: relative;
  min-height: calc(100svh - var(--marketing-header-h));
  background: var(--paper);
  overflow: hidden;
}

/* Right golden-ratio panel: flush to right + bottom, full height */
.landing-hero__media {
  position: absolute;
  inset: 0 0 0 var(--hero-seam-x); /* left edge pinned to the seam so the
                                      headline's ink→paper boundary aligns to it */
  margin: 0;
}

.landing-hero__still,
.landing-hero__reel {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;          /* crop top & bottom; never sideways */
  object-position: center;
}
/* Only the BOTTOM layer carries the dark base (shown while the poster loads and
   whenever the reel has no frame). The reel stays transparent until it plays, so
   it never occludes the poster. */
.landing-hero__still { z-index: 0; background: var(--ink); }
.landing-hero__reel  { z-index: 1; }

/* Left-weighted ink scrim: dark under the copy, fading toward centre so the
   seller stays visible, with a faint floor to the right edge so line-tails and
   the CTA never fall onto a bright frame. Tuned on the RENDERED page to keep
   paper copy ≥4.5:1 (≥5:1 under the primary CTA); worst case = her bright shirt. */
.landing-hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 2;
  background:
    linear-gradient(90deg,
      color-mix(in oklab, var(--ink) 90%, transparent) 0%,
      color-mix(in oklab, var(--ink) 82%, transparent) 30%,
      color-mix(in oklab, var(--ink) 70%, transparent) 50%,
      color-mix(in oklab, var(--ink) 22%, transparent) 74%,
      color-mix(in oklab, var(--ink) 12%, transparent) 100%);
}

/* Copy pinned just inside the video's left edge at any viewport width:
   (100% − 61.8%) = the 38.2% paper gutter, then a comfortable reading gutter. */
.landing-hero__copy {
  position: relative;
  z-index: 3;
  padding-left: var(--grid-inset); /* share the site grid's left edge (logo + sections) */
  padding-right: clamp(20px, 4vw, 80px);
  padding-top: clamp(2rem, 10vh, 6.5rem);
  padding-bottom: 2.5rem;
}

/* The headline starts on paper (ink) and bridges the seam onto the video (paper).
   A hard colour stop at the video's left edge — offset by the copy's left padding
   so it pins to the viewport seam — flips each glyph ink→paper as it crosses. The
   scrim keeps the paper half legible; the subhead + CTAs stay left of the seam so
   nothing else ever lands ink-on-dark. */
.landing-hero__copy .landing-display-xl {
  max-width: min(56rem, 66vw);
  font-size: clamp(2.75rem, 7vw, 5.75rem);
  line-height: 1.03;
  letter-spacing: -0.02em;
  /* background-clip:text fills only within the box, so the tight line-height clips
     the last line's descenders (the "y" in "system."). A little bottom padding
     extends the fill box so descenders render fully. */
  padding-bottom: 0.16em;
  background-image: linear-gradient(90deg,
    var(--ink) 0 calc(var(--hero-seam-x) - var(--grid-inset)),
    var(--paper) calc(var(--hero-seam-x) - var(--grid-inset)) 100%);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

.landing-hero__sub {
  max-width: min(28rem, 32vw); /* stays left of the seam → ink on paper */
  color: var(--muted);
  font-size: 1.0625rem;
  line-height: 1.6;
}

/* Actions stay on paper, left of the seam (never ink-on-dark). */
.landing-hero__actions {
  max-width: min(30rem, 33vw);
  flex-wrap: wrap;
}

/* Forced-colors / Windows High Contrast: the clipped-gradient headline uses a
   transparent fill, which would render invisible. Fall back to the system text
   colour (the DOM text is already intact for screen readers; this is the visual
   path). Mobile is already safe — it sets solid ink. */
@media (forced-colors: active) {
  .landing-hero__copy .landing-display-xl {
    background-image: none;
    -webkit-text-fill-color: CanvasText;
    color: CanvasText;
  }
}

/* Reduced motion / autoplay refused: hide the reel; the poster still carries it. */
@media (prefers-reduced-motion: reduce) {
  .landing-hero__reel { display: none; }
}

/* Below lg: stack (video banner on top, copy below) and flip copy back to
   ink-on-paper so the phone read is never paper-on-bright. */
@media (max-width: 1023px) {
  .landing-hero {
    min-height: 0;
    overflow: visible;
  }
  .landing-hero__media {
    position: relative;
    inset: auto;
    width: 100%;
    aspect-ratio: 3 / 4;
  }
  .landing-hero__scrim {
    background: linear-gradient(0deg,
      color-mix(in oklab, var(--ink) 22%, transparent) 0%, transparent 46%);
  }
  .landing-hero__copy {
    padding: 2.25rem var(--grid-inset) 0.5rem; /* copy aligns with the grid on mobile too */
  }
  /* Drop the two-tone bridge: solid ink headline on paper, nothing over the video. */
  .landing-hero__copy .landing-display-xl {
    max-width: none;
    font-size: clamp(2.5rem, 9vw, 3.25rem);
    background-image: none;
    -webkit-background-clip: border-box;
    background-clip: border-box;
    -webkit-text-fill-color: var(--ink);
    color: var(--ink);
  }
  .landing-hero__sub { max-width: none; }
  .landing-hero__actions { max-width: none; }
}

.landing-ledger__kpi {
  background: var(--mist);
  border-radius: 2px;
  padding: 16px 18px;
}

/* Figure tile (Analyze money anchor) — bordered white, no fill, mirroring the
   canonical AnalyzeSheet tile. White (not mist) keeps the gray kicker label that
   captions each figure above the ≥5:1 money/primary sunlight floor. */
.landing-figure {
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: 14px 16px;
}

.landing-ledger__row {
  border-top: 1px solid var(--line);
}

/* Order-status chip — a single state (the real app shows one status, not a toggle).
   One line, never wraps; ink label on mist ≈ 18:1. */
.landing-status-chip {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  white-space: nowrap;
  background: var(--mist);
  border-radius: 9999px;
  padding: 6px 14px;
  font-family: var(--ui-font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink);
}

/* Hand-written 2-digit piece code — the real product's selling point: a tag you
   write on any label, no barcode/QR printer. Rendered in ink (it's the feature). */
.landing-code {
  display: inline-block;
  min-width: 1.75rem;
  text-align: center;
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: 1px 6px;
  font-family: var(--ui-font-mono);
  font-size: 11px;
  color: var(--ink);
}

/* Line-art receipt (Capture step) — the parsed source, drawn in HTML (never a
   raster photo). A dashed edge evokes a torn paper receipt; stays strict Mono. */
.landing-receipt {
  background: var(--paper);
  border: 1px dashed var(--line);
  border-radius: 2px;
  padding: 16px;
}

/* Neutral photo placeholder for a piece thumbnail in the journey mocks. Grayscale
   only — Mono keeps colour for profit/loss, never for product imagery. Mirrors the
   46×56 swatch of the real InventoryScreen list row, scaled for the landing. */
.landing-swatch {
  flex-shrink: 0;
  width: 40px;
  height: 52px;
  background: var(--mist);
  border: 1px solid var(--line);
  border-radius: 2px;
}

/* Lightweight scope bar (Inventory mock) — reinforces which slice of the list you
   are looking at (✅ WWDC26-292 scope bar). Carries its active state by ink weight +
   underline, never a fill: it is a filter, not a tab bar and not a 2-state toggle. */
.landing-scope {
  display: flex;
  gap: 20px;
  border-bottom: 1px solid var(--line);
}

.landing-scope__item {
  font-family: var(--ui-font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--gray);
  padding-bottom: 10px;
  margin-bottom: -1px; /* sit the active underline on the bar's own hairline */
  border-bottom: 1.5px solid transparent;
}

.landing-scope__item--active {
  color: var(--ink);
  border-bottom-color: var(--ink);
}

/* ----- Proof strip ----- */
.landing-proof__value {
  font-family: var(--ui-font-mono);
  font-weight: 500;
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  line-height: 1.1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}

.landing-proof__label {
  font-family: var(--ui-font-mono);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--gray);
  margin-top: 8px;
}

/* ----- How-it-works hairline rows ----- */
.landing-step-num {
  font-family: var(--ui-font-display);
  font-weight: 340;
  font-size: 32px;
  line-height: 1;
  color: var(--gray);
}

/* ----- Static profit chart (illustrative, ink bars) ----- */
.landing-chart {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 2px;
}

/* Split track: a tall gains zone above the x-axis and a short loss band below it.
   Profit bars stand on the axis growing up; the loss bar hangs beneath it growing
   down — so a loss reads as a loss, not a short red profit. Columns are explicit so
   each bar lines up with its weekday label in .landing-chart__axis. */
.landing-chart__grid {
  position: relative;
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  grid-template-rows: 180px 40px;
  column-gap: 4%;
}

/* the x-axis, drawn full-width at the gains/loss boundary */
.landing-chart__grid::after {
  content: "";
  grid-column: 1 / -1;
  grid-row: 1 / 2;
  align-self: end;
  border-bottom: 1.5px solid var(--ink);
  pointer-events: none;
}

.landing-chart__bar {
  grid-row: 1;
  align-self: end;
  background: var(--ink);
  border-radius: 1px 1px 0 0;
}

.landing-chart__bar:nth-child(1) { grid-column: 1; }
.landing-chart__bar:nth-child(2) { grid-column: 2; }
.landing-chart__bar:nth-child(3) { grid-column: 3; }
.landing-chart__bar:nth-child(4) { grid-column: 4; }
.landing-chart__bar:nth-child(5) { grid-column: 5; }
.landing-chart__bar:nth-child(6) { grid-column: 6; }
.landing-chart__bar:nth-child(7) { grid-column: 7; }

.landing-chart__bar--loss {
  grid-row: 2;
  align-self: start;
  background: var(--brick);
  border-radius: 0 0 1px 1px;
}

.landing-chart__axis {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  column-gap: 4%;
  margin-top: 8px;
}

.landing-chart__axis span {
  text-align: center;
}

/* ----- Pricing bento (Mono: the bento IS the plan) -----
   Asymmetric board: ink 2×2 hero (the €0 price), wide fees tile (poster 15%),
   quiet feature tiles, full-width CTA. Spans are declared per breakpoint so the
   8-tile EU and 7-tile BR variants both tile hole-free via source order alone. */
.landing-bento {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
}

.landing-bento__tile {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: 2px;
  padding: 20px;
  display: flex;
  flex-direction: column;
}

.landing-bento__tile--hero {
  padding: 28px;
  justify-content: space-between;
  gap: 40px;
}

.landing-bento__cta {
  grid-column: 1 / -1;
}

@media (min-width: 640px) {
  .landing-bento {
    grid-template-columns: repeat(2, 1fr);
  }

  .landing-bento__tile--hero,
  .landing-bento__tile--wide {
    grid-column: span 2;
  }
}

@media (min-width: 1024px) {
  .landing-bento {
    grid-template-columns: repeat(4, 1fr);
  }

  .landing-bento__tile--hero {
    grid-column: span 2;
    grid-row: span 2;
  }
}

.landing-bento__tile--ink {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
  /* local re-skin: everything var()-based inside the ink tile flips to paper-on-ink */
  --text: var(--paper);
  --muted: color-mix(in oklab, var(--paper) 72%, transparent);
  --subtle: color-mix(in oklab, var(--paper) 60%, transparent);
  --border: color-mix(in oklab, var(--paper) 22%, transparent);
  --line: color-mix(in oklab, var(--paper) 22%, transparent);
  --ink-local: var(--paper);
}

.landing-bento__tile--ink .landing-kicker {
  color: color-mix(in oklab, var(--paper) 70%, transparent);
}

/* .landing-display-* hardcode color: var(--ink) — flip them inside the ink tile */
.landing-bento__tile--ink .landing-display-md,
.landing-bento__tile--ink .landing-display-lg {
  color: var(--paper);
}

/* The €0 poster price inside the hero (color inherits: paper on ink) */
.landing-bento__price {
  font-family: var(--ui-font-mono);
  font-weight: 500;
  font-size: clamp(2.25rem, 4vw, 3.25rem);
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

/* Poster figure on paper tiles (100%, ∞) — one dominant element per tile */
.landing-bento__figure {
  font-family: var(--ui-font-mono);
  font-weight: 500;
  font-size: clamp(2rem, 3.5vw, 3rem);
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

/* ∞ has a small glyph body in Plex Mono — scale it up so the glyph poster
   reads at the same optical size as the numeric figures */
.landing-bento__figure--glyph {
  font-size: clamp(2.75rem, 4.8vw, 4.125rem);
}

/* ----- FAQ: hairline accordion rows ----- */
.landing-accordion {
  border-top: 1px solid var(--line);
}

.landing-accordion summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  padding: 20px 0;
  cursor: pointer;
  list-style: none;
  font-weight: 500;
  color: var(--ink);
}

.landing-accordion summary::-webkit-details-marker,
.landing-accordion summary::marker {
  display: none;
}

.landing-accordion__icon {
  font-family: var(--ui-font-mono);
  font-size: 20px;
  font-weight: 400;
  line-height: 1;
  color: var(--gray);
  flex-shrink: 0;
  transition: transform 200ms ease;
}

.landing-accordion[open] .landing-accordion__icon {
  transform: rotate(45deg);
}

.landing-accordion__body {
  padding: 0 0 20px;
  color: var(--gray);
  max-width: 65ch;
}

/* FAQ accordion (legacy helper used on /faq page) */
.faq-accordion summary::-webkit-details-marker,
.faq-accordion summary::marker {
  display: none;
}

/* ----- Focus ring: ink everywhere, paper inside the ink band ----- */
.landing a:focus-visible,
.landing button:focus-visible,
.landing select:focus-visible,
.landing summary:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
  border-radius: 2px;
}

.landing .landing-button:focus-visible,
.landing .landing-button-lg:focus-visible {
  border-radius: 9999px;
}

.landing-section-ocean a:focus-visible,
.landing-section-ocean button:focus-visible,
.landing-bento__tile--ink a:focus-visible,
.landing-bento__tile--ink button:focus-visible {
  outline-color: var(--paper);
}

/* ----- Scroll reveal (JS-gated: nothing hides without html.js-anim) ----- */
html.js-anim .landing [data-reveal] {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 500ms cubic-bezier(0.16, 1, 0.3, 1), transform 500ms cubic-bezier(0.16, 1, 0.3, 1);
}

html.js-anim .landing [data-reveal].reveal--in {
  opacity: 1;
  transform: none;
}

/* Auth pages fade in place — drop the 16px rise so the form doesn't shift
   under the autofocused field. Opacity still animates. Specificity (0,4,1)
   beats the base reveal rule (0,3,1); .reveal--in is already transform:none. */
html.js-anim .landing.landing-auth [data-reveal] {
  transform: none;
}

/* Pricing bento cascade: the grid container itself never hides (hiding it
   would defeat the stagger — everything would pop as one), only its children
   do, rising 8px in 42ms nth-child steps once the container's single
   IntersectionObserver entry adds .reveal--in. Specificity (0,4,1) beats the
   base reveal rule (0,3,1). */
html.js-anim .landing .landing-bento[data-reveal] {
  opacity: 1;
  transform: none;
  transition: none;
}

html.js-anim .landing .landing-bento[data-reveal] > * {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity var(--mono-dur-enter) var(--mono-ease-emph), transform var(--mono-dur-enter) var(--mono-ease-emph);
}

html.js-anim .landing .landing-bento[data-reveal].reveal--in > * {
  opacity: 1;
  transform: none;
}

html.js-anim .landing .landing-bento[data-reveal] > *:nth-child(2) { transition-delay: calc(var(--mono-stagger-step) * 1); }
html.js-anim .landing .landing-bento[data-reveal] > *:nth-child(3) { transition-delay: calc(var(--mono-stagger-step) * 2); }
html.js-anim .landing .landing-bento[data-reveal] > *:nth-child(4) { transition-delay: calc(var(--mono-stagger-step) * 3); }
html.js-anim .landing .landing-bento[data-reveal] > *:nth-child(5) { transition-delay: calc(var(--mono-stagger-step) * 4); }
html.js-anim .landing .landing-bento[data-reveal] > *:nth-child(6) { transition-delay: calc(var(--mono-stagger-step) * 5); }
html.js-anim .landing .landing-bento[data-reveal] > *:nth-child(7) { transition-delay: calc(var(--mono-stagger-step) * 6); }
html.js-anim .landing .landing-bento[data-reveal] > *:nth-child(8) { transition-delay: calc(var(--mono-stagger-step) * 7); }
html.js-anim .landing .landing-bento[data-reveal] > *:nth-child(n+9) { transition-delay: calc(var(--mono-stagger-step) * 8); }

/* Failsafe: if the reveal controller never connects (JS module-graph load
   failure), an inline timeout adds .js-anim-failsafe so content hidden by
   html.js-anim can never stay stranded invisible. */
html.js-anim-failsafe .landing [data-reveal] {
  opacity: 1 !important;
  transform: none !important;
}

html.js-anim-failsafe .landing .landing-bento[data-reveal] > * {
  opacity: 1 !important;
  transform: none !important;
}

@media (prefers-reduced-motion: reduce) {
  html.js-anim .landing [data-reveal] {
    opacity: 1;
    transform: none;
    transition: none;
  }

  html.js-anim .landing .landing-bento[data-reveal] > * {
    opacity: 1;
    transform: none;
    transition: none;
  }

  .landing-accordion__icon {
    transition: none;
  }
}

/* Print/PDF: content hidden behind the JS reveal must land on paper — the
   mono-motion contract ("print, PDF, and reduced-motion always land on
   content, never a stuck opacity:0") applies to the scroll reveal too. */
@media print {
  html.js-anim .landing [data-reveal],
  html.js-anim .landing .landing-bento[data-reveal] > * {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}

/* Legacy divider (keep for backward compatibility) */
.landing-divider {
  border-top: 1px solid var(--line);
}

.text-muted {
  color: var(--muted);
}

.text-subtle {
  color: var(--subtle);
}

.text-link {
  color: var(--link);
}

.text-primary {
  color: var(--primary);
}

.text-success {
  color: var(--success);
}

.text-warning {
  color: var(--warning);
}

.text-danger {
  color: var(--danger);
}

/* Design system buttons */
.ds-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--size-8);
  /* Defaults for buttons without a --sm/--md/--lg size modifier.
     Size modifiers below override padding/font-size/min-height. */
  padding: 12px 24px;
  border: 1px solid transparent;
  border-radius: 12px;
  font-size: 16px;
  font-weight: 600;
  letter-spacing: 0.01em;
  cursor: pointer;
  transition: background-color 150ms ease, border-color 150ms ease, color 150ms ease, transform var(--mono-dur-fast) var(--mono-spring);
}

.ds-button:focus-visible {
  outline: 2px solid var(--focus_ring);
  outline-offset: 2px;
}

.ds-button--lg {
  min-height: var(--size-x-large);
  padding: 0 var(--padding-large);
  font-size: 16px;
}

.ds-button--md {
  min-height: var(--size-large);
  padding: 0 var(--padding-medium);
  font-size: 14px;
}

.ds-button--sm {
  min-height: var(--size-medium);
  padding: 0 var(--padding-small);
  font-size: 12px;
}

.ds-button--primary {
  background: var(--primary);
  color: var(--primary_fg);
  border-color: var(--primary);
}

.ds-button--secondary {
  background: color-mix(in oklab, var(--button-secondary-color, var(--secondary-blue)) 35%, var(--surface2));
  color: var(--button-secondary-text, var(--text));
  border-color: color-mix(in oklab, var(--button-secondary-color, var(--secondary-blue)) 55%, var(--border));
}

.ds-button--secondary-neutral {
  background: var(--surface2);
  color: var(--text);
  border-color: var(--border);
}

/* The inventory "to review" filter chip, when its filter is ON. An ink ring (not a solid fill)
   reads as a SELECTED filter while leaving "New batch" the only filled CTA on the view. */
.ds-button--secondary-neutral.is-active {
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
  color: var(--ink);
}

/* ── Mono field — the ONE selected/focus frame for every form control (text input / select /
   textarea). DS forms/Input.jsx + Select.jsx: "hairline border thickens to a 2px ink edge on
   focus — no glow, no colour." On focus the field's resting border recolours to ink and a 1px
   inset ink line completes the SAME 2px ink edge as the .ds-button--secondary-neutral.is-active
   "to review" chip above (no layout shift); invalid = brick. This is the FOCUS FRAME ONLY —
   each field keeps its own resting border + radius (squaring legacy rounded-xl/lg fields to the
   Mono 2px is a separate follow-up). Replaces the ring-2 primary / focus_ring / accent /
   default-blue / literal-blue / color-mix-halo one-offs that had accreted across the app.
   `--ink` (not `--focus_ring`) so the frame is ink on EVERY surface, incl. the root/landing
   scope where --focus_ring is blue. */
.ds-field {
  transition: border-color var(--mono-dur-hover, 140ms) var(--mono-ease, ease),
              box-shadow var(--mono-dur-hover, 140ms) var(--mono-ease, ease);
}

.ds-field:focus,
.ds-field:focus-visible,
.ds-field:focus-within {
  outline: none;
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
}

.ds-field[aria-invalid="true"],
.ds-field.is-invalid {
  border-color: var(--brick);
  box-shadow: inset 0 0 0 1px var(--brick);
}

.ds-button--tertiary {
  background: transparent;
  color: var(--text);
  border-color: transparent;
}

.ds-button--negative {
  background: var(--danger);
  color: var(--danger_fg);
  border-color: var(--danger);
}

.ds-button--negative-secondary {
  background: color-mix(in oklab, var(--danger) 12%, var(--surface2));
  color: var(--danger);
  border-color: color-mix(in oklab, var(--danger) 30%, var(--border));
}

.ds-button--negative-tertiary {
  background: transparent;
  color: var(--danger);
  border-color: transparent;
}

.ds-button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

/* Physicality: EVERY ds-button presses with the one Mono spring (generalizes the old
   primary-only scale 0.98), reduced-motion-safe. Pairs with the transform spring in the
   base .ds-button transition above. Under reduced-motion the :active scale simply doesn't
   apply, so nothing moves. (Intentional scoping difference vs .mono-press, which gates BOTH
   its transition and :active under no-preference: here the base transition stays unconditional
   so color/background transitions — non-vestibular — survive reduced-motion; only the
   transform :active is gated. Functionally identical press, no vestibular motion when reduced.) */
@media (prefers-reduced-motion: no-preference) {
  .ds-button:active:not(:disabled) {
    transform: scale(var(--mono-press-scale));
  }
}

.ds-button--primary:disabled {
  background: var(--surface2);
  color: var(--muted);
  border: 1px solid var(--border);
  cursor: not-allowed;
  opacity: 0.7;
}

.ds-button--full-width {
  width: 100%;
}

@media (hover: hover) {
  .ds-button--primary:hover:not(:disabled) {
    background: var(--primary_hover);
    border-color: var(--primary_hover);
  }

  .ds-button--secondary:hover {
    background: color-mix(in oklab, var(--button-secondary-color, var(--secondary-blue)) 45%, var(--surface2));
  }

  .ds-button--secondary-neutral:hover {
    background: var(--surface);
  }

  .ds-button--tertiary:hover {
    background: var(--surface);
  }

  .ds-button--negative:hover {
    background: color-mix(in oklab, var(--danger) 85%, black);
    border-color: color-mix(in oklab, var(--danger) 85%, black);
  }

  .ds-button--negative-secondary:hover {
    background: color-mix(in oklab, var(--danger) 18%, var(--surface2));
  }

  .ds-button--negative-tertiary:hover {
    background: color-mix(in oklab, var(--danger) 12%, transparent);
  }
}

/* ── Toolbar — the screen's sticky top bar (DS ui_kits/erp/Toolbar.jsx → Mono) ──
   Answers "where am I" (the page <h1> title) + "what can I do" (icon+label actions).
   Translucent paper bg + blur + a bottom hairline; ink only, no gradient. Hand-authored
   in the Mono token idiom (NOT a Tailwind utility, so no tailwind:freeze needed). */
.ds-toolbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--size-12);
  padding: 14px 0 12px;
  background: color-mix(in oklab, var(--bg) 82%, transparent);
  border-bottom: 1px solid var(--border);
  backdrop-filter: saturate(140%) blur(12px);
  -webkit-backdrop-filter: saturate(140%) blur(12px);
}

.ds-toolbar__lead {
  display: flex;
  align-items: baseline;
  gap: var(--size-12);
  min-width: 0; /* allow the title to wrap, never force overflow on the row */
}

/* The page title — display voice (Fraunces under .app-mono), ink, balanced, never truncates.
   Scoped under .ds-toolbar (specificity 0,2,0) so the DS-contract weight 360 beats the
   pre-existing `.app-mono h1 { font-weight: 600 }` (0,1,1) on the analyze screen. */
.ds-toolbar .ds-toolbar__title {
  font-family: var(--ui-font-display);
  font-size: 26px;
  font-weight: 360;
  line-height: 1.1;
  letter-spacing: -0.01em;
  color: var(--ink, var(--text));
  text-wrap: balance;
  margin: 0;
}

/* Optional count — mono ledger voice, uppercase, muted, nowrap. */
.ds-toolbar__count {
  font-family: var(--ui-font-mono);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
  align-self: center;
}

/* Catalog cart count, shown inside the toolbar "Sell" (ds-button--primary). Inverts the button's
   own ink/paper so it reads as a chip; .mono-tick gives it a non-money overshoot bump on each add. */
.ds-toolbar__action-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  margin-left: 6px;
  padding: 0 6px;
  border-radius: 9999px;
  background: var(--primary_fg);
  color: var(--primary);
  font-family: var(--ui-font-mono);
  font-weight: 600;
  font-size: 12px;
  line-height: 1;
}

.ds-toolbar__actions {
  display: flex;
  align-items: center;
  gap: var(--size-8);
  flex-shrink: 0;
}

/* Toolbar action buttons: icon + label, ≥44px target. */
.ds-toolbar__actions .ds-button {
  min-height: 44px;
  gap: 6px;
  text-decoration: none;
}

/* stack_on_mobile: below sm, the title sits ABOVE the actions (opt-in via Ui::ToolbarComponent)
   so a screen with two actions doesn't crowd the <h1> on a phone. Other toolbars are unaffected. */
@media (max-width: 639px) {
  .ds-toolbar--stack {
    flex-direction: column;
    align-items: stretch;
  }

  /* Three labeled 44px actions (New batch · New pop-up · "to review") overflow a ~360px phone,
     so let the row wrap; the trailing chip drops to a second line as a unit, full label + target
     intact. */
  .ds-toolbar--stack .ds-toolbar__actions {
    justify-content: flex-start;
    flex-wrap: wrap;
  }
}

/* ===== Search field (Ui::SearchFieldComponent) =====
   The one DS-aligned search pill — consolidates the three bespoke `.search-pill` blocks
   (inventory, catalog, marketplace browse). Mono tokens only; pill-shaped; INK hairline
   border (focus → stronger ink border + soft ink ring). Leading magnifying glass, scope
   placeholder, trailing clear (×). ≥44px targets. */
.ds-search-form {
  width: 100%;
}

.ds-search {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 44px;
  padding-left: var(--size-16);
  gap: var(--size-8);
  background: var(--surface);
  border: 1px solid color-mix(in oklab, var(--text) 30%, transparent);
  border-radius: 9999px;
  font-family: var(--ui-font-sans);
  transition: border-color 0.15s ease;
}

/* Leading magnifying glass — always reads as "search", muted ink. */
.ds-search__lead {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  color: var(--muted);
  pointer-events: none;
}

/* The input — transparent so the pill is the surface; ink text, muted placeholder. */
.ds-search .ds-search__input {
  flex: 1 1 auto;
  min-width: 0;
  height: 42px;
  background: transparent;
  border: none;
  /* 16px avoids iOS Safari focus auto-zoom; the 42px pill accommodates it. */
  font-size: 16px;
  color: var(--text);
  outline: none !important;
  box-shadow: none !important;
}

.ds-search .ds-search__input::placeholder {
  color: var(--muted);
}

/* Token (pills) mode — the catalog search gathers several codes/searches at once. Pills + the live
   input wrap inside the pill; the input keeps growing to fill the row. */
.ds-search__pills {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--size-8);
  flex: 1 1 auto;
  min-width: 0;
  padding: 6px 0;
}

.ds-search__pills .ds-search__input {
  flex: 1 1 80px;
  min-width: 80px;
  height: auto;
}

.ds-search__pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
  padding: 3px 6px 3px 10px;
  border-radius: 9999px;
}

.ds-search__pill button {
  line-height: 1;
  font-size: 14px;
}

/* Suppress the native type=search clear (×) so it doesn't double the custom .ds-search__clear. */
.ds-search .ds-search__input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

/* Clear (×) — ≥44px hit target, muted → ink on hover. */
.ds-search__clear {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  width: 44px;
  height: 44px;
  color: var(--muted);
  transition: color 0.15s ease;
}

.ds-search__clear:hover {
  color: var(--text);
}

/* Optional visible submit (marketplace keeps its submit button).
   No margin: a 44px submit inside a 44px pill keeps the pill at the 44px
   baseline (matching the inventory/catalog pills); inner padding carries the gap. */
.ds-search__submit {
  flex-shrink: 0;
  padding: var(--size-8) var(--size-16);
  min-height: 44px;
  border: none;
  border-radius: 9999px;
  background: var(--primary);
  color: var(--primary_fg, var(--bg));
  font-family: var(--ui-font-sans);
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

/* Focus = the same 2px ink edge as .ds-field (1px border + 1px inset ink), NOT a soft halo —
   so the search pill's selected frame matches every other field. Reduced-motion safe (only the
   transition is gated, below). */
.ds-search:focus-within {
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
}

@media (prefers-reduced-motion: no-preference) {
  .ds-search {
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
  }
}

/* Design system cards — Mono: hairline border (replaces Wise bg-only). */
.ds-card {
  background: var(--surface2);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-card);
  color: var(--text);
  display: flex;
  flex-direction: column;
  gap: var(--size-12);
  position: relative;
}

/* Mono "white hairline surface": flat, hairline border, no elevation. */
.ds-surface-card {
  background: var(--surface2);
  border: 1px solid var(--line);
  color: var(--text);
}

.ds-card--small {
  min-height: 88px;
  max-height: 300px;
  padding: var(--padding-small);
  border-radius: var(--radius-medium);
}

.ds-card--large {
  min-height: 300px;
  padding: var(--padding-large);
  border-radius: var(--radius-x-large);
}

.ds-card--soft {
  background: var(--surface2);
}

.ds-card--clickable {
  cursor: pointer;
  transition: background-color 150ms ease, box-shadow 150ms ease, transform var(--mono-dur-fast) var(--mono-spring);
}
/* Physicality: a clickable card presses with the one Mono spring (transform only — like
   .mono-press for cards, no tint), reduced-motion-safe. The press scale must beat the
   .ds-card--clickable:hover translateY(-3px) (both touch transform, equal weight), and that
   :hover rule is LATER in source — so the bare :active would lose on a hovering device
   (desktop mouse-down = :active AND :hover both true). Chaining the always-present base
   `.ds-card` raises the press to specificity (0,3,0) > hover (0,2,0), so it wins regardless
   of source order — same specificity-bump pattern as .landing-button-lg:active:not(:disabled).
   On touch (@media hover:none) the hover rule never applies anyway. */
@media (prefers-reduced-motion: no-preference) {
  .ds-card.ds-card--clickable:active {
    transform: scale(var(--mono-press-scale));
  }
}

.ds-card-grid {
  display: grid;
  gap: var(--size-16);
}

.ds-card-stack {
  display: grid;
  gap: var(--size-16);
}

.ds-card__row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--size-12);
}

.ds-card__stack {
  display: flex;
  flex-direction: column;
  gap: var(--size-4);
}

.ds-card__eyebrow {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.18em;
  color: var(--subtle);
  font-weight: 600;
}

.ds-card__label {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
}

.ds-card__title {
  font-size: 16px;
  font-weight: 600;
  color: var(--text);
}

.ds-card__value {
  font-size: 24px;
  font-weight: 700;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.ds-card__value--lg {
  font-size: 28px;
}

.ds-card__value--xl {
  font-size: 32px;
}

.ds-card__value--sm {
  font-size: 20px;
}

/* StatTile: the figure is always Plex Mono (ledger voice), medium, no tracking. */
.ds-stat-tile__value {
  font-family: var(--ui-font-mono);
  font-weight: 500;
  letter-spacing: normal;
}

/* StatTile label: Plex Mono uppercase eyebrow, 11px, 0.1em tracking, muted. */
.ds-stat-tile__label {
  font-family: var(--ui-font-mono);
  font-weight: 500;
  font-size: 11px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

/* Value color modifiers for financial data (accessible contrast maintained) */
.ds-card__value--success {
  color: var(--success);
}

.ds-card__value--danger {
  color: var(--danger);
}

.ds-card__meta {
  font-size: 12px;
  color: var(--muted);
}

.ds-card__divider {
  height: 1px;
  background: var(--border);
}

.ds-card__pill-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--size-8);
}

.ds-card__pill {
  padding: var(--size-4) var(--size-12);
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
  border: 1px solid transparent;
}

.ds-card__pill--active {
  background: color-mix(in oklab, var(--primary) 14%, var(--surface));
  border-color: color-mix(in oklab, var(--primary) 30%, transparent);
  color: var(--primary);
}

.ds-card__tag {
  padding: var(--size-4) var(--size-12);
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  color: var(--success);
  background: color-mix(in oklab, var(--success) 18%, var(--surface));
}

.ds-card__sparkline {
  width: 100%;
  height: 64px;
}

.ds-card__sparkline path {
  stroke: var(--sparkline-color, var(--success));
  stroke-width: 3;
  fill: none;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.ds-card__sparkline circle {
  fill: var(--sparkline-color, var(--success));
}

.ds-card__bar {
  height: 12px;
  border-radius: 999px;
  background: color-mix(in oklab, var(--bar-color, var(--primary)) 18%, var(--surface));
  overflow: hidden;
}

.ds-card__bar-fill {
  height: 100%;
  border-radius: inherit;
  background: var(--bar-color, var(--primary));
}

.ds-card__link {
  color: var(--link);
  font-weight: 600;
  font-size: 13px;
  text-decoration: none;
}

.ds-card__link:hover {
  text-decoration: underline;
}

.ds-card__icon-circle {
  width: var(--size-medium);
  height: var(--size-medium);
  border-radius: 999px;
  background: color-mix(in oklab, var(--icon-color, var(--primary)) 18%, var(--surface));
  color: var(--icon-color, var(--primary));
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.ds-card__footer {
  margin-top: auto;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--size-12);
}

.ds-card__dismiss {
  position: absolute;
  top: var(--size-8);
  right: var(--size-8);
  width: var(--size-medium);
  height: var(--size-medium);
  border-radius: 999px;
  background: var(--surface);
  color: var(--muted);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  transition: color 150ms ease, background-color 150ms ease;
}

.ds-card__dismiss:hover {
  color: var(--text);
  background: color-mix(in oklab, var(--surface) 85%, var(--primary) 15%);
}

.ds-card__media-block {
  height: 120px;
  border-radius: var(--radius-medium);
  background: color-mix(in oklab, var(--primary) 12%, var(--surface));
}

.ds-card__media-block--overlap {
  margin-bottom: calc(var(--size-16) * -1);
}

@media (min-width: 768px) {
  .ds-card-grid {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: start;
  }
}

@media (hover: hover) {
  .ds-card--clickable:hover {
    background: var(--surface2-hover);
    box-shadow: 0 12px 28px rgba(0, 0, 0, 0.18), 0 4px 10px rgba(0, 0, 0, 0.12);
    transform: translateY(-3px);
  }
}

html[data-theme="dark"] .ds-card--clickable:hover {
  box-shadow: 0 12px 28px rgba(0, 0, 0, 0.5), 0 4px 10px rgba(0, 0, 0, 0.35);
}

/* Design system form inputs */
.ds-input-label {
  display: block;
  margin-bottom: 4px;
  font-size: 14px;
  font-weight: 600;
  color: var(--text);
  letter-spacing: 0.0125em;
}

/* Mono: square 2px corners (the brand resists roundness); 48px control height. */
.ds-input-container {
  background: var(--surface2);
  border: 1px solid color-mix(in oklab, var(--text) 30%, transparent);
  border-radius: 2px;
  padding: 8px 12px;
  min-height: 48px;
  display: flex;
  align-items: center;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.ds-input-container:hover {
  border-color: color-mix(in oklab, var(--text) 45%, transparent);
}

/* Mono focus = solid 2px ink edge (1px border + 1px inset), NO coloured ring. Ink (not
   --focus_ring) so it matches .ds-field on every surface, incl. root/landing where focus_ring is blue. */
.ds-input-container:focus-within {
  border-color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--ink);
}

.ds-input {
  width: 100%;
  background: transparent;
  color: var(--text);
  border: none;
  outline: none;
}

.ds-input::placeholder {
  color: var(--subtle);
}

/* Design system upload input (Wise-inspired) */
.ds-upload {
  position: relative;
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 16px;
  background: var(--surface2);
  border: 1px solid color-mix(in oklab, var(--text) 30%, transparent);
  border-radius: 12px;
  padding: 16px 20px;
  cursor: pointer;
  transition: border-color 0.15s ease, background-color 0.15s ease, box-shadow 0.15s ease;
}

.ds-upload:hover {
  background: var(--surface2-hover);
  border-color: color-mix(in oklab, var(--text) 45%, transparent);
}

.ds-upload:focus-within {
  border-color: color-mix(in oklab, var(--text) 60%, transparent);
  box-shadow: 0 0 0 2px color-mix(in oklab, var(--primary) 20%, transparent);
}

.ds-upload__icon {
  flex-shrink: 0;
  width: 24px;
  height: 24px;
  color: var(--primary);
}

.ds-upload__content {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  text-align: left;
}

.ds-upload__title {
  font-size: 16px;
  font-weight: 600;
  color: var(--primary);
  text-decoration: underline;
  text-underline-offset: 2px;
}

.ds-upload__description {
  font-size: 14px;
  color: var(--muted);
  margin-top: 2px;
}

.ds-upload input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* Mobile bottom nav - solid background, permanently pinned to the bottom */
.mobile-bottom-nav {
  background-color: var(--bg);
  border-top: 1px solid var(--border);
}

/* Sales-tab "to ship" count badge. Mono law: this is WORK TO DO (not a loss), so it is
   INK — solid --text fill with the page --bg as the number, never brick. Anchored to the
   top-right of the nav icon; the count drives its presence (rendered only when > 0). */
.nav-to-ship-badge {
  position: absolute;
  top: -6px;
  right: -8px;
  min-width: 16px;
  height: 16px;
  padding: 0 4px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 9999px;
  background-color: var(--text);
  color: var(--bg);
  font-family: var(--ui-font-mono);
  font-size: 10px;
  font-weight: 600;
  line-height: 1;
}

/* Live-tick badge target — a transparent wrapper around the absolute .nav-to-ship-badge pill that
   stays in the DOM at count 0 so the broadcast (update_all .js-to-ship-badge-target) always has a
   target. display:contents removes it from layout, so the pill still positions against the .relative
   icon span and the wrapper never affects spacing. aria-live (on the element) announces a count change.
   a11y caveat (accepted): pre-Chrome-89 / old mobile Firefox dropped display:contents nodes from the
   accessibility tree, which would suppress this announcement; on the modern-phone target audience it is
   reliable, so we accept it. If the audience ever broadens, move aria-live to the .nav-to-ship-badge pill. */
.js-to-ship-badge-target { display: contents; }

/* App chrome: desktop left sidebar (Wise-like) */
.desktop-sidebar {
  display: none;
}

@media (min-width: 1024px) {
  :root {
    /* Future: can be reduced to show icon-only collapsed rail */
    --sidebar-width: 18rem;
  }

  /* When the desktop sidebar is present, make the global header span edge-to-edge. */
  body:has(.desktop-sidebar) .global-header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 50;
    background: var(--bg);
    border-bottom: none;
  }

  .desktop-sidebar {
    display: block;
    position: fixed;
    left: 0;
    top: var(--app-header-height);
    bottom: 0;
    width: var(--sidebar-width);
    z-index: 40;
    background: var(--bg);
    border-right: none;
    overflow-y: auto;
  }

  /* Desktop sidebar item states */
  .desktop-sidebar__link {
    color: var(--muted);
  }

  .desktop-sidebar__link:hover {
    background: transparent;
    color: var(--primary);
  }

  /* data-active comes from the ERB partial */
  .desktop-sidebar__link[data-active="true"] {
    background-color: color-mix(in oklab, var(--primary) 10%, transparent);
    color: var(--text);
  }

  /* Remove "outlined icon" look */
  .desktop-sidebar__icon {
    border: none;
    box-shadow: none;
    outline: none;
  }

  /* Only offset pages that actually render the sidebar */
  body:has(.desktop-sidebar) {
    padding-left: var(--sidebar-width);
    padding-top: var(--app-header-height);
  }

  /* Ensure mobile bottom nav never appears on desktop, even if Tailwind breakpoints don't rebuild */
  .mobile-bottom-nav {
    display: none !important;
  }

  /* Sidebar height is now below the fixed header. */
  body:has(.desktop-sidebar) .desktop-sidebar {
    height: calc(100vh - var(--app-header-height));
  }
}

/* ============================================================
   Authenticated app — Mono Edition ("Ink does everything")
   Opt-in per page via <body class="app-mono"> (content_for :body_class).
   Mirrors the .landing token re-pin so every var()-based component
   re-skins automatically; light-only (no dark mode in-app).
   Strict ink + brick: positive/neutral status reads by icon + weight;
   brick (#993D33) is the ONLY color, reserved for error/loss/defective.
   Plain CSS, served as-is by Propshaft — no Tailwind freeze needed.
   Revert a page = delete its `content_for :body_class` line.
   ============================================================ */
.app-mono {
  color-scheme: light;

  /* ── Activation motion + figure tokens (mono_activation_tokens handoff) ──
     Count-up speeds, named springs, tier-0 micro durations, and the money-figure
     scale that the peak / END / StatTile components anchor to. The .mono-* motion
     vocabulary itself lives in the "Mono Motion" section appended to this file. */
  --dur-count: 1000ms;       /* dashboard money tiles */
  --dur-count-peak: 1400ms;  /* first-sale / payout peak figure */
  --dur-ring: 320ms;         /* success ring stamp (SuccessModal / coda mark) */
  --dur-coda: 360ms;         /* END coda screen settling at the exhale */
  --dur-settle: 240ms;       /* a figure's settle as the count-up lands */
  --dur-tap: 90ms;
  --dur-hover: 140ms;
  --dur-flash: 200ms;
  --figure-hero: 52px;       /* the HERO money figure — the anchor */
  --figure-lg: 30px;
  --figure-md: 20px;
  --figure-sm: 15px;

  /* Mono palette */
  --ink: #0E0E0C;
  --paper: #FFFFFF;
  --mist: #F4F4F2;
  --gray: #6B6B66;
  --line: #E4E4E0;
  --brick: #993D33;
  --stone: #999998; /* faintest lifecycle state (reserved dot ring) */

  /* Re-skin the shared token vocabulary */
  --bg: var(--paper);
  --surface: var(--mist);
  --surface2: var(--paper);
  --surface2-hover: var(--mist);
  --border: var(--line);
  --text: var(--ink);
  --muted: #4A4A45; /* Figma DS1 spec: 9:1 on white, distinct from --subtle's #6B6B66 */
  --subtle: var(--gray);
  --primary: var(--ink);
  --accent: var(--primary);
  --primary_hover: #2A2A26;
  --primary_active: #000000;
  --primary_fg: var(--paper);
  --link: var(--ink);
  --focus_ring: var(--ink);
  --focus_ring_offset: var(--paper);
  --button-secondary-text: var(--ink);
  /* Re-pin the legacy teal --secondary-blue to ink: .ds-button--secondary resolves its fill +
     border through var(--button-secondary-color, var(--secondary-blue)), so without this every
     secondary CTA under Mono (first-sale "Share shop", journey-coda, capture/order_items wizard)
     would render the off-brand :root teal. Mono = ink only; brick is the single allowed color. */
  --secondary-blue: var(--ink);

  /* Strict ink + brick: state tokens collapse to ink; only loss = brick.
     This converts every var(--success/--warning/--danger)-based component
     (badges, alerts, the profit chart) to Mono without touching markup. */
  --success: var(--ink);
  --success_fg: var(--paper);
  --warning: var(--ink);
  --warning_fg: var(--paper);
  --danger: var(--brick);
  --danger_fg: var(--paper);
  --error: var(--danger);
  /* Lifecycle ramp — ink DARKENS as the item progresses (Figma StatusChip 24:104):
     in-stock faint → reserved mid → listed gray → sold full ink → defective brick. */
  --status-in-stock: color-mix(in oklab, var(--ink) 16%, var(--paper));
  --status-reserved: color-mix(in oklab, var(--ink) 38%, var(--paper));
  --status-listed: var(--gray);
  --status-sold: var(--ink);
  --status-defective: var(--brick);

  /* Inventory status-bar accents → ink tints; defective → brick */
  --bright-orange: color-mix(in oklab, var(--ink) 14%, var(--paper));
  --bright-yellow: color-mix(in oklab, var(--ink) 22%, var(--paper));
  --bright-blue: color-mix(in oklab, var(--ink) 30%, var(--paper));
  --bright-pink: color-mix(in oklab, var(--brick) 16%, var(--paper));

  /* Flash toast badges — Strict ink+brick (success=ink, error=brick) */
  --flash-success-bg: var(--ink);
  --flash-error-bg: var(--brick);

  /* Legacy ocean/mist aliases resolve to ink/mist */
  --core-ocean-blue: var(--ink);
  --core-mist-blue: var(--mist);
  --core-bright-blue: var(--paper);

  /* Type voices: Fraunces display, IBM Plex Mono ledger */
  --ui-font-display: "Fraunces", Georgia, "Times New Roman", serif;
  --ui-font-mono: "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Hairline aesthetic: flat 2px corners on data containers */
  --radius-small: 2px;
  --radius-medium: 2px;
  --radius-large: 2px;
  --radius-x-large: 2px;
  --radius-2x-large: 2px;

  background: var(--bg);
  color: var(--text);
}

/* ── Mono StatusChip — lifecycle dot + ink label (Figma "StatusChip" 24:104) ──
   Replaces the pastel .status-badge--* fills with a white pill + 8px status dot.
   Dot ramp: in-stock hollow ink ring → listed gray → sold full ink → defective
   brick. Pure CSS, scoped to .app-mono; no markup/JS change. App-wide (any page
   that wears .app-mono: order_items today, sell once it ramps). */
.app-mono .status-badge {
  display: inline-flex !important;
  align-items: center;
  gap: 8px;
  background-color: var(--paper) !important;
  border: 1px solid var(--ink) !important;
  color: var(--ink) !important;
  border-radius: 999px !important;
  height: 28px;
  padding: 0 12px 0 11px !important;
  font-family: var(--ui-font-mono);
  font-weight: 500; /* Plex Mono medium applies to ALL chips */
  font-size: 12px;
  letter-spacing: 0.04em;
  line-height: 1;
}
.app-mono .status-badge::before {
  content: "";
  flex: none;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: var(--ink);
}
.app-mono .status-badge--in_stock::before {
  background-color: var(--paper);
  box-shadow: inset 0 0 0 1.5px var(--ink); /* hollow ink ring — the faintest lifecycle state stays perceivable */
}
.app-mono .status-badge--reserved::before {
  background-color: var(--paper);
  box-shadow: inset 0 0 0 1.5px var(--stone); /* stone ring — reserved is distinct from in_stock's ink ring */
}
.app-mono .status-badge--listed::before { background-color: var(--gray); }
.app-mono .status-badge--sold::before { background-color: var(--ink); }
.app-mono .status-badge--defective::before { background-color: var(--brick); }

/* Status SELECTOR pills (radio group): selected = ink (brick for defective);
   unchecked keep their mist + ink markup defaults. */
.app-mono .peer:checked + .status-pill {
  background-color: var(--ink) !important;
  border-color: var(--ink) !important;
  color: var(--paper) !important;
}
.app-mono .peer:checked + .status-pill--defective {
  background-color: var(--brick) !important;
  border-color: var(--brick) !important;
}

/* Category / tag chips (shared pill-input) → neutral Mono chip; the JS validation
   states (bg-red-800 / bg-emerald-800) → brick / ink. */
.app-mono .category-tag-chip {
  background-color: var(--mist) !important;
  border: 1px solid var(--line) !important;
  color: var(--ink) !important;
}
.app-mono .category-tag-chip button {
  color: var(--gray) !important;
}
.app-mono .category-tag-chip button:hover {
  color: var(--ink) !important;
}
/* Search keyword tags sit on a field whose background IS --mist (== the pill fill), so the default
   --line hairline disappears. Give search pills the field's own ink hairline so each tag reads as a
   distinct chip. Scoped to .ds-search__pills → covers BOTH server-rendered (.ds-search__pill) and
   JS-created (.category-tag-chip) pills; catalog category pills + the Sell code field are untouched. */
.app-mono .ds-search__pills .category-tag-chip {
  border-color: color-mix(in oklab, var(--ink) 24%, transparent) !important;
}
.app-mono [data-pill-input-target="pill"].bg-red-800 {
  background-color: var(--brick) !important;
  border-color: var(--brick) !important;
  color: var(--paper) !important;
}
.app-mono [data-pill-input-target="pill"].bg-emerald-800 {
  background-color: var(--ink) !important;
  border-color: var(--ink) !important;
  color: var(--paper) !important;
}

/* Page titles wear the Fraunces display voice in-app */
.app-mono h1 {
  font-family: var(--ui-font-display);
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* Faithful Mono: flatten rounded data containers and design-system buttons to 2px.
   Excludes rounded-full (avatars, pills) and small radii (rounded/sm/md). */
.app-mono .rounded-lg,
.app-mono .rounded-xl,
.app-mono .rounded-2xl,
.app-mono .rounded-3xl,
.app-mono .ds-button { border-radius: 2px; }

/* Square (Mono 2px) corners on form controls APP-WIDE, even outside .app-mono
   (marketplace, ship, pos render on layouts that never get the .app-mono body
   class). Tagging the rounded-* utility with its element (input/select/textarea)
   raises specificity above the bare utility, so this overrides inline
   rounded-md/lg/xl with NO per-view edits and NO Tailwind freeze. Scoped to form
   controls only — cards, avatars, and rounded-full pills are untouched. */
input.rounded-sm, input.rounded, input.rounded-md, input.rounded-lg, input.rounded-xl, input.rounded-2xl,
select.rounded-sm, select.rounded, select.rounded-md, select.rounded-lg, select.rounded-xl, select.rounded-2xl,
textarea.rounded-sm, textarea.rounded, textarea.rounded-md, textarea.rounded-lg, textarea.rounded-xl, textarea.rounded-2xl {
  border-radius: 2px;
}

/* Mono form controls: replace the native select arrow with a custom ink chevron
   (mirrors .landing-select), and pull the native date calendar icon to the SAME
   12px inset + vertical center so the two controls line up. padding-right: 38px
   moves the arrow off the corner and keeps text clear of the icon. */
.app-mono select {
  -webkit-appearance: none;
  appearance: none;
  /* Spec chevron: 12px at stroke-width 1.4 (lighter than the old 14px / 2). */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230E0E0C' stroke-width='1.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 12px;
  padding-right: 38px;
}

.app-mono input[type="date"] {
  padding-right: 38px;
  position: relative;
}

/* WebKit/Blink only — Firefox draws its own non-stylable date control. */
.app-mono input[type="date"]::-webkit-calendar-picker-indicator {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  width: 14px;
  height: 14px;
  margin: 0;
  padding: 0;
  cursor: pointer;
}

/* Money / ledger numerals (add .app-money where figures appear) */
.app-mono .app-money {
  font-family: var(--ui-font-mono);
  font-variant-numeric: tabular-nums;
}

/* Capture processing status tiles — base (non-Mono) look.
   `_processing_card` is a SHARED partial: it also renders on the inventory
   index (#email-processing-container) and via Turbo broadcasts, which are NOT
   .app-mono. These un-scoped rules restore the original rounded blue/amber/red
   the markup used to carry inline; .app-mono overrides them to ink/brick below. */
.capture-status__icon {
  border-radius: 0.75rem; /* was Tailwind rounded-xl on the tile */
  background: rgb(59 130 246 / 0.2); /* blue-500/20 — processing/pending */
  color: rgb(96 165 250); /* blue-400 */
}
.capture-status__icon--parsed {
  background: rgb(245 158 11 / 0.2); /* amber-500/20 */
  color: rgb(251 191 36); /* amber-400 */
}
.capture-status__icon--alert {
  background: rgb(239 68 68 / 0.2); /* red-500/20 */
  color: rgb(248 113 113); /* red-400 */
}
.capture-status__msg--alert { color: rgb(248 113 113); } /* red-400 */

/* Capture processing status tiles — Strict ink + brick under Mono.
   Higher specificity (.app-mono …) overrides the base rules above so the
   piloted /capture page reads as strict ink + brick with zero markup edits. */
.app-mono .capture-status__icon {
  border-radius: 2px;
  background: color-mix(in oklab, var(--ink) 8%, transparent);
  color: var(--ink);
}
.app-mono .capture-status__icon--alert {
  background: color-mix(in oklab, var(--brick) 12%, transparent);
  color: var(--brick);
}
.app-mono .capture-status__msg--alert { color: var(--brick); }

/* Retire dark mode in-app: hide the theme toggle on Mono pages
   (full removal of the control + Stimulus controller deferred to the
   global-promote PR). The token block already pins light regardless. */
.app-mono .account-menu__theme-row,
.app-mono [data-theme-target="toggle"] { display: none !important; }

/* Modal panels must follow the light Mono surface regardless of the persisted
   data-theme. `.arrival-modal__panel` hardcodes a dark #292d29 !important and
   only flips to white under html[data-theme="light"]; but the in-app theme
   toggle is hidden above, so a user with a stored dark theme would keep a dark
   panel while Mono pins text to ink → dark-on-dark title/hint. Pin it to paper. */
.app-mono .arrival-modal__panel {
  background-color: var(--paper) !important;
  background-image: none;
  box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
}

/* ============================================================
   Marketplace — buyer storefront chrome (Mono via .landing scope)
   ============================================================ */
.mkt-header {
  background: var(--paper);
  border-bottom: 1px solid var(--border);
}
.mkt-flash {
  margin-bottom: 24px;
  padding: 12px 16px;
  border: 1px solid var(--border);
  background: var(--mist);
  color: var(--ink);
  font-size: 14px;
}
.mkt-flash--alert {
  border-color: var(--brick);
  color: var(--brick);
  background: var(--paper);
}

/* ============================================================
   Product detail page — "The Archive"
   ============================================================ */
.pdp__top {
  display: grid;
  grid-template-columns: 1fr;
  gap: 32px;
  align-items: start;
}
@media (min-width: 900px) {
  .pdp__top {
    grid-template-columns: minmax(0, 1.6fr) minmax(320px, 1fr);
    gap: 56px;
  }
  .pdp__rail { position: sticky; top: 24px; }
}

/* Gallery — the object framed as an archive accession */
.pdp-gallery { margin: 0; }
.pdp-gallery__lead {
  position: relative;
  aspect-ratio: 4 / 5;
  background: var(--mist);
  border: 1px solid var(--line);
  overflow: hidden;
}
.pdp-gallery__img { width: 100%; height: 100%; object-fit: cover; display: block; }
.pdp-gallery__placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--ui-font-sans);
  font-size: 12px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--gray);
}
.pdp-gallery__counter,
.pdp-catalog-plate {
  position: absolute;
  top: 16px;
  font-family: var(--ui-font-mono);
  font-size: 12px;
  color: var(--gray);
  z-index: 1;
}
.pdp-gallery__counter { left: 16px; }
.pdp-catalog-plate { right: 16px; }
.pdp-gallery__thumbs {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
}
.pdp-gallery__thumb {
  display: block;
  width: 100%;
  padding: 0;
  aspect-ratio: 4 / 5;
  background: var(--mist);
  border: 1px solid var(--line);
  overflow: hidden;
  cursor: pointer;
  /* Physicality: the gallery thumb presses with the one Mono spring, reduced-motion-safe. */
  transition: border-color 0.15s ease, transform var(--mono-dur-fast) var(--mono-spring);
}
.pdp-gallery__thumb[aria-current="true"] { border-color: var(--ink); }
.pdp-gallery__thumb:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
@media (prefers-reduced-motion: no-preference) {
  .pdp-gallery__thumb:active:not(:disabled) {
    transform: scale(var(--mono-press-scale));
  }
}
.pdp-gallery__thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Info rail */
.pdp__title { margin: 8px 0 0; letter-spacing: -0.015em; line-height: 1.03; }
.pdp-scarcity { font-family: var(--ui-font-sans); font-size: 13px; color: var(--gray); }

/* Condition / size / tag chips (flat Mono corners) */
.pdp-tags { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 18px; }
.pdp-tag {
  font-family: var(--ui-font-sans);
  font-size: 12px;
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: var(--radius-small);
  padding: 5px 12px;
}

.pdp__actions { margin-top: 24px; display: flex; flex-direction: column; gap: 12px; }
.pdp__cta { width: 100%; text-align: center; }

/* Provenance — first-class trust */
.pdp-provenance { margin-top: 28px; padding-top: 20px; border-top: 1px solid var(--line); }
.pdp-provenance__text { margin: 8px 0 0; font-size: 14px; line-height: 1.5; color: var(--muted); }

/* Spec block (DS4 ledger row) */
.pdp-spec { margin: 28px 0 0; padding-top: 8px; border-top: 1px solid var(--line); }
.pdp-spec__row {
  display: flex;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 0;
  border-bottom: 1px solid var(--line);
}
.pdp-spec__row dt {
  font-family: var(--ui-font-sans);
  font-size: 12px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--gray);
}
.pdp-spec__row dd { margin: 0; font-size: 14px; color: var(--ink); text-align: right; }

/* Seller — a warm credibility card in EVERY variant (grafted from the Flea direction); the
   strongest honest trust lever on a solo-seller marketplace. */
.pdp-seller {
  margin-top: 28px;
  padding: 16px 18px;
  border: 1px solid var(--line);
  border-radius: var(--radius-small);
  background: var(--paper);
  display: flex;
  align-items: center;
  gap: 14px;
}
.pdp-seller__avatar {
  width: 44px;
  height: 44px;
  border-radius: 999px;
  border: 1px solid var(--line);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--ui-font-display);
  color: var(--ink);
}
.pdp-seller__name { margin: 0; font-weight: 500; color: var(--ink); }

/* Lower full-width sections */
.pdp__section { margin-top: 56px; padding-top: 32px; border-top: 1px solid var(--line); }
.pdp__description { margin-top: 12px; max-width: 60ch; font-size: 16px; line-height: 1.7; color: var(--muted); }
.pdp-details__item { border-bottom: 1px solid var(--line); padding: 18px 0; }
.pdp-details__item summary {
  cursor: pointer;
  list-style: none;
  display: flex;
  justify-content: space-between;
  font-size: 16px;
  color: var(--ink);
}
.pdp-details__item summary::-webkit-details-marker { display: none; }
.pdp-details__item summary::after { content: "+"; font-family: var(--ui-font-mono); color: var(--gray); }
.pdp-details__item[open] summary::after { content: "–"; }
.pdp-details__item p { margin: 12px 0 0; font-size: 14px; line-height: 1.6; color: var(--muted); max-width: 60ch; }
.pdp-protection { border: 1px solid var(--line); background: var(--paper); padding: 20px 24px; }
.pdp-protection__title { margin: 0; font-weight: 500; color: var(--ink); }
.pdp-protection__text { margin: 6px 0 0; font-size: 14px; color: var(--muted); }

/* More from this shop */
.pdp-related__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  margin-bottom: 24px;
}
.pdp-related__grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
@media (min-width: 900px) {
  .pdp-related__grid { grid-template-columns: repeat(4, 1fr); }
}

/* Mobile sticky buy bar — the one action stays reachable on the long phone page */
.pdp__buybar {
  position: sticky;
  bottom: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  margin-top: 40px;
  padding: 12px 16px;
  background: var(--paper);
  border-top: 1px solid var(--line);
}
.pdp__buybar-price { font-size: 18px; }
.pdp__buybar-cta { flex: 0 0 auto; }
@media (min-width: 900px) {
  .pdp__buybar { display: none; }
}

/* ============================================================
   PDP — honest pricing ledger, seller signals, scrim, variants
   ============================================================ */

/* Gallery overlay chips get a REAL background so the counter/plate text is never gray-on-photo
   (the one contrast the page couldn't certify). Now ink-on-paper with a hairline — known ≥ floor. */
.pdp-gallery__counter,
.pdp-catalog-plate {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-small);
  padding: 4px 9px;
  color: var(--ink);
}

/* The honest 1-of-1 signal given real (but Mono) presence — an ink catalog tag, not a whisper.
   Never a fake countdown or "N viewing". */
.pdp-scarcity--inline {
  display: inline-block;
  margin-top: 14px;
  font-family: var(--ui-font-mono);
  font-size: 11px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink);
  border-top: 1px solid var(--ink);
  padding-top: 6px;
}

/* Transparent price ledger — Item + Buyer Protection + You-pay-today, with shipping stated honestly
   as calculated-at-checkout. Same numbers the checkout charges (single source on the model). */
.pdp-ledger { margin-top: 22px; }
.pdp-ledger__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 16px;
  padding: 7px 0;
}
.pdp-ledger__label { font-family: var(--ui-font-sans); font-size: 14px; color: var(--muted); }
.pdp-ledger__value { font-family: var(--ui-font-mono); font-size: 15px; color: var(--ink); }
.pdp-ledger__row .landing-money { font-size: 18px; }
.pdp-ledger__row--total {
  margin-top: 4px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}
.pdp-ledger__row--total .pdp-ledger__label { color: var(--ink); font-weight: 500; }
.pdp-ledger__row--total .landing-money { font-size: 22px; }
.pdp-ledger__note { margin: 10px 0 0; font-family: var(--ui-font-sans); font-size: 12px; line-height: 1.5; color: var(--muted); max-width: 44ch; }
.pdp-ledger__shipping { margin: 10px 0 0; font-family: var(--ui-font-sans); font-size: 13px; color: var(--gray); }

/* Disabled-CTA reason (class avoids the `pdp__cta` substring so a `scan("pdp__cta")` CTA-count
   assertion doesn't double-count this hint). */
.pdp__buy-hint { margin: 8px 0 0; font-size: 13px; color: var(--muted); }

/* Section kicker promoted to a real <h2> — strip the heading default margins */
.pdp__section-head { margin: 0; }

/* Honest seller signals line (member-since · pieces · sold) */
.pdp-seller__signals { margin: 3px 0 4px; font-family: var(--ui-font-sans); font-size: 13px; color: var(--muted); }

/* Breadcrumb — orientation (Marketplace › seller shop › item). Quiet mono row above the title. */
.pdp-breadcrumb { margin-bottom: 18px; }
.pdp-breadcrumb ol {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  font-family: var(--ui-font-sans);
  font-size: 13px;
  color: var(--muted);
}
.pdp-breadcrumb a { color: var(--muted); }
.pdp-breadcrumb a:hover { color: var(--ink); }
.pdp-breadcrumb__sep { color: var(--gray); }
.pdp-breadcrumb__current { color: var(--ink); }

/* Buyer order-confirmation END — a calm Mono peak (ink, never green) that sets expectations */
.mkt-confirm { max-width: 460px; margin: 24px auto; text-align: center; }
.mkt-confirm__mark { color: var(--ink); display: flex; justify-content: center; margin-bottom: 20px; }
.mkt-confirm__title { margin: 0; }
.mkt-confirm__lead { margin: 12px auto 0; max-width: 42ch; font-size: 15px; line-height: 1.6; color: var(--muted); }
.mkt-confirm__next { margin-top: 32px; padding: 20px 22px; border: 1px solid var(--line); background: var(--paper); text-align: left; }
.mkt-confirm__steps { list-style: none; margin: 12px 0 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.mkt-confirm__steps li { display: flex; align-items: flex-start; gap: 12px; font-size: 14px; line-height: 1.5; color: var(--text); }
.mkt-confirm__n {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  border-radius: 999px;
  border: 1px solid var(--ink);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--ui-font-mono);
  font-size: 12px;
  color: var(--ink);
}
.mkt-confirm__cta { display: inline-block; margin-top: 28px; }

/* Buyer auth (passwordless email + code) — Mono, calm, one field at a time */
.mkt-auth { max-width: 400px; margin: 24px auto; }
.mkt-auth__title { margin: 0; }
.mkt-auth__intro { margin: 10px 0 24px; font-size: 14px; line-height: 1.5; color: var(--muted); }
.mkt-auth__form { display: flex; flex-direction: column; gap: 16px; }
.mkt-field { display: flex; flex-direction: column; gap: 6px; }
.mkt-field__label { font-family: var(--ui-font-sans); font-size: 13px; color: var(--text); }
.mkt-field__input {
  width: 100%;
  padding: 10px 12px;
  font-family: var(--ui-font-sans);
  font-size: 15px;
  color: var(--ink);
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius-small);
}
.mkt-field__input:focus { outline: 2px solid var(--ink); outline-offset: 1px; }
.mkt-field__input--code { font-family: var(--ui-font-mono); letter-spacing: 0.3em; text-align: center; font-size: 22px; }
.mkt-auth__submit { width: 100%; text-align: center; margin-top: 4px; }
.mkt-auth__alt { margin-top: 18px; font-size: 14px; color: var(--muted); text-align: center; }
.mkt-auth__errors { border: 1px solid var(--brick); background: var(--paper); padding: 12px 14px; border-radius: var(--radius-small); }
.mkt-auth__errors ul { margin: 0; padding-left: 18px; }
.mkt-auth__errors li { font-size: 13px; color: var(--brick); }

/* Marketplace index pagination (kaminari) — Mono */
.marketplace-pagination { margin-top: 48px; display: flex; justify-content: center; }
.marketplace-pagination .pagination { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; font-family: var(--ui-font-mono); font-size: 13px; }
.marketplace-pagination a,
.marketplace-pagination .current {
  display: inline-block;
  padding: 6px 12px;
  border: 1px solid var(--line);
  color: var(--ink);
  text-decoration: none;
}
.marketplace-pagination .current { background: var(--mist); }
.marketplace-pagination a:hover { background: var(--mist); }

/* ============================================================
   Mono shared primitives — seeded by the design-system sweep.
   New shared classes for components whose CSS had no home yet
   (Kicker, Checkbox, Toggle, Tabs). Token-driven; the Mono values
   resolve inside .app-mono / .landing, with literal fallbacks so
   they degrade sanely on legacy pages.
   ============================================================ */

/* ----- Kicker (eyebrow): Plex Mono, 500, 12px, 0.1em, uppercase, muted ----- */
.app-kicker {
  font-family: var(--ui-font-mono, "IBM Plex Mono", ui-monospace, monospace);
  font-weight: 500;
  font-size: 12px;
  line-height: 1.2;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--subtle);
}

/* ----- Checkbox: 20px ink box, 2px radius, ink fill + white tick on :checked ----- */
.checkbox {
  appearance: none;
  -webkit-appearance: none;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  border: 1px solid var(--ink);
  border-radius: 2px;
  background: var(--white);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  transition: background 150ms ease, border-color 150ms ease;
}
.checkbox:checked {
  border-width: 0;
  background: var(--ink);
  background-image: url("data:image/svg+xml,%3Csvg width='12' height='12' viewBox='0 0 12 12' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M2.5 6.2l2.3 2.3L9.5 3.5' stroke='%23FFFFFF' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: center;
}
.checkbox:focus-visible {
  outline: none;
  border-width: 2px;
  border-color: var(--ink);
}
.checkbox:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.checkbox-label {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  font-family: var(--ui-font-sans);
  font-size: 16px;
  color: var(--ink);
  cursor: pointer;
}
.checkbox-label:has(.checkbox:disabled) {
  opacity: 0.5;
  cursor: not-allowed;
}

/* ----- Toggle: pill switch, ink ON track, white sliding knob ----- */
.ds-toggle {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  cursor: pointer;
  font-family: var(--ui-font-sans);
  font-size: 16px;
  color: var(--ink);
}
.ds-toggle:has(input:disabled) {
  opacity: 0.5;
  cursor: not-allowed;
}
.ds-toggle input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}
.ds-toggle__track {
  position: relative;
  width: 44px;
  height: 26px;
  border-radius: 9999px;
  background: var(--white);
  border: 1px solid var(--line);
  transition: background 150ms ease, border-color 150ms ease;
  flex-shrink: 0;
}
.ds-toggle__knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 20px;
  height: 20px;
  border-radius: 9999px;
  background: var(--ink);
  transition: left 150ms ease, background 150ms ease;
}
.ds-toggle input:checked + .ds-toggle__track {
  background: var(--ink);
  border-color: var(--ink);
}
.ds-toggle input:checked + .ds-toggle__track .ds-toggle__knob {
  left: 20px;
  background: var(--white);
}
.ds-toggle input:focus-visible + .ds-toggle__track {
  border-width: 2px;
}

/* ----- Tabs (navigation): flat underline bar, no pill ----- */
.ds-tabs {
  display: flex;
  gap: 28px;
  border-bottom: 1px solid var(--line);
}
.ds-tab {
  position: relative;
  padding: 0 0 14px;
  background: none;
  border: none;
  cursor: pointer;
  font-family: var(--ui-font-sans);
  font-weight: 500;
  font-size: 14px;
  color: var(--muted);
  transition: color 150ms ease;
}
.ds-tab[aria-selected="true"],
.ds-tab.is-active {
  font-weight: 600;
  color: var(--ink);
}
.ds-tab::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1px;
  height: 2px;
  background: transparent;
}
.ds-tab[aria-selected="true"]::after,
.ds-tab.is-active::after {
  background: var(--ink);
}
.ds-tab:focus-visible {
  outline: none;
  box-shadow: none;
}
.ds-tab:focus-visible::after {
  background: var(--ink);
}

/* ============================================================
   Collections — "playlist" cards, curated-edits rail, hero, page.
   Authored against canonical Mono tokens (rendered under body.landing).
   Photography is the only colour; everything else is ink on paper.
   ============================================================ */

/* Featured-edit hero (the magazine cover) */
.collection-hero {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
  margin-bottom: 44px;
}
@media (min-width: 720px) {
  .collection-hero {
    grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr);
    gap: 40px;
    align-items: center;
  }
}
.collection-hero__cover {
  position: relative;
  display: block;
  aspect-ratio: 16 / 10;
  background: var(--mist);
  border: 1px solid var(--line);
  overflow: hidden;
}
.collection-hero__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 240ms ease;
}
.collection-hero__cover:hover .collection-hero__img { transform: scale(1.03); }
.collection-hero__body { display: flex; flex-direction: column; align-items: flex-start; gap: 14px; }
.collection-hero__title { display: block; color: var(--ink); text-decoration: none; }
.collection-hero__title:hover { text-decoration: underline; text-underline-offset: 4px; }
.collection-hero__meta { margin: 0; font-family: var(--ui-font-mono); font-size: 13px; color: var(--gray); }
.collection-hero__cta { margin-top: 4px; }

/* Curated-edits rail (horizontal scroll-snap) */
.edits-rail { margin-bottom: 48px; }
.edits-rail__head { display: flex; align-items: baseline; justify-content: space-between; margin-bottom: 16px; }
.edits-rail__track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: minmax(180px, 1fr);
  gap: 20px;
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  padding-bottom: 8px;
  scrollbar-width: thin;
}
@media (min-width: 720px) {
  .edits-rail__track { grid-auto-columns: minmax(220px, 1fr); }
}
.edits-rail__track > * { scroll-snap-align: start; }

/* Collection card (the "playlist" tile) */
.collection-card { display: flex; flex-direction: column; text-decoration: none; }
.collection-card__cover {
  position: relative;
  aspect-ratio: 4 / 5;
  background: var(--mist);
  border: 1px solid var(--line);
  overflow: hidden;
}
.collection-card__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  transition: transform 200ms ease;
}
.collection-card:hover .collection-card__img { transform: scale(1.04); }
.collection-card__placeholder {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--ui-font-sans);
  font-size: 11px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--gray);
}
.collection-card__title {
  margin: 12px 0 0;
  font-family: var(--ui-font-display);
  font-size: 18px;
  line-height: 1.2;
  color: var(--ink);
}
.collection-card__meta { margin: 4px 0 0; font-family: var(--ui-font-mono); font-size: 12px; color: var(--gray); }

/* Collection page (the "mini popup shop") */
.collection-page { margin-bottom: 40px; }
.collection-page__head { margin-bottom: 8px; }
.collection-page__headrow { display: flex; align-items: baseline; justify-content: space-between; gap: 16px; margin-top: 8px; }
.collection-page__title { margin: 0; color: var(--ink); }
.collection-page__share { background: none; border: 0; padding: 0; font: inherit; cursor: pointer; }
.collection-page__meta { margin: 12px 0 0; font-family: var(--ui-font-mono); font-size: 13px; color: var(--gray); }

/* ============================================================
   Mono Motion (mirrored from the design system's handoff/mono-motion +
   handoff/activation-journey → tokens/motion.css). "One spring, five tiers,
   used sparingly, always functional." Namespaced (--mono-* / .mono-*) so it
   coexists with Tailwind; lives here in ui.css (plain Propshaft CSS, already
   loaded app-wide after :app) rather than a separate @import.

   Rules baked in: the visible end-state IS the base style — animations play
   FROM hidden and ONLY under (prefers-reduced-motion: no-preference), so print,
   PDF, and reduced-motion always land on content (never a stuck opacity:0).
   Affirmations run ONCE. Every animating figure still renders its real value
   (JS counts toward it; CSS never hides it). Transform/opacity only.
   ============================================================ */
:root {
  --mono-ease:         cubic-bezier(0.2, 0.8, 0.2, 1);    /* standard quiet ease-out */
  --mono-ease-emph:    cubic-bezier(0.2, 0.9, 0.1, 1);    /* decisive reveal */
  --mono-ease-exit:    cubic-bezier(0.4, 0, 1, 1);        /* dismiss */
  --mono-spring:       cubic-bezier(0.34, 1.42, 0.5, 1);  /* the one overshoot spring */
  --mono-spring-soft:  cubic-bezier(0.34, 1.26, 0.46, 1); /* gentler, for larger surfaces */

  /* Response / reveal / transition */
  --mono-dur-tap:    90ms;   /* tier-0 immediate tap (below --mono-dur-fast) */
  --mono-dur-fast:   120ms;
  --mono-dur-hover:  140ms;
  --mono-dur:        160ms;
  --mono-dur-flash:  200ms;
  --mono-dur-enter:  220ms;
  --mono-dur-settle: 240ms;  /* a figure's settle as a count-up lands */
  --mono-dur-screen: 280ms;
  --mono-dur-sheet:  300ms;
  --mono-dur-spring: 320ms;  /* the success ring stamp (= --mono-dur-ring) */
  --mono-dur-coda:   360ms;  /* an END coda screen settling at the exhale */
  --mono-dur-draw:   560ms;
  --mono-stagger-step: 42ms;

  /* Count-up speeds — the felt money figure */
  --mono-dur-count:      1000ms; /* dashboard money tiles */
  --mono-dur-count-peak: 1400ms; /* first-sale / payout peak figure */

  /* Micro shared values */
  --mono-press-scale: 0.97;
  --mono-hover-lift:  -3px;
  --mono-nudge-from:  2px;
  --mono-overscroll-max: 96px; /* rubber-band: max damped pull past a scroll boundary */
}

/* Physicality — physical-correctness: contain overscroll so a bounded scroller (modal,
   sheet, long list) never chains its scroll to the page or fires an accidental
   pull-to-refresh. Not an animation, so it lives outside the reduced-motion gate.
   The elastic rubber-band on top of this is the overscroll_controller (transform-only). */
.mono-overscroll { overscroll-behavior: contain; }

@keyframes mono-rise        { from { opacity:0; transform:translateY(8px); }  to { opacity:1; transform:none; } }
@keyframes mono-fade        { from { opacity:0; }                              to { opacity:1; } }
@keyframes mono-sheet-up    { from { opacity:0; transform:translateY(16px); } to { opacity:1; transform:none; } }
@keyframes mono-screen-push { from { opacity:0; transform:translateX(24px); } to { opacity:1; transform:none; } }
@keyframes mono-screen-pop  { from { opacity:0; transform:translateX(-24px);} to { opacity:1; transform:none; } }
@keyframes mono-stamp       { from { opacity:0; transform:scale(0.6); }       to { opacity:1; transform:scale(1); } }
@keyframes mono-tick        { from { transform:scale(0); } 70% { transform:scale(1.18); } to { transform:scale(1); } }
@keyframes mono-pulse-once  { 0% { transform:scale(1); } 40% { transform:scale(1.06); } 100% { transform:scale(1); } }
@keyframes mono-draw        { from { stroke-dashoffset: var(--draw-len,1000); } to { stroke-dashoffset:0; } }
/* tier-0 micro */
@keyframes mono-nudge       { from { opacity:0; transform:translateY(var(--mono-nudge-from)); } to { opacity:1; transform:none; } }
@keyframes mono-flash       { from { color: var(--gray, #6B6B66); } to { color: var(--ink, #0E0E0C); } }
@keyframes mono-settle      { from { transform:scale(1.04); } to { transform:scale(1); } }

/* Success checkmark — mirrored VERBATIM from the Brechó Brand Book Figma Motion (node 378:7):
   the ink ring does a damped ball-drop bounce, then the ink tick draws on via path-trim. Figma
   exports this as a 2.2s infinite loop; the .mono-check__* classes below play it ONCE then hold,
   per the house rule above ("Affirmations run ONCE"). Stops / easings / values are unchanged from
   the export — only Figma's `translate:` shorthand is written as `transform: translateY()` to match
   the codebase idiom. The translateY px resolve in the SVG's 84-unit space, so the bounce scales
   with the rendered size. mono-check-draw needs pathLength="1" on the path (set in the component).

   SANCTIONED EXCEPTION (do not "simplify" to a single overshoot): the multi-stage ball-drop is a
   LOUDER motion than the system's "one overshoot spring" affirmations (mono-tick/stamp). It is the
   brand book's own checkmark, adopted deliberately and approved to ship as-is — the design source
   wins here. If you're tempted to flatten it to mono-stamp/mono-tick, that's a brand-owner decision,
   not a cleanup. */
@keyframes mono-check-ring {
  0%      { animation-timing-function: cubic-bezier(0,0,0.15,1); transform: translateY(0); }
  6.818%  { animation-timing-function: cubic-bezier(0.55,0,1,1); transform: translateY(-28px); }
  15.909% { animation-timing-function: cubic-bezier(0,0,0.15,1); transform: translateY(0); }
  22.727% { animation-timing-function: cubic-bezier(0.55,0,1,1); transform: translateY(-13px); }
  29.545% { animation-timing-function: cubic-bezier(0,0,0.15,1); transform: translateY(0); }
  34.091% { animation-timing-function: ease-out;                 transform: translateY(-5px); }
  38.636% { transform: translateY(0); }
  100%    { transform: translateY(0); }
}
@keyframes mono-check-draw {
  0%      { stroke-dasharray: 0 1; visibility: hidden; }
  45.455% { animation-timing-function: ease-in-out; stroke-dasharray: 0 1; visibility: hidden; }
  86.364% { stroke-dasharray: 1 1; visibility: visible; }
  100%    { stroke-dasharray: 1 1; visibility: visible; }
}

/* Headroom for the bounce. The ring (r=40.75 @ cx/cy 42) sits flush to the 84 viewBox, so the
   default SVG `overflow:hidden` would slice its top into a flat-cut "U" while it hops up — reading
   as a glitch for the ~1s before the tick draws. overflow:visible lets the WHOLE ring bounce inside
   the host's padding (modal p-8 / coda pt-12 absorb the ~19–21px rise at 56/64px). Not a motion
   value, so it lives outside the reduced-motion gate. */
.mono-check { overflow: visible; }

@media (prefers-reduced-motion: no-preference) {
  /* 0 · MICRO */
  .mono-tap { transition: transform var(--mono-dur-tap) var(--mono-spring), background var(--mono-dur-hover) var(--mono-ease), border-color var(--mono-dur-hover) var(--mono-ease); }
  .mono-tap:active { transform: scale(var(--mono-press-scale)); }
  .mono-hover { transition: color var(--mono-dur-hover) var(--mono-ease), background var(--mono-dur-hover) var(--mono-ease), border-color var(--mono-dur-hover) var(--mono-ease); }
  .mono-underline { position: relative; }
  .mono-underline::after { content:""; position:absolute; left:0; right:0; bottom:-2px; height:1px; background:currentColor; transform:scaleX(0); transform-origin:left; transition: transform var(--mono-dur-hover) var(--mono-ease-emph); }
  .mono-underline:hover::after { transform: scaleX(1); }
  .mono-nudge  { animation: mono-nudge var(--mono-dur-fast) var(--mono-spring) both; }
  .mono-flash  { animation: mono-flash var(--mono-dur-flash) var(--mono-ease) 1; }
  /* Physicality — money settles with weight, never bounces: --mono-ease-emph decelerates
     into rest without dipping below 1.0 (the spring would overshoot under target). Resolves
     the count-up overshoot question — on numbers, a bounce reads as untrustworthy. Spring
     overshoot stays for non-money affirmations (.mono-tick / .mono-stamp / .mono-press). */
  .mono-settle { animation: mono-settle var(--mono-dur-settle) var(--mono-ease-emph) 1; }
  .mono-coda   { animation: mono-rise var(--mono-dur-coda) var(--mono-spring-soft) both; }

  /* 1 · RESPONSE */
  .mono-press { transition: transform var(--mono-dur-fast) var(--mono-spring); }
  .mono-press:active { transform: scale(var(--mono-press-scale)); }
  .mono-hover-lift { transition: transform var(--mono-dur) var(--mono-spring), box-shadow var(--mono-dur) var(--mono-ease); }
  .mono-hover-lift:hover { transform: translateY(var(--mono-hover-lift)); box-shadow: var(--shadow-card, 0 8px 24px -12px rgba(14,14,12,.16)); }

  /* 2 · REVEAL */
  .mono-rise { animation: mono-rise var(--mono-dur-enter) var(--mono-ease-emph) both; }
  .mono-fade { animation: mono-fade var(--mono-dur) var(--mono-ease) both; }
  .mono-stagger > * { animation: mono-rise var(--mono-dur-enter) var(--mono-ease-emph) both; }
  .mono-stagger > *:nth-child(1){animation-delay:0ms}
  .mono-stagger > *:nth-child(2){animation-delay:calc(var(--mono-stagger-step)*1)}
  .mono-stagger > *:nth-child(3){animation-delay:calc(var(--mono-stagger-step)*2)}
  .mono-stagger > *:nth-child(4){animation-delay:calc(var(--mono-stagger-step)*3)}
  .mono-stagger > *:nth-child(5){animation-delay:calc(var(--mono-stagger-step)*4)}
  .mono-stagger > *:nth-child(6){animation-delay:calc(var(--mono-stagger-step)*5)}
  .mono-stagger > *:nth-child(7){animation-delay:calc(var(--mono-stagger-step)*6)}
  .mono-stagger > *:nth-child(8){animation-delay:calc(var(--mono-stagger-step)*7)}
  .mono-stagger > *:nth-child(n+9){animation-delay:calc(var(--mono-stagger-step)*8)}

  /* 3 · TRANSITION */
  .mono-sheet       { animation: mono-sheet-up var(--mono-dur-sheet) var(--mono-spring-soft) both; }
  .mono-screen-push { animation: mono-screen-push var(--mono-dur-screen) var(--mono-ease-emph) both; }
  .mono-screen-pop  { animation: mono-screen-pop var(--mono-dur-screen) var(--mono-ease-emph) both; }

  /* 4 · AFFIRMATION (runs once) */
  .mono-stamp { animation: mono-stamp var(--mono-dur-spring) var(--mono-spring) both; }
  .mono-tick  { animation: mono-tick var(--mono-dur-spring) var(--mono-spring) both; }
  .mono-pulse { animation: mono-pulse-once 240ms var(--mono-spring) 1; }
  .mono-draw  { animation: mono-draw var(--mono-dur-draw) var(--mono-ease-emph) both; }

  /* The Figma success checkmark, played ONCE on entry (2.2s master clock is linear; the
     per-segment easing lives in the keyframes). Ring bounces, then tick draws; `both` holds the
     finished mark at rest. The resting (un-animated) path has no dasharray, so the BASE style is a
     complete tick — that's what reduced-motion lands on instantly (the reduce block neutralizes the
     animation) and what every paint holds after one 2.2s play. (No-JS still runs this CSS animation;
     a print fired mid-play is covered by the @media print reset below.) */
  .mono-check__ring { animation: mono-check-ring 2.2s linear 1 both; }
  .mono-check__draw { animation: mono-check-draw 2.2s linear 1 both; }
}

/* Print / PDF: snapshot the FINISHED mark, never a mid-draw frame — receipts and payout records
   must show the tick even if printed during the 2.2s play. */
@media print {
  .mono-check__ring,.mono-check__draw{ animation:none!important; visibility:visible!important; transform:none!important; }
}

@media (prefers-reduced-motion: reduce) {
  .mono-press,.mono-hover-lift,.mono-rise,.mono-fade,.mono-stagger>*,
  .mono-sheet,.mono-screen-push,.mono-screen-pop,
  .mono-stamp,.mono-tick,.mono-pulse,.mono-draw,
  .mono-check__ring,.mono-check__draw,
  .mono-tap,.mono-hover,.mono-nudge,.mono-flash,.mono-settle,.mono-coda{
    animation:none!important;transition:none!important;transform:none!important;
    opacity:1!important;stroke-dashoffset:0!important;visibility:visible!important;
  }
  .mono-underline::after{ transition:none!important; transform:scaleX(1)!important; }
}

/* ============================================================
   View Transitions (Turbo 8) — the signature shared-element morph between the marketplace
   grid and the PDP. Opted in per-page via <meta name="view-transition" content="same-origin">
   on browse index + show ONLY, so live turbo_stream refreshes elsewhere never crossfade.
   Turbo skips the whole transition under prefers-reduced-motion (so this block is moot then).
   Tuned to the Mono screen tier so the carry feels like the rest of the system, not a UA default.
   ============================================================ */
@media (prefers-reduced-motion: no-preference) {
  ::view-transition-group(*) {
    animation-duration: var(--mono-dur-screen);
    animation-timing-function: var(--mono-ease-emph);
  }
}
