/* app-core.css — the fiddle web client's stylesheet.

   Lifted VERBATIM out of public/desktop.html alongside app-core.js, so the shell
   and every route it hosts share one stylesheet instead of a copy of it. 273
   class names live in here; a second shell redefining even the generic ones
   (.on, .sub, .empty, .brand) is how two designs start overwriting each other.
   Route-specific styling belongs in its own file, namespaced under that route.
*/
/* ═══════════════════════════════════════════════════════════════════════════
   DESKTOP SHELL · PROTOTYPE
   -------------------------------------------------------------------------
   A throwaway shell for evaluating the web-side layout BEFORE the extraction
   work in index.html starts. Deliberately standalone: it shares nothing with
   index.html except tokens.css, so nothing here can break the shipping app.

   What it proves out:
     · left nav rail replacing the bottom tab bar (Instagram web)
     · a width-constrained centre column instead of a full-bleed phone screen
     · a right rail carrying what the phone flow has nowhere to put
     · modal-over-feed routing with real URLs and a working back button
     · desktop affordances — hover, focus rings, keyboard shortcuts

   It reads the REAL /api/feed and /api/events (both ungated today), so the
   cards below are production data, not lorem. If the endpoints are
   unreachable it drops to a clearly-labelled demo set so the layout is still
   reviewable offline.
   ═══════════════════════════════════════════════════════════════════════════ */

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

:root {
  --rail-w: 244px;
  --feed-w: 630px;
  --aside-w: 320px;
  --gutter: 32px;
  --page-max: 1180px;   /* ONE page container width — chrome and content share it */
  --page-pad: 24px;
  --bar-h: 66px;        /* topbar height, so sticky offsets below can reference it */
  /* Anything else that sticks has to clear the header rather than slide under it.
     Derived, so changing --bar-h moves them all together. */
  --sticky-top: calc(var(--bar-h) + 16px);

  /* ── glass material · the §9 structural pass ──────────────────────────────
     Two grades. CHROME (topbar, rail, dropdowns, modal, drawers) is true glass:
     translucent fill + backdrop-filter, because content really slides under it.
     CARDS get the look — translucent fill, hairline, lit top edge — but NO
     backdrop-filter: dozens of them scroll in the feed, and per-card blur is
     the canonical way to make a feed drop frames. Behind a card is only the
     page substrate anyway, so the frost would blur an almost-flat wash. */
  /* The elevation ladder, dark-UI convention: lighter = closer, and the
     lightest surfaces are RESERVED for what deserves attention. Five steps,
     each a visible luminance jump over the one below:
       rail (deepest chrome, recedes) < canvas < quiet panels (aside)
       < content cards (the emphasis) < overlays (dropdowns, modal).       */
  --glass-quiet:  color-mix(in srgb, var(--surface-solid-1) 64%, transparent);
  --glass-card:   color-mix(in srgb, var(--surface-solid-1) 88%, transparent);
  --glass-card-2: color-mix(in srgb, var(--surface-solid-2) 88%, transparent);
  --glass-over:   color-mix(in srgb, var(--surface-solid-2) 84%, transparent);
  --glass-hi:     inset 0 1px 0 rgba(255,255,255,0.055);   /* lit top edge     */
  --frost:        blur(var(--blur-glass)) saturate(1.35);
  --frost-heavy:  blur(var(--blur-heavy)) saturate(1.25);
}

html, body { margin: 0; padding: 0; }
/* Scrollbar ALWAYS present, not merely gutter-reserved. Short routes (Parts, Search)
   would otherwise have no scrollbar while tall ones (Paddock, Garage) do, changing the
   viewport width by ~15px between tabs and sliding everything sideways. overflow-y:
   scroll pins it on every route; scrollbar-gutter backs it up where the scrollbar is
   an overlay rather than a classic one. */
html { overflow-y: scroll; scrollbar-gutter: stable; }
body {
  /* Substrate for the glass: frost is invisible over a flat solid, so the page
     carries the §10 two-light wash — teal upper-left, info blue upper-right —
     just strong enough for translucent surfaces to pick up. Fixed, so the
     light sources hold still while the feed scrolls through them.
     (This is NOT the retired foot-glow: no accent band, nothing at the foot.) */
  background:
    radial-gradient(1100px 640px at 18% -8%, rgba(51,230,196,0.055), transparent 62%),
    radial-gradient(860px 560px at 96% 2%, rgba(78,167,255,0.05), transparent 64%),
    var(--bg);
  background-attachment: fixed;
  background-repeat: no-repeat;
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; }
button { font: inherit; color: inherit; cursor: pointer; }
/* Anchors that stand in for buttons: the element changed for crawlers and ⌘-click,
   the appearance must not. */
a.card-open, a.bcard, a.grow, a.act-btn, a.rowlink { text-decoration: none; color: inherit; cursor: pointer; }
:focus-visible { outline: var(--focus-ring); outline-offset: 2px; border-radius: 6px; }

.mono { font-family: var(--font-mono); }
.eyebrow {
  font-family: var(--font-mono); font-size: var(--fs-xs); font-weight: 500;
  letter-spacing: var(--track-eyebrow); text-transform: uppercase; color: var(--faint);
}


/* ── left nav rail ────────────────────────────────────────────────────────── */
#rail {
  position: fixed; top: 0; bottom: 0; left: 0; width: var(--rail-w); z-index: 40;
  display: flex; flex-direction: column; gap: 4px;
  padding: 22px 12px 16px;
  background: color-mix(in srgb, var(--p-void-0) 62%, transparent);
  -webkit-backdrop-filter: var(--frost); backdrop-filter: var(--frost);
  border-right: 1px solid var(--line);
  box-shadow: var(--glass-hi);
}
#rail .wm { padding: 6px 12px 26px; display: block; }
#rail .wm svg { width: 92px; height: auto; color: var(--ink); fill: currentColor; }
#rail .wm .mark { display: none; }

.nav-item {
  display: flex; align-items: center; gap: 15px;
  padding: 11px 12px; border: none; background: transparent;
  border-radius: 10px; width: 100%; text-align: left;
  color: var(--muted); transition: background .13s, color .13s;
}
.nav-item svg { flex: 0 0 24px; }
.nav-item .lbl { font-size: var(--fs-ui); white-space: nowrap; }
.nav-item:hover { background: var(--surface-solid-1); color: var(--ink); }
.nav-item.on { color: var(--ink); }
.nav-item.on .lbl { font-weight: var(--fw-semibold); }
.nav-item.on svg { color: var(--accent); }
.nav-spacer { flex: 1; }

/* ── rail groups ──────────────────────────────────────────────────────────
   Four destinations, a rule, then the personal shelf. The rule is what makes the
   four read as a closed set rather than as the top of a longer list — without it
   the shelf below looks like two more places to go. */
.nav-sep { height: 1px; background: var(--line); margin: 14px 4px 10px; }

.nav-sec {
  display: flex; align-items: center; gap: 8px; width: 100%;
  padding: 5px 12px 7px; border: none; background: transparent;
  color: var(--faint); text-align: left;
}
.nav-sec:hover { color: var(--muted); }
.nav-sec .lbl { flex: 1; }              /* .eyebrow supplies the type */
.nav-sec .chev { flex: 0 0 14px; transition: transform .15s; }
.nav-sec[aria-expanded="false"] .chev { transform: rotate(-90deg); }

.nav-sub {
  display: flex; align-items: center; gap: 12px;
  padding: 8px 12px; border: none; background: transparent;
  border-radius: 9px; width: 100%; text-align: left;
  color: var(--muted); transition: background .13s, color .13s;
}
.nav-sub:hover { background: var(--surface-solid-1); color: var(--ink); }
.nav-sub.on { color: var(--ink); }
/* the dot carries the active state at icon size, so the collapsed rail keeps it */
.nav-sub .dot { flex: 0 0 7px; height: 7px; border-radius: 50%; background: var(--surface-solid-3); }
.nav-sub.on .dot { background: var(--accent); box-shadow: 0 0 0 3px var(--accent-a14); }
.nav-sub .lbl { font-size: var(--fs-ui); min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ── account card ─────────────────────────────────────────────────────────
   A card, not a fifth nav item: it is not a destination the way the four above
   are, and the border is what stops the rail ending on a ragged row. */
.acct {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 9px 10px; border: 1px solid var(--line); border-radius: 12px;
  background: var(--surface-solid-1); color: var(--muted); text-align: left;
  transition: background .13s, border-color .13s;
}
.acct:hover { background: var(--surface-solid-2); border-color: var(--line-2); }
.acct .av { width: 30px; height: 30px; flex: 0 0 30px; font-size: 11px; }
.acct .who { flex: 1; min-width: 0; }
.acct .who b, .acct .who span { display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.acct .who b { font-size: var(--fs-ui); font-weight: var(--fw-semibold); color: var(--ink); }
.acct .who span { font-size: var(--fs-xs); color: var(--faint); }
.acct .cog { flex: 0 0 16px; opacity: .65; }

/* ── main column ──────────────────────────────────────────────────────────── */
#main { margin-left: var(--rail-w); min-height: 100vh; }
.main-inner {
  display: flex; gap: var(--gutter); justify-content: flex-start;
  padding: 26px var(--page-pad) 90px; margin: 0 auto;
  max-width: var(--page-max);
}
/* The content block FILLS the page container rather than sitting centred inside it.
   Centring left ~75px of slack per side, so the body sat visibly inset from the search
   field and Sign in above it. Filling puts the column edges on the header's verticals.

   On routes with no aside the feed simply takes the whole container. On the Paddock it
   does NOT: the feed holds its 630px reading measure and the ASIDE absorbs the slack
   instead (see the data-route="paddock" rules below), so alignment and a narrow feed
   both hold. The aside runs wider than a conventional 320px rail as a result — that is
   the price, and it buys back the two-line wrapping the narrow rail forced on longer
   car names. */
#feed-col { flex: 1 1 auto; min-width: 0; }
#aside-col { flex: 0 0 var(--aside-w); width: var(--aside-w); position: sticky; top: var(--sticky-top); align-self: flex-start; }

/* ── Paddock: feed pinned to its reading measure, aside takes the remainder ──
   flex on the aside rather than a hardcoded 470px, so the slack is absorbed at
   whatever container width the viewport gives us instead of only at --page-max. */
#main[data-route="paddock"] #feed-col { flex: 0 0 var(--feed-w); }
#main[data-route="paddock"] #aside-col { flex: 1 1 auto; width: auto; min-width: var(--aside-w); }

/* ── top bar ──────────────────────────────────────────────────────────────
   Full-bleed across #main so the bar itself never changes width; its contents
   sit in the shared --page-max container so they line up with the content
   below on every route. Height is fixed by --bar-h rather than left to the
   tallest child, which is what made it grow and shrink between routes.      */
#topbar {
  position: sticky; top: 0; z-index: 35; height: var(--bar-h);
  background: color-mix(in srgb, var(--bg) 72%, transparent);
  -webkit-backdrop-filter: var(--frost); backdrop-filter: var(--frost);
  border-bottom: 1px solid var(--line);
  box-shadow: var(--glass-hi), 0 12px 32px rgba(0,0,0,0.22);
}
.topbar-in {
  height: 100%; max-width: var(--page-max); margin: 0 auto;
  padding: 0 var(--page-pad); display: flex; align-items: center; gap: 14px;
}
#topbar .wm-sm { display: none; }
/* fixed basis, not flex:1 — a search field that resized with the column was half
   the reason the bar looked unstable when the route changed */
.search-wrap { position: relative; flex: 0 1 440px; min-width: 0; }
.search-wrap svg { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--faint); pointer-events: none; }
/* ── FEDERATED SEARCH PANEL ───────────────────────────────────────────────
   The scope chip is gone: a field that searches everything needs no label
   saying which one thing it searches. Grouping in the results does that job
   better, because it shows what was found rather than promising a corpus.  */
