/* ---------- Design tokens ---------- */
:root {
  --bg: #0b0d12;
  --bg-elevated: #11141b;
  --bg-card: #161a23;
  --bg-card-hover: #1c212c;
  --border: #262c39;
  --border-soft: #1d222c;
  --text: #eef1f6;
  --text-dim: #9aa4b6;
  --text-faint: #7d8798;
  --accent: #4ee08a;
  --accent-dim: #2c8f5c;
  --accent-contrast: #06110a;
  --accent-2: #5aa2ff;
  --danger: #ff6b6b;
  --radius-sm: 8px;
  --radius-md: 14px;
  --radius-lg: 20px;
  --shadow: 0 8px 30px rgba(0,0,0,0.35);
  /* Real rendered height of the sticky .topbar. Kept exact by syncTopbarHeight() in
     app.js, which re-measures on resize — the topbar wraps to two rows under 880px,
     so this is not a constant and hardcoding one left the sidebar overshooting the
     viewport by the difference. This value is only the pre-JS fallback. */
  --topbar-h: 66px;
  --font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

/* Light tokens apply when the system prefers light and the user hasn't picked a theme
   explicitly (data-theme absent), OR when the user has explicitly picked light regardless
   of system preference. Explicit dark (data-theme="dark") always wins over both, since
   dark is what :root already is by default. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme]) {
    --bg: #f4f6fa;
    --bg-elevated: #ffffff;
    --bg-card: #ffffff;
    --bg-card-hover: #f0f3f9;
    --border: #dfe4ee;
    --border-soft: #e7eaf2;
    --text: #161a23;
    --text-dim: #545e70;
    --text-faint: #656f81;
    --accent: #0f7a46;
    --accent-dim: #0b5c34;
    --accent-contrast: #ffffff;
    --accent-2: #2a6fdb;
    --shadow: 0 8px 30px rgba(20,30,50,0.08);
  }
}
:root[data-theme="light"] {
  --bg: #f4f6fa;
  --bg-elevated: #ffffff;
  --bg-card: #ffffff;
  --bg-card-hover: #f0f3f9;
  --border: #dfe4ee;
  --border-soft: #e7eaf2;
  --text: #161a23;
  --text-dim: #545e70;
  --text-faint: #656f81;
  --accent: #0f7a46;
  --accent-dim: #0b5c34;
  --accent-contrast: #ffffff;
  --accent-2: #2a6fdb;
  --shadow: 0 8px 30px rgba(20,30,50,0.08);
}
:root[data-theme="dark"] {
  --bg: #0b0d12;
  --bg-elevated: #11141b;
  --bg-card: #161a23;
  --bg-card-hover: #1c212c;
  --border: #262c39;
  --border-soft: #1d222c;
  --text: #eef1f6;
  --text-dim: #9aa4b6;
  --text-faint: #7d8798;
  --accent: #4ee08a;
  --accent-dim: #2c8f5c;
  --accent-contrast: #06110a;
  --accent-2: #5aa2ff;
  --shadow: 0 8px 30px rgba(0,0,0,0.35);
}

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
button { font-family: inherit; }
input { font-family: inherit; }

/* ---------- Screen-reader-only text ---------- */
/* Real text, removed from the visual layout but left in the accessibility tree. Used where a
   visible label would be redundant to a sighted reader but the bare value isn't self-
   explanatory read aloud — e.g. a pill reading just "SUV".
   Deliberately NOT `aria-label` on the span: a bare <span> is role=generic, and ARIA
   prohibits accessible names on generic elements. Chrome computes one anyway, so it looks
   right in the a11y tree, but screen readers commonly drop it. Real text has no such
   caveat. Avoids display:none/visibility:hidden, which remove it from the tree too. */
.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ---------- Focus ---------- */
/* One ring for every focusable thing, keyboard-only. --accent-2 rather than --accent: it
   clears AA against every surface in both themes, and it doesn't collide with the green
   that already means "winning value" in the compare table. :focus-visible is what keeps
   this off mouse clicks, so no rule anywhere needs to suppress outlines wholesale. */
:focus-visible {
  outline: 2px solid var(--accent-2);
  outline-offset: 2px;
}

.app { min-height: 100%; display: flex; flex-direction: column; }

/* ---------- Topbar ---------- */
.topbar {
  position: sticky; top: 0; z-index: 30;
  display: flex; align-items: center; gap: 20px;
  padding: 14px 24px;
  background: color-mix(in srgb, var(--bg-elevated) 92%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--border-soft);
}
.brand {
  display: flex; align-items: center; gap: 8px; flex-shrink: 0;
  background: none; border: none; padding: 0; margin: 0; font: inherit; color: inherit;
  cursor: pointer; text-decoration: none;
}
.brand-mark {
  display: inline-flex; align-items: center; justify-content: center;
}
.brand-mark svg { display: block; }
.brand-name { font-weight: 700; font-size: 17px; letter-spacing: -0.02em; }

/* Server-rendered fallback for a single car page — a complete, real rendering of the car's
   title, price, full spec table, links, and similar vehicles for crawlers/clients that don't
   run JS. Reuses .modal-* classes directly (title/trim/price/summary/section/grid/row/links)
   since the content is structurally identical to the live detail modal, just non-interactive
   and outside a modal backdrop. app.js removes this whole block as soon as it boots, since the
   real modal (or the redirect-to-homepage fallback for a since-removed car) takes over. */
.static-car-detail { padding: 20px 24px 0; max-width: 640px; }
.static-similar-list { list-style: none; padding: 0; margin: 0; font-size: 13px; }
.static-similar-list li { padding: 6px 0; border-bottom: 1px dashed var(--border-soft); }
.static-similar-list a { color: var(--accent-2); text-decoration: none; }
.static-similar-list a:hover { text-decoration: underline; }

.topbar-search { flex: 1; max-width: 420px; }
.topbar-search input {
  width: 100%; padding: 9px 14px;
  background: var(--bg-card); border: 1px solid var(--border);
  border-radius: 10px; color: var(--text); font-size: 14px;
  transition: border-color .15s;
}
.topbar-search input:focus { border-color: var(--accent); }
.topbar-search input::placeholder { color: var(--text-faint); }

.topbar-actions { display: flex; align-items: center; gap: 14px; margin-left: auto; flex-shrink: 0; }

.theme-toggle-btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 33px; height: 33px; flex-shrink: 0;
  border: 1px solid var(--border); background: var(--bg-card); color: var(--text-dim);
  border-radius: 9px; font-size: 15px; cursor: pointer; transition: all .15s; padding: 0;
}
.theme-toggle-btn:hover { background: var(--bg-card-hover); color: var(--text); }

