/* ============================================================
   GOLDMAN — style.css
   Import order: tokens.css → style.css. Every colour resolves to a
   custom property; there are no hex values in this file.

   Conventions
   -----------
   · Flat specificity — single class selectors. No .section .card h3
     chains. Section padding collisions are the single most common
     failure when a stylesheet is written in pieces.
   · BEM-ish naming: .block, .block__element, .block--modifier.
   · Mobile-first. Every breakpoint lands on a container step
     (576 / 768 / 992 / 1200 / 1400) — a split at 900px sits inside a
     720px container and gives each column ~300px, which is not a column.
   · Radius is 2px on everything. This is load-bearing.
   ============================================================ */

/* ------------------------------------------------------------------
   1. RESET & BASE
   ------------------------------------------------------------------ */

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

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}
/* Lenis drives scrolling; native smooth behaviour fights it. */
html.lenis, html.lenis body { height: auto; }
.lenis.lenis-smooth { scroll-behavior: auto !important; }

body {
  margin: 0;
  background: var(--gm-paper);
  color: var(--gm-graphite);
  font-family: var(--ff-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
  text-rendering: optimizeLegibility;
}

img, svg, video, canvas { display: block; max-width: 100%; }
img, video { height: auto; }

/* <dd> carries a browser default `margin-inline-start: 40px`, which is
   a hangover from the days when a definition list was rendered as an
   indented glossary. Every dl on this site is a LABEL/VALUE pair laid
   out with flex or grid, so that indent is never wanted — it pushed the
   value 40px right of its own label on the product cards, the verify
   page's warranty record, and the admin enquiry details.

   Reset here rather than per component: three of the four dd classes
   had missed it, which is the tell that the next one would too. */
dd { margin: 0; }

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

button, input, select, textarea {
  font: inherit;
  color: inherit;
}
button { background: none; border: 0; cursor: pointer; }

h1, h2, h3, h4, h5, h6, p, figure, blockquote, ul, ol, dl { margin: 0; }
ul, ol { padding: 0; list-style: none; }

address { font-style: normal; }

::selection { background: var(--gm-gold); color: var(--gm-obsidian); }

/* Visible focus ring on every interactive element. Never remove this
   without a replacement — keyboard users lose the page otherwise. */
:focus-visible {
  outline: var(--focus-ring);
  outline-offset: var(--focus-offset);
  border-radius: var(--radius);
}
/* Dark panels need the ring to sit against the light side of gold. */
.section--obsidian :focus-visible,
.site-footer :focus-visible,
.mobile-nav :focus-visible { outline-color: var(--gm-gold-lite); }

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

.skip-link {
  position: absolute;
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  top: 0; left: 50%;
  transform: translate(-50%, -110%);
  z-index: var(--z-skiplink);
  padding: 12px 24px;
  background: var(--gm-obsidian);
  color: var(--gm-gold);
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  border-radius: 0 0 var(--radius) var(--radius);
  transition: transform var(--dur-fast) var(--ease-out);
}
.skip-link:focus { transform: translate(-50%, 0); }

/* Honeypot — invisible to a person, present for a bot. */
.hp {
  position: absolute;
  left: -9999px;
  width: 1px; height: 1px;
  overflow: hidden;
}

/* ------------------------------------------------------------------
   2. LAYOUT
   ------------------------------------------------------------------ */

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

.section { padding-block: var(--section-y); position: relative; }
.section--tight { padding-block: clamp(38px, 4vw, 8px); }
.section--flush-top { padding-top: 0; }

.section--paper     { background: var(--gm-paper); }
.section--champagne { background: var(--gm-champagne); }
.section--card      { background: var(--gm-card); }

.section--obsidian {
  background: var(--gm-obsidian);
  color: rgba(251, 250, 247, .74);
}
.section--obsidian .h2,
.section--obsidian .h3,
.section--obsidian .h4 { color: var(--gm-paper); }
.section--obsidian .eyebrow { color: var(--gm-gold); }
.section--obsidian .lede { color: rgba(251, 250, 247, .78); }

.stack > * + * { margin-top: var(--space-4); }
.stack--lg > * + * { margin-top: var(--space-6); }

.grid { display: grid; gap: var(--space-5); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 768px) {
  .grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid--3 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (min-width: 992px) {
  .grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .grid--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .grid--split { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(40px, 6vw, 88px); align-items: start; }
}
@media (min-width: 1200px) {
  .grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.section__head { max-width: 46rem; margin-bottom: clamp(40px, 5vw, 72px); }

/* A closing line BELOW a section's content — statement, evidence, then
   what it means. Same 46rem measure as the head so the section reads as
   one column of prose with the grid set into it, rather than a full-
   bleed line running past everything above it. */
.section__close {
  max-width: 46rem;
  margin-top: clamp(32px, 4vw, 56px);
  color: var(--gm-graphite);
}
.section__head--center { margin-inline: auto; text-align: center; }
.section__head--center .eyebrow { justify-content: center; }

/* ------------------------------------------------------------------
   3. TYPOGRAPHY
   ------------------------------------------------------------------ */

.h1, .h2, .h3, .h4 {
  font-family: var(--ff-display);
  color: var(--gm-ink);
  font-weight: 600;
  letter-spacing: var(--tracking-tight);
  text-wrap: balance;
}

.h1 { font-size: var(--fs-display-xl); line-height: var(--lh-display); font-weight: 700; }
.h2 { font-size: var(--fs-display-l);  line-height: var(--lh-h2); }
.h3 { font-size: var(--fs-display-m);  line-height: var(--lh-h3); }
.h4 { font-size: var(--fs-display-s);  line-height: 1.28; letter-spacing: -.01em; }

/* Eyebrow — the packaging's tracked-out caps. Never below 11px, never
   --gm-slate, and never --gm-gold on a light ground (2.4:1). */
.eyebrow {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-gold-text);
  margin-bottom: var(--space-4);
}
.eyebrow::before {
  content: "";
  width: 32px; height: 1px;
  background: var(--gm-gold);
  flex: none;
}
.section--obsidian .eyebrow::before { background: var(--gm-gold); }

.lede {
  font-size: var(--fs-body-l);
  line-height: var(--lh-body-l);
  color: var(--gm-graphite);
  max-width: var(--measure);
}
.prose { max-width: var(--measure); }
.prose > * + * { margin-top: var(--space-4); }
.prose p { color: var(--gm-graphite); }

.caption {
  font-size: var(--fs-caption);
  line-height: 1.5;
  color: var(--gm-slate);
}

.data {
  font-family: var(--ff-data);
  font-size: var(--fs-data);
  font-variant-numeric: tabular-nums;
  letter-spacing: -.01em;
}
.data--sm { font-size: var(--fs-data-s); }

.label {
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-ink);
}

.text-gold  { color: var(--gm-gold-text); }   /* any size, light ground */
.text-slate { color: var(--gm-slate); }
.text-ink   { color: var(--gm-ink); }

/* A gold hairline used as a section divider. */
.rule {
  height: 1px;
  background: var(--gm-hairline);
  border: 0;
  margin-block: var(--space-7);
}
.rule--labelled {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  margin-block: clamp(40px, 5vw, 72px);
}
.rule--labelled::before,
.rule--labelled::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--gm-gold);
}
.rule__label {
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-gold-text);
  white-space: nowrap;
}

/* ------------------------------------------------------------------
   4. BUTTONS
   ------------------------------------------------------------------ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  min-height: 48px;               /* >= 44px touch target */
  padding: 14px 28px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  text-align: center;
  white-space: nowrap;
  cursor: pointer;
  transition:
    background-color var(--dur-fast) var(--ease-out),
    border-color var(--dur-fast) var(--ease-out),
    color var(--dur-fast) var(--ease-out),
    box-shadow var(--dur-fast) var(--ease-out);
}
.btn .icon { flex: none; transition: transform var(--dur-fast) var(--ease-out); }
.btn:hover .icon { transform: translateX(3px); }

.btn--gold {
  background: var(--gm-gold-deep);
  border-color: var(--gm-gold-deep);
  color: var(--gm-paper);
}
.btn--gold:hover { background: var(--gm-ink); border-color: var(--gm-ink); }

.btn--ink {
  background: var(--gm-ink);
  border-color: var(--gm-ink);
  color: var(--gm-paper);
}
.btn--ink:hover { background: var(--gm-obsidian); border-color: var(--gm-obsidian); }

.btn--outline {
  border-color: var(--gm-edge);
  color: var(--gm-ink);
  background: transparent;
}
.btn--outline:hover { border-color: var(--gm-gold); background: var(--gm-gold-wash); }

.btn--ghost {
  color: var(--gm-gold-text);
  background: transparent;
  border-color: transparent;
}
.btn--ghost:hover { background: var(--gm-gold-wash); }

/* On obsidian, --gm-gold is 8.1:1 and legal as text at any size. */
.btn--on-dark {
  border-color: rgba(251, 250, 247, .28);
  color: var(--gm-paper);
  background: transparent;
}
.btn--on-dark:hover { border-color: var(--gm-gold); color: var(--gm-gold); }

.btn--sm { min-height: 44px; padding: 11px 18px; }
.btn--block { width: 100%; }

/* Inline arrow link — not a button, but a nav affordance. */
.link-arrow {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  gap: var(--space-2);
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-gold-text);
  padding-block: var(--space-2);
  border-bottom: 1px solid var(--gm-hairline);
  transition: border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.link-arrow:hover { border-color: var(--gm-gold); color: var(--gm-ink); }
.link-arrow .icon { transition: transform var(--dur-fast) var(--ease-out); }
.link-arrow:hover .icon { transform: translateX(3px); }
.section--obsidian .link-arrow { color: var(--gm-gold); }
.section--obsidian .link-arrow:hover { color: var(--gm-gold-lite); }

/* ------------------------------------------------------------------
   5. HEADER
   A sticky bar ABOVE the hero, not overlaid on it. Overlaying forces
   every hero image to protect a text-safe zone forever.
   ------------------------------------------------------------------ */

.site-header {
  position: sticky;
  top: 0;
  z-index: var(--z-header);
  background: var(--gm-paper);
  border-bottom: 1px solid var(--gm-edge);
  transition: box-shadow var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out);
}
.site-header.is-scrolled {
  background: rgba(251, 250, 247, .92);
  box-shadow: var(--shadow-md);
}
@supports (backdrop-filter: blur(12px)) {
  .site-header.is-scrolled { backdrop-filter: blur(12px); }
}

/* One source of truth for the header's height. Anything that sticks
   below the header (the depth rail, scroll-margin on anchor targets)
   reads --header-h, so the two cannot drift apart when this changes. */
:root { --header-h: 64px; }
@media (min-width: 992px) { :root { --header-h: 88px; } }

/* ALIGNED TO THE CONTENT COLUMN.
   This used to be max-width: var(--container-wide) — 1440px — while every
   block on every page uses the stepped var(--container), which caps at
   1320px. The logo therefore started 60px left of the first heading below
   it at 1440, and 70px left at 1280, so the page's left edge visibly
   stepped inward under the header. Matching --container is what makes the
   logo, the headings and the footer share one edge at every width.

   The nav has to fit the narrower column, which is what the two rules
   below pay for. */
.site-header__inner {
  display: flex;
  align-items: center;
  gap: var(--space-5);
  height: var(--header-h);
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--container-pad);
}

/* The client's horizontal lockup. tools/make-logos.sh trims the
   transparent margin off the original, so the file's bounding box is the
   artwork and `height` here is the height the logo actually appears at.

   Sized by HEIGHT with width:auto — the lockup is 5.74:1 and driving it
   from width would make its height depend on a ratio written in two
   places. The subtitle lines stay legible down to about 36px, which is
   what sets the mobile size. */
.brand {
  display: flex;
  align-items: center;
  min-height: 44px;
  margin-right: auto;
}
.brand__logo {
  height: 34px;
  width: auto;
  display: block;
}
@media (min-width: 992px) { .brand__logo { height: 44px; } }

.main-nav { display: none; }
@media (min-width: 1200px) { .main-nav { display: block; } }
.main-nav__list { display: flex; align-items: center; gap: var(--space-4); }
/* Only the widest step has room to breathe. Between 1200 and 1399 the
   content column is 1140px and seven links, a logo and a call to action
   have to share it — the gap is the cheapest thing to give up, and it
   costs less than crowding the words themselves. */
@media (min-width: 1400px) { .main-nav__list { gap: var(--space-5); } }
.main-nav__item { position: relative; }

.main-nav__link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding-block: var(--space-3);
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-ink);
  position: relative;
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease-out);
}
.main-nav__link::after {
  content: "";
  position: absolute;
  left: 0; right: 0;
  bottom: 0;
  height: 1px;
  background: var(--gm-gold);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform var(--dur-base) var(--ease-out);
}
.main-nav__link:hover::after,
.main-nav__link.is-active::after { transform: scaleX(1); }
.main-nav__caret { display: inline-flex; transition: transform var(--dur-fast) var(--ease-out); }
.main-nav__item--mega.is-open .main-nav__caret { transform: rotate(180deg); }

.site-header__actions { display: none; gap: var(--space-2); }
@media (min-width: 1200px) { .site-header__actions { display: flex; } }
/* The header carries exactly ONE action, whichever it is. Two buttons
   cost 147px more than the content column has spare, and that is what
   used to make the header wider than every heading below it. The markup
   in _header.php decides which one; this rule exists so a second button
   added later is caught here rather than silently breaking alignment. */
.site-header__actions > * ~ * { display: none; }

/* --- Products mega-panel --- */

.mega {
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  z-index: var(--z-mega);
  width: min(1100px, calc(100vw - 2 * var(--container-pad)));
  background: var(--gm-card);
  border: 1px solid var(--gm-edge);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  padding: var(--space-6);
}
.mega[hidden] { display: none; }
.mega__inner { display: grid; grid-template-columns: repeat(5, 1fr); gap: var(--space-5); }

.mega__heading {
  display: block;
  min-height: 32px;
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-gold-text);
  padding-bottom: var(--space-3);
  margin-bottom: var(--space-3);
  border-bottom: 1px solid var(--gm-hairline);
}
.mega__heading--plain { color: var(--gm-slate); }

.mega__list > li + li { margin-top: 2px; }
.mega__item {
  display: block;
  min-height: 44px;
  padding: var(--space-3);
  margin-inline: calc(var(--space-3) * -1);
  border-radius: var(--radius);
  transition: background-color var(--dur-fast) var(--ease-out);
}
.mega__item:hover { background: var(--gm-champagne); }
.mega__name {
  display: block;
  font-family: var(--ff-display);
  font-size: 1rem;
  font-weight: 600;
  color: var(--gm-ink);
  line-height: 1.2;
}
.mega__meta {
  display: flex;
  gap: var(--space-2);
  align-items: baseline;
  margin-top: 3px;
  font-family: var(--ff-data);
  font-size: .6875rem;
  font-variant-numeric: tabular-nums;
  color: var(--gm-slate);
}
.mega__years { color: var(--gm-gold-text); font-weight: 500; }

.mega__all {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  gap: var(--space-2);
  margin-top: var(--space-4);
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-gold-text);
}
.mega__all:hover { color: var(--gm-ink); }

/* --- Country-of-origin chip --- */

/* One element covers both placements the client marked. .brand carries
   margin-right:auto, so in the header's flex row this chip sits hard
   against whatever follows it: the nav at 1200px and up, the hamburger
   below that. No duplicate markup, and nothing to keep in sync. */
.origin-badge {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  flex: none;
  padding: 5px 9px;
  border: 1px solid var(--gm-hairline);
  border-radius: var(--radius);
  background: var(--gm-champagne);
}

/* A hairline in the flag's own red rather than a neutral border: the
   PRC red is dark enough to sit on champagne unaided, but the 1px edge
   stops it bleeding into the chip fill at 18px wide. */
.origin-badge__flag {
  display: block;
  width: 18px;
  height: 12px;
  flex: none;
  border: 1px solid rgba(138, 21, 10, .30);
}

.origin-badge__text {
  font-family: var(--ff-display);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
  color: var(--gm-ink);
}

/* Image variant: the client's badge carries its own frame, flag and
   wording, so the chip's fill, border and padding come off. */
.origin-badge--image {
  padding: 0;
  border: 0;
  background: none;
}

.origin-badge__img {
  display: block;
  width: 54px;
  height: auto;
}

/* Under 420px the header row is logo + chip + hamburger inside a narrow
   content column, and the three do not fit with the wordmark legible.
   The text goes and the flag stays: the chip still reads as an origin
   mark, and `title` on the wrapper keeps it explicable on hover. */