#gpanel {
  /* Hangs below the HEADER, not the field. The field is 42px centred in a --bar-h bar,
     so there is (--bar-h - 42) / 2 of bar left beneath it; clearing that plus 8px stops
     the panel punching up through the header's bottom border and blur. */
  position: absolute; top: calc(100% + (var(--bar-h) - 42px) / 2 + 8px);
  left: 0; width: min(600px, 78vw);
  max-height: min(560px, 70vh); overflow-y: auto; z-index: 60; display: none;
  /* Denser than --glass-over: this panel floats over the text-dense feed, and
     at 84% the headlines behind ghosted through legibly enough to clash with
     the results. 94% keeps a whisper of frost without the crosstalk. */
  background: color-mix(in srgb, var(--surface-solid-2) 94%, transparent);
  -webkit-backdrop-filter: var(--frost-heavy); backdrop-filter: var(--frost-heavy);
  border: 1px solid var(--line-2); border-radius: 13px;
  box-shadow: var(--glass-hi), 0 24px 70px rgba(0,0,0,0.62); padding: 6px;
}
#gpanel.on { display: block; animation: drop-in var(--dur-fast) var(--ease-out); }
.ggroup + .ggroup { border-top: 1px solid var(--line); margin-top: 5px; padding-top: 5px; }
.ghead {
  display: flex; align-items: center; gap: 8px; padding: 9px 11px 7px;
  font-family: var(--font-mono); font-size: var(--fs-2xs); letter-spacing: var(--track-caps);
  text-transform: uppercase; color: var(--faint);
}
.ghead .n { flex: 1; }
.ghead .cnt { color: var(--muted); }
.ghead .spin {
  width: 11px; height: 11px; border-radius: 50%; border: 1.5px solid var(--line-2);
  border-top-color: var(--accent); animation: gspin .7s linear infinite;
}
@keyframes gspin { to { transform: rotate(360deg); } }
.grow {
  display: flex; align-items: center; gap: 11px; width: 100%; text-align: left;
  padding: 9px 11px; border: none; background: transparent; border-radius: 9px;
}
.grow:hover, .grow.sel { background: var(--surface-solid-3); }
.grow.sel { box-shadow: inset 0 0 0 1px var(--accent-a48); }
.grow .gi {
  position: relative; width: 32px; height: 32px; flex: 0 0 32px; border-radius: 8px;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-solid-2); border: 1px solid var(--line); overflow: hidden;
}
/* The glyph is always in the DOM underneath; the img covers it and removes itself on
   error, so a dead harvested URL degrades to the glyph, never a broken-image icon.
   Same contract as .pthumb in index.html. */
.grow .gi img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.grow .gt { flex: 1; min-width: 0; }
/* display:block is load-bearing on BOTH. They are spans, so they were inline boxes:
   inline boxes ignore width, overflow and text-overflow, so the title and the subtitle
   ran side by side on one line and neither ever ellipsised — the panel scrolled
   sideways instead. Blocks stack, and the truncation below then actually applies. */
.grow .gt .t1 { display: block; font-size: var(--fs-ui); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.grow .gt .t2 { display: block; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--faint);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin-top: 2px; }
.grow .gb { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--muted); flex: 0 0 auto; }
.gall {
  display: flex; align-items: center; gap: 7px; width: 100%; padding: 8px 11px;
  border: none; background: transparent; border-radius: 9px;
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--accent);
}
.gall:hover, .gall.sel { background: var(--accent-a07); }
.gall.sel { box-shadow: inset 0 0 0 1px var(--accent-a48); }
/* Suggested searches. Deliberately a lighter row than .grow: a suggestion is a phrase,
   not a thing, so it carries no 32px thumbnail well, no subtitle and no badge — and
   without the thumbnail it must not sit at the thumbnail's indent either, or the panel
   reads as one list with a column of holes punched in it. The magnifier glyph is the
   whole left-hand affordance, at the size of the one in the field above. */
.gsug {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 8px 11px; border: none; background: transparent; border-radius: 9px;
  color: var(--text); font-size: var(--fs-ui);
}
.gsug:hover, .gsug.sel { background: var(--surface-solid-3); }
.gsug.sel { box-shadow: inset 0 0 0 1px var(--accent-a48); }
.gsug svg { flex: 0 0 auto; color: var(--faint); }
.gsug .gq { flex: 1; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* The half you already typed recedes; the completion is the new information and holds
   the weight. Inverted from the usual highlight-the-match, on purpose — the match is
   the part you can already see in the field. */
.gsug .gq .typed { color: var(--muted); font-weight: 400; }
.gsug .gq b { font-weight: 600; }
.gempty { padding: 26px 14px; text-align: center; color: var(--faint);
  font-family: var(--font-mono); font-size: var(--fs-sm); line-height: 1.8; }
.gfoot {
  display: flex; align-items: center; gap: 14px; padding: 9px 12px 5px; margin-top: 4px;
  border-top: 1px solid var(--line); font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--faint);
}
.gfoot kbd { border: 1px solid var(--line); border-radius: 4px; padding: 1px 5px; margin-right: 4px; }

/* ── entry form ── */
.ef { margin-bottom: 14px; }
.ef-l { display: block; font-family: var(--font-mono); font-size: var(--fs-2xs); text-transform: uppercase;
  letter-spacing: var(--track-caps); color: var(--faint); margin-bottom: 6px; }
.ef-i {
  width: 100%; padding: 11px 13px; font: inherit; font-size: var(--fs-ui);
  background: var(--surface-solid-2); border: 1px solid var(--line); border-radius: 10px;
  color: var(--ink); outline: none; resize: vertical;
}
.ef-i:focus { border-color: var(--accent-a48); background: var(--surface-solid-3); }
.ef-i::placeholder { color: var(--faint); }
select.ef-i { appearance: none; cursor: pointer; }
.ef-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.ef-row:has(.ef:only-child) { grid-template-columns: 1fr; }
.ef-seg { display: flex; gap: 4px; padding: 3px; background: var(--surface-solid-2);
  border: 1px solid var(--line); border-radius: 11px; margin-bottom: 14px; }
.ef-seg button { flex: 1; padding: 8px 6px; border: none; border-radius: 8px; background: transparent;
  color: var(--faint); font-family: var(--font-mono); font-size: var(--fs-sm); }
.ef-seg button.on { background: var(--accent-a14); color: var(--accent); }
.ef-seg button:hover:not(.on) { color: var(--ink); }
.ef-check { display: flex; align-items: center; gap: 9px; margin: 4px 0 18px; font-size: var(--fs-md); color: var(--muted); cursor: pointer; }
.ef-check input { width: 16px; height: 16px; accent-color: var(--accent); }
.ef-actions { display: flex; gap: 10px; }
.ef-actions .gar-cta { margin-bottom: 0; }
/* The part panel's actions are a ROW. .gar-cta is width:100% because everywhere
   else it is a full-bleed sheet button, and inside a wrapping flex row that put
   every action on a line of its own — a stack taller than the part it belongs to,
   and taller again once the search route's "log it" made four of them. A flex
   basis lets them share the row here and wrap in pairs when the window is narrow. */
#pp-acts .gar-cta { flex: 1 1 140px; width: auto; min-width: 0; }

/* ── publish sheet ── */
.pub-row { display: flex; align-items: flex-start; gap: 11px; padding: 11px 0; cursor: pointer; }
.pub-row + .pub-row { border-top: 1px solid var(--line); }
.pub-row input { width: 17px; height: 17px; margin-top: 2px; flex: 0 0 17px; accent-color: var(--accent); }
.pub-t { flex: 1; min-width: 0; }
.pub-t .t { display: block; font-size: var(--fs-ui); }
.pub-t .s { display: block; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--faint); margin-top: 3px; line-height: 1.6; }

/* an active query carried into a page shows as a removable chip in its header */
.qchip {
  display: inline-flex; align-items: center; gap: 7px; padding: 4px 8px 4px 11px; border-radius: 999px;
  background: var(--accent-a14); border: 1px solid var(--accent-a34); color: var(--accent);
  font-family: var(--font-mono); font-size: var(--fs-xs);
}
.qchip button { border: none; background: none; color: inherit; padding: 0 2px; line-height: 1; opacity: .75; }
.qchip button:hover { opacity: 1; }

@media (max-width: 767px) {
  #gpanel { position: fixed; left: 8px; right: 8px; width: auto; top: 62px; }
}
#q {
  width: 100%; padding: 9px 12px 9px 36px;
  background: var(--surface-solid-1); border: 1px solid var(--line);
  border-radius: 10px; color: var(--ink); font: inherit; font-size: var(--fs-ui); outline: none;
}
#q::placeholder { color: var(--faint); }
#q:focus { border-color: var(--accent-a48); background: var(--surface-solid-2); }
/* No "/" badge in the field. The SHORTCUT it advertised is untouched — the keydown
   handler still focuses search on "/" from anywhere outside an input; only the hint
   sitting in the field is gone. */
.tb-spacer { flex: 1; }
.tb-btn {
  display: flex; align-items: center; gap: 8px; padding: 8px 14px;
  border-radius: 9px; border: 1px solid var(--line); background: var(--surface-solid-1);
  font-size: var(--fs-md);
}
.tb-btn:hover { border-color: var(--line-2); background: var(--surface-solid-2); }
.tb-btn.primary { background: var(--accent); border-color: var(--accent); color: var(--on-accent); font-weight: var(--fw-semibold); }
.tb-btn.primary:hover { background: var(--accent-a65); }