/* ---------- Buttons ---------- */
.btn {
  border: 1px solid var(--border); background: var(--bg-card); color: var(--text);
  padding: 8px 14px; border-radius: 9px; font-size: 13px; font-weight: 600;
  cursor: pointer; transition: all .15s; white-space: nowrap;
}
.btn:hover:not(:disabled) { background: var(--bg-card-hover); }
.btn:disabled { opacity: .45; cursor: default; }
.btn-ghost { background: transparent; }
.btn-ghost:hover:not(:disabled) { background: var(--bg-card); }
.btn-primary {
  background: var(--accent); border-color: var(--accent); color: var(--accent-contrast);
}
.btn-primary:hover:not(:disabled) { background: var(--accent-dim); border-color: var(--accent-dim); }
.btn-sm { padding: 5px 10px; font-size: 12px; }

/* ---------- Layout ---------- */
.layout { display: flex; flex: 1; min-height: 0; }
.sidebar {
  width: 300px; flex-shrink: 0;
  border-right: 1px solid var(--border-soft);
  height: calc(100dvh - var(--topbar-h));
  position: sticky; top: var(--topbar-h);
  overflow-y: auto;
  padding: 18px 16px 100px;
  background: var(--bg-elevated);
}
.sidebar-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12px; }
.sidebar-header h2 { font-size: 14px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-dim); }

/* Mobile-only controls: hidden on desktop, shown inside the 880px media query below */
.filters-toggle-btn { display: none; }
.sidebar-close { display: none; }
.sidebar-backdrop { display: none; }

.content { flex: 1; min-width: 0; padding: 20px 24px; }
/* Clearance so the floating compare-bar pill doesn't sit on top of the last row of
   cards — scoped to the results view specifically, not .content as a whole, since the
   compare view's own .compare-scroll is already a self-contained scroll region with its
   own bounded height; adding this same bottom padding there just pushed the page's total
   height past the viewport by a fixed amount, creating a second, near-useless outer
   scrollbar alongside the real one. */
#viewResults { padding-bottom: 80px; }

/* ---------- Compare mode: filter chrome doesn't apply ---------- */
body.compare-view .sidebar,
body.compare-view .topbar-search,
body.compare-view .filters-toggle-btn,
body.compare-view #resetFiltersBtn { display: none; }

/* ---------- Filter groups ---------- */
.filter-group { margin-bottom: 6px; border-bottom: 1px solid var(--border-soft); }
.filter-group-header {
  display: flex; align-items: center; justify-content: space-between;
  padding: 10px 4px; cursor: pointer; user-select: none;
}
.filter-group-header h3 { font-size: 13px; font-weight: 600; }
.filter-group-header .chev { color: var(--text-faint); transition: transform .15s; font-size: 11px; }
.filter-group.collapsed .chev { transform: rotate(-90deg); }
.filter-group-body { padding: 2px 4px 14px; display: flex; flex-direction: column; gap: 12px; }
.filter-group.collapsed .filter-group-body { display: none; }

.filter-field-label {
  font-size: 12.5px; color: var(--text-dim); font-weight: 600; margin-bottom: 6px; display: block;
}

.checkbox-row { display: flex; align-items: center; gap: 8px; font-size: 13px; color: var(--text); padding: 3px 0; cursor: pointer; }
.checkbox-row input { accent-color: var(--accent); width: 15px; height: 15px; cursor: pointer; }
.checkbox-row .count { margin-left: auto; color: var(--text-faint); font-size: 11.5px; }

.bool-toggle {
  display: flex; align-items: center; justify-content: space-between;
  font-size: 13px; padding: 2px 0;
}
.bool-toggle-label { color: var(--text); }
.switch { position: relative; width: 36px; height: 20px; flex-shrink: 0; }
.switch input { opacity: 0; width: 0; height: 0; }
.switch-track {
  position: absolute; inset: 0; background: var(--border); border-radius: 999px; cursor: pointer; transition: background .15s;
}
.switch-track::before {
  content: ""; position: absolute; width: 16px; height: 16px; left: 2px; top: 2px;
  background: var(--bg-elevated); border-radius: 50%; transition: transform .15s;
}
.switch input:checked + .switch-track { background: var(--accent); }
.switch input:checked + .switch-track::before { transform: translateX(16px); background: var(--accent-contrast); }

.range-values { display: flex; justify-content: space-between; font-size: 11.5px; color: var(--text-faint); margin-top: 4px; }
.dual-range { position: relative; height: 28px; }
.dual-range input[type=range] {
  position: absolute; top: 10px; left: 0; width: 100%; margin: 0;
  -webkit-appearance: none; appearance: none; background: transparent; pointer-events: none; height: 8px;
}
.dual-range input[type=range]::-webkit-slider-thumb {
  -webkit-appearance: none; pointer-events: auto;
  width: 16px; height: 16px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--bg-elevated); cursor: pointer; margin-top: -4px;
  box-shadow: 0 1px 4px rgba(0,0,0,0.4);
}
.dual-range input[type=range]::-moz-range-thumb {
  pointer-events: auto; width: 16px; height: 16px; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--bg-elevated); cursor: pointer;
}
.dual-range input[type=range]::-webkit-slider-runnable-track { height: 8px; background: transparent; }
.dual-range-track {
  position: absolute; top: 10px; left: 0; right: 0; height: 8px; border-radius: 999px; background: var(--border);
}
.dual-range-fill { position: absolute; top: 10px; height: 8px; border-radius: 999px; background: var(--accent); }