@media (max-width: 419.98px) {
  .origin-badge { padding: 5px 6px; }
  .origin-badge--image { padding: 0; }
  .origin-badge__img { width: 46px; }
  .origin-badge__text { display: none; }
}

/* --- Mobile toggle + overlay --- */

.nav-toggle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px;
  margin-right: calc(var(--space-2) * -1);
  border-radius: var(--radius);
}
@media (min-width: 1200px) { .nav-toggle { display: none; } }

.nav-toggle__box { display: block; width: 22px; height: 14px; position: relative; }
.nav-toggle__bar {
  position: absolute;
  left: 0;
  width: 100%;
  height: 1.5px;
  background: var(--gm-ink);
  transition: transform var(--dur-base) var(--ease-out),
              opacity 160ms var(--ease-out);
}
.nav-toggle__bar:nth-child(1) { top: 0; }
.nav-toggle__bar:nth-child(2) { top: 6.25px; }
.nav-toggle__bar:nth-child(3) { top: 12.5px; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(1) { transform: translateY(6.25px) rotate(45deg); }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(2) { opacity: 0; }
.nav-toggle[aria-expanded="true"] .nav-toggle__bar:nth-child(3) { transform: translateY(-6.25px) rotate(-45deg); }

.mobile-nav {
  position: fixed;
  inset: 64px 0 0;
  z-index: var(--z-overlay);
  background: var(--gm-paper);
  overflow-y: auto;
  overscroll-behavior: contain;
}
.mobile-nav[hidden] { display: none; }
@media (min-width: 1200px) { .mobile-nav { display: none !important; } }

.mobile-nav__inner {
  padding: var(--space-6) var(--container-pad) var(--space-9);
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
  min-height: 100%;
}
.mobile-nav__item { border-bottom: 1px solid var(--gm-edge-soft); }

.mobile-nav__link {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  min-height: 56px;
  padding-block: var(--space-4);
  font-family: var(--ff-display);
  font-size: 1.375rem;
  font-weight: 600;
  color: var(--gm-ink);
  text-align: left;
  letter-spacing: -.01em;
}
.mobile-nav__link.is-active { color: var(--gm-gold-text); }
.mobile-nav__caret { display: inline-flex; color: var(--gm-slate); transition: transform var(--dur-fast) var(--ease-out); }
.mobile-nav__link[aria-expanded="true"] .mobile-nav__caret { transform: rotate(180deg); }

.mobile-nav__panel { padding-bottom: var(--space-4); }
.mobile-nav__panel[hidden] { display: none; }
.mobile-nav__sub {
  display: block;
  min-height: 48px;
  padding: var(--space-3) 0 var(--space-3) var(--space-4);
  border-left: 1px solid var(--gm-hairline);
  color: var(--gm-graphite);
  font-size: var(--fs-body);
}
.mobile-nav__sub--all { color: var(--gm-gold-text); font-weight: 600; }

.mobile-nav__actions { display: flex; flex-direction: column; gap: var(--space-3); margin-top: auto; }

body.is-nav-open { overflow: hidden; }

/* ------------------------------------------------------------------
   6. FOOTER — the dark footer anchors a light page.
   ------------------------------------------------------------------ */

.site-footer {
  background: var(--gm-obsidian);
  color: rgba(251, 250, 247, .68);
  padding-top: clamp(56px, 7vw, 96px);
}
.site-footer__inner {
  display: grid;
  gap: var(--space-7);
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--container-pad);
  padding-bottom: var(--space-8);
}
@media (min-width: 768px) { .site-footer__inner { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 992px) { .site-footer__inner { grid-template-columns: 1.6fr 1fr 1fr 1.2fr; gap: var(--space-6); } }

.footer-brand {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.footer-brand__logo {
  height: 46px;
  width: auto;
  display: block;
}
@media (min-width: 992px) { .footer-brand__logo { height: 54px; } }

.site-footer__line {
  margin-top: var(--space-4);
  max-width: 30ch;
  font-size: var(--fs-caption);
  line-height: 1.6;
  color: rgba(251, 250, 247, .58);
}

.social { display: flex; gap: var(--space-2); margin-top: var(--space-5); }
.social__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px;
  border: 1px solid var(--gm-edge-dark);
  border-radius: var(--radius);
  color: rgba(251, 250, 247, .72);
  transition: border-color var(--dur-fast) var(--ease-out), color var(--dur-fast) var(--ease-out);
}
.social__link:hover { border-color: var(--gm-gold); color: var(--gm-gold); }

.site-footer__heading {
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-gold);
  margin-bottom: var(--space-4);
}
.site-footer__list > li + li { margin-top: var(--space-1); }
.site-footer__list a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding-block: 7px;
  font-size: var(--fs-caption);
  color: rgba(251, 250, 247, .68);
  transition: color var(--dur-fast) var(--ease-out);
}
.site-footer__list a:hover { color: var(--gm-paper); }

.site-footer__contact {
  margin-top: var(--space-5);
  padding-top: var(--space-5);
  border-top: 1px solid var(--gm-edge-dark);
  display: flex;
  flex-direction: column;
  /* No gap: the links carry their own height now, and a gap on top of
     that would space the block out twice. The address keeps its margin
     below instead. */
  font-size: var(--fs-caption);
  line-height: 1.6;
}
/* A phone number in a footer is TAPPED, on a phone, by someone standing
   next to a car. At 21px tall it was a 21px target. These became real
   links only when company.phone and company.email were confirmed on
   10 Sep 2026 — before that cfg_confirmed() returned null and the block
   rendered nothing, which is why this never showed up earlier.

   inline-flex rather than padding so the 44px is the LINK's box and not
   a gap around it: padding on an inline element does not grow the hit
   area vertically the way it appears to. */
.site-footer__contact a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  align-self: flex-start;   /* the target is the text width, not the column */
  color: rgba(251, 250, 247, .78);
}
.site-footer__contact a:hover { color: var(--gm-gold); }
.site-footer__addr {
  color: rgba(251, 250, 247, .58);
  max-width: 26ch;
  margin-bottom: var(--space-2);
}

.site-footer__bar {
  border-top: 1px solid var(--gm-edge-dark);
  padding-block: var(--space-5);
}
.site-footer__bar {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  align-items: flex-start;
  width: 100%;
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--container-pad);
}
@media (min-width: 768px) {
  .site-footer__bar { flex-direction: row; justify-content: space-between; align-items: center; }
}
.site-footer__copy { font-size: var(--fs-caption); color: rgba(251, 250, 247, .5); }
.site-footer__legal { display: flex; flex-wrap: wrap; gap: var(--space-5); }
.site-footer__legal a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  font-size: var(--fs-caption);
  color: rgba(251, 250, 247, .5);
}
.site-footer__legal a:hover { color: var(--gm-gold); }

/* ------------------------------------------------------------------
   7. HERO
   Full-bleed image with copy in the left safe zone on desktop. Below
   992px the copy STACKS BELOW the image — the portrait crop is filled
   edge to edge by the car and overlaid text becomes unreadable.
   ------------------------------------------------------------------ */

/* The image's ideal height per hero variant. At >=992px .hero__media
   reads this as a MIN, so the picture never gets shorter than this and
   never stops the hero growing to fit its copy. clamp() folds the old
   aspect-ratio / min-height / max-height triple into one value. */
.hero {
  display: block;
  background: var(--gm-paper);
  --hero-media-h: clamp(400px, calc(100vw * 750 / 1920), 720px);
}
.hero--range  { --hero-media-h: clamp(380px, calc(100vw * 900 / 2000), 60vh); }
/* ONE HEIGHT FOR EVERY INNER-PAGE BANNER.
   Client request, 12 Sep 2026: the banner must be the same size on every
   page. It was not — the hero grows to fit its copy, so a three-line
   heading made /about and /dealers 503px and /warranty 551px against a
   450px baseline on the other eleven pages.

   A flat 500px, capped so it can still never exceed the viewport it is
   pinned in — a sticky banner taller than the screen is the defect
   /_viewport.html exists to catch. The three long headings were
   shortened to two lines so they fit inside it; see about.php,
   dealers.php and warranty.php. */
.hero--sticky { --hero-media-h: min(500px, calc(100dvh - var(--header-h) - 24px)); }

/* THE RANGE BANNERS SHOW THEIR WHOLE PICTURE.

   .hero--range already asked for the 20:9 of the artwork (line above),
   and .hero--sticky then overrode it with a flat 500px — later in the
   file, same specificity, so the cap won. At 1920 that made the box
   1920x500, a ratio of 3.84 against the image's 2.22: the full width
   showed but only 57.9% of the HEIGHT did, and 42% of every banner was
   trimmed off the top and bottom. On these nine that reads as the car
   being cut in half.

   The 500px cap is not arbitrary and is kept as the second term: a
   STICKY banner taller than the viewport cannot be scrolled past, which
   is the failure /_viewport.html exists to catch. So this takes the
   ratio height when the screen can afford it and the viewport bound
   when it cannot:

     1920x1045 -> min(864, 956) = 864, the whole image
     1280x800  -> min(576, 711) = 576, the whole image
     1920x800  -> min(864, 711) = 711, cropped rather than unreachable

   Scoped to the range pages, which are the ones whose art is 20:9. The
   other banners borrow slots at other ratios and keep the flat cap. */
.hero--banner.hero--sticky {
  --hero-media-h: min(calc(100vw * 9 / 20), calc(100dvh - var(--header-h) - 24px));
}

.hero__media {
  position: relative;
  width: 100%;                                  /* pin explicitly: aspect-ratio
                                                   plus min-height with no width
                                                   makes the box solve for width
                                                   and overflow the viewport */
  aspect-ratio: 4 / 5;
  max-height: 68vh;
  overflow: hidden;
  background: var(--gm-mist);
}
.hero__img { width: 100%; height: 100%; object-fit: cover; }

/* ---------- Range banner: one frame per tier ------------------------
   Stacked layers that crossfade. The scrim is .hero__media::after, so
   it sits above every layer and one gradient serves them all.

   OPACITY ONLY for the fade — the layers are full-bleed and compositing
   them is free; animating anything that triggers layout here would be
   fighting a sticky banner.

   NO JS REQUIRED to be correct: the first slide carries .is-active from
   the markup, so with JavaScript off, or before main.js runs, the banner
   is exactly the single still it was before. Rotation is the
   enhancement, not the baseline. */
.hero__slide {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 1.1s var(--ease-out);
  pointer-events: none;
}
.hero__slide.is-active { opacity: 1; }

/* The settle. Each frame arrives fractionally large and eases back to
   exactly 1, so it FINISHES at native resolution rather than living
   scaled up — the same direction the home hero uses, and the reason a
   slow zoom here does not soften the picture the way a translate-based
   parallax would (see the note in _page-hero.php).

   6s against a 5.2s hold, so it is still moving when the next frame
   takes over and never visibly stops. */
.hero__slide .hero__img {
  transform: scale(1.05);
  transition: transform 6s linear;
  will-change: transform;
}
.hero__slide.is-active .hero__img { transform: scale(1); }

@media (prefers-reduced-motion: reduce) {
  /* main.js never starts the rotation under reduced motion, so only the
     first slide is ever shown — but the settle is CSS and would still
     run, so it is switched off here too. */
  .hero__slide { transition: none; }
  .hero__slide .hero__img,
  .hero__slide.is-active .hero__img { transform: none; transition: none; }
}

.hero__body { padding-block: clamp(40px, 6vw, 72px); }
.hero__title { margin-bottom: var(--space-5); }
.hero__lede { max-width: 42rem; margin-bottom: var(--space-6); }
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); }

@media (min-width: 992px) {
  /* 🔴 A ONE-CELL GRID, not an absolutely-positioned overlay.
     .hero__body used to be `position: absolute; inset: 0`, so it
     contributed NO height. The hero's height came only from the image's
     aspect ratio, and when the copy needed more room than the picture
     happened to give it, the copy spilled over both edges — 38px past
     the top AND bottom on /warranty at 700px tall, with the lede landing
     on bare page background below the image.

     Overlaying both children in one grid cell makes the row
     max(image, copy), so the copy can no longer overflow: it is now one
     of the things deciding how tall the hero is. */
  .hero {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
  }
  .hero__media,
  .hero__body { grid-area: 1 / 1; }

  .hero__media {
    /* Ideal height as a FLOOR, never a cap. A max-height here would stop
       the picture stretching when the copy makes the row taller and leave
       a strip of bare background beneath it — the same bug in a hat. */
    aspect-ratio: auto;
    min-height: var(--hero-media-h);
    max-height: none;
  }
  /* 🔴 THE IMAGE MUST NOT DECIDE HOW TALL THE HERO IS.
     With aspect-ratio removed above, a `height: 100%` image inside an
     auto-height box falls back to its own intrinsic ratio. /technology
     borrows a 4:3 photograph, so at 1280px wide it demanded 960px of
     height in a 690px viewport — and because that hero is sticky, the
     bottom of it could never be reached. Taking the image out of flow
     leaves the row as max(ideal height, copy), which is the whole point,
     and object-fit does the cropping it was always meant to do. */
  .hero__img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  .hero__media::after {
    /* Keeps the left safe zone legible over a bright image without
       darkening the whole frame. */
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(
      100deg,
      rgba(251, 250, 247, .97) 0%,
      rgba(251, 250, 247, .90) 26%,
      rgba(251, 250, 247, .30) 52%,
      rgba(251, 250, 247, 0) 72%
    );
  }
  /* ---- Rotating range banners --------------------------------------
     The tier banners delivered 16 Sep 2026 are far darker on the left
     than the single heroes this scrim was tuned for, and the lede runs
     furthest right of the three lines. Measured on /clear-ppf at 1280px
     against the ORIGINAL curve: Dolphin 4.25:1, Shark 2.28:1, Orca
     2.24:1, all on the lede, against a 4.5 requirement. Unreadable on
     two of the three.

     Two changes, and deliberately BOTH rather than one taken further:

       · the veil holds .78 out to 52% instead of falling to .30 there,
         which is where the copy actually ends;
       · the lede is capped at 34rem instead of 42rem, so it stops
         before the veil thins.

     Fixing it with opacity alone needed the gradient near-opaque across
     three-fifths of the frame, which would have buried the picture the
     client asked to show. Past 78% the veil is gone entirely — and in
     all nine banners the animal and the car sit in exactly that band.

     SCOPED to .hero--rotating so the commissioned heroes on the other
     pages, which were framed for the softer curve, keep it. */
  .hero--rotating .hero__media::after {
    background: linear-gradient(
      100deg,
      rgba(251, 250, 247, .97) 0%,
      rgba(251, 250, 247, .94) 30%,
      rgba(251, 250, 247, .78) 52%,
      rgba(251, 250, 247, .22) 72%,
      rgba(251, 250, 247, 0) 85%
    );
  }
  .hero--rotating .hero__lede { max-width: 34rem; }

  /* Positioning only. This used to ALSO carry max-width, margin-inline
     and padding-inline — the whole container recipe — while the markup
     inside it already has a .container of its own. Two containers meant
     two gutters, so hero copy started 48px right of every other block on
     the page and the left edge visibly stepped inward at the hero. The
     nested .container is the one that sets the width; this only puts the
     copy over the image. */
  .hero__body {
    position: relative;        /* in flow — its height now counts */
    display: flex;
    flex-direction: column;
    justify-content: center;
    /* Breathing room for the case this fix exists for: when the copy is
       what makes the hero tall, it must not touch the edges. */
    padding-block: var(--space-7);
  }
  .hero__inner { max-width: 34rem; }
}

/* Product-range hero — half viewport, depth marker top-right.
   MOBILE ONLY. This selector is more specific than the `.hero__media`
   rule in the >=992px block, so left unscoped its max-height beat the
   grid sizing there and re-capped the image — the copy then overflowed
   by up to 123px on /warranty instead of the row growing to fit it.
   Desktop height comes from --hero-media-h. */
@media (max-width: 991.98px) {
  .hero--range .hero__media { aspect-ratio: 16 / 11; max-height: 56vh; }
}
/* Desktop sizing comes from --hero-media-h; a max-height here would
   re-cap the image and undo the grid fix above. */

/* .depth-marker was removed 13 Sep 2026 along with the metre readings
   it displayed — see app/lib/products.php. Its rules, including the
   mobile container-padding fix added the same day, went with it. */

