/* ==========================================================================
   Projavi — target aesthetic token layer
   Derived from brand-spec.md (values sampled from the 2026-08-28 mockups).
   Drop-in replacement for the :root / .dark blocks in
   frontend/src/styles/globals.css.

   Two documented deviations from the raw samples, both forced by WCAG AA:
   - Text-bearing solid fills use --brand-strong (#027997, 5.15:1 vs white)
     rather than --brand (#038DB1, 3.94:1). #038DB1 is retained for the logo
     mark, non-text fills, and focus rings, where 3:1 applies.
   - Light-mode --muted is darkened ~0.025 L from the sampled #6B7A85
     (4.36:1 on white) to clear 4.5:1 for small text.
   ========================================================================== */

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

  /* six core tokens — dark */
  --bg:          oklch(0.223 0.041 226);   /* #011F2A */
  --surface:     oklch(0.302 0.059 233);   /* #043347 */
  --fg:          oklch(0.995 0.003 200);   /* #FBFFFF */
  --muted:       oklch(0.685 0.020 232);   /* #8E9CA4 — sampled #8FA2AC, +0.025 L for 4.5:1 on --surface */
  --border:      oklch(0.328 0.058 230);   /* #0C3A4D */
  --accent:      oklch(0.699 0.115 206);   /* #1CB2C1 — interactive text */

  /* derived surfaces */
  --surface-2:   oklch(0.362 0.062 233);   /* hover / raised rows */
  --surface-3:   oklch(0.262 0.049 230);   /* inset wells, inputs */
  --fg-2:        oklch(0.872 0.014 225);   /* secondary text */
  --border-soft: oklch(0.290 0.050 230);
  --chip:        oklch(0.304 0.078 246);   /* #003153 icon chip */
  --chip-fg:     oklch(0.699 0.115 206);
  --sheet:       var(--bg);

  /* brand */
  --brand:        oklch(0.598 0.112 224);  /* #038DB1 mark, non-text fill */
  --brand-strong: oklch(0.535 0.099 223);  /* #027997 text-bearing fill */
  --brand-hover:  oklch(0.465 0.086 223);  /* pressed / hover, darker */
  --brand-wash:   oklch(0.335 0.055 226);  /* tinted panel */
  --on-brand:     oklch(1 0 90);

  /* state */
  --gold:        oklch(0.815 0.175 84);    /* #F7BA00 Premium only */
  --on-gold:     oklch(0.245 0.035 228);
  /* gold is a fill token. As a foreground it is only legible on a dark backing,
     so glyphs drawn IN gold use --gold-text; dark mode can alias it (7.46:1). */
  --gold-text:   var(--gold);
  --success:     oklch(0.735 0.148 156);
  --danger:      oklch(0.672 0.184 25);
  /* --danger is a 4.15:1 text colour on --surface — icons and borders only.
     --danger-text is the lifted tone for destructive labels: 4.71:1 minimum. */
  --danger-text: oklch(0.760 0.125 28);    /* #F69184 */
  /* Ink for a --danger *fill* (the unread count on the mobile bottom bar).
     Dark --danger is #F25D59 — light enough that white on it is only 3.23:1,
     so a 10px count would fail. Petrol ink measures 5.28:1 on the same fill. */
  --on-danger:   var(--bg);                /* #011F2A on #F25D59 — 5.28:1 */
  --success-text: var(--success);          /* #48C481 clears 6.07:1 on --surface */
  --warning:     oklch(0.800 0.140 75);
  /* --warning is already light enough in dark mode to carry text on its own
     tinted mixes (4.56:1), so the text tone aliases it here */
  --warning-text: var(--warning);

  --shadow-card: 0 1px 2px oklch(0.15 0.03 230 / 0.45),
                 0 8px 24px -12px oklch(0.12 0.03 230 / 0.65);
  --shadow-lift: 0 2px 4px oklch(0.15 0.03 230 / 0.5),
                 0 18px 40px -18px oklch(0.10 0.03 230 / 0.8);
  --plate-veil: 0.06;
}

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

  /* six core tokens — light */
  --bg:          oklch(0.968 0.008 242);   /* #F0F5F9 */
  --surface:     oklch(1 0 90);            /* #FFFFFF */
  --fg:          oklch(0.245 0.035 228);   /* #0A2430 */
  --muted:       oklch(0.535 0.016 240);   /* AA-corrected #6B7A85 */
  --border:      oklch(0.925 0.010 235);   /* #DEE7EC */
  --accent:      oklch(0.535 0.099 223);   /* #027997 — AA link on white */

  --surface-2:   oklch(0.955 0.011 240);
  --surface-3:   oklch(0.978 0.007 240);
  --fg-2:        oklch(0.400 0.025 230);
  --border-soft: oklch(0.948 0.008 238);
  --chip:        oklch(0.955 0.018 215);   /* #E3F4F8 */
  --chip-fg:     oklch(0.465 0.086 223);
  --sheet:       var(--surface);

  --brand:        oklch(0.598 0.112 224);
  --brand-strong: oklch(0.535 0.099 223);
  --brand-hover:  oklch(0.465 0.086 223);
  --brand-wash:   oklch(0.968 0.020 218);  /* #E6F8FE — lifted from the sampled
                                              tint: --accent on the old value
                                              reached only 4.47:1 */
  --on-brand:     oklch(1 0 90);

  --gold:        oklch(0.815 0.175 84);
  --on-gold:     oklch(0.245 0.035 228);
  /* #B07300 — brand gold is 1.80:1 on white, so a gold glyph needs its own
     tone: 3.97:1 on --surface, 3.49:1 on --surface-2, both clear the 3:1 icon floor */
  --gold-text:   oklch(0.600 0.175 84);
  --success:     oklch(0.545 0.148 152);
  --danger:      oklch(0.545 0.190 27);
  --danger-text: var(--danger);            /* #C72E2B already clears 4.79:1 */
  --on-danger:   oklch(1 0 90);            /* white on #C72E2B — 5.46:1 */
  /* --success at L .545 is 4.61:1 on white but only 4.20:1 on --bg and 4.33:1
     on --surface-3, so a "Lowest ₦…" label would fail the moment it sat in an
     inset well. --success-text drops 0.04 L to clear 4.5:1 on all three. The
     brighter --success stays on fills and icons, where 3:1 is the bar. */
  --success-text: oklch(0.505 0.140 152);
  --warning:     oklch(0.660 0.150 70);
  /* #CB7F00 is only 2.56:1 on a 22% warning-on-white tint, so a glyph sitting
     in its own tinted disc would fail the 3:1 non-text floor. −0.14 L gives
     4.52:1 there and 5.66:1 on plain white. */
  --warning-text: oklch(0.520 0.150 70);

  --shadow-card: 0 1px 2px oklch(0.60 0.02 235 / 0.10),
                 0 8px 24px -14px oklch(0.50 0.03 235 / 0.22);
  --shadow-lift: 0 2px 6px oklch(0.60 0.02 235 / 0.14),
                 0 18px 40px -20px oklch(0.45 0.03 235 / 0.30);
  --plate-veil: 0.02;
}

/* The petrol header is theme-invariant — brand-spec.md rule 3. */
:root {
  --header-top:    oklch(0.302 0.059 233);
  --header-bottom: oklch(0.223 0.041 226);
  --on-header:     oklch(0.995 0.003 200);
  --on-header-2:   oklch(0.660 0.020 232);
  --header-line:   oklch(0.328 0.058 230);
  --header-well:   oklch(0.262 0.049 230);

  /* The header create button needs its own theme-invariant fill. --brand-strong
     measures only 2.67:1 against --header-top (below the 3:1 non-text floor) and
     --brand-hover collapses to 1.97:1, so btn-primary cannot live in the header.
     This fill measures 5.22:1 against the header with 6.65:1 ink on top. */
  --header-cta:       oklch(0.699 0.115 206);  /* #1DB2C1 */
  --header-cta-hover: oklch(0.760 0.115 206);  /* #3DC6D5 — L +0.061, 8.29:1 ink */
  --on-header-cta:    oklch(0.223 0.041 226);  /* #011F2A petrol ink */

  --r-sheet: 28px;
  --r-card: 16px;
  --r-chip: 12px;
  --r-input: 12px;
  --r-pill: 999px;

  --display: "Space Grotesk", "SF Pro Display", system-ui, sans-serif;
  --body: "Inter", -apple-system, "Segoe UI", system-ui, sans-serif;
  --mono: "JetBrains Mono", ui-monospace, monospace;

  --rail: 252px;
  --head: 64px;

  /* the one flourish: construction blueprint line art (mockup rule 2) */
  --blueprint: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 640 340' fill='none' stroke='%23ffffff' stroke-width='1.4' stroke-linecap='round'><g opacity='.95'><path d='M150 330V80'/><path d='M136 330h28M136 290h28M136 250h28M136 210h28M136 170h28M136 130h28M136 90h28'/><path d='M136 330l28-40M164 330l-28-40M136 290l28-40M164 290l-28-40M136 250l28-40M164 250l-28-40M136 210l28-40M164 210l-28-40M136 170l28-40M164 170l-28-40M136 130l28-40M164 130l-28-40'/><path d='M40 80h520M40 66h520'/><path d='M150 40l190 26M150 40L58 66M150 80V34'/><path d='M420 80v54'/><path d='M402 134h36M402 134l18-16 18 16'/></g><g opacity='.7'><path d='M300 330V200h120v130'/><path d='M300 244h120M300 288h120M340 200v130M380 200v130'/><path d='M470 330V242h92v88'/><path d='M470 286h92M516 242v88'/></g><g opacity='.45'><path d='M-20 252c60-26 120-26 180 0s120 26 180 0 120-26 180 0 120 26 180 0'/><path d='M-20 300c60-26 120-26 180 0s120 26 180 0 120-26 180 0 120 26 180 0'/></g></svg>");
}

/* ---------------------------------------------------------------- reset */
*, *::before, *::after { box-sizing: border-box; }

/* Any component that declares its own `display` silently defeats the UA
   `[hidden] { display: none }` rule, because a class selector beats a
   type-and-attribute one. That has now bitten three separate blocks —
   the browse filter counters, the thread's empty state, and every
   progressively-disclosed `.field` in the create flow — so it is settled
   here once rather than restated per component. `!important` is warranted:
   `hidden` is an absolute instruction, not a suggestion a layout rule
   should be able to outvote. */
[hidden] { display: none !important; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--bg);
  color: var(--fg);
  font-family: var(--body);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
[data-theme="dark"] body {
  background: linear-gradient(180deg,
    oklch(0.302 0.059 233) 0%,
    oklch(0.250 0.047 228) 34%,
    var(--bg) 78%) no-repeat;
  background-attachment: fixed;
}
img, svg { display: block; max-width: 100%; }
button, input, select, textarea { font: inherit; color: inherit; }
a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 3px; }
/* underline is for inline prose links only — never for component anchors */
.nav-item:hover, .logo:hover, .btn:hover, .pill:hover, .badge:hover,
.tocbar a:hover, .contact-row:hover, .merchant a:hover, .crumbs a:hover,
a.card:hover, a.stretch:hover { text-decoration: none; }
h1, h2, h3, h4 { font-family: var(--display); margin: 0; letter-spacing: -0.02em; font-weight: 600; }
p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }

:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  border-radius: 6px;
}
.sr-only {
  position: absolute; width: 1px; height: 1px; margin: -1px;
  padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.i { width: 18px; height: 18px; flex: none; }

/* ---------------------------------------------------------- type scale */
.t-display { font-family: var(--display); font-size: 34px; line-height: 1.1; letter-spacing: -0.025em; font-weight: 600; }
.t-h1 { font-family: var(--display); font-size: 26px; line-height: 1.2; }
.t-h2 { font-family: var(--display); font-size: 20px; line-height: 1.25; }
.t-h3 { font-family: var(--display); font-size: 16px; line-height: 1.35; }
.t-body { font-size: 15px; line-height: 1.55; }
.t-small { font-size: 13px; line-height: 1.5; }
.t-label {
  font-size: 11px; font-weight: 600; letter-spacing: 0.08em;
  text-transform: uppercase; color: var(--muted);
}
.t-price {
  font-family: var(--display); font-weight: 600;
  font-variant-numeric: tabular-nums; letter-spacing: -0.02em;
}
.t-mono { font-family: var(--mono); font-size: 12.5px; }
.muted { color: var(--muted); }
.fg-2 { color: var(--fg-2); }

/* --------------------------------------------------------------- shell */
.app { min-height: 100vh; }

.header {
  position: sticky; top: 0; z-index: 40;
  height: var(--head);
  display: flex; align-items: center; gap: 20px;
  padding: 0 24px;
  background: linear-gradient(180deg, var(--header-top), var(--header-bottom));
  border-bottom: 1px solid var(--header-line);
  color: var(--on-header);
}
.header::after {
  content: ""; position: absolute; inset: 0;
  background-image: var(--blueprint);
  background-size: 520px auto; background-position: right -60px top -30px;
  opacity: 0.07; pointer-events: none;
}
.header > * { position: relative; z-index: 1; }

.logo { display: flex; align-items: center; gap: 10px; flex: none; }
.logo-mark {
  width: 32px; height: 32px; border-radius: 9px; flex: none;
  background: linear-gradient(160deg, var(--brand), var(--brand-strong));
  display: grid; place-items: center;
  box-shadow: 0 2px 8px oklch(0.30 0.09 224 / 0.55);
}
.logo-mark svg { width: 18px; height: 18px; color: #fff; }
.logo-word {
  font-family: var(--display); font-size: 19px; font-weight: 600;
  letter-spacing: -0.03em; color: var(--on-header);
}

.searchbar {
  flex: 1; max-width: 560px;
  display: flex; align-items: center; gap: 10px;
  height: 40px; padding: 0 14px;
  background: var(--header-well);
  border: 1px solid var(--header-line);
  border-radius: var(--r-pill);
  transition: border-color .16s, background .16s;
}
.searchbar:focus-within {
  border-color: var(--brand);
  background: oklch(0.282 0.052 230);
}
.searchbar svg { width: 17px; height: 17px; color: var(--on-header-2); flex: none; }
.searchbar input {
  flex: 1; min-width: 0; border: 0; background: none; outline: none;
  color: var(--on-header); font-size: 14px;
}
.searchbar input::placeholder { color: var(--on-header-2); }
.searchbar kbd {
  font-family: var(--mono); font-size: 10.5px;
  color: var(--on-header-2); border: 1px solid var(--header-line);
  border-radius: 5px; padding: 2px 5px; flex: none;
}

.head-actions { display: flex; align-items: center; gap: 8px; margin-left: auto; flex: none; }

/* icon button — brand-spec rule 4: icons live in a chip, never bare */
.iconbtn {
  width: 40px; height: 40px; flex: none;
  display: grid; place-items: center;
  border-radius: var(--r-chip);
  border: 1px solid transparent;
  background: var(--chip); color: var(--chip-fg);
  cursor: pointer; position: relative;
  transition: background .16s, border-color .16s, transform .16s;
}
.iconbtn svg { width: 19px; height: 19px; }
.iconbtn:hover { background: oklch(0.372 0.084 246); border-color: var(--brand); }
[data-theme="light"] .iconbtn:hover { background: oklch(0.900 0.040 215); border-color: var(--brand); }
.iconbtn:active { transform: translateY(1px); }
.header .iconbtn { background: var(--header-well); color: var(--on-header); border-color: var(--header-line); }
.header .iconbtn:hover { background: oklch(0.360 0.062 233); border-color: var(--brand); color: var(--on-header); }

/* Header create action. This is the app's only primary create entry point, so
   page surfaces must not repeat it. Sits in the header on every route, which is
   why it earns a label rather than a bare icon.

   Geometry belongs to the header, not to .btn: every other control in the bar —
   searchbar, icon wells, theme button — is a 40px pill or chip, so the button's
   default 44px height and 12px radius read as a foreign object dropped into the
   cluster. It matches the bar's 40px pill family instead and stays the only
   filled element in it, which is what carries the primary weight. */
.head-cta {
  background: var(--header-cta); color: var(--on-header-cta);
  min-height: 32px; height: 32px;
  padding: 0 13px; gap: 6px;
  border-radius: var(--r-pill);
  font-size: 12.5px;
  flex: none; letter-spacing: -0.005em;
}
.head-cta svg { width: 15px; height: 15px; }
.head-cta:hover { background: var(--header-cta-hover); color: var(--on-header-cta); }
.head-cta:active { transform: translateY(1px); }
/* ring sits on the header, not on the fill, so it only needs 3:1 against petrol */
.head-cta:focus-visible { outline: 2px solid var(--on-header); outline-offset: 3px; }
/* the comment above the rule explains the 40px family; the CTA sits 32px inside
   it so the filled shape reads as inset rather than as a peer of the wells.
   32px clears WCAG 2.5.8's 24px target floor; this is desktop chrome, and the
   mobile bottom bar carries the create action at full size. */
@media (max-width: 1120px) {
  .head-cta { width: 32px; padding: 0; }
  .head-cta .lbl { display: none; }
}
.iconbtn .dot {
  position: absolute; top: 8px; right: 8px;
  width: 7px; height: 7px; border-radius: 50%;
  background: var(--gold); border: 2px solid var(--header-bottom);
  box-sizing: content-box;
}

.avatar {
  border-radius: 50%; flex: none; overflow: hidden;
  display: grid; place-items: center;
  font-family: var(--display); font-weight: 600; color: #fff;
  background: linear-gradient(150deg, var(--brand), oklch(0.42 0.08 235));
  border: 1px solid var(--border);
}
.avatar-26 { width: 26px; height: 26px; font-size: 10.5px; }
.avatar-32 { width: 32px; height: 32px; font-size: 12px; }
.avatar-40 { width: 40px; height: 40px; font-size: 14px; }
.avatar-88 { width: 88px; height: 88px; font-size: 30px; border-width: 3px; border-color: var(--sheet); }

.body-grid { display: grid; grid-template-columns: var(--rail) minmax(0, 1fr); }

/* The rail is only as tall as its own content. It used to take
   height: calc(100vh - var(--head)), which on a tall desktop viewport left a
   dead teal column between "Log out" and the Premium card — the column was
   sized by the window rather than by the navigation. max-height keeps the
   scroll safety net for short viewports without inventing height, and the
   closed bottom edge (border + radius) is what makes a column that stops
   mid-page read as a panel rather than as a clipped background. */
.rail {
  position: sticky; top: var(--head); align-self: start;
  max-height: calc(100vh - var(--head));
  display: flex; flex-direction: column;
  padding: 14px 12px 16px;
  border-right: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  border-bottom-right-radius: 18px;
  background: var(--surface);
  overflow-y: auto;
}
[data-theme="dark"] .rail { background: oklch(0.268 0.052 231); }
/* footer group. Was an inline margin-top:auto in every screen, which pinned the
   Premium card to the viewport floor and was the other half of the tall-column
   problem; it now follows the last nav group. */
.rail-foot { margin-top: 20px; }

.nav { display: flex; flex-direction: column; gap: 2px; }
.nav-item {
  display: flex; align-items: center; gap: 11px;
  min-height: 44px; padding: 5px 10px;
  border-radius: var(--r-chip);
  border: 1px solid transparent;
  background: none; width: 100%; text-align: left;
  color: var(--fg); font-size: 14px; font-weight: 500;
  cursor: pointer;
  transition: background .16s, border-color .16s;
}
.nav-item .chip {
  width: 30px; height: 30px; flex: none;
  border-radius: 9px; background: var(--chip); color: var(--chip-fg);
  display: grid; place-items: center;
}
.nav-item .chip svg { width: 16px; height: 16px; }
.nav-item:hover { background: var(--surface-2); border-color: var(--border); }
.nav-item[aria-current="page"] {
  background: var(--brand-wash); border-color: var(--brand);
  color: var(--fg); font-weight: 600;
}
.nav-item[aria-current="page"] .chip { background: var(--brand-strong); color: #fff; }
.nav-item .tail { margin-left: auto; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
/* --muted drops to 3.78:1 on --surface-2, so the tail lifts with the hover */
.nav-item:hover .tail { color: var(--fg-2); }
.nav-item[aria-current="page"] .tail { color: var(--fg-2); }
.rail-sep { height: 1px; background: var(--border); margin: 12px 4px; }

/* ---------------------------------------------------------------------------
   Desktop breakpoint. This block must stay AFTER the .rail rules above: it was
   originally declared before them, and since `.rail { display: none }` and
   `.rail { display: flex }` have identical specificity the later unconditional
   rule won at every width. The media query was inert, so below 1081px the rail
   did not hide — it became the first row of the collapsed single-column grid
   and rendered as a full-height petrol block above the content on every screen.
   Order is the whole fix; nothing else about the rail changed.
   --------------------------------------------------------------------------- */
@media (max-width: 1080px) {
  .body-grid { grid-template-columns: minmax(0, 1fr); }
  .rail { display: none; }
}

.main { min-width: 0; }
.sheet {
  background: var(--sheet);
  border-top-left-radius: var(--r-sheet);
  border-top-right-radius: var(--r-sheet);
  min-height: calc(100vh - var(--head));
  padding: 28px 32px 56px;
  position: relative;
}
[data-theme="light"] .sheet { box-shadow: 0 -1px 0 var(--border); }
@media (max-width: 720px) { .sheet { padding: 20px 16px 48px; } }

.wrap { max-width: 1180px; margin: 0 auto; }

/* -------------------------------------------------------------- pieces */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-card);
  box-shadow: var(--shadow-card);
  position: relative;
}
a.card, button.card { display: block; text-align: left; cursor: pointer; color: inherit; text-decoration: none; }
a.card:hover, button.card:hover {
  border-color: var(--brand);
  box-shadow: var(--shadow-lift);
  transform: translateY(-2px);
  text-decoration: none;
}
a.card, button.card { transition: transform .18s, box-shadow .18s, border-color .18s; }

.pill {
  display: inline-flex; align-items: center; gap: 6px;
  height: 30px; padding: 0 13px;
  border-radius: var(--r-pill);
  border: 1px solid var(--border);
  background: var(--surface);
  color: var(--fg-2);
  font-size: 12.5px; font-weight: 600;
  cursor: pointer; white-space: nowrap;
  transition: background .16s, border-color .16s, color .16s;
}
.pill:hover { background: var(--surface-2); border-color: var(--brand); color: var(--fg); }
.pill[aria-pressed="true"], .pill[aria-selected="true"], .pill.is-on {
  background: var(--brand-strong); border-color: var(--brand-strong); color: #fff;
}
.pill[aria-pressed="true"]:hover, .pill[aria-selected="true"]:hover, .pill.is-on:hover { background: var(--brand-hover); border-color: var(--brand-hover); color: #fff; }
.pill svg { width: 14px; height: 14px; }

.badge {
  display: inline-flex; align-items: center; gap: 5px;
  height: 24px; padding: 0 9px;
  border-radius: 7px;
  font-size: 11.5px; font-weight: 600; letter-spacing: 0.01em;
  white-space: nowrap;
}
.badge svg { width: 13px; height: 13px; }
.badge-premium { background: var(--gold); color: var(--on-gold); }
.badge-verified { background: var(--chip); color: var(--chip-fg); }
.badge-promoted {
  background: var(--brand-strong); color: #fff;
}
.badge-quiet { background: var(--surface-2); color: var(--fg-2); border: 1px solid var(--border); }
.badge-neg { background: color-mix(in oklab, var(--success) 18%, var(--surface)); color: var(--success); }

/* buttons — one solid primary per action, per screen */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  min-height: 44px; padding: 0 20px;
  border-radius: var(--r-input);
  border: 1px solid transparent;
  font-size: 14.5px; font-weight: 600; font-family: var(--body);
  cursor: pointer; white-space: nowrap;
  transition: background .16s, border-color .16s, color .16s, transform .12s;
}
.btn svg { width: 17px; height: 17px; }
.btn-primary { background: var(--brand-strong); color: var(--on-brand); }
.btn-primary:hover { background: var(--brand-hover); color: var(--on-brand); }
.btn-primary:active { transform: translateY(1px); }
.btn-secondary { background: var(--surface); color: var(--fg); border-color: var(--border); }
.btn-secondary:hover { background: var(--surface-2); border-color: var(--brand); color: var(--fg); }
.btn-ghost { background: transparent; color: var(--accent); border-color: var(--border); }
.btn-ghost:hover { background: var(--brand-wash); border-color: var(--brand); color: var(--accent); }
.btn-quiet { background: transparent; color: var(--fg); border-color: transparent; }
.btn-quiet:hover { background: var(--surface-2); color: var(--fg); }
.btn:disabled, .btn[aria-disabled="true"] {
  background: var(--surface-2); color: var(--muted);
  border-color: var(--border); cursor: not-allowed; transform: none;
}
.btn-block { width: 100%; }

/* forms */
.field { display: flex; flex-direction: column; gap: 7px; }
.field > label, .field > .flabel { font-size: 13px; font-weight: 600; color: var(--fg-2); }
.control {
  display: flex; align-items: center; gap: 10px;
  min-height: 46px; padding: 0 14px;
  background: var(--surface-3);
  border: 1px solid var(--border);
  border-radius: var(--r-input);
  transition: border-color .16s, background .16s, box-shadow .16s;
}
.control svg { width: 17px; height: 17px; color: var(--muted); flex: none; }
.control input, .control select, .control textarea {
  flex: 1; min-width: 0; border: 0; background: none; outline: none; color: var(--fg);
}
.control input::placeholder { color: var(--muted); }
.control:focus-within { border-color: var(--brand); background: var(--surface); box-shadow: 0 0 0 3px var(--brand-wash); }
.control:hover:not(:focus-within) { border-color: var(--border-soft); background: var(--surface-2); }
.control.is-error { border-color: var(--danger); }
.control.is-error:focus-within { box-shadow: 0 0 0 3px color-mix(in oklab, var(--danger) 22%, transparent); }
.field-error { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--danger-text); font-weight: 500; }
.field-error svg { width: 14px; height: 14px; flex: none; }
.field-hint { font-size: 12.5px; color: var(--muted); }
.ghost-link { background: none; border: 0; padding: 0; color: var(--accent); font-size: 13px; font-weight: 600; cursor: pointer; }
.ghost-link:hover { text-decoration: underline; text-underline-offset: 3px; }

/* tabs */
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); }
.tab {
  position: relative; background: none; border: 0;
  padding: 11px 16px 13px; border-radius: 10px 10px 0 0;
  font-size: 14.5px; font-weight: 600; color: var(--muted); cursor: pointer;
  transition: color .16s, background .16s;
}
.tab:hover { color: var(--fg); background: var(--surface-2); }
.tab:hover .count { color: var(--fg-2); }
.tab[aria-selected="true"] { color: var(--fg); }
.tab[aria-selected="true"]::after {
  content: ""; position: absolute; left: 12px; right: 12px; bottom: -1px;
  height: 2px; border-radius: 2px; background: var(--brand);
}
.tab .count { font-variant-numeric: tabular-nums; color: var(--muted); font-weight: 500; margin-left: 5px; }
.tab[aria-selected="true"] .count { color: var(--fg-2); }

/* Feed strip — production's TabSwitcher renders the two feed modes and the
   recommended subcategories as one horizontally-scrolling row of identical
   chips, and exactly one of them is ever active: choosing a subcategory
   replaces the Discover feed rather than narrowing it. So this stays a single
   exclusive tablist, not a tab row plus a filter row. Rest state is
   borderless — thirteen outlined chips would add thirteen rectangles to the
   composition — and the border appears on hover only.

   The row is split into a pinned group and a scroller. Discover / Following /
   Nearby are the feed's primary modes and must never scroll out of reach; the
   ten recommended subcategories are a dynamic, arbitrary-length list, so they
   own the overflow. The two groups sit inside one role="tablist" and the inner
   wrappers are role="presentation", which keeps chip ownership and exclusive
   selection intact while letting the layout differ. */
.strip-row { display: flex; align-items: center; min-width: 0; max-width: 100%; }
.strip-fixed { position: relative; display: flex; align-items: center; gap: 6px; flex: none; }
/* hairline between the pinned modes and the scrolling subcategories. A
   pseudo-element rather than a DOM node, which would sit illegally between the
   tablist and its chips. */
.strip-fixed::after {
  content: ""; position: absolute; right: -8px; top: 50%;
  transform: translateY(-50%);
  width: 1px; height: 18px; background: var(--border);
}
.subtabs-wrap { position: relative; flex: 1 1 auto; min-width: 0; margin-left: 15px; }
.subtabs {
  display: flex; align-items: center; gap: 6px;
  min-width: 0;
  overflow-x: auto; overscroll-behavior-x: contain;
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.subtabs::-webkit-scrollbar { display: none; }
.strip-row .pill {
  position: relative; flex: none;
  height: 36px; padding: 0 15px; font-size: 13px;
  background: transparent; border-color: transparent;
}
.strip-row .pill:hover { background: var(--surface-2); border-color: var(--border); color: var(--fg); }
.strip-row .pill[aria-selected="true"], .strip-row .pill[aria-pressed="true"] { background: var(--brand-strong); border-color: var(--brand-strong); color: var(--on-brand); }
.strip-row .pill[aria-selected="true"]:hover, .strip-row .pill[aria-pressed="true"]:hover { background: var(--brand-hover); border-color: var(--brand-hover); color: var(--on-brand); }
.strip-row .pill .count {
  font-variant-numeric: tabular-nums; font-weight: 500;
  margin-left: 6px; color: var(--muted);
}
.strip-row .pill:hover .count { color: var(--fg-2); }
.strip-row .pill[aria-selected="true"] .count { color: var(--on-brand); opacity: .78; }

/* Below the rail breakpoint the pinned group would leave the scroller only a
   sliver, so the modes give up their labels' breathing room first and the
   whole row scrolls as one if it still cannot fit. */
@media (max-width: 720px) {
  .strip-row { overflow-x: auto; scrollbar-width: none; }
  .strip-row::-webkit-scrollbar { display: none; }
  .strip-fixed .pill { padding: 0 12px; }
  .subtabs { overflow: visible; }
}

/* edge fade, painted only while the scroller genuinely overflows in that
   direction. z-index is load-bearing: the chips are positioned (for the
   selected-state stacking), so an unlayered pseudo-element would paint
   underneath them and the clipped chip at the edge would read as broken
   rather than scrollable. */
.subtabs-wrap::before, .subtabs-wrap::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: 44px;
  z-index: 2; pointer-events: none; opacity: 0; transition: opacity .18s;
}
.subtabs-wrap::before { left: 0; background: linear-gradient(90deg, var(--sheet) 18%, transparent); }
.subtabs-wrap::after { right: 0; background: linear-gradient(270deg, var(--sheet) 18%, transparent); }
.subtabs-wrap[data-edge~="start"]::before { opacity: 1; }
.subtabs-wrap[data-edge~="end"]::after { opacity: 1; }