/* ── compact-viewport chrome ──────────────────────────────────────────────
   Desktop keeps the account in the rail card; once the rail is gone the
   avatar surfaces in the bar. At the smallest phones the search field
   collapses to an icon and reopens as an overlay hanging under the bar. */
.tb-av {
  display: none; width: 36px; height: 36px; flex: 0 0 36px; border-radius: 50%;
  border: 1px solid var(--line); background: var(--surface-solid-2); color: var(--accent);
  font-family: var(--font-mono); font-size: var(--fs-ui); overflow: hidden;
  align-items: center; justify-content: center; padding: 0;
}
.tb-av img { width: 100%; height: 100%; object-fit: cover; display: block; }
.tb-icon {
  display: none; width: 38px; height: 38px; flex: 0 0 38px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--surface-solid-1); color: var(--muted);
  align-items: center; justify-content: center; padding: 0;
}
.tb-icon:hover { color: var(--ink); border-color: var(--line-2); }
@media (max-width: 767px) { .tb-av { display: flex; } }
@media (max-width: 560px) {
  .search-wrap { display: none; }
  .tb-spacer { display: none; }
  .tb-icon { display: flex; margin-left: auto; }
  #topbar.search-open .search-wrap {
    display: block; position: fixed; left: 8px; right: 8px;
    top: calc(var(--bar-h) + 8px); z-index: 65; flex: none;
    animation: drop-in var(--dur-fast) var(--ease-out);
  }
  #topbar.search-open #gpanel { top: calc(var(--bar-h) + 58px); }
}

/* ── filter chips ─────────────────────────────────────────────────────────── */
.chips { display: flex; gap: 8px; margin-bottom: 18px; flex-wrap: wrap; }
/* the kinds multi-select — a chip that drops a small glass menu of checkboxes */
.kmenu-wrap { position: relative; display: inline-block; }
.kmenu {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 45; min-width: 180px;
  padding: 6px; border-radius: 12px; border: 1px solid var(--line-2);
  background: var(--glass-over);
  -webkit-backdrop-filter: var(--frost); backdrop-filter: var(--frost);
  box-shadow: var(--glass-hi), 0 18px 50px rgba(0,0,0,0.55);
  animation: drop-in var(--dur-fast) var(--ease-out);
}
.chip {
  padding: 7px 14px; border-radius: 999px;
  border: 1px solid var(--line); background: var(--surface-solid-1);
  font-family: var(--font-mono); font-size: var(--fs-sm); letter-spacing: var(--track-mono);
  text-transform: uppercase; color: var(--muted);
}
.chip:hover { border-color: var(--line-2); color: var(--ink); }
.chip.on { background: var(--accent-a14); border-color: var(--accent-a48); color: var(--accent); }

/* ── feed card ────────────────────────────────────────────────────────────── */
.card {
  background: var(--glass-card); border: 1px solid var(--line);
  border-radius: 14px; margin-bottom: 18px; overflow: hidden;
  box-shadow: var(--glass-hi);
  transition: border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.card:hover { border-color: var(--line-2); transform: translateY(-2px);
  box-shadow: var(--glass-hi), var(--shadow-soft); }
.card-hd { display: flex; align-items: center; gap: 11px; padding: 14px 16px 0; }
.av {
  width: 38px; height: 38px; border-radius: 50%; flex: 0 0 38px;
  object-fit: cover; background: var(--surface-solid-3);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: var(--fs-ui); color: var(--accent);
  border: 1px solid var(--line);
  position: relative; overflow: hidden;
}
/* an OAuth avatar paints over the initial; on 404 the img removes itself and
   the initial is simply there again — same contract as .pcard .img */
.av > img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; border-radius: inherit; }
.who { flex: 1; min-width: 0; }
.who .n { font-weight: var(--fw-semibold); font-size: var(--fs-ui); }
.who .n:hover { text-decoration: underline; cursor: pointer; }
.who .c { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.when { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--faint); flex: 0 0 auto; }
.card-act { padding: 12px 16px 0; font-size: var(--fs-ui); }
.card-act b { font-weight: var(--fw-semibold); }
.card-body { padding: 0 16px; }
.card-open { display: block; width: 100%; text-align: left; background: none; border: none; padding: 0 0 14px; }
.rowlink { display: flex; align-items: center; gap: 11px; flex: 1; min-width: 0; }
.rowlink .rt { display: block; flex: 1; min-width: 0; }
.rowlink .rt .t, .rowlink .rt .s { display: block; }
.rowlink:hover .t { text-decoration: underline; }

.mods { margin-top: 10px; display: flex; flex-direction: column; gap: 6px; }
/* Two rows, not one line: the title wraps in full and the part number sits UNDER it.
   Sharing a flex line, the two competed for the same width — and the title, the thing
   anyone actually reads, is what lost, ellipsised mid-word so an SKU nobody scans could
   hold the right edge. Grid instead: the dot owns column 1, and BOTH the title and the
   number are placed in column 2, so the number can never be drawn up beside the title
   however short the title happens to be. */
.mod-row { display: grid; grid-template-columns: 7px minmax(0, 1fr); column-gap: 9px; row-gap: 2px;
           align-items: start; font-size: var(--fs-md); }
.dot { width: 7px; height: 7px; border-radius: 50%; flex: 0 0 7px; }
.mod-row .dot { margin-top: .42em; }          /* optically on the first line of the title */
.mod-row .t { grid-column: 2; min-width: 0; overflow-wrap: anywhere; }
.mod-row .pn { grid-column: 2; font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--faint); }

.big { font-family: var(--font-display); font-size: 34px; line-height: 1.1; margin-top: 10px; letter-spacing: var(--track-display); }
.big .u { font-size: 14px; color: var(--faint); font-family: var(--font-mono); }
.sub { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--faint); margin-top: 4px; }

.tags { display: flex; gap: 7px; margin-top: 10px; flex-wrap: wrap; }
.tag {
  font-family: var(--font-mono); font-size: var(--fs-xs); font-style: normal;
  padding: 4px 9px; border-radius: 999px; letter-spacing: var(--track-mono);
}

.pbar { margin-top: 12px; }
.pbar .track { height: 7px; border-radius: 4px; background: var(--surface-solid-3); overflow: hidden; display: flex; }
.pbar .stock { background: rgba(var(--slate-rgb),0.45); }
.pbar .gained { background: var(--gain); }
.pbar .lg { display: flex; justify-content: space-between; margin-top: 6px; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--faint); }

.vid { margin-top: 12px; position: relative; border-radius: 10px; overflow: hidden; aspect-ratio: 16/9; background: var(--p-void-0); }
.vid img { width: 100%; height: 100%; object-fit: cover; display: block; }
.vid .play { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.vid iframe { width: 100%; height: 100%; border: 0; display: block; }

.gal { margin-top: 12px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 3px; border-radius: 10px; overflow: hidden; }
.gal div { aspect-ratio: 1; background: var(--surface-solid-3); }

/* card action row — the desktop affordance the phone build has no room for */
.card-ft {
  display: flex; align-items: center; gap: 4px; padding: 4px 10px 8px;
  border-top: 1px solid var(--line); margin-top: 2px;
}
.act-btn {
  display: flex; align-items: center; gap: 7px; padding: 8px 11px;
  border: none; background: transparent; border-radius: 8px;
  font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--faint);
}
.act-btn:hover { background: var(--surface-solid-2); color: var(--ink); }
.act-btn.on { color: var(--accent); }
/* respect fills; follow turns its + into an × — the same two signals the app uses */
.act-btn.resp.on { color: var(--hot); }
.act-btn.resp.on svg { fill: var(--hot); stroke: var(--hot); }
.act-btn.foll.on svg { transform: rotate(45deg); }
.act-btn svg { transition: transform .16s ease; }
.btn-sm.on { color: var(--accent); border-color: var(--accent-a48); background: var(--accent-a07); }
@media (prefers-reduced-motion: reduce) { .act-btn svg { transition: none; } }
.ft-spacer { flex: 1; }

/* ── right rail panels ────────────────────────────────────────────────────── */
/* Aside panels sit one step BELOW the content cards on purpose: the feed is
   the read, the aside is the furniture. Same shape, quieter surface. */
.panel {
  background: var(--glass-quiet); border: 1px solid var(--line);
  border-radius: 14px; padding: 15px 16px; margin-bottom: 16px;
  box-shadow: var(--glass-hi);
}
.panel h3 { margin: 0 0 12px; font-family: var(--font-mono); font-size: var(--fs-sm);
  letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--faint); font-weight: 500; }
.panel .more { float: right; color: var(--accent); font-size: var(--fs-xs); text-transform: none; letter-spacing: 0; cursor: pointer; }

.veh-hero { border-radius: 10px; aspect-ratio: 16/10; background:
  linear-gradient(140deg, var(--surface-solid-3), var(--surface-solid-2)); margin-bottom: 11px;
  display: flex; align-items: flex-end; padding: 12px; }
.veh-name { font-family: var(--font-display); font-size: var(--fs-xl); }
.veh-stats { display: grid; grid-template-columns: repeat(3,1fr); gap: 8px; margin-top: 12px; }
.stat { text-align: center; }
.stat .v { font-family: var(--font-mono); font-size: var(--fs-2xl); color: var(--ink); }
.stat .k { font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--faint); text-transform: uppercase; letter-spacing: var(--track-caps); }

.row { display: flex; align-items: center; gap: 11px; padding: 8px 0; }
.row + .row { border-top: 1px solid var(--line); }
.row-click { cursor: pointer; border-radius: 9px; margin: 0 -6px; padding: 8px 6px; transition: background var(--dur-fast) var(--ease-out); }
.row-click:hover { background: var(--surface-solid-1); }
.row .rt { flex: 1; min-width: 0; }
.row .rt .t { font-size: var(--fs-md); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.row .rt .s { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--faint); }
.row .btn-sm { border: 1px solid var(--line); background: transparent; border-radius: 8px; padding: 5px 11px;
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--accent); }
.row .btn-sm:hover { background: var(--accent-a07); border-color: var(--accent-a34); }
.date-blk { flex: 0 0 42px; text-align: center; border: 1px solid var(--line); border-radius: 8px; padding: 4px 0; }
.date-blk .m { font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--hot); text-transform: uppercase; }
.date-blk .d { font-family: var(--font-mono); font-size: var(--fs-xl); line-height: 1.1; }

.foot { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--faint); line-height: 1.9; padding: 4px 2px 0; }
.foot a { color: var(--faint); text-decoration: none; }
.foot a:hover { color: var(--muted); text-decoration: underline; }