.enum-list { display: flex; flex-direction: column; gap: 2px; }

.enum-filter-input {
  width: 100%; padding: 6px 10px; margin-bottom: 6px;
  background: var(--bg); border: 1px solid var(--border); border-radius: 7px;
  color: var(--text); font-size: 12.5px; transition: border-color .15s;
}
.enum-filter-input:focus { border-color: var(--accent); }
.enum-filter-input::placeholder { color: var(--text-faint); }

.enum-expand-btn {
  border: none; background: transparent; color: var(--accent-2); font-size: 12px; font-weight: 600;
  cursor: pointer; padding: 6px 0 0; text-align: left;
}
.enum-expand-btn:hover { text-decoration: underline; }

/* ---------- Active filter chips ---------- */
/* The sidebar's own state is the only record of what's applied, and it scrolls out of view
   (and doesn't exist at all under 880px). These sit directly above the results so what's
   narrowing them is always visible, and each one is individually removable — the sidebar
   alone offered no undo short of hunting down the control you'd touched. */
.active-filters { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-bottom: 14px; }
.active-filters[hidden] { display: none; }
.active-filters-label { font-size: 12.5px; color: var(--text-dim); font-weight: 600; }
.filter-chip {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 12.5px; font-weight: 600; color: var(--text);
  background: var(--bg-card); border: 1px solid var(--border);
  border-radius: 999px; padding: 4px 6px 4px 12px;
}
.filter-chip-remove {
  display: inline-flex; align-items: center; justify-content: center;
  width: 18px; height: 18px; flex-shrink: 0; padding: 0;
  border: none; border-radius: 50%; background: transparent;
  color: var(--text-faint); font-size: 15px; line-height: 1; cursor: pointer;
  transition: background .15s, color .15s;
}
.filter-chip-remove:hover { background: var(--border); color: var(--text); }
/* The hub scope reads as a different kind of thing from a removable filter: filled rather
   than outlined, and with no × of its own — leaving a hub is a navigation, so it gets the
   adjacent "All N models" link instead of a control that looks like the filter chips. */
.filter-chip-scope {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border-color: color-mix(in srgb, var(--accent) 38%, var(--border));
  font-weight: 700; padding: 4px 12px;
}

.filter-chip-clear {
  border: none; background: transparent; color: var(--accent-2);
  font-size: 12.5px; font-weight: 600; cursor: pointer; padding: 4px 2px;
}
.filter-chip-clear:hover { text-decoration: underline; }
a.filter-chip-clear { text-decoration: none; }

/* ---------- Loading skeleton ---------- */
/* The dataset is a ~400KB fetch with nothing rendered until it lands. Before this, a slow
   connection showed an empty grid next to a count reading "0 vehicles" — which reads as
   "no matches", not "loading". Mirrors .ev-card's own box so the swap doesn't reflow. */
.skeleton-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 16px; }
.skeleton-card {
  background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 16px; display: flex; flex-direction: column; gap: 10px; min-height: 186px;
}
.skeleton-line { height: 10px; border-radius: 999px; background: var(--border); }
.skeleton-shimmer {
  background: linear-gradient(90deg, var(--border) 25%, var(--bg-card-hover) 50%, var(--border) 75%);
  background-size: 200% 100%;
  animation: skeleton-pulse 1.4s ease-in-out infinite;
}
@keyframes skeleton-pulse {
  from { background-position: 200% 0; }
  to { background-position: -200% 0; }
}
.skeleton-row { display: flex; gap: 10px; align-items: center; }
.skeleton-icon { width: 40px; height: 40px; border-radius: 10px; background: var(--border); flex-shrink: 0; }

/* Data failed to load entirely — the fetch previously had no catch at all, so this state
   used to be a permanently blank page with an unhandled rejection in the console. */
.load-error { text-align: center; padding: 64px 20px; color: var(--text-dim); }
.load-error h3 { color: var(--text); margin-bottom: 6px; }
.load-error p { font-size: 14px; margin: 0 0 16px; }

/* ---------- Body-style silhouettes ---------- */
/* bodyIcon() in fields.js emits an inline <svg class="body-icon"> whose shapes are 72 x 30.7,
   so width drives height. Sized per context here rather than in the markup: the same glyph
   appears in a 40px card tile, a 30px similar-vehicle tile, and inline beside two different
   titles. Fill is currentColor, so each context's text colour carries it. */
.body-icon { display: block; width: 32px; height: auto; flex-shrink: 0; }
.ev-card-icon .body-icon { width: 34px; }
.similar-card-icon .body-icon { width: 25px; }

/* These two sit inline next to title text rather than inside a tile, so they need to be
   inline-block and optically centred against the cap height. */
.compare-col-icon { display: inline-block; vertical-align: middle; margin-right: 3px; }
.compare-col-icon .body-icon { width: 22px; }
.modal-title .body-icon { display: inline-block; vertical-align: middle; width: 30px; margin-right: 8px; }

/* ---------- Card grid ---------- */
.intro-line { font-size: 14px; color: var(--text-dim); line-height: 1.5; margin: 0 0 16px; }
/* Hub landing pages carry a real page heading, unlike the homepage where the intro line is
   itself the h1. */
/* Homepage category nav. Server-rendered so crawlers follow it; a sitemap entry with no
   inbound link is a weak signal. */
.hub-links { display: flex; flex-wrap: wrap; align-items: baseline; gap: 6px 10px; margin: 0 0 16px; }
.hub-links:empty { display: none; }
.hub-links-label { font-size: 12.5px; font-weight: 600; color: var(--text-dim); }
.hub-links a {
  font-size: 12.5px; color: var(--accent-2); text-decoration: none;
  border: 1px solid var(--border); border-radius: 999px; padding: 3px 11px;
}
.hub-links a:hover { border-color: var(--accent-2); }

/* On a phone the ten category chips wrapped to roughly 340px — taller than the first car card,
   so the list a visitor actually came for started below the fold. One swipeable row instead,
   which is the native chip idiom on both platforms. Every link stays in the DOM and rendered,
   so this costs nothing for crawlers or for the internal linking these pills exist to provide.
   Scoped to #hubLinks: the make/body navs share .hub-links but sit below the grid, where
   wrapping is fine and 34 chips in one scroller would not be. */