/* ------------------------------------------------------------------
   8. THE SIX MARKS — a direct reproduction of the black panel on the
   packaging. --gm-gold is legal as text here (8.1:1 on obsidian).
   ------------------------------------------------------------------ */

.marks { display: grid; grid-template-columns: repeat(2, 1fr); }
@media (min-width: 480px) { .marks { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 992px) { .marks { grid-template-columns: repeat(6, 1fr); } }

.mark {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-3);
  text-align: center;
  padding: var(--space-6) var(--space-3);
  border-right: 1px solid rgba(201, 162, 39, .22);
  border-bottom: 1px solid rgba(201, 162, 39, .22);
}
.mark:nth-child(2n) { border-right: 0; }
@media (min-width: 480px) {
  .mark { border-right: 1px solid rgba(201, 162, 39, .22); }
  .mark:nth-child(2n) { border-right: 1px solid rgba(201, 162, 39, .22); }
  .mark:nth-child(3n) { border-right: 0; }
  .mark:nth-last-child(-n+3) { border-bottom: 0; }
}
@media (min-width: 992px) {
  .mark:nth-child(3n) { border-right: 1px solid rgba(201, 162, 39, .22); }
  .mark:last-child { border-right: 0; }
  .mark { border-bottom: 0; }
}
.mark__icon { color: var(--gm-gold); }
.mark__label {
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-gold);
  line-height: 1.4;
}

/* ------------------------------------------------------------------
   9. CARDS
   ------------------------------------------------------------------ */

.card {
  background: var(--gm-card);
  border: 1px solid var(--gm-edge);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  padding: var(--space-6);
  transition: transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
.card--link:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-md);
  border-color: var(--gm-hairline);
}
.card__icon { color: var(--gm-gold); margin-bottom: var(--space-4); }
.card__title { margin-bottom: var(--space-2); }
.card__body { font-size: var(--fs-body); color: var(--gm-graphite); }

/* Cards on obsidian invert. */
.section--obsidian .card {
  background: var(--gm-obsidian-2);
  border-color: var(--gm-edge-dark);
  box-shadow: none;
}
.section--obsidian .card__title { color: var(--gm-paper); }
.section--obsidian .card__body { color: rgba(251, 250, 247, .7); }

/* Numbered step list. */
.steps { counter-reset: step; display: grid; gap: var(--space-5); }
@media (min-width: 768px) { .steps { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1200px) { .steps--4 { grid-template-columns: repeat(4, 1fr); } }
.step { counter-increment: step; padding-top: var(--space-5); border-top: 1px solid var(--gm-hairline); }
.step::before {
  content: counter(step, decimal-leading-zero);
  display: block;
  font-family: var(--ff-data);
  font-size: var(--fs-data-s);
  font-variant-numeric: tabular-nums;
  color: var(--gm-gold-text);
  margin-bottom: var(--space-3);
}
.step__title { margin-bottom: var(--space-2); }

/* ------------------------------------------------------------------
   10. THE DEPTH INDEX
   The signature. A rail calibrated in metres rides above full-width
   bands, each film pinned at its depth, illuminating as you scroll past.

   Layout note: the bands are full-width SIBLINGS and the rail lives in
   its own absolutely-positioned column above them. Nesting the bands
   inside a grid alongside the rail and bleeding each background with
   `left:50%; margin-left:-50vw` centres the bleed on the content column
   rather than the viewport — offset by the rail, that runs ~150px past
   the right edge at every desktop width.
   ------------------------------------------------------------------ */

.depth { position: relative; }

/* ── The rail, mobile first ────────────────────────────────────────────
   Under 992px there is no room for a 240px column beside the cards, so
   the same nav becomes a sticky horizontal strip under the header. It
   was previously display:none, which left phone visitors with no index
   of the range at all and nothing to navigate it by. */
.depth__rail-layer {
  position: sticky;
  top: var(--header-h);
  z-index: 3;
}
.depth__rail-layer .container { padding-inline: 0; }

.depth-rail {
  background: var(--gm-paper);
  border-block: 1px solid var(--gm-edge);
}
.depth-rail__title {
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-gold-text);
}

@media (max-width: 991.98px) {
  .depth-rail__title {
    padding: var(--space-3) var(--container-pad) 0;
    margin-bottom: var(--space-2);
  }
  /* Horizontal chip strip. overflow-x lives here so the page body never
     scrolls sideways — the strip scrolls inside itself. */
  .depth-rail__track {
    display: flex;
    gap: var(--space-2);
    overflow-x: auto;
    scroll-snap-type: x proximity;
    padding: 0 var(--container-pad) var(--space-3);
    scrollbar-width: none;
  }
  .depth-rail__track::-webkit-scrollbar { display: none; }
  .depth-tick {
    flex: none;
    scroll-snap-align: center;
    padding: var(--space-2) var(--space-4);
    border: 1px solid var(--gm-edge);
    border-radius: var(--radius);
    background: var(--gm-card);
  }
  .depth-tick__name  { font-size: .8125rem; }
  .depth-tick__depth { font-size: .6875rem; }
  .depth-tick[aria-current] {
    border-color: var(--gm-gold);
    background: var(--gm-gold-wash);
  }
}

/* ── Desktop: the original sticky card ───────────────────────────────── */
@media (min-width: 992px) {
  .depth__rail-layer {
    position: absolute;
    inset: 0 0 0 0;
    pointer-events: none;
    z-index: 2;
    top: 0;
  }
  .depth__rail-layer .container {
    height: 100%;
    padding-inline: var(--container-pad);
  }
  .depth-rail {
    position: sticky;
    /* --header-h, not a hard-coded 88px: the header's height is defined
       once and this has to follow it. */
    top: calc(var(--header-h) + var(--space-6));
    width: 240px;
    pointer-events: auto;
    border: 1px solid var(--gm-edge);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
    padding: var(--space-5);

    /* 🔴 A STICKY ELEMENT CANNOT BE SCROLLED PAST — so it must never be
       taller than the space it is pinned in. Nine films made this rail
       722px tall; on a laptop with 690px of page height the last two,
       Sperm Whale and Seadevil, were cut off the bottom and there was no
       way to reach them. Scrolling the page just moved the rail with it.

       This is a VIEWPORT HEIGHT failure, which is why width-based
       testing never caught it. `dvh` rather than `vh` so a mobile
       browser's collapsing toolbar is accounted for; the vh fallback is
       for anything that does not know dvh. */
    max-height: calc(100vh  - var(--header-h) - var(--space-6) * 2);
    max-height: calc(100dvh - var(--header-h) - var(--space-6) * 2);
    overflow-y: auto;
    /* Reaching the end of the rail must not start scrolling the page
       underneath it. */
    overscroll-behavior: contain;
    scrollbar-width: thin;
  }
  .depth-rail__title { margin-bottom: var(--space-5); }
}
.depth-rail__title {
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-gold-text);
  margin-bottom: var(--space-5);
}
@media (min-width: 992px) {
  .depth-rail__track { position: relative; padding-left: var(--space-5); }
  .depth-rail__track::before {
    content: "";
    position: absolute;
    left: 0; top: 4px; bottom: 4px;
    width: 1px;
    background: linear-gradient(
      to bottom,
      var(--gm-hairline) 0%,
      var(--gm-gold) 40%,
      var(--gm-obsidian) 100%
    );
  }
  .depth-tick { padding-block: var(--space-3); }
  .depth-tick::before {
    content: "";
    position: absolute;
    left: calc(var(--space-5) * -1);
    top: 50%;
    width: 9px; height: 1px;
    background: var(--gm-edge);
    transform: translate(-4px, -50%);
    transition: background-color var(--dur-base) var(--ease-out),
                width var(--dur-base) var(--ease-out);
  }
  /* The active mark. `aria-current` drives it rather than a class, so the
     thing a screen reader announces and the thing the eye follows can
     never disagree. */
  .depth-tick[aria-current]::before { background: var(--gm-gold); width: 15px; }
}

.depth-tick {
  position: relative;
  /* The tick used to be two lines — film name over its depth reading —
     and that made it tall enough to touch. Losing the depth line on
     13 Sep 2026 dropped it under the 44px floor at every width below
     992px, which /_audit.html caught. The minimum is set explicitly
     rather than restored with padding, so it cannot drift again if the
     type changes. */
  display: flex;
  flex-direction: column;
  justify-content: center;
  min-height: 44px;
  font-family: var(--ff-data);
  font-size: var(--fs-data-s);
  font-variant-numeric: tabular-nums;
  color: var(--gm-slate);
  text-decoration: none;
  transition: color var(--dur-base) var(--ease-out),
              border-color var(--dur-base) var(--ease-out),
              background-color var(--dur-base) var(--ease-out);
}
.depth-tick:hover { color: var(--gm-ink); }
.depth-tick:focus-visible {
  outline: 2px solid var(--gm-gold);
  outline-offset: 3px;
  border-radius: var(--radius);
}
.depth-tick[aria-current] { color: var(--gm-ink); }
.depth-tick[aria-current] .depth-tick__name { color: var(--gm-gold-text); }
.depth-tick__name { display: block; font-family: var(--ff-display); font-size: .9375rem; font-weight: 600; }
.depth-tick__depth { display: block; font-size: .75rem; }

/* Clicking a tick must not land the card under the sticky header (or,
   on mobile, under the header AND the strip). Anchor scrolling honours
   this without any JavaScript. */
/* On mobile the sticky strip sits below the sticky header, so an anchor
   target has to clear BOTH. --depth-strip-h is measured and set by
   main.js because the strip's height depends on its own type and
   wrapping; the fallback here is the measured height at 390px, so the
   offset is close enough to be usable with JavaScript off and exact
   with it on. */
.depth-band,
.product-card {
  scroll-margin-top: calc(var(--header-h) + var(--depth-strip-h, 133px) + var(--space-4));
}
@media (min-width: 992px) {
  .depth-band,
  .product-card { scroll-margin-top: calc(var(--header-h) + var(--space-6)); }
}

/* Bands. Full width, so the surface reaches the viewport edge and the
   page genuinely darkens as the range descends. */
.depth-band { padding-block: clamp(28px, 4vw, 56px); }
.depth-band--paper     { background: var(--gm-paper); }
.depth-band--champagne { background: var(--gm-champagne); }
.depth-band--obsidian  { background: var(--gm-obsidian); }

/* Clear the rail column on desktop. */
@media (min-width: 992px) {
  .depth-band__inner { margin-left: calc(240px + clamp(32px, 5vw, 72px)); }
}

/* --- Product card --- */

.product-card {
  display: grid;
  gap: var(--space-5);
  padding: var(--space-6);
  background: var(--gm-card);
  border: 1px solid var(--gm-edge);
  border-radius: var(--radius);
  box-shadow: var(--shadow-sm);
  transition: transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out),
              border-color var(--dur-fast) var(--ease-out);
}
@media (min-width: 768px) {
  .product-card { grid-template-columns: 1fr 1.35fr; align-items: center; gap: var(--space-6); }
}
.product-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-md);
  border-color: var(--gm-hairline);
}

.product-card__media {
  aspect-ratio: 1 / 1;
  width: 100%;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--gm-mist);
}
.product-card__img { width: 100%; height: 100%; object-fit: cover; }

.product-card__eyebrow {
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-gold-text);
  margin-bottom: var(--space-3);
}
.product-card__name {
  font-family: var(--ff-display);
  font-size: 2rem;
  line-height: 1.05;
  font-weight: 600;
  letter-spacing: var(--tracking-tight);
  color: var(--gm-ink);
  margin-bottom: var(--space-3);
}
.product-card__character {
  color: var(--gm-graphite);
  max-width: 42ch;
  margin-bottom: var(--space-5);
}
.product-card__specs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-5);
  padding-block: var(--space-4);
  border-top: 1px solid var(--gm-edge-soft);
  border-bottom: 1px solid var(--gm-edge-soft);
  margin-bottom: var(--space-5);
}
.product-card__spec { display: flex; flex-direction: column; gap: 2px; }
.product-card__spec-label {
  font-size: .6875rem;
  letter-spacing: .06em;
  text-transform: uppercase;
  color: var(--gm-slate);
}
.product-card__spec-value {
  font-family: var(--ff-data);
  font-size: var(--fs-data-s);
  font-variant-numeric: tabular-nums;
  color: var(--gm-ink);
}

/* Apex tier — Orca. Gold-ruled, per PRD 01 §3. */
.product-card--apex { border-color: var(--gm-gold); box-shadow: 0 0 0 1px var(--gm-gold-wash), var(--shadow-sm); }

/* Cards on the obsidian sections invert. */
.section--obsidian .product-card {
  background: var(--gm-obsidian-2);
  border-color: var(--gm-edge-dark);
  box-shadow: none;
}
.section--obsidian .product-card__name { color: var(--gm-paper); }
.section--obsidian .product-card__character { color: rgba(251, 250, 247, .72); }
.section--obsidian .product-card__eyebrow { color: var(--gm-gold); }
.section--obsidian .product-card__specs { border-color: var(--gm-edge-dark); }
.section--obsidian .product-card__spec-value { color: var(--gm-paper); }
.section--obsidian .product-card__spec-label { color: rgba(251, 250, 247, .5); }
.section--obsidian .product-card__media { background: var(--gm-obsidian); }

/* ------------------------------------------------------------------
   11. SPECIFICATION TABLES
   Below 992px these stack into cards rather than side-scrolling — the
   table needs ~700px and the container only reaches that at 992px.
   ------------------------------------------------------------------ */

.spec-table__wrap { overflow-x: auto; }

.spec-table { width: 100%; border-collapse: collapse; }
.spec-table caption {
  text-align: left;
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-gold-text);
  padding-bottom: var(--space-4);
}
.spec-table th, .spec-table td {
  text-align: left;
  padding: var(--space-4);
  border-bottom: 1px solid var(--gm-edge-soft);
  vertical-align: top;
}
.spec-table thead th {
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-ink);
  border-bottom: 1px solid var(--gm-gold);
  white-space: nowrap;
}
.spec-table tbody tr:nth-child(odd) { background: var(--gm-mist); }
.spec-table__item { color: var(--gm-ink); font-weight: 500; }
.spec-table__value {
  font-family: var(--ff-data);
  font-size: var(--fs-data);
  font-variant-numeric: tabular-nums;
  color: var(--gm-ink);
  white-space: nowrap;
}
.spec-table__method { font-family: var(--ff-data); font-size: var(--fs-data-s); color: var(--gm-slate); white-space: nowrap; }

.spec-tbc {
  display: inline-block;
  margin-left: var(--space-2);
  padding: 1px 6px;
  border: 1px solid var(--gm-hairline);
  border-radius: var(--radius);
  font-family: var(--ff-label);
  font-size: .625rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--gm-gold-text);
  vertical-align: 2px;
}

/* Stacked-card form below 992px. */
@media (max-width: 991px) {
  .spec-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .spec-table, .spec-table tbody, .spec-table tr, .spec-table td { display: block; width: 100%; }
  /* The caption has to come with them. Left as display:table-caption
     inside a table that is no longer a table, it is orphaned from the
     formatting context and shrink-wraps to its widest unbreakable word:
     measured at 104px inside a 335px column, which wrapped
     "Sunroof Film — specification" onto three lines and left the em
     dash alone on a 12px line of its own.

     At full width the same text is 221px and fits on one line, so this
     is the whole fix — the dash was the symptom, not the cause. Scoped
     to this breakpoint because above it the table IS a table, and a
     block caption there would be wrapped in an anonymous row and pulled
     inside the table box. */
  .spec-table caption { display: block; width: 100%; }
  .spec-table tr {
    border: 1px solid var(--gm-edge);
    border-radius: var(--radius);
    margin-bottom: var(--space-3);
    padding: var(--space-4);
    background: var(--gm-card);
  }
  .spec-table tbody tr:nth-child(odd) { background: var(--gm-card); }
  .spec-table td { border: 0; padding: 0; }
  .spec-table td + td { margin-top: var(--space-2); }
  .spec-table__item { font-size: var(--fs-caption); color: var(--gm-slate); font-weight: 400; }
  .spec-table__value { font-size: 1.0625rem; white-space: normal; }
  .spec-table__method::before {
    content: "Method · ";
    font-family: var(--ff-body);
    color: var(--gm-slate);
  }
}

/* ------------------------------------------------------------------
   12. STAT COUNTERS
   Numbers render at 56px so --gm-gold is legal as text (>= 24px).
   ------------------------------------------------------------------ */

.stats { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-6); }
@media (min-width: 992px) { .stats { grid-template-columns: repeat(4, 1fr); } }