/* section heads */
.sec-head { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; margin-bottom: 14px; }
.sec-line { display: flex; align-items: center; gap: 10px; min-width: 0; flex-wrap: wrap; }
.sec-head p { color: var(--muted); font-size: 13px; margin-top: 3px; }

.divider { height: 1px; background: var(--border); border: 0; margin: 0; }

.stat { display: flex; flex-direction: column; gap: 2px; }
.stat .v { font-family: var(--display); font-size: 20px; font-weight: 600; font-variant-numeric: tabular-nums; }

/* ------------------------------------------------- material plates
   Photography stand-in: no image model was reachable in this runtime, so
   listing media is a stylized CSS material plate rather than a grey box.
   Swap .plate-* for <img> when real product photography lands.          */
.plate {
  position: relative; overflow: hidden; display: block;
  border-radius: var(--r-card) var(--r-card) 0 0;
  background-color: oklch(0.55 0.012 240);
}
.plate::after {
  content: ""; position: absolute; inset: 0;
  background: linear-gradient(180deg,
    oklch(0.30 0.05 230 / 0.18) 0%,
    transparent 38%,
    oklch(0.20 0.04 230 / 0.42) 100%);
}
.plate-4x3 { aspect-ratio: 4 / 3; }
.plate-16x9 { aspect-ratio: 16 / 9; }

.plate-cement {
  background-image:
    repeating-linear-gradient(178deg,
      oklch(0.74 0.008 240) 0 13px, oklch(0.69 0.010 240) 13px 15px,
      oklch(0.78 0.007 240) 15px 30px, oklch(0.66 0.010 240) 30px 33px),
    radial-gradient(circle at 22% 30%, oklch(0.84 0.006 240 / 0.5), transparent 55%);
}
.plate-rebar {
  background-image:
    repeating-linear-gradient(96deg,
      oklch(0.42 0.020 45) 0 6px, oklch(0.62 0.030 55) 6px 11px,
      oklch(0.74 0.034 60) 11px 14px, oklch(0.48 0.024 48) 14px 22px),
    linear-gradient(160deg, oklch(0.70 0.03 55 / 0.45), transparent 70%);
}
.plate-granite {
  background-image:
    radial-gradient(circle at 18% 22%, oklch(0.30 0.006 240) 0 2px, transparent 3px),
    radial-gradient(circle at 62% 14%, oklch(0.82 0.004 240) 0 3px, transparent 4px),
    radial-gradient(circle at 38% 62%, oklch(0.26 0.006 240) 0 4px, transparent 5px),
    radial-gradient(circle at 84% 74%, oklch(0.76 0.004 240) 0 2px, transparent 3px),
    radial-gradient(circle at 8% 88%, oklch(0.34 0.006 240) 0 3px, transparent 4px),
    linear-gradient(170deg, oklch(0.58 0.008 240), oklch(0.44 0.010 240));
  background-size: 44px 44px, 58px 58px, 36px 36px, 66px 66px, 50px 50px, 100% 100%;
}
.plate-timber {
  background-image:
    repeating-linear-gradient(88deg,
      oklch(0.66 0.055 68) 0 9px, oklch(0.58 0.060 62) 9px 12px,
      oklch(0.72 0.048 72) 12px 24px, oklch(0.52 0.058 60) 24px 27px),
    linear-gradient(200deg, oklch(0.78 0.04 70 / 0.4), transparent 65%);
}
.plate-blocks {
  background-image:
    repeating-linear-gradient(0deg,
      oklch(0.34 0.008 240) 0 2px, transparent 2px 46px),
    repeating-linear-gradient(90deg,
      oklch(0.34 0.008 240) 0 2px, transparent 2px 72px),
    linear-gradient(165deg, oklch(0.68 0.010 240), oklch(0.50 0.012 240));
}
.plate-sand {
  background-image:
    radial-gradient(circle at 25% 35%, oklch(0.86 0.045 78) 0 2px, transparent 3px),
    radial-gradient(circle at 70% 60%, oklch(0.62 0.050 70) 0 2px, transparent 3px),
    linear-gradient(175deg, oklch(0.78 0.052 76), oklch(0.60 0.056 68));
  background-size: 22px 22px, 30px 30px, 100% 100%;
}
.plate-site {
  background-image: var(--blueprint),
    linear-gradient(165deg, oklch(0.36 0.062 233), oklch(0.20 0.038 226));
  background-size: 620px auto, 100% 100%;
  background-position: center bottom, center;
  background-repeat: no-repeat, no-repeat;
}
.plate-site::after { background: none; }

/* corner overlays on media — anchored, inset, never crossing the edge */
.plate-tl, .plate-tr, .plate-bl, .plate-br { position: absolute; z-index: 2; }
.plate-tl { top: 10px; left: 10px; }
.plate-tr { top: 10px; right: 10px; }
.plate-bl { bottom: 10px; left: 10px; }
.plate-br { bottom: 10px; right: 10px; }
.frost {
  background: oklch(0.20 0.04 230 / 0.78);
  color: oklch(0.99 0.003 200);
  backdrop-filter: blur(8px);
  border: 1px solid oklch(0.99 0.003 200 / 0.16);
  box-shadow: 0 2px 10px oklch(0.15 0.03 230 / 0.5);
}
/* a count is a single fact, so it never wraps: at the 96px media width the
   horizontal taxonomy card used, "6 subcategories" broke onto two lines and
   the chip grew taller than the plate inset allowed for */
