/* ═══════════════════════════════════════════════════════════════
   Canonical Design System — L3 component styles
   Consumed ONLY via the Jinja macros in templates/components/ui/;
   pages never use .ds-* classes directly. Every value here is a
   theme.css token or a recipe from the design reference — no new
   colors, radii, or shadows may be invented in this file.
   See docs/DESIGN_SYSTEM.md.
   ═══════════════════════════════════════════════════════════════ */

/* ── Typography ─────────────────────────────────────────────── */

.ds-display {
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: clamp(38px, 5.2vw, 52px);
  line-height: 1.03;
  letter-spacing: -0.02em;
  color: var(--foreground);
  margin: 0;
}

/* Display scale steps from the design, in size order:
   md 44 · md-plus 48 · lg 52 (default) · xl 64 · xl-plus 68 · 2xl 72.

   The two `-plus` steps are half-steps the design really uses (data-delivery's
   section heads at 48, pricing's page title at 68). They were page-local
   overrides of `--lg` and `--xl` until they were promoted here: a page that
   overrides a display variant silently opts out of every later retune of the
   scale, which is the single thing the scale exists to prevent. They are named
   as half-steps rather than taking a t-shirt name so the ladder still reads in
   size order — `--md-plus` sits just above `--md`, not below it. */
.ds-display--md { font-size: clamp(32px, 4.4vw, 44px); }
.ds-display--md-plus { font-size: clamp(34px, 4.8vw, 48px); line-height: 1.04; }
.ds-display--lg { font-size: clamp(38px, 5.2vw, 52px); }
.ds-display--xl { font-size: clamp(42px, 6.4vw, 64px); line-height: 1.02; letter-spacing: -0.025em; }
.ds-display--xl-plus { font-size: clamp(42px, 6.8vw, 68px); line-height: 1; letter-spacing: -0.025em; }
.ds-display--2xl { font-size: clamp(44px, 7.2vw, 72px); line-height: 0.98; letter-spacing: -0.025em; }

.ds-h2 {
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: clamp(24px, 2.6vw, 28px);
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--foreground);
  margin: 0;
}

.ds-h3 {
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 19px;
  line-height: 1.3;
  letter-spacing: -0.01em;
  color: var(--foreground);
  margin: 0;
}

.ds-body {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 16px;
  line-height: 1.5;
  letter-spacing: -0.01em;
  color: var(--body-foreground);
  margin: 0;
}

.ds-body--lg { font-size: 17px; }

.ds-caption {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 12px;
  line-height: 1.45;
  color: var(--muted-foreground);
  margin: 0;
}

.ds-overline {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted-foreground);
  margin: 0;
}

/* ── Button ─────────────────────────────────────────────────── */

.ds-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 7px;
  height: 36px;
  padding: 0 16px;
  border: 0;
  border-radius: var(--radius-full);
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 600;
  letter-spacing: -0.01em;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: filter 0.15s ease, background-color 0.15s ease, color 0.15s ease;
}

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

.ds-btn--primary {
  background: var(--primary);
  background-image: linear-gradient(var(--primary-face-top), var(--primary-face-bottom));
  box-shadow: var(--shadow-raised-dark);
  color: var(--primary-foreground);
}

.ds-btn--primary:hover { color: var(--primary-foreground); filter: brightness(1.12); }

.ds-btn--secondary {
  background: var(--secondary);
  box-shadow: 0 0 0 1px var(--ring), var(--shadow-raised-light);
  color: var(--secondary-foreground);
}

.ds-btn--secondary:hover { color: var(--foreground); }

.ds-btn--ghost {
  background: transparent;
  color: var(--body-foreground);
}

.ds-btn--ghost:hover { background: var(--muted); color: var(--foreground); }

.ds-btn--link {
  background: transparent;
  padding: 0;
  height: auto;
  color: var(--accent);
}

.ds-btn--link:hover { text-decoration: underline; color: var(--accent); }

.ds-btn--destructive {
  background: var(--destructive);
  color: var(--destructive-foreground);
}

.ds-btn--destructive:hover { color: var(--destructive-foreground); filter: brightness(1.1); }

.ds-btn--sm { height: 28px; padding: 0 14px; font-size: 13.5px; }
.ds-btn--lg { height: 44px; padding: 0 22px; font-size: 15px; }
.ds-btn--icon { width: 36px; height: 36px; padding: 0; flex: none; }

/* Full-width button — a row of plan-card CTAs that must share a width. */
.ds-btn--block { width: 100%; }

/* Pending state, applied by static/js/form-loading.js on submit: the label is
   replaced with a spinner + "Working…" and the control disabled. Lived in
   app.css as .btn--loading until Phase 4; it is a state of THIS component. */
.ds-btn--loading { pointer-events: none; }
.ds-btn__spinner {
  display: inline-block;
  width: 12px;
  height: 12px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: var(--radius-full);
  animation: ds-btn-spin 0.7s linear infinite;
  flex-shrink: 0;
}
@keyframes ds-btn-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .ds-btn__spinner { animation: none; border-right-color: currentColor; opacity: 0.6; }
}

/* The button's `icon=` slot SIZES ITS OWN SVG. Without this the macro
   silently rendered invisible icons: an inline `<svg viewBox="…">` with no
   width/height attribute has no intrinsic size, so as a flex item in
   `.ds-btn` it resolved to a 0-width box. The icon was in the DOM, painted
   nothing, and nothing errored — which is why it survived review on
   /examples ("Run this search", "Export") and the funding record card.

   Sized HERE and not at the call site, deliberately. The alternative was to
   ask every caller to remember `width="16" height="16"`, which is the same
   contract `.ds-link-arrow svg` already declines to impose, and which two of
   the three callers had already got wrong. A caller CAN still pass explicit
   attributes (the hero submit does) — these rules just make it unnecessary,
   and make the icon scale with the button instead of staying 16px on a
   28px `--sm`. `flex: none` so a long label never squeezes the glyph. */
.ds-btn svg { width: 16px; height: 16px; flex: none; }
.ds-btn--sm svg { width: 14px; height: 14px; }
.ds-btn--lg svg { width: 17px; height: 17px; }

/* Rect shape — the design's content CTAs (navbar keeps pills). */
.ds-btn--rect { border-radius: var(--radius); padding: 0 20px; }

.ds-btn--rect.ds-btn--secondary {
  border: 1px solid var(--border);
  background: var(--background);
  background-image: linear-gradient(oklch(1 0 0), oklch(0.97 0.005 85));
  box-shadow: var(--shadow-raised-light);
  color: var(--foreground);
}

.ds-btn--rect.ds-btn--secondary:hover { border-color: var(--foreground); }

/* Blue text-link with trailing arrow (card footers in the design). */
.ds-link-arrow {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  font-family: var(--font-sans);
  font-size: 11.5px;
  color: var(--accent);
  text-decoration: none;
}

.ds-link-arrow svg { width: 13px; height: 13px; transition: transform 0.15s ease; }
.ds-link-arrow:hover { color: var(--accent); }
.ds-link-arrow:hover svg { transform: translateX(2px); }

.ds-btn[disabled], .ds-btn--disabled {
  opacity: 0.5;
  pointer-events: none;
}

/* ── Pill / badge ───────────────────────────────────────────── */

.ds-pill {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px;
  border-radius: var(--radius-full);
  font-family: var(--font-sans);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  white-space: nowrap;
}

.ds-pill--outline {
  border: 1px solid var(--border);
  color: var(--foreground);
}

.ds-pill--ink {
  background: var(--primary);
  color: var(--primary-foreground);
}

/* The .ds-tint-* classes below are shared by the example card (which paints
   its category as .ds-example-card__cat) and pill(tint=…) below, which pairs
   one of them with .ds-pill--tint to render a status badge. */

.ds-tint-orange { --_tint: var(--tint-orange); --_tint-ink: var(--tint-orange-ink); }
.ds-tint-green { --_tint: var(--tint-green); --_tint-ink: var(--tint-green-ink); }
.ds-tint-blue { --_tint: var(--tint-blue); --_tint-ink: var(--tint-blue-ink); }
.ds-tint-purple { --_tint: var(--tint-purple); --_tint-ink: var(--tint-purple-ink); }
.ds-tint-teal { --_tint: var(--tint-teal); --_tint-ink: var(--tint-teal-ink); }
.ds-tint-pink { --_tint: var(--tint-pink); --_tint-ink: var(--tint-pink-ink); }
.ds-tint-red { --_tint: var(--tint-red); --_tint-ink: var(--tint-red-ink); }

/* Tinted status badge (pill(tint=…)): the same --_tint / --_tint-ink pair the
   example card uses for its category, on a pill. */
.ds-pill--tint {
  border-color: transparent;
  background: var(--_tint);
  color: var(--_tint-ink);
}

/* Chip — hairline mini pill for feature/fact rows (12px, 6×13 padding). */
.ds-chip {
  display: inline-flex;
  align-items: center;
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  padding: 6px 13px;
  background: var(--background);
  font-family: var(--font-sans);
  font-size: 12px;
  color: var(--body-foreground);
  white-space: nowrap;
}

/* `--sm` is the dense variant for chip rows inside a card, where the default
   is too wide: the data-delivery destination cards give the row 246px at a
   1024px viewport and two default chips need 270, so they wrapped and pushed
   every card in the 3-up grid 42px taller. 11px is fine ABOVE the readability
   band — and the ≤768px floor raises it to 12px, by which point those cards
   are 1-up with 563px and cannot wrap.
   Declared AFTER .ds-chip: equal specificity, so source order decides. */
.ds-chip--sm { padding: 5px 11px; font-size: 11px; }

/* ── Row helpers — CTA rows and chip rows (shared page rhythm) ──
   Promoted from per-page copies (home/pricing/data-delivery each re-declared
   them and the margins had already drifted); one definition, page CSS may
   override spacing. */
.ds-ctas { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 36px; }

.ds-ctas--center { justify-content: center; margin-top: 34px; }

.ds-chips { display: flex; flex-wrap: wrap; gap: 10px; margin: 26px 0 0; padding: 0; list-style: none; }

.ds-chips--tight { gap: 8px; margin-top: 12px; }

/* Long fact chips wrap on phones instead of setting the page's min width. */
@media (max-width: 640px) {
  .ds-chip { white-space: normal; }
}

/* Soft pill — sentence-case white pill (hero badge style). */
.ds-pill--soft {
  height: 28px;
  padding: 0 14px;
  border: 1px solid var(--border-subtle);
  background: var(--card);
  box-shadow: 0 6px 16px -12px oklch(0.19 0.012 75 / 0.22);
  font-size: 13.5px;
  letter-spacing: -0.01em;
  text-transform: none;
  color: var(--body-foreground);
}

/* ── Brand lockup (chrome-less surfaces) ────────────────────────
   The mark + wordmark as a link home, for the surfaces with no navbar to
   carry the brand: auth pages, the error page, the OAuth interstitial.
   Same masked logo and same wordmark metrics as the navbar, so the brand
   object does not fork a second time (the pre-DS auth/error pages each had
   their own `sparkles_icon` treatment). */

.ds-brand {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  color: var(--foreground);
  text-decoration: none;
}

.ds-brand__mark {
  flex: none;
  display: inline-block;
  background: currentColor;
  -webkit-mask: url("/static/logo-mark.svg") center / contain no-repeat;
  mask: url("/static/logo-mark.svg") center / contain no-repeat;
}

.ds-brand__wordmark {
  font-family: var(--font-sans);
  font-weight: 600;
  letter-spacing: -0.02em;
}

.ds-brand--md .ds-brand__mark { width: 21px; height: 21px; }
.ds-brand--md .ds-brand__wordmark { font-size: 17px; }

.ds-brand--sm .ds-brand__mark { width: 19px; height: 19px; }
.ds-brand--sm .ds-brand__wordmark { font-size: 15px; }

/* ── Search trigger (opens a search palette) ────────────────── */