@media (max-width: 640px) {
  #hubLinks {
    flex-wrap: nowrap; overflow-x: auto; margin-bottom: 12px; padding-bottom: 2px;
    scroll-snap-type: x proximity;
    scrollbar-width: none; -webkit-overflow-scrolling: touch;
    /* Fades the right edge so the row reads as scrollable rather than clipped. */
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 28px), transparent);
  }
  #hubLinks::-webkit-scrollbar { display: none; }
  #hubLinks > * { flex: 0 0 auto; scroll-snap-align: start; }
  #hubLinks a { padding: 5px 12px; }
}

/* Breadth linking below the grid rather than 34 pills above it, which would bury the
   results. Conventional placement for this kind of nav, and position matters far less to a
   crawler than existence does. */
.hub-links-footer { margin: 28px 0 0; padding-top: 16px; border-top: 1px solid var(--border-soft); }
.hub-links-footer + .hub-links-footer { margin-top: 10px; padding-top: 0; border-top: none; }
.static-hub-links a { color: var(--accent-2); text-decoration: none; }
.static-hub-links a:hover { text-decoration: underline; }

.hub-title { font-size: 21px; font-weight: 700; letter-spacing: -0.01em; margin: 0 0 8px; }
.results-toolbar { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; margin-bottom: 14px; }
.results-toolbar-label { font-size: 12.5px; color: var(--text-dim); font-weight: 600; white-space: nowrap; }
/* Sits with the results it describes instead of across the page in the topbar. This is
   also the aria-live region, so filter/search changes get announced. */