.stat { padding-top: var(--space-5); border-top: 1px solid var(--gm-hairline); }
.stat__value {
  font-family: var(--ff-display);
  font-size: clamp(2.25rem, 5vw, 3.5rem);
  line-height: 1;
  font-weight: 700;
  letter-spacing: -.03em;
  /* --gm-gold measures 2.32:1 here and AA large text needs 3:1. */
  color: var(--gm-gold-deep);
  font-variant-numeric: tabular-nums;
  margin-bottom: var(--space-3);
}
.stat__label { font-size: var(--fs-body); color: var(--gm-graphite); margin-bottom: 2px; }
.stat__method { font-family: var(--ff-data); font-size: var(--fs-caption); color: var(--gm-slate); }

/* ------------------------------------------------------------------
   13. FORMS
   ------------------------------------------------------------------ */

.field { display: block; margin-bottom: var(--space-5); }
.field__label {
  display: block;
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-ink);
  margin-bottom: var(--space-2);
}
.field__optional { color: var(--gm-slate); font-weight: 400; letter-spacing: .04em; text-transform: none; }

.input, .select, .textarea {
  display: block;
  width: 100%;
  /* A text control's min-content width is its default `size` — about 20
     characters, ~332px here — and width:100% does not override it. As a
     grid or flex item that intrinsic floor wins, so on a 320px phone the
     contact form forced the whole page 32px wider than the screen and
     every section inherited the horizontal scroll. min-width:0 lets the
     control shrink to whatever the track actually offers. */
  min-width: 0;
  min-height: 48px;
  padding: 13px var(--space-4);
  background: var(--gm-card);
  border: 1px solid var(--gm-edge);
  border-radius: var(--radius);
  color: var(--gm-ink);
  font-family: var(--ff-body);
  font-size: var(--fs-body);
  transition: border-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.input::placeholder, .textarea::placeholder { color: var(--gm-slate); }
.input:focus, .select:focus, .textarea:focus {
  border-color: var(--gm-gold);
  box-shadow: 0 0 0 3px var(--gm-gold-wash);
  outline: none;                       /* replaced by the ring above */
}
.input:focus-visible, .select:focus-visible, .textarea:focus-visible {
  outline: var(--focus-ring);
  outline-offset: 1px;
}
.textarea { min-height: 140px; resize: vertical; padding-block: var(--space-4); }

.select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23464E5C' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m5 9 7 7 7-7'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right var(--space-4) center;
  background-size: 18px;
  padding-right: var(--space-8);
}

.field__help { display: block; margin-top: var(--space-2); font-size: var(--fs-caption); color: var(--gm-slate); }

/* Errors sit beside the field they belong to, never only at the top. */
.field--error .input,
.field--error .select,
.field--error .textarea { border-color: #A3231F; }
.field__error {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-top: var(--space-2);
  font-size: var(--fs-caption);
  color: #8E1F1B;
}

.form-actions { display: flex; flex-wrap: wrap; gap: var(--space-3); margin-top: var(--space-6); }

/* ------------------------------------------------------------------
   14. VERIFY WIDGET + STATUS CHIPS
   Chips carry colour AND text — colour is never the sole carrier of
   meaning (PRD 01 §8).
   ------------------------------------------------------------------ */

.verify-panel {
  background: var(--gm-card);
  border: 1px solid var(--gm-edge);
  border-radius: var(--radius);
  box-shadow: var(--shadow-md);
  padding: clamp(24px, 4vw, 40px);
}
.verify-panel__title { margin-bottom: var(--space-2); }
.verify-panel__lede { margin-bottom: var(--space-6); font-size: var(--fs-body); color: var(--gm-graphite); }

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 5px var(--space-3);
  border: 1px solid;
  border-radius: var(--radius);
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}
.chip::before { content: ""; width: 6px; height: 6px; border-radius: 50%; background: currentColor; flex: none; }
.chip--active    { color: var(--gm-gold-text); border-color: var(--gm-gold); background: var(--gm-gold-wash); }
.chip--expired   { color: #4C535F; border-color: var(--gm-edge); background: var(--gm-mist); }
.chip--cancelled { color: #8E1F1B; border-color: rgba(142, 31, 27, .3); background: rgba(142, 31, 27, .06); }
.chip--claim     { color: #6E5111; border-color: var(--gm-hairline); background: var(--gm-champagne); }

.record { display: grid; gap: var(--space-4); margin-top: var(--space-6); }
@media (min-width: 576px) { .record { grid-template-columns: repeat(2, 1fr); } }
.record__cell { padding-top: var(--space-3); border-top: 1px solid var(--gm-edge-soft); }
.record__key {
  display: block;
  font-size: .6875rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--gm-slate);
  margin-bottom: 4px;
}
.record__val {
  font-family: var(--ff-data);
  font-size: var(--fs-data);
  font-variant-numeric: tabular-nums;
  color: var(--gm-ink);
}
.record__val--name { font-family: var(--ff-body); }

.notice {
  display: flex;
  gap: var(--space-3);
  padding: var(--space-5);
  border: 1px solid var(--gm-edge);
  border-left: 2px solid var(--gm-gold);
  border-radius: var(--radius);
  background: var(--gm-champagne);
  font-size: var(--fs-body);
  color: var(--gm-graphite);
}
.notice__icon { color: var(--gm-gold-text); flex: none; }
.notice--quiet { border-left-color: var(--gm-edge); background: var(--gm-card); }

/* ------------------------------------------------------------------
   15. FILM CONSTRUCTION DIAGRAM
   ------------------------------------------------------------------ */

.layers { display: grid; gap: var(--space-6); align-items: center; }
@media (min-width: 992px) { .layers { grid-template-columns: 1fr 1fr; gap: clamp(40px, 6vw, 88px); } }

/* The construction diagram inside an OBSIDIAN section (/technology).
   Two things have to change on a dark ground:

     · --gm-slate is the caption colour everywhere else and measures
       about 3:1 on --gm-obsidian, which fails AA for body text;
     · --gm-edge is a dark hairline meant for light grounds, so the
       panel border disappears entirely.

   The media box is ALREADY obsidian (.figure-panel--diagram), so the
   image bleeds into the section by design — the border is a faint light
   edge to define the panel, not a frame around it. */
.section--obsidian .figure-panel__media { border-color: rgba(251, 250, 247, .14); }
.section--obsidian .figure-panel__caption { color: rgba(251, 250, 247, .72); }

.layer-stack { display: grid; gap: 3px; }
.layer-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--gm-edge);
  border-radius: var(--radius);
  background: var(--gm-card);
  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.layer-bar:hover { border-color: var(--gm-gold); background: var(--gm-gold-wash); }
.layer-bar__name { font-family: var(--ff-display); font-weight: 600; font-size: 1rem; color: var(--gm-ink); }
.layer-bar__fn { display: block; font-size: var(--fs-caption); color: var(--gm-slate); margin-top: 2px; max-width: 34ch; }
.layer-bar__thickness {
  font-family: var(--ff-data);
  font-size: var(--fs-data-s);
  font-variant-numeric: tabular-nums;
  color: var(--gm-gold-text);
  white-space: nowrap;
  flex: none;
}
.layer-total {
  margin-top: var(--space-4);
  padding-top: var(--space-4);
  border-top: 1px solid var(--gm-gold);
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--space-4);
}
.layer-total__label {
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-ink);
}
.layer-total__value {
  font-family: var(--ff-data);
  font-size: 1.125rem;
  font-variant-numeric: tabular-nums;
  color: var(--gm-gold-text);
}

/* ------------------------------------------------------------------
   16. TIER TABS (product range pages)
   Real anchors, enhanced with JS — they still work with JS off.
   ------------------------------------------------------------------ */

.tiers { border-bottom: 1px solid var(--gm-edge); margin-bottom: var(--space-7); }
.tiers__list { display: flex; gap: var(--space-2); overflow-x: auto; scrollbar-width: none; }
.tiers__list::-webkit-scrollbar { display: none; }
.tier-tab {
  display: inline-flex;
  align-items: baseline;
  gap: var(--space-2);
  min-height: 48px;
  padding: var(--space-4) var(--space-5);
  border-bottom: 2px solid transparent;
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-slate);
  white-space: nowrap;
  transition: color var(--dur-fast) var(--ease-out), border-color var(--dur-fast) var(--ease-out);
}
.tier-tab:hover { color: var(--gm-ink); }
.tier-tab.is-active { color: var(--gm-ink); border-bottom-color: var(--gm-gold); }
.tier-tab__years { font-family: var(--ff-data); font-size: .6875rem; color: var(--gm-gold-text); }

.tier-panel[hidden] { display: none; }

/* ------------------------------------------------------------------
   17. GALLERY
   ------------------------------------------------------------------ */

.filters { display: flex; flex-wrap: wrap; gap: var(--space-2); margin-bottom: var(--space-7); }
.filter-chip {
  min-height: 44px;
  padding: 10px var(--space-4);
  border: 1px solid var(--gm-edge);
  border-radius: var(--radius);
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-graphite);
  background: var(--gm-card);
  white-space: nowrap;
  transition: border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.filter-chip:hover { border-color: var(--gm-gold); }
.filter-chip[aria-pressed="true"] {
  background: var(--gm-ink);
  border-color: var(--gm-ink);
  color: var(--gm-paper);
}

.gallery { display: grid; gap: var(--space-3); grid-template-columns: repeat(2, 1fr); }
@media (min-width: 768px) { .gallery { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1200px) { .gallery { grid-template-columns: repeat(4, 1fr); } }
.gallery__item { position: relative; overflow: hidden; border-radius: var(--radius); background: var(--gm-mist); aspect-ratio: 4 / 3; }
.gallery__item img { width: 100%; height: 100%; object-fit: cover; transition: transform 400ms var(--ease-out); }
.gallery__item:hover img { transform: scale(1.03); }
.gallery__item[hidden] { display: none; }

/* ------------------------------------------------------------------
   18. UTILITIES
   ------------------------------------------------------------------ */

.flow > * + * { margin-top: var(--space-5); }
.mt-0 { margin-top: 0; }
.mt-4 { margin-top: var(--space-4); }
.mt-5 { margin-top: var(--space-5); }
.mt-6 { margin-top: var(--space-6); }
.mt-7 { margin-top: var(--space-7); }
.mb-4 { margin-bottom: var(--space-4); }
.mb-5 { margin-bottom: var(--space-5); }
.mb-6 { margin-bottom: var(--space-6); }
.text-center { text-align: center; }

.list-check > li,
.list-cross > li {
  display: flex;
  gap: var(--space-3);
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--gm-edge-soft);
  color: var(--gm-graphite);
}
.list-check > li::before,
.list-cross > li::before {
  flex: none;
  width: 18px;
  font-family: var(--ff-data);
  font-size: var(--fs-data-s);
  line-height: 1.6;
}
.list-check > li::before { content: "+"; color: var(--gm-gold-text); }
.list-cross > li::before { content: "\2212"; color: var(--gm-slate); }

.badge-tbc {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: 4px var(--space-3);
  border: 1px dashed var(--gm-hairline);
  border-radius: var(--radius);
  font-family: var(--ff-label);
  font-size: .625rem;
  font-weight: 600;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--gm-gold-text);
  background: var(--gm-gold-wash);
}

/* ------------------------------------------------------------------
   19. MOTION
   Initial hidden states are gated behind .js so the page is fully
   readable when JavaScript is disabled or fails to load. Nothing may be
   permanently invisible.
   ------------------------------------------------------------------ */

.js [data-reveal] { opacity: 0; }
.js [data-hero-line] { opacity: 0; }

/* Reduced motion short-circuits everything: main.js neither starts Lenis
   nor registers ScrollTrigger, and these rules restore the final state
   in case the stylesheet loads before the script decides. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .js [data-reveal],
  .js [data-hero-line] { opacity: 1 !important; transform: none !important; }
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ------------------------------------------------------------------
   20. PRINT
   ------------------------------------------------------------------ */

@media print {
  .site-header, .mobile-nav, .site-footer, .skip-link, .btn { display: none !important; }
  body { background: #fff; color: #000; }
  .section { padding-block: 12pt; }
  a[href^="http"]::after { content: " (" attr(href) ")"; font-size: 9pt; }
}

/* ------------------------------------------------------------------
   21. HOME — DEPTH TEASER
   A condensed reading of the Depth Index. The row surface follows the
   finish, so the list genuinely darkens as it descends.
   ------------------------------------------------------------------ */

.depth-teaser { border-top: 1px solid var(--gm-edge); }
.depth-teaser__row { border-bottom: 1px solid var(--gm-edge); }
.depth-teaser__row--paper     { background: var(--gm-paper); }
.depth-teaser__row--champagne { background: var(--gm-champagne); }
.depth-teaser__row--obsidian  { background: var(--gm-obsidian); border-bottom-color: var(--gm-edge-dark); }
.depth-teaser__row--apex      { box-shadow: inset 2px 0 0 var(--gm-gold); }

.depth-teaser__link {
  display: grid;
  /* The depth column went with the metre readings on 13 Sep 2026.
     Leaving `auto` here kept an empty first column and a --space-5 gap
     in front of every film name. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-areas:
    "name"
    "meta";
  align-items: center;
  gap: 2px var(--space-5);
  padding: var(--space-5) var(--space-4);
  transition: background-color var(--dur-fast) var(--ease-out);
}
@media (min-width: 768px) {
  .depth-teaser__link {
    grid-template-columns: 8rem 1fr auto auto;
    grid-template-areas: "depth name range years";
    gap: var(--space-5);
    padding-inline: var(--space-5);
  }
}
.depth-teaser__link:hover { background: var(--gm-gold-wash); }
.depth-teaser__row--obsidian .depth-teaser__link:hover { background: rgba(201, 162, 39, .12); }

.depth-teaser__depth {
  grid-area: depth;
  font-family: var(--ff-data);
  font-size: var(--fs-data-s);
  font-variant-numeric: tabular-nums;
  color: var(--gm-gold-text);
  white-space: nowrap;
}
.depth-teaser__name {
  grid-area: name;
  font-family: var(--ff-display);
  font-size: 1.375rem;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: var(--tracking-tight);
  color: var(--gm-ink);
}
.depth-teaser__range {
  grid-area: meta;
  font-size: var(--fs-caption);
  color: var(--gm-slate);
}
@media (min-width: 768px) { .depth-teaser__range { grid-area: range; text-align: right; } }
.depth-teaser__years {
  grid-area: meta;
  justify-self: end;
  font-family: var(--ff-data);
  font-size: var(--fs-data-s);
  font-variant-numeric: tabular-nums;
  color: var(--gm-graphite);
  white-space: nowrap;
}
@media (min-width: 768px) { .depth-teaser__years { grid-area: years; min-width: 5rem; text-align: right; } }

/* Obsidian rows invert. Gold is legal as text here. */
.depth-teaser__row--obsidian .depth-teaser__name  { color: var(--gm-paper); }
.depth-teaser__row--obsidian .depth-teaser__depth { color: var(--gm-gold); }
.depth-teaser__row--obsidian .depth-teaser__range { color: rgba(251, 250, 247, .55); }
.depth-teaser__row--obsidian .depth-teaser__years { color: rgba(251, 250, 247, .78); }

/* ------------------------------------------------------------------
   22. TRACE LIST + CTA BAND
   ------------------------------------------------------------------ */

.trace-list > li {
  display: flex;
  align-items: flex-start;
  gap: var(--space-4);
  padding-block: var(--space-4);
  border-top: 1px solid var(--gm-edge);
  color: var(--gm-graphite);
}
.trace-list > li > .icon { color: var(--gm-gold); flex: none; margin-top: 2px; }

.cta-band { text-align: center; }
.cta-band__inner { max-width: 44rem; margin-inline: auto; }
.cta-band__inner .lede { margin-inline: auto; }

/* ------------------------------------------------------------------
   23. VERIFY RESULT
   Rendered by JavaScript into an aria-live region. Status chips carry
   colour AND text — colour is never the sole carrier of meaning.
   ------------------------------------------------------------------ */

.verify-result:empty { display: none; }
.verify-result { margin-top: var(--space-6); }
.verify-result__head {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding-bottom: var(--space-4);
  border-bottom: 1px solid var(--gm-gold);
}
.verify-result__no {
  font-family: var(--ff-data);
  font-size: 1.0625rem;
  font-variant-numeric: tabular-nums;
  color: var(--gm-ink);
}
.verify-form .field:last-of-type { margin-bottom: var(--space-6); }
.btn[aria-busy="true"] { opacity: .65; pointer-events: none; }

/* ------------------------------------------------------------------
   24. (Depth bands moved to §10 with the rest of the Depth Index.)
   ------------------------------------------------------------------ */

/* ------------------------------------------------------------------
   25. TIER PANELS
   ------------------------------------------------------------------ */

.tier-panel__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-5);
  margin-bottom: var(--space-7);
}
.tier-panel__head > div { flex: 1 1 24rem; }