.ds-search-trigger {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  height: 32px;
  padding: 0 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--card);
  color: var(--muted-foreground);
  font-family: var(--font-sans);
  font-size: 12.5px;
  letter-spacing: -0.01em;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}

.ds-search-trigger:hover {
  border-color: var(--foreground);
  color: var(--foreground);
}

.ds-search-trigger__icon { width: 14px; height: 14px; flex: none; }

.ds-search-trigger__kbd {
  font-family: var(--font-mono);
  font-size: 10.5px;
  padding: 1px 5px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--muted);
  color: var(--muted-foreground);
}

/* Phones: the label and the hint have no room beside the chrome's other
   controls, so the trigger becomes an icon-only square. Kept a real 36px box
   here and grown to 44 by the pointer-keyed tap block below. */
@media (max-width: 640px) {
  .ds-search-trigger {
    width: 36px;
    height: 36px;
    padding: 0;
    justify-content: center;
  }
  .ds-search-trigger__label,
  .ds-search-trigger__kbd { display: none; }
}

/* ── Card ───────────────────────────────────────────────────── */

.ds-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--card);
  padding: 28px;
  box-shadow: var(--shadow-card);
}

.ds-card--tinted {
  background: var(--muted-deep);
  box-shadow: none;
}

.ds-card--flat { box-shadow: none; }

/* ── Field / input ──────────────────────────────────────────── */

.ds-field { display: block; }

.ds-field__label {
  display: block;
  font-family: var(--font-sans);
  font-size: 13px;
  font-weight: 600;
  color: var(--foreground);
  margin-bottom: 6px;
}

.ds-input {
  width: 100%;
  border: 1px solid var(--input);
  border-radius: var(--radius);
  background: var(--card);
  padding: 9px 14px;
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.5;
  letter-spacing: -0.01em;
  color: var(--foreground);
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

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

.ds-input:focus {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px oklch(0.55 0.12 255 / 0.18);
}

.ds-field--error .ds-input {
  border-color: var(--destructive);
}

.ds-field--error .ds-input:focus {
  box-shadow: 0 0 0 3px oklch(0.55 0.18 25 / 0.15);
}

.ds-field__helper {
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--muted-foreground);
  margin: 6px 0 0;
}

.ds-field--error .ds-field__helper { color: var(--destructive); }

/* Borderless input inside a shell card (hero search pattern). */
.ds-input--bare,
.ds-input--bare:focus {
  border: none;
  box-shadow: none;
  background: transparent;
  padding: 0 4px;
  font-size: 16px;
}

/* A bare textarea is a bare input that may grow. It keeps the 16px font
   (iOS zooms anything smaller on focus) and drops the row-height chrome. */
.ds-input--bare-area {
  min-height: 120px;
  resize: none;
  line-height: 1.55;
}

.ds-shell {
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-2xl);
  background: var(--card);
  box-shadow: 0 34px 80px -44px oklch(0.19 0.012 75 / 0.3);
  padding: 16px;
}

/* ── Choice rows (checkbox / toggle) ────────────────────────── */

.ds-choice {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--foreground);
  cursor: pointer;
}

/* ── Select (native, chevron affordance) ────────────────────── */

.ds-select {
  appearance: none;
  padding-right: 36px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23847d70' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'><path d='m6 9 6 6 6-6'/></svg>");
  background-repeat: no-repeat;
  background-position: right 12px center;
}

/* ── Table wrapper (wide tables scroll inside, never the page) ─ */

.ds-table-wrap {
  overflow-x: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--card);
}

/* Rule 6 caveat: an inner-scrolling table is NOT enough on phones — mobile
   Chrome counts the table's full box into the root scroll area and the whole
   page pans sideways. A wide table must also be given a phone layout that
   actually fits (compact cells + wrapped values — see pricing.css), and the
   page checked for docW == viewport at 375px. */

:where(.ds-table-wrap) .table { min-width: 100%; }

/* ── Skeleton / divider ─────────────────────────────────────── */

/* One loading look for the whole site. This was a pulse until the examples
   detail page shipped a second, shimmering copy — the shimmer won (it reads as
   "loading" rather than "disabled") and the page copy is gone. Callers set
   their own width/height; only the treatment lives here. */
.ds-skeleton {
  display: block;
  border-radius: var(--radius);
  background: linear-gradient(90deg, var(--muted) 25%, var(--muted-deep) 37%, var(--muted) 63%);
  background-size: 400% 100%;
  animation: ds-shimmer 1.4s ease-in-out infinite;
}

@keyframes ds-shimmer {
  0% { background-position: 100% 50%; }
  100% { background-position: 0 50%; }
}

/* Belongs with the component, not with one page that happened to remember it. */
@media (prefers-reduced-motion: reduce) {
  .ds-skeleton { animation: none; }
}

/* Busy overlay — a pending htmx region. Pair with htmx's own indicator class:
   <div class="ds-busy htmx-indicator" id="x-spinner"> inside a
   position:relative container; htmx flips opacity while a request is in
   flight. The 20px ring is the button spinner's bigger sibling. */
.ds-busy {
  position: absolute;
  inset: 0;
  z-index: 5;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: var(--radius-xl);
  background: color-mix(in oklab, var(--card) 75%, transparent);
  pointer-events: none;
}
.ds-spinner {
  display: inline-block;
  width: 20px;
  height: 20px;
  border: 2px solid currentColor;
  border-right-color: transparent;
  border-radius: var(--radius-full);
  color: var(--muted-foreground);
  animation: ds-btn-spin 0.7s linear infinite;
}
@media (prefers-reduced-motion: reduce) {
  .ds-spinner { animation: none; border-right-color: currentColor; opacity: 0.6; }
}

.ds-divider {
  border: 0;
  border-top: 1px solid var(--border);
  margin: 0;
}

/* ── Glass (navbar / floating chrome) ───────────────────────── */

.ds-glass {
  border-radius: var(--radius-full);
  background: var(--glass-bg);
  -webkit-backdrop-filter: blur(22px) saturate(1.6);
  backdrop-filter: blur(22px) saturate(1.6);
  border: 1px solid var(--glass-border);
  box-shadow: var(--glass-shadow);
}

/* ── Section rhythm (the design's page band) ────────────────── */

.ds-section {
  max-width: 1200px;
  margin: 0 auto;
  padding: 96px 32px;
}

.ds-section--centered { text-align: center; }

/* ── Editorial split: heading rail left, body column right ───
   The site's one long-form section layout.

   Promoted because TWO page stylesheets had it and had already drifted:
   `.dd__split` was `minmax(0, 0.62fr) minmax(0, 1fr)` with a
   `clamp(28px, 4vw, 56px)` gap collapsing at 960px, and `.dsx__split` was a
   bare `0.62fr 1fr` with a `28px 64px` gap collapsing at 860px. Two copies,
   two gaps, two breakpoints, and one of them written the way this repo's own
   comment says it must not be — grid items default to `min-width: auto`, so
   a bare `1fr` lets one nowrap string set the column's floor and push the
   document wider than the viewport.

   CORRECTION, because the first version of this comment said "four verbatim
   copies … three of the four written as bare 1fr": that was wrong.
   `.exd__split` and `.alt__split` did not pre-exist — they were created
   earlier in this same branch (7ece3d6d) and removed by the promotion a
   commit later, so counting them described this branch's own churn as
   pre-existing site drift. Two is still two too many; the inflation was not.

   Collapses at 1080px, not at either inherited breakpoint. Solve for the
   measure rather than picking a number: prose is `(box - 64) / 1.62`, so a
   1136px box gives 662px and the measure passes through 60ch at a box of
   about 1075px. */
.ds-split {
  display: grid;
  grid-template-columns: minmax(0, 0.62fr) minmax(0, 1fr);
  gap: 22px 64px;
  align-items: start;
}

@media (max-width: 1080px) {
  .ds-split {
    grid-template-columns: minmax(0, 1fr);
    gap: 20px;
  }
}

.ds-section-header__overline {
  font-size: 11px;
  letter-spacing: 0.14em;
}

.ds-section-header .ds-display { margin-top: 20px; }

.ds-section-header__intro {
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.65;
  color: var(--body-foreground);
  margin: 20px 0 0;
  max-width: 58ch;
  text-wrap: pretty;
}

.ds-section--centered .ds-section-header__intro { margin-left: auto; margin-right: auto; }

/* ── Stat (baseline number + label) ─────────────────────────── */

.ds-stat {
  display: flex;
  gap: 14px;
  align-items: baseline;
  font-family: var(--font-sans);
}

.ds-stat__value {
  font-weight: 600;
  font-size: 26px;
  color: var(--foreground);
}

.ds-stat__label {
  font-size: 14px;
  color: var(--body-foreground);
}

/* Stat tile — a bordered, tinted number box. The tall sibling of .ds-stat:
   overline label, big number, unit caption. Used by the /search overlays,
   which need a tone (a shortfall reads red, a rate limit amber) and ids on
   the number and unit so agent-chat.js can fill them. */
.ds-stat-tile {
  flex: 1 1 0;
  min-width: 0;
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--card);
  padding: 12px 20px;
  text-align: center;
  font-family: var(--font-sans);
}

.ds-stat-tile--danger { border-color: transparent; background: var(--tint-red); }
.ds-stat-tile--warning { border-color: transparent; background: var(--tint-orange); }

.ds-stat-tile__label {
  margin: 0 0 4px;
  font-size: 12px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--muted-foreground);
}

.ds-stat-tile__value {
  margin: 0;
  font-size: 22px;
  font-weight: 600;
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--foreground);
}

.ds-stat-tile__unit {
  margin: 4px 0 0;
  font-size: 12px;
  color: var(--muted-foreground);
}

/* Toned tiles paint their own ink so the label and unit keep AA on the tint. */
.ds-stat-tile--danger .ds-stat-tile__label,
.ds-stat-tile--danger .ds-stat-tile__unit,
.ds-stat-tile--danger .ds-stat-tile__value { color: var(--tint-red-ink); }
.ds-stat-tile--warning .ds-stat-tile__label,
.ds-stat-tile--warning .ds-stat-tile__unit,
.ds-stat-tile--warning .ds-stat-tile__value { color: var(--tint-orange-ink); }

/* Icon disc — the 56px tinted circle an empty/blocked state opens with. */
.ds-icon-disc {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 56px;
  height: 56px;
  border-radius: var(--radius-full);
  background: var(--muted-deep);
  color: var(--foreground);
}

.ds-icon-disc > svg { width: 24px; height: 24px; }
.ds-icon-disc--danger { background: var(--tint-red); color: var(--tint-red-ink); }
.ds-icon-disc--warning { background: var(--tint-orange); color: var(--tint-orange-ink); }

/* ── Avatar ─────────────────────────────────────────────────── */

.ds-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  border-radius: var(--radius-full);
  background: var(--card);
  border: 1px solid var(--border-subtle);
  overflow: hidden;
  font-family: var(--font-sans);
  font-weight: 600;
  color: var(--body-foreground);
}

.ds-avatar img { width: 100%; height: 100%; object-fit: cover; }

.ds-avatar--sm { width: 24px; height: 24px; font-size: 10px; }
.ds-avatar--md { width: 30px; height: 30px; font-size: 12px; }
.ds-avatar--lg { width: 40px; height: 40px; font-size: 15px; }

/* Logo tile — white rounded square with hairline ring (MCP/example icons). */
.ds-logo-tile {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  border-radius: 11px;
  background: var(--card);
  box-shadow: 0 0 0 1px oklch(0.92 0.008 85);
  overflow: hidden;
}

.ds-logo-tile img, .ds-logo-tile > span, .ds-logo-tile svg { width: 58%; height: 58%; object-fit: contain; display: block; }
.ds-logo-tile--sm { width: 34px; height: 34px; border-radius: 9px; }
.ds-logo-tile--md { width: 38px; height: 38px; }
.ds-logo-tile--lg { width: 44px; height: 44px; }