.result-count { color: var(--text-dim); font-size: 13px; white-space: nowrap; margin-left: auto; }
.sort-select {
  padding: 7px 30px 7px 12px; background: var(--bg-card); border: 1px solid var(--border);
  border-radius: 8px; color: var(--text); font-size: 13px; cursor: pointer;
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%239aa4b6' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 12px center;
}
.sort-select:focus { border-color: var(--accent); }
.card-grid {
  display: grid; grid-template-columns: repeat(auto-fill, minmax(270px, 1fr)); gap: 16px;
}
.ev-card {
  background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 16px; cursor: pointer; transition: border-color .15s, transform .15s;
  display: flex; flex-direction: column; gap: 10px;
}
@media (hover: hover) {
  .ev-card:hover { border-color: var(--accent-dim); transform: translateY(-2px); }
}
.ev-card-top { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.ev-card-icon {
  width: 40px; height: 40px; border-radius: 10px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(145deg, color-mix(in srgb, var(--accent) 22%, transparent), color-mix(in srgb, var(--accent-2) 18%, transparent));
}
.ev-card-title { font-size: 15px; font-weight: 700; line-height: 1.25; }
.ev-card-trim { font-size: 12.5px; color: var(--text-dim); margin-top: 2px; }
.ev-card-price { font-size: 13px; color: var(--accent); font-weight: 700; white-space: nowrap; }
.ev-card-stats { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 10px; margin-top: 2px; }
.ev-stat { display: flex; flex-direction: column; }
.ev-stat-label { font-size: 11px; color: var(--text-faint); text-transform: uppercase; letter-spacing: .04em; }
.ev-stat-value { font-size: 13px; font-weight: 600; }
.ev-card-footer { display: flex; align-items: center; justify-content: space-between; margin-top: auto; padding-top: 8px; border-top: 1px solid var(--border-soft); }
.ev-card-add { display: flex; align-items: center; gap: 6px; font-size: 12.5px; color: var(--text-dim); }
.ev-card-add input { accent-color: var(--accent); width: 15px; height: 15px; }
.ev-card-badges { display: flex; gap: 5px; flex-wrap: wrap; }
.badge {
  font-size: 10.5px; padding: 2px 7px; border-radius: 999px; background: var(--border-soft); color: var(--text-dim);
  border: 1px solid var(--border);
}
/* Body style: a category, not a feature, so it reads as a filled pill against the outline
   feature badges. Text is --text rather than --accent deliberately — accent text on this
   tint measures 3.71:1 in light mode, a fail; --text lands at 11.98 light / 15.99 dark.
   The fill, not the text colour, is what carries the category distinction. */
.badge-body {
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  border-color: color-mix(in srgb, var(--accent) 38%, var(--border));
  color: var(--text); font-weight: 700;
}

.empty-state { grid-column: 1 / -1; text-align: center; padding: 80px 20px; color: var(--text-dim); }
.empty-state h3 { color: var(--text-dim); margin-bottom: 6px; }

/* ---------- Compare ---------- */
.compare-header { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 16px; margin-bottom: 16px; }
.compare-header h2 { font-size: 18px; margin-right: auto; min-width: 0; }
/* A native horizontal scrollbar on a tall, capped-height table sits at that table's own
   bottom edge — reachable in principle, but easy to lose track of and awkward to grab on
   some setups. These buttons live up here in the header, which is never capped/scrolled,
   so they're always reachable regardless of table height. Desktop only — mobile scrolls
   the table by touch directly, no scrollbar-hunting problem to solve there. */
.compare-scroll-nav { display: none; gap: 6px; }
.compare-scroll-nav[hidden] { display: none; }
.compare-scroll-nav-btn {
  width: 30px; height: 30px; border-radius: 8px; border: 1px solid var(--border);
  background: var(--bg-card); color: var(--text-dim); font-size: 18px; line-height: 1; cursor: pointer;
}
.compare-scroll-nav-btn:hover { background: var(--bg-card-hover); color: var(--text); }
.compare-scroll-nav-btn:disabled { opacity: .35; cursor: default; }
@media (min-width: 881px) {
  .compare-scroll-nav:not([hidden]) { display: flex; }
}
/* overflow-x:auto here is needed so wide tables scroll sideways with the row-label column
   pinned via position:sticky;left:0 below — but per the CSS overflow spec, giving an element
   overflow-x anything other than visible forces overflow-y's *used* value to auto too, even
   if left unset. That default vertical scrollbox has no bounded height, so it never actually
   scrolls — which quietly breaks the header's position:sticky;top, since sticky positions
   relative to its nearest scrolling ancestor, and this element only ever *looks* like a
   non-scrolling one. Bounding the height and setting overflow-y:auto explicitly embraces
   that forced behavior instead of fighting it: the table now scrolls in its own contained
   viewport (like a spreadheet), and the sticky header pins correctly to *that*. */
/* The label column's width, in one place: the sticky offset for the left chevron, the table's
   min-width maths and scroll-padding-left all have to agree with it or the chevron parks in the
   wrong lane and snapped columns land underneath the labels. */
.compare-scroll {
  --compare-label-w: 180px;
  overflow-x: auto; overflow-y: auto; max-height: calc(100dvh - var(--topbar-h) - 130px);
  border: 1px solid var(--border-soft); border-radius: var(--radius-md);
}
.compare-table { border-collapse: collapse; width: 100%; min-width: 640px; }
.compare-table th, .compare-table td {
  padding: 10px 16px; text-align: left; border-bottom: 1px solid var(--border-soft); font-size: 13px; white-space: nowrap;
}
/* A vertical divider per car, from the header straight down through every data row, so each
   car reads as one clear lane instead of a value's owner being ambiguous mid-table. Excludes
   the first column (the sticky row-label column) on both the header row (an empty <th></th>)
   and every body row (the label <th>), so it only ever falls between car columns. */
.compare-table th:not(:first-child), .compare-table td:not(:first-child) {
  border-left: 1px solid var(--border-soft);
}
/* ...except on the group rows. A long group name ("Performance & Drivetrain") is wider than
   the label column on a phone even wrapped, and overflowed across the first divider. The cells
   to its right are empty, so there is nothing for it to collide with — dropping the dividers
   just here lets it run, and the row reads as one full-width section band. */
.compare-table tr.group-row td { border-left: none; }

/* Scroll chevrons. iOS hides overlay scrollbars while idle, so a phone had no standing cue that
   the table continued past the viewport edge; on desktop, clicking these is friendlier than
   finding a horizontal scrollbar parked at the bottom of a tall table. The group rows are the
   right home for them: they repeat the whole way down, so both the cue and the control stay on
   screen however far you have scrolled vertically, and their cells are empty so there is nothing
   to displace.

   Each rides in a sticky *cell* rather than an overlay, which is what glues it to the edge
   without needing a wrapper element around the scroll container. The right-hand one pins the last
   group cell to the scrollport's right edge; the left-hand one pins the first to the label
   column's width — deliberately not 0, so it parks beside the labels rather than underneath them
   (sticking these cells at 0 is exactly what once covered the group name up). Group cells share
   one background and have no dividers, so a shifted cell is invisible.

   `:first-of-type`/`:last-of-type`, not `:first-child`: the row's first child is the label `<th>`,
   so no `<td>` in it is ever a first child and the left chevron silently never matches. */
.compare-table tr.group-row td:first-of-type { position: sticky; left: var(--compare-label-w); }
.compare-table tr.group-row td:last-of-type { position: sticky; right: 0; text-align: right; }

.group-scroll-btn {
  display: none;
  border: none; background: transparent; cursor: pointer;
  color: var(--accent); opacity: .75;
  font-size: 13px; line-height: 1; letter-spacing: 0;
  /* Negative margin buys a comfortable tap target out of the group row's own padding rather
     than making the row taller. */
  padding: 8px 10px; margin: -8px 0;
}
.group-scroll-btn:hover { opacity: 1; }
/* `.can-scroll-*` come from updateCompareScrollNav, so a chevron only appears — and is only
   clickable — while there is actually more table that way. */
.compare-scroll.can-scroll-left .group-scroll-left,
.compare-scroll.can-scroll-right .group-scroll-right { display: inline-block; }
.compare-table thead th {
  position: sticky; top: 0; background: var(--bg-elevated); z-index: 5; vertical-align: top; padding-top: 14px;
}
/* The corner cell. Without `left: 0` here the header row's empty first cell scrolls away
   while the body's label column stays pinned, so a scrolled-past car's title slid into the
   label lane even though its data cells were correctly disappearing under it. z-index has to
   clear both the rest of the header (5) and the label column (4), since it overlaps both. */
.compare-table thead th:first-child { left: 0; z-index: 6; }
.compare-table tbody th {
  position: sticky; left: 0; background: var(--bg); z-index: 4; color: var(--text-dim); font-weight: 600; white-space: normal; min-width: var(--compare-label-w);
}
/* Marks a row where the cars actually differ, so there's a real winner to jump to — e.g. on
   a 5-car mobile comparison you can tap "Tow Capacity" instead of swiping through every
   column to find out which one wins. */
.row-label-clickable { cursor: pointer; transition: color .15s; }
.row-label-clickable:hover { color: var(--accent); }
/* Visual only — deliberately no `position: sticky` here. The group row is `<th>Group</th>`
   plus one empty `<td>` per car, and sticking the *tds* too pinned each empty cell to the
   scrollport's left edge, where the first car's cell (118px wide) painted straight over the
   88px label column and hid the group name. Being in the positioned layer, it also painted
   over the scroll container's scrollbar gutter. The `th` still sticks — it's a `tbody th`,
   so it picks that up from the rule above; this rule only recolors it. */
.compare-table tr.group-row th, .compare-table tr.group-row td {
  background: var(--bg-elevated); color: var(--accent); font-size: 11px; text-transform: uppercase; letter-spacing: .06em; font-weight: 700;
}
.compare-col-header { min-width: 200px; }
.compare-col-header-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
.compare-col-title { font-size: 14px; font-weight: 700; }
.compare-col-trim { font-size: 12px; color: var(--text-dim); margin-top: 2px; font-weight: 400; white-space: normal; }
.compare-col-price { font-size: 13px; color: var(--accent); font-weight: 700; margin-top: 6px; }
/* Price is always shown in accent green (matches the card grid), so highlighting the
   cheapest car the same way everything else is highlighted would be invisible — dim
   the others instead to make the cheapest one stand out by contrast. */
.compare-col-price-not-cheapest { color: var(--text-dim); font-weight: 600; }
.compare-col-remove {
  border: none; background: transparent; color: var(--text-faint); cursor: pointer; font-size: 16px; line-height: 1; flex-shrink: 0;
}
.compare-col-remove:hover { color: var(--danger); }
.cell-yes { color: var(--accent); font-weight: 700; }
.cell-no { color: var(--text-faint); }
.cell-empty { color: var(--text-faint); }
.cell-na { color: var(--text-faint); }
.cell-pending { color: var(--text-faint); font-style: italic; }
.cell-winner { color: var(--accent); font-weight: 700; }

.compare-table td a { color: var(--accent-2); text-decoration: none; margin-right: 10px; }
.compare-table td a:hover { text-decoration: underline; }

/* ---------- Compare bar ---------- */
.compare-bar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 40;
  display: flex; justify-content: center; padding: 16px;
  pointer-events: none;
}
.compare-bar[hidden] { display: none; }
.compare-bar-inner {
  pointer-events: auto;
  display: flex; align-items: center; gap: 18px;
  background: var(--bg-elevated); border: 1px solid var(--border);
  padding: 10px 12px 10px 18px; border-radius: 999px; box-shadow: var(--shadow);
  font-size: 13px; font-weight: 600;
}
.compare-bar-actions { display: flex; gap: 8px; }