/* ── post modal (Instagram's modal-over-feed) ─────────────────────────────── */
#scrim {
  position: fixed; inset: 0; z-index: 80; display: none;
  /* Green-black, like the ground it dims. rgba(6,8,12) is Holo's blue-black and it
     outlived the repaint: the scrim is the largest surface on screen whenever a part
     panel is open, so a cast that disagreed with #0B0E0D by 4 points of blue read as
     a cold wash over a warm page. Same hue as the phone's --scrim, and the alpha is
     unchanged — this moves the colour, not how much it dims. */
  background: rgba(6,10,8,0.72);
  -webkit-backdrop-filter: var(--frost-heavy); backdrop-filter: var(--frost-heavy);
  align-items: center; justify-content: center; padding: 40px 24px;
}
#scrim.on { display: flex; animation: scrim-in var(--dur-med) ease; }
#scrim.on #modal { animation: modal-in var(--dur-med) var(--ease-pop); }
#modal {
  background: var(--glass-over);
  -webkit-backdrop-filter: var(--frost); backdrop-filter: var(--frost);
  border: 1px solid var(--line-2);
  border-radius: 16px; width: min(880px, 100%); max-height: 100%;
  overflow-y: auto; box-shadow: var(--glass-hi), 0 30px 90px rgba(0,0,0,0.6);
}
#modal-x {
  position: fixed; top: 44px; right: 28px; z-index: 82;
  width: 38px; height: 38px; border-radius: 50%; border: 1px solid var(--line);
  background: var(--surface-solid-1); display: flex; align-items: center; justify-content: center;
}
#modal-x:hover { background: var(--surface-solid-3); }
.m-hd { padding: 22px 26px 0; }
.m-car { font-family: var(--font-display); font-size: 26px; letter-spacing: var(--track-display); }
.m-sub { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--faint); margin-top: 5px; }
.m-body { padding: 20px 26px 26px; }
.m-note {
  margin-top: 18px; padding: 13px 15px; border-radius: 10px;
  background: var(--accent-a07); border: 1px solid var(--accent-a20);
  font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--muted); line-height: 1.75;
}
.m-note code { color: var(--accent); }
/* build-viewer furniture — index.html's classes so the modal reads like the phone's
   sheet: filter chips, the owner-garage dropdown, social links, the photo strip */
.fchip { font-family: var(--font-mono); font-size: var(--fs-xs); letter-spacing: 0.1em; text-transform: uppercase; padding: 7px 12px; border-radius: var(--radius-pill); border: var(--border); background: transparent; color: var(--muted); cursor: pointer; white-space: nowrap; }
.fchip.on { color: var(--accent); border-color: var(--accent-a48); background: var(--chip-fill); }
.lm-row { width: 100%; display: flex; align-items: center; gap: 9px; padding: 8px; border-radius: 9px; background: transparent; border: 0; color: var(--muted); cursor: pointer; font-family: inherit; font-size: 13px; }
.lm-row:hover { background: rgba(255,255,255,0.07); }
.evt-menu { position: absolute; top: calc(100% + 4px); left: 0; z-index: 29; width: 214px; background: rgba(16,18,22,0.94); backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); border: 1px solid rgba(255,255,255,0.14); border-radius: 14px; padding: 8px; box-shadow: 0 12px 34px rgba(0,0,0,0.55); }
.soc-row { display: flex; gap: 10px; margin-top: 12px; }
.soc-row a { display: flex; align-items: center; gap: 6px; padding: 7px 11px; border-radius: var(--radius-pill); border: 1px solid var(--line-2); color: var(--muted); font-family: var(--font-mono); font-size: var(--fs-xs); text-decoration: none; }
.bv-gal { display: flex; gap: 8px; overflow-x: auto; margin: 14px 0 4px; padding-bottom: 4px; scroll-snap-type: x mandatory; scrollbar-width: none; }
.bv-gal::-webkit-scrollbar { display: none; }
.bv-gal img { height: 200px; border-radius: 14px; border: 1px solid var(--line-2); scroll-snap-align: start; flex-shrink: 0; object-fit: cover; }

/* ── states ───────────────────────────────────────────────────────────────── */
.skel { background: var(--glass-card); border: 1px solid var(--line); border-radius: 14px;
  height: 190px; margin-bottom: 18px; position: relative; overflow: hidden; }
.skel::after { content: ''; position: absolute; inset: 0;
  background: linear-gradient(90deg, transparent, rgba(255,255,255,0.035), transparent);
  animation: sweep 1.3s infinite; }
@keyframes sweep { to { transform: translateX(100%); } }
.empty { text-align: center; padding: 60px 24px; color: var(--faint); }
.empty .et { font-family: var(--font-display); font-size: 20px; color: var(--muted); }
.empty .es { font-family: var(--font-mono); font-size: var(--fs-sm); margin-top: 8px; line-height: 1.8; }
/* a button that reads as a link — the empty state's copy is a sentence, and a second
   .gar-cta under the first would read as two equal choices when one is the fallback */
.linkish { font: inherit; color: var(--accent); background: none; border: 0; padding: 0;
  cursor: pointer; text-decoration: underline; text-underline-offset: 2px; }
#more {
  width: 100%; padding: 13px; border-radius: 12px; border: 1px solid var(--line);
  background: var(--glass-card); font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--muted);
  box-shadow: var(--glass-hi);
}
#more:hover { border-color: var(--line-2); color: var(--ink); }

/* ── bottom tab bar · mobile web only ─────────────────────────────────────── */
#tabs {
  display: none; position: fixed; left: 0; right: 0; bottom: 0; z-index: 45;
  height: 62px; background: color-mix(in srgb, var(--bg-2) 70%, transparent);
  -webkit-backdrop-filter: var(--frost); backdrop-filter: var(--frost);
  border-top: 1px solid var(--line);
  align-items: center; justify-content: space-around; padding-bottom: env(safe-area-inset-bottom);
}
/* #tabs holds ONE child — the div paintNav fills — so the flex row above was laying out
   that wrapper, not the four buttons inside it. The buttons fell back to block flow and
   stacked down the page. The row has to be declared on the element that actually holds
   them. */
#tabs #tab-items { display: flex; flex: 1; align-items: center; justify-content: space-around; }
#tabs .nav-item { flex-direction: column; gap: 3px; width: auto; padding: 8px 12px; }
#tabs .nav-item .lbl { font-family: var(--font-mono); font-size: var(--fs-2xs); text-transform: uppercase; letter-spacing: 1px; }

/* ── GARAGE ───────────────────────────────────────────────────────────────
   The phone garage is one vertical stack: hero → stats → nudge → CTA → story.
   Widening it is not "the same stack, centred" — the pieces that were stacked
   only because a phone is 390px wide get to sit side by side:
     · cover becomes a banner, identity overlays it
     · readout and stat tiles share a row
     · the story and the photo/spec column share a row
   The feed keeps its 630px column. This does not. That contrast IS the point —
   Instagram constrains the feed to 630 and lets the profile grid run to 935.  */

/* Only the ASIDE changes per route now — the page container is constant and the content
   column always fills it, so the header never moves when you switch tabs. */
#main[data-route="garage"] #aside-col { display: none; }

.gar-cover {
  position: relative; border-radius: 16px; overflow: hidden;
  aspect-ratio: 16/5; margin-bottom: 18px;
  background: linear-gradient(135deg, var(--surface-solid-3), var(--surface-solid-1));
  border: 1px solid var(--line);
}
.gar-cover img { width: 100%; height: 100%; object-fit: cover; display: block; }
.gar-gear { position: absolute; top: 14px; right: 14px; z-index: 3;
  width: 34px; height: 34px; display: flex; align-items: center; justify-content: center;
  border-radius: 50%; border: 1px solid var(--line-2); color: var(--muted);
  background: color-mix(in srgb, var(--bg) 70%, transparent); backdrop-filter: blur(6px); }
.gar-gear:hover { color: var(--ink); border-color: var(--accent-a48); }
.gar-barewrap { position: relative; }   /* no cover: the gear still needs an anchor */

/* vehicle picker rows */
.veh-pick { display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 13px 14px; margin-bottom: 8px; border-radius: 11px;
  background: var(--glass-card-2); border: 1px solid var(--line); box-shadow: var(--glass-hi); }
.veh-pick:hover { border-color: var(--line-2); }
.veh-pick.on { border-color: var(--accent-a48); background: var(--accent-a07); }
.vp-t { flex: 1; min-width: 0; }
.vp-t .t { display: block; font-size: var(--fs-ui); }
.vp-t .s { display: block; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--faint); margin-top: 3px; }
.vp-n { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--muted); flex: 0 0 auto; }
.vp-a { font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--accent);
  text-transform: uppercase; letter-spacing: var(--track-caps); flex: 0 0 auto; }

/* service-interval rows: label, miles, months */
.cs-mrow { display: grid; grid-template-columns: 1fr 74px auto 74px auto; align-items: center; gap: 8px; padding: 7px 0; }
.cs-mrow + .cs-mrow { border-top: 1px solid var(--line); }
.cs-ml { font-size: var(--fs-md); min-width: 0; }
.cs-mrow .ef-i { padding: 8px 9px; font-size: var(--fs-md); text-align: right; }
.cs-mrow .ef-i.own { border-color: var(--accent-a48); color: var(--accent); }
.cs-mu { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--faint); }

.gar-cover .fade { position: absolute; inset: 0; background: linear-gradient(0deg, rgba(6,8,12,0.9) 0%, rgba(6,8,12,0.15) 55%, transparent 100%); }
.gar-id { position: absolute; left: 24px; bottom: 20px; right: 24px; }
.gar-cover .gar-id { color: var(--ink); }
.gar-title { font-family: var(--font-display); font-size: 38px; line-height: 1.05; letter-spacing: var(--track-display); }
.gar-sub { font-family: var(--font-mono); font-size: var(--fs-md); color: var(--muted); margin-top: 6px; }
.gar-sub em { color: var(--accent); font-style: normal; }
.use-chip {
  display: inline-block; margin-left: 8px; padding: 2px 8px; border-radius: 999px;
  background: var(--accent-a14); border: 1px solid var(--accent-a34);
  color: var(--accent); font-size: var(--fs-2xs); letter-spacing: var(--track-caps); text-transform: uppercase;
}
/* no cover set — identity sits on a plain band, exactly as the phone drops the hero */
.gar-id.bare { position: static; margin-bottom: 20px; }