/* Numbered step badge (24px muted circle). */
.ds-num-badge {
  width: 24px;
  height: 24px;
  flex: none;
  border-radius: var(--radius-full);
  background: oklch(0.965 0.006 85);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-sans);
  font-size: 11.5px;
  color: var(--body-foreground);
}

/* ── MCP client picker ───────────────────────────────────────
   Promoted out of home.css (`.home-mcp*`), consumed via
   components/ui/mcp_picker.html by the home page and both datasets
   clusters. The raw `oklch()` literals the home version carried are tokens
   here — a rule-2 violation is worse in a shared component than in a page
   stylesheet, and the deltas are imperceptible: 0.915 -> --border (0.905),
   0.985 -> --muted (0.977, and "tinted panel" is what these cards are),
   0.28 -> --secondary-foreground (0.3, "text on light controls"). */
.ds-mcp {
  display: grid;
  grid-template-columns: minmax(0, 0.42fr) minmax(0, 0.42fr) minmax(0, 1fr);
  gap: 16px;
  margin-top: 44px;
  align-items: start;
}

/* The client grid spans the first two tracks and lays its own cards out in
   two columns. The home version achieved this by looping the client list
   twice in the template with an `index0 % 2 == col` filter — real logic in a
   template to produce what `grid-template-columns` states in one line, and
   it made the markup order non-sequential for a screen reader and for
   arrow-key tab order. */
.ds-mcp__clients {
  grid-column: 1 / 3;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  align-content: start;
}

.ds-mcp__client {
  display: flex;
  align-items: center;
  gap: 14px;
  padding: 14px 16px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--muted);
  cursor: pointer;
  text-align: left;
  transition: border-color 0.15s ease, background-color 0.15s ease;
}

.ds-mcp__client:hover { border-color: var(--foreground); }
.ds-mcp__client.is-active { background: var(--card); border-color: var(--foreground); }

.ds-mcp__client-name {
  font-family: var(--font-sans);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--foreground);
}

/* Grid item holding nowrap content (the server URL), so it needs an explicit
   min-width — grid items default to `min-width: auto`. The move dropped this
   (it was `.home-mcp__detail`); `.ds-mcp__url`'s own `min-width: 0` happens
   to hold the line today, but that is one rule away from not doing so. */
.ds-mcp__detail { min-width: 0; }

.ds-mcp__detail-head { display: flex; align-items: flex-start; gap: 14px; }

.ds-mcp__detail-name {
  font-family: var(--font-sans);
  font-size: 24px;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--foreground);
}

.ds-mcp__detail-note {
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--muted-foreground);
  margin-top: 4px;
}

.ds-mcp__steps { display: grid; gap: 14px; margin-top: 28px; }

.ds-mcp__step {
  display: flex;
  align-items: center;
  gap: 14px;
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.5;
  color: var(--secondary-foreground);
}

.ds-mcp__urlbar {
  margin-top: 26px;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--muted);
  padding: 0 8px 0 18px;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}

.ds-mcp__url {
  font-family: var(--font-mono);
  font-size: 13.5px;
  letter-spacing: -0.01em;
  color: var(--secondary-foreground);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  /* flex item: without this the nowrap URL sets the row's min width and
     forces horizontal page overflow on phones */
  min-width: 0;
}

.ds-mcp__copy {
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 600;
  height: 36px;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 0 14px;
  border: 0;
  border-radius: var(--radius-full);
  background: var(--card);
  box-shadow: 0 0 0 1px var(--ring);
  cursor: pointer;
  flex: none;
  color: var(--secondary-foreground);
  transition: box-shadow 0.15s ease;
}

.ds-mcp__copy:hover { box-shadow: 0 0 0 1px var(--foreground); }
.ds-mcp__copy.is-copied { background: var(--accent); color: var(--accent-foreground); box-shadow: none; }
.ds-mcp__copy svg { width: 13px; height: 13px; flex: none; }

/* The "works in anything" note under the picker. The six cards are examples,
   not the supported set, and the pre-DS page said so. */
.ds-mcp__any {
  grid-column: 1 / -1;
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--muted-foreground);
  margin: 14px 0 0;
}

/* Narrow-column form — the datasets pages put the picker inside the right
   half of a two-column split, where three tracks would give the two client columns ~144px each and cards
   of ~146px (measured on /funding-data/deal-sourcing at 1440). */
.ds-mcp--compact { grid-template-columns: minmax(0, 1fr); gap: 14px; margin-top: 26px; }
.ds-mcp--compact .ds-mcp__clients { grid-column: 1; }
.ds-mcp--compact .ds-mcp__detail-name { font-size: 20px; }
.ds-mcp--compact .ds-mcp__steps { margin-top: 22px; }

/* Two steps down, not one. At 900px the three tracks give the client cards
   ~190px each, which is where a two-line client name starts wrapping — so
   the cards go full width above the detail there, and only go single-file at
   640px. `minmax(0, 1fr)` and never a bare `1fr`: grid items default to
   `min-width: auto`, so the nowrap server URL would otherwise set the
   column's floor and push the whole page wider than the viewport. */
@media (max-width: 900px) {
  .ds-mcp { grid-template-columns: minmax(0, 1fr); }
  .ds-mcp__clients { grid-column: 1; }
}

@media (max-width: 640px) {
  .ds-mcp__clients { grid-template-columns: minmax(0, 1fr); }
  .ds-mcp__urlbar { height: auto; padding: 10px 10px 10px 14px; flex-wrap: wrap; }
  .ds-mcp__detail-name { font-size: 20px; }
}

.ds-avatar-group { display: inline-flex; align-items: center; }

.ds-avatar-group .ds-avatar {
  box-shadow: 0 0 0 2px var(--card);
}

.ds-avatar-group .ds-avatar + .ds-avatar { margin-left: -8px; }

.ds-avatar--more {
  background: var(--primary);
  border-color: var(--primary);
  color: var(--primary-foreground);
}

/* ── Navbar (fixed glass pill, from the design canvas) ──────── */

.ds-navbar {
  position: fixed;
  top: 16px;
  left: 0;
  right: 0;
  z-index: 50;
  padding: 0 24px;
}

.ds-navbar__inner {
  max-width: 1160px;
  margin: 0 auto;
  padding: 0 22px;
  height: 60px;
  display: flex;
  align-items: center;
  gap: clamp(10px, 1.6vw, 26px);
  /* No overflow:hidden here — the Datasets dropdown panel is absolutely
     positioned inside the pill and would be clipped. Narrow widths are
     handled by the clamped gap and the ≤860px link collapse instead. */
}

.ds-navbar__brand {
  display: flex;
  align-items: center;
  gap: 9px;
  margin-right: 8px;
  color: var(--foreground);
  text-decoration: none;
  flex: none;
}

.ds-navbar__mark,
.ds-footer__mark {
  width: 19px;
  height: 19px;
  flex: none;
  display: inline-block;
  background: currentColor;
  -webkit-mask: url("/static/logo-mark.svg") center / contain no-repeat;
  mask: url("/static/logo-mark.svg") center / contain no-repeat;
}

.ds-navbar__wordmark {
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 22px;
  letter-spacing: -0.01em;
}

.ds-navbar__links {
  display: flex;
  align-items: center;
  gap: clamp(10px, 1.5vw, 24px);
  min-width: 0;
  flex: 0 1 auto;
  /* No overflow:hidden — it would clip the Datasets dropdown panel. Narrow
     widths are handled by the clamped gaps and the ≤860px collapse. */
  white-space: nowrap;
}

.ds-navbar__link {
  font-family: var(--font-sans);
  font-size: clamp(12.5px, 1.25vw, 14px);
  color: var(--body-foreground);
  text-decoration: none;
  transition: color 0.15s ease;
}

.ds-navbar__link:hover { color: var(--foreground); }

.ds-navbar__link--current { color: var(--foreground); font-weight: 600; }

/* Navbar dropdown (Datasets). The trigger shares .ds-navbar__link so it
   renders identically to its <a> siblings; the button resets are restated
   here because UA form-control styling doesn't reliably yield to a class. */
.ds-navbar__dd {
  position: relative;
  display: flex;
  align-items: center;
}

.ds-navbar__dd-trigger {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 0;
  margin: 0;
  background: none;
  border: 0;
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
}

.ds-navbar__dd-chevron {
  width: 12px;
  height: 12px;
  transition: transform 0.15s ease;
}

.ds-navbar__dd:hover .ds-navbar__dd-chevron,
.ds-navbar__dd-trigger[aria-expanded="true"] .ds-navbar__dd-chevron {
  transform: rotate(180deg);
}

.ds-navbar__dd-menu {
  position: absolute;
  top: calc(100% + 14px);
  left: 50%;
  z-index: 60;
  min-width: 252px;
  padding: 6px;
  transform: translateX(-50%) translateY(-4px);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--background);
  box-shadow: var(--shadow-card);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition:
    opacity 0.15s ease,
    transform 0.15s ease,
    visibility 0.15s;
}

/* Bridges the 14px gap between trigger and panel so the pointer doesn't
   cross dead space on the way down (which would close a hover-open menu). */
.ds-navbar__dd-menu::before {
  content: "";
  position: absolute;
  top: -14px;
  right: 0;
  left: 0;
  height: 14px;
}

.ds-navbar__dd:hover .ds-navbar__dd-menu,
.ds-navbar__dd-menu.is-open {
  transform: translateX(-50%) translateY(0);
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
}

.ds-navbar__dd-item {
  display: block;
  padding: 9px 11px;
  border-radius: var(--radius);
  text-decoration: none;
  transition: background-color 0.15s ease;
}

.ds-navbar__dd-item:hover,
.ds-navbar__dd-item:focus-visible {
  background: var(--muted);
}

.ds-navbar__dd-label {
  display: block;
  font-family: var(--font-sans);
  font-size: 13.5px;
  font-weight: 500;
  color: var(--foreground);
}

.ds-navbar__dd-desc {
  display: block;
  margin-top: 2px;
  font-family: var(--font-sans);
  font-size: 12px;
  line-height: 1.35;
  color: var(--muted-foreground);
}

.ds-navbar__auth {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 6px;
  white-space: nowrap;
  flex: none;
}

.ds-navbar__login { font-size: 14px; padding: 8px 12px; }

/* ── Mobile menu (≤860px, where the middle links collapse) ── */

.ds-navbar__burger {
  display: none; /* desktop */
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 38px;
  height: 38px;
  padding: 0 8px;
  margin: 0;
  background: none;
  border: 0;
  appearance: none;
  -webkit-appearance: none;
  cursor: pointer;
}

.ds-navbar__burger span {
  display: block;
  height: 2px;
  border-radius: 2px;
  background: var(--foreground);
  transition: transform 0.15s ease;
}

.ds-navbar__burger[aria-expanded="true"] span:first-child { transform: translateY(3.5px) rotate(45deg); }

.ds-navbar__burger[aria-expanded="true"] span:last-child { transform: translateY(-3.5px) rotate(-45deg); }

.ds-navbar__mobile {
  margin: 8px 0 0;
  padding: 10px;
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--card);
  box-shadow: var(--shadow-card);
  display: flex;
  flex-direction: column;
}

.ds-navbar__mobile-group {
  padding: 12px 12px 2px;
  font-family: var(--font-sans);
  /* 12px, not the desktop overline's 10.5-11px: the panel only exists on
     phones, where 12px is the readability floor (hard rule 6). */
  font-size: 12px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted-foreground);
}

.ds-navbar__mobile-link {
  display: block;
  padding: 11px 12px;
  border-radius: var(--radius);
  font-family: var(--font-sans);
  font-size: 15px;
  color: var(--foreground);
  text-decoration: none;
}

.ds-navbar__mobile-link:hover { background: var(--muted); }

.ds-navbar__mobile-link--child { padding-left: 22px; }