/* ---------- Modal ---------- */
.modal-backdrop {
  position: fixed; inset: 0; background: rgba(6,8,12,0.6); backdrop-filter: blur(3px);
  display: flex; align-items: center; justify-content: center; z-index: 50; padding: 24px;
}
.modal-backdrop[hidden] { display: none; }
.modal {
  background: var(--bg-elevated); border: 1px solid var(--border); border-radius: var(--radius-lg);
  max-width: 640px; width: 100%; max-height: 86vh; position: relative;
  box-shadow: var(--shadow);
  /* Scrolling happens on .modal-body, not here — a native scrollbar drawn directly on an
     overflow:auto element doesn't reliably clip to a rounded corner in every browser (it can
     poke out past the curve). Clipping it here with overflow:hidden on the outer box fixes it
     regardless of how any particular browser draws the inner scrollbar. */
  overflow: hidden;
  display: flex; flex-direction: column;
}
.modal-body {
  /* No top padding: .modal-head supplies it instead, so the head's natural position starts flush
     with the scrollport. Give it a negative top margin here instead and `position: sticky` pins
     it immediately at rest, shifting it down visually while layout keeps it higher up — which
     slides the following paragraph underneath the header. */
  overflow-y: auto; padding: 0 28px 28px;
  min-height: 0; /* let this flex child actually shrink and scroll instead of stretching .modal */
}
/* z-index has to beat .modal-head's. Both are positioned elements and .modal-body creates no
   stacking context (overflow alone doesn't), so they compete directly — and the sticky head,
   which bleeds the full width of the modal and covers this corner, was painting straight over
   the button. It stayed clickable-in-theory but was invisible and unhittable, leaving
   backdrop-tap as the only way out of the modal on a phone. */
.modal-close {
  position: absolute; top: 16px; right: 16px; z-index: 2;
  border: none; background: var(--bg-card); color: var(--text-dim);
  width: 30px; height: 30px; border-radius: 50%; font-size: 18px; cursor: pointer; line-height: 1;
}
.modal-close:hover { color: var(--text); }
.modal-title { font-size: 20px; font-weight: 700; margin-bottom: 2px; }
.modal-header-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 2px; }
.modal-header-row .modal-title { margin-bottom: 0; }
.icon-btn { display: inline-flex; align-items: center; gap: 6px; flex-shrink: 0; }
/* Sticks to the top of .modal-body's scrollport so the car's identity stays on screen the whole
   way down. Bleeds horizontally over .modal-body's 28px padding and re-adds it, so the opaque
   background covers the full width and specs scroll cleanly underneath rather than beside it.
   .modal-close sits above this (z-index 2): being outside the scroll box does not put it on top,
   since both are positioned in the same stacking context and this one raised itself to 1. */
.modal-head {
  position: sticky; top: 0; z-index: 1;
  background: var(--bg-elevated);
  margin: 0 -28px 14px; padding: 24px 28px 10px;
  border-bottom: 1px solid var(--border-soft);
}
/* One row, so the pinned block stays two lines tall. Wraps on a narrow phone rather than
   letting a long trim name squeeze the price. */
.modal-subline { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; }
.modal-trim { color: var(--text-dim); font-size: 14px; }
.modal-price { font-size: 18px; color: var(--accent); font-weight: 700; margin-left: auto; }
/* No negative top margin: that used to close the gap under the old standalone .modal-price
   block, but the price now lives in the sticky .modal-head, so pulling up here slid the
   summary's first line underneath the pinned header. */
.modal-summary { font-size: 14px; color: var(--text-dim); line-height: 1.5; margin: 0 0 18px; }
.modal-section { margin-bottom: 18px; }
.modal-section h4 { font-size: 11.5px; text-transform: uppercase; letter-spacing: .06em; color: var(--text-faint); margin-bottom: 8px; }
.modal-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 8px 16px; }
.modal-row { display: flex; justify-content: space-between; font-size: 13px; gap: 10px; border-bottom: 1px dashed var(--border-soft); padding-bottom: 4px; }
.modal-row .k { color: var(--text-dim); }
.modal-row .v { font-weight: 600; text-align: right; }
.modal-links { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 8px; }
.modal-links a {
  font-size: 12.5px; color: var(--accent-2); text-decoration: none; border: 1px solid var(--border); padding: 6px 12px; border-radius: 999px;
}
.modal-links a:hover { border-color: var(--accent-2); }
.modal-actions { display: flex; gap: 10px; margin-top: 20px; margin-bottom: 26px; }