/* ------------------------------------------------------------------
   26. COVERAGE DIAGRAM
   The option buttons carry the meaning in text. The diagram is
   decorative and aria-hidden — coverage is never communicated by the
   highlight alone.
   ------------------------------------------------------------------ */

.coverage { display: grid; gap: var(--space-7); align-items: start; }
@media (min-width: 992px) {
  .coverage { grid-template-columns: 1fr 22rem; gap: clamp(40px, 5vw, 80px); }
}

.coverage__options { display: grid; gap: var(--space-3); }
.coverage__btn {
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--space-5);
  background: var(--gm-card);
  border: 1px solid var(--gm-edge);
  border-left: 2px solid var(--gm-edge);
  border-radius: var(--radius);
  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.coverage__btn:hover { border-color: var(--gm-hairline); }
.coverage__btn[aria-pressed="true"] {
  border-left-color: var(--gm-gold);
  background: var(--gm-gold-wash);
}
.coverage__label {
  display: block;
  font-family: var(--ff-display);
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--gm-ink);
  margin-bottom: 4px;
}
.coverage__blurb { display: block; font-size: var(--fs-caption); color: var(--gm-graphite); }

.coverage__figure { margin: 0; display: grid; gap: var(--space-4); justify-items: center; }
.coverage-svg { width: 100%; max-width: 15rem; height: auto; }

.coverage-svg__shell path {
  fill: var(--gm-card);
  stroke: var(--gm-edge);
  stroke-width: 1.5;
}
.coverage-svg__glass * { fill: var(--gm-mist); }

/* Panels default to unfilled and light up when their group is selected.
   Transitioning `fill` rather than opacity keeps the shell edges crisp. */
.coverage-svg__panel * {
  fill: transparent;
  transition: fill var(--dur-base) var(--ease-out);
}
.coverage-svg__panel.is-on * { fill: var(--gm-gold); }
.coverage-svg__panel.is-on-soft * { fill: var(--gm-gold-lite); }

/* ------------------------------------------------------------------
   27. LEGAL / PROSE PAGES
   ------------------------------------------------------------------ */

.legal { max-width: 44rem; }
.legal h2 { margin-top: var(--space-8); margin-bottom: var(--space-4); }
.legal h3 { margin-top: var(--space-6); margin-bottom: var(--space-3); }
.legal p, .legal li { color: var(--gm-graphite); }
.legal p + p { margin-top: var(--space-4); }
.legal ul { margin-top: var(--space-4); }
.legal ul > li {
  position: relative;
  padding-left: var(--space-5);
  padding-block: var(--space-2);
}
.legal ul > li::before {
  content: "";
  position: absolute;
  left: 0; top: 15px;
  width: 8px; height: 1px;
  background: var(--gm-gold);
}
/* ---------- Legal: a list that runs in columns ----------------------
   For the service-network city list on /warranty-terms. Sixteen
   one-word items in a single column inside the 44rem .legal measure is
   a column of whitespace with a word at the left of it; two columns
   halves the height and keeps the tick bullets the rest of the page
   uses. Single column on a phone, where two would be too narrow.
   break-inside stops a bullet being orphaned from its word. */
.legal .list-cols { columns: 2; column-gap: var(--space-6); }
.legal .list-cols > li { break-inside: avoid; }
@media (max-width: 479.98px) {
  .legal .list-cols { columns: 1; }
}

/* ---------- Legal: exclusions and claim steps -----------------------
   Two devices for the warranty terms, and both earn their place by
   doing something prose cannot.

   The EXCLUSIONS block exists because "not covered" is the part a
   claimant needs to find and the part a run-on sentence hides. It is
   set apart rather than decorated: no colour that reads as a warning
   banner, because these are ordinary contractual limits and dressing
   them as an alarm is its own kind of misleading.

   The STEPS list is numbered because it is a checklist of evidence
   someone assembles — the order is the order they will gather it in. */
.terms-exclude {
  margin-top: var(--space-5);
  padding: var(--space-5);
  background: var(--gm-mist);
  border-radius: var(--radius);
}
.terms-exclude__label {
  font-family: var(--ff-label);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-ink);
  margin: 0 0 var(--space-3);
}
/* Its own bullet: a short rule rather than the tick-like mark the
   .legal list uses, because these items are the opposite of a tick. */
.legal .terms-exclude__list { margin-top: 0; }
.legal .terms-exclude__list > li { padding-left: var(--space-5); }
.legal .terms-exclude__list > li::before {
  content: "";
  position: absolute;
  left: 0;
  /* 15px, matching the base .legal bullet: the li carries
     padding-block, so an em offset from the box top lands above the
     text rather than on its middle. */
  top: 15px;
  width: 10px;
  height: 1px;
  background: var(--gm-slate);
  border-radius: 0;
}