/* Log in only, and only in the band where the bar drops it. The bar's primary
   CTA stays visible while this panel is open, so a copy of it here rendered the
   same button twice on the same screen (the reported duplicate) — the panel now
   carries only what the bar hides. Above 480px the bar still shows Log in, so
   the block has nothing to add and does not draw its rule. */
.ds-navbar__mobile-auth {
  display: none;
  padding: 12px 4px 4px;
  margin-top: 6px;
  border-top: 1px solid var(--border);
}

.ds-navbar__mobile-auth .ds-btn { width: 100%; }

@media (max-width: 860px) {
  .ds-navbar__links { display: none; }
  .ds-navbar__burger { display: flex; }
}

@media (max-width: 480px) {
  /* brand + Sign up + burger is all that fits; Log in lives in the panel */
  .ds-navbar__login { display: none; }
  .ds-navbar__mobile-auth { display: block; }
  .ds-navbar { padding: 0 12px; }
  .ds-navbar__inner { padding: 0 12px 0 16px; }
}

@media (min-width: 861px) {
  /* never show the panel on desktop, even if JS left it open before a resize */
  .ds-navbar__mobile { display: none !important; }
}

/* ── Chrome-less pages ──
   Auth templates set body_class "auth-chromeless" (base.html block): the DS
   navbar is hidden. There is no body offset left to drop — theme.css's body
   rule paints only the token background and the DS type face now, and the legacy 80px navbar
   offset this class used to cancel (via `padding-top: 0 !important`) was
   deleted at the source (marketing.css) in the same stage-5 fix, so the
   cancel is gone too. Lives here (not marketing.css) because it styles DS
   chrome — new-migration CSS never lands in legacy files. */
.auth-chromeless .ds-navbar { display: none !important; }

/* ── Footer (muted band, from the design canvas) ────────────── */

.ds-footer {
  border-top: 1px solid var(--border);
  background: var(--muted);
  font-family: var(--font-sans);
}

.ds-footer__grid {
  max-width: 1200px;
  margin: 0 auto;
  padding: 72px 32px 40px;
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) repeat(4, minmax(0, 1fr));
  gap: 40px;
}

.ds-footer__wordmark {
  font-size: 26px;
  font-weight: 600;
  margin: 0;
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--foreground);
}

.ds-footer__mark { width: 21px; height: 21px; }

.ds-footer__tagline {
  font-size: 14px;
  line-height: 1.6;
  color: var(--body-foreground);
  margin: 12px 0 0;
  max-width: 34ch;
  text-wrap: pretty;
}

.ds-footer__contact {
  margin-top: 18px;
  display: grid;
  gap: 6px;
  font-size: 12px;
}

.ds-footer__contact a { color: var(--body-foreground); text-decoration: none; }
.ds-footer__contact a:hover { color: var(--foreground); }

.ds-footer__heading {
  font-size: 10.5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--muted-foreground);
  margin: 0 0 14px;
  font-weight: 400;
}

.ds-footer__links {
  display: grid;
  gap: 9px;
  font-size: 14px;
}

.ds-footer__links a { color: var(--body-foreground); text-decoration: none; transition: color 0.15s ease; }
.ds-footer__links a:hover { color: var(--foreground); }

.ds-footer__legal-wrap {
  max-width: 1200px;
  margin: 0 auto;
  padding: 0 32px 56px;
}

.ds-footer__legal {
  font-size: 12px;
  line-height: 1.7;
  /* Was a hardcoded oklch(0.62 …), which is both a raw design value in a
     component file (rule 5) and a 3.42:1 contrast failure. WCAG has no
     fine-print exemption, so legal copy takes the same token as every other
     piece of secondary text. */
  color: var(--muted-foreground);
  margin: 0;
  border-top: 1px solid var(--border);
  padding-top: 24px;
  max-width: 110ch;
  text-wrap: pretty;
}

/* Phones keep the 2-up link-column layout (the legacy footer's mobile
   pattern) — brand block full-width above, then Product/Resources/Company/
   Legal flowing two per row. Never collapse to a single column: four
   stacked columns read as one long unstructured list. */
@media (max-width: 960px) {
  .ds-footer__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 32px; }
  .ds-footer__brand { grid-column: 1 / -1; }

  /* The heading earns its weight back at exactly the width the layout stops
     doing the job for it. On the desktop 5-up grid a 40px gutter separates the
     columns, so a quiet muted overline is enough to label one. Stacked two-up
     on a phone there is no gutter and the columns run together, and a 12px
     400-weight muted label above 14px 400-weight muted links is not a heading —
     it is the first item of the list, which is what a reader reported it as.
     Weight and ink, not size: the 12px phone floor below still applies, and
     these stay overlines rather than becoming a second type scale. */
  .ds-footer__heading {
    font-weight: 600;
    color: var(--foreground);
  }
}

@media (max-width: 560px) {
  /* Was 56/32 + 28px rows. The band that carries 1054px of footer does not
     also need the desktop's opening and closing air — same reason as the tap
     targets above. */
  .ds-footer__grid { padding: 40px 20px 28px; gap: 24px 20px; }
  .ds-footer__legal-wrap { padding: 0 20px 40px; }
}

/* ── App sidebar (components/ui/sidebar.html) ─────────────────
   Basecoat's .sidebar does the state machine (open/hidden, phone scrim,
   inert, the margin on the following <main>) and the item styling; these
   rules add what it does not know about: our header row, the New-chat
   button, the chat rows with a delete control, the credits card and the
   user row. Every declaration here is unlayered, so it beats Basecoat's
   layered rules whatever the specificity — do not "fix" a rule by adding
   specificity, fix it by being explicit about which element it targets. */

/* Basecoat's `.sidebar nav` paints itself via `--color-sidebar*`, which
   Basecoat's own `@layer theme` computes from `--sidebar*` AT :root — back
   when the DS palette lived under a per-page opt-in class (theme.css), that
   computed value inherited down to this nav frozen at Basecoat's un-scoped
   default; the page's own `--sidebar*` override never got a chance to
   re-resolve it. Same class of bug as the "Basecoat reinforcement" block below (a
   Basecoat-owned default winning over a DS token), so it gets the same fix:
   paint the real properties directly from our tokens instead of trusting the
   indirection. `border-right` (not `border-color`) because Tailwind's
   preflight also resets border-width to 0 — see that block's comment.
   POST-FOLD (stage 5): --sidebar* lives at :root now, so Basecoat's
   indirection resolves correctly on its own and these explicit paints are
   belt-and-braces rather than load-bearing. Left deliberately — dropping
   them is a change with its own before/after, not a comment edit. */
.ds-sidebar nav {
  background: var(--sidebar);
  color: var(--sidebar-foreground);
  border-right: 1px solid var(--sidebar-border);
  font-family: var(--font-sans);
}

.ds-sidebar__header {
  flex-direction: row;   /* Basecoat stacks header children; unlayered wins */
  align-items: center;
  justify-content: space-between;
  padding: 12px 12px 4px 16px;
}

.ds-sidebar__toggle { color: var(--muted-foreground); }

/* Basecoat puts `overflow-y: auto` on `.sidebar nav > section`, and our
   section holds the New chat button AND the chat list — so the button
   scrolled away with the rows. Move the scroll down to the list itself:
   the button and the "Chats" label stay put, only the rows move. */
.ds-sidebar nav > section { overflow-y: hidden; }

.ds-sidebar__new {
  padding: 8px 12px 4px;
  flex: none;
}

.ds-sidebar nav > section > [role="group"] {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  /* Without this the group refuses to shrink below its content and the
     overflow never engages — the whole column grows instead. */
  min-height: 0;
}

.ds-sidebar__chats {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
}

/* Group label. Basecoat paints h3 at 70% of --sidebar-foreground, which on
   the muted ground measures under 4.5:1; --muted-foreground is 4.52 there. */
.ds-sidebar nav > section h3 {
  color: var(--muted-foreground);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

/* Chat rows (rendered by app/partials/chats_sidebar.html). A row is a link
   plus a delete button. The link takes the WHOLE row; the delete control
   floats over the row's right edge and is invisible at rest, so it must not
   reserve width — the first version gave it a 28px flex slot and every title
   was truncated 30px early for a button nobody could see. While the control
   is shown (hover / focus / coarse pointer) the link pads its right edge so
   the title clips under it instead of behind it. */
.ds-sidebar__chat {
  position: relative;
  display: flex;
  align-items: center;
}
.ds-sidebar nav > section ul li.ds-sidebar__chat > a {
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
  color: var(--body-foreground);
}
.ds-sidebar__chat:hover > a,
.ds-sidebar__chat:focus-within > a { padding-right: 36px; }
/* Basecoat paints a row's hover AND active/current background from
   `--color-sidebar-accent`, the same frozen-at-:root indirection as the nav
   background above (see that rule's comment) — it never picked up the
   page's own `--sidebar-accent`. Set it explicitly rather than trust it.
   POST-FOLD (stage 5): --sidebar* lives at :root now, so Basecoat's
   indirection resolves correctly on its own and these explicit paints are
   belt-and-braces rather than load-bearing. Left deliberately — dropping
   them is a change with its own before/after, not a comment edit. */
.ds-sidebar nav > section ul li.ds-sidebar__chat > a:hover,
.ds-sidebar nav > section ul li.ds-sidebar__chat > a[aria-current="page"] {
  background-color: var(--sidebar-accent);
}
.ds-sidebar nav > section ul li.ds-sidebar__chat > a[aria-current="page"] { color: var(--foreground); }
.ds-sidebar nav li > .ds-sidebar__chat-del {
  position: absolute;
  right: 4px;
  top: 50%;
  translate: 0 -50%;
  width: 28px;
  height: 28px;
  padding: 0;
  flex: none;
  opacity: 0;
  color: var(--muted-foreground);
}
.ds-sidebar__chat:hover .ds-sidebar__chat-del,
.ds-sidebar__chat-del:focus-visible,
.ds-sidebar__chat:focus-within .ds-sidebar__chat-del { opacity: 1; }
/* A mouse-initiated delete returns focus to this button after the confirm
   dialog closes; :focus (not just :focus-visible) keeps it visible then too,
   since the row it belongs to is otherwise invisible again. */
.ds-sidebar__chat-del:focus { opacity: 1; }
.ds-sidebar__chat-del:hover { color: var(--destructive); background: var(--muted-deep); }

/* Marquee for a long title: clipped at rest, slides through on hover.
   Moved from app.css (.ss-clip/.ss-text) with the sidebar. */
.ds-sidebar__chat-clip {
  display: inline-block;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  max-width: 100%;
  vertical-align: bottom;
}
.ds-sidebar__chat-text { display: inline; white-space: nowrap; }
.ds-sidebar__chat:hover .ds-sidebar__chat-clip { text-overflow: clip; }
.ds-sidebar__chat:hover .ds-sidebar__chat-text {
  display: inline-block;
  animation: ds-sidebar-marquee 8s linear infinite;
}
@keyframes ds-sidebar-marquee { to { transform: translateX(-100%); } }
@media (prefers-reduced-motion: reduce) {
  .ds-sidebar__chat:hover .ds-sidebar__chat-text { animation: none; }
}

/* The last answer in this chat did not finish (interrupted or errored). */
.ds-sidebar__chat-dot {
  width: 5px;
  height: 5px;
  border-radius: var(--radius-full);
  background: var(--tint-orange-ink);
  flex-shrink: 0;
  margin-left: 6px;
}

.ds-sidebar__chats-empty,
.ds-sidebar__chats-more {
  padding: 12px 8px;
  font-size: 12px;
  color: var(--muted-foreground);
  text-align: center;
}

/* Footer */
.ds-sidebar__footer {
  gap: 6px;
  padding: 8px 12px 12px;
  border-top: 1px solid var(--sidebar-border);
}

.ds-sidebar__credits {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "label cta" "value cta";
  align-items: center;
  gap: 2px 10px;
  padding: 10px 12px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--card);
  color: var(--foreground);
  text-decoration: none;
  transition: border-color 0.15s ease;
}
.ds-sidebar__credits:hover { border-color: var(--foreground); }
.ds-sidebar__credits-label {
  grid-area: label;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted-foreground);
}
.ds-sidebar__credits #sidebar-credit-badge { grid-area: value; }
.ds-sidebar__credits-value {
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}
.ds-sidebar__credits-cta {
  grid-area: cta;
  font-size: 12px;
  font-weight: 600;
  color: var(--accent);
}