/* ---------- Similar vehicles (in detail modal) ---------- */
.similar-hint { font-size: 12.5px; color: var(--text-faint); margin: -4px 0 10px; }
.similar-grid { display: flex; flex-direction: column; gap: 10px; }
.similar-card {
  background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 12px; cursor: pointer; transition: border-color .15s, transform .15s;
}
@media (hover: hover) {
  .similar-card:hover { border-color: var(--accent-dim); transform: translateY(-1px); }
}
.similar-card-top { display: flex; gap: 10px; align-items: flex-start; }
.similar-card-icon {
  width: 30px; height: 30px; border-radius: 8px; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: linear-gradient(145deg, color-mix(in srgb, var(--accent) 22%, transparent), color-mix(in srgb, var(--accent-2) 18%, transparent));
}
.similar-card-title { font-size: 13.5px; font-weight: 700; }
.similar-card-trim { font-size: 12px; color: var(--text-dim); margin-top: 1px; }
.similar-card-price {
  font-size: 13px; font-weight: 700; color: var(--accent); margin-top: 8px;
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
}
.similar-price-delta { font-size: 11.5px; font-weight: 500; color: var(--text-faint); }
.similar-diff-list { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.diff-badge {
  font-size: 11px; padding: 3px 8px; border-radius: 999px; border: 1px solid var(--border); color: var(--text-dim);
}
.diff-badge.diff-gain { color: var(--accent); border-color: color-mix(in srgb, var(--accent) 45%, var(--border)); }
.diff-badge.diff-loss { color: var(--text-faint); }

/* Bleeds to the modal's edges (negating .modal-body's own padding) so this reads as a
   distinct footer zone rather than blending into the spec rows above it. */
.similar-section {
  margin: 28px -28px -28px; padding: 20px 28px 28px;
  background: var(--bg-card); border-top: 1px solid var(--border);
}
.similar-section h4 { color: var(--accent); margin-bottom: 0; }
.similar-section-header {
  display: flex; align-items: center; justify-content: space-between; gap: 10px 14px;
  flex-wrap: wrap; margin-bottom: 8px;
}

/* ---------- Scrollbars ---------- */
::-webkit-scrollbar { width: 10px; height: 10px; }
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 999px; }
::-webkit-scrollbar-track { background: transparent; }
/* The little square where a horizontal and vertical scrollbar meet (e.g. bottom-right of the
   compare table) isn't covered by the rules above and defaults to a stark white/UA-styled
   box otherwise. */
::-webkit-scrollbar-corner { background: var(--bg-elevated); }

/* ---------- Responsive ---------- */
@media (max-width: 880px) {
  .layout { flex-direction: column; }
  .content { padding: 16px; }

  /* The card's compare checkbox, 50% up from 15px. Tapping a card opens its detail view, and
     at 15px a thumb aimed at the box lands outside it often enough to be annoying — you get
     the modal instead of a tick. The <label> already swallows the click for its whole area
     (text included), so the padding here is the part that actually widens the safe zone; the
     negative margin spends the card footer's existing slack rather than making cards taller. */
  .ev-card-add input { width: 22px; height: 22px; }
  .ev-card-add { padding: 6px 8px 6px 0; margin: -6px 0; }

  /* The topbar used to wrap to three rows here — 149px of a 812px phone viewport (18%),
     permanently sticky. Everything below collapses it to one row (~56px): the wordmark
     drops to just the mark, search shares the row instead of claiming its own, and Reset
     goes away because the active-filter chips above the results now carry both the
     "what's applied" signal and a Clear all. The vehicle count moved next to the sort
     control (see .results-toolbar), which is where it belongs at every width. */
  .topbar { flex-wrap: nowrap; gap: 10px; padding: 11px 12px; }
  .brand-name { display: none; }
  .topbar-search { max-width: none; flex: 1 1 auto; min-width: 0; }
  .topbar-actions { gap: 8px; }
  #resetFiltersBtn { display: none; }

  /* --topbar-h covers the header itself; this subtracts the compare header + content
     padding above the table, which is tighter here than on desktop. */
  .compare-scroll { max-height: calc(100dvh - var(--topbar-h) - 110px); }

  /* Keep the sticky header (inherited from the base rule), but compact it: the icon is
     decorative, and price is already visible before you scroll — dropping just those two
     saves real space. Trim and the View details button stay: trim is often the only thing
     distinguishing two rows of the same model (e.g. two Mach-E trims), and losing the
     button while scrolled meant no way to reach full detail without scrolling back to the
     top first. */
  .compare-col-icon,
  .compare-table thead .compare-col-price {
    display: none;
  }
  .compare-header h2 { font-size: 16px; }
  .compare-col-header { padding-top: 10px; padding-bottom: 10px; }
  .compare-col-trim { margin-top: 1px; }
  .compare-col-view-btn { margin-top: 6px; padding: 4px 8px; font-size: 11px; }

  /* iOS Safari auto-zooms the page on focusing any input with font-size below 16px. */
  .topbar-search input, .enum-filter-input { font-size: 16px; }

  /* Filter sidebar becomes an off-canvas drawer instead of stacking above results,
     so the results are visible immediately instead of below a long filter list. */
  .filters-toggle-btn { display: inline-flex; }

  .sidebar {
    position: fixed; top: 0; left: 0; z-index: 60;
    width: min(320px, 86vw); height: 100dvh;
    border-right: 1px solid var(--border-soft); border-bottom: none;
    transform: translateX(-100%);
    transition: transform .25s ease;
    padding-bottom: 24px;
  }
  .sidebar.open { transform: translateX(0); }

  .sidebar-close {
    display: inline-flex; align-items: center; justify-content: center;
    width: 30px; height: 30px; border-radius: 50%; border: none;
    background: var(--bg-card); color: var(--text-dim); font-size: 18px; cursor: pointer; line-height: 1;
  }
  .sidebar-close:hover { color: var(--text); }

  .sidebar-backdrop {
    display: block; position: fixed; inset: 0; z-index: 55;
    background: rgba(6,8,12,0.5);
  }
  .sidebar-backdrop[hidden] { display: none; }

  body.sidebar-open-lock { overflow: hidden; }

  /* Compare table: the sticky label column at desktop width (180-200px) eats most of a
     phone viewport, leaving almost no room for car data. Shrink it so at least one full
     car column is readable, with more reachable via horizontal scroll. */
  /* table-layout:auto (the default) sizes columns off cell content, so a min-width alone
     gets overridden by the widest label ("Collision Avoidance Auto-Brake", etc). Switch to
     fixed layout with explicit widths so the label column actually stays narrow. */

  /* Two cars is the common comparison, and on a phone the table came out ~1.25x the
     viewport: the container then scrolled on both axes at once and a drag drifted
     diagonally, which is disorienting.

     A car column is exactly half the space left over after the 88px label column, so the
     whole table is 88px + N half-widths. That one relationship gives all the behavior:
     two cars come to exactly 100% and there is no horizontal scroll at all; three or more
     overflow by a whole number of car columns, so every snap point below is one car wide
     and a swipe pages cleanly from cars 1-2 to 2-3. Picking a fixed column width instead
     (118px) is what made three cars scroll by a useless 111px — less than one column, so
     there was nothing to snap to and the drag just floated. --compare-cars is set on the
     table by renderCompareTable, since CSS can't branch on how many columns exist. */
  .compare-table {
    table-layout: fixed;
    min-width: calc(var(--compare-label-w) + var(--compare-cars, 2) * (100% - var(--compare-label-w)) / 2);
  }
  .compare-table th, .compare-table td { padding: 8px 10px; font-size: 12px; }
  .compare-table thead th:first-child,
  .compare-table tbody th { width: var(--compare-label-w); min-width: 0; max-width: none; }
  /* No width on the car columns: under fixed layout they split whatever is left over
     evenly, which is what keeps two of them flush with the viewport edge. The base
     min-widths have to be cleared or they'd re-impose the old 150px floor. */
  .compare-col-header, .compare-table thead th:not(:first-child) { width: auto; min-width: 0; }
  .compare-col-title { font-size: 13px; white-space: normal; }
  .compare-col-view-btn { white-space: normal; }
  .compare-table tr.group-row th, .compare-table tr.group-row td { font-size: 10px; }
  /* Snap so a swipe lands on a column edge rather than anywhere in between. `mandatory`,
     not `proximity`: proximity is advisory and iOS simply declined to snap most gestures,
     which was the whole complaint. It only constrains the x axis, so vertical scrolling is
     untouched — a mostly-vertical drag just keeps whatever column it was already snapped to.
     scroll-padding-left keeps a snapped column clear of the sticky label column instead of
     sliding underneath it, and overscroll-behavior-x stops a horizontal swipe that runs out
     of table from chaining into the browser's back gesture. */
  .compare-scroll {
    --compare-label-w: 88px;
    scroll-snap-type: x mandatory;
    scroll-padding-left: var(--compare-label-w);
    overscroll-behavior-x: contain;
  }
  /* Every car cell is a snap target, not just the header. Chromium only considers x-snap
     areas that also intersect the scrollport *vertically*, so with the header cells as the
     only targets, snapping worked at the top of the table and silently stopped once you
     scrolled down past them — confirmed by A/B: header-only snaps 70 to 122 at scrollTop 0
     and leaves it at 70 by scrollTop 900. Body cells run the table's whole height, and every
     cell in a column shares the same x, so they all resolve to the same snap position. */
  .compare-table thead th:not(:first-child),
  .compare-table tbody td { scroll-snap-align: start; }
}