.legal .terms-steps {
  margin-top: var(--space-4);
  padding: 0;
  list-style: none;
  counter-reset: terms-step;
  display: grid;
  gap: var(--space-3);
}
.legal .terms-steps > li {
  counter-increment: terms-step;
  position: relative;
  padding-left: calc(var(--space-6) + 4px);
}
.legal .terms-steps > li::before {
  content: counter(terms-step, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 0;
  font-family: var(--ff-data);
  font-size: var(--fs-data-s);
  font-variant-numeric: tabular-nums;
  color: var(--gm-gold-text);   /* under 24px on paper — the text gold */
  background: none;
  width: auto;
  height: auto;
  border-radius: 0;
}

.legal__updated {
  font-family: var(--ff-data);
  font-size: var(--fs-caption);
  color: var(--gm-slate);
  padding-bottom: var(--space-5);
  border-bottom: 1px solid var(--gm-edge);
  margin-bottom: var(--space-6);
}

/* ------------------------------------------------------------------
   28. PAGE HEADER (non-hero pages)
   ------------------------------------------------------------------ */

.page-head { padding-block: clamp(48px, 7vw, 104px) clamp(32px, 4vw, 56px); }
.page-head__inner { max-width: 48rem; }

/* ------------------------------------------------------------------
   29. TWO-COLUMN COVERED / NOT COVERED
   ------------------------------------------------------------------ */

.cover-cols { display: grid; gap: var(--space-6); }
@media (min-width: 992px) { .cover-cols { grid-template-columns: repeat(3, 1fr); gap: var(--space-7); } }
.cover-col__title {
  padding-bottom: var(--space-4);
  margin-bottom: var(--space-4);
  border-bottom: 1px solid var(--gm-gold);
}

/* ------------------------------------------------------------------
   30. TECHNOLOGY LIST
   ------------------------------------------------------------------ */

.tech-list { display: grid; gap: 0; border-top: 1px solid var(--gm-edge); }
.tech-item {
  display: grid;
  gap: var(--space-3) var(--space-6);
  grid-template-columns: 1fr;
  padding-block: var(--space-6);
  border-bottom: 1px solid var(--gm-edge);
}
@media (min-width: 768px) { .tech-item { grid-template-columns: 5rem 1fr; } }
.tech-item__index {
  font-family: var(--ff-data);
  font-size: var(--fs-data-s);
  font-variant-numeric: tabular-nums;
  color: var(--gm-gold-text);
}
.tech-item__body { max-width: var(--measure); }
.tech-item__icon {
  display: inline-flex;
  vertical-align: -4px;
  margin-right: var(--space-3);
  color: var(--gm-gold);
}

/* ------------------------------------------------------------------
   31. DOWNLOADS
   ------------------------------------------------------------------ */

.downloads { display: grid; gap: var(--space-3); }
@media (min-width: 768px) { .downloads { grid-template-columns: repeat(2, 1fr); } }

.download {
  display: flex;
  align-items: center;
  gap: var(--space-4);
  padding: var(--space-5);
  background: var(--gm-card);
  border: 1px solid var(--gm-edge);
  border-radius: var(--radius);
  transition: border-color var(--dur-fast) var(--ease-out),
              transform var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
a.download:hover {
  border-color: var(--gm-gold);
  transform: translateY(-3px);
  box-shadow: var(--shadow-md);
}
.download__icon { color: var(--gm-gold); flex: none; }
.download__text { flex: 1; min-width: 0; }
.download__label { display: block; font-weight: 600; color: var(--gm-ink); }
.download__sub { display: block; font-size: var(--fs-caption); color: var(--gm-slate); margin-top: 2px; }
.download__arrow { color: var(--gm-gold-text); flex: none; }
.download--pending { opacity: .68; }

/* ------------------------------------------------------------------
   32. TECHNICAL REFERENCE PANEL
   A quiet bordered card. Deliberately NOT a badge: no seal, no
   checkmark, no laboratory logo, no "certified". PRD 01 §7.1.
   ------------------------------------------------------------------ */

.tech-ref {
  max-width: 52rem;
  padding: clamp(24px, 4vw, 40px);
  background: var(--gm-card);
  border: 1px solid var(--gm-edge);
  border-left: 2px solid var(--gm-gold);
  border-radius: var(--radius);
}
.tech-ref__title { margin-bottom: var(--space-4); }
.tech-ref__intro { color: var(--gm-graphite); margin-bottom: var(--space-5); }
.tech-ref__list { display: grid; gap: 0; }
.tech-ref__list > li {
  padding-block: var(--space-3);
  border-top: 1px solid var(--gm-edge-soft);
  font-family: var(--ff-data);
  font-size: var(--fs-data-s);
  line-height: 1.6;
  color: var(--gm-graphite);
}

/* ------------------------------------------------------------------
   33. CLAIM STEPS
   ------------------------------------------------------------------ */

.claim-steps { counter-reset: claim; }
.claim-steps > li {
  counter-increment: claim;
  display: flex;
  gap: var(--space-4);
  padding-block: var(--space-3);
  border-bottom: 1px solid var(--gm-edge-soft);
  color: var(--gm-graphite);
}
.claim-steps > li::before {
  content: counter(claim);
  flex: none;
  width: 24px; height: 24px;
  display: grid;
  place-items: center;
  border: 1px solid var(--gm-hairline);
  border-radius: var(--radius);
  font-family: var(--ff-data);
  font-size: .75rem;
  color: var(--gm-gold-text);
  margin-top: 2px;
}

/* ------------------------------------------------------------------
   34. ABOUT
   ------------------------------------------------------------------ */

.about-grid { display: grid; gap: var(--space-7); }
@media (min-width: 992px) { .about-grid { grid-template-columns: repeat(2, 1fr); gap: var(--space-8) clamp(40px, 6vw, 88px); } }
.about-item { max-width: 34rem; }
.about-item p { color: var(--gm-graphite); }

.about-figures { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--space-5); }
.about-figures > li { padding-top: var(--space-4); border-top: 1px solid var(--gm-hairline); }
.about-figures__n {
  display: block;
  font-family: var(--ff-display);
  font-size: clamp(2rem, 4vw, 3rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.03em;
  color: var(--gm-gold-deep);
  font-variant-numeric: tabular-nums;
}
.about-figures__l { display: block; margin-top: var(--space-2); font-size: var(--fs-caption); color: var(--gm-graphite); }

/* ------------------------------------------------------------------
   35. FORMS — layout
   ------------------------------------------------------------------ */

/* minmax(0, 1fr), never the implicit `auto` or a bare 1fr: both size the
   track to its content's minimum, which is exactly the overflow this
   file already fixes the same way in a dozen other grids. */
.field-row { display: grid; gap: 0 var(--space-4); grid-template-columns: minmax(0, 1fr); }
@media (min-width: 576px) { .field-row { grid-template-columns: repeat(2, minmax(0, 1fr)); } }

.form-status:empty { display: none; }
.form-status {
  margin-top: var(--space-5);
  padding: var(--space-4) var(--space-5);
  border: 1px solid var(--gm-edge);
  border-left: 2px solid var(--gm-gold);
  border-radius: var(--radius);
  background: var(--gm-champagne);
  font-size: var(--fs-body);
}
.form-status--error { border-left-color: #A3231F; background: rgba(163, 35, 31, .05); color: #8E1F1B; }

.contact-details { display: grid; gap: var(--space-4); }
/* An email address is one unbreakable token. Sat next to its icon on a
   320px screen, contact@goldmanprotection.com measures 250px starting at
   x=89 and pushed the whole page 19px wider than the viewport. The row
   has to be allowed to shrink (flex items default to min-content) and
   the address has to be allowed to break. */
.contact-details__row { display: flex; gap: var(--space-4); align-items: flex-start; color: var(--gm-graphite); min-width: 0; }
.contact-details__row > :not(.contact-details__icon) { min-width: 0; overflow-wrap: anywhere; }
.contact-details__icon { color: var(--gm-gold); flex: none; margin-top: 2px; }
.contact-details a:hover { color: var(--gm-gold-text); }

/* ------------------------------------------------------------------
   36. VERIFY PAGE
   ------------------------------------------------------------------ */

.verify-page__inner { max-width: 40rem; margin-inline: auto; }
.verify-page__help {
  margin-top: var(--space-7);
  padding-top: var(--space-6);
  border-top: 1px solid var(--gm-edge);
  text-align: center;
}
.verify-page__help p { color: var(--gm-graphite); }
.verify-samples[hidden] { display: none; }

/* ------------------------------------------------------------------
   37. EMPTY STATE
   ------------------------------------------------------------------ */

.empty-state {
  max-width: 34rem;
  margin-inline: auto;
  text-align: center;
  padding: clamp(40px, 6vw, 80px) var(--space-5);
  border: 1px dashed var(--gm-hairline);
  border-radius: var(--radius);
  background: var(--gm-card);
}
.empty-state__icon { display: inline-flex; color: var(--gm-gold); margin-bottom: var(--space-5); }
.empty-state .lede { margin-inline: auto; }

/* ------------------------------------------------------------------
   38. HERO SLIDER
   Four slides, each with its own copy.

   Stacking is gated behind `.js`. Without JavaScript the slides render
   as four normal sections rather than three of them being hidden by a
   rule whose controls never arrived.
   ------------------------------------------------------------------ */

.hero-slider { position: relative; background: var(--gm-paper); }
.hero-slider__viewport { position: relative; }

/* No JS: slides stack and read as ordinary sections. */
.hero-slide { position: relative; }
.hero-slide + .hero-slide { border-top: 1px solid var(--gm-edge); }

/* With JS: one slide occupies the box, the rest are stacked under it.
   grid-template-columns is minmax(0, 1fr), not the implicit `auto`.

   At 992px the media has aspect-ratio 2400/1000 AND min-height 460px,
   so its content demands 460 x 2.4 = 1104px of width. An auto-sized
   grid track honours that max-content request and the page overflows by
   112px. minmax(0, 1fr) caps the track at the container width and lets
   the image crop instead, which is what object-fit: cover is for. */
.js .hero-slider__viewport {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
}
.js .hero-slide {
  grid-area: 1 / 1;
  border-top: 0;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 700ms var(--ease-out), visibility 0s linear 700ms;
}
.js .hero-slide.is-active {
  opacity: 1;
  visibility: visible;
  pointer-events: auto;
  transition: opacity 700ms var(--ease-out), visibility 0s;
}

/* GSAP owns the crossfade once main.js confirms it loaded — see the
   hero slider block in main.js. Only the OPACITY transition is dropped;
   the visibility one has to stay or the outgoing slide is pulled out of
   the accessibility tree the instant the class flips and the fade has
   nothing left to fade. The CSS above remains the fallback for a visitor
   whose GSAP request failed. */
.hero-slider--anim .hero-slide            { transition: visibility 0s linear 700ms; }
.hero-slider--anim .hero-slide.is-active  { transition: visibility 0s; }

.hero-slide__media {
  position: relative;
  width: 100%;                       /* pinned: aspect-ratio + max-height
                                        with no width solves for width and
                                        overflows the viewport */
  aspect-ratio: 16 / 11;
  max-height: 62vh;
  overflow: hidden;
  background: var(--gm-mist);
}
.hero-slide__img { width: 100%; height: 100%; object-fit: cover; }

/* The hero clip. Sits over its own poster and under the legibility
   scrim, so the copy is never fighting moving pictures for contrast.

   opacity 0 until main.js has a `playing` event — not until the src is
   set, and not on `canplay`. A video that stalls, is blocked by a data
   saver, or fails to decode therefore shows nothing at all, and the
   poster underneath simply stays put. Nobody sees a black rectangle. */
.hero-slide__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  transition: opacity .8s ease;
  pointer-events: none;   /* the slider's own controls stay clickable */
}
.hero-slide__video.is-playing { opacity: 1; }

/* Belt and braces. main.js never assigns a src under reduced motion, so
   this should be unreachable — but a stylesheet that contradicts the
   preference is worse than one rule too many. */
@media (prefers-reduced-motion: reduce) {
  .hero-slide__video { display: none; }
}

.hero-slide__body { padding-block: clamp(36px, 5vw, 64px) clamp(0px, 6vw, 76px); }

/* PHONES: the hero has to fit around the browser's own chrome.
   At 375px the media is 258px (16/11) and the tallest slide's body is
   499px, so the primary CTA finished 800px down the page. An iPhone 16
   shows roughly 700-750px before Safari's bars, so that button was cut
   in half with nothing to suggest it continued.

   The source photographs are 2400x1000 — 2.4:1 — so 16/11 was already
   cropping well into them. Going to 2:1 on a phone is both shorter and
   closer to how the image was framed. With the tighter block padding
   that is ~82px reclaimed, which lifts the CTA clear of the fold.

   Reported on an iPhone 16, 13 Sep 2026. */
/* The stacked-phone block that used to live here (12/5 media, tightened
   rhythm, 13 Sep 2026) is gone: the phone hero is now an overlay and
   those rules fought it. The fold problem it solved is still solved —
   see the svh note above, and the measurements in the reply that
   accompanied this change. */
/* ---- PHONE AND TABLET: copy OVER the picture -----------------------
   Rebuilt 13 Sep 2026 at the client's request — most of this site's
   traffic is on a phone, and the stacked layout (picture, then a block
   of copy under it) was the plainest thing on the site while the
   desktop hero was the most expensive-looking.

   The desktop scrim runs LEFT to RIGHT, which is useless here: at
   390px there is no left third to hide copy in. So the phone gets its
   own gradient running BOTTOM to TOP, the copy sits at the foot of the
   frame, and the picture keeps the whole upper two-thirds.

   ONE TREATMENT FOR ALL FOUR SLIDES. The desktop hero flips between a
   light and a dark scrim depending on the picture; here the scrim is
   dark on every slide. That is not laziness — a bottom scrim has to be
   opaque enough to carry copy regardless of what is behind it, and once
   it is, a light variant would have to be equally opaque and would look
   like fog over the picture. Dark reads as cinema. It also makes the
   contrast predictable: light copy on a dark floor, every slide, rather
   than four different answers.

   HEIGHT is the constraint that bit last time. Chrome for iOS leaves
   ~653px on an iPhone 16, and the previous layout pushed the second CTA
   84px past the fold. svh is the small viewport height — the value with
   the browser's toolbars SHOWING — so the hero fits the worst case
   rather than the best one. dvh would grow and shrink as the toolbar
   hides, which moves the copy while you read it. */
@media (max-width: 991.98px) {
  .hero-slide {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
  }
  .hero-slide__media,
  .hero-slide__body { grid-area: 1 / 1; }

  .hero-slide__media {
    aspect-ratio: auto;
    /* HEIGHT COMES FROM THE CONTENT, not from the viewport.

       Three viewport-unit attempts went wrong in three different ways:
       a percentage does not know about the sticky header above it, svh
       and dvh disagree about toolbars, and none of them can be trusted
       in an emulator — every size I tested returned the same 566px,
       which is how I know I was tuning against a number that was not
       real.

       The grid row already grows to fit whichever is taller, the
       picture's floor or the copy plus its padding. Letting the copy
       win means the hero is exactly as tall as it needs to be — around
       500px with this copy — which sits inside the smallest phone
       screen this site sees with room to spare, and needs no
       arithmetic about browser chrome to stay there.

       The floor is for a slide whose copy is short; it should still
       read as a hero and not as a banner. */
    min-height: 460px;
    max-height: none;
    background: var(--gm-obsidian);   /* never light behind light copy */
  }
  .hero-slide__img,
  .hero-slide__video {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }

  /* Bottom-up. The curve is steep and it is steep for a measured
     reason: the copy block — eyebrow, two-line headline, four-line
     lede, two stacked buttons — is about 350px of a 504px hero, so it
     starts around 15% DOWN the frame, not near the bottom. A gentle
     gradient that only darkens past halfway leaves the eyebrow and the
     headline sitting on raw picture.

     Measured with the first version of this curve, over the real clips:
     1.02:1, 1.19:1, 1.85:1, 3.31:1. Invisible.

     Raised again on 13 Sep 2026 when the clips went back to their full
     ten seconds: the restored footage on slide 2 includes a bright
     neon-lit reveal the shorter cut never contained, and the eyebrow
     came to 4.12:1 against a 4.5 requirement there.

     So it reaches .62 by 12%, which is where the copy starts. What that
     costs is the top eighth of the frame being the only part at full
     strength; what it buys is copy that is readable on all four clips,
     bright and dark alike. On a phone the picture is atmosphere and the
     words are the message — this is the right way round. */
  .hero-slide__media::after,
  .hero-slide[data-hero-tone="dark"] .hero-slide__media::after {
    background: linear-gradient(
      180deg,
      rgba(11, 15, 21, .38) 0%,
      rgba(11, 15, 21, .70) 12%,
      rgba(11, 15, 21, .88) 30%,
      rgba(11, 15, 21, .93) 60%,
      rgba(11, 15, 21, .96) 100%
    );
  }

  .hero-slide__body {
    position: relative;               /* in flow — its height counts */
    display: flex;
    flex-direction: column;
    justify-content: flex-end;        /* copy sits on the floor */
    /* Bottom padding reserves the strip the controls now sit in. They
       are pinned over the picture on this breakpoint, so without it the
       second CTA and the progress bar would occupy the same pixels. */
    /* Reserve the controls' MEASURED height, published by main.js as
       --hero-controls-h, plus the 8px the strip is offset from the bottom
       and 28px of air.

       This was 84px, from "44px of control + 8px + 28px of air". 44px is
       the pause BUTTON; the control strip is 77px, so the true figure was
       113px and the copy was overlapping the controls by design — masked
       only because the last CTA happened to clear the button itself by
       15px. Any downward shift ate that, which is what the load tween was
       doing. The fallback matches a measured single-row strip, so the
       layout is right even in the frame before main.js runs. */
    padding-block: var(--space-4) calc(var(--hero-controls-h, 77px) + 36px);
  }

  /* THE SHADE BETWEEN THE COPY AND THE PICTURE — client request,
     13 Sep 2026, phones only. The media already carries a scrim; this
     is a second, softer one that belongs to the COPY rather than to the
     picture, so it moves with the text block however tall it gets
     instead of being pinned to a percentage of the frame. Contrast was
     already passing at 6.7-11:1 — this is about the words sitting on
     something, which is a different question from whether they can be
     read. */
  .hero-slide__body {
    background: linear-gradient(
      180deg,
      rgba(11, 15, 21, 0) 0%,
      rgba(11, 15, 21, .18) 22%,
      rgba(11, 15, 21, .42) 52%,
      rgba(11, 15, 21, .56) 100%
    );
  }

  /* ---- Controls, over the picture ---------------------------------
     Same treatment as desktop rather than a bar underneath. On a phone
     the hero is the whole screen, so a control strip below it was a
     second object competing with the CTAs. */
  .hero-slider__controls {
    position: absolute;
    left: 0; right: 0;
    bottom: var(--space-2);
    z-index: 3;
    padding-block: 0;
    border-bottom: 0;
  }
  .hero-slider__controls-inner { flex-wrap: nowrap; }

  /* Light, because they now sit on a dark picture. --gm-edge is 10%
     ink — invisible against a video. */
  .hero-slider__dot-track            { background: rgba(251, 250, 247, .34); }
  .hero-slider__dot:hover .hero-slider__dot-track { background: rgba(251, 250, 247, .52); }
  .hero-slider__dot.is-active .hero-slider__dot-track { background: rgba(251, 250, 247, .52); }
  .hero-slider__pause {
    background: rgba(251, 250, 247, .94);
    border-color: transparent;
    color: var(--gm-ink);
  }
  .hero-slider__pause:hover { background: var(--gm-paper); border-color: transparent; }

  /* Light copy on every slide, light or dark clip alike.

     --gm-gold-lite, not --gm-gold, and this one is arithmetic rather
     than taste. The eyebrow is 11px, so it needs 4.5:1. Over a bright
     frame, --gm-gold would need the scrim at .77 opacity to get there
     and --gm-gold-lite needs .61 — the difference between a picture you
     can still see and a black rectangle. It keeps the warm accent;
     plain white would need only .51 but throws the brand colour away
     for 0.1 of alpha. */
  .hero-slide .eyebrow           { color: var(--gm-gold-lite); }
  .hero-slide .eyebrow::before   { background: var(--gm-gold-lite); }
  .hero-slide__title             { color: var(--gm-paper); }
  .hero-slide__lede              { color: rgba(251, 250, 247, .86); }

  .hero-slide .btn--ink {
    background: var(--gm-paper);
    border-color: var(--gm-paper);
    color: var(--gm-ink);
  }
  .hero-slide .btn--outline {
    border-color: rgba(251, 250, 247, .34);
    color: var(--gm-paper);
    background: transparent;
  }

}

/* --- Starting price (26 Sep 2026) ---------------------------------
   --gm-gold-lite for the figure, for the same arithmetic as the
   eyebrow above: over a bright frame --gm-gold would need the scrim
   at .77 to reach 4.5:1, gold-lite needs .61. The figure is large
   enough that --gm-gold would technically pass, but matching the
   eyebrow keeps one accent on this surface instead of two golds a
   shade apart, which reads as a mistake rather than a hierarchy.

   The gold rule down the left is what makes it read as highlighted
   without a filled panel — a solid block here would compete with the
   white CTA directly beneath it. */
.hero-price {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 10px;
  margin: 0 0 var(--space-5);
  padding-left: var(--space-4);
  border-left: 2px solid var(--gm-gold-lite);
}
.hero-price__label {
  font-family: var(--ff-display);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: rgba(251, 250, 247, .78);
}
.hero-price__value {
  font-family: var(--ff-display);
  font-size: clamp(1.5rem, 3.2vw, 2rem);
  font-weight: 700;
  line-height: 1;
  letter-spacing: -.01em;
  font-variant-numeric: tabular-nums;
  color: var(--gm-gold-lite);
}
.hero-price__note {
  font-size: var(--fs-caption);
  color: rgba(251, 250, 247, .68);
}
/* The label sits above the figure rather than beside it once the
   three parts stop fitting on one line — beside it, "STARTING FROM"
   and the note end up on separate wrapped lines with the figure
   stranded between them. */
@media (max-width: 575.98px) {
  .hero-price { flex-direction: column; align-items: flex-start; gap: 2px; }
  .hero-price__value { font-size: 1.5rem; }
}

/* Phones only: one more step off the rhythm, and the buttons go full
   width. Two half-width buttons side by side at 360px give each about
   150px, which is a tap target pretending to be a button. */
@media (max-width: 575.98px) {
  .hero-slide__media   { min-height: 700px; }
  /* The bottom value is the controls' strip, NOT rhythm — it has to
     survive this block. Written as var(--space-5) here first, it
     overrode the reservation set above and the pause button landed on
     top of the second CTA.

     It then carried its own copy of the 84px literal, so fixing the rule
     above did nothing at this breakpoint — which is the one a phone
     actually gets. Same measured value as above; there is now one source
     of truth for this number instead of two that drifted apart. */
  .hero-slide__body    { padding-block: var(--space-3) calc(var(--hero-controls-h, 77px) + 36px); }
  .hero-slide .eyebrow { margin-bottom: var(--space-2); }
  .hero-slide__title   { margin-bottom: var(--space-3); }
  /* Smaller and tighter on a phone. Slide 1's lede carries one clause
     more than the others and runs to a fourth line at 17px; at 15px the
     hero keeps a comfortable margin above the control strip on every
     slide instead of just clearing it.

     ⚠️ If you are measuring this layout: only the ACTIVE slide's
     geometry is real. Inactive slides sit ~23px lower because their
     entrance tween has never run, and a probe that walks all four will
     report an overlap that nobody can see. Measure
     `.hero-slide.is-active`, and settle at least 800ms for the tween. */
  .hero-slide__lede    { margin-bottom: var(--space-4); font-size: .9375rem; line-height: 1.55; }
  .hero-slide__actions { gap: var(--space-2); flex-direction: column; align-items: stretch; }
  .hero-slide__actions .btn { width: 100%; }
}

.hero-slide__title { margin-bottom: var(--space-5); }
.hero-slide__lede  { max-width: 40rem; margin-bottom: var(--space-6); }
@media (min-width: 992px) {
  /* 30rem keeps the measure near 60 characters AND pulls the end of
     the longest line back out of the part of the frame where the
     scrim has thinned. Two reasons, same number. */
  .hero-slide__lede { max-width: 30rem; }
}
.hero-slide__actions { display: flex; flex-wrap: wrap; gap: var(--space-3); }

@media (min-width: 992px) {
  /* Same one-cell grid as .hero, for the same reason: .hero-slide__body
     is absolutely positioned and contributes no height, so the tallest
     slide's buttons ran into the control strip pinned at the bottom.
     Reserving space in the body alone only narrowed it from 37px to 29px
     — the copy has to be able to make the SLIDE taller, not just sit
     higher inside a fixed one. */
  .hero-slide {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
  }
  .hero-slide__media,
  .hero-slide__body { grid-area: 1 / 1; }

  .hero-slide__media {
    /* Ideal height as a floor, not a cap — see .hero__media. */
    aspect-ratio: auto;
    min-height: clamp(460px, calc(100vw * 1000 / 2400), 760px);
    max-height: none;
  }
  /* Out of flow, for the same reason as .hero__img above. */
  .hero-slide__img {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    object-fit: cover;
  }
  /* The scrim that keeps the left third legible. Every hero image is
     commissioned with this in mind — see docs/IMAGE-PROMPTS.md. */
  .hero-slide__media::after {
    content: "";
    position: absolute;
    inset: 0;
    /* RE-TUNED 13 Sep 2026, when the hero became video.
       The old curve was down to .45 by 52%, which was fine over a
       commissioned still whose left third was quiet by design. A clip
       has no such contract: its mid-tones wander, and the lede's
       longest line ends at 56-60% of the frame on a 1024px screen —
       the narrowest desktop, and therefore the worst case, because
       .hero-slide__inner is a fixed 34rem and eats a bigger FRACTION
       the narrower the viewport gets.

       Measured there: 2.07:1 on slide 3. Holding .72 to 58% puts the
       end of that line back above 4.5:1. Past 78% the veil is gone
       entirely, which is where the subject of all four clips sits.

       The dark curve below is DELIBERATELY weaker (.66) rather than
       matched for tidiness. It measures 7.1-8.0:1 as it stands, so
       matching this one would only hide more of two clips that are
       already legible. Symmetry is not a reason to veil a picture. */
    background: linear-gradient(
      100deg,
      rgba(251, 250, 247, .97) 0%,
      rgba(251, 250, 247, .94) 30%,
      rgba(251, 250, 247, .72) 58%,
      rgba(251, 250, 247, .12) 78%,
      rgba(251, 250, 247, 0) 100%
    );
  }
  /* ---- Dark slides -------------------------------------------------
     A hero picture that is nearly black cannot carry ink-on-paper copy.
     Measured before this existed: the lede on slides 1 and 2 came out at
     1.06:1 against its own background. Not "a bit low" — invisible.

     So the scrim flips rather than thickens. Thickening it was the
     obvious fix and it is the wrong one: to get ink legible over black
     the veil has to stay near-opaque across 62% of the frame, which is
     most of the picture the client asked to put there.

     Every colour below is one the site already uses on obsidian, where
     the palette is proven — --gm-gold measures 8.1:1 there and is legal
     as text at any size.

     Keyed off the SLIDE, not off video: the poster is the same dark
     frame, so this has to hold when no video ever plays. */
  .hero-slide[data-hero-tone="dark"] .hero-slide__media::after {
    background: linear-gradient(
      100deg,
      rgba(11, 15, 21, .95) 0%,
      rgba(11, 15, 21, .92) 30%,
      rgba(11, 15, 21, .66) 58%,
      rgba(11, 15, 21, .14) 78%,
      rgba(11, 15, 21, 0) 100%
    );
  }
  /* The box itself, not just the scrim. .hero-slide__media defaults to
     --gm-mist, which is nearly white: if the poster 404s or has not
     arrived, light copy would sit on a light box with only the scrim
     between them. Measured worst case that way, the 11px gold eyebrow
     came to 2.95:1. With the box obsidian the floor is gold-on-obsidian
     at 8.1:1, and it no longer depends on an image loading at all. */
  .hero-slide[data-hero-tone="dark"] .hero-slide__media { background: var(--gm-obsidian); }
  .hero-slide[data-hero-tone="dark"] .hero-slide__title { color: var(--gm-paper); }
  .hero-slide[data-hero-tone="dark"] .hero-slide__lede  { color: rgba(251, 250, 247, .82); }
  .hero-slide[data-hero-tone="dark"] .eyebrow           { color: var(--gm-gold); }
  .hero-slide[data-hero-tone="dark"] .eyebrow::before   { background: var(--gm-gold); }

  /* The primary button inverts. Left as .btn--ink it would be a dark
     block on a dark ground — present in the DOM, invisible on screen. */
  .hero-slide[data-hero-tone="dark"] .btn--ink {
    background: var(--gm-paper);
    border-color: var(--gm-paper);
    color: var(--gm-ink);
  }
  .hero-slide[data-hero-tone="dark"] .btn--ink:hover {
    background: var(--gm-gold-lite);
    border-color: var(--gm-gold-lite);
    color: var(--gm-ink);
  }
  /* Same treatment .btn--on-dark already carries elsewhere. */
  .hero-slide[data-hero-tone="dark"] .btn--outline {
    border-color: rgba(251, 250, 247, .28);
    color: var(--gm-paper);
  }
  .hero-slide[data-hero-tone="dark"] .btn--outline:hover {
    border-color: var(--gm-gold);
    color: var(--gm-gold);
    background: transparent;
  }

  /* Positioning only — the same double-gutter bug .hero__body had.
     The markup inside already has a .container, so carrying max-width
     and padding-inline here too gave the slider copy two gutters and
     started the home page's first headline 48px right of everything
     below it. */
  .hero-slide__body {
    position: relative;      /* in flow — its height now counts */
    display: flex;
    flex-direction: column;
    justify-content: center;
    /* RESERVE THE CONTROL STRIP. The prev/next/dots/pause row is pinned
       to the bottom of the slider, while this copy is centred and grows
       downward — so the tallest slide ("Sell a film you can stand
       behind", three heading lines plus two buttons) ran its buttons 37px
       INTO the controls at 1280x700. Padding the bottom biases the
       centring upward by exactly the strip's height, so the two can no
       longer meet however tall the copy gets.
       44px control + its --space-6 offset + a --space-4 gap. */
    /* 44px control + its 32px bottom offset = a 76px strip, plus a
       32px gap so the buttons do not merely miss the controls but sit
       clear of them. Measured: at 1200x600 a 92px reservation still left
       8px of overlap on the tallest slide. */
    padding-block: 0 calc(44px + var(--space-6) + var(--space-6));
  }
  .hero-slide__inner { max-width: 34rem; }
}

/* --- Controls --------------------------------------------------------
   Revealed by main.js. Hidden by default so they never appear without
   anything to drive. */
.hero-slider__controls[hidden] { display: none; }
.hero-slider__controls { padding-block: var(--space-4); border-bottom: 1px solid var(--gm-edge); }
@media (min-width: 992px) {
  .hero-slider__controls {
    position: absolute;
    left: 0; right: 0;
    bottom: var(--space-6);
    z-index: 3;
    padding-block: 0;
    border-bottom: 0;
  }
}

.hero-slider__controls-inner {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  /* Wrap rather than overflow. At 320px the row wanted 384px against
     280px of available width and pushed the pause button 84px off the
     right edge of the page. The narrow-screen sizing below normally
     keeps it on one line; this is the safety net for a width nobody
     tested — 320px was not in /_audit.html's list, which is how it got
     this far. */
  flex-wrap: wrap;
  justify-content: start;
  row-gap: var(--space-2);
}
@media (min-width: 992px) {
  .hero-slider__controls-inner { max-width: 34rem; flex-wrap: nowrap; justify-content: flex-start; }
}


/* .hero-slider__arrow was removed with the buttons on 13 Sep 2026.
   44x44 is not styling — it is the WCAG 2.5.8 touch target, and this is
   now the only button in the strip. It was briefly 34px while the
   controls were being rebuilt on 13 Sep 2026; /_audit.html caught it at
   all seven widths. Do not shrink it to make the row fit — take the
   space from the dots, which give up width and keep their height. */
.hero-slider__pause {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px;
  flex: none;
  border: 1px solid var(--gm-edge);
  border-radius: var(--radius);
  background: var(--gm-card);
  color: var(--gm-ink);
  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.hero-slider__pause:hover { border-color: var(--gm-gold); background: var(--gm-gold-wash); }

.hero-slider__pause-icon {
  width: 10px; height: 12px;
  border-left: 3px solid currentColor;
  border-right: 3px solid currentColor;
}
/* Paused: the control offers "play", so it shows a triangle. */
.hero-slider__pause[aria-pressed="true"] .hero-slider__pause-icon {
  width: 0; height: 0;
  border: 6px solid transparent;
  border-left: 10px solid currentColor;
  border-right: 0;
}
.hero-slider__dots { display: flex; align-items: center; gap: var(--space-2); margin-inline: var(--space-2); }

/* A slide marker is a BAR, and the one for the slide you are watching
   grows into a progress track for its clip. The width difference is the
   whole affordance: you can see which slide you are on, and how much of
   it is left, in one object. 2px radius, like everything else. */
.hero-slider__dot {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 44px;              /* the tap target — never negotiable */
  padding: 0 8px;
  background: none;
  border: 0;
}
.hero-slider__dot-track {
  display: block;
  width: 22px;
  height: 3px;
  flex: none;
  overflow: hidden;          /* clips the fill to the track */
  border-radius: 2px;
  background: var(--gm-edge);
  transition: width var(--dur-base) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
.hero-slider__dot:hover .hero-slider__dot-track { background: var(--gm-slate); }
.hero-slider__dot.is-active .hero-slider__dot-track {
  width: 72px;
  background: var(--gm-mist);
}

/* The clip's progress. Width in PER CENT, because the track it runs
   along changes width when a slide becomes active — a pixel value would
   be wrong the moment the bar grew. main.js sets this on every
   timeupdate, and falls back to a timed CSS transition when no clip is
   pacing the slide. */
.hero-slider__dot-fill {
  display: block;
  height: 100%;
  width: 0;
  background: var(--gm-gold);
  border-radius: 2px;
}

/* Over a dark slide the whole strip inverts. main.js sets this class
   from the active slide's tone, because one control strip serves four
   slides and two of them are dark. */
.hero-slider--on-dark .hero-slider__dot-track { background: rgb(255 255 255); }
.hero-slider--on-dark .hero-slider__dot:hover .hero-slider__dot-track,
.hero-slider--on-dark .hero-slider__dot.is-active .hero-slider__dot-track { background: rgba(251, 250, 247, .52); }
.hero-slider--on-dark .hero-slider__pause {
  background: rgba(251, 250, 247, .94);
  border-color: transparent;
  color: var(--gm-ink);
}
.hero-slider--on-dark .hero-slider__pause:hover { background: var(--gm-paper); border-color: transparent; }

/* ── Narrow screens ───────────────────────────────────────────────────
   Bars give up WIDTH, never HEIGHT. The 44px tap target is untouched;
   only the visible rule inside it shrinks.

   ⚠️ This block MUST stay below the rules above — it was once written
   higher up and silently lost the cascade. */
@media (max-width: 479.98px) {
  .hero-slider__controls-inner { gap: var(--space-2); }
  .hero-slider__dots { gap: 2px; margin-inline: 0; }
  .hero-slider__dot  { padding: 0 5px; }
  .hero-slider__dot-track { width: 16px; }
  .hero-slider__dot.is-active .hero-slider__dot-track { width: 52px; }
}

/* ------------------------------------------------------------------
   39. PARALLAX
   Background images only. Never the hero — copy sits over it and would
   drift against the text.

   The image is scaled inside an overflow-hidden box so the drift can
   never expose an edge, and only `transform` is animated.
   ------------------------------------------------------------------ */

.parallax {
  position: relative;
  overflow: hidden;
  width: 100%;
  height: 100%;
}
.parallax__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* The overscan is applied by main.js via gsap.set(), NOT here.
   A `transform: scale()` in the stylesheet is overwritten the moment
   GSAP writes its own inline transform for the drift, so the two cannot
   both live in CSS. This class only hints the compositor. */
.js .parallax.is-parallaxing .parallax__img { will-change: transform; }

@media (prefers-reduced-motion: reduce) {
  .js .parallax.is-parallaxing .parallax__img {
    transform: none !important;
    will-change: auto;
  }
}

/* A full-bleed band that carries a parallax image behind its content. */
.media-band { position: relative; overflow: hidden; }
.media-band__media { position: absolute; inset: 0; z-index: 0; }
.media-band__media .parallax,
.media-band__media picture,
.media-band__media img { width: 100%; height: 100%; }
/* object-fit: cover, not the browser default of `fill`.
   The band's height comes from the copy inside it, so it is never the
   image's own ratio — 599px of picture was being squeezed into 557px of
   band and every car in one of these was 7% shorter than it should be.
   `cover` crops instead of distorting, which is what the images are
   commissioned for. */
.media-band__media img { object-fit: cover; object-position: center; }
.media-band__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(180deg, rgb(11 15 21 / 62%) 0%, rgb(11 15 21 / 46%) 100%);
}
.media-band > .container { position: relative; z-index: 2; }

/* ------------------------------------------------------------------
   40. SECTION FIGURES
   A framed image beside a block of copy. Used on technology, warranty,
   dealers, about and contact.
   ------------------------------------------------------------------ */

/* ---------- Home: finish showcase ----------------------------------
   Two up from 768, staying two at desktop rather than going to four.
   Four across puts each card near 300px, and the photograph is the point
   of this section — a small one is not premium, it is a thumbnail. */
.showcase {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 768px) {
  .showcase { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.showcase__card {
  background: var(--gm-card);
  border: 1px solid var(--gm-edge);
  border-radius: var(--radius);
  overflow: hidden;
}
.showcase__link {
  display: flex;
  flex-direction: column;
  height: 100%;
  text-decoration: none;
  color: inherit;
}
.showcase__link:focus-visible { outline: 2px solid var(--gm-gold); outline-offset: -2px; }

.showcase__media { aspect-ratio: 4 / 3; overflow: hidden; background: var(--gm-mist); }
.showcase__media img {
  width: 100%; height: 100%; object-fit: cover;
  transition: transform 500ms var(--ease-out);
}
/* Hover only where hovering is a thing. On touch this fires on tap and
   the card appears to twitch as the page navigates away. */
@media (hover: hover) {
  .showcase__link:hover .showcase__media img { transform: scale(1.04); }
}

.showcase__body {
  padding: var(--space-6);
  display: flex; flex-direction: column; gap: var(--space-3);
  flex: 1;                      /* equal heights, CTA pinned to the foot */
}
.showcase__eyebrow {
  font-family: var(--ff-label); font-size: var(--fs-eyebrow); font-weight: 600;
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--gm-gold-text);
}
.showcase__title { font-family: var(--ff-display); font-size: 1.5rem; color: var(--gm-ink); }
.showcase__text  { color: var(--gm-graphite); }
.showcase__cta   { margin-top: auto; }

/* ---------- Home: why Goldman PCU -----------------------------------
   Image LEFT, reasons right — mirrored from the construction section
   further down, which runs copy-left / image-right. Alternating them
   reads as rhythm; repeating the same arrangement twice on one page
   reads as a template. */
.why {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}
@media (min-width: 992px) {
  .why { grid-template-columns: 5fr 6fr; gap: clamp(40px, 5vw, 72px); align-items: center; }
}

.why__media {
  margin: 0;
  aspect-ratio: 3 / 2;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--gm-mist);
}
.why__media img { width: 100%; height: 100%; object-fit: cover; }

.why__list { margin: 0; display: grid; gap: 0; }

/* A hairline between reasons rather than four boxes. These are one
   argument in four parts, not four separate objects — a card each would
   say the opposite. */
.why__item { padding-block: var(--space-5); border-top: 1px solid var(--gm-edge); }
.why__item:first-child { border-top: 0; padding-top: 0; }
.why__item:last-child  { padding-bottom: 0; }

.why__term {
  font-family: var(--ff-display);
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--gm-ink);
  margin-bottom: var(--space-2);
}
.why__desc { margin: 0; color: var(--gm-graphite); }

/* ---------- Home: TPU vs PCU ----------------------------------------
   Two columns, formally IDENTICAL. That is the argument: a comparison
   that styles one side to look better has already lost it. The only
   thing that separates them is the colour of the three letters, which
   is how the page says "this one is ours" without weighting the scales.

   Deliberately not a table. The client removed the layer spec table
   from this page on 13 Sep 2026; anything that reads as a data grid is
   the wrong register for the home page. */
.polymer {
  display: grid;
  gap: 0;
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 768px) {
  .polymer { grid-template-columns: 1fr 1fr; }
}

.polymer__col { padding-block: var(--space-6); border-top: 1px solid var(--gm-edge-dark); }
.polymer__col:first-child { border-top: 0; padding-top: 0; }

@media (min-width: 768px) {
  /* At two-up the rule turns vertical and the top borders go away —
     the columns are side by side, so a horizontal rule would read as a
     table header. */
  .polymer__col { border-top: 0; padding-top: 0; padding-right: clamp(24px, 4vw, 56px); }
  .polymer__col + .polymer__col {
    border-left: 1px solid var(--gm-edge-dark);
    padding-left: clamp(24px, 4vw, 56px);
    padding-right: 0;
  }
}

.polymer__mark {
  font-family: var(--ff-display);
  font-size: clamp(2.5rem, 6vw, 3.75rem);
  font-weight: 600;
  line-height: 1;
  letter-spacing: .02em;
  margin: 0 0 var(--space-3);
  color: rgba(251, 250, 247, .55);   /* 5.98:1 on obsidian */
}
.polymer__col--ours .polymer__mark { color: var(--gm-gold); }

.polymer__name {
  font-family: var(--ff-display);
  font-size: 1.0625rem;
  font-weight: 600;
  letter-spacing: .01em;
  color: var(--gm-paper);
  margin: 0 0 var(--space-3);
}
.polymer__body { margin: 0; }

/* The one line on this page that reports the packaging rather than
   making a claim of our own. Kept visually quiet on purpose — see the
   guard comment in index.php. */
.polymer__note {
  margin: var(--space-4) 0 0;
  font-size: var(--fs-caption);
  color: var(--gm-gold-lite);
}

.polymer__figure { margin: var(--space-7) 0 0; }
.polymer__media {
  aspect-ratio: 2 / 1;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--gm-obsidian);
}
.polymer__media img { width: 100%; height: 100%; object-fit: cover; }
.polymer__caption {
  margin: var(--space-4) auto 0;
  max-width: 62ch;
  text-align: center;
  font-size: var(--fs-caption);
}
@media (max-width: 767px) {
  /* 2:1 is a letterbox slit on a phone, so the box goes 3:2 here — but
     the image in this slot is a LABELLED diagram, and `cover` into a
     taller box trims 12.5% off each side, which takes the T off "TPU"
     and the U off "PCU". `contain` keeps the whole frame; the bars it
     leaves are --gm-obsidian, the same colour as the box. Same lesson
     as section-construction in app/lib/images.php. */
  .polymer__media { aspect-ratio: 3 / 2; }
  .polymer__media img { object-fit: contain; }
  .polymer__caption { text-align: left; }
}

/* ---------- Home: where the difference is made ----------------------
   Copy LEFT, picture RIGHT, and the picture is PORTRAIT — the only
   upright image on this page. That is doing real work: this is the
   fourth image-beside-text section on the home page, and shape is what
   stops it reading as the third one again. Ratio and background both
   differ from every neighbour.

   The numbers are not decoration. This list is one installation in the
   order it happens, so the sequence is information; the four-item list
   in "Why PCU" is a set of parallel properties and is deliberately NOT
   numbered. */
.steps {
  display: grid;
  gap: var(--space-6);
  grid-template-columns: minmax(0, 1fr);
  align-items: start;
}
/* Copy first, picture second, at EVERY width. The picture is a
   companion to the list and is read after it, which is also what lets
   the frame stay uncropped on a phone — see .steps__media below. */
.steps__media { order: 2; }
.steps__list  { order: 1; }
@media (min-width: 992px) {
  /* The companion takes the narrower track. */
  .steps { grid-template-columns: 6fr 5fr; gap: clamp(40px, 5vw, 72px); align-items: center; }
}

.steps__media {
  margin: 0;
  aspect-ratio: 4 / 5;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--gm-obsidian);
}
.steps__media img { width: 100%; height: 100%; object-fit: cover; }
/* This frame was 3:2 on a phone, because upright is right beside a
   column of text and wrong above one — at 390px a 4:5 frame is 488px
   tall and pushed the first step off the screen.

   It is 4:5 at every width now, for a reason that outranks that one:
   the image carries callouts 01-04 answering the four steps, and
   `cover` into a 3:2 box kept only the middle 53% of its height —
   marker 02 disappeared completely, and 01 and 04 were left pointing
   at a tray and a certificate that had been cut off the bottom edge.
   The height objection is answered instead by the order rules above,
   which now put the list before the picture on a phone too, so the
   first step is what a reader meets. */