.ds-sidebar__links {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
/* Basecoat lays out list links only inside `nav > section`; these live in the
   footer, so the row layout (icon beside label) is ours to state. Tailwind's
   preflight also makes the svg display:block, which is what stacked the icon
   above its label before this rule existed. */
.ds-sidebar nav .ds-sidebar__links li > a {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  color: var(--body-foreground);
}
.ds-sidebar nav .ds-sidebar__links li > a > svg { flex: none; width: 16px; height: 16px; }
/* Same frozen-`--color-sidebar-accent` hover as the chat rows above. */
.ds-sidebar nav .ds-sidebar__links li > a:hover { background-color: var(--sidebar-accent); }

/* User row: the dropdown trigger is a full-width row, not a pill. */
.ds-sidebar__user { width: 100%; }
.ds-sidebar__user-trigger {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  padding: 6px 8px;
  border: 0;
  border-radius: var(--radius);
  background: transparent;
  color: var(--foreground);
  font-family: var(--font-sans);
  text-align: left;
  cursor: pointer;
}
.ds-sidebar__user-trigger:hover,
.ds-sidebar__user-trigger[aria-expanded="true"] { background: var(--sidebar-accent); }
.ds-sidebar__user-trigger:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.ds-sidebar__user-trigger > svg { margin-left: auto; color: var(--muted-foreground); flex: none; }
.ds-sidebar__user-text { display: flex; flex-direction: column; min-width: 0; }
.ds-sidebar__user-name,
.ds-sidebar__user-org,
.ds-sidebar__org-name,
.ds-sidebar__org-plan { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
.ds-sidebar__user-name { font-size: 13px; font-weight: 600; }
/* `--muted-foreground` on the trigger's own ground (`--sidebar`/`--muted`) is
   fine, but the trigger repaints to `--sidebar-accent` (`--muted-deep`) on
   hover/expanded — the documented trap — where it measures 4.25:1, under the
   4.5 floor. `.ds-sidebar__org-plan` never sits on that tint (it is a row
   inside the white popover), so only the trigger's own org label needs the
   swap. Measured on painted canvas pixels, not computed: sanity 21.00,
   `--muted-foreground` on `--muted-deep` 4.25, `--body-foreground` there 7.48. */
.ds-sidebar__user-org { font-size: 11.5px; color: var(--body-foreground); }
.ds-sidebar__org-plan { font-size: 11.5px; color: var(--muted-foreground); }

/* The menu: opens ABOVE the row (data-side="top"), as wide as the sidebar
   allows. Org items carry avatar + two lines + a check on the current one. */
.ds-sidebar__user > [data-popover] { width: 100%; min-width: 0; }
.ds-sidebar__user-email {
  padding: 8px 10px 6px;
  font-size: 11.5px;
  color: var(--muted-foreground);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
/* Two rules land on this element and both are wrong for an address.
   Basecoat lays a menu heading out with `display: flex`, and
   `text-overflow: ellipsis` does nothing on a flex container — so a long
   email hard-clipped mid-character instead of truncating. And the shared
   `.dropdown-menu [role="menu"] [role="heading"]` rule below uppercases and
   letter-spaces headings, which is right for a category label like
   ORGANIZATIONS and wrong for an email: it inflates the string by about 15%
   and an address is not shouted. Specificity here has to clear that rule's
   (0,3,0), hence the long form — the same shape the 768px override uses. */
.ds-sidebar__user > [data-popover] [role="heading"].ds-sidebar__user-email {
  display: block;
  text-transform: none;
  letter-spacing: normal;
}

.ds-sidebar__user [role="menu"] > .ds-sidebar__org {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  height: auto;
  padding: 6px 10px;
  text-align: left;
}
.ds-sidebar__org-text { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; }
.ds-sidebar__org-name { font-size: 13px; font-weight: 500; }
.ds-sidebar__org[data-active="true"] { background: var(--muted); }
.ds-sidebar__org > svg { color: var(--foreground); flex: none; }

/* The reopen control the layout renders in <main>: hidden whenever the
   sidebar is showing on desktop, always available on phones (where the
   open drawer's scrim covers it anyway). */
.ds-sidebar-open {
  position: fixed;
  top: 12px;
  left: 12px;
  z-index: 30;
}
@media (min-width: 48rem) {
  .sidebar:not([aria-hidden="true"]) + main .ds-sidebar-open { display: none; }
}

/* Phones: the delete control has no hover to appear on, so it is always
   shown and given the row's full height as its target. Rules live in the
   shared coarse-pointer media block below (the "Mobile readability floor"
   section) — that is the one place all `.ds-*` touch sizing is declared,
   so a second standalone block never forms here. */

/* ── Example card (components/ui/example_card.html) ──────────
   Canvas: hairline card r18, tinted tile + tinted category label, query as
   the title, footer with count + arrow. Pair with a .ds-tint-* class. */

.ds-example-card {
  display: flex;
  flex-direction: column;
  gap: 14px;
  min-height: 186px;
  padding: 22px;
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--card);
  text-decoration: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}

.ds-example-card:hover {
  border-color: var(--foreground);
  box-shadow: var(--shadow-raised-light);
}

/* space-between + right-aligned label: the tile anchors the left, the category
   sits at the right edge. This is the canvas's own card top — the home page's
   pre-promotion copy had it, and it was lost when the card was promoted, which
   silently restyled a page already signed off. */
.ds-example-card__top { display: flex; align-items: center; justify-content: space-between; gap: 10px; }

.ds-example-card__tile {
  width: 34px;
  height: 34px;
  flex: none;
  border-radius: 10px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: var(--_tint, var(--muted));
  color: var(--_tint-ink, var(--foreground));
}

.ds-example-card__tile svg { width: 17px; height: 17px; }

.ds-example-card__cat {
  text-align: right;
  font-family: var(--font-sans);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--_tint-ink, var(--muted-foreground));
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ds-example-card__title {
  font-family: var(--font-sans);
  font-size: 16.5px;
  font-weight: 500;
  line-height: 1.35;
  letter-spacing: -0.01em;
  color: var(--foreground);
  margin: 0;
  flex: 1;
  text-wrap: pretty;
}

.ds-example-card__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  font-family: var(--font-sans);
  font-size: 11.5px;
  color: var(--muted-foreground);
}

.ds-example-card__arrow {
  width: 14px;
  height: 14px;
  flex: none;
  color: var(--_tint-ink, var(--accent));
  transition: transform 0.15s ease;
}

.ds-example-card:hover .ds-example-card__arrow { transform: translateX(2px); }

/* ── Filter chip (components/ui/filter_chip.html) ────────────
   Canvas chip(): 13px, 8x15, pill; hairline unselected, ink selected. */

.ds-filter-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  /* 44px min tap target on phones is handled by the padding below plus the
     line-height; keep the desktop look at the canvas's 13px. */
  padding: 8px 15px;
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--background);
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--body-foreground);
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: border-color 0.15s ease, background-color 0.15s ease, color 0.15s ease;
}

.ds-filter-chip:hover { border-color: var(--foreground); color: var(--foreground); }

.ds-filter-chip.active {
  border-color: var(--primary);
  background: var(--primary);
  color: var(--primary-foreground);
}

.ds-filter-chip > svg { flex: none; width: 14px; height: 14px; }

/* ── Pagination (components/ui/pagination.html) ──────────────
   Canvas: 36px circles, ink current, hairline others. */

.ds-pagination { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }

.ds-pagination__btn {
  width: 36px;
  height: 36px;
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--border);
  border-radius: var(--radius-full);
  background: var(--background);
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--body-foreground);
  text-decoration: none;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}

.ds-pagination__btn:hover { border-color: var(--foreground); color: var(--foreground); }

.ds-pagination__btn.is-active {
  border-color: var(--primary);
  background: var(--primary);
  color: var(--primary-foreground);
  cursor: default;
}

.ds-pagination__btn[disabled] { opacity: 0.4; cursor: not-allowed; }

.ds-pagination__btn[disabled]:hover { border-color: var(--border); color: var(--body-foreground); }

.ds-pagination__gap {
  width: 20px;
  text-align: center;
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--muted-foreground);
}

/* ═══ Mobile readability floor (hard rule 6) ═════════════════
   The design's micro-type (10.5px overlines, 11.5px badges) is a desktop
   scale; phones get the 12px floor. This lives with the COMPONENTS, not in
   each page's stylesheet — rule 6 says a component ships its own phone
   behavior, and every page that renders an overline needs the same floor.

   Keyed at 768px, not 640px: the shipping checklist's readability band is
   "<=768px", and at 640 an iPad in portrait still got the 10.5px desktop
   micro-type. Page stylesheets that add to this floor must use 768 too — they
   load AFTER ds.css, so a page rule still keyed at 640 wins at equal
   specificity and silently reopens the 641-768 band. */
@media (max-width: 768px) {
  .ds-overline,
  .ds-num-badge,
  .ds-pill,
  .ds-chip--sm,
  .ds-link-arrow,
  .ds-footer__heading,
  .ds-example-card__cat,
  .ds-example-card__foot { font-size: 12px; }

  /* Sidebar micro-type (components/ui/sidebar.html): the 11/11.5/10.5px
     desktop scale for the group label, credits label, org name/plan and the
     user-menu email line all fall under this floor too. */
  .ds-sidebar nav > section h3,
  .ds-sidebar__credits-label,
  .ds-sidebar__user-org,
  .ds-sidebar__org-plan { font-size: 12px; }

  /* `.dropdown-menu [role="menu"] [role="heading"]` (generic popover-heading
     style, below) also matches this element at 10.5px and outranks a plain
     `.ds-sidebar__user-email` selector (3 simple selectors vs. 1) regardless
     of which comes later in the file — matched here, not raised there, since
     that rule is shared by every dropdown-menu heading, not just this one. */
  .ds-sidebar__user > [data-popover] [role="heading"].ds-sidebar__user-email {
    font-size: 12px;
  }

  /* --soft is the only floor target whose base is ABOVE 12px, so the floor
     was SHRINKING it (13.5 -> 12) across the whole band. A floor raises; it
     must never lower. Re-asserted here so it does not depend on order. */
  .ds-pill--soft { font-size: 13.5px; }
}

/* 44px tap targets (shipping checklist #13).

   Keyed on the POINTER, not on width: a 44px minimum is about fingers, and a
   700px-wide desktop window driven by a mouse should keep the design's button
   scale (width-keying flattened sm/md/lg into three identical 44px buttons for
   every narrow desktop window).

   `any-pointer: coarse`, NOT `(hover: none) and (pointer: coarse)`. The
   compound query asks "is the PRIMARY input a finger", which is false on the
   two devices that most need the target: an iPad with a Magic Keyboard and a
   Surface with a mouse both report `hover: hover` / `pointer: fine` while
   still being touched constantly. `any-pointer: coarse` asks "is a finger one
   of the available inputs" — true on both, and still false on a pure-mouse
   desktop, which is the case the width key got wrong.

   The asymmetry decides it: a hybrid laptop getting a 44px control costs a
   slightly larger button that is already in-system (`--lg` is 44px), while a
   finger getting 36px is an accessibility failure. This file's OWN iOS
   zoom-on-focus workaround (at the end of ds.css, moved here from the
   deleted app.css/marketing.css in stage 5) uses the compound form —
   same syntax, opposite stakes, so it is not a precedent for this block.
   `docs-ds.css`'s search-hint toggle is a second documented exception for
   its own, different reason (see its own note). `examples-ds.css` had a
   compound-form use on a TAP target, which is the case this paragraph
   argues against, and is fixed. */
