/* Vantage Wholesale -- design system foundation.
 *
 * Ported from the prototype's <style> block (docs/vantage-portal.html
 * lines 17-449, foundation only). Page-specific patterns (cards,
 * tables, modals, marketplace, kanban, etc.) land in the commits that
 * port the views that use them, so we don't ship CSS we don't use yet.
 *
 * Brand palette: navy #112A36, mint #6FC6A0, light wash #DBEEE0,
 * deep accent #1F534B. Gold is reserved for Priority-tier value-adds
 * only -- never general CTAs.
 */

/* ---------- Variables ---------- */

:root {
  /* Brand palette -- exact values from the Vantage Wholesale brand
     guideline (2026) + vantagewholesale.co.uk:
       #1C2D35 ink/navy · #75C69E mint · #2C584F deep · #FCFCFC off-white
       #D9EDE0 light-mint · #5D6F6A muted. The -2/-3 shades are
       harmonised tints/shades of those bases. */
  --navy: #1c2d35;      /* brand ink -- sidebar + dark surfaces */
  --navy-2: #24383f;    /* lifted navy (hover/rows on dark) */
  --navy-3: #2f4550;    /* navy borders on dark */
  --mint: #75c69e;      /* brand mint -- primary accent */
  --mint-2: #5faf88;    /* darker mint (hover/active) */
  --mint-3: #a7dcc2;    /* lighter mint */
  --light: #d9ede0;     /* brand light-mint -- tint surfaces */
  --light-2: #eaf5ee;   /* lighter tint */
  --deep: #2c584f;      /* brand deep green */
  --deep-2: #21443d;    /* darker deep green */
  --white: #fcfcfc;     /* brand off-white */
  --ink: #1c2d35;       /* brand ink -- body text */
  --ink-2: #3a4a53;     /* secondary text */
  --muted: #5d6f6a;     /* brand muted grey-green */
  --line: #e2ebee;      /* light borders */
  --line-2: #cfdde1;
  --danger: #d85858;
  --warn: #e2a73b;
  --ok: #2f9e6c;

  /* Priority tier accent -- reserved exclusively for Priority-tier
     value-adds (chips, badges, exclusive-deal markers, upgrade CTAs).
     Never use for general CTAs; the colour earns its meaning by scarcity. */
  --gold:        #d4af5a;
  --gold-bright: #e8c87a;
  --gold-deep:   #8a6520;
  --gold-soft:   rgba(212, 175, 90, 0.10);
  --gold-ink:    #1a1408;

  /* Premium "modern fintech" radius scale (2026-07-29, H-approved):
     tight 6px on containers/cards, 8px on the few large surfaces,
     4px on chips/controls. Was a soft 12–16px. */
  --radius:    6px;
  --radius-sm: 4px;
  /* Card elevation now comes from a hairline border, not a glow; the
     shadow tokens survive for TRUE overlays (modals, dropdowns). */
  --shadow-sm: 0 1px 2px rgba(28, 45, 53, 0.05);
  --shadow-md: 0 8px 24px rgba(28, 45, 53, 0.08);
  --shadow-lg: 0 18px 40px rgba(28, 45, 53, 0.14);
  /* Body = Jost, display/headings/logo = Syne (brand typefaces). */
  --font: "Jost", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-display: "Syne", "Jost", -apple-system, sans-serif;
}

/* ---- Theming (2026-07-28, client-requested light + dark) -----------
   Surfaces are the one thing that inverts between themes, so they get
   dedicated SEMANTIC tokens rather than piggy-backing on brand colours.
   Brand tokens (--navy/--deep/--white/--mint/--gold) keep fixed
   meaning in BOTH themes -- the sidebar stays navy, mint stays mint.
   Only foreground/border/surface roles flip. The effective theme is
   set as data-theme on <html> before first paint (see the no-flash
   script in the layout head), so there is never a light-then-dark
   flash. Default follows the OS via that script; a manual toggle wins. */
:root {
  color-scheme: light;
  --bg:        #fcfcfc;   /* page background (was body's var(--white)) */
  --surface:   #ffffff;   /* cards, panels, modals, dropdowns */
  --surface-2: #f4f8f6;   /* subtly raised / striped rows on a card */
}

:root[data-theme="dark"] {
  color-scheme: dark;
  /* Navy-family dark ramp -- brand cohesion over flat neutral grey.
     bg (darkest) < surface (cards) < surface-2 (raised); the
     always-navy sidebar reads as another surface alongside them. */
  --bg:        #0e171c;
  --surface:   #182831;
  --surface-2: #21343d;

  /* Foreground + borders flip (these tokens are text/border only). */
  --ink:    #e9eef0;   /* body text */
  --ink-2:  #b4c3c9;   /* secondary text */
  --muted:  #8ba0a6;   /* muted grey-green */
  --line:   #2b3d45;   /* borders + subtle divider fills */
  --line-2: #35474f;

  /* Tint surfaces darken so they don't glow as light patches. */
  --light:   #21343d;
  --light-2: #1c2c34;
  --gold-soft: rgba(212, 175, 90, 0.14);

  /* Shadows are near-invisible on dark; deepen them and lean on
     borders for card separation. */
  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.35);
  --shadow-md: 0 6px 18px rgba(0, 0, 0, 0.40);
  --shadow-lg: 0 18px 40px rgba(0, 0, 0, 0.50);
}

* { box-sizing: border-box; }
html, body { height: 100%; }

body {
  margin: 0;
  font-family: var(--font);
  color: var(--ink);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  font-size: 14px;
  line-height: 1.45;
}

a { color: var(--deep); }
a:hover { color: var(--navy); }
h1, h2, h3, h4 { color: var(--ink); }
/* Syne (display) on the top-level page headings + the brand wordmark;
   body / dense UI text stays on Jost for legibility. */
h1, h2 { font-family: var(--font-display); letter-spacing: -0.015em; }
.brand-name-1, .brand-name-2, .brand-mark { font-family: var(--font-display); }

/* ---------- Layout: signed-in shell ---------- */

#app {
  display: grid;
  grid-template-columns: 248px 1fr;
  min-height: 100vh;
}

.sidebar {
  background: var(--navy);
  color: #cfdde1;
  display: flex;
  flex-direction: column;
  padding: 16px 12px;
  border-right: 1px solid rgba(255, 255, 255, .06);
  position: sticky; top: 0; height: 100vh;
}
.sidebar .brand {
  border-bottom-color: rgba(255, 255, 255, .07);
  padding: 6px 8px 16px;
  margin-bottom: 6px;
}

.brand {
  display: flex; align-items: center; gap: 12px;
  padding: 4px 6px 20px;
  border-bottom: 1px solid var(--navy-3);
  margin-bottom: 16px;
  text-decoration: none;
}
.brand-mark {
  width: 36px; height: 36px;
  border-radius: 6px;
  flex-shrink: 0;
  display: block;
}
.brand-text { display: flex; flex-direction: column; line-height: 1.05; }

/* Brand text colours default to the LIGHT surface treatment (anon
   card, future marketing pages). The sidebar inverts these for its
   navy background. Choosing "light surface" as the default rather
   than the historical "dark surface" because the first page every
   new user sees -- sign-in -- is on a light surface. */
.brand-name-1 { color: var(--navy); font-weight: 700; font-size: 15px; }
.brand-name-2 { color: var(--deep); font-weight: 700; font-size: 15px; }

