/* ============================================================
   CHICAGO MOTOR CARS — main
   Loaded after tokens.css.
   ============================================================ */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  /* clip, not hidden: `hidden` makes the body a scroll container and
     silently kills position:sticky on every descendant. U6 says clip
     for exactly this reason. */
  overflow-x: clip;
}

h1, h2, h3, p, dl, dd, figure { margin: 0; }
ul, ol { margin: 0; padding: 0; list-style: none; }   /* ol too — the chapter rail and the sell steps are both ordered */
img, svg { display: block; max-width: 100%; }

a { color: inherit; text-decoration: none; }

button, input, select { font: inherit; color: inherit; }

:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  border-radius: 2px;
}

.u-visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.shell {
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* ---- Micro label ------------------------------------------
   The machine register. Uppercase, tracked, never below 14px. */
.micro {
  font-family: var(--font-label);
  /* The alignment the monospace used to give for free, asked for
     explicitly. This is the mechanism that was always doing the work
     for figures; the face was never the reason. */
  font-variant-numeric: tabular-nums;
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  line-height: var(--lh-label);
}

/* ============================================================
   HEADER — transparent over the hero
   ============================================================ */
/* Fixed, not absolute: the page scrolls now, and a header that scrolls
   away takes the phone number with it. */
.masthead {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: 70;      /* above .menu, so the toggle stays the way out */
  height: var(--header-h);
  transition: background-color var(--dur-4) var(--ease-out),
              backdrop-filter var(--dur-4) var(--ease-out);
}

/* ---- The top gradient --------------------------------------
   MENU and the call carry their own grounds; the wordmark does not, and
   it was crossing whatever scrolled beneath it — the Featured Inventory
   eyebrow and heading were reading straight through the header.

   This fixes it the same way the hero does: at the GROUND layer, not
   with a panel behind the type. A solid bar was already rejected and
   would be the wrong answer anyway — it draws an edge across the page
   for the sake of one wordmark.

   Two details that matter more than they look:

   It is TALLER than the header (1.9x) so the fade finishes well below
   the chrome. A gradient that ends on the header's own bottom edge puts
   its termination exactly where the eye is already looking, and the
   fade reads as a band with a hard bottom.

   The last stop is `rgb(var(--bg-rgb) / 0)`, not `transparent`. They are
   not the same thing to interpolate through — the keyword can take the
   ramp through a greyed midpoint and leave a dirty smear over a navy
   page. Fading a colour to nothing means fading THAT colour to zero
   alpha. The intermediate stops approximate an ease so it does not
   band across such a shallow ramp. */
.masthead::before {
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  z-index: -1;                /* behind the rail and the two controls */
  height: calc(var(--header-h) * 1.9);
  pointer-events: none;
  opacity: 0;
  /* The ramp HOLDS across the header's own height and only then falls
     away. The first attempt fell linearly across the whole 1.9x, which
     put alpha at roughly 0.44 exactly at the header's bottom edge —
     measured, the heading passing beneath was still coming through at
     rgb(205,204,204) against a wordmark at 244. Covering where the
     chrome actually is, and fading below it, is what a header scrim is
     for; a straight ramp spends most of its opacity on empty air. */
  background: linear-gradient(to bottom,
    rgb(var(--bg-rgb) / 0.95) 0%,
    rgb(var(--bg-rgb) / 0.92) 34%,
    rgb(var(--bg-rgb) / 0.78) 53%,   /* the header's bottom edge */
    rgb(var(--bg-rgb) / 0.42) 72%,
    rgb(var(--bg-rgb) / 0.14) 88%,
    rgb(var(--bg-rgb) / 0)   100%);
  transition: opacity var(--dur-4) var(--ease-out);
}

/* Only after the hero. Over the photograph the scrim already darkens
   the top band — the masthead measured 6.11:1 there without help — and
   a second wash would just dim a frame chosen for its light. */
.has-js.is-condensed .masthead::before { opacity: 1; }

/* ---- The scrolled masthead ---------------------------------
   The condensed state used to dissolve to nothing: the rail's ground,
   border and blur all went, and what was left was a wordmark, a MENU
   and a phone number floating separately over whatever happened to be
   behind them. Each object was fine and the BAR had stopped existing —
   three things at the top of a page are not a header, they are three
   things.

   So the chrome moves down a level instead of disappearing. The rail
   still dissolves — that decision stands and its reasoning is below —
   but the masthead itself picks up a quiet ground and a rule, which is
   what puts the three objects back on one line and gives the page an
   edge to scroll under.

   THE RULE HAS TWO JOBS AND IS ONE DEVICE. It separates the header from
   the page, and it reports read position by filling in --accent. That is
   deliberate economy: a separate progress bar would be a second element
   doing a job this line is already the right shape for. It is also the
   one place the accent belongs by this project's own rationing — a mark,
   never a ground, arriving in response to what the visitor is doing.

   Scroll-driven, so it costs nothing per frame and the user keeps the
   transport. Under reduced motion, and anywhere scroll timelines are not
   supported, the rule is simply a rule: still separating, no fill. */
/* No ground on the masthead itself. A full-width bar was tried here and
   was both wrong and dead — the ground belongs under the nav items only
   (see .navbar__groups), and a later rule in this file was already
   setting `background: none` on the same selector, so it never rendered.
   The rule and its progress fill are what the scrolled masthead gets. */

/* ---- Read position, bottom edge ----------------------------
   Fixed to the bottom of the VIEWPORT, not to the header. 3px rather
   than a hairline: at 1px it was a scratch under the bar, and a line
   that is reporting something has to be thick enough to be read as a
   quantity rather than as a border.

   Still scroll-driven, so it costs nothing per frame and the user keeps
   the transport. Under reduced motion, and anywhere scroll timelines
   are unsupported, the track is simply a track — no fill, no lie. */
.readbar {
  position: fixed;
  inset-block-end: 0;
  inset-inline: 0;
  z-index: 55;              /* under .menu (60) so an open panel covers it */
  height: 3px;
  background: rgb(var(--bg-rgb) / 0.55);
  opacity: 0;
  transition: opacity var(--dur-4) var(--ease-out);
  pointer-events: none;
}
/* Only after the hero. Over the photograph it would cut across the air
   under the claim, which is the one thing that space is for. */
.has-js.is-condensed .readbar { opacity: 1; }

.readbar__fill {
  display: block;
  height: 100%;
  background: var(--accent);
  transform-origin: left center;
  scale: 0 1;
}

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: scroll()) {
    .readbar__fill {
      animation-name: read-progress;
      animation-fill-mode: both;
      animation-timing-function: linear;
      animation-timeline: scroll(root block);
    }
  }
}
@keyframes read-progress { to { scale: 1 1; } }

@media (prefers-reduced-motion: reduce) {
  .masthead,
  .readbar { transition: none; }
}

/* Full bleed, not contained. The rest of the page reads inside
   --container (1440px), but the header is chrome rather than reading
   matter: on anything wider it was capping at 1440 and centring, which
   left the wordmark floating inland on a 2560px screen instead of
   holding the corner. It keeps the gutter — the mark and the bar still
   line up with the page's own margin — but loses the ceiling, so the
   two ends stay pinned to the two edges at any width. */
.masthead__inner {
  height: 100%;
  width: 100%;
  padding-inline: var(--gutter);
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* Down one step, from 32 to 16. This is the gap between the rail and
     the call, so it is what sits between MENU and the number once the
     rail has condensed — and with the rail's own 12px of right padding
     on top of it, 32 was reading as 44px of nothing between two objects
     that belong together. Now 28. */
  gap: var(--sp-4);
}

/* ---- The wordmark -------------------------------------------
   THE MARK IS INK, AND IT IS OPTICALLY CORRECTED AT SIZE.

   Two defects, both of them in how the mark was RENDERED rather than in
   how it is drawn — the outlines are untouched, which the mandate
   requires.

   1. Colour. The file had #FEFEFE baked into every path, so the
      identity was the only cold-white object on a page whose ink is
      --ink #F3EEE6. On the hero's blue sky that is the worst available
      pairing: cold white on cold blue, no hue separation at all. It is
      currentColor now, so the mark takes the ink of wherever it stands
      and can be set --bone-ink on the light band without a second file.

   2. Weight. This is the real one, and it is exactly the failure a token
      check cannot see. The script's thinnest strokes are about 2.5 units
      of a 1271-unit box — 0.29px at the 146px the rail can afford — so
      they never fill a pixel and arrive as partial coverage.

      Measured by rasterising the mark at 146px and reading the alpha
      channel, against the composited ground behind it (30% --bg over the
      hero sky = #2E527E, L 0.082):

        nominal  #F3EEE6 on that ground ......... 7.12:1   passes
        actual   34.4% of the mark's pixels sit
                 below 40% coverage, i.e. at ..... ≤2.5:1  fails

      So the mark's COLOUR was never the problem and re-inking it fixes
      nothing on its own. The problem is mass: the mark had 861 inked
      pixels carrying 514 units of ink where the mono beside it carries
      solid stems, which is why the navigation was outranking the
      identity in the identity's own header.

      Scaling up does not fix it either — 1px hairlines want ~508px of
      width, which no header has. So the correction goes on the stroke:
      0.6px of NON-SCALING stroke, constant in CSS pixels at every render
      size. Measured at 146px it takes the mark from 861 inked pixels /
      514 ink units to 1314 / 789 — 53% more ink mass, same letterforms —
      and lifts the fully-inked share from 33.0% to 36.6%. At display
      sizes the same 0.6px is 0.07% of the mark and invisible.

      Tested at 0.4 / 0.6 / 0.8 / 1.0 over the real hero: 1.0 closes the
      counters of "g" and "a" at 118px, 0.4 does not clear the pixel.
      0.6 is the measured middle.

   `paint-order: stroke fill` keeps the stroke behind the fill so the
   outline never eats into the letterform's own shape.               */
/* THE MARK HAS TWO SIZES, BECAUSE IT HAS TWO AMOUNTS OF ROOM.

   Measured at 1440, which is the width that decides this:

     full rail  — wordmark + six groups + My Garage + call + padding
                  needs 1364px of 1441. Spare: 77px, and every pixel the
                  mark takes pushes the compact breakpoint up by one.
     condensed  — the rail has dissolved; only the mark, MENU and the
                  call are left. Used: 443px. Spare: 882px.

   One ramp for both states was therefore the wrong shape: it was being
   held down by a constraint that only exists while the navigation is
   unpacked, and starved the state where the mark stands alone with most
   of the header empty beside it. So this is the mark's natural size, and
   .navbar below is the only place it gets cut back. */
.masthead__wm {
  display: block;
  width: clamp(200px, 17vw, 280px);
  height: auto;
  color: var(--ink);
  fill: currentColor;
  stroke: currentColor;
  stroke-width: 0.6;
  paint-order: stroke fill;
}

/* NO HOVER STATE, on purpose. The mark is already at full --ink, so the
   only lift available is toward pure white — which is the exact cold
   value this whole rule set just took OUT of the identity, and a 3%
   luminance move nobody perceives. A home link on a wordmark needs no
   affordance beyond being a wordmark. Focus is covered by the global
   :focus-visible ring at the top of this file. */

/* Wherever the mark stands on the light band it takes that ink instead.
   It could not do this at all while the fill was baked into the file —
   the light-ground case needed a second copy of the logo. */
.on-bone .masthead__wm { color: var(--bone-ink); }

/* ---- The navigation rail ------------------------------------
   One object carrying the wordmark and the whole navigation, with the
   call sitting outside it. That structure is the reference's, and it
   is also what makes the scrolled state calm: the rail keeps its box,
   so nothing slides when its contents change.

   The direction asks for minimum decorative glassmorphism, so the blur
   is down from 24px to 12px and the ground carries the legibility
   instead. The blur was the decoration; the ground is the function. */
.navbar {
  position: relative;
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  /* The padding STAYS even though the ground is gone. It is what holds
     the wordmark --sp-4 in from the gutter, and the hero claim aligns to
     exactly that — see .hero__foot. Removing it here would silently move
     the claim's left edge on the other side of the file. */
  padding: var(--sp-3) var(--sp-3) var(--sp-3) var(--sp-4);
  border-radius: var(--r-rail);
  /* NO GROUND OVER THE HERO. The rail was a frosted capsule at rest, and
     over a photograph this good it was a box drawn around nothing — the
     navigation reads on the sky without it, and the first screen goes
     back to being the picture plus the words rather than the picture
     plus a widget. Kept transparent rather than deleted so the border
     still occupies its pixel and nothing shifts when it arrives below.

     The ground is not gone from the project, it moved to the two places
     that need it: the open dropdown, immediately below, and the
     condensed masthead once the header has left the hero. */
  background: transparent;
  border: 1px solid transparent;
  transition: background-color var(--dur-3) var(--ease-out),
              border-color var(--dur-3) var(--ease-out),
              backdrop-filter var(--dur-3) var(--ease-out);
}

/* The open-panel ground moved to .navbar__groups — see there. The rail
   itself now carries no ground in any state. */

.navbar .masthead__mark { flex: none; }

/* ONE SIZE NOW, AND IT IS THE BIG ONE.

   The mark used to shrink to 12.8vw/196 while the rail was packed and
   only reach 17vw/280 once the rail dissolved on scroll. The scrolled
   size is the right size, so it became the size — the rail no longer
   gets to make the identity smaller than it should be.

   What paid for it: "My Garage" collapses to its glyph and count while
   the rail is packed, which is worth 67px. That is not an improvisation
   — the COMPACT HEADER block below has said since it was written that
   when the rail runs out of room the width has to come from the content
   rather than from squeezing padding, and named this exact control.

   Net effect is better than a straight trade. The mark is 33% wider than
   it was a moment ago and 79% wider than it started, and the compact
   breakpoint still came DOWN, 1410 to 1390, because the glyph freed more
   than the mark took. */
.navbar .masthead__wm { width: clamp(200px, 17vw, 280px); }

/* The condensed state now inherits the same size, so nothing about the
   mark changes on scroll. The transition stays declared because the
   surrounding rail still dissolves around it under --dur-4, and a mark
   whose box is animated by nothing while its neighbours move is the one
   element that would look pinned. */
.has-js.is-condensed .navbar .masthead__wm {
  transition: width var(--dur-4) var(--ease-out);
}
@media (prefers-reduced-motion: reduce) {
  .has-js.is-condensed .navbar .masthead__wm { transition: none; }
}

/* The garage label is present for assistive tech at every width; it is
   only the packed rail that hides it from sight, because that is the one
   state short of room. Above the breakpoint the glyph carries it. */
.navbar__garage-icon { display: none; flex: none; }
@media (min-width: 1391px) {
  .navbar__garage-icon { display: block; }
  .navbar__garage-label {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0; border: 0;
    overflow: hidden; white-space: nowrap;
    clip-path: inset(50%);
  }
}

/* Centred in whatever the wordmark and the counter leave over. */
/* THE GROUND IS UNDER THE NAV ITEMS AND NOWHERE ELSE.

   It used to be the whole rail — one capsule from the wordmark across to
   My Garage — and that box was mostly empty. Over a photograph a
   translucent panel is only legible while it is a contained object, and
   a container that is 60% air is not containing anything; it was reading
   as a widget laid on the picture.

   So the capsule shrank to the six items that actually need a ground to
   sit on. `flex: 0 1 auto` with auto margins is what makes it hug them
   while staying centred in whatever the mark and the garage leave over —
   `flex: 1 1 auto` was stretching it to fill, which is why it could not
   carry a background before. */
.navbar__groups {
  flex: 0 1 auto;
  min-width: 0;
  margin-inline: auto;
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  /* Thicker than the items need. The capsule is an object now rather
     than a tint behind some words, and an object wants its own margin
     before its edge — at the triggers' own height it was shrink-wrap. */
  padding-block: var(--sp-3);
  padding-inline: var(--sp-4);
  border-radius: var(--r-rail);
  background: transparent;
  border: 1px solid transparent;
  transition: background-color var(--dur-3) var(--ease-out),
              border-color var(--dur-3) var(--ease-out),
              box-shadow var(--dur-3) var(--ease-out),
              backdrop-filter var(--dur-3) var(--ease-out);
}

/* It arrives in exactly two situations, both of them the same reason —
   the type underneath stops being reliable:

     scrolled  — the header is now over page content and whatever
                 photograph the hero slider happens to be holding;
     open      — a panel is hanging off the bar and needs something to
                 hang off.

   Over the hero at rest it stays transparent, which is the state the
   first screen is actually judged on. */
/* NO GROUND UNDER THE WORDMARK. A glass capsule was tried here and
   removed at your call — the mark stands on the page bare, like the
   instruction said. The collision it was covering for is real but
   small: the finder bar passes under the mark for a few hundred pixels
   of scroll. If that ever needs solving it should be solved by the
   finder, not by putting a box round the identity. */

.has-js.is-condensed .navbar__groups,
.navbar:has(.navmenu__trigger[aria-expanded="true"]) .navbar__groups {
  background: rgb(var(--bg-rgb) / 0.52);
  border-color: rgba(243, 238, 230, 0.16);
  /* GLASS, and this is a REVERSAL worth naming. The art direction's own
     terms include "minimum decorative glassmorphism", and the blur was
     cut 24px to 12px under it on the grounds that the ground did the
     legibility work and the blur was decoration. Client direction now
     asks for glass, so it is glass — recorded here rather than applied
     quietly, which is this project's rule for reversing a stated term.

     What makes it glass rather than frost: url(#nav-glass) displaces the
     backdrop, so the photograph BENDS through the capsule and keeps
     bending as it scrolls. Blur and saturation alone are frosting; the
     refraction is the part that reads as a material.

     The order matters — displace first, then blur, then lift the colour.
     Blurring before the displacement would smear the noise field itself
     and the warp would vanish into mush. */
  backdrop-filter: url(#nav-glass) blur(10px) saturate(1.5) brightness(1.06);
  -webkit-backdrop-filter: blur(12px) saturate(1.5) brightness(1.06);
  /* The two edges a pane of glass actually has: a lit top rim where it
     catches light and a darker underside where it sits off the surface.
     Without them the capsule reads as a hole, not a solid. */
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.22),
    inset 0 -1px 0 rgba(0, 0, 0, 0.28),
    0 8px 30px rgb(var(--bg-rgb) / 0.45);
}

.navmenu { position: relative; }

.navmenu__trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  border: 0;
  background: transparent;
  cursor: pointer;
  font-family: var(--font-label);
  font-size: var(--t-micro);          /* 14px floor */
  font-weight: 400;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--ink-2);
  padding: var(--sp-3);
  border-radius: var(--r-control);
  transition: color var(--dur-2) var(--ease-out),
              background-color var(--dur-2) var(--ease-out);
}
.navmenu__trigger:hover,
.navmenu__trigger[aria-expanded="true"] {
  color: var(--ink);
  background: rgba(243, 238, 230, 0.08);
}

.navmenu__chev {
  opacity: 0.6;
  transition: transform var(--dur-2) var(--ease-out);
}
.navmenu__trigger[aria-expanded="true"] .navmenu__chev { transform: rotate(180deg); }

/* A top-level item with no children — Sell us your car. Same metrics as
   a trigger so the row stays one line of type; it simply has no chevron
   and nothing to open. */
.navbar__link {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-label);
  font-size: var(--t-micro);
  font-weight: 400;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--ink-2);
  padding: var(--sp-3);
  border-radius: var(--r-control);
  transition: color var(--dur-2) var(--ease-out),
              background-color var(--dur-2) var(--ease-out);
}
.navbar__link:hover,
.navbar__link:focus-visible {
  color: var(--ink);
  background: rgba(243, 238, 230, 0.08);
}

/* The panel is anchored to ITS OWN trigger and sized to its content.

   The earlier version spanned the whole bar, which was eco's geometry —
   but eco has three evenly-loaded groups and this tree does not. Here
   Inventory carries eighteen links over three levels and Services
   carries three; one shared width either strands Services in a 950px
   void or squeezes Inventory. So the panel opens under the word that
   opened it, and takes the width its own content needs. */
.navmenu { position: relative; }

.navmenu__panel {
  position: absolute;
  inset-inline-start: 0;
  /* Detached, not docked. At 8px it read as a panel stuck to the
     underside of the bar — neither one object nor two. At 16px it is
     plainly its own surface floating over the photograph, which is also
     what its own blur and border were already claiming.
     The pointer crosses this gap in far less than the 180ms grace
     period on the bar, so hover does not break. */
  top: calc(100% + var(--sp-4));
  display: flex;
  align-items: flex-start;
  gap: var(--sp-8);
  /* max-content sized it to the longest label and no further, which read
     as cramped — the links were touching the walls. A floor plus columns
     that share the slack gives the list room without letting a short
     menu sprawl. */
  width: max-content;
  min-width: 24rem;
  max-width: min(60rem, calc(100vw - 2 * var(--gutter)));
  padding: var(--sp-6);
  border-radius: var(--r-panel);
  background: rgb(var(--bg-rgb) / 0.82);
  border: 1px solid rgba(243, 238, 230, 0.10);
  backdrop-filter: blur(40px) saturate(1.1);
  -webkit-backdrop-filter: blur(40px) saturate(1.1);
}
.navmenu__panel[hidden] { display: none; }

/* Panels near the right edge open leftward so they cannot run off the
   viewport — the alternative is a menu the last group cannot show. */
.navmenu:nth-last-child(-n + 2) .navmenu__panel {
  inset-inline-start: auto;
  inset-inline-end: 0;
}

/* Columns share whatever width the panel has over its content, so the
   extra room lands between the lists rather than at one edge. */
.navmenu__group { min-width: 0; flex: 1 1 auto; }
.navmenu__group--wide { flex: 2 1 auto; }

.navmenu__list { display: grid; align-content: start; }
.navmenu__list--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); column-gap: var(--sp-6); }

/* Inventory carries eighteen links; the rest carry three to six. */
.navmenu__panel--mega { min-width: 46rem; }

.navmenu__list a {
  display: block;
  font-size: 0.9375rem;
  line-height: 1.35;
  color: var(--ink-2);
  padding: var(--sp-2) var(--sp-3);
  margin-inline: calc(var(--sp-3) * -1);
  border-radius: 8px;
  transition: color var(--dur-1) var(--ease-out),
              background-color var(--dur-1) var(--ease-out);
}
.navmenu__list a:hover,
.navmenu__list a:focus-visible {
  color: var(--ink);
  background: rgba(243, 238, 230, 0.08);
}

/* Entering decelerates; nothing moves for anyone who asked it not to. */
@media (prefers-reduced-motion: no-preference) {
  .navmenu__panel { animation: nav-drop var(--dur-2) var(--ease-out) both; }
}
@keyframes nav-drop {
  from { opacity: 0; transform: translateY(-4px); }
  to   { opacity: 1; transform: none; }
}

/* The pill that closes the bar. Same object as .btn--fill — bone at
   rest, accent on hover — so the control register is one system and
   not a lookalike. */
.navbar__cta {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--font-label);
  font-size: var(--t-micro);
  letter-spacing: var(--tr-label);
  flex: none;
  padding: var(--sp-4) var(--sp-5);
  border-radius: var(--r-control);
  background: transparent;
  color: var(--ink);
  border: 1px solid rgba(243, 238, 230, 0.18);
  transition: background-color var(--dur-2) var(--ease-out),
              border-color var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out);
}
/* Through the ACCENT ACTION tokens, not a second copy of the red. The
   call is an action surface; when it fills, it fills with the system's
   action colour. */
.navbar__cta:hover,
.navbar__cta:focus-visible {
  background: var(--action);
  border-color: var(--action);
  color: var(--action-ink);            /* 6.30:1 */
}
/* Outlined rather than filled, and outside the rail. In the reference
   it is its own object beside the rail, not the light mass closing it —
   which also frees the one bone fill on the screen for the hero's
   primary action, where it does more work. */
.navbar__cta .navbar__cta-icon { display: block; opacity: 0.55; }
/* The glyph is the phone's affordance, not the desktop's — the number
   itself says "call" perfectly well when there is room to print it. */
.navbar__cta-icon { display: none; }

/* My Garage. Present in their header, so present here — but outlined
   rather than filled: one light mass in the bar, and the call has it. */
.navbar__garage {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  font-family: var(--font-label);
  font-size: var(--t-micro);
  letter-spacing: var(--tr-label);
  white-space: nowrap;
  color: var(--ink-2);
  padding: var(--sp-3);
  border-radius: var(--r-control);
  transition: color var(--dur-2) var(--ease-out),
              background-color var(--dur-2) var(--ease-out);
}
.navbar__garage:hover,
.navbar__garage:focus-visible {
  color: var(--ink);
  background: rgba(243, 238, 230, 0.08);
}
.navbar__count {
  min-width: 1.375rem;
  padding-inline: 0.3em;
  text-align: center;
  border-radius: 999px;
  background: rgba(243, 238, 230, 0.12);
  color: var(--ink);
}

/* ---- Condensed, once the header leaves the hero -------------
   The previous version swapped display:none for display:flex, and that
   is exactly why it read as crude: the groups did not fade, they
   vanished, and the rail lost most of its width in one frame, so the
   wordmark and the counter jumped sideways. The abruptness was the
   GEOMETRY, not the speed — a slower display swap would have been just
   as bad.

   So nothing moves now. The rail keeps its box and the wordmark keeps
   its place; only the contents dissolve. The groups and the counter go
   out on opacity, the toggle comes in on opacity in the same trailing
   slot. `visibility` is stepped at the end of the fade so nothing
   invisible stays tabbable, and at --dur-4 it is a slow dissolve rather
   than a cut — the cinematic register the direction asks for.

   The call does NOT fold: it is the one action on a $1.2M car, and
   putting it behind a tap exactly as the visitor gets more interested
   is the wrong way round. */
.navbar__groups,
.navbar__garage {
  transition: opacity var(--dur-4) var(--ease-out),
              visibility 0s linear var(--dur-4);
}
/* THE NAVIGATION SURVIVES THE SCROLL NOW.

   It used to crossfade out and hand over to a MENU toggle at every
   width. That is gone above the compact breakpoint: the bar stays
   sticky and keeps all six groups, and the ground under the items
   arrives instead. A hamburger on a 1440 screen was hiding a navigation
   that fits, which costs a click on every use and is the one thing a
   sticky header exists to avoid.

   Below 1390 the toggle is still the whole way in — there the nav
   genuinely does not fit, and that rule lives in the COMPACT HEADER
   block, untouched. */
@media (max-width: 1390px) {
  .has-js.is-condensed .navbar__groups,
  .has-js.is-condensed .navbar__garage {
    opacity: 0;
    visibility: hidden;
    pointer-events: none;
  }
}

/* Absolute, so it fades in exactly where the groups fade out and takes
   no part in the rail's layout — an appearing toggle must not push
   anything sideways. */
.has-js .menu-toggle {
  position: absolute;
  inset-inline-end: var(--sp-3);
  inset-block-start: 50%;
  translate: 0 -50%;
  display: inline-flex;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-4) var(--ease-out),
              visibility 0s linear var(--dur-4);
}
/* Only below the breakpoint now — above it the groups never leave, so
   there is nothing for the toggle to replace. */
@media (max-width: 1390px) {
.has-js.is-condensed .menu-toggle {
  opacity: 1;
  visibility: visible;
  /* Flush to the rail's edge so the gap to the call is the masthead's
     own, and the two read as a spaced pair rather than one control
     tucked inside a container and another loose beside it. */
  inset-inline-end: 0;
  transition: opacity var(--dur-4) var(--ease-out),
              visibility 0s linear 0s,
              background-color var(--dur-4) var(--ease-out),
              border-color var(--dur-4) var(--ease-out),
              border-radius var(--dur-4) var(--ease-out);
}
}

/* The rail has no ground in any state now, so there is nothing left for
   this to dissolve — it only has to stop moving things.

   THE WORDMARK DOES NOT MOVE ON SCROLL. The left padding used to
   collapse to zero here, sliding the mark 16px onto the gutter line,
   and the note that used to sit in this comment argued that 24px over
   --dur-4 was "a settle, not a jump". It was still a move, and it was
   the identity making it — the one thing on the page that should be
   nailed down while everything else scrolls past. It also dragged the
   mark off the line the hero claim is aligned to, so the two edges
   agreed at rest and disagreed the moment you touched the wheel.

   The padding stays. Nothing in the header changes position on scroll;
   what changes is the ground arriving under the nav items and the rule
   arriving under the bar. */
.has-js.is-condensed .navbar {
  background: transparent;
  border-color: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-radius: 0;
}
.has-js .navbar {
  transition: background-color var(--dur-4) var(--ease-out),
              border-color var(--dur-4) var(--ease-out),
              padding var(--dur-4) var(--ease-out);
}

/* NO continuous header. The masthead stays a transparent frame and the
   chrome lives only in the objects themselves — a full-width bar would
   put a band across the page for the sake of two controls, and it is
   the heavier answer to a state that is supposed to be lighter.

   Each object carries its own ground instead, which is also what keeps
   them legible over whatever scrolls beneath. */
.has-js.is-condensed .masthead {
  background: none;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
}

/* The rail SPLITS rather than dissolves. Its treatment — radius, ground,
   hairline, blur — does not disappear; it is inherited by the two
   objects that survive, so the scrolled state is recognisably the same
   material as the state it came from, just broken in two.
   The wordmark keeps no box: three boxes would be a row of chips. */
.has-js.is-condensed .menu-toggle,
.has-js.is-condensed .navbar__cta {
  border-radius: var(--r-rail);
  background: rgb(var(--bg-rgb) / 0.30);
  border-color: rgba(243, 238, 230, 0.12);
  backdrop-filter: blur(12px);
  -webkit-backdrop-filter: blur(12px);
}
.has-js.is-condensed .menu-toggle:hover,
.has-js.is-condensed .navbar__cta:hover {
  background: rgb(var(--bg-rgb) / 0.52);
  border-color: rgba(243, 238, 230, 0.28);
}
/* The call keeps its accent on hover — the one action that outranks the
   surface it sits on. */
/* Re-asserted at this specificity only because the condensed rule above
   sets a background at the same weight — the VALUE is not repeated. */
.has-js.is-condensed .navbar__cta:hover,
.has-js.is-condensed .navbar__cta:focus-visible {
  background: var(--action);
  border-color: var(--action);
}

@media (prefers-reduced-motion: no-preference) {
  .has-js .masthead { transition: background-color var(--dur-3) var(--ease-out); }
}
@media (prefers-reduced-motion: reduce) {
  .navbar__groups, .navbar__garage,
  .has-js .menu-toggle, .has-js.is-condensed .menu-toggle { transition: none; }
}

/* ---- Menu toggle + panel (mobile) --------------------------
   Hidden until JS confirms the panel can actually open. Without
   JS the nav row itself stays on the page — the routes are never
   locked behind a script. */
/* ---- The header pair ---------------------------------------
   MENU and the call are ONE PAIR and are drawn as one: identical
   height, padding, border, radius and type. They had drifted apart —
   40px against 52px, one filled and one not — which read as two
   controls that happened to land next to each other rather than a set.

   The MENU button itself is after ballance.framer.website: a compact
   labelled rectangle beside its glyph, not a bare icon. Same
   mono/uppercase register as the nav labels it replaces, so the swap
   reads as one system changing state. It also says what it does,
   which three stacked lines never quite manage. */
.menu-toggle,
.navbar__cta {
  min-height: 46px;
  padding-block: 0;
  padding-inline: var(--sp-5);
  border: 1px solid rgba(243, 238, 230, 0.18);
  border-radius: var(--r-control);
  background: transparent;
  color: var(--ink);
  font-family: var(--font-label);
  font-size: var(--t-micro);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  white-space: nowrap;
  /* The radius travels with everything else when the rail splits, so
     the two objects grow into the rail's shape rather than snapping
     into it a frame later. */
  transition: background-color var(--dur-4) var(--ease-out),
              border-color var(--dur-4) var(--ease-out),
              border-radius var(--dur-4) var(--ease-out),
              color var(--dur-2) var(--ease-out);
}

.menu-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  cursor: pointer;
  transition: background-color var(--dur-2) var(--ease-out),
              border-color var(--dur-2) var(--ease-out),
              color var(--dur-2) var(--ease-out);
}
.menu-toggle:hover {
  border-color: rgba(243, 238, 230, 0.34);
  background: rgba(243, 238, 230, 0.08);
}

.menu-toggle__bars {
  display: flex;
  flex-direction: column;
  gap: 5px;
  width: 15px;
  flex: none;
}
.menu-toggle__bars i {
  display: block;
  height: 1.5px;
  background: var(--ink);
  transition: transform var(--dur-2) var(--ease);
}
.menu-toggle[aria-expanded="true"] .menu-toggle__bars i:first-child {
  transform: translateY(3.25px) rotate(45deg);
}
.menu-toggle[aria-expanded="true"] .menu-toggle__bars i:last-child {
  transform: translateY(-3.25px) rotate(-45deg);
}

/* Not a solid slab. Ballance's panel keeps the page faintly present
   underneath — you can still see where you were — which is what stops a
   full-screen menu feeling like a page navigation. A heavy scrim plus
   blur does that; an opaque fill does not. */
.menu {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: rgb(var(--bg-rgb) / 0.94);
  backdrop-filter: blur(28px);
  -webkit-backdrop-filter: blur(28px);
  overflow-y: auto;
}
.menu[hidden] { display: none; }

/* Slow, and staggered by column. The panel arrives, then the groups
   settle into it — one idea unfolding rather than a block appearing.
   Under reduced motion the whole thing is simply there. */
@media (prefers-reduced-motion: no-preference) {
  .menu:not([hidden]) { animation: menu-in var(--dur-3) var(--ease-out) both; }
  .menu:not([hidden]) .menu__group {
    animation: menu-rise var(--dur-4) var(--ease-out) both;
  }
  .menu:not([hidden]) .menu__group:nth-child(2) { animation-delay: 60ms; }
  .menu:not([hidden]) .menu__group:nth-child(3) { animation-delay: 120ms; }
  .menu:not([hidden]) .menu__group:nth-child(4) { animation-delay: 180ms; }
  .menu:not([hidden]) .menu__group:nth-child(5) { animation-delay: 240ms; }
}
@keyframes menu-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes menu-rise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* The trigger does not turn into a different control — it relabels.
   Same box, same place, same size, so the eye never loses it. */
.menu-toggle__label::after { content: "Menu"; }
.menu-toggle[aria-expanded="true"] .menu-toggle__label::after { content: "Close"; }

/* Two bands: the navigation from under the header down, and the
   showrooms closing the panel along the bottom edge.

   Two ROWS, not `align-content: start`, and that is what changed when
   the feature cards left. With three bands the panel filled itself and
   stacking from the top was right. With two it no longer does: at
   1440x900 the content is 742px, so starting from the top left 159px of
   dead ground under the showrooms — and at 1920x1080 it left 339. The
   leftover now falls BETWEEN the two bands instead of below them, which
   is what the direction asks of a panel like this anyway: mass at the
   top, the tail settling onto the bottom edge, the air in the middle
   where it separates two different kinds of thing.

   At short viewports 1fr collapses to nothing and the two bands simply
   sit at their gap, so this costs the tight cases nothing. */
.menu__inner {
  min-height: 100%;
  display: grid;
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr;
  gap: var(--sp-7) var(--sp-8);
  padding-block: calc(var(--header-h) + var(--sp-6)) var(--sp-8);
}
.menu__foot { align-self: end; }
/* .menu__feature and the whole .fcard component are GONE — see the
   comment where they used to sit in index.html. assets/img/stock/ still
   holds both frames. */

/* Four groups now, so the rows drop a rank: the group heading carries
   the structure and the rows sit under it. A 2.25rem row per item would
   run four screens deep for a menu that used to be one. */
.menu__nav {
  display: flex;
  flex-direction: column;
  gap: var(--sp-7);
}

/* The panel is no longer phone-only — the condensed header opens it at
   any width. One column of thirty-five links would be a page of its own
   on a desktop, so the groups sit side by side where there is room. */
@media (min-width: 901px) {
  /* Column widths come from the CONTENT, not from equal division.
     Inventory has eighteen SHORT labels and wants two narrow columns;
     Financing has five LONG ones ("Trucks & Equipment Online Credit
     Approval") and wants a wide single. Four equal columns gave the
     long labels no room — they broke to three and four lines each —
     and handed the short ones space they had no use for. */
  .menu__nav {
    display: grid;
    grid-template-columns: 1.75fr 1.2fr 0.95fr 1fr;
    gap: var(--sp-8) var(--sp-7);
    align-items: start;
  }
  /* Inventory carries eighteen of the thirty-four links. Left in one
     column it ran three times the depth of its neighbours and dragged
     the whole panel down with it. */
  /* Selector raised on purpose: .menu__group is declared after this
     block with the same specificity, so `display: flex` was winning on
     source order and the links stayed in one column while the group
     obediently spanned two. Fourth time this trap has come up here —
     raise the selector, never !important. */
  .menu__nav .menu__group--wide {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--sp-6);
    align-content: start;
  }
  .menu__nav .menu__group--wide .menu__label { grid-column: 1 / -1; }
}

/* The childless entries sit as a row beneath the columns rather than as
   a fifth column with nothing at its head — which would leave its links
   starting a heading's height above everyone else's and read as a
   misalignment rather than a decision. */
.menu__nav .menu__group--plain {
  flex-direction: row;
  flex-wrap: wrap;
  gap: var(--sp-3) var(--sp-6);
  padding-block-start: var(--sp-5);
  border-top: 1px solid var(--rule);
}
@media (min-width: 901px) {
  .menu__nav .menu__group--plain { grid-column: 1 / -1; }
}

.menu__group { display: flex; flex-direction: column; }
.menu__group .menu__label {
  margin-block-end: var(--sp-3);
  padding-block-end: var(--sp-3);
  border-bottom: 1px solid var(--rule);
}
/* 17px, not 20px. At 20px the long financing labels could not hold a
   line in any column arrangement that also fitted four groups across.
   This is the size the reference sets its own menu at. */
.menu__nav a {
  font-size: 1.0625rem;
  font-weight: 400;
  letter-spacing: -0.005em;
  line-height: 1.35;
  padding-block: var(--sp-3);
  color: var(--ink-2);
  transition: color var(--dur-2) var(--ease-out);
}
.menu__nav a:hover, .menu__nav a:focus-visible { color: var(--ink); }

.menu__label {
  color: var(--ink-3);
  margin-block-end: var(--sp-4);
}
.menu__places li {
  display: flex;
  justify-content: space-between;
  gap: var(--sp-4);
  padding-block: var(--sp-3);
  border-bottom: 1px solid var(--rule);
}
.menu__places span {
  font-family: var(--font-label);
  font-size: var(--t-micro);
  color: var(--ink-2);
}

/* ---- FITTING THE PANEL TO THE SCREEN ------------------------
   The panel must not scroll. A menu you have to scroll to reach its own
   links has stopped being a menu — the way out of the page becomes a
   second page.

   Measured at 1440x900 with the feature cards already removed: 1071px
   of panel in 901px of viewport, 170px over. The whole overflow came
   from two places, and both were structure rather than air.

     Inventory alone .... 467px of a 602px nav. It carries 18 of the 34
                          links; in two sub-columns that is 9 rows, and
                          it set the height of the panel by itself while
                          its three neighbours ended 140–280px short.
     Showrooms .......... 237px for four addresses, because they were
                          stacked. Four showrooms are a SET, not a list —
                          stacking them spends a fifth of the panel
                          saying so, and reads as four separate facts
                          rather than one fact with four parts.

   So: Inventory goes to three sub-columns (9 rows to 6), and the
   showrooms go to one row of four. Nothing is cut, no type shrinks, no
   tap target moves. Measured after: 786px in 901. */
/* Inventory's sub-columns are decided by the LABEL, not by a breakpoint.

   The constraint is one string: "Schedule Test Drive" measures 154.4px at
   17px and is the longest in the group. Below that the row wraps, and a
   wrapped row costs more height than the column saved — which is exactly
   what a hand-picked 1200px breakpoint produced when it was tried: three
   columns at 139px each, every third row on two lines, TALLER than two
   columns had been.

   So the floor goes in the track instead. `auto-fit` with a 9.7rem
   minimum takes three columns wherever three fit and two where they do
   not, at every width, with no breakpoint to keep in sync.

   The 24px gap is not a saving either: these three are sub-columns of ONE
   group, so their gap has to read as clearly tighter than the 48px
   between the groups themselves. Internal gap < external gap.

   auto-fit is scoped to 1200 and up on purpose. Below that the Inventory
   track drops under 334px — two 155px columns plus the gap — and auto-fit
   would collapse it to a SINGLE column of eighteen rows, which is worse
   than the wrapping it was brought in to prevent. There it stays pinned
   at two.

   The four track widths are set from the longest label in each group,
   measured, not divided evenly: Services needs 181.5px for "Schedule
   Appointment" and had 172; About Us needs 116.8 for "Our Dealership" and
   had 191. Two of the four were the wrong way round. */
@media (min-width: 1200px) {
  .menu__nav { grid-template-columns: 2.7fr 1.15fr 1fr 0.8fr; }
  .menu__nav .menu__group--wide {
    grid-template-columns: repeat(auto-fit, minmax(9.7rem, 1fr));
  }
}
@media (min-width: 901px) {
  .menu__nav .menu__group--wide { column-gap: var(--sp-5); }
}

/* Rows are 39px on a desktop panel, not 47. The 24px of vertical padding
   was set for a thumb; this panel opens under a mouse at every width
   above 900, and 12px of it was buying nothing but height in a control
   that has 34 rows. 39px still clears WCAG 2.5.8 target minimum (24px)
   with room. Below 901 the touch rhythm is untouched. */
@media (min-width: 901px) {
  .menu__nav a { padding-block: var(--sp-2); }
}

/* SHORT VIEWPORTS: the panel tightens rather than scrolls.

   A 1280x800 laptop cannot take the full rhythm — measured at 860px of
   content against 801 of viewport. Rather than let it scroll, the panel
   spends its air: the clearance under the header drops to the header
   plus one step, the tail padding halves, and the three bands close up.
   Nothing is cut and no type moves.

   Bounded to min-width 901 on purpose. A phone in portrait is often
   under 900px tall too (iPhone 14 is 844), and it must NOT inherit
   desktop row heights — there the panel scrolls, which is the honest
   answer for 34 links under a thumb. */
@media (min-width: 901px) and (max-height: 880px) {
  .menu__inner {
    padding-block: calc(var(--header-h) + var(--sp-3)) var(--sp-6);
    gap: var(--sp-6) var(--sp-8);
  }
  .menu__nav { gap: var(--sp-6) var(--sp-7); }
  .menu__nav .menu__group--plain { padding-block-start: var(--sp-4); }
}

/* Second step, for the 1366x768 class of laptop — about 700px of viewport
   once the browser has taken its chrome. Measured at 751px of content in
   721, so the first step is not enough. The clearance under the header
   goes to zero extra (the header's own height is the whole reservation,
   which is all it ever needed to clear), the tail and the band gaps drop
   one more step, and the group headings pull their rules in.

   This is the floor. Below roughly 660px of viewport the panel scrolls,
   and that is the correct answer rather than a failure: 34 links and four
   showrooms do not fit in half a screen, and cutting routes or dropping
   type under the 14px floor to pretend otherwise would break something
   that matters more than the scrollbar. */
@media (min-width: 901px) and (max-height: 800px) {
  .menu__inner {
    padding-block: var(--header-h) var(--sp-5);
    gap: var(--sp-5) var(--sp-8);
  }
  .menu__nav { gap: var(--sp-5) var(--sp-7); }
  .menu__group .menu__label {
    margin-block-end: var(--sp-2);
    padding-block-end: var(--sp-2);
  }
  .menu__nav .menu__group--plain { padding-block-start: var(--sp-3); }
}

/* One row of four, with the rule above rather than below: the showrooms
   read as one band closing the panel instead of four stacked entries. */
@media (min-width: 901px) {
  .menu__places {
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    gap: var(--sp-6);
  }
  .menu__places li {
    flex-direction: column;
    align-items: start;
    gap: var(--sp-2);
    padding-block: var(--sp-3) 0;
    border-bottom: 0;
    border-top: 1px solid var(--rule);
  }
}

/* ============================================================
   THE SINGLE-SCREEN PAGE
   Scoped to `body.page--screen`, never to the element, because
   main.css is also the design system's stylesheet and design.html
   is a long scrolling document that must keep its scrollbar.
   ============================================================ */
/* The single-screen lock is gone now that a section follows the hero.
   The hero is still exactly 100dvh — the fold below it is a decision,
   not an accident. */

/* The sentinel is the height of the header, pinned at the top of the
   document. Once it has scrolled out of view the header has left the
   hero, and that is the moment the bar condenses. Watched by
   IntersectionObserver, so this costs nothing per frame. */
.scroll-sentinel {
  position: absolute;
  inset-block-start: 0;
  inset-inline-start: 0;
  width: 1px;
  height: var(--header-h);
  pointer-events: none;
}

/* Placeholder for the next chapter. No border, no rule: the hero's
   gradient already resolves to this exact colour, so there is nothing
   for a divider to divide. */
.after {
  min-height: 100svh;
  background: var(--bg);
}

/* ============================================================
   HERO
   One frame, full bleed, one screen.
   ============================================================ */
.hero {
  position: relative;
  height: 100dvh;
  background: var(--bg);
  isolation: isolate;
  overflow: hidden;
}

/* ---- Ground ------------------------------------------------
   An <img> rather than a CSS background: it can carry
   fetchpriority, intrinsic dimensions and a decoding hint, and it
   is the LCP element on a page that is currently nothing else. */
.hero__media {
  position: absolute;
  inset: 0;
  z-index: 0;
}

/* ---- Framing --------------------------------------------------
   The re-saved frame carries deliberate air on every side, so the crop
   is now a composition decision rather than a rescue. Three numbers,
   in one place, and nothing else needs touching to re-frame:

     --frame-zoom  height relative to the box. 100% = the whole frame
                   height fits; above that it crops top and bottom.
     --frame-x     which part of the width stays when it overflows
     --frame-y     which part of the height stays when it overflows
     --frame-drop  how far the frame is pushed BELOW the hero's bottom
                   edge, as a share of the hero height. This is what
                   lowers the car in the composition.

   They are custom properties rather than literal values so a re-frame
   is one edit read as a sentence, not four unrelated numbers hidden
   in a shorthand. Overriding them per breakpoint is the whole mobile
   story below.

   WHY --frame-drop EXISTS, AND WHY --frame-y COULD NOT DO IT.

   --frame-y is inert on every ordinary viewport, which is not obvious
   from reading it. `cover` only honours the vertical half of
   object-position when the image OVERFLOWS vertically, and inside this
   box it does not: at 1440x820 the box is 1426x1051 and the frame
   scales to 1691x1051 — exactly the box height, 265px of overflow all
   of it horizontal. So --frame-x does the work and --frame-y changes
   nothing. It only wakes up past roughly 2:1, where the box finally
   gets wider than the frame's own 1.609.

   What actually decides the vertical crop is the ANCHOR: the element is
   128% of the hero and pinned to its bottom, so the top 28% is what
   hangs off the top edge and gets clipped. Moving the subject down means
   moving that anchor, not repointing the image.

   Hence the ceiling on this number, which is a real constraint and not
   a preference: --frame-drop may never exceed --frame-zoom minus 100%.
   Past that the frame's own top edge comes down past the hero's and the
   composition opens a gap of background above the sky. Here that is 28%,
   and 8% spends less than a third of it. */
.hero__photo {
  --frame-zoom: 128%;
  --frame-x: 54%;
  --frame-y: 62%;
  --frame-drop: 8%;

  position: absolute;
  inset-inline: 0;
  inset-block-end: calc(-1 * var(--frame-drop));
  width: 100%;
  height: var(--frame-zoom);
  object-fit: cover;
  object-position: var(--frame-x) var(--frame-y);
}

/* ---- The slides --------------------------------------------
   Stacked and crossfaded, never slid sideways. A horizontal slide is a
   change of place; a crossfade is a change of subject, and these two
   are the same car in two registers rather than two positions in a
   filmstrip. It is also the only transition that costs nothing when the
   thing arriving is a 21 MB video decode.

   Both slides are always in the DOM and always laid out — only opacity
   moves — so nothing reflows and the frame that is leaving keeps its
   crop while it goes. */
.hero__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity var(--dur-4) var(--ease-out);
}
.hero__slide.is-current { opacity: 1; }

/* ---- The transition: the film pulls back and defocuses -----
   The effect is carried ENTIRELY by the video slide, and that is a
   decision rather than laziness. The photograph is the base layer and
   is on screen from the first paint — blurring it in its resting state
   would mean the page opens on an out-of-focus picture for however long
   the film takes to arrive.

   So only the film moves: it settles from 1.05 and 10px of blur as it
   arrives, and lifts back out to them as it goes. Leaving, that reads
   as the film receding and the frame beneath resolving into focus —
   which is the sequence the hero actually is.

   scale + filter on one element, both transitioned on the same 760ms
   curve, so the two never separate into "it moved, then it sharpened".
   Blur is the expensive half; 10px on one full-screen element for one
   transition is affordable, 30px would not be. */
.hero__slide[data-slide="0"] {
  transform: scale(1.05);
  filter: blur(10px);
  transition: opacity var(--dur-4) var(--ease-out),
              transform var(--dur-4) var(--ease-out),
              filter var(--dur-4) var(--ease-out);
}
.hero__slide[data-slide="0"].is-current {
  transform: none;
  filter: none;
}

@media (prefers-reduced-motion: reduce) {
  /* No crossfade, no zoom, no defocus — a full-viewport scale-and-blur
     is precisely the large-area motion the preference exists to stop. */
  .hero__slide { transition: none; }
  .hero__slide[data-slide="0"] { transition: none; transform: none; filter: none; }
}

/* The photograph is the BASE and never fades out — it is what everything
   else is layered over.

   Found by breaking it: with both slides crossfading, the opening was a
   black screen. The video is transparent until playback actually starts
   (see main.js), so while it waited, slide 1 was showing an empty box
   and slide 2 was faded out — nothing was on screen but the hero's own
   near-black ground.

   Making the still the floor fixes the cause rather than the symptom.
   The video sits above it and dissolves in when it is genuinely running;
   choosing slide 2 simply takes the video away again. Nothing ever has
   to cover a gap, because there is no gap. */
.hero__slide[data-slide="1"] { opacity: 1; z-index: 0; }

/* AND IT HAS TO BE BEHIND. Making the photograph the base fixed the
   black opening but introduced a worse fault: it comes later in the DOM,
   so at equal z-index it painted ON TOP and the video — playing, opaque,
   reporting every sign of health — was covered by it. The state said
   is-playing, the screen said photograph, and both were true.

   Explicit layers rather than document order, because the order here is
   meaningful in the other direction: the film is slide ONE and belongs
   above the still it falls back to. */
.hero__slide[data-slide="0"] { z-index: 1; }

/* ---- The video ---------------------------------------------
   Its own framing variables, not the photograph's. The still is 2301x1430
   (1.61) and the video is 1920x1080 (1.78), so the same --frame-zoom
   would not produce the same crop on the same screen — inheriting the
   numbers would look like sharing a decision while actually making a
   different one.

   The video sits ABOVE the still and starts transparent. main.js fades
   it in only once the browser reports it can actually play through, so
   a slow connection shows the composition rather than a stalled frame,
   and there is never a flash of empty video element. */
.hero__video {
  --frame-zoom: 100%;
  --frame-x: 50%;
  --frame-y: 50%;

  position: absolute;
  inset: 0;
  width: 100%;
  height: var(--frame-zoom);
  object-fit: cover;
  object-position: var(--frame-x) var(--frame-y);
  opacity: 0;
  transition: opacity var(--dur-4) var(--ease-out);
}
.hero__video.is-playing { opacity: 1; }

/* Under reduced motion the element is removed from the picture entirely
   rather than merely paused: a paused video still downloads, still holds
   a decode buffer, and still shows a first frame that may not be the
   frame we composed. The still is the whole hero there, and it was
   built to be. */
@media (prefers-reduced-motion: reduce) {
  .hero__video { display: none; }
}

/* A portrait viewport sees roughly a quarter of a 16:9 frame — measured,
   26% of the width at 390x844 — so no object-position shows a whole car
   and pretending otherwise gives you a wheel and a door. Recomposed
   rather than cropped-and-hoped: the crop moves to the front end, where
   the nose, headlight and splitter still read as a car coming at you.

   The condition is the aspect ratio, not a width breakpoint, because
   the aspect ratio is what actually causes it — a short landscape
   window on a laptop has the same problem at a much larger width.

   This is the interim answer. The real one is a portrait frame shot or
   selected for the phone; a landscape original re-pointed is a repair,
   not an art direction. */
@media (max-aspect-ratio: 1 / 1) {
  /* --frame-drop MUST go back to 0 here. The zoom is 100% in portrait,
     so there is no overhang to spend: any drop at all would pull the
     frame's top edge down into the hero and open bare ground above the
     sky. This is the ceiling named above, hitting its floor. */
  .hero__photo { --frame-zoom: 100%; --frame-x: 60%; --frame-y: 62%; --frame-drop: 0%; }
}

/* ---- The scrim ---------------------------------------------
   Dramatic is a wider tonal range, not a darker picture. So this
   deepens the two ends and leaves the middle band alone: the sky goes
   down at the top, the tarmac goes down at the bottom, and the strip
   where the car and the mountains actually live keeps its daylight.
   Flattening the whole frame with one even wash would throw away the
   reason this photograph was picked.

   Three gradients, each with a job:

   BOTTOM is the heaviest. The tarmac was the brightest thing in the
   frame and it competed with the car; taking it down lifts the car off
   its own floor. It also fixes the worst-measured region of the picture
   — the lower left ran to 1.58:1 against near-white — at the GROUND
   layer, which is where colour I4 says legibility gets fixed, so the
   claim that goes there later needs no panel behind it.

   TOP is light. The sky was already the deepest part of the frame and
   the masthead already cleared AA over it at 6.11:1; this is for the
   frame's shape, not for the chrome.

   LEFT is the asymmetry. The car sits right of centre and the left half
   is empty sky, so darkening from the left builds the contrast toward
   the subject instead of ringing the picture evenly. A symmetrical
   vignette would read as a filter applied to a photo rather than a
   composition. */
.hero__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    /* Built from --bg-rgb — the SAME token the page ground uses — so the
       bottom edge cannot drift away from the section beneath it. These
       were hard-coded rgb(11,11,12); the ground then went navy and the
       two would have parted by fourteen points of blue, drawing a band
       exactly where the join is. A gradient whose job is to match a
       colour has to be made OF that colour, not of a copy of it.

       It reaches full opacity at the bottom edge, not merely dark: at
       0.85 the last 15% of the tarmac still showed through and left a
       faint line at the seam. The last stops sit close together so the
       ramp does its work higher up and the solid strip stays thin. */
    /* The 32/44% stops came up — 0.22 to 0.30 and 0.04 to 0.12 — and the
       left ramp went 0.46 to 0.56. Measured reason, not taste: the
       eyebrow's worst pixel sat at 3.73:1 against 4.5, and closing that
       needed about 0.14 more alpha in this one band. It is spent here,
       at the ground, rather than on a plate behind the type, because
       colour I4 puts legibility at the background layer.

       The car is not paying for it. Its body runs 41-70% up the frame
       where the bottom ramp is 0.12 falling to zero, and the left ramp
       darkens the empty quarter the subject is not in — which is the job
       that ramp was given in the first place. */
    /* FOURTH LAYER: the claim's own ground, shaped like the claim.
       Added when the display went to 107px — at that size the headline
       stopped living in the tarmac and started crossing the car and the
       bright ground beside it, and the first line measured 1.66:1.

       A horizontal band strong enough to fix that would have taken the
       car's whole lower half with it. An ellipse anchored at the bottom
       left does not: it is 0.27 where the first line's worst pixel sits
       and 0.11 across the car's front, because the subject is outside
       the block's footprint and the falloff knows it.

       It is a fourth gradient in one device, not a fourth device — the
       scrim is still the ground, and this is the ground being shaped for
       what stands on it. */
    /* FIFTH LAYER: the same thing, on the other side, for the finder.

       The bottom-left ellipse was built for the claim and the claim only,
       so the rail on the opposite corner has been standing on the plain
       bottom ramp this whole time. Measured on the composite against the
       film's brightest pixels behind the rail, its value lines came out
       at 4.39:1 with --ink-2 and 3.58:1 with --ink-3 — a functional line
       stating what a control is set to, under the 4.5 floor either way.

       Spent here rather than on the type, for the reason already written
       above: colour I4 puts legibility at the background layer. 0.30 at
       the corner falling to nothing by 42% across, which lands the rail
       around 0.20 and its worst pixel at 4.95:1. The car sits outside
       that footprint, the same way it sits outside the claim's. */
    radial-gradient(58% 62% at 100% 100%,
      rgb(var(--bg-rgb) / 0.30) 0%,
      rgb(var(--bg-rgb) / 0.22) 45%,
      rgb(var(--bg-rgb) / 0.08) 75%,
      transparent 100%),
    radial-gradient(110% 95% at 0% 100%,
      rgb(var(--bg-rgb) / 0.74) 0%,
      rgb(var(--bg-rgb) / 0.58) 35%,
      rgb(var(--bg-rgb) / 0.36) 62%,
      rgb(var(--bg-rgb) / 0.12) 82%,
      transparent 100%),
    linear-gradient(to top,
      rgb(var(--bg-rgb)) 0%,
      rgb(var(--bg-rgb) / 0.97) 5%,
      rgb(var(--bg-rgb) / 0.66) 17%,
      rgb(var(--bg-rgb) / 0.36) 32%,
      rgb(var(--bg-rgb) / 0.18) 44%,
      transparent 58%),
    linear-gradient(to bottom,
      rgb(var(--bg-rgb) / 0.55) 0%,
      rgb(var(--bg-rgb) / 0.16) 20%,
      transparent 36%),
    /* Raised and extended for the VIDEO, not for the still.
       A photograph has one worst pixel; a moving picture has a new one
       every frame, and the average lies about it. Measured under the
       claim across the clip with the copy hidden: the average ground ran
       12–17:1 while the WORST pixel hit 2.49:1 at the opening frame and
       3.49:1 at 44s — headlights and reflections crossing exactly where
       the type sits. The old stops were tuned against a single still and
       had no reason to survive that.
       Still the ground layer, not a panel behind the words (colour I4):
       the ramp is longer and starts higher, and the picture keeps its
       right-hand two thirds untouched. */
    linear-gradient(to right,
      rgb(var(--bg-rgb) / 0.72) 0%,
      rgb(var(--bg-rgb) / 0.52) 22%,
      rgb(var(--bg-rgb) / 0.28) 44%,
      rgb(var(--bg-rgb) / 0.08) 64%,
      rgb(var(--bg-rgb) / 0) 80%),
    /* The RIGHT ramp, for the search rail. Weaker than the left one and
       reaching further in, because the claim is 106px bold type that
       needs less help than a 14px label does, and because a hard onset
       here would be the panel edge coming back by another route.
       Mirrored device, not a mirrored curve. */
    linear-gradient(to left,
      rgb(var(--bg-rgb) / 0.62) 0%,
      rgb(var(--bg-rgb) / 0.46) 18%,
      rgb(var(--bg-rgb) / 0.24) 38%,
      rgb(var(--bg-rgb) / 0.07) 58%,
      rgb(var(--bg-rgb) / 0) 76%);
}

/* ---- The claim, bottom left --------------------------------
   Placed low and left because that is where the photograph has room:
   the car sits right of centre with its mass in the upper-middle band,
   and the tarmac below it is the only large quiet field in the frame.
   Putting the claim there uses the picture's own empty quarter instead
   of covering its subject — the type and the car are not competing for
   the same ground, so neither has to be weakened for the other.

   It also gives the first screen a diagonal: mark top left, car centre
   right, claim bottom left. The eye enters at the identity, crosses the
   subject, and lands on the sentence and the two doors.

   NO PANEL, and the block was SHORTENED instead. This is the part that
   had to be measured to be got right, and the first attempt was wrong.

   Measured on the composite — the frame drawn with its real cover maths
   and the three scrim gradients over it, sampled under the actual glyph
   rects at 1440x900. First attempt, block 362px tall with its top at
   y=477:

     eyebrow  14px --ink-2 ....... 1.81:1   needs 4.5   FAIL
     claim    81px --ink ......... 2.58:1   needs 3.0   FAIL
     lede     16px --ink-2 ....... 3.86:1   needs 4.5   FAIL

   All three, and the reason is structural rather than a wrong colour.
   The scrim's bottom ramp is only meaningful below about a third of the
   frame — deliberately, because the band where the car and the mountains
   live is what the photograph was chosen for. A block 362px tall put its
   own top squarely in that daylight band, on tarmac reading 152-198 per
   channel.

   The wrong fix was available and rejected: darkening the ramp enough to
   rescue 14px type at y=477 needs about 0.78 alpha there, and the car's
   front wheel and splitter sit at y=470-530. That trades the subject for
   a caption. So the block lost 100px instead — smaller claim, one-line
   lede, tighter stack, less bottom offset — which moves the small type
   down into ground the scrim was already darkening for it, and the
   eyebrow went to full --ink because at 62% it cannot clear 4.5 over
   anything this photograph offers above its bottom quarter. */
/* THE LEFT EDGE IS THE WORDMARK'S, AND IT IS NOT THE GUTTER.

   The mark does not sit on the gutter while the rail is packed — the
   rail carries --sp-4 of its own padding on that side, so the glyph
   starts 16px further in. Setting the claim to --gutter alone put it
   16px to the LEFT of the mark, which reads worse than no alignment at
   all: two edges that near each other look like one edge drawn twice.

   Below the compact breakpoint, and once condensed, the rail drops that
   padding and the mark settles onto the gutter itself — so the claim
   follows it there too. Same rule, two states, one visible edge in each.

   If the rail's padding changes, this has to change with it. That is the
   cost of aligning to another component's inside rather than to a shared
   token, and it is worth paying here because those two objects are the
   only things on the frame. */
.hero__foot {
  position: absolute;
  z-index: 2;
  /* A RIGHT gutter now, not 0. It could end at the edge while the claim
     was the only thing here; the search panel lives at that end and the
     brief asks for it not to be stuck to the edge. Same gutter as the
     left, so the two objects sit inside one measure. */
  inset-inline: var(--gutter);
  /* Lifted off the bottom edge rather than sitting on it: the scrim
     reaches solid --bg in its last 5%, and type inside that strip would
     read as sitting on the page ground rather than on the photograph. */
  /* Real air under the block now, not just clearance. The claim used to
     sit close to the bottom edge; this leaves the corner open, which is
     what stops a 106px headline from looking like it fell to the floor
     of the frame. It is also the room the slider's controls will want. */
  inset-block-end: clamp(var(--sp-7), 8vh, var(--sp-9));
}
/* The packed rail holds the mark --sp-4 in from the gutter, so the claim
   goes there too. Below the breakpoint the rail drops that padding and
   both fall back to the gutter, which the rule above already does. */
@media (min-width: 1391px) {
  .hero__foot { inset-inline-start: calc(var(--gutter) + var(--sp-4)); }
}

/* Wide enough for the longest authored line to hold. At 44rem the box
   was 704px and "worth chasing." wants ~720 at 107px, so it broke into
   "worth / chasing." and the claim rendered as THREE lines — the one
   thing the two spans exist to prevent. */
/* The claim and the search share the hero's foot: statement left,
   search right, both lifted off the bottom edge by the same amount so
   they sit on one line rather than one floating above the other.
   flex-end, not centre — they are anchored to the same baseline of the
   composition, which is what makes the panel read as belonging to the
   hero rather than as a card dropped on it. */
.hero__foot {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--sp-8);
}
.hero__claim { max-width: min(56rem, 100%); flex: 1 1 auto; min-width: 0; }

/* ---- The hero search rail ----------------------------------
   NOT A CARD. There is no border, no radius, no panel edge and no
   silhouette — the container was removed rather than softened, because
   a rounded rectangle with a lighter fill is still a rectangle and the
   test is whether you notice the shape before the content.

   What replaces it is a CONTRAST FIELD: a semi-transparent navy wash
   that exists only to make type legible over film, and that is MASKED so
   it has no edge to notice. A mask is the only way to do this honestly —
   backdrop-filter and background both paint to the element's box and
   stop dead at it, however soft the colour is. The mask fades the field
   itself to nothing before it reaches any boundary, so there is no line
   anywhere for the eye to find.

   No blur. It was the last thing the field needed and the first thing
   that would have given it away: a blurred region has a visible
   perimeter wherever the mask has not fully reached zero, which is
   exactly the silhouette this is removing. The wash alone carries the
   legibility — measured below.

   The field bleeds well past the content on every side so the fade
   happens in empty space rather than across the words. */
.hunt {
  position: relative;
  flex: none;
  width: clamp(340px, 27vw, 390px);
  padding-block: var(--sp-2);
}
/* NO FIELD OF ITS OWN. A masked wash behind the rail was tried first and
   failed the brief's own test: at any strength that made the labels
   legible it read as a soft rectangle with a findable edge, and the
   shape arrived before the content.

   The legibility comes from the FRAME instead. The scrim already carries
   a left-hand ramp for the claim; it now carries a right-hand one for
   the rail — see .hero__scrim. That belongs to the photograph's tonal
   structure rather than to the search, so there is no object behind the
   search at all, and therefore no edge to notice. It is also the same
   device, in the same place, that every other piece of type on this
   frame already sits on. */

.hunt__eyebrow {
  /* --ink-2, not --ink-3. The third ink level is calibrated against a
     SOLID ground; on a feathered wash over film it never reaches 4.5:1.
     Same correction the hero eyebrow needed for the same reason. */
  color: var(--ink-2);
  margin-block-end: var(--sp-5);
}

/* NO BOX PER FIELD. The row is a label over a value with a hairline
   under it — a spec sheet, not a form. Four bordered slots is precisely
   what makes a search widget read as a booking engine. */
/* ONE LINE WHEN EMPTY, two when answered.

   The label and the control occupy the SAME grid cell while nothing is
   chosen, so the row reads as a single line — the category and its
   chevron, nothing else. "MAKE" over "Select Make" was the category
   said twice, and saying it twice is what made the rail read as a form.

   The placeholder text is still in the <option>; it is only hidden in
   the CLOSED state. Open the menu and "Select Make" is there at the top
   where it is useful. Emptying the option instead would have bought the
   clean line by leaving a blank first row in every dropdown. */
.hunt__field {
  position: relative;
  display: grid;
  grid-template-columns: 1fr;
  /* TWO ROWS, ALWAYS — the space for a value is held from the start.

     Without this the row grew from one line to two the moment a value
     was chosen, and measured, that moved everything above it 23px: fill
     two fields and the rail's top climbed 45px while the visitor was
     looking at it. The button stayed put because the rail is bottom
     aligned, which made it worse, not better — the thing that moved was
     the part being read.

     The cost is honest and small: the empty rows carry air under the
     label instead of closing up tight. That is the price of a rail that
     never moves under the pointer, and it is the right way round. */
  /* The reserved height is held by min-height, and the content is
     CENTRED inside it rather than pinned to the top. That was the fault
     in the first attempt: the label sat on row one, the empty value on
     row two, and the chevron went down with it — a label at the top of
     the row and an arrow at the bottom with a hole between them.

     align-content centres the whole stack, so an empty row reads as one
     line sitting on the row's middle and a filled row reads as two
     lines sitting on the same middle. Neither is anchored to an edge,
     so neither looks like it is waiting for something. */
  min-height: 4.5rem;
  align-content: center;
  row-gap: 3px;
  /* Padding INSIDE by default, and the row pulled back out by the same
     amount. That gives three things at once:
       · the hairline spans the row's padded box, so it is wider than
         the words rather than ending exactly where they do;
       · the hover wash can match that line precisely, because they are
         now the same box;
       · the text still starts on the rail's own left edge, in line with
         FIND YOUR NEXT CAR above and the button below — which is why
         the padding is paired with a negative margin instead of simply
         indenting the row. */
  padding-block: var(--sp-3);
  padding-inline: var(--sp-4);
  margin-inline: calc(var(--sp-4) * -1);
  border-bottom: 1px solid rgba(243, 238, 230, 0.16);
  transition: border-color var(--dur-2) var(--ease-out);
}

/* ---- Hover -------------------------------------------------
   The rows had no affordance at all: they read as type, not as
   controls, and nothing said they could be operated.

   The wash is on a PSEUDO-ELEMENT that bleeds sideways, not on the row
   itself. Padding the row would indent its text, and that text is on
   the same left edge as the eyebrow above and the button below —
   one hover would have knocked a whole column out of alignment. The
   bleed gives the highlight air around the words without moving them,
   and the hairline keeps its own length.

   Kept far lighter than a filled block: this rail had every trace of
   card language taken out of it, and a solid panel appearing under the
   pointer would put that back one row at a time. 0.055 is enough to
   read as a target and not enough to read as a box.

   focus-within as well as hover, so the keyboard gets the same signal
   rather than a lesser one. */
/* The chevron, centred on the row's full height. One mark for the whole
   control, level with the middle of the stack in both states. */
.hunt__field::after {
  content: "";
  position: absolute;
  /* Inset by the row's own padding, so the chevron clears the highlight's
     edge by the same amount the label clears it on the left. At 0 it sat
     flush against the wash and read as stuck to it rather than as living
     inside the row. */
  inset-inline-end: var(--sp-4);
  inset-block-start: 50%;
  translate: 0 -50%;
  width: 9px;
  height: 6px;
  background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='9' height='6' viewBox='0 0 9 6' fill='none'%3E%3Cpath d='m1 1 3.5 3.5L8 1' stroke='%23F4F2EF' stroke-opacity='.62' stroke-width='1.3' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E") no-repeat center / 9px 6px;
  pointer-events: none;
  transition: opacity var(--dur-2) var(--ease-out);
  opacity: 0.75;
}
.hunt__field:hover::after,
.hunt__field:focus-within::after { opacity: 1; }

.hunt__field::before {
  content: "";
  position: absolute;
  z-index: -1;
  /* Flush with the row's own box, which is the line's box — so the
     highlight ends exactly where the hairline ends. It bled 16px past
     it before, which read as a wash floating loose of the rule it was
     supposed to belong to. */
  inset: 2px 0;
  border-radius: 4px;
  background: transparent;
  transition: background-color var(--dur-2) var(--ease-out);
  pointer-events: none;
}
.hunt__field:hover::before,
.hunt__field:focus-within::before { background: rgba(243, 238, 230, 0.055); }

/* The rule under the row firms up with it — the row's own edge
   responding, rather than a second thing lighting up. */
.hunt__field:hover,
.hunt__field:focus-within { border-bottom-color: rgba(243, 238, 230, 0.34); }

/* The label lifts out of its resting level. The value is already --ink
   and has nowhere to go, which is correct: the answer does not need to
   brighten, the question does. */
.hunt__field:hover .hunt__label,
.hunt__field:focus-within .hunt__label { color: var(--ink); }

.hunt__label {
  grid-area: 1 / 1;
  color: var(--ink-2);
  transition: color var(--dur-2) var(--ease-out);
}

/* The native control, stripped to its text and given the site's own
   chevron. It stays a real <select>: the platform picker beats any
   custom listbox on a touch device and it is keyboard-complete free. */
/* THE CONTROL IS THE WHOLE ROW, and it is invisible.

   It used to sit in the grid as a second line, which meant an empty row
   still carried an empty control's height: the label centred above the
   true middle and the chevron sat 15px below it — measured. Taking the
   select out of the flow lets the row hold ONLY what it has to show, so
   a label alone centres properly and lines up with the chevron.

   It also makes the entire row the hit target rather than just the line
   of text, which is what a row that looks like a control should be. */
.hunt__control {
  position: absolute;
  inset: 0;
  z-index: 1;
  appearance: none;
  width: 100%;
  height: 100%;
  border: 0;
  background: transparent;
  opacity: 0;                       /* invisible, still fully operable */
  cursor: pointer;
  color: transparent;
  font-family: var(--font-body);
  font-size: 1.125rem;
  line-height: 1.35;
  padding: 0 var(--sp-5) 0 0;
  cursor: pointer;
  /* A stroked CHEVRON, not a filled triangle. The two-gradient trick
     draws a solid wedge, which is heavier than anything else in this
     rail and belongs to a different drawing vocabulary — every other
     mark here is a hairline.

     It is the same path the nav's chevrons use (`m1 1 3.5 3.5L8 1` at
     1.3 stroke in a 9x6 box), so the two controls are provably the same
     glyph rather than two things that look alike. A data URI cannot
     inherit currentColor, so --ink-2's components are written out —
     #F3EEE6 at 0.62, which is that token, spelled. */
  /* No chevron here any more. It sat on the control, so it rode the
     control's line — which after the height was reserved put it at the
     bottom of the row while the label sat at the top. It belongs to the
     ROW, not to the value, so it is drawn on the row and centred there;
     see .hunt__field::after. */
  transition: color var(--dur-2) var(--ease-out);
}
.hunt__control:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; border-radius: 2px; }

/* ---- Answered ----------------------------------------------
   The row opens into two lines and the hierarchy inverts: the category
   demotes to a caption and the VALUE takes the rank the label had.

   Two lines rather than the value replacing the label outright. With
   four fields, a rail reading FERRARI / MODEL / SUV / PRICE is a list
   of unrelated words — you can no longer tell an answer from a
   question. The caption costs one line and keeps every row legible as
   a pair.

   The rail is bottom-aligned with the claim, so this growth happens
   UPWARD: the CTA never moves while the fields fill. */
/* The value is its own line, and it only exists once there is one.
   Empty rows therefore hold a single centred label; filled rows hold a
   centred pair. Both sit on the row's middle, level with the chevron. */
.hunt__value {
  display: none;
  color: var(--ink);
  font-size: 1.125rem;
  line-height: 1.35;
  padding-inline-end: var(--sp-5);   /* clear of the chevron */
}
.hunt__field.is-filled .hunt__value { display: block; }
.hunt__field.is-filled .hunt__label { color: var(--ink-3); }
/* The list is drawn by the OS and cannot inherit any of this, so it is
   given the page's own ground rather than left white. */
.hunt__control option { background: var(--bg-raised); color: var(--ink); }

/* The one hard-edged object in the rail, and deliberately so: it is the
   final action, not part of a container. Warm-white pill, full width. */
.hunt__go {
  width: 100%;
  margin-block-start: var(--sp-6);
  justify-content: center;
}

/* ---- The panel on smaller screens --------------------------
   Two stages, and the first one is NOT a breakpoint on width alone.

   Between roughly 1100 and 1280 the claim and the panel both still fit
   side by side but the headline starts losing its authored two-line
   break to the narrower column — so the panel gives up width first,
   down to its 300px floor, before anything stacks. */
@media (max-width: 1180px) {
  .hunt { width: 300px; padding: var(--sp-4); }
  .hero__foot { gap: var(--sp-6); }
}

/* Below this the two cannot share a line without one of them becoming a
   column of wrapped fragments, so the panel goes UNDER the copy rather
   than beside it — the brief's instruction, and the right one: a 300px
   panel next to a squeezed headline reads as a leftover, not a layout.

   The hero also stops being a fixed 100dvh here: the foot is in normal
   flow, so the section takes the height its contents need instead of
   clipping them. That is the one structural change on mobile and it is
   what stops the panel from being cut off by the fold. */
@media (max-width: 900px) {
  .hero { height: auto; min-height: 100svh; }
  .hero__foot {
    position: relative;
    inset: auto;
    flex-direction: column;
    align-items: stretch;
    gap: var(--sp-7);
    padding-block: calc(var(--header-h) + var(--sp-8)) var(--sp-8);
  }
  .hunt { width: 100%; max-width: 34rem; }
  /* The media is absolute inside a now-taller hero, so it still covers
     the whole section rather than only the first screen of it. */
  .hero__media { position: absolute; inset: 0; }
}

/* ---- Slider pagination, right edge --------------------------
   Vertical, on the gutter opposite the claim, so the first screen reads
   left-to-right as claim then position — the same order a spread's
   folio sits in. Ticks rather than dots: a dot says "one of several
   equal things", a rule of varying length says "you are here in a
   sequence", which is what a hero slider actually communicates.

   Inert and aria-hidden until the slider exists — see the markup. */
/* ---- The slide counter -------------------------------------
   An instrument detail, not a carousel widget: a thin ring, a large
   muted number, and a red arc that reports how much of the current
   slide is left. It sits in the claim's stack above the eyebrow, on the
   same left edge as everything else in the text block, so it belongs to
   the composition rather than floating near it.

   Third in the hierarchy by construction — the number is --ink-3 and
   the ring is barely there. Nothing here is allowed to reach the
   headline's weight. */
/* The instrument and the slide's name read as one line. The margin
   that used to sit on the counter moves here, so the row is the thing
   spaced from the claim rather than the dial inside it. */
.hero__meter {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  margin-block-end: var(--sp-5);
}

/* The name of the current slide. Same technical register the eyebrow
   had, because it IS the eyebrow's words — tracked uppercase mono at
   the 14px floor. Full --ink rather than --ink-2: it sits high on the
   frame where the scrim is thinnest, which is the measurement that put
   the eyebrow at full ink in the first place. */
/* 16px and 2px tracking as briefed — and therefore NOT red.

   The two requests are incompatible on this ground, and the numbers
   say so rather than taste. Measured under the word itself with the
   text hidden, the worst pixel of the frame behind it is rgb(10,39,54).
   Against that, --accent #BC0F0D reads 2.70:1. At 16px the floor is
   4.5:1, and font-weight cannot buy the exemption: "large" starts at
   18.66px bold, or 24px at any weight.

   The red that WOULD pass here is #F35A5A at 4.73:1 — a coral, not
   this brand's red. Shipping that would mean quietly changing the
   colour to keep the word "red" true, which is worse than not being
   red.

   So the accent stays where it already works and is already legal: the
   arc, which is a rule and not text. Two ways to have red here if it
   is wanted — 20px bold, where the 3:1 floor applies and #BC0F0D
   clears at 3.31; or a darker ground under the word, which would mean
   putting back the plate the hero just had removed. */
.hero__slide-title {
  color: var(--ink);        /* 12.5:1 on the measured ground */
  font-size: 1rem;          /* 16px, as briefed */
  font-weight: 500;   /* DM Mono tops out at 500 */
  letter-spacing: 2px;
  line-height: 1;
  transition: opacity var(--dur-2) var(--ease-out);
}
/* It changes with the slide, so it crossfades rather than cutting —
   the one thing on this row that swaps its content. */
.hero__slide-title.is-swapping { opacity: 0; }

.counter {
  position: relative;
  display: grid;
  place-items: center;
  flex: none;
  width: 68px;
  height: 68px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}
.counter:focus-visible { outline: 2px solid var(--ink); outline-offset: 6px; border-radius: 999px; }

.counter__ring {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  overflow: visible;
}
.counter__track,
.counter__arc {
  fill: none;
  stroke-width: 1.75;
}
/* Very thin, very low contrast — the ring is a bezel, not a border. */
.counter__track { stroke: rgba(243, 238, 230, 0.18); }

/* The arc. dasharray is the full circumference so dashoffset can run it
   from empty to full; both are set here rather than in JS so the
   geometry lives with the drawing. r=32.6 -> C=204.8. */
.counter__arc {
  stroke: var(--accent);
  stroke-linecap: round;
  stroke-dasharray: 204.8;
  stroke-dashoffset: 204.8;          /* empty */
}

/* Driven by the video: main.js writes the film's real duration into
   --run and flips --run-state with playback, so the arc reports the
   film rather than a timer running alongside it. Linear, because an
   eased instrument misreports the quantity it is showing. */
.counter.is-timing .counter__arc {
  animation: counter-run var(--run, 47s) linear forwards;
  animation-play-state: var(--run-state, running);
}
@keyframes counter-run {
  from { stroke-dashoffset: 204.8; }
  to   { stroke-dashoffset: 0; }
}
/* A slide with nothing timing it shows NO ARC — not a full one.

   The photograph has no duration, so a complete red ring would be the
   instrument reporting a quantity that does not exist, and a full red
   circle reads as "finished" or worse as an alarm. Empty is the honest
   state: the bezel and the number, nothing running, because nothing is.
   The arc returns the moment the film does. */
.counter.is-static .counter__arc { stroke-dashoffset: 204.8; }

.counter__n {
  position: relative;
  font-family: var(--font-label);
  font-size: 1.5rem;
  font-weight: 500;
  letter-spacing: 0.02em;
  line-height: 1;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;   /* 01 and 02 must not shift width */
  transition: color var(--dur-2) var(--ease-out);
}
.counter:hover .counter__n { color: var(--ink-2); }

@media (prefers-reduced-motion: reduce) {
  .counter.is-timing .counter__arc { animation: none; stroke-dashoffset: 0; }
  .counter__n { transition: none; }
}

/* Below this the hero stacks and the instrument has nowhere quiet to
   sit; the film still hands over on its own and the search rail is the
   thing that matters on a phone. */
@media (max-width: 900px) {
  .hero__meter { display: none; }
}

/* THE CLAIM IS THE GROTESQUE, BOLD, AT FULL DISPLAY SIZE.

   Client direction, and it is a reversal of the serif display that was
   itself a reversal — recorded here rather than quietly applied, which
   is this project's standing rule for the display face.

   It brings --tr-display back into use. That token reads "a grotesque at
   display wants tight" and had been stranded when the display face went
   serif; --tr-claim, added an hour ago for the serif, is now the stranded
   one and stays only for whatever else takes that face.

   It also happens to help the measurement. Bold sans lays down far more
   ink per glyph than a 400-weight didone, so the worst-case pixel under
   the claim is a solid stem rather than a hairline serif — the same
   reason the wordmark needed a stroke put back on it. */
.claim {
  font-family: var(--font-body);
  font-size: clamp(2.75rem, 7.4vw, 6.75rem);
  font-weight: 700;
  line-height: var(--lh-display);
  letter-spacing: var(--tr-display);
  /* 15ch breaks it as "Find the car / worth chasing." — two lines with
     the verb and the object on the first. A measure that let it run to
     one line would put the whole sentence across half the frame and take
     the diagonal out of the composition. */
}
/* The break is authored, so no measure gets to add one. `nowrap` is the
   guarantee: two spans only promise two lines if neither can wrap inside
   itself, and a container 16px too narrow was enough to break that.

   Safe at every width because the size is 7.4vw between its stops, so
   the line scales with the viewport it has to fit in — and the clamp
   floor is 2.75rem rather than 3.25 for exactly this reason: at 3.25 the
   longest line needed 350px on a 390px phone with 48px of gutters, and
   nowrap would have pushed 8px off the side. */
.claim__line { display: block; white-space: nowrap; }

.lede {
  font-size: var(--t-lede);
  line-height: var(--lh-body);
  color: var(--ink-2);
  /* One line at desktop widths. Two lines put 25px of block height into
     the bright band for a sentence that fits without them. */
  max-width: 48ch;
  text-wrap: pretty;
}

/* The hero's stack is tighter than the page default, and the reason is
   the measurement above, not preference: every step here is block height
   that lifts the small type toward the daylight band. */
.hero__claim.stack > .micro + *   { margin-block-start: var(--sp-2); }
.hero__claim.stack > h1 + *       { margin-block-start: var(--sp-4); }
.hero__claim.stack > * + .cta-row { margin-block-start: var(--sp-5); }

/* ---- The claim leaves on the scroll -------------------------
   MOTION READ. Role: transition, not decoration — it is the hero
   handing the page over. Subject: the claim, which is the thing the
   first screen is about; the photograph stays put and the type is what
   moves, so the frame reads as a fixed stage the words step off. One
   temporal idea for this viewport, and it is this one.

   SCROLL-DRIVEN, NOT SCRIPTED. `view-timeline` on the hero, so the
   whole thing runs on the compositor and the page keeps its scroll —
   the user drives it, can stop it anywhere, and every stopped frame is
   a designed frame. The alternative was a scroll listener, which this
   project already rejected once for the header: it would run every
   frame to answer a question the browser can answer for free.

   THE STAGGER is the four rows leaving on four different ranges, so the
   block opens out as it rises instead of sliding as one slab. The
   eyebrow goes first because it is nearest the edge it leaves by, and
   the buttons last because they are the last thing worth keeping.

   @supports IS LOAD-BEARING, not politeness. Without a timeline an
   `animation` with `fill: both` falls back to the document timeline,
   runs once on load, and leaves the whole claim blurred to zero opacity
   on a browser that simply does not do scroll-driven animation. The
   guard is what stops "no animation" from meaning "no hero". */
/* Only a `to`, and only a `from`, on purpose. The missing keyframe in
   each is the UNDERLYING value, which is what lets these two coexist on
   one element without fighting: hero-in resolves against the base style,
   and hero-exit resolves against whatever hero-in has left behind. Give
   either one both ends and the later effect starts clobbering the
   earlier one at scroll 0. */
@keyframes hero-in {
  from {
    translate: 0 var(--in-rise);
    filter: blur(var(--in-blur));
    opacity: 0;
  }
}

@keyframes hero-exit {
  to {
    translate: 0 var(--rise);
    filter: blur(var(--blur));
    opacity: 0;
  }
}

@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .hero { view-timeline: --hero block; }

    /* FIVE MOVERS, NOT FOUR CHILDREN — and this is the reason the claim
       is two spans. The lines have to travel independently, so the thing
       that animates is the LINE, not the heading that contains it.

       Only leaves are listed. Animating .claim as well as its lines
       would compound two translates on the same pixels and the second
       line would leave at the sum of both speeds. */
    /* SCOPED TO THE HERO, and that is a bug fix rather than tidiness.
       These read `.lede` and `.cta-row` unqualified, so the inventory
       section's own lede picked up the hero's scroll-exit: by the time
       you had scrolled to it, hero-exit had driven it to opacity 0 and
       blurred it off, and the paragraph was invisible while still taking
       its space in the flow. Any component class used as an animation
       target has to be qualified by the section that owns the gesture. */
    .hero__claim .claim__line,
    .hero__claim .lede,
    .hero__claim .cta-row {
      /* TWO animations, two timelines, one element. hero-in runs on the
         document timeline once, at load; hero-exit runs on the hero's
         view timeline, driven by the wheel. They are listed in that
         order so the exit composes on top of whatever the entrance has
         settled to — see the keyframes for why each has only one end. */
      animation-name: hero-in, hero-exit;
      animation-duration: var(--dur-4), auto;
      animation-delay: var(--in-delay), 0s;
      animation-fill-mode: both, both;
      animation-timing-function: var(--ease-out), linear;
      animation-timeline: auto, --hero;
      animation-range: normal, var(--exit-range);
      will-change: translate, filter, opacity;
    }

    /* Different distances over overlapping ranges IS the difference in
       speed: same scroll, further to go, faster. Top travels furthest
       because it has the least to say and the shortest way to the edge;
       the buttons travel least because they are the last thing worth
       keeping on screen. The block stretches open as it goes rather than
       sliding as a slab.

       Blur is tied to speed, not applied evenly — the faster a line
       moves the more it smears, which is the only reading of blur that
       is about motion rather than about frosting. */
    /* The rise is roughly half as far again as it was. At the old
       distances the top line cleared the frame before it had visibly
       smeared, so the blur arrived and left without being seen; further
       to travel over the same range means more speed, which means the
       blur has time to register.

       The entrance is the same idea run backwards: up out of blur from
       below, stepped, and the step order is REVERSED. Coming in, the
       eyebrow leads because it is the first thing to read; going out, it
       leads because it is the first thing to lose. Same lean, opposite
       reasons, and it keeps the two gestures from looking like one
       animation played in reverse. */
    /* RE-TIMED after the eyebrow was removed. It was the leader of both
       gestures — first in at 80ms, first out at exit 0%, and the deepest
       lift at -340px. Deleting it and leaving the rest untouched would
       have started the entrance 180ms late for no reason a viewer could
       see, and shortened the exit's reach by the whole of its first
       step. So the claim inherits the lead: the delays shift down by the
       eyebrow's 100ms, and the exit ranges and lifts each move up one
       place. Same shape, one fewer element. */
    .hero__claim .claim__line:first-child {
      --rise: -300px; --blur: 17px; --exit-range: exit  0% exit 42%;
      --in-rise:  52px; --in-blur: 16px; --in-delay:  80ms;
    }
    .hero__claim .claim__line:last-child {
      --rise: -225px; --blur: 13px; --exit-range: exit  4% exit 48%;
      --in-rise:  52px; --in-blur: 16px; --in-delay: 180ms;
    }
    .hero__claim .lede {
      --rise: -150px; --blur:  9px; --exit-range: exit  9% exit 55%;
      --in-rise:  36px; --in-blur: 10px; --in-delay: 300ms;
    }
    .hero__claim .cta-row {
      --rise:  -90px; --blur:  6px; --exit-range: exit 14% exit 62%;
      --in-rise:  28px; --in-blur:  8px; --in-delay: 400ms;
    }
  }
}

/* Wraps rather than overflows: at 390px two pills do not fit on a line,
   and a horizontal scrollbar on the first screen is worse than a second
   row. */
.cta-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-4);
}
.cta-row .btn { align-self: start; }

/* Matched to the nav chevron's 1.3 stroke — one icon set, one weight,
   per typography I9. It moves on hover because the button already
   promises a destination; nothing else in the pill moves. */
.btn__arrow {
  flex: none;
  transition: translate var(--dur-2) var(--ease-out);
}
.btn:hover .btn__arrow { translate: 2px -2px; }
@media (prefers-reduced-motion: reduce) {
  .btn__arrow { transition: none; }
  .btn:hover .btn__arrow { translate: none; }
}

/* No serif to lean on: the rank is size and weight, and the stress is
   a WEIGHT DROP rather than an italic — the move the 360 record
   extracted when the client took the serifs away. */


/* The stress is a weight drop, not an italic. Against 400 the 200 is
   four steps away — nothing can be mistaken for "the same word,
   emphasised". */

/* ---- The roster -------------------------------------------
   Real stock, named. Everything else on this page could belong to any
   dealer; this list could not. */


.roster__list a:hover,
.roster__list a:focus-visible {
  color: var(--ink);
  padding-inline-start: var(--sp-3);
}

.roster__more:hover,
.roster__more:focus-visible { color: var(--ink); }

/* ============================================================
   THE DOORS — buying and selling in one band
   ============================================================ */
.doors {
  position: relative;
  border-top: 1px solid var(--rule);
  background: rgba(6, 6, 7, 0.52);
  backdrop-filter: blur(16px);
}

.doors__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: stretch;
  gap: 0;
}

.finder {
  min-width: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr)) minmax(0, 1.4fr) auto;
  align-items: stretch;
  min-width: 0;
}

.field {
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: var(--sp-1);
  padding: var(--sp-4) var(--sp-5) var(--sp-4) 0;
  min-width: 0;
}

.field + .field {
  padding-inline-start: var(--sp-5);
  border-inline-start: 1px solid var(--rule);
}

.field__label { color: var(--ink-3); }

.field__control {
  appearance: none;
  width: 100%;
  border: 0;
  background: transparent;
  padding: 0;
  font-size: 1.0625rem;
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink);
  cursor: pointer;
}

.field__control::placeholder { color: var(--ink-3); font-weight: 400; }
input.field__control { cursor: text; }

select.field__control {
  padding-inline-end: var(--sp-5);
  background-image: linear-gradient(45deg, transparent 50%, var(--ink-2) 50%),
                    linear-gradient(135deg, var(--ink-2) 50%, transparent 50%);
  background-position: right 6px center, right 1px center;
  background-size: 5px 5px, 5px 5px;
  background-repeat: no-repeat;
}
select.field__control option { background: var(--bg-raised); color: var(--ink); }

/* ============================================================
   BUTTONS
   Pills, and the primary is light rather than red.

   What was wrong: the primary was a saturated #BC0F0D rectangle at
   full rail height. It passed contrast at 5.74:1 and was still the
   wrong object — measurement cannot catch register. Three vault
   entries say so directly:

     polestar-com-us (2, in), weaknesses — "Large discounts, monthly
     lease prices, incentives and promotional disclaimers appear very
     early... this weakens the brand's premium confidence."
     mclaren-com (2, in), works — the accents are "used functionally,
     adding recognition and energy WITHOUT COVERING THE INTERFACE IN
     THE BRAND COLOR."
     rolls-roycemotorcars-com (3, in), measured at capture — "3 pill
     controls and 0 circular controls at 1440x900."

   So: the primary is a bone pill with a near-black label. On a black
   page it is the only light mass, which makes it primary without any
   hue at all — and it is the most expensive control available here.

   The red is not deleted, it is moved to where McLaren puts papaya:
   it arrives on HOVER. Red becomes the response to intent rather than
   a plea for it. Everywhere else the accent is a mark, never a
   ground — active chapter, active reel number, the error glyph, a
   link on hover.
   ============================================================ */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-3);
  align-self: center;
  border: 0;
  border-radius: 999px;
  padding: var(--sp-4) clamp(var(--sp-6), 2.4vw, var(--sp-7));
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  white-space: nowrap;
  cursor: pointer;
  transition: background-color var(--dur-1) var(--ease-out),
              box-shadow var(--dur-1) var(--ease-out),
              color var(--dur-1) var(--ease-out);
}

/* Primary — the light mass. 16.8:1 at rest, 5.74:1 on hover. */
.btn--fill {
  background: var(--bone);
  color: var(--bone-ink);
}
/* Hover changes the colour, focus draws the ring, and neither does the
   other's job. Folding :focus-visible into the colour change also made
   the specimen dishonest: .is-focus cannot fire :focus-visible, so the
   forced row showed bone while the live control went red. */
.btn--fill:hover,
.btn--fill.is-hover {
  background: var(--action);
  color: var(--action-ink);      /* 6.30:1 — via the tokens, not a literal */
}

/* ---- ACCENT ACTION -----------------------------------------
   A high-priority conversion action, used selectively on dark or
   cinematic surfaces. Search Inventory, Get an Offer, and their like.

   IT IS NOT A DANGER VARIANT. Nothing here means destructive, error or
   warning, and it must never be reached for to mean them — which is
   the whole reason it is named for the job rather than the colour.
   `.btn--danger` would be a different component with a different
   meaning that happened to share a hue.

   USE SPARINGLY: at most one dominant Accent Action inside a single
   primary visual region. Red works in this system by scarcity — it is
   otherwise a mark, never a ground — and a page where every call to
   action is red has no call to action at all.

   Hover and press go DENSER, never brighter. A CTA that lights up on
   approach reads as a toy; one that deepens reads as taking the weight.
   No glow, no gradient, no gloss, no shadow — the colour is the whole
   signal. */
.btn--accent {
  background: var(--action);
  color: var(--action-ink);              /* 6.30:1 */
  box-shadow: inset 0 0 0 1px var(--action-edge);
}
.btn--accent:hover,
.btn--accent.is-hover {
  background: var(--action-hover);       /* 7.01:1 */
}
.btn--accent:active {
  background: var(--action-press);       /* 8.50:1 */
}
.btn--accent:focus-visible,
.btn--accent.is-focus {
  outline: 2px solid var(--action-focus);
  outline-offset: 3px;
}

/* Secondary — the same pill, drawn instead of filled. */
.btn--line {
  background: transparent;
  color: var(--ink);
  box-shadow: inset 0 0 0 1px var(--rule-strong);
}
.btn--line:hover,
.btn--line.is-hover { box-shadow: inset 0 0 0 1px var(--ink); }

/* The ring follows the pill instead of boxing it. */
.btn:focus-visible,
.btn.is-focus {
  outline: 2px solid var(--ink);
  outline-offset: 3px;
  border-radius: 999px;
}

/* On the light band both invert or they disappear into it.

   These carry their own hover rules on purpose. `.on-bone .btn--fill`
   is (0,2,0) and so is `.btn--fill:hover` — equal specificity, and this
   block comes later, so without the pair below the base rule silently
   won and hover did nothing on the bone band. Third time this exact
   trap has appeared in this project. Raise the selector, never reach
   for !important. */
.on-bone .btn--fill, .record .btn--fill {
  background: var(--bone-ink);
  color: var(--bone);
}
.on-bone .btn--fill:hover, .on-bone .btn--fill.is-hover,
.record .btn--fill:hover, .record .btn--fill.is-hover {
  background: var(--action);
  color: var(--action-ink);      /* the action colour does not invert */
}
.on-bone .btn--line, .record .btn--line {
  color: var(--bone-ink);
  box-shadow: inset 0 0 0 1px var(--bone-rule);
}
.on-bone .btn--line:hover, .on-bone .btn--line.is-hover,
.record .btn--line:hover, .record .btn--line.is-hover {
  box-shadow: inset 0 0 0 1px var(--bone-ink);
}
.on-bone .btn:focus-visible, .on-bone .btn.is-focus,
.record .btn:focus-visible, .record .btn.is-focus {
  outline-color: var(--bone-ink);
}

.doors__sell {
  display: flex;
  align-items: center;
  gap: var(--sp-5);
  padding: var(--sp-4) 0 var(--sp-4) clamp(var(--sp-5), 3vw, var(--sp-8));
  border-inline-start: 1px solid var(--rule);
}

.doors__label { color: var(--ink-3); white-space: nowrap; }

/* ============================================================
   2 — THE RECORD
   ============================================================ */
.record {
  background: var(--bone);
  color: var(--bone-ink);
}

.record__inner {
  /* bottom-heavy, 1:1.5 — the section gets a clean ending instead of
     being shoved into the next one. */
  padding-block: clamp(var(--sp-8), 7vw, var(--sp-9)) clamp(var(--sp-9), 11vw, var(--sp-11));
  display: flex;
  flex-direction: column;
  gap: var(--sp-7);
}

.record__eyebrow { color: var(--bone-ink-2); }

.record__claim {
  font-family: var(--font-display);
  font-size: clamp(2.125rem, 4.4vw, 4.25rem);
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: var(--tr-display);
  max-width: 22ch;
}

.record__claim em { font-style: normal; font-weight: 200; }

/* Not four figures at one weight — that is a data wall, and the
   headline above already says thirty thousand in words. One number
   becomes a moment; the rest stay quiet and support it. */
.ledger {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) repeat(3, minmax(0, 1fr));
  align-items: end;
  border-top: 1px solid var(--bone-rule);
}

.ledger__cell {
  padding: var(--sp-5) var(--sp-5) 0 0;
  min-width: 0;
}

.ledger__cell + .ledger__cell {
  padding-inline-start: var(--sp-5);
  border-inline-start: 1px solid var(--bone-rule);
}

/* Raised above `.ledger dd`, which is (0,1,1) and would otherwise win
   on source order — the same trap the promoted plate figure hit. */
.ledger__cell--lead { padding-block-start: var(--sp-6); }
.ledger .ledger__cell--lead dd {
  font-size: clamp(3rem, 7.4vw, 6.5rem);
  line-height: 0.86;
  letter-spacing: -0.05em;
}

.ledger dt {
  color: var(--bone-ink-2);
  margin-block-end: var(--sp-3);
}

.ledger dd {
  font-family: var(--font-display);
  font-size: clamp(1.375rem, 2.1vw, 1.875rem);
  font-weight: 600;
  line-height: 1;
  letter-spacing: var(--tr-count);
  font-variant-numeric: tabular-nums;
}

.record__places {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: var(--sp-3) var(--sp-5);
  padding-block-start: var(--sp-5);
  border-top: 1px solid var(--bone-rule);
  font-size: var(--t-micro);
  color: var(--bone-ink-2);
}

.record__places span { color: var(--bone-ink); }

/* ============================================================
   RESPONSIVE — the hero is recomposed, not stacked
   ============================================================ */

/* ============================================================
   COMPACT HEADER — the measured breakpoint
   ============================================================
   1410px is not a round number chosen by eye. It is what the full rail
   MEASURES, re-derived after the second increase to the wordmark:

     wordmark 196 + six groups 676 + My Garage 135 + call 165
     + 28 rail padding + 48 rail gaps + 32 inner gap + 128 gutters = 1408

   It has moved twice, and that is the point of writing it as a sum: it
   is DERIVED, so it has to be re-derived every time one of its terms
   moves. 1360 when the mark capped at 146, 1390 at 175, 1410 at 196.
   Below it the items have nowhere to go and they overlap the wordmark,
   which is exactly what a 900px breakpoint inherited from the old layout
   was letting happen for 460px of viewport width.

   The figures above are the WIDE case — the mark at its 196px cap and
   64px gutters. Nearer the breakpoint both are smaller (12.8vw and 4vw),
   so the real overlap does not start until about 1370; 1410 keeps a
   margin rather than sitting on the edge.

   THE COST, stated plainly: a 1400px window now gets the compact header
   where it used to get the full rail. That is a 20px band of viewport
   widths, and it is what the larger mark was bought with. 1440, 1512,
   1728 and 1920 all keep the full navigation.

   If the full nav needs to survive a 1280 laptop, the width has to come
   from the CONTENT — dropping "My Garage" to its glyph frees 100px —
   not from squeezing the padding further.
   ============================================================ */
@media (max-width: 1410px) {
  /* No JS: the rail unpacks into a plain wrapped list under the
     wordmark and every panel stands open, so all six groups stay
     reachable without a script. The frosting comes off — a translucent
     surface over a photograph is legible only while it is one contained
     object, and this is now a column. */
  .masthead {
    position: static;
    height: auto;
    background: var(--bg-sunk);
    border-bottom: 1px solid var(--rule);
  }
  .masthead__inner { flex-wrap: wrap; padding-block: var(--sp-4); row-gap: var(--sp-4); }
  .navbar {
    flex-wrap: wrap;
    background: none;
    border: 0;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    padding: 0;
    gap: var(--sp-4);
  }
  .navbar__groups { flex-wrap: wrap; gap: var(--sp-4); justify-content: flex-start; }
  .navmenu { position: static; }
  .navmenu__trigger { pointer-events: none; padding-inline: 0; }
  .navmenu__chev { display: none; }
  .navmenu__panel {
    position: static;
    display: block;
    width: auto;
    max-width: none;
    padding: 0;
    background: none;
    border: 0;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
    animation: none;
  }
  .navmenu__panel[hidden] { display: block; }
  .navmenu__list--2 { grid-template-columns: 1fr; }
  .navmenu__list a { margin-inline: 0; padding-inline: 0; }
  .navbar__garage { padding-inline: 0; }

  /* With JS the header goes back to being an overlay and the panel
     takes over. */
  .has-js .masthead {
    position: fixed;
    height: var(--header-h);
    background: none;
    border-bottom: 0;
  }
  /* THE LOGO WAS BEING CUT OFF BY THE TOP OF THE SCREEN.

     .masthead__inner was already nowrap here, but the wordmark lives
     inside .navbar, and .navbar carries its own flex-wrap: wrap at this
     breakpoint. So the bar held one line while the rail inside it broke
     into two: measured at 375, the mark sat at -13 (half of it above the
     viewport) and MENU hung at 81, below the 68px bar it belongs to.

     The rail stops wrapping with the bar, and the mark comes down to fit
     the row it now has to share: 375 less two 24px gutters is 327, of
     which MENU takes 119, the call 46 and the gaps about 24. 138 is what
     is left, so the wordmark is capped under it. */
  .has-js .masthead__inner { flex-wrap: nowrap; padding-block: 0; }
  .has-js .navbar { flex-wrap: nowrap; }

  .has-js .navbar__groups,
  .has-js .navbar__garage { display: none; }

  /* THE BUG THIS FIXES: the base rule hides the toggle with
     opacity/visibility so it can crossfade on scroll. The old compact
     rule only set `display`, so at narrow widths the button existed,
     occupied space, and was completely invisible — a header with no way
     into the navigation at all. Every property the base rule sets has
     to be undone here, not just the one. */
  .has-js .menu-toggle {
    position: static;
    translate: none;
    display: inline-flex;
    opacity: 1;
    visibility: visible;
    transition: none;
  }

  .has-js .navbar {
    padding: 0;
    background: none;
    border: 0;
    backdrop-filter: none;
    -webkit-backdrop-filter: none;
  }

  /* Groups and My Garage are hidden below this breakpoint, so the rail
     is not being shared here either — the mark takes its natural size,
     same as the condensed state, for the same reason. */
  .has-js .navbar .masthead__wm { width: clamp(124px, 33vw, 240px); }

  /* THE MARK KEEPS THE CORNER AT EVERY WIDTH.

     This rail used to carry `margin-inline-start: auto; flex: none`, which
     did keep the call beside the toggle — and took the WORDMARK with it,
     because the wordmark lives inside the rail. So for the whole range
     560–1360px, which is every tablet and most laptops, the identity let
     go of the left gutter and arrived shoulder-to-shoulder with MENU, with
     the entire left half of the header empty. Both other states of this
     header — full rail, and condensed after scroll — have the mark alone
     on the left. It was the one thing the header was supposed to hold
     constant, and it was the one thing that moved.

     The auto margin belongs on the TOGGLE, not on the rail. The rail keeps
     its full width, the mark keeps the gutter, and the controls still
     close the right end together, which is all the old rule was for. */
  .has-js .navbar { flex: 1 1 auto; }
  .has-js .menu-toggle { margin-inline-start: auto; }
}

/* The call keeps its number while there is room to print one, and drops
   to the glyph only when the wordmark, the toggle and a twelve-digit
   number stop fitting together — measured, not assumed. */
@media (max-width: 560px) {
  .has-js .navbar__cta {
    width: 46px;
    padding-inline: 0;
    justify-content: center;
  }
  .has-js .navbar__cta-text { display: none; }
}

@media (max-width: 900px) {

  /* One column. The roster stays — it is the content, not decoration
     — but shortens to what fits above the search. */


  /* The note stays — it carries facts that exist nowhere else on the
     page. Only the roster shortens, and it was a sample of 299 to
     begin with; the count and the link still state the whole. */
  .roster__list li:nth-child(n + 5) { display: none; }

  /* One column: the two doors stack, and each keeps its full width
     so neither reads as the secondary one. */
  .doors__inner { grid-template-columns: 1fr; }
  .finder { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .field { padding: var(--sp-3) var(--sp-4) var(--sp-3) 0; }
  /* Targeted by class, not by position: the form's first child is a
     visually-hidden <h2>. It is out of grid flow but nth-child still
     counts it, so every positional selector here is off by one. */
  .field:nth-of-type(odd) { padding-inline-start: 0; border-inline-start: 0; }
  .field.field--wide {
    grid-column: 1 / -1;
    padding-inline-start: 0;
    border-inline-start: 0;
    border-top: 1px solid var(--rule);
  }
  .btn { grid-column: 1 / -1; justify-self: stretch; }
  /* The label and the button are both nowrap; side by side their
     min-content exceeded 390px and stretched the whole grid column,
     which overflow-x:hidden then quietly cropped. They wrap here. */
  .doors__sell {
    flex-wrap: wrap;
    padding-inline-start: 0;
    border-inline-start: 0;
    border-top: 1px solid var(--rule);
    padding-block: var(--sp-4);
    gap: var(--sp-3);
  }
  .doors__sell .btn { flex: 1 1 100%; padding-block: var(--sp-4); }
  .ledger { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .ledger__cell--lead { grid-column: 1 / -1; }
  .ledger__cell { padding: var(--sp-4) var(--sp-4) 0 0; }
  .ledger__cell + .ledger__cell { padding-inline-start: 0; border-inline-start: 0; }
  .ledger__cell:nth-child(even) {
    padding-inline-start: var(--sp-4);
    border-inline-start: 1px solid var(--bone-rule);
  }
  .ledger__cell:nth-child(n + 3) { border-top: 1px solid var(--bone-rule); }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

/* ============================================================
   THE VEHICLE CARD
   No container, no border, no button. The photograph is the card
   and the whole card is the target — a repeated filled CTA across a
   grid is what turns a decision point into wallpaper.
   ============================================================ */
/* Full-height column with the metadata pushed to the bottom. That is
   what gives the row its common baseline: the photographs top-align at
   their own heights, the captions bottom-align on one line, and the
   air between them is the composition rather than an accident.

   Without it the featured card's caption hung 250px below the other
   three and the four read as unrelated search results. */
/* EVERYTHING HANGS FROM THE BOTTOM LINE.

   First attempt bottom-aligned only the captions and left the images
   where they fell. That gave a common baseline and a 340px hole in the
   middle of the row — the three small photographs floated at the top
   with nothing under them until their captions. Technically aligned,
   visually broken.

   `justify-content: flex-end` drops the whole card — photograph and
   caption together — onto the baseline. The leftover air moves to the
   TOP of the row, where it reads as breathing space under the section
   header instead of as a hole in the middle of the module. The images
   now hang from a common line at different heights, which is the
   editorial arrangement rather than a grid that failed to fill. */
.vcard {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-width: 0;
  height: 100%;
}

.vcard__meta {
  display: flex;
  flex-direction: column;
  padding-block-start: var(--sp-5);
}

/* NO CAR IS CROPPED. Measured, because this looked like a styling
   preference and is not:

     senna     326x210   1.552
     458       326x175   1.863
     aventador 326x209   1.560
     diablo    327x191   1.712

   There is no single aspect ratio that holds that spread. At 4/3 the
   458 lost 30% of its width; at 16/10 the others lost their roofs. Any
   `cover` box crops most of the set, and picking the ratio that crops
   least is still choosing which cars to damage.

   So the box stops being a crop and becomes a MOUNT: the frame is
   contained whole, and the leftover is a deliberate mat a shade below
   the page rather than a failed fill. Every car arrives entire,
   whatever the feed sends — which is the point, because the feed is
   going to send exactly this spread.

   This is the treatment the deleted feature-card component had worked
   out and took with it when it went. Same reasoning, recovered.

   When the real feed lands with consistent crops this can go back to
   cover and the mat disappears on its own. */
/* THE MAT IS GONE, and the live feed is why.

   The mount treatment above it existed because the screenshot-derived
   frames ran 1.55 to 1.86 and no single ratio held them without cutting
   a car. The real feed does not have that problem — measured on the four
   downloaded frames, 1.504 / 1.504 / 1.504 / 1.538. So a 3:2 box crops
   at most 2.3%, on one car, at its edges.

   That is the condition the old note set for going back: "when the real
   feed lands with consistent crops this can go back to cover and the mat
   disappears on its own." It landed. Full-bleed photography, no grey
   bars, and effectively nothing lost.

   2px radius, not 6. Rounding is the strongest single signal of an
   ecommerce product tile, which the brief rules out; a near-square
   corner reads as a mounted plate. */
.vcard__media {
  position: relative;
  display: block;
  /* 4:3 on the supporting cards so their photographs have real height
     at a 300px column — a 3:2 box there was 179px tall and the cars were
     too small to appreciate. It crops 11% of the frame width, 5.5% off
     each end, and these are centred studio profiles with margin to spare.
     The featured card keeps the true 3:2 below. */
  aspect-ratio: 4 / 3;
  overflow: hidden;
  background: var(--bg-raised);
  border: 1px solid rgba(243, 238, 230, 0.07);
  border-radius: 2px;
}

.vcard__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: opacity var(--dur-3) var(--ease-out),
              scale var(--dur-3) var(--ease-out);
}

/* Hover budget is ONE move. Scale is out — it resamples the frame and
   costs a repaint; the rule firming up is the cheaper, quieter signal,
   and it has keyboard parity. */
/* Hover: the frame lifts off the page and the car grows inside its
   mount. Two moves that are one idea — the mount stays put, the picture
   comes forward — rather than the whole card sliding, which would break
   the row's alignment on every pointer pass. Keyboard parity throughout. */
.vcard__media {
  transition: border-color var(--dur-2) var(--ease-out),
              transform var(--dur-2) var(--ease-out),
              box-shadow var(--dur-2) var(--ease-out);
}
/* Restrained: the border firms up and the car grows a little inside its
   mount. The lift and the drop-shadow are gone — "heavy shadows" is on
   the brief's avoid list, and a card that rises off the page is the
   ecommerce tile gesture. Nothing moves; the frame just wakes up. */
.vcard:hover .vcard__media,
.vcard:focus-visible .vcard__media {
  border-color: rgba(243, 238, 230, 0.26);
}
.vcard:hover .vcard__media img,
.vcard:focus-visible .vcard__media img { scale: 1.03; }

/* ---- View details -------------------------------------------
   A SPAN, not a link. The whole card is already one <a>, and a link
   inside a link is invalid markup that browsers silently unnest — so
   this is the affordance for the card's own target, not a second one.
   That also keeps the earlier objection satisfied: there is still only
   one thing to click per card, so the row is not four competing CTAs. */
.vcard__more {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
  align-self: flex-start;
  margin-block-start: var(--sp-5);
  font-family: var(--font-label);
  font-size: var(--t-micro);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--ink-3);
  transition: color var(--dur-2) var(--ease-out);
}
.vcard__more svg { transition: translate var(--dur-2) var(--ease-out); }
.vcard:hover .vcard__more,
.vcard:focus-visible .vcard__more { color: var(--ink); }
.vcard:hover .vcard__more svg { translate: 3px 0; }

@media (prefers-reduced-motion: reduce) {
  .vcard__more, .vcard__more svg { transition: none; }
  .vcard:hover .vcard__more svg { translate: none; }
}

@media (prefers-reduced-motion: reduce) {
  .vcard__media, .vcard__media img { transition: none; }
  .vcard:hover .vcard__media, .vcard:focus-visible .vcard__media { transform: none; }
  .vcard:hover .vcard__media img, .vcard:focus-visible .vcard__media img { scale: 1; }
}

/* The showroom, because four of them is a real differentiator and the
   buyer needs to know which floor the car is standing on. */
/* Bottom-left, because every one of their frames carries the script
   wordmark burned in along the top. A pill at the top lands on it. The
   real photographs found this; the placeholders never would have. */
.vcard__place {
  position: absolute;
  inset-block-end: var(--sp-3);
  inset-inline-start: var(--sp-3);
  font-family: var(--font-label);
  font-size: var(--t-micro);
  letter-spacing: var(--tr-label);
  color: var(--ink);
  background: rgba(6, 6, 7, 0.72);
  backdrop-filter: blur(8px);
  padding: var(--sp-1) var(--sp-3);
  border-radius: 999px;
}

/* FOUR RANKS, NOT FOUR THINGS AT THE SAME WEIGHT.

   The old caption set year, model, price and mileage at nearly one
   value, so the eye had nowhere to land and the block read as a data
   row. Now: the model is the first thing read after the photograph,
   the price answers it, the year/make and mileage are technical
   support, and the action is the quietest thing in the card. */

/* 1 — model. Largest type in the card. */
.vcard__name {
  font-size: 1.3125rem;
  font-weight: 600;
  line-height: 1.2;
  letter-spacing: -0.015em;
  color: var(--ink);
  margin-block-start: var(--sp-2);
}

/* 2 — year + make. Technical label above the name. */
.vcard__trim {
  color: var(--ink-3);
  letter-spacing: var(--tr-label);
}

/* 3 — price strong, mileage quiet. A middot between them rather than a
   rule: the divider was drawing a spreadsheet line across every card. */
.vcard__facts {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-3);
  margin-block-start: var(--sp-4);
  font-family: var(--font-label);
  font-variant-numeric: tabular-nums;
}
.vcard__price {
  font-size: 1.0625rem;
  font-weight: 500;
  color: var(--ink);
  letter-spacing: -0.01em;
}
.vcard__miles {
  font-size: var(--t-micro);
  color: var(--ink-3);
}
.vcard__miles::before {
  content: "·";
  margin-inline-end: var(--sp-3);
  color: var(--ink-3);
}

.on-bone .vcard__trim,
.on-bone .vcard__facts { color: var(--bone-ink-2); }
.on-bone .vcard__facts { border-color: var(--bone-rule); }
.on-bone .vcard__facts b { color: var(--bone-ink); }

/* ============================================================
   THE STACK — differentiated vertical rhythm
   Proximity IS the hierarchy. A uniform gap flattens a heading and
   its lead into a list; these relationships are set once, as a
   component, so no stack is spaced by hand.
   ============================================================ */
.stack > * + *              { margin-block-start: var(--sp-4); }
.stack > .micro + *         { margin-block-start: var(--sp-3); }
.stack > h1 + *,
.stack > h2 + *             { margin-block-start: var(--sp-5); }
.stack > .lede + *          { margin-block-start: var(--sp-6); }
.stack > * + .cta-row       { margin-block-start: var(--sp-7); }

/* ---- Skeleton ---------------------------------------------
   Shape-matched, opacity only. No shimmer sweep — that is the
   template look — and the box matches the real card's frame so the
   swap shifts nothing. */
.skeleton {
  background: var(--bg-raised);
  border-radius: 3px;
  animation: skeleton-pulse 1.6s var(--ease-out) infinite;
}
.skeleton--media { aspect-ratio: 4 / 3; }
.skeleton--line  { height: 1em; }
.skeleton--short { width: 42%; }

@keyframes skeleton-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.55; }
}

@media (prefers-reduced-motion: reduce) {
  .skeleton { animation: none; opacity: 0.8; }
}

/* ============================================================
   FORCED STATES
   A system page must never draw a state by hand. These classes are
   targeted by the SAME rules as the real pseudo-classes, so a
   specimen cannot drift away from the component it documents.
   ============================================================ */
.is-focus { outline: 2px solid var(--ink); outline-offset: 3px; border-radius: 2px; }

.vcard:hover .vcard__media,
.vcard:focus-visible .vcard__media,
.vcard.is-hover .vcard__media { box-shadow: inset 0 0 0 1px var(--rule-strong); }

.roster__list a:hover, .roster__list a:focus-visible, .roster__list a.is-hover {
  color: var(--ink);
  padding-inline-start: var(--sp-3);
}

/* ---- The standalone field ---------------------------------
   The finder rail has its own layout, but the field itself is a
   component and has to exist outside it. */
.field--single {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: 0;
  border: 0;
}
.field--single .field__control {
  border-bottom: 1px solid var(--rule);
  padding-block-end: var(--sp-2);
  transition: border-color var(--dur-1) var(--ease-out);
}
.field--single .field__control:hover,
.field--single .field__control.is-hover { border-color: var(--rule-strong); }
.field--single .field__control:focus-visible {
  border-color: var(--ink);
  outline: 2px solid var(--ink);
  outline-offset: 4px;
}
.field--single .field__control:disabled,
.field--single .field__control.is-disabled {
  color: var(--ink-3);
  border-color: rgba(243, 238, 230, 0.07);
  cursor: not-allowed;
}
.field--error .field__control { border-color: var(--accent); }
.field__error {
  font-family: var(--font-label);
  font-size: var(--t-micro);
  color: var(--ink);            /* the message is ink; red is the marker, not the text */
}
.field__error::before { content: "! "; color: var(--accent); }

/* ============================================================
   3 — INVENTORY
   ============================================================ */
/* overflow-x: clip, not hidden — the full-bleed stage below uses 50vw,
   which counts the scrollbar and pushed 7px of horizontal scroll onto
   the page. `clip` contains it at the section without creating a scroll
   container, so nothing inside loses position: sticky. */
.inv {
  background: var(--bg);
  overflow-x: clip;
}

/* ---- The inventory container ------------------------------- */
.inv__inner {
  width: min(100% - 2 * clamp(24px, 3.4vw, 60px), 1580px);
  margin-inline: auto;
  max-width: none;
  padding-inline: 0;
  /* The section now ends shortly after the index, not a screen later.
     The old tail was leftover column height rather than negative space —
     C11: a section resolves, it does not stop because the module ended. */
  padding-block: 0 clamp(var(--sp-8), 5vw, var(--sp-9));
}

/* ---- Section intro -----------------------------------------
   The browse link sits on the EYEBROW's line, not adrift at the side.
   Together they make a utility line running the width of the section,
   with the title beneath it — so the link reads as part of the intro
   rather than as something parked in the corner.

   The title carries real display scale now. At its previous size it was
   smaller than the vehicle it was introducing and read as a label on a
   module rather than the name of a section. */
/* A single column now. The two-column grid existed to seat an eyebrow
   on the left and a browse-all link on the right, on one line above the
   title; both are gone, and a grid whose second column has nothing to
   put in it is a rule describing a layout that no longer happens.
   The heading and its lede are all that is left, so they are simply
   stacked, and --sp-4 above the title goes with the eyebrow that used
   to need clearing. */
.inv__intro {
  margin-block-end: clamp(var(--sp-6), 2.2vw, var(--sp-7));
}
/* The block read loose for one reason, and it was inside the heading
   rather than around it: at 57.6px the title was inheriting the body's
   1.6 line-height and rendering in a 92px box — 34px of leading a
   display size does not want. Tightening the line does most of the
   compacting on its own; the margins around it only finish the job. */
.inv__title {
  font-size: clamp(2.25rem, 3.4vw, 3.5rem);
  line-height: 1.05;
  letter-spacing: var(--tr-display);
}
.inv__intro .lede {
  margin-block-start: var(--sp-4);
  max-width: 46ch;
}

/* ============================================================
   FEATURED INVENTORY — one photographic mass, one typographic index
   ============================================================
   The Vault's verdict on the grid this replaces is filed under
   rmsothebys-com, rating 1, OUT: "the repeated card cadence becomes
   metronomic. The grid measures correctly without composing
   meaningfully."

   The counterweight is DIRECTION and CONTRAST, not area. A wide
   horizontal photograph is answered by a narrow vertical column, and
   inside that column one record is given real typographic scale while
   three are compressed. Four equal entries would be the card grid again
   in typographic clothing.
   ============================================================ */
.showcase,
.fin__showcase {
  position: relative;
  display: grid;
  /* REBALANCED to roughly 60/40. It was 1fr against a 23-31rem column,
     which on a wide screen resolved closer to 72/28 — the index read as
     something parked beside the picture rather than the second half of
     one composition. Fractions rather than a fixed rem cap, so the
     ratio holds at every width instead of drifting as the viewport
     grows.

     The counterweight argument in the note above still stands and is
     not undone by this: a wide horizontal photograph answered by a
     narrow vertical column. 40% is still narrow against 60% — it is
     simply no longer a margin. */
  grid-template-columns: minmax(0, 59fr) minmax(21rem, 41fr);
  gap: clamp(var(--sp-7), 3.4vw, var(--sp-8));
  align-items: stretch;

  /* ONE FIELD FOR THE WHOLE SECTION.

     This is the four-layer ground that was on .showcase__stage. Lifting
     it here is what unifies the section: the car and the index are now
     two things standing in one modelled light, rather than a card
     beside a list. The radius belongs to the scene, not to the picture.

     The light is re-centred at 34% rather than 50%, because the subject
     it is modelling now occupies the left 59% of a wider box. Leaving it
     at centre would have pooled the brightest part of the ground in the
     gap between the two columns — lighting the seam instead of the car.

     Same layers, same reasoning as before: a corner falloff pulling the
     edges down, a broad atmospheric field, a tighter core behind the
     car's mass, and a base grading to near-black at the floor. Depth
     from several tonal events, not from raising the light. */
  padding: clamp(var(--sp-6), 2.6vw, var(--sp-8));
  border-radius: 26px;
  overflow: hidden;
  isolation: isolate;
  background:
    /* THE CORNER FALLOFF, unchanged — it is the only layer here that
       SHOULD be even, because it is the box darkening at its edges. */
    radial-gradient(125% 118% at 34% 44%,
      transparent 34%, rgba(4, 7, 14, 0.5) 82%, rgba(3, 5, 11, 0.72) 100%),

    /* AND THE LIGHT, WHICH SHOULD NOT BE. Two concentric ellipses on one
       centre resolve into a circle no matter how soft they are, and a
       circle behind a car reads as a vignette filter rather than as a
       lamp in a room. Four lobes at four centres, none of them
       matching, so the outline never closes on itself: a core just
       behind the car's mass, a broad field carrying up and left, a
       shallow spill low and right where the floor catches it, and one
       small high-left flare that keeps the top edge from being a clean
       arc. Same total light as before, redistributed. */
    radial-gradient(24% 27% at 31% 56%,
      rgba(206, 222, 248, 0.115) 0%, rgba(150, 175, 214, 0.05) 54%, transparent 76%),
    radial-gradient(58% 62% at 38% 37%,
      rgba(138, 162, 200, 0.10) 0%, rgba(96, 118, 156, 0.045) 44%, transparent 78%),
    radial-gradient(46% 34% at 21% 62%,
      rgba(120, 146, 190, 0.07) 0%, rgba(88, 110, 148, 0.03) 50%, transparent 80%),
    radial-gradient(30% 44% at 52% 46%,
      rgba(150, 176, 216, 0.055) 0%, transparent 74%),
    radial-gradient(18% 16% at 24% 28%,
      rgba(178, 200, 236, 0.05) 0%, transparent 72%),

    linear-gradient(180deg, #241C15 0%, #1A140F 48%, #0E0B08 100%);
}

/* ---- The "Just in" ribbon ----------------------------------
   Anchored to .showcase, not to the stage. The stage clips its own
   overflow so the cut-outs stay in their field, so a ribbon inside it
   could never break the corner it is supposed to cross — it would be
   sheared off at exactly the edge that makes the gesture work.

   Slimmer and about a quarter smaller than the reference: at the
   reference's weight it read as a sale flash across the artwork. The
   point is one editorial gesture, so it is a thin band, a shallow
   angle, and two words at caption size rather than a banner.

   Navy to muted blue, one hairline of light along the top edge, and no
   drop shadow — the ribbon is meant to sit ON the composition, not hover
   above it casting anything. */
/* A STATUS PILL IN THE FIELD, NOT A FLAG ON ITS CORNER.

   Two treatments have been tried and removed here. A long diagonal band
   crossed the composition and read as sale tape; a small angled flag
   clipped to the corner was better but still an object stuck ONTO the
   frame rather than something living inside it, and its overhang made
   the field's boundary look accidental.

   A pill sitting properly inside the field is the quiet version: it
   belongs to the picture, it cannot half-escape anything, and at this
   size it registers without ever competing with the car. */
.justin {
  position: absolute;
  z-index: 3;
  inset-block-start: clamp(var(--sp-5), 2.4vw, var(--sp-6));
  inset-inline-start: clamp(var(--sp-5), 2.4vw, var(--sp-6));
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-3);
  /* A touch more vertical room than the labels strictly need, so the two
     voices sit in air rather than against the pill wall. */
  padding: 10px 19px 11px;
  /* 8px, not a full pill. Fully rounded is the single strongest tell of
     a generic UI chip; a soft rectangle reads as a plate. */
  border-radius: 8px;
  white-space: nowrap;
  text-transform: uppercase;
  pointer-events: none;
  /* Gradient inside the pill rather than a flat fill — a single tone at
     this size reads as a UI chip; a shallow gradient reads as a surface
     catching light, which is what keeps it in the photograph's world. */
  background: linear-gradient(133deg,
    rgba(38, 58, 110, 0.92) 0%,
    rgba(27, 41, 78, 0.92) 52%,
    rgba(20, 30, 56, 0.92) 100%);
  /* One hairline of light along the top edge and one of shadow beneath —
     no outer shadow and no glow, so it sits on the field rather than
     hovering above it. */
  box-shadow: inset 0 1px 0 rgba(178, 205, 255, 0.16),
              inset 0 -1px 0 rgba(0, 0, 0, 0.28);
}

/* ONE LABEL. "NEW ARRIVAL" WAS REMOVED.

   Three attempts at the pair failed the same way. Same mono at two
   opacities read as a disabled chip; changing the face helped; adding a
   dot separator helped again — and it still read as a tag component,
   because the second label was never carrying anything. It restated
   "Just in" exactly, so every version of it was decoration wearing the
   costume of information.

   Subtract before adding. One label set properly is stronger than two
   set weakly, and it also puts the pill back over the 14px functional
   floor that the 12px descriptor had been sitting under. */
.justin__lead {
  font-family: var(--font-label);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: var(--tr-label);
  color: var(--ink);
  line-height: 1;
}


/* Below the two-mass width the stage goes edge to edge, so there is no
   corner left to break and the ribbon would sit on the viewport rather
   than on the composition. */
@media (max-width: 1080px) {
  .justin { display: none; }
}

/* ---- The dominant field ------------------------------------
   A cut-out has no environment of its own, which is the whole reason it
   was chosen — the dealer photography brought turntables, curtains and
   burned-in branding with it. So the field has to supply what the crop
   removed, or the car reads as a sticker dropped on a page.

   Three layers do that, and each has one job:
     stage__light  a soft radial behind the car, off-centre, so the body
                   separates from the ground by luminance rather than by
                   an outline
     stage__floor  a faint tonal band low in the field — an implied
                   surface for the car to stand on rather than float over
     ::after       an elliptical contact shadow directly under the wheels,
                   tight and dark at the tyre and dissolving outward

   The radius is on this field ONLY. Nothing else in the section is
   rounded, so the corner reads as a deliberate frame rather than as the
   house style — one rounded object is a decision, six is a UI kit. */
.showcase__stage {
  position: relative;
  display: grid;
  place-items: center;
  /* 16:10, not 4:3. A cut-out car is a wide, shallow object; a tall
     field left ~200px of empty box above and below it and the car read
     as floating in a container rather than filling a frame. The field is
     now shaped like its subject. */
  /* WIDTH-LED, and that matters more than it looks. The parent stretches
     its items, so with only an aspect-ratio the stage took its height
     from the row and then demanded the width to match — it was running
     1128px wide inside a 780px column. The old `overflow: hidden` hid
     that by clipping it, so removing the card revealed an overflow that
     had been there all along. Pinning the width and centring makes the
     ratio derive height from width, which is the direction it was always
     meant to work in. */
  width: 100%;
  align-self: center;
  aspect-ratio: 16 / 10;
  /* NO GROUND OF ITS OWN, AND NO RADIUS. The four-layer field that used
     to live here has moved up to .showcase — see there for why. Keeping
     it meant the car sat in a rounded box while the index sat on bare
     page, which is exactly what made the section read as "picture on the
     left, widget on the right". */
}

/* Off-centre and elliptical. A centred circular glow reads as a vignette
   filter; placing it behind the car's mass and slightly high reads as a
   light source in a room. */
.stage__field { position: absolute; inset: 0; }
/* The single flat radial that used to live here is folded into the
   field's own background above — one element carrying four layers reads
   as depth; two elements each carrying one read as a stack. */
.stage__light { display: none; }
/* The tonal floor band was REMOVED. It was meant to imply a surface and
   it rendered as a grey smudge sitting below the car — a horizontal
   lighter band with visible edges, reading as a dirty patch rather than
   as ground. The cut-outs already carry their own soft contact shadow,
   so the surface was being described twice and neither description was
   good. The radial alone now does the separating. */

.stage__cars {
  position: relative;
  width: 100%;
  height: 100%;
  display: grid;
  place-items: center;
}

/* No added contact shadow either. The PNGs are cut out WITH one baked in,
   directly under the tyres where it belongs; the ellipse added here sat
   well below the wheels and read as a second, detached shadow. Two
   shadows for one car is what made it look pasted. */

.stage__car {
  position: absolute;
  z-index: 1;
  /* Sits above the floor line, not centred in the box — a car centred in
     its field looks suspended. Width is capped so the vehicle keeps air
     on both sides and never touches the index column. */
  /* 80%, down from 92%. At the larger size the car ran almost to both
     side walls of its field and the clearances read as an accident of
     fit rather than as air anyone chose. Smaller lets the negative space
     become part of the composition instead of a margin the car happened
     to leave. Still the dominant mass by a wide margin. */
  width: 80%;
  max-width: none;
  height: auto;
  /* PER-ASSET, because the cut-outs are not cropped alike. The GT2 RS and
     the Aventador were traced with real margin around the bodywork; the
     Regera and the 812 run almost edge to edge in their own PNG. Sized
     identically, those two read as crowded against the field walls while
     the other two read as placed — the difference is in the asset, so the
     correction has to be too. One width for all four is only fair if all
     four were cut the same way. */
  /* ALL FOUR STAND ON ONE GROUND LINE.

     They used to be centred in the field, and because the four cut-outs
     are different heights, centring put each car's wheels somewhere
     different. Measured on the render at 1783px: the tyre line landed
     127px / 136px / 163px / 150px above the floor of the field — a 36px
     swing on a 526px stage. Switching records made the car rise or drop,
     which is the movement that read as the section jumping.

     The correction cannot be one number, because the swing has two
     causes. Each PNG carries a different strip of empty pixels below its
     shadow, sampled off the alpha channel: 1.5% / 2.1% / 1.4% / 2.1% of
     its own height. --sit is that strip. A percentage translate resolves
     against the element's OWN height, so `translate: 0 var(--sit)` drops
     each image by exactly its own empty margin and lands every car's
     shadow on the element's bottom edge — whatever that image's height
     happens to be.

     Anchoring those edges to one --ground then puts every tyre line in
     the same place. A taller car is still taller; it grows upward, the
     way a taller car does when it is parked next to a shorter one. */
  --ground: 24%;
  --sit: 0%;
  inset-block-end: var(--ground);
  translate: 0 var(--sit);
  opacity: 0;

  /* THE RESTING STATE IS THE *LEAVING* STATE.

     A car with no .is-active is either on its way out or waiting off
     stage, and both want the same thing: small, soft, gone. Declaring it
     once here means the element losing .is-active inherits this rule's
     timing automatically, while the element gaining it picks up the
     timing on .is-active. One transition per direction, no JS involved.

     WHY THIS REPLACED THE 420ms CROSSFADE.

     The old rule faded both cars on one symmetric curve. Measured on the
     render through a single arrow press, the two overlapped at 0.66/0.33
     and again at 0.19/0.80 — a window where two saturated silhouettes,
     a red 911 and a blue Aventador, were both plainly legible at once.
     That is a double exposure, not a blend, and no duration fixes it:
     slowing it down only lengthens the ghost.

     So the two cars no longer do the same thing. The outgoing one
     recedes and defocuses on --ease-in; the incoming one pulls into
     focus on --ease-out after --stage-lag. During the overlap neither is
     sharp, so there is never an instant with two readable cars in it.

     Scale pivots on the default centre, which is correct HERE and would
     not have been earlier: the --sit / --ground apparatus further up is
     superseded by the centring rule near the end of this file, so the
     car floats on the field's centre line rather than standing on a
     floor. A centred object recedes about its middle. If that block is
     ever reverted to a ground line, this needs transform-origin: bottom.

     transform, not translate. The centring rule owns `translate`, and
     the two are separate properties that compose — writing scale into
     `transform` leaves that rule untouched. Putting both in one property
     would have silently dropped the vertical centring the moment a car
     changed, which is exactly the source-order trap this file has hit
     before.

     --side is written per element by script and is 1 or -1. A car that
     is leaving carries the side OPPOSITE to the one arriving, so the two
     cross rather than pile up on the same edge. A car merely waiting off
     stage keeps whatever side it last had, which is invisible: it is at
     opacity 0 and only its own next arrival will set it again. */
  transform:
    translateX(calc(var(--side, 1) * var(--slide-reach)))
    scale(var(--stage-recede));
  filter: blur(var(--stage-defocus));
  /* BLUR RUNS ON ITS OWN, SHORTER CLOCK — this is the part that works.

     Matching blur to the fade left the outgoing car only ~2.5px soft at
     the crossover, and 2.5px across a 640px vehicle hides nothing: the
     blue Aventador was still plainly itself behind the white Regera. A
     linear defocus reaches full blur while the car is still visible, so
     it stops being a readable OBJECT before it stops being visible. What
     overlaps the incoming vehicle is then a coloured haze, which is what
     a dissolve is supposed to be.

     linear, not --ease-in: an eased defocus spends its first frames
     barely blurring, which is exactly the window that needed covering.

     The fade is --dur-1 too. At --dur-2 the outgoing car measured 0.78
     while the incoming one was already half here, and a 14px haze at
     0.78 tints the arriving vehicle instead of sitting behind it — the
     red 911 washed across the blue Lamborghini. Leaving is not the part
     worth watching.

     Recorded against motion-taste I3: an exit may be a step faster than
     its entrance and this is three steps faster. The rule guards against
     exits that DRAG, the opposite failure; nothing in it argues for a
     slow departure. Stated rather than assumed. */
  transition:
    opacity   var(--dur-1) var(--ease-in),
    transform var(--dur-1) var(--ease-in),
    filter    var(--dur-1) linear;
}

/* Recorded yield — motion-taste I4, "animate opacity and transform only".
   filter: blur() is animated here, on the stated exit that the ghost is a
   figure-ground failure and opacity alone cannot solve it. It is the same
   language Alex approved for the hero slide change, so the section and the
   hero now describe a change the same way.

   Cost measured rather than assumed, since that is the whole basis of I4.
   Same transition run twice, once with filter forced to none: median frame
   16.7ms both ways, p95 17.6 against 17.5, worst frame identical at 17.7,
   19 frames over budget against 18 across 42. The blur is free here because
   it is two composited layers, not a backdrop filter over live content.
   Re-measure if this ever moves onto a backdrop.

   Blur is never a resting state: both ends of both transitions are 0. */
.stage__car.is-active {
  opacity: 1;
  transform: translateX(0) scale(1);
  filter: blur(0);
  transition:
    opacity   var(--dur-3) var(--ease-out) var(--stage-lag),
    transform var(--dur-3) var(--ease-out) var(--stage-lag),
    filter    var(--dur-3) var(--ease-out) var(--stage-lag);
}

/* The designed static path, not the blanket reset degrading into one.
   Under reduced motion the vehicle simply IS the selected vehicle: no
   blur to pass through, no scale to settle, nothing that depends on a
   transition having run. The section still works if the swap is
   instant, which is the test I1 actually asks. */
@media (prefers-reduced-motion: reduce) {
  .stage__car,
  .stage__car.is-active {
    transform: none;
    filter: none;
    transition: opacity 1ms linear;
  }
}
/* WIDTH IS SET ON THE CAR, NOT ON THE FILE.

   The old rule sized the PNG: 80%, with regera and 812-gts knocked down
   to 71% by eye because they looked crowded. But the crowding was never
   about those two cars — it was about how much empty canvas each file
   carries. Sampled off the alpha channel, the car occupies 94.9% / 96.2%
   / 92.6% / 95.1% of its own file's width, so one width for all four put
   four different-sized cars on screen: 638px, 647px, 554px and 569px of
   actual vehicle. Selecting the Regera shrank the subject by 13%, and
   the hand-tuned 71% had made that worse, not better.

   Each width below is that asset's own coverage divided into one target,
   so all four render the SAME LENGTH OF CAR and the air either side is
   identical whatever the file did. Re-sample if an asset is replaced. */
.stage__car[src*="gt2rs"]     { width: 79.9%; }   /* 75.8 / 0.949 */
.stage__car[src*="aventador"] { width: 78.8%; }   /* 75.8 / 0.962 */
.stage__car[src*="regera"]    { width: 81.9%; }   /* 75.8 / 0.926 */
.stage__car[src*="812-gts"]   { width: 79.7%; }   /* 75.8 / 0.951 */

/* --sit is measured, not chosen: the empty strip under each cut-out's
   shadow, read off the alpha channel of the file itself at threshold 24.
   If a PNG is ever replaced, re-sample it — a guessed value here puts
   that one car back off the ground line and nothing else will show it. */
.stage__car[src*="gt2rs"]    { --sit: 1.5%; }
.stage__car[src*="aventador"]{ --sit: 2.1%; }
.stage__car[src*="regera"]   { --sit: 1.4%; }
.stage__car[src*="812-gts"]  { --sit: 2.1%; }

/* The old `.stage__car.is-active { opacity: 1 }` stood here. It sat AFTER
   the focus-pull rule above at equal specificity, so it was one edit away
   from silently winning — the trap this file has hit five times. The
   active state is declared once, up there, with its timing. */

/* ---- The index ---------------------------------------------
   Records distributed down the column so the last one lands near the
   photograph's lower edge — the two masses resolve together instead of
   one hanging below the other. */
.showcase__index { display: flex; flex-direction: column; }

/* ---- The index's last line ---------------------------------
   A BUTTON now, and the outlined variant rather than the accent one:
   the page's one Accent Action in this region is Search Inventory in
   the hero, and the documented rule is at most one dominant accent per
   primary visual region. A second red fill here would split the
   decision between two reds of equal weight.

   It keeps its place as the conclusion of the list — four cars, then
   the way to the rest of them. */
.index__all {
  align-self: flex-start;
  margin-block-start: var(--sp-6);
}

/* FOUR FIXED SLOTS. This is what stops the column jumping.

   Selecting a record used to change three things that each alter its
   height — the row's padding, the detail opening from 0fr to 1fr, and
   the model's font-size — so the active row grew, the others gave way,
   and every boundary below the pointer moved. The list's TOTAL height
   was constant, which is why an earlier measurement of the section
   found 0px and missed it entirely: the section never moved, the rows
   inside it did.

   Equal grid rows fix it at the cause. Every slot is the same height
   whatever is in it, so a record can grow all it likes inside its own
   and nothing outside it can feel the difference.

   The cost is that the column is sized for its LARGEST state rather
   than its average, so it is taller at rest than it used to be. That is
   the correct trade: the alternative is a list that rearranges itself
   under the reader. */
.recs {
  display: grid;
  grid-template-rows: repeat(4, minmax(0, 1fr));
  flex: 1;
  min-height: 44rem;
  border-top: 1px solid var(--rule);
}

/* No card, no box, no thumbnail, no pill. A record is a hairline, an
   index numeral and ranked type — P3, metadata composed rather than
   appended, promoted to the structure of the section. */
.rec {
  display: grid;
  /* number · body · thumbnail. The thumbnail is marginalia in the outer
     margin of the record, which is why it sits past the text rather than
     inside it. */
  grid-template-columns: auto minmax(0, 1fr) auto;
  /* start, not center — the numeral belongs on the make line at the top of
     the record. Centred, it drifted down beside the second line of a
     wrapped model name and read as a misalignment. The thumbnail centres
     on its own below. */
  align-items: start;
  gap: var(--sp-4);
  /* Generous on the inactive rows so the column fills its height by
     RHYTHM rather than by space-between prising four items apart —
     that left ~110px voids between records and read as a gappy list. */
  /* The column is wider now, so the rows are given more air to match —
     a wider measure at the same rhythm reads as looser horizontally and
     tighter vertically. */
  /* Fixed padding, and the content anchored to the TOP of the slot. The
     padding used to change with selection, which was one of the three
     things moving the rows.

     `start`, not `center`. Centring was the last thing still moving, and
     it moved the part a reader is actually looking at. The slot is a
     fixed 233px; an inactive record is 45px of content and an active one
     185px, so centring parked them at 94px and 24px from the top of the
     same slot — the numeral and the make line LEAPT 70px upward the
     instant you hovered, and dropped back when you left. The rows never
     changed size, which is why measuring the rows found nothing; the
     type was sliding around inside rows that stood perfectly still.

     Anchored to the top, the numeral and the make line are nailed to one
     y for the life of the page and the disclosure grows DOWNWARD into
     space that was already reserved for it. The slot needs no new height
     to allow this: 185 + 24 + 24 is exactly the 233 it already had. */
  padding-block: var(--sp-5);
  align-content: start;
  border-bottom: 1px solid var(--rule);
  transition: border-color var(--sel-dur) var(--sel-ease);
}

/* THE BLOOM BEHIND THE SELECTED RECORD.

   A soft radial sitting behind the type, not a background on a box —
   it has no edges anywhere, so the index gains a lit region rather than
   a card. inset is negative and the falloff reaches transparent well
   inside that, which is what keeps the light from ever drawing a
   rectangle or clipping against the hairlines above and below.

   Weighted toward the left, where the type mass actually is, rather
   than centred in the row — a centred glow would light the empty
   channel between the model name and the thumbnail.

   Same timing as the rest of the selection, so it arrives with the
   emphasis instead of trailing it. */
.rec {
  position: relative;
  isolation: isolate;
}
.rec::before {
  content: "";
  position: absolute;
  /* THE BOTTOM IS FLUSH, EVERY OTHER SIDE OVERRUNS.

     inset-block-end is 0, so the light stops dead on the record's own
     bottom edge — that is the crisp cut, and because the border sits
     outside the padding box the divider underneath stays clean and the
     glow cannot bleed into the next row.

     Top, left and right run well past the record instead, which is what
     gives the falloff somewhere to happen. Nothing is visible up there:
     the gradient has already reached transparent long before those
     edges, so no rectangle is ever drawn. */
  /* A LARGE LIGHT SOURCE WITH ITS CENTRE ON THE DIVIDER, BOTTOM HALF CUT.

     The previous pass got this wrong by changing two things at once: it
     moved the centre AND shrank the radial to a shallow band, which left
     effectively nothing but the blue line. Only the position was meant
     to move. The field is large again.

     inset-block-end is 0 and the gradient's centre is at 100%, so the
     source sits exactly on the row's bottom rule and everything below it
     is physically cut — the line runs through the middle of the light and
     only the upper dome is ever visible. Because the border sits outside
     the padding box, the divider itself stays crisp and nothing reaches
     the row beneath.

     Every other side overruns far past the row so the falloff finishes
     in open space. Offsets and radii are both PX on purpose: percentage
     radii rescaled with the box, so enlarging the box never bought any
     falloff and the gradient was still live where it ended — that was
     the vertical seam. */
  inset: -260px -300px 0 -260px;
  z-index: -1;
  pointer-events: none;
  /* Light arriving from OUTSIDE the lower right, not a halo centred on
     anything. The source sits low and right — around the thumbnail,
     price and action — and washes up and left across the row, leaving
     the upper left, where the year and model sit, substantially darker.
     That asymmetry is the whole effect; a centred glow would just be a
     spotlight circle.

     Muted electric blue rather than the pale blue-grey this started as.
     Against this ground that read as a white bloom — brighter instead of
     richer. Saturated navy at low alpha adds colour density and almost
     no luminance. */
  /* Broad both ways — 320 across, 300 up — so what shows above the rule
     is a wide dome rather than a strip. It rises roughly 275px, which
     covers the active row's full height and a little beyond, so no top
     edge is ever reachable.

     62% of this box lands around 74% across the ROW itself, once the
     box's own left and right overhangs are accounted for: the light is
     weighted toward the thumbnail and action without the model name
     dropping out of it.

     Radii are set under the distance to every free edge — ~345px to the
     right of centre, further left, ~570px up — and also under the gap to
     the section's overflow clip, so the falloff completes before any
     boundary rather than being sliced by one. */
  /* Centre moved left. 62% of this box was landing around 74% across the
     ROW; 45% lands around 44%, so the light now sits under the model
     name and price rather than out by the thumbnail. Checked against the
     free edges after the move — the falloff completes ~285px out, with
     478px to the box's left edge and 578px to its right, so shifting it
     has not brought either boundary into view. */
  background: radial-gradient(310px 200px at 45% 100%,
    rgba(40, 91, 190, 0.34) 0%,
    rgba(34, 78, 164, 0.21) 22%,
    rgba(26, 62, 130, 0.095) 44%,
    rgba(18, 44, 94, 0.032) 64%,
    rgba(12, 28, 62, 0.008) 80%,
    transparent 92%);
  opacity: 0;
  transition: opacity var(--sel-dur) var(--sel-ease);
}
/* Active only. Hover already promotes a record to active, so lighting
   hover separately would double-light during the crossfade and the
   inactive rows would never be as quiet as they should be. */
.rec.is-active::before { opacity: 1; }

.rec__no {
  font-family: var(--font-label);
  font-size: var(--t-micro);
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
  transition: color var(--sel-dur) var(--sel-ease);
}
.rec__body { display: flex; flex-direction: column; min-width: 0; }

/* THUMBNAILS AS MARGINALIA. The cut-outs are used at small size with no
   frame at all — transparent PNGs on the page ground, so there is no
   card, no border, no radius and no image box to make the index look
   like a listing. They read as small objects in the margin, which is
   the whole point: they preview and they reinforce the hover, and they
   never compete with the type. */
.rec__thumb {
  /* Sized once, at the ACTIVE size, and scaled DOWN when inactive. The
     box therefore never changes, so the thumbnail cannot move the row
     it sits in — animating width did, every time. transform-origin
     right keeps it pinned to the record's outer edge as it grows. */
  width: 104px;
  height: auto;
  justify-self: end;
  /* Pinned to the top of the record, and scaled from its top-right rather
     than its middle. Centred, it was the second thing moving: its grid
     track is as tall as the body beside it, and the body goes 45px to
     185px on selection, so a centred thumbnail rode 70px up with the type.
     Origin at the top edge means the scale from 0.71 to 1 grows it
     downward without shifting the edge it hangs from. */
  align-self: start;
  transform-origin: 100% 0%;
  scale: 0.71;
  opacity: 0.34;
  transition: scale var(--sel-dur) var(--sel-ease),
              opacity var(--sel-dur) var(--sel-ease);
}
/* Hover is gone from the index: the arrows select now, so a pointer
   merely crossing the column must not brighten anything. :focus-visible
   stays — that is the keyboard telling you where it is, not an effect. */
.rec:focus-visible .rec__thumb { opacity: 0.62; }
.rec.is-active .rec__thumb { scale: 1; opacity: 1; }
.rec__make { color: var(--ink-3); letter-spacing: var(--tr-label); }

/* INACTIVE: compressed. One line of make, one of model, nothing else —
   the facts and the action are withheld until the record is the one on
   screen, which is what stops four equal entries forming. */
.rec__model {
  font-size: 1.0625rem;
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--ink-2);
  margin-block-start: var(--sp-1);
  /* font-size IS an animatable length, so this interpolates rather than
     snapping. It is kept in preference to transform: scale() because a
     scaled 17px glyph blown up 2.3x renders soft, and the model name is
     the thing the eye lands on. */
  transition: color var(--sel-dur) var(--sel-ease),
              font-size var(--sel-dur) var(--sel-ease),
              margin var(--sel-dur) var(--sel-ease);
}
/* THE DISCLOSURE ANIMATES, AND THAT IS THE WHOLE FIX.

   This used to be height:0 -> height:auto. `auto` is not an animatable
   length, so the browser had nothing to interpolate and the block
   appeared in a single frame — which is what made the column snap and
   shove its neighbours down. Everything else in the record was already
   transitioning; this one property was doing all the damage.

   grid-template-rows 0fr -> 1fr IS interpolatable, so the detail opens
   and closes over a measured duration and the records below glide
   instead of jumping. --sel-dur/--sel-ease are section-local so the
   selection has one timing across type, thumbnail and disclosure. */
.recs { --sel-dur: 320ms; --sel-ease: cubic-bezier(0.22, 1, 0.36, 1); }

.rec__detail {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--sel-dur) var(--sel-ease);
}
.rec__detail-in {
  overflow: hidden;
  min-height: 0;
  opacity: 0;
  transition: opacity var(--sel-dur) var(--sel-ease);
}
.rec.is-active .rec__detail { grid-template-rows: 1fr; }
.rec.is-active .rec__detail-in { opacity: 1; }

/* ACTIVE: the record becomes the counterweight. The model name steps up
   several ranks, the facts and the action appear, and the rule under it
   firms up. It gains INK and SCALE — never a background, a box or a
   colour. Legible as the active one in a still screenshot, which was
   the test. */
/* The active rule is drawn in the glow's own blue rather than in the
   neutral --rule-strong. It is the same hairline weight as every other
   divider — only the colour changes — so the line reads as the precise,
   technical edge of the light rather than as a heavier border. Inactive
   dividers stay neutral and quiet. */
/* No padding change: the slot is fixed and the content centres in it. */
.rec.is-active {
  border-bottom-color: rgba(58, 108, 200, 0.62);
}
.rec.is-active .rec__no { color: var(--ink); }
.rec.is-active .rec__model {
  font-size: clamp(1.875rem, 2.15vw, 2.5rem);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: -0.022em;
  color: var(--ink);
  margin-block-start: var(--sp-3);
}
.rec.is-active /* The facts and the action sat flush — "1,641 milesView vehicle" — because
   nothing separated two inline spans. */
.rec__detail-in { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--sp-3) var(--sp-6); }

.rec__facts {
  margin-block-start: var(--sp-5);
  font-family: var(--font-label);
  font-size: 1.0625rem;
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
/* More intentional than the inactive rows' silence, still not a button:
   it lifts to full ink with the arrow leading it, and gains its own air
   above. No box, no pill, no fill. */
.rec.is-active .rec__go {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  margin-block-start: var(--sp-6);
  color: var(--ink);
  letter-spacing: var(--tr-label);
  transition: gap var(--dur-2) var(--ease-out);
}
.rec__dot { margin-inline: var(--sp-2); }
.rec__arrow { flex: none; transition: translate var(--dur-2) var(--ease-out); }

.rec:focus-visible .rec__model { color: var(--ink); }

/* A record with no cut-out yet still lights and still links, but it is
   never given the enlarged active treatment — that state means "this is
   the car in the field", and awarding it to a vehicle the field is not
   showing would be the interface telling a lie. */
.rec--noasset { cursor: pointer; }
.rec.is-active:hover .rec__go { color: var(--ink); }
.rec.is-active:hover .rec__arrow { translate: 3px 0; }

@media (prefers-reduced-motion: reduce) {
  .stage__frame, .rec, .rec__no, .rec__model,
  .rec__facts, .rec__go, .rec__arrow { transition: none; }
  .rec.is-active:hover .rec__arrow { translate: none; }
}

/* ---- Recomposition ------------------------------------------
   The governing idea is one dominant vehicle with an index beside it.
   Below the two-mass width it is re-established vertically: the stage
   goes edge to edge and the index sits under it, still an index. */
@media (max-width: 1080px) {
  .showcase { grid-template-columns: minmax(0, 1fr); gap: clamp(var(--sp-6), 4vw, var(--sp-7)); }
  .showcase__stage { margin-inline: calc(50% - 50vw); aspect-ratio: 2 / 1; }
  .recs { flex: none; }
}
@media (max-width: 620px) {
  .showcase__stage { aspect-ratio: 3 / 2; }
}

/* ---- The finder bar ----------------------------------------
   Lifted over the join so the two chapters are stitched rather than
   stacked. -40px is bounded on purpose: the hero claim's bottom offset
   is clamp(--sp-7, 8vh, --sp-9), whose floor is 48px, so the bar can
   never reach the buttons at any viewport height.

   It carries the ground the rail gave up. That is not a contradiction:
   over the hero a capsule around navigation was a box drawn around
   nothing, whereas this one is holding four controls that need to read
   as one instrument, and it straddles the seam between a photograph and
   the page — the one place on this page a contained object earns its
   container. */
/* It shares .inv__inner's box, so its edges line up with the cards
   below — 1580 at desktop, which is the brief's 1500–1600 and 82vw at
   1920. Height 96px sits in the asked-for 90–105.

   -44px of overlap (asked: 35–50). Still bounded by the hero claim's own
   bottom offset, whose floor is 48px, so it cannot reach the buttons. */
.finder-bar {
  position: relative;
  z-index: 3;
  /* 100%, not its own min() — it already sits inside .inv__inner, which
     is the 1580 box. Repeating the gutter calculation here subtracted it
     twice and the bar came out 1460 against the cards' 1580, so its
     edges missed theirs by 60px on each side. */
  width: 100%;
  margin-block: -44px clamp(var(--sp-8), 6vw, var(--sp-9));
  min-height: 96px;
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  align-items: center;
  gap: var(--sp-5);
  padding: var(--sp-3) var(--sp-3) var(--sp-3) var(--sp-6);
  border-radius: var(--r-rail);
  background: rgb(var(--bg-rgb) / 0.78);
  border: 1px solid rgba(243, 238, 230, 0.12);
  backdrop-filter: url(#nav-glass) blur(14px) saturate(1.4);
  -webkit-backdrop-filter: blur(16px) saturate(1.4);
  /* No outer drop shadow. A floating panel with a soft shadow under it
     is the SaaS dashboard the brief rules out; the hairline and the
     glass are enough to lift it off the photograph. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.16);
}

/* More room per control — the fields were set for a 1311px bar and now
   have half again the width to use. */
.finder-bar .field { padding-block: var(--sp-3); }
.finder-bar .field + .field { padding-inline-start: var(--sp-6); }
.finder-bar .field__control { font-size: 1.125rem; }
.finder-bar .field__label { letter-spacing: var(--tr-label); }

/* The CTA stays the strongest thing in the bar: the only filled mass,
   and now full-height against a 96px rail. */
.finder-bar__go {
  align-self: stretch;
  padding-inline: clamp(var(--sp-6), 2.6vw, var(--sp-8));
}

/* Four fields, then the control that acts on them. */
.finder-bar .finder {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

.finder-bar__go { align-self: stretch; }

/* 1100, not 900: at 1024 the four fields were 155px each and "Select
     Body Style" at 18px does not fit one — they stack before they
     truncate. */
@media (max-width: 1100px) {
  .finder-bar {
    grid-template-columns: 1fr;
    padding: var(--sp-4);
    gap: var(--sp-4);
    margin-block-start: var(--sp-6);   /* no overlap where the hero is recomposed */
  }
  .finder-bar .finder { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .finder-bar .field + .field:nth-child(3) { padding-inline-start: 0; border-inline-start: 0; }
  .finder-bar__go { justify-content: center; }
}
@media (max-width: 560px) {
  .finder-bar .finder { grid-template-columns: 1fr; }
  .finder-bar .field + .field {
    padding-inline-start: 0;
    border-inline-start: 0;
    border-top: 1px solid var(--rule);
    padding-block-start: var(--sp-3);
  }
}

/* Centred, and the only button in the section — the cards deliberately
   carry none, so this is where "more" lives. */
/* `display: flex` + `width: fit-content`, not inline-flex. Auto inline
   margins do not centre an inline-level box, so `margin-inline: auto` on
   the inherited inline-flex was doing nothing and the button sat left. */
.inv__more {
  display: flex;
  width: fit-content;
  margin-block-start: clamp(var(--sp-8), 6vw, var(--sp-9));
  margin-inline: auto;
}

/* ============================================================
   THE OVERLAY TRANSITION
   One move, not ten. The hero pins and the record band travels up
   over it, so the two chapters are connected rather than stacked —
   the page stops being a sequence of modules.

   "The strongest scroll effect does not animate objects inside the
   layout — it changes how the layout occupies space."

   Everything after this releases into normal flow. Nothing is
   animated: no transform, no scroll listener, no hijack. It is
   position: sticky and a z-index, which costs no frames and cannot
   jank.
   ============================================================ */
.stack-stage { position: relative; }

@media (min-width: 901px) and (prefers-reduced-motion: no-preference) {
  .stack-stage .hero {
    position: sticky;
    inset-block-start: 0;
    z-index: 0;
  }

  /* The record is the panel that covers it. It needs its own ground —
     the layering is only readable because one plane is dark media and
     the other is a pale information surface. */
  .record {
    position: relative;
    z-index: 1;
    box-shadow: 0 -24px 48px -24px rgba(6, 6, 7, 0.55);
  }
}

/* Reduced motion and mobile both get the plain stack. Pinning changes
   what a reader can reach, so it is not applied where it costs more
   than it gives — a phone has no spare viewport to pin. */

/* ============================================================
   THE CHAPTER RAIL
   The device that makes a long page read as authored chapters
   rather than a stack. Marks where you are; lets you jump to a
   layer. Fixed, quiet, and it never covers content — it sits in
   the gutter and disappears when there isn't one.
   ============================================================ */
.chapters {
  position: fixed;
  inset-block-start: 50%;
  inset-inline-start: max(var(--sp-4), calc((100vw - var(--container)) / 2 - var(--sp-8)));
  transform: translateY(-50%);
  z-index: 40;
}

@media (max-width: 1580px) { .chapters { display: none; } }

.chapters__list { display: flex; flex-direction: column; gap: var(--sp-3); }

.chapters a {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  font-family: var(--font-label);
  font-size: var(--t-micro);
  letter-spacing: var(--tr-label);
  color: var(--ink-3);
  transition: color var(--dur-1) var(--ease-out);
}
.chapters a:hover, .chapters a:focus-visible { color: var(--ink); }

.chapters__t {
  max-width: 0;
  overflow: hidden;
  white-space: nowrap;
  opacity: 0;
  transition: max-width var(--dur-2) var(--ease-out), opacity var(--dur-2) var(--ease-out);
}
.chapters:hover .chapters__t,
.chapters a:focus-visible .chapters__t { max-width: 12rem; opacity: 1; }

.chapters .is-here { color: var(--ink); }
.chapters .is-here .chapters__n { box-shadow: inset 0 -1px 0 0 var(--accent); }

/* On the light chapter the rail has to invert or it disappears. */
.chapters.on-light a { color: var(--bone-ink-2); }
.chapters.on-light a:hover, .chapters.on-light .is-here { color: var(--bone-ink); }

/* ---- the mark inside each section ------------------------- */
.chap-mark {
  color: var(--ink-3);
  margin-block-end: var(--sp-6);
}
.chap-mark b { font-weight: 500; color: var(--ink-2); margin-inline-end: var(--sp-3); }
.record .chap-mark { color: var(--bone-ink-2); }
.record .chap-mark b { color: var(--bone-ink); }
.hero__stamp b { font-weight: 500; color: var(--ink-2); margin-inline-end: var(--sp-3); }

/* ============================================================
   4 — SELL
   ============================================================ */
.sell { background: var(--bg-raised); }

.sell__inner {
  padding-block: clamp(var(--sp-8), 7vw, var(--sp-9)) clamp(var(--sp-9), 11vw, var(--sp-11));
}

.sell__grid {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: clamp(var(--sp-6), 5vw, var(--sp-9));
}

.sell__title {
  font-family: var(--font-display);
  font-size: clamp(1.875rem, 3.6vw, 3.25rem);
  font-weight: 600;
  line-height: 1.05;
  letter-spacing: var(--tr-display);
  max-width: 16ch;
}

.sell__note {
  max-width: 44ch;
  line-height: 1.65;
  color: var(--ink-2);
  margin-block: var(--sp-5) var(--sp-6);
}

.sell .btn { display: inline-flex; padding-block: var(--sp-4); }

.steps { border-top: 1px solid var(--rule); }
.steps li {
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr);
  gap: var(--sp-2) var(--sp-4);
  padding-block: var(--sp-5);
  border-bottom: 1px solid var(--rule);
}
.steps__n {
  grid-row: span 2;
  font-family: var(--font-label);
  font-size: var(--t-micro);
  color: var(--ink-3);
  padding-block-start: 0.2em;
}
.steps__t { font-weight: 500; }
.steps__d { color: var(--ink-2); line-height: 1.6; }

/* ============================================================
   5 — WHERE
   ============================================================ */
.where { background: var(--bg); }

.where__inner {
  padding-block: clamp(var(--sp-8), 7vw, var(--sp-9)) clamp(var(--sp-9), 11vw, var(--sp-11));
}

.where__title {
  font-family: var(--font-display);
  font-size: clamp(1.875rem, 3.6vw, 3.25rem);
  font-weight: 600;
  line-height: 1;
  letter-spacing: var(--tr-display);
  margin-block-end: var(--sp-7);
}

.places { border-top: 1px solid var(--rule); }

.places__row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.4fr);
  gap: var(--sp-4);
  padding-block: var(--sp-5);
  border-bottom: 1px solid var(--rule);
}

.places dt {
  font-size: clamp(1.125rem, 1.8vw, 1.5rem);
  font-weight: 500;
  letter-spacing: -0.01em;
}
.places dt span {
  display: block;
  font-family: var(--font-label);
  font-size: var(--t-micro);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--ink-3);
  margin-block-start: var(--sp-2);
}

.places dd {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  color: var(--ink-2);
}
.places dd a {
  font-family: var(--font-label);
  font-size: var(--t-micro);
  color: var(--ink);
  transition: color var(--dur-1) var(--ease-out);
}
.places dd a:hover, .places dd a:focus-visible { color: var(--accent); }

@media (max-width: 900px) {
  .sell__grid { grid-template-columns: 1fr; }
  .places__row { grid-template-columns: 1fr; gap: var(--sp-3); }
}

/* ============================================================
   THE REEL — the opening as a sequence, not a still
   Three campaigns. Each slide is its own atmosphere and still the
   same house. Crossfade only: content changing in place has not
   travelled through space, and a whoosh would claim it had.
   ============================================================ */
/* The reel IS the hero's first row — not a layer behind it. Absolute
   positioning took it out of the grid, which let the doors band climb
   into the masthead and blur the whole page with its backdrop-filter. */
.reel {
  position: relative;
  display: grid;
  min-height: 0;
}

.reel__slide {
  grid-area: 1 / 1;
  position: relative;
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-3) var(--ease-out), visibility 0s linear var(--dur-3);
}
.reel__slide.is-on {
  opacity: 1;
  visibility: visible;
  transition: opacity var(--dur-3) var(--ease-out), visibility 0s;
}

.reel__media { position: absolute; inset: 0; }
.reel__media img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 45%; }

/* The reading zone is held at the ground, never on the type. */
.reel__media::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    /* Measured: the eyebrow ran 3.61:1 against the sky in one frame.
       The copy column is held hard and the release starts later —
       fixed at the ground, per GI5, not by lifting the ink. */
    linear-gradient(to top, rgba(6,6,7,.94) 0%, rgba(6,6,7,.72) 26%, rgba(6,6,7,.22) 64%, rgba(6,6,7,.50) 100%),
    linear-gradient(to right, rgba(6,6,7,.86) 0%, rgba(6,6,7,.72) 24%, rgba(6,6,7,.36) 54%, rgba(6,6,7,.06) 100%);
}

.reel__body {
  position: relative;
  height: 100%;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding-block-end: clamp(9rem, 22vh, 15rem);
}

/* Over photography the dimmed ink levels do not apply. --ink-3 is
   calibrated against a solid ground; on a frame it lands wherever the
   image is brightest and measured 3.6:1 there. D5: over imagery, ink
   or near-white — nothing dimmer. */
.reel__eyebrow { color: var(--ink-2); margin-block-end: var(--sp-4); }

.reel__title {
  font-family: var(--font-display);
  font-size: clamp(2.75rem, 8vw, 8.5rem);
  font-weight: 600;
  line-height: 0.88;
  letter-spacing: -0.04em;
  margin-block-end: var(--sp-5);
}

.reel__facts {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-3) var(--sp-6);
  font-family: var(--font-label);
  font-size: var(--t-micro);
  color: var(--ink-2);
  font-variant-numeric: tabular-nums;
}
.reel__facts b { font-size: 1.25rem; font-weight: 500; color: var(--ink); }

/* ---- the numbered navigation ------------------------------- */
.reel__foot {
  position: absolute;
  inset-block-end: clamp(var(--sp-7), 9vh, var(--sp-9));
  inset-inline: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
}

.reel__nav { display: flex; gap: var(--sp-5); }

.reel__dot {
  position: relative;
  background: none;
  border: 0;
  padding: var(--sp-2) 0;
  font-family: var(--font-label);
  font-size: var(--t-micro);
  letter-spacing: var(--tr-label);
  color: var(--ink-3);
  cursor: pointer;
  transition: color var(--dur-1) var(--ease-out);
}
.reel__dot::after {
  content: "";
  position: absolute;
  inset-block-end: 0;
  inset-inline: 0;
  height: 1px;
  background: var(--rule);
}
.reel__dot:hover, .reel__dot:focus-visible { color: var(--ink); }
.reel__dot.is-on { color: var(--ink); }
.reel__dot.is-on::after { background: var(--accent); height: 2px; }

@media (max-width: 900px) {
  .reel__media img { object-position: 60% 45%; }
  .reel__body { padding-block-end: clamp(13rem, 34vh, 20rem); }
  .reel__foot { inset-block-end: clamp(var(--sp-6), 6vh, var(--sp-8)); }
}

/* ============================================================
   STAGE ARROWS
   ============================================================
   Bottom-left of the image field, mirroring the "Just in" pill's
   inset and radius in the opposite corner. Same family, one gesture.

   Revealed on hover of the FIELD, not of the buttons themselves —
   a control that only appears once you are already on top of it is
   a control nobody finds. The reveal is opacity plus a short rise,
   so it arrives rather than blinks.

   Glass is kept at the project's reduced 12px, not the 24px this
   kind of control usually gets. The field behind them is already
   dark and modelled; more blur here would read as decoration.  */
.stage__nav {
  position: absolute;
  z-index: 3;
  inset-block-end: clamp(var(--sp-5), 2.4vw, var(--sp-6));
  inset-inline-start: clamp(var(--sp-5), 2.4vw, var(--sp-6));
  display: flex;
  gap: 8px;
}

/* THE ARROWS ARE PERMANENT.

   They were revealed on hover, from a time when hovering the index also
   selected a record and the arrows were a second way in. They are now
   the only control that changes the photograph on its own, and a control
   that has to be hunted for by hovering the thing it operates is a
   control most people never find. They sit in the field, quietly, all
   the time — which is also what the bottom-line alignment with the
   index CTA was for.

   The touch exception below is retired with the reveal it existed for. */

.stage__arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 44px;
  block-size: 44px;
  padding: 0;
  border: 0;
  border-radius: 8px;
  cursor: pointer;
  color: var(--ink);
  background: rgba(24, 36, 66, 0.52);
  backdrop-filter: blur(12px) saturate(1.1);
  -webkit-backdrop-filter: blur(12px) saturate(1.1);
  /* The two inset hairlines are what make it read as a pane of glass
     with thickness rather than a translucent rectangle: light catches
     the top edge, the bottom edge falls into shadow. Same construction
     as the pill. */
  box-shadow: inset 0 1px 0 rgba(178, 205, 255, 0.16),
              inset 0 -1px 0 rgba(0, 0, 0, 0.28);
  transition: background var(--dur-1) var(--ease-out),
              scale var(--dur-1) var(--ease-out);
}
.stage__arrow svg { inline-size: 20px; block-size: 20px; display: block; }
.stage__arrow:hover { background: rgba(38, 58, 110, 0.66); }
.stage__arrow:active { scale: 0.94; }
.stage__arrow:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

@media (prefers-reduced-motion: reduce) {
  .stage__arrow { transition: background var(--dur-1) var(--ease-out); }
  .stage__arrow:active { scale: none; }
}

/* ============================================================
   SELL / TRADE — the inverted band
   ============================================================ */
.sell {
  position: relative;
  background: var(--bone);
  color: var(--bone-ink);
  /* The band tucks under the dark section above it rather than butting
     into it edge to edge. One corner, generous, on the side the reading
     starts from — a radius on all four would make it a card, and this
     is a ground. */
  border-start-start-radius: clamp(40px, 5vw, 72px);
  overflow: hidden;

  /* THE TOP EDGE IS CUT, NOT STRAIGHT.

     A shelf over the copy, then a bevel dropping the edge for the rest
     of the width. It is doing work, not decoration: the shelf is exactly
     where the eyebrow and the headline sit, so the bone runs highest
     over the reading and the dark section above keeps its full weight
     over the photograph, where the wall is empty anyway.

     The rounded start corner survives this — (0,0) is inside the shape,
     so border-start-start-radius still shapes it. */
  --cut-start: 58%;                     /* where the shelf ends */
  --cut-run: 15%;                       /* horizontal travel of the fall */
  --cut-drop: clamp(28px, 3vw, 52px);   /* how far the right side sits down */

  /* Straight bevel as the floor. Every engine has polygon(). */
  clip-path: polygon(
    0 0,
    var(--cut-start) 0,
    calc(var(--cut-start) + var(--cut-run)) var(--cut-drop),
    100% var(--cut-drop),
    100% 100%,
    0 100%
  );
}

/* AND THE SOFT VERSION WHERE IT CAN BE HAD.

   The fall wants to be an S, not a chamfer, and it wants to take its
   time across the width — a long, shallow easing rather than a corner
   knocked off. path() cannot do it: its coordinates are absolute px, so
   the curve would stop scaling with the band the moment the viewport
   changed. shape() takes the same percentages and calc() the polygon
   does, so the curve stays proportional at every width.

   Both control points sit at the midpoint of the run, one level with the
   shelf and one level with the lower edge. That is what makes the two
   ends leave and arrive flat, with all the movement in the middle —
   the fall never shows a corner at either end.

   @supports tests shape() with a real shape, not the bare keyword, since
   the property parses long before the function does. */
@supports (clip-path: shape(from 0 0, line to 1px 1px, close)) {
  .sell {
    clip-path: shape(
      from 0 0,
      line to var(--cut-start) 0,
      curve to calc(var(--cut-start) + var(--cut-run)) var(--cut-drop)
        with calc(var(--cut-start) + var(--cut-run) * 0.5) 0
           / calc(var(--cut-start) + var(--cut-run) * 0.5) var(--cut-drop),
      line to 100% var(--cut-drop),
      line to 100% 100%,
      line to 0 100%,
      close
    );
  }
}

/* The bevel is a gesture at desktop width and a nuisance at phone width,
   where 20% is barely 75px and the drop eats the top of the eyebrow. */
@media (max-width: 700px) {
  .sell { clip-path: none; }
}

/* The photograph fills the band and everything sits ON it. */
.sell__bg { position: absolute; inset: 0; z-index: 0; }
.sell__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Held right-of-centre: the car is there and the wall the copy needs
     is on the left, so as the band narrows it is the empty wall that
     gets eaten rather than the subject. */
  object-position: 68% center;
}

.sell__grid {
  position: relative;
  z-index: 1;
  display: grid;
  /* One column of copy over the left of the frame, and the steps under
     it. The old two-column split is gone with the panel it framed. */
  grid-template-columns: minmax(0, 1fr);
}

/* A column, not a block of inline items. The button is inline-flex and
   the link was inline-block, so they landed on the same line with the
   text jammed against the button's edge — "GET AN OFFERHOW IT WORKS".
   align-items: start keeps each one its own width instead of stretching
   the button across the measure. */
.sell__copy {
  display: flex;
  flex-direction: column;
  align-items: start;
  padding-block: clamp(var(--sp-9), 8vw, var(--sp-11));
  padding-inline-start: clamp(var(--sp-7), 5vw, var(--sp-10));
  /* 32rem is the MEASURE, so the indent has to be added on top of it
     rather than taken out of it. Under border-box the flat 32rem was the
     padding plus the text, which left the copy 432px wide — 13px short of
     the 445px "Your car deserves" needs, so the title's own <br> was
     overridden by a wrap and the headline came out in three ragged lines
     instead of the two it is written as. The lede is capped at 34ch and
     the buttons size to content, so nothing else widens with this. */
  max-width: calc(32rem + clamp(var(--sp-7), 5vw, var(--sp-10)));
  justify-self: start;
}

/* 4.78:1 on --bone. The same red is a mark only on the dark ground,
   where it reads 3.31:1 — this is the one part of the page where the
   accent is allowed to BE text, and it is the ground that permits it. */
.sell__eyebrow {
  /* --accent-deep, not --accent. Measured on the photograph, not the token:
     the wall's darkest patch under this line is rgb(213,200,184), where
     #BC0F0D gives 3.50:1 — short of the 4.5 this 14px line needs. #940C0A
     reads 4.77:1 on the same pixels. On the flat --bone band the mark itself
     is fine; it is only on the photographed wall that it has to step down. */
  color: var(--accent-deep);
  letter-spacing: var(--tr-label);
  margin-block-end: var(--sp-6);
}
.sell__eyebrow span { margin-inline: 0.4em; opacity: 0.5; }

.sell__title {
  font-size: clamp(2.25rem, 3.6vw, 3.5rem);
  font-weight: 700;
  line-height: 1.06;
  letter-spacing: var(--tr-display);
  color: var(--bone-ink);          /* 15.23:1 */
}

.sell__lede {
  margin-block-start: var(--sp-5);
  max-width: 34ch;
  color: var(--bone-ink-2);        /* 5.67:1 */
  line-height: 1.6;
}

/* align-self:start against the base .btn, which carries align-self:center
   for the rows it usually lives in. In a flex COLUMN that centred it
   horizontally, so the button sat indented from the eyebrow, headline
   and lede it belongs under. */
.sell__go { align-self: start; margin-block-start: var(--sp-7); }

/* A text link, not a second button: the band already has its one
   Accent Action and a second filled control beside it would split the
   decision. The rule under it is the accent doing what it does on the
   dark side — marking, not filling. */
.sell__how {
  display: inline-block;
  margin-block-start: var(--sp-6);
  color: var(--bone-ink);
  letter-spacing: var(--tr-label);
  padding-block-end: var(--sp-2);
  border-bottom: 1px solid var(--accent-deep);
  transition: color var(--dur-2) var(--ease-out);
}
/* --accent-deep on both, because this band's ground is a photograph.
   The rule is non-text and clears 3:1 either way, but hover recolours the
   LABEL, and 14px needs 4.5 — #BC0F0D gives 3.67:1 on this wall, #940C0A
   gives 4.77:1. One deep red for the whole light band rather than two. */
.sell__how:hover, .sell__how:focus-visible { color: var(--accent-deep); }

/* Flush to the band's top and right: the picture is the other half of
   the ground, not an image sitting on it. */
.sell__media {
  align-self: stretch;
  min-height: 100%;
  position: relative;
}
.sell__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: center;
}

/* ---- The three steps --------------------------------------- */
.sell__steps {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  border-top: 1px solid rgba(26, 20, 14, 0.22);
  margin-inline: clamp(var(--sp-7), 5vw, var(--sp-10));
  /* The gap above was a section break's worth of space on top of the
     row's own padding, which left the three steps stranded in bone with
     no relationship to the block they conclude. They are the last line
     of this band, not a section after it. */
  padding-block: clamp(var(--sp-6), 2.4vw, var(--sp-7));
  margin-block-start: 0;
}
.sell__steps li {
  display: grid;
  gap: var(--sp-3);
  padding-inline: clamp(var(--sp-5), 3vw, var(--sp-7));
}
.sell__steps li:first-child { padding-inline-start: 0; }
/* Rules BETWEEN, not around: a border on every item would box each step
   and turn three stages into three cards. */
.sell__steps li + li { border-inline-start: 1px solid rgba(26, 20, 14, 0.22); }

/* No accent here, for two reasons that agree.
   Register: the accent has four roles and a resting step number is not one
   of them — that is the same fifth-role drift that put red on .finding__rule.
   Measurement: the 4.78:1 this line used to claim was taken on the flat bone
   band. The ground is a photograph now, and under these numerals it falls to
   rgb(147,137,125) where #BC0F0D reads 2.28:1 — not a near miss. Washing the
   floor bright enough to rescue it would take a 0.65 bone scrim and bleach out
   the car's reflection, which is the best thing in the frame.
   So the sequence is carried by weight and by the rules between the steps.
   --bone-ink holds 5.33:1 on that same darkest pixel with no help at all. */
.sell__step-n {
  color: var(--bone-ink);
  font-weight: 500;
}
.sell__step-t {
  color: var(--bone-ink);
  font-weight: 400;
  letter-spacing: var(--tr-label);
}

/* ---- Narrow --------------------------------------------------
   The photograph goes ABOVE the copy rather than beside it, and keeps a
   real height: dropping it, or letting it collapse to a strip, would
   remove the only thing in this band a visitor looks at rather than
   reads. */
@media (max-width: 900px) {
  /* The frame is 1.74 wide; on a phone it would crop to a slice of
     wall with no car in it. So the image holds the car and the copy
     gets its own legible ground beneath — see .sell::after. */
  .sell__bg img { object-position: 72% center; }
  .sell__copy {
    justify-self: stretch;
    max-width: none;
    padding-inline: var(--gutter);
    padding-block: clamp(var(--sp-8), 9vw, var(--sp-9));
  }
  .sell__steps {
    grid-template-columns: minmax(0, 1fr);
    margin-inline: var(--gutter);
    gap: var(--sp-5);
  }
  .sell__steps li { padding-inline: 0; }
  .sell__steps li + li {
    border-inline-start: 0;
    border-block-start: 1px solid var(--bone-rule);
    padding-block-start: var(--sp-5);
  }
}

/* The records are buttons now, so the UA's own button skin has to go —
   background, border, centred text, inherited font. Everything about how
   a record LOOKS is unchanged; only the element under it is different. */
.rec {
  appearance: none;
  -webkit-appearance: none;
  background: none;
  border: 0;
  border-bottom: 1px solid var(--rule);
  width: 100%;
  text-align: start;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

/* ---- Sell: the two actions are a pair -----------------------
   They used to stack, an accent pill with a small underlined link
   adrift below it, which read as one control and one afterthought.
   On one row they read as the choice they are. */
.sell__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-5);
  margin-block-start: var(--sp-7);
}
.sell__go { margin-block-start: 0; }

/* The outlined variant, rewritten for THIS ground. The base .btn--line
   draws itself in --ink for the dark page; on the bone band that is very
   nearly the ground itself. --bone-ink for the label, and the hairline in
   the deep red rather than the bright one: the ground here is a
   PHOTOGRAPH, and the earlier measurement on this wall stands — #BC0F0D
   falls to 3.67:1 on it, #940C0A holds 4.77:1. Non-text would clear 3:1
   either way, but hover recolours the label, so the border and the label
   have to agree on one red. */
.sell__how {
  color: var(--bone-ink);
  letter-spacing: var(--tr-label);
  box-shadow: inset 0 0 0 1px rgba(26, 20, 14, 0.28);
  border-bottom: 0;
  padding-block-end: 0;
  display: inline-flex;
  margin-block-start: 0;
  background: transparent;
}
.sell__how:hover, .sell__how:focus-visible {
  color: var(--accent-deep);
  box-shadow: inset 0 0 0 1px var(--accent-deep);
}

/* ============================================================
   REFINEMENT PASS — hero finder, chapter marker, sell boundary
   ============================================================ */

/* ---- The finder reads as a set instrument, not four blanks ----

   Four labels over four empty lines. Nothing stated what any control was
   currently set to, so the rail read as a form waiting to be filled in
   rather than as a filter already open on everything. ruadh-com records
   this exact failure from the other side: its navigation, names and
   prices are microtype and its calls to action sit under the hero
   without enough contrast, and the note names both as what stops a
   confident art direction from being a usable one.

   The value line is now printed in every state. It is the option list's
   own wording — "Any Make" — so the resting line is a real statement
   about the control, not a placeholder pretending to be one. Rank
   inverts on answer: unset, the LABEL leads and the value sits back;
   answered, the value takes full ink and the label drops to --ink-3,
   because at that point the question is settled and the answer is the
   thing being read.

   No box, no chevron field, no card. The row is still a label over a
   value on a hairline. */
.hunt__value {
  display: block;
  /* --ink-2, not --ink-3. Measured on the composite render against the
     brightest pixels the film puts behind this rail, --ink-3 came out at
     3.58:1 — the exact failure the eyebrow above it was already corrected
     for, and this line is functional text stating what a control is set
     to, so 4.5 is a floor and not a target. */
  color: var(--ink-2);
  /* 16px, set open. At 17px — and 18px once answered — these four lines
     were the densest type in the rail and had started reading as its
     content rather than as its settings. They state what a control is
     currently on; that is a caption's job, not a headline's. A step down
     with a little tracking and a looser line gives them air and puts them
     back at the rank they actually hold.

     Colour is unchanged, so the 4.96:1 measured on the composite still
     stands: 16px normal answers to 4.5 and clears it. */
  font-size: 1rem;
  letter-spacing: 0.012em;
  line-height: 1.45;
}

/* RANK IS CARRIED BY INK ALONE.

   The answered value used to step up in size as well. That meant the line
   resized under the reader every time a filter was set — the same small
   movement this pass has spent its time removing everywhere else on the
   page. Ink is enough, and it is unambiguous: the value comes up to full
   --ink at the same moment the label drops to --ink-3, so the pair
   inverts, the question recedes, the answer leads, and nothing moves. */
.hunt__field.is-filled .hunt__value { color: var(--ink); }

/* The title of the finder, not a caption above it. It was --ink-2 at the
   mono floor and read as one more piece of metadata in a rail full of
   metadata; at full ink and a step up in size it names the instrument.
   Still the mono voice — this is the technical register the whole rail
   speaks in, and a fourth voice for one line would be a voice without a
   job (typography I8). */
.hunt__eyebrow {
  color: var(--ink);
  font-size: 0.9375rem;
  letter-spacing: var(--tr-label);
  margin-block-end: var(--sp-6);
}

/* ---- The chapter marker ----
   Number and name are one object now, so they are spaced as one. */
.hero__slide-title {
  color: var(--ink);
  letter-spacing: var(--tr-label);
  transition: opacity var(--dur-2) var(--ease-out);
}
.hero__slide-title.is-swapping { opacity: 0; }

/* ---- The sell boundary, at half amplitude ----

   The fall was 28-52px over 15% of the width. Long is right — a short
   drop reads as a notch — but at that depth the long run stopped being a
   surface and became a wave, and a wave is a decorative section divider
   from a different kind of website.

   Halved. The same length, half the descent: the boundary now reads as
   one panel passing behind another rather than as liquid.

   And the corner radius comes down with it. At clamp(40,5vw,72) the band
   had TWO curved events competing to be the gesture — a soft card corner
   at the left and a sweep in the middle. One gesture is the rule, so the
   corner becomes a manufactured fillet and the fall keeps the job. */
.sell {
  --cut-drop: clamp(16px, 1.6vw, 26px);
  border-start-start-radius: clamp(20px, 2vw, 30px);
}

/* ============================================================
   THE RECORD WAKES UP INSIDE A FIXED FRAME
   ============================================================
   Every band in a record is declared once, at its ACTIVE height, and
   never changes again. Inactive rows own exactly the same geometry as
   the active one; they simply stand in it more quietly. Activation moves
   opacity, translate, scale and colour — nothing that lays out.

   The budget is the frame that already exists and may not grow:
   135px per row, padding 16 top and bottom, 103px of content.

       make line        20
       gap               4
       name band        38   <- sized for the DOMINANT model, always
       gap              14
       detail band      27   <- price, dot, mileage, action, always
       ----------------------
                       103

   Both model sizes are real text in the same grid cell, crossfaded.
   One node animated between two sizes would be layout work on every
   frame and would render soft mid-transition; two nodes are sharp at
   both ends and cost one opacity each. The large copy is aria-hidden so
   the accessible name stays single.
   ============================================================ */
.recs {
  /* Rows are content-driven, and the content is now a constant, so the
     panel keeps the height it had before this pass. minmax(0,1fr) lets a
     track refuse to grow rather than pushing the module taller — the
     frame wins over the content if they ever disagree. */
  grid-template-rows: repeat(4, minmax(0, 1fr));
  min-height: 0;
  --sel-dur: 300ms;
  --sel-ease: cubic-bezier(0.22, 1, 0.36, 1);
}

.rec { padding-block: var(--sp-4); }

/* ---- the name band ---- */
.rec__name {
  display: grid;
  min-height: 38px;
  margin-block-start: var(--sp-1);
}
.rec__name > .rec__model { grid-area: 1 / 1; align-self: start; }

.rec__model {
  font-size: 1.0625rem;
  font-weight: 500;
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--ink-2);
  margin-block-start: 0;
  opacity: 1;
  transition: opacity var(--sel-dur) var(--sel-ease);
}
.rec__model--lead {
  /* 24-30px. Measured against the body column: every one of the four
     names sets on one line here, which is what the 38px band assumes.
     A longer name than "Aventador SVJ Roadster" must be checked before
     it goes in. */
  font-size: clamp(1.5rem, 1.85vw, 1.875rem);
  font-weight: 600;
  line-height: 1.12;
  letter-spacing: -0.022em;
  color: var(--ink);
  opacity: 0;
  translate: 0 7px;
  transition: opacity var(--sel-dur) var(--sel-ease),
              translate var(--sel-dur) var(--sel-ease);
}
/* The old rule blew the quiet copy up to 40px on activation. It is the
   lead copy's job now, so the quiet one keeps its index size and only
   fades. */
.rec.is-active .rec__model {
  font-size: 1.0625rem;
  font-weight: 500;
  line-height: 1.25;
  margin-block-start: 0;
  opacity: 0;
}
.rec.is-active .rec__model--lead { opacity: 1; translate: 0 0; }

/* ---- the detail band ---- */
.rec__detail {
  display: block;
  grid-template-rows: none;      /* the 0fr->1fr disclosure is retired */
  min-height: 27px;
  margin-block-start: var(--sp-4);
  transition: none;
}
.rec__detail-in {
  display: flex;
  flex-wrap: nowrap;
  align-items: baseline;
  gap: var(--sp-5);
  overflow: visible;
  opacity: 1;
  transition: none;
}

/* Both are laid out in every state and hidden by opacity, which is what
   makes the space pre-allocated rather than reclaimed. They arrive after
   the name, a beat apart, so the row resolves in one direction instead
   of everything appearing at once. Delays sit on the ACTIVE state only,
   so leaving is parallel and immediate. */
.rec__facts,
.rec.is-active .rec__facts {
  display: block;
  margin-block-start: 0;
  font-size: 1rem;
  color: var(--ink-2);
  white-space: nowrap;
}
.rec__facts, .rec__go {
  opacity: 0;
  translate: 0 6px;
  transition: opacity var(--sel-dur) var(--sel-ease),
              translate var(--sel-dur) var(--sel-ease);
}
.rec__go,
.rec.is-active .rec__go {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-3);
  margin-block-start: 0;
  color: var(--ink);
  letter-spacing: var(--tr-label);
  white-space: nowrap;
}
.rec.is-active .rec__facts { opacity: 1; translate: 0 0; transition-delay: 70ms; }
.rec.is-active .rec__go    { opacity: 1; translate: 0 0; transition-delay: 130ms; }

/* ---- the thumbnail ---- */
/* The layout box is 104px in both states. Only the painted image scales,
   so the column it sits in cannot change width and the row cannot change
   height. Centred is safe now that the body's height is a constant. */
.rec__thumb {
  width: 104px;
  align-self: center;
  transform-origin: 100% 50%;
  scale: 0.94;
  opacity: 0.5;
  transition: scale var(--sel-dur) var(--sel-ease),
              opacity var(--sel-dur) var(--sel-ease);
}
.rec.is-active .rec__thumb { scale: 1.06; opacity: 1; }

/* ---- hover is a preview, not a selection ---- */
/* A pointer crossing the column previews the row under it and the field
   follows; taking the pointer away returns everything to the row that
   was actually chosen. Nothing about the preview is sticky, so the list
   never ends up showing a car nobody picked. */
.rec:hover .rec__model { color: var(--ink); }

@media (prefers-reduced-motion: reduce) {
  .rec__model, .rec__model--lead, .rec__facts, .rec__go, .rec__thumb,
  .rec::before, .rec { transition: none; }
  .rec__model--lead, .rec__facts, .rec__go { translate: none; }
}

/* ---- The frame is locked by construction, not by luck ----

   .recs is given the height the panel already had, and the four tracks
   divide it. The module can therefore not move when the content inside a
   row changes, which is the whole brief — a row that wanted an extra
   line would overflow its own track rather than push the section taller.

   Content budget per row: 135 track - 32 padding - 1 rule = 102.

       make        20
       gap          4   sp-1
       name band   36   sized for the dominant model
       gap         12   sp-3
       detail band 28
       ----------------
                  100   <= 102

   THE BUG THIS REPLACES, recorded because it cost a pass: the detail row
   inherited flex-wrap:wrap from `.rec.is-active .rec__detail-in`, which
   outranks a bare `.rec__detail-in` three ways to one. On the Ferrari the
   price and the action came to 440px in a 420px measure, wrapped to two
   lines, and took the row to 167 — and because 1fr tracks in an
   auto-height grid all resolve to the TALLEST item, every other row grew
   with it. One row wrapping moved the whole module 129px. */
.recs { min-height: 33.75rem; }

.rec__name { min-height: 36px; }
.rec__detail { min-height: 28px; margin-block-start: var(--sp-3); }

/* Matched specificity, or the active row keeps the old wrapping rule.
   space-between rather than a fixed gap: the price takes the left of the
   measure and the action the right, so whatever is left over becomes the
   interval instead of the interval being fixed and the pair overflowing. */
.rec .rec__detail-in,
.rec.is-active .rec__detail-in {
  display: flex;
  flex-wrap: nowrap;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--sp-4);
}

/* 15px, down from 16. The pair has to sit on one line in a 420px measure
   and it did not: 246 + 162 with a 32px gap is 440. At 15px the price
   comes to 231 and the row closes at 409 with air to spare. Still mono,
   still well clear of the 14px floor that functional text answers to. */
.rec__facts,
.rec.is-active .rec__facts { font-size: 0.9375rem; }

/* ============================================================
   THE TWO COLUMNS SHARE A TOP LINE AND A BOTTOM LINE
   ============================================================
   The stage was a 16:10 box centred in a taller row, so the objects
   living in it — the "Just in" pill, the prev/next arrows — were hung
   off a box whose edges corresponded to nothing on the right-hand side.
   The pill floated 83px below the top of the index, the arrows stopped
   76px above the bottom of it, and neither misalignment was intentional;
   they were both just where the invisible box happened to end.

   The stage now spans the full row, exactly like the index beside it.
   It still has no ground of its own — the four-layer field belongs to
   .showcase — so nothing about it is visible; it is a positioning frame,
   and a positioning frame should agree with the composition it serves.

   With that, `0` is the right answer for both objects instead of a
   guessed inset, and the two masses open and close on the same lines. */
.showcase__stage {
  align-self: stretch;
  aspect-ratio: auto;
}

/* Re-tuned so the CAR DOES NOT MOVE. --ground is a percentage of the
   stage's height, and the stage just grew from 526 to 626, so the same
   24% would have dropped the tyre line 51px. 28% of the new box puts it
   back on the pixel it was already standing on. */
.showcase__stage { --ground: 28%; }
.stage__car { --ground: 28%; }

.justin { inset-block-start: 0; }
.stage__nav { inset-block-end: 0; }

/* ---- The way out of the section, at the weight of a way out ----
   Full column width, so it closes the index on the same right edge every
   record is measured against, and reads as the list's last line rather
   than a small control parked under it. Taller, brighter rule, larger
   label.

   Deliberately NOT filled. The car is the dominant mass here; a 585px
   bone pill under it would be a second bright object competing for that
   job, and the section would have two subjects. Width and edge carry the
   prominence instead of fill. */
.index__all {
  align-self: stretch;
  justify-content: center;
  padding-block: var(--sp-5);
  font-size: 0.9375rem;
  letter-spacing: var(--tr-label);
  box-shadow: inset 0 0 0 1px rgba(243, 238, 230, 0.42);
}
.index__all:hover,
.index__all:focus-visible {
  box-shadow: inset 0 0 0 1px var(--ink);
  background: rgba(243, 238, 230, 0.055);
}

/* ---- The field name leads, the setting annotates ----

   Reversed from the previous pass. The rail reads as a spec sheet, and on
   a spec sheet the FIELD is the structure and its current setting is the
   annotation hanging off it — so MAKE takes full ink and a step up in
   size, and "Any Make" sits under it smaller and quieter.

   Rank still inverts on an answer, and still by INK ALONE: once a real
   make is chosen the value comes up to full ink and the label drops back,
   because at that point the answer is the content. Sizes never change, so
   the line cannot resize under the reader.

   14px is the floor, not a preference — typography's functional-text
   invariant. The value states what a control is set to, so it stops
   exactly there and goes no smaller, whatever the visual argument for
   more contrast between the two lines.

   COLOUR IS AS FAR APART AS IT CAN GO HERE. --ink-3 was the obvious next
   step down for the value and it does not survive the ground: measured on
   the composite against the brightest pixels the film puts behind this
   rail, it comes out at 3.98:1 against a 4.5 floor. --ink-2 measures
   4.96:1 and stays. The separation is carried by the label going UP to
   full ink instead. */
.hunt__label {
  font-size: 0.9375rem;
  color: var(--ink);
  letter-spacing: var(--tr-label);
}
.hunt__value {
  font-size: 0.875rem;
  color: var(--ink-2);
}
.hunt__field.is-filled .hunt__label { color: var(--ink-2); }
.hunt__field.is-filled .hunt__value { color: var(--ink); }

/* ============================================================
   THE ACTIVE ROW, LOOSENED
   ============================================================
   Three complaints, one cause: everything in the row was arriving at
   once, in one register, with nothing between it. The fixes are a
   different voice for the figures, a shorter word for the action, and
   real air between the name and the data line.

   The row budget grows with it. This is a static change to every row
   equally, not something that happens on hover — the frame is still
   locked and still cannot move when a record activates, which was the
   point of locking it.

       track       152
       padding      32
       rule          1
       ---------------
       content     119

       make         20
       gap           4
       name band    36
       gap          24   <- was 12; this is the air that was missing
       detail band  34   <- sized for the pill
       ---------------
                   118
   ============================================================ */
.recs { min-height: 38rem; }
.rec__detail { min-height: 34px; margin-block-start: var(--sp-5); }

/* THE FIGURES GET THEIR OWN VOICE.

   Mono was doing two different jobs — the technical captions AND the
   price — and at the size the price needs it read as a terminal, not as
   a number on a car. Inter Tight is condensed and holds weight, so the
   figure can be bold and still take less width than the mono did: the
   line went from 246px to about 200 in a 420 measure, which is most of
   the horizontal crowding gone on its own.

   Tabular figures so the four prices align down the column, and a tight
   optical tracking because that is what the face is for. */
.rec__facts,
.rec.is-active .rec__facts {
  font-family: var(--font-data);
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: -0.012em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.rec__dot { color: var(--ink-3); font-weight: 400; }

/* THE ACTION IS A PILL NOW, AND THE ARROW IS GONE.

   "View vehicle →" was 162px of tracked mono plus a glyph, sitting on the
   same baseline as the price with nothing to separate them — two pieces
   of text of similar weight reading as one run. "Details" in a pill is a
   different KIND of object: it stops competing with the figure beside it
   and reads as the thing you press.

   The arrow went with it. At this size an arrow inside a pill is a second
   affordance saying what the pill already says. */
.rec__go,
.rec.is-active .rec__go {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0;
  padding: 8px 18px;
  border-radius: 999px;
  font-size: var(--t-micro);
  letter-spacing: var(--tr-label);
  color: var(--ink);
  box-shadow: inset 0 0 0 1px rgba(243, 238, 230, 0.34);
  margin-block-start: 0;
}
.rec.is-active:hover .rec__go {
  box-shadow: inset 0 0 0 1px var(--ink);
  background: rgba(243, 238, 230, 0.07);
}

/* ---- The row's action is gone, and it should be ----

   "Details" sat inside a <button> whose only job is to change the
   photograph. Pressing it selected a car; it did not open details. A
   control that names a destination it cannot reach is worse than no
   control, and the way to the full record already exists once, at full
   width, at the foot of the list.

   What is left on the line is the fact — price and mileage — which is
   what the active row was for. The band comes down to the height of one
   figure line; the row keeps its 152 so the frame does not move, and the
   space the pill was holding becomes air under the price. */
.rec__detail { min-height: 26px; }
.rec__go { display: none; }

/* ============================================================
   THE FACT IS PERMANENT; THE EMPHASIS IS WHAT MOVES
   ============================================================
   Price and mileage now stand in every row, not only the chosen one. The
   list stops being four names with one priced record and becomes what it
   should have been from the start: a priced index. Activation no longer
   REVEALS the figure, it promotes it — which is a better job for a hover
   to be doing, because nothing a visitor needs is hidden behind it.
   ============================================================ */
.rec__facts {
  opacity: 1;
  translate: none;
  color: var(--ink-2);
  transition: color var(--sel-dur) var(--sel-ease);
}
.rec.is-active .rec__facts {
  opacity: 1;
  translate: none;
  color: var(--ink);
  transition-delay: 0s;
}

/* Air between the make and the name. At 4px the two lines were one
   block and the eyebrow read as part of the title rather than as the
   line above it. */
.rec__name { margin-block-start: var(--sp-3); }

/* ---- The thumbnail steps aside and a chevron takes the margin ----

   On activation the vehicle slides left and a chevron arrives in the
   space it leaves. It is the row saying where it goes without adding a
   button to say it — the "Details" pill this replaces was a whole object
   competing with the price beside it; this is one glyph in the margin
   that is empty the rest of the time.

   Neither of them lays out. The thumbnail moves on `translate` and keeps
   its 104px box; the chevron is absolutely positioned, so it occupies no
   column and the row cannot change width or height when it appears. */
.rec__thumb {
  translate: 0 0;
  transition: scale var(--sel-dur) var(--sel-ease),
              opacity var(--sel-dur) var(--sel-ease),
              translate var(--sel-dur) var(--sel-ease);
}
.rec.is-active .rec__thumb { translate: -36px 0; }

.rec__chev {
  position: absolute;
  inset-inline-end: 0;
  inset-block-start: 50%;
  display: flex;
  color: var(--ink);
  pointer-events: none;
  opacity: 0;
  translate: 8px -50%;
  transition: opacity var(--sel-dur) var(--sel-ease),
              translate var(--sel-dur) var(--sel-ease);
}
.rec.is-active .rec__chev { opacity: 1; translate: 0 -50%; transition-delay: 80ms; }

@media (prefers-reduced-motion: reduce) {
  .rec__chev, .rec__thumb { transition: none; }
  .rec__chev { translate: 0 -50%; }
}

/* ============================================================
   THE INDEX AS FOUR PANELS — built to the draft
   ============================================================
   The hairline list becomes four outlined panels separated by a gap, and
   the active one is marked by its own edge and a light sitting on its
   bottom border. That moves the whole burden of "which one is chosen"
   off the TYPE and onto the PANEL, which is why the model name is now
   one size in every row: the two-copy crossfade existed to make the name
   dominant, the frame does that job now, and a device with no job does
   not stay.

   Everything the previous pass bought is kept: the panel heights are
   equal and fixed, nothing reflows on activation, and only opacity,
   colour and transform move.
   ============================================================ */
.recs {
  gap: 18px;
  min-height: 40.5rem;      /* 4 panels of 148 + 3 gaps of 18 */
  border-top: 0;
}

.rec {
  /* number | body | thumbnail | arrow, all on one centred line */
  grid-template-columns: auto minmax(0, 1fr) auto auto;
  align-items: center;
  align-content: center;
  gap: var(--sp-5);
  padding: var(--sp-4) var(--sp-5);
  border: 1px solid rgba(243, 238, 230, 0.10);
  border-radius: 14px;
  transition: border-color var(--sel-dur) var(--sel-ease),
              background-color var(--sel-dur) var(--sel-ease);
}
.rec:hover,
.fin__rec:hover { border-color: rgba(243, 238, 230, 0.20); }
.rec.is-active,
.fin__rec.is-lead {
  border-color: rgba(104, 156, 236, 0.55);
  background: rgba(28, 44, 82, 0.22);
}

.rec__no {
  align-self: center;
  color: var(--ink-3);
  font-size: 1rem;
  transition: color var(--sel-dur) var(--sel-ease);
}
.rec.is-active .rec__no { color: var(--ink); }

.rec__body { gap: 0; }
.rec__make { color: var(--ink-3); }
.rec__name { margin-block-start: 6px; min-height: 0; }
.rec__model,
.rec.is-active .rec__model {
  font-size: 1.4375rem;
  font-weight: 500;
  line-height: 1.2;
  letter-spacing: -0.015em;
  color: var(--ink);
  opacity: 1;
}
.rec__detail { min-height: 0; margin-block-start: 8px; }
.rec__facts { font-size: 1rem; }

/* Larger, and it keeps its own box in every state — the slide-left is
   retired with the chevron that used to arrive in the space, because the
   arrow has a column of its own in this layout. */
.rec__thumb {
  width: 140px;
  align-self: center;
  translate: none;
  scale: 1;
  opacity: 0.86;
  transition: opacity var(--sel-dur) var(--sel-ease);
}
.rec.is-active .rec__thumb { scale: 1; translate: none; opacity: 1; }

/* Present on every panel, quiet until the panel is chosen. */
.rec__chev {
  position: static;
  display: flex;
  align-self: center;
  translate: none;
  opacity: 1;
  color: var(--ink-3);
  transition: color var(--sel-dur) var(--sel-ease),
              translate var(--sel-dur) var(--sel-ease);
}
.rec.is-active .rec__chev { opacity: 1; translate: none; color: var(--ink); }
.rec.is-active:hover .rec__chev { translate: 4px 0; }

/* THE LIGHT SITS ON THE BOTTOM EDGE.

   Not the large field that washed up the whole row before — a narrow
   source lying along the panel's own bottom border, brightest at the
   centre and gone by the corners. It reads as the edge being lit rather
   than as a glow behind the type, which is what the draft shows and what
   keeps the figures on the row legible while it is on. */
.rec::before {
  content: "";
  position: absolute;
  inset: auto 8% -1px 8%;
  height: 40px;
  z-index: -1;
  border-radius: 0;
  background: radial-gradient(52% 100% at 50% 100%,
    rgba(168, 202, 255, 0.85) 0%,
    rgba(104, 156, 236, 0.34) 34%,
    rgba(70, 116, 200, 0.12) 58%,
    transparent 78%);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--sel-dur) var(--sel-ease);
}
.rec.is-active::before { opacity: 1; }

/* ---- Measured off the draft ----
   The numeral sits on the MAKE line, not in the middle of the panel; the
   gap between it and the text is wide enough to read as a margin rather
   than as a prefix; the thumbnail is larger and the arrow keeps its own
   air at the panel's edge. All of it geometry, none of it state. */
.rec {
  padding: var(--sp-5) var(--sp-6);
  column-gap: var(--sp-6);
}
.rec__no {
  align-self: start;
  padding-block-start: 3px;
}
.rec__thumb { width: 160px; }
.rec__chev { margin-inline-start: var(--sp-4); }

/* ---- The two masses rebalanced to the draft: roughly 53 / 47 ----

   The panels need a wider column than the hairline list did. Measured on
   the draft the index takes about 47% of the card, and it has to: with a
   160px thumbnail, an arrow column and 32px gutters, a 41% column left
   the model 239px of measure and "911 GT2 RS Weissach" wrapped to two
   lines. One wrapped name took every panel from 148 to 165, because the
   four tracks are equal — the same mechanism that cost a pass earlier.

   At 47% the measure is ~325px, every name sets on one line, the panels
   fall back to the floor height, and the list stops depending on how
   long the longest model name happens to be. */
.showcase { grid-template-columns: minmax(0, 53fr) minmax(21rem, 47fr); }

/* ============================================================
   THE PANEL, QUIETED AND OPENED OUT
   ============================================================
   Four changes, one intent: the card should hold still. The only thing
   that moves inside it on hover is the arrow — everything else changes
   by colour, which the eye reads as emphasis rather than as motion.
   ============================================================ */

/* The index numerals are gone with the column that held them. They were
   ordinal decoration on a list of four: the order is visible, and
   nothing on the page refers to a record by its number. */
.rec,
.fin__rec {
  grid-template-columns: minmax(0, 1fr) auto auto;
  padding: var(--sp-6);
  column-gap: var(--sp-6);
}

/* TWO LINES, ALWAYS RESERVED.

   The name is smaller and its band holds two lines whether or not the
   name needs them. That is the last dependency on content length in this
   list: a longer model than any of these four can arrive without taking
   the panel — and every other panel with it — to a new height. The name
   centres in the band, so the common one-line case sits balanced rather
   than hanging from the top of an empty box. */
.rec__name {
  display: grid;
  align-items: center;
  min-height: 3.1rem;
  margin-block-start: var(--sp-2);
}
.rec__model,
.rec.is-active .rec__model {
  font-size: 1.25rem;
  font-weight: 500;
  line-height: 1.28;
  letter-spacing: -0.012em;
}

/* ---- Price and mileage as two small plates ----
   Separated into their own objects, the figures stop reading as one run
   of text with a dot in it and start reading as two facts. The pill is
   the smallest container on the page and it earns that: it is what makes
   a number look measured rather than written. */
.rec__facts {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
  margin-block-start: var(--sp-3);
}
.rec__pill {
  display: inline-flex;
  align-items: center;
  padding: 5px 12px;
  border-radius: 999px;
  font-family: var(--font-data);
  font-size: 0.875rem;
  font-weight: 600;
  letter-spacing: -0.008em;
  font-variant-numeric: tabular-nums;
  color: var(--ink-2);
  background: rgba(243, 238, 230, 0.05);
  box-shadow: inset 0 0 0 1px rgba(243, 238, 230, 0.09);
  transition: color var(--sel-dur) var(--sel-ease),
              background-color var(--sel-dur) var(--sel-ease),
              box-shadow var(--sel-dur) var(--sel-ease);
}
.rec.is-active .rec__pill {
  color: var(--ink);
  background: rgba(122, 168, 240, 0.13);
  box-shadow: inset 0 0 0 1px rgba(122, 168, 240, 0.26);
}

/* ---- Only the arrow moves ----
   And it moves on HOVER of the panel, not on selection, because that is
   what a hover is for: it answers "is this pressable", and the answer is
   the same whether or not this row is the one currently shown. */
.rec__chev { translate: none; }
.rec.is-active .rec__chev { translate: none; }
.rec:hover .rec__chev,
.rec.is-active:hover .rec__chev { translate: 5px 0; }

/* ---- Softer switching, and the chosen panel lifts ----
   420ms instead of 300: the panels are large objects and a fast crossfade
   between two of them reads as a flicker rather than as a handover. The
   shadow is what makes the active one sit in front of the other three —
   a real elevation, small, no coloured bloom, so it reads as the panel
   being nearer rather than as another light. */
.recs { --sel-dur: 420ms; }
.rec {
  transition: border-color var(--sel-dur) var(--sel-ease),
              background-color var(--sel-dur) var(--sel-ease),
              box-shadow var(--sel-dur) var(--sel-ease);
}
.rec.is-active {
  box-shadow: 0 14px 34px -14px rgba(0, 0, 0, 0.62),
              0 3px 10px -4px rgba(0, 0, 0, 0.4);
}

/* ============================================================
   SELL / TRADE — the copy arrives with the scroll
   ============================================================
   One temporal idea for the whole section: the text rises into place as
   the band enters. Not four separate animations — one gesture, applied
   to four elements at four offsets, so the block resolves top to bottom
   the way it is read.

   Driven by the scroll position, not by a timer: `view()` ties each
   element's progress to where the section actually is in the viewport,
   so scrolling back up reverses it and stopping halfway leaves a frame
   that was designed rather than a frame that was interrupted.

   Wrapped in no-preference, so reduced motion is not a fallback that has
   to be written twice — it is simply the static page, complete, with
   nothing hidden. Nothing here uses opacity outside the animation, so
   there is no state in which the copy can fail to appear. */
@keyframes sell-rise {
  from { opacity: 0; translate: 0 18px; }
  to   { opacity: 1; translate: 0 0; }
}

/* The pair stays a pair at every width down to the phone breakpoint —
   side by side, primary left, secondary right. They were already in one
   row; this stops the row from wrapping the moment the label grew from
   "How it works" to "Talk to our specialist". */
.sell__actions { flex-wrap: nowrap; }
@media (max-width: 560px) {
  .sell__actions { flex-wrap: wrap; }
}

/* 8px was enough while the pills were bare text and not enough once the
   active state gives each of them a ring and a fill: two rings 8px apart
   read as one object with a seam. */
.rec__facts { gap: var(--sp-3); }

/* ---- THE STEPS ARE NOT ANIMATED, AND THE RANGES ARE MADE SAFE ----

   Two defects in the pass above, both mine.

   The three steps were dimmed and left that way. Measured with the row
   centred in the viewport they sat at opacity 0.23 and never finished:
   a range written as `entry X% cover Y%` mixes two phases whose progress
   runs at completely different rates for a short element near the foot of
   a tall section, and the animation simply never reached its end. With
   `both` fill, an animation that never ends is content that never
   appears. They come out of the animation altogether — the earlier brief
   asked for the sequence to stay calm and technical, and animating it was
   my addition, not the request.

   The copy keeps the rise, but every range is now expressed in ONE phase.
   `entry` completes, by definition, when the element is fully in view, so
   there is no arithmetic left that can strand an element at 23%. */

/* ---- THE COPY ARRIVES AS A STAIRCASE, NOT AS A BLOCK ----

   The previous ranges overlapped almost entirely — 10-62, 16-72, 22-82,
   28-92 — so all four lines were in motion at the same moment and the
   block read as one mass fading up. A stagger you cannot count is not a
   stagger.

   Each beat now opens after the one before it is most of the way home,
   with a short overlap so the sequence flows rather than clicking through
   four discrete states. And each travels a different distance over a
   different span, which is what makes them read as separate objects
   moving at their own speeds instead of one object with a delay. */
@keyframes sell-rise {
  from { opacity: 0; translate: 0 var(--rise, 18px); }
  to   { opacity: 1; translate: 0 0; }
}

/* ============================================================
   THE FOOT OF THE BAND — a ground for the sequence
   ============================================================
   The three steps sat directly on the photograph's floor, which is a
   mid-tone with the car's reflection running through it. They read as
   printed on the picture rather than as the line that closes the band.

   The gradient is confined to the bottom of the frame and reaches full
   bone only at the very edge. That is the whole reason it is allowed:
   the earlier note here rejected a bone scrim because washing the floor
   bright enough would bleach out the reflection, and it was right — so
   this one does not touch the floor where the reflection is. It is a
   ramp in the last fifth of the image, under the steps and nowhere else,
   and the car keeps its reflection above it.

   Built from --bone so the band's foot cannot drift away from the
   section's own colour the way a hard-coded value would. */
/* ---- THE LEFT EDGE CLOSES ------------------------------------------
   A white car is cut in half by the frame's left edge. At the band's own
   width there is nothing behind it, so it reads as a photograph that ran
   out rather than as a room that continues — and it sits directly under
   the headline, which is the worst place on the band for a distraction.

   A bone ramp from the left closes it. It is the same device as the foot
   ramp below, turned ninety degrees, and it uses the same colour, so the
   picture dissolves into the band's own ground instead of being masked
   by a panel. It runs deep enough to swallow the fragment and stops well
   short of the blue car, which is the subject.

   It also does the copy a favour: the headline and lede sit on the part
   of the wall this ramp lightens. */
.sell__bg::before {
  content: "";
  position: absolute;
  inset: 0 auto 0 0;
  inline-size: clamp(200px, 26%, 460px);
  z-index: 1;
  pointer-events: none;
  background: linear-gradient(to right,
    var(--bone) 0%,
    rgba(236, 228, 214, 0.92) 22%,
    rgba(236, 228, 214, 0.68) 46%,
    rgba(236, 228, 214, 0.32) 72%,
    rgba(236, 228, 214, 0) 100%);
}

.sell__bg::after {
  content: "";
  position: absolute;
  inset: auto 0 0 0;
  z-index: 2;
  height: clamp(170px, 24%, 300px);
  pointer-events: none;
  background: linear-gradient(to top,
    var(--bone) 0%,
    rgba(236, 228, 214, 0.92) 26%,
    rgba(236, 228, 214, 0.66) 52%,
    rgba(236, 228, 214, 0.28) 78%,
    rgba(236, 228, 214, 0) 100%);
}

/* ---- The sequence, made solid ----
   The numerals carry the count, so they are the part that gets weight:
   larger, tabular, and set in the data face the prices already use, which
   is what the face is in the project for. The titles come up to 500 and
   drop their wide tracking — at 0.14em a sentence-length label reads as a
   caption spaced out to fill a slot rather than as a step in a process. */
.sell__steps { border-top-color: rgba(26, 20, 14, 0.3); }
.sell__steps li + li { border-inline-start-color: rgba(26, 20, 14, 0.3); }
/* A NUMERAL, NOT A LABEL. The two tracking roles are for uppercase mono
   set as words; a figure at 22px/700 is the opposite case and wants to
   close up rather than open out, so this is correct as it stands — I had
   wrongly swept it into the label role and it is out again.

   Numerals are a THIRD role in this register and they are not settled:
   --tr-count is -0.045em for the story figures, this runs at -0.01em.
   Two answers to one question, named here so the next pass starts from
   it instead of discovering it again. */
.sell__step-n {
  font-family: var(--font-data);
  font-size: 1.375rem;
  font-weight: 500;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
  line-height: 1;
}
.sell__step-t {
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: var(--tr-label);
}

/* ---- NOTHING INSIDE THE CARD MOVES. MEASURED, NOT ASSERTED. ----

   The make and the model were sliding 6px down and the figures 6px up
   whenever a card activated. Cause, for the third time in this file:
   `.rec.is-active .rec__facts` is (0,3,0) and still carried
   `margin-block-start: 0` from an earlier layout, so the plain
   `.rec__facts { margin-block-start: 12px }` lost to it. The detail band
   went 44px to 32px, the body went 130 to 118, and `align-content:
   center` split the missing 12 between the top and the bottom.

   Both states are pinned here at matched specificity — same margin, same
   font-size, same everything. The only difference between an inactive and
   an active card is now colour, ring, light and shadow. */
.rec__facts,
.rec.is-active .rec__facts {
  margin-block-start: var(--sp-3);
  font-size: 1rem;
  line-height: 1.6;
}

/* ---- THE GAP WAS NOT BEING APPLIED AT ALL ON THE ACTIVE CARD ----

   Same trap, same file, one more time — and this one was invisible to a
   declared-value check, because `gap: 12px` computed correctly the whole
   while. It just had nothing to act on: an older
   `.rec.is-active .rec__facts { display: block }` at (0,3,0) was beating
   the flex declaration at (0,1,0), and `gap` does nothing in a block box.
   With no whitespace between the two pills in the markup either, the
   chosen card rendered them at a measured 0px apart while the other
   three sat correctly at 12.

   Pinned at matched specificity, and the gap goes to 16 while we are
   here — with a ring and a fill on each, 12 was tight even where it
   worked. */
.rec__facts,
.rec.is-active .rec__facts {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
}

/* ============================================================
   6 — STORY
   ============================================================ */
.story {
  position: relative;
  background: var(--bg);
  overflow-x: clip;
}
.story__inner {
  max-width: var(--measure-wide, 100rem);
  margin-inline: auto;
  padding-inline: clamp(var(--sp-6), 5vw, var(--sp-9));
  padding-block: clamp(var(--sp-9), 9vw, var(--sp-11));
  display: grid;
  grid-template-columns: minmax(0, 46fr) minmax(0, 54fr);
  column-gap: clamp(var(--sp-7), 5vw, var(--sp-9));
  align-items: center;
}

.story__copy { max-width: 34rem; }
.story__eyebrow { color: var(--ink-3); letter-spacing: var(--tr-label); }
/* The date is the one word in the line that is a FACT rather than a
   label, so it is the one word that carries colour. Cool, not the accent:
   red is the action colour on this ground and a date is not an action. */
.story__est { color: var(--ink-2); }

.story__title {
  margin-block-start: var(--sp-5);
  font-size: clamp(2.125rem, 3.4vw, 3.25rem);
  font-weight: 600;
  line-height: 1.08;
  letter-spacing: var(--tr-display);
}
/* Two authored lines, because the break is a composition decision and not
   something a measure should be left to discover — and because each line
   arrives on its own beat. */
.story__line { display: block; }

/* The one piece of accent in the section, and it is a mark, not a fill:
   a short rule under the claim, the same gesture the sell band uses. */
.story__rule {
  display: block;
  width: 52px;
  height: 2px;
  margin-block: var(--sp-6) var(--sp-6);
  background: var(--accent);
}

.story__lede {
  max-width: 44ch;
  line-height: 1.7;
  color: var(--ink-2);
}

.story__media {
  margin: 0;
  border-radius: 16px;
  overflow: hidden;
  /* The frame carries its own edge rather than a shadow: on this ground a
     drop shadow under a dark photograph is invisible work. */
  box-shadow: inset 0 0 0 1px rgba(243, 238, 230, 0.08);
}
.story__media img {
  display: block;
  width: 100%;
  height: 100%;
  aspect-ratio: 3 / 2;
  object-fit: cover;
  object-position: 58% 50%;
}

/* ---- The figures ----
   Three columns, rules between them rather than around them — the same
   construction as the sell steps, so the two sequences on this page read
   as one system. */
.story__stats {
  grid-column: 1 / -1;
  margin-block-start: clamp(var(--sp-8), 6vw, var(--sp-9));
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
}
.stat { position: relative; padding-inline: clamp(var(--sp-5), 3vw, var(--sp-7)); }
.stat:first-child { padding-inline-start: 0; }
.stat + .stat { border-inline-start: 1px solid var(--rule); }
.stat__label { color: var(--ink-2); letter-spacing: var(--tr-label); }
.stat__fig { margin: 0; margin-block-start: var(--sp-4); position: relative; }
.stat__n {
  display: block;
  font-family: var(--font-data);
  font-size: clamp(3rem, 5.4vw, 5.25rem);
  font-weight: 700;
  line-height: 0.95;
  letter-spacing: -0.03em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
/* The same light that sits under a chosen inventory panel, reused here
   under each figure. Not a new device — the section inherits the one the
   index already established, which is what keeps the two dark chapters
   speaking the same language. */
.stat__fig::after {
  content: "";
  position: absolute;
  inset-inline: 4% 34%;
  inset-block-end: -14px;
  height: 26px;
  pointer-events: none;
  background: radial-gradient(50% 100% at 50% 100%,
    rgba(168, 202, 255, 0.8) 0%,
    rgba(104, 156, 236, 0.3) 34%,
    transparent 74%);
}

.story__cta {
  grid-column: 1 / -1;
  justify-self: start;
  margin-block-start: clamp(var(--sp-7), 4vw, var(--sp-8));
  padding-block-start: 0;
}
/* A rule across the full measure above the action, so the section closes
   on a line the way the index does. */
.story__stats + .story__cta { position: relative; }
.story__stats + .story__cta::before {
  content: "";
  position: absolute;
  inset-block-start: calc(-1 * clamp(var(--sp-6), 2.6vw, var(--sp-7)));
  inset-inline-start: 0;
  width: 100vw;
  max-width: var(--measure-wide, 100rem);
  height: 1px;
  background: var(--rule);
}

@media (max-width: 900px) {
  .story__inner { grid-template-columns: 1fr; row-gap: clamp(var(--sp-7), 6vw, var(--sp-8)); }
  .story__copy { max-width: none; }
  .story__stats { grid-template-columns: 1fr; row-gap: var(--sp-6); }
  .stat { padding-inline: 0; }
  .stat + .stat { border-inline-start: 0; border-block-start: 1px solid var(--rule); padding-block-start: var(--sp-5); }
}

/* ---- The story text passes through the viewport ----

   In on the way up, out on the way down: one keyframe set covering the
   whole of each element's time on screen, so the same gesture does both
   ends and there is no second animation to keep in sync.

   The hold between 24% and 72% is the point — a pass that is always in
   motion never lets anything be read. It is still for roughly half of
   its life on screen, which is when it is doing its job.

   The FIGURES are excluded on purpose. They are counted up by script,
   and a number sliding away while it is still counting would be an
   instrument you cannot read. They fade only. */
@keyframes story-pass {
  0%   { opacity: 0; translate: 0 var(--rise, 26px); }
  24%  { opacity: 1; translate: 0 0; }
  72%  { opacity: 1; translate: 0 0; }
  100% { opacity: 0; translate: 0 calc(-0.55 * var(--rise, 26px)); }
}
@keyframes story-fade {
  0%   { opacity: 0; }
  24%  { opacity: 1; }
  76%  { opacity: 1; }
  100% { opacity: 0.35; }
}

/* ---- IN ON entry, OUT ON exit — two single-phase animations ----

   The `cover`-based pass was not wrong, it was unreadable. `view()` builds
   its timeline from the ELEMENT, not the section, so a figure sitting low
   in the viewport is genuinely still early in its own cover progress —
   which rendered as three statistics at three different opacities, side
   by side, looking like a gradient across the row rather than a stagger.

   Split in two. `entry` completes, by definition, the moment the element
   is fully in view, so everything on screen is at full strength and the
   row reads as one. `exit` starts the moment it begins to leave. No
   arithmetic across phases, which is the third time that arithmetic has
   produced something stuck at a value nobody chose.

   The out animation fills FORWARDS only. With `both` it would hold its
   from-state before the exit range began and quietly overwrite the
   entry animation still running underneath it. */
@keyframes story-in  { from { opacity: 0; translate: 0 var(--rise, 26px); }
                       to   { opacity: 1; translate: 0 0; } }
@keyframes story-out { from { opacity: 1; translate: 0 0; }
                       to   { opacity: 0; translate: 0 calc(-0.5 * var(--rise, 26px)); } }


/* ============================================================
   STORY, REBUILT — the room is the ground
   ============================================================
   The framed photograph on the right is gone. The picture is the section
   now: copy stands on it top-left, the figures close it along the bottom,
   and the whole band is one image rather than a column of text beside a
   rectangle. That is also what lets the figures get the scale they need —
   there is no second object competing for the right-hand half.
   ============================================================ */
.story { isolation: isolate; }
.story__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}
.story__bg img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 62% 50%;
}
/* THE GROUND IS DARKENED WHERE THE TEXT IS, AND ONLY THERE.

   Two layers doing two different jobs. A wash from the left carries the
   copy block, which sits over the busiest part of any showroom frame; a
   ramp from the bottom carries the figures. Between them the photograph
   is left alone, which is the point — a flat scrim over the whole image
   would buy legibility by throwing away the reason the image is here. */
.story__bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to right,
      rgb(var(--bg-rgb) / 0.94) 0%,
      rgb(var(--bg-rgb) / 0.86) 26%,
      rgb(var(--bg-rgb) / 0.52) 52%,
      rgb(var(--bg-rgb) / 0.22) 74%,
      rgb(var(--bg-rgb) / 0.12) 100%),
    linear-gradient(to top,
      rgb(var(--bg-rgb) / 0.92) 0%,
      rgb(var(--bg-rgb) / 0.64) 18%,
      rgb(var(--bg-rgb) / 0.2) 38%,
      transparent 58%);
}

.story__inner {
  position: relative;
  z-index: 1;
  display: block;
  min-height: clamp(38rem, 46vw, 52rem);
  padding-block: clamp(var(--sp-9), 8vw, var(--sp-11));
}

.story__copy { max-width: 33rem; }
.story__lede { max-width: 40ch; }
.story__cta {
  display: inline-flex;
  margin-block-start: clamp(var(--sp-6), 3vw, var(--sp-7));
}
/* The wrapper that replaced it. It holds the same position and the same
   air; the editorial-action styling stayed behind with the class, which
   is why this is a sibling rule and not an override war. */
.story__act {
  display: inline-flex;
  margin-block-start: clamp(var(--sp-6), 3vw, var(--sp-7));
}

/* ---- The figures along the foot ----
   One leads, two answer. The interval after the lead is deliberately much
   larger than the interval between the pair, so the row reads as a claim
   followed by its context rather than as three equal statistics. */
.story__stats {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  column-gap: clamp(var(--sp-7), 4vw, var(--sp-8));
  row-gap: var(--sp-7);
  margin-block-start: clamp(var(--sp-8), 8vw, var(--sp-10));
}
.story__stats .stat {
  padding-inline: 0;
  border-inline-start: 0;
}
.stat--lead { margin-inline-end: clamp(var(--sp-8), 8vw, 10rem); }

/* The label sits UNDER the figure now — the number arrives first and the
   caption tells you what it counted, which is the order the eye takes a
   statistic in anyway. */
.stat__fig { margin: 0; }
.stat__label {
  display: block;
  margin-block-start: var(--sp-4);
  color: var(--ink-2);
  letter-spacing: var(--tr-label);
}
.stat__n { font-size: clamp(2.5rem, 3.6vw, 3.5rem); }
.stat--lead .stat__n { font-size: clamp(4rem, 9vw, 8.5rem); letter-spacing: -0.035em; }
.stat--lead .stat__label { font-size: 1rem; letter-spacing: var(--tr-label); }

/* A short accent rule under the two SUBORDINATE labels only. The lead has
   its scale; marking it as well would be saying the same thing twice.
   Under the pair it does real work — it binds them as a set answering the
   figure to their left. */
.stat:not(.stat--lead) .stat__label::after {
  content: "";
  display: block;
  width: 34px;
  height: 2px;
  margin-block-start: var(--sp-4);
  background: var(--accent);
}

/* The light under each figure is retired here. On a photographic ground it
   read as a lens flare rather than as the edge-light it is on the flat
   panels of the index — same device, wrong surface. */
.stat__fig::after { content: none; }

@media (max-width: 900px) {
  .story__inner { min-height: 0; }
  .stat--lead { margin-inline-end: 0; flex-basis: 100%; }
}

/* ---- Tuned to the McLaren frame ----

   This picture needs almost no help, and that is the whole reason it is
   the right one. Its left third is already near-black and its lower
   third is empty reflected floor, which is exactly where the copy and
   the figures stand — the scrims that carried them over the Ford GT
   frame were fighting a picture that was lit where the text goes.

   So they come down hard: 0.78 -> 0.46 on the left and 0.9 -> 0.62 at
   the foot. Enough to hold the ink steady against the lamp reflections
   on the floor, not enough to grey the cars or flatten the mural, which
   is the part of this frame worth keeping.

   1.85:1 against a band nearer 1.84 — it very nearly fits, so cover
   takes almost nothing off either axis and the composition arrives as
   shot. */
.story__bg img { object-position: 56% 50%; }
.story__bg::after {
  background:
    linear-gradient(to right,
      rgb(var(--bg-rgb) / 0.46) 0%,
      rgb(var(--bg-rgb) / 0.38) 24%,
      rgb(var(--bg-rgb) / 0.18) 46%,
      rgb(var(--bg-rgb) / 0.04) 64%,
      transparent 80%),
    linear-gradient(to top,
      rgb(var(--bg-rgb) / 0.62) 0%,
      rgb(var(--bg-rgb) / 0.36) 16%,
      rgb(var(--bg-rgb) / 0.08) 34%,
      transparent 52%);
}

/* ============================================================
   THE PHOTOGRAPH IS THE MASS. THE SCRIM IS LOCAL.
   ============================================================
   The previous build buried it, and the culprit was not the left wash —
   it was the BOTTOM RAMP, which ran the full width at 0.9 and pulled the
   right-hand third of the frame down with it. A gradient that darkens
   everything is not a scrim, it is an opacity.

   Rewritten as three LOCAL sources, each one placed over the thing it is
   protecting and nowhere else:

     1. the copy block, top-left      — an ellipse, strong
     2. the lead figure, bottom-left  — an ellipse, strong
     3. the two small figures, ~55-78% across the foot — a small pool,
        weak, because the floor there is already near-black in the frame

   Everything past roughly two-thirds is left alone: the blue GT, the red
   rim light, the reflections and the Cobra mural come through at the
   photograph's own contrast. There is no full-width layer left anywhere
   in this section. */
.story__bg::after {
  background:
    /* 1 — behind the copy */
    radial-gradient(78% 82% at 4% 30%,
      rgb(var(--bg-rgb) / 0.92) 0%,
      rgb(var(--bg-rgb) / 0.8) 34%,
      rgb(var(--bg-rgb) / 0.44) 62%,
      rgb(var(--bg-rgb) / 0.12) 84%,
      transparent 100%),
    /* 2 — under the lead figure */
    radial-gradient(58% 46% at 6% 100%,
      rgb(var(--bg-rgb) / 0.9) 0%,
      rgb(var(--bg-rgb) / 0.66) 38%,
      rgb(var(--bg-rgb) / 0.24) 70%,
      transparent 100%),
    /* 3 — under the pair, and no stronger than they need */
    radial-gradient(30% 34% at 62% 100%,
      rgb(var(--bg-rgb) / 0.62) 0%,
      rgb(var(--bg-rgb) / 0.34) 46%,
      transparent 82%),
    /* 4 — a hairline seam at the very bottom so the band closes into the
       page rather than ending on a cut edge. 40px, not a ramp. */
    linear-gradient(to top,
      rgb(var(--bg-rgb)) 0%,
      rgb(var(--bg-rgb) / 0.55) 2%,
      transparent 5%);
}

/* The frame is worth more room now that it is doing real work. */
.story__inner { min-height: clamp(42rem, 52vw, 58rem); }

/* ============================================================
   STORY, ALIGNED TO THE DESIGN SYSTEM
   ============================================================
   Three things in this section were invented rather than consumed, and
   all three go. The rule is the sell band: same eyebrow construction,
   same label register, same colour roles. Nothing new.

   1. THE BLUE. var(--ink-2) was a colour this project does not have. The
      accent is the only chromatic role on a dark ground and the eyebrow
      is where the sell band spends it, so the date takes --accent and
      the rest of the line takes --ink-3 — the same shape as SELL / TRADE.

   2. THE RED BARS. A rule under the headline and two more under the
      labels: three marks doing what nothing in the sell band does. The
      figures have scale and the eyebrow has colour; a bar as well was a
      third way of saying the same thing.

   3. THE LABEL BELOW THE FIGURE. It sits above now, which is where every
      other label in this system sits — the sell steps, the inventory
      records, the finder rows all name the field before they answer it.
   ============================================================ */
.story__est { color: var(--accent); }
.story__eyebrow span[aria-hidden] { margin-inline: 0.4em; opacity: 0.5; }
.stat:not(.stat--lead) .stat__label::after { content: none; }

.stat__label {
  display: block;
  margin-block-start: 0;
  margin-block-end: var(--sp-4);
  color: var(--ink-3);
  letter-spacing: var(--tr-label);
}
.stat--lead .stat__label {
  font-size: var(--t-micro);
  letter-spacing: var(--tr-label);
  margin-block-end: var(--sp-3);
}

/* The lead is still the lead — by SCALE, which is the one mechanism this
   system uses for rank and the only one it needs here. */
.story__stats { align-items: flex-start; }

/* ---- The figures arrive as a staircase ----
   The same shape as the sell copy: each beat opens once the one before it
   is most of the way home, and each travels its own distance. Wide enough
   apart to be counted, which is what the sell band had to be corrected
   to and what this had not been given yet. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .stat:nth-child(1) { --rise: 26px; animation-range: entry 10% entry 46%, exit 14% exit 76%; }
    .stat:nth-child(2) { --rise: 20px; animation-range: entry 26% entry 62%, exit 14% exit 76%; }
    .stat:nth-child(3) { --rise: 20px; animation-range: entry 40% entry 76%, exit 14% exit 76%; }
  }
}

/* ============================================================
   NARROW: THE LEFT SIDE GOES, THE CARDS CARRY THE IMAGERY
   ============================================================
   The two-mass composition needs a wide index, and the index needs it
   because a panel now holds a name, two figure plates, a 160px vehicle
   and an arrow on one line. Below roughly 1200px the column starves: the
   model wraps to three lines, the plates stack, one panel takes the other
   three with it and the stage stretches to match — which is how a section
   that measured perfectly at 1783 came out as a tall empty field beside a
   column of broken cards.

   The dominant vehicle is dropped rather than shrunk. A cut-out car at a
   third of its size stops being a dominant mass and becomes a second,
   smaller thumbnail beside four real ones, and then the section has no
   subject at all. Removing it re-establishes the governing idea honestly:
   at this width the index IS the section, so the vehicles move into the
   panels and get the size to carry it.

   This supersedes the 1080px rule above, which stacked the stage over the
   index. That was written for a hairline list with 58px thumbnails and
   does not survive the panels. */
@media (max-width: 1200px) {
  .showcase {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
  }
  .showcase__stage { display: none; }
  .recs { flex: none; min-height: 0; }
  .rec { grid-template-columns: minmax(0, 1fr) auto auto; }
  /* The photograph the section lost on the left comes back here. */
  .rec__thumb { width: clamp(150px, 17vw, 230px); }
  .rec__name { min-height: 0; }
}

/* Below the panel's own comfortable measure the vehicle steps down again
   rather than crushing the name beside it. */
@media (max-width: 720px) {
  .rec { padding: var(--sp-5); column-gap: var(--sp-5); }
  .rec__thumb { width: clamp(96px, 22vw, 150px); }
  .rec__chev { display: none; }
}

/* ---- The outlined button on a photograph, for the third time ----

   .btn--line draws its ring in --rule-strong, which is 0.28 alpha. On the
   flat page ground that is a considered hairline; on a photograph it is
   nothing, and the button reads as a washed-out ghost of itself. The sell
   band needed the same correction and so did the index CTA — three
   sections in, this is a gap in the variant rather than three local
   accidents, and it belongs in the DS the next time that file is opened.

   Fixed the same way both times before: a ring the ground cannot swallow,
   and a hover that closes to full ink. */

/* The copy's ellipse is stretched to cover the action as well. It was
   sized for the text block and the button hangs below it, so at narrower
   widths the CTA ended up standing on whatever the crop put there. */
.story__bg::after {
  background:
    radial-gradient(82% 96% at 4% 34%,
      rgb(var(--bg-rgb) / 0.92) 0%,
      rgb(var(--bg-rgb) / 0.8) 36%,
      rgb(var(--bg-rgb) / 0.44) 64%,
      rgb(var(--bg-rgb) / 0.12) 85%,
      transparent 100%),
    radial-gradient(58% 46% at 6% 100%,
      rgb(var(--bg-rgb) / 0.9) 0%,
      rgb(var(--bg-rgb) / 0.66) 38%,
      rgb(var(--bg-rgb) / 0.24) 70%,
      transparent 100%),
    radial-gradient(30% 34% at 62% 100%,
      rgb(var(--bg-rgb) / 0.62) 0%,
      rgb(var(--bg-rgb) / 0.34) 46%,
      transparent 82%),
    linear-gradient(to top,
      rgb(var(--bg-rgb)) 0%,
      rgb(var(--bg-rgb) / 0.55) 2%,
      transparent 5%);
}

/* ---- The story copy block, measured against the sell block ----

   Two faults, both from removing things that were holding the block up.

   THE LEDE had no top margin at all. The red rule I deleted carried
   margin-block: var(--sp-6) on both sides, so taking it out took the
   interval with it and the paragraph closed up under the headline. Sell
   sets var(--sp-5) between title and lede; so does this now.

   THE BUTTON was 38px tall against sell's 54, with padding-top 0 and
   padding-bottom 16 — a leftover from when the CTA was a grid row and
   needed its top interval suppressed. It is a flex child in the copy
   block now, so it takes the .btn geometry whole, and the tracking comes
   up to the 0.18em the sell secondary uses. Same variant, same section
   role, same measurements. */
.story__lede { margin-block-start: var(--sp-5); }

/* Dimmed. At 0.85 the core was the brightest thing in the section — it
   competed with the vehicle it was supposed to be marking, and on the
   pale cut-outs it washed into the car's own reflection. The geometry is
   unchanged; only the intensity comes down, so it still reads as the
   panel's edge being lit rather than as a light of its own. */
.rec::before {
  background: radial-gradient(52% 100% at 50% 100%,
    rgba(168, 202, 255, 0.44) 0%,
    rgba(104, 156, 236, 0.19) 34%,
    rgba(70, 116, 200, 0.07) 58%,
    transparent 78%);
}

/* ============================================================
   THE BAND IS CUT AT BOTH ENDS NOW
   ============================================================
   The same gesture, repeated at the foot, and in the SAME DIRECTION —
   high on the left, low on the right, on both edges. That makes the band
   a sheared slab whose two cuts are parallel, rather than a shape that
   tapers. A wedge would have said the section is running out; a shear
   says it is passing through, which is the automotive read the boundary
   was asked for in the first place.

   Amplitude up from clamp(16,1.6vw,26) to clamp(26,2.8vw,48) — about
   1.8x. It was halved once for reading as an agency wave; what made it a
   wave then was depth over a long lazy run with nothing answering it at
   the other end. With a matching cut below, the slope is legible as
   geometry and can afford to be seen. */
.sell {
  --cut-drop: clamp(26px, 2.8vw, 48px);
  clip-path: polygon(
    0 0,
    var(--cut-start) 0,
    calc(var(--cut-start) + var(--cut-run)) var(--cut-drop),
    100% var(--cut-drop),
    100% 100%,
    calc(var(--cut-start) + var(--cut-run)) 100%,
    var(--cut-start) calc(100% - var(--cut-drop)),
    0 calc(100% - var(--cut-drop))
  );
}

@supports (clip-path: shape(from 0 0, line to 1px 1px, close)) {
  .sell {
    clip-path: shape(
      from 0 0,
      line to var(--cut-start) 0,
      curve to calc(var(--cut-start) + var(--cut-run)) var(--cut-drop)
        with calc(var(--cut-start) + var(--cut-run) * 0.5) 0
           / calc(var(--cut-start) + var(--cut-run) * 0.5) var(--cut-drop),
      line to 100% var(--cut-drop),
      line to 100% 100%,
      line to calc(var(--cut-start) + var(--cut-run)) 100%,
      curve to var(--cut-start) calc(100% - var(--cut-drop))
        with calc(var(--cut-start) + var(--cut-run) * 0.5) 100%
           / calc(var(--cut-start) + var(--cut-run) * 0.5) calc(100% - var(--cut-drop)),
      line to 0 calc(100% - var(--cut-drop)),
      close
    );
  }
}

/* The foot cut takes up to 48px off the LEFT of the bottom edge, which is
   exactly where "01 Tell us about your car" sits. The band gets that back
   as padding so the sequence clears its own boundary instead of being
   trimmed by it. */
.sell__inner,
.sell__grid { padding-block-end: var(--cut-drop); }

@media (max-width: 700px) {
  .sell { clip-path: none; }
  .sell__inner, .sell__grid { padding-block-end: 0; }
}

/* ============================================================
   STORY — final polish, then frozen
   ============================================================ */

/* 2 — 2003 AND $2B+ ARE FACTS, NOT ODOMETERS.
   Their counters are gone from the markup. They keep the reveal the row
   already had — the fade and short rise every .stat carries — and the
   existing entry ranges put them a beat behind the figure that does
   count. No new motion was added to replace what was removed. */

/* 5 — THE PAIR IS ONE PROOF GROUP.
   They were two items of whatever width their content happened to be,
   which is what made them read as floating rather than placed. Equal
   columns line up both the labels and the figures, so the group has an
   internal grid of its own and answers the lead as a single object.
   Alignment only — no rule, no box, no background. */
.stat:not(.stat--lead) {
  flex: 0 0 auto;
  min-inline-size: 11.5rem;
}
.story__stats { column-gap: clamp(var(--sp-6), 2.4vw, var(--sp-7)); }
.stat--lead { margin-inline-end: clamp(var(--sp-8), 7vw, 8.5rem); }

/* 3 — THE PARAGRAPH CARRIES A LITTLE MORE.
   Still secondary to the headline and still --ink-2, but at full-page
   scale it was reading as a caption under a display line rather than as
   the section's one piece of prose. One step of size and a slightly
   tighter measure, which is what actually makes a paragraph feel present:
   the line length, not the weight. */
.story__lede {
  font-size: 1.125rem;
  line-height: 1.65;
  max-width: 36ch;
}

/* 4 — THE ACTION IS TEXT, NOT A CONTAINER.
   The pill is gone. What is left is the label, the arrow, and the space
   between them — the arrow moves on hover and nothing else does. No
   underline: a rule here would be the decorative line this pass rules
   out, and the arrow already says the direction. */
.story__cta,
.fin__tert {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-4);
  padding: 0;
  border: 0;
  background: none;
  box-shadow: none;
  font-family: var(--font-label);
  font-size: var(--t-micro);
  font-weight: 500;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--ink);
  transition: gap var(--dur-2) var(--ease-out);
}
.story__cta-arrow,
.fin__tert .story__cta-arrow { flex: none; transition: translate var(--dur-2) var(--ease-out); }
.story__cta:hover,
.story__cta:focus-visible,
.fin__tert:hover,
.fin__tert:focus-visible { background: none; box-shadow: none; }
.story__cta:hover .story__cta-arrow,
.story__cta:focus-visible .story__cta-arrow,
.fin__tert:hover .story__cta-arrow,
.fin__tert:focus-visible .story__cta-arrow { translate: 5px 0; }
.story__cta:focus-visible { outline: 2px solid var(--ink); outline-offset: 6px; }
.fin__tert:focus-visible { outline: 2px solid var(--ink); outline-offset: 6px; }

/* ============================================================
   STORY PROOF — the reveal, and nothing else
   ============================================================
   The three figures come off the scroll timeline. They were fading in
   and out with the rest of the copy, which meant the two secondary facts
   were already sitting in their final positions long before the counter
   had anything to say — the exact complaint. They are driven by the
   counter now.

   .is-armed is added by script, and only after it has checked that
   motion is allowed. Nothing here hides anything without it: script off,
   script broken, or reduced motion, and the row is three finished
   figures with no travel and no count.

   DIALECT YIELD — motion-taste D1 caps travel at 8px and prefers a
   crossfade to a slide. This slides 34px, horizontally. The condition it
   yields on is the stated one: direction encoding progress. The whole
   point of the sequence is that these two arrive AFTER the claim lands,
   and a horizontal entrance from outside the group is what makes "after"
   legible — an 8px fade in place would read as three things appearing at
   slightly different times, which is what this replaces. The floor still
   binds: opacity and transform only, no layout touched, reduced motion
   complete. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .stat { animation: none; }
  }
}

.story__stats.is-armed .stat--lead { opacity: 0; translate: 0 8px; }
.story__stats.is-armed.is-live .stat--lead { opacity: 1; translate: 0 0; }
.stat--lead {
  transition: opacity var(--dur-3) var(--ease-out),
              translate var(--dur-3) var(--ease-out);
}

.story__stats.is-armed .stat--proof { opacity: 0; translate: 34px 0; }
.story__stats.is-armed .stat--proof.is-in { opacity: 1; translate: 0 0; }
.stat--proof {
  transition: opacity var(--dur-3) var(--ease-out),
              translate var(--dur-3) var(--ease-out);
}

/* MOBILE IS AUTHORED, NOT INHERITED. In one column the pair sits under
   the claim rather than beside it, so "in from the right" points at
   nothing — the group it would be entering is not to the right of
   anything. The sequence and its timing are kept; the travel becomes a
   short rise in the reading direction, which is what the dialect asks
   for by default anyway. */
@media (max-width: 900px) {
  .story__stats.is-armed .stat--proof { translate: 0 12px; }
}

/* ============================================================
   SELL / TRADE OVERLAPS STORY — a real overlap, not a gradient
   ============================================================
   The two chapters were flush at the join, which sounds like continuity
   and reads as a break: the bone plate ended, and what appeared below it
   was the dark top of the next section behaving like page background. The
   shaped bottom cut terminated Sell instead of revealing anything.

   Story is pulled up under Sell now and Sell paints over it. The cut is
   a window: what shows through it is the Story photograph, already in
   place, already lit — the dark chapter arriving underneath the light
   panel rather than starting after it.

   Nothing here is a colour transition, a spacer or a strip to hide the
   seam. It is z-order and a negative margin, and the thing revealed is
   the actual next section.

   The overlap is added back to the Story's own top padding, so the
   composition inside it does not move by a pixel — only the section's
   box starts higher. Header is z-70 and untouched. */
.story {
  --story-overlap: clamp(72px, 7vw, 120px);
  margin-block-start: calc(-1 * var(--story-overlap));
  z-index: 0;
}
.sell { z-index: 1; }
.story__inner {
  padding-block-start: calc(clamp(var(--sp-9), 8vw, var(--sp-11)) + var(--story-overlap));
}

/* The frame is taller than the band at both ends so the handoff below can
   move it without ever exposing an edge. */
.story__bg { inset: -56px 0; }

/* THE HANDOFF, AND ONLY THAT.
   34px of rise as the section enters, on transform, finishing before the
   content does. It is the panel-under-panel idea given a moment of
   movement — not parallax, which would keep moving for the whole scroll
   and turn the photograph into a background effect. */
@keyframes story-lift {
  from { translate: 0 34px; }
  to   { translate: 0 0; }
}
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .story__bg {
      animation: story-lift linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 64%;
    }
  }
}

/* ---- The cut is mirrored: the shelf moves to the RIGHT ----
   Same shear, same amplitude, same parallel edges — reflected. The high
   side is now the right, the fall descends toward the left, and the
   corner radius follows the shelf to the corner that is actually raised.

   Two derived stops so the mirror stays tied to the originals rather
   than becoming a second set of numbers to keep in sync:
     --cut-a  where the fall begins, measured from the left
     --cut-b  where the shelf begins */
.sell {
  --cut-a: calc(100% - var(--cut-start) - var(--cut-run));
  --cut-b: calc(100% - var(--cut-start));
  border-start-start-radius: 0;
  border-start-end-radius: clamp(20px, 2vw, 30px);
  clip-path: polygon(
    0 var(--cut-drop),
    var(--cut-a) var(--cut-drop),
    var(--cut-b) 0,
    100% 0,
    100% calc(100% - var(--cut-drop)),
    var(--cut-b) calc(100% - var(--cut-drop)),
    var(--cut-a) 100%,
    0 100%
  );
}

@supports (clip-path: shape(from 0 0, line to 1px 1px, close)) {
  .sell {
    clip-path: shape(
      from 0 var(--cut-drop),
      line to var(--cut-a) var(--cut-drop),
      curve to var(--cut-b) 0
        with calc(var(--cut-a) + var(--cut-run) * 0.5) var(--cut-drop)
           / calc(var(--cut-a) + var(--cut-run) * 0.5) 0,
      line to 100% 0,
      line to 100% calc(100% - var(--cut-drop)),
      line to var(--cut-b) calc(100% - var(--cut-drop)),
      curve to var(--cut-a) 100%
        with calc(var(--cut-a) + var(--cut-run) * 0.5) calc(100% - var(--cut-drop))
           / calc(var(--cut-a) + var(--cut-run) * 0.5) 100%,
      line to 0 100%,
      close
    );
  }
}

/* ---- The car is centred on the field's height ----

   It was standing on a ground line 28% up from the floor, which was right
   when the stage was a 16:10 box. The stage stretches to the index now
   and the index has grown with the panels, so the same percentage left
   the car sitting low in a very tall field with dead space above it.

   Centred on the CAR, not on the file. Each cut-out carries a different
   strip of empty pixels above and below its bodywork — sampled off the
   alpha channel, the top strips run 7.65 / 5.73 / 10.21 / 6.54 percent
   against bottoms of 1.46 / 2.08 / 1.38 / 2.07 — so centring the image
   box would leave every car a little low by its own asymmetry, the
   Koenigsegg worst at 4.4%. --centre-fix is half that difference, lifting
   each one until the vehicle itself is on the centre line. */
.stage__car {
  inset-block-start: 50%;
  inset-block-end: auto;
  translate: 0 calc(-50% - var(--centre-fix, 0%));
}
.stage__car[src*="gt2rs"]     { --centre-fix: 3.09%; }
.stage__car[src*="aventador"] { --centre-fix: 1.83%; }
.stage__car[src*="regera"]    { --centre-fix: 4.42%; }
.stage__car[src*="812-gts"]   { --centre-fix: 2.24%; }

/* ============================================================
   THE BREAK — a held shot between two dense chapters
   ============================================================
   A band, not a screen. The hero is the full viewport and gets to be;
   this is shorter on purpose, because a second full-height film would
   read as a second opening rather than as a pause inside one page. */
.brk {
  position: relative;
  background: var(--bg);
  /* Height is driven by the viewport WIDTH, not its height. The frame
     is 16:9 and a vh-based band would crop it differently on every
     laptop; a vw-based one keeps the same slice of the room on all of
     them, which is what makes it a composition rather than a window. */
  /* PINNED, and the sell band rides up over it. Full height now, which
     the earlier note argued against — that reasoning held while this was
     a band you scrolled past. Pinned, a short one leaves a strip of the
     next section showing beneath it for the whole hold, and the overlay
     stops reading as an overlay. svh rather than vh so a mobile URL bar
     retracting does not resize the pin mid-scroll. */
  position: sticky;
  inset-block-start: 0;
  min-block-size: 100svh;
  overflow: hidden;
}

/* The wrapper bounds the pin. Nothing else: no positioning, no stacking
   context — a stacking context here would trap .sell's z-index and the
   overlay would stop working. */
.stick { position: relative; }

/* The band that does the covering. It is later in flow AND positioned,
   so it paints over the pinned film without needing a z-index war; the
   z-index is stated anyway because "later in the DOM" is a fact someone
   will reorder one day. */
.sell { z-index: 1; }

.brk__frame {
  position: absolute;
  inset: 0;
}

.brk__video {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  /* The lower third of this frame is empty floor. Pulling the crop up
     keeps the cars and the mural in the band and spends the loss on
     polished concrete, which is the part with nothing in it. */
  object-position: center 38%;
  display: block;
}

/* TWO SCRIMS FROM THE PAGE'S OWN GROUND, one element.
   --bg-rgb rather than a black — the band has to dissolve into the
   sections above and below it, and those are #15100C, not black. The
   last stop is `rgb(... / 0)` and not `transparent`, because
   `transparent` interpolates through transparent BLACK and leaves a
   grey bruise across the middle of the fade. That bug is already in
   this file's history once. */
.brk__scrim {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    /* The edges — dissolving the band into the sections it sits between. */
    linear-gradient(to bottom,
      rgb(var(--bg-rgb) / 0.92) 0%,
      rgb(var(--bg-rgb) / 0.55) 8%,
      rgb(var(--bg-rgb) / 0.12) 20%,
      rgb(var(--bg-rgb) / 0) 34%),
    linear-gradient(to top,
      rgb(var(--bg-rgb) / 0.92) 0%,
      rgb(var(--bg-rgb) / 0.55) 8%,
      rgb(var(--bg-rgb) / 0.12) 20%,
      rgb(var(--bg-rgb) / 0) 34%),
    /* AND A FLAT WASH ACROSS THE WHOLE BAND, which is what makes copy
       possible on it at all. The alpha is not a mood setting: --ink has
       0.89 luminance, and for a 17px line to clear 4.5:1 the ground
       under it must sit at or below 0.154. This film reaches near-white
       on the polished floor, and --bg at this alpha pulls that worst
       case down far enough with margin left over. Lower it and the
       lede fails on exactly the frames nobody screenshots. */
    linear-gradient(rgb(var(--bg-rgb) / 0.72), rgb(var(--bg-rgb) / 0.72));
}

/* ---- The title card ----------------------------------------
   Centred, and centred on purpose: STORY sets its statement in a left
   column with a data row beside it, and this band has to be a
   different KIND of object or the two cancel out. A title card is
   short, symmetrical and carries no figures. */
.brk__copy {
  position: relative;
  z-index: 1;
  block-size: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding-inline: var(--sp-6);
  padding-block: clamp(var(--sp-8), 8vw, var(--sp-10));
}

.brk__eyebrow {
  color: var(--ink-2);
  letter-spacing: var(--tr-label);
  margin-block-end: var(--gap-eyebrow);
}

.brk__title {
  font-size: clamp(2rem, 4.4vw, 3.75rem);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: var(--tr-display);
  color: var(--ink);
  max-inline-size: 18ch;
}
.brk__title .ttl-line { display: block; }

.brk__lede {
  margin-block-start: var(--sp-5);
  /* Wider than the headline on purpose — it is one sentence of fact
     under a line of voice, and breaking it into three short lines
     would give it an emphasis it should not have. */
  max-inline-size: 46ch;

  /* FULL --ink, NOT --ink-2, and the reason generalises beyond this
     band. At 0.62 the lede measured 3.79:1 against the brightest
     ground it lands on, rgb(79,83,91). Darkening the wash barely moved
     it: +0.18 alpha only reached 4.35, still short. A translucent ink
     INHERITS its ground — darken behind it and the ink darkens with
     it, so the contrast it gains at the back it gives away at the
     front. Against a ground that varies, a secondary ink is
     self-defeating; full strength reads 6.91:1 on the same pixels.

     Hierarchy survives it. 16px/400 under 60px/700 is already two
     steps apart, and this is the one line on the card carrying facts
     rather than voice — the tone was never what separated them. */
  color: var(--ink);
}

.brk__cta { margin-block-start: clamp(var(--sp-6), 3vw, var(--sp-7)); }
.brk__cta .btn { align-self: center; }

/* THE HEADLINE ARRIVES FROM THE LEFT LIKE EVERY OTHER ONE, but the
   copy is centred, so the shared -56px would have pulled each line
   out of the composition and let it drift back. Halved, and stated:
   the entry language is the page's, the distance is this band's. */
.reveal-armed [data-reveal] .brk__title .ttl-line { translate: -28px 0; }



/* ---- The card leaves as the next band covers it -------------
   The same gesture as the hero, and literally the same keyframes:
   hero-exit is `translate: 0 var(--rise); filter: blur(var(--blur));
   opacity: 0`, and it reads its distance and its blur from custom
   properties. Redefining it here under another name would be two
   places to change one motion.

   DRIVEN BY THE COVERING SECTION, NOT BY THIS ONE. .brk is pinned, so
   its own view() progress stops advancing the moment it sticks — the
   element is not moving through the viewport any more, which is the
   whole point of a pin. The thing that IS moving is .sell, so it owns
   the timeline and .brk__copy borrows it through timeline-scope.

   Different distances over overlapping ranges is what makes the block
   stretch open as it goes rather than slide as a slab: the eyebrow has
   the least to say and the furthest to travel, the button the least.
   Blur is tied to speed, not applied evenly — the faster a thing moves
   the more it smears, which is the only reading of blur that is about
   motion rather than about frosting. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    .stick { timeline-scope: --sell-in; }
    .sell  { view-timeline: --sell-in block; }

    .brk__eyebrow,
    .brk__title .ttl-line,
    .brk__lede,
    .brk__cta {
      animation: hero-exit linear both;
      animation-timeline: --sell-in;
      animation-range: var(--lift-range);
      will-change: translate, filter, opacity;
    }

    /* THE RANGES ARE SHORT BECAUSE OF vault/morningstar-ventures.
       Its recorded weakness is this exact gesture done at length: "the
       page can stop on long transitional frames where the outgoing text
       is heavily blurred and the incoming content is still faint or
       invisible — the user controls the scroll, but not every stoppable
       frame works as a complete composition."

       Measured on the first version, which ran to entry 72%: walking the
       handoff at 60px steps, 38% of the stopping points had the copy
       neither present nor gone — half-lit, half-blurred, with the
       covering band still 500-800px below. Six broken frames out of
       sixteen. The scroll belongs to the reader, so every one of those
       is a composition someone can be left looking at.

       Finishing by entry 34% puts the whole lift inside about 290px of
       scroll and makes the settled states dominate: film with copy, or
       film without it. The blur is the same language as the hero either
       way; it is the DURATION that was the defect. */
    .brk__eyebrow            { --rise: -260px; --blur: 15px; --lift-range: entry  0% entry 20%; }
    .brk__title .ttl-line    { --rise: -210px; --blur: 12px; --lift-range: entry  3% entry 25%; }
    .brk__title .ttl-line + .ttl-line
                             { --rise: -180px; --blur: 10px; --lift-range: entry  6% entry 28%; }
    .brk__lede               { --rise: -140px; --blur:  8px; --lift-range: entry  9% entry 31%; }
    .brk__cta                { --rise: -110px; --blur:  6px; --lift-range: entry 12% entry 34%; }
  }
}

/* ---- Reduced motion ----------------------------------------
   The film is never fetched and never runs; the poster IS the band.
   The control stays, because a still frame with a play button is a
   complete, honest offer — the visitor can start it if they want it.
   Nothing here depends on the transition having run. */
@media (prefers-reduced-motion: reduce) {
}

@media (max-width: 760px) {
}

/* ============================================================
   6 — WARRANTY / PROTECTION — the second inverted band
   ============================================================
   The page's grounds now run dark, dark, bone, dark, bone. The two
   bone bands are deliberately the same device rather than two
   different ones: an inverted ground is a statement about the page,
   and making the second one look invented separately would turn a
   system into a collection. Same cut, same colour, same ink. */
.warranty {
  position: relative;
  background: var(--bone);
  color: var(--bone-ink);
  border-start-start-radius: clamp(40px, 5vw, 72px);
  overflow: hidden;

  /* The cut mirrors SELL's rather than repeating it: the shelf sits on
     the RIGHT here, because this band's copy is on the left and the
     shelf belongs over the copy. Same mechanism, read the other way. */
  --cut-start: 34%;
  --cut-run: 16%;
  --cut-drop: clamp(24px, 2.6vw, 44px);
  /* Straight bevel as the floor — every engine has polygon(). */
  clip-path: polygon(
    0 var(--cut-drop),
    var(--cut-start) var(--cut-drop),
    calc(var(--cut-start) + var(--cut-run)) 0,
    100% 0,
    100% 100%,
    0 100%
  );
}

/* AND THE SOFT VERSION WHERE IT CAN BE HAD.
   The chamfer was the thing Alex called harsh, and he is right: sell
   has had the S for a while and this band was still showing the corner
   the polygon fallback knocks off. Same grammar, mirrored — this fall
   RISES left to right because the shelf is on the left here.

   Both control points sit at the midpoint of the run, one level with
   the shelf and one level with the upper edge, which is what makes the
   two ends leave and arrive flat with all the movement in the middle.
   A curve with the handles anywhere else shows a corner at one end.

   @supports tests shape() with a real shape rather than the bare
   keyword, because the property parses long before the function does. */
@supports (clip-path: shape(from 0 0, line to 1px 1px, close)) {
  .warranty {
    clip-path: shape(
      from 0 var(--cut-drop),
      line to var(--cut-start) var(--cut-drop),
      curve to calc(var(--cut-start) + var(--cut-run)) 0
        with calc(var(--cut-start) + var(--cut-run) * 0.5) var(--cut-drop)
           / calc(var(--cut-start) + var(--cut-run) * 0.5) 0,
      line to 100% 0,
      line to 100% 100%,
      line to 0 100%,
      close
    );
  }
}

/* .warranty__inner carries no vertical padding: the band sets its own
   height and its own air, and the two were stacking — that, not the
   min-height, was most of the empty concrete under the actions. */

/* .warranty__grid retired: the photograph became the ground, so there is
   no two-column split left to size or to bleed. */

/* ---- Left: the claim ---------------------------------------- */
.warranty__copy {
  display: flex;
  flex-direction: column;
  align-items: start;
}

/* --accent-deep, not --accent. The rule for this project is now simple
   and worth keeping simple: on the bone ground red steps down one
   value. It was arrived at on the sell band's photograph, where
   #BC0F0D measured 3.50:1, and holding to it here means the two
   inverted bands speak the same red rather than two nearby reds. */
.warranty__eyebrow {
  color: var(--accent-deep);
  letter-spacing: var(--tr-label);
  margin-block-end: var(--sp-6);
}
.warranty__eyebrow span { margin-inline: 0.45em; opacity: 0.5; }

.warranty__title {
  font-size: clamp(2.25rem, 3.9vw, 3.5rem);
  font-weight: 700;
  line-height: 1.04;
  letter-spacing: var(--tr-display);
  color: var(--bone-ink);
}
.warranty__title .ttl-line { display: block; }

.warranty__lede {
  margin-block-start: var(--sp-5);
  max-width: 34ch;
  color: var(--bone-ink-2);
}

/* A ROW AGAIN. This was a column, and the reason was honest at the
   time: the pair measured 276 + 308 + 16 = 600px against a column that
   was 490px at every width, so they could not sit together and a wrap
   left to chance is worse than a stack declared. Removing the schematic
   changed the arithmetic rather than the taste — the copy column is
   587px now, and a decision made against a constraint should not
   outlive the constraint. flex-wrap stays as the safety net. */
.warranty__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
  margin-block-start: var(--sp-7);
  /* THE ROW MUST SPAN THE COLUMN, or the basis below is measured
     against the wrong number. .warranty__copy is a flex column with
     align-items: start, which shrink-wraps its children — so this row
     was 569px inside a 619px column, and the pair wrapped for want of
     23px that were sitting right there unused. */
  align-self: stretch;
}

/* EQUAL WIDTHS, AND THE BASIS IS THE BREAKPOINT.
   At their natural widths the pair is 255 + 287 + 16 = 558px, and the
   column is 619px at 1440 but only 506px at 1180 — so "side by side"
   would have been true on wide screens and quietly false on a 1280
   laptop, which is most of them. A media query guessing at that
   crossover would be a second place for the answer to live.

   flex-basis lets the row itself decide: two of them plus the gap need
   twice the basis, so above that they sit together and grow to equal
   halves, and below it they wrap and each takes the full column. Both
   outcomes are compositions rather than accidents.

   THE BASIS IS NOT WHAT DECIDES IT, and that is worth knowing before
   anyone tunes this number. Flex items default to min-width: auto, so
   they cannot shrink below their own content: the row forms only when
   the column can hold both buttons at their NATURAL widths, measured
   here at 260 + 236 + a 16px gap = 512px. The basis only matters when
   it is larger than that.

   At "Talk to our specialist" the natural pair came to 558 and the
   crossover landed above 1280 and 1360 — the two most ordinary laptop
   widths — so both stacked. "Ask a specialist" is shorter and reads
   more directly for it, and the row now forms from 512px of column,
   which is every viewport from about 1250 up. Between the 1080
   breakpoint and there the pair stacks at equal full width, which is a
   composition rather than a failure. */
.warranty__actions .btn { flex: 1 1 15rem; }
.warranty__actions .btn { align-self: start; }



/* ---- Right: the panel --------------------------------------- */
.warranty__panel {
  /* Raised by tone, not by a border or a shadow. A 1px outline here
     would draw a card and the three provisions would read as three
     objects inside a frame instead of one surface holding a list. */
  background: rgba(26, 20, 14, 0.045);
  border-radius: clamp(18px, 1.8vw, 26px);
  padding: clamp(var(--sp-6), 3vw, var(--sp-8));
}

.warranty__panel-label {
  display: flex;
  align-items: center;
  gap: var(--sp-4);
  color: var(--bone-ink-2);
  letter-spacing: var(--tr-label);
  margin-block-end: clamp(var(--sp-6), 3vw, var(--sp-7));
}
/* The rule runs to the edge of the label's line and stops — a measure
   mark, the same grammar as the technical captions elsewhere. */
.warranty__panel-rule {
  flex: 1;
  height: 1px;
  background: rgba(26, 20, 14, 0.22);
}

.warranty__points {
  list-style: none;
  margin: 0;
  padding: 0;
}

.warranty__point {
  display: grid;
  /* Two columns now, not three. The diagrams on the right are gone at
     Alex's direction, and with them the reason the row was three wide. */
  grid-template-columns: auto 1fr;
  align-items: start;
  gap: clamp(var(--sp-4), 2vw, var(--sp-6));
  padding-block: clamp(var(--sp-5), 2.4vw, var(--sp-6));
}
/* Rules BETWEEN, not around — the same reason the sell steps have
   them: a border on every item boxes each provision. */
.warranty__point + .warranty__point {
  border-block-start: 1px solid rgba(26, 20, 14, 0.16);
}
.warranty__point:first-child { padding-block-start: 0; }
.warranty__point:last-child { padding-block-end: 0; }

/* MATERIAL SYMBOLS, OUTLINED — the real paths, fetched from Google
   rather than approximated by hand. A drawn-from-memory wrench is the
   thing that makes an interface look almost right.

   They replace 01/02/03. The numerals were doing two jobs badly: they
   implied a SEQUENCE, and these three provisions are not steps — they
   are three facts about one contract, true at the same time. The sell
   band's 01/02/03 are genuinely ordered, so they stay numerals there;
   the difference between the two sections is now meaningful instead of
   decorative.

   build / tune / verified: what the warranty pays for, choosing among
   plans, and the assurance itself. Deliberately NOT a plain shield —
   the assurance strip at the foot of this section already carries one,
   and the same glyph twice in one section reads as a system with one
   idea rather than two elements agreeing.

   --bone-ink at 0.78 rather than --bone-ink-2. A filled glyph carries
   more visual weight per unit of area than a letterform, so it needs
   less ink to hold the same rank; and unlike --bone-ink-2 this stays a
   solid colour, which is the lesson the break band's lede taught —
   translucency inherits its ground. */
.warranty__ico {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  color: var(--bone-ink);
  opacity: 0.78;
}
.warranty__ico svg { display: block; }

.warranty__h {
  font-size: 1.0625rem;
  font-weight: 600;
  line-height: 1.25;
  color: var(--bone-ink);
  margin: 0;
}

.warranty__p {
  margin-block-start: var(--sp-3);
  /* 32ch was the measure while a diagram held the third column. With
     that column gone the same cap left ~260px of dead panel to the
     right of every line, which reads as a layout that lost something
     rather than one that never had it. 52ch fills the surface and is
     still inside a comfortable measure at this size. */
  max-inline-size: 52ch;
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--bone-ink-2);
}


/* ---- The rule of three -------------------------------------- */
.warranty__assurances {
  list-style: none;
  margin: clamp(var(--sp-8), 6vw, var(--sp-9)) 0 0;
  padding-block-start: clamp(var(--sp-6), 3vw, var(--sp-7));
  border-block-start: 1px solid rgba(26, 20, 14, 0.18);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(var(--sp-5), 3vw, var(--sp-7));
}
.warranty__assurances > li {
  display: flex;
  align-items: start;
  gap: var(--sp-4);
}
.warranty__assurances > li + li {
  padding-inline-start: clamp(var(--sp-5), 3vw, var(--sp-7));
  border-inline-start: 1px solid rgba(26, 20, 14, 0.16);
}

.warranty__icon {
  flex: none;
  width: 1.875rem;
  height: 1.875rem;
  color: var(--accent-deep);
}

.warranty__a-label {
  color: var(--bone-ink);
  letter-spacing: var(--tr-label);
}
.warranty__a-note {
  margin-block-start: var(--sp-2);
  font-size: 0.9375rem;
  line-height: 1.5;
  color: var(--bone-ink-2);
}

/* ---- Narrow --------------------------------------------------
   One column, and the panel's diagrams drop out rather than shrink.
   At 6rem they were already the smallest legible version of
   themselves; below that they stop being diagrams and become
   texture, and texture beside a warranty provision is noise. */
@media (max-width: 1080px) {
  /* .warranty__grid is gone — the band is a single overlaid column. */
}

@media (max-width: 760px) {
  .warranty__assurances {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-5);
  }
  .warranty__assurances > li + li {
    padding-inline-start: 0;
    border-inline-start: 0;
    padding-block-start: var(--sp-5);
    border-block-start: 1px solid rgba(26, 20, 14, 0.16);
  }
  .warranty__actions .btn { width: 100%; justify-content: center; }
}

/* ============================================================
   THE TWO CHAPTER TITLES ARRIVE THE SAME WAY
   ============================================================
   Both headlines slide in from the left, a line at a time; both
   paragraphs rise from below. One gesture per role, shared across the
   two sections, so the pair reads as one authored language rather than
   as two sections that each animate.

   The eyebrows are gone with them — Est. 2003 and Sell / Trade — so the
   headline is the first thing in both blocks and the sequence starts on
   the line that matters.

   DIALECT YIELD, the same one already taken on the proof row:
   motion-taste D1 caps travel at 8px and prefers a crossfade. These
   travel 44px horizontally. The condition is the stated one — direction
   encoding progress: the lines are a sequence, and a stagger of 8px
   fades reads as one block flickering rather than as lines arriving in
   order. Floor holds: opacity and transform only, nothing laid out,
   reduced motion complete and static.
   ============================================================ */
@keyframes ttl-in {
  from { opacity: 0; translate: -44px 0; }
  to   { opacity: 1; translate: 0 0; }
}
@keyframes lede-in {
  from { opacity: 0; translate: 0 26px; }
  to   { opacity: 1; translate: 0 0; }
}

.ttl-line { display: block; }


/* ---- The sell cut: back to falling right, and top only ----
   The mirror is undone — the shelf returns to the left and the edge
   descends toward the right — and the foot of the band goes flat. One
   cut, at the head, where it does the work of handing the page from the
   dark chapter into the light one. The bottom no longer needs a gesture:
   the overlap with Story is what closes the band now, and a second cut
   there was competing with it. */
.sell {
  border-start-start-radius: clamp(20px, 2vw, 30px);
  border-start-end-radius: 0;
  clip-path: polygon(
    0 0,
    var(--cut-start) 0,
    calc(var(--cut-start) + var(--cut-run)) var(--cut-drop),
    100% var(--cut-drop),
    100% 100%,
    0 100%
  );
}
@supports (clip-path: shape(from 0 0, line to 1px 1px, close)) {
  .sell {
    clip-path: shape(
      from 0 0,
      line to var(--cut-start) 0,
      curve to calc(var(--cut-start) + var(--cut-run)) var(--cut-drop)
        with calc(var(--cut-start) + var(--cut-run) * 0.5) 0
           / calc(var(--cut-start) + var(--cut-run) * 0.5) var(--cut-drop),
      line to 100% var(--cut-drop),
      line to 100% 100%,
      line to 0 100%,
      close
    );
  }
}
/* The foot padding went in for the cut that is no longer there. */
.sell__inner, .sell__grid { padding-block-end: 0; }

/* ---- Two opposing cuts. Top falls right; bottom rises left. ---- */
.sell {
  --cut-drop-b: clamp(44px, 5vw, 88px);
  --bcut-a: calc(100% - var(--cut-start) - var(--cut-run));
  --bcut-b: calc(100% - var(--cut-start));
  clip-path: polygon(
    0 0,
    var(--cut-start) 0,
    calc(var(--cut-start) + var(--cut-run)) var(--cut-drop),
    100% var(--cut-drop),
    100% calc(100% - var(--cut-drop-b)),
    var(--bcut-b) calc(100% - var(--cut-drop-b)),
    var(--bcut-a) 100%,
    0 100%
  );
}
@supports (clip-path: shape(from 0 0, line to 1px 1px, close)) {
  .sell {
    clip-path: shape(
      from 0 0,
      line to var(--cut-start) 0,
      curve to calc(var(--cut-start) + var(--cut-run)) var(--cut-drop)
        with calc(var(--cut-start) + var(--cut-run) * 0.5) 0
           / calc(var(--cut-start) + var(--cut-run) * 0.5) var(--cut-drop),
      line to 100% var(--cut-drop),
      line to 100% calc(100% - var(--cut-drop-b)),
      line to var(--bcut-b) calc(100% - var(--cut-drop-b)),
      curve to var(--bcut-a) 100%
        with calc(var(--bcut-a) + var(--cut-run) * 0.5) calc(100% - var(--cut-drop-b))
           / calc(var(--bcut-a) + var(--cut-run) * 0.5) 100%,
      line to 0 100%,
      close
    );
  }
}
/* The bottom cut lifts the RIGHT of the foot, which is where step 03 is. */
.sell__inner, .sell__grid { padding-block-end: var(--cut-drop-b); }

/* Deep enough that the bottom cut always opens onto the Story photograph
   rather than onto the page. */
.story { --story-overlap: clamp(96px, 8vw, 140px); }

/* ---- The car is brought onto the band's centre line ----
   Measured on the source: the vehicle occupies 50-80% of the frame's
   height, centred at 65%, so at the band's own proportions it rendered
   at two-thirds down. object-position could not fix it — the frame only
   has 100px of vertical crop to give, and the car needed three times
   that.

   Scaled from the BOTTOM edge instead, which lifts the subject without
   ever opening a gap under it, and about 62% horizontally so the growth
   runs off the right side rather than into the wall the copy stands on. */
.sell__bg img {
  transform-origin: 62% 100%;
  scale: 1.3;
}

/* ---- And the foot carries more, because the steps sit on it ----
   The ramp was built when the band ended flat. It now ends on a cut that
   lifts the right-hand side, which is exactly where step 03 sits, over
   the brightest part of the floor. Deeper and stronger. */
.sell__bg::after {
  height: clamp(260px, 34%, 420px);
  background: linear-gradient(to top,
    var(--bone) 0%,
    var(--bone) 18%,
    rgba(236, 228, 214, 0.94) 38%,
    rgba(236, 228, 214, 0.72) 58%,
    rgba(236, 228, 214, 0.38) 78%,
    rgba(236, 228, 214, 0) 100%);
}

/* ============================================================
   SECTION ENTRY — the homepage's motion language below the hero
   ============================================================
   One system, every section after the hero. Not a per-section effect:
   any new chapter opts in with `data-reveal` and marks its parts, and it
   inherits the same choreography without a line of new CSS.

   The language, and it does not vary by section:

     headline   one LINE at a time, in from the LEFT, 130ms apart
     paragraph  one block, rising from BELOW, after the last line
     action     a quiet vertical settle, last
     eyebrow    a fade with almost no travel — it must not compete

   Direction carries meaning here, which is why it is fixed: horizontal
   is the title arriving, vertical is the body answering it. Swapping
   them section to section would make the page read as several authors.

   SCROLL-TRIGGERED, NOT SCROLL-LINKED. An IntersectionObserver fires
   once per section and the class stays. The earlier `view()` timelines
   are gone with it — they ran the animation off scroll position, which
   meant the text was mid-entrance whenever the reader stopped mid-scroll
   and re-entered every time they came back.

   THE HERO IS EXCLUDED. It has no `data-reveal` and keeps its own
   cinematic behaviour.

   Nothing is hidden until the script says so: `.reveal-armed` goes on
   the root, by script, only after it has checked that motion is allowed.
   Script off, script broken, or reduced motion, and every section is
   simply complete.

   DIALECT YIELD, stated once for the whole system: motion-taste D1 caps
   travel at 8px and prefers a crossfade. Headline lines travel 56px
   horizontally. The condition is the stated one — direction encoding
   progress: the lines are a sequence and the staircase is the gesture.
   The floor holds — transform and opacity only, no geometry animated,
   reduced motion complete.
   ============================================================ */

.reveal-armed [data-reveal] .ttl-line,
.reveal-armed [data-reveal] .lede,
.reveal-armed [data-reveal] .sell__lede,
.reveal-armed [data-reveal] .story__lede,
.reveal-armed [data-reveal] .sell__actions,
.reveal-armed [data-reveal] .story__cta,
.reveal-armed [data-reveal] .story__act,
.reveal-armed [data-reveal] .warranty__actions,
.reveal-armed [data-reveal] .brk__cta,
.reveal-armed [data-reveal] .svc__actions,
.reveal-armed [data-reveal] .fin__tools,
.reveal-armed [data-reveal] .fin__act,
.reveal-armed [data-reveal] .index__all,
.reveal-armed [data-reveal] .inv__eyebrow,
.reveal-armed [data-reveal] .fin__label {
  opacity: 0;
  will-change: opacity, translate;
}

/* headline — the staircase */
.reveal-armed [data-reveal] .ttl-line {
  translate: -56px 0;
  transition: opacity var(--rv-line) var(--ease-out),
              translate var(--rv-line) var(--ease-out);
}
.reveal-armed [data-reveal].is-revealed .ttl-line { opacity: 1; translate: 0 0; }
.reveal-armed [data-reveal].is-revealed .ttl-line:nth-child(2) { transition-delay: var(--rv-step); }
.reveal-armed [data-reveal].is-revealed .ttl-line:nth-child(3) { transition-delay: calc(var(--rv-step) * 2); }
.reveal-armed [data-reveal].is-revealed .ttl-line:nth-child(4) { transition-delay: calc(var(--rv-step) * 3); }

/* paragraph — one block, from below, after the last line */
.reveal-armed [data-reveal] .lede,
.reveal-armed [data-reveal] .sell__lede,
.reveal-armed [data-reveal] .story__lede {
  translate: 0 32px;
  transition: opacity var(--rv-lede) var(--ease-out),
              translate var(--rv-lede) var(--ease-out);
}
.reveal-armed [data-reveal].is-revealed .lede,
.reveal-armed [data-reveal].is-revealed .sell__lede,
.reveal-armed [data-reveal].is-revealed .story__lede {
  opacity: 1;
  translate: 0 0;
  transition-delay: calc(var(--rv-step) * 2.2);
}

/* action — quiet, last */
.reveal-armed [data-reveal] .sell__actions,
.reveal-armed [data-reveal] .story__cta,
.reveal-armed [data-reveal] .story__act,
.reveal-armed [data-reveal] .warranty__actions,
.reveal-armed [data-reveal] .brk__cta,
.reveal-armed [data-reveal] .svc__actions,
.reveal-armed [data-reveal] .fin__tools,
.reveal-armed [data-reveal] .fin__act,
.reveal-armed [data-reveal] .index__all {
  translate: 0 10px;
  transition: opacity var(--rv-cta) var(--ease-out),
              translate var(--rv-cta) var(--ease-out);
}
.reveal-armed [data-reveal].is-revealed .sell__actions,
.reveal-armed [data-reveal].is-revealed .story__cta,
.reveal-armed [data-reveal].is-revealed .story__act,
.reveal-armed [data-reveal].is-revealed .warranty__actions,
.reveal-armed [data-reveal].is-revealed .brk__cta,
.reveal-armed [data-reveal].is-revealed .svc__actions,
.reveal-armed [data-reveal].is-revealed .fin__tools,
.reveal-armed [data-reveal].is-revealed .fin__act,
.reveal-armed [data-reveal].is-revealed .index__all {
  opacity: 1;
  translate: 0 0;
  transition-delay: calc(var(--rv-step) * 3.4);
}

/* eyebrow — present, not competing */
.reveal-armed [data-reveal] .inv__eyebrow,
.reveal-armed [data-reveal] .fin__label {
  translate: 0 6px;
  transition: opacity var(--rv-cta) var(--ease-out),
              translate var(--rv-cta) var(--ease-out);
}
.reveal-armed [data-reveal].is-revealed .inv__eyebrow,
.reveal-armed [data-reveal].is-revealed .fin__label { opacity: 1; translate: 0 0; }

/* The sell steps are that section's one content gesture, and they come
   after its text — the same restrained rise, stepped. */
.reveal-armed [data-reveal] .sell__steps li,
.reveal-armed [data-reveal] .fin__row {
  opacity: 0;
  translate: 0 18px;
  transition: opacity var(--rv-cta) var(--ease-out),
              translate var(--rv-cta) var(--ease-out);
}
.reveal-armed [data-reveal].is-revealed .sell__steps li,
.reveal-armed [data-reveal].is-revealed .fin__row { opacity: 1; translate: 0 0; }
.reveal-armed [data-reveal].is-revealed .sell__steps li:nth-child(1),
.reveal-armed [data-reveal].is-revealed .fin__row:nth-child(1) { transition-delay: calc(var(--rv-step) * 4); }
.reveal-armed [data-reveal].is-revealed .sell__steps li:nth-child(2),
.reveal-armed [data-reveal].is-revealed .fin__row:nth-child(2) { transition-delay: calc(var(--rv-step) * 4.7); }
.reveal-armed [data-reveal].is-revealed .sell__steps li:nth-child(3),
.reveal-armed [data-reveal].is-revealed .fin__row:nth-child(3) { transition-delay: calc(var(--rv-step) * 5.4); }

/* ---- The hidden state, stated once per role ----
   The previous pair — a hidden rule plus a shown rule — was losing the
   hidden half somewhere in a file this long, and a scroll reveal that
   never hides is just content. Inverted: the REVEALED state is the
   element's ordinary self, and the only rule that exists is the one that
   holds it back while the section has not been reached. One rule, one
   direction, and `:not()` puts it out of reach of the cascade below it.
   The transitions sit on the elements unconditionally so both directions
   animate. */
html.reveal-armed [data-reveal]:not(.is-revealed) .ttl-line {
  opacity: 0;
  translate: -56px 0;
}
html.reveal-armed [data-reveal]:not(.is-revealed) .lede,
html.reveal-armed [data-reveal]:not(.is-revealed) .sell__lede,
html.reveal-armed [data-reveal]:not(.is-revealed) .story__lede {
  opacity: 0;
  translate: 0 32px;
}
html.reveal-armed [data-reveal]:not(.is-revealed) .sell__actions,
html.reveal-armed [data-reveal]:not(.is-revealed) .story__cta,
html.reveal-armed [data-reveal]:not(.is-revealed) .story__act,
html.reveal-armed [data-reveal]:not(.is-revealed) .fin__tools,
html.reveal-armed [data-reveal]:not(.is-revealed) .fin__act,
html.reveal-armed [data-reveal]:not(.is-revealed) .ab-cta__acts,
html.reveal-armed [data-reveal]:not(.is-revealed) .index__all {
  opacity: 0;
  translate: 0 10px;
}

/* ---- ABOUT'S PARTS JOIN THE EXISTING ROLES ---------------------------
   Added to these lists rather than given their own block, which is the
   project rule: a new chapter opts in with data-reveal and the existing
   role classes, and does not author a second entrance. Nothing here is a
   new duration, a new distance or a new easing.

   Worth naming while passing through, because it is the reason this edit
   was needed at all: these selectors are bound to INSTANCES
   (.sell__actions, .fin__row) rather than to roles, so a new page
   inherits nothing and the failure is silent — the section simply
   arrives already visible and no check reports anything wrong.
   motion-judgment's invariant on exactly this. Not refactored here: the
   lists are shared with the approved index.html, and rewriting them is
   its own change with its own verification.

   The prose columns and the showroom rows take the same +18px settle the
   page's other lists take (.ig__cell, .rv, .loc), so the two new lists
   read as members of the set rather than as a new idea. The photograph
   takes the paragraph's role — it is the mass the section is about, and
   it arrives the way a block of content arrives. */
html.reveal-armed [data-reveal]:not(.is-revealed) .ab-shot__fig,
html.reveal-armed [data-reveal]:not(.is-revealed) .lo-room__film {
  opacity: 0;
  translate: 0 32px;
}
html.reveal-armed [data-reveal]:not(.is-revealed) .ab-story__col,
html.reveal-armed [data-reveal]:not(.is-revealed) .ab-room,
html.reveal-armed [data-reveal]:not(.is-revealed) .lo-room__body {
  opacity: 0;
  translate: 0 18px;
}
html.reveal-armed [data-reveal]:not(.is-revealed) .inv__eyebrow,
html.reveal-armed [data-reveal]:not(.is-revealed) .ab-eyebrow,
html.reveal-armed [data-reveal]:not(.is-revealed) .fin__label {
  opacity: 0;
  translate: 0 6px;
}
html.reveal-armed [data-reveal]:not(.is-revealed) .sell__steps li,
html.reveal-armed [data-reveal]:not(.is-revealed) .fin__row {
  opacity: 0;
  translate: 0 18px;
}

/* Revealed is simply the element, so these only carry timing. */
html.reveal-armed [data-reveal] .ttl-line {
  transition: opacity var(--rv-line) var(--ease-out),
              translate var(--rv-line) var(--ease-out);
}
html.reveal-armed [data-reveal] .ttl-line:nth-child(2) { transition-delay: var(--rv-step); }
html.reveal-armed [data-reveal] .ttl-line:nth-child(3) { transition-delay: calc(var(--rv-step) * 2); }
html.reveal-armed [data-reveal] .ttl-line:nth-child(4) { transition-delay: calc(var(--rv-step) * 3); }

html.reveal-armed [data-reveal] .lede,
html.reveal-armed [data-reveal] .sell__lede,
html.reveal-armed [data-reveal] .story__lede {
  transition: opacity var(--rv-lede) var(--ease-out),
              translate var(--rv-lede) var(--ease-out);
  transition-delay: calc(var(--rv-step) * 2.2);
}
html.reveal-armed [data-reveal] .sell__actions,
html.reveal-armed [data-reveal] .story__cta,
html.reveal-armed [data-reveal] .story__act,
html.reveal-armed [data-reveal] .ab-cta__acts,
html.reveal-armed [data-reveal] .index__all,
html.reveal-armed [data-reveal] .inv__eyebrow,
html.reveal-armed [data-reveal] .ab-eyebrow {
  transition: opacity var(--rv-cta) var(--ease-out),
              translate var(--rv-cta) var(--ease-out);
  transition-delay: calc(var(--rv-step) * 3.4);
}
html.reveal-armed [data-reveal] .inv__eyebrow,
html.reveal-armed [data-reveal] .ab-eyebrow { transition-delay: 0s; }

/* About's photograph and its two lists.

   THE DELAY IS ONE VALUE PER SELECTOR AND THE SHORTHAND IS WRITTEN OUT,
   because a single-value transition-delay applies to EVERY property in
   the shorthand — that is what put a 338ms lag on the location panels'
   hover, where the stagger's delay landed on flex-grow as well. Here
   only opacity and translate are in the transition, so both are meant to
   wait, and nothing else is listening. */
html.reveal-armed [data-reveal] .ab-shot__fig {
  transition: opacity var(--rv-lede) var(--ease-out),
              translate var(--rv-lede) var(--ease-out);
  transition-delay: calc(var(--rv-step) * 2.2);
}
/* A showroom block is its own [data-reveal], so its two parts arrive as
   a pair rather than as members of a five-deep staircase — the film
   first and the facts a beat behind it, five times over. A staircase
   across five blocks would mean the last one waiting four steps for a
   section the reader is already looking at. */
html.reveal-armed [data-reveal] .lo-room__film {
  transition: opacity var(--rv-lede) var(--ease-out),
              translate var(--rv-lede) var(--ease-out);
}
html.reveal-armed [data-reveal] .lo-room__body {
  transition: opacity var(--rv-cta) var(--ease-out),
              translate var(--rv-cta) var(--ease-out);
  transition-delay: calc(var(--rv-step) * 1.4);
}
html.reveal-armed [data-reveal] .ab-story__col,
html.reveal-armed [data-reveal] .ab-room {
  transition: opacity var(--rv-cta) var(--ease-out),
              translate var(--rv-cta) var(--ease-out);
}
/* The staircase starts after the headline has finished travelling, and
   the interval is shorter than the headline's — a list of five reads as
   one gesture only if it does not take five beats to arrive. */
html.reveal-armed [data-reveal] .ab-story__col:nth-child(1),
html.reveal-armed [data-reveal] .ab-room:nth-child(1) { transition-delay: calc(var(--rv-step) * 2.6); }
html.reveal-armed [data-reveal] .ab-story__col:nth-child(2),
html.reveal-armed [data-reveal] .ab-room:nth-child(2) { transition-delay: calc(var(--rv-step) * 3.0); }
html.reveal-armed [data-reveal] .ab-story__col:nth-child(3),
html.reveal-armed [data-reveal] .ab-room:nth-child(3) { transition-delay: calc(var(--rv-step) * 3.4); }
html.reveal-armed [data-reveal] .ab-story__col:nth-child(4),
html.reveal-armed [data-reveal] .ab-room:nth-child(4) { transition-delay: calc(var(--rv-step) * 3.8); }
html.reveal-armed [data-reveal] .ab-room:nth-child(5) { transition-delay: calc(var(--rv-step) * 4.2); }
html.reveal-armed [data-reveal] .sell__steps li {
  transition: opacity var(--rv-cta) var(--ease-out),
              translate var(--rv-cta) var(--ease-out);
}
html.reveal-armed [data-reveal] .sell__steps li:nth-child(1) { transition-delay: calc(var(--rv-step) * 4); }
html.reveal-armed [data-reveal] .sell__steps li:nth-child(2) { transition-delay: calc(var(--rv-step) * 4.7); }
html.reveal-armed [data-reveal] .sell__steps li:nth-child(3) { transition-delay: calc(var(--rv-step) * 5.4); }

/* ============================================================
   SELL / TRADE MIRRORED — the page stops repeating itself
   ============================================================
   Three chapters running text-left / vehicle-right is a rhythm the eye
   stops reading. Only this one flips, and only its PHOTOGRAPH and its
   copy column: nothing in the DOM is mirrored, so type, numbers and
   buttons are untouched and the three process steps keep running left to
   right, because a process does.

   Featured Inventory  vehicle left  / index right
   Sell / Trade        vehicle left  / copy  right
   Story               copy left     / photograph right
   ============================================================ */

/* The photographic layer only. scaleX through the negative on the scale
   property, with the origin mirrored to match — the 62% that pushed the
   growth off the right edge becomes 38% to push it off the left. */
.sell__bg img {
  scale: -1.3 1.3;
  transform-origin: 38% 100%;
  /* Re-art-directed after the flip rather than left on the default: this
     holds the Carrera clear of the left edge and keeps the architectural
     wall — now on the right — open behind the copy. */
  object-position: 44% center;
}

/* The copy crosses to the open wall. Right-hand column, still left-aligned
   text: an editorial block placed on the right, not a mirrored template. */
.sell__copy {
  justify-self: end;
  padding-inline-start: 0;
  padding-inline-end: clamp(var(--sp-7), 5vw, var(--sp-10));
}

/* The cuts follow the flow. Top: shelf on the RIGHT, falling to the left.
   Bottom: short on the LEFT, dropping to full depth on the right — so the
   geometry runs vehicle-mass left, open space right, and hands down into
   Story. Same amplitudes, same language, mirrored bias. */
.sell {
  border-start-start-radius: 0;
  border-start-end-radius: clamp(20px, 2vw, 30px);
  clip-path: polygon(
    0 var(--cut-drop),
    var(--bcut-a) var(--cut-drop),
    var(--bcut-b) 0,
    100% 0,
    100% 100%,
    calc(var(--cut-start) + var(--cut-run)) 100%,
    var(--cut-start) calc(100% - var(--cut-drop-b)),
    0 calc(100% - var(--cut-drop-b))
  );
}
@supports (clip-path: shape(from 0 0, line to 1px 1px, close)) {
  .sell {
    clip-path: shape(
      from 0 var(--cut-drop),
      line to var(--bcut-a) var(--cut-drop),
      curve to var(--bcut-b) 0
        with calc(var(--bcut-a) + var(--cut-run) * 0.5) var(--cut-drop)
           / calc(var(--bcut-a) + var(--cut-run) * 0.5) 0,
      line to 100% 0,
      line to 100% 100%,
      line to calc(var(--cut-start) + var(--cut-run)) 100%,
      curve to var(--cut-start) calc(100% - var(--cut-drop-b))
        with calc(var(--cut-start) + var(--cut-run) * 0.5) 100%
           / calc(var(--cut-start) + var(--cut-run) * 0.5) calc(100% - var(--cut-drop-b)),
      line to 0 calc(100% - var(--cut-drop-b)),
      close
    );
  }
}

/* THE ONE DIRECTIONAL EXCEPTION IN THE GLOBAL SYSTEM.
   Sell's headline enters from the RIGHT, because its composition is the
   mirrored one and a left entrance would cross the whole photograph to
   arrive. The paragraph and the actions still rise; Story and Featured
   Inventory are untouched. Recorded here rather than in the shared block
   so the global rule stays one rule with one stated exception. */
html.reveal-armed .sell[data-reveal]:not(.is-revealed) .ttl-line {
  translate: 56px 0;
}

/* ---- Re-art-directed after the flip ----
   At origin 38% the 1.3 scale threw the Carrera off the left edge and cut
   it in half. The origin is the only horizontal control here — the frame
   covers by width, so there is no crop for object-position to move — so
   it comes in to 21%, which lands the car clear of the edge with its
   whole length readable and the wall still open behind the copy.

   And the copy comes off the right edge: at 5vw the headline was running
   into the margin. */

.sell__copy { padding-inline-end: clamp(var(--sp-8), 8vw, var(--sp-11)); }

/* The origin, computed rather than nudged. A negative scale reflects
   ABOUT the origin, so moving the origin left pushes the subject further
   right and vice versa — the opposite of the intuition, which is how the
   car went off the left edge twice. Solving O + (x - O) x -1.3 for the
   car's source position lands the origin at 52%, which puts it a little
   over 400px in from the left with the wall open behind the copy. */

/* 54%, not 52% — the tail was sitting on the left edge with nothing to
   spare. This gives the car about 95px of margin and still leaves the
   whole right half of the wall to the copy. */
.sell__bg img { transform-origin: 54% 100%; }

/* ---- The mirrored frame re-aimed for the narrow band ----
   Side by side, a vehicle and a text column need width the section stops
   having below about 1200. At 1000 the 1.3 scale put the Carrera's nose
   under "Get an offer" — the car was not too big, the room was too small.
   Less scale and an origin computed for this width push it left until the
   wall is clear behind the copy, and the car reads as the mass it is
   without holding the text. */
@media (max-width: 1200px) {
  .sell__bg img { scale: -1.15 1.15; transform-origin: 48% 100%; }
  /* The column places this block now, so it fills the column. The old
     max-width and end padding were how it was positioned before the grid
     existed; left in place they made the copy narrower than its own
     column and parked it against the column start, with the wall to its
     right empty. */
  .sell__copy { max-width: none; padding-inline-end: 0; }
}

/* The pair was pinned nowrap for the wide band, where it has the room.
   In the narrow column it ran off the right edge of the viewport, so it
   is allowed to break there — primary on its line, secondary under it. */
@media (max-width: 1200px) {
  .sell__actions { flex-wrap: wrap; }
}

/* ============================================================
   ONE HORIZONTAL SHELL FOR THE WHOLE PAGE
   ============================================================
   Measured at 1920 before this pass: the header's wordmark sat 81px from
   the edge, the inventory content 162, the story 152 — and the inventory
   capped at 1580 while the story capped at 1600. Three shells, none of
   them agreeing, which is why the header read as a different site.

   Every outer container now resolves from --page-gutter and --shell.
   Sections may still run a narrower editorial column inside this; what
   they may not do is invent their own outer edge.
   ============================================================ */
.masthead__inner,
.inv__inner,
.sell__inner,
.story__inner {
  width: min(100% - 2 * var(--page-gutter), var(--shell));
  max-width: none;
  margin-inline: auto;
  padding-inline: 0;
}

/* ---- The header controls are one family ----
   MENU and the phone share a height, a radius, a border weight and a
   type size from the same tokens — their widths differ because their
   content does, and nothing else about them is styled apart. Compact by
   intent: --control-h-header is 46 against --control-h-primary at 54,
   and that gap is a decision recorded in the tokens rather than two
   selectors that drifted. */
.menu-toggle,
.masthead__call {
  height: var(--control-h-header);
  min-height: var(--control-h-header);
  padding-block: 0;
  padding-inline: var(--sp-5);
  display: inline-flex;
  align-items: center;
  border-radius: 6px;
  font-size: var(--t-micro);
  line-height: 1;
}

/* The rail's own left padding held the wordmark 17px inside the shell at
   1920 and 1440 and released it at 1024 — the logo sitting on a different
   line depending on width, which is exactly the breakpoint-specific
   second system this pass is for. The rail keeps its right padding, which
   is doing real work between the nav items and the call. */
.navbar { padding-inline-start: 0; }

/* One tracking for the pair. The padding, height, radius and type size
   were already identical; the secondary was set at 0.18em against the
   primary's 0.08em, which pushed its label wider inside an identical box
   and made two buttons of one family read as two different controls. */
.sell__how { letter-spacing: var(--tr-label); }

/* The copy column ends ON the shell line, so the secondary action's right
   edge lands on the same vertical as the header's phone control. It had
   its own end padding on top of the shell, which put it 20px inboard of
   everything else on the page's right edge. */
.sell__copy { padding-inline-end: 0; }
@media (max-width: 1200px) {
  .sell__copy { padding-inline-end: 0; }
}

/* .sell__grid is full-bleed — the shell lives on .sell__inner, which this
   section does not route its copy through — so the column takes the page
   gutter directly. Same value, same line as the header control and as
   every other section's right edge. */
.sell__copy,
.sell__copy:not(#x) { padding-inline-end: var(--page-gutter); }

/* ---- The header goes back to full width ----
   It was pulled onto the content shell in the grid pass; the rail is
   meant to span the viewport and only its contents sit on the gutter. */
.masthead__inner {
  width: 100%;
  max-width: none;
  margin-inline: 0;
  padding-inline: var(--page-gutter);
}

/* ---- The sell copy takes the SEARCH INVENTORY inset ----
   That control is the reference for anything that runs to an edge: its
   rail carries 16px of inline padding and the button sits on it. The sell
   column now uses the same value, so "Talk to our specialist" ends on the
   same line SEARCH does instead of on a gutter of its own. */
.sell__copy,
.sell__copy:not(#x) {
  padding-inline: var(--sp-4);
}
@media (max-width: 1200px) {
  .sell__copy { padding-inline: var(--sp-4); }
}

/* Reverted to the page gutter. Tying this column to the finder rail's own
   16px inner padding was wrong: that is padding INSIDE a floating rail,
   not the line the rail sits on. At 1440 it put the button 16px from the
   viewport while the header, inventory and story all sit at 49. Until the
   column grid is rebuilt properly, this at least agrees with them. */
.sell__copy,
.sell__copy:not(#x) { padding-inline: var(--page-gutter); }
@media (max-width: 1200px) {
  .sell__copy { padding-inline: var(--page-gutter); }
}

/* ============================================================
   THE PAGE GRID — Phase 1: header, hero, finder
   ============================================================
   One shell, one column system, applied as a utility rather than baked
   into each section. Sections keep their own class for everything that
   is theirs; .wrap decides only where their contents begin and end.
   ============================================================ */
.wrap {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--pad);
}
.grid12 {
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 0 var(--grid-gap);
}

/* ---- Phase 1 ----
   The masthead BAR stays full-bleed; its contents move onto the shell,
   which is what puts the wordmark and the phone on the same verticals as
   the sections below. Previously the bar padded itself by the gutter and
   ignored --maxw entirely, so at 1920 the logo sat at 60 while the page
   started at 170.

   The hero foot and the finder rail come off their own inset-inline and
   onto the same shell. The finder keeps its internal 16px — that is
   component padding and always was; confusing it for a page gutter is
   what sent the Sell column to the wrong line. */
.masthead__inner,
.hero__foot {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--pad);
}
.hero__foot { inset-inline: 0; }

/* ---- Phase 2: Featured Inventory onto the shell ----
   It was computing its own edge from a private clamp and a private
   1580 cap — the same numbers by coincidence, not by system. Same
   formula as the header and hero now, so a change to --pad or --maxw
   moves all of them together. */
.inv__inner {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--pad);
}

/* ============================================================
   Phase 3: SELL / TRADE on the twelve columns
   ============================================================
   The copy column was placed by justify-self plus a padding that had to
   be guessed, which is why the secondary CTA kept ending up on the
   viewport edge — the button row was deciding the page width instead of
   living inside a column.

   It now occupies columns 7 through 11 of the shared grid. Column 12 is
   left empty on purpose: that is the clearance between the longest CTA
   and the shell edge, and it is structural rather than a padding someone
   has to remember. The vehicle keeps the left of the frame; the
   composition is unchanged.

   Every local positioning hack on this column goes with it — the
   justify-self, the invented end padding, and the :not(#x) written to
   outrank an earlier block. */
.sell__grid {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--pad);
  display: grid;
  grid-template-columns: repeat(12, minmax(0, 1fr));
  column-gap: var(--grid-gap);
}
.sell__copy,
.sell__copy:not(#x) {
  grid-column: 7 / -1;
  justify-self: stretch;
  max-width: none;
  padding-inline: 0;
}
.sell__steps { grid-column: 1 / -1; margin-inline: 0; }

/* The pair sits inside the column and is measured against it, not
   against the viewport. Below the two-mass width the copy takes the full
   twelve and the actions stack — a compositional break decided here,
   before they are cramped, rather than at the moment they collide. */
.sell__actions { flex-wrap: nowrap; }
@media (max-width: 1200px) {
  .sell__copy,
  .sell__copy:not(#x) { grid-column: 1 / -1; }
  .sell__actions { flex-wrap: wrap; }
}

/* ---- Phase 4: Story onto the shell ----
   It was the last container computing its own edge — a 100rem cap and a
   third gutter clamp. Same formula as the other four now. The band stays
   full-bleed and the photograph still runs edge to edge behind it; only
   the contents resolve against the shell. */
.story__inner {
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-inline: var(--pad);
}

/* ---- Regression from the grid migration, fixed at the photograph ----
   The copy column now starts at column 7 rather than wherever a padding
   put it, which at 1440 is 117px further left than before — and the frame
   was still aimed for the wide band, so the Carrera's tail crossed into
   the text. The collision starts around 1500, not 1200, so the narrow
   aim begins there. This is the photograph being re-aimed per band, the
   same value that already exists below 1200; the column is not moved and
   nothing is nudged. */
@media (max-width: 1500px) {
  .sell__bg img { scale: -1.15 1.15; transform-origin: 46% 100%; }
}


/* ---- The 1200 collapse was wrong, and it was mine ----
   Sending the copy to 1 / -1 put it back on top of the vehicle, which is
   the composition this section exists to avoid. The two masses still fit
   at this width — the column just has to stay on the right. It keeps the
   right seven of twelve, and the CTA pair stacks, which is the change
   that was actually needed here.

   The real collapse waits until 900, where the frame genuinely cannot
   hold a vehicle and a text column side by side. There the copy takes
   the full twelve and the photograph is re-aimed to sit under it rather
   than beside it. */
@media (max-width: 1200px) {
  .sell__copy,
  .sell__copy:not(#x) { grid-column: 6 / -1; }
}
@media (max-width: 900px) {
  .sell__copy,
  .sell__copy:not(#x) { grid-column: 1 / -1; }
  .sell__bg img { scale: -1.05 1.05; transform-origin: 38% 100%; }
}

/* ============================================================
   THE SELL EDGES BECOME SCROLL-DRIVEN
   ============================================================
   Two independent systems, deliberately: the text enters ONCE on an
   IntersectionObserver and stays; the edges are scrubbed continuously by
   scroll and never latch. They must not be merged — a one-time entrance
   that scrubs would re-run under the reader, and a scrubbed geometry that
   latches would freeze mid-shape.

   The depths are registered so they can be interpolated at all; an
   unregistered custom property is a string to the animation engine and
   would jump between keyframes rather than travel.

   The static values this replaces sat at roughly 40 and 71 at 1440, so
   the range is centred there: the shape already approved is the middle
   state, and scroll opens it either side of that. The bottom moves
   further than the top, which is what widens the window onto Story as
   the band leaves. Its maximum stays under the 96px overlap, so Story is
   never uncovered — only revealed. Geometry only; no height changes. */
@property --cut-drop {
  syntax: '<length>';
  inherits: true;
  initial-value: 40px;
}
@property --cut-drop-b {
  syntax: '<length>';
  inherits: true;
  initial-value: 71px;
}
/* The HORIZONTAL position of the fall, registered for the same reason
   the depths are: unregistered, a custom property is a string to the
   animation engine and would jump between keyframes instead of
   travelling. This is what Alex asked to move — the cut drifting
   sideways as the band passes, rather than only deepening. */
@property --cut-start {
  syntax: '<percentage>';
  inherits: true;
  initial-value: 58%;
}

@keyframes sell-edges {
  /* AMPLITUDE IS THE WHOLE POINT, and the first attempt got it wrong.
     Six points either side of 58% is about 100px of travel spread over
     the entire passage of the band through the viewport — roughly 5px
     of movement per 100px of scroll, which is not slow, it is invisible.
     The resolved clip-path was genuinely changing and the effect still
     read as nothing.

     38% to 74% is 36% of the width, some 450px at 1440, and about 20px
     per 100px of scroll. The ceiling is set by the geometry rather than
     by taste: --bcut-a is calc(100% - --cut-start - --cut-run), so with
     a 15% run anything past 85% would fold the top shelf through the
     right edge. */
  from { --cut-drop: 26px; --cut-drop-b: 46px; --cut-start: 38%; }
  to   { --cut-drop: 56px; --cut-drop-b: 94px; --cut-start: 74%; }
}

/* Warranty gets the same treatment on its own numbers. Its range is
   `entry` rather than `cover`: it is the last section on the page, so it
   never fully exits the top and a cover-based range would leave the
   travel unfinished at the bottom of the document. */
@keyframes warranty-edge {
  from { --cut-start: 14%; }
  to   { --cut-start: 54%; }
}

@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .sell {
      animation: sell-edges linear both;
      animation-timeline: view();
      animation-range: cover 0% cover 100%;
    }
    .warranty {
      animation: warranty-edge linear both;
      animation-timeline: view();
      animation-range: entry 0% entry 100%;
    }
  }
}

/* The foot clearance must not ride the animated depth, or the section
   grows and shrinks with the scroll and takes the document height with
   it. It is pinned to the deepest the bottom edge ever reaches, so the
   steps clear the cut at every point in the scrub and the geometry moves
   without anything laying out. */
.sell__inner,
.sell__grid { padding-block-end: 94px; }

/* ---- The document height was being set by the Story backdrop ----
   .story__bg is inset -56px top and bottom so the scroll handoff can move
   it without exposing an edge. It is absolutely positioned, but .story
   never clipped, so that overhang joined the page's scrollable area — and
   because story-lift translates the frame by 34px, the document grew and
   shrank by exactly that as it scrubbed.

   Clipped at its own section, which is where the overhang was always
   meant to live. `clip`, not `hidden`: hidden would make .story a scroll
   container and change how the sticky header and the scroll timelines
   see it. Nothing visual changes — the overhang was never on screen. */
.story { overflow: clip; }


/* ---- Sell's copy column sits on Featured Inventory's ----
   Twelve equal columns put it at 724 while the inventory cards above
   start at 773 — no twelfth lands on that line, so it could never match.
   Both sections are the same shape: one wide mass, one right-hand
   column. Sell now uses the showcase's own 53/47 split, so the two right
   columns share a vertical. The steps still span the whole width. */
.sell__grid {
  grid-template-columns: minmax(0, 53fr) minmax(21rem, 47fr);
  column-gap: clamp(var(--sp-7), 3.4vw, var(--sp-8));
}
.sell__copy,
.sell__copy:not(#x) { grid-column: 2; }
.sell__steps { grid-column: 1 / -1; }
@media (max-width: 1200px) {
  .sell__grid { grid-template-columns: minmax(0, 1fr); }
  .sell__copy,
  .sell__copy:not(#x) { grid-column: 1; }
}

/* ============================================================
   8 — SERVICE
   ============================================================
   The chapter the page does not have: no display headline, no
   paragraph, no pill pair under a statement. A numbered register, and
   a film that keeps its whole frame.

   THE FILM IS NOT CROPPED, and that is the replacement asset's doing.
   The first cut ran three acts and its workshop act was too bright for
   type anywhere, so the register sat on a column of page ground beside
   a film cut to 64%. This film is one continuous shot of three cars in
   a dark hall, and two measurements reversed the composition:

     — it is quiet enough to carry type. Upper-left reads p95 0.090,
       the upper band 0.099, against a 0.154 body-text ceiling. The old
       film's best zone was 0.272 and failed even the large-text floor.
     — its subject spans the FULL width. Energy is even across all
       twelve columns, because the picture is a lineup: a green 911, the
       blue STO, a red Ferrari with its doors up. A 36% ground column
       would have removed the Porsche entirely and clipped the STO.
       Cropping this frame is fighting the asset.

   So the film takes the whole band and the register sits on it, in the
   upper-left — the quietest field in every sampled frame and the corner
   reading starts from. No plate, no glass, no border: the measurement
   says none is needed, and a wash is only there to hold the worst
   percentile rather than to make a surface.

   THE APERTURE IS UNCHANGED. Service is still pulled up over Warranty
   and the film still opens as a horizontal slit; it simply opens to the
   full width now instead of stopping at a column.
   ============================================================ */

@property --svc-open {
  syntax: '<percentage>';
  inherits: true;
  initial-value: 0%;
}

.svc {
  position: relative;
  background: var(--bg);
  color: var(--ink);
  /* Pulled up over the bone band so the opening happens INSIDE Warranty
     rather than at a seam below it. The overlap is the transition. */
  margin-block-start: calc(var(--sp-9) * -1);
  z-index: 2;
  min-block-size: clamp(34rem, 62vw, 44rem);
  overflow: hidden;
  border-start-end-radius: var(--r-section);
}

/* ---- The film ----------------------------------------------
   Full bleed, no frame, no player, no controls. */
.svc__film {
  position: absolute;
  inset: 0;
  overflow: hidden;
  /* The aperture: a horizontal slit widening to the full band. */
  clip-path: inset(var(--svc-open) 0 var(--svc-open) 0);
}

.svc__video {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  /* Centre, because the lineup is symmetrical and its three cars are
     the composition. The previous 62% was for a frame whose subject sat
     right of centre; this one has no spare side. */
  object-position: center;
  display: block;
}

/* A directional wash from the reading edge, not a panel behind the
   text. --bg-rgb so it is cut from the page's own ground, and the last
   stop is `rgb(... / 0)` rather than `transparent`, which interpolates
   through transparent BLACK and leaves a grey bruise mid-fade. It
   exists to hold the 99th percentile — the film's p95 already clears
   body text on its own. */
.svc__edge {
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    linear-gradient(to right,
      rgb(var(--bg-rgb) / 0.82) 0%,
      rgb(var(--bg-rgb) / 0.62) 24%,
      rgb(var(--bg-rgb) / 0.22) 46%,
      rgb(var(--bg-rgb) / 0) 62%),
    /* THE SEAM CLOSES AT BOTH ENDS. This layer used to start at 0.55,
       which is a wash rather than a merge — the film was still visible
       on the very edge, so a hard horizontal line sat where the section
       met the ground above it. Full --bg at 0% and a long fall means the
       frame has no edge at all: it emerges from the page and returns to
       it. The foot gets the same treatment, because Service closes the
       page and its lower edge met the ground the same way. */
    linear-gradient(to bottom,
      rgb(var(--bg-rgb) / 1) 0%,
      rgb(var(--bg-rgb) / 0.92) 5%,
      rgb(var(--bg-rgb) / 0.45) 16%,
      rgb(var(--bg-rgb) / 0) 30%),
    linear-gradient(to top,
      rgb(var(--bg-rgb) / 1) 0%,
      rgb(var(--bg-rgb) / 0.90) 4%,
      rgb(var(--bg-rgb) / 0.40) 13%,
      rgb(var(--bg-rgb) / 0) 26%);
}

/* ---- The register ------------------------------------------
   On the film, in its quietest field. */
.svc__register {
  position: relative;
  z-index: 1;
  display: flex;
  flex-direction: column;
  align-items: start;
  max-inline-size: min(34rem, 46%);
  padding-inline: clamp(var(--sp-6), 4vw, var(--sp-9));
  padding-block: clamp(var(--sp-8), 6vw, var(--sp-9));
}

.svc__label {
  color: var(--ink-2);
  margin-block-end: clamp(var(--sp-4), 2vw, var(--sp-5));
}

.svc__list {
  list-style: none;
  margin: 0;
  padding: 0;
  align-self: stretch;
}
/* The row's geometry moves onto the anchor so the whole line is the
   target, not just the words in it. */
.svc__list li { display: block; }
.svc__row {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: clamp(var(--sp-4), 2vw, var(--sp-5));
  align-items: baseline;
  padding-block: clamp(var(--sp-2), 1vw, var(--sp-3));
  text-decoration: none;
  color: inherit;
  transition: color var(--sel-dur) var(--sel-ease);
}
/* Rules BETWEEN, not around — the grammar the sell steps and the
   warranty provisions already use, which is what makes this a register
   rather than a new device. */
.svc__list li + li { border-block-start: 1px solid var(--rule); }

.svc__n {
  font-family: var(--font-display);
  /* 14px, not 13. A register numeral is functional text and the floor
     applies to it — the smaller size looked right and was not allowed. */
  font-size: 0.875rem;
  font-weight: 500;
  color: var(--ink-3);
  font-variant-numeric: tabular-nums;
}
.svc__item {
  font-size: clamp(0.9375rem, 1.1vw, 1.0625rem);
  line-height: 1.3;
  color: var(--ink);
}

/* .svc__proof — the line it styled was removed; the rule goes with it. */

.svc__actions {
  display: flex;
  flex-wrap: wrap;
  align-self: stretch;
  gap: var(--sp-4);
  margin-block-start: clamp(var(--sp-5), 2.6vw, var(--sp-6));
}
.svc__actions .btn { flex: 1 1 14rem; }

/* ---- The aperture opens on scroll --------------------------
   Scrubbed, unlike the register, which reveals once. Two systems: the
   geometry is scroll-driven and the content is not. */
@media (prefers-reduced-motion: no-preference) {
  @supports (animation-timeline: view()) {
    @keyframes svc-aperture {
      from { --svc-open: 46%; }
      to   { --svc-open: 0%; }
    }
    .svc {
      animation: svc-aperture linear both;
      animation-timeline: view();
      /* entry, not cover: Service is near the foot of the document and
         a cover range would leave the aperture unfinished at the bottom
         of the page. */
      animation-range: entry 0% entry 68%;
    }
  }
}

/* The designed static path: with no scroll timeline, or under reduced
   motion, the film is simply open. Nothing depends on the scrub. */
@media (prefers-reduced-motion: reduce) {
  .svc { --svc-open: 0%; }
}

/* ---- Narrow --------------------------------------------------
   The register can no longer sit in a corner of the frame, so the film
   becomes a band above it and the register returns to the page ground.
   D7 bans hiding imagery on mobile; a film squeezed behind a full
   column of type is hidden by another means. */
@media (max-width: 1000px) {
  .svc {
    min-block-size: 0;
    display: grid;
    grid-template-rows: auto auto;
    margin-block-start: calc(var(--sp-8) * -1);
  }
  .svc__film {
    position: relative;
    grid-row: 1;
    aspect-ratio: 16 / 10;
  }
  .svc__edge {
    background: linear-gradient(to bottom,
      rgb(var(--bg-rgb) / 0.45) 0%,
      rgb(var(--bg-rgb) / 0) 34%);
  }
  .svc__register {
    grid-row: 2;
    max-inline-size: none;
    padding-block: clamp(var(--sp-7), 8vw, var(--sp-8));
  }
}
@media (max-width: 560px) {
  .svc__actions .btn { flex: 1 1 100%; }
}

/* ---- Grouped figures keep their grouping --------------------
   The monospace used to do this for nothing: every glyph including the
   space carried one advance, so 630 221 1800 fell into three blocks on
   its own. A proportional face with letter-spacing widens the digits
   and not the space — measured, the gap came to 3.9px against a 9.5px
   digit, 41%, and the number read as 630 2211800.

   word-spacing, not a wider letter-spacing: the grouping is what has to
   grow, and tracking the digits further would only make it worse.

   The markup had to change with it. The separators were U+202F, a
   narrow no-break space — correct typography for a phone number, and
   invisible here for two compounding reasons: a monospace gave it a
   full advance so it never looked narrow, and CSS word-spacing applies
   only to U+0020, so this rule could not have reached it. They are
   U+00A0 now: still unbreakable, and a real word separator. */
[href^="tel:"],
.navbar__cta-text,
.where__phone,
.menu__phone {
  word-spacing: 0.3em;
}

/* ---- THE DISPLAY VOICE IS THE SERIF ------------------------
   Applied in one place so the page cannot drift back to five answers.
   Weight 400 because Instrument Serif has no other; a synthesised bold
   smears the outline and reads as a rendering fault, not as emphasis.
   Tracking is --tr-serif rather than --tr-display: the negative value
   that flatters a grotesque closes this face's counters. */
.claim__line,
.inv__title,
.svc__title,
.brk__title,
.warranty__title,
.story__line,
.sell__title,
.ttl-line {
  font-family: var(--font-display);
  /* 700, not 400. Inter Tight holds weight; the 400 was there because
     Instrument Serif had no other, and it does not apply to a grotesque
     whose whole value at this size is mass. */
  font-weight: 700;
  letter-spacing: var(--tr-display);
}

/* ============================================================
   ART-DIRECTION PASS — Alex, 2026-08-11
   ============================================================
   Direct corrections, applied as given. Only the marked items.
   ============================================================ */

/* 1 — WARRANTY: radius on all four corners.
   It had one, top-left. The band reads as a block now rather than as a
   full-bleed field, which is why the bottom pair matters as much as the
   top pair. */
.warranty {
  border-radius: var(--r-section);
}

/* 2 — SPACE BETWEEN WARRANTY AND SERVICE.
   Service was pulled up 96px so its aperture opened inside the bone
   band. That overlap is what left no air at the seam. It sits below
   Warranty now with the page ground between them, which is also what
   makes both sets of new corners visible. */
.svc {
  margin-block-start: clamp(var(--sp-7), 4vw, var(--sp-9));
}

/* 3 — STORY: radius on all four corners. */
.story {
  border-radius: var(--r-section);
}

/* 4 — STORY: move the content in off the edge. */
.story__inner,
.story__inner:not(#x) {
  padding-inline: clamp(var(--sp-7), 6vw, var(--sp-10));
}

/* 5 — SERVICE: the register aligns to Warranty.
   Not by matching a number — by sitting in the same .shell that carries
   Warranty's copy, so "Our Services" starts on the exact vertical of
   "Warranty / Protection" and the two chapters read as linked. The
   register's own inline padding goes, or it would add to the shell's. */
.svc__inner {
  position: relative;
  z-index: 1;
}
.svc__register {
  padding-inline: 0;
}


/* ============================================================
   SELL IS FLIPPED — COPY RIGHT, FRAME MIRRORED
   ============================================================
   Alex's direction, 2026-08-11, after seeing the copy-left version.

   Measured on the frame as supplied: a 24x12 occupancy pass puts its
   clean field on the LEFT — bright wall from y6% to y38%, bright floor
   from y45% to y88%, across x3%-x42% — with the cars grouped right of
   centre (orange AMG x45-60%, the blue Aventador x40-85%, the G-wagen
   x72-87%). So a copy column on the right needs the picture mirrored;
   the two moves are one decision, not two.

   I argued against the mirror on the copy-left pass: it reverses the
   Lamborghini badge and the AMG grille, and on a dealership page a
   reversed car is a false statement about a car. Alex has directed the
   flip with that on the table, so it stands as his call. It is also a
   weaker objection against THIS file than it was against a photograph —
   the frame reads as a render and has not been tied to a specific car
   in inventory (see the sidecar).

   Done in CSS, not by rewriting the master, so the file on disk stays
   the file that was supplied and the flip lifts with one line.

   The 1.3 scale from 62%/bottom is still gone. It aimed at the frame
   before this one and nothing about it survives the change. */
.sell__grid {
  grid-template-columns: minmax(0, 56fr) minmax(20rem, 44fr);
}
.sell__copy,
.sell__copy:not(#x) {
  grid-column: 2;
  justify-self: stretch;
  max-width: none;
}
.sell__steps { grid-column: 1 / -1; }

/* object-position resolves in the element's OWN box, before the mirror
   is applied — so 18% still holds the frame's left edge, and the mirror
   is what carries that edge to the screen's right, under the copy. The
   crop keeps coming off the frame's right, where the far white car is
   the least of the subject. */
.sell__bg img,
.sell__bg img:not(#x) {
  scale: -1 1;
  transform-origin: 50% 50%;
  object-position: 18% center;
}

/* The old 1200 rule sent the copy full-width and left. That was written
   when the wall was on the left; after the flip it drops the whole block
   onto the cars — the lede lands on the G-wagen and TALK TO OUR
   SPECIALIST disappears into the Aventador. The column stays on the
   right and simply takes a larger share of a narrower band. */
@media (max-width: 1200px) {
  .sell__grid { grid-template-columns: minmax(0, 46fr) minmax(17rem, 54fr); }
  .sell__copy,
  .sell__copy:not(#x) { grid-column: 2; }
}

/* Below 900 the band genuinely cannot hold a vehicle and a text column
   side by side, so the copy takes the full width. 38% mirrored puts the
   wall under the headline and the cars below it. */
@media (max-width: 900px) {
  .sell__grid { grid-template-columns: minmax(0, 1fr); }
  .sell__copy,
  .sell__copy:not(#x) { grid-column: 1; }
  .sell__bg img,
  .sell__bg img:not(#x) { object-position: 38% center; }

  /* The steps stack here, which makes the row three times taller and
     pushes its top to about 52% of the band — well above the bone ramp,
     which is sized for the short desktop row at 34%. So 01 was reading
     against a car instead of against bone. This is not new with the
     flip: the row has always run onto the picture at this width, it was
     a different car under it.

     The ramp is sized to the row rather than to a fixed height, and goes
     effectively solid before the row begins. It costs the lower part of
     the frame at phone widths, which is the trade — an unreadable step
     is not a trade, it is a defect. */
  .sell__bg::after {
    height: 56%;
    background: linear-gradient(to top,
      var(--bone) 0%,
      var(--bone) 78%,
      rgba(236, 228, 214, 0.92) 88%,
      rgba(236, 228, 214, 0.55) 95%,
      rgba(236, 228, 214, 0) 100%);
  }
}

/* The lede stands on the photograph, not on the bone band, so it takes
   the on-film ink. Measured, glyph-located, on the composited render. */
.sell__lede,
.sell__lede:not(#x) { color: var(--bone-ink-2-onfilm); }

/* ============================================================
   ALEX'S PASS — CORNERS ON ALL SIDES, THE SEAM CLOSED,
   AND THE CUTS GIVEN REAL TRAVEL
   ============================================================

   1 — WHY border-radius DID NOTHING ON WARRANTY.
   It has carried `border-radius: clamp(40px,5vw,72px)` since the last
   pass and rendered four square corners anyway. clip-path does not
   intersect with the border box: once a shape() is resolved the element
   is clipped to THAT outline, and the outline had 90-degree corners at
   0/100%. The radius has to be cut into the shape itself, as arcs, or it
   is not there at all. So the shapes below are the same silhouettes —
   same shelf, same run, same fall — with the four corners arced.

   The polygon() floor keeps square corners. It is the no-shape() engine
   path and has always been the plainer bevel; a rounded polygon would be
   sixteen hand-computed points that drift the moment --cut-drop moves. */

@supports (clip-path: shape(from 0 0, arc to 1px 1px of 1px, close)) {
  .warranty {
    clip-path: shape(
      from var(--r-cut) var(--cut-drop),
      line to var(--cut-start) var(--cut-drop),
      curve to calc(var(--cut-start) + var(--cut-run)) 0
        with calc(var(--cut-start) + var(--cut-run) * 0.26) var(--cut-drop)
           / calc(var(--cut-start) + var(--cut-run) * 0.74) 0,
      line to calc(100% - var(--r-cut)) 0,
      arc to 100% var(--r-cut) of var(--r-cut) cw,
      line to 100% calc(100% - var(--r-cut)),
      arc to calc(100% - var(--r-cut)) 100% of var(--r-cut) cw,
      line to var(--r-cut) 100%,
      arc to 0 calc(100% - var(--r-cut)) of var(--r-cut) cw,
      line to 0 calc(var(--cut-drop) + var(--r-cut)),
      arc to var(--r-cut) var(--cut-drop) of var(--r-cut) cw,
      close
    );
  }

  /* Sell carries two falls, top and bottom, so it has two shelf corners
     as well as the two outer ones — four arcs, same radius. */
  .sell {
    clip-path: shape(
      from var(--r-cut) var(--cut-drop),
      line to var(--bcut-a) var(--cut-drop),
      curve to var(--bcut-b) 0
        with calc(var(--bcut-a) + var(--cut-run) * 0.26) var(--cut-drop)
           / calc(var(--bcut-a) + var(--cut-run) * 0.74) 0,
      line to calc(100% - var(--r-cut)) 0,
      arc to 100% var(--r-cut) of var(--r-cut) cw,
      line to 100% calc(100% - var(--r-cut)),
      arc to calc(100% - var(--r-cut)) 100% of var(--r-cut) cw,
      line to calc(var(--cut-start) + var(--cut-run)) 100%,
      curve to var(--cut-start) calc(100% - var(--cut-drop-b))
        with calc(var(--cut-start) + var(--cut-run) * 0.74) 100%
           / calc(var(--cut-start) + var(--cut-run) * 0.26) calc(100% - var(--cut-drop-b)),
      line to var(--r-cut) calc(100% - var(--cut-drop-b)),
      arc to 0 calc(100% - var(--cut-drop-b) - var(--r-cut)) of var(--r-cut) cw,
      line to 0 calc(var(--cut-drop) + var(--r-cut)),
      arc to var(--r-cut) var(--cut-drop) of var(--r-cut) cw,
      close
    );
  }
}

/* 2 — THE SEAM BETWEEN WARRANTY AND SERVICE CLOSES.
   The last pass opened it to 57.6px so both sets of corners could be
   read against the page ground. Alex has looked at it and wants it shut,
   so Service butts straight onto Warranty. Not the old -96px overlap
   either — that pulled the aperture up INSIDE the bone band. Zero. */
.svc,
.svc:not(#x) { margin-block-start: 0; }

/* 3 — THE CUTS GET REAL TRAVEL.
   The amplitude was already raised once, from 6% either side of 58% to a
   38%-74% sweep, and it is still reading as a static bevel: about 20px
   of horizontal drift per 100px of scroll, with the depth moving 30px
   across the entire passage of the band.

   Depth is the half a viewer actually notices — the fall changing shape
   under the eye — and it was the half barely moving. It roughly triples
   here, and the horizontal sweep widens to 24%-78%.

   The ceiling is still geometry, not taste: --bcut-a is
   calc(100% - --cut-start - --cut-run), so at 78% with a 15% run the top
   shelf ends at 7% and still has somewhere to land. Past about 85% it
   would fold through the right edge. */
@keyframes sell-edges {
  from { --cut-drop: 14px; --cut-drop-b: 30px; --cut-start: 24%; }
  to   { --cut-drop: 78px; --cut-drop-b: 142px; --cut-start: 78%; }
}

/* Warranty's fall was the flatter of the two and its depth did not move
   at all — a clamp, set once. It travels now, on the same reasoning. */
@keyframes warranty-edge {
  from { --cut-start: 6%;  --cut-drop: 16px; }
  to   { --cut-start: 66%; --cut-drop: 72px; }
}

/* ---- STORY: the dead tail under the counters goes ----
   The counters end and the band keeps running for another 115px before
   Warranty starts. The two sections are already flush — Story's bottom
   is 4812 and Warranty's top is 4812, no margin between them — so the
   gap Alex is pointing at is Story's own padding-block-end, symmetric
   with a top padding that has real work to do and this one does not.

   Asymmetric on purpose: the top still needs its air because the
   headline arrives into it, while the foot is closing on a section that
   opens with its own cut. What stays below the counters is that cut —
   --cut-drop, up to 72px of Warranty scooped away to show the ground
   behind it — which is the device, not a gap. */
.story__inner,
.story__inner:not(#x) {
  /* 31px was too tight under a 96px numeral — the counters sat on the
     section edge. Raised to ~72px at 1440, which is still well short of
     the 115px this was cut from: that value read as dead space, this one
     reads as the figures standing clear of the edge. */
  padding-block-end: clamp(var(--sp-7), 5vw, var(--sp-9));
}

/* ---- The corner comes down, everywhere at once ----
   Alex pointed at Sell's bottom right. --r-section was 72px at this
   width and is 36px now, and the three declarations that had the old
   clamp typed out rather than referencing the token follow it: Story,
   Warranty and Service's single corner. Shrinking Sell alone would have
   left two corner sizes on one page, which is the drift the ledger
   exists to catch. */

/* ============================================================
   7 — FINANCING
   ============================================================
   Alex's composition. One card carrying the photograph, the statement
   centred over it, the three routes as a row on the floor, and the
   payment method closing the card beneath them.

   Symmetrical on purpose: every other chapter on this page reads left,
   so the closing one is the single centred composition. It is also the
   only arrangement that leaves the vehicles the middle of the field
   without anything sitting on top of them. */
/* Financing takes Featured Inventory's measure.

   The shared .shell resolves to --container / --gutter, which is 1440
   and 57.6 here; .inv__inner runs on --maxw / --pad, which is 1700 and
   49. The two systems put the financing card at 58 from each edge while
   the inventory card sat at 49 — eighteen pixels narrower, on the same
   screen, for no reason a reader could see.

   Scoped to this section only. Eleven other sections still use .shell
   at its own measure, and quietly moving all of them is a page-wide
   decision, not a fix to one section. */
.fin__inner {
  max-width: var(--maxw);
  padding-inline: var(--pad);
}

/* The financing copy gets air between its three voices.

   Measured: 12px under the eyebrow and 19px under a 63px headline. At
   that headline size 19px is less than a third of a line, so the lede
   read as a second deck of the title rather than as the sentence
   answering it, and the whole block sat as one dense lump at the top of
   a very tall card.

   The intervals now scale with the type they separate rather than being
   fixed: the eyebrow keeps the tightest bond to the headline it labels,
   and the lede stands off far enough to be its own voice. The card
   itself takes a little more head room so the group is not pinned to
   its top edge. */

.fin {
  position: relative;
  background: var(--bg);
  color: var(--ink);
  padding-block: clamp(var(--sp-8), 6vw, var(--sp-10));
}

/* On the shell, so the card's edges land on the vertical Service uses
   directly above it. Its proportion is close to the photograph's own,
   which is what keeps the line-up uncropped. */
.fin__card {
  position: relative;
  isolation: isolate;
  overflow: hidden;
  border-radius: 26px;
  background: var(--bg-sunk);
  min-block-size: clamp(34rem, 58vw, 53rem);
  display: grid;
  grid-template-rows: auto 1fr auto;
  padding: clamp(var(--sp-7), 4vw, var(--sp-9)) clamp(var(--sp-6), 4vw, var(--sp-9));
}

.fin__bg { position: absolute; inset: 0; z-index: 0; }
.fin__bg img {
  inline-size: 100%; block-size: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
}

/* Two soft ends, not a flat scrim: the statement sits on the ceiling's
   smoke and the row sits on the floor, so each gets just enough ground
   while the middle of the frame — the vehicles — stays untouched. */
.fin__wash {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(to bottom,
      rgb(var(--bg-rgb) / 0.72) 0%,
      rgb(var(--bg-rgb) / 0.30) 22%,
      rgb(var(--bg-rgb) / 0) 40%),
    linear-gradient(to top,
      rgb(var(--bg-rgb) / 0.80) 0%,
      rgb(var(--bg-rgb) / 0.42) 18%,
      rgb(var(--bg-rgb) / 0) 40%);
}

.fin__copy {
  position: relative;
  z-index: 2;
  grid-row: 1;
  justify-self: center;
  text-align: center;
  max-inline-size: 44rem;
}

/* GROUPING, per spacing I1 — the eyebrow belongs TO the headline, so it
   sits close enough to be read as its mark rather than as a third item
   in a list. Both gaps used to resolve to about 30px, which is the exact
   failure the rule names: uniform intervals flatten a group into a
   stack. One step in, one step out. */
.fin__label {
  margin: 0 0 var(--gap-eyebrow);
  font-family: var(--font-label);
  font-size: var(--t-micro);
  font-weight: 500;
  /* --lh-label, not 1.2: the token is what .micro uses, and an eyebrow
     with its own leading lands its ink on a different line than every
     other eyebrow even when the margin below is identical. That was 4px
     of the drift. */
  line-height: var(--lh-label);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--accent);
}

/* And the supporting line is a separate utterance, so it takes roughly
   twice the eyebrow's interval — the group now has an inside and an
   outside. */
.fin__title {
  /* Was clamp(sp-4, 1.3vw, sp-5) — 19px under a 63px headline, less
     than a third of its own line, so the lede read as a second deck of
     the title instead of the sentence answering it. 37px then went too
     far the other way and the lede came adrift; this sits between. */
  margin: 0 0 clamp(var(--sp-5), 1.9vw, var(--sp-6));
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.5rem, 4.4vw, 4.25rem);
  line-height: 0.95;
  letter-spacing: var(--tr-display);
}

.fin__lede {
  margin: 0 auto;
  max-inline-size: 46ch;
  font-size: clamp(1rem, 1.15vw, 1.0625rem);
  line-height: 1.6;
  color: var(--ink-2);
}

/* ---- The foot: three routes and the payment method --------- */
.fin__foot {
  position: relative;
  z-index: 2;
  grid-row: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: clamp(var(--sp-6), 2.6vw, var(--sp-7));
}

.fin__rows {
  inline-size: 100%;
  max-inline-size: 62rem;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(var(--sp-4), 1.4vw, var(--sp-5));
}

/* The page's own card surface — the same corner and hairline Featured
   Inventory's records carry, on the ground beneath the vehicles. */
/* Centred, icon over label, no arrow. The glyphs are the page's own set
   — Material Symbols, the same family and the same filled weight the
   Warranty provisions carry — sized down to sit under a 15px label
   rather than beside a 17px one. A second icon family would be a second
   voice for the same job. */
.fin__row {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--sp-4);
  padding: clamp(var(--sp-6), 2.6vw, var(--sp-7)) clamp(var(--sp-5), 2.2vw, var(--sp-6));
  border: 1px solid rgba(243, 238, 230, 0.10);
  border-radius: 14px;
  background: rgb(var(--bg-rgb) / 0.42);
  text-decoration: none;
  font-family: var(--font-body);
  font-size: clamp(0.9375rem, 1.05vw, 1.0625rem);
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink-2);
  transition: border-color var(--sel-dur) var(--sel-ease),
              background-color var(--sel-dur) var(--sel-ease),
              color var(--sel-dur) var(--sel-ease);
}
.fin__row:hover { border-color: rgba(243, 238, 230, 0.20); color: var(--ink); }
.fin__row.is-lead {
  border-color: rgba(104, 156, 236, 0.55);
  background: rgba(28, 44, 82, 0.42);
  color: var(--ink);
}

.fin__ico {
  display: grid;
  place-items: center;
  color: var(--ink-3);
  transition: color var(--sel-dur) var(--sel-ease);
}
.fin__ico svg { display: block; }
.fin__row.is-lead .fin__ico { color: var(--accent); }
.fin__row:hover .fin__ico { color: var(--ink); }
.fin__row:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* Centred under the row, the wordmark over its label. */
/* The lockup shrinks as a PAIR — the wordmark comes down and the label
   loses weight and gains air. "Accepted" itself stays at 14px: it is the
   half of this line that carries the meaning, and typography's floor
   under functional text does not yield to a size request. */
.fin__tert {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--sp-2, 8px);
  text-decoration: none;
  font-family: var(--font-label);
  font-size: var(--t-micro);
  font-weight: 400;
  letter-spacing: 0.06em;
  color: var(--ink-3);
  opacity: 0.82;
  transition: opacity var(--dur-1) var(--ease-out);
}
.fin__tert:hover { opacity: 1; }
.fin__bitpay {
  block-size: 1.05rem;
  inline-size: auto;
  display: block;
  opacity: 0.62;
  transition: opacity var(--dur-1) var(--ease-out);
}
.fin__tert:hover .fin__bitpay { opacity: 1; }
.fin__tert:focus-visible { outline: 2px solid var(--ink); outline-offset: 6px; border-radius: 4px; }

@media (max-width: 900px) {
  .fin__card { min-block-size: 0; grid-template-rows: auto auto auto; row-gap: clamp(var(--sp-7), 8vw, var(--sp-8)); }
  .fin__bg { position: relative; grid-row: 2; aspect-ratio: 16 / 11; border-radius: 14px; overflow: hidden; }
  .fin__wash { display: none; }
  .fin__rows { grid-template-columns: minmax(0, 1fr); }
}


/* ---- SERVICE: the chapter gets a heading ---------------------
   It was the only chapter on the page carrying a 14px label where every
   other one carries a title, which is why it read as a list floating in
   a film rather than as a chapter. Promoted to the rung that already
   exists for exactly this kind of name — Featured Inventory's, which is
   also a section name rather than a claim — so nothing new is invented
   and the two sit on the same step. */
.svc__title {
  margin: 0 0 clamp(var(--sp-6), 3vw, var(--sp-7));
  font-size: clamp(2rem, 3.4vw, 3.0625rem);
  font-weight: 700;
  line-height: 1.05;
  letter-spacing: var(--tr-display);
  color: var(--ink);
  text-transform: none;
}

/* The two actions sit side by side rather than stacked. They were
   stretching to fill the register's width and wrapping; sized to their
   own content, they fit the column as a pair. */
/* One at each end of the register, not a huddled pair — the column's
   own width becomes the interval between them. */
.svc__actions,
.svc__actions:not(#x) {
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
}
.svc__actions .btn,
.svc__actions .btn:not(#x) { flex: 0 0 auto; }

@media (max-width: 700px) {
  .svc__actions .btn,
  .svc__actions .btn:not(#x) { flex: 1 1 100%; justify-content: center; }
}

/* The register opens just enough for the pair. 544 held two shorter
   labels; "Speak with our specialist" is wider than what it replaced, so
   the two needed 589 and were wrapping into a stack. Widened to the
   nearest value that fits them rather than shrinking the page's own
   control padding, which would have made this section's pill a
   different pill. */
.svc__register,
.svc__register:not(#x) { max-inline-size: min(600px, 50%); }

/* ---- The navigation reads at full ink ----------------------
   It sat at --ink-2, which is the page's SECONDARY text colour — right
   for a caption under a heading, dim for the only way around the site.
   The hover state does not lose its signal by this: it was never
   carrying the change on colour alone, it lights a background pill, and
   that still reads. */
.navbar__link,
.navmenu__trigger {
  color: var(--ink);
}

/* The proof line under the register is gone, so the actions take their
   distance from the list itself rather than inheriting the gap that was
   sized for a sentence in between. */
.svc__actions,
.svc__actions:not(#x) {
  margin-block-start: clamp(var(--sp-7), 3.4vw, var(--sp-8));
}

/* ---- The three cards: no default emphasis, hover carries it ----
   The lead card was highlighted at rest, which made the other two read
   as disabled rather than as alternatives. All three now sit at the same
   weight with the label at full ink; the card that is different is the
   one under the pointer.

   The reveal is the page's own stepped rise — the same rule and the same
   staircase that lifts Sell's steps, extended by selector. A new chapter
   does not get a new entrance. */
.fin__row,
.fin__row:not(#x) {
  color: var(--ink);
  border-color: rgba(243, 238, 230, 0.12);
  background: rgb(var(--bg-rgb) / 0.38);
}
.fin__ico,
.fin__ico:not(#x) { color: var(--ink-2); }

@media (hover: hover) {
  .fin__row:hover,
  .fin__row:hover:not(#x) {
    border-color: rgba(243, 238, 230, 0.28);
    background: rgb(var(--bg-rgb) / 0.62);
    translate: 0 -2px;
  }
  .fin__row:hover .fin__ico { color: var(--ink); }
}
.fin__row {
  transition: border-color var(--sel-dur) var(--sel-ease),
              background-color var(--sel-dur) var(--sel-ease),
              translate var(--dur-2) var(--ease-out),
              opacity var(--rv-cta) var(--ease-out);
}
.fin__row:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* The cards come down in height: the vertical padding steps back and the
   glyph sits closer to its label, so the pair reads as one unit instead
   of two things stacked in a tall box. Nothing about the width or the
   surface changes. */
.fin__row,
.fin__row:not(#x) {
  padding-block: clamp(var(--sp-5), 1.6vw, var(--sp-6));
  gap: var(--sp-3);
}

/* ---- The register's hover ----------------------------------
   Restrained on purpose: this is a list of six, and a loud state on each
   would turn a quiet register into a menu. The numeral comes up to full
   ink, the item shifts by the width of a hairline's worth of air, and
   nothing else moves. The colour change is the signal; the shift only
   confirms which row the cursor is on. */
.svc__n,
.svc__item { transition: color var(--sel-dur) var(--sel-ease),
                         translate var(--dur-2) var(--ease-out); }

@media (hover: hover) {
  .svc__row:hover .svc__n { color: var(--ink); }
  .svc__row:hover .svc__item { translate: 4px 0; }
}
.svc__row:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 4px;
  border-radius: 4px;
}
.svc__row:focus-visible .svc__n { color: var(--ink); }

/* ---- The cards come down again, and the hover softens -------
   TWO THINGS, and the second was a defect rather than taste. The
   transitions on these cards were written against --sel-dur, which is
   defined only inside .recs — Featured Inventory's list. Unresolved
   there, the whole shorthand entry was dropped, so the border and the
   ground were snapping instantly while only the lift animated. That is
   why it read hard. They use the page's own duration tokens now.

   With the timing real, the amplitude comes down: the hairline firms
   less, the ground lifts less, and the card rises 1px instead of 2, over
   the slow step rather than the quick one. */
.fin__row,
.fin__row:not(#x) {
  padding-block: clamp(var(--sp-4), 1.3vw, var(--sp-5));
}
/* The transition is declared WITHOUT the :not(#x) escalation on purpose.
   That trick carries an id's specificity, so writing the shorthand there
   outranked the reveal rules and their stagger — the cards all arrived
   together. Plain selector, so the staircase below can still win. */
/* Softer. --dur-3 is 520ms on the way in AND on the way out, which on a
   hover reads as the card lagging behind the pointer. The arrival is
   quicker than that and the departure slower, which is how a hover is
   supposed to feel: it answers immediately and lets go unhurriedly. */
.fin__row {
  transition: border-color 260ms var(--ease-out),
              background-color 260ms var(--ease-out),
              translate 260ms var(--ease-out),
              opacity var(--rv-cta) var(--ease-out);
}
.fin__row:hover {
  transition-duration: 170ms, 170ms, 170ms, var(--rv-cta);
}

@media (hover: hover) {
  /* Gentler at rest too: the edge lifts a little less far and the fill
     a little less, so the card brightens rather than switching on. */
  .fin__row:hover,
  .fin__row:hover:not(#x) {
    border-color: rgba(243, 238, 230, 0.17);
    background: rgb(var(--bg-rgb) / 0.48);
    translate: 0 -2px;
  }
}

/* The register's hover was written the same way and was equally dead. */
.svc__row,
.svc__n,
.svc__item {
  transition: color var(--dur-2) var(--ease-out),
              translate var(--dur-2) var(--ease-out);
}
.fin__ico,
.fin__ico:not(#x) { transition: color var(--dur-3) var(--ease-out); }

/* ---- The staircase, restored -------------------------------
   The three cards were arriving together. The cause was mine and it is
   worth naming: the block that softened the hover set `transition` as a
   SHORTHAND, and the shorthand resets transition-delay to 0. It sits
   below the reveal rules, so it silently wiped the stagger they had
   set — the rise still played, all three at once.

   The delays are declared on their own property here, after everything,
   so no later shorthand can take them again. They follow the copy: the
   label, the claim and the line land first, then the cards step in. */
.reveal-armed [data-reveal].is-revealed .fin__row:nth-child(1) { transition-delay: calc(var(--rv-step) * 2.2); }
.reveal-armed [data-reveal].is-revealed .fin__row:nth-child(2) { transition-delay: calc(var(--rv-step) * 2.9); }
.reveal-armed [data-reveal].is-revealed .fin__row:nth-child(3) { transition-delay: calc(var(--rv-step) * 3.6); }

/* ---- Financing's copy rises with its cards ------------------
   One staircase for the chapter: the label, the claim and the line step
   in from below, then the three cards continue the same climb. The rise
   is the page's existing role — the one Sell's steps and these cards
   already use — chosen over the headline's usual entrance from the left
   because this chapter is centred, and a line arriving sideways into a
   symmetrical composition fights it.

   Transitions are declared on plain selectors and the delays on their
   own property: the shorthand resets delay, and the file's :not(#x)
   escalation outranks the reveal rules. Both of those have already cost
   this section its stagger once. */
.reveal-armed [data-reveal] .fin__label,
.reveal-armed [data-reveal] .fin__title,
.reveal-armed [data-reveal] .fin__lede {
  opacity: 0;
  translate: 0 18px;
  will-change: opacity, translate;
}
.fin__label,
.fin__title,
.fin__lede {
  transition: opacity var(--rv-cta) var(--ease-out),
              translate var(--rv-cta) var(--ease-out);
}
.reveal-armed [data-reveal].is-revealed .fin__label,
.reveal-armed [data-reveal].is-revealed .fin__title,
.reveal-armed [data-reveal].is-revealed .fin__lede { opacity: 1; translate: 0 0; }

.reveal-armed [data-reveal].is-revealed .fin__label { transition-delay: 0ms; }
.reveal-armed [data-reveal].is-revealed .fin__title { transition-delay: calc(var(--rv-step) * 0.7); }
.reveal-armed [data-reveal].is-revealed .fin__lede  { transition-delay: calc(var(--rv-step) * 1.4); }

html.reveal-armed [data-reveal]:not(.is-revealed) .fin__label,
html.reveal-armed [data-reveal]:not(.is-revealed) .fin__title,
html.reveal-armed [data-reveal]:not(.is-revealed) .fin__lede {
  opacity: 0;
  translate: 0 18px;
}

/* ============================================================
   6 — WARRANTY / PROTECTION, rebuilt
   ============================================================
   The reference's structure in the page's own materials: bone ground,
   the project red, the two faces, the Material Symbols the section
   already carried, and every gap off the --sp-* scale. The mock's teal
   and its near-white are not carried across; a borrowed layout is not a
   licence to borrow a palette. */
/* ---- THE BAND ------------------------------------------------------
   Full-bleed frame, copy standing on its empty left. The band is sized
   by the picture's own 16:9 with a floor and a ceiling, so the copy
   never runs out of wall on a short viewport and the frame never
   swallows the page on a wide one. */
.warranty__band {
  position: relative;
  isolation: isolate;
  display: grid;
  align-items: center;
  /* The band was 777px against 400px of copy, which left 187px of empty
     concrete between the actions and the cards — the picture had stopped
     saying anything long before the section ended. Shorter, and the crop
     it costs is floor, not car. */
  min-block-size: clamp(30rem, 44vw, 46rem);
  /* ASYMMETRIC, AND THE HEADER IS WHY. Scrolled so the band's top meets
     the masthead, the header's own dark wash falls across the first
     ~110px of the section: measured under the eyebrow it took the ground
     to (170,169,169) and --accent-deep to 2.92:1, under the 4.5 a 15px
     label needs. The copy starts below that falloff now. The total is
     within 8px of the symmetric version, so the cards do not move. */
  padding-block: clamp(var(--sp-9), 9vw, var(--sp-11)) clamp(var(--sp-6), 2.6vw, var(--sp-7));
}
/* THE FRAME HANGS BELOW ITS FRAME. The picture's box is 166% of the
   band and anchored to its top, so the band shows the upper 60% of it
   and the cars fall to the middle of the composition instead of riding
   the headline. object-position alone could only move them 88px — the
   whole vertical crop a 16:9 master has against this box — and that was
   not enough.

   The overflow lives HERE rather than on .warranty__band, and the ramps
   with it: they have to measure against the band's own edges. Hung off
   the picture's box they would have sat 400px below the section. */
.warranty__bg { position: absolute; inset: 0; z-index: -1; overflow: clip; }
.warranty__bg img {
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  inline-size: 100%;
  block-size: 166%;
  object-fit: cover;
  /* Held right-of-centre: the cars are there and the empty concrete the
     copy needs is on the left, so as the band narrows it is the empty
     ground that gets eaten rather than the subjects. */
  /* Right-of-centre horizontally so the empty concrete the copy needs is
     what the crop eats. Vertically it is centred, because the hang above
     is what places the cars — object-position has no slack left to give
     once the box is taller than the picture. */
  object-position: 72% center;
  display: block;
}

/* ---- The two edges close into the bone -----------------------------
   The frame's concrete sits within a few values of --bone, so these
   ramps are not hiding a seam — they are letting the picture and the
   band's own ground read as one surface. Top and bottom both, because
   the section arrives on a wave cut and leaves into the three cards. */
.warranty__bg::before,
.warranty__bg::after {
  content: "";
  position: absolute;
  inset-inline: 0;
  pointer-events: none;
}
/* THE LEFT FIELD IS RAMPED, AND CONTRAST IS WHY. The frame's upper left
   is a wall in shadow at (145,143,144) — measured, --accent-deep on it
   is 2.44:1, and the eyebrow is 15px of text that needs 4.5. The ramp
   lifts that field to within reach of --bone, where this red is already
   known to sit at 4.78.

   It stops at 54%, short of the Porsche's nose, so it lightens the wall
   the copy stands on and never touches the cars. The top ramp rides in
   the same layer because both are doing the same job: closing the
   picture into the band's own ground. */
.warranty__bg::before {
  inset: 0;
  background:
    linear-gradient(to right,
      var(--bone) 0%,
      rgba(236, 228, 214, 0.88) 20%,
      rgba(236, 228, 214, 0.55) 36%,
      rgba(236, 228, 214, 0) 54%),
    linear-gradient(to bottom,
      var(--bone) 0%,
      rgba(236, 228, 214, 0.66) 9%,
      rgba(236, 228, 214, 0) 20%);
}
.warranty__bg::after {
  inset-block: auto 0;
  block-size: clamp(150px, 26%, 320px);
  background: linear-gradient(to top,
    var(--bone) 0%,
    rgba(236, 228, 214, 0.9) 30%,
    rgba(236, 228, 214, 0.55) 58%,
    rgba(236, 228, 214, 0) 100%);
}

.warranty__inner { position: relative; z-index: 1; }
.warranty__copy { max-inline-size: 34rem; }


/* The chapter has to END. With .warranty__inner's padding gone the cards
   were sitting on the section's own edge; this is the bone band's
   closing air, bottom-heavy the way every other section here is. */
.warranty__foot {
  position: relative;
  z-index: 1;
  padding-block-end: clamp(var(--sp-8), 6.5vw, var(--sp-10));
}

/* The eyebrow's separator is a mark, not a word — the middle dot takes
   the accent and the two halves stay in the label's own ink. */
.warranty__eyebrow,
.warranty__eyebrow:not(#x) {
  display: flex;
  align-items: center;
  gap: 0.6em;
  margin: 0 0 var(--gap-eyebrow);
  /* Red, because that is this page's convention for an eyebrow on the
     bone ground — Warranty's was red before the rebuild and Sell's still
     is. The reference tints it with its own accent; ours is --accent-deep,
     which is the value already measured at 4.78:1 here. */
  color: var(--accent-deep);
}
.warranty__eyebrow span[aria-hidden] { opacity: 0.55; }

.warranty__title,
.warranty__title:not(#x) { margin: 0 0 clamp(var(--sp-5), 2.2vw, var(--sp-6)); }
.warranty__title .ttl-line { display: block; }

/* THE MEASURE RESPECTS THE PICTURE. With the frame lowered, the Porsche's
   nose arrives at x=430 across exactly the rows this lede occupies, and
   at 46ch its first two lines ran onto it — measured 1.13:1, which is
   text on a black car. 33ch keeps every line inside the lit concrete.
   The alternative was pushing the left ramp out to cover the lede's full
   width, which would have washed the Porsche's front off the picture to
   protect a line that can simply be shorter. */
.warranty__lede,
.warranty__lede:not(#x) {
  margin: 0 0 clamp(var(--sp-7), 3.4vw, var(--sp-8));
  max-inline-size: 33ch;
}

.warranty__actions,
.warranty__actions:not(#x) {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-4);
}

@media (max-width: 900px) {
  /* Narrow, the empty concrete is the first thing the crop eats, so the
     band gets taller and the frame slides toward its left edge to keep
     wall under the copy. */
  .warranty__band { min-block-size: clamp(30rem, 82vw, 40rem); }
  .warranty__bg img { object-position: 62% center; }
}

/* ---- The three provisions, as cards ------------------------
   Raised rather than outlined, which is what the old panel already
   established on this ground: a border on bone draws a box, a lift
   reads as a surface. */
.wcards {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(var(--sp-5), 2vw, var(--sp-6));
}

.wcard {
  padding: clamp(var(--sp-6), 2.6vw, var(--sp-7));
  border-radius: var(--r-panel);
  background: rgba(255, 255, 255, 0.58);
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.7) inset,
              0 12px 28px -18px rgba(26, 20, 14, 0.35);
  transition: background-color var(--dur-3) var(--ease-out),
              box-shadow var(--dur-3) var(--ease-out),
              translate var(--dur-3) var(--ease-out);
}
/* On hover the card BRIGHTENS and lifts; the cast shadow does not grow
   with it. A drop shadow that deepens on hover is the decorative reading
   of a lift — it says "this is floating" when the only thing that needs
   saying is "this is the one you are on". The resting shadow stays, so
   the card is still seated on the bone; only the ground under the text
   and the 2px of travel answer the pointer. */
@media (hover: hover) {
  .wcard:hover {
    background: rgba(255, 255, 255, 0.82);
    box-shadow: 0 1px 0 rgba(255, 255, 255, 0.8) inset;
    translate: 0 -2px;
  }
}

/* The glyph sits on a quiet tile. The reference tints it with its accent;
   red is rationed on this page — it belongs to the eyebrow's mark and the
   primary action — so the tile is ink at low alpha and the glyph is ink. */
.wcard__ico {
  display: grid;
  place-items: center;
  inline-size: 42px;
  block-size: 42px;
  margin-block-end: clamp(var(--sp-5), 2.2vw, var(--sp-6));
  border-radius: 12px;
  background: rgba(26, 20, 14, 0.06);
  color: var(--bone-ink);
}
.wcard__ico svg { display: block; }

.wcard__h {
  margin: 0 0 var(--sp-3);
  font-family: var(--font-body);
  font-size: clamp(1rem, 1.15vw, 1.0625rem);
  font-weight: 600;
  line-height: 1.25;
  color: var(--bone-ink);
}
.wcard__p {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--bone-ink-2);
}

@media (max-width: 900px) {
  /* .warranty__grid is gone — the band is a single overlaid column. */
  .warranty__copy { max-inline-size: none; }
  .wcards { grid-template-columns: minmax(0, 1fr); }
}

/* ---- Entrance: the page's own staircase --------------------
   Copy first, then the image, then the three cards — one climb, the
   same rise the other chapters use. Plain selectors for the transition
   and a separate property for the delays, for the reasons already
   recorded in Financing. */
.reveal-armed [data-reveal] .wcard {
  opacity: 0;
  translate: 0 18px;
  will-change: opacity, translate;
}
/* THE GROUND FADES, IT DOES NOT RISE. The picture is now the band's
   background at inset: 0 — 18px of travel would open a strip of bare
   bone along its top edge for the length of the entrance. It was a
   figure in a column when that rise was written; it is the room now. */
.reveal-armed [data-reveal] .warranty__bg { opacity: 0; }
.warranty__bg,
.wcard {
  transition: opacity var(--rv-cta) var(--ease-out),
              translate var(--rv-cta) var(--ease-out),
              background-color var(--dur-3) var(--ease-out),
              box-shadow var(--dur-3) var(--ease-out);
}
.reveal-armed [data-reveal].is-revealed .warranty__bg { opacity: 1; }
.reveal-armed [data-reveal].is-revealed .wcard { opacity: 1; translate: 0 0; }

.reveal-armed [data-reveal].is-revealed .warranty__bg { transition-delay: calc(var(--rv-step) * 1.6); }
.reveal-armed [data-reveal].is-revealed .wcard:nth-child(1) { transition-delay: calc(var(--rv-step) * 2.6); }
.reveal-armed [data-reveal].is-revealed .wcard:nth-child(2) { transition-delay: calc(var(--rv-step) * 3.3); }
.reveal-armed [data-reveal].is-revealed .wcard:nth-child(3) { transition-delay: calc(var(--rv-step) * 4); }

html.reveal-armed [data-reveal]:not(.is-revealed) .warranty__bg { opacity: 0; }
html.reveal-armed [data-reveal]:not(.is-revealed) .wcard {
  opacity: 0;
  translate: 0 18px;
}

/* ---- The warranty card's hover, completed -------------------
   The ground and the shadow were already responding; the LIFT was not.
   `.wcard:hover` sets translate at two class-weights, and the revealed
   state pins it to 0 at four — the reveal was quietly cancelling the
   only part of the state that moves. Declared inside the same revealed
   context here so it can win, rather than by escalating the plain hover
   and starting a specificity war with the entrance.

   The glyph answers too: its tile warms and the mark goes to the accent,
   which is the one place red is spent inside these cards. */
@media (hover: hover) {
  .reveal-armed [data-reveal].is-revealed .wcard:hover,
  .wcard:hover { translate: 0 -3px; }

  .wcard:hover .wcard__ico {
    background: rgba(188, 15, 13, 0.10);
    color: var(--accent-deep);
  }
}
.wcard__ico {
  transition: background-color var(--dur-3) var(--ease-out),
              color var(--dur-3) var(--ease-out);
}
.wcard:focus-within { translate: 0 -3px; }

/* ============================================================
   10 — KARMA · AUTHORIZED DEALER
   ============================================================
   The shortest chapter on the page and the quietest of the tail. One
   mark, one line, one route — a brand inside the brand needs scale and
   air, not a feature grid. */
.karma {
  position: relative;
  isolation: isolate;
  overflow: clip;
  /* THE GROUND CHANGES COLOUR TWICE ON THE WAY DOWN, because this
     chapter has a different neighbour at each end. About above it is
     --bg; the tail below it is --bg-black; the chapter itself is the
     page's last --bg-sunk. So the ground starts on About's colour, sinks
     to its own through the upper quarter, and ramps out to the tail's
     over the lower half. Neither seam is a line because at each one the
     two sides are the same value. */
  background: linear-gradient(to bottom,
    var(--bg) 0%,
    var(--bg-sunk) 24%,
    var(--bg-sunk) 50%,
    var(--bg-black) 100%);
  color: var(--ink);
  text-align: center;
  /* Tall on purpose. This chapter's content is a mark, two short lines
     and one button, and the film behind them is the rest of it — at the
     page's ordinary section height the video read as a texture on a
     strip rather than as a room. --sp-12 exists for this. */
  padding-block: clamp(var(--sp-10), 16vw, var(--sp-12));
}
/* ---- the film ------------------------------------------------------
   Karma's headlight footage, dimmed hard. Two layers do the dimming and
   they do different jobs: the video's own opacity takes the whole frame
   down so the brightest specular on the lens stops competing with the
   wordmark, and the wash above it is a vertical ramp that closes the
   chapter's top and bottom edges into the ground — the same device the
   Service band uses horizontally, turned ninety degrees because this
   chapter is centred and its seams are above and below.

   The section clips it, so the film respects the rounded top corners the
   chapter already had; without that the video would square them off. */
.karma__film {
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
}
.karma__video {
  position: absolute;
  inset: 0;
  inline-size: 100%;
  block-size: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
  opacity: 0.30;
}
.karma__wash {
  position: absolute;
  inset: 0;
  /* The wash follows the ground, and it is OPAQUE at both ends. At 0% it
     is solid --bg and at 97% solid --bg-black, so the film has not begun
     at the top edge and has already finished at the bottom one — the
     video never reaches either seam, which is what makes both joins
     invisible rather than merely close. */
  background:
    linear-gradient(to bottom,
      rgb(var(--bg-rgb) / 1) 0%,
      rgb(var(--bg-rgb) / 0.86) 7%,
      rgb(7 11 19 / 0.55) 26%,
      rgb(var(--bg-black-rgb) / 0.42) 55%,
      rgb(var(--bg-black-rgb) / 0.88) 84%,
      rgb(var(--bg-black-rgb) / 1) 97%),
    /* a little extra under the centred lockup, where every glyph lands */
    radial-gradient(58% 62% at 50% 52%, rgb(7 11 19 / 0.52), transparent 72%);
}

.karma__inner { display: grid; justify-items: center; position: relative; }
.karma__logo { display: block; }
.karma__mark {
  display: block;
  inline-size: clamp(150px, 17vw, 260px);
  block-size: auto;
}
.karma__label,
.karma__label:not(#x) {
  /* The space ABOVE stays generous: it separates the caption from the
     Karma mark, and an external gap must beat the internal one. Below is
     the page's single eyebrow gap, so "Authorized dealer" reads as the
     eyebrow to the claim rather than as a free-floating line. */
  margin: clamp(var(--sp-5), 2.2vw, var(--sp-6)) 0 var(--gap-eyebrow);
  color: var(--ink-2);
}
.karma__title {
  margin: 0 0 clamp(var(--sp-7), 3.4vw, var(--sp-8));
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2rem, 3.4vw, 3.0625rem);
  line-height: 1.05;
  letter-spacing: var(--tr-display);
}
.karma__act { display: flex; }

/* ============================================================
   THE EYEBROW, EVERYWHERE
   ============================================================
   Every chapter's opening label resolves here and nowhere else — the
   same argument as --gap-eyebrow, which fixed the space between the
   eyebrow and the title while leaving the eyebrow itself in eight
   different states. Size, weight, tracking and ink are the token's; only
   the colour splits, and only because red on the dark ground measures
   3.41:1 and cannot carry 15px of text.

   Escalated with :not(#x) because several of these already carried it
   and would otherwise win against a plain selector. */
.brk__eyebrow:not(#x),
.warranty__eyebrow:not(#x),
.fin__label:not(#x),
.karma__label:not(#x),
.about__eyebrow:not(#x),
.ig__eyebrow:not(#x),
.revs__eyebrow:not(#x),
.locs__eyebrow:not(#x) {
  font-family: var(--font-label);
  font-size: var(--t-eyebrow);
  font-weight: var(--fw-eyebrow);
  letter-spacing: var(--tr-eyebrow);
  text-transform: uppercase;
  line-height: var(--lh-label);
}
/* Dark grounds: --ink-2 was 0.62 and read as small print. */
.brk__eyebrow:not(#x),
.karma__label:not(#x),
.about__eyebrow:not(#x),
.ig__eyebrow:not(#x),
.revs__eyebrow:not(#x),
.locs__eyebrow:not(#x) { color: var(--ink-eyebrow); }
/* Bone grounds keep the red — it is the page's convention there, and at
   600 it is the weight that makes it solid rather than the value. */
.warranty__eyebrow:not(#x),
.fin__label:not(#x) { color: var(--accent-deep); }

/* ============================================================
   THE DIRTY-BLUE BLOOM
   ============================================================
   A lighting event, not a panel. Three overlapping ellipses at different
   radii and offsets, so the outline never resolves into a circle, then
   blurred — the blur is what makes it read as light in the room rather
   than as a shape someone drew.

   The blue is dirty on purpose: a steel blue pulled toward grey, so it
   sits under the deep navy ground as a bloom instead of arriving as a
   second brand colour. It is under everything (z-index -1, inside the
   section's own stacking context) and lights nothing that carries text
   at speed.

   The section clips it on BOTH axes, so a bloom belongs to its chapter
   and stops at the seam. Bleeding across the join sounded right and read
   wrong: About's light was climbing into Karma and putting a second,
   unexplained glow on a section that had none.

   The clip alone was not enough. Measured at the About/Instagram seam —
   same ground on both sides — the cut stepped 8 levels in one pixel row,
   because blur() spreads ink 44px past the bloom's own box and the
   section was slicing straight through that ramp. So the bloom carries
   its own vertical mask.

   The mask is a percentage of the BLOOM, so the bloom's height has to be
   a percentage of the SECTION for the two to line up — at 132% the fade
   was still 60% opaque where the section ended and the step came back.
   Vertical size is therefore fixed at 100%: taller buys nothing, since
   anything past the section edge is cropped anyway. A chapter grows its
   bloom on the WIDTH, which runs off the page and costs no seam.

   One rule, two consumers: [data-bloom="left"] and ="right". Do not
   author a second one per section. */
[data-bloom] { position: relative; isolation: isolate; overflow: clip; }
[data-bloom]::before {
  content: "";
  position: absolute;
  z-index: -1;
  pointer-events: none;
  inline-size: var(--bloom-w, clamp(420px, 46vw, 780px));
  block-size:  100%;
  inset-block-start: var(--bloom-y, 46%);
  translate: 0 -50%;
  /* Size and strength are the two things a chapter is allowed to tune;
     the lobes, the blue and the blur stay shared, or it stops being one
     light and becomes two effects. */
  opacity: var(--bloom-opacity, 1);
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 15%, #000 85%, transparent 100%);
          mask-image: linear-gradient(to bottom, transparent 0, #000 15%, #000 85%, transparent 100%);
  background:
    radial-gradient(52% 58% at 34% 38%, rgba(63, 94, 142, 0.56), transparent 66%),
    radial-gradient(40% 34% at 72% 24%, rgba(48, 72, 116, 0.48), transparent 70%),
    radial-gradient(32% 46% at 58% 80%, rgba(40, 60, 100, 0.44), transparent 68%),
    radial-gradient(26% 22% at 18% 70%, rgba(70, 100, 148, 0.34), transparent 72%),
    radial-gradient(22% 30% at 84% 58%, rgba(44, 66, 106, 0.30), transparent 74%);
  filter: blur(44px);
}
[data-bloom="left"]::before   { inset-inline-start: clamp(-340px, -19vw, -150px); }
[data-bloom="right"]::before  { inset-inline-end:   clamp(-340px, -19vw, -150px); }
/* Centred, the bloom stops being a light coming from off-page and becomes
   the ground the chapter sits on. It has to grow and dim to do that job:
   at the side's strength it would read as a spotlight behind the cards. */
[data-bloom="center"]::before { inset-inline-start: 50%; translate: -50% -50%; }

/* ============================================================
   9 — ABOUT US
   ============================================================
   CMC's own words, and the only long-form prose on the page. Two masses:
   the claim on the left, the paragraphs on the right at a read measure.
   The chapter has no action — it states who they are and hands the reader
   on. That also keeps the tail's control count at one per chapter. */
.about {
  /* The corner belongs to whichever dark chapter meets the bone Service
     band, and after the reorder that is this one. Karma sits between two
     dark chapters now and needs no edge. */
  border-start-start-radius: var(--r-section);
  border-start-end-radius: var(--r-section);
  --bloom-y: 50%;
  --bloom-w: clamp(700px, 80vw, 1340px);
  --bloom-opacity: 0.46;
  background: var(--bg);
  color: var(--ink);
  padding-block: clamp(var(--sp-9), 7.5vw, var(--sp-11)) clamp(var(--sp-9), 7vw, var(--sp-10));
}
/* TWO COLUMNS, TWO ROWS. The claim and the prose share the top row; the
   contact block and the action share the bottom one, which is the whole
   reason the action can sit level with the social marks instead of
   floating under the prose on its own. Row 2's column gap is the same as
   row 1's, so the button's left edge lands on the prose's. */
.about__inner {
  display: grid;
  grid-template-columns: minmax(0, 0.86fr) minmax(0, 1fr);
  column-gap: clamp(var(--sp-7), 5vw, var(--sp-9));
  row-gap: clamp(var(--sp-7), 4vw, var(--sp-9));
  align-items: start;
}
/* The action drops to the FOOT of its row so it lines up with the last
   thing in the contact block — the icons — rather than with the first,
   which is the address. And it sits at the RIGHT of its column, on the
   prose's right edge: the chapter reads left-to-right across two masses,
   so the one control belongs at the far end of the second, not tucked
   back under the start of it. */
.about__act {
  align-self: end;
  display: flex;
  justify-content: flex-end;
}
/* Red is a fill-and-rule colour on this ground — --accent measures 3.41:1
   against --bg, under AA for a 14px label — so the dark-ground eyebrows
   are --ink-2, as on Instagram, Reviews and Locations. */
.about__eyebrow,
.about__eyebrow:not(#x) { margin: 0 0 var(--gap-eyebrow); }
.about__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.25rem, 4vw, 3.75rem);
  line-height: 1.02;
  letter-spacing: var(--tr-display);
}
.about__title .ttl-line { display: block; }

/* I1 in one block: the address's two lines are tightest, the phone sits
   a step off it, the marks a step further, and the whole group sits a
   long way under the claim. Internal gaps rise; the external gap beats
   all of them. */
/* The grid's row-gap carries this now; a margin here would double it. */
/* ONE ICON COLUMN, ONE TEXT COLUMN. The address and the phone share a
   fixed 15px gutter so their glyphs sit on one vertical and their words
   sit on another — without it the phone started 21px left of the street
   and the block read as two unrelated lines. The pin and the handset are
   different widths, so the width is the COLUMN's, not the glyph's, and
   each mark centres inside it. */
.about__ico {
  flex: none;
  inline-size: 15px;
  display: grid;
  place-items: center;
  /* aligned to the first LINE, not to the block's centre — a two-line
     address would otherwise float the mark into the gap between them */
  block-size: 1.55em;
  color: var(--accent);
}
.about__ico svg { display: block; }

.about__addr,
.about__addr:not(#x) {
  display: flex;
  gap: var(--sp-3);
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--ink-2);
}
.about__tel {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  margin-block-start: var(--sp-4);
  font-family: var(--font-label);
  font-size: var(--t-micro);
  letter-spacing: var(--tr-label);
  color: var(--ink);
  text-decoration: none;
  transition: color var(--dur-2) var(--ease-out);
}
.about__tel:hover { color: var(--accent); }
.about__tel .about__ico { block-size: 1.4em; }
.about__socials {
  display: flex;
  gap: var(--sp-3);
  margin-block-start: clamp(var(--sp-6), 2.4vw, var(--sp-7));
}

.about__body { max-inline-size: 62ch; }
/* I1: the gap between paragraphs is smaller than the gap to anything
   outside the block, so the two read as one voice rather than two items. */
.about__p {
  margin: 0;
  font-size: var(--t-lede);
  line-height: var(--lh-body);
  color: var(--ink-2);
  text-wrap: pretty;
}
.about__p + .about__p { margin-block-start: clamp(var(--sp-5), 2vw, var(--sp-6)); }

@media (max-width: 900px) {
  .about__inner { grid-template-columns: minmax(0, 1fr); }
}

/* ============================================================
   11 — INSTAGRAM
   ============================================================
   A contact sheet rather than a grid: three cell shapes and a row that
   runs off the edge, so it reads as a feed passing through rather than
   as a tile set that happens to be square. */
.ig {
  background: var(--bg-black);
  color: var(--ink);
  padding-block: clamp(var(--sp-9), 7vw, var(--sp-10));
  overflow: hidden;
}
.ig__head {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: var(--sp-6);
  margin-block-end: clamp(var(--sp-7), 3.4vw, var(--sp-8));
}
.ig__eyebrow,
.ig__eyebrow:not(#x) { margin: 0 0 var(--gap-eyebrow); }
.ig__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.75rem, 3vw, 2.75rem);
  line-height: 1.05;
  letter-spacing: var(--tr-display);
}

.ig__sheet {
  list-style: none;
  margin: 0;
  padding-inline: var(--gutter);
  display: flex;
  gap: clamp(var(--sp-4), 1.4vw, var(--sp-5));
  overflow-x: auto;
  scroll-snap-type: x proximity;
  scroll-padding-inline: var(--gutter);
  scrollbar-width: none;
  align-items: start;
  --ig-h: clamp(190px, 16.7vw, 280px);
}
/* Draggable: the cursor says so, and the images stop being drag targets
   of their own — a native image drag would otherwise start a file drag
   two pixels into the gesture. */
[data-drag-scroll] { cursor: grab; }
[data-drag-scroll] img { -webkit-user-drag: none; user-select: none; }
[data-drag-scroll].is-dragging { cursor: grabbing; scroll-snap-type: none; scroll-behavior: auto; }
[data-drag-scroll].is-dragging * { pointer-events: none; }
.ig__sheet::-webkit-scrollbar { display: none; }

/* ONE HEIGHT, ONE WIDTH, AND THE WIDTH IS THE NARROW ONE. The frames are
   3:2 masters cropped to 5:4 in the cell — narrower than the picture, so
   the row reads as a column of portraits passing by rather than as a
   filmstrip of landscapes. object-position is centred: these are
   turntable shots with the car dead centre, so a centre crop takes the
   background off both ends and leaves the subject whole.

   The reel comes along: at 9:16 it fills the 5:4 cell on width and the
   crop takes the top and bottom, which is the frame it was shot for.

   Masters are 640px and the cell is 300px wide at 1440 — everything
   downscales. */
.ig__cell {
  position: relative;
  flex: none;
  block-size: var(--ig-h);
  inline-size: calc(var(--ig-h) * 0.8);
  border-radius: var(--r-panel);
  overflow: hidden;
  scroll-snap-align: start;
  background: var(--bg-raised-black);
}
.ig__cell img {
  block-size: 100%;
  inline-size: 100%;
  object-fit: cover;
  object-position: center;
  display: block;
  transition: scale var(--dur-4) var(--ease-out), opacity var(--dur-3) var(--ease-out);
  opacity: 0.86;
}

/* ---- the mark on each frame ----------------------------------------
   Small, top right, and carrying its own shadow rather than a scrim:
   these frames run from a bright showroom wall to a black curtain, and a
   plate dark enough for the wall would sit on the curtain like a sticker.
   A drop-shadow on the glyph reads on both. */
.ig__tag {
  position: absolute;
  inset-block-start: var(--sp-3);
  inset-inline-end: var(--sp-3);
  display: block;
  color: #fff;
  opacity: 0.9;
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.55));
  pointer-events: none;
  transition: opacity var(--dur-3) var(--ease-out);
}
.ig__tag svg { display: block; }
.ig__cell:hover .ig__tag { opacity: 1; }
@media (hover: hover) {
  .ig__cell:hover img { scale: 1.04; opacity: 1; }
}

/* ============================================================
   12 — REVIEWS
   ============================================================
   Google-style cards, one per platform CMC's own testimonials page
   aggregates. The card is the page's existing raised panel — the same
   object the inventory rail and the warranty cards are made of — so the
   only new thing here is the star row and the avatar disc.

   The stars are --ink. Amber is the detail that makes a review block read
   as a stock dealership template, and at 14px the five-pointed shape
   already says "rating" without help from colour.

   The header repeats the Instagram header exactly: eyebrow, title, one
   outline action on the right. Repetition across a page is rhythm; this
   is the tail's second use of a grammar the page already owns. The title
   sits below Locations' scale on purpose — the last big statement on the
   page belongs to the four floors, not to this. */
.revs {
  --bloom-y: 50%;
  --bloom-w: clamp(640px, 84vw, 1420px);
  --bloom-opacity: 0.52;
  background: var(--bg-black);
  color: var(--ink);
  padding-block: clamp(var(--sp-8), 5.5vw, var(--sp-10));
}
.revs__head {
  display: flex;
  flex-wrap: wrap;
  align-items: end;
  justify-content: space-between;
  gap: var(--sp-6);
  margin-block-end: clamp(var(--sp-7), 3.4vw, var(--sp-8));
}
.revs__eyebrow,
.revs__eyebrow:not(#x) { margin: 0 0 var(--gap-eyebrow); }
.revs__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.625rem, 2.6vw, 2.375rem);
  line-height: 1.05;
  letter-spacing: var(--tr-display);
}

/* A rail, not a grid. Same mechanism as the Instagram sheet — scroll-snap
   on a horizontal track — so the page gains a page-turn without gaining a
   second carousel language. Three cards per view; the snap points sit on
   every third card so a swipe turns a PAGE, not a card.

   --revs-per is the one number the script reads to build the dots, and
   the breakpoints below are the only places it changes. */
.revs__list {
  --revs-per: 3;
  --revs-gap: clamp(var(--sp-5), 1.8vw, var(--sp-6));
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - (var(--revs-per) - 1) * var(--revs-gap)) / var(--revs-per));
  gap: var(--revs-gap);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
  /* the cards lift on hover; the track must not clip the lift */
  padding-block: 2px;
}
.revs__list::-webkit-scrollbar { display: none; }
.rv { display: flex; }
.rv:nth-child(3n + 1) { scroll-snap-align: start; }

@media (prefers-reduced-motion: reduce) { .revs__list { scroll-behavior: auto; } }

/* ---- the dots ------------------------------------------------------
   Built by script from --revs-per, so they cannot disagree with the
   layout. Nothing is hidden without them: no script means no dots and a
   rail you can still push. */
.revs__dots {
  display: flex;
  justify-content: center;
  gap: var(--sp-3);
  margin-block-start: clamp(var(--sp-6), 2.6vw, var(--sp-7));
}
.revs__dots:empty { display: none; }
.revs__dot {
  inline-size: 7px;
  block-size: 7px;
  padding: 0;
  border: 0;
  border-radius: 50%;
  background: rgba(243, 238, 230, 0.26);
  cursor: pointer;
  transition: background-color var(--dur-2) var(--ease-out),
              scale var(--dur-2) var(--ease-out);
}
.revs__dot:hover { background: rgba(243, 238, 230, 0.5); }
.revs__dot[aria-selected="true"] { background: var(--ink); scale: 1.15; }
/* The tap target is 44px even though the dot is 7px. */
.revs__dot::before { content: ""; position: absolute; inset: -18px; }
.revs__dot { position: relative; }
.rv__card {
  display: flex;
  flex-direction: column;
  gap: var(--sp-5);
  inline-size: 100%;
  padding: clamp(var(--sp-6), 2.4vw, var(--sp-7));
  border: 1px solid var(--rule);
  border-radius: var(--r-panel);
  background: var(--bg-raised-black);
  color: inherit;
  text-decoration: none;
  transition: border-color var(--dur-3) var(--ease-out),
              background-color var(--dur-3) var(--ease-out);
}

.rv__head { display: flex; align-items: center; gap: var(--sp-4); }
/* A disc with an initial, not a photograph. The reviewers are real people
   and none of them gave this page a face to use. */
.rv__avatar {
  flex: none;
  inline-size: 40px;
  block-size: 40px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: rgba(243, 238, 230, 0.10);
  font-family: var(--font-label);
  font-size: 0.9375rem;
  color: var(--ink);
}
.rv__who { min-inline-size: 0; }
.rv__name,
.rv__name:not(#x) {
  margin: 0;
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: -0.01em;
}
.rv__meta,
.rv__meta:not(#x) {
  margin: var(--sp-1) 0 0;
  font-family: var(--font-label);
  font-size: var(--t-micro);
  letter-spacing: var(--tr-label);
  color: var(--ink-3);
}

.rv__stars { display: flex; gap: 3px; color: var(--ink); }
.rv__stars svg { fill: currentColor; display: block; }

.rv__text,
.rv__text:not(#x) {
  margin: 0;
  font-size: 0.9375rem;
  line-height: 1.62;
  color: var(--ink-2);
  text-wrap: pretty;
}

@media (hover: hover) {
  .rv__card:hover {
    border-color: rgba(243, 238, 230, 0.26);
    background: rgba(243, 238, 230, 0.055);
  }
}
.rv__card:focus-visible { border-color: rgba(243, 238, 230, 0.26); }

@media (max-width: 1000px) {
  .revs__list { --revs-per: 2; }
  .rv:nth-child(3n + 1) { scroll-snap-align: none; }
  .rv:nth-child(2n + 1) { scroll-snap-align: start; }
}
@media (max-width: 680px) {
  .revs__list { --revs-per: 1; }
  .rv { scroll-snap-align: start; }
}

/* ============================================================
   13 — LOCATIONS · FINAL CTA
   ============================================================
   A register of four peers on hairlines — the grammar the page already
   uses for a list — and no bottom rule on the last one, so the chapter
   runs out into the footer instead of closing against it. */
.locs {
  background: var(--bg-black);
  color: var(--ink);
  padding-block: clamp(var(--sp-9), 7vw, var(--sp-10)) clamp(var(--sp-8), 5vw, var(--sp-9));
}
.locs__head { margin-block-end: clamp(var(--sp-8), 4vw, var(--sp-9)); }
.locs__eyebrow,
.locs__eyebrow:not(#x) { margin: 0 0 var(--gap-eyebrow); }
.locs__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.25rem, 4vw, 3.75rem);
  line-height: 1.02;
  letter-spacing: var(--tr-display);
}
.locs__title .ttl-line { display: block; }

.locs__list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  /* Five, since Newport Beach joined. At 1440 that is 265px a column
     and the longest address line — "West Chicago, IL 60185" — sets at
     165px, so the register still reads as five peers rather than five
     squeezed ones. */
  grid-template-columns: repeat(5, minmax(0, 1fr));
  gap: 0;
}
.loc {
  padding: clamp(var(--sp-6), 2.6vw, var(--sp-7)) clamp(var(--sp-5), 2vw, var(--sp-6));
  border-block-start: 1px solid var(--rule);
}
.loc:first-child { padding-inline-start: 0; }
.loc + .loc { border-inline-start: 1px solid var(--rule); }
.loc__city {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  margin: 0 0 var(--sp-4);
  font-family: var(--font-body);
  font-size: clamp(1.0625rem, 1.2vw, 1.1875rem);
  font-weight: 600;
  letter-spacing: -0.01em;
}
/* The pin is a mark, not a bullet: it takes the accent, which is one of
   this palette's stated jobs for red — a small locating mark on a dark
   ground, never a run of text. */
.loc__pin { flex: none; color: var(--accent); }

.loc__addr {
  margin: 0 0 var(--sp-5);
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--ink-2);
}
.loc__tel {
  font-family: var(--font-label);
  font-size: var(--t-micro);
  letter-spacing: var(--tr-label);
  color: var(--ink);
  text-decoration: none;
  transition: color var(--dur-2) var(--ease-out);
}
.loc__tel:hover { color: var(--accent); }

/* ---- THE ONE THAT IS NOT OPEN YET ---------------------------------
   A showroom you cannot drive to should not look like four you can, and
   it should not carry an address to prove it. So this row says where and
   says when, and nothing else — the difference is carried by the pin
   dropping out of the accent and by a status line standing where the
   phone number stands on its neighbours.

   Same type as .loc__tel, deliberately: it occupies that slot in the
   register, so it should sit on that line. It is not a link because
   there is nowhere yet to send anyone. */
.loc--soon .loc__pin { color: var(--ink-3); }
/* "Opening soon" sits on the address's SECOND line, where the other four
   put their city and ZIP. That is what puts the phone on the same line as
   theirs — the row was one line short and floating a step high. Quieter
   than the address around it, so it still reads as a status rather than
   as a street. */
.loc__soon { color: var(--ink-3); }

/* The nav's version of the same fact, at label scale. */
.navmenu__soon {
  margin-inline-start: 0.5em;
  font-size: var(--t-micro);
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--ink-3);
}

/* ============================================================
   14 — FOOTER
   ============================================================ */
.foot {
  background: var(--bg-black);
  color: var(--ink);
  padding-block: clamp(var(--sp-8), 5vw, var(--sp-9)) clamp(var(--sp-7), 4vw, var(--sp-8));
  border-block-start: 1px solid var(--rule);
}
.foot__mark { display: inline-block; color: var(--ink-2); }
.foot__wm {
  display: block;
  inline-size: clamp(160px, 15vw, 220px);
  block-size: auto;
  fill: currentColor;
  stroke: none;
  transition: color var(--dur-2) var(--ease-out);
}
.foot__mark:hover { color: var(--ink); }

.foot__nav {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(var(--sp-6), 3vw, var(--sp-7));
  margin-block: clamp(var(--sp-7), 4vw, var(--sp-8));
}
.foot__col { display: flex; flex-direction: column; align-items: flex-start; gap: var(--sp-3); }
.foot__h,
.foot__h:not(#x) { margin: 0 0 var(--sp-2); color: var(--ink-3); }
.foot__col a {
  font-size: 0.9375rem;
  color: var(--ink-2);
  text-decoration: none;
  transition: color var(--dur-2) var(--ease-out);
}
.foot__col a:hover { color: var(--ink); }

/* ---- the social row ------------------------------------------------
   Four marks, no labels. It sits ABOVE the legal rule rather than in the
   base line, so the last thing on the page is the copyright and not a
   row of buttons. The word "Instagram" is gone from here: the feed
   chapter already says it at 43px, and a text link repeating a link the
   page has just made is the footer padding itself out. */
.foot__socials {
  display: flex;
  gap: var(--sp-3);
  margin-block-end: clamp(var(--sp-6), 2.6vw, var(--sp-7));
}
/* .soc is the mark itself and is shared — the footer and About use the
   same object, because two rows of social icons on one page that do not
   match is exactly the kind of drift the eyebrow gap just got fixed for.
   The wrappers (.foot__socials, .about__socials) do the layout; the mark
   is defined once. */
.soc {
  display: grid;
  place-items: center;
  inline-size: 38px;
  block-size: 38px;
  border: 1px solid var(--rule);
  border-radius: 50%;
  color: var(--ink-2);
  transition: color var(--dur-2) var(--ease-out),
              border-color var(--dur-2) var(--ease-out);
}
.soc svg { display: block; }
.soc:hover { color: var(--ink); border-color: rgba(243, 238, 230, 0.3); }

.foot__base {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-5);
  padding-block-start: clamp(var(--sp-6), 2.6vw, var(--sp-7));
  border-block-start: 1px solid var(--rule);
}
.foot__legal,
.foot__by { margin: 0; font-size: 0.875rem; color: var(--ink-3); }

@media (max-width: 900px) {
  .locs__list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* Five into two leaves a widow; the fifth takes the full row. */
  .loc:last-child:nth-child(odd) { grid-column: 1 / -1; }
  .foot__nav { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 620px) {
  .locs__list, .foot__nav { grid-template-columns: minmax(0, 1fr); }
  .loc + .loc { border-inline-start: 0; }
}

/* ---- The tail joins the page's staircase -------------------- */
.reveal-armed [data-reveal] .karma__logo,
.reveal-armed [data-reveal] .about__contact,
.reveal-armed [data-reveal] .about__p,
.reveal-armed [data-reveal] .ig__cell,
.reveal-armed [data-reveal] .rv,
.reveal-armed [data-reveal] .loc {
  opacity: 0;
  translate: 0 18px;
}
.karma__logo, .about__contact, .about__p, .ig__cell, .rv, .loc {
  transition: opacity var(--rv-cta) var(--ease-out),
              translate var(--rv-cta) var(--ease-out);
}
.reveal-armed [data-reveal].is-revealed .karma__logo,
.reveal-armed [data-reveal].is-revealed .about__contact,
.reveal-armed [data-reveal].is-revealed .about__p,
.reveal-armed [data-reveal].is-revealed .ig__cell,
.reveal-armed [data-reveal].is-revealed .rv,
.reveal-armed [data-reveal].is-revealed .loc { opacity: 1; translate: 0 0; }

.reveal-armed [data-reveal].is-revealed .about__p:nth-of-type(1) { transition-delay: calc(var(--rv-step) * 1.4); }
.reveal-armed [data-reveal].is-revealed .about__p:nth-of-type(2) { transition-delay: calc(var(--rv-step) * 2.0); }
.reveal-armed [data-reveal].is-revealed .about__contact { transition-delay: calc(var(--rv-step) * 2.6); }
.reveal-armed [data-reveal].is-revealed .rv:nth-child(1) { transition-delay: calc(var(--rv-step) * 1.4); }
.reveal-armed [data-reveal].is-revealed .rv:nth-child(2) { transition-delay: calc(var(--rv-step) * 1.9); }
.reveal-armed [data-reveal].is-revealed .rv:nth-child(3) { transition-delay: calc(var(--rv-step) * 2.4); }

.reveal-armed [data-reveal].is-revealed .ig__cell:nth-child(1) { transition-delay: calc(var(--rv-step) * 1.4); }
.reveal-armed [data-reveal].is-revealed .ig__cell:nth-child(2) { transition-delay: calc(var(--rv-step) * 1.8); }
.reveal-armed [data-reveal].is-revealed .ig__cell:nth-child(3) { transition-delay: calc(var(--rv-step) * 2.2); }
.reveal-armed [data-reveal].is-revealed .ig__cell:nth-child(n+4) { transition-delay: calc(var(--rv-step) * 2.6); }
.reveal-armed [data-reveal].is-revealed .loc:nth-child(1) { transition-delay: calc(var(--rv-step) * 1.6); }
.reveal-armed [data-reveal].is-revealed .loc:nth-child(2) { transition-delay: calc(var(--rv-step) * 2.1); }
.reveal-armed [data-reveal].is-revealed .loc:nth-child(3) { transition-delay: calc(var(--rv-step) * 2.6); }
.reveal-armed [data-reveal].is-revealed .loc:nth-child(4) { transition-delay: calc(var(--rv-step) * 3.1); }

html.reveal-armed [data-reveal]:not(.is-revealed) .karma__logo,
html.reveal-armed [data-reveal]:not(.is-revealed) .about__contact,
html.reveal-armed [data-reveal]:not(.is-revealed) .about__p,
html.reveal-armed [data-reveal]:not(.is-revealed) .ig__cell,
html.reveal-armed [data-reveal]:not(.is-revealed) .rv,
html.reveal-armed [data-reveal]:not(.is-revealed) .loc {
  opacity: 0;
  translate: 0 18px;
}

/* ---- The mobile menu's groups, collapsed ----
   Everything here hangs off .menu-collapsible, which only the script
   adds — so with no script the panels do not exist and the menu is the
   full list it has always been. */
.menu-collapsible .menu__trigger {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  width: 100%;
  padding-block: var(--sp-4);
  appearance: none;
  background: none;
  border: 0;
  border-block-end: 1px solid var(--rule);
  text-align: start;
  cursor: pointer;
  color: var(--ink-2);
  transition: color var(--dur-2) var(--ease-out);
}
.menu-collapsible .menu__trigger:hover,
.menu-collapsible .menu__group.is-open .menu__trigger { color: var(--ink); }
.menu-collapsible .menu__trigger:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

.menu-collapsible .menu__chev {
  flex: none;
  transition: rotate var(--dur-2) var(--ease-out);
}
.menu-collapsible .menu__group.is-open .menu__chev { rotate: 180deg; }

/* 0fr -> 1fr is the one disclosure that interpolates; height:auto does
   not, and this file has paid for that lesson once already. */
.menu-collapsible .menu__panel {
  display: grid;
  grid-template-rows: 0fr;
  transition: grid-template-rows var(--dur-3) var(--ease-out);
}
.menu-collapsible .menu__group.is-open .menu__panel { grid-template-rows: 1fr; }

/* visibility, not just overflow: a closed panel must leave the focus
   order, or tabbing walks through eighteen links nobody can see. */
.menu-collapsible .menu__panel-in {
  display: flex;
  flex-direction: column;
  overflow: hidden;
  min-height: 0;
  visibility: hidden;
  transition: visibility var(--dur-3) step-end;
}
.menu-collapsible .menu__group.is-open .menu__panel-in {
  visibility: visible;
  transition: visibility var(--dur-3) step-start;
}

@media (prefers-reduced-motion: reduce) {
  .menu-collapsible .menu__panel,
  .menu-collapsible .menu__chev { transition: none; }
}

/* ---- The mobile menu reads at its real scale ----
   The headings were 14px uppercase — a caption doing a navigation
   heading's job, and on a phone the four things the whole menu is FOR
   were the smallest type on the screen. They take display scale now,
   sentence case, because at 26px uppercase stops helping and starts
   costing width.

   The rows under them come up with the headings so the ranking holds,
   and both get more room to be tapped: 56px of trigger and 48px of row
   clear the 44px target with margin. */
.menu-collapsible .menu__trigger {
  font-size: clamp(1.5rem, 6.4vw, 1.75rem);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
  text-transform: none;
  padding-block: var(--sp-6);
  color: var(--ink);
}
.menu-collapsible .menu__trigger:hover,
.menu-collapsible .menu__group.is-open .menu__trigger { color: var(--ink); }
.menu-collapsible .menu__chev { width: 16px; height: 11px; }

.menu-collapsible .menu__panel-in a {
  font-size: 1.1875rem;
  line-height: 1.35;
  padding-block: var(--sp-4);
}
/* The first row stands clear of the heading it belongs to instead of
   butting against its rule. */
.menu-collapsible .menu__panel-in { padding-block: var(--sp-3) var(--sp-5); }
.menu-collapsible .menu__panel-in a:first-child { padding-block-start: var(--sp-3); }

/* And the groups stand apart from each other. 48px between a shut group
   and the next was tuned when the heading was a 14px caption; against a
   28px heading it read as the same interval as the rows inside. */
.menu-collapsible .menu__nav { row-gap: var(--sp-3); }

/* ---- The disclosure was running at cinematic speed ----
   --dur-3 is 520ms. That is right for a section arriving on scroll and
   much too slow for a control someone has just tapped: the panel was
   still opening well after the finger left the glass, which reads as the
   menu thinking about it. 260ms in, and the visibility swap rides the
   same clock so a closing panel leaves the focus order exactly when it
   stops being visible.

   The spacing comes in with it. At 25px headings the old intervals were
   tuned for 14px captions, and the tail of the menu — the showrooms and
   their numbers — sat below the fold with nothing to suggest it existed. */
.menu-collapsible .menu__panel { transition: grid-template-rows 260ms var(--ease-out); }
.menu-collapsible .menu__panel-in { transition: visibility 260ms step-end; }
.menu-collapsible .menu__group.is-open .menu__panel-in { transition: visibility 260ms step-start; }
.menu-collapsible .menu__chev { transition: rotate 260ms var(--ease-out); }

.menu-collapsible .menu__trigger { padding-block: var(--sp-5); }
.menu-collapsible .menu__nav { row-gap: var(--sp-2); }
.menu-collapsible .menu__panel-in { padding-block: var(--sp-2) var(--sp-4); }
.menu-collapsible .menu__panel-in a { padding-block: var(--sp-3); }
.menu-collapsible .menu__panel-in a:first-child { padding-block-start: var(--sp-2); }

/* ---- Closing was a shear, not a fade ----
   The panel only animated its HEIGHT. The links inside kept full opacity
   the whole way down and were simply cut off by the shrinking row, so
   the last line vanished mid-glyph — and visibility flipped on step-end,
   which snapped whatever was left. That combination is the jerk.

   The content now fades as the row collapses, and it leads: 150ms of
   opacity against 300ms of height, so the links are gone before the box
   has finished closing and nothing is ever caught being clipped. Opening
   reverses it — the box makes room first, the content arrives into space
   that already exists.

   Height is the only property here that lays out, and it is a grid track
   rather than a height, so it interpolates instead of jumping. */
.menu-collapsible .menu__panel {
  transition: grid-template-rows 300ms var(--ease-out);
}
.menu-collapsible .menu__panel-in {
  opacity: 0;
  transition: opacity 150ms var(--ease-out),
              visibility 0s linear 300ms;
}
.menu-collapsible .menu__group.is-open .menu__panel-in {
  opacity: 1;
  visibility: visible;
  transition: opacity 220ms var(--ease-out) 90ms,
              visibility 0s linear 0s;
}
.menu-collapsible .menu__chev { transition: rotate 300ms var(--ease-out); }

@media (prefers-reduced-motion: reduce) {
  .menu-collapsible .menu__panel,
  .menu-collapsible .menu__panel-in,
  .menu-collapsible .menu__chev { transition: none; }
}

/* ---- The easing was the jerk, not the duration ----
   --ease-out is cubic-bezier(0.22, 1, 0.36, 1) — an expo-out. It is the
   right curve for something arriving from off-screen, because it lands
   softly, but it spends about ninety percent of its travel in the first
   third of its time. Traced on the close, the panel fell from 914px to
   122px in 88ms and then crawled the rest: almost the whole movement
   happened at once, which is exactly what reads as a snap however long
   the declared duration is.

   A symmetric curve instead. The travel is spread evenly across the
   300ms, so the panel moves at one speed from start to finish and there
   is no moment where it lurches. */
.menu-collapsible .menu__panel {
  transition: grid-template-rows 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
.menu-collapsible .menu__chev {
  transition: rotate 300ms cubic-bezier(0.4, 0, 0.2, 1);
}
.menu-collapsible .menu__panel-in {
  transition: opacity 160ms cubic-bezier(0.4, 0, 0.2, 1),
              visibility 0s linear 300ms;
}
.menu-collapsible .menu__group.is-open .menu__panel-in {
  transition: opacity 240ms cubic-bezier(0.4, 0, 0.2, 1) 60ms,
              visibility 0s linear 0s;
}

/* ============================================================
   FEATURED INVENTORY ON A PHONE — the card holds its own car
   ============================================================
   The desktop composition is one dominant field with an index beside it.
   That has no meaning in one column: the field is already hidden here, so
   the index was left carrying four rows of type and a 160px thumbnail
   that showed the car at a size where it read as a sticker.

   The thumbnail becomes the car. Same asset, different job at this
   width — no new markup, and nothing on the desktop changes. The open
   card shows its vehicle full width under the figures; the shut ones are
   name and numbers only. First card open on load, which is the state the
   markup already ships.

   Tapping a shut card opens it and closes the others — the existing
   select() already does exactly that, so this is presentation only.
   ============================================================ */
@media (max-width: 760px) {
  /* one column: make, model, figures, then the car */
  .rec {
    grid-template-columns: minmax(0, 1fr);
    align-items: start;
    row-gap: 0;
    padding: var(--sp-5);
  }

  /* The arrow belonged to a row that pointed somewhere. A card that
     opens in place is not going anywhere, so it goes. */
  .rec__chev { display: none; }

  /* THE THUMBNAIL BECOMES THE VEHICLE.
     Full width, its own row under the pills, and it only exists on the
     open card. Height animates from 0 so the card grows into it rather
     than jumping — the image itself only fades and scales, so nothing
     below it is dragged around by a half-loaded picture. */
  .rec__thumb {
    width: 100%;
    max-width: 100%;
    height: auto;
    justify-self: center;
    align-self: center;
    margin-block-start: 0;
    scale: 0.94;
    opacity: 0;
    max-height: 0;
    transition: max-height 320ms cubic-bezier(0.4, 0, 0.2, 1),
                margin-block-start 320ms cubic-bezier(0.4, 0, 0.2, 1),
                opacity 240ms cubic-bezier(0.4, 0, 0.2, 1),
                scale 320ms cubic-bezier(0.4, 0, 0.2, 1);
  }
  .rec.is-active .rec__thumb {
    max-height: 62vw;
    margin-block-start: var(--sp-5);
    opacity: 1;
    scale: 1;
  }

  /* The open card is the one being read, so it carries the edge and the
     light; the shut ones stay quiet and equal. */
  .rec.is-active { border-color: rgba(104, 156, 236, 0.55); }
}

/* The vehicle was still sitting in the thumbnail's old column and taking
   its 160px width with it. It spans the card now, at the card's width. */
@media (max-width: 760px) {
  .rec > .rec__thumb,
  .rec .rec__thumb {
    grid-column: 1 / -1;
    width: 100%;
    max-width: none;
  }
  .rec.is-active .rec__thumb { max-height: 58vw; }
}

/* ---- The shut cards recede, and the open one arrives slowly ----

   Two corrections, both from the render.

   SIZE. The four cards were the same box with different contents, which
   reads as a list rather than as one card being open. A shut card now
   sits smaller on every axis that carries weight — padding, the model's
   size, the plates — so the open one is the largest thing in the column
   and the state is legible before a word is read.

   MOTION. 320ms was still arriving as an event. The box now takes 520ms
   and the vehicle 420ms behind it, and the type resizes on the same
   clock so the whole card breathes as one object instead of a box
   growing while its contents pop. The curve stays symmetric — the
   project's --ease-out is an expo and spends most of its travel in the
   first third, which is what made every earlier attempt feel snapped
   however long the duration said it was. */
@media (max-width: 760px) {
  .rec {
    padding: var(--sp-4);
    transition: padding 520ms cubic-bezier(0.33, 0, 0.2, 1),
                border-color 520ms cubic-bezier(0.33, 0, 0.2, 1),
                background-color 520ms cubic-bezier(0.33, 0, 0.2, 1);
  }
  .rec.is-active { padding: var(--sp-6) var(--sp-5); }

  .rec__model,
  .rec.is-active .rec__model {
    transition: font-size 520ms cubic-bezier(0.33, 0, 0.2, 1),
                color 520ms cubic-bezier(0.33, 0, 0.2, 1);
  }
  .rec__model { font-size: 1.0625rem; color: var(--ink-2); }
  .rec.is-active .rec__model { font-size: 1.375rem; color: var(--ink); }

  .rec__make { opacity: 0.62; transition: opacity 520ms cubic-bezier(0.33, 0, 0.2, 1); }
  .rec.is-active .rec__make { opacity: 1; }

  .rec__pill { padding: 4px 10px; font-size: 0.8125rem;
               transition: padding 520ms cubic-bezier(0.33, 0, 0.2, 1),
                           font-size 520ms cubic-bezier(0.33, 0, 0.2, 1); }
  .rec.is-active .rec__pill { padding: 6px 13px; font-size: 0.875rem; }

  /* the car, unhurried and a beat behind the box */
  .rec__thumb {
    transition: max-height 520ms cubic-bezier(0.33, 0, 0.2, 1),
                margin-block-start 520ms cubic-bezier(0.33, 0, 0.2, 1),
                opacity 300ms cubic-bezier(0.33, 0, 0.2, 1),
                scale 520ms cubic-bezier(0.33, 0, 0.2, 1);
  }
  .rec.is-active .rec__thumb {
    transition: max-height 520ms cubic-bezier(0.33, 0, 0.2, 1),
                margin-block-start 520ms cubic-bezier(0.33, 0, 0.2, 1),
                opacity 420ms cubic-bezier(0.33, 0, 0.2, 1) 120ms,
                scale 520ms cubic-bezier(0.33, 0, 0.2, 1);
  }

  @media (prefers-reduced-motion: reduce) {
    .rec, .rec__model, .rec__make, .rec__pill, .rec__thumb { transition: none; }
  }
}

/* The shut cards measured the same HEIGHT as the open one despite every
   other dimension being smaller: their model wrapped to two lines and
   their two plates wrapped to two rows, and the wrapping put back
   exactly what the smaller padding had taken away. Both rows hold one
   line now, so a shut card is shorter as well as quieter. */
@media (max-width: 760px) {
  .rec__facts { flex-wrap: nowrap; }
  .rec__pill { white-space: nowrap; }
  .rec:not(.is-active) .rec__model {
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
}

/* And this is what actually held them equal: .recs still hands out four
   tracks of 1fr, the reservation built for the desktop index where every
   row had to own the tallest active state so the column could not resize
   under the reader. On a phone there is no column to protect and the
   cards ARE the disclosure — equal tracks made a shut card exactly as
   tall as the open one no matter how much smaller its contents were. */
@media (max-width: 760px) {
  .recs {
    grid-template-rows: none;
    grid-auto-rows: min-content;
    min-height: 0;
  }
}

/* The card's field carried a radial on the phone too. It was lit for a
   1580px stage with a car standing in the middle of it; at 390 the same
   gradient is a bright smear behind four small boxes, doing no modelling
   because there is no longer a mass for it to model. Flat ground here —
   the cards carry their own edges and the open one carries the light. */
@media (max-width: 760px) {
  .showcase { background: var(--bg-sunk); }
}

/* ---- Sell / Trade, composed for one column ----
   The band's landscape photograph carries the car on one side and the
   wall the copy stands on at the other. At 390 it can hold one or the
   other and not both, so the phone gets a portrait frame of the same
   showroom, in the flow: copy, then the picture, then the three steps.
   The background photograph and its scrim stand down here — with the
   image in the flow, keeping a second one behind the type would be two
   photographs fighting over the same 390 pixels. */
.sell__mobile { display: none; }

@media (max-width: 760px) {
  .sell__bg { display: none; }
  .sell { background: var(--bone); }

  .sell__mobile {
    display: block;
    width: 100%;
    height: auto;
    margin-block: 0 var(--sp-5);
    grid-column: 1 / -1;
    order: 2;
  }
  .sell__copy { order: 1; }
  .sell__steps { order: 3; margin-block-start: var(--sp-4); }

  /* The actions stack and go full width — at this measure a pair side by
     side leaves neither of them room to be read. */
  .sell__actions { flex-direction: column; align-items: stretch; gap: var(--sp-4); }
  .sell__actions .btn { width: 100%; justify-content: center; }
}

/* ---- The phone frame runs edge to edge ----
   It was sitting inside the copy column and wearing that column's
   padding, so a 390 screen showed it at 327 with the showroom clipped
   down both sides. It is a photograph, not a paragraph: it takes the
   full width of the screen and escapes the container that was holding
   it in.

   100vw with a negative inline margin of half the difference, so it is
   centred on the viewport rather than on the column — and vw rather than
   percentages because the column is not the thing it should be measured
   against. */
@media (max-width: 760px) {
  .sell__mobile {
    /* Measured against the CONTAINER, not the viewport. 100vw counts the
       scrollbar, so the frame ran 15px wide and hung 8px off each edge.
       Cancelling the grid own inline padding lands it exactly edge to
       edge whether a scrollbar is present or not. */
    width: calc(100% + 2 * var(--pad));
    max-width: none;
    margin-inline: calc(-1 * var(--pad));
  }
}

/* ---- The phone frame is the GROUND, not a block in the flow ----
   It was stacked between the copy and the steps, which is what pushed
   the section to 1525px — copy, then a picture, then the steps, three
   masses in a row. In the concept there is one photograph and everything
   stands on it: the claim at the top of the showroom, the three steps
   along the floor.

   So the image goes behind, cover-cropped, and the content sits over it.
   The section takes its height from the content again rather than from
   the sum of three blocks. The desktop background stays where it was —
   this only replaces it below the breakpoint. */
@media (max-width: 760px) {
  .sell { position: relative; isolation: isolate; }

  .sell__mobile {
    position: absolute;
    inset: 0;
    z-index: 0;
    width: 100%;
    max-width: none;
    height: 100%;
    margin: 0;
    object-fit: cover;
    object-position: 50% 42%;
  }

  .sell__grid { position: relative; z-index: 1; }
  .sell__copy { order: 0; }
  .sell__steps { order: 0; }

  /* The band is no longer three stacked masses, so the air that
     separated them goes with them. */
  .sell__inner, .sell__grid { padding-block: var(--sp-7) 0; }
  .sell__copy { padding-block: 0; }
  .sell__steps { margin-block-start: clamp(var(--sp-8), 22vw, var(--sp-10)); }
}

/* ---- Where the photograph runs out, the CSS floor continues ----
   Stretched to cover the whole section the frame had to crop hard, and
   the steps ended up over the cars. It sits at its own aspect ratio now,
   anchored to the top, and the section carries a ground colour underneath
   it for whatever height the content needs beyond the picture.

   The colour is sampled off the image rather than chosen: the bottom four
   rows of the file average rgb(235, 235, 227) and the bottom tenth
   rgb(233, 233, 225). Taking the mean of the two makes the join
   invisible — the concrete simply keeps going. */
@media (max-width: 760px) {
  .sell { background: rgb(234, 234, 226); }
  .sell__mobile {
    inset: 0 0 auto 0;
    height: auto;
    object-fit: cover;
    object-position: 50% 50%;
  }
  /* The steps stand on the CSS floor under the photograph, not on the
     cars — so they get whatever room they need without the frame having
     to grow to meet them. */
  .sell__steps { margin-block-start: clamp(var(--sp-7), 12vw, var(--sp-9)); }
}

/* The tall master is back, so the frame carries its own ceiling again and
   the copy has clean wall to stand on. object-position moves down: with
   1602px of picture at a 0.45 ratio the crop can afford to sit low, which
   puts the cars in the middle of the band instead of under the lede. */
@media (max-width: 760px) {
  .sell__mobile { object-position: 50% 30%; }
}

/* The negative inline margins were left over from when the picture was a
   block in the flow escaping the column's padding. It is absolutely
   positioned against the section now, so those margins only push it off
   its own containing block. Cleared — the frame spans the section
   exactly, edge to edge. */
@media (max-width: 760px) {
  .sell__mobile {
    /* The containing block is .sell__grid, not .sell — the grid is
       positioned so the copy can sit above the picture, and an absolute
       child resolves against that element PADDING box. inset-inline: 0
       therefore landed the frame inside the gutter at 327 wide. Pulling
       both edges out by the gutter puts it on the grid border box,
       which is the full width of the section. */
    inset-inline: calc(-1 * var(--pad));
    inset-block-start: 0;
    /* Stated, not auto: on a REPLACED element auto resolves to the
       intrinsic 716px and the insets do not stretch it. */
    width: calc(100% + 2 * var(--pad));
    height: auto;
    max-width: none;
    margin: 0;
  }
}

/* Room under the third step. It fitted the section — 773 against 805 —
   but with nothing beneath it, so on any screen where the band does not
   start at the very top it was the thing that got cut. The floor keeps
   going under it now. */
@media (max-width: 760px) {
  /* The claim was starting almost on the cut. The band opens on a
     shaped edge here, so the first line needs to clear the deepest part
     of that curve, not just the box — otherwise it reads as stuck to it. */
  .sell__inner, .sell__grid { padding-block: clamp(var(--sp-9), 26vw, var(--sp-10)) var(--sp-8); }
}

/* ============================================================
   SELL / TRADE ON A PHONE — THREE PARTS, STACKED
   ============================================================
   The photograph-as-ground is abandoned. Every attempt to place the copy
   ON the picture put something over a car: with a fixed aspect frame the
   text moves and the vehicles do not, so the secondary action kept
   landing on the Aventador however the crop was aimed.

   Three separate parts instead, none overlapping:
     1  the claim, the lede and the two actions, on the plain band
     2  the photograph, full width, in the flow
     3  the three steps, under it

   Nothing is positioned over anything, so nothing can collide — and the
   frame no longer has to be tall enough to host type, which is why the
   mid-section master is 716x699 rather than 1602.
   ============================================================ */
@media (max-width: 760px) {
  .sell { background: var(--bone); }
  .sell__bg { display: none; }

  .sell__grid { position: static; display: flex; flex-direction: column; }
  /* The claim sat 64px under a shaped top edge and read as hung from
     it. The first block drops clear of the cut before it starts. */
  /* The head starts under the top cut and the foot clears the bottom
     one — the band opens and closes on shaped edges, and 03 was
     landing inside the lower curve. --cut-drop-b is what has to be
     cleared, so the padding is derived from it rather than guessed. */
  /* On the GRID only. Applied to both, .sell__inner and .sell__grid are
     nested, so every value was being paid twice — which is why pulling
     the stack up made the band taller. */
  .sell__inner { padding-block: 0; }
  .sell__grid {
    padding-block: var(--sp-4) calc(var(--cut-drop-b) + var(--sp-4));
  }

  /* Photograph first, then the claim, then the steps. */
  .sell__copy { order: 2; padding-block: 0; }

  .sell__mobile {
    order: 1;
    position: static;
    display: block;
    width: calc(100% + 2 * var(--pad));
    max-width: none;
    height: auto;
    margin-inline: calc(-1 * var(--pad));
    margin-block: var(--sp-7) 0;
  }

  .sell__steps { order: 3; margin-block-start: var(--sp-7); }
}

/* Compacted. The three parts were sitting on desktop intervals — the
   actions had 48px under them before the rule, and each step row carried
   32px top and bottom. On a 375 column that spacing is not rhythm, it is
   distance, and it was pushing the third step onto the lower cut. */
@media (max-width: 760px) {
  .sell__lede { margin-block-start: var(--sp-4); }
  .sell__actions { margin-block-start: var(--sp-5); gap: var(--sp-3); }
  .sell__steps li { padding-block: var(--sp-4); }
}

/* The lower cut was falling too steeply on a phone: 76px of drop over a
   short run puts the edge at a sharp diagonal, which reads as a corner
   torn off rather than as a panel edge. Shallower — less drop across a
   longer run, so the slope flattens on both counts. The top cut is left
   alone; only the foot is being corrected. */
@media (max-width: 760px) {
  .sell {
    --cut-drop-b: 34px;
    --cut-run: 30%;
  }
}

/* THE STEEP FOOT WAS THE ANIMATION, NOT A VALUE.
   sell-edges drives --cut-drop-b from 30px to 142px on scroll, and an
   animated custom property outranks any declaration — which is why the
   mobile override above measured 76 rather than the 34 it asked for.

   The scroll morph is a desktop gesture: it reads against a 1580px band
   where the edge has room to travel. On a 375 column the same travel is
   a corner being torn off. The animation stands down here and the foot
   takes a fixed, shallow angle instead. */
@media (max-width: 760px) {
  .sell { animation: none; }
  .sell {
    --cut-drop: 20px;
    --cut-drop-b: 34px;
    --cut-run: 30%;
  }
}

/* Tighter again. Everything that separates the three parts comes down one
   step on the scale — the joins, the ends, and the intervals inside the
   copy. Nothing changes rank, only distance. */
@media (max-width: 760px) {
  .sell__grid {
    padding-block: var(--sp-3) calc(var(--cut-drop-b) + var(--sp-4));
  }
  .sell__mobile { margin-block: 0 var(--sp-4); }
  .sell__steps { margin-block-start: var(--sp-3); }

  .sell__lede { margin-block-start: var(--sp-3); }
  .sell__actions { margin-block-start: var(--sp-4); gap: var(--sp-3); }
  .sell__steps li { padding-block: var(--sp-3); }
}

/* ============================================================
   WARRANTY ON A PHONE — the Sell / Trade pattern
   ============================================================
   Photograph, then the claim and its actions, then the three cards.
   Nothing over anything, for the reason that took four attempts to
   establish on Sell: with a fixed-aspect frame the type moves and the
   subject does not, so type placed over a photograph collides with it at
   some width no matter how the crop is aimed.
   ============================================================ */
.warranty__mobile { display: none; }

@media (max-width: 760px) {
  .warranty__bg { display: none; }
  .warranty__band { background: var(--bone); }

  .warranty__mobile {
    display: block;
    /* Sibling of .shell, not a child of it — the band itself carries
       no inline padding, so there is nothing to escape and a negative
       margin only pushes it off the screen. */
    width: 100%;
    max-width: none;
    height: auto;
    margin-inline: 0;
    margin-block: 0 var(--sp-4);
  }

  /* The band opens and closes on shaped edges here too, so the copy
     starts under the upper one and the foot clears the lower. */
  .warranty__inner { padding-block: var(--sp-3) var(--sp-4); }
  .warranty__lede { margin-block-start: var(--sp-3); }
  .warranty__actions {
    margin-block-start: var(--sp-4);
    flex-direction: column;
    align-items: stretch;
    gap: var(--sp-3);
  }
  .warranty__actions .btn { width: 100%; justify-content: center; }

  .warranty__foot { padding-block-start: var(--sp-4); }
  .wcards { grid-template-columns: minmax(0, 1fr); gap: var(--sp-4); }
}

/* The actions row is a flex COLUMN here, and the buttons were stretching
   to fill its height rather than taking their own — one of them came out
   as a 200px red lozenge. align-self:start on the items gives them back
   their intrinsic height while the row keeps them full width. */
@media (max-width: 760px) {
  .warranty__actions .btn {
    align-self: stretch;
    flex: 0 0 auto;
    min-block-size: 0;
    block-size: auto;
    padding-block: var(--sp-4);
  }
}

/* Financing takes its own frame on a phone. The desktop lineup is a wide
   landscape crop built for a 1580px card; at 375 it holds three vehicles
   at a size where none of them reads. This one is framed for the column. */
@media (max-width: 760px) {
  .fin__bg img {
    content: url("../img/financing/fin-mobile-900.jpg");
    object-position: 50% 50%;
  }
}

/* ============================================================
   STORY ON A PHONE — the same three parts
   ============================================================
   Photograph, then the claim and its action, then the proof. The
   background frame and its scrim stand down: with a picture in the flow
   a second one behind the type is two photographs over 375 pixels.

   The three figures become a stacked light card under the photograph
   rather than a row — 30,000+ beside 2003 beside $2B+ in one column
   leaves each of them about a hundred pixels, which is not a dominant
   fact, it is a caption.
   ============================================================ */
.story__mobile { display: none; }

@media (max-width: 760px) {
  .story__bg { display: none; }
  /* THE SEAM. --bg is rgb(21, 16, 12) and the bottom of the frame is
     rgb(6, 8, 7) — near-black with no blue in it — so the join between
     the picture and the section read as a hard line. The ground takes
     the photograph own last rows instead, sampled rather than guessed,
     and the two meet invisibly. The page ground below is unaffected;
     this is the section own fill. */
  .story { background: rgb(6, 8, 7); }

  .story__mobile {
    display: block;
    width: 100%;
    max-width: none;
    height: auto;
    margin: 0 0 var(--sp-5);
  }

  .story__inner { padding-block: 0 var(--sp-6); }
  .story__lede { margin-block-start: var(--sp-3); }
  .story__act { margin-block-start: var(--sp-5); }

  /* The proof, as a card on the section's own raised ground. */
  .story__stats {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sp-5);
    margin-block-start: var(--sp-6);
    padding: var(--sp-5);
    border: 1px solid rgba(243, 238, 230, 0.10);
    border-radius: 14px;
    background: var(--bg-sunk);
  }
  .stat { display: flex; flex-direction: column; gap: var(--sp-2); }
  .stat--lead .stat__n { font-size: clamp(2.75rem, 15vw, 3.75rem); }
  .stat--proof .stat__n { font-size: clamp(1.75rem, 9vw, 2.25rem); }
}

/* ---- The showrooms arrive one at a time ------------------------------
   The same restrained rise the sell steps and the financing cards take,
   extended to five and slowed a little: these are photographs rather than
   rows of type, and a 130ms step across five reads as a flicker while the
   same interval across three reads as a sequence.

   They come AFTER the section's own text — the headline is already
   walking in from the left when the first panel starts — so the chapter
   resolves in one direction rather than everything arriving together.

   Only opacity and translate, so the row's geometry is untouched: the
   accordion's open panel keeps its width through the whole entrance and
   nothing reflows behind the reveal. */
.reveal-armed [data-reveal] .locs__list > li {
  opacity: 0;
  translate: 0 26px;
  /* Slower than the shared --rv-lede, and stated rather than tokenised:
     these are the only five photographs on the page that enter one by
     one, and 560ms across that many made the last one land while the
     first was still settling. 820ms lets each finish before its
     neighbour starts. Changing --rv-lede would have slowed every
     paragraph on the site to fix one row. */
  /* FLEX-GROW IS LISTED HERE ON PURPOSE. `transition` is a shorthand: a
     second declaration replaces the first outright, it does not add to
     it. .v2 .loc carries `transition: flex-grow var(--dur-4)` — the
     accordion own opening — and this selector outranks it, so declaring
     only opacity and translate here deleted that opening and the panels
     snapped. On both index_2 and index_3, since this rule is in main.css.
     Any property added to this list in future has to keep it. */
  transition: opacity 820ms var(--ease-out),
              translate 820ms var(--ease-out),
              flex-grow var(--dur-3) cubic-bezier(0.32, 0, 0.24, 1);
}
.reveal-armed [data-reveal].is-revealed .locs__list > li { opacity: 1; translate: 0 0; }

/* THE STAGGER DELAYS THE ARRIVAL, NOT THE OPENING — and getting that
   wrong is why the panels felt like they hesitated under the pointer.

   `transition-delay` with ONE value applies to every property in the
   shorthand above it, and that list carries flex-grow. So the entry
   staircase — which exists to make five photographs land one at a time —
   was also hanging its delay on the accordion. The third panel sat on
   338ms of it: hover, wait a third of a second, then watch it widen.
   No delay was ever written for the hover; it was inherited from the
   arrival and never expired.

   Three values now, positionally matched to the three properties:
   opacity, translate, then ZERO for flex-grow. The arrival still walks
   across the row; the opening starts on the frame the pointer lands.

   Any property added to the shorthand above must be given its own value
   here, or it inherits the staircase too. */
.reveal-armed [data-reveal].is-revealed .locs__list > li:nth-child(1) { transition-delay: calc(var(--rv-step) * 2.6), calc(var(--rv-step) * 2.6), 0s; }
.reveal-armed [data-reveal].is-revealed .locs__list > li:nth-child(2) { transition-delay: calc(var(--rv-step) * 3.7), calc(var(--rv-step) * 3.7), 0s; }
.reveal-armed [data-reveal].is-revealed .locs__list > li:nth-child(3) { transition-delay: calc(var(--rv-step) * 4.8), calc(var(--rv-step) * 4.8), 0s; }
.reveal-armed [data-reveal].is-revealed .locs__list > li:nth-child(4) { transition-delay: calc(var(--rv-step) * 5.9), calc(var(--rv-step) * 5.9), 0s; }
.reveal-armed [data-reveal].is-revealed .locs__list > li:nth-child(5) { transition-delay: calc(var(--rv-step) * 7.0), calc(var(--rv-step) * 7.0), 0s; }

@media (prefers-reduced-motion: reduce) {
  .reveal-armed [data-reveal] .locs__list > li { transition: none; translate: none; }
}

/* ---- the showroom Instagram accounts ---------------------------------
   A line under the main handle, not a row of buttons.

   The ask was "follow buttons for the different location IG accounts",
   and the count decides the form: five locations, two accounts. Five
   equal buttons with three of them dark reads as a section someone
   abandoned; a line reads as a fact, and it takes a third handle
   without a relayout.

   The main account keeps the headline, the feed and the filled CTA — it
   IS the account. These are its showrooms, so they sit under it in the
   caption register, one step down in every dimension: smaller, quieter,
   and marked as links only by the hairline they carry.

   The two are not weighted against each other either. SC has 16k
   followers and KC has 503; sizing them equally would be the page
   making a claim about reach that it has no business making. Same
   treatment, alphabetical by state — the page presents them, it does
   not rank them. */
.ig__also {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: var(--sp-3) var(--sp-5);
  margin-block-start: var(--sp-5);
}
.ig__also-k { color: var(--ink-3); letter-spacing: var(--tr-label); }

.ig__also-a {
  display: inline-flex;
  align-items: baseline;
  gap: var(--sp-3);
  color: var(--ink-2);
  text-decoration: none;
  font-family: var(--font-label);
  font-size: var(--t-micro);
  transition: color var(--dur-2) var(--ease-out);
}
/* THE RULE SITS UNDER THE HANDLE, NOT UNDER THE PAIR. On the whole link
   it drew two segments with a gap between them — the flex gap punching a
   hole in what should be one line — and it also underlined the city,
   which is a description rather than the thing being linked to. The
   handle is the link's name; the place is what tells you which one you
   want. */
.ig__also-h { box-shadow: 0 1px 0 var(--rule); transition: box-shadow var(--dur-2) var(--ease-out); }
.ig__also-a:hover { color: var(--ink); }
.ig__also-a:hover .ig__also-h { box-shadow: 0 1px 0 currentColor; }
.ig__also-a:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* The place, not the handle, is what a reader is actually looking for —
   so it travels with it rather than being something to work out from
   two letters. Quieter than the handle, because you scan the handle and
   read this only if it is the one you wanted. */
.ig__also-loc { color: var(--ink-3); font-family: var(--font-body); }

@media (max-width: 40rem) {
  .ig__also { gap: var(--sp-2) var(--sp-4); }
  .ig__also-a { flex-direction: column; gap: 2px; }
}