@media (any-pointer: coarse) {
  /* min-height, not padding alone: 11px + a 19.5px line box lands at 43.5, so
     the target depended on the font's metrics rather than on the rule. */
  .ds-filter-chip { padding: 11px 16px; min-height: 44px; }
  .ds-pagination__btn { width: 44px; height: 44px; }
  .ds-search-trigger { min-height: 44px; }
  /* Menu rows are 31px tall at the design's density — fine for a mouse,
     under the floor for a finger. */
  .dropdown-menu [role^="menuitem"] { min-height: 44px; }
  /* The bare input has no border and no padding block, so its height is its
     line box and nothing else — 24px on the hero and /examples search
     shells. It is the page's primary control on both. Excludes
     `.ds-input--bare-area` (the bare textarea variant, added later): equal
     specificity + later source order let this 44px floor beat that
     variant's own 120px `min-height`, so the composer's textarea silently
     lost 21px of height on every touch device. See the guard in
     tests/test_search_chat_ds.py. */
  .ds-input--bare:not(.ds-input--bare-area) { min-height: 44px; }
  /* Segmented tabs sit at 30px in a 36px track. The track has to give up its
     fixed height first or the taller buttons just clip. */
  .tabs > nav[role="tablist"] { height: auto; }
  .tabs > nav [role="tab"] { height: auto; min-height: 44px; }

  /* The picker's copy button and its client cards. The copy button had a
     `height: 44px` override in home.css's own coarse block; promoting the
     component moved the base rule and left the override behind, so it
     regressed to 36px on all four surfaces that now render the picker. A
     promoted component has to bring its media queries with it. */
  .ds-mcp__copy { height: 44px; }
  .ds-mcp__client { min-height: 44px; }
  /* The exhibit's tab strip is page-local art, not `.tabs`. */
  .ds-bm__tab { min-height: 44px; }

  /* Checkbox and switch: the LABEL is the tap target (the input is 16x16 and
     34x20 by design and must stay that size — it is the drawn control), so
     the minimum goes on the wrapper. Note this is a real miss and not the
     /examples case, which looked identical in a raw sweep but was a small
     input inside an already-44px label. Whether a small control is a finding
     depends entirely on the box around it. */
  .ds-choice { min-height: 44px; }

  /* `a.ds-link-arrow`, NOT `.ds-link-arrow`. The class is worn by two
     different things: the `link_arrow()` macro's real <a> (sole content of an
     <li> in the related-pages lists — 18px tall and its own tap target), and
     a decorative <span> inside a card that is ALREADY a link (home's
     how-it-works, data-delivery's use-case cards, the datasets spokes). The
     span's tap target is the card, which is enormous; giving it a 44px box
     would only pad the card out. Sixteen of these stack in one list on an
     /alternatives spoke, which is what made them worth finding. */
  a.ds-link-arrow { min-height: 44px; }

  /* Site chrome. These were missed until a sweep covered the marketing pages
     as a group rather than one at a time: the navbar brand sits at 33px and
     every footer link at 18–21px, on EVERY page that renders the DS chrome.
     They are inline-flex so a min-height actually gives them a box. */
  .ds-navbar__brand,
  .ds-brand {
    display: inline-flex;
    align-items: center;
    min-height: 44px;
  }

  /* Footer links need the box before they can have a height at all (they are
     inline <a>s); the height itself is set with the spacing below. */
  .ds-footer__links a,
  .ds-footer__contact a {
    display: inline-flex;
    align-items: center;
  }

  /* The chrome-less surfaces' lockup is icon+wordmark centred on its own
     line, so it needs the box on both axes. */
  .ds-brand { justify-content: flex-start; }

  /* 36px, not the 44px this block gives every other touch control, and the
     one place in the repo where that floor is deliberately not met.

     Measured at 375px with a coarse pointer, on the four link columns' 14
     links plus the 2 contact links:

       44px targets + the 9px gap ....... 1135px  (more than a screen of footer)
       44px targets, gap zeroed ......... 1054px  <- was
       36px targets + a 2px gap .........  932px  <- current
       no minimums at all ...............  772px

     The 44px version was shipped with a comment calling 282px of extra footer
     "the honest cost" and naming WCAG AA's 24x24 as the revisit if it ever
     proved too much. It did: a phone reader reported the footer as loose and
     unstructured, which is what a column of 21px labels in 44px boxes with the
     gap zeroed looks like — 23px of dead air per row, and no gap to say where
     one link ends and the next begins.

     36px + 2px keeps every target well clear of the 24x24 AA floor (2.5.8) and
     restores a gap, so adjacent targets read as separate rows AND are harder to
     mis-tap than the flush 44px ones were. Everything else in this block stays
     at 44: this is a dense list of secondary navigation, not a primary control.
     Rule 7 holds — on a mouse-driven desktop the footer is untouched. */
  .ds-footer__links a,
  .ds-footer__contact a { min-height: 36px; }
  .ds-footer__links { gap: 2px; }
  .ds-footer__contact { gap: 2px; }

  /* :not() the two variants that are not boxes: --icon is a SQUARE (a bare
     min-height turns the design's 36px circle into a 36x44 oval, because
     min-height beats height), and --link is bare text. --icon grows to a
     44px square instead; --link is left alone. */
  .ds-btn:not(.ds-btn--icon):not(.ds-btn--link) { min-height: 44px; }
  .ds-btn--icon { width: 44px; height: 44px; }
  /* --sm keeps its compact type but not its 28px box. */
  .ds-btn--sm:not(.ds-btn--icon):not(.ds-btn--link) {
    min-height: 44px;
    height: auto;
    padding-top: 8px;
    padding-bottom: 8px;
  }

  /* Sidebar chat rows: the delete control has no hover to appear on, so it
     is always shown and given the row height as its target. Chat rows and
     their delete control stay at 36px on a coarse pointer on purpose — the
     same documented exception the DS footer links make above (a dense
     column of secondary navigation, not a primary control). */
  .ds-sidebar nav li > .ds-sidebar__chat-del { opacity: 1; width: 36px; height: 36px; }
  /* Always-visible control → the title always clips under it. */
  .ds-sidebar nav > section ul li.ds-sidebar__chat > a { min-height: 36px; padding-right: 44px; }
  /* The footer's Examples/Documentation links are the same dense-list case. */
  .ds-sidebar nav .ds-sidebar__links li > a { min-height: 36px; }
  /* The user row IS a primary control (it opens the org/settings/logout
     menu), so it gets the full 44px floor, not the footer-links exception. */
  .ds-sidebar__user-trigger { min-height: 44px; }

  .ds-tab-link { min-height: 44px; }
}

/* The burger is the ONE control whose size must follow its visibility, not the
   pointer: it only exists below 860px and it is the sole way to open the mobile
   nav. Pointer-keying it left a 38px sole affordance on a touch hybrid and on a
   phone requesting "Desktop site" (layout viewport ~980px, so shown-and-small).
   Its own breakpoint, so the two keys cannot disagree. */
@media (max-width: 860px) {
  .ds-navbar__burger { width: 44px; height: 44px; }
}

/* Icon-only below 640px, so a bare min-height would make it a 36x44 oval —
   the .ds-btn--icon trap again. Grown on both axes instead. */
@media (any-pointer: coarse) and (max-width: 640px) {
  .ds-search-trigger { width: 44px; height: 44px; }
}

/* ═══ Basecoat reinforcement (TEMPORARY until the Tailwind v4 upgrade) ═══
   Tailwind v3's preflight is UNLAYERED, so it beats Basecoat's @layer rules
   for the properties preflight resets (universal border-width:0, --tw-*
   shadow variables, button padding). These unlayered rules restore — and
   restyle to the design reference — the affected pieces. Basecoat's JS and
   ARIA behavior are untouched. Delete this section when the compiled
   Tailwind moves to v4 (its preflight is layered).  */

/* Dropdown / popover panel */
.dropdown-menu [data-popover] {
  background: var(--popover);
  border: 1px solid var(--border);
  border-radius: 14px;
  box-shadow: 0 12px 26px -18px oklch(0.19 0.012 75 / 0.4), 0 0 0 1px oklch(0.19 0.012 75 / 0.03);
  padding: 5px;
  min-width: 180px;
}

.dropdown-menu [role^="menuitem"] {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 7px 10px;
  border-radius: 9px;
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--foreground);
  cursor: pointer;
  text-decoration: none;
}

.dropdown-menu [role^="menuitem"]:hover,
.dropdown-menu [role^="menuitem"]:focus-visible,
/* Basecoat marks the arrow-key row with .active — it moves no focus, so
   without this the keyboard user sees nothing move. */
.dropdown-menu [role^="menuitem"].active {
  background: var(--muted);
  color: var(--foreground);
  outline: none;
}

/* An action item (no href) is a real <button>, which preflight gives a
   background and a border and which does not stretch to the row. Font is
   deliberately NOT `inherit` here: `inherit` would pull whatever family the
   surrounding page happens to set instead of the DS `--font-sans` the
   [role^="menuitem"] rule above sets — every page inherits Inter from
   `body` now, but this rule does not rely on that staying true. */
.dropdown-menu button[role^="menuitem"] {
  width: 100%;
  background: none;
  border: 0;
  text-align: left;
}

.dropdown-menu [role="menu"] [role="heading"] {
  padding: 7px 10px 3px;
  font-family: var(--font-sans);
  font-size: 10.5px;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted-foreground);
}

.dropdown-menu [role="separator"] {
  border: 0;
  border-top: 1px solid var(--border);
  margin: 5px -5px;
}

/* Trigger chevron flips while the menu is open (Basecoat maintains
   aria-expanded on the trigger). */
.dropdown-menu [aria-haspopup="menu"] > svg:last-child {
  transition: transform 0.18s ease;
}

.dropdown-menu [aria-haspopup="menu"][aria-expanded="true"] > svg:last-child {
  transform: rotate(180deg);
}

/* ── Benchmark exhibit ───────────────────────────────────────
   Promoted out of home.css (`.home-bm__*`), consumed via
   components/ui/benchmark.html by the home page and the /alternatives
   spokes. Both render the same build_benchmark_context(); before this they
   rendered it through two separate, drifting designs. */

/* The cell body and the per-platform description used to borrow the home
   page's `.home-card__body`. A component that reaches for a page's class is
   not a component — it breaks the moment a second page adopts it, which is
   exactly what happened here. Both carry their own typography now. */
.ds-bm__cell-body,
.ds-bm__found-desc {
  display: block;
  font-family: var(--font-sans);
  font-size: 14px;
  line-height: 1.55;
  color: var(--body-foreground);
  margin: 8px 0 0;
}

.ds-bm__found-desc { margin: 10px 0 20px; }

.ds-bm__cells {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  overflow: hidden;
  margin-top: 40px;
}

.ds-bm__cell { background: var(--background); padding: 30px; }

.ds-bm__cell--stat { padding: 24px 30px; }

.ds-bm__cell--wide { grid-column: span 2; }

.ds-bm__big {
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 44px;
  line-height: 1;
  color: var(--foreground);
}

.ds-bm__big--accent { color: var(--accent); }

.ds-bm__big span { font-size: 20px; color: var(--muted-foreground); font-weight: 600; }

.ds-bm__tabs { display: flex; gap: 2px; margin-top: 32px; border-bottom: 1px solid var(--border); }

.ds-bm__tab {
  border: 0;
  background: transparent;
  padding: 12px 18px;
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 600;
  color: var(--body-foreground);
  cursor: pointer;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

.ds-bm__tab[aria-selected="true"] { color: var(--foreground); border-bottom-color: var(--foreground); }

.ds-bm__panel { padding-top: 34px; }

.ds-bm__panel-title {
  font-family: var(--font-sans);
  font-size: 24px;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--foreground);
  margin: 14px 0 0;
  max-width: 34ch;
}

.ds-bm__bars { display: grid; gap: 16px; margin-top: 28px; max-width: 720px; }