/* ---------- Documentation pages ---------- */
/* /data/ is a standalone document, not the app: no sidebar, no grid, just prose at a
   readable measure. Shares the same tokens so it can't drift from the app's theming. */
.doc { max-width: 720px; margin: 0 auto; padding: 28px 24px 72px; }
.doc h1 { font-size: 27px; margin-bottom: 14px; letter-spacing: -0.02em; }
.doc h2 { font-size: 17px; margin: 32px 0 10px; }
.doc h3 { font-size: 13px; margin: 0 0 3px; color: var(--accent); }
.doc p { font-size: 14.5px; line-height: 1.65; color: var(--text-dim); margin: 0 0 12px; }
.doc p strong { color: var(--text); font-weight: 650; }
.doc a { color: var(--accent-2); }
.doc code {
  font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12.5px;
  background: var(--bg-card); border: 1px solid var(--border-soft);
  border-radius: 5px; padding: 1px 5px; color: var(--text);
}
.doc-lede { font-size: 15.5px !important; }
.doc-table { width: 100%; border-collapse: collapse; margin: 4px 0 8px; }
.doc-table th, .doc-table td { text-align: left; padding: 9px 10px; border-bottom: 1px solid var(--border-soft); font-size: 13.5px; vertical-align: top; }
.doc-table th { font-size: 11px; text-transform: uppercase; letter-spacing: .05em; color: var(--text-faint); font-weight: 700; }
.doc-table td { color: var(--text-dim); }
.doc-table td:first-child { white-space: nowrap; }
.doc-pre {
  background: var(--bg-card); border: 1px solid var(--border-soft); border-radius: var(--radius-sm);
  padding: 13px 15px; overflow-x: auto; margin: 0 0 12px;
}
.doc-pre code { background: none; border: none; padding: 0; font-size: 12.5px; line-height: 1.6; color: var(--text-dim); }
.ds-groups { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 14px 22px; margin-bottom: 8px; }
.ds-group p { font-size: 13px; line-height: 1.55; margin: 0; }
.doc-link { font-size: 12.5px; color: var(--text-dim); }
.doc-link a { color: var(--accent-2); }
.doc-back { margin-top: 34px; padding-top: 18px; border-top: 1px solid var(--border-soft); }
/* The doc topbar is a single brand link, so it needs the anchor reset the <button> version got. */
.topbar a.brand { text-decoration: none; color: inherit; }

/* ---------- Reduced motion ---------- */
/* Last in the file so it wins over every transition/animation declared above, including the
   ones inside the media queries. Keeps `forwards`-style end states intact by collapsing
   durations rather than setting `animation: none`, and leaves opacity/color changes to
   happen instantly rather than removing the feedback altogether. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  .ev-card:hover, .similar-card:hover { transform: none; }
}