/* readout + stat tiles, side by side */
.gar-top { display: grid; grid-template-columns: minmax(0,1fr) 420px; gap: 18px; margin-bottom: 18px; }
.readout { background: var(--glass-card); border: 1px solid var(--line); border-radius: 14px; padding: 20px 22px; box-shadow: var(--glass-hi); }
.readout .num { font-family: var(--font-display); font-size: 58px; line-height: 1; letter-spacing: -0.02em; display: flex; align-items: baseline; gap: 10px; }
.readout .num .u { font-family: var(--font-mono); font-size: 16px; color: var(--faint); }
.readout .num.unset { color: var(--faint); }
.gain-pill {
  display: inline-flex; align-items: center; gap: 5px; padding: 4px 10px; border-radius: 999px;
  background: rgba(var(--gain-rgb),0.14); color: #6EE7A0;
  font-family: var(--font-mono); font-size: var(--fs-sm); vertical-align: middle;
}
.readout .cap { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--faint); margin-top: 9px; line-height: 1.7; }
.spark { margin-top: 16px; }
.spark svg { width: 100%; height: 62px; display: block; overflow: visible; }
.spark .lbl { display: flex; justify-content: space-between; margin-top: 7px;
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--faint); }

.gar-tiles { display: grid; grid-template-columns: repeat(2, 1fr); gap: 12px; align-content: start; }
.tile {
  background: var(--glass-card); border: 1px solid var(--line); border-radius: 14px;
  padding: 16px 18px; text-align: left; box-shadow: var(--glass-hi);
  transition: border-color var(--dur-fast) var(--ease-out), background var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out);
}
.tile:hover { border-color: var(--line-2); background: var(--glass-card-2); transform: translateY(-2px); }
.tile.on { border-color: var(--accent-a48); background: var(--accent-a07); }
.tile .v { font-family: var(--font-display); font-size: 30px; line-height: 1.1; }
.tile .k { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--faint);
  text-transform: uppercase; letter-spacing: var(--track-caps); margin-top: 3px; }

/* Service-due lives in the side column, not a full-width band. On the phone it earns
   the interrupt because it is the only thing between you and the story; on desktop the
   story is already visible beside it, so a banner spanning 1180px shouts louder than a
   maintenance reminder should. It sits under the actions, which is where you go to act. */
.nudge {
  display: flex; align-items: flex-start; gap: 10px; padding: 12px 14px; width: 100%;
  text-align: left; margin-top: 4px;
  border-radius: 11px; background: var(--glass-card); border: 1px solid rgba(var(--warn-rgb),0.3);
  font-size: var(--fs-md); line-height: 1.6;
}
.nudge:hover { border-color: rgba(var(--warn-rgb),0.55); background: var(--glass-card-2); }
.nudge .dot { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px; background: var(--warn); margin-top: 6px; }
/* The account nudge is the same component in the shell's own colour rather than a
   warning one: an anonymous garage is not a fault, it is an unfinished sign-up. */
.nudge.acct { border-color: rgba(var(--accent-rgb),0.3); }
.nudge.acct:hover { border-color: rgba(var(--accent-rgb),0.55); background: var(--glass-card-2); }
.nudge.acct .dot { background: var(--accent); }
.nudge.due { border-color: rgba(var(--hot-rgb),0.35); }
.nudge.due:hover { border-color: rgba(var(--hot-rgb),0.6); }
.nudge.due .dot { background: var(--hot); }
.nudge .tx { flex: 1; }
.nudge .tx b { font-weight: var(--fw-semibold); }

/* the story ⟷ side column */
.gar-grid { display: grid; grid-template-columns: minmax(0,1fr) 320px; gap: 32px; align-items: start; }
.sec-eye { font-family: var(--font-mono); font-size: var(--fs-sm); letter-spacing: var(--track-caps);
  text-transform: uppercase; color: var(--faint); margin-bottom: 12px; }

.grp-h {
  display: flex; align-items: center; gap: 8px; padding: 11px 4px 9px; margin-top: 8px;
  font-family: var(--font-mono); font-size: var(--fs-sm); letter-spacing: var(--track-mono);
  text-transform: uppercase; color: var(--faint); border-bottom: 1px solid var(--line);
}
.grp-h b { color: var(--ink); font-weight: var(--fw-medium); }
.grp-h svg { margin-left: auto; transition: transform .16s; }
.grp-h.closed svg { transform: rotate(-90deg); }
.grp-h:hover { color: var(--muted); }

.tl-entry {
  display: flex; align-items: center; gap: 13px; padding: 13px 15px; margin-top: 8px;
  background: var(--glass-card); border: 1px solid var(--line); border-radius: 12px;
  box-shadow: var(--glass-hi);
  width: 100%; text-align: left; transition: border-color .13s, transform .13s;
}
.tl-entry:hover { border-color: var(--line-2); transform: translateX(2px); }
.tl-entry.milestone { border-color: rgba(var(--mod-rgb),0.4); }

/* GHOST ROWS — the empty story used to bottom out into a few hundred pixels of
   nothing: the column simply stopped under "Start the story". These are the SHAPE
   of an entry, deliberately not a loading skeleton (that is .f-skel, filled and
   animated, and it would promise rows that are not coming). Dashed rather than
   solid, static rather than shimmering, and carrying NO text — a placeholder
   reading "Coilovers · 12 Mar" would be an invented record on a page whose whole
   claim is that it holds the real one. They fade down the stack so the eye reads a
   receding suggestion rather than three failed rows. aria-hidden and
   pointer-events:none — there is nothing here to announce or to click. */
.tl-ghost {
  display: flex; align-items: center; gap: 13px; padding: 13px 15px; margin-top: 8px;
  background: transparent; border: 1px dashed var(--line); border-radius: 12px;
  pointer-events: none;
}
.tl-ghost .g-ic {
  width: 38px; height: 38px; flex: 0 0 38px; border-radius: 10px;
  border: 1px dashed var(--line);
}
.tl-ghost .g-b { flex: 1; min-width: 0; }
.tl-ghost .g-1, .tl-ghost .g-2 { display: block; border-radius: 5px; background: var(--line); }
.tl-ghost .g-1 { height: 9px; }
.tl-ghost .g-2 { height: 7px; margin-top: 8px; opacity: .62; }
.tl-ic {
  width: 38px; height: 38px; flex: 0 0 38px; border-radius: 10px;
  display: flex; align-items: center; justify-content: center;
  background: var(--surface-solid-3); border: 1px solid var(--line);
  position: relative; overflow: hidden;
}
/* covers the dot when the catalog gave us a picture; if it 404s it removes itself
   and the dot is simply there again */
.tl-pic { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

/* ── part type-ahead ── */
.ef-ta { position: relative; }
.ef-menu {
  display: none; position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 5;
  max-height: 300px; overflow-y: auto; padding: 5px;
  background: var(--glass-over);
  -webkit-backdrop-filter: var(--frost); backdrop-filter: var(--frost);
  border: 1px solid var(--line-2); border-radius: 11px;
  box-shadow: var(--glass-hi), 0 18px 50px rgba(0,0,0,0.55);
  animation: drop-in var(--dur-fast) var(--ease-out);
}
.ef-sug-row { display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  padding: 8px 9px; border: none; background: transparent; border-radius: 8px; }
.ef-sug-row:hover { background: var(--surface-solid-3); }
.ef-sug-img { width: 34px; height: 34px; flex: 0 0 34px; border-radius: 7px; overflow: hidden;
  background: var(--surface-solid-2); border: 1px solid var(--line); }
.ef-sug-img img { width: 100%; height: 100%; object-fit: cover; }
.ef-sug-t { flex: 1; min-width: 0; }
.ef-sug-t .t1 { display: block; font-size: var(--fs-md); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.ef-sug-t .t2 { display: block; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--faint); margin-top: 2px; }
.ef-sug-p { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--muted); flex: 0 0 auto; }
/* margin-top was -6px, which is 6px of real overlap: .ef-i carries no bottom
   margin, so the chip's border and fill sat on top of the input's bottom edge.
   6px of clearance instead — the chip still reads as belonging to the field
   above it without colliding with it. */
.ef-picked { display: flex; align-items: center; gap: 9px; margin: 6px 0 14px; padding: 7px 9px;
  background: var(--accent-a07); border: 1px solid var(--accent-a20); border-radius: 9px;
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--muted); }
.ef-picked img { width: 30px; height: 30px; border-radius: 6px; object-fit: cover; flex: 0 0 30px; }
.ef-picked span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ef-picked button { border: none; background: none; color: var(--faint); padding: 2px 4px; }
.ef-picked button:hover { color: var(--hot); }
.tl-body { flex: 1; min-width: 0; }
.tl-t { font-size: var(--fs-ui); display: block; }
.tl-m { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--faint); margin-top: 3px; display: block; }
.tl-m b { color: var(--muted); font-weight: 500; }
.tl-tag {
  font-family: var(--font-mono); font-size: var(--fs-2xs); padding: 4px 9px; border-radius: 999px;
  letter-spacing: var(--track-caps); text-transform: uppercase; flex: 0 0 auto;
}

/* service ledger — visits, not entries */
.visit { display: flex; gap: 15px; padding: 4px 0; }
.visit-rail { flex: 0 0 22px; display: flex; flex-direction: column; align-items: center; }
.visit-node { width: 22px; height: 22px; border-radius: 50%; border: 1px solid var(--line-2);
  background: var(--surface-solid-1); display: flex; align-items: center; justify-content: center; color: var(--accent); }
.visit-line { flex: 1; width: 1px; background: var(--line); margin: 4px 0; min-height: 12px; }
.visit-b { flex: 1; padding-bottom: 18px; }
.visit-hd { display: flex; align-items: baseline; gap: 10px; }
.visit-d { font-family: var(--font-mono); font-size: var(--fs-md); }
.visit-meta { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--faint); margin-left: auto; }
.visit-item { display: flex; align-items: center; gap: 9px; padding: 6px 0 0; font-size: var(--fs-md); color: var(--muted); }

.photo-grid { display: grid; grid-template-columns: repeat(3,1fr); gap: 5px; }
.photo-grid div, .photo-grid img { aspect-ratio: 1; border-radius: 7px; background: var(--surface-solid-3); object-fit: cover; width: 100%; display: block; }
.photo-add { display: flex; align-items: center; justify-content: center; color: var(--faint); border: 1px dashed var(--line-2); cursor: pointer; }
.photo-add:hover { color: var(--accent); border-color: var(--accent-a34); }
/* A tile is a frame, not just a picture: the cover mark and the two controls sit on
   top of it, and a tile still waiting on the account's copy of its photo holds its
   square rather than collapsing the grid. */
.photo-cell { position: relative; overflow: hidden; }
.photo-cell img { position: absolute; inset: 0; height: 100%; }
.photo-cell.cover { box-shadow: inset 0 0 0 1.5px var(--accent); }
.photo-cell.bare { display: flex; align-items: center; justify-content: center;
  border: 1px solid var(--line); padding: 6px; text-align: center; }