.ds-bm__bar-row { display: grid; grid-template-columns: 90px 1fr 120px; align-items: center; gap: 16px; }

.ds-bm__bar-label { font-family: var(--font-sans); font-size: 14px; color: var(--body-foreground); }

.ds-bm__bar-label.is-us { font-weight: 600; color: var(--foreground); }

.ds-bm__track { height: 10px; background: var(--border-subtle); border-radius: var(--radius-full); overflow: hidden; }

/* The one literal left in this block, deliberately: it is a BAR FILL for the
   non-Canonical platforms — a graphic, not text, so it carries no contrast
   floor, and no token means "a neutral mid-grey that is clearly not the
   accent". Adding a token for one graphic would be worse than the literal. */
.ds-bm__fill { height: 100%; background: oklch(0.62 0.012 75); }

.ds-bm__fill.is-us { background: var(--accent); }

.ds-bm__bar-value { font-family: var(--font-sans); font-size: 12px; color: var(--body-foreground); }

.ds-bm__note { font-family: var(--font-sans); font-size: 14px; font-style: italic; color: var(--body-foreground); margin: 0 0 20px; max-width: 62ch; }

.ds-bm__matrix {
  border-collapse: collapse;
  font-family: var(--font-sans);
  font-size: 13px;
  background: var(--background);
}

.ds-bm__matrix th,
.ds-bm__matrix td {
  text-align: left;
  padding: 12px 20px;
  border: 1px solid var(--border);
  color: var(--foreground);
  font-weight: 400;
  min-width: 96px;
}

.ds-bm__matrix thead th { font-weight: 500; }
.ds-bm__matrix thead th:first-child { color: var(--muted-foreground); }
.ds-bm__matrix tbody th { font-weight: 500; }

/* Tokenised from `oklch(0.955 0.02 255)` / `oklch(0.45 0.12 255)`, which
   moved the measured ratio 6.58 -> 5.17 at 13px. That is a real LOSS of
   headroom and still clears AA by 0.67, and it is the trade worth making:
   the literals were a one-off blue nothing else on the site used, while this
   pair is the tinted-chip pair (8 other consumers, `--tint-*-ink` measured
   4.95 orange / 5.17 blue / 5.81 green on their own tints). A tint that
   reads as "the same blue" everywhere is worth more than 1.4 of surplus
   contrast on one table cell. Do not lighten either token further — blue has
   the least room of the three. Measured on painted pixels, not computed. */
.ds-bm__matrix td.ds-bm__diag--us { background: var(--tint-blue); color: var(--tint-blue-ink); }
.ds-bm__matrix td.ds-bm__diag { background: var(--muted-deep); }
/* A zero-overlap cell is a RESULT, and the most interesting one in the
   matrix — it may be de-emphasised but not below AA. oklch(0.66) was 3.07:1
   on the matrix's --background ground; --muted-foreground is 4.79. (Not
   caught by the live sweep: the class only renders when the data actually
   contains a 0, so it is contrast that comes and goes with the numbers.) */
.ds-bm__matrix td.ds-bm__zero { color: var(--muted-foreground); }

.ds-bm__matrix-note {
  font-family: var(--font-sans);
  font-size: 13.5px;
  color: var(--muted-foreground);
  margin: 16px 0 0;
}

/* `--flush` is gone: it existed only because the Speed panel had no
   title, and it now has one like the other two. */

.ds-bm__speed {
  display: grid;
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  overflow: hidden;
  max-width: 720px;
}

.ds-bm__speed-row {
  background: var(--background);
  padding: 20px 24px;
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 16px;
  font-family: var(--font-sans);
}

.ds-bm__speed-name { font-size: 15px; color: var(--body-foreground); }
.ds-bm__speed-name.is-us { font-weight: 600; color: var(--foreground); }
.ds-bm__speed-val { font-size: 13px; color: var(--body-foreground); }
.ds-bm__speed-val.is-us { color: var(--accent); }

.ds-bm__panel-lede {
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.65;
  color: var(--body-foreground);
  margin: 12px 0 0;
  max-width: 64ch;
}

/* What each platform found */
.ds-bm__found-intro { margin-top: 56px; }

.ds-bm__found-intro .ds-bm__panel-title { margin: 0; font-size: 28px; }

.ds-bm__lede {
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.6;
  color: var(--muted-foreground);
  margin: 10px 0 0;
}

.ds-bm__found {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 16px;
  margin-top: 28px;
}

.ds-bm__found-card {
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  padding: 26px;
  background: var(--muted);
}

.ds-bm__found-card--us { border-color: var(--foreground); background: var(--background); }

.ds-bm__found-head { display: flex; justify-content: space-between; gap: 10px; align-items: baseline; }

.ds-bm__found-name { font-family: var(--font-sans); font-size: 16px; font-weight: 600; color: var(--foreground); }

.ds-bm__found-tag {
  font-family: var(--font-sans);
  font-size: 10.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--accent);
}


.ds-bm__found-list {
  margin: 0;
  padding: 0;
  list-style: none;
  display: grid;
  gap: 9px;
  font-family: var(--font-sans);
  font-size: 12.5px;
  color: var(--foreground);
}

.ds-bm__found-card:not(.ds-bm__found-card--us) .ds-bm__found-list { color: var(--body-foreground); }

/* The ordinal numbering ("01"…"09") is structure, not decoration, so it
   carries the AA floor like any other text. oklch(0.68) was 2.84:1 on
   --background and 2.70:1 on --muted, the two grounds this list renders on;
   --muted-foreground is 4.79 / 4.55. */
.ds-bm__found-list li span { color: var(--muted-foreground); margin-right: 10px; }

.ds-bm__found-more {
  margin-top: 16px;
  font-family: var(--font-sans);
  font-size: 11.5px;
  color: var(--muted-foreground);
}

/* Methodology footer — the query every platform ran + the judge. */
.ds-bm__footer {
  margin-top: 48px;
  padding-top: 22px;
  border-top: 1px solid var(--border);
}

.ds-bm__footer-meta {
  font-family: var(--font-sans);
  font-size: 13.5px;
  color: var(--muted-foreground);
  margin: 0;
}

.ds-bm__footer-query { color: var(--foreground); }

.ds-bm__footer-dot { margin: 0 6px; }

/* ── Benchmark responsive ──
   Was in home.css's own @media blocks. `minmax(0, 1fr)` and never a bare
   `1fr`: grid items default to `min-width: auto`, so the nowrap platform
   labels and the matrix would set the column's floor and push the page
   wider than the viewport. */
@media (max-width: 960px) {
  .ds-bm__cells { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
  .ds-bm__found { grid-template-columns: minmax(0, 1fr); }
}

@media (max-width: 640px) {
  .ds-bm__cells { grid-template-columns: minmax(0, 1fr); }
  .ds-bm__found { grid-template-columns: minmax(0, 1fr); }
  .ds-bm__cell--wide { grid-column: auto; }
  .ds-bm__bar-row { grid-template-columns: 72px minmax(0, 1fr) 90px; }
  /* The matrix is the one table here that cannot become a card stack — a
     pairwise matrix IS its grid — so it gets the inner scroller, which rule
     6 allows for a table behind a tab rather than visible at load. */
  .ds-bm__matrix-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch; }
}

/* Micro-type floor at 768px, NOT 640. The promotion moved these two labels
   out of home.css's 768px block and into the 640px one, which silently undid
   a documented fix — home.css's comment said in as many words that "at 640 a
   tablet in portrait still got the desktop micro-type". Measured after the
   move: `.ds-bm__found-tag` 10.5px and `.ds-bm__found-more` 11.5px at
   viewport widths 700 and 760, on both `/` and the alternatives spokes.
   Type floors go at 768 (shipping checklist #13); layout goes where the
   layout needs it, which is why the matrix scroller above stays at 640. */
@media (max-width: 768px) {
  .ds-bm__found-tag,
  .ds-bm__found-more { font-size: 12px; }
}


/* ── Accordion / FAQ ─────────────────────────────────────────
   Promoted after three clusters grew three drifting versions (see
   components/ui/accordion.html for the table). Native `<details>`: no JS, no
   ARIA to keep in sync, keyboard and screen-reader behaviour from the
   browser. Values are the middle of the three, so no cluster is the
   arbitrary winner.

   The row runs the full band width; the ANSWER keeps a 76ch measure. A row
   is a control, not prose — capping the row is what made data-delivery's FAQ
   stop 236px short of its section's right edge, with a hairline rule drawing
   attention to exactly where it stopped. */
.ds-accordion { border-top: 1px solid var(--border); }

.ds-accordion__row { border-bottom: 1px solid var(--border); }

.ds-accordion__q {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 20px;
  padding: 22px 0;
  font-family: var(--font-sans);
  font-size: 16px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--foreground);
  cursor: pointer;
  /* Both are needed: `list-style` kills the marker in Firefox and modern
     Chrome, the pseudo-element kills it in Safari and older WebKit. */
  list-style: none;
}

.ds-accordion__q::-webkit-details-marker { display: none; }

/* Plus/minus drawn with two bars — no icon asset, and it cannot go missing
   the way an inline SVG with no intrinsic size can (see `.ds-btn svg`). */
.ds-accordion__marker {
  position: relative;
  flex: none;
  width: 14px;
  height: 14px;
}

.ds-accordion__marker::before,
.ds-accordion__marker::after {
  content: "";
  position: absolute;
  inset: 50% 0 auto;
  height: 1.5px;
  background: var(--muted-foreground);
  transform: translateY(-50%);
}

.ds-accordion__marker::after {
  transform: translateY(-50%) rotate(90deg);
  transition: opacity 0.15s ease;
}

.ds-accordion__row[open] .ds-accordion__marker::after { opacity: 0; }

.ds-accordion__a {
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.7;
  color: var(--body-foreground);
  margin: 0 0 24px;
  max-width: 76ch;
  text-wrap: pretty;
}

@media (any-pointer: coarse) {
  /* The summary IS the control; 22px + a 16px line box already clears 44,
     but pin it so it does not rest on the font's metrics. */
  .ds-accordion__q { min-height: 44px; }
}

/* Tabs — segmented pill control (hero Agent|Search pattern) */
.tabs > nav[role="tablist"] {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px;
  border-radius: var(--radius-full);
  background: var(--muted-deep);
}

.tabs > nav [role="tab"] {
  height: 30px;
  padding: 0 14px;
  border: 0;
  border-radius: var(--radius-full);
  background: transparent;
  font-family: var(--font-sans);
  font-size: 13.5px;
  font-weight: 600;
  /* --body-foreground, not --muted-foreground. The track behind these is
     --muted-deep, and --muted-foreground measures 4.26:1 there — under AA.
     That pairing has now failed three times in this codebase (this control,
     the home onboarding timing label, and the reference token table's own
     0.58 row), so treat it as a known-bad combination rather than a
     coincidence: **--muted-foreground clears AA on --background (4.79) and
     --muted (4.55) but NOT on --muted-deep.** Anything secondary sitting on
     the deep tint needs --body-foreground (7.43). The de-emphasis survives —
     the selected tab is --foreground on a white pill. */
  color: var(--body-foreground);
  cursor: pointer;
}

.tabs > nav [role="tab"][aria-selected="true"] {
  background: var(--card);
  color: var(--foreground);
  box-shadow: var(--shadow-raised-light);
}

.tabs > [role="tabpanel"] { padding-top: 16px; }

/* Link tabs (tab_links): the same segmented pill, on anchors. Navigation
   between server-rendered sections, so no tablist role and no panels. */