.plate-count {
  display: inline-flex; align-items: center; gap: 5px;
  height: 24px; padding: 0 9px; border-radius: 7px;
  font-size: 11.5px; font-weight: 600; font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.plate-count svg { width: 13px; height: 13px; }
.save-btn {
  width: 34px; height: 34px; border-radius: 9px;
  display: grid; place-items: center; cursor: pointer;
  transition: background .16s, color .16s;
}
.save-btn svg { width: 17px; height: 17px; }
.save-btn:hover { background: oklch(0.28 0.05 230 / 0.9); color: #fff; }
.save-btn[aria-pressed="true"] { background: var(--gold); color: var(--on-gold); border-color: transparent; }
.save-btn[aria-pressed="true"]:hover { background: oklch(0.740 0.150 82); color: var(--on-gold); }

/* ------------------------------------------------------------- post card
   Horizontal tile matching PostCard.tsx information order:
   seller + overflow menu / hairline / 132px thumbnail + detail column.
   Geometry scaled from the production 360x165 (120px thumb, 11px body) so
   body copy clears 13px and every target clears WCAG 2.5.8.
   The stretched link is a sibling overlay, so the seller button, menu,
   category link, stars and save control are never nested inside an anchor. */
.grid-postcards {
  display: grid; gap: 14px;
  grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
}
.postcard {
  position: relative; display: flex; flex-direction: column;
  overflow: visible;
  transition: transform .18s, box-shadow .18s, border-color .18s;
}
.postcard:has(a.stretch:hover) {
  border-color: var(--brand); box-shadow: var(--shadow-lift); transform: translateY(-2px);
}
.postcard:has(a.stretch:focus-visible) { border-color: var(--brand); box-shadow: var(--shadow-lift); }
.postcard:has(a.stretch:focus-visible) a.stretch::after {
  outline: 2px solid var(--brand); outline-offset: 3px;
}
/* every control out-ranks the stretched link overlay (z-index 1) */
.postcard button, .postcard .pc-cat { position: relative; z-index: 2; }
.pc-menu-wrap { position: relative; z-index: 4; margin-left: auto; flex: none; }

/* top bar — seller identity, then the overflow menu */
.pc-top { display: flex; align-items: center; gap: 4px; padding: 2px 6px 2px 7px; min-height: 32px; }
.pc-seller {
  display: inline-flex; align-items: center; gap: 8px; min-width: 0;
  background: none; border: 0; padding: 3px 6px; border-radius: 10px;
  font: inherit; color: inherit; text-align: left; cursor: pointer;
  transition: background .15s;
}
.pc-seller:hover { background: var(--surface-2); }
.pc-seller:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.pc-name {
  font-size: 13px; font-weight: 600; color: var(--fg); min-width: 0;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* verification badge — production types are blue / grey / gold */
.pc-vbadge {
  flex: none; display: grid; place-items: center;
  width: 16px; height: 16px; border-radius: 50%;
}
.pc-vbadge svg { width: 10px; height: 10px; }
.pc-vbadge[data-badge="blue"] { background: var(--brand-strong); color: var(--on-brand); }
.pc-vbadge[data-badge="grey"] { background: var(--muted); color: var(--sheet); }
.pc-vbadge[data-badge="gold"] { background: var(--gold); color: var(--on-gold); }

.pc-menu-btn {
  width: 30px; height: 30px; border-radius: 9px;
  display: grid; place-items: center;
  background: none; border: 0; color: var(--fg-2); cursor: pointer;
  transition: background .15s, color .15s;
}
.pc-menu-btn svg { width: 18px; height: 18px; }
.pc-menu-btn:hover { background: var(--surface-2); color: var(--fg); }
.pc-menu-btn[aria-expanded="true"] { background: var(--brand-strong); color: var(--on-brand); }
.pc-menu-btn:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.pc-menu {
  position: absolute; top: calc(100% + 5px); right: 0;
  min-width: 178px; padding: 6px;
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: 13px; box-shadow: var(--shadow-lift);
  display: none; flex-direction: column;
}
.pc-menu[data-open="true"] { display: flex; }
.pc-menu button {
  display: flex; align-items: center; gap: 9px;
  min-height: 38px; padding: 0 10px; border-radius: 9px;
  background: none; border: 0; color: var(--fg); font: inherit; font-size: 13px;
  text-align: left; white-space: nowrap; cursor: pointer;
  transition: background .14s;
}
.pc-menu button svg { width: 16px; height: 16px; flex: none; color: var(--fg-2); }
.pc-menu button:hover { background: var(--brand-wash); }
.pc-menu button:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.pc-menu .danger { color: var(--danger-text); }
.pc-menu .danger svg { color: var(--danger-text); }
.pc-menu-sep { height: 1px; background: var(--border); margin: 5px 2px; }
.pc-hair { height: 1px; background: var(--border-soft); }

/* body — OPTION A geometry. The media is flush to the card's left, bottom and
   hairline edges, so the 9px inset that used to ring it is gone and the square
   grows into that space (136 -> 143) while the card gets shorter (191 -> 176).
   The side is an explicit px value rather than `height: 100%` + aspect-ratio:
   in a grid the column width would then depend on the row height that the
   column itself contributes to, which resolves to 0 in Chrome. The detail
   column carries its own padding since .pc-main no longer has any, and its
   stack sums to ~141px against the square's 146 — the square must stay the
   taller of the two, because that is what keeps it flush against the bottom
   border. Any slack lands below the rating instead, where it is invisible. */
.pc-main { display: grid; grid-template-columns: 146px minmax(0, 1fr); gap: 11px; padding: 0; flex: 1 1 auto; }
.pc-thumb {
  position: relative; width: 146px; height: 146px; aspect-ratio: 1 / 1;
  align-self: start;
  border-radius: 0 0 0 calc(var(--r-card) - 1px);
  overflow: hidden; background: var(--surface-3);
}
.pc-thumb .plate { position: absolute; inset: 0; border-radius: 0; }
.pc-thumb .save-btn { position: absolute; right: 6px; bottom: 6px; width: 28px; height: 28px; border-radius: 8px; }
.pc-thumb .save-btn svg { width: 14px; height: 14px; }
/* missing or broken image — production shows a package glyph, not a blank box */
.pc-thumb-empty { display: grid; place-items: center; height: 100%; color: var(--muted); }
.pc-thumb-empty svg { width: 30px; height: 30px; }

/* detail column — rating pinned to the bottom so cards in a row stay level.
   Brand•category and location stay on their own rows: pairing them onto one
   row saved only ~6px and truncated the category on most cards. */
.pc-info { display: flex; flex-direction: column; gap: 2px; min-width: 0; padding: 7px 10px 6px 0; }
.pc-title {
  font-family: var(--display); font-size: 13.5px; font-weight: 600; line-height: 1.2;
  margin: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}
.pc-title a.stretch { color: var(--fg); }
.pc-price {
  display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; line-height: 1.25;
  font-family: var(--display); font-size: 14px; font-weight: 700;
  color: var(--success); font-variant-numeric: tabular-nums; letter-spacing: 0.01em;
}
.pc-price .none { color: var(--muted); font-weight: 500; font-size: 12.5px; letter-spacing: 0; }
.pc-price .neg {
  flex: none; font-family: var(--body); font-size: 9.5px; font-weight: 700;
  text-transform: uppercase; letter-spacing: 0.08em;
  padding: 2px 6px; border-radius: var(--r-pill);
  background: color-mix(in oklab, var(--success) 16%, var(--surface));
}
/* brand • category — a link, except under the Opportunities section */
.pc-cat {
  display: block; width: 100%; text-align: left;
  background: none; border: 0; padding: 0;
  font: inherit; font-size: 12px; line-height: 1.25; color: var(--accent);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; cursor: pointer;
}
.pc-cat:hover { text-decoration: underline; text-underline-offset: 3px; }
.pc-cat:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 4px; }
.pc-cat:disabled { color: var(--muted); cursor: not-allowed; text-decoration: none; }
.pc-loc {
  display: flex; align-items: center; gap: 5px;
  font-size: 12px; line-height: 1.25; color: var(--muted); min-width: 0;
}
.pc-loc svg { width: 12.5px; height: 12.5px; flex: none; }
.pc-loc span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* rating — five live stars, tier colour follows the average (icons, 3:1) */
.pc-rate { display: flex; flex-direction: column; gap: 0; }
.pc-info > .pc-rate { margin-top: auto; }
.pc-stars { display: flex; margin-left: -4px; }
.pc-star {
  width: 28px; height: 24px; display: grid; place-items: center;
  background: none; border: 0; padding: 0; cursor: pointer; color: var(--muted);
  transition: transform .12s, color .12s;
}
.pc-star svg { width: 15px; height: 15px; }
.pc-star:hover { transform: scale(1.14); }
.pc-star:focus-visible { outline: 2px solid var(--brand); outline-offset: -3px; border-radius: 6px; }
.pc-rate[data-tier="low"]  .pc-star[aria-pressed="true"] { color: var(--danger); }
.pc-rate[data-tier="mid"]  .pc-star[aria-pressed="true"] { color: var(--warning); }
.pc-rate[data-tier="high"] .pc-star[aria-pressed="true"] { color: var(--success); }
.pc-rate-cap { font-size: 11px; line-height: 1.3; color: var(--muted); margin: 0; padding-left: 1px; }

/* promotion tier — Basic 30, Premium 60, Enterprise 90 (weight thresholds) */
.pc-promo {
  position: absolute; top: 6px; left: 6px; z-index: 3;
  display: inline-flex; align-items: center; gap: 4px;
  height: 20px; padding: 0 6px; border-radius: 7px;
  font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.06em;
}
.pc-promo svg { width: 11px; height: 11px; }
.pc-promo[data-tier="basic"] {
  background: var(--surface-2); color: var(--fg-2); border: 1px solid var(--border);
}
.pc-promo[data-tier="premium"] { background: var(--gold); color: var(--on-gold); }
.pc-promo[data-tier="enterprise"] { background: var(--fg); color: var(--sheet); }

@media (max-width: 460px) {
  .grid-postcards { grid-template-columns: minmax(0, 1fr); }
  .pc-main { grid-template-columns: 128px minmax(0, 1fr); gap: 10px; padding: 0; }
  .pc-thumb { width: 128px; height: 128px; }
  .pc-star { width: 26px; }
}

.grid-listings {
  display: grid; gap: 18px;
  grid-template-columns: repeat(auto-fill, minmax(258px, 1fr));
}
.listing { overflow: visible; transition: transform .18s, box-shadow .18s, border-color .18s; }
a.stretch { color: inherit; text-decoration: none; }
a.stretch:hover { text-decoration: none; }
a.stretch::after {
  content: ""; position: absolute; inset: 0; z-index: 1;
  border-radius: var(--r-card);
}
.listing:has(a.stretch:hover) {
  border-color: var(--brand); box-shadow: var(--shadow-lift); transform: translateY(-2px);
}
.listing:has(a.stretch:focus-visible) {
  border-color: var(--brand); box-shadow: var(--shadow-lift);
}
.listing:has(a.stretch:focus-visible) a.stretch::after {
  outline: 2px solid var(--brand); outline-offset: 3px;
}
a.stretch:focus-visible { outline: none; }
.listing-body { padding: 13px 14px 14px; display: flex; flex-direction: column; gap: 9px; }
.listing-title {
  font-family: var(--display); font-size: 15.5px; font-weight: 600; line-height: 1.3;
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
  min-height: 2.6em;
}
.listing-price { display: flex; align-items: baseline; gap: 7px; flex-wrap: wrap; }
.listing-price .amount { font-size: 19px; }
.listing-price .unit { font-size: 12.5px; color: var(--muted); font-weight: 500; }
.listing-meta {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  font-size: 12.5px; color: var(--muted);
}
.listing-meta .m { display: inline-flex; align-items: center; gap: 5px; }
.listing-meta svg { width: 13px; height: 13px; }
.listing-foot {
  display: flex; align-items: center; gap: 9px;
  padding-top: 10px; border-top: 1px solid var(--border-soft);
}
.listing-foot .who { min-width: 0; }
.listing-foot .name {
  font-size: 12.5px; font-weight: 600; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
.rate { display: inline-flex; align-items: center; gap: 4px; margin-left: auto; flex: none; font-size: 12.5px; font-weight: 600; }
.rate svg { width: 14px; height: 14px; color: var(--gold-text); }
.rate .n { color: var(--muted); font-weight: 500; }

/* Theme toggle — one button, two icons swapping.
   It rides the .iconbtn chip so it belongs to the header's 40px control family,
   and it shows the destination rather than the current state: sun in dark mode
   means "go light". The label is the accessible name and is rewritten on every
   change, so there is no aria-pressed ambiguity about what "pressed" would mean
   on a two-value control. Icons are stacked in the same 40px box, so the
   crossfade cannot shift the button's size. */
.theme-btn svg {
  position: absolute; width: 19px; height: 19px;
  transition: opacity .18s ease, transform .24s cubic-bezier(.2, .7, .3, 1);
}
.theme-btn [data-ic="sun"]  { opacity: 0; transform: rotate(-50deg) scale(.6); }
.theme-btn [data-ic="moon"] { opacity: 0; transform: rotate(50deg) scale(.6); }
[data-theme="dark"]  .theme-btn [data-ic="sun"],
[data-theme="light"] .theme-btn [data-ic="moon"] { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .theme-btn svg { transition: none; }
  .theme-btn [data-ic="sun"], .theme-btn [data-ic="moon"] { transform: none; }
}

/* toast */
.toast {
  position: fixed; left: 50%; bottom: 28px; transform: translate(-50%, 16px);
  display: flex; align-items: center; gap: 10px;
  padding: 12px 18px; border-radius: var(--r-pill);
  background: var(--surface); border: 1px solid var(--brand);
  box-shadow: var(--shadow-lift); color: var(--fg);
  font-size: 13.5px; font-weight: 600;
  opacity: 0; pointer-events: none; z-index: 90;
  transition: opacity .2s, transform .2s;
}
.toast.is-on { opacity: 1; transform: translate(-50%, 0); }
.toast svg { width: 17px; height: 17px; color: var(--accent); }

/* ==========================================================================
   BROWSE GRID — the archetype behind the 12 taxonomy browse routes
   (/products, /services, /opportunities × subsection / category / subcategory)

   Production swaps the left column rather than adding a second one:
   DynamicSidebar renders FilterSidebar in place of the nav rail on these
   routes, so a browse screen has one 268px rail, not two. We follow that and
   add a breadcrumb, because losing the nav also loses the way back out.
   ========================================================================== */

/* --- filter rail ------------------------------------------------------- */
.rail-filters { padding: 0; }
.frail-head {
  position: sticky; top: 0; z-index: 3;
  padding: 14px 14px 12px;
  background: var(--surface); border-bottom: 1px solid var(--border);
}
[data-theme="dark"] .frail-head { background: oklch(0.268 0.052 231); }
.frail-sub { display: block; margin-bottom: 7px; }
.frail-title { display: flex; align-items: center; gap: 8px; }
.frail-title h2 { font-family: var(--display); font-size: 16px; font-weight: 600; }
.frail-title > svg { width: 15px; height: 15px; color: var(--accent); }
.fcount {
  margin-left: auto;
  display: inline-flex; align-items: center; justify-content: center;
  min-width: 22px; height: 22px; padding: 0 6px;
  border-radius: var(--r-pill);
  background: var(--brand-strong); color: #fff;
  font-size: 11.5px; font-weight: 700; font-variant-numeric: tabular-nums;
}
/* [hidden] is settled globally at the top of this file. */
.frail-body { padding: 14px; display: flex; flex-direction: column; gap: 15px; }
.frail-foot {
  position: sticky; bottom: 0;
  padding: 12px 14px; margin-top: auto;
  background: var(--surface); border-top: 1px solid var(--border);
  display: flex; align-items: center; justify-content: space-between; gap: 10px;
}
[data-theme="dark"] .frail-foot { background: oklch(0.268 0.052 231); }
.frail-foot .n { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* Named sections. The rail previously ran two unlabelled groups, a bare
   <hr>, two more, another <hr>, then a lone "Attributes" heading — so one
   third of it was titled and the rest was not. Every group now belongs to a
   named section, and the rule is the section's own top border rather than a
   free-floating separator. */
.fsec { display: flex; flex-direction: column; gap: 12px; padding-top: 14px; border-top: 1px solid var(--border); }
.fsec:first-child { padding-top: 0; border-top: 0; }
.fsec-h { display: flex; align-items: center; gap: 7px; min-width: 0; }
.fsec-h svg { width: 13px; height: 13px; flex: none; color: var(--accent); }
/* the section heading and its group labels would otherwise be the identical
   11px muted uppercase, flattening two levels into one */
.fsec-h .t-label { color: var(--fg); font-weight: 700; letter-spacing: 0.09em; }

.fgroup { display: flex; flex-direction: column; gap: 7px; }
.fgroup > .lab { display: flex; align-items: baseline; gap: 8px; }
.fgroup > .lab .t-label { flex: 1; min-width: 0; }
/* a per-group clear is a text button, never a second solid control */
.fgroup .clr { font-size: 11.5px; font-weight: 600; color: var(--accent); background: none; border: 0; padding: 0; cursor: pointer; }
.fgroup .clr:hover { text-decoration: underline; text-underline-offset: 3px; }
.fgroup .clr:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 3px; }
/* compact variant of .control for a 268px rail */
.fgroup .control { min-height: 38px; padding: 0 11px; font-size: 13px; }
.fgroup .control svg { width: 15px; height: 15px; }
.fgroup .control select { font-size: 13px; font-family: inherit; cursor: pointer; appearance: none; }
.fgroup .control[data-disabled="true"] { opacity: .55; pointer-events: none; }

/* boolean attribute filter — a row, not a floating checkbox */
.fswitch {
  display: flex; align-items: center; gap: 10px;
  min-height: 40px; padding: 0 11px;
  background: var(--surface-3); border: 1px solid var(--border);
  border-radius: var(--r-input);
  font-size: 13px; font-weight: 500; color: var(--fg);
  width: 100%; text-align: left; cursor: pointer;
  transition: border-color .16s, background .16s;
}
.fswitch:hover { background: var(--surface-2); border-color: var(--border-soft); }
.fswitch:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.fswitch .box {
  width: 17px; height: 17px; flex: none; border-radius: 5px;
  border: 1.5px solid var(--muted); display: grid; place-items: center;
  transition: background .16s, border-color .16s;
}
.fswitch .box svg { width: 12px; height: 12px; color: #fff; opacity: 0; }
.fswitch[aria-pressed="true"] { border-color: var(--brand); background: var(--brand-wash); }
.fswitch[aria-pressed="true"] .box { background: var(--brand-strong); border-color: var(--brand-strong); }
.fswitch[aria-pressed="true"] .box svg { opacity: 1; }
.fswitch .n { margin-left: auto; font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; }
/* --muted is 4.20:1 on dark --brand-wash, so the count lifts when selected */
.fswitch[aria-pressed="true"] .n, .fswitch:hover .n { color: var(--fg-2); }

/* --- dual price range -------------------------------------------------- */
.frange { display: flex; flex-direction: column; gap: 7px; }
/* the readout is an output, so it sits in an inset well rather than floating
   as loose text above the track */
.frange-out {
  display: flex; align-items: center; justify-content: space-between; gap: 8px;
  padding: 6px 10px; border-radius: var(--r-input);
  background: var(--surface-3); border: 1px solid var(--border);
  font-family: var(--display); font-size: 13.5px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.frange-out .to { color: var(--muted); font-weight: 500; font-family: var(--body); font-size: 12px; }
.frange-slot { position: relative; height: 26px; }
.frange-track {
  position: absolute; top: 11px; left: 2px; right: 2px; height: 4px;
  border-radius: var(--r-pill);
  background: color-mix(in oklab, var(--fg) 16%, var(--surface));
}
.frange-fill {
  position: absolute; top: 11px; height: 4px; border-radius: var(--r-pill);
  background: var(--brand); pointer-events: none;
}
.frange input[type="range"] {
  position: absolute; top: 0; left: 0; width: 100%; height: 26px;
  margin: 0; padding: 0; background: none; border: 0;
  -webkit-appearance: none; appearance: none;
  pointer-events: none; /* only the thumbs are grabbable, so both stay reachable */
}
.frange input[type="range"]::-webkit-slider-thumb {
  -webkit-appearance: none; pointer-events: auto;
  width: 19px; height: 19px; border-radius: 50%;
  background: var(--surface); border: 2px solid var(--brand-strong);
  box-shadow: var(--shadow-card); cursor: grab;
}
.frange input[type="range"]::-moz-range-thumb {
  pointer-events: auto;
  width: 19px; height: 19px; border-radius: 50%;
  background: var(--surface); border: 2px solid var(--brand-strong);
  box-shadow: var(--shadow-card); cursor: grab;
}
.frange input[type="range"]:focus-visible { outline: 2px solid var(--brand); outline-offset: 3px; border-radius: var(--r-pill); }
.frange-scale { display: flex; justify-content: space-between; font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* --- active filter chips ---------------------------------------------- */
.chipset { display: flex; align-items: center; flex-wrap: wrap; gap: 7px; }
.fchip {
  display: inline-flex; align-items: center; gap: 7px;
  height: 30px; padding: 0 6px 0 11px;
  border-radius: var(--r-pill);
  background: var(--brand-wash); border: 1px solid var(--brand);
  color: var(--fg); font-size: 12.5px; font-weight: 500;
}
.fchip .k { color: var(--fg-2); }
.fchip .x {
  display: grid; place-items: center;
  width: 20px; height: 20px; flex: none; border-radius: 50%;
  background: none; border: 0; color: var(--fg-2); cursor: pointer;
  transition: background .14s, color .14s;
}
.fchip .x svg { width: 11px; height: 11px; }
.fchip .x:hover { background: var(--brand-strong); color: #fff; }
.fchip .x:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.chipset .clr-all { font-size: 12.5px; font-weight: 600; color: var(--accent); background: none; border: 0; padding: 4px 2px; cursor: pointer; }
.chipset .clr-all:hover { text-decoration: underline; text-underline-offset: 3px; }
.chipset .clr-all:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 4px; }

/* --- breadcrumb -------------------------------------------------------- */
/* A wrapping breadcrumb reads as two ragged lines of grey, so this one holds
   a single line at every width and sheds depth instead. Ancestors drop from
   the front as the column narrows — the nearest ones carry the most meaning,
   and the rail's Explore item is the way back to the root. `data-depth` gates
   the folding so a short chain can never fold itself down to nothing. */
.crumbs { display: flex; align-items: center; flex-wrap: nowrap; gap: 2px; min-width: 0; font-size: 12.5px; margin-bottom: 12px; }
.crumbs > * { flex: none; }
.crumbs a { color: var(--muted); font-weight: 500; padding: 3px 5px; border-radius: 6px; white-space: nowrap; }
.crumbs a:hover { color: var(--fg); background: var(--surface-2); text-decoration: none; }
.crumbs svg { width: 12px; height: 12px; color: var(--muted); flex: none; opacity: .7; }
/* the deepest label is the only one allowed to shrink, and it truncates
   rather than wrapping so the line height never doubles */
.crumbs [aria-current="page"] {
  color: var(--fg); font-weight: 600; padding: 3px 5px;
  flex: 0 1 auto; min-width: 0; white-space: nowrap;
  overflow: hidden; text-overflow: ellipsis;
}
/* crumbs sit at odd child positions, separators at even, so folding one
   ancestor means hiding one odd/even pair from the front */
@media (max-width: 1180px) {
  .crumbs[data-depth="4"] > :nth-child(-n + 2),
  .crumbs[data-depth="5"] > :nth-child(-n + 2),
  .crumbs[data-depth="6"] > :nth-child(-n + 2) { display: none; }
}
@media (max-width: 900px) {
  .crumbs[data-depth="3"] > :nth-child(-n + 2),
  .crumbs[data-depth="5"] > :nth-child(-n + 4),
  .crumbs[data-depth="6"] > :nth-child(-n + 4) { display: none; }
}
@media (max-width: 760px) {
  .crumbs[data-depth="4"] > :nth-child(-n + 4) { display: none; }
}
@media (max-width: 620px) {
  .crumbs[data-depth="5"] > :nth-child(-n + 6),
  .crumbs[data-depth="6"] > :nth-child(-n + 8) { display: none; }
}

/* --- market price primitives: band, movement, unit --------------------
   Used by the one market-price card (.subcat), which appears both as the
   level-3 grid item and as the browse grid's page lead. The band is filled
   encoding — the fill length is low→current, so a glance says "near the
   bottom of the range".

   Both the former .ticker-* layout and the later .subcat-lead scale variant
   are retired. Each was a second arrangement of the same six facts, so
   walking level 3 → the grid re-taught the reader the same commodity in a
   different shape. One card at one size now carries both positions.
   ---------------------------------------------------------------------- */

/* The price *is* the control. The figure and its unit sit inside one button
   with `white-space: nowrap`, which makes the two structurally incapable of
   landing on separate lines, and clicking anywhere on them advances to the
   next rung. This replaced a bordered <select> chip beside the figure: the
   chip could wrap away from the amount it qualified, and a 24px control next
   to a 19px price read as a second object rather than as part of it.

   Quiet at rest so the price still reads as a figure — no plate, no border,
   just a dashed underline on the unit and a cycle glyph, the conventional
   "this value is editable" pair. Hover and focus raise a --brand-wash plate
   on negative margins, so nothing in the card shifts position. */
.pricecyc {
  display: inline-flex; align-items: baseline; gap: 5px;
  padding: 2px 6px; margin: -2px -6px;
  background: none; border: 0; border-radius: var(--r-chip);
  font: inherit; color: inherit; text-align: left; cursor: pointer;
  white-space: nowrap; max-width: 100%;
  transition: background .16s;
}
.pricecyc .u {
  font-size: 11px; font-weight: 500; color: var(--muted);
  /* derived from --muted rather than --border: at 1px on a dark card a
     --border hairline was invisible in the render, which left the 12px glyph
     as the only rest-state affordance. A border is not text, so tracking the
     label's own tone costs nothing in the contrast audit. */
  border-bottom: 1px dashed color-mix(in oklab, var(--muted) 72%, transparent);
  /* last-resort guard: the widest rung fits the narrowest column by
     measurement, but if content ever outgrew it the unit shrinks rather than
     the button overflowing its card */
  flex: 0 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis;
  transition: color .16s, border-color .16s;
}
.pricecyc > svg {
  width: 12px; height: 12px; flex: none;
  align-self: center; color: var(--muted);
  transition: color .16s, transform .3s;
}
/* --fg-2, not --muted: --muted on --brand-wash computes to 4.20:1 in dark,
   which would be a contrast drop on hover. Both lifts are rises. */
.pricecyc:hover { background: var(--brand-wash); }
.pricecyc:hover .u { color: var(--fg-2); border-bottom-color: var(--brand); }
.pricecyc:hover > svg { color: var(--fg-2); transform: rotate(60deg); }
.pricecyc:focus-visible { background: var(--brand-wash); outline: 2px solid var(--brand); outline-offset: 1px; }
.pricecyc:focus-visible .u { color: var(--fg-2); border-bottom-color: var(--brand); }
.pricecyc:focus-visible > svg { color: var(--fg-2); }
@media (prefers-reduced-motion: reduce) {
  .pricecyc, .pricecyc .u, .pricecyc > svg { transition: none; }
  .pricecyc:hover > svg { transform: none; }
}

.band-slot { position: relative; height: 16px; }
.band-track {
  position: absolute; top: 6px; left: 0; right: 0; height: 5px;
  border-radius: var(--r-pill);
  background: color-mix(in oklab, var(--fg) 14%, var(--surface));
}
.band-fill { position: absolute; top: 6px; left: 0; height: 5px; border-radius: var(--r-pill); background: var(--brand); }
.band-mark {
  position: absolute; top: 0; width: 4px; height: 17px; margin-left: -2px;
  border-radius: 2px; background: var(--fg);
  box-shadow: 0 0 0 2px var(--surface);
}
.band-caps { display: flex; justify-content: space-between; gap: 10px; margin-top: 5px; font-size: 11.5px; font-weight: 600; font-variant-numeric: tabular-nums; }
/* The keys are visible, not sr-only. Two bare numbers under a track read as a
   range without saying which reading is which, and at the 328px grid minimum
   the 188px detail column holds "Lowest ₦8,600 / Highest ₦10,800" (~154px at
   10.5px) with room to spare. Only below 400px does the column lose them. */
.band-caps .k { color: var(--muted); font-weight: 500; }
.band-caps .lo { color: var(--success-text); }
.band-caps .hi { color: var(--danger-text); }

.mv { display: inline-flex; align-items: center; gap: 2px; }.mv > span {
  display: inline-flex; align-items: center; justify-content: center;
  height: 24px; background: var(--surface-3);
  font-size: 12px; font-weight: 700; font-variant-numeric: tabular-nums;
  color: var(--fg-2);
}
.mv .w { width: 28px; border-radius: var(--r-pill) 0 0 var(--r-pill); }
.mv .d { padding: 0 9px; }
.mv .a { width: 26px; border-radius: 0 var(--r-pill) var(--r-pill) 0; }
.mv .a svg { width: 14px; height: 14px; }
.mv[data-dir="down"] .d, .mv[data-dir="down"] .a { color: var(--success-text); }
.mv[data-dir="up"] .d, .mv[data-dir="up"] .a { color: var(--danger-text); }
.mv[data-dir="up"] .a svg { transform: rotate(180deg); }

/* --- control row: verification, sort, filters -------------------------
   One right-aligned cluster of three identically built controls, in the order
   a buyer narrows a set: *which* merchants, then what *order*, then the full
   filter surface. Two earlier versions of this row are gone and worth naming,
   so neither gets rebuilt by accident. It first carried a result count, which
   restated the ticker's own "12 active listings" one row above; the count now
   lives on as an sr-only live region, because a filter that silently changes
   the set still has to be announced. It then split left/right around that
   count — and with the text gone, a two-sided row had nothing to balance.

   Verification was a segmented radiogroup (three 28px buttons in a tinted
   well, selected segment filled --brand-strong). Three visible options is the
   upper limit that pattern reads well at, and it made the row asymmetric: a
   compound multi-button well beside two single controls. Collapsing it to a
   select means all three share one geometry — 36px tall, --r-input, 1px
   border on --surface — so the cluster reads as one control family. */
.resbar {
  display: flex; align-items: center; justify-content: flex-end;
  gap: 10px; flex-wrap: wrap;
  padding: 12px 0 11px; margin-bottom: 16px;
  border-bottom: 1px solid var(--border);
}

.sortsel {
  display: inline-flex; align-items: center; gap: 8px;
  height: 36px; padding: 0 11px;
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--r-input);
  font-size: 13px; font-weight: 600; color: var(--fg);
  transition: border-color .16s, background .16s;
}
.sortsel:hover { border-color: var(--brand); background: var(--surface-2); }
.sortsel:focus-within { border-color: var(--brand); box-shadow: 0 0 0 3px var(--brand-wash); }
.sortsel .k { color: var(--muted); font-weight: 500; }
.sortsel:hover .k { color: var(--fg-2); }
.sortsel select { border: 0; background: none; outline: none; color: inherit; font: inherit; cursor: pointer; appearance: none; padding-right: 2px; }
.sortsel > svg { width: 14px; height: 14px; color: var(--muted); flex: none; }

/* below the rail breakpoint the filter entry joins the pair, so it takes the
   same 36px / --r-input geometry rather than the 30px pill default */
.filterbtn { display: none; height: 36px; border-radius: var(--r-input); font-size: 13px; }

/* Three 36px controls need ~380px; below that they share the row instead of
   wrapping into a ragged second line. Each select gives up its intrinsic
   width and truncates its own value, and the group stops hugging the right
   edge once it spans the full width. */
@media (max-width: 620px) {
  .resbar { justify-content: stretch; gap: 8px; }
  .resbar .sortsel { flex: 1 1 0; min-width: 0; padding: 0 9px; gap: 6px; }
  .resbar .sortsel select { min-width: 0; text-overflow: ellipsis; }
  .filterbtn { flex: none; }
}
@media (max-width: 400px) {
  .resbar .sortsel .k { display: none; }
}

/* --- empty state ------------------------------------------------------- */
.empty { text-align: center; padding: 54px 20px 60px; }
.empty .ic {
  width: 56px; height: 56px; margin: 0 auto 16px;
  border-radius: 16px; background: var(--chip); color: var(--chip-fg);
  display: grid; place-items: center;
}
.empty .ic svg { width: 26px; height: 26px; }
.empty h2 { font-family: var(--display); font-size: 19px; font-weight: 600; }
.empty p { max-width: 42ch; margin: 8px auto 18px; color: var(--muted); font-size: 13.5px; line-height: 1.55; }

/* --- load more --------------------------------------------------------- */
.loadbar { display: flex; flex-direction: column; align-items: center; gap: 10px; padding: 26px 0 8px; }
.loadbar .n { font-size: 12.5px; color: var(--muted); font-variant-numeric: tabular-nums; }

/* --- mobile filter sheet ---------------------------------------------- */
.fsheet { position: fixed; inset: 0; z-index: 80; display: none; }
.fsheet[data-open="true"] { display: block; }
.fsheet-scrim { position: absolute; inset: 0; background: oklch(0.15 0.03 228 / 0.62); }
.fsheet-panel {
  position: absolute; left: 0; right: 0; bottom: 0;
  max-height: 88vh; display: flex; flex-direction: column;
  background: var(--surface);
  border-radius: var(--r-sheet) var(--r-sheet) 0 0;
  box-shadow: var(--shadow-lift);
  animation: fsheet-in .22s ease;
}
@keyframes fsheet-in { from { transform: translateY(14px); opacity: .6 } to { transform: none; opacity: 1 } }
.fsheet-panel .frail-body { overflow-y: auto; }
.fsheet-grab { width: 38px; height: 4px; border-radius: var(--r-pill); background: var(--border); margin: 9px auto 0; }
.fsheet-x {
  display: grid; place-items: center; width: 32px; height: 32px; flex: none;
  border-radius: 50%; background: var(--surface-3); border: 1px solid var(--border);
  color: var(--fg-2); cursor: pointer;
}
.fsheet-x svg { width: 15px; height: 15px; }
.fsheet-x:hover { background: var(--surface-2); color: var(--fg); }
.fsheet-x:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* =====================================================================
   EXPLORE — taxonomy levels 1–3
   SubSectionCard and CategoryCard are functionally identical in production:
   a 140px image plus a 29px truncated name and nothing else. That makes
   levels 1 and 2 three clicks of near-empty cards before anything tradeable
   appears. These cards keep the same skeleton but carry the two facts a
   buyer needs in order to choose a branch — how much supply sits under it,
   and what the next level down actually contains — so the peek chips let a
   user drop two levels in one click instead of walking the tree.
   ===================================================================== */

/* The three taxonomy trees are separate routes, so the switcher is anchors
   with aria-current, not tabs with panels. */
.tabs a.tab { text-decoration: none; }
.tabs a.tab:hover { text-decoration: none; }
.tab[aria-current="page"] { color: var(--fg); }
.tab[aria-current="page"]::after {
  content: ""; position: absolute; left: 12px; right: 12px; bottom: -1px;
  height: 2px; border-radius: 2px; background: var(--brand);
}
.tab[aria-current="page"] .count { color: var(--fg-2); }

/* Tree switcher. Three routes must sit side by side at 320px without a
   scroller, so the counts render compact (1.4k) with the exact figure kept
   for assistive tech, and the padding tightens twice on the way down.
   Budget at 320px: 3 x (label + 24 padding) + 8 gap against 292px of wrap. */
/* The switcher names the page's entire subject, so the grid below it needs a
   gap: flush against the tab rule, the first card row read as a fourth tab. */
.tabs-tree { gap: 2px; margin-bottom: 18px; }
.tabs-tree .tab { padding: 10px 10px 12px; white-space: nowrap; }
.tabs-tree .count { margin-left: 4px; }
/* The selected branch is the page's entire subject, so it takes a tinted
   seat and a thicker full-width rule rather than a colour change alone —
   at three near-identical labels, colour by itself was too quiet. */
.tabs-tree .tab[aria-current="page"] { background: var(--brand-wash); font-weight: 700; }
.tabs-tree .tab[aria-current="page"]::after { left: 0; right: 0; height: 3px; background: var(--brand-strong); }
.tabs-tree .tab[aria-current="page"] .count { color: var(--fg-2); }
@media (max-width: 560px) {
  .tabs-tree .tab { padding: 10px 9px 12px; font-size: 13.5px; }
}
@media (max-width: 380px) {
  .tabs-tree .tab { padding: 10px 8px 12px; font-size: 13px; }
}

/* one-line page head — no lede band, no floated CTA gap */
.xhead { display: flex; align-items: flex-end; justify-content: space-between; gap: 18px; flex-wrap: wrap; margin: 15px 0 13px; }
.xhead h1 { font-family: var(--display); font-size: 23px; font-weight: 600; letter-spacing: -0.01em; line-height: 1.15; }
.xdot { width: 3px; height: 3px; border-radius: 50%; background: currentColor; opacity: .55; flex: none; }

/* skip-ahead link: jumps past the taxonomy to the listing grid for this level */
.xlink { display: inline-flex; align-items: center; gap: 5px; font-size: 13px; font-weight: 600; color: var(--accent); text-decoration: none; padding: 5px 8px; margin: -5px -8px; border-radius: 8px; }
.xlink svg { width: 14px; height: 14px; flex: none; }
.xlink:hover { background: var(--surface-2); color: var(--fg); text-decoration: underline; text-underline-offset: 3px; }
.xlink:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }

/* sibling strip — lateral movement across one taxonomy level */
.sibs { display: flex; gap: 7px; overflow-x: auto; scrollbar-width: none; padding: 1px 0 3px; margin-bottom: 14px; min-width: 0; }
.sibs::-webkit-scrollbar { display: none; }
.sibs > * { flex: none; }
.sibs-lab { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; font-weight: 600; color: var(--muted); align-self: center; padding-right: 3px; }

/* --- taxonomy card (levels 1 and 2) --------------------------------- */
.tax-grid { display: grid; gap: 14px; grid-template-columns: repeat(auto-fill, minmax(252px, 1fr)); }
.taxcard { padding: 0; overflow: hidden; display: flex; flex-direction: column; }
.taxcard { transition: transform .18s, box-shadow .18s, border-color .18s; }
.taxcard:has(a.stretch:hover) { transform: translateY(-2px); box-shadow: var(--shadow-lift); border-color: var(--brand); }
.taxcard:has(a.stretch:focus-visible) { border-color: var(--brand); box-shadow: var(--shadow-lift); }
.taxcard:has(a.stretch:focus-visible) a.stretch::after { outline: 2px solid var(--brand); outline-offset: -3px; border-radius: var(--r-card); }
.tax-media { position: relative; aspect-ratio: 16 / 9; border-bottom: 1px solid var(--border); }
.tax-media .plate { display: block; position: absolute; inset: 0; }
.tax-body { padding: 11px 13px 12px; display: flex; flex-direction: column; gap: 6px; flex: 1; }
.tax-name { font-family: var(--display); font-size: 15.5px; font-weight: 600; line-height: 1.28; }
.tax-name a.stretch { color: var(--fg); }
.tax-count { font-size: 12px; color: var(--muted); display: flex; align-items: center; gap: 6px; }
.tax-count b { color: var(--fg-2); font-weight: 600; font-variant-numeric: tabular-nums; }

/* peek chips out-rank the stretched overlay (z-index 1) so they stay clickable */
.tax-peek { display: flex; flex-wrap: wrap; gap: 5px; margin-top: auto; padding-top: 4px; }
.tax-peek a, .tax-peek .more { position: relative; z-index: 2; display: inline-flex; align-items: center; height: 25px; border-radius: var(--r-pill); font-size: 11.5px; font-weight: 500; }
.tax-peek a {
  padding: 0 9px; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  background: var(--surface-3); border: 1px solid var(--border-soft); color: var(--fg-2);
  transition: background .16s, border-color .16s, color .16s;
}
/* hover swaps both channels in one rule: 8.6:1 → 5.02:1 white-on-brand-strong,
   both above AA, and the background moves 0.27 L in dark / 0.44 L in light */
.tax-peek a:hover { background: var(--brand-strong); border-color: var(--brand-strong); color: #fff; text-decoration: none; }
.tax-peek a:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.tax-peek .more { color: var(--muted); font-weight: 600; padding: 0 3px; }

/* The bespoke sponsored tile (.tax-ad / .ad-*) was removed: promotion in this
   product is a promotion_weight on an ordinary post, so every paid surface is
   a .postcard strip. An ad creative would have needed its own content type. */

/* --- subcategory card (level 3): the tradeable one -------------------
   Compact form of the market-price card. The unit control is per card and so
   is the conversion: each card declares its own ladder, because the base unit
   differs by subcategory (50 kg bag / 20 kg bag / 25 kg bag / drum), and a
   document-wide rewrite would convert figures that do not share a unit.

   328px, not 268: the detail column has to hold the widest price beside the
   unit control on one line — ₦16,400 is ~74px and the control ~96px, so the
   column needs ~174px and 328 − 116 pane − 24 padding gives 188. It also
   settles the row count honestly: at 268 a 1130px column fitted four cards,
   not the three this comment used to claim. */
.subcat-grid { display: grid; gap: 12px; grid-template-columns: repeat(auto-fill, minmax(328px, 1fr)); }

/* Horizontal card with the media flush to three edges — the settled postcard
   frame at taxonomy scale. It fixes both halves of one problem: a 68px thumb
   inside a 425px card gave five-sixths of the width to four text rows, so the
   card read as oversized *and* the picture as an afterthought. Dropping the
   grid minimum 298 → 268 takes the row 2-up → 3-up, and the pane takes
   whatever height the detail column sets, so there is no inset moat and never
   a gap beneath the image regardless of how the name wraps. */
.subcat { padding: 0; overflow: hidden; display: grid; grid-template-columns: 116px minmax(0, 1fr); transition: transform .18s, box-shadow .18s, border-color .18s; }
.subcat:has(a.stretch:hover) { transform: translateY(-2px); box-shadow: var(--shadow-lift); border-color: var(--brand); }
.subcat:has(a.stretch:focus-visible) { border-color: var(--brand); box-shadow: var(--shadow-lift); }
.subcat:has(a.stretch:focus-visible) a.stretch::after { outline: 2px solid var(--brand); outline-offset: -3px; border-radius: var(--r-card); }

.sc-pane { position: relative; display: block; align-self: stretch; overflow: hidden; border-right: 1px solid var(--border); }
.sc-pane .plate { display: block; position: absolute; inset: 0; }
/* The movement chip rides the pane instead of the price row: at a 139px text
   column the row holds the price or the chip, not both. Corner-anchored with
   an even 8px inset, segments opaque so every audited contrast pair survives
   over arbitrary media, and a shadow to separate it from the picture. */
.sc-mv { position: absolute; left: 8px; bottom: 8px; z-index: 1; gap: 0; border-radius: var(--r-pill); overflow: hidden; border: 1px solid var(--border-soft); box-shadow: var(--shadow-lift); }
.sc-mv > span { height: 20px; font-size: 10.5px; }
.sc-mv .w { width: 22px; }
.sc-mv .d { padding: 0 6px; }
.sc-mv .a { width: 20px; }
.sc-mv .a svg { width: 11px; height: 11px; }

.sc-detail { display: flex; flex-direction: column; gap: 6px; padding: 10px 12px 11px; min-width: 0; }
.sc-name { font-family: var(--display); font-size: 14.5px; font-weight: 600; line-height: 1.24; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.sc-name a.stretch { color: var(--fg); }
.sc-sub { font-size: 11px; color: var(--muted); margin-top: 2px; font-variant-numeric: tabular-nums; }
.sc-sub b { color: var(--fg-2); font-weight: 600; }
/* the button carries both figure and unit, so the row has a single child and
   nothing left to wrap; baseline-aligned because the unit is text now, not a
   bordered chip needing optical centring */
.sc-price { display: flex; align-items: baseline; min-width: 0; }
.sc-price .v { font-family: var(--display); font-size: 19px; font-weight: 700; line-height: 1.05; letter-spacing: -0.01em; font-variant-numeric: tabular-nums; }
/* static unit text, for a card whose unit has no conversion ladder — the
   control is the default, this is the fallback */
.sc-price > .u { font-size: 11px; color: var(--muted); font-weight: 500; white-space: nowrap; margin-left: 4px; }
.sc-price .na { font-family: var(--body); font-size: 12.5px; font-weight: 500; color: var(--muted); }
/* the card-wide stretch link overlays the whole card at z-index 1, so the unit
   control has to sit above it or the card swallows every click on it */
.sc-price .pricecyc { position: relative; z-index: 2; }
/* The band sits at the foot of the detail column so every card's track lines
   up regardless of how its name wrapped. */
.sc-band { margin-top: auto; }
.sc-band .band-slot { height: 14px; }
.sc-band .band-caps { margin-top: 4px; font-size: 10.5px; gap: 8px; }
/* no observed spread yet — a band with nothing in it would read as a zero
   price rather than as missing data */
.sc-nob { margin-top: auto; font-size: 11px; line-height: 1.4; color: var(--muted); display: flex; align-items: flex-start; gap: 5px; }
.sc-nob svg { width: 12px; height: 12px; flex: none; margin-top: 2px; }
/* third movement state: production only encodes up and down, but an unchanged
   median needs its own read rather than a fake direction */
.mv[data-dir="flat"] .d { color: var(--fg-2); border-radius: 0 var(--r-pill) var(--r-pill) 0; }

/* --- .subcat-here: the you-are-here instance --------------------------
   The browse grid's page lead states exactly what a level-3 card states —
   name, supply, today's price, the observed spread, the day's move — so it is
   *the same card*, not a scale variant of it. The earlier .subcat-lead block
   (132px pane, 20px name, 23px price, a three-column detail row and visible
   Lowest/Highest keys) is deleted: two sizes of one component meant walking
   Cement & binders → Portland cement re-taught the same commodity in a new
   shape, which is what the two-page comparison exposed.

   All that survives is the selected state. This card is the one the reader
   clicked at level 3, so it takes a brand hairline and a wash seat, and its
   name is not a link — it points at the page it is already on. It never
   lifts on hover for the same reason. */
.subcat-here { border-color: var(--brand); background: var(--brand-wash); box-shadow: none; }
.subcat-here .sc-pane { border-right-color: var(--brand); }
.subcat-here .sc-name { color: var(--fg); }
/* The wash seat changes the pairing under the price control, so it is restated
   here rather than inherited. At rest the unit lifts to --fg-2, because --muted
   on --brand-wash is 4.20:1 in dark and would fail for 11px text. The hover
   plate becomes --surface, since a --brand-wash plate on a --brand-wash card
   would be invisible, and the unit goes to --fg there — --fg-2 on --surface is
   7.25:1, a drop from the 8.05:1 it has at rest on the wash. */
.subcat-here .pricecyc .u,
.subcat-here .pricecyc > svg { color: var(--fg-2); }
.subcat-here .pricecyc:hover,
.subcat-here .pricecyc:focus-visible { background: var(--surface); }
.subcat-here .pricecyc:hover .u,
.subcat-here .pricecyc:focus-visible .u { color: var(--fg); border-bottom-color: var(--brand); }
.subcat-here .pricecyc:hover > svg,
.subcat-here .pricecyc:focus-visible > svg { color: var(--fg); }

/* The price control is one nowrap atom now, so nothing needs relocating at
   this width — the widest rung (₦370,000 / pallet, ~149px) clears the ~200px
   detail column of a single-column card at 320px. */
@media (max-width: 400px) {
  /* clipped, not display:none — the key still names which end of the range
     each figure is when the column can no longer show the word */
  .sc-band .band-caps .k { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
}

@media (max-width: 1080px) {
  .filterbtn { display: inline-flex; }
}

/* Below 620px production swaps the card grid for SubSectionListItem rows.
   Same move here — a horizontal row keeps the peek chips, which a 160px
   two-up cell could not hold. */
@media (max-width: 620px) {
  .tax-grid, .subcat-grid { grid-template-columns: minmax(0, 1fr); gap: 10px; }
  .taxcard { flex-direction: row; align-items: stretch; }
  /* 124px, not 96: the count chip is the constraint on this card, and at 96px
     "6 subcategories" could not sit on one line at any legible size. The plate
     is inset 10px left, so 106px is free — enough for the chip once its icon
     drops (decorative) and the type eases to 10.5px. */
  .tax-media { aspect-ratio: 1 / 1; width: 124px; flex: none; border-bottom: 0; border-right: 1px solid var(--border); }
  .tax-media .plate-count { height: 21px; padding: 0 7px; font-size: 10.5px; gap: 0; }
  .tax-media .plate-count svg { display: none; }
  .tax-body { padding: 10px 12px; min-width: 0; }
  .tax-peek { padding-top: 6px; }
  /* one card per row means the detail column is wide and the text rows go
     short, so the pane would otherwise shrink with them */
  .subcat { grid-template-columns: 132px minmax(0, 1fr); }
  .xhead h1 { font-size: 20px; }
}

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

/* =====================================================================
   CREATE LISTING
   ---------------------------------------------------------------------
   Production is one long scrolling form whose later steps render only
   once the earlier ones are answered. The gating is correct — the
   attribute set is a function of the sub category and the district list
   a function of the state, so neither can be drawn early. What it lacks
   is any sense of length, because a hidden step is indistinguishable
   from a step that does not exist.

   So every step is always present, and an unmet one collapses to its
   numbered head with a lock. That is the whole progress indicator. An
   earlier build also carried a four-item ledger above the form, which
   announced "step 2 is next" directly above a card numbered 2, while the
   review rail said it a third time.

   Spacing is one scale: 20 between and inside cards, 14 between fields,
   10 and 6 within a field. No other value appears in this block.
   ===================================================================== */
.cp-grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 320px;
  gap: 20px;
  align-items: start;
}

/* --- step card ------------------------------------------------------- */
.cp-card { padding: 20px; margin-bottom: 20px; scroll-margin-top: 82px; }
.cp-card:last-child { margin-bottom: 0; }
.cp-head { display: flex; align-items: center; gap: 10px; margin-bottom: 20px; }
.cp-card[data-state="locked"] .cp-head { margin-bottom: 0; }
.cp-head h2 { font-family: var(--display); font-size: 17px; font-weight: 600; letter-spacing: -0.005em; }
/* Three-state ladder, loudest on the step you are actually in. --chip was
   the first attempt and the render killed it: in dark mode --chip is
   L 0.304 against a card at L 0.302, so the open step's disc vanished
   while the locked ones — on --surface-2 — were the only visible discs. */
.cp-num {
  width: 26px; height: 26px; flex: none; border-radius: 50%;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--brand-strong); color: var(--on-brand);
  font-family: var(--mono); font-size: 12.5px; font-weight: 600;
}
.cp-num svg { width: 14px; height: 14px; }
.cp-lockic { display: none; width: 15px; height: 15px; margin-left: auto; color: var(--muted); flex: none; }

/* A locked step keeps its head and drops its body, so the length of the
   form is legible before any of it is answered. No reason line: the lock
   and the muted head say it, and the review rail carries the wording. */
.cp-card[data-state="locked"] { background: var(--surface-3); box-shadow: none; }
.cp-card[data-state="locked"] .cp-head h2 { color: var(--muted); font-weight: 500; }
/* --fg-2, not --muted: --muted on --surface-2 is 3.78:1 in dark, and the
   digit inside this chip is 12.5px normal text. The chip reads quiet
   because the disc is grey rather than brand, not because the digit is. */
.cp-card[data-state="locked"] .cp-num { background: var(--surface-2); color: var(--fg-2); }
.cp-card[data-state="locked"] .cp-body { display: none; }
.cp-card[data-state="locked"] .cp-lockic { display: block; }
.cp-card[data-state="done"] .cp-num {
  background: color-mix(in oklab, var(--success) 20%, var(--surface));
  color: var(--success-text);
}

/* --- fields ---------------------------------------------------------- */
/* Two columns everywhere, one below 620px. There were three different
   field grids before, plus a nested two-column split inside step 3, so
   the same control changed width depending on which step it sat in. */
.cp-fields { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
.cp-fields .wide { grid-column: 1 / -1; }
/* the free-text field that "Others" opens, nested inside its own field */
.custom-wrap { margin-top: 6px; }
.field .req { color: var(--danger-text); font-weight: 700; margin-left: 1px; }
.control select { appearance: none; cursor: pointer; color: var(--fg); font: inherit; padding-right: 4px; }
.control select:disabled { cursor: not-allowed; color: var(--muted); }
.control:has(select:disabled) { background: var(--surface-3); opacity: .72; }
.control .suffix { font-size: 12.5px; color: var(--muted); flex: none; }
.control input[type="month"] { color-scheme: dark; }
[data-theme="light"] .control input[type="month"] { color-scheme: light; }

/* --- labelled sub-block inside a step -------------------------------- */
/* Replaces the tinted well that used to hold pricing. A well inside a
   card inside a sheet was three surface levels deep, and the fields in
   it were the only ones on the page sitting on --surface-3. */
.cp-part { margin-top: 20px; padding-top: 14px; border-top: 1px solid var(--border); }
.cp-part-h { display: flex; align-items: baseline; gap: 10px; margin-bottom: 10px; }
.cp-part-h .count { margin-left: auto; font-family: var(--mono); font-size: 11.5px; letter-spacing: 0; color: var(--muted); }

/* --- searchable select (combobox) ------------------------------------- */
.combo { position: relative; }
.combo-list {
  position: absolute; z-index: 30; top: calc(100% + 6px); left: 0; right: 0;
  max-height: 230px; overflow-y: auto; margin: 0; padding: 6px;
  list-style: none; background: var(--surface);
  border: 1px solid var(--border); border-radius: var(--r-input);
  box-shadow: var(--shadow-lift);
}
.combo-list li {
  padding: 10px; border-radius: 8px; font-size: 13.5px; color: var(--fg);
  cursor: pointer; min-height: 38px; display: flex; align-items: center;
}
.combo-list li:hover, .combo-list li.is-active { background: var(--brand-wash); color: var(--fg); }
.combo-list .combo-empty { color: var(--muted); cursor: default; font-size: 13px; }
.combo-list .combo-empty:hover { background: none; color: var(--muted); }

/* --- price ----------------------------------------------------------- */
.cp-price-naira { font-family: var(--display); font-size: 15px; color: var(--fg-2); flex: none; }
.cp-preview {
  margin-top: 14px;
  font-family: var(--display); font-size: 17px; font-weight: 600; color: var(--fg);
  font-variant-numeric: tabular-nums;
}
.cp-preview.is-empty { font-family: var(--body); font-size: 13px; font-weight: 400; color: var(--muted); }
.check {
  display: flex; align-items: center; gap: 10px; cursor: pointer;
  min-height: 36px; font-size: 13.5px; color: var(--fg);
}
.check input {
  appearance: none; width: 19px; height: 19px; flex: none; margin: 0; cursor: pointer;
  border: 1.5px solid var(--border); border-radius: 5px; background: var(--surface);
  display: grid; place-content: center; transition: background .14s, border-color .14s;
}
.check input::after {
  content: ""; width: 10px; height: 10px; transform: scale(0);
  background: var(--on-brand); transition: transform .12s;
  clip-path: polygon(14% 47%, 38% 71%, 86% 22%, 94% 32%, 39% 88%, 6% 55%);
}
.check input:checked { background: var(--brand-strong); border-color: var(--brand-strong); }
.check input:checked::after { transform: scale(1); }
.check:hover input { border-color: var(--brand); }
.check input:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

/* --- description + photos -------------------------------------------- */
.cp-ta {
  min-height: 150px; padding: 10px 14px; resize: vertical; line-height: 1.6;
  font: inherit; font-size: 13.5px; color: var(--fg);
  background: var(--surface-3); border: 1px solid var(--border); border-radius: var(--r-input);
  width: 100%;
}
.cp-ta::placeholder { color: var(--muted); }
.cp-ta:hover { border-color: var(--border-soft); background: var(--surface-2); }
.cp-ta:focus { outline: none; border-color: var(--brand); background: var(--surface); box-shadow: 0 0 0 3px var(--brand-wash); }

/* Production's upload target is a 48px dashed strip for a required field
   that accepts ten files — the smallest control on the page carrying the
   largest task. This is a real drop zone. */
.cp-drop {
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px;
  width: 100%; min-height: 104px; padding: 20px;
  background: var(--surface-3); color: var(--fg-2);
  border: 1.5px dashed color-mix(in oklab, var(--brand) 34%, var(--border));
  border-radius: var(--r-input); cursor: pointer;
  font: inherit; font-size: 13.5px; font-weight: 600;
  transition: background .16s, border-color .16s, color .16s;
}
.cp-drop svg { width: 22px; height: 22px; color: var(--chip-fg); }
.cp-drop small { font-size: 12px; font-weight: 400; color: var(--muted); }
.cp-drop:hover { background: var(--brand-wash); border-color: var(--brand); color: var(--fg); }
.cp-drop:hover small { color: var(--fg-2); }
.cp-drop:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.cp-thumbs { display: grid; grid-template-columns: repeat(auto-fill, minmax(78px, 1fr)); gap: 10px; margin-top: 14px; }
.cp-thumb {
  position: relative; aspect-ratio: 1; border-radius: 10px; overflow: hidden;
  border: 1px solid var(--border);
}
.cp-thumb .plate { position: absolute; inset: 0; border-radius: 0; }
.cp-cover {
  position: absolute; bottom: 6px; left: 6px; z-index: 2;
  font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: .06em;
  background: var(--brand-strong); color: var(--on-brand);
  padding: 2px 6px; border-radius: var(--r-pill);
}
.cp-rm {
  position: absolute; top: 6px; right: 6px; z-index: 3;
  width: 26px; height: 26px; border-radius: 50%; border: 0; cursor: pointer;
  display: grid; place-content: center;
  background: oklch(0.16 0.03 228 / .74); color: #fff;
  transition: background .14s;
}
.cp-rm svg { width: 13px; height: 13px; }
.cp-rm:hover { background: var(--danger); }
.cp-rm:focus-visible { outline: 2px solid #fff; outline-offset: -1px; }

/* --- review rail ----------------------------------------------------- */
.cp-review { position: sticky; top: calc(var(--head) + 20px); padding: 20px; }
.cp-rh { display: flex; align-items: center; gap: 10px; }
.cp-rh .mark {
  width: 26px; height: 26px; flex: none; border-radius: 50%;
  display: grid; place-content: center;
}
.cp-rh .mark svg { width: 15px; height: 15px; }
.cp-rh h2 { font-family: var(--display); font-size: 16px; font-weight: 600; }
.cp-review[data-ready="false"] .mark { background: color-mix(in oklab, var(--warning) 22%, var(--surface)); color: var(--warning-text); }
.cp-review[data-ready="true"] .mark { background: color-mix(in oklab, var(--success) 20%, var(--surface)); color: var(--success-text); }

/* The generated title. Named as generated because it is: no field
   produces it and no field can override it. */
.cp-title {
  margin-top: 20px; padding: 14px; border-radius: var(--r-input);
  background: var(--brand-wash); border: 1px solid color-mix(in oklab, var(--brand) 26%, var(--border));
}
.cp-title .k { display: block; margin-bottom: 6px; }
#cp-title-out {
  font-family: var(--display); font-size: 15px; font-weight: 600;
  line-height: 1.32; color: var(--fg); overflow-wrap: anywhere;
}
#cp-title-out.is-empty { font-family: var(--body); font-size: 12.5px; font-weight: 400; color: var(--fg-2); }

/* Production reports the first unmet gate only, inside one toast that
   concatenates every error with commas. Each requirement is its own row
   here, and each row jumps to the step that owns it. */
.cp-gaps { list-style: none; margin: 20px 0 0; display: flex; flex-direction: column; gap: 6px; }
.cp-gaps li {
  display: flex; align-items: flex-start; gap: 10px;
  font-size: 12.5px; line-height: 1.45; color: var(--fg-2);
}
.cp-gaps li svg { width: 14px; height: 14px; flex: none; margin-top: 1px; color: var(--warning-text); }
.cp-gaps li a { color: var(--fg-2); text-decoration: none; }
.cp-gaps li a:hover { color: var(--fg); text-decoration: underline; text-underline-offset: 3px; }
.cp-gaps li.ok { color: var(--success-text); font-weight: 600; }
.cp-gaps li.ok svg { color: var(--success-text); }
.cp-review .btn { margin-top: 20px; }

/* One column below 1240px: a 320px rail plus a 700px form is the point
   at which the field grid would drop to one field per row anyway. */
@media (max-width: 1240px) {
  .cp-grid { grid-template-columns: minmax(0, 1fr); }
  .cp-review { position: static; }
}
@media (max-width: 620px) {
  .cp-card { padding: 14px; }
  .cp-fields { grid-template-columns: minmax(0, 1fr); }
}

/* =====================================================================
   INBOX — messages and notifications
   ---------------------------------------------------------------------
   Production ships these as two routes, /messages/$chatId and
   /notifications, but the mobile bottom bar already merges them into one
   badged Inbox slot. Two panels of one screen is what that shell implies,
   and it also removes the duplicate list chrome the two routes each build
   separately.
   ===================================================================== */
.inbox-tabs { margin-bottom: 0; }
.inbox-tabs .tab { display: inline-flex; align-items: center; gap: 8px; }
.inbox-tabs .n {
  min-width: 19px; height: 19px; padding: 0 5px; border-radius: var(--r-pill);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--danger); color: var(--on-danger);
  font-family: var(--mono); font-size: 10.5px; font-weight: 600;
  font-variant-numeric: tabular-nums;
}

/* --- messages: two panes --------------------------------------------- */
.msg-shell {
  display: grid; grid-template-columns: 316px minmax(0, 1fr);
  /* Reserve = sheet top padding (14) + the tab row (46) + clearance below.
     It was 190 while the screen also carried a breadcrumb and a title band;
     both are gone, so holding 190 would have left the panel ~100px short of
     the viewport for no reason. */
  height: min(680px, calc(100vh - var(--head) - 90px));
  min-height: 470px;
  border: 1px solid var(--border); border-top: 0;
  border-radius: 0 0 var(--r-card) var(--r-card);
  background: var(--surface); overflow: hidden;
}
.cv-pane { display: flex; flex-direction: column; min-width: 0; border-right: 1px solid var(--border); }
.cv-head { padding: 14px 14px 11px; border-bottom: 1px solid var(--border); flex: none; }
.cv-title { display: flex; align-items: center; gap: 9px; margin-bottom: 11px; }
.cv-title h2 { font-family: var(--display); font-size: 15.5px; font-weight: 600; }
.cv-title .n {
  min-width: 20px; height: 20px; padding: 0 6px; border-radius: var(--r-pill);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--brand-strong); color: var(--on-brand);
  font-family: var(--mono); font-size: 11px; font-weight: 600;
}
.cv-head .control { min-height: 38px; padding: 0 12px; }
.cv-head .control input { font-size: 13px; }
.cv-list { flex: 1; overflow-y: auto; padding: 7px; display: flex; flex-direction: column; gap: 2px; }
.cv-item {
  display: flex; align-items: flex-start; gap: 11px; width: 100%; text-align: left;
  padding: 10px; border: 0; border-radius: 10px; cursor: pointer;
  background: none; color: inherit; font: inherit; min-height: 62px;
  transition: background .14s;
}
.cv-item:hover { background: var(--surface-2); }
/* --muted computes to 3.78:1 on dark --surface-2 — a hover must not lower
   contrast, so the preview line lifts to --fg-2 instead of holding --muted */
.cv-item:hover .cv-last, .cv-item:hover .cv-when { color: var(--fg-2); }
.cv-item:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.cv-item[aria-current="true"] { background: var(--brand-strong); }
.cv-item[aria-current="true"] .cv-name,
.cv-item[aria-current="true"] .cv-when,
.cv-item[aria-current="true"] .cv-last { color: var(--on-brand); }
.cv-item[aria-current="true"]:hover { background: var(--brand-hover); }
.cv-item[aria-current="true"] .avatar { border-color: color-mix(in oklab, #fff 40%, transparent); }
.cv-ava { position: relative; flex: none; }
.cv-unread {
  position: absolute; top: -4px; right: -5px; z-index: 2;
  min-width: 19px; height: 19px; padding: 0 5px; border-radius: var(--r-pill);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--danger); color: var(--on-danger);
  border: 2px solid var(--surface);
  font-family: var(--mono); font-size: 10px; font-weight: 600;
}
.cv-body { min-width: 0; flex: 1; }
.cv-top { display: flex; align-items: baseline; gap: 9px; }
.cv-name {
  font-size: 13.5px; font-weight: 600; color: var(--fg);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; min-width: 0;
}
.cv-when { font-size: 11.5px; color: var(--muted); flex: none; }
.cv-last {
  font-size: 12px; color: var(--muted); line-height: 1.45; margin-top: 2px;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.cv-item[data-unreadrow="true"] .cv-name { color: var(--fg); }
.cv-item[data-unreadrow="true"] .cv-last { color: var(--fg-2); font-weight: 500; }

/* --- thread ---------------------------------------------------------- */
.th-pane { display: flex; flex-direction: column; min-width: 0; background: var(--bg); }
.th-head {
  display: flex; align-items: center; gap: 11px; flex: none;
  padding: 11px 14px; border-bottom: 1px solid var(--border); background: var(--surface);
}
.th-back { display: none; }
.th-who { min-width: 0; flex: 1; }
.th-who .n { font-size: 14px; font-weight: 600; color: var(--fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.th-who .s { font-size: 11.5px; color: var(--muted); margin-top: 1px; }
.msg-list { flex: 1; overflow-y: auto; padding: 16px 14px; display: flex; flex-direction: column; gap: 9px; }
.msg-day {
  align-self: center; font-size: 10.5px; font-weight: 700; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted);
  background: var(--surface); border: 1px solid var(--border);
  padding: 3px 10px; border-radius: var(--r-pill); margin: 4px 0;
}
.msg { display: flex; }
.msg-out { justify-content: flex-end; }
.msg .bubble {
  max-width: 78%; padding: 9px 12px 7px; border-radius: 14px;
  box-shadow: var(--shadow-card);
}
.msg-in .bubble { background: var(--surface); border: 1px solid var(--border); border-bottom-left-radius: 5px; }
.msg-out .bubble { background: var(--brand-strong); border-bottom-right-radius: 5px; }
.msg .bubble p { font-size: 13px; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
.msg-in .bubble p { color: var(--fg); }
.msg-out .bubble p { color: var(--on-brand); }
.bfoot { display: flex; align-items: center; justify-content: flex-end; gap: 7px; margin-top: 4px; }
.bfoot .time { font-family: var(--mono); font-size: 10px; }
.msg-in .bfoot .time { color: var(--muted); }
.msg-out .bfoot .time { color: color-mix(in oklab, var(--on-brand) 82%, transparent); }
.tick { display: inline-flex; align-items: center; color: var(--on-brand); }
.tick svg { width: 15px; height: 15px; }
.tick[data-status="read"] svg { color: var(--on-brand); }
.spin {
  width: 11px; height: 11px; border-radius: 50%;
  border: 1.6px solid color-mix(in oklab, var(--on-brand) 55%, transparent);
  border-top-color: var(--on-brand);
  animation: spin .7s linear infinite; display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* A quoted listing inside a bubble: production has no such component, but
   every one of these threads starts from a post, and a thread with no
   subject is the reason a merchant has to ask "which order?" */
.msg-quote {
  display: flex; align-items: center; gap: 10px; margin-bottom: 8px;
  padding: 7px 9px 7px 7px; border-radius: 10px;
  background: var(--surface-3); border: 1px solid var(--border);
  text-decoration: none;
}
.msg-out .msg-quote { background: oklch(0.16 0.03 228 / .3); border-color: color-mix(in oklab, #fff 22%, transparent); }
.msg-quote .plate { width: 46px; height: 46px; border-radius: 8px; flex: none; }
.msg-quote .qt { min-width: 0; }
.msg-quote .qn { font-size: 11.5px; font-weight: 600; color: var(--fg); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.msg-quote .qp { font-family: var(--display); font-size: 12px; color: var(--fg-2); margin-top: 1px; }
.msg-out .msg-quote .qn, .msg-out .msg-quote .qp { color: var(--on-brand); }
.msg-quote:hover { border-color: var(--brand); }

.msg-form { flex: none; display: flex; align-items: flex-end; gap: 8px; padding: 11px 14px; border-top: 1px solid var(--border); background: var(--surface); }
.msg-form .control { flex: 1; min-height: 42px; }
.msg-form .control input { font-size: 13.5px; }
.emoji-wrap { position: relative; flex: none; }
.emoji-pop {
  position: absolute; bottom: calc(100% + 8px); right: 0; z-index: 40;
  width: 186px; padding: 7px; border-radius: var(--r-input);
  background: var(--surface); border: 1px solid var(--border); box-shadow: var(--shadow-lift);
  display: grid; grid-template-columns: repeat(5, 1fr); gap: 3px;
}
.emoji-pop button {
  height: 32px; border: 0; border-radius: 7px; background: none; cursor: pointer;
  font-size: 17px; line-height: 1;
}
.emoji-pop button:hover { background: var(--brand-wash); }
.emoji-pop button:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.iconbtn-quiet {
  width: 42px; height: 42px; flex: none; border-radius: var(--r-input);
  border: 1px solid transparent; background: none; color: var(--muted);
  display: grid; place-content: center; cursor: pointer; transition: background .14s, color .14s;
}
.iconbtn-quiet svg { width: 20px; height: 20px; }
.iconbtn-quiet:hover { background: var(--surface-2); color: var(--fg); }
.iconbtn-quiet:focus-visible { outline: 2px solid var(--brand); outline-offset: 1px; }
.iconbtn-quiet[aria-expanded="true"] { background: var(--brand-wash); color: var(--fg); }
.msg-send {
  width: 42px; height: 42px; flex: none; border-radius: 50%; border: 0; cursor: pointer;
  background: var(--brand-strong); color: var(--on-brand);
  display: grid; place-content: center; transition: background .14s;
}
.msg-send svg { width: 18px; height: 18px; }
.msg-send:hover:not(:disabled) { background: var(--brand-hover); color: var(--on-brand); }
.msg-send:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }
.msg-send:disabled { background: var(--surface-2); color: var(--muted); cursor: not-allowed; }

.th-empty {
  flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 10px; padding: 40px 24px; text-align: center;
}
.th-empty .ic {
  width: 52px; height: 52px; border-radius: 16px;
  background: var(--chip); color: var(--chip-fg);
  display: grid; place-items: center;
}
.th-empty .ic svg { width: 24px; height: 24px; }
.th-empty h3 { font-family: var(--display); font-size: 16px; font-weight: 600; }
.th-empty p { font-size: 13px; color: var(--muted); max-width: 34ch; line-height: 1.55; }

/* --- notifications --------------------------------------------------- */
.nt-shell {
  border: 1px solid var(--border); border-top: 0;
  border-radius: 0 0 var(--r-card) var(--r-card);
  background: var(--surface); overflow: hidden;
}
.nt-head {
  display: flex; align-items: center; gap: 11px; flex-wrap: wrap;
  padding: 13px 16px; border-bottom: 1px solid var(--border);
}
.nt-head h2 { font-family: var(--display); font-size: 15.5px; font-weight: 600; }
.nt-head .spacer { flex: 1 1 auto; }
.nt-head .n {
  min-width: 20px; height: 20px; padding: 0 6px; border-radius: var(--r-pill);
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--brand-strong); color: var(--on-brand);
  font-family: var(--mono); font-size: 11px; font-weight: 600;
}
.nt-list { display: flex; flex-direction: column; }
.nt-day {
  padding: 9px 16px; background: var(--surface-3);
  border-bottom: 1px solid var(--border);
  font-size: 10.5px; font-weight: 700; letter-spacing: .07em; text-transform: uppercase;
  color: var(--muted);
}
.nt-item {
  display: flex; align-items: flex-start; gap: 12px; width: 100%; text-align: left;
  padding: 13px 16px; border: 0; border-bottom: 1px solid var(--border);
  background: none; color: inherit; font: inherit; cursor: pointer;
  transition: background .14s;
}
.nt-item:hover { background: var(--surface-2); }
.nt-item:focus-visible { outline: 2px solid var(--brand); outline-offset: -2px; }
.nt-item[data-unread="true"] { background: var(--brand-wash); }
/* same arithmetic as the tree switcher's count: --muted on dark --brand-wash
   is 4.20:1, so the timestamp on an unread row takes --fg-2 (8.05:1) */
.nt-item[data-unread="true"] .nt-when { color: var(--fg-2); }
.nt-item[data-unread="true"]:hover { background: color-mix(in oklab, var(--brand) 16%, var(--surface)); }
.nt-ava { position: relative; flex: none; }
.nt-kind {
  position: absolute; right: -3px; bottom: -3px; z-index: 2;
  width: 20px; height: 20px; border-radius: 50%;
  display: grid; place-content: center;
  background: var(--surface); border: 1px solid var(--border);
}
.nt-kind svg { width: 12px; height: 12px; }
.nt-kind[data-kind="comment"] svg { color: var(--danger-text); }
.nt-kind[data-kind="message"] svg { color: var(--chip-fg); }
.nt-kind[data-kind="follower"] svg, .nt-kind[data-kind="endorser"] svg { color: var(--success-text); }
.nt-kind[data-kind="saved_post"] svg { color: var(--warning-text); }
.nt-kind[data-kind="verification"] svg { color: var(--chip-fg); }
.nt-kind[data-kind="subscription_expired"] svg { color: var(--warning-text); }
.nt-kind[data-kind="default"] svg { color: var(--muted); }
.nt-body { min-width: 0; flex: 1; }
.nt-top { display: flex; align-items: baseline; gap: 12px; }
.nt-t { font-size: 13.5px; font-weight: 600; color: var(--fg); flex: 1; min-width: 0; }
.nt-when { font-size: 11.5px; color: var(--muted); flex: none; white-space: nowrap; }
.nt-c { font-size: 12.5px; color: var(--fg-2); line-height: 1.5; margin-top: 3px; }
.nt-new {
  display: inline-block; margin-top: 6px;
  font-size: 10px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
  color: var(--chip-fg);
}
.nt-foot { padding: 15px 16px; text-align: center; font-size: 12.5px; color: var(--muted); }

/* Below 900px the two panes cannot coexist: at 316px + 320px the bubbles
   have ~200px of usable width. The list becomes the view and the thread
   replaces it, which is production's own /messages/$chatId behaviour. */
@media (max-width: 900px) {
  .msg-shell { grid-template-columns: minmax(0, 1fr); height: auto; min-height: 0; }
  .cv-pane { border-right: 0; }
  .msg-shell[data-view="list"] .th-pane { display: none; }
  .msg-shell[data-view="list"] .cv-list { max-height: none; }
  .msg-shell[data-view="thread"] .cv-pane { display: none; }
  .msg-shell[data-view="thread"] .th-pane { min-height: 60vh; }
  .th-back { display: grid; }
  .msg-list { max-height: 54vh; }
  .msg .bubble { max-width: 86%; }
}
@media (max-width: 560px) {
  .nt-item, .nt-head { padding-left: 13px; padding-right: 13px; }
  .nt-top { flex-direction: column; align-items: flex-start; gap: 2px; }
  .nt-when { order: -1; }
  .msg-form { padding: 10px 11px; gap: 6px; }
}

/* =====================================================================
   MOBILE SHELL — everything below the 1081px rail breakpoint
   ---------------------------------------------------------------------
   The rail hides at 1080px and, until now, nothing took its place: below
   the breakpoint the app had no navigation at all. This section supplies
   the replacement, following production's three-part mobile pattern
   (MobileHeader / BottomNavigation / hamburger drawer):

   1. the header compacts to logo + search + theme + menu;
   2. a fixed bottom bar carries the five primary destinations, including
      the create action — which is why .head-cta hides here rather than
      shipping two primary Post buttons in one viewport;
   3. the hamburger opens a drawer holding everything the rail carried
      that the five-slot bar cannot: Sections, Notifications, Settings,
      Log out, Premium.

   "No taller than necessary" is a hard rule for both new surfaces. The
   drawer is capped by its own content, like the rail (top: --head, no
   bottom anchor, closed bottom edge) rather than stretched to the
   viewport, and the bar's item row is capped at 460px and centred so a
   1024px tablet does not spread five icons across the full width.
   ===================================================================== */

:root { --botbar: 58px; }

/* the hamburger does not exist on desktop, where the rail *is* the nav */
.navtoggle { display: none; }
.navtoggle [data-ic="close"] { display: none; }
.navtoggle[aria-expanded="true"] [data-ic="menu"] { display: none; }
.navtoggle[aria-expanded="true"] [data-ic="close"] { display: block; }

/* ------------------------------------------------------------- drawer */
.drawer { position: fixed; inset: 0; z-index: 90; display: none; }
.drawer[data-open="true"] { display: block; }
/* the scrim starts below the header so the bar — and the hamburger's
   menu→close flip — stay visible and operable while the drawer is open */
.drawer-scrim {
  position: absolute; left: 0; right: 0; bottom: 0; top: var(--head);
  background: oklch(0.15 0.03 228 / 0.62);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
}
.drawer-panel {
  position: absolute; top: var(--head); right: 0;
  width: min(336px, 88vw);
  max-height: calc(100vh - var(--head) - 14px);
  display: flex; flex-direction: column;
  padding: 12px 12px 16px;
  background: var(--surface);
  border-left: 1px solid var(--border);
  border-bottom: 1px solid var(--border);
  border-bottom-left-radius: 18px;
  box-shadow: var(--shadow-lift);
  overflow-y: auto;
  animation: drawer-in .2s ease;
}
[data-theme="dark"] .drawer-panel { background: oklch(0.268 0.052 231); }
@keyframes drawer-in { from { transform: translateX(14px); opacity: .55 } to { transform: none; opacity: 1 } }

.drawer-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 10px; padding: 2px 4px 10px;
}
.drawer-x {
  display: grid; place-items: center; width: 32px; height: 32px; flex: none;
  border-radius: 50%; background: var(--surface-3); border: 1px solid var(--border);
  color: var(--fg-2); cursor: pointer;
}
.drawer-x svg { width: 15px; height: 15px; }
.drawer-x:hover { background: var(--surface-2); color: var(--fg); }
.drawer-foot { margin-top: 16px; }

/* Theme row. Below 1081px the header keeps only the logo, the search field and
   the hamburger, so the theme control lives here instead. It is a nav row, not
   a bare icon: the visible text is the accessible name and states the
   destination ("Light mode" while dark is active), so no aria-label is set on
   it — an aria-label would silently replace the words a voice-control user
   reads aloud. .theme-name is rewritten by apply() alongside the icon flip. */
.dw-theme {
  font-family: inherit; text-align: left;
}
.dw-theme .chip { position: relative; }
.dw-theme .chip svg { position: absolute; inset: 0; margin: auto; transition: opacity .2s, transform .2s; }
.dw-theme [data-ic="sun"]  { opacity: 0; transform: rotate(-45deg) scale(.62); }
.dw-theme [data-ic="moon"] { opacity: 0; transform: rotate(45deg) scale(.62); }
[data-theme="dark"]  .dw-theme [data-ic="sun"],
[data-theme="light"] .dw-theme [data-ic="moon"] { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .dw-theme .chip svg { transition: none; }
  .dw-theme [data-ic="sun"], .dw-theme [data-ic="moon"] { transform: none; }
}

/* --------------------------------------------------------- bottom bar */
.botbar { display: none; }
.botbar-row {
  display: flex; align-items: stretch; height: var(--botbar);
  max-width: 460px; margin: 0 auto;
}
.bb-item {
  flex: 1; min-width: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 3px; padding: 6px 2px 5px;
  background: none; border: 0; border-radius: 0; cursor: pointer;
  position: relative;
  color: var(--muted); font-size: 10.5px; font-weight: 600; letter-spacing: 0.005em;
  text-decoration: none; line-height: 1;
  transition: color .16s, background .16s;
}
.bb-item svg { width: 21px; height: 21px; }
.bb-item .lbl { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* hover pairs the lift in foreground with a lift in background: --muted on
   --surface-2 is only 3.78:1, so the label moves to --fg-2 (7.25:1 dark /
   8.04:1 light) rather than staying put and losing contrast */
.bb-item:hover { color: var(--fg-2); background: var(--surface-2); }
.bb-item[aria-current="page"] { color: var(--fg); }
.bb-item[aria-current="page"]::before {
  content: ""; position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: 26px; height: 2px; border-radius: 0 0 3px 3px;
  background: var(--brand-strong);
}
[data-theme="dark"] .bb-item[aria-current="page"]::before { background: var(--accent); }
.bb-item:focus-visible { outline: 2px solid var(--brand); outline-offset: -3px; }

/* Create. The bottom bar is the only Post entry below 1081px, so this is
   the one filled shape in the bar. --brand-strong measures 3.40:1 against
   the dark sheet and 5.02:1 against the light one, clearing the 3:1 floor
   for a non-text fill, and white ink on it is 5.02:1 in both modes.
   --header-cta was rejected here: it is 2.57:1 on a white sheet. */
.bb-post .bb-ico {
  width: 40px; height: 30px; border-radius: var(--r-pill);
  background: var(--brand-strong); color: var(--on-brand);
  display: grid; place-items: center;
}
.bb-post .bb-ico svg { width: 19px; height: 19px; }
.bb-post { color: var(--fg-2); }
.bb-post:hover .bb-ico { background: var(--brand-hover); }
[data-theme="dark"] .bb-post:hover .bb-ico { background: var(--brand); }

/* merged notification + message count, as production does on this slot */
.bb-badge {
  position: absolute; top: 5px; left: 50%; margin-left: 3px;
  min-width: 17px; height: 17px; padding: 0 4px;
  display: grid; place-items: center;
  border-radius: var(--r-pill);
  background: var(--danger); color: var(--on-danger);
  font-size: 10px; font-weight: 700; font-variant-numeric: tabular-nums;
  line-height: 1;
}

@media (max-width: 1080px) {
  /* Header compacts. Messages, notifications, the avatar and the create
     action all have a thumb-reachable home in the bar or the drawer;
     leaving them here is what overflowed the bar at 360px. */
  .header { gap: 12px; padding: 0 14px; }
  .head-cta, .header .hide-mob { display: none; }
  .searchbar { max-width: none; }
  .navtoggle { display: grid; }

  .botbar {
    display: block;
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 70;
    background: var(--sheet);
    border-top: 1px solid var(--border);
    box-shadow: 0 -6px 22px -14px oklch(0.15 0.03 230 / 0.55);
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }
  /* content clears the fixed bar; env() keeps it off the iOS home indicator */
  .sheet { padding-bottom: calc(var(--botbar) + env(safe-area-inset-bottom, 0px) + 30px); }
  /* the toast would otherwise land on top of the bar it sits above in z-order */
  .toast { bottom: calc(var(--botbar) + env(safe-area-inset-bottom, 0px) + 16px); }
  /* the sheet's min-height already reserves a screen; the bar sits over it */
  .app { padding-bottom: 0; }
}

/* Phone. The drawer goes full-bleed — a 336px panel beside 24px of scrim
   is neither a panel nor a page — and the header drops to the logo mark. */
@media (max-width: 560px) {
  .drawer-panel {
    width: 100%; max-height: calc(100vh - var(--head));
    border-left: 0; border-bottom: 0; border-bottom-left-radius: 0;
    padding: 12px 14px 22px;
  }
  .header { gap: 10px; padding: 0 12px; }
  .logo-word { display: none; }
  .searchbar { height: 38px; padding: 0 12px; gap: 8px; }
  .searchbar kbd { display: none; }
  .searchbar input { font-size: 13.5px; }
  .bb-item { font-size: 10px; gap: 2px; }
}

/* 320px is the narrowest viewport worth supporting. At that width the header's
   fixed furniture is 24 padding + 32 logo mark + 20 (two 10px header gaps) +
   40 theme + 8 + 40 menu = 164px, leaving 156px of search field against a
   49px minimum, so the bar provably cannot overflow. */
@media (max-width: 380px) {
  .searchbar input::placeholder { font-size: 13px; }
  .bb-item svg { width: 20px; height: 20px; }
  .bb-post .bb-ico { width: 36px; }
}