.sidebar .brand-name-1 { color: #fff; }
.sidebar .brand-name-2 { color: var(--mint); }

.nav {
  display: flex; flex-direction: column; gap: 2px;
  /* The sidebar is height:100vh; the footer must stay visible no
     matter how many nav items render. Letting the nav scroll
     internally pins the footer at the bottom. min-height:0 lets
     the flex shrink-to-fit work inside the column-flex sidebar. */
  overflow-y: auto;
  min-height: 0;
  flex: 1 1 auto;
  scrollbar-width: thin;
  scrollbar-color: var(--navy-3) transparent;
}
.nav::-webkit-scrollbar { width: 6px; }
.nav::-webkit-scrollbar-track { background: transparent; }
.nav::-webkit-scrollbar-thumb { background: var(--navy-3); border-radius: 3px; }

/* Tiny uppercase group heading between nav clusters. */
.nav-section {
  font-size: 10px; font-weight: 600; letter-spacing: 1.3px;
  text-transform: uppercase; color: #6f858d;
  padding: 0 10px; margin: 15px 0 6px;
}
.nav-section:first-child { margin-top: 2px; }

.nav-item {
  display: flex; align-items: center; gap: 11px;
  padding: 8px 10px; border-radius: 8px;
  color: #a4b8bf;
  font-size: 13.5px; font-weight: 500;
  text-decoration: none;
  user-select: none;
  transition: background .12s ease, color .12s ease;
}
.nav-item .ico { color: #7f97a0; flex-shrink: 0; transition: color .12s ease; }
.nav-item .nav-label { flex: 1; min-width: 0; }
.nav-item:hover        { background: rgba(255, 255, 255, .055); color: #fff; }
.nav-item:hover .ico   { color: #cfe4d8; }
.nav-item.active       { background: rgba(117, 198, 158, .13); color: #fff; }
.nav-item.active .ico  { color: var(--mint); }

/* Sidebar attention badge (3.6.h.3). Mint pill at the right edge
 * of the nav item carrying the per-view count from
 * AttentionCalculator. Mirrors the prototype's .nav-badge
 * (docs/vantage-portal.html:1368-1379). Borderless so it visually
 * sits inside the nav-item row without competing with the active-
 * state background fill. */
.nav-badge {
  margin-left: auto;
  background: var(--mint);
  color: var(--navy);
  font-size: 10.5px;
  font-weight: 700;
  padding: 2px 7px;
  border-radius: 999px;
  min-width: 18px;
  text-align: center;
  line-height: 1.4;
}

/* Foot-of-sidebar account menu (initials avatar + name/role, dropdown). */
.side-account {
  margin-top: auto;
  padding-top: 12px;
  border-top: 1px solid rgba(255, 255, 255, .07);
  position: relative;
}
.side-account-trigger {
  display: flex; align-items: center; gap: 10px; width: 100%;
  background: transparent; border: 0; cursor: pointer;
  padding: 7px; border-radius: 6px; text-align: left;
  color: inherit; font-family: inherit;
  transition: background .12s ease;
}
.side-account-trigger:hover { background: rgba(255, 255, 255, .05); }
.side-avatar {
  width: 32px; height: 32px; border-radius: 9px; flex-shrink: 0;
  background: rgba(117, 198, 158, .16); color: var(--mint);
  display: grid; place-items: center;
  font-family: var(--font-display); font-weight: 700; font-size: 12.5px;
}
.side-account-meta { display: flex; flex-direction: column; line-height: 1.25; min-width: 0; flex: 1; }
.side-account-name { color: #fff; font-size: 13px; font-weight: 600;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.side-account-role { color: #8ea3ab; font-size: 11px; }
.side-account-chev { color: #8ea3ab; display: grid; place-items: center;
  flex-shrink: 0; transition: transform .18s ease; }
.side-account.open .side-account-chev { transform: rotate(180deg); }

.side-account-menu {
  position: absolute; bottom: calc(100% + 8px); left: 4px; right: 4px;
  /* Dark glass (console language over the navy sidebar -- a light
     tray would fight the light-on-dark item text). */
  background: linear-gradient(180deg, rgba(47, 69, 80, 0.92), rgba(36, 56, 63, 0.86));
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  backdrop-filter: blur(14px) saturate(1.3);
  border: 1px solid var(--navy-3);
  border-radius: 6px; padding: 6px;
  box-shadow: 0 14px 36px rgba(0, 0, 0, .4);
  animation: tray-drop 0.3s var(--ease-out-expo);
}
.side-account-menu::before {
  content: "";
  position: absolute;
  top: -1px; left: 16px; right: 16px;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--mint), transparent);
  opacity: 0.35;
}
@supports not (backdrop-filter: blur(1px)) {
  .side-account-menu { background: var(--navy-2); }
}
.side-account-menu-email {
  font-size: 11px; color: #8ea3ab; padding: 6px 8px 8px;
  border-bottom: 1px solid var(--navy-3); margin-bottom: 4px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.side-account-menu .button_to { margin: 0; }
.side-account-signout {
  display: flex; align-items: center; gap: 9px; width: 100%;
  background: transparent; border: 0; cursor: pointer;
  color: #cfdde1; font-size: 13px; font-weight: 500; font-family: inherit;
  padding: 8px; border-radius: 7px; text-align: left;
}
.side-account-signout:hover { background: rgba(255, 255, 255, .06); color: #fff; }

/* Plain block flow rather than flex column. Flex on the grid item was
   creating an implicit formatting context that prevented .topbar's
   position: sticky from anchoring to the viewport on scroll. With block
   flow, .topbar + .view-root stack naturally and sticky behaves. */
.main { min-width: 0; }

.topbar {
  display: flex; align-items: center; justify-content: space-between;
  padding: 22px 28px 14px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 20;
}
.topbar h1 {
  margin: 0;
  font-size: 22px;
  letter-spacing: -0.2px;
  color: var(--ink);
}
.topbar .subtitle { margin: 2px 0 0; color: var(--muted); font-size: 13px; }
.topbar-right { display: flex; gap: 10px; position: relative; align-items: center; }

/* ---------- Notification bell + panel (3.6.h.11) ----------
 * Aligned to the prototype's bell + panel CSS
 * (docs/vantage-portal.html:2452-2592). The bell IS the button
 * (34x34 square, 8px radius, mint-tint hover, mint border accent
 * when there's unread). The dot top-right carries the count. Panel
 * 380px wide with prototype-shaped head, body, and per-row layout
 * (icon column + body, with an unread ::before dot in mint). */

.notification-bell-wrap {
  position: relative;
}

.notification-bell {
  position: relative;
  width: 34px;
  height: 34px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  display: grid;
  place-items: center;
  cursor: pointer;
  transition: border-color 120ms ease, background 120ms ease;
}
.notification-bell:hover { border-color: var(--mint-3); background: var(--light); }
.notification-bell.has-unread { border-color: var(--mint); }

.notification-dot {
  position: absolute;
  top: 4px;
  right: 4px;
  min-width: 16px;
  height: 16px;
  border-radius: 999px;
  background: #c25555;
  color: #fff;
  font-size: 10px;
  font-weight: 700;
  display: grid;
  place-items: center;
  padding: 0 4px;
  line-height: 1;
  box-shadow: 0 0 0 2px #fff;
}
.notification-dot[hidden] { display: none; }

.notification-panel {
  position: absolute;
  top: 44px;
  right: 0;
  width: min(380px, 95vw);
  max-height: 70vh;
  /* Glass tray (console language). Animation fires on OPEN (the
     [hidden] toggle re-inserts it into rendering); turbo-stream
     prepends land INSIDE the body and never re-trigger it. */
  background: linear-gradient(180deg, rgba(252, 252, 252, 0.94), rgba(234, 245, 238, 0.78));
  -webkit-backdrop-filter: blur(14px) saturate(1.4);
  backdrop-filter: blur(14px) saturate(1.4);
  border: 1px solid rgba(226, 235, 238, 0.9);
  border-radius: 8px;
  box-shadow: 0 18px 40px -14px rgba(28, 45, 53, 0.25);
  z-index: 100;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: tray-drop 0.3s var(--ease-out-expo);
}
@supports not (backdrop-filter: blur(1px)) {
  .notification-panel { background: var(--surface); }
}
.notification-panel[hidden] { display: none; }
.notification-panel-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
}
.notification-panel-head h3 {
  margin: 0;
  font-size: 14px;
  font-weight: 700;
  color: var(--ink);
}
.notification-panel-mark-all {
  border: none;
  background: transparent;
  color: var(--deep);
  font-size: 11.5px;
  font-weight: 600;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: 4px;
}
.notification-panel-mark-all:hover { background: var(--light); }
.notification-panel-mark-all:disabled { color: var(--muted); cursor: default; }
.notification-panel-mark-all:disabled:hover { background: transparent; }

.notification-panel-body {
  flex: 1;
  overflow-y: auto;
}
.notification-empty {
  padding: 36px 20px;
  text-align: center;
  color: var(--muted);
  font-size: 13px;
}
.notification-empty-icon {
  font-size: 28px;
  margin-bottom: 6px;
  opacity: 0.5;
  color: var(--deep);
  font-weight: 300;
}

.notification-list { display: flex; flex-direction: column; }
.notification-row {
  display: flex;
  gap: 10px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--line);
  cursor: pointer;
  transition: background 80ms ease;
  position: relative;
}
.notification-row:hover { background: var(--light-2); }
.notification-row:last-child { border-bottom: 0; }
.notification-row.unread { background: var(--light); }
.notification-row.unread::before {
  content: "";
  position: absolute;
  left: 6px;
  top: 18px;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--mint);
}
.notification-row.unread .notification-row-title {
  color: var(--ink);
  font-weight: 700;
}

.notification-row-icon {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  border-radius: 50%;
  background: var(--light);
  display: grid;
  place-items: center;
  font-size: 13px;
  color: var(--deep);
  margin-top: 2px;
}
.notification-row-body {
  flex: 1;
  min-width: 0;
}
.notification-row-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.35;
}
.notification-row-summary {
  font-size: 11.5px;
  color: var(--muted);
  margin-top: 3px;
  line-height: 1.4;
  overflow: hidden;
  text-overflow: ellipsis;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}
.notification-row-when {
  font-size: 10.5px;
  color: var(--muted);
  margin-top: 4px;
  font-weight: 500;
}

.view-root { padding: 24px 28px 48px; }

/* 3.7.g -- the old plain .client-tier-banner strip was retired
 * in favour of the gradient .lock-banner rendered by
 * shared/_client_tier_banner. Classes deleted; the partial still
 * exists with the new lock-banner markup. */

/* ---------- Layout: anonymous / Devise pages ---------- */

/* Centred-card layout used by sign-in, OTP step, password reset,
   invitation accept. Replaces the sidebar+topbar shell when there's
   no signed-in user. */
.anon-shell {
  min-height: 100vh;
  display: grid;
  grid-template-columns: 1.05fr 0.95fr;
  background: var(--light-2);
}
/* Left column: navy brand hero with the animated supply-network graphic.
 * Pinned: sticky at exactly one viewport tall, so the hero renders
 * identically on every anon page (sign-in, register, reset) and stays
 * put while the right column grows or scrolls -- a VAT toggle or a
 * long form must never reflow the brand side. */
.anon-hero {
  background: var(--navy);
  color: #fff;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: 30px;
  padding: 56px clamp(36px, 5vw, 76px);
  overflow: hidden;
  position: sticky;
  top: 0;
  align-self: start;
  height: 100vh;
  height: 100dvh;
  box-sizing: border-box;
}
.anon-hero-eyebrow {
  font-family: var(--font-display);
  text-transform: uppercase;
  letter-spacing: 2.5px;
  font-size: 11px;
  font-weight: 700;
  color: var(--mint);
}
.anon-hero h2 {
  color: #fff;
  font-size: clamp(28px, 3.2vw, 40px);
  line-height: 1.06;
  letter-spacing: -0.02em;
  margin: 12px 0 14px;
}
.anon-hero h2 .accent { color: var(--mint); }
.anon-hero-sub { color: #aebfc4; font-size: 15px; line-height: 1.55; max-width: 400px; }
.anon-card-col { display: grid; place-items: center; padding: 32px 20px; }

/* Below 900px: drop the hero, centre the card (mobile / narrow). */
@media (max-width: 900px) {
  .anon-shell { grid-template-columns: 1fr; }
  .anon-hero { display: none; }
  .anon-card-col { min-height: 100vh; }
}
.anon-card {
  width: 100%;
  max-width: 420px;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 28px 28px 24px;
  box-shadow: var(--shadow-md);
}
/* Wider variant for the multi-section register form. Driven by a
 * marker INSIDE the Turbo Frame (:has), so the width follows the
 * content across in-frame navigation; a class on the card itself
 * would go stale when only the frame swaps. */
.anon-card:has(.anon-card-wide-marker) { max-width: 620px; }
.anon-card h1 {
  margin: 0 0 6px;
  font-size: 22px;
  letter-spacing: -0.3px;
}
.anon-card p {
  color: var(--ink-2);
  font-size: 13.5px;
  margin: 0 0 16px;
}
.anon-card .brand {
  border-bottom: 0;
  padding: 0 0 20px;
  margin-bottom: 0;
}
.anon-card .brand-mark { background: var(--navy); color: #fff; }

/* Action row beneath an .anon-card form -- single button right now,
   the .actions class lets us add a Cancel beside it later without
   re-templating. */
.anon-actions {
  display: flex;
  gap: 10px;
  margin-top: 18px;
}
.anon-actions .btn { flex: 1 0 auto; }

/* Inline checkbox row used for "remember me" type options inside an
   .anon-card. Smaller than a full .field; sits between the last
   field and the submit row. */
.anon-inline-checkbox {
  display: flex;
  align-items: center;
  gap: 8px;
  font-size: 12.5px;
  color: var(--ink-2);
  margin: 4px 0 0;
  cursor: pointer;
}

/* Cross-page links beneath the anon-card form (Forgot password / Sign in).
   Sits below the form; muted by default, deep on hover. */
.anon-links {
  margin-top: 18px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  display: flex;
  gap: 14px;
  flex-wrap: wrap;
  font-size: 12.5px;
}
.anon-links a {
  color: var(--muted);
  text-decoration: none;
  font-weight: 500;
}
.anon-links a:hover { color: var(--deep); text-decoration: underline; }
/* "Create your account" row under the sign-in links. */
.anon-register-cta span { color: var(--muted); }

/* Small muted line beneath a .field input, e.g. "We've pre-filled
   this from your email; please change it." Distinct from per-field
   error text (.field-error) which is danger-coloured. */
.field-help {
  font-size: 11.5px;
  margin-top: 4px;
  line-height: 1.45;
}

/* ---------- 2FA enrolment + backup codes (staff onboarding) -------
 *
 * The 2FA enrolment page shows a QR code beside a "can't scan?"
 * fallback with the secret text. Two-column layout on wide
 * viewports; stacks below 600px. Backup-codes page renders the ten
 * one-time codes in a 2-col monospace grid for legibility / paste-
 * friendliness. */

.otp-enrol-grid {
  display: grid;
  grid-template-columns: minmax(180px, 220px) 1fr;
  gap: 24px;
  align-items: start;
  margin: 8px 0 16px;
}
.otp-enrol-qr svg { width: 100%; height: auto; max-width: 220px; }
.otp-enrol-secret-code {
  display: inline-block;
  padding: 6px 10px;
  border-radius: 6px;
  background: var(--light-2);
  font-size: 12.5px;
  color: var(--ink);
  letter-spacing: 0.04em;
  word-break: break-all;
  margin-top: 4px;
}
@media (max-width: 600px) {
  .otp-enrol-grid { grid-template-columns: 1fr; }
}

.backup-codes-list {
  list-style: none;
  padding: 0;
  margin: 16px 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 8px 16px;
}
.backup-codes-list li code {
  display: block;
  padding: 8px 12px;
  background: var(--light-2);
  border: 1px solid var(--line);
  border-radius: 6px;
  font-size: 13px;
  letter-spacing: 0.04em;
  text-align: center;
}
@media (max-width: 480px) {
  .backup-codes-list { grid-template-columns: 1fr; }
}

/* ---------- Buttons ---------- */

.btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 8px 14px; border-radius: 8px;
  font-size: 13px; font-weight: 600;
  cursor: pointer; border: 1px solid transparent;
  transition: all 120ms ease;
  font-family: inherit;
  text-decoration: none;
}
.btn-primary { background: var(--navy); color: #fff; }
.btn-primary:hover { background: var(--deep); color: #fff; }
/* Gold variant for Priority-tier CTAs (Reserve on a Priority-only deal,
 * Upgrade-to-Priority button on the account card). Same shape as
 * btn-primary so they swap cleanly. */
.btn-priority {
  background: var(--gold-deep);
  color: #fff;
}
.btn-priority:hover {
  background: var(--gold);
  color: #fff;
}
.btn-accent  { background: var(--mint); color: var(--navy); }
.btn-accent:hover { background: var(--mint-2); color: var(--navy); }
.btn-ghost   { background: transparent; color: var(--ink-2); border-color: var(--line-2); }
.btn-ghost:hover { background: var(--light-2); }
.btn-danger  { background: var(--surface); color: var(--danger); border-color: #f1c9c9; }
.btn-danger:hover { background: #fdecec; }
.btn-sm      { padding: 5px 9px; font-size: 12px; }
.btn-xs      { padding: 3px 8px; font-size: 11.5px; }

/* button_to wraps the actual <button> in a <form>; the form should
   not introduce extra layout space. */
form.button_to { display: inline; margin: 0; }

/* ---------- Form fields ---------- */

.input, .select, .textarea {
  width: 100%; padding: 8px 10px;
  border: 1px solid var(--line-2); border-radius: 8px;
  background: var(--surface); color: var(--ink);
  font-family: inherit; font-size: 13px;
  transition: border-color 120ms ease, box-shadow 120ms ease;
}
.input:focus, .select:focus, .textarea:focus {
  outline: none; border-color: var(--mint);
  box-shadow: 0 0 0 3px rgba(111, 198, 160, 0.2);
}
.textarea { min-height: 80px; resize: vertical; }

/* Console-language selects, portal-wide (2026-07-19): the OS
   spinner dies everywhere. appearance:none drops the native
   arrow, the navy chevron replaces it; the popup stays native.
   [multiple] keeps the native listbox. Image properties only --
   never the background shorthand -- so state tints (.field.invalid)
   can recolour without killing the chevron. */
.select:not([multiple]) {
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%231c2d35' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  padding-right: 34px;
  border-radius: 6px;
  cursor: pointer;
}
/* Compact variant for tight inline spots (ROI band rows, export
   period, seat roles): smaller chevron inset so nothing clips. */
.select--sm:not([multiple]) {
  padding-right: 26px;
  background-position: right 8px center;
  background-size: 10px;
  border-radius: 6px;
}

.field { display: flex; flex-direction: column; gap: 4px; }
.field label {
  font-size: 11.5px;
  color: var(--muted);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}
.field-row   { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.field-row-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }

/* Client-side validation error state. The Stimulus controller adds
   .invalid to .field elements on submit when a cross-field check
   fails, and clears it again on input. Mirrors the prototype's
   .field.invalid treatment. */
.field.invalid > .input,
.field.invalid > .select,
.field.invalid > div > .input,
.field.invalid > div > .select {
  border-color: var(--danger);
  /* background-color, not the shorthand: the shorthand would wipe
     the select chevron background-image. */
  background-color: #fdf3f3;
}
.field.invalid > label { color: var(--danger); }
.field.invalid > label .required-marker { color: var(--danger); }

/* ---------- Bulk-upload staging table (3.7.k) ----------
   The bulk page renders an editable grid of inline inputs. Each
   cell is .adv-cell; client-side validation flips bad cells to
   .adv-invalid (red border + tinted background). Ported from the
   prototype's #adv-table styles (docs/vantage-portal.html:2008-
   2011). */
.adv-cell.adv-invalid {
  border-color: var(--danger) !important;
  background: #fdf3f3 !important;
}
.bulk-card { padding: 18px 20px; }
.bulk-step-label { margin-bottom: 8px; }
.bulk-step-copy { font-size: 12px; line-height: 1.55; margin: 0 0 6px; }
.bulk-step-cols { font-size: 11.5px; line-height: 1.6; margin: 0; }
.bulk-step1-row { gap: 12px; flex-wrap: wrap; align-items: flex-start; }
.bulk-step1-copy { flex: 1; min-width: 240px; }
.bulk-step1-cta { flex-shrink: 0; }
.bulk-file-input { padding: 8px; font-size: 12.5px; }
.bulk-file-name { font-size: 11.5px; margin-top: 6px; min-height: 1em; }
.bulk-step2-actions { margin-top: 12px; }
.bulk-paste-details { margin-top: 16px; }
.bulk-paste-summary { cursor: pointer; font-size: 12px; color: var(--deep); font-weight: 600; }
.bulk-paste-body { margin-top: 10px; }
.bulk-paste-copy { font-size: 11.5px; line-height: 1.5; margin: 0 0 8px; }
.bulk-paste-textarea { font-family: var(--mono, monospace); font-size: 11.5px; }
.bulk-paste-actions { margin-top: 8px; }
.bulk-staging-card { padding: 0; overflow: hidden; }
.bulk-staging-head { padding: 14px 18px; border-bottom: 1px solid var(--line); }
.bulk-staging-title { font-weight: 700; font-size: 14px; }
.bulk-staging-count { font-weight: 500; }
.bulk-staging-sub { font-size: 11.5px; }
.bulk-clear-all { color: #c25555; }
.bulk-empty { padding: 40px 20px; }
.bulk-empty-sub { margin-top: 6px; font-size: 13px; }
.bulk-table-scroller { overflow-x: auto; }
.bulk-table { margin: 0; min-width: 900px; }
.bulk-table th { font-size: 11px; }
.bulk-cell { padding: 4px; }
.bulk-cell-del { text-align: center; }
.bulk-del-btn { color: #c25555; padding: 4px 8px; }
.bulk-staging-footer { padding: 16px 18px; border-top: 1px solid var(--line); }
.bulk-disclaimer { font-size: 12px; line-height: 1.55; margin-bottom: 12px; }

/* ---------- Cards ---------- */

/* Generic surface container, used for any "panel of related content"
   (deal list rows, supplier-portal form sections, marketplace cards in
   Phase 3.3, etc). Ported from the prototype's .card. */
.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 20px;
  /* Flat: the hairline border carries the edge, no glow (premium sweep). */
  box-shadow: none;
}
/* Card headings read as institutional micro-labels with a hairline
   rule under them (2026-07-29 sweep). */
.card-title {
  margin: 0 0 14px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  color: var(--muted);
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.card-title small {
  color: var(--muted);
  font-weight: 500;
  font-size: 11px;
  text-transform: none;
  letter-spacing: 0;
}

/* Form section header inside a card. Tiny uppercase label that
   visually groups several .field rows. */
.form-section {
  margin-top: 18px;
  margin-bottom: 8px;
  font-size: 11.5px;
  color: var(--muted);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.form-section:first-child { margin-top: 0; }

.form-actions {
  display: flex; gap: 10px;
  margin-top: 22px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
}

/* ---------- Stats grid (dashboard) ----------
 *
 * Four-up tile row used on the supplier dashboard. Each card has a
 * coloured 3px accent stripe on the left edge to differentiate the
 * metric meaning at a glance (default mint = neutral, navy / deep =
 * informational, warn = needs attention). Ported from the prototype's
 * .stat-grid / .stat-card. */

.stat-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: 14px;
  margin-bottom: 20px;
}
.stat-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 18px 20px;
  transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
}
/* Accent is a small dot beside the label -- refined, not a template
   left-stripe. Colour still conveys the metric's meaning. */
.stat-label {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 10px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
  line-height: 1.2;
  font-weight: 600;
  /* Fixed 2-line box so a wrapping label never shoves its number out
     of line with the others across a stat row. */
  min-height: 2.3em;
}
.stat-label::before {
  content: ""; width: 5px; height: 5px; border-radius: 50%;
  background: var(--mint); flex-shrink: 0;
}
.stat-card.navy .stat-label::before { background: var(--navy); }
.stat-card.deep .stat-label::before { background: var(--deep); }
.stat-card.warn .stat-label::before { background: var(--warn); }
/* Figures are Jost, NEVER Syne (H, 2026-08-04: "no numbers in that
   font... all numbers in the font shown for deals on deal card").
   Syne's stylised digits read as branding, not as money -- the deal
   card's £26.90 is the reference. tabular-nums so columns of figures
   line up on the decimal instead of jittering. Syne stays for
   headings and labels; it just never carries a number. */
.stat-value {
  font-family: var(--font);
  font-variant-numeric: tabular-nums;
  font-size: 30px;
  font-weight: 700;
  margin-top: 8px;
  letter-spacing: -0.4px;
  color: var(--ink);
  line-height: 1;
}
.stat-delta {
  font-size: 11.5px;
  margin-top: 8px;
  color: var(--muted);
}

/* A stat card rendered as a link (e.g. "Live on marketplace" clicks
   through to the Live Deals page). Keeps the card visuals, removes
   default link styling, and adds a subtle hover lift to make the
   affordance discoverable. */
.stat-card-link,
a.stat-card {
  display: block;
  text-decoration: none;
  color: inherit;
  transition: transform 120ms ease, box-shadow 120ms ease;
}
.stat-card-link:hover,
a.stat-card:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-md);
  color: inherit;
  text-decoration: none;
}

/* ---------- Choice cards (form-top mode chooser) --------------------
 *
 * Two big segmented buttons used to pick between two mutually-
 * exclusive form modes (e.g. supplier submit: "Yes, include the
 * ASIN" / "No, just the product details"). The active card uses the
 * deep accent colour so the choice is obvious at a glance; inactive
 * cards stay neutral white. Ported from the prototype's inline
 * data-submit-mode buttons (docs/vantage-portal.html ~20718). */

.choice-cards {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.choice-card {
  flex: 1 1 240px;
  min-width: 240px;
  padding: 10px 14px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  border-radius: 8px;
  cursor: pointer;
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.3;
  text-align: left;
  text-decoration: none;
  transition: background 120ms ease, border-color 120ms ease, color 120ms ease;
}
.choice-card:hover { border-color: var(--mint-3); text-decoration: none; }
.choice-card.active {
  background: var(--deep);
  color: #fff;
  border-color: var(--deep);
}
.choice-card.active:hover { color: #fff; }
.choice-card-title { font-weight: 600; }
.choice-card-sub {
  font-weight: 400;
  font-size: 11px;
  opacity: 0.85;
  margin-top: 2px;
}

/* ---------- Sub-tabs (single | bulk selector under chooser) --------- */

.sub-tabs {
  display: flex;
  gap: 8px;
  flex-wrap: wrap;
}
.sub-tab {
  padding: 7px 16px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  border-radius: 7px;
  cursor: pointer;
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 600;
  display: inline-flex;
  align-items: center;
  text-decoration: none;
}
.sub-tab:hover {
  text-decoration: none;
  border-color: var(--mint-3);
  color: var(--ink);
}
.sub-tab.active {
  background: var(--deep);
  color: #fff;
  border-color: var(--deep);
}
.sub-tab.active:hover { color: #fff; }
/* Coming-soon variant: rendered as disabled so the user can see the
   eventual two-path UI without it pretending to work. */
.sub-tab:disabled,
.sub-tab[disabled] {
  cursor: not-allowed;
  opacity: 0.55;
}

/* ---------- Form-mode banner (above form sections) -----------------
 *
 * Wide horizontal info strip at the top of a form, used to explain
 * the implication of the chosen mode. Two variants: mint for
 * "neutral / supportive" mode (no-ASIN: we handle the heavy lifting),
 * gold for "elevated / scored" mode (with-ASIN: deal goes through
 * auto-scoring). Ported from the prototype's renderSingleDealForm
 * inline-styled blocks (docs/vantage-portal.html ~20244-20253). */

.form-mode-banner {
  padding: 10px 14px;
  border-radius: 6px;
  margin-bottom: 18px;
  border-left: 3px solid transparent;
}
.form-mode-banner.mint {
  background: var(--light);
  border-left-color: var(--mint);
}
.form-mode-banner.gold {
  background: var(--gold-soft);
  border-left-color: var(--gold);
}
.form-mode-banner-title {
  font-weight: 600;
  font-size: 12.5px;
  margin-bottom: 4px;
}
.form-mode-banner.mint .form-mode-banner-title { color: var(--deep); }
.form-mode-banner.gold .form-mode-banner-title { color: var(--gold-deep); }
.form-mode-banner-body {
  font-size: 12px;
  line-height: 1.5;
  color: var(--muted);
}

/* ---------- Info card (small inline explainer block) ---------------
 *
 * Tiny help block used inside a .field slot in place of an input
 * (e.g. "No ASIN needed: our team matches the product..." in
 * no-ASIN mode). Less heavy than form-mode-banner. */

.info-card {
  font-size: 11.5px;
  line-height: 1.5;
  padding: 8px 12px;
  border-radius: 6px;
  color: var(--muted);
}
.info-card.mint { background: var(--light); }

/* ---------- Form label adornments ---------------------------------- */

/* The asterisk shown beside required labels. Keeps the required
   indicator separate from the label text so the visual weight is
   tunable in one place. */
.required-marker {
  color: var(--danger);
  font-weight: 700;
}
/* Muted parenthetical hint inside a label, e.g. "Case size (units/case) *".
   Smaller, non-uppercase, regular weight -- visually subordinate to
   the label's primary text. */
.label-sub {
  font-weight: 400;
  font-size: 11px;
  text-transform: none;
  letter-spacing: 0;
  color: var(--muted);
}

/* ---------- Welcome banner + welcome card (supplier dashboard) ------
 *
 * lock-banner: full-width navy-to-deep gradient strip used at the top
 * of role dashboards. Ported from the prototype's .lock-banner; the
 * prototype also has a .priority variant for gold-trim Priority client
 * surfaces -- not ported yet because we don't render Priority client
 * pages in 3.1. */

.lock-banner {
  background: linear-gradient(135deg, var(--navy) 0%, var(--deep) 100%);
  color: #fff;
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 12px;
  display: flex;
  align-items: center;
  gap: 8px;
  line-height: 1.4;
}
.lock-banner strong { font-weight: 700; }

/* Chip variant for use on dark surfaces (lock-banner, hero strips).
   Default .chip is designed for light backgrounds; this one inverts.
   Chained selector (.chip.chip-on-dark) so it beats .chip on specificity
   regardless of stylesheet rule order. */
.chip.chip-on-dark {
  background: rgba(255, 255, 255, 0.15);
  color: #fff;
  border: none;
}

/* Greeting card sitting under the welcome banner. The padding +
   spacing values are tuned to feel like a single conversational unit
   (heading + paragraph + CTAs + footnote), not a generic content card. */
.welcome-card {
  padding: 18px 20px;
}
.welcome-greeting {
  margin: 0 0 6px;
  font-size: 20px;
  letter-spacing: -0.2px;
}
.welcome-copy {
  font-size: 13.5px;
  margin: 0;
  line-height: 1.55;
}
.welcome-actions {
  margin-top: 18px;
  flex-wrap: wrap;
}
.welcome-subnote {
  font-size: 11.5px;
  margin-top: 8px;
  line-height: 1.5;
}

/* "How submissions work" 4-step explainer (supplier dashboard). Each
   step is a heading + a muted body paragraph; this rule styles the
   step heading consistently across the four cells. */
.how-step-title {
  font-weight: 600;
  color: var(--deep);
  font-size: 13.5px;
  margin-bottom: 4px;
}
.how-step-body {
  font-size: 12.5px;
  line-height: 1.55;
}

/* ---------- Status chips ----------
 *
 * Small inline pill for Deal#status, Order#status, etc. The base .chip
 * is muted gray; the variants apply meaning. Each label still includes
 * the text status -- the colour is a visual reinforcement, not the
 * primary signal (accessibility). */

/* Aligned to prototype's .chip (docs/vantage-portal.html:281-293).
 * Padding 3px 8px, font-size 11px, light mint base with deep green
 * text -- reads as a quiet pill rather than a hard-bordered tag.
 * Variants use the prototype's exact colour values. */
.chip {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 3px 7px;
  /* Squared status-badge, not a pill — the premium tell. Colour
     variants below keep their semantic meaning (green/amber/red/gold);
     only the SHAPE and case change. */
  border-radius: 4px;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  background: var(--light);
  color: var(--deep);
  white-space: nowrap;
}
.chip-ok      { background: #d9f0e3; color: #1f6a48; }
/* Chip rendered as a tap-to-navigate link (client Performance
   "Categories to watch" -> category-filtered Deal Flow). Bigger tap
   target than a static chip, no underline, and a clear hover/active
   response so it reads as "this goes somewhere" rather than a label. */
a.chip-link {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 5px 10px;
  text-decoration: none;
  transition: filter .12s ease, transform .12s ease;
}
a.chip-link:hover  { filter: brightness(0.96); text-decoration: none; }
a.chip-link:active { transform: scale(0.97); }
.chip-warn    { background: #faedd0; color: #8a6616; }
/* Inactive option in a chip rail (Deal Scout reason rail): quiet
   until chosen, hover hints it's clickable. */
.chip-ghost   { background: transparent; color: var(--ink-2); box-shadow: inset 0 0 0 1px var(--line); }
.chip-ghost:hover { background: #eef4f0; color: var(--navy); }
.chip-danger  { background: #fadcdc; color: #8e2e2e; }
.chip-info    { background: #e3ebef; color: var(--navy); }
.chip-navy    { background: #e3ebef; color: var(--navy); }
.chip-priority {
  background: #f5e8c8;
  color: var(--gold-deep);
  border: 1px solid rgba(212, 175, 90, 0.5);
}

/* ---------- Deal-list rows (supplier dashboard) ---------- */

.deal-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 8px 18px;
  padding: 14px 16px;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
  align-items: start;
  box-shadow: var(--shadow-sm);
}
.deal-row + .deal-row { margin-top: 10px; }
.deal-row .title    { font-weight: 600; color: var(--ink); }
.deal-row .meta     { color: var(--muted); font-size: 12.5px; margin-top: 3px; }
.deal-row .meta strong { color: var(--ink-2); font-weight: 600; }
.deal-row .right    { text-align: right; display: flex; flex-direction: column; align-items: flex-end; gap: 6px; }

/* Empty-state card for the supplier dashboard when no deals exist. */
.empty-state {
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 44px 28px;
  text-align: center;
  background: var(--surface);
}
.empty-state h2 {
  margin: 0 0 6px;
  font-size: 17px;
  color: var(--ink);
}
.empty-state p {
  margin: 0 0 16px;
  color: var(--ink-2);
  font-size: 13.5px;
}

/* ---------- Two-column panel grid (Live Deals cards) ---------------
 *
 * 2-col grid used by the supplier's Live Deals page; ported from the
 * prototype's .panel-grid-2 (~vantage-portal.html:268). Collapses to
 * one column at narrow widths so cards don't squish below readable
 * width. Width-based collapse is fine here even though we don't ship
 * a mobile layout overall: this is an intra-page reflow, not a shell-
 * level layout decision. */

.panel-grid-2 {
  display: grid;
  grid-template-columns: 2fr 1fr;
  gap: 16px;
}
.panel-grid-3 {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 16px;
}
@media (max-width: 900px) {
  .panel-grid-2 { grid-template-columns: 1fr; }
  .panel-grid-3 { grid-template-columns: 1fr; }
}

/* ---------- Progress bars (stock commitment, etc.) -----------------
 *
 * Thin pill bar used on the Live Deals cards to show the paid + held
 * stock split against total units. Two layered .progress-bar children
 * inside one .progress track: the committed bar (paid + held) sits
 * underneath at lighter opacity; the paid bar sits on top at full
 * opacity. Width is set inline per deal. Colour variant changes with
 * commitment rate (mint < 50%, deep 50-79%, warn >= 80%). */

.progress {
  height: 8px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
  position: relative;
}
.progress-bar {
  height: 100%;
  background: var(--mint);
  transition: width 200ms ease;
  position: absolute;
  top: 0;
  left: 0;
}
.progress-bar-mint { background: var(--mint); }
.progress-bar-deep { background: var(--deep); }
.progress-bar-warn { background: var(--warn); }

/* The "committed" layer sits under the "paid" layer at reduced
   opacity so the supplier sees the paid-vs-reserved split as a
   single bar with two intensities rather than two separate bars. */
.progress-bar-committed { opacity: 0.45; z-index: 0; }
.progress-bar-paid      { opacity: 1;    z-index: 1; }

/* ---------- Attention banner (above list views) --------------------
 *
 * Compact "N need your attention" strip rendered above the supplier
 * Live Deals / My Submissions lists when something changed since
 * the supplier last visited. Mirrors the prototype's
 * renderAttentionBanner (~vantage-portal.html:5587). */

.attention-banner {
  background: var(--light);
  border: 1px solid var(--mint);
  border-radius: var(--radius);
  padding: 12px 14px;
  display: flex;
  gap: 12px;
  align-items: flex-start;
}
.attention-banner-dot {
  font-size: 18px;
  line-height: 1;
  color: var(--deep);
  font-weight: 300;
  flex-shrink: 0;
  margin-top: 1px;
}
.attention-banner-body { flex: 1; min-width: 0; }
.attention-banner-title {
  font-weight: 600;
  font-size: 13px;
  color: var(--ink);
}
.attention-banner-sub {
  font-size: 12px;
  margin-top: 2px;
  line-height: 1.45;
}
.attention-banner-hint {
  font-size: 11px;
  margin-left: 6px;
  color: var(--muted);
}

/* ---------- Tables -------------------------------------------------
 *
 * Base .table styling: uppercase muted headers on a light-wash
 * background, generous cell padding, single-pixel row separators,
 * hover-tint on body rows. Used by the supplier My Submissions
 * table (and any future list view). Ported verbatim from the
 * prototype (~vantage-portal.html:273-278) -- this got missed when
 * the table view shipped in 3.1.a.3 and only the attention-row
 * extension rules below were added; the headers were rendering
 * with browser defaults. */

.table { width: 100%; border-collapse: collapse; font-size: 13px; }
.table th, .table td {
  text-align: left;
  padding: 10px 12px;
  border-bottom: 1px solid var(--line);
  /* Centre vertically. Default HTML <td> alignment is `baseline`,
     which top-aligns single-line cells (chips, dates, money) against
     multi-line cells (title + muted subtitle). Looks like the chips
     are hovering at the top of the row. `middle` keeps each column's
     content visually centred against the tallest cell in the row. */
  vertical-align: middle;
}
.table th {
  font-size: 10px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
  font-weight: 700;
  background: transparent;
  border-bottom: 1px solid var(--line);
  padding-top: 8px;
  padding-bottom: 9px;
}
/* Row hover tint only on true pointer (mouse) devices. On touch the
   :hover state STICKS after a tap, leaving the row pale-mint with no
   action taken -- which reads as "I tapped it and nothing happened".
   Desktop (hover: hover) keeps the tint unchanged. */
@media (hover: hover) {
  .table tbody tr:hover { background: var(--light-2); }
}
.table tbody tr.clickable { cursor: pointer; }
.table td .muted { color: var(--muted); font-size: 12px; }
/* Right-align utility for table cells (e.g. numeric columns).
   More specific than the generic .right utility so it beats the
   .table td text-align: left default. */
.table th.right, .table td.right { text-align: right; }

/* ---------- Attention-row highlighting -----------------------------
 *
 * Subtle mint-tinted background + 3px left border on rows / cards
 * that have updated since the user last viewed the list. Two flavors:
 * .table tr.attention-row for table rows, and .attention-row on a
 * .card / stock-card for the Live Deals grid. Ported from the
 * prototype (~vantage-portal.html:2659-2671). */

/* Attention rows: soft mint wash + a 3px mint accent bar (H, 2026-07-29
   asked for the green fill back). */
.table tr.attention-row > td {
  background: rgba(111, 198, 160, 0.07);
}
/* 3.6.h.22: the 3px mint accent strip belongs to the LEFTMOST
 * cell only -- applying inset box-shadow to every <td> drew a
 * vertical mint line between every column, which read as cell
 * dividers cutting through the inline order stepper. */
.table tr.attention-row > td:first-child {
  box-shadow: inset 3px 0 0 0 var(--mint);
}
.table tr.attention-row:hover > td {
  background: rgba(111, 198, 160, 0.12);
}
.card.attention-row,
.stock-card.attention-row {
  border-left: 3px solid var(--mint);
  background: rgba(111, 198, 160, 0.05);
}

/* ---------- Table-in-card wrapper ----------------------------------
 *
 * A .table laid inside a .card needs the card's padding to be 0 (so
 * the table edges align with the card's outline) and overflow:hidden
 * so the table's first / last row corners are clipped by the card's
 * border-radius. */

.card.table-card {
  padding: 0;
  overflow: hidden;
}

/* 3.6.h.20: table-card needs explicit padding on the heading so
 * "Order history" / "15 tickets" don't sit flush against the
 * card edge. Card's own padding is 0 to keep the table flush. */
.card.table-card > .card-title {
  padding: 16px 20px;
}
/* Wider per-cell horizontal padding inside table-cards so the
 * Status cell's stepper + the Updated cell's verb/actor lines
 * don't crash into each other. */
.card.table-card .table th,
.card.table-card .table td {
  padding-left: 16px;
  padding-right: 16px;
}

/* ---------- Stat detail modal (3.6.h.20) ----------
 * Generic <dialog> driven by the dialog-trigger Stimulus
 * controller. Each My Orders stat card opens its own dialog
 * showing the evidence behind the figure: contributing orders
 * + a totals line at the bottom. */
.stat-modal {
  width: min(820px, calc(100vw - 32px));
  max-height: 82vh;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 0;
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.stat-modal::backdrop { background: rgba(12, 31, 41, 0.45); }
.stat-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 22px;
  border-bottom: 1px solid var(--line);
}
.stat-modal-head h2 {
  margin: 0;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.2px;
}
.stat-modal-subtitle {
  margin: 2px 0 0;
  font-size: 12.5px;
}
.stat-modal-close {
  background: none;
  border: 0;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  color: var(--muted);
  padding: 0 2px;
}
.stat-modal-close:hover { color: var(--ink); }
.stat-modal-body {
  padding: 22px;
  overflow-y: auto;
  max-height: calc(82vh - 64px);
}
.stat-modal-body > p.muted {
  margin: 0;
  font-size: 13px;
}
.stat-modal-total {
  margin-top: 16px;
  padding-top: 14px;
  border-top: 2px solid var(--navy);
  display: flex;
  justify-content: space-between;
  font-size: 15px;
  font-weight: 700;
  color: var(--navy);
}
/* Explanatory bullet list inside a stat modal that has no evidence
   table (e.g. "Live deals available" -- a count, not a set of rows). */
.stat-modal-list {
  margin: 10px 0 0;
  padding-left: 18px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  font-size: 13px;
  color: var(--ink-2);
}
.stat-modal-list li { line-height: 1.45; }

/* stat-card rendered as a clickable button. Same visuals as the
 * link variant above; resets the default button chrome so it
 * looks identical to the regular .stat-card. */
button.stat-card {
  appearance: none;
  text-align: left;
  width: 100%;
  font: inherit;
  color: inherit;
  cursor: pointer;
  transition: transform 120ms ease, box-shadow 120ms ease;
}
button.stat-card:hover {
  transform: translateY(-1px);
  box-shadow: var(--shadow-md);
}
button.stat-card:focus-visible {
  outline: 2px solid var(--mint);
  outline-offset: 2px;
}

/* ---------- Live Deals page (supplier_live_deals#index) ------------ */

.live-deals-header        { gap: 16px; align-items: flex-start; }
.live-deals-title         { margin: 0; font-size: 18px; }
.live-deals-count         { font-weight: 500; font-size: 13px; }
.live-deals-subtitle      { font-size: 12.5px; }
.live-deals-scoreboard    { text-align: right; }
.live-deals-committed-value {
  font-size: 18px;
  font-weight: 700;
  color: var(--deep);
}
.live-deals-scoreboard-sub { font-size: 11.5px; }
.live-deals-footnote {
  font-size: 11.5px;
  line-height: 1.55;
  max-width: 780px;
}

/* ---------- Stock card (one deal on Live Deals grid) --------------- */

.stock-card                   { padding: 16px 18px; }
.stock-card-head              { margin-bottom: 8px; }
.stock-card-ref               { font-size: 11px; font-weight: 600; }
.stock-card-title {
  font-weight: 600;
  font-size: 14px;
  line-height: 1.35;
  color: var(--ink);
  margin-bottom: 4px;
}
.stock-card-ids               { font-size: 11.5px; margin-bottom: 14px; }
.stock-card-movement          { margin-bottom: 14px; }
.stock-card-movement-head     { margin-bottom: 6px; }
.stock-card-committed-label   { font-size: 12.5px; font-weight: 600; color: var(--ink); }
.stock-card-committed-pct     { font-size: 12.5px; font-weight: 700; }
.stock-card-committed-pct.stock-card-bar-mint  { color: var(--mint-2); }
.stock-card-committed-pct.stock-card-bar-deep  { color: var(--deep); }
.stock-card-committed-pct.stock-card-bar-warn  { color: var(--warn); }
.stock-card-progress          { margin: 0; }
.stock-card-breakdown {
  font-size: 11px;
  margin-top: 6px;
}
.stock-card-terms {
  font-size: 11.5px;
  line-height: 1.6;
  padding-top: 10px;
  border-top: 1px solid var(--line);
}
.stock-card-terms-strong { color: var(--ink); }
.stock-card-posted       { margin-top: 3px; }

/* ---------- My Submissions page (supplier_my_submissions#index) ---- */

.submissions-header        { gap: 16px; align-items: flex-start; }
.submissions-title         { margin: 0; font-size: 18px; }
.submissions-count         { font-weight: 500; font-size: 13px; }
.submissions-subtitle      { font-size: 12.5px; }
.submissions-product-title { font-weight: 600; }
.submissions-product-sub   { font-size: 11.5px; }
.submissions-footnote {
  font-size: 11.5px;
  line-height: 1.55;
}
/* The staff rejection reason, shown inline under the Rejected chip so
   the supplier knows what to fix (the data is stamped on reject). */
.submissions-reject-reason {
  margin-top: 6px;
  max-width: 320px;
  font-size: 12px;
  line-height: 1.45;
  color: var(--ink-2);
  border-left: 2px solid var(--line-2);
  padding-left: 8px;
}

/* ---------- Error summary ----------
 *
 * Rendered at the top of forms when the model fails validation. Lists
 * the offending fields so the user doesn't have to scan the form for
 * the inline error message. */
.error-summary {
  background: #fdecec;
  border: 1px solid #f1c9c9;
  border-left: 4px solid var(--danger);
  border-radius: 6px;
  padding: 12px 16px;
  margin-bottom: 16px;
}
.error-summary h3 {
  margin: 0 0 6px;
  font-size: 13.5px;
  color: #8a2a2a;
}
.error-summary ul { margin: 0; padding-left: 20px; color: #8a2a2a; font-size: 13px; }
.error-summary li { margin: 2px 0; }

/* Per-field error message (under the input). */
.field-error {
  font-size: 11.5px;
  color: var(--danger);
  margin-top: 2px;
}

/* ---------- Flash messages ---------- */

/* Replaces the prototype's toast system. Devise renders notice + alert
   via the layout; toasts go via Stimulus once we have a real broadcast
   channel (Phase 4). For Phase 3, plain inline banners. */
.flash {
  margin-bottom: 16px;
  padding: 11px 14px 11px 16px;
  border-radius: 6px;
  font-size: 13.5px;
  font-weight: 500;
  border: 1px solid transparent;
  border-left-width: 4px;
  display: flex;
  align-items: center;
  gap: 10px;
}
.flash::before {
  font-weight: 700;
  font-size: 14px;
  line-height: 1;
}
.flash-notice {
  background: var(--light-2);
  color: var(--deep-2);
  border-color: var(--mint-3);
  border-left-color: var(--mint);
}
.flash-notice::before { content: "\2713"; color: var(--deep); }
.flash-alert {
  background: #fdecec;
  color: #8a2a2a;
  border-color: #f1c9c9;
  border-left-color: var(--danger);
}
.flash-alert::before  { content: "\26A0"; color: var(--danger); }

/* ---------- Staff orders workflow --------------------------------- *
 *
 * Index page: KPI cards (reuses .stat-grid), status-pill filter row,
 * search form. Show page: allocation card + shared order-timeline +
 * transition buttons + inline transition forms.
 */

/* Status-pill filter row (above the orders table). Same visual
 * primitive as the marketplace category pills but with a count
 * badge attached to each pill. */
.status-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.status-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px 4px 12px;
  border-radius: 6px;
  font-size: 12.5px;
  background: var(--light-2);
  color: var(--ink-2);
  text-decoration: none;
  border: 1px solid var(--line);
  transition: background-color 0.12s, border-color 0.12s, color 0.12s;
}

.status-pill:hover {
  background: var(--surface);
  border-color: var(--navy);
  color: var(--navy);
}

.status-pill.active {
  background: var(--navy);
  color: #fff;
  border-color: var(--navy);
}

.status-pill .pill-count {
  font-size: 10.5px;
  font-weight: 700;
  background: rgba(17, 42, 54, 0.10);
  color: var(--ink);
  padding: 1px 6px;
  border-radius: 8px;
  min-width: 16px;
  text-align: center;
}

.status-pill.active .pill-count {
  background: rgba(255, 255, 255, 0.20);
  color: #fff;
}

.status-pill.muted-pill {
  color: var(--muted);
}

/* Search form sits in its own card just above the table. */
.orders-search-form {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

/* Shared order-timeline partial (used by staff_orders/show and
 * client_orders/show in 3.4.c). Two-column row layout: label
 * left, value right. Tight vertical rhythm. */
.order-timeline .timeline-row {
  display: grid;
  grid-template-columns: 130px 1fr;
  gap: 12px;
  padding: 6px 0;
  align-items: baseline;
}

.order-timeline .timeline-row + .timeline-row {
  border-top: 1px solid var(--line);
}

.order-timeline .timeline-label {
  font-size: 11.5px;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.order-timeline .timeline-value {
  font-size: 13px;
  color: var(--ink);
}

/* Inline transition form (the <details> collapsible on the staff
 * order workflow card). Open state shows the form below the
 * trigger button. */
.transition-form {
  display: inline-block;
  margin-right: 8px;
}

.transition-form > summary {
  list-style: none;
  cursor: pointer;
  display: inline-block;
}

.transition-form > summary::-webkit-details-marker {
  display: none;
}

.transition-form[open] {
  display: block;
  margin-top: 0;
  padding-top: 12px;
}

.transition-form[open] > summary {
  margin-bottom: 12px;
}

.transition-form[open] form {
  max-width: 520px;
  padding: 12px;
  background: var(--light-2);
  border-radius: 6px;
}

/* ---------- Client orders (My Orders) ------------------------------ *
 *
 * Index list + show detail + cancel button. Mostly leans on existing
 * stat-grid + table-card + card primitives; just a couple of
 * affordances unique to the orders surface.
 */

/* Deal-name link in the order table row -- standard table cell uses
 * the default link blue, which feels heavy alongside the muted
 * supporting text. Inherit colour + carry a subtle hover underline. */
.deal-link {
  color: var(--ink);
  font-weight: 600;
  text-decoration: none;
}

.deal-link:hover {
  color: var(--navy);
  text-decoration: underline;
}

/* The stats bar above the table reuses .stat-grid; .order-stats-bar
 * exists as a hook for any future overrides specific to the client
 * view (no per-card tweaks today). */

/* ---------- Client marketplace (Deal Flow) ------------------------- *
 *
 * Card grid + per-card layout for the client-facing deal browse
 * (ClientMarketplaceController#index). Ports the prototype's
 * VIEWS.marketplace + renderMarketplaceCard (~vantage-portal.html:
 * 8348 / 8956). Card width tuned for 3-up on a typical staff/
 * client laptop, dropping to 2-up below ~1200px and 1-up below
 * ~800px (which is a degraded-but-usable target, not a designed
 * mobile experience -- v1 is desktop-first per the migration plan).
 */

/* Tabs row (3.6.f). Tabs on the left, CSV download on the right.
 * Single horizontal rule under the row so the tabs read as
 * navigation while the export sits inline without competing. */
.marketplace-tabs-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  border-bottom: 1px solid var(--line);
  gap: 12px;
}

.marketplace-csv-link {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--muted);
  text-decoration: none;
  padding: 6px 10px;
  border-radius: 6px;
  white-space: nowrap;
  transition: color 120ms ease, background-color 120ms ease;
}
.marketplace-csv-link:hover {
  color: var(--navy);
  background: var(--light-2);
}

/* Status tab strip (3.6.c). Sits above the search/filters,
 * partitions the marketplace by Active / Sold out / Expired / All.
 * Pill-shaped underline rather than a chip rail so it reads as
 * navigation, not filter chips. */
.marketplace-tabs {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  padding-bottom: 0;
}

.marketplace-tab {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 8px 14px;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  transition: color 120ms ease, border-color 120ms ease;
}

.marketplace-tab:hover {
  color: var(--ink-2);
}

.marketplace-tab.active {
  color: var(--navy);
  border-bottom-color: var(--navy);
}

.marketplace-tab-count {
  font-size: 11px;
  font-weight: 700;
  padding: 1px 7px;
  border-radius: 999px;
  background: var(--light-2);
  color: var(--ink-2);
  border: 1px solid var(--line);
}

.marketplace-tab.active .marketplace-tab-count {
  background: var(--navy);
  color: #fff;
  border-color: var(--navy);
}

.marketplace-filters {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* ---------- Phase 3.7.e: Deal Flow filter toolbar ----------
 *
 * Prototype-aligned filter row (Show: + status pills + deal
 * count) plus the second toolbar row (search + sort + filters
 * + CSV). Pills follow the same shape as the .status-pills /
 * .category-pill family already on the page so the surface
 * reads as one unified control rail.
 */
.mkt-filter-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}
.mkt-toolbar-form {
  position: relative;
}
.mkt-filter-row-label {
  font-size: 12px;
  margin-right: 4px;
}
/* Staff marketplace preview (staff_deal_flow) -- the lens bar that
   stands in for client identity, and its read-only reminder. */
.staff-preview-bar {
  padding: 12px 16px;
}
.staff-preview-note {
  margin-left: auto;
  font-size: 12px;
}
.mkt-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 5px 12px;
  border-radius: 6px;
  font-size: 12.5px;
  font-weight: 500;
  background: var(--light-2);
  color: var(--ink-2);
  text-decoration: none;
  border: 1px solid var(--line);
  transition: background-color 0.12s, border-color 0.12s, color 0.12s;
}
.mkt-pill:hover {
  background: var(--surface);
  border-color: var(--navy);
  color: var(--navy);
}
.mkt-pill.active {
  background: var(--navy);
  color: #fff;
  border-color: var(--navy);
}
.mkt-pill.active small { color: rgba(255,255,255,0.85); }
.mkt-pill small { font-size: 10.5px; color: var(--muted); }
.mkt-search-input {
  flex: 1;
  min-width: 200px;
  max-width: 320px;
}
.mkt-sort {
  width: auto;
  min-width: 200px;
}
.mkt-filters-toggle { padding: 6px 12px; }
.mkt-filters-count {
  margin-left: 6px;
  padding: 1px 7px;
  font-size: 11px;
  background: var(--navy);
  color: #fff;
}
.mkt-csv-link { padding: 6px 12px; }
.mkt-deal-count {
  margin-left: auto;
  font-size: 12px;
}
.mkt-filters-panel {
  flex-basis: 100%;
  margin-top: 10px;
  margin-bottom: 8px;
  padding: 14px 16px;
}
.mkt-filters-panel-row {
  display: flex;
  gap: 16px;
  flex-wrap: wrap;
  align-items: flex-end;
}
.mkt-filters-panel-row .field {
  flex: 1;
  min-width: 130px;
  margin-bottom: 0;
}
.mkt-filters-panel-row label {
  font-size: 11.5px;
  margin-bottom: 4px;
  color: var(--muted);
}
.mkt-filters-apply { padding: 7px 18px; white-space: nowrap; }
.mkt-filters-clear { padding: 6px 12px; white-space: nowrap; }
.mkt-filters-hint {
  font-size: 11px;
  margin-top: 8px;
}

.marketplace-search-form {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.category-pills {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

/* "More" overflow for categories without a live deal (2026-07-19):
   the summary reads as one more pill; opening it reveals the rest on
   their own quiet row below, full width of the pill rail. */
.category-more { display: contents; }
.category-more > summary { cursor: pointer; list-style: none; }
.category-more > summary::-webkit-details-marker { display: none; }
.category-more[open] > summary { opacity: 0.6; }
.category-more-panel {
  flex-basis: 100%;
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 6px;
}

.category-pill {
  display: inline-block;
  padding: 4px 12px;
  border-radius: 6px;
  font-size: 12px;
  background: var(--light-2);
  color: var(--ink-2);
  text-decoration: none;
  border: 1px solid var(--line);
  transition: background-color 0.12s, border-color 0.12s;
}

.category-pill:hover {
  background: var(--surface);
  border-color: var(--navy);
  color: var(--navy);
}

.category-pill.active {
  background: var(--navy);
  color: #fff;
  border-color: var(--navy);
}

/* Bell glyph that appears inside a category pill when the client
 * is currently subscribed to alerts for that category. Subtle --
 * the toggle button at the end of the row is the loud signal;
 * the per-pill bells are an at-a-glance survey. */
.category-pill-bell {
  display: inline-flex;
  align-items: center;
  margin-left: 4px;
  opacity: 0.9;
  vertical-align: -1px;
}

/* Alert-me bell CTA on the deal flow page. Same pill shape as the
 * category pills so it sits cleanly at the end of the row. Active
 * state = "you're subscribed" -> mint background. */
.category-alert-form {
  display: inline;
  margin: 0;
  margin-left: 8px;
}
.category-alert-cta {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 12px;
  border-radius: 6px;
  font-size: 12px;
  background: var(--surface);
  color: var(--ink-2);
  border: 1px solid var(--line);
  cursor: pointer;
  transition: background-color 0.12s, border-color 0.12s, color 0.12s;
}
/* The alert bell sits slightly smaller than the label baseline. */
.category-alert-cta .ico { flex: 0 0 auto; }
.category-alert-cta:hover {
  background: var(--light-2);
  border-color: var(--navy);
  color: var(--navy);
}
.category-alert-cta.active {
  background: #d9f0e3;
  border-color: var(--mint);
  color: #1f6a48;
}

.marketplace-grid {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
  margin-top: 10px;
}

@media (max-width: 1200px) {
  .marketplace-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 800px) {
  .marketplace-grid { grid-template-columns: 1fr; }
}

/* Deal card (3.6.h visual fidelity). Aligned to prototype's
 * .mkt-card values (docs/vantage-portal.html:539-737):
 *   - 18px padding, var(--radius), var(--shadow-sm)
 *   - Hover lifts (translateY) + mint-3 border tint
 *   - Econ panel sits between border-top and border-bottom, no bg
 *     fill (the prototype uses negative-space to separate it)
 *   - Stock bar is mint-on-line at 4px height, NOT navy-on-tint
 *
 * .mkt-* in prototype maps to .market-card-* here; same structure,
 * Rails-style class naming. */
.market-card {
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 18px;
  gap: 12px;
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: transform 120ms ease, box-shadow 120ms ease, border-color 120ms ease;
}

.market-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
  border-color: var(--mint-3);
}

.market-card.is-sold-out {
  opacity: 0.65;
}
.market-card.is-sold-out:hover {
  transform: none;
  box-shadow: var(--shadow-sm);
  border-color: var(--line);
}

/* Priority-unlocked card -- gold border + soft gold gradient so a
 * Priority member sees the exclusive economics in a visibly
 * different envelope from Access-tier deals. */
.market-card.priority-unlocked {
  background: linear-gradient(135deg, var(--gold-soft), transparent 70%);
  border-color: var(--gold);
}
.market-card.priority-unlocked:hover {
  border-color: var(--gold-deep);
}

.market-card-head {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  align-items: flex-start;
  margin-bottom: 4px;
}

.market-card-head-left {
  flex: 1;
  min-width: 0;
}

.market-card-head-right {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
  flex-shrink: 0;
}

.market-card-category {
  font-size: 11.5px;
  margin-top: 2px;
}

/* Watchlist star (3.7.e). Quiet outline by default; active state
 * fills in deep-green so a watched card reads at a glance in a
 * grid of cards. Lives in the top-right of the card alongside the
 * expiry / Priority chips. */
.market-card-star {
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 2px 4px;
  font-size: 18px;
  line-height: 1;
  color: var(--muted);
  transition: color 0.12s ease, transform 0.12s ease;
}
.market-card-star:hover {
  color: var(--deep);
  transform: scale(1.05);
}
.market-card-star.active {
  color: var(--deep);
}
.market-card-star form { margin: 0; display: inline; }

.market-card-meta {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  margin-bottom: 2px;
}

/* VW-### deal ref (3.6.h). Mono + deep-green + tracking, sits
 * above the title -- matches the prototype's .mkt-ref. */
.market-card-ref {
  font-size: 11px;
  font-weight: 600;
  color: var(--deep);
  letter-spacing: 0.03em;
  margin-bottom: 2px;
}

.market-card-title {
  font-size: 15px;
  font-weight: 700;
  margin: 0;
  line-height: 1.3;
  color: var(--ink);
  /* Reserve two title lines so cards in a row have stats / CTAs
   * aligned regardless of one-line vs two-line titles. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  min-height: calc(15px * 1.3 * 2);
}

.market-card-supplier {
  font-size: 11.5px;
}

.market-card-econ {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 8px;
  padding: 10px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
  margin-top: auto;
}

.econ-cell {
  display: flex;
  flex-direction: column;
}

.econ-label {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 600;
  color: var(--muted);
}

.econ-value {
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
  margin-top: 2px;
}

/* Per-unit profit cell pops in deep green -- matches the
 * prototype's .mkt-stat-val.profit highlight. */
.econ-cell:first-child .econ-value {
  color: var(--deep);
}

.market-card-stock {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* 3.7.e -- stock meta on a single flex row above the bar. Units
 * label sits left, case/MOQ/sales sits right. Matches the
 * prototype's docs/vantage-portal.html:9124-9127 mkt-meta layout. */
.market-card-stock-meta {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 10px;
  font-size: 12px;
  color: var(--ink-2);
}
.market-card-stock-units strong { color: var(--ink); }
.market-card-stock-detail { font-size: 11.5px; }
/* The "N% claimed" urgency read next to units-left -- mint-deep so it
   registers as momentum, not alarm. */
.market-card-stock-claimed {
  color: #1f6a48;
  font-weight: 600;
}

/* ---------- Phase 3.7.e: transient toast ----------
 *
 * Fixed bottom-right stack matching the prototype's
 * #toast-root (docs/vantage-portal.html:412). Each toast pops
 * in on mount, sits for ~2.5s, then fades out + slides down.
 * The watchlist star uses ok / warn variants; future client-
 * side actions can reuse the same helper.
 */
.toast-root {
  position: fixed;
  bottom: 20px;
  right: 20px;
  display: flex;
  flex-direction: column;
  gap: 8px;
  z-index: 1100;
  pointer-events: none;
}
.toast {
  pointer-events: auto;
  background: var(--navy);
  color: #fff;
  border-radius: 6px;
  padding: 10px 14px;
  font-size: 13px;
  box-shadow: var(--shadow-lg);
  max-width: 340px;
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 180ms ease, transform 180ms ease;
}
.toast-shown {
  opacity: 1;
  transform: translateY(0);
}
.toast-leaving {
  opacity: 0;
  transform: translateY(8px);
}
.toast-ok   { background: var(--deep); color: #fff; }
.toast-warn { background: #f5e8c8; color: var(--ink); border-left: 3px solid var(--gold); }
.toast-err  { background: #c64545; color: #fff; }

/* ---------- Phase 3.7.f: order ticket thread ----------
 *
 * Full-width strip bubbles per the prototype, NOT chat-style
 * floats. Each bubble spans the card; the side is signalled by
 * the soft fill colour alone (mint wash for client, neutral grey
 * for Vantage Ops). No borders -- the fill carries the meaning,
 * a border on top would be visual noise.
 */
.ticket-thread-card { padding-bottom: 16px; }
.ticket-thread {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 12px;
}
.ticket-thread-bubble {
  padding: 10px 14px;
  border-radius: 8px;
  font-size: 13px;
  line-height: 1.45;
  border: none;
}
.ticket-thread-bubble-client { background: #dff0e2; }
.ticket-thread-bubble-ops    { background: #eef1f3; }

.ticket-thread-meta {
  display: flex;
  gap: 10px;
  align-items: baseline;
  margin-bottom: 4px;
  font-size: 12.5px;
}
.ticket-thread-meta strong { color: var(--ink); font-weight: 700; }
.ticket-thread-time { font-size: 12px; }
.ticket-thread-headline {
  color: var(--ink);
  font-weight: 500;
}
.ticket-thread-body {
  font-size: 12.5px;
  margin-top: 2px;
  line-height: 1.5;
  color: var(--muted);
}

.stock-line {
  font-size: 12px;
  color: var(--muted);
}

.stock-bar {
  position: relative;
  height: 4px;
  background: var(--line);
  border-radius: 999px;
  overflow: hidden;
  margin-top: 4px;
}

.stock-bar-paid,
.stock-bar-held {
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
}

.stock-bar-paid {
  background: var(--mint);
  z-index: 2;
}

/* Held = lighter mint, semantically "may free up". Same hue,
 * lower alpha so it reads as "in-flight" stock without adding a
 * second colour. */
.stock-bar-held {
  background: rgba(111, 198, 160, 0.45);
  z-index: 1;
}

.stock-meta {
  font-size: 11.5px;
}

/* Potential-profit-on-remaining row (3.6.c). Sits between the
 * stock row and the reserve CTA. Quiet panel -- this is a
 * derived figure, not a primary buy decision, so it shouldn't
 * compete with the per-unit profit number above. Suppressed for
 * sold-out / expired cards (no remaining to profit on). */
.market-card-potential {
  font-size: 12px;
  padding: 8px 10px;
  background: var(--light-2);
  border: 1px solid var(--line);
  border-radius: 6px;
  margin-top: 4px;
}
.market-card-potential strong {
  color: var(--deep);
}

/* Prominent mint pill for the potential-profit total. Matches the
 * prototype's docs/vantage-portal.html ~9090 highlighted block --
 * the figure is the "money on the table" sell for the card so it
 * deserves its own colour treatment over the muted .market-card-
 * potential band above. */
.market-card-potential-pill {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  padding: 8px 12px;
  background: var(--light);
  border: 1px solid var(--mint);
  border-radius: 8px;
  margin-top: 4px;
  font-size: 12.5px;
}
.market-card-potential-label {
  color: var(--ink);
}
.market-card-potential-pill strong {
  color: var(--deep);
  font-weight: 700;
}
.market-card-priority-note {
  font-size: 11px;
  text-align: center;
  margin-top: 6px;
  color: var(--gold-deep);
}

/* ---------- Deal-detail modal (3.6.d) ----------
 * Native <dialog> styled to feel native to the marketplace --
 * three-column body for the prototype's per-unit / stock /
 * market-signal layout, sticky header + footer for scrolling
 * dialogs on narrower viewports, backdrop click-out wired
 * via the Stimulus controller. */

.deal-modal {
  width: min(960px, calc(100vw - 32px));
  max-height: calc(100vh - 32px);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0;
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.deal-modal::backdrop {
  background: rgba(17, 42, 54, 0.55);
}
.deal-modal-body {
  max-height: calc(100vh - 32px);
  overflow-y: auto;
  padding: 22px 24px 0;
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.deal-modal-loading,
.deal-modal-error {
  padding: 28px;
  text-align: center;
  font-size: 14px;
}

/* 3.7.e -- "Live deal · VW-###" eyebrow sits on its own row at
 * the top of the modal, with the close X aligned to the right.
 * The header proper (title + chips) sits below it. */
.deal-modal-eyebrow {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-bottom: 12px;
  margin-bottom: 12px;
  border-bottom: 1px solid var(--line);
}
.deal-modal-eyebrow-label {
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
}
.deal-modal-eyebrow-label .mono { color: var(--deep); }

.deal-modal-head {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 16px;
  align-items: start;
  padding-bottom: 14px;
  border-bottom: 1px solid var(--line);
}
.deal-modal-head-left {
  min-width: 0;
}
.deal-modal-ref {
  font-size: 11.5px;
  font-weight: 600;
  color: var(--deep);
  letter-spacing: 0.03em;
  margin-bottom: 4px;
}
.deal-modal-title-row {
  display: flex;
  align-items: flex-start;
  gap: 8px;
}
.deal-modal-title {
  margin: 0 0 4px;
  font-size: 18px;
  font-weight: 700;
  letter-spacing: -0.2px;
  color: var(--ink);
  flex: 1;
}
.deal-modal-star {
  margin-top: 2px;
}
.deal-modal-subtitle {
  font-size: 12.5px;
}
.deal-modal-head-right {
  text-align: right;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 4px;
}
.deal-modal-chips {
  display: inline-flex;
  flex-wrap: wrap;
  gap: 4px;
  justify-content: flex-end;
  align-items: center;
}
.deal-modal-stock-summary {
  font-size: 12px;
}

/* ---------- Deal review (modal + full page) (3.7.l.2) ----------
   Gold callout box flagging that a deal arrived via the supplier
   portal. Mirrors the prototype's portal-submission card at
   vantage-portal.html:13532-13540. */
.deal-portal-callout {
  background: #fdf6df;
  border: 1px solid #f0dca5;
  border-left: 3px solid #c79a1c;
  padding: 12px 16px;
}
.deal-portal-callout-title {
  font-weight: 600;
  color: #806a18;
  font-size: 13px;
  margin-bottom: 4px;
}
.deal-portal-callout-body {
  font-size: 12.5px;
  line-height: 1.55;
}
.deal-portal-callout-quote {
  margin-top: 8px;
  padding: 8px 10px;
  background: rgba(255, 255, 255, 0.55);
  border-radius: 6px;
  font-style: italic;
  color: var(--ink);
}

/* Spacing helpers inside each card of the 3-col review grid. The
   prototype uses tiny labels above values separated by 6-10px. */
.deal-review-label {
  font-size: 11.5px;
  color: var(--muted);
}
.deal-review-label-spaced {
  margin-top: 10px;
}
/* Explainer under the "Deal headroom" card title when the client
   price isn't set yet (review-stage supplier-cost economics). */
.deal-review-headroom-note {
  font-size: 11.5px;
  line-height: 1.45;
  margin: -2px 0 8px;
}
/* WHY a deal misses threshold: reason list under the red chip in the
   review modal (Deal#threshold_failures). */
.threshold-reasons {
  margin: 6px 0 0;
  padding-left: 16px;
  font-size: 11.5px;
  line-height: 1.5;
  color: #a13030;
}
.threshold-reasons li { margin-bottom: 2px; }
/* Same reasons as compact micro-lines in the supplier-submissions
   Profitability cell (mirrors .staff-subs-reject-reason sizing). */
.staff-subs-threshold-reasons {
  margin-top: 4px;
  max-width: 220px;
  font-size: 10.5px;
  line-height: 1.45;
  color: #9a6a1a;
}
/* EAN + ASIN identifiers line in the review-modal header; the ASIN
   deep-links to the tracked Amazon listing for manual spot checks. */
.deal-review-identifiers {
  font-size: 11.5px;
  margin-top: 3px;
}
.deal-asin-link {
  color: var(--deep);
  font-weight: 600;
  text-decoration: underline;
  text-underline-offset: 2px;
}
/* Amazon product photo on the staff listing preview, matching the
   client card's thumb sizing. */
.listing-preview-thumb {
  width: 56px;
  height: 56px;
  object-fit: contain;
  border-radius: 8px;
  background: var(--surface);
  border: 1px solid var(--line);
  margin-right: 12px;
  flex-shrink: 0;
}
.listing-preview-head:has(.listing-preview-thumb) {
  justify-content: flex-start;
}

/* Stack the review grid on narrow screens (modal at <1024px) so
   the cards don't crush. */
@media (max-width: 1024px) {
  .deal-review-grid { grid-template-columns: 1fr; }
}

/* ---------- Public listing preview --------------------------
   Mirrors the prototype's renderListingPreview at vantage-portal
   .html:13887. Shows the marketplace card a subscriber will see
   once the deal is posted, with a soft mint frame so it doesn't
   pretend to be the real card. */
.listing-preview-wrap {
  background: var(--light);
  border-radius: 8px;
  padding: 12px;
}
.listing-preview-help {
  font-size: 11.5px;
  margin-bottom: 10px;
}
.listing-preview-card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  padding: 14px 16px;
}
.listing-preview-head {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 12px;
}
.listing-preview-ref {
  font-size: 11px;
  color: var(--deep);
  font-weight: 600;
  letter-spacing: 0.02em;
}
.listing-preview-title {
  font-size: 14.5px;
  font-weight: 700;
  margin: 2px 0;
}
.listing-preview-cat {
  font-size: 11.5px;
}
.listing-preview-stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
  padding: 12px 0;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.listing-preview-stat-label {
  font-size: 10.5px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.listing-preview-stat-val {
  font-size: 16px;
  font-weight: 700;
  margin-top: 2px;
}
.listing-preview-meta {
  display: flex;
  justify-content: space-between;
  font-size: 11.5px;
  color: var(--muted);
  margin-top: 12px;
}
.listing-preview-bar {
  height: 6px;
  background: var(--line);
  border-radius: 4px;
  margin-top: 6px;
  overflow: hidden;
}
.listing-preview-bar-fill {
  height: 100%;
  background: var(--mint-3);
}
.listing-preview-cta {
  margin-top: 12px;
}
.listing-preview-cta .btn { width: 100%; }

@media (max-width: 720px) {
  .listing-preview-stats { grid-template-columns: 1fr 1fr; }
}
.deal-modal-stock-held {
  font-size: 11px;
}
.deal-modal-close {
  background: none;
  border: 0;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  color: var(--muted);
  padding: 0 2px;
}
.deal-modal-close:hover { color: var(--ink); }

.deal-modal-col-title small {
  margin-left: 4px;
  font-size: 11px;
  font-weight: 400;
}
.deal-modal-amazon-link {
  margin-left: 6px;
  font-size: 11.5px;
}

/* 3.7.e -- three-column body. Each column is a clean white card
 * with a subtle border + breathing room, matching the prototype's
 * deal-detail panel. Rows are STACKED (label on top, value below)
 * like a definition list, not split left/right -- the spec is
 * specifically per the prototype's docs/vantage-portal.html. */
.deal-modal-cols {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 14px;
}
.deal-modal-col {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 18px 18px 16px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}
.deal-modal-col-title {
  margin: 0;
  font-size: 14px;
  font-weight: 700;
  color: var(--ink);
}
.deal-modal-row {
  display: flex;
  flex-direction: column;
  gap: 2px;
  font-size: 13px;
}
.deal-modal-row-label {
  color: var(--muted);
  font-weight: 500;
  font-size: 12.5px;
}
.deal-modal-row-value {
  color: var(--ink);
  font-weight: 700;
  font-size: 14px;
  line-height: 1.3;
}
.deal-modal-row-value .mono { font-weight: 500; color: var(--ink-2); }
.deal-modal-row-value small { font-weight: 400; font-size: 11.5px; margin-left: 4px; }

/* Per-unit profit / ROI / Margin trio sits below the stacked rows.
 * Same column-of-3 grid the prototype uses; labels small + muted
 * above the values. */
.deal-modal-econ-trio {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 12px;
  padding-top: 4px;
  border-top: 1px solid var(--line);
  padding-top: 14px;
}
.deal-modal-econ-trio .econ-label {
  font-size: 10.5px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  font-weight: 600;
}
.deal-modal-econ-trio .econ-value {
  font-size: 15px;
  font-weight: 700;
  color: var(--ink);
  margin-top: 3px;
}

/* "How it works" tinted band -- mint background matches the
 * prototype's confidence-cue at the bottom of the modal. */
.deal-modal-how {
  background: var(--light);
  border: 1px solid var(--mint);
  border-radius: var(--radius);
  padding: 12px 16px;
  font-size: 12.5px;
  color: var(--ink-2);
  line-height: 1.5;
  margin-top: 14px;
}
.deal-modal-how strong { color: var(--ink); }

.deal-modal-footer {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding: 14px 0 22px;
  border-top: 1px solid var(--line);
  margin-top: auto;
  position: sticky;
  bottom: 0;
  background: var(--surface);
}
.deal-modal-identity {
  font-size: 12.5px;
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.deal-modal-actions {
  display: inline-flex;
  gap: 8px;
}

.deal-modal-unavailable {
  padding: 32px 24px;
  text-align: center;
}
.deal-modal-unavailable-title {
  margin: 0 0 6px;
  font-size: 17px;
  font-weight: 700;
}

/* ---------- Order lifecycle stepper (3.6.g) ----------
 * Ported from the prototype's order-progress / op-step / op-dot
 * classes. Used in two sizes: the full labelled stepper inside
 * the order modal, and a compact dot-only variant inside each
 * My Orders row's Status cell. CSS is shared; the compact
 * variant just hides labels + shrinks the dots. */

/* Aligned to prototype values (docs/vantage-portal.html:1291-1366).
 * Six-step lifecycle (3.6.h): Reserved -> Pro-forma issued ->
 * Payment sent -> Payment confirmed -> Dispatched -> Delivered.
 * Premium marker track (2026-07-29): current step is a solid marker
 * with a soft static ring; done steps fill mint with a tick; upcoming
 * are hollow hairline circles. No step numbers, no pulse. */
.order-progress {
  background: var(--light-2);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 18px 20px 16px;
  margin-top: 18px;
}
.op-track {
  display: flex;
  align-items: center;
  gap: 0;
}
.op-step {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  flex: 0 0 auto;
  min-width: 80px;
}
/* Premium lifecycle markers (2026-07-29): calm dots, no numbers (label
   + position carry the meaning), no pulsing. Done = mint tick, current
   = solid navy with a soft static ring, upcoming = hollow hairline. */
.op-dot {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  display: grid;
  place-items: center;
  font-size: 11px;
  font-weight: 700;
  border: 1.5px solid var(--line-2);
  background: var(--surface);
  color: transparent;   /* upcoming: no number */
  transition: all 150ms ease;
}
.op-label {
  font-size: 9.5px;
  font-weight: 600;
  color: var(--muted);
  text-align: center;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  line-height: 1.25;
  max-width: 84px;
}
.op-connector {
  flex: 1 1 auto;
  height: 1.5px;
  background: var(--line-2);
  margin: 0 -4px 24px;
  min-width: 20px;
}

.op-step.done .op-dot { background: var(--mint); color: #fff; border-color: var(--mint); }
.op-step.done .op-label { color: var(--ink-2); }
.op-connector.done { background: var(--mint); }

.op-step.current .op-dot {
  background: var(--navy);
  color: transparent;
  border-color: var(--navy);
  box-shadow: 0 0 0 3px rgba(28, 45, 53, 0.10);
}
.op-step.current .op-label {
  color: var(--navy);
  font-weight: 700;
}
/* Dark: navy current-dot would vanish on the dark surface — flip it to
   the light ink so the active stage still reads, and label to match. */
:root[data-theme="dark"] .op-step.current .op-dot {
  background: var(--ink);
  border-color: var(--ink);
  box-shadow: 0 0 0 3px rgba(233, 238, 240, 0.12);
}
:root[data-theme="dark"] .op-step.current .op-label { color: var(--ink); }

.op-next {
  margin-top: 14px;
  padding: 10px 14px;
  background: var(--surface);
  border-left: 3px solid var(--mint);
  border-radius: 6px;
  font-size: 13px; color: var(--ink-2); line-height: 1.55;
}
.op-next strong { color: var(--navy); }

/* Narrow-viewport wrap (prototype:1427-1431). At six steps the
 * fixed-width row gets tight under 800px; hide connectors and let
 * steps wrap to two rows. */
@media (max-width: 800px) {
  .op-track { flex-wrap: wrap; }
  .op-connector { display: none; }
  .op-step { flex: 1 1 30%; margin-bottom: 8px; }
}

.order-progress-cancelled {
  background: var(--surface);
  color: var(--ink-2);
  border: 1px solid var(--line);
  border-left: 3px solid #c0392b;
  padding: 10px 14px;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 500;
}

/* Inline (per-row) variant for the My Orders table Status cell.
 * Matches the prototype's labelled 18px-dot row stepper
 * (docs/vantage-portal.html:15192-15214). Each step shows its
 * label under the dot so the client can read the journey at a
 * glance without opening the modal. Connector aligns to dot
 * centre via margin-top so it visually bridges the 18px discs.
 * Collapses out of view on narrow viewports (table wraps badly
 * with 6 steps + labels under ~900px). */
.op-track-inline {
  display: inline-flex;
  align-items: flex-start;
  gap: 0;
  margin-top: 6px;
}
.op-track-inline .op-step {
  min-width: 56px;
  gap: 7px;
}
.op-track-inline .op-label {
  display: block;
  font-size: 8.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  line-height: 1.2;
  max-width: 62px;
  color: var(--muted);
}
.op-track-inline .op-step.done .op-label    { color: var(--ink-2); font-weight: 600; }
.op-track-inline .op-step.current .op-label { color: var(--navy);  font-weight: 700; }
.op-track-inline .op-dot {
  width: 14px;
  height: 14px;
  font-size: 8px;
  border-width: 1.5px;
}
.op-track-inline .op-step.done .op-dot    { font-size: 9px; }
.op-track-inline .op-step.current .op-dot { box-shadow: 0 0 0 2.5px rgba(28, 45, 53, 0.09); }
.op-track-inline .op-connector {
  flex: 0 1 auto;
  min-width: 12px;
  height: 1.5px;
  margin: 6px -1px 0;
}

@media (max-width: 900px) {
  .op-track-inline { display: none; }
}

/* ---------- Whose-turn chip (3.6.g) ----------
 * Borderless to match the prototype's chip base. Warn-tinted for
 * client-owes (action pending on them); navy-tinted for vantage-
 * owes (informational, "we'll handle it"). */
.chip-turn-client  { background: #faedd0; color: #8a6616; }
.chip-turn-vantage { background: #e3ebef; color: var(--navy); }
/* 3.7.j -- "Action needed" / "With <counterparty>" chip variants
 * for the viewer-aware whose_turn_chip partial. Action needed
 * uses the danger red (chip-danger inherits the same) sized down
 * a touch so the row doesn't look alarmed -- matches the
 * prototype's font-size:10.5px sizing. The counterparty pill is
 * deliberately quiet -- the row isn't waiting on the viewer. */
.chip-action-needed { font-size: 10.5px; font-weight: 600; }
.chip-counterparty {
  font-size: 10.5px;
  background: #eef2f5;
  color: #6b7782;
  border-color: transparent;
}

/* ---------- Invoice (3.6.h.2 visual fidelity) ----------
 * Ported from the prototype's invoice CSS
 * (docs/vantage-portal.html:1124-1191). The invoice card was
 * shipped at 3.6.g without any invoice-specific styling, so the
 * structure existed but the layout fell back to generic .card +
 * default typography. These classes give it the document-style
 * feel the prototype carries (heavy navy rule under the header,
 * mono numbers, light tabular grid, BACS panel with subtle tint).
 * Re-used unchanged in the PDF print path. */
.invoice-card { padding: 0; overflow: hidden; }
.invoice-sheet {
  background: var(--surface);
  padding: 28px 32px;
  font-size: 13px;
  color: var(--ink);
}
.invoice-head {
  display: flex; justify-content: space-between; align-items: flex-start;
  padding-bottom: 18px;
  border-bottom: 2px solid var(--navy);
}
.invoice-label {
  font-size: 10.5px; color: var(--muted); text-transform: uppercase;
  letter-spacing: 0.08em; font-weight: 700; margin-bottom: 4px;
}
.invoice-number {
  font-size: 22px; font-weight: 700; color: var(--navy); letter-spacing: -0.3px;
}
.invoice-brand { text-align: right; }
.invoice-brand-name { font-size: 15px; color: var(--deep); }
.invoice-brand-meta { color: var(--muted); font-size: 11.5px; }
.invoice-parties {
  display: flex; justify-content: space-between;
  margin: 24px 0;
  gap: 24px;
}
.invoice-parties .right { text-align: right; }
.invoice-table {
  width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 13px;
}
.invoice-table th {
  background: var(--light-2);
  font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--ink-2); font-weight: 700;
  padding: 10px 12px; text-align: left;
  border-bottom: 2px solid var(--navy);
}
.invoice-table td {
  padding: 14px 12px; border-bottom: 1px solid var(--line);
}
.invoice-table .right { text-align: right; }
.invoice-totals {
  margin: 18px 0 0 auto; max-width: 320px;
}
.invoice-totals .row-between {
  display: flex; justify-content: space-between; padding: 4px 0; color: var(--ink-2);
}
.invoice-totals .invoice-grand {
  border-top: 2px solid var(--navy); margin-top: 8px;
  padding-top: 10px; font-size: 16px; color: var(--navy);
}
.invoice-bank {
  background: var(--light-2); border: 1px solid var(--light);
  border-radius: 8px; padding: 14px 16px; margin-top: 24px;
}
.invoice-bank .grid-2 {
  display: grid; grid-template-columns: 1fr 1fr;
  gap: 6px 24px; font-size: 12.5px;
}
.invoice-terms {
  margin-top: 18px; font-size: 11px; line-height: 1.5;
  color: var(--muted);
}
.invoice-actions {
  display: flex; justify-content: flex-end; gap: 8px;
  padding: 12px 18px; border-top: 1px solid var(--line);
  background: var(--light-2);
}

/* PDF download layout (3.6.h.2). Grover renders /orders/:id/invoice.pdf
 * with no chrome -- just the sheet inside a print-sized page. The
 * `print:` rules below also fire when a client uses Cmd-P from the
 * on-screen card, so both "Save as PDF via browser" and "tap
 * Download PDF" paths give a clean document. */
.invoice-pdf-page {
  background: var(--surface);
  margin: 0;
  padding: 0;
}
.invoice-pdf-page .invoice-sheet {
  max-width: 720px;
  margin: 0 auto;
  padding: 0;
  font-size: 12px;
}
@media print {
  body { background: var(--surface); }
  .sidebar, .topbar, .order-modal-head, .invoice-actions,
  .order-modal-loading, .order-modal-error,
  nav, .flash, .attention-banner { display: none !important; }
  .invoice-card { box-shadow: none; border: none; }
  .invoice-sheet { padding: 0; }
}

/* ---------- Order modal (3.6.g, 3.6.h visual fidelity) ----------
 * Sibling dialog to .deal-modal + .reserve-modal. The prototype's
 * order ticket modal uses modal-lg (1100px) -- biggest body in
 * the portal because it carries the 3-card grid plus invoice +
 * tracking + (future) ticket thread + notes. Aligned to prototype
 * values: 14px radius, 90vh cap, 17px title with -0.2px tracking,
 * 22px body padding. */

.order-modal {
  width: min(1100px, calc(100vw - 32px));
  max-height: 90vh;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 0;
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.order-modal::backdrop { background: rgba(12, 31, 41, 0.45); }
.order-modal-body {
  max-height: 90vh;
  overflow-y: auto;
  padding: 0;
}

.order-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 22px;
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  background: var(--surface);
  z-index: 1;
}
.order-modal-title {
  margin: 0;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.2px;
}
.order-modal-close {
  background: none;
  border: 0;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  color: var(--muted);
  padding: 0 2px;
}
.order-modal-close:hover { color: var(--ink); }

.order-modal-body-inner {
  padding: 22px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.order-modal-loading,
.order-modal-error {
  padding: 28px;
  text-align: center;
  font-size: 14px;
}

.order-modal-chips {
  display: inline-flex;
  flex-direction: column;
  gap: 4px;
  align-items: flex-end;
}

.order-status-cell {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  align-items: center;
}

/* ---------- My Orders attention banner (3.6.g) ---------- */
.attention-banner {
  background: rgba(226, 167, 59, 0.12);
  border: 1px solid rgba(226, 167, 59, 0.35);
  border-left: 3px solid var(--warn);
  border-radius: var(--radius);
  padding: 10px 14px;
  font-size: 13px;
  color: var(--ink);
}
.attention-banner strong { color: #6e4d10; }

/* ---------- Payment countdown banner (3.6.g) ---------- */
.payment-countdown {
  background: var(--light-2);
  border: 1px solid var(--line);
  border-left: 3px solid var(--navy);
  border-radius: var(--radius);
  padding: 12px 16px;
  font-size: 13px;
  color: var(--ink);
  transition: background-color 200ms ease, border-color 200ms ease;
}
.payment-countdown-head { font-size: 13px; }
.payment-countdown-body { margin-top: 4px; color: var(--ink-2); }
.payment-countdown.payment-countdown-warn {
  background: rgba(226, 167, 59, 0.12);
  border-left-color: var(--warn);
}
.payment-countdown.payment-countdown-urgent {
  background: rgba(216, 88, 88, 0.10);
  border-left-color: var(--danger);
  color: #8a2a2a;
}
.payment-countdown.payment-countdown-expired {
  background: #fadcdc;
  border-color: #f1c9c9;
  color: #8e2e2e;
}

/* ---------- Clickable table rows (3.6.g) ---------- */
.table tbody tr.clickable {
  cursor: pointer;
}
.table tbody tr.clickable:focus-visible {
  outline: 2px solid var(--mint);
  outline-offset: -2px;
}

/* ---------- Reserve modal (3.6.f) ----------
 * Sibling dialog to .deal-modal. Narrower than the three-column
 * detail (the reserve form is a single column), but otherwise the
 * same native-<dialog> treatment + Stimulus-driven open/close. */

.reserve-modal {
  width: min(720px, calc(100vw - 32px));
  max-height: calc(100vh - 32px);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0;
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.reserve-modal::backdrop {
  background: rgba(17, 42, 54, 0.55);
}
.reserve-modal-body {
  max-height: calc(100vh - 32px);
  overflow-y: auto;
  padding: 0;
}

.reserve-modal-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 18px 22px 14px;
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  background: var(--surface);
  z-index: 1;
}
.reserve-modal-title {
  margin: 0;
  font-size: 17px;
  font-weight: 700;
  letter-spacing: -0.2px;
}
.reserve-modal-close {
  background: none;
  border: 0;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  color: var(--muted);
  padding: 0 2px;
}
.reserve-modal-close:hover { color: var(--ink); }

.reserve-modal-body-inner {
  padding: 18px 22px 22px;
}

.reserve-modal-loading,
.reserve-modal-error {
  padding: 28px;
  text-align: center;
  font-size: 14px;
}

/* Hover affordance on cards now that they're clickable (3.6.d).
 * Pointer + focus ring keep the role="button" promise visible. */
.market-card {
  cursor: pointer;
}
.market-card:focus-visible {
  outline: 2px solid var(--mint);
  outline-offset: 2px;
}

@media (max-width: 800px) {
  .deal-modal-cols { grid-template-columns: 1fr; }
  .deal-modal-head { grid-template-columns: 1fr auto; }
  .deal-modal-head-right { grid-column: 1 / -1; align-items: flex-start; text-align: left; }
}

.market-card-cta {
  /* Flex column so the disabled-reserve button can sit above an
   * explainer note (visible-text instead of a flaky native
   * tooltip). align-items: stretch (the flex column default)
   * makes the button take full width. */
  display: flex;
  flex-direction: column;
  gap: 4px;
  margin-top: 4px;
}

.market-card-cta .btn {
  text-align: center;
}

.market-card-cta-note {
  font-size: 11px;
  text-align: center;
}

.btn.disabled {
  opacity: 0.55;
  cursor: not-allowed;
  pointer-events: none;
}

/* CEO "view as": full-width read-only banner pinned above the shell,
   with a one-click exit. Gold so it reads as a distinct system state,
   not a normal flash. */
.impersonation-bar {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 16px;
  flex-wrap: wrap;
  padding: 9px 20px;
  background: #e0b878;
  color: #3a2708;
  font-size: 13px;
  font-weight: 600;
  position: sticky;
  top: 0;
  z-index: 100;
}
.impersonation-bar-text {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
.impersonation-bar-exit form,
.impersonation-bar-exit {
  margin: 0;
}
.impersonation-bar-exit button,
button.impersonation-bar-exit {
  background: #3a2708;
  color: #fdf3e7;
  border: 0;
  border-radius: 6px;
  padding: 5px 14px;
  font-size: 12.5px;
  font-weight: 600;
  cursor: pointer;
}
.impersonation-bar-exit button:hover,
button.impersonation-bar-exit:hover {
  background: #241905;
}
/* Foot-of-sidebar "View as" link -- matches the sign-out row. The menu
   sits on a dark navy background (--navy-2), so it needs LIGHT text +
   a light hover tint, exactly like .side-account-signout. (It used to
   use --ink / a black hover overlay -- both invisible on dark, which
   left the row barely legible.) */
.side-account-viewas {
  display: flex; align-items: center; gap: 9px; width: 100%;
  color: #cfdde1; font-size: 13px; font-weight: 500;
  text-decoration: none;
  padding: 8px; border-radius: 7px;
}
.side-account-viewas:hover { background: rgba(255, 255, 255, .06); color: #fff; }
/* Quiet danger variant: ghost weight, red intent (client deactivate). */
.btn-danger-ghost {
  color: var(--danger);
  border-color: color-mix(in srgb, var(--danger) 45%, transparent);
}
.btn-danger-ghost:hover {
  background: #fdecec;
}

/* ---------- Priority banner (Access viewers only) ----------------- */

.priority-banner {
  border-left: 3px solid var(--gold);
  background: #fdf7e7;
  padding: 14px 16px;
}

.priority-banner-body {
  display: flex;
  align-items: center;
  gap: 12px;
}

.priority-banner-icon {
  font-size: 22px;
  color: var(--gold);
  flex: 0 0 auto;
}

.priority-banner-text {
  flex: 1;
  font-size: 13px;
  color: var(--ink);
}

.priority-banner-cta {
  flex: 0 0 auto;
  white-space: nowrap;
}

/* ---------- Kanban (staff Deals Pipeline) -------------------------- *
 *
 * Six-column horizontal flow used by the staff deal-review workflow.
 * Mirrors the prototype's kanban (~docs/vantage-portal.html:12527,
 * .kanban / .kanban-col / .kanban-card). Cards are link_to wrappers
 * (whole-card click into review). Column width tuned for desktop
 * staff use -- narrower than the marketplace cards because we want
 * all six visible at typical staff laptop widths without horizontal
 * scroll.
 */

.kanban {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 12px;
  margin-top: 8px;
}

.kanban-col {
  background: var(--light-2);
  border-radius: 8px;
  padding: 10px;
  min-height: 200px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.kanban-col-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  font-weight: 600;
  color: var(--ink-2);
  padding: 4px 6px 8px;
  border-bottom: 1px solid var(--line);
  margin-bottom: 2px;
}

/* Per-stage money under each kanban column header (H, 2026-08-04):
   where the value is sitting matters as much as how much it is. */
.kanban-col-metrics {
  display: flex;
  justify-content: space-between;
  gap: 6px;
  padding: 5px 6px 7px;
  font: 600 11px var(--font);
  color: var(--muted);
  border-bottom: 1px solid var(--line);
  margin-bottom: 6px;
  white-space: nowrap;
}
.kanban-col-metric-mint { color: var(--deep); }

/* Pipeline money strip: Vantage's own economics on the left, what the
   pipeline puts in front of clients on the right. */
.pipeline-metrics {
  display: grid;
  grid-template-columns: 1.35fr 1fr;
  gap: 20px;
  padding: 14px 18px;
}
.pipeline-metrics-side + .pipeline-metrics-side {
  border-left: 1px solid var(--line);
  padding-left: 20px;
}
.pipeline-metrics-side-label {
  font: 700 10px var(--font-display);
  letter-spacing: 1.1px;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
}
.pipeline-metrics-lens {
  font-weight: 600;
  letter-spacing: .4px;
  text-transform: none;
  color: var(--deep);
  cursor: help;
  border-bottom: 1px dotted var(--line);
  margin-left: 6px;
}
.pipeline-metrics-row { display: flex; gap: 26px; flex-wrap: wrap; }
.pipeline-metric-label {
  font-size: 11px;
  color: var(--muted);
  margin-bottom: 2px;
}
.pipeline-metric-value {
  font: 700 19px var(--font);
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.pipeline-metric-mint { color: var(--deep); }
.pipeline-metric-sub {
  font: 600 12px var(--font);
  color: var(--muted);
  margin-left: 4px;
}
@media (max-width: 820px) {
  .pipeline-metrics { grid-template-columns: 1fr; gap: 14px; }
  .pipeline-metrics-side + .pipeline-metrics-side {
    border-left: 0;
    border-top: 1px solid var(--line);
    padding-left: 0;
    padding-top: 14px;
  }
  .pipeline-metrics-row { gap: 18px; }
  .pipeline-metric-value { font-size: 17px; }
}

.kanban-col-count {
  background: var(--navy);
  color: #fff;
  font-size: 10.5px;
  font-weight: 700;
  padding: 1px 7px;
  border-radius: 6px;
  min-width: 18px;
  text-align: center;
}

.kanban-col-empty {
  font-size: 12px;
  color: var(--muted);
  font-style: italic;
  padding: 14px 6px;
  text-align: center;
}

.kanban-card {
  display: block;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 10px 12px;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  box-shadow: 0 1px 0 rgba(17, 42, 54, 0.04);
  transition: border-color 0.12s ease, box-shadow 0.12s ease,
              transform 0.06s ease;
}

.kanban-card:hover {
  border-color: var(--navy);
  box-shadow: 0 2px 6px rgba(17, 42, 54, 0.08);
}

.kanban-card:active {
  transform: translateY(1px);
}

/* Attention treatment for cards in received -- green
   left border + a tinted background so the pipeline kanban surfaces
   what needs triage at a glance. Mirrors the prototype's
   .attention-row styling at vantage-portal.html:2659-2668. */
.kanban-card.attention-row {
  border-left: 3px solid var(--mint);
  background: var(--light);
}

/* Right-side chip cluster on each card: source ("Portal"), threshold
   ("✓ threshold" / "new" / "check"). Flex so chips sit side-by-side
   and wrap as needed. */
.kanban-card-chips {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
}
.kanban-card-chips .chip {
  font-size: 9.5px;
  padding: 1px 6px;
}

/* Gold-tinted source chip -- shown on every card today because the
   only Deal creator on the live app is the supplier portal. Distinct
   colour from chip-info so it doesn't conflict with category chips. */
.chip-portal {
  background: #fff5d8;
  color: #806a18;
  border: 1px solid #f2dba0;
}

/* Staff Deals Pipeline toolbar -- search box + category dropdown +
   Export CSV. Sizes match the prototype's inline styles at
   vantage-portal.html:12515-12519. */
.staff-deals-search   { width: 260px; max-width: 100%; }
.staff-deals-category { width: 180px; max-width: 100%; }

/* ---------- Supplier Submissions (3.7.l.3) ----------
   Triage queue mirroring the prototype's supplier_submissions_staff
   view at vantage-portal.html:21052-21271. Stat cards are server-
   side filter links; the active one tints `deep` / `navy`. Search
   + supplier dropdown sit in a toolbar row above the table. */
.staff-subs-intro {
  font-size: 12.5px;
  line-height: 1.55;
  max-width: 780px;
}
.staff-subs-toolbar { gap: 12px; flex-wrap: wrap; }
.staff-subs-toolbar-inputs { flex-wrap: wrap; }
.staff-subs-search          { width: 260px; max-width: 100%; }
.staff-subs-supplier-filter { width: 240px; max-width: 100%; }
.staff-subs-count {
  font-size: 12.5px;
  align-self: center;
}

.staff-subs-empty       { text-align: center; }
.staff-subs-empty-sub   { margin-top: 6px; font-size: 13px; }
.staff-subs-table-card  { padding: 0; overflow: hidden; }
.staff-subs-table       { margin: 0; }
.staff-subs-th-sub      { font-weight: 500; font-size: 10px; }

.staff-subs-row         { cursor: pointer; }
.staff-subs-row:hover   { background: var(--light); }

.staff-subs-product-title { font-weight: 600; }
.staff-subs-product-sub   { font-size: 11.5px; }
.staff-subs-asin-chip {
  font-size: 9.5px;
  padding: 1px 6px;
  vertical-align: middle;
  margin-left: 4px;
}
.staff-subs-asin-pending  { color: #b88317; }
.staff-subs-supplier-notes {
  font-size: 11.5px;
  line-height: 1.5;
  margin-top: 6px;
  padding: 6px 10px;
  background: #fdf6df;
  border-left: 2px solid #c79a1c;
  border-radius: 4px;
  color: var(--ink);
  font-style: italic;
  max-width: 420px;
}
.staff-subs-supplier-name    { font-weight: 600; }
.staff-subs-supplier-contact { font-size: 11.5px; }
.staff-subs-cost-sub         { font-size: 11.5px; }

.staff-subs-threshold-pill {
  display: inline-block;
  font-size: 9.5px;
  font-weight: 700;
  padding: 2px 7px;
  border-radius: 4px;
  margin-bottom: 5px;
}
.staff-subs-threshold-pass {
  background: #e7f5ee;
  color: var(--deep);
  border: 1px solid var(--mint);
}
.staff-subs-threshold-fail {
  background: #fdf3e7;
  color: #9a6a1a;
  border: 1px solid #e0b878;
}
.staff-subs-roi      { font-size: 12px; line-height: 1.5; }
.staff-subs-roi-sub  { font-size: 11.5px; line-height: 1.5; }
.staff-subs-not-scored { font-size: 11.5px; }

.staff-subs-reject-reason {
  font-size: 11px;
  margin-top: 4px;
  line-height: 1.4;
  max-width: 200px;
}
.staff-subs-action-cell { white-space: nowrap; }
.staff-subs-reject-btn:hover { background: #fdf3f3; color: var(--danger); }

/* Reject-submission dialog. Same native <dialog> approach as the
   deal-modal -- centred, scrim background. Form posts to the
   existing /staff/deals/:id/reject endpoint so behaviour stays
   consistent with the kanban + show-page reject affordances. */
.reject-submission-modal {
  width: min(540px, 96vw);
  max-height: 90vh;
  border: none;
  border-radius: 6px;
  padding: 0;
  box-shadow: 0 24px 60px rgba(17, 42, 54, 0.32);
}
.reject-submission-modal::backdrop {
  background: rgba(17, 42, 54, 0.45);
}
.reject-submission-modal-head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 14px 18px;
  border-bottom: 1px solid var(--line);
}
.reject-submission-modal-head h3 {
  margin: 0;
  font-size: 14.5px;
}
.reject-submission-modal-close {
  background: transparent;
  border: 0;
  font-size: 22px;
  cursor: pointer;
  color: var(--muted);
  line-height: 1;
}
.reject-submission-modal-body { padding: 16px 18px; }
.reject-submission-modal-intro {
  font-size: 12.5px;
  line-height: 1.55;
  margin: 0 0 14px;
}
.reject-submission-modal-hint {
  font-size: 11.5px;
  line-height: 1.5;
  margin-top: 8px;
}
.reject-submission-modal-footer {
  display: flex;
  justify-content: flex-end;
  gap: 8px;
  padding: 12px 18px;
  border-top: 1px solid var(--line);
}

.kanban-card-top {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 6px;
  margin-bottom: 6px;
  /* Long category chips (e.g. "Stationery & Office Supplies") can
   * exceed the card width; allow the chip to wrap to its own row
   * rather than overflow the card boundary. */
  flex-wrap: wrap;
  row-gap: 4px;
}

/* Cap the chip width so even a single wrapped chip can't exceed
 * the card's content box; allow internal text to wrap if the
 * category name is very long. */
.kanban-card-top .chip {
  max-width: 100%;
  white-space: normal;
  line-height: 1.3;
}

.kanban-card-ref {
  font-size: 10.5px;
  color: var(--muted);
  font-weight: 600;
  letter-spacing: 0.03em;
}

.kanban-card-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--ink);
  line-height: 1.35;
  margin-bottom: 6px;
  /* Two-line clamp so taller titles don't push cards out of alignment */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.kanban-card-meta {
  font-size: 11.5px;
  display: flex;
  align-items: center;
  gap: 4px;
  flex-wrap: wrap;
}

/* Economics strip on the card face (cost / profit / ROI / margin)
   so the CEO can triage a column without opening each modal. */
.kanban-card-econ {
  font-size: 11px;
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin: 4px 0 2px;
  color: var(--muted);
}
.kanban-card-econ strong { color: var(--deep); font-weight: 700; }
.kanban-card-econ .econ-pos strong { color: #1d7a4f; }
.kanban-card-econ .econ-neg strong { color: #a13030; }

/* Trading hero: segmented period tabs + headline P&L + 30-day SVG
   chart, replacing the old ranks of identical cards. The tabs ARE
   the drill-down: a period scopes the hero figures and the ledger. */
.trading-tabs {
  display: flex;
  gap: 4px;
  padding: 4px;
  background: var(--light);
  border-radius: 6px;
  width: fit-content;
  margin-bottom: 18px;
  flex-wrap: wrap;
}
.trading-tab {
  padding: 7px 16px;
  border-radius: 7px;
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
}
.trading-tab:hover { color: var(--deep); }
.trading-tab-active {
  background: var(--surface);
  color: var(--deep);
  box-shadow: 0 1px 4px rgba(28, 45, 53, 0.12);
}
.trading-tab-booked { border-left: 1px solid var(--line); border-radius: 0 7px 7px 0; }
.trading-hero-body {
  display: grid;
  grid-template-columns: minmax(220px, 1fr) minmax(0, 2fr);
  gap: 28px;
  align-items: center;
}
@media (max-width: 1023px) {
  .trading-hero-body { grid-template-columns: 1fr; }
}
.trading-hero-figures {
  display: flex;
  flex-direction: column;
  gap: 18px;
}
.trading-figure-value {
  /* Jost, not Syne — this is the headline P&L, a figure not a wordmark. */
  font-family: var(--font);
  font-variant-numeric: tabular-nums;
  font-size: 34px;
  font-weight: 700;
  line-height: 1.15;
  color: var(--deep);
}
.trading-figure-profit { color: #1d7a4f; }
.trading-chart-svg { width: 100%; height: auto; display: block; }
.chart-band        { fill: rgba(53, 197, 140, 0.10); }
.chart-grid        { stroke: var(--line); stroke-dasharray: 3 4; stroke-width: 1; }
.chart-axis        { stroke: var(--line); stroke-width: 1; }
.chart-bar-revenue { fill: var(--deep); opacity: 0.82; }
.chart-bar-profit  { fill: #35c58c; }
.chart-label       { font-size: 10px; fill: var(--muted); font-family: inherit; }
.chart-empty       { font-size: 12px; fill: var(--muted); font-family: inherit; }
.trading-chart-legend {
  display: flex;
  gap: 14px;
  font-size: 11.5px;
  color: var(--muted);
  margin-top: 6px;
}
.chart-dot {
  display: inline-block;
  width: 8px; height: 8px;
  border-radius: 2px;
  margin-right: 5px;
}
.chart-dot-revenue { background: var(--deep); opacity: 0.82; }
.chart-dot-profit  { background: #35c58c; }
.trading-booked-line {
  display: block;
  margin-top: 10px;
  font-size: 12.5px;
  text-decoration: none;
  color: var(--muted);
}
.trading-booked-line strong { color: var(--deep); }
.trading-booked-line:hover .trading-booked-cta { text-decoration: underline; }
.trading-booked-cta { color: #1d7a4f; font-weight: 600; }
/* Hero figures are buttons that pop the attribution breakdown modal. */
button.trading-figure {
  display: block;
  width: 100%;
  color: inherit;
  text-align: left;
  font: inherit;
  background: none;
  border: 0;
  cursor: pointer;
  border-radius: 6px;
  padding: 6px 8px;
  margin: -6px -8px;
}
button.trading-figure:hover { background: var(--light); }
/* Chart day columns: clickable bars + selected-day marker. */
a.chart-day { cursor: pointer; }
a.chart-day:hover .chart-bar-revenue { opacity: 1; }
.chart-day-marker {
  fill: none;
  stroke: #35c58c;
  stroke-width: 1.5;
}
/* Attribution breakdown modal (native <dialog>, mirrors .order-modal). */
.trading-modal {
  width: min(980px, calc(100vw - 32px));
  max-height: 88vh;
  border: 1px solid var(--line);
  border-radius: 6px;
  padding: 0;
  background: var(--surface);
  color: var(--ink);
  box-shadow: var(--shadow-lg);
  overflow: hidden;
}
.trading-modal::backdrop { background: rgba(12, 31, 41, 0.45); }
.trading-modal-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  padding: 16px 22px;
  border-bottom: 1px solid var(--line);
  background: var(--surface);
}
.trading-modal-title {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
  letter-spacing: -0.2px;
}
.trading-modal-title small {
  display: block;
  margin-top: 2px;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--muted);
}
.trading-modal-close {
  background: none;
  border: 0;
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  color: var(--muted);
  padding: 2px;
}
.trading-modal-close:hover { color: var(--ink); }
.trading-modal-body {
  padding: 18px 22px 22px;
  overflow-y: auto;
  max-height: calc(88vh - 70px);
}

.trading-breakdown-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 24px;
}
@media (max-width: 1023px) {
  .trading-breakdown-grid { grid-template-columns: 1fr; }
}
.trading-breakdown-heading {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 6px;
}
.trading-breakdown-sub { font-size: 11px; }
.trading-day-clear {
  color: #a13030;
  font-weight: 600;
  text-decoration: none;
  margin-left: 4px;
}
.trading-day-clear:hover { text-decoration: underline; }

/* Narrower kanban inside the staff content column -- below ~1280px
 * the six columns crowd; let them scroll horizontally rather than
 * stacking vertically (stacking would defeat the visual "pipeline"). */
@media (max-width: 1280px) {
  .kanban {
    grid-template-columns: repeat(6, minmax(180px, 1fr));
    overflow-x: auto;
    padding-bottom: 6px;
  }
}

/* ---------- Reject affordance (staff deal review) ----------------- *
 *
 * <details>/<summary> based collapsible. The summary doubles as the
 * trigger button (Reject this deal…); opening exposes a textarea +
 * submit form. Styled to look attached to the card it sits inside.
 */

.reject-affordance {
  margin-top: 14px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
}

.reject-affordance > summary {
  list-style: none;
  cursor: pointer;
}

.reject-affordance > summary::-webkit-details-marker {
  display: none;
}

/* ---------- Utility ---------- */

.row         { display: flex; align-items: center; gap: 8px; }
.row-between { display: flex; align-items: center; justify-content: space-between; }
.gap-sm { gap: 8px; }   .gap-md { gap: 14px; }   .gap-lg { gap: 20px; }
.mt-sm { margin-top: 10px; }   .mt-md { margin-top: 18px; }   .mt-lg { margin-top: 28px; }
.mb-sm { margin-bottom: 10px; } .mb-md { margin-bottom: 18px; }
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
.grid-3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px; }
.muted  { color: var(--muted); }
.mono   { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
.right  { text-align: right; }
.center { text-align: center; }
.divider { height: 1px; background: var(--line); margin: 14px 0; }

/* ---------- Client Account view (3.7.a) ---------- */
.account-section-label {
  /* Console language (2026-07-19): the Syne engraved eyebrow. */
  font-family: "Syne", sans-serif;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  margin: 6px 0 8px;
  color: var(--muted);
}
.account-section-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  margin-top: 14px;
}
.account-same-as {
  font-size: 12px;
  color: var(--ink);
  display: flex;
  align-items: center;
  gap: 6px;
  cursor: pointer;
}
.account-save-bar {
  display: flex;
  justify-content: flex-end;
  margin-top: 16px;
  padding: 16px 0 0;
  border-top: 1px solid var(--line);
}
.account-plan-name {
  font-size: 22px;
  font-weight: 700;
  color: var(--navy);
  margin-top: 2px;
}
.account-plan-name.priority { color: var(--gold-deep); }
.account-plan-mrr {
  font-size: 18px;
  font-weight: 600;
}
.account-upgrade-cta,
.account-upgrade-pending {
  padding: 14px 16px;
  border-left: 3px solid var(--mint);
  background: var(--light);
  margin-top: 12px;
}
.account-upgrade-cta.priority,
.account-upgrade-pending.priority {
  background: var(--gold-soft);
  border-left-color: var(--gold);
}
.account-upgrade-cta-title,
.account-upgrade-pending-title {
  font-weight: 700;
  font-size: 14px;
  color: var(--deep);
}
.account-upgrade-cta.priority .account-upgrade-cta-title,
.account-upgrade-pending.priority .account-upgrade-pending-title {
  color: var(--gold-deep);
}
.account-upgrade-cta-pitch {
  font-size: 12px;
  margin-top: 4px;
  line-height: 1.5;
}
.account-upgrade-pending-sub {
  font-size: 12px;
  margin-top: 3px;
}
.card.priority-account {
  background: linear-gradient(135deg, var(--gold-soft), transparent 70%);
  border-color: var(--gold);
}

/* ---------- Phase 3.7.a extensions ---------- */
.account-section-copy {
  font-size: 12.5px;
  line-height: 1.55;
  margin-top: 4px;
}
.account-paperwork-pointer {
  align-items: center;
}
.account-paperwork-pointer-title {
  font-weight: 600;
  color: var(--ink);
  margin-bottom: 4px;
}
.account-paperwork-pointer-copy {
  font-size: 12.5px;
}

/* My documents upload */
.account-upload-form { margin: 0; }
.account-upload-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); border: 0;
}
.account-upload-form label.btn { cursor: pointer; margin: 0; }
.account-uploads-table { margin-top: 10px; }
.account-uploads-table .btn { margin: 0; }
.account-uploads-table form { margin: 0; }

/* Category alert chips */
.account-cat-alert-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 10px;
}
.account-cat-alert-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 6px 4px 10px;
  border-radius: 6px;
  background: var(--light-2);
  border: 1px solid var(--line);
  font-size: 12.5px;
  color: var(--ink);
}
.account-cat-alert-chip-muted {
  background: #faedd0;
  border-color: #f0d090;
  color: #8a6616;
}
.account-cat-alert-x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 18px; height: 18px;
  border-radius: 9px;
  border: none;
  background: rgba(17, 42, 54, 0.10);
  color: var(--ink);
  cursor: pointer;
  font-size: 13px;
  line-height: 1;
  padding: 0;
}
.account-cat-alert-x:hover {
  background: rgba(17, 42, 54, 0.20);
}
.account-cat-alert-chip form { display: inline; margin: 0; }
.account-cat-alert-mute-label {
  font-size: 11.5px;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin: 14px 0 6px;
}

/* Email preferences */
.email-pref-row {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: 16px;
  align-items: start;
  padding: 10px 0;
  border-top: 1px solid var(--line);
}
.email-pref-row:first-of-type { border-top: none; padding-top: 6px; }
.email-pref-title {
  font-weight: 600;
  color: var(--ink);
  font-size: 13.5px;
}
.email-pref-copy {
  font-size: 11.5px;
  margin-top: 2px;
  line-height: 1.5;
}
.email-pref-footnote {
  font-size: 11px;
  line-height: 1.5;
  margin-top: 10px;
}
.email-pref-saved-indicator {
  display: inline-block;
  margin-left: 8px;
  color: #1f6a48;
  font-weight: 600;
}
.account-subscription-footnote { font-size: 12px; }
.account-activity-profit-note { font-size: 11px; margin-top: 3px; }

/* ---------- Phase 3.7.d: client Dashboard ----------
 *
 * Layout primitives only -- the stat-card / panel-grid-2 / card
 * styles all come from earlier phases. View-tier preview hero,
 * mint opportunity pills, and the welcome lock-banner are the
 * Dashboard-specific bits.
 */

/* View-tier preview hero card. Mint left border + subtle wash. */
.dashboard-preview-hero {
  border-left: 3px solid var(--mint);
  background: linear-gradient(to bottom right, rgba(111, 198, 160, 0.08), transparent 60%);
}
.dashboard-preview-hero-eyebrow {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--deep);
  font-weight: 600;
}
.dashboard-preview-hero-headline {
  margin: 6px 0 8px;
  font-size: 26px;
  color: var(--navy);
  letter-spacing: -0.3px;
}
.dashboard-preview-hero-sub {
  font-size: 13px;
  line-height: 1.55;
  margin-bottom: 14px;
  max-width: 620px;
}
.dashboard-preview-pending {
  cursor: default;
  opacity: 0.85;
}
.dashboard-preview-list {
  padding-left: 18px;
  line-height: 1.9;
  font-size: 13px;
  color: var(--ink);
  margin: 6px 0 0;
}
.dashboard-preview-list li { list-style: none; }

/* "Your next move" all-clear card -- mint border + check icon. */
.whats-next-allclear {
  background: var(--light);
  border-color: var(--mint);
  display: flex;
  gap: 12px;
  align-items: center;
  padding: 14px 18px;
}
.whats-next-allclear-icon {
  font-size: 24px;
  color: var(--deep);
  flex-shrink: 0;
}
.whats-next-allclear-body { flex: 1; }
.whats-next-allclear-title {
  font-weight: 700;
  color: var(--ink);
  margin-bottom: 2px;
}
.whats-next-allclear-sub { font-size: 12.5px; }

/* "Your next move" populated card -- header + tone-coloured rows. */
/* "Your team's next move" — premium sweep (2026-07-29). Pastel
   greeting-card rows retired for clean hairline rows whose urgency is
   carried by a single thin LEFT accent bar; the ★/✕/✓ emoji is gone. */
.whats-next-card { padding: 0; }
.whats-next-header {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  padding: 13px 16px;
  border-bottom: 1px solid var(--line);
}
.whats-next-title {
  font-weight: 700;
  color: var(--muted);
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.09em;
}
.whats-next-priority-count { font-size: 11px; color: var(--muted); }
.whats-next-list {
  display: flex;
  flex-direction: column;
}
.whats-next-row {
  display: flex;
  gap: 12px;
  align-items: center;
  padding: 15px 16px;
  border-bottom: 1px solid var(--line);
  border-left: 2px solid transparent;
  background: transparent;
  color: var(--ink);
}
.whats-next-list .whats-next-row:last-child { border-bottom: 0; }
.whats-next-row.tone-danger { border-left-color: #c0392b; }
.whats-next-row.tone-warn   { border-left-color: #c98a2b; }
.whats-next-row.tone-mint   { border-left-color: var(--mint); }
.whats-next-row.tone-navy   { border-left-color: var(--muted); }
.whats-next-row:hover { background: var(--surface-2); }
/* The tone emoji goes — the accent bar carries the tone. Real order
   thumbnails stay (they're signal, not decoration). */
.whats-next-row-icon { display: none; }
/* Product thumb replaces the tone icon when the move is about an
 * order -- the deal's image makes the row instantly recognisable. */
.whats-next-row-thumb {
  width: 44px;
  height: 44px;
  flex-shrink: 0;
  object-fit: contain;
  border-radius: 8px;
  background: var(--surface);
  border: 1px solid rgba(0, 0, 0, 0.08);
}
.whats-next-row-body { flex: 1; min-width: 0; }
.whats-next-row-product {
  font-weight: 700;
  font-size: 13.5px;
  letter-spacing: -0.1px;
}
.whats-next-row-headline {
  font-weight: 600;
  font-size: 13.5px;
}
/* Age of the underlying event (H, 2026-08-01) — hover shows the exact
   date/time. Muted so the headline stays the read. */
.whats-next-row-age {
  font-weight: 500;
  font-size: 10.5px;
  color: var(--muted-ink, #8a8a8a);
  background: color-mix(in srgb, var(--navy) 6%, transparent);
  border-radius: 999px;
  padding: 1px 7px;
  margin-left: 5px;
  white-space: nowrap;
  vertical-align: 1px;
}
.whats-next-row-product + .whats-next-row-headline {
  font-weight: 500;
  font-size: 12.5px;
  margin-top: 1px;
}
.whats-next-row-sub {
  font-size: 12px;
  line-height: 1.4;
  margin-top: 2px;
  color: var(--muted);
}
.whats-next-row-action {
  background: var(--surface);
  border: 1px solid var(--line-2);
  color: var(--ink);
  flex-shrink: 0;
}
.whats-next-row-action:hover { border-color: var(--navy); }
/* Action button + dismiss × grouped at the row's right edge. */
.whats-next-row-actions {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  flex-shrink: 0;
}
/* Dismiss × — currentColor so it reads on both the tone-tinted rows
   (production) and the hairline refined rows (staging). Muted until
   hover, like the notification bell's clear affordance. */
.whats-next-row-dismiss {
  border: 0;
  background: transparent;
  color: currentColor;
  cursor: pointer;
  font-size: 18px;
  line-height: 1;
  width: 26px;
  height: 26px;
  border-radius: 6px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  opacity: 0.45;
  transition: opacity 0.15s ease, background 0.15s ease;
}
.whats-next-row-dismiss:hover {
  opacity: 1;
  background: color-mix(in srgb, currentColor 12%, transparent);
}
.whats-next-overflow {
  font-size: 11.5px;
  text-align: center;
  margin: 0;
  padding: 12px 16px;
  border-top: 1px solid var(--line);
}

/* Priority variant -- gold-trim treatment matching the prototype
 * (docs/vantage-portal.html:1063-1112). Deep gold-ink gradient
 * with a subtle diagonal shine overlay, like polished metal.
 * Reserved for Priority subscribers; never used elsewhere. */
.lock-banner.priority {
  background: linear-gradient(115deg,
    #3a2a14 0%,
    #5a4220 30%,
    #8b6a2e 60%,
    #a78239 85%,
    #b8923f 100%);
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
  position: relative;
  overflow: hidden;
}
.lock-banner.priority::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0; bottom: 0;
  background: linear-gradient(105deg,
    transparent 0%,
    rgba(255, 240, 200, 0.06) 30%,
    rgba(255, 240, 200, 0.14) 45%,
    rgba(255, 240, 200, 0.06) 60%,
    transparent 100%);
  pointer-events: none;
}
.lock-banner.priority > * {
  position: relative;
  z-index: 1;
}

/* Tier chips rendered INSIDE the lock-banner. Default mint pill
 * reads on the dark navy/deep Access surface (prototype value:
 * docs/vantage-portal.html:1120-1122 uses var(--mint) verbatim);
 * the Priority variant uses a metallic gold pill so it stamps on
 * the dark gold backdrop. */
.lock-banner .chip-navy,
.lock-banner .chip:not(.chip-priority):not(.chip-warn):not(.chip-ok):not(.chip-info) {
  background: var(--mint);
  color: var(--navy);
  border: none;
}
.lock-banner.priority .chip-priority {
  background: linear-gradient(135deg, var(--gold-bright) 0%, var(--gold) 100%);
  color: var(--gold-ink);
  border: 1px solid var(--gold);
  text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.25);
}

/* Chip-priority outside the banner (e.g. on the marketplace card)
 * keeps the softer cream pill. Inside the lock-banner the rule
 * above overrides this. */
.chip-priority {
  background: var(--gold-soft);
  color: var(--gold-deep);
}

/* Mint opportunity pill cards. Mirrors the marketplace "potential
 * profit" pill so the visual language is consistent across surfaces. */
.dashboard-opportunity {
  background: linear-gradient(135deg, var(--light) 0%, #fff 100%);
  border-color: var(--mint);
  padding: 16px 18px;
  display: flex;
  gap: 14px;
  align-items: center;
}
.dashboard-opportunity-link {
  cursor: pointer;
  text-decoration: none;
  color: inherit;
}
.dashboard-opportunity-link:hover {
  border-color: var(--deep);
  box-shadow: 0 2px 8px rgba(17, 42, 54, 0.06);
}
.dashboard-opportunity-glyph {
  font-size: 32px;
  line-height: 1;
  color: var(--deep);
  font-weight: 300;
  flex-shrink: 0;
}
.dashboard-opportunity-body { flex: 1; min-width: 0; }
.dashboard-opportunity-label {
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--muted);
  margin-bottom: 2px;
  font-weight: 600;
}
.dashboard-opportunity-value {
  font-size: 26px;
  font-weight: 700;
  color: var(--deep);
  line-height: 1.1;
}
.dashboard-opportunity-sub {
  font-size: 12px;
  margin-top: 4px;
}
.dashboard-opportunity-cta {
  color: var(--deep);
  font-size: 13px;
  font-weight: 600;
  flex-shrink: 0;
  align-self: center;
}

/* Subscription card on the dashboard -- Priority plan name in gold. */
.dashboard-sub-plan-name {
  font-size: 22px;
  font-weight: 700;
  color: var(--navy);
  margin-top: 2px;
}
.dashboard-sub-plan-name.priority { color: var(--gold-deep); }
.dashboard-sub-priority-star {
  font-size: 12px;
  color: var(--gold);
  letter-spacing: 0.06em;
}
.dashboard-sub-mrr-suffix { font-size: 11px; }
.dashboard-empty-recent-orders {
  padding: 24px;
  text-align: center;
}

.dashboard-help-footer { text-align: center; }
.dashboard-help-copy {
  font-size: 11.5px;
  margin-bottom: 6px;
}
.dashboard-help-priority {
  color: var(--gold-deep);
  font-weight: 600;
}

/* ---------- Phase 3.7.d: Performance ---------- */

/* View-tier gate card -- same shape on Paperwork, Performance,
 * and other Access-gated views. */
.view-tier-gate {
  text-align: center;
  padding: 40px 24px;
}
.view-tier-gate-icon {
  font-size: 32px;
  color: var(--mint);
  margin-bottom: 8px;
}
.view-tier-gate-title {
  font-size: 16px;
  font-weight: 700;
  color: var(--ink);
  margin-bottom: 8px;
}
.view-tier-gate-copy {
  font-size: 13px;
  line-height: 1.55;
  max-width: 480px;
  margin: 0 auto 16px;
  color: var(--muted);
}

/* Empty-state for the "no paid orders yet" performance page. */
.performance-empty-title {
  font-size: 15px;
  color: var(--ink);
  font-weight: 600;
  margin-bottom: 6px;
}
.performance-empty-copy {
  font-size: 13px;
  max-width: 480px;
  margin: 0 auto 14px;
}

/* Monthly bar chart. Pure CSS bars sized by inline width %. */
.performance-monthly-bars {
  display: flex;
  flex-direction: column;
  gap: 8px;
  margin-top: 8px;
}
.performance-monthly-row {
  display: flex;
  align-items: center;
  gap: 10px;
}
.performance-monthly-label {
  width: 60px;
  font-size: 12px;
  color: var(--muted);
}
.performance-monthly-track {
  flex: 1;
  height: 18px;
  background: var(--light-2);
  border-radius: 4px;
  overflow: hidden;
}
.performance-monthly-fill {
  height: 100%;
  background: var(--mint);
  border-radius: 4px;
  transition: width 0.2s ease;
}
.performance-monthly-value {
  width: 90px;
  text-align: right;
  font-size: 12px;
  font-weight: 600;
  color: var(--deep);
}
.performance-monthly-value.is-zero { color: var(--muted); font-weight: 500; }
.performance-monthly-orders {
  width: 60px;
  text-align: right;
  font-size: 11px;
}
.performance-deal-meta { font-size: 11px; }
.performance-table-empty { padding: 14px; }
.performance-table-footnote {
  font-size: 11.5px;
  padding: 8px 12px 0;
  line-height: 1.45;
}

/* iOS-style toggle switch (used by email preferences) */
.toggle-switch {
  position: relative;
  display: inline-block;
  width: 38px;
  height: 22px;
  flex-shrink: 0;
}
.toggle-switch-input {
  opacity: 0;
  width: 0;
  height: 0;
  position: absolute;
}
.toggle-switch-slider {
  position: absolute;
  inset: 0;
  background: #d0d5d8;
  border-radius: 22px;
  cursor: pointer;
  transition: background-color 0.18s ease;
}
.toggle-switch-slider::before {
  content: "";
  position: absolute;
  top: 2px; left: 2px;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: 0 1px 2px rgba(17, 42, 54, 0.20);
  transition: transform 0.18s ease;
}
.toggle-switch-input:checked + .toggle-switch-slider {
  background: var(--mint);
}
.toggle-switch-input:checked + .toggle-switch-slider::before {
  transform: translateX(16px);
}
.toggle-switch-input:focus-visible + .toggle-switch-slider {
  outline: 2px solid var(--navy);
  outline-offset: 2px;
}

/* ---------- Phase 3.7.b: Paperwork ----------
 *
 * Intro card (subtle mint gradient, same shape as the Account
 * intro), VAT export card, kind-pill labels in the table, action
 * cell spacing. The pill colours match the lifecycle palette
 * already in use: invoice = navy (formal), receipt = mint (paid
 * positive), delivery_note = warm (movement).
 */
.paperwork-intro {
  background: linear-gradient(135deg, var(--light) 0%, #fff 100%);
  border-color: var(--mint);
}
.paperwork-intro-glyph {
  font-size: 28px;
  line-height: 1;
  color: var(--deep);
  font-weight: 300;
}
.paperwork-intro-title {
  font-weight: 700;
  color: var(--ink);
  margin-bottom: 4px;
}
.paperwork-intro-copy {
  font-size: 12.5px;
  line-height: 1.55;
}
.paperwork-vat-export {
  border-color: var(--mint);
  background: var(--light);
}
.paperwork-vat-export-title {
  font-weight: 700;
  color: var(--ink);
  margin-bottom: 4px;
}
.paperwork-vat-export-copy {
  font-size: 12.5px;
  line-height: 1.55;
}
.paperwork-vat-export-buttons {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  align-items: flex-start;
}
.paperwork-vat-custom {
  position: relative;
}
.paperwork-vat-custom > summary {
  list-style: none;
  cursor: pointer;
}
.paperwork-vat-custom > summary::-webkit-details-marker {
  display: none;
}
.paperwork-vat-custom[open] > summary {
  background: var(--navy);
  color: #fff;
  border-color: var(--navy);
}
.paperwork-vat-custom-form {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  /* Glass tray (console language, 2026-07-19). The CSV form's
     turbo:false + target=_blank stay load-bearing (PWA back-chrome). */
  background: linear-gradient(180deg, rgba(252, 252, 252, 0.94), rgba(234, 245, 238, 0.78));
  -webkit-backdrop-filter: blur(14px) saturate(1.4);
  backdrop-filter: blur(14px) saturate(1.4);
  border: 1px solid rgba(226, 235, 238, 0.9);
  border-radius: 8px;
  padding: 12px;
  min-width: 240px;
  box-shadow: 0 18px 40px -14px rgba(28, 45, 53, 0.25);
  z-index: 10;
  display: flex;
  flex-direction: column;
  gap: 8px;
  animation: tray-drop 0.3s var(--ease-out-expo);
}
@supports not (backdrop-filter: blur(1px)) {
  .paperwork-vat-custom-form { background: var(--surface); }
}
.paperwork-vat-custom-form .field {
  margin: 0;
}
.paperwork-kind-pill {
  display: inline-block;
  font-size: 11.5px;
  font-weight: 600;
  padding: 2px 8px;
  border-radius: 6px;
  background: var(--light-2);
  color: var(--ink-2);
  white-space: nowrap;
}
.paperwork-kind-invoice {
  background: rgba(17, 42, 54, 0.10);
  color: var(--ink);
}
.paperwork-kind-receipt {
  background: #d9f0e3;
  color: #1f6a48;
}
.paperwork-kind-delivery_note {
  background: #faedd0;
  color: #8a6616;
}
.paperwork-actions-cell {
  white-space: nowrap;
}
.paperwork-actions-cell .btn + .btn {
  margin-left: 4px;
}
.paperwork-table .nowrap {
  white-space: nowrap;
  font-size: 11.5px;
}

/* ---------- Phase 3.7.c: Messages ----------
 *
 * Thread view: list of speech-bubble rows with left/right
 * alignment by sender. Bell-chat shape -- staff bubbles on the
 * left, client (self) bubbles on the right when viewed from the
 * client side, and vice versa from staff. Width-limited so long
 * paragraphs wrap legibly.
 */
.messages-intro-copy {
  font-size: 12.5px;
  line-height: 1.55;
  max-width: 540px;
}
.message-unread-chip {
  margin-left: 6px;
}
.message-author-line {
  font-size: 11px;
  margin-top: 2px;
}
.message-client-sub {
  font-size: 11.5px;
}
/* 3.7.h -- conversation thread area. The whole .message-thread
 * sits inside a card; we tint the thread band itself so the
 * bubbles sit on a soft contrasting surface, then the reply
 * composer below (separated by a thin divider) returns to the
 * card's white background. */
.msg-conversation-title {
  margin: 0;
  font-size: 16px;
  font-weight: 700;
  color: var(--ink);
}
.msg-conversation-status {
  text-transform: lowercase;
}
.msg-conversation-card { padding: 0; overflow: hidden; }
.message-thread {
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-height: 480px;
  overflow-y: auto;
  padding: 18px 22px;
  background: var(--light-2);
  border-bottom: 1px solid var(--line);
}
/* "Mine vs theirs" is resolved in CSS, not in the partial: the thread
 * container carries data-viewer-role (stamped per-request, when
 * current_user is known) and each row carries data-sender-role (safe
 * to emit from a Turbo broadcast, which has NO current_user). Matching
 * roles = my message (right, navy); anything else = theirs (left,
 * white). This makes a live-appended bubble identical to a page-
 * rendered one -- the old .from-self server-side check left your own
 * just-sent message styled as the other side until a reopen. */
.message-row {
  display: flex;
  justify-content: flex-start;
}
.message-thread[data-viewer-role="client"] .message-row[data-sender-role="client"],
.message-thread[data-viewer-role="staff"]  .message-row[data-sender-role="staff"] {
  justify-content: flex-end;
}
/* Bubbles get rounded corners with a single squared "tail" corner
 * on the from-edge -- mirrors the prototype's chat shape. */
.message-bubble {
  padding: 10px 14px;
  border-radius: 6px;
  border-bottom-left-radius: 4px;
  max-width: 72%;
  background: var(--surface);
  border: 1px solid var(--line);
  box-shadow: var(--shadow-sm);
  font-size: 13.5px;
  line-height: 1.5;
}
.message-thread[data-viewer-role="client"] .message-row[data-sender-role="client"] .message-bubble,
.message-thread[data-viewer-role="staff"]  .message-row[data-sender-role="staff"]  .message-bubble {
  border-bottom-left-radius: 14px;
  border-bottom-right-radius: 4px;
  background: var(--navy);
  color: #fff;
  border-color: var(--navy);
}
.message-thread[data-viewer-role="client"] .message-row[data-sender-role="client"] .message-bubble strong,
.message-thread[data-viewer-role="staff"]  .message-row[data-sender-role="staff"]  .message-bubble strong {
  color: #fff;
}
/* Meta + body sized exactly to the prototype's .msg-meta /
 * .msg-body so the bubble height matches piece by piece. */
.message-meta {
  font-size: 10.5px;
  font-weight: 600;
  margin-bottom: 4px;
  opacity: 0.8;
  display: flex;
  gap: 4px;
  align-items: baseline;
  color: var(--muted);
}
.message-thread[data-viewer-role="client"] .message-row[data-sender-role="client"] .message-meta,
.message-thread[data-viewer-role="staff"]  .message-row[data-sender-role="staff"]  .message-meta {
  color: rgba(255, 255, 255, 0.75);
}
.message-time { font-size: 10.5px; font-weight: 500; }
.message-body {
  font-size: 13.5px;
  line-height: 1.5;
  white-space: pre-wrap;
  word-wrap: break-word;
}
.message-error-banner {
  background: #faedd0;
  border-color: #f0d090;
  padding: 8px 12px;
  margin-bottom: 12px;
  font-size: 12.5px;
}

/* Reply composer inside the conversation card -- sits BELOW the
 * .message-thread tint, on the card's white background, separated
 * by the thread's border-bottom. */
.msg-reply-form {
  padding: 14px 18px 16px;
}
.msg-reply-input {
  width: 100%;
  min-height: 60px;
  resize: vertical;
}
.msg-reply-actions {
  margin-top: 8px;
}

.scroll  { overflow: auto; }
.hidden  { display: none !important; }

/* ---------- Responsive ----------
 *
 * v1 is a desktop / laptop product (Section 3 of the migration plan).
 * Mobile is not a target -- the workflows we're building are sit-down
 * operations work, not on-the-go.
 *
 * We deliberately do NOT collapse the sidebar at narrow widths. The
 * earlier media query that did this looked broken at ~800px because
 * a navy strip stacked above the content read as "half-done hamburger
 * menu" rather than "intentional mobile layout". Better to let narrow
 * viewports show a horizontally-scrollable desktop layout (visually
 * imperfect but structurally intact) than to ship a half-implemented
 * mobile mode. If a mobile experience becomes a real requirement in
 * Phase 4+, build it deliberately rather than via a degrade-down rule. */

/* ============================================================
   Brand hero -- animated supply-network graphic (anon shell).
   Namespaced under .brand-hero so nothing leaks to app UI
   (the app already uses .chip, etc). Motion is transform/
   opacity/dash only; prefers-reduced-motion freezes it clean.
   ============================================================ */
.brand-hero { width: 100%; max-width: 600px; }
.brand-hero svg { display: block; width: 100%; height: auto; }
.brand-hero .bh-lbl { font-family: var(--font-display); font-weight: 700; letter-spacing: 2px; fill: var(--muted); text-transform: uppercase; font-size: 11px; }
.brand-hero .bh-lbl.bh-hublabel { fill: var(--light); font-weight: 800; letter-spacing: 3px; font-size: 13px; }
.brand-hero .bh-chiptext { font-family: var(--font); font-weight: 500; font-size: 13px; fill: var(--mint); }
.brand-hero .bh-glyphlbl { font-family: var(--font); font-weight: 400; font-size: 12px; fill: var(--light); }
.brand-hero .bh-conn { fill: none; stroke: rgba(117,198,158,.22); stroke-width: 1.5; }
.brand-hero .bh-flow { fill: none; stroke: var(--mint); stroke-width: 1.6; stroke-linecap: round; stroke-dasharray: 2 14; opacity: .9; }
.brand-hero .bh-chip { opacity: 0; }
.brand-hero .bh-fb { transform-box: fill-box; transform-origin: center; }

@media (prefers-reduced-motion: no-preference) {
  @keyframes bhFlowDash  { 100% { stroke-dashoffset: -96; } }
  @keyframes bhOrbitSpin { 100% { transform: rotate(360deg); } }
  @keyframes bhHubPulse  { 0%,100% { transform: scale(1); } 50% { transform: scale(1.035); } }
  @keyframes bhHaloPulse { 0%,100% { transform: scale(1); opacity: .9; } 50% { transform: scale(1.45); opacity: .15; } }
  @keyframes bhGlowPulse { 0%,100% { opacity: .45; } 50% { opacity: .85; } }
  @keyframes bhChipFloat { 0% { opacity: 0; transform: translateY(5px); } 8%,38% { opacity: 1; transform: translateY(0); } 46%,100% { opacity: 0; transform: translateY(-7px); } }
  @keyframes bhDotRipple { 0% { r: 6px; opacity: .6; } 55%,100% { r: 17px; opacity: 0; } }
  @keyframes bhSrcPulse  { 0%,100% { transform: scale(1); opacity: .9; } 50% { transform: scale(1.06); opacity: 1; } }

  .brand-hero .bh-flow { animation: bhFlowDash 3.6s linear infinite; }
  .brand-hero .bh-flow.bh-f2 { animation-duration: 4.4s; animation-delay: -1.1s; }
  .brand-hero .bh-flow.bh-f3 { animation-duration: 4.0s; animation-delay: -.6s; }
  .brand-hero .bh-flow.bh-f4 { animation-duration: 4.8s; animation-delay: -2.0s; }
  .brand-hero .bh-flow.bh-f5 { animation-duration: 4.2s; animation-delay: -1.6s; }
  .brand-hero .bh-flow.bh-fin { animation-duration: 5.2s; }
  .brand-hero .bh-orbit { animation: bhOrbitSpin 26s linear infinite; }
  .brand-hero .bh-hub { animation: bhHubPulse 6s ease-in-out infinite; }
  .brand-hero .bh-glow { animation: bhGlowPulse 6s ease-in-out infinite; }
  .brand-hero .bh-halo { animation: bhHaloPulse 5.5s ease-in-out infinite; }
  .brand-hero .bh-ripple { animation: bhDotRipple 5.5s ease-in-out infinite; }
  .brand-hero .bh-srcbox { animation: bhSrcPulse 7s ease-in-out infinite; }
  .brand-hero .bh-m0 .bh-halo, .brand-hero .bh-m0 .bh-ripple { animation-delay: 0s; }
  .brand-hero .bh-m1 .bh-halo, .brand-hero .bh-m1 .bh-ripple { animation-delay: -1.1s; }
  .brand-hero .bh-m2 .bh-halo, .brand-hero .bh-m2 .bh-ripple { animation-delay: -2.2s; }
  .brand-hero .bh-m3 .bh-halo, .brand-hero .bh-m3 .bh-ripple { animation-delay: -3.3s; }
  .brand-hero .bh-m4 .bh-halo, .brand-hero .bh-m4 .bh-ripple { animation-delay: -4.4s; }
  .brand-hero .bh-chip { animation: bhChipFloat 12s ease-in-out infinite; }
  .brand-hero .bh-chipA { animation-delay: 0s; }
  .brand-hero .bh-chipB { animation-delay: -4s; }
  .brand-hero .bh-chipC { animation-delay: -8s; }
}
@media (prefers-reduced-motion: reduce) {
  .brand-hero * { animation: none !important; }
  .brand-hero .bh-chip { opacity: 1; }
  .brand-hero .bh-flow { opacity: .7; }
}

/* ---------- Public register form (/register) + staff Applications ---- */

/* Honeypot: visually removed but still in the DOM for bots to find.
 * display:none would work too, but off-screen positioning defeats the
 * lazier "skip hidden inputs" bot heuristics. */
.register-hp {
  position: absolute;
  left: -9999px;
  top: auto;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

.register-section-label {
  font-size: 12px;
  color: var(--muted);
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  margin: 26px 0 12px;
}
.register-section-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  flex-wrap: wrap;
  margin: 26px 0 12px;
}
.register-section-row .register-section-label { margin: 0; }

.anon-card .grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.anon-card .field-span { grid-column: 1 / 3; }
@media (max-width: 560px) {
  .anon-card .grid-2 { grid-template-columns: 1fr; }
  .anon-card .field-span { grid-column: auto; }
}

.optional-marker { color: var(--muted); font-weight: 400; }
.field-hint {
  font-size: 11.5px;
  color: var(--muted);
  margin-top: 4px;
}
.register-error-list { margin: 6px 0 0; padding-left: 18px; }

/* Staff Applications review cards */
.application-facts {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  gap: 10px 18px;
  margin: 6px 0 14px;
  font-size: 13.5px;
}
.application-fact-wide { grid-column: 1 / -1; }
.application-fact-label {
  display: block;
  font-size: 11px;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-weight: 600;
  margin-bottom: 2px;
}
.application-actions {
  display: flex;
  gap: 10px;
  align-items: center;
  flex-wrap: wrap;
  border-top: 1px solid var(--line);
  padding-top: 14px;
}
.application-decline-form {
  display: flex;
  gap: 8px;
  align-items: center;
  flex: 1;
  min-width: 260px;
}
.application-decline-form .input { max-width: 340px; }

/* Terms checkbox on /register: top-align the box with wrapped text. */
.register-terms { align-items: flex-start; margin-top: 14px; }
.register-terms input { margin-top: 3px; }

/* Vertical rhythm for the register form's stacked (non-grid) fields --
 * hints must not run straight into the next label. */
.anon-card form > .field { margin-top: 16px; }
.anon-card .grid-2 { gap: 14px 12px; }
.register-terms { margin-top: 22px; }
.register-terms + .field-hint { margin: 12px 0 0; }
.anon-card:has(.anon-card-wide-marker) .anon-actions { margin-top: 20px; }

/* =====================================================================
 * PHONE SHELL (the app layout) -- kept at the END of the file so these
 * overrides win the cascade over every base component rule above (media
 * queries don't add specificity, so source order breaks the tie).
 *
 * The signed-in shell is a 248px sidebar + main grid; on phones that
 * crushed main into a ~70px strip. Below 820px the sidebar becomes an
 * off-canvas drawer (topbar hamburger + mobile-nav controller) and main
 * goes full-width; grids collapse and wide tables scroll.
 * ===================================================================== */
.topbar-burger { display: none; }
.mobile-scrim  { display: none; }
body.nav-locked { overflow: hidden; }
/* Client mobile bottom tab bar: in the DOM for client users on every
 * page but hidden on desktop (>820px). The @media block at the end of
 * this file shows + pins it at <=820px. Desktop stays pixel-identical. */
.client-tabbar { display: none; }

@media (max-width: 820px) {
  #app { grid-template-columns: 1fr; }

  .sidebar {
    position: fixed; top: 0; left: 0; bottom: 0;
    width: min(84vw, 300px);
    /* 100vh on mobile Safari is the LARGE viewport (URL bar retracted),
       so a fixed 100vh drawer runs taller than the visible screen and the
       margin-top:auto account footer ("who am I") lands under the browser
       toolbar / home indicator -- invisible. 100dvh tracks the actually-
       visible height so the footer sits just above the bottom chrome; the
       100vh line is the fallback for engines without dvh. The bottom pad
       (plus the iOS safe-area inset where the browser exposes it) keeps
       the account row clear of the home indicator. */
    height: 100vh;
    height: 100dvh;
    overflow-y: auto; -webkit-overflow-scrolling: touch;
    padding-bottom: calc(16px + env(safe-area-inset-bottom, 0px));
    transform: translateX(-100%);
    transition: transform .28s cubic-bezier(.4, 0, .2, 1);
    z-index: 60; will-change: transform;
  }
  #app.nav-open .sidebar { transform: none; box-shadow: 0 0 50px rgba(0, 0, 0, .45); }

  /* Scrim only exists while the drawer is open. (It must be gated on
     nav-open -- a bare `.mobile-scrim { display: block }` overrode the
     `hidden` attribute and left an invisible full-screen overlay
     swallowing every tap.) */
  #app.nav-open .mobile-scrim {
    display: block; position: fixed; inset: 0; z-index: 55;
    background: rgba(12, 20, 25, .55);
  }

  .topbar-burger {
    display: inline-flex; align-items: center; justify-content: center;
    flex: none; width: 40px; height: 40px; margin-left: -8px;
    border: 0; background: transparent; color: var(--ink);
    border-radius: 6px; cursor: pointer;
  }
  .topbar-burger:active { background: var(--light-2); }

  .topbar { padding: 10px 14px; gap: 6px; }
  .topbar h1 { font-size: 17px; }
  .topbar .subtitle { display: none; }
  .topbar-right { margin-left: auto; }

  .view-root { padding: 14px 14px 40px; }
  /* Clients get extra bottom padding so the last card clears the fixed
     tab bar (56px row + its >=20px bottom cushion + 14px breathing)
     defined at the end of this block. Other roles have no tab bar, so
     they keep the base 40px. */
  body.role-client .view-root { padding-bottom: calc(70px + max(20px, env(safe-area-inset-bottom, 0px))); }

  /* KPI grids (4-up on desktop) -> 2-up on phones. Both the shared
     .stat-grid and any 2-panel row stack to fit. */
  .stat-grid    { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .panel-grid-2 { grid-template-columns: 1fr; }
  .stat-card { padding: 14px 14px; }
  .stat-card .stat-value { font-size: 26px; }

  /* Multi-column card grids stack on phones -- e.g. the order modal's
     Allocation / Timeline / Payment (.grid-3) and the invoice's paired
     sections (.grid-2), which were side-by-side and cramped. */
  .grid-3 { grid-template-columns: 1fr; }
  .grid-2 { grid-template-columns: 1fr; }

  /* Wide tables scroll horizontally instead of wrapping to shreds. */
  .table-card { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .table-card .table { white-space: nowrap; }

  /* The VAT-invoice line-items table has 5 columns; let it scroll
     within its card rather than clip off-screen, and tighten the type. */
  .invoice-table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .invoice-table { font-size: 12px; }
  .invoice-table th, .invoice-table td { padding-left: 8px; padding-right: 8px; }

  /* The pro-forma is the document a client reads to pay by bank
     transfer, and clients live on phones. Its header + parties rows
     are flex space-between with no stacking, so at 375px the invoice
     number sits squashed beside the company block, and Billed-to beside
     Issued/due. Stack them, left-align, and trim the sheet padding so
     the whole document reads cleanly on a phone. */
  .invoice-sheet { padding: 18px 16px; }
  .invoice-head,
  .invoice-parties { flex-direction: column; gap: 14px; }
  .invoice-brand,
  .invoice-parties .right { text-align: left; }

  /* My Orders: a 6-column table can't fit a phone, so instead of a
     cramped horizontally-scrolled header that doesn't match the desktop
     portal, render each order as a stacked card. Scoped to
     .orders-cards so no other table is affected; the numeric cells that
     lose their header carry a data-label rendered inline. */
  .orders-cards { overflow-x: visible; }
  .orders-cards .table { white-space: normal; }
  .orders-cards .table thead { display: none; }
  .orders-cards .table,
  .orders-cards .table tbody,
  .orders-cards .table tr,
  .orders-cards .table td { display: block; width: auto; }
  .orders-cards .table tr {
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 12px 14px;
    margin-bottom: 10px;
  }
  .orders-cards .table tr:last-child { margin-bottom: 0; }
  .orders-cards .table td {
    border: 0 !important;
    padding: 2px 0 !important;
    text-align: left !important;
  }
  .orders-cards .table td[data-label]::before {
    content: attr(data-label) ": ";
    color: var(--muted);
    font-weight: 500;
  }
  /* Ticket ref reads as the card heading; a little air before status. */
  .orders-cards .table td:first-child { padding-bottom: 6px !important; }
  .orders-cards .table td:first-child .mono { font-size: 14px; font-weight: 600; }
  .orders-cards .table .order-status-cell { margin-top: 6px; }

  /* iOS auto-zooms the viewport when a user focuses a form control whose
     computed font-size is < 16px, and never zooms back out afterwards --
     leaving the whole app stuck zoomed after the user sends a message.
     The base .input/.select/.textarea rule is 13px, so force >= 16px on
     every focusable control on mobile. Placed at the END of the 820px
     block (after the base rule in source order) so it wins without
     !important; desktop is untouched because this query only matches
     <= 820px, where the base 13px still governs. */
  input, textarea, select,
  .input, .textarea, .select {
    font-size: 16px;
  }

  /* Messages index header: on phones the intro copy and the
     "+ New message" button were pinned to opposite edges of a
     .row-between flex row (no gap, no wrap), so the button jammed
     against / overlapped the wrapping intro text. Stack them -- text on
     top, full-width button beneath -- scoped to .messages-index-head so
     no other .row-between row changes; desktop keeps side-by-side. */
  .messages-index-head {
    flex-direction: column;
    align-items: stretch;
    gap: 14px;
  }
  .messages-index-head .btn-accent {
    width: 100%;
    justify-content: center;
  }

  /* Fixed bottom tab bar (client, phones). Pinned to the bottom edge
     with safe-area padding for the iPhone home indicator; the mint
     active state matches the sidebar's active icon colour. The
     role-scoped .view-root padding above keeps content clear of it. */
  .client-tabbar {
    display: flex;
    position: fixed;
    left: 0; right: 0; bottom: 0;
    z-index: 50;
    background: var(--surface);
    border-top: 1px solid var(--line);
    /* Native apps keep a cushion under the tab row for the iPhone home
       indicator. env(safe-area-inset-bottom) reads 0 in the installed
       PWA (no viewport-fit=cover -- deliberate), which left the labels
       flush against the screen edge; the 20px floor guarantees the
       breathing room, and max() still honours a larger real inset. */
    padding-bottom: max(20px, env(safe-area-inset-bottom, 0px));
    box-shadow: 0 -1px 12px rgba(28, 45, 53, .06);
  }
  .client-tab {
    flex: 1 1 0;
    display: flex; flex-direction: column; align-items: center; justify-content: center;
    gap: 3px;
    min-width: 0;
    height: 56px;
    padding: 6px 2px;
    border: 0; background: transparent;
    color: var(--muted);
    font-family: inherit; font-size: 10.5px; font-weight: 600;
    letter-spacing: .1px;
    text-decoration: none;
    cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .client-tab .ico { color: currentColor; }
  .client-tab-label {
    max-width: 100%;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .client-tab.active { color: var(--mint-2); }
  .client-tab.active .ico { color: var(--mint-2); }
  .client-tab:active { background: var(--light-2); }
  /* Unread dot on the Orders / Messages icons (the sidebar's count
     badge is too wide for a cramped tab; a dot carries the signal). */
  .client-tab-ico-wrap { position: relative; display: inline-flex; }
  .client-tab-dot {
    position: absolute; top: -2px; right: -3px;
    width: 8px; height: 8px; border-radius: 999px;
    background: var(--mint);
    border: 1.5px solid #fff;
  }
}

@media (max-width: 480px) {
  .stat-grid { grid-template-columns: 1fr; }
}

/* ---------- Mobile bottom-sheet client modals (<=820px) ----------
 * Turns the client modal family (.order-modal -- incl. the Messages
 * thread/compose reuse -- .deal-modal, .reserve-modal, .stat-modal)
 * into native-feel bottom sheets on phones. Scoped under
 * body.role-client so staff/supplier/affiliate keep centred dialogs
 * on THEIR phones, and gated inside the 820px query so desktop
 * (>820px) is pixel-identical -- this block is last in source order,
 * so it wins over the base dialog rules without !important. The
 * <dialog> box pins to the bottom edge; each inner *-body keeps its
 * own overflow-y:auto so content scrolls internally. The grab-handle
 * is a pure-CSS ::before bar on the dialog (no partial edits) -- the
 * dialog itself has overflow:hidden and never scrolls, so the
 * absolutely-positioned handle stays pinned at the top-centre. */
@media (max-width: 820px) {
  body.role-client .order-modal,
  body.role-client .deal-modal,
  body.role-client .reserve-modal,
  body.role-client .stat-modal {
    /* Pin to bottom, full width, rounded top only. margin:auto auto 0
       keeps the box bottom-centred in the dialog top layer; explicit
       width + max-width override the desktop min() widths. */
    width: 100%;
    max-width: 100%;
    margin: auto auto 0;
    max-height: 90dvh;
    border-radius: 16px 16px 0 0;
    border-bottom: 0;
    animation: sheet-up 0.28s cubic-bezier(0.32, 0.72, 0, 1);
  }

  /* Centre grab-handle -- pinned to the top of the (non-scrolling)
     dialog box, above the sticky heads. */
  body.role-client .order-modal::before,
  body.role-client .deal-modal::before,
  body.role-client .reserve-modal::before,
  body.role-client .stat-modal::before {
    content: "";
    position: absolute;
    top: 8px;
    left: 50%;
    transform: translateX(-50%);
    width: 40px;
    height: 5px;
    border-radius: 3px;
    background: rgba(12, 31, 41, 0.22);
    z-index: 3;
    pointer-events: none;
  }

  /* Give content room to clear the handle. .order-modal /
     .reserve-modal have a position:sticky head -- pad the head so the
     handle doesn't overlap the title. .deal-modal / .stat-modal have
     no sticky head, so pad the body / head block instead. */
  body.role-client .order-modal .order-modal-head,
  body.role-client .reserve-modal .reserve-modal-head {
    padding-top: 22px;
  }
  body.role-client .deal-modal .deal-modal-body {
    padding-top: 26px;
  }
  body.role-client .stat-modal .stat-modal-head {
    padding-top: 24px;
  }

  /* Internal scroll caps track the 90dvh sheet (base rules use 90vh /
     calc(100vh-32px) / calc(82vh-64px)); align them and cushion the
     last row clear of the screen edge / home indicator. As with the
     tab bar, env(safe-area-inset-bottom) reads 0 in the installed PWA
     (no viewport-fit=cover), so the 20px floor does the real work. */
  body.role-client .order-modal .order-modal-body,
  body.role-client .deal-modal .deal-modal-body,
  body.role-client .reserve-modal .reserve-modal-body {
    max-height: 90dvh;
    padding-bottom: max(20px, env(safe-area-inset-bottom, 0px));
  }
  body.role-client .stat-modal .stat-modal-body {
    max-height: calc(90dvh - 88px);
    padding-bottom: max(20px, env(safe-area-inset-bottom, 0px));
  }

  /* Slightly stronger scrim on phones to sell the sheet. */
  body.role-client .order-modal::backdrop,
  body.role-client .deal-modal::backdrop,
  body.role-client .reserve-modal::backdrop,
  body.role-client .stat-modal::backdrop {
    background: rgba(12, 31, 41, 0.55);
  }

  /* Deal sheet footer: the desktop one-row layout (identity ·
     Close · Reserve) overflows a phone-width sheet. Stack it --
     identity line on top, then the two actions side-by-side each
     taking half the width, thumb-sized. */
  body.role-client .deal-modal-footer {
    flex-wrap: wrap;
    gap: 10px;
  }
  body.role-client .deal-modal-identity {
    flex: 1 1 100%;
  }
  body.role-client .deal-modal-actions {
    flex: 1 1 100%;
    display: flex;
    gap: 10px;
  }
  body.role-client .deal-modal-actions .btn {
    flex: 1 1 0;
    justify-content: center;
    text-align: center;
  }
}

@keyframes sheet-up {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}

/* Respect reduced-motion: skip the slide-up. */
@media (prefers-reduced-motion: reduce) {
  body.role-client .order-modal,
  body.role-client .deal-modal,
  body.role-client .reserve-modal,
  body.role-client .stat-modal {
    animation: none;
  }
}

/* ---------- "Liquid glass" mobile skin ----------
 * iOS-26-style frosted chrome for the phone app (H adopted it as the
 * default, 2026-07-07): the tab bar is a floating glass pill
 * (Instagram / App Store style), and the topbar, off-canvas drawer,
 * and bottom sheets are translucent with a backdrop blur. Everything
 * sits inside the <=820px query, so desktop is untouched. These rules
 * come after the base phone-shell rules in source order and reskin
 * them (positioning/behaviour stays in the base rules). */
@media (max-width: 820px) {
  /* --- Floating pill tab bar ------------------------------------ */
  .client-tabbar {
    left: 14px;
    right: 14px;
    bottom: calc(8px + max(12px, env(safe-area-inset-bottom, 0px)));
    padding-bottom: 0;
    border: 1px solid rgba(255, 255, 255, 0.55);
    border-top: 1px solid rgba(255, 255, 255, 0.55);
    border-radius: 32px;
    background: rgba(255, 255, 255, 0.62);
    -webkit-backdrop-filter: blur(22px) saturate(1.8);
    backdrop-filter: blur(22px) saturate(1.8);
    box-shadow: 0 8px 32px rgba(28, 45, 53, 0.18),
                inset 0 1px 0 rgba(255, 255, 255, 0.6);
    overflow: hidden;
  }
  .client-tab {
    height: 58px;
    margin: 4px 3px;
    border-radius: 26px;
  }
  /* App-Store-style capsule highlight behind the active tab. */
  .client-tab.active {
    background: rgba(117, 198, 158, 0.18);
  }
  .client-tab:active {
    background: rgba(28, 45, 53, 0.08);
  }
  .client-tab-dot { border-color: rgba(255, 255, 255, 0.9); }

  /* --- Frosted sticky topbar ------------------------------------ */
  .topbar {
    background: rgba(255, 255, 255, 0.65);
    -webkit-backdrop-filter: blur(20px) saturate(1.8);
    backdrop-filter: blur(20px) saturate(1.8);
    border-bottom: 1px solid rgba(226, 235, 238, 0.6);
  }

  /* --- Frosted navy drawer -------------------------------------- */
  .sidebar {
    background: rgba(28, 45, 53, 0.86);
    -webkit-backdrop-filter: blur(24px) saturate(1.6);
    backdrop-filter: blur(24px) saturate(1.6);
  }

  /* --- Glass bottom sheets -------------------------------------- */
  body.role-client .order-modal,
  body.role-client .deal-modal,
  body.role-client .reserve-modal,
  body.role-client .stat-modal {
    border-radius: 24px 24px 0 0;
    background: rgba(255, 255, 255, 0.78);
    -webkit-backdrop-filter: blur(26px) saturate(1.8);
    backdrop-filter: blur(26px) saturate(1.8);
  }
  /* Sticky sheet heads keep their own light frost so the title stays
     readable while content scrolls beneath them. */
  body.role-client .order-modal .order-modal-head,
  body.role-client .reserve-modal .reserve-modal-head {
    background: rgba(255, 255, 255, 0.72);
    -webkit-backdrop-filter: blur(16px);
    backdrop-filter: blur(16px);
  }
  /* The deal sheet's sticky footer follows suit. */
  body.role-client .deal-modal-footer {
    background: rgba(255, 255, 255, 0.72);
    -webkit-backdrop-filter: blur(16px);
    backdrop-filter: blur(16px);
  }
}

/* ---------- One-tap reserve polish (<=820px) ----------
 * Phone refinements for the deal-drop push target: the full reserve
 * page (client_orders/new -> .reserve-page wrapper) plus the reserve
 * bottom sheet -- both render _reserve_body.html.erb, so the classes
 * below improve BOTH surfaces at once. The two base rules outside the
 * media query are new-class-only: .qty-step keeps the stepper buttons
 * out of the desktop render, and .reserve-cases-input reproduces the
 * input's old inline `width: 120px` exactly (placed after the .input
 * base rule in source order, so it wins width:100% the same way the
 * inline style did). Desktop >820px stays pixel-identical. */
.qty-step { display: none; }
.reserve-cases-input { width: 120px; }

@media (max-width: 820px) {
  /* Push tap -> Reserve should be one screen. Drop the tier banner on
     the reserve page ONLY (every other client page keeps it). :has()
     degrades gracefully -- an old engine simply keeps the banner. */
  body.role-client .view-root:has(.reserve-page) .lock-banner { display: none; }

  /* Buy / Profit / ROI stay a compact 3-up row; the global
     .grid-3 -> 1fr stack (earlier in this file) made the summary card
     three rows of single labels. Equal specificity, later in source
     order, so this wins without !important. */
  .reserve-econ-trio { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 10px; }

  /* Cases: thumb stepper. Buttons exist in the DOM at all widths but
     only display here. */
  /* Stepper trimmed to sit in line with the rest of the form rather
     than tower over it (H, 2026-07-11): shorter controls, narrower
     +/- keys, 16px input (>=16px so focus never iOS-zooms). */
  .qty-stepper { display: flex; align-items: stretch; gap: 8px; }
  .qty-step {
    display: inline-flex; align-items: center; justify-content: center;
    flex: none; width: 44px; min-height: 40px;
    border: 1px solid var(--line-2); border-radius: 6px;
    background: var(--surface); color: var(--ink);
    font-size: 19px; font-weight: 600; line-height: 1;
    font-family: inherit; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .qty-step:active { background: var(--light-2); }
  .qty-stepper .reserve-cases-input {
    width: auto; flex: 1 1 auto; min-width: 0;
    min-height: 40px; text-align: center;
    font-size: 16px; font-weight: 700;
  }

  /* "Your reservation": receipt rows (label left, value right)
     instead of four stacked mini-columns. The mobile .grid-3 -> 1fr
     rule still supplies the single column; each cell becomes a row. */
  .reserve-totals { gap: 8px; }
  .reserve-totals > div {
    display: flex; align-items: baseline;
    justify-content: space-between; gap: 12px;
  }
  .reserve-totals .econ-value { margin-top: 0; }

  /* Reserve CTA: full-width, thumb-sized, primary on top (the submit
     is last in the DOM; column-reverse lifts it above Cancel). */
  .reserve-actions { display: flex; flex-direction: column-reverse; gap: 10px; }
  .reserve-actions .btn {
    width: 100%; justify-content: center; text-align: center;
    min-height: 52px; font-size: 15px; border-radius: 6px;
  }

  /* Sold-out / expired / capped dead-end: keep the explainer copy,
     make the way back to Deal Flow a real thumb target. */
  .reserve-blocked .reserve-blocked-cta {
    display: flex; align-items: center; justify-content: center;
    min-height: 48px; margin-top: 6px;
    border: 1px solid var(--line-2); border-radius: 6px;
    background: var(--surface); color: var(--deep-2);
    font-size: 14px; font-weight: 600; text-decoration: none;
  }
  .reserve-blocked .reserve-blocked-cta:active { background: var(--light-2); }

  /* Success moment: the post-reserve flash (redirect lands on Deal
     Flow, flash renders first inside .view-root) reads like a
     confirmation card, not a whisper. Client phones only. */
  body.role-client .flash-notice {
    padding: 14px 16px;
    font-size: 15px; line-height: 1.5;
    border-radius: 6px;
    box-shadow: 0 8px 28px rgba(28, 45, 53, 0.12);
    animation: flash-pop 0.32s cubic-bezier(0.32, 0.72, 0, 1);
  }
  body.role-client .flash-notice::before { font-size: 18px; }
  body.role-client .flash-notice a { font-weight: 700; }
}

@keyframes flash-pop {
  from { transform: translateY(-8px); opacity: 0; }
  to   { transform: translateY(0); opacity: 1; }
}

/* animation is only ever set inside the <=820px block above, so this
   is a no-op on desktop. */
@media (prefers-reduced-motion: reduce) {
  body.role-client .flash-notice { animation: none; }
}

/* ---------- App-like touch + sheet-footer polish ----------
 * touch-action: manipulation kills double-tap-to-zoom on touch
 * devices app-wide (pinch is blocked by the viewport meta in the
 * installed PWA). Touch-only behaviour -- desktop rendering is
 * untouched, so it can sit outside the media query. */
html { touch-action: manipulation; }

@media (max-width: 820px) {
  /* Deal sheet footer: a floating rounded glass action card, matching
     the tab bar + the sheet's other cards -- the desktop treatment (a
     sharp full-width band with a border-top) read as unfinished next
     to them. Sticky bottom offset floats it clear of the sheet edge +
     home indicator; the glass block above supplies the frost. */
  body.role-client .deal-modal .deal-modal-body { padding-bottom: 0; }
  body.role-client .deal-modal-footer {
    /* Rest near the sheet's bottom edge at end-of-scroll (the sticky
       offset is the floor it can't sink below), with clear in-flow
       margin separating it from the last content card above. */
    bottom: calc(6px + max(8px, env(safe-area-inset-bottom, 0px)));
    margin-top: 16px;
    border: 1px solid var(--line);
    border-radius: 8px;
    padding: 12px 14px 14px;
    box-shadow: 0 8px 28px rgba(28, 45, 53, 0.14);
  }

  /* Breathing room between the cases stepper and the Notes field --
     the 48px stepper made the default field gap read as squashed. */
  .qty-stepper { margin-bottom: 12px; }
}

/* ---- Deal Radar (live marketplace layer) --------------------------
 * Additive only: nothing here touches .marketplace-grid /
 * .market-card base rules, and no new media queries land near the
 * 800px grid or 820px shell breakpoints -- the ping block is a plain
 * full-width flex row that stacks identically at 375px and on
 * desktop. */
#deal-radar-pings {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
#deal-radar-pings:not(:empty) {
  margin-bottom: 12px;
}

.radar-ping {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  background: var(--surface);
  border: 1px solid var(--mint-3);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  text-decoration: none;
  color: inherit;
  animation: radar-ping-in 240ms ease;
}
.radar-ping:hover { border-color: var(--mint); }

.radar-ping-dot {
  flex: 0 0 8px;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--mint);
  animation: radar-ping-pulse 1.6s ease infinite;
}

.radar-ping-copy {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}
.radar-ping-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.85em;
}
.radar-ping-cta {
  flex: 0 0 auto;
  font-weight: 600;
  color: #1f6a48;
  font-size: 0.85em;
}

@keyframes radar-ping-in {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}
@keyframes radar-ping-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}

/* ---- Mobile Deal Flow filters: pills -> native dropdowns ----------
 * On a phone the status tabs + category pills wrapped many rows deep,
 * pushing deals ~30% down the screen. Swap each for a native <select>
 * (the OS wheel/menu picker -- compact, familiar, thumb-friendly) and
 * break the "alert me on every new deal" CTA out into a standalone
 * full-width button so it isn't buried in a pill row. Desktop (>820px)
 * keeps the pill rows verbatim; the dropdowns + standalone button are
 * hidden there and shown only <=820px, so exactly one presentation is
 * ever visible. nav_select_controller navigates on change. */
.mkt-mobile-filters,
.mkt-alert-standalone,
.mkt-mobile-meta { display: none; }

@media (max-width: 820px) {
  /* Desktop pill rows off; native dropdowns on. */
  .mkt-status-row,
  .category-pills { display: none; }

  /* Status + category dropdowns sit side by side as one compact
     filter bar. */
  .mkt-mobile-filters {
    display: flex;
    gap: 8px;
    margin-bottom: 10px;
  }
  .mkt-mobile-select {
    flex: 1 1 0;
    min-width: 0;              /* let long category names ellipsize */
    /* >=16px so focusing the picker never triggers iOS zoom. */
    font-size: 16px;
    line-height: 1.3;
    padding: 11px 34px 11px 13px;
    border: 1px solid var(--line);
    border-radius: 6px;
    background-color: var(--surface);
    color: var(--ink);
    font-weight: 500;
    text-overflow: ellipsis;
    white-space: nowrap;
    -webkit-appearance: none;
    appearance: none;
    /* Custom chevron (appearance:none drops the native arrow). */
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%231c2d35' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
  }

  /* The alert CTA, freed from the pill row, as a standalone full-width
     button that reads as a clear affordance in its own right -- sits
     just above the deal grid. */
  .mkt-alert-standalone { display: block; margin-bottom: 4px; }
  .mkt-alert-standalone .category-alert-form { display: block; margin: 0; }
  .mkt-alert-standalone-btn {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 8px;
    width: 100%;
    font-size: 14px;
    font-weight: 600;
    padding: 12px 14px;
    border-radius: 6px;
  }

  /* ---- Cohesive filter zone ---------------------------------------
   * The search + sort controls previously used the base .input/.select
   * look (13px, radius 8, heavier --line-2 border) while the filter
   * dropdowns used 16px/radius 12/--line -- four mismatched boxes that
   * read as "chunky blocks everywhere". Unify them into ONE control
   * family, demote Download CSV from a chunky button to a quiet text
   * link, and tighten the vertical rhythm so the zone feels composed
   * and the grid stays close. */
  .mkt-toolbar-form {
    gap: 8px;
    margin-bottom: 10px;
    row-gap: 8px;
  }
  .mkt-toolbar-form .input,
  .mkt-toolbar-form .select {
    font-size: 16px;              /* >=16px: no iOS focus zoom */
    padding: 11px 13px;
    border-radius: 6px;
    border-color: var(--line);
  }
  /* Search takes its own line; sort + Filters share the next. */
  .mkt-search-input {
    flex: 1 0 100%;
    min-width: 0;
    max-width: none;
  }
  .mkt-sort {
    flex: 1 1 auto;
    min-width: 0;
    padding-right: 34px;
    -webkit-appearance: none;
    appearance: none;
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%231c2d35' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: right 12px center;
  }
  .mkt-filters-toggle {
    flex: 0 0 auto;
    padding: 11px 16px;
    border-radius: 6px;
    font-size: 14px;
    border-color: var(--line);
  }
  /* The toolbar's own count + CSV are hidden on mobile -- they move to
     the .mkt-mobile-meta line below the alert button instead. */
  .mkt-toolbar-form .mkt-deal-count,
  .mkt-toolbar-form .mkt-csv-link { display: none; }

  /* Mobile meta line (below the alert): deal count left, quiet CSV
     link right. */
  .mkt-mobile-meta {
    display: flex;
    align-items: center;
    justify-content: space-between;
    margin-top: 10px;
  }
  .mkt-mobile-meta-count { font-size: 12.5px; }
  .mkt-mobile-csv {
    font-size: 12.5px;
    font-weight: 500;
    color: var(--muted);
    text-decoration: none;
  }
  .mkt-mobile-csv:hover { color: var(--ink-2); text-decoration: underline; }
}

/* Bigger tap targets on phones: the small/extra-small button variants
   (Send reply, Open thread, Track parcel) sit at ~24-28px tall, below
   the ~44px comfortable-tap minimum. Give them a floor on the client's
   phone surfaces without touching desktop density. */
@media (max-width: 820px) {
  body.role-client .btn-sm,
  body.role-client .btn-xs {
    min-height: 40px;
    justify-content: center;
  }
}

/* ---- Swipe-to-watch + pull-to-refresh (mobile trading-app feel) ----
 * Both are driven by Stimulus touch controllers; the classes below are
 * only ever applied on a real touch gesture, so desktop is unaffected. */

/* While a card is being swiped, kill text selection + the tap hover. */
.market-card.swiping {
  user-select: none;
  -webkit-user-select: none;
  cursor: grabbing;
}
/* Past the commit threshold: the card arms with a mint edge and the
   watchlist star swells, so you feel the toggle before you let go. */
.market-card.swipe-armed {
  border-color: var(--mint);
  box-shadow: 0 0 0 2px var(--mint-3), 0 10px 28px rgba(28, 45, 53, 0.12);
}
.market-card.swipe-armed .market-card-star {
  transform: scale(1.4);
  color: var(--mint);
  transition: transform 0.12s ease, color 0.12s ease;
}

/* Pull-to-refresh: the host is the page content the controller drags
   down with the finger (translateY, rubber-banded). The spinner sits
   ABOVE the content at a negative top inside the host, so the drag
   reveals it in the opened gap -- the social-media pull feel. */
.pull-refresh-host {
  position: relative;
}
.pull-refresh-indicator {
  position: absolute;
  top: -52px;
  left: 50%;
  transform: translateX(-50%);
  opacity: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--surface);
  color: var(--mint);
  border: 1px solid var(--line);
  box-shadow: 0 6px 18px rgba(28, 45, 53, 0.16);
  pointer-events: none;
}
.pull-refresh-indicator.spinning { animation: pull-refresh-spin 0.8s linear infinite; }
@keyframes pull-refresh-spin { to { transform: translateX(-50%) rotate(360deg); } }

/* Desktop never pulls-to-refresh -- keep the badge out of the DOM flow. */
@media (min-width: 821px) {
  .pull-refresh-indicator { display: none; }
}

/* ---------- Hotwire Native shell adaptations (Phase 2) ----------
 * body.hotwire-native = the request came from the iOS/Android app
 * (see ApplicationHelper#hotwire_native_app?). The WEB tab bar stays
 * (H, 2026-08-03, first TestFlight build: "missing the bottom
 * navigation thing we added" — the planned native tab bar never
 * happened, and the web one is the shipped design). The shell hides
 * its own top navigation bar instead; the portal chrome IS the UI. */

/* ---------- Pro-forma pay-by alert (Maxwell, 2026-07-10) ----------
 * Client My Orders: one unmissable gold card per proforma_issued
 * order ("that needs to be ultra loud"). A <button> so the whole
 * card opens the order modal via the page's order-modal controller.
 * Additive element on ALL breakpoints -- it does not restyle any
 * existing desktop chrome. */
.proforma-alert {
  display: flex;
  align-items: center;
  gap: 14px;
  width: 100%;
  text-align: left;
  padding: 14px 16px;
  border: 1.5px solid var(--gold);
  border-radius: 6px;
  background: linear-gradient(135deg, var(--gold-soft), rgba(212, 175, 90, 0.18));
  cursor: pointer;
  font: inherit;
}
.proforma-alert-icon { color: var(--gold-deep); display: inline-flex; flex-shrink: 0; }
.proforma-alert-body { display: flex; flex-direction: column; gap: 2px; min-width: 0; flex: 1; }
.proforma-alert-title { font-weight: 700; color: var(--gold-deep); }
.proforma-alert-sub { font-size: 13px; color: var(--ink, #1c2d35); opacity: 0.85; }
.proforma-alert-cta {
  flex-shrink: 0;
  font-weight: 700;
  font-size: 13.5px;
  color: #fff;
  background: var(--gold-deep);
  border-radius: 8px;
  padding: 9px 14px;
  white-space: nowrap;
}
/* Expired window: red, not gold (H, 2026-07-14) -- the gold card
 * reads as "action possible"; expired is a problem state. */
.proforma-alert.proforma-alert-expired {
  border-color: #e7a3a3;
  background: linear-gradient(135deg, #fadcdc, rgba(231, 163, 163, 0.22));
}
.proforma-alert-expired .proforma-alert-icon,
.proforma-alert-expired .proforma-alert-title { color: #8e2e2e; }
.proforma-alert-expired .proforma-alert-cta { background: #8e2e2e; }

@media (max-width: 820px) {
  /* Phone: CTA drops under the text, full width -- thumb target. */
  .proforma-alert { flex-wrap: wrap; }
  .proforma-alert-cta { flex-basis: 100%; text-align: center; padding: 12px 14px; }

  /* Deal card CTA "not loud enough" (Maxwell voice note, 2026-07-10):
   * the mint Reserve button washed into the mint profit/stock boxes.
   * Phone-only: the PRIMARY card CTA goes brand-navy so it pops off
   * the green. Priority (gold) CTA + desktop stay untouched. */
  body.role-client .market-card-cta .btn-accent {
    background: var(--navy, #1c2d35);
    border-color: var(--navy, #1c2d35);
    color: #fff;
  }
}

/* ---------- Client My Orders: status seg -> dropdown on phones
 * (H, 2026-07-10). Same dual-render contract as the Deal Flow filter
 * bar above: the seg track on desktop, one native select <=820px.
 * (2026-07-19: pills became the console seg track; selector updated.) */
.orders-mobile-filters { display: none; }

@media (max-width: 820px) {
  body.role-client .orders-status-seg { display: none; }
  body.role-client .orders-mobile-filters { display: block; }
  body.role-client .orders-mobile-filters .mkt-mobile-select { width: 100%; }
}

/* ---------- Deal card spread bar + returns strip (H, 2026-07-10) ----
 * The arbitrage as one picture: cost -> Amazon fees -> YOUR PROFIT,
 * stacked to the 90-day avg sell. Sits between the buy/sell pair and
 * the returns strip (profit / ROI / margin), giving the card a
 * cost -> market -> picture -> returns reading order. */
/* Cards WITH a spread bar frame all three sections as one block:
 * top rule above buy/sell, bottom rule under the returns strip,
 * nothing between. Cards without (incomplete economics) keep the
 * original framed econ pair. */
.market-card:has(.econ-spread) .market-card-econ { border-bottom: none; padding-bottom: 4px; }
.econ-spread { padding: 2px 0 10px; }
.econ-spread-bar {
  display: flex;
  height: 10px;
  border-radius: 6px;
  overflow: hidden;
  background: var(--line);
}
.spread-seg { display: block; min-width: 3px; }
.spread-seg.spread-cost   { background: var(--navy); }
.spread-seg.spread-fee    { background: #b9c6c0; }
.spread-seg.spread-profit { background: var(--mint); }
.econ-spread-legend {
  display: flex;
  gap: 14px;
  margin-top: 6px;
  font-size: 10.5px;
  letter-spacing: 0.02em;
  color: var(--muted, #5d6f6a);
}
.econ-spread-legend span { display: inline-flex; align-items: center; gap: 5px; }
.spread-dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  display: inline-block;
}
.spread-dot.spread-cost   { background: var(--navy); }
.spread-dot.spread-fee    { background: #b9c6c0; }
.spread-dot.spread-profit { background: var(--mint); }

/* Returns strip mirrors .market-card-econ's cell styling in a
 * 3-up grid: profit (mint, leads) / ROI / margin. */
.market-card-returns {
  display: grid;
  grid-template-columns: 1.35fr 1fr 1fr;
  gap: 8px;
  padding: 10px 0;
  border-bottom: 1px solid var(--line);
}

/* ---------- Scarcity heat (H, 2026-07-11) ----------
 * The urgency side of the deal card, escalating with the claim level
 * inside the lens-free radar fragment (so it heats up LIVE as other
 * clients reserve): 50%+ = gold "Selling fast", 80%+ = red "Almost
 * gone" with a pulsing bar. The countdown chip mirrors it on the
 * time axis: gold under 24h (existing chip-warn), red + pulse under
 * the final hour (.chip-urgent). */
.market-card-stock.stock-heat-hot .stock-bar-paid,
.market-card-stock.stock-heat-hot .stock-bar-held {
  background: var(--gold);
}
.market-card-stock.stock-heat-hot .market-card-stock-claimed {
  color: var(--gold-deep);
  font-weight: 700;
}
.market-card-stock.stock-heat-critical .stock-bar-paid,
.market-card-stock.stock-heat-critical .stock-bar-held {
  background: #b04a3a;
}
.market-card-stock.stock-heat-critical .market-card-stock-claimed {
  color: #93392b;
  font-weight: 700;
}

.chip-urgent {
  background: #f6ded8;
  color: #93392b;
  font-weight: 700;
}

@media (prefers-reduced-motion: no-preference) {
  .market-card-stock.stock-heat-critical .stock-bar,
  .chip-urgent {
    animation: scarcity-pulse 1.6s ease-in-out infinite;
  }
  @keyframes scarcity-pulse {
    0%, 100% { opacity: 1; }
    50%      { opacity: 0.62; }
  }
}

/* ---------- Deal detail modal: double-down layer (H, 2026-07-11) --
 * Spread bar + claim heat + scarcity strip inside the detail sheet,
 * reusing the card's spread/heat classes so both layers speak the
 * same visual language. */
.deal-modal-spread { margin-top: 12px; }
.deal-modal-claim { margin-top: 10px; }
.deal-modal-claim .market-card-stock-claimed { font-size: 12px; }

.deal-modal-scarcity {
  display: flex;
  align-items: center;
  gap: 10px;
  margin: 12px 0 0;
  padding: 11px 14px;
  border-radius: 6px;
  font-size: 13px;
}
.deal-modal-scarcity.stock-heat-hot {
  background: var(--gold-soft);
  border: 1px solid var(--gold);
  color: var(--gold-deep);
}
.deal-modal-scarcity.stock-heat-critical {
  background: #f6ded8;
  border: 1px solid #d9917f;
  color: #93392b;
}
.deal-modal-scarcity strong { font-weight: 700; }

/* ---------- Deal sheet header compression (H, 2026-07-11) ----------
 * On phones the sheet header ate a quarter of the screen before any
 * economics: ref shown twice (eyebrow + green tag), padded eyebrow,
 * then chip and units each on their own row. Compress: drop the
 * duplicate ref, tighten the vertical rhythm, and lay the countdown
 * chip + units-available inline as one meta row. */
@media (max-width: 820px) {
  body.role-client .deal-modal-ref { display: none; }
  body.role-client .deal-modal-eyebrow { padding-bottom: 7px; margin-bottom: 8px; }
  body.role-client .deal-modal-head { padding-bottom: 10px; gap: 6px; }
  body.role-client .deal-modal-title { margin-bottom: 2px; }
  body.role-client .deal-modal-head-right {
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
    gap: 8px;
    text-align: left;
  }
  body.role-client .deal-modal-stock-summary,
  body.role-client .deal-modal-stock-held { font-size: 12px; }
}

/* ---------- Reserve flow: profit slot machine (H, 2026-07-11) ------
 * "Every case added = more profit" made visceral: the profit cell is
 * the receipt's hero (mint, larger), counts up with a pop on every
 * stepper change, and floats a "+£X profit" delta chip. Below the
 * receipt, the on-the-table line keeps the next case's prize
 * visible; the max chip takes the whole allocation in one tap.
 * Numbers stay honest -- only the presentation celebrates. */
.reserve-profit-hero { position: relative; }
.reserve-profit-hero .econ-value {
  /* Same size as its sibling cells (H, 2026-07-11: the enlarged
     number "looked funny") -- highlighted by colour + a mint
     underline instead of a size bump. The count-up pop still fires. */
  color: var(--mint-deep, #1f6a48);
  text-decoration: underline;
  text-decoration-color: var(--mint);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}
@media (prefers-reduced-motion: no-preference) {
  .reserve-profit-hero.profit-pop .econ-value {
    animation: profit-pop 0.38s ease-out;
  }
  @keyframes profit-pop {
    0%   { transform: scale(1); }
    45%  { transform: scale(1.14); }
    100% { transform: scale(1); }
  }
}
.profit-delta {
  position: absolute;
  left: 0;
  top: -6px;
  font-size: 12.5px;
  font-weight: 700;
  white-space: nowrap;
  pointer-events: none;
  animation: profit-delta-float 0.9s ease-out forwards;
}
.profit-delta.up   { color: var(--mint-deep, #1f6a48); }
.profit-delta.down { color: var(--muted, #5d6f6a); }
@keyframes profit-delta-float {
  0%   { opacity: 0; transform: translateY(4px); }
  15%  { opacity: 1; }
  100% { opacity: 0; transform: translateY(-22px); }
}

.reserve-on-table {
  margin-top: 10px;
  font-size: 13px;
  color: var(--muted, #5d6f6a);
}
.reserve-on-table strong { color: var(--mint-deep, #1f6a48); }
.reserve-on-table.maxed strong { color: var(--gold-deep); }

.reserve-max-chip {
  /* Full-width block, not a squashed inline pill (H, 2026-07-11).
     Reads as a deliberate action button in line with the receipt +
     Reserve button below it. Symmetric vertical margin so it sits
     evenly between the stepper above and the Notes field below
     rather than crowding Notes (H, second pass). */
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 5px;
  width: 100%;
  margin: 16px 0;
  padding: 12px 14px;
  border: 1.5px solid var(--mint);
  border-radius: 6px;
  background: rgba(117, 198, 158, 0.12);
  color: var(--mint-deep, #1f6a48);
  font-size: 13.5px;
  font-weight: 700;
  cursor: pointer;
}

/* ---------- Amazon product images (Keepa enrichment, 2026-07-13) ----
 * Rendered only when the pipeline captured an image; keyless installs
 * are pixel-identical to before. Thumbnail leads the card header;
 * the detail sheet gets a larger photo beside the title. */
.market-card-thumb {
  flex: none;
  width: 56px;
  height: 56px;
  object-fit: contain;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface);
  padding: 3px;
}
.deal-modal-image {
  flex: none;
  width: 84px;
  height: 84px;
  object-fit: contain;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface);
  padding: 4px;
  margin-right: 2px;
}
@media (max-width: 820px) {
  body.role-client .deal-modal-image { width: 64px; height: 64px; }
}

/* ---------- Deal Scout (2026-07-15) ---------------------------------
 * CEO-only price-list scanner. Mostly rides the card / table / chip /
 * btn primitives; these few classes cover the mapping preview and the
 * results table's product cell. */
.scout-upload-row { align-items: end; }
.scout-product {
  display: flex;
  align-items: center;
  gap: 10px;
  min-width: 220px;
}
.scout-thumb {
  flex: none;
  width: 40px;
  height: 40px;
  object-fit: contain;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: var(--surface);
  padding: 2px;
}
.scout-amz-title {
  font-size: 11.5px;
  max-width: 340px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Needs-eyeball identity block (H, 2026-07-31: "make clear what's the
   title from the price list and what's from Amazon and show the picture
   differences straight off the bat"). Two labelled thumbs side by side —
   Amazon's listing image and the barcode's real pack (auto-fetched as
   the row scrolls into view) — with source-labelled title lines. */
.scout-compare {
  flex: none;
  display: flex;
  gap: 8px;
  align-items: flex-start;
}
/* The two-thumb block eats ~140px of the flex row, so the eyeball rows
   need a wider product cell or the titles wrap one word per line and
   the rows tower (seen live 2026-08-01). */
.scout-product:has(.scout-compare) { min-width: 460px; max-width: 560px; }
.scout-product:has(.scout-compare) .scout-amz-line { max-width: none; }
.scout-compare-fig {
  margin: 0;
  text-align: center;
  width: 64px;
}
.scout-compare-img,
.scout-compare-slot {
  width: 64px;
  height: 64px;
  object-fit: contain;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: #fff;
  padding: 3px;
  display: block;
}
.scout-compare-slot { display: grid; place-items: center; overflow: hidden; }
.scout-compare-slot img { max-width: 100%; max-height: 100%; object-fit: contain; }
.scout-compare-fig figcaption {
  font-size: 10px;
  line-height: 1.25;
  color: var(--muted-ink, #7a7a7a);
  margin-top: 3px;
}
.scout-compare-empty { font-size: 10px; color: var(--muted-ink, #9a9a9a); }
.scout-src-lbl {
  display: inline-block;
  font-size: 9.5px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 1px 6px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--navy) 10%, transparent);
  color: var(--navy);
  vertical-align: 1px;
  margin-right: 4px;
}
.scout-src-lbl.scout-src-amz {
  background: color-mix(in srgb, var(--mint) 22%, transparent);
  color: color-mix(in srgb, var(--navy) 80%, var(--mint));
}
.scout-amz-line {
  font-size: 12px;
  margin-top: 1px;
  max-width: 380px;
}
/* Eyeball identity block (H, 2026-08-02: "cramped and unclear").
   A fixed label column aligns the two names vertically so the eye
   compares PRICE LIST vs AMAZON in one saccade; the Amazon title
   clamps to two lines (keyword-stuffed tails add noise, full text on
   hover); chips live on their own row instead of crowding the name. */
.scout-id-row {
  display: flex;
  gap: 7px;
  align-items: baseline;
  margin-bottom: 2px;
}
.scout-id-row .scout-src-lbl {
  flex: none;
  width: 66px;
  text-align: center;
  margin-right: 0;
}
.scout-id-name {
  font-size: 13px;
  line-height: 1.35;
  min-width: 0;
}
.scout-id-amz {
  font-size: 12.5px;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.scout-id-chips {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 5px;
  margin: 5px 0 3px;
}
.scout-id-chips:empty { display: none; }
/* Multi-select mint (H, 2026-08-02): the bar sits above the eyeball
   table; the checkbox column stays narrow so the identity block keeps
   its width. */
.scout-bulk-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  justify-content: flex-end;
  padding: 6px 2px 8px;
  font-size: 12.5px;
}
.scout-bulk-col {
  width: 30px;
  text-align: center;
  vertical-align: top;
  padding-top: 14px;
}
.scout-bulk-col input[type="checkbox"] {
  width: 15px;
  height: 15px;
  accent-color: var(--navy);
  cursor: pointer;
}
/* Identity Auto-Verdict: the ONE named check a one-question/conflicts
   row carries (H, 2026-08-01) -- what to verify, not just "verify". */
.scout-identity-q {
  font-size: 11.5px;
  font-weight: 600;
  color: #8a6d1a;
  margin-top: 2px;
}
[data-theme="dark"] .scout-identity-q { color: #d9b64a; }
[data-theme="dark"] .scout-compare-img,
[data-theme="dark"] .scout-compare-slot { background: #f4f4f2; }
/* Mapping preview: cap cell width so a 30-column sheet stays scannable. */
.scout-mapping-table td.scout-preview-cell {
  max-width: 180px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  font-size: 12.5px;
}
.scout-role-select { font-size: 12px; padding: 4px 6px; }
.scout-flag { display: inline-block; margin: 1px 2px 1px 0; }
.scout-action-cell { white-space: nowrap; text-align: right; }
/* Results table: scrolls horizontally when the window is narrower
 * than its columns, with the action column pinned to the right edge
 * so Create deal / View deal never leaves the viewport (it sat
 * off-screen on H's first live run, 2026-07-16). Sticky cells need
 * an opaque background — they slide OVER the other columns. */
.scout-table-wrap { overflow-x: auto; }
.scout-items-table th:last-child,
.scout-items-table td.scout-action-cell {
  position: sticky;
  right: 0;
  background: var(--surface);
  box-shadow: -10px 0 10px -10px rgba(28, 45, 53, 0.22);
}
@media (hover: hover) {
  .scout-items-table tbody tr:hover td.scout-action-cell { background: var(--light-2); }
}
/* Sub-line under a sortable header ("ex VAT", "last 90 days"). */
.scout-th-sub {
  font-weight: 500;
  font-size: 10px;
  letter-spacing: 0.03em;
  color: var(--muted);
}
/* A row pinned by ?keep after a units correction: highlighted until
 * the CEO refreshes and it files where it now belongs. */
.scout-items-table tr.scout-kept > td {
  background: rgba(111, 198, 160, 0.07);
}
/* The sticky action cell slides over other columns, so its kept-row
 * tint must be the OPAQUE equivalent of the rgba wash above. */
.scout-items-table tr.scout-kept > td.scout-action-cell {
  background: #f5fbf8;
}
.scout-items-table tr.scout-kept > td:first-child {
  box-shadow: inset 3px 0 0 0 var(--mint);
}
/* Products view: an ASIN group header row above its source rows;
 * the best source (cheapest per single unit) carries a mint wash. */
.scout-products-table .scout-product-header td {
  background: var(--light-2);
  border-top: 2px solid var(--line-2);
}
.scout-products-table .scout-best td { background: rgba(117, 198, 158, 0.12); }

/* ============================================================
   MOTION & POLISH LAYER (2026-07-19) — the "elite" pass.
   Purposeful motion only: guide attention, reward action,
   communicate state. Everything transform/opacity (GPU-cheap,
   phone-silky); prefers-reduced-motion collapses the lot.
   ============================================================ */
:root {
  --ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
  --ease-spring:   cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* ---- Reveal choreography ------------------------------------
   The reveal Stimulus controller stamps data-reveal on a
   container's children, then flips .revealed as each enters the
   viewport, staggered via --reveal-i. No-JS never hides content
   (the attribute only exists once JS runs). */
[data-reveal] { opacity: 0; transform: translateY(16px); }
[data-reveal].revealed {
  animation: reveal-rise 0.65s var(--ease-out-expo) forwards;
  animation-delay: calc(var(--reveal-i, 0) * 65ms);
}
@keyframes reveal-rise {
  to { opacity: 1; transform: translateY(0); }
}

/* ---- Card physics -------------------------------------------
   Interactive cards lift toward the cursor with a mint edge —
   the shelf feels alive without shouting. */
.market-card,
.stat-card,
.dashboard-opportunity {
  transition: transform 0.28s var(--ease-out-expo),
              box-shadow 0.28s var(--ease-out-expo),
              border-color 0.28s ease;
  will-change: transform;
}
.market-card:hover,
.dashboard-opportunity:hover {
  transform: translateY(-4px);
  box-shadow: 0 14px 34px rgba(28, 45, 53, 0.13);
  border-color: var(--mint);
}
.stat-card:hover { transform: translateY(-3px); box-shadow: 0 10px 26px rgba(28, 45, 53, 0.10); }
.market-card .market-card-thumb {
  transition: transform 0.5s var(--ease-out-expo);
}
.market-card:hover .market-card-thumb { transform: scale(1.06) rotate(-1deg); }

/* ---- Button feel --------------------------------------------
   Press-in on tap; primary/accent CTAs carry a light sweep on
   hover — the "reserve" moment feels like a trigger, not a link. */
.btn:active { transform: scale(0.965); }
.btn-accent, .btn-primary, .btn-priority { position: relative; overflow: hidden; }
.btn-accent::after, .btn-primary::after, .btn-priority::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(105deg, transparent 40%, rgba(255, 255, 255, 0.35) 50%, transparent 60%);
  transform: translateX(-110%);
  pointer-events: none;
}
.btn-accent:hover::after, .btn-primary:hover::after, .btn-priority:hover::after {
  animation: btn-sheen 0.7s var(--ease-out-expo);
}
@keyframes btn-sheen { to { transform: translateX(110%); } }

/* ---- Sidebar: gliding active rail --------------------------- */
.nav a { transition: background 0.2s ease, color 0.2s ease, padding-left 0.22s var(--ease-out-expo); }
.nav a:hover { padding-left: 18px; }

/* ---- Modals: sleek open AND close --------------------------- */
/* H, 2026-07-28: the old open "feels outdated" — a bare snap in, no
   backdrop fade, and an instant snap shut. This animates every app
   dialog both ways with pure CSS (no per-controller JS): the
   allow-discrete transitions on overlay/display keep the element in
   the top layer for the duration of the EXIT, so .close() plays a
   fade-out instead of vanishing. @starting-style is the entry frame.
   Opacity + backdrop animate at every width; the translate/scale lift
   is desktop-only so the mobile bottom-sheet's swipe-drag (which owns
   the inline transform) is never fought. Graceful on engines without
   @starting-style — they simply snap, exactly as before. */
dialog.deal-modal,
dialog.order-modal,
dialog.reserve-modal {
  opacity: 0;
  transition:
    opacity 0.26s var(--ease-out-expo),
    transform 0.34s var(--ease-out-expo),
    overlay 0.34s var(--ease-out-expo) allow-discrete,
    display 0.34s var(--ease-out-expo) allow-discrete;
}
dialog.deal-modal[open],
dialog.order-modal[open],
dialog.reserve-modal[open] { opacity: 1; }
@starting-style {
  dialog.deal-modal[open],
  dialog.order-modal[open],
  dialog.reserve-modal[open] { opacity: 0; }
}

/* Backdrop fades with the panel (was an instant grey slab before). The
   final tints match the per-modal ::backdrop rules defined earlier;
   [open] specificity wins over those, transparent is the from/to. */
dialog.deal-modal::backdrop,
dialog.order-modal::backdrop,
dialog.reserve-modal::backdrop {
  background: rgba(17, 42, 54, 0);
  transition:
    background 0.34s ease,
    overlay 0.34s ease allow-discrete,
    display 0.34s ease allow-discrete;
}
dialog.deal-modal[open]::backdrop,
dialog.reserve-modal[open]::backdrop { background: rgba(17, 42, 54, 0.55); }
dialog.order-modal[open]::backdrop  { background: rgba(12, 31, 41, 0.55); }
@starting-style {
  dialog.deal-modal[open]::backdrop,
  dialog.order-modal[open]::backdrop,
  dialog.reserve-modal[open]::backdrop { background: rgba(17, 42, 54, 0); }
}

@media (min-width: 821px) {
  dialog.deal-modal,
  dialog.order-modal,
  dialog.reserve-modal { transform: translateY(20px) scale(0.98); }
  dialog.deal-modal[open],
  dialog.order-modal[open],
  dialog.reserve-modal[open] { transform: translateY(0) scale(1); }
  @starting-style {
    dialog.deal-modal[open],
    dialog.order-modal[open],
    dialog.reserve-modal[open] { transform: translateY(20px) scale(0.98); }
  }
}

@media (prefers-reduced-motion: reduce) {
  dialog.deal-modal,
  dialog.order-modal,
  dialog.reserve-modal {
    transition: opacity 0.12s linear, overlay 0.12s allow-discrete, display 0.12s allow-discrete;
    transform: none;
  }
}

/* ---- Branded page-loading veil ------------------------------ */
/* Centred Vantage mark + sweeping mint ring, shown by the app-loader
   controller while a heavy visit renders. Theme-aware via tokens; the
   scrim uses the page bg so it reads on light AND dark. */
.app-loader {
  position: fixed;
  inset: 0;
  z-index: 2000;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  background: color-mix(in srgb, var(--bg) 66%, transparent);
  -webkit-backdrop-filter: blur(4px) saturate(1.03);
  backdrop-filter: blur(4px) saturate(1.03);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.22s ease, visibility 0.22s ease;
}
.app-loader.is-active {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}
.app-loader-stage {
  position: relative;
  width: 72px;
  height: 72px;
  display: grid;
  place-items: center;
  animation: app-loader-pop 0.42s var(--ease-out-expo) both;
}
.app-loader-ring {
  position: absolute;
  inset: 0;
  width: 72px;
  height: 72px;
  border-radius: 50%;
  /* faint full ring + a bright mint leading arc that sweeps round */
  border: 4px solid color-mix(in srgb, var(--mint) 20%, transparent);
  border-top-color: var(--mint);
  border-right-color: var(--mint);
  animation: app-loader-spin 0.9s linear infinite;
}
.app-loader-mark {
  /* The V is an SVG MASK, not a font glyph (H, 2026-07-31: the live
     glyph kept clipping), and the path is the REAL Syne ExtraBold "V"
     outline (H, 2026-08-02: "this v does not fit the vantage branding
     font") — extracted from the brand font with fontTools, bbox-centred
     in the 44x44 box, Y-flipped. Brand-exact letterform, zero font-metric
     fragility: it can never be cut off because no font is involved at
     render time. Colour is background-color through the mask, so the
     dark-theme override still applies. */
  width: 28px;
  height: 28px;
  background-color: var(--navy);
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 44 44'%3E%3Cpath d='M16.78 31.71L5 12.29H13.56L23.61 30.96H20.51L30.56 12.29H39L27.34 31.71Z'/%3E%3C/svg%3E") center / contain no-repeat;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 44 44'%3E%3Cpath d='M16.78 31.71L5 12.29H13.56L23.61 30.96H20.51L30.56 12.29H39L27.34 31.71Z'/%3E%3C/svg%3E") center / contain no-repeat;
  animation: app-loader-breathe 1.6s ease-in-out infinite;
}
[data-theme="dark"] .app-loader-mark { background-color: var(--mint-3); }
.app-loader-label {
  font-size: 11.5px;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--muted);
}
@keyframes app-loader-spin { to { transform: rotate(360deg); } }
@keyframes app-loader-pop { from { opacity: 0; transform: scale(0.82); } to { opacity: 1; transform: scale(1); } }
@keyframes app-loader-breathe { 0%, 100% { opacity: 0.82; } 50% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .app-loader-ring { animation-duration: 1.8s; }
  .app-loader-mark, .app-loader-stage { animation: none; }
}

/* ---- Deal modal: skeleton ----------------------------------- */
.skeleton-line {
  height: 14px; border-radius: 7px; margin-bottom: 12px;
  background: linear-gradient(90deg, #eef2f0 25%, #e2e9e5 37%, #eef2f0 63%);
  background-size: 400% 100%;
  animation: shimmer 1.2s ease infinite;
}
.skeleton-line.lg { height: 26px; width: 65%; }
.skeleton-line.md { width: 90%; }
.skeleton-line.sm { width: 45%; }
@keyframes shimmer {
  0% { background-position: 100% 0; }
  100% { background-position: 0 0; }
}

/* ---- Progress: fills grow in, live segment breathes --------- */
.journey-bar { overflow: hidden; }
.journey-seg { transition: background 0.4s ease; }
.journey-seg.filled { animation: seg-fill 0.6s var(--ease-out-expo) backwards; }
.journey-seg.filled:last-of-type { animation: seg-fill 0.6s var(--ease-out-expo) backwards, seg-breathe 2.6s ease-in-out 1s infinite; }
@keyframes seg-fill { from { transform: scaleX(0); transform-origin: left; } }
@keyframes seg-breathe { 0%, 100% { filter: brightness(1); } 50% { filter: brightness(1.18); } }

/* ---- The Atlantic arc (USA hub hero) ------------------------ */
.us-arc-path {
  stroke-dasharray: 6 7;
  animation: arc-flow 30s linear infinite;
}
@keyframes arc-flow { to { stroke-dashoffset: -520; } }
.us-arc-dot {
  offset-path: path("M 20 78 Q 150 -14 288 62");
  animation: arc-travel 5.5s var(--ease-out-expo) infinite;
}
@keyframes arc-travel {
  0%   { offset-distance: 0%;   opacity: 0; }
  8%   { opacity: 1; }
  88%  { opacity: 1; }
  100% { offset-distance: 100%; opacity: 0; }
}
.us-arc-pin { animation: pin-pulse 2.4s ease-in-out infinite; transform-origin: center; transform-box: fill-box; }
@keyframes pin-pulse { 0%, 100% { transform: scale(1); opacity: 0.9; } 50% { transform: scale(1.35); opacity: 1; } }

/* ---- Flash notices: arrive, don't appear -------------------- */
.flash, .notice, .alert { animation: reveal-rise 0.45s var(--ease-out-expo); }

/* ---- Chips settle in ---------------------------------------- */
.chip { transition: transform 0.18s var(--ease-spring); }
.chip:hover { transform: translateY(-1px); }

/* ---- Reduced motion: everything above collapses ------------- */
@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1; transform: none; }
  [data-reveal].revealed { animation: none; }
  .market-card, .stat-card, .dashboard-opportunity, .market-card .market-card-thumb,
  .btn, .nav a, .chip { transition: none; }
  .market-card:hover, .stat-card:hover, .dashboard-opportunity:hover { transform: none; }
  dialog.deal-modal[open], .skeleton-line, .journey-seg.filled,
  .us-arc-path, .us-arc-dot, .us-arc-pin, .flash, .notice, .alert { animation: none; }
  .us-arc-dot { offset-distance: 100%; opacity: 1; }
}

/* ---- "Allocation secured" celebration (2026-07-19) ----------
   One-shot takeover after a successful reserve. The check draws
   itself, sparks rise, the profit counts up (countup controller).
   Auto-dismissed by the celebration controller. */
.celebration {
  position: fixed; inset: 0; z-index: 300;
  display: flex; align-items: center; justify-content: center;
  background: rgba(28, 45, 53, 0.55);
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  animation: celebration-in 0.3s ease-out;
  cursor: pointer;
}
@keyframes celebration-in { from { opacity: 0; } }
.celebration-card {
  position: relative;
  background: #fcfcfc;
  border-radius: 18px;
  padding: 36px 44px 30px;
  text-align: center;
  max-width: 420px; width: calc(100% - 48px);
  box-shadow: 0 30px 80px rgba(0, 0, 0, 0.35);
  animation: celebration-pop 0.55s var(--ease-spring);
  overflow: hidden;
}
@keyframes celebration-pop {
  from { transform: scale(0.82) translateY(14px); opacity: 0; }
  to   { transform: scale(1) translateY(0); opacity: 1; }
}
.celebration-check { display: block; margin: 0 auto 14px; }
.celebration-check-ring {
  stroke-dasharray: 195; stroke-dashoffset: 195;
  animation: check-draw 0.55s var(--ease-out-expo) 0.15s forwards;
}
.celebration-check-tick {
  stroke-dasharray: 42; stroke-dashoffset: 42;
  animation: check-draw 0.35s var(--ease-out-expo) 0.62s forwards;
}
@keyframes check-draw { to { stroke-dashoffset: 0; } }
.celebration-headline {
  font-family: "Syne", sans-serif;
  font-size: 24px; font-weight: 800; color: #1c2d35;
  letter-spacing: -0.01em;
}
.celebration-deal { margin-top: 6px; font-size: 14px; font-weight: 600; color: #1c2d35; }
.celebration-meta { margin-top: 2px; font-size: 12.5px; color: #5d6f6a; }
.celebration-profit {
  margin-top: 14px;
  font-size: 34px; font-weight: 800; color: var(--mint-dark, #2e7d5b);
  animation: celebration-in 0.4s ease-out 0.8s backwards;
}
.celebration-profit-label {
  font-size: 11px; text-transform: uppercase; letter-spacing: 0.07em; color: #5d6f6a;
}
.celebration-hint { margin-top: 16px; font-size: 11.5px; color: #8fa39c; }
.celebration-spark {
  position: absolute; bottom: -8px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--mint, #75c69e);
  opacity: 0;
  animation: spark-rise 1.5s ease-out forwards;
}
@keyframes spark-rise {
  0%   { opacity: 0; transform: translateY(0) scale(1); }
  15%  { opacity: 0.9; }
  100% { opacity: 0; transform: translateY(-340px) scale(0.4); }
}
.celebration-spark-0 { left: 8%;  animation-delay: 0.25s; }
.celebration-spark-1 { left: 18%; animation-delay: 0.55s; width: 5px; height: 5px; }
.celebration-spark-2 { left: 28%; animation-delay: 0.35s; }
.celebration-spark-3 { left: 38%; animation-delay: 0.75s; width: 4px; height: 4px; }
.celebration-spark-4 { left: 50%; animation-delay: 0.30s; }
.celebration-spark-5 { left: 60%; animation-delay: 0.65s; width: 5px; height: 5px; }
.celebration-spark-6 { left: 70%; animation-delay: 0.45s; }
.celebration-spark-7 { left: 80%; animation-delay: 0.85s; width: 4px; height: 4px; }
.celebration-spark-8 { left: 90%; animation-delay: 0.50s; }
.celebration-spark-9 { left: 44%; animation-delay: 1.00s; width: 5px; height: 5px; }
.celebration-out { opacity: 0; transition: opacity 0.4s ease; }
@media (prefers-reduced-motion: reduce) {
  .celebration, .celebration-card, .celebration-profit { animation: none; }
  .celebration-check-ring, .celebration-check-tick { animation: none; stroke-dashoffset: 0; }
  .celebration-spark { display: none; }
}

/* ==================================================================
   DEAL FLOW COMMAND CONSOLE (2026-07-19)
   ==================================================================
   H on the old five-row filter stack: "way too old fashioned,
   lacks sleekness and wow factor." The whole cluster condenses
   into one glass console: segmented status track (the navy thumb
   GLIDES between slots via Turbo 8 view transitions), inset
   search well with a mint focus bloom, bespoke sort, a one-line
   selling/category rail, and a footer strip with the sourcing
   whisper + live deal readout. Sticky on tall desktop viewports;
   the glass deepens once detached (.is-stuck via the mkt-filters
   controller's sentinel observer).

   SCOPE DISCIPLINE: every shared-class override here is scoped
   under .mkt-console. (The staff deal-flow preview got its own
   console via the generic .console-surface components on
   2026-07-19 -- see CONSOLE LANGUAGE section below; .mkt-pill
   remains only as a legacy family.) GRID COUPLING: the toolbar
   form is display:contents, so its children are console-grid
   items -- any future toolbar element needs a slot in the
   grid-template below.
   ================================================================== */

.mkt-console-sentinel { height: 1px; }

.mkt-console {
  position: relative;
  z-index: 40;
  border-radius: 20px;
  padding: 14px 16px 10px;
  margin-bottom: 16px;
  border: 1px solid rgba(226, 235, 238, 0.9);
  background: linear-gradient(180deg, rgba(252, 252, 252, 0.86), rgba(234, 245, 238, 0.55));
  -webkit-backdrop-filter: blur(14px) saturate(1.4);
  backdrop-filter: blur(14px) saturate(1.4);
  box-shadow: 0 1px 2px rgba(28, 45, 53, 0.05),
              0 12px 32px -12px rgba(28, 45, 53, 0.14),
              inset 0 1px 0 rgba(255, 255, 255, 0.7);
  /* No entrance animation on purpose: every filter tap re-renders
     this page, so a slide-in would replay on each click AND fight
     the view-transition snapshot. The root crossfade IS the
     entrance. */
}
/* Mint power-line across the top edge. */
.mkt-console::before {
  content: "";
  position: absolute;
  top: -1px; left: 24px; right: 24px;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--mint), transparent);
  opacity: 0.5;
}
@supports not (backdrop-filter: blur(1px)) {
  .mkt-console { background: var(--surface); }
}

/* Sticky only where it earns its keep: wide + tall viewports
   (short laptops at high zoom shouldn't lose a third of the
   screen to a pinned toolbar). */
@media (min-width: 821px) and (min-height: 700px) {
  .mkt-console { position: sticky; top: 10px; }
}
.mkt-console.is-stuck {
  -webkit-backdrop-filter: blur(22px) saturate(1.5);
  backdrop-filter: blur(22px) saturate(1.5);
  background: linear-gradient(180deg, rgba(252, 252, 252, 0.72), rgba(234, 245, 238, 0.5));
  box-shadow: 0 18px 40px -14px rgba(28, 45, 53, 0.22),
              inset 0 1px 0 rgba(255, 255, 255, 0.7);
  transition: box-shadow 0.3s ease, background 0.3s ease;
}

/* ---- Console grid (desktop) ------------------------------------ */
@media (min-width: 821px) {
  .mkt-console {
    display: grid;
    grid-template-columns: minmax(220px, 1fr) auto auto;
    grid-template-areas:
      "seg    seg  seg"
      "search sort fkey"
      "rail   rail rail"
      "foot   foot foot";
    gap: 10px;
    align-items: center;
  }
  .mkt-console .mkt-seg-status    { grid-area: seg; }
  .mkt-console .mkt-search-input  { grid-area: search; }
  .mkt-console .mkt-sort          { grid-area: sort; }
  .mkt-console .mkt-filters-toggle{ grid-area: fkey; }
  .mkt-console .mkt-rail          { grid-area: rail; }
  .mkt-console .mkt-console-foot  { grid-area: foot; }
  .mkt-console .mkt-mobile-filters{ display: none; }
}
/* Wide desktops: the full one-line command row. */
@media (min-width: 1420px) {
  .mkt-console {
    grid-template-columns: auto minmax(220px, 1fr) auto auto;
    grid-template-areas:
      "seg  search sort fkey"
      "rail rail   rail rail"
      "foot foot   foot foot";
  }
}
/* The form dissolves into the grid; children stay form-associated
   (hidden fields + panel submit exactly as before). */
.mkt-console .mkt-toolbar-form { display: contents; }

/* ---- Segmented tracks ------------------------------------------ */
.mkt-seg {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border-radius: 999px;
  background: rgba(28, 45, 53, 0.05);
  box-shadow: inset 0 1px 2px rgba(28, 45, 53, 0.06);
  justify-self: start;
}
.mkt-seg-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
  transition: color 0.18s ease, background-color 0.18s ease, transform 0.12s var(--ease-spring);
}
.mkt-seg-item:hover {
  color: var(--navy);
  background: rgba(252, 252, 252, 0.7);
}
.mkt-seg-item:active { transform: scale(0.96); }
.mkt-seg-item.active {
  background: var(--navy);
  color: var(--white);
  box-shadow: 0 2px 8px rgba(28, 45, 53, 0.28),
              inset 0 1px 0 rgba(255, 255, 255, 0.12);
}
/* The thumb: Turbo 8 morphs the element carrying this name across
   the round-trip -- the navy pill physically glides along the
   track. One name per track, only ever on the single .active item. */
.mkt-seg-status .mkt-seg-item.active { view-transition-name: mkt-seg-status; }
.mkt-seg-flow   .mkt-seg-item.active { view-transition-name: mkt-seg-flow; }
.mkt-seg-item small {
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
  opacity: 0.75;
  vertical-align: 3px;
  position: relative;
  top: -3px;
}
.mkt-seg-divider {
  width: 1px;
  height: 16px;
  margin: 0 4px;
  background: var(--line-2);
}
.mkt-watch-star { opacity: 0.6; }
.mkt-watch-star.has-items { color: var(--mint-2); opacity: 1; }
.mkt-seg-item.active .mkt-watch-star.has-items { color: var(--mint-3); }

/* ---- Search well + bespoke sort + Filters key ------------------ */
.mkt-console .mkt-search-input {
  max-width: none;
  min-width: 0;
  height: 38px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: rgba(28, 45, 53, 0.045)
              url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='%235d6f6a' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'/%3E%3C/svg%3E")
              no-repeat 12px center;
  box-shadow: inset 0 1px 3px rgba(28, 45, 53, 0.07);
  padding: 0 14px 0 36px;
  font-size: 13.5px;
  transition: background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.45s var(--ease-out-expo);
}
.mkt-console .mkt-search-input:focus {
  outline: none;
  background-color: var(--surface);
  border-color: var(--mint);
  box-shadow: inset 0 1px 3px rgba(28, 45, 53, 0.04),
              0 0 0 4px rgba(117, 198, 158, 0.18),
              0 6px 18px -6px rgba(117, 198, 158, 0.35);
}
.mkt-console .mkt-sort {
  appearance: none;
  -webkit-appearance: none;
  height: 38px;
  min-width: 200px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface)
              url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%231c2d35' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E")
              no-repeat right 12px center;
  padding: 0 34px 0 14px;
  font-size: 13px;
  font-weight: 500;
  color: var(--navy);
  cursor: pointer;
  transition: border-color 0.2s var(--ease-out-expo), transform 0.2s var(--ease-out-expo);
}
.mkt-console .mkt-sort:hover { border-color: var(--mint-2); transform: translateY(-1px); }
.mkt-console .mkt-filters-toggle {
  height: 38px;
  padding: 0 16px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background: var(--surface);
  font-size: 13px;
  font-weight: 500;
  color: var(--navy);
  transition: border-color 0.2s var(--ease-out-expo), transform 0.2s var(--ease-out-expo);
}
.mkt-console .mkt-filters-toggle:hover { border-color: var(--mint-2); transform: translateY(-1px); }
.mkt-console .mkt-filters-count {
  min-width: 16px;
  margin-left: 6px;
  padding: 1px 5px;
  border-radius: 999px;
  font-size: 10px;
  background: var(--mint);
  color: var(--navy);
}

/* ---- Filters tray (inside the form; drops from the console) ---- */
@media (min-width: 821px) {
  .mkt-console .mkt-filters-panel {
    position: absolute;
    top: calc(100% + 10px);
    left: 0;
    right: 0;
    z-index: 30;
    margin: 0;
    border-radius: 8px;
    border: 1px solid rgba(226, 235, 238, 0.9);
    background: linear-gradient(180deg, rgba(252, 252, 252, 0.92), rgba(234, 245, 238, 0.7));
    -webkit-backdrop-filter: blur(14px) saturate(1.4);
    backdrop-filter: blur(14px) saturate(1.4);
    box-shadow: 0 18px 40px -14px rgba(28, 45, 53, 0.25);
    transform-origin: top;
    animation: tray-drop 0.3s var(--ease-out-expo);
  }
}
@keyframes tray-drop {
  from { opacity: 0; transform: translateY(-6px); }
  to   { opacity: 1; transform: none; }
}
/* Syne engraved labels inside the tray (graft: editorial spec). */
.mkt-console .mkt-filters-panel label {
  font-family: "Syne", sans-serif;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ---- Selling / category rail ----------------------------------- */
.mkt-rail {
  display: flex;
  align-items: center;
  gap: 10px;
  row-gap: 8px;
  /* Wrap: on mid-width desktops the flow track + chips + pinned
     CTA can't share one line -- the scroller's min-width forces
     the category strip + pinned zone onto their own row. */
  flex-wrap: wrap;
  min-width: 0;
}
.mkt-rail-eyebrow {
  font-family: "Syne", sans-serif;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--muted);
  opacity: 0.7;
}
.mkt-rail-divider {
  width: 1px;
  height: 20px;
  background: var(--line);
  flex: 0 0 auto;
}
/* Below the one-line width the divider would dangle at a line
   end -- the wrap itself is the separation. */
@media (max-width: 1419px) {
  .mkt-rail-divider { display: none; }
}
/* Scroller + pinned zone as one wrap unit: on mid-width desktops
   the whole unit drops below the flow track rather than orphaning
   More/CTA alone on a line. */
.mkt-rail-cats {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 1 1 auto;
  min-width: min(480px, 100%);
}
/* The category strip becomes a one-line scroller with edge fades. */
.mkt-console .category-pills {
  flex: 1 1 auto;
  min-width: 0;
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
  /* End padding keeps the last chip clear of the right fade; the
     left fade is a whisper (6px) so the first chip -- often the
     active navy All -- never looks clipped at rest. */
  padding: 2px 36px 2px 4px;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6px, #000 calc(100% - 32px), transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6px, #000 calc(100% - 32px), transparent);
}
.mkt-console .category-pills::-webkit-scrollbar { display: none; }
.mkt-console .category-pill {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  height: 30px;
  padding: 0 13px;
  border-radius: 999px;
  background: transparent;
  border: 1px solid var(--line);
  transition: border-color 0.18s ease, color 0.18s ease, transform 0.12s var(--ease-spring);
}
.mkt-console .category-pill:hover {
  background: transparent;
  border-color: var(--mint-2);
  color: var(--deep);
}
.mkt-console .category-pill:active { transform: scale(0.96); }
.mkt-console .category-pill.active {
  background: var(--navy);
  border-color: var(--navy);
  color: var(--white);
}
.mkt-console .category-pill-bell { color: var(--mint-2); opacity: 0.85; }
.mkt-console .category-pill.active .category-pill-bell { color: var(--mint-3); }

/* Pinned zone: More + the alert CTA never scroll away. */
.mkt-rail-pinned {
  display: flex;
  align-items: center;
  gap: 8px;
  flex: 0 0 auto;
  position: relative;
}
/* More becomes a dropdown card (display:contents can't live in a
   scroller, and it left the scroller anyway). */
.mkt-console .category-more { display: block; position: relative; }
.mkt-console .category-more-panel {
  position: absolute;
  right: 0;
  top: calc(100% + 8px);
  z-index: 30;
  min-width: 300px;
  margin: 0;
  padding: 12px;
  border-radius: 6px;
  border: 1px solid rgba(226, 235, 238, 0.9);
  background: linear-gradient(180deg, rgba(252, 252, 252, 0.95), rgba(234, 245, 238, 0.8));
  -webkit-backdrop-filter: blur(14px);
  backdrop-filter: blur(14px);
  box-shadow: 0 18px 40px -14px rgba(28, 45, 53, 0.25);
  animation: tray-drop 0.3s var(--ease-out-expo);
}
/* The rail's single mint object: the alert CTA. */
.mkt-console .category-alert-cta {
  height: 30px;
  padding: 0 14px;
  border: 0;
  border-radius: 999px;
  font-weight: 600;
  background: linear-gradient(180deg, var(--mint-3), var(--mint));
  color: var(--navy);
  box-shadow: 0 4px 12px -4px rgba(95, 175, 136, 0.5);
  transition: box-shadow 0.2s ease, transform 0.12s var(--ease-spring);
}
.mkt-console .category-alert-cta:hover {
  background: linear-gradient(180deg, var(--mint-3), var(--mint));
  color: var(--navy);
  box-shadow: 0 6px 16px -4px rgba(95, 175, 136, 0.65);
}
.mkt-console .category-alert-cta:active { transform: scale(0.96); }
.mkt-console .category-alert-cta.active {
  background: linear-gradient(180deg, var(--mint), var(--mint-2));
  color: var(--navy);
}
/* Bell-ring on hover (graft): one shake, never looping. */
.mkt-console .category-alert-cta:hover .ico,
.mkt-alert-standalone-btn:hover .ico {
  animation: bell-ring 0.55s var(--ease-spring);
  transform-origin: top center;
}
@keyframes bell-ring {
  0% { transform: rotate(0); }
  30% { transform: rotate(11deg); }
  60% { transform: rotate(-8deg); }
  80% { transform: rotate(3deg); }
  100% { transform: rotate(0); }
}

/* ---- Footer strip ---------------------------------------------- */
.mkt-console-foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  padding-top: 8px;
  border-top: 1px solid rgba(226, 235, 238, 0.7);
  min-width: 0;
}
.mkt-want { min-width: 0; position: relative; }
.mkt-want-line {
  cursor: pointer;
  list-style: none;
  font-size: 12px;
  font-weight: 600;
  color: var(--deep);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.mkt-want-line::-webkit-details-marker { display: none; }
.mkt-want-line:hover { color: var(--mint-2); }
.mkt-want-card {
  margin-top: 8px;
  max-width: 560px;
  padding: 14px;
}
.mkt-want-row {
  display: flex;
  gap: 6px;
  flex-wrap: wrap;
  align-items: center;
}
.mkt-want-kind { height: 32px; font-size: 13px; }
.mkt-want-subject { flex: 1; min-width: 200px; height: 32px; font-size: 13px; }
.mkt-want-note { width: 100%; height: 32px; font-size: 12.5px; margin-top: 6px; }
.mkt-want-detail { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 6px; }
.mkt-want-detail-field { flex: 1; min-width: 120px; height: 32px; font-size: 12.5px; }
.mkt-want-requests { margin-top: 10px; }
.mkt-want-request-row {
  display: flex;
  gap: 8px;
  align-items: baseline;
  font-size: 12.5px;
  margin-top: 4px;
}
/* Live readout: pulse dot + Syne small-caps count + CSV whisper. */
.mkt-foot-readout {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
}
.mkt-live-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--mint);
  animation: live-pulse 2.4s ease-out infinite;
}
@keyframes live-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(117, 198, 158, 0.5); }
  70%  { box-shadow: 0 0 0 6px rgba(117, 198, 158, 0); }
  100% { box-shadow: 0 0 0 0 rgba(117, 198, 158, 0); }
}
.mkt-deal-readout {
  font-family: "Syne", sans-serif;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.mkt-console .mkt-csv-link {
  font-size: 12.5px;
  color: var(--muted);
  text-decoration: none;
  padding: 0;
}
.mkt-console .mkt-csv-link:hover { color: var(--deep); text-decoration: underline; }

/* ---- Focus discipline (several default borders were removed) --- */
.mkt-console .mkt-seg-item:focus-visible,
.mkt-console .category-pill:focus-visible,
.mkt-console .category-alert-cta:focus-visible,
.mkt-console .mkt-sort:focus-visible,
.mkt-console .mkt-filters-toggle:focus-visible,
.mkt-console .mkt-csv-link:focus-visible,
.mkt-console .mkt-want-line:focus-visible {
  outline: 2px solid var(--mint-3);
  outline-offset: 3px;
}

/* ---- View-transition tuning ------------------------------------ */
::view-transition-group(mkt-seg-status),
::view-transition-group(mkt-seg-flow) {
  animation-duration: 0.45s;
  animation-timing-function: var(--ease-out-expo);
}
::view-transition-old(root),
::view-transition-new(root) {
  animation-duration: 0.25s;
}

/* ---- Mobile console (<=820px) ----------------------------------
   The proven nav-select OS-picker mechanism stays; it just gets
   the console's glass-well skin. Sticky is OFF (pull-refresh +
   viewport), the rail + desktop readout hide (the standalone
   alert button + meta line below the console carry those), and
   the whole stack becomes a flex-wrap band layout: search first,
   then the 2x2 select grid, then sort + Filters. 16px font floor
   everywhere -- no iOS focus zoom. */
@media (max-width: 820px) {
  .mkt-console {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    position: static;
    border-radius: 8px;
    padding: 12px;
    -webkit-backdrop-filter: blur(10px) saturate(1.3);
    backdrop-filter: blur(10px) saturate(1.3);
    margin-bottom: 12px;
  }
  /* The earlier .mkt-status-row hide (5748) loses the cascade to
     .mkt-seg's display:inline-flex (later in file) -- re-hide the
     desktop track explicitly here. */
  .mkt-console .mkt-seg-status { display: none; }
  .mkt-console .mkt-rail { display: none; }
  .mkt-console .mkt-foot-readout { display: none; }
  .mkt-console .mkt-search-input {
    order: 1;
    flex: 1 0 100%;
    height: 44px;
    font-size: 16px;
  }
  /* 2x2 select grid (graft: today all four squeeze onto one row). */
  .mkt-console .mkt-mobile-filters {
    order: 2;
    flex: 1 0 100%;
    flex-wrap: wrap;
    margin-bottom: 0;
  }
  .mkt-console .mkt-mobile-select { flex: 1 1 45%; }
  .mkt-console .mkt-mobile-select,
  .mkt-console .mkt-sort {
    height: 44px;
    border: 1px solid transparent;
    border-radius: 6px;
    background-color: rgba(28, 45, 53, 0.045);
    box-shadow: inset 0 1px 3px rgba(28, 45, 53, 0.06);
  }
  .mkt-console .mkt-sort { order: 3; flex: 1 1 0; min-width: 0; font-size: 16px; }
  .mkt-console .mkt-filters-toggle { order: 3; flex: 0 0 auto; height: 44px; font-size: 14px; }
  .mkt-console .mkt-filters-panel {
    order: 4;
    flex: 1 0 100%;
    position: static;
    animation: none;
  }
  .mkt-console .mkt-console-foot {
    order: 5;
    flex: 1 0 100%;
    border-top: 1px solid rgba(226, 235, 238, 0.7);
  }
  /* The standalone alert button takes the mint CTA skin. */
  .mkt-alert-standalone-btn {
    border: 0;
    background: linear-gradient(180deg, var(--mint-3), var(--mint));
    color: var(--navy);
    box-shadow: 0 4px 12px -4px rgba(95, 175, 136, 0.5);
  }
  .mkt-alert-standalone-btn.active {
    background: linear-gradient(180deg, var(--mint), var(--mint-2));
    color: var(--navy);
  }
}

/* ---- Reduced motion -------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
  .mkt-console,
  .mkt-console .mkt-filters-panel,
  .mkt-console .category-more-panel { animation: none; }
  .mkt-live-dot { animation: none; }
  .mkt-console .category-alert-cta:hover .ico,
  .mkt-alert-standalone-btn:hover .ico { animation: none; }
  .mkt-seg-item, .mkt-console .category-pill,
  .mkt-console .mkt-sort, .mkt-console .mkt-filters-toggle { transition: none; }
  .mkt-console .mkt-sort:hover, .mkt-console .mkt-filters-toggle:hover { transform: none; }
}

/* ==================================================================
   CONSOLE LANGUAGE — SHARED COMPONENTS (2026-07-19)
   ==================================================================
   H on the Deal Flow console: "i love this new look... apply the
   same visuals throughout the client and staff portal where
   appropriate." These are the generic, unscoped versions of the
   .mkt-console recipes (which stay byte-identical above): glass
   surfaces for COMMAND zones only -- filter bars, toolbars, trays.
   Content (cards, tables, forms) keeps the light card language.
   At most ONE mint object per surface; gold stays Priority-only.
   ================================================================== */

/* ---- Glass console shell --------------------------------------- */
.console-surface {
  position: relative;
  border-radius: 20px;
  padding: 12px 16px;
  border: 1px solid rgba(226, 235, 238, 0.9);
  background: linear-gradient(180deg, rgba(252, 252, 252, 0.86), rgba(234, 245, 238, 0.55));
  -webkit-backdrop-filter: blur(14px) saturate(1.4);
  backdrop-filter: blur(14px) saturate(1.4);
  box-shadow: 0 1px 2px rgba(28, 45, 53, 0.05),
              0 12px 32px -12px rgba(28, 45, 53, 0.14),
              inset 0 1px 0 rgba(255, 255, 255, 0.7);
}
.console-surface::before {
  content: "";
  position: absolute;
  top: -1px; left: 24px; right: 24px;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--mint), transparent);
  opacity: 0.5;
}
@supports not (backdrop-filter: blur(1px)) {
  .console-surface { background: var(--surface); }
}

/* ---- Segmented track ------------------------------------------- */
.seg-track {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  padding: 3px;
  border-radius: 999px;
  background: rgba(28, 45, 53, 0.05);
  box-shadow: inset 0 1px 2px rgba(28, 45, 53, 0.06);
}
.seg-item {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  padding: 6px 14px;
  border-radius: 999px;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
  border: 0;
  background: transparent;
  cursor: pointer;
  transition: color 0.18s ease, background-color 0.18s ease, transform 0.12s var(--ease-spring);
}
.seg-item:hover {
  color: var(--navy);
  background: rgba(252, 252, 252, 0.7);
}
.seg-item:active { transform: scale(0.96); }
.seg-item.active {
  background: var(--navy);
  color: var(--white);
  box-shadow: 0 2px 8px rgba(28, 45, 53, 0.28),
              inset 0 1px 0 rgba(255, 255, 255, 0.12);
}
.seg-item small {
  font-size: 10.5px;
  font-variant-numeric: tabular-nums;
  opacity: 0.75;
  position: relative;
  top: -3px;
}
.seg-item--muted { opacity: 0.65; }
.seg-divider {
  width: 1px;
  height: 16px;
  margin: 0 4px;
  background: var(--line-2);
  flex: 0 0 auto;
}
/* Per-page thumb-glide names (Turbo 8 view transitions). One name
   per rendered page; the same name on two visible tracks silently
   kills the glide. Reuse across DIFFERENT pages is safe. */
.seg--orders .seg-item.active        { view-transition-name: seg-orders; }
.seg--paperwork .seg-item.active     { view-transition-name: seg-paperwork; }
.seg--preview-tier .seg-item.active  { view-transition-name: seg-preview-tier; }
.seg--preview-vat .seg-item.active   { view-transition-name: seg-preview-vat; }
.seg--preview-tab .seg-item.active   { view-transition-name: seg-preview-tab; }
.seg--staff-orders .seg-item.active  { view-transition-name: seg-staff-orders; }
.seg--scout .seg-item.active         { view-transition-name: seg-scout; }
/* Deal Scout's channel/filter pills DON'T glide (H, 2026-08-02: caught
   the morph snapshots floating mid-table). Scout pages are huge and
   reload scrolled (sorts, restored scroll), so the pill's old and new
   positions sit a viewport apart — the transition ghosts fly across
   the rows and freeze if interrupted. Short pages keep the glide. */
/* Channel bar + sub-tabs stack, both shrink-wrapped. */
.scout-tab-stack {
  display: inline-flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 6px;
}
::view-transition-group(seg-orders),
::view-transition-group(seg-paperwork),
::view-transition-group(seg-preview-tier),
::view-transition-group(seg-preview-vat),
::view-transition-group(seg-preview-tab),
::view-transition-group(seg-staff-orders),
::view-transition-group(seg-scout) {
  animation-duration: 0.45s;
  animation-timing-function: var(--ease-out-expo);
}

/* ---- Inset search well ------------------------------------------ */
.well-input {
  height: 38px;
  border: 1px solid transparent;
  border-radius: 6px;
  background: rgba(28, 45, 53, 0.045)
              url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='15' height='15' viewBox='0 0 24 24' fill='none' stroke='%235d6f6a' stroke-width='2.2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='7'/%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'/%3E%3C/svg%3E")
              no-repeat 12px center;
  box-shadow: inset 0 1px 3px rgba(28, 45, 53, 0.07);
  padding: 0 14px 0 36px;
  font-size: 13.5px;
  font-family: inherit;
  color: var(--ink);
  width: 100%;
  transition: background-color 0.2s ease, border-color 0.2s ease, box-shadow 0.45s var(--ease-out-expo);
}
.well-input:focus {
  outline: none;
  background-color: var(--surface);
  border-color: var(--mint);
  box-shadow: inset 0 1px 3px rgba(28, 45, 53, 0.04),
              0 0 0 4px rgba(117, 198, 158, 0.18),
              0 6px 18px -6px rgba(117, 198, 158, 0.35);
}

/* ---- Console-scale select --------------------------------------- */
.well-select {
  height: 38px;
  border: 1px solid var(--line);
  border-radius: 6px;
  background-color: var(--surface);
  font-size: 13px;
  font-weight: 500;
  color: var(--navy);
  padding: 0 34px 0 14px;
  transition: border-color 0.2s var(--ease-out-expo), transform 0.2s var(--ease-out-expo);
}
.well-select:hover { border-color: var(--mint-2); transform: translateY(-1px); }

/* ---- Engraved eyebrow ------------------------------------------- */
.eyebrow {
  font-family: "Syne", sans-serif;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ---- Live readout ----------------------------------------------- */
.live-readout {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: "Syne", sans-serif;
  font-size: 10.5px;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--muted);
}
.live-dot {
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--mint);
  flex: 0 0 auto;
  animation: live-pulse 2.4s ease-out infinite;
}

/* ---- Glass tray (dropdown panels) ------------------------------- */
.glass-tray {
  border-radius: 8px;
  border: 1px solid rgba(226, 235, 238, 0.9);
  background: linear-gradient(180deg, rgba(252, 252, 252, 0.94), rgba(234, 245, 238, 0.78));
  -webkit-backdrop-filter: blur(14px) saturate(1.4);
  backdrop-filter: blur(14px) saturate(1.4);
  box-shadow: 0 18px 40px -14px rgba(28, 45, 53, 0.25);
  animation: tray-drop 0.3s var(--ease-out-expo);
}
@supports not (backdrop-filter: blur(1px)) {
  .glass-tray { background: var(--surface); }
}

/* ---- Edge-fade chip rail ---------------------------------------- */
.chip-rail {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: nowrap;
  overflow-x: auto;
  scrollbar-width: none;
  min-width: 0;
  padding: 2px 36px 2px 4px;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6px, #000 calc(100% - 32px), transparent);
  mask-image: linear-gradient(90deg, transparent, #000 6px, #000 calc(100% - 32px), transparent);
}
.chip-rail::-webkit-scrollbar { display: none; }

/* ---- Mint CTA modifier ------------------------------------------ */
.cta-mint {
  border: 0;
  background: linear-gradient(180deg, var(--mint-3), var(--mint));
  color: var(--navy);
  font-weight: 600;
  box-shadow: 0 4px 12px -4px rgba(95, 175, 136, 0.5);
}
.cta-mint:hover {
  background: linear-gradient(180deg, var(--mint-3), var(--mint));
  color: var(--navy);
  box-shadow: 0 6px 16px -4px rgba(95, 175, 136, 0.65);
}

/* ---- Console chips (any .console-surface) ----------------------- */
.console-surface .category-pill {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  height: 30px;
  padding: 0 13px;
  border-radius: 999px;
  background: transparent;
  border: 1px solid var(--line);
  transition: border-color 0.18s ease, color 0.18s ease, transform 0.12s var(--ease-spring);
}
.console-surface .category-pill:hover {
  background: transparent;
  border-color: var(--mint-2);
  color: var(--deep);
}
.console-surface .category-pill:active { transform: scale(0.96); }
.console-surface .category-pill.active {
  background: var(--navy);
  border-color: var(--navy);
  color: var(--white);
}

/* ---- Staff Deal Flow preview console (batch 6) ------------------ */
.preview-console {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
.preview-console-head,
.preview-console-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.preview-search-form {
  flex: 1 1 240px;
  min-width: 240px;
  display: flex;
}
.preview-search-form .well-input { max-width: 380px; }

/* ---- Paperwork seg (client, batch 4) ---------------------------- */
.paperwork-seg {
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
}
.paperwork-seg::-webkit-scrollbar { display: none; }

/* ---- My Orders console (client, batch 3) ------------------------ */
.orders-console {
  display: flex;
  flex-direction: column;
  gap: 10px;
}
/* Eight slots outgrow mid-width rows: the track scrolls rather
   than wrapping (a wrapped seg reads broken). */
.orders-status-seg {
  max-width: 100%;
  overflow-x: auto;
  scrollbar-width: none;
}
.orders-status-seg::-webkit-scrollbar { display: none; }
.orders-console .well-input { max-width: 420px; }
.orders-console-clear {
  font-size: 12.5px;
  color: var(--muted);
  text-decoration: none;
  white-space: nowrap;
}
.orders-console-clear:hover { color: var(--deep); text-decoration: underline; }
.orders-console-foot {
  display: flex;
  align-items: center;
  padding-top: 8px;
  border-top: 1px solid rgba(226, 235, 238, 0.7);
}
@media (max-width: 820px) {
  .orders-console { border-radius: 8px; padding: 12px; }
  .orders-console .well-input {
    max-width: none;
    height: 44px;
    font-size: 16px;            /* iOS zoom floor */
  }
  /* The mobile status select takes the glass-well skin inside the
     console (same recipe as the Deal Flow mobile wells). */
  .orders-console .mkt-mobile-select {
    height: 44px;
    border: 1px solid transparent;
    border-radius: 6px;
    background-color: rgba(28, 45, 53, 0.045);
    box-shadow: inset 0 1px 3px rgba(28, 45, 53, 0.06);
  }
}

/* ---- Visit-in-flight feedback ----------------------------------- */
/* Turbo marks <html aria-busy> for the whole visit. Slow server
   renders (Deal Scout's 38k-row lists) left clicks FEELING dead --
   the seg tracks now dim while the next page is being built, and
   the progress bar is mint and unmissable. The 0.15s delay keeps
   fast pages flicker-free. */
.turbo-progress-bar {
  height: 3px;
  background: linear-gradient(90deg, var(--mint-2), var(--mint));
}
html[aria-busy="true"] .seg-track,
html[aria-busy="true"] .mkt-seg {
  opacity: 0.55;
  transition: opacity 0.2s ease 0.15s;
}

/* ---- Focus + reduced-motion discipline -------------------------- */
.seg-item:focus-visible,
.well-input:focus-visible,
.well-select:focus-visible,
.select:focus-visible,
.cta-mint:focus-visible {
  outline: 2px solid var(--mint-3);
  outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
  .live-dot { animation: none; }
  .glass-tray, .notification-panel, .side-account-menu { animation: none; }
  .seg-item, .well-select { transition: none; }
  .well-select:hover { transform: none; }
}

/* ---- /join beta landing hero (2026-07-20) ----------------------- */
.anon-hero-points {
  list-style: none;
  padding: 0;
  margin: 18px 0 0;
  display: grid;
  gap: 9px;
}
.anon-hero-points li {
  position: relative;
  padding-left: 24px;
  font-size: 14px;
  color: rgba(252, 252, 252, 0.88);
}
.anon-hero-points li::before {
  content: "";
  position: absolute;
  left: 0; top: 5px;
  width: 14px; height: 14px;
  border-radius: 50%;
  background: rgba(117, 198, 158, 0.18);
  box-shadow: inset 0 0 0 1.5px var(--mint);
}
.anon-hero-points li::after {
  content: "";
  position: absolute;
  left: 3.5px; top: 8.5px;
  width: 7px; height: 4.5px;
  border-left: 1.5px solid var(--mint);
  border-bottom: 1.5px solid var(--mint);
  transform: rotate(-45deg);
}
.anon-hero-offer {
  margin-top: 20px;
  font-family: "Syne", sans-serif;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--mint-3);
}

/* ---- /join hero showpiece: "Console Drop" (2026-07-20) -----------
   The live-deal-feed illustration for the beta landing, at the same
   craft grade as the sign-in brand hero. Namespaced under .join-hero
   so nothing leaks to app UI. Pure SVG + CSS, no JS. Reuses the
   brand hero's bh* keyframes (defined earlier in this file inside
   the same no-preference media query); new keyframes are jh-prefixed.
   The buzz -> Reserve tap -> Reserved pulse trio share one 6s clock
   so the claim chain reads causally, not as three loose loops. */
.join-hero { width: 100%; max-width: min(580px, 64vh); }
.join-hero svg { display: block; width: 100%; height: auto; }
.anon-hero:has(.join-hero) { gap: 22px; }

.join-hero .jh-lbl      { font-family: var(--font-display); font-weight: 700; letter-spacing: 2px; text-transform: uppercase; font-size: 11px; fill: var(--muted); }
.join-hero .jh-cardlbl  { font-family: var(--font-display); font-weight: 800; letter-spacing: 2.5px; font-size: 10px; fill: var(--mint); }
.join-hero .jh-chiptext { font-family: var(--font); font-weight: 500; font-size: 13px; fill: var(--mint); }
.join-hero .jh-glyphlbl { font-family: var(--font); font-weight: 400; font-size: 12px; fill: var(--light); }
.join-hero .jh-microlbl { font-family: var(--font); font-weight: 500; font-size: 9.5px; letter-spacing: .5px; fill: var(--mint-3); }
.join-hero .jh-prodname { font-family: var(--font); font-weight: 500; font-size: 13.5px; fill: #ecf4ef; }
.join-hero .jh-prodsub,
.join-hero .jh-foot     { font-family: var(--font); font-weight: 400; font-size: 11px; fill: #8ea6ad; }
.join-hero .jh-statlbl  { font-family: var(--font-display); font-weight: 700; font-size: 9px; letter-spacing: 1.5px; fill: #8ea6ad; }
/* Figures in Jost, not Syne — same rule as .stat-value (H, 2026-08-04). */
.join-hero .jh-statval  { font-family: var(--font); font-variant-numeric: tabular-nums; font-weight: 700; font-size: 19px; letter-spacing: -.01em; fill: #fff; }
.join-hero .jh-statroi  { font-family: var(--font); font-variant-numeric: tabular-nums; font-weight: 800; font-size: 24px; letter-spacing: -.01em; fill: var(--mint); }
.join-hero .jh-statsub  { font-family: var(--font); font-weight: 400; font-size: 9.5px; fill: #8ea6ad; }
.join-hero .jh-btntext  { font-family: var(--font); font-weight: 600; font-size: 13px; fill: var(--navy); }
.join-hero .jh-lanetext { font-family: var(--font-display); font-weight: 700; font-size: 10.5px; letter-spacing: 1.5px; fill: var(--light); }

.join-hero .jh-conn { fill: none; stroke: rgba(117,198,158,.22); stroke-width: 1.5; }
.join-hero .jh-flow { fill: none; stroke: var(--mint); stroke-width: 1.6; stroke-linecap: round; stroke-dasharray: 2 14; opacity: .9; }
.join-hero .jh-chip { opacity: 0; }        /* fades in via bhChipFloat */
.join-hero .jh-tap { opacity: 0; }         /* tap ripple only visible mid-animation */
.join-hero .jh-buzzmark { opacity: 0; }    /* vibration marks flash during buzz burst */
.join-hero .jh-lane { opacity: .8; }
.join-hero .jh-plane { fill: var(--mint); }
.join-hero .jh-plane-run { opacity: .9; }
.join-hero .jh-fb { transform-box: fill-box; transform-origin: center; }

@media (prefers-reduced-motion: no-preference) {
  /* phone buzz burst: still for ~half the cycle, then a rattle */
  @keyframes jhBuzz {
    0%, 54%, 82%, 100% { transform: rotate(0deg); }
    58% { transform: rotate(-3deg); }
    62% { transform: rotate(3deg); }
    66% { transform: rotate(-2.5deg); }
    70% { transform: rotate(2.5deg); }
    74% { transform: rotate(-1.5deg); }
    78% { transform: rotate(1.5deg); }
  }
  @keyframes jhBuzzMark { 0%, 50%, 88%, 100% { opacity: 0; } 58%, 78% { opacity: .9; } }
  /* queue cards nudging forward toward the console */
  @keyframes jhNudge { 0%, 100% { transform: translateX(0); } 50% { transform: translateX(5px); } }
  /* lane pills brighten in sequence: FBA -> FBM -> USA */
  @keyframes jhLaneGlow { 0%, 100% { opacity: .55; } 18% { opacity: 1; } 36% { opacity: .55; } }
  /* the US airlift: translate deltas are +/- from the route midpoint
     set on the outer <g> in the SVG, so reduced-motion parks the
     plane mid-flight */
  @keyframes jhPlaneHop {
    0%        { transform: translate(-48px, 15px); opacity: 0; }
    10%       { opacity: .9; }
    44%       { opacity: .9; }
    54%, 100% { transform: translate(48px, -15px); opacity: 0; }
  }

  .join-hero .jh-flow { animation: bhFlowDash 3.8s linear infinite; }
  .join-hero .jh-fq1 { animation-duration: 4.6s; }
  .join-hero .jh-fq2 { animation-duration: 5.2s; animation-delay: -1.4s; }
  .join-hero .jh-fq3 { animation-duration: 4.2s; animation-delay: -2.3s; }
  .join-hero .jh-fm1 { animation-duration: 3.6s; }
  .join-hero .jh-fm2 { animation-duration: 4.4s; animation-delay: -1.1s; }
  .join-hero .jh-fm3 { animation-duration: 4.0s; animation-delay: -2.0s; }
  .join-hero .jh-trace { animation: bhFlowDash 9s linear infinite; }
  .join-hero .jh-glow { animation: bhGlowPulse 6s ease-in-out infinite; }
  .join-hero .jh-cardhalo { animation: bhGlowPulse 6s ease-in-out infinite; animation-delay: -3s; }
  .join-hero .jh-live-dot { animation: bhGlowPulse 2.6s ease-in-out infinite; }
  .join-hero .jh-live-ripple { animation: bhDotRipple 2.6s ease-out infinite; }
  .join-hero .jh-btn { animation: bhHubPulse 5s ease-in-out infinite; }
  .join-hero .jh-halo { animation: bhHaloPulse 5.5s ease-in-out infinite; }
  .join-hero .jh-ripple { animation: bhDotRipple 5.5s ease-in-out infinite; }
  .join-hero .jh-m1 .jh-halo, .join-hero .jh-m1 .jh-ripple { animation-delay: -1.8s; }
  .join-hero .jh-m2 .jh-halo, .join-hero .jh-m2 .jh-ripple { animation-delay: -3.6s; }
  .join-hero .jh-q { animation: jhNudge 7s ease-in-out infinite; }
  .join-hero .jh-q2 { animation-delay: -1.2s; }
  .join-hero .jh-q3 { animation-delay: -2.4s; }
  /* causal claim chain on one 6s clock: the phone rattles (58-78%),
     the Reserve tap ripples just after, the Reserved node pulses last */
  .join-hero .jh-phone { animation: jhBuzz 6s ease-in-out infinite; }
  .join-hero .jh-buzzmark { animation: jhBuzzMark 6s ease-in-out infinite; }
  .join-hero .jh-tap { animation: bhDotRipple 6s ease-out infinite; animation-delay: -1.3s; }
  .join-hero .jh-m0 .jh-halo { animation: bhHaloPulse 6s ease-in-out infinite; animation-delay: -.7s; }
  .join-hero .jh-m0 .jh-ripple { animation: bhDotRipple 6s ease-in-out infinite; animation-delay: -.7s; }
  .join-hero .jh-chip { animation: bhChipFloat 12s ease-in-out infinite; }
  .join-hero .jh-chipA { animation-delay: 0s; }
  .join-hero .jh-chipB { animation-delay: -6s; }
  .join-hero .jh-lane { animation: jhLaneGlow 7.5s ease-in-out infinite; }
  .join-hero .jh-lane2 { animation-delay: -5s; }   /* peaks 2nd in the cycle */
  .join-hero .jh-lane3 { animation-delay: -2.5s; } /* peaks 3rd */
  .join-hero .jh-plane-run { animation: jhPlaneHop 7.5s ease-in-out infinite; animation-delay: -2.5s; } /* takes off as USA pill peaks */
}
@media (prefers-reduced-motion: reduce) {
  .join-hero * { animation: none !important; }
  .join-hero .jh-chip { opacity: 1; }
  .join-hero .jh-flow { opacity: .7; }
  .join-hero .jh-buzzmark { opacity: .8; }
  .join-hero .jh-tap { opacity: 0; }
  .join-hero .jh-lane { opacity: .9; }
  .join-hero .jh-plane-run { opacity: .9; } /* parked mid-route */
}

/* checklist points in a compact 2x2 grid so the whole panel fits one
   viewport with the showpiece; reuses the mint-tick li styling. */
.jh-points-2col { grid-template-columns: 1fr 1fr; column-gap: 22px; margin-top: 0; }
.jh-points-2col li { font-size: 13px; }
.jh-copy-foot .anon-hero-offer { margin-top: 18px; }

/* ---- Deal Scout refine console (2026-07-21) ---------------------
   H: "i need it to look premium, clean, sleek." Re-cut from a flat
   row of bare min/max boxes into the approved console language: a
   glass surface, a uniform grid of labelled controls, capsule
   range-pairs that bloom mint on focus, one mint Apply CTA. */
.scout-refine { display: flex; flex-direction: column; gap: 13px; margin: 10px 0 4px; }
.scout-refine-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.refine-apply { padding: 0 22px; font-size: 12.5px; height: 34px; border-radius: 6px; }
.scout-refine-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 12px 14px;
}
.range-field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.range-lbl {
  font-family: "Syne", sans-serif;
  font-size: 9.5px; font-weight: 700; line-height: 1;
  letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--muted); padding-left: 2px;
}
.range-pair {
  display: flex; align-items: center; height: 34px;
  border: 1px solid transparent; border-radius: 6px;
  background: rgba(28, 45, 53, 0.05);
  box-shadow: inset 0 1px 2px rgba(28, 45, 53, 0.06);
  padding: 0 4px;
  transition: background-color 0.2s ease, border-color 0.2s ease,
              box-shadow 0.45s var(--ease-out-expo);
}
.range-pair:focus-within {
  background: var(--surface); border-color: var(--mint);
  box-shadow: inset 0 1px 2px rgba(28, 45, 53, 0.03),
              0 0 0 4px rgba(117, 198, 158, 0.16),
              0 6px 16px -8px rgba(117, 198, 158, 0.4);
}
.range-pair input {
  flex: 1 1 0; min-width: 0; width: 100%;
  border: 0; background: transparent; height: 32px;
  font-size: 12.5px; font-family: inherit; color: var(--ink);
  text-align: center; outline: none;
  -moz-appearance: textfield; appearance: textfield;
}
.range-pair input::-webkit-outer-spin-button,
.range-pair input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.range-pair input::placeholder { color: var(--muted); opacity: 0.6; }
.range-dash { flex: none; color: var(--muted); font-size: 12px; opacity: 0.55; }
.refine-select {
  height: 34px; width: 100%;
  border: 1px solid var(--line); border-radius: 6px;
  background-color: var(--surface);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%235d6f6a' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 11px center;
  padding: 0 30px 0 12px;
  font-size: 12.5px; font-weight: 500; color: var(--navy);
  appearance: none; -webkit-appearance: none; cursor: pointer;
  transition: border-color 0.2s ease, box-shadow 0.2s ease;
}
.refine-select:hover { border-color: var(--mint-2); }
.refine-select:focus { outline: none; border-color: var(--mint); box-shadow: 0 0 0 4px rgba(117, 198, 158, 0.16); }
.refine-note { font-size: 11.5px; color: var(--muted); }
.scout-refine-active {
  display: flex; align-items: center; gap: 7px; flex-wrap: wrap;
  padding-top: 11px; border-top: 1px solid rgba(226, 235, 238, 0.7);
}
.refine-chip { font-size: 11px; }
.refine-clear { font-size: 12px; color: var(--muted); text-decoration: none; margin-left: 2px; }
.refine-clear:hover { color: var(--navy); }

/* ---- Deal Scout completion estimate (2026-07-21) ---------------- */
.scan-eta { display: flex; align-items: center; gap: 6px; margin-top: 8px; }
.scan-eta .live-dot { flex: none; }

/* ---- Deal Scout ASIN-family matrix (2026-07-21) ------------------
   Every listing matched for one product, three channels side by
   side, folded under the row behind the "N listings" chip. */
.fam-chip {
  display: inline-flex; align-items: center; gap: 3px;
  font: 500 11px var(--font); color: var(--navy);
  background: #e3ebef; border: 0; border-radius: 999px;
  padding: 2px 9px; margin-left: 6px; cursor: pointer;
}
.fam-chip:hover { background: #d5e2e8; }
.fam-chip.open .fam-caret { transform: rotate(180deg); }
.fam-caret { display: inline-block; transition: transform .15s ease; font-size: 9px; }
.scout-family-row td { background: #f4f9f6; padding: 10px 14px 12px; }
.fam-grid { display: grid; gap: 6px; }
.fam-head, .fam-row {
  display: grid; align-items: start;
  grid-template-columns: minmax(234px, 1.4fr) .7fr .8fr 1fr 1fr 1fr 1fr 118px;
  gap: 10px;
}
.fam-head {
  font: 700 10px var(--font-display); letter-spacing: 1.2px;
  text-transform: uppercase; color: var(--muted);
  border-bottom: 1px solid var(--line); padding-bottom: 4px;
}
.fam-row { font-size: 12.5px; padding: 3px 0; }
.fam-row.fam-current { background: #ecf4ef; border-radius: 8px; padding: 3px 6px; margin: 0 -6px; }
.fam-sub { font-size: 11px; }
/* Listing cell = pack shot + text, so near-identical variants can be
   told apart by eye rather than by reading five similar titles. */
.fam-listing { display: flex; align-items: flex-start; gap: 8px; min-width: 0; }
.fam-listing-txt { min-width: 0; }
.fam-thumb {
  flex: none;
  width: 36px; height: 36px;
  object-fit: contain;
  border: 1px solid var(--line);
  border-radius: 7px;
  background: var(--surface);
  padding: 2px;
}
/* Placeholder keeps the text column aligned when a listing has no
   image, so the rows don't stagger. */
.fam-thumb-empty { display: block; background: var(--bg); }
/* Dotted underline = "this is an inferred or absent figure, hover me",
   so a missing Keepa reading never reads as a confident zero. */
.fam-nodata { border-bottom: 1px dotted var(--line); display: inline-block; cursor: help; }
.fam-this {
  font: 600 9.5px var(--font); letter-spacing: .5px; text-transform: uppercase;
  color: var(--mint); margin-left: 5px;
}
.fam-lens { border-left: 2px solid var(--line); padding-left: 8px; }
.fam-lens.fam-pass { border-left-color: var(--mint); }

/* ---- First-order runway (dashboard, 2026-07-20) ----------------- */
.runway-steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 12px;
  counter-reset: runway;
}
.runway-steps li {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  font-size: 13.5px;
}
.runway-steps li > div { flex: 1; }
.runway-steps li .muted { font-size: 12px; margin-top: 2px; }
.runway-check {
  flex: 0 0 auto;
  width: 22px; height: 22px;
  border-radius: 50%;
  border: 1.5px solid var(--line-2);
  position: relative;
  top: 1px;
  counter-increment: runway;
}
.runway-check::before {
  content: counter(runway);
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  font-size: 11px;
  font-weight: 700;
  color: var(--muted);
}
.runway-steps li.done .runway-check {
  background: var(--mint);
  border-color: var(--mint);
}
.runway-steps li.done .runway-check::before {
  content: "✓";
  color: var(--navy);
}
.runway-steps li.done strong { color: var(--muted); text-decoration: line-through; }

/* ---- Onboarding wizard (2026-07-26) --------------------------------
   First-visit spotlight tour. The backdrop dims the portal, the
   spotlight ring cuts a window over the element being introduced, and
   the card carries the copy — beside the target on desktop, a bottom
   sheet on phones (sitting above the tabbar it points at). */
body.onb-open { overflow: hidden; }

.onb-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(28, 45, 53, 0.62); /* --navy at depth */
  z-index: 240;
}

.onb-spotlight {
  position: fixed;
  z-index: 241;
  border-radius: 6px;
  box-shadow: 0 0 0 9999px rgba(28, 45, 53, 0.62), 0 0 0 2px var(--gold);
  transition: top 0.28s ease, left 0.28s ease, width 0.28s ease, height 0.28s ease;
  pointer-events: none;
}

/* When a spotlight is visible it carries the dimming shadow itself,
   so the flat backdrop doubles it. Kept — the doubled navy reads as a
   deliberate deepening rather than a glitch, and it means centred
   steps (no spotlight) still dim correctly. */

.onb-card {
  position: fixed;
  z-index: 242;
  width: min(360px, calc(100vw - 32px));
  max-height: calc(100vh - 32px);
  max-height: calc(100dvh - 32px);
  overflow-y: auto;
  background: var(--surface);
  border-radius: 8px;
  padding: 22px 22px 18px;
  box-shadow: 0 24px 64px rgba(28, 45, 53, 0.35);
  transition: top 0.28s ease, left 0.28s ease;
}

.onb-eyebrow {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--gold-deep);
  margin-bottom: 6px;
}

.onb-title {
  font-family: var(--font-display, inherit);
  font-size: 19px;
  margin: 0 0 8px;
  color: var(--navy);
}

.onb-body {
  font-size: 13.5px;
  line-height: 1.55;
  color: #45565e;
  margin: 0 0 16px;
}

.onb-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
}

.onb-skip {
  background: none;
  border: none;
  padding: 6px 0;
  font-size: 12.5px;
  color: #7a8a92;
  cursor: pointer;
}
.onb-skip:hover { color: var(--navy); text-decoration: underline; }

.onb-nav { display: flex; gap: 8px; margin-left: auto; }

.onb-btn {
  border: none;
  border-radius: 999px;
  padding: 9px 18px;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
}

.onb-next {
  background: var(--gold);
  color: var(--gold-ink);
}
.onb-next:hover { background: var(--gold-bright); }

.onb-back {
  background: transparent;
  color: var(--navy);
  border: 1px solid var(--line-2);
}
.onb-back:hover { border-color: var(--navy); }

@media (max-width: 820px) {
  .onb-card { border-radius: 18px; padding-bottom: max(18px, env(safe-area-inset-bottom)); }
}

/* ====================================================================
   DARK MODE OVERRIDES (2026-07-28)
   Everything that couldn't ride the token remap: semantic status tints
   (chips, tone rows, message bubbles) that must keep their hue while
   lifting text to read on dark, plus a few hardcoded light surfaces.
   All rules are [data-theme="dark"]-prefixed, so specificity wins
   regardless of source order and light mode is byte-for-byte unchanged.
   ==================================================================== */

/* Theme toggle button -- matches the notification bell so the two sit
   as a tidy pair in the topbar. */
.theme-toggle {
  width: 34px;
  height: 34px;
  border-radius: 8px;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  display: grid;
  place-items: center;
  cursor: pointer;
  padding: 0;
  transition: border-color 120ms ease, background 120ms ease;
}
.theme-toggle:hover { border-color: var(--mint-3); background: var(--light); }
.theme-toggle .icon-sun { display: none; }
.theme-toggle .icon-moon { display: inline-flex; }
:root[data-theme="dark"] .theme-toggle .icon-sun  { display: inline-flex; }
:root[data-theme="dark"] .theme-toggle .icon-moon { display: none; }

/* Status chips: keep the traffic-light meaning, invert for dark. */
:root[data-theme="dark"] .chip { color: var(--ink-2); }
:root[data-theme="dark"] .chip-ok      { background: rgba(117, 198, 158, 0.16); color: #8fd8b3; }
:root[data-theme="dark"] .chip-warn,
:root[data-theme="dark"] .chip-turn-client { background: rgba(226, 167, 59, 0.16); color: #eec173; }
:root[data-theme="dark"] .chip-danger  { background: rgba(216, 88, 88, 0.18); color: #f0a3a3; }
:root[data-theme="dark"] .chip-info,
:root[data-theme="dark"] .chip-navy    { background: var(--surface-2); color: var(--ink); }
:root[data-theme="dark"] .chip-priority { background: rgba(212, 175, 90, 0.16); color: var(--gold-bright); }
:root[data-theme="dark"] .chip-ghost:hover { background: var(--surface-2); color: var(--ink); }

/* "What's next" rows are transparent with a left accent bar now
   (premium sweep) — no dark pastel fills to override. */

/* Order/ticket message bubbles. */
:root[data-theme="dark"] .ticket-thread-bubble-client { background: rgba(117, 198, 158, 0.16); }
:root[data-theme="dark"] .ticket-thread-bubble-ops    { background: var(--surface-2); }

/* Danger button soft-hover pinks. */
:root[data-theme="dark"] .btn-danger:hover { background: rgba(216, 88, 88, 0.18); }

/* Deal Scout family rows (staff tool, but dark applies globally). */
:root[data-theme="dark"] .fam-chip { background: var(--surface-2); }
:root[data-theme="dark"] .fam-chip:hover { background: var(--line-2); }
:root[data-theme="dark"] .scout-family-row td { background: var(--surface); }
:root[data-theme="dark"] .fam-row.fam-current { background: rgba(117, 198, 158, 0.12); }

/* Frosted "white gradient" cards -> dark surface gradients. Left alone
   they run dark-to-white and glow. */
:root[data-theme="dark"] .dashboard-opportunity,
:root[data-theme="dark"] .paperwork-intro {
  background: linear-gradient(135deg, var(--surface-2) 0%, var(--surface) 100%);
}
:root[data-theme="dark"] .notification-panel,
:root[data-theme="dark"] .paperwork-vat-custom-form,
:root[data-theme="dark"] .mkt-console,
:root[data-theme="dark"] .mkt-console.is-stuck {
  background: linear-gradient(180deg, var(--surface) 0%, var(--surface-2) 100%);
}
:root[data-theme="dark"] .proforma-alert.proforma-alert-expired {
  background: linear-gradient(135deg, rgba(216, 88, 88, 0.20), rgba(216, 88, 88, 0.08));
}
/* Loading shimmer: dark track instead of a pale one. */
:root[data-theme="dark"] .skeleton-line {
  background: linear-gradient(90deg, #1b2a31 25%, #24343c 37%, #1b2a31 63%);
}

/* Dark mobile chrome: the phone shell's frosted-glass surfaces are
   translucent WHITE, which glows in dark. Re-tint to translucent navy
   while keeping the blur. Must stay inside the phone media query. */
@media (max-width: 820px) {
  :root[data-theme="dark"] .client-tabbar {
    background: rgba(24, 40, 49, 0.82);
    border-color: rgba(255, 255, 255, 0.10);
    border-top-color: rgba(255, 255, 255, 0.10);
    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255, 255, 255, 0.06);
  }
  :root[data-theme="dark"] .client-tab.active { background: rgba(117, 198, 158, 0.22); }
  :root[data-theme="dark"] .topbar {
    background: rgba(14, 23, 28, 0.72);
    border-bottom-color: rgba(255, 255, 255, 0.08);
  }
  :root[data-theme="dark"] body.role-client .order-modal,
  :root[data-theme="dark"] body.role-client .deal-modal,
  :root[data-theme="dark"] body.role-client .reserve-modal,
  :root[data-theme="dark"] body.role-client .stat-modal {
    background: rgba(20, 32, 39, 0.88);
  }
  :root[data-theme="dark"] body.role-client .order-modal .order-modal-head,
  :root[data-theme="dark"] body.role-client .reserve-modal .reserve-modal-head,
  :root[data-theme="dark"] body.role-client .deal-modal-footer {
    background: rgba(20, 32, 39, 0.82);
  }
}

/* Anon-card wordmark: "Vantage" is --navy and "wholesale" is --deep,
   both near-invisible on the dark sign-in card. Lift them (the sidebar
   keeps its own #fff/mint rule at higher scope). */
:root[data-theme="dark"] .anon-card .brand-name-1 { color: var(--ink); }
:root[data-theme="dark"] .anon-card .brand-name-2 { color: var(--mint); }

/* ================================================================
   DARK MODE: controls that hardcoded LIGHT-surface colours
   (H, 2026-08-03: "I can't see some of the text and things I need to
   see in dark mode ... walk through every screen in dark mode and
   ensure everything is actually visible")

   Root cause of the whole class: dark mode remaps --surface to
   #182831 but deliberately does NOT remap --navy / --deep (they're
   brand tokens, still used for dark FILLS). So every control that
   set `color: var(--navy)` or `background: var(--navy)` on a themed
   surface became navy-on-navy — invisible. Found by walking every
   client screen in the simulator in dark.
   ================================================================ */

/* --- 1. Glass consoles: light frosted panels glowed WHITE ---------
   Deal Flow's .mkt-console had a dark rule; Orders' .console-surface
   (the "All (0)" + search panel) never did, so it rendered as a bright
   slab on the dark page. Re-tint both, including the light border and
   the white inset highlight. */
:root[data-theme="dark"] .console-surface {
  border-color: rgba(255, 255, 255, 0.10);
  background: linear-gradient(180deg, var(--surface) 0%, var(--surface-2) 100%);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35),
              0 12px 32px -12px rgba(0, 0, 0, 0.55),
              inset 0 1px 0 rgba(255, 255, 255, 0.05);
}
:root[data-theme="dark"] .mkt-console,
:root[data-theme="dark"] .mkt-console.is-stuck {
  border-color: rgba(255, 255, 255, 0.10);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.35),
              0 12px 32px -12px rgba(0, 0, 0, 0.55),
              inset 0 1px 0 rgba(255, 255, 255, 0.05);
}

/* --- 2. Console controls: navy text on a dark surface -------------
   Sort select + Filters button read as empty boxes. */
:root[data-theme="dark"] .mkt-console .mkt-sort,
:root[data-theme="dark"] .mkt-console .mkt-filters-toggle {
  color: var(--ink);
  background-color: var(--surface-2);
  border-color: var(--line-2);
}

/* --- 3. Select chevrons were a hardcoded navy SVG -----------------
   Invisible on every dark select: Deal Flow sort, Orders status,
   Logistics "which deal?", Account VAT. Same glyph, light stroke. */
:root[data-theme="dark"] .select,
:root[data-theme="dark"] .mkt-mobile-select,
:root[data-theme="dark"] .mkt-console .mkt-sort {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%23b4c3c9' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
}

/* --- 4. Console footer: light rule + deep-green link --------------
   "Can't find what you sell? Request it from the sourcing desk". */
:root[data-theme="dark"] .mkt-console-foot { border-top-color: var(--line); }
:root[data-theme="dark"] .mkt-want-line { color: var(--mint); }

/* --- 5. Primary buttons on dark cards -----------------------------
   .btn-primary is navy-on-navy in dark, so the SELECTED period pill
   (Paperwork YTD) read as less prominent than the unselected ghosts,
   and Logistics' "Request quote" barely read as a button. Mint fill
   with navy ink is the dark-mode primary — same hierarchy the light
   theme gets from navy-on-white. */
:root[data-theme="dark"] .btn-primary {
  background: var(--mint);
  color: var(--navy);
}
:root[data-theme="dark"] .btn-primary:hover {
  background: var(--mint-2, var(--mint));
  color: var(--navy);
}

/* --- 6. Stat-card dots ---------------------------------------------
   .stat-label::before is tinted per card variant; the navy and deep
   variants vanished on dark cards ("LIVE DEALS AVAILABLE", "OPEN
   ORDERS", "BLENDED ROI") while the mint/warn siblings were fine. */
:root[data-theme="dark"] .stat-card.navy .stat-label::before,
:root[data-theme="dark"] .stat-card.deep .stat-label::before {
  background: var(--ink-2);
}

/* Pipeline money strip in dark mode. --deep is a DARK green used for
   accents on light surfaces and is deliberately NOT remapped for dark
   (it still serves as a dark fill), so anything setting `color: deep`
   goes near-invisible on the dark board -- the same class of bug as
   the dark-mode control sweep (2026-08-03). Mint is the dark-surface
   accent. */
:root[data-theme="dark"] .pipeline-metric-mint,
:root[data-theme="dark"] .kanban-col-metric-mint,
:root[data-theme="dark"] .pipeline-metrics-lens { color: var(--mint); }

/* Per-listing mint button inside the "N listings" dropdown. Right-
   aligned so the column reads as an action rail, and the button never
   stretches to the full cell. */
.fam-action { display: flex; justify-content: flex-end; align-items: flex-start; }
.fam-action form { margin: 0; }
.fam-action .btn { white-space: nowrap; }

/* ---- Take-all manifest: one deal card per product ------------------
   H (2026-08-04): "i need better break down of data per product within
   the deal, needs to be like rows of deal cards for each product...
   these take all deals will contribute to the biggest revenue deals."
   The old 9-column table made the reader do the arithmetic; a take-all
   buyer is committing to EVERY line, so each SKU gets card treatment.
   Figures are Jost + tabular-nums, same as every other money in the
   app. */
.ta-lines { display: flex; flex-direction: column; gap: 10px; }

.ta-line {
  border: 1px solid var(--line);
  border-radius: 12px;
  padding: 12px 14px;
  background: var(--surface);
}
/* A line whose cover is long enough to be worth a second look. A hint,
   never a verdict — the lot can still be a buy. */
.ta-line-slow { border-color: color-mix(in srgb, var(--warn) 45%, var(--line)); }

.ta-line-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 12px; margin-bottom: 10px;
}
.ta-line-title { font-size: 14px; font-weight: 700; margin: 0 0 2px; line-height: 1.3; }
.ta-line-meta { font-size: 11.5px; }
.ta-line-qty { text-align: right; flex: none; }
.ta-line-qty-n {
  display: block; font: 700 18px var(--font);
  font-variant-numeric: tabular-nums; color: var(--ink); line-height: 1;
}
.ta-line-qty-l {
  font-size: 10px; text-transform: uppercase; letter-spacing: .08em;
  color: var(--muted);
}

.ta-line-econ {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px;
  padding: 10px 0; border-top: 1px solid var(--line);
}
.ta-econ-label { font-size: 10.5px; color: var(--muted); margin-bottom: 2px; }
.ta-econ-value {
  font: 600 13.5px var(--font); font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.ta-econ-strong { font-weight: 700; color: var(--deep); }

.ta-line-foot {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px;
  padding-top: 9px; border-top: 1px solid var(--line);
}
.ta-foot-label {
  display: block; font-size: 10px; text-transform: uppercase;
  letter-spacing: .07em; color: var(--muted); margin-bottom: 2px;
}
.ta-foot-value {
  font: 700 13.5px var(--font); font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.ta-foot-mint { color: var(--deep); }
.ta-foot-warn { color: var(--warn-ink, #a05a1a); }
.ta-foot-hint {
  display: inline-block; margin-left: 3px; cursor: help;
  font-size: 9px; font-weight: 700; color: var(--muted);
  border: 1px solid var(--line); border-radius: 50%;
  width: 13px; height: 13px; line-height: 12px; text-align: center;
}

@media (max-width: 820px) {
  .ta-line-econ { grid-template-columns: repeat(3, 1fr); }
  .ta-line-foot { grid-template-columns: repeat(2, 1fr); row-gap: 8px; }
}

/* Dark mode: --deep is a dark green that dark mode deliberately does
   not remap (see .stat-value's note), so mint is the accent here. */
:root[data-theme="dark"] .ta-econ-strong,
:root[data-theme="dark"] .ta-foot-mint { color: var(--mint); }
:root[data-theme="dark"] .ta-foot-warn { color: var(--warn); }

/* Supplier lead time control on the Deal Scout list header. */
.scout-leadtime-row {
  display: flex; align-items: center; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
}
.scout-leadtime-set { display: flex; align-items: center; gap: 6px; }

/* Lead time on the client deal card — a buying factor when timing
   stock, previously only visible inside the detail modal. */
.market-card-lead {
  display: flex; align-items: center; gap: 5px;
  font-size: 11.5px; margin-top: 3px;
}
.market-card-lead svg { flex: none; opacity: .75; }

/* US lane signal on a Deal Scout row: rank-drop velocity fallback and
   the risk chips (Amazon on the listing, thin buy box). */
.scout-us-drops { border-bottom: 1px dotted var(--line); cursor: help; }
.scout-us-chip { font-size: 9.5px; margin-top: 3px; display: inline-block; }