.ds-tabs--links {
  display: inline-flex;
  /* Basecoat's `.tabs:not(:has(> [role=tablist][aria-orientation=vertical]))`
     sets flex-direction: column for the tablist+panels layout; a link strip
     has no tablist, so that rule would stack the links. Row, explicitly. */
  flex-direction: row;
  width: fit-content;
  align-items: center;
  gap: 4px;
  padding: 4px;
  border-radius: var(--radius-full);
  background: var(--muted-deep);
}
.ds-tab-link {
  display: inline-flex;
  align-items: center;
  height: 30px;
  padding: 0 14px;
  border-radius: var(--radius-full);
  font-family: var(--font-sans);
  font-size: 13.5px;
  font-weight: 600;
  color: var(--body-foreground);  /* --muted-foreground fails AA on --muted-deep */
  text-decoration: none;
}
.ds-tab-link:hover { color: var(--foreground); }
.ds-tab-link[aria-current="page"] {
  background: var(--card);
  color: var(--foreground);
  box-shadow: var(--shadow-raised-light);
}

/* Dialog (modal) */
dialog.dialog {
  /* margin:auto restores native centering (a global margin reset zeroes it) */
  margin: auto;
  width: min(480px, 92vw);
  max-height: 85vh;
  overflow: auto;
  border: 1px solid var(--border);
  border-radius: var(--radius-xl);
  background: var(--card);
  color: var(--foreground);
  box-shadow: 0 40px 90px -50px oklch(0.19 0.012 75 / 0.35);
  padding: 0;
}

dialog.dialog::backdrop {
  background: oklch(0.19 0.012 75 / 0.4);
  -webkit-backdrop-filter: blur(3px);
  backdrop-filter: blur(3px);
}

/* Basecoat centers this inner box with left/top 50% + translate -50%; the
   translate gets zeroed by an unlayered reset, so neutralize the whole
   scheme — the <dialog> itself is already centered by margin:auto. */
dialog.dialog > div {
  position: relative;
  left: auto;
  top: auto;
  translate: none;
  transform: none;
  width: auto;
  max-width: none;
  padding: 24px;
}

dialog.dialog header h2 {
  margin: 0 0 6px;
  font-family: var(--font-sans);
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--foreground);
}

dialog.dialog header p {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--muted-foreground);
}

dialog.dialog section { margin: 14px 0 0; font-family: var(--font-sans); }

/* A quoted subject inside a dialog — the chat title being deleted. */
.ds-modal-quote {
  margin: 0;
  padding: 8px 12px;
  border-radius: var(--radius);
  background: var(--muted);
  font-size: 13px;
  line-height: 1.5;
  color: var(--foreground);
  word-break: break-word;
}

dialog.dialog footer {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 20px;
}

dialog.dialog > div > .btn[data-modal-close] {
  position: absolute;
  top: 14px;
  right: 14px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  border: 0;
  border-radius: var(--radius-full);
  background: transparent;
  color: var(--muted-foreground);
  cursor: pointer;
}

dialog.dialog > div > .btn[data-modal-close]:hover { background: var(--muted); color: var(--foreground); }

dialog.dialog > div > .btn[data-modal-close] svg { width: 16px; height: 16px; }

/* Toasts */
.toaster {
  position: fixed;
  bottom: 20px;
  right: 20px;
  z-index: 100;
  display: flex;
  flex-direction: column;
  gap: 10px;
  max-width: 360px;
}

.toast {
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--popover);
  box-shadow: 0 12px 26px -18px oklch(0.19 0.012 75 / 0.4);
  padding: 14px 16px;
}

.toast .toast-content { display: flex; gap: 10px; align-items: flex-start; }

.toast .toast-content > svg { width: 18px; height: 18px; flex: none; margin-top: 1px; }

.toast[data-category="success"] .toast-content > svg { color: oklch(0.55 0.12 150); }
.toast[data-category="error"] .toast-content > svg { color: var(--destructive); }
.toast[data-category="warning"] .toast-content > svg { color: oklch(0.68 0.14 70); }
.toast[data-category="info"] .toast-content > svg { color: var(--accent); }

.toast h2 {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 14px;
  font-weight: 600;
  color: var(--foreground);
}

.toast p {
  margin: 2px 0 0;
  font-family: var(--font-sans);
  font-size: 13px;
  color: var(--muted-foreground);
}

.toast footer { display: flex; gap: 8px; margin-top: 10px; }

/* Alert */
.alert {
  display: grid;
  grid-template-columns: auto 1fr;
  column-gap: 10px;
  align-items: start;
  border: 1px solid var(--border);
  border-radius: 14px;
  background: var(--card);
  padding: 14px 16px;
  font-family: var(--font-sans);
}

.alert > svg { width: 18px; height: 18px; margin-top: 1px; color: var(--foreground); }

.alert > h2 {
  margin: 0;
  font-size: 14px;
  font-weight: 600;
  color: var(--foreground);
  align-self: center;
}

.alert > section {
  grid-column: 2;
  font-size: 13.5px;
  color: var(--muted-foreground);
}

.alert > section .ds-body { font-size: 13.5px; color: var(--muted-foreground); }

.alert[data-variant="destructive"] { border-color: oklch(0.55 0.18 25 / 0.35); }
.alert[data-variant="destructive"] > svg,
.alert[data-variant="destructive"] > h2 { color: var(--destructive); }

/* Success — the confirmation panel (auth flow, "you're connected"). Painted
   from the existing --tint-green* pair rather than a new semantic colour. */
.alert[data-variant="success"] {
  border-color: var(--tint-green-ink);
  background: var(--tint-green);
}
.alert[data-variant="success"] > svg,
.alert[data-variant="success"] > h2 { color: var(--tint-green-ink); }
/* `.ds-body` has to be named explicitly. `> section` sets the colour on the
   section and a bare `body()` inside INHERITS it — but `.alert > section
   .ds-body` above sets --muted-foreground on the element itself, which beats
   an inherited value outright. Result: a success alert with a body() read
   4.33:1 on the green tint while the title beside it read 5.80. Caught on
   /dev/components; production auth pages use the title-only form, so no
   shipped page was affected. The lesson is the general one about this file —
   a variant that recolours a container must also recolour anything inside it
   that paints itself. */
.alert[data-variant="success"] > section,
.alert[data-variant="success"] > section .ds-body { color: var(--tint-green-ink); }

/* Table.

   `:where(.ds-table-wrap)` contributes ZERO specificity, so these base rules
   land at (0,1,1) — one class plus an element — instead of (0,2,1). That
   matters: a page that needs different cell metrics writes
   `.my-table-el td { … }`, which is also (0,1,1), and wins because its
   stylesheet loads after ds.css. With the descendant class counted, no
   single-class page rule could ever beat the base and the override failed
   SILENTLY (the pricing compare table's compact phone cells were inert at
   every width for exactly this reason). A component base should be the
   easiest thing in the cascade to override, not the hardest. */
:where(.ds-table-wrap) .table {
  border-collapse: collapse;
  font-family: var(--font-sans);
  font-size: 14px;
  color: var(--foreground);
}

:where(.ds-table-wrap) .table th {
  text-align: left;
  padding: 12px 16px;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted-foreground);
  border-bottom: 1px solid var(--border);
  background: var(--muted);
}

:where(.ds-table-wrap) .table td {
  padding: 12px 16px;
  border-bottom: 1px solid var(--border-subtle);
}

:where(.ds-table-wrap) .table tbody tr:last-child td { border-bottom: 0; }

/* Half-strength, NOT `--muted`: the header above is painted `--muted`, so a
   full-strength hover made the hovered row read as a second header row.
   Basecoat's own table uses a 50% color-mix here for exactly that reason;
   our override had flattened it. `--row-hover` (theme.css). */
:where(.ds-table-wrap) .table tbody tr:hover { background: var(--row-hover); }

/* Sidebar open/hidden (components/ui/sidebar.html, D1). Basecoat hides the
   aside by setting --tw-translate-x to ±100% under [aria-hidden=true] nav,
   then reading it back via `translate: var(--tw-translate-x) var(--tw-translate-y)`
   — but that rule lives in a Tailwind layer, and the TW3 preflight's
   unlayered `*{--tw-translate-x:0;--tw-translate-y:0}` (same freeze as the
   checkbox knob below) always wins, so the custom property never moves and
   the aside never actually leaves the screen even though aria-hidden and the
   <main> margin update correctly. Set `translate` directly so the sidebar
   really closes; Basecoat's own transition (transform/translate, 0.3s) still
   applies since it targets the property, not this custom-property plumbing. */
.sidebar:not([data-side])[aria-hidden="true"] nav,
.sidebar[data-side="left"][aria-hidden="true"] nav { translate: -100% 0; }
.sidebar[data-side="right"][aria-hidden="true"] nav { translate: 100% 0; }

/* ═══ end Basecoat reinforcement ═══ */

/* ── Checkbox & switch (fully owned — no Basecoat classes) ────
   Basecoat's checkbox/switch visuals depend on layered rules the TW3
   preflight destroys (knob translate, tick mask), so the DS draws its own. */

.ds-checkbox,
.ds-switch {
  appearance: none;
  -webkit-appearance: none;
  margin: 0;
  flex: none;
  cursor: pointer;
}

.ds-checkbox:focus-visible,
.ds-switch:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.ds-checkbox {
  width: 16px;
  height: 16px;
  border: 1px solid var(--border);
  border-radius: 5px;
  background: var(--card);
  transition: background-color 0.15s ease, border-color 0.15s ease;
}

.ds-checkbox:checked {
  background-color: var(--primary);
  border-color: var(--primary);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='white' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'><path d='M20 6 9 17l-5-5'/></svg>");
  background-size: 10px 10px;
  background-position: center;
  background-repeat: no-repeat;
}

.ds-switch {
  position: relative;
  width: 34px;
  height: 20px;
  border: 0;
  border-radius: var(--radius-full);
  background: var(--border);
  transition: background-color 0.18s ease;
}

.ds-switch::before {
  content: "";
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: var(--radius-full);
  background: oklch(1 0 0);
  box-shadow: 0 1px 2px oklch(0.19 0.012 75 / 0.3);
  transition: left 0.18s ease;
}

.ds-switch:checked { background: var(--primary); }

.ds-switch:checked::before { left: 16px; }

/* ── Radio (fully owned — no Basecoat classes) ─────────────────
   Promoted from standalone-ds.css's `.sa-radio` (stage 4 review, I3): a
   radio is a generic form control same as the checkbox/switch above, so it
   does not belong in a page stylesheet once a second page needs it. Unlike
   `checkbox()`/`toggle()`, which own a `<label class="ds-choice">` wrapper
   because the whole row is one text label, a radio here is one control
   inside a caller-built composite row (a plan tile, an org card with its
   own pill and meta line) — see `radio()` in components/ui/input.html,
   which renders the bare input only and leaves the row markup to the
   caller. Visuals are unchanged from `.sa-radio`: a drawn ring that grows
   into a filled dot on `:checked`. */

.ds-radio {
  appearance: none;
  -webkit-appearance: none;
  flex: none;
  margin: 0;
  width: 18px;
  height: 18px;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--card);
  cursor: pointer;
  transition: border-color 0.15s ease, border-width 0.15s ease;
}

.ds-radio:checked {
  border-color: var(--primary);
  border-width: 5px;
}

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

/* ── iOS no-zoom-on-focus (site-wide) ──────────────────────────────
   iOS Safari zooms the whole page when a focused text field's font-size is
   < 16px. Force every text-entry control to 16px on touch devices so
   focusing any input/textarea/select never triggers that zoom. Buttons
   (submit/button) are excluded so their labels keep their size. !important
   beats the per-field Tailwind text-[Npx]/text-sm classes; the
   coarse-pointer scope leaves the desktop type scale untouched. Moved here
   (stage 5, tasks 3+4) from app.css and marketing.css, which each carried
   an identical copy (the two layouts never load both files at once) — this
   is a component-level rule about text inputs, and ds.css owns .ds-input. */
@media (hover: none) and (pointer: coarse) {
  input[type="text"],
  input[type="email"],
  input[type="password"],
  input[type="number"],
  input[type="search"],
  input[type="tel"],
  input[type="url"],
  input:not([type]),
  textarea,
  select {
    font-size: 16px !important;
  }
}