.steps__list {
  margin: 0;
  padding: 0;
  list-style: none;       /* the numbers are drawn, not generated */
  display: grid;
  gap: 0;
}

.steps__item {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  column-gap: var(--space-4);
  padding-block: var(--space-5);
  border-top: 1px solid var(--gm-edge-dark);
}
.steps__item:first-child { border-top: 0; padding-top: 0; }
.steps__item:last-child  { padding-bottom: 0; }

/* --ff-data, not the display face: these are figures, and the mono
   face keeps 01 and 04 the same width so the titles start on one line. */
.steps__num {
  grid-row: 1 / span 2;
  margin: 0;
  font-family: var(--ff-data);
  font-size: var(--fs-data-s);
  font-variant-numeric: tabular-nums;
  line-height: 1.5;
  color: var(--gm-gold);
  padding-top: 2px;
}
.steps__title {
  font-family: var(--ff-display);
  font-size: 1.0625rem;
  font-weight: 600;
  color: var(--gm-paper);
  margin: 0 0 var(--space-2);
}
.steps__text { margin: 0; }

/* ---------- Home: Japanese studio reference band --------------------
   A native scroll-snap track. The cards are wider than the viewport on
   purpose — a row that visibly runs off the edge is what tells a reader
   it scrolls, where a neat grid does not.

   The track breaks out of .container so cards can bleed to the screen
   edge, then pads itself back to the gutter. Doing it the other way —
   container round the track — clips the overflow and kills the scroll. */