.ph-msg { font-family: var(--font-mono); font-size: 9px; line-height: 1.35; color: var(--faint); }
.ph-flag { position: absolute; left: 4px; bottom: 4px; z-index: 2; padding: 1px 5px; border-radius: var(--radius-pill);
  background: rgba(6,10,8,0.72); color: var(--accent); font-family: var(--font-mono); font-size: 8px;
  letter-spacing: .08em; text-transform: uppercase; }
/* hidden until the tile is pointed at or focused — five permanent × buttons would
   turn a gallery into a control panel. Touch has no hover, so there they stay put. */
.ph-acts { position: absolute; top: 4px; right: 4px; z-index: 2; display: flex; gap: 3px;
  opacity: 0; transition: opacity .14s ease; }
.photo-cell:hover .ph-acts, .photo-cell:focus-within .ph-acts { opacity: 1; }
@media (hover: none) { .ph-acts { opacity: 1; } }
.ph-act { display: flex; align-items: center; justify-content: center; width: 20px; height: 20px; padding: 0;
  border: 1px solid rgba(255,255,255,0.14); border-radius: 6px; background: rgba(6,10,8,0.72);
  color: var(--ink); cursor: pointer; aspect-ratio: auto; }
.ph-act:hover { border-color: var(--accent-a34); color: var(--accent); }
.ph-act.on { color: var(--accent); border-color: var(--accent-a34); }
/* spans the grid — a sentence explaining the state beats three blank squares */
.photo-grid .photo-empty { grid-column: 1 / -1; aspect-ratio: auto; background: transparent;
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--faint);
  line-height: 1.7; padding: 4px 0; }

.spec-row { display: flex; justify-content: space-between; padding: 7px 0; font-size: var(--fs-md); }
.spec-row + .spec-row { border-top: 1px solid var(--line); }
.spec-row .k { color: var(--faint); font-family: var(--font-mono); font-size: var(--fs-sm); }

.gar-cta {
  width: 100%; padding: 12px; border-radius: 11px; border: 1px solid var(--accent-a34);
  background: var(--accent-a14); color: var(--accent); font-family: var(--font-display); font-size: var(--fs-lg);
  display: flex; align-items: center; justify-content: center; gap: 9px; margin-bottom: 10px;
}
.gar-cta:hover { background: var(--accent-a20); border-color: var(--accent-a48); }
.gar-cta.ghost { background: transparent; border-color: var(--line); color: var(--muted); }
.gar-cta.ghost:hover { border-color: var(--line-2); color: var(--ink); background: var(--surface-solid-2); }

/* ── WORK PAGES · Parts / Events / Search ─────────────────────────────────
   All three are the same problem: a filtered list over a big corpus. On the
   phone every one of them hides its filters behind a pull-out panel, because
   there is no room — and that panel is why a filtered list can look like an
   empty one. Desktop has the room, so the facets are permanently visible and
   the active ones are readable without opening anything. Standard e-commerce
   facet rail (eBay, Etsy, Amazon); the phone keeps its pull-out below 1100px. */

#main[data-route="parts"] #aside-col,
#main[data-route="events"] #aside-col,
#main[data-route="search"] #aside-col { display: none; }

.work { display: grid; grid-template-columns: 236px minmax(0, 1fr); gap: 30px; align-items: start; }
.facets { position: sticky; top: var(--sticky-top); }
/* All three exist only on a phone — see the 767px block. Declared here so the desktop
   and tablet layouts are never handed a stray button or a veil to lay out. */
.facet-btn, .facet-close, #facet-veil { display: none; }
.facet-grp { margin-bottom: 22px; }
/* second-level header inside the Category facet: the taxonomy group above its leaves */
.facet-sub {
  margin: 10px 0 2px; padding: 0 8px; font-family: var(--font-mono); font-size: var(--fs-2xs);
  letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--faint);
}
.facet-sub:first-child { margin-top: 0; }
.facet-grp h4 {
  margin: 0 0 9px; font-family: var(--font-mono); font-size: var(--fs-xs);
  letter-spacing: var(--track-caps); text-transform: uppercase; color: var(--faint); font-weight: 500;
}
.facet {
  display: flex; align-items: center; gap: 9px; width: 100%; padding: 6px 8px;
  border: none; background: transparent; border-radius: 7px;
  font-size: var(--fs-md); color: var(--muted); text-align: left;
}
.facet:hover { background: var(--surface-solid-1); color: var(--ink); }
.facet .box {
  width: 15px; height: 15px; flex: 0 0 15px; border-radius: 4px;
  border: 1px solid var(--line-2); display: flex; align-items: center; justify-content: center; color: var(--on-accent);
}
.facet.on .box { background: var(--accent); border-color: var(--accent); }
.facet.on { color: var(--ink); }
.facet .box svg { opacity: 0; }
.facet.on .box svg { opacity: 1; }
.facet .n { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.facet .c { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--faint); }
.facet .sw { width: 8px; height: 8px; border-radius: 50%; flex: 0 0 8px; }
.facet-clear {
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--accent);
  background: none; border: none; padding: 4px 8px;
}
.facet-clear:hover { text-decoration: underline; }

/* Hot ⟷ Results. Both stay reachable rather than a search replacing the list —
   what the chassis runs and what the catalog stocks answer different questions. */
.gseg { display: flex; gap: 4px; padding: 3px; margin-bottom: 18px;
  background: var(--surface-solid-2); border: 1px solid var(--line); border-radius: 11px; }
.gseg button { flex: 1; padding: 9px 10px; border: none; border-radius: 8px; background: transparent;
  color: var(--faint); font-family: var(--font-mono); font-size: var(--fs-sm); }
.gseg button.on { background: var(--accent-a14); color: var(--accent); }
.gseg button:hover:not(.on) { color: var(--ink); }

.work-hd { display: flex; align-items: baseline; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
.work-hd .t { font-family: var(--font-display); font-size: 22px; letter-spacing: var(--track-display); }
.work-hd .c { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--faint); }

/* ── parts grid ── */
.pgrid { display: grid; grid-template-columns: repeat(3, minmax(0,1fr)); gap: 16px; }
.pcard {
  background: var(--glass-card); border: 1px solid var(--line); border-radius: 13px;
  overflow: hidden; text-align: left; display: flex; flex-direction: column; padding: 0;
  box-shadow: var(--glass-hi);
  transition: border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.pcard:hover { border-color: var(--line-2); transform: translateY(-2px);
  box-shadow: var(--glass-hi), var(--shadow-soft); }
.pcard .img {
  aspect-ratio: 4/3; background: var(--surface-solid-2); display: flex; align-items: center; justify-content: center;
  border-bottom: 1px solid var(--line); position: relative;
}
/* The placeholder is always in the DOM underneath; the img covers it and removes itself
   on error, so a rotted CDN link degrades to the placeholder, never a broken-image icon.
   Same contract as .pthumb in index.html. */
.pcard .img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; padding: 10px; background: var(--surface-solid-2); }
.pcard .img .ph { color: var(--faint); opacity: .35; }
/* the hot list is a top-25 leaderboard — the grid card wears its rank the way the
   phone's rows do (first place picks up the mod accent) */
.pcard .img .rank { position: absolute; left: 8px; top: 8px; z-index: 1; min-width: 20px; height: 20px; padding: 0 5px; box-sizing: border-box; border-radius: 6px; background: var(--surface-solid-2); border: 1px solid var(--line-2); font-family: var(--font-mono); font-size: 11px; font-weight: 700; line-height: 18px; color: var(--muted); text-align: center; }
.pcard .img .rank.r1 { color: var(--mod); border-color: rgba(var(--mod-rgb),0.55); }
/* The grade used to sit over the product photo, where a light vendor cutout washed it
   out — a translucent tint on an unknown background is unreadable by construction. It
   sits on the brand row now, on the card's own surface, so its contrast is ours to set.
   Right-aligned there because the brand is the label and the grade is the verdict. */
.brandrow { display: flex; align-items: center; gap: 8px; }
.brandrow .brand { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.grade {
  flex: 0 0 auto; min-width: 22px; height: 20px; padding: 0 7px; border-radius: 6px;
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid transparent;
  font-family: var(--font-mono); font-size: var(--fs-xs); font-weight: 600;
  letter-spacing: var(--track-mono);
}
.pcard .b { padding: 12px 14px 14px; flex: 1; display: flex; flex-direction: column; }
/* ── part panel + event modal presentation — the phone's, ported ──────────
   Class-for-class from index.html so the two shells read as one product;
   only the widths change (the modal is 880px, so the attendee grid runs
   3-up and the source rows breathe). */
.sum-ov { font-size: 13.5px; color: var(--muted); line-height: 1.65; }
.sum-ov p { margin: 0 0 10px; } .sum-ov p:last-child { margin-bottom: 0; }
.sum-ov ul { margin: 0 0 10px; padding-left: 18px; display: flex; flex-direction: column; gap: 6px; }
.src-sect { font-family: var(--font-mono); font-size: var(--fs-2xs); letter-spacing: 0.14em; text-transform: uppercase; color: var(--faint); margin: 12px 0 6px; }
.src-row { display: flex; align-items: center; gap: 10px; padding: 11px 12px; margin-bottom: 8px; border-radius: 11px;
  background: var(--glass-card-2); border: 1px solid var(--line); text-decoration: none; color: inherit;
  transition: border-color var(--dur-fast) var(--ease-out); }
.src-row:hover { border-color: var(--line-2); }
.src-row.best { background: rgba(var(--gain-rgb),0.10); border-color: rgba(var(--gain-rgb),0.3); }
.src-info { flex: 1; min-width: 0; }
.src-name { font-size: var(--fs-md); font-weight: var(--fw-semibold); color: var(--ink); }
.src-pno { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--faint); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.src-badge { font-family: var(--font-mono); font-size: var(--fs-2xs); font-weight: 600; letter-spacing: 0.5px; padding: 2px 6px; border-radius: 4px; flex-shrink: 0; }
.src-badge.oem { color: #7FB0F5; background: rgba(59,130,246,0.16); border: 1px solid rgba(59,130,246,0.42); }
.src-badge.aftm { color: #FCB77A; background: rgba(251,146,60,0.16); border: 1px solid rgba(251,146,60,0.42); }
.src-price { font-family: var(--font-mono); font-size: 14px; font-weight: 700; color: var(--gain); flex-shrink: 0; }
.src-arrow { color: var(--faint); flex-shrink: 0; }
.fit-insight { padding: 14px 16px; border-radius: 12px; border: 1px solid var(--accent-a34); background: var(--accent-a07);
  color: var(--ink); font-size: var(--fs-md); line-height: 1.55; margin: 0 0 12px; }
.fit-insight b { color: var(--accent); }
.info-chips { display: flex; flex-wrap: wrap; gap: 8px; margin: 0 0 14px; }
.info-chip { display: flex; align-items: center; gap: 6px; padding: 8px 12px; border-radius: 999px; border: 1px solid var(--line);
  background: var(--glass-card-2); font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--muted); }
.info-chip svg { color: var(--accent); flex-shrink: 0; }
.info-chip b { color: var(--ink); font-weight: var(--fw-medium); }
.rule-li { display: flex; gap: 9px; margin: 0 0 8px; padding: 10px 13px; font-size: var(--fs-md); color: var(--muted); line-height: 1.45;
  background: var(--glass-card-2); border: 1px solid var(--line); border-radius: 11px; }
.rule-li::before { content: "—"; color: var(--accent); flex-shrink: 0; }
.att-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.att-card { position: relative; aspect-ratio: 4/3; border-radius: 12px; overflow: hidden; border: 1px solid var(--line-2);
  background: var(--surface-solid-2); cursor: pointer; transition: border-color var(--dur-fast) var(--ease-out); }
.att-card.nolink { cursor: default; }
.att-card img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.att-card:hover:not(.nolink) { border-color: var(--accent-a48); }
.ac-mono { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: 24px; letter-spacing: 0.1em; color: rgba(172,189,207,0.35);
  background: radial-gradient(120% 90% at 50% 0%, #1B2430 0%, #10161E 100%); }
.ac-fade { position: absolute; inset: auto 0 0 0; height: 68%; background: linear-gradient(180deg, rgba(10,13,18,0) 0%, rgba(10,13,18,0.88) 82%); pointer-events: none; }
.ac-txt { position: absolute; left: 10px; right: 10px; bottom: 8px; }
.ac-car { font-family: var(--font-display); font-weight: 500; font-size: 13.5px; color: var(--ink); line-height: 1.25; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ac-name { font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--muted); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ac-name span { color: var(--accent); }
.att-row { display: flex; align-items: center; gap: 10px; padding: 10px 12px; margin-bottom: 8px;
  background: var(--glass-card-2); border: 1px solid var(--line); border-radius: 10px; }
.att-row .nm { flex: 1; min-width: 0; }
.att-row .bld { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--accent); text-decoration: none; cursor: pointer; }

/* the part panel's hero + facts split */
.pp-grid { display: grid; grid-template-columns: 230px minmax(0, 1fr); gap: 20px; align-items: start; }
.pp-img { position: relative; aspect-ratio: 4/3; border-radius: 12px; border: 1px solid var(--line);
  background: var(--surface-solid-2); display: flex; align-items: center; justify-content: center; overflow: hidden; }
.pp-img .ph { color: var(--faint); opacity: .35; }
.pp-img img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: contain; padding: 12px; background: var(--surface-solid-2); }
@media (max-width: 560px) { .pp-grid { grid-template-columns: 1fr; } }
/* part-panel tabs — index.html's detail sheet classes verbatim (.dtabs/.dtab/.dpanel,
   .dload spinner): tokens.css aliases (--ff-disp, --orange, --surface-2/3, --green)
   resolve identically here, so the modal chunks exactly like the phone's panel */
.dtabs { display: flex; gap: 6px; background: var(--surface-2); border: 1px solid var(--line); border-radius: 12px; padding: 5px; margin: 6px 0 14px; overflow-x: auto; -webkit-overflow-scrolling: touch; scrollbar-width: none; }
.dtabs::-webkit-scrollbar { display: none; }
.dtab { flex: 0 0 auto; height: 38px; padding: 0 15px; border-radius: 9px; border: none; background: transparent; color: var(--muted); font-family: var(--ff-disp); font-weight: var(--fw-semibold); font-size: 13px; letter-spacing: -0.01em; white-space: nowrap; cursor: pointer; transition: background .12s, color .12s; }
.dtab.on { background: var(--orange); color: #052420; }
.dpanel { display: none; }
.dpanel.on { display: block; }
.dload { display: flex; align-items: center; gap: 10px; padding: 16px 0; }
.dload span { font-family: var(--ff-mono); font-size: 11px; letter-spacing: 1px; color: var(--faint); }
.src-spinner { width: 18px; height: 18px; border-radius: 50%; border: 2px solid var(--surface-3); border-top-color: var(--green); animation: spin .8s linear infinite; flex-shrink: 0; }
@keyframes spin { to { transform: rotate(360deg); } }
/* part-panel furniture ported from index.html: the hero-strip badges, the 4-segment
   fit-grade pills, install-video rows, and the small empty/error states */
.pop-badge { font-family: var(--font-mono); font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; padding: 3px 9px; border-radius: 6px; border: 1px solid currentColor; white-space: nowrap; line-height: 1.4; }
.fit-strip { display: flex; align-items: center; gap: 10px; margin: 0 0 12px; }
.fit-strip .own, .row .own { font-family: var(--font-mono); font-size: 12px; font-weight: 700; color: var(--accent); flex-shrink: 0; text-align: right; }
.fit-strip .own span, .row .own span { display: block; font-size: var(--fs-2xs); font-weight: 400; color: var(--faint); letter-spacing: 0.08em; text-transform: uppercase; }
.fit-strip .own .fg-pills, .row .own .fg-pills { display: flex; gap: 3px; justify-content: flex-end; margin: 4px 0 3px; color: inherit; }
.fit-strip .own .fg-pills i, .row .own .fg-pills i { width: 11px; height: 5px; border-radius: 3px; background: currentColor; }
.fit-strip .own .fg-pills i.off, .row .own .fg-pills i.off { opacity: 0.18; }
.fit-strip .own.est { opacity: 0.72; }
.yt-row { display: flex; gap: 11px; padding: 9px; margin-bottom: 8px; border-radius: 11px; background: var(--surface-2); border: 1px solid var(--line); text-decoration: none; }
.yt-thumb-wrap { position: relative; width: 92px; height: 56px; border-radius: 8px; overflow: hidden; flex-shrink: 0; background: #000; }
.yt-thumb { width: 100%; height: 100%; object-fit: cover; }
.yt-play { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center; }
.yt-info { min-width: 0; }
.yt-title { font-size: 13px; color: var(--ink); line-height: 1.35; overflow: hidden; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
.yt-channel { font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--faint); margin-top: 4px; }
.yt-search-link { display: flex; align-items: center; justify-content: center; gap: 8px; height: 46px; border-radius: 11px; background: var(--surface-2); border: 1px solid var(--line-2); color: var(--ink); text-decoration: none; font-weight: var(--fw-semibold); font-size: 14px; margin-top: 4px; }
.dempty { text-align: center; padding: 24px 16px; color: var(--faint); }
.dempty svg { opacity: 0.3; margin-bottom: 10px; }
.dempty p { font-family: var(--font-mono); font-size: 11px; letter-spacing: 0.5px; line-height: 1.7; margin: 0; }
.dmsg { font-family: var(--font-mono); font-size: 11px; line-height: 1.6; padding: 12px 0; }
.dmsg.err { color: #f77; }
.pcard .brand { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--accent); text-transform: uppercase; letter-spacing: var(--track-caps); }
.pcard .t { font-size: var(--fs-ui); margin-top: 4px; line-height: 1.4; }
.pcard .pn { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--faint); margin-top: 5px; }
.pcard .ft { display: flex; align-items: center; gap: 8px; margin-top: 11px; padding-top: 11px; border-top: 1px solid var(--line); }
.pcard .owners { font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--muted); flex: 1; }
.pcard .msrp { font-family: var(--font-mono); font-size: var(--fs-md); }

/* ── events ── */
.ecard {
  display: flex; gap: 16px; padding: 16px; width: 100%; text-align: left;
  background: var(--glass-card); border: 1px solid var(--line); border-radius: 13px; margin-bottom: 12px;
  box-shadow: var(--glass-hi);
  transition: border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.ecard:hover { border-color: var(--line-2); transform: translateY(-2px);
  box-shadow: var(--glass-hi), var(--shadow-soft); }
.ecard .date {
  flex: 0 0 58px; text-align: center; border: 1px solid var(--line); border-radius: 10px;
  padding: 8px 0; background: var(--surface-solid-2); align-self: flex-start;
}
.ecard .date .m { font-family: var(--font-mono); font-size: var(--fs-xs); text-transform: uppercase; letter-spacing: var(--track-caps); }
.ecard .date .d { font-family: var(--font-display); font-size: 24px; line-height: 1.15; }
.ecard .b { flex: 1; min-width: 0; }
.ecard .n { font-size: var(--fs-xl); font-family: var(--font-display); }
.ecard .o { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--faint); margin-top: 3px; }
.ecard .v { font-size: var(--fs-md); color: var(--muted); margin-top: 7px; }
.ecard .d2 { font-size: var(--fs-md); color: var(--faint); margin-top: 6px; line-height: 1.6; }
.ecard .acts { display: flex; gap: 8px; margin-top: 12px; }
/* keyless Google embed — ported from index.html; the invert filter darkens the tiles */
.ev-map { margin: 10px 0 0; border-radius: 12px; overflow: hidden; border: 1px solid var(--line); }
.ev-map iframe { width: 100%; height: 170px; border: 0; display: block; filter: invert(0.88) hue-rotate(180deg) saturate(0.75) contrast(0.95); }
.etype {
  padding: 3px 9px; border-radius: 999px; font-family: var(--font-mono);
  font-size: var(--fs-2xs); letter-spacing: var(--track-caps); text-transform: uppercase; flex: 0 0 auto;
}
.date-sep {
  font-family: var(--font-mono); font-size: var(--fs-sm); letter-spacing: var(--track-caps);
  text-transform: uppercase; color: var(--faint); margin: 22px 0 12px; padding-bottom: 8px; border-bottom: 1px solid var(--line);
}
.date-sep:first-child { margin-top: 0; }

/* ── search results ── */
.bcard {
  display: flex; gap: 15px; padding: 15px; width: 100%; text-align: left;
  background: var(--glass-card); border: 1px solid var(--line); border-radius: 13px; margin-bottom: 12px;
  box-shadow: var(--glass-hi);
  transition: border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.bcard:hover { border-color: var(--line-2); transform: translateY(-2px);
  box-shadow: var(--glass-hi), var(--shadow-soft); }
.bcard .th { width: 92px; height: 68px; flex: 0 0 92px; border-radius: 9px; object-fit: cover;
  background: var(--surface-solid-3); display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: 20px; color: var(--accent); }
.bcard .b { flex: 1; min-width: 0; }
.bcard .t { font-size: var(--fs-xl); font-family: var(--font-display); }
.bcard .o { font-family: var(--font-mono); font-size: var(--fs-sm); color: var(--faint); margin-top: 3px; }
.bnums { display: flex; gap: 20px; margin-top: 11px; }
.bnum .v { font-family: var(--font-mono); font-size: var(--fs-xl); }
.bnum .k { font-family: var(--font-mono); font-size: var(--fs-2xs); color: var(--faint); text-transform: uppercase; letter-spacing: var(--track-caps); }
.why {
  display: inline-flex; align-items: center; gap: 6px; margin-top: 10px; padding: 4px 10px; border-radius: 999px;
  background: var(--accent-a07); border: 1px solid var(--accent-a20); color: var(--accent);
  font-family: var(--font-mono); font-size: var(--fs-xs);
}

@media (max-width: 1100px) {
  .gar-top { grid-template-columns: 1fr; }
  .gar-tiles { grid-template-columns: repeat(4, 1fr); }
  .gar-grid { grid-template-columns: 1fr; }
  /* facets fold back into a scrolling row — the phone's pull-out, one step up */
  .work { grid-template-columns: 1fr; gap: 20px; }
  .facets { position: static; display: flex; gap: 22px; overflow-x: auto; padding-bottom: 12px;
    border-bottom: 1px solid var(--line); }
  .facet-grp { margin-bottom: 0; min-width: 168px; flex: 0 0 auto; }
  .pgrid { grid-template-columns: repeat(2, minmax(0,1fr)); }
}
@media (max-width: 560px) {
  .gar-tiles { grid-template-columns: repeat(2, 1fr); }
  .gar-title { font-size: 27px; }
  .readout .num { font-size: 44px; }
  .gar-cover { aspect-ratio: 16/9; }
  .pgrid { grid-template-columns: 1fr; }
  .ecard, .bcard { flex-direction: column; gap: 12px; }
  .ecard .date { flex-basis: auto; width: 58px; }
  .bcard .th { width: 100%; flex-basis: auto; height: 130px; }
}

/* ── motion ────────────────────────────────────────────────────────────────
   One vocabulary, three entrances: dropdowns drop, the modal pops, routes
   rise. The route stagger is gated on .route-in, which go()/popstate add for
   ~650ms and then remove — so a scroll-append repaint (which rebuilds the same
   children) does NOT replay the entrance every time you reach the feed's end. */
@keyframes drop-in  { from { opacity: 0; transform: translateY(-6px); } }
@keyframes scrim-in { from { opacity: 0; } }
@keyframes modal-in { from { opacity: 0; transform: translateY(14px) scale(.97); } }
@keyframes rise-in  { from { opacity: 0; transform: translateY(10px); } }

.route-in #feed-col > *, .route-in #aside-col > * {
  animation: rise-in var(--dur-med) var(--ease-out) backwards;
}
.route-in #feed-col > :nth-child(2), .route-in #aside-col > :nth-child(1) { animation-delay: 45ms; }
.route-in #feed-col > :nth-child(3), .route-in #aside-col > :nth-child(2) { animation-delay: 90ms; }
.route-in #feed-col > :nth-child(4), .route-in #aside-col > :nth-child(3) { animation-delay: 135ms; }
.route-in #feed-col > :nth-child(5) { animation-delay: 180ms; }
.route-in #feed-col > :nth-child(n+6) { animation-delay: 220ms; }

/* The lit edge and lift stay; the entrance is the only thing motion-gated.
   Blanket kill — covers every animation and transition declared above,
   including the ones added for the glass pass. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-delay: 0ms !important;
    transition-duration: 0.01ms !important;
  }
}

/* ══ BREAKPOINTS ══════════════════════════════════════════════════════════ */

/* tablet / small laptop — rail collapses to icons, right rail drops away */
@media (max-width: 1263px) {
  :root { --rail-w: 72px; }
  #rail { padding: 22px 10px 16px; align-items: center; }
  #rail .wm { padding: 6px 0 26px; }
  #rail .wm svg { display: none; }
  /* The collapsed mark was three strokes meant to read as an f, and reading as a
     scribble at 30px. The wordmark's own letter in the wordmark's own face cannot be
     mistaken for anything else. */
  #rail .wm .mark {
    display: flex; align-items: center; justify-content: center;
    width: 38px; height: 38px; margin: 0 auto;
    font-family: var(--font-display); font-style: italic; font-weight: 600;
    font-size: 27px; line-height: 1; color: var(--accent);
  }
  /* Scoped to #rail. Unscoped these also matched #tabs .nav-item — the phone tab bar
     falls inside this breakpoint too — and stripped the labels off the bottom nav. */
  #rail .nav-item { justify-content: center; padding: 12px; }
  #rail .nav-item .lbl { display: none; }
  /* Icons only, so the shelf keeps its dots (the active car stays readable) and
     loses its heading — a heading with nothing legible under it is just a gap. */
  .nav-sep { width: 30px; margin: 12px auto 8px; }
  .nav-sec { display: none; }
  .nav-sub { justify-content: center; padding: 10px; }
  .nav-sub .lbl { display: none; }
  .acct { justify-content: center; padding: 8px; }
  .acct .who, .acct .cog { display: none; }
  #aside-col { display: none; }
  /* container stays --page-max; the aside is what goes away, not the page width.
     With no aside there is nothing to absorb the slack, so the Paddock feed has to
     fill again — pinning it to 630 here would strand ~335px of dead space on the
     right and re-create exactly the inset the alignment work removed. */
  #main[data-route="paddock"] #feed-col { flex: 1 1 auto; }
}

/* mobile web — rail out, bottom tabs in, full-bleed cards */
@media (max-width: 767px) {
  :root { --rail-w: 0px; }
  #rail { display: none; }
  #tabs { display: flex; }
  #main { margin-left: 0; }
  :root { --page-pad: 12px; --bar-h: 56px; }
  .main-inner { padding: 14px var(--page-pad) 84px; }
  #topbar .wm-sm { display: block; width: 74px; flex: 0 0 74px; color: var(--ink); fill: currentColor; }
  #topbar .tb-btn .lbl { display: none; }
  .search-wrap { flex: 1 1 auto; }
  .card { border-radius: 12px; }
  .big { font-size: 28px; }
  /* ── filters become a pull-out ───────────────────────────────────────────
     At 1100px the facet groups fold into a horizontal scrolling strip, which holds up
     until the viewport is a phone: four groups in a sideways scroller eat the top of
     every list and hide most of themselves off the right edge. Below 768px the same
     element becomes an off-canvas drawer instead — the SAME element, not a second copy,
     so a facet added to either route shows up in the drawer for free. */
  .facet-btn {
    display: flex; align-items: center; justify-content: center; gap: 8px;
    width: 100%; padding: 11px 14px; margin-bottom: 14px;
    border: 1px solid var(--line); border-radius: 10px;
    background: var(--surface-solid-1); color: var(--ink);
    font-family: var(--font-mono); font-size: var(--fs-sm);
    letter-spacing: var(--track-mono); text-transform: uppercase;
  }
  .facet-btn .n { color: var(--accent); }
  .facets {
    position: fixed; top: 0; bottom: 0; left: 0; z-index: 60;
    width: min(86vw, 340px); display: block; overflow-y: auto; overscroll-behavior: contain;
    padding: 18px 16px calc(18px + env(safe-area-inset-bottom));
    background: color-mix(in srgb, var(--bg-2) 78%, transparent);
    -webkit-backdrop-filter: var(--frost-heavy); backdrop-filter: var(--frost-heavy);
    border-right: 1px solid var(--line); border-bottom: none;
    transform: translateX(-100%); transition: transform .22s ease;
  }
  .facets.on { transform: none; }
  .facet-grp { margin-bottom: 22px; min-width: 0; flex: none; }
  .facet-close {
    display: block; width: 100%; margin: 0 0 16px; padding: 10px;
    border: 1px solid var(--line); border-radius: 9px;
    background: transparent; color: var(--muted);
    font-family: var(--font-mono); font-size: var(--fs-sm);
    letter-spacing: var(--track-mono); text-transform: uppercase;
  }
  #facet-veil {
    display: block; position: fixed; inset: 0; z-index: 55; background: rgba(0,0,0,.55);
    opacity: 0; pointer-events: none; transition: opacity .22s;
  }
  #facet-veil.on { opacity: 1; pointer-events: auto; }
  #scrim { padding: 0; }
  #modal { border-radius: 0; max-height: 100vh; height: 100vh; }
  #modal-x { top: 38px; right: 12px; }
}

/* ── imageless part card — a composed plate, not an apology ────────────────
   Cards that never had a photo (demo rows, unharvested vendors) get a monogram
   plate in the part's own category tint, so a grid without photography still
   reads as designed rather than broken. It is an OVERLAY, not a substitute: the
   ghost .ph stays in the DOM under every card so a rotted CDN link degrades to
   it, which is why the tint is painted over an opaque base — a bare rgba(...,.15)
   would let the wrench glyph show through behind the monogram. */
.pcard .img .ph2 {
  position: absolute; inset: 0;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 7px;
}
.pcard .img .ph2 b {
  font-family: var(--font-display); font-weight: 600; font-size: 40px; line-height: 1;
  opacity: 0.92;
}
.pcard .img .ph2 > span {
  font-family: var(--font-mono); font-size: var(--fs-2xs); letter-spacing: var(--track-caps);
  text-transform: uppercase; opacity: 0.75;
}

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

/* ── the shell, after the search became the front door ──────────────────────
   There is no left navigation for now. The only chrome is the search header in
   #find, which the results page and the garage share, so #find has to sit above
   #main in the document and stay in it — findChrome() in app-find.js decides
   which of its parts are up.

   Paddock and Events are parked, not deleted: ?v=paddock and ?v=events still
   paint, they just have nothing pointing at them. */
.hide { display: none !important; }   /* app-find.css scopes its own under #find */

#rail, #tabs { display: none; }
#main { margin-left: 0; }
#find { display: block; }

/* app-core's own top bar is the second search field — the federated one over
   garage/parts/builds/events. The header in #find is the only one now, so this
   one is hidden rather than stacked under it.

   --bar-h has to follow, because --sticky-top derives from it: anything that
   sticks (the results rail, the aside) has to clear the header that is actually
   on screen, and that is now the 71px one in #find. */
#topbar { display: none; }
:root { --bar-h: 71px; }


/* ── the Paddock, centred ────────────────────────────────────────────────
   It wears the same header as the garage and runs as ONE column down the middle.
   The aside it used to carry is gone from this route: the active vehicle now
   lives in the header, and Upcoming Events points at a route that is parked, so
   what is left would be a sidebar of one panel pushing the feed off centre. */
#main[data-route="paddock"] .main-inner { justify-content: center; }
#main[data-route="paddock"] #aside-col { display: none; }
#main[data-route="paddock"] #feed-col { flex: 0 0 var(--feed-w); max-width: 100%; }