.studios { margin-top: var(--space-7); }

.studios__track {
  display: flex;
  gap: var(--space-5);
  list-style: none;
  margin: 0;
  padding: 0 0 var(--space-4);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}
.studios__track::-webkit-scrollbar { display: none; }
.studios__track:focus-visible { outline: 2px solid var(--gm-gold); outline-offset: 4px; }

.studio-card {
  flex: 0 0 78%;
  max-width: 340px;
  scroll-snap-align: start;
  background: var(--gm-card);
  border: 1px solid var(--gm-edge);
  border-radius: var(--radius);
  overflow: hidden;
}
@media (min-width: 768px)  { .studio-card { flex-basis: 42%; max-width: 380px; } }
@media (min-width: 1200px) { .studio-card { flex-basis: 30%; max-width: 400px; } }

.studio-card__media { aspect-ratio: 3 / 2; overflow: hidden; background: var(--gm-mist); }
.studio-card__media img { width: 100%; height: 100%; object-fit: cover; }

.studio-card__body {
  padding: var(--space-5);
  display: flex; flex-direction: column; gap: var(--space-2);
}
.studio-card__name {
  font-family: var(--ff-display); font-size: 1.25rem; color: var(--gm-ink);
}
.studio-card__place {
  font-family: var(--ff-label); font-size: var(--fs-eyebrow); font-weight: 600;
  letter-spacing: var(--tracking-label); text-transform: uppercase;
  color: var(--gm-gold-text);
}
.studio-card__known { color: var(--gm-graphite); font-size: var(--fs-caption); }

.studios__foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-5); flex-wrap: wrap;
  margin-top: var(--space-5);
}
.studios__note { color: var(--gm-graphite); font-size: var(--fs-caption); margin: 0; }

.studios__controls { display: flex; gap: var(--space-3); }
.studios__controls[hidden] { display: none; }
.studios__btn {
  width: 44px; height: 44px;                 /* the tap-target floor */
  display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--gm-edge);
  border-radius: 50%;
  background: var(--gm-card);
  color: var(--gm-ink);
  cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease-out),
              color var(--dur-fast) var(--ease-out);
}
.studios__btn:hover:not(:disabled) { border-color: var(--gm-gold); color: var(--gm-gold-text); }
.studios__btn:focus-visible { outline: 2px solid var(--gm-gold); outline-offset: 2px; }
.studios__btn:disabled { opacity: .35; cursor: default; }

/* ---------- Home: what the film stops ------------------------------- */
.defends {
  display: grid;
  gap: var(--space-5);
  grid-template-columns: minmax(0, 1fr);
}
@media (min-width: 640px) { .defends { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 992px) { .defends { grid-template-columns: repeat(3, minmax(0, 1fr)); } }

.defends__card {
  background: var(--gm-card);
  border: 1px solid var(--gm-edge);
  border-radius: var(--radius);
  overflow: hidden;
  display: flex; flex-direction: column;
}
.defends__media { aspect-ratio: 4 / 3; overflow: hidden; background: var(--gm-mist); }
.defends__media img { width: 100%; height: 100%; object-fit: cover; }
.defends__body {
  padding: var(--space-5);
  display: flex; flex-direction: column; gap: var(--space-3);
  flex: 1;
}
/* --gm-gold is a FILL here, never text — see CLAUDE.md §12.1. An icon
   stroke is not text and passes at this size on a light ground. */
.defends__icon { color: var(--gm-gold-deep); line-height: 0; }
.defends__title { font-family: var(--ff-display); font-size: 1.0625rem; color: var(--gm-ink); }
.defends__text  { color: var(--gm-graphite); font-size: .9375rem; }

.figure-panel { margin: 0; }
.figure-panel__media {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border: 1px solid var(--gm-edge);
  border-radius: var(--radius);
  background: var(--gm-mist);
}
.figure-panel__media img { width: 100%; height: 100%; object-fit: cover; }
.figure-panel--wide .figure-panel__media { aspect-ratio: 8 / 5; }

/* A DIAGRAM IS NOT A PHOTOGRAPH. `object-fit: cover` on a labelled
   drawing cuts the labels off, and the panel's own 4/3 did exactly that
   to the five-layer diagram. Matching ratio plus `contain` guarantees
   the whole artwork is shown at every width. The dark ground matches
   the diagram, so even a rounding sliver cannot flash light. */
.figure-panel--diagram .figure-panel__media {
  aspect-ratio: 3 / 2;
  background: var(--gm-obsidian, #14181d);
}
.figure-panel--diagram .figure-panel__media img { object-fit: contain; }
.figure-panel__caption {
  margin-top: var(--space-3);
  font-size: var(--fs-caption);
  color: var(--gm-slate);
}

/* A small image alongside each technology behaviour. */
.tech-item__media {
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border: 1px solid var(--gm-edge);
  border-radius: var(--radius);
  background: var(--gm-mist);
  margin-top: var(--space-4);
}
.tech-item__media img { width: 100%; height: 100%; object-fit: cover; }
@media (min-width: 992px) {
  .tech-item { grid-template-columns: 5rem 1fr 18rem; align-items: start; }
  .tech-item__media { margin-top: 0; }
}

/* ------------------------------------------------------------------
   41. LIGHT COMPONENTS ON DARK SECTIONS
   A component that paints its own light surface must also declare its
   own text colour. Otherwise .section--obsidian's heading and body rules
   reach inside it and paint paper-on-white — 1.04:1.

   Found by the contrast audit after /warranty moved onto an obsidian
   media band; the verify panel had been white-on-champagne until then,
   so nothing had exercised it.
   ------------------------------------------------------------------ */

.section--obsidian .verify-panel { color: var(--gm-graphite); }
.section--obsidian .verify-panel .h2,
.section--obsidian .verify-panel .h3,
.section--obsidian .verify-panel .h4 { color: var(--gm-ink); }
.section--obsidian .verify-panel .field__label { color: var(--gm-ink); }
.section--obsidian .verify-panel .field__help,
.section--obsidian .verify-panel .verify-panel__lede { color: var(--gm-slate); }
.section--obsidian .verify-panel .record__val { color: var(--gm-ink); }
.section--obsidian .verify-panel .record__key { color: var(--gm-slate); }

/* The trace list is body copy, so on obsidian it takes the section's
   body colour rather than --gm-graphite. */
.section--obsidian .trace-list > li {
  color: rgba(251, 250, 247, .82);
  border-top-color: var(--gm-edge-dark);
}
.section--obsidian .trace-list > li > .icon { color: var(--gm-gold); }

/* Same for a notice sitting on a dark band. */
.section--obsidian .notice {
  background: var(--gm-obsidian-2);
  border-color: var(--gm-edge-dark);
  color: rgba(251, 250, 247, .82);
}
.section--obsidian .notice__icon { color: var(--gm-gold); }

/* ------------------------------------------------------------------
   42. STICKY PAGE BANNER
   The banner stays where it is and the page scrolls over it.

   Pure CSS. Nothing animates and nothing is transformed, so the image
   is never scaled and stays exactly as sharp as it was delivered —
   which is why this is used here rather than the translate-based
   parallax on the section bands. An overscanned 1536px source across a
   full-bleed banner is visibly soft; this is not.

   Not used on the home page: a sticky banner under a rotating slider
   fights itself.
   ------------------------------------------------------------------ */

.hero--sticky {
  position: sticky;
  top: 0;
  z-index: 0;
}

/* Everything after the banner rides over it. Each section already paints
   an opaque background, so the banner is covered as the stack arrives. */
.hero-stack {
  position: relative;
  z-index: 1;
  background: var(--gm-paper);
}

/* The banner is only revealed while it is the top-most thing on screen,
   so it never needs to be taller than the viewport. */
/* Banner height is --hero-media-h on .hero--sticky. */

/* Below 992px the copy stacks under the image, so the section is taller
   than the viewport and sticky would pin the copy off-screen. Static. */
@media (max-width: 991px) {
  .hero--sticky { position: static; }
  .hero-stack { background: none; }
}

/* Sticky positioning fails inside a scroll container. body carries
   overflow-x: hidden for the full-bleed bands, which is enough to break
   it in some engines — clip does the same job without establishing one. */
@supports (overflow: clip) {
  body { overflow-x: clip; }
}

/* ══════════════════════════════════════════════════════════════════
   DEALER DIRECTORY — /dealers, 23 Sep 2026
   ══════════════════════════════════════════════════════════════════ */

/* The filter bar. Wraps rather than collapsing at a breakpoint: the
   number of controls is not fixed — the city and state selects only
   render when there is more than nothing to choose from — so a fixed
   column count would leave a hole on a site with dealers in one city. */
.dealer-filters {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-4);
  align-items: flex-end;
  padding: var(--space-5);
  margin-bottom: var(--space-5);
  background: var(--gm-champagne);
  border: 1px solid var(--gm-hairline);
  border-radius: var(--radius);
}
.dealer-filters__field {
  display: flex;
  flex-direction: column;
  gap: 6px;
  flex: 1 1 200px;
  min-width: 0;                     /* or a long placeholder pushes the row wide */
}
.dealer-filters__label {
  font-family: var(--ff-display);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-gold-text);
}
.dealer-filters__actions {
  display: flex;
  gap: var(--space-3);
  flex: 0 0 auto;
}

.dealer-count {
  margin: 0 0 var(--space-5);
  color: var(--gm-slate);
  font-size: var(--fs-caption);
}

.dealer-empty {
  padding: var(--space-7) var(--space-5);
  text-align: center;
  border: 1px dashed var(--gm-hairline);
  border-radius: var(--radius);
  color: var(--gm-graphite);
}
.dealer-empty p { margin: 0 0 var(--space-4); }
.dealer-empty p:last-child { margin-bottom: 0; }

/* auto-fill, not auto-fit: with one dealer, auto-fit collapses the empty
   tracks and stretches that single card the full width of the page,
   which reads as a bug rather than as a list of one. */
.dealer-grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-5);
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 300px), 1fr));
}

.dealer-card {
  display: flex;
  flex-direction: column;
  background: var(--gm-card);
  border: 1px solid var(--gm-edge);
  border-radius: var(--radius);
  overflow: hidden;
}

/* Fixed ratio so a row of cards keeps its baselines whatever shape the
   uploaded photos were. Uploads are re-encoded to a known max edge, but
   not to a known RATIO. */
.dealer-card__media {
  aspect-ratio: 3 / 2;
  background: var(--gm-mist);
}
.dealer-card__img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.dealer-card__body {
  padding: var(--space-5);
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  flex: 1;
}

.dealer-card__name {
  font-family: var(--ff-display);
  font-size: 1.125rem;
  font-weight: 600;
  line-height: 1.25;
  color: var(--gm-ink);
  margin: 0;
  text-wrap: balance;
}

.dealer-card__city {
  margin: 0;
  font-family: var(--ff-display);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--gm-gold-text);
}

.dealer-card__desc {
  margin: var(--space-2) 0 0;
  font-size: var(--fs-body);
  color: var(--gm-graphite);
}

.dealer-card__meta {
  margin: var(--space-3) 0 0;
  padding-top: var(--space-3);
  border-top: 1px solid var(--gm-edge-soft, rgba(20,24,31,.06));
  display: grid;
  gap: var(--space-2);
}
.dealer-card__row {
  display: grid;
  grid-template-columns: 5.5rem minmax(0, 1fr);
  gap: var(--space-3);
  align-items: start;
}
.dealer-card__row dt {
  font-family: var(--ff-display);
  font-size: var(--fs-eyebrow);
  font-weight: 600;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--gm-slate);
  padding-top: 2px;
}
.dealer-card__row dd {
  margin: 0;
  font-size: var(--fs-caption);
  color: var(--gm-graphite);
  min-width: 0;
  overflow-wrap: anywhere;          /* a long address must not widen the card */
}
.dealer-card__tel {
  font-family: var(--ff-data);
  color: var(--gm-gold-text);
  white-space: nowrap;
}
.dealer-card__tel:hover { color: var(--gm-ink); }

/* Below 420px the label column costs more than it earns: 5.5rem of
   "ADDRESS" against a two-line value leaves the value about 9 characters
   wide. Stack instead. */
@media (max-width: 419.98px) {
  .dealer-card__row { grid-template-columns: minmax(0, 1fr); gap: 2px; }
}

/* ── Pager ─────────────────────────────────────────────────────────
   Markup from gm_pager() in app/lib/pagination.php. The admin panel
   styles the same structure under .adm-pager in its own stylesheet. */
.pager {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: 1px solid var(--gm-edge);
}
.pager__count {
  margin: 0;
  font-size: var(--fs-caption);
  color: var(--gm-slate);
  font-variant-numeric: tabular-nums;
}
.pager__list {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  list-style: none;
  margin: 0;
  padding: 0;
}
/* 44px square on the public site — this one IS a touch target, and
   _audit.html fails the page if it is not. */
.pager__link {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  height: 44px;
  padding: 0 var(--space-3);
  border: 1px solid var(--gm-edge);
  border-radius: var(--radius);
  font-family: var(--ff-data);
  font-size: var(--fs-caption);
  font-variant-numeric: tabular-nums;
  color: var(--gm-ink);
  background: var(--gm-card);
  transition: border-color var(--dur-fast) var(--ease-out),
              background-color var(--dur-fast) var(--ease-out);
}
a.pager__link:hover {
  border-color: var(--gm-gold);
  background: var(--gm-gold-wash);
}
.pager__link.is-current {
  background: var(--gm-ink);
  border-color: var(--gm-ink);
  color: var(--gm-paper);
  font-weight: 600;
}
.pager__link--step { padding: 0 var(--space-4); }
.pager__gap {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 28px;
  height: 44px;
  color: var(--gm-slate);
}

@media (max-width: 559.98px) {
  .pager { flex-direction: column; align-items: flex-start; }
}
