/* ============================================================
   OZARK TOURS — Main Stylesheet
   Mobile-first · BEM-inspired · CSS custom properties
   ============================================================ */

/* ---- Fonts ---- */
/* Northlake (Komet & Flicker, David Phillips) — licensed webfont, self-hosted.
   Three weights only: Light 300, Medium 500, Bold 700. No italic. */
@font-face {
  font-family: 'Northlake';
  src: url('../fonts/Northlake-Light.woff2') format('woff2');
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Northlake';
  src: url('../fonts/Northlake-Medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Northlake';
  src: url('../fonts/Northlake-Bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* ---- Custom Properties ---- */
:root {
  --forest:       #1a2e1a;
  --forest-mid:   #2d4a2d;
  --forest-dark:  #0d1a0d;
  --amber:        #c9893a;
  --amber-light:  #e8a84e;
  --cream:        #f5f0e8;
  --cream-dark:   #e8e0d0;
  --gray:         #6b7280;
  --white:        #ffffff;

  /* Display face: Northlake — a rustic serif; warm, vintage, softly rounded. */
  --font-serif: 'Northlake', Georgia, 'Times New Roman', serif;
  --font-sans:  'Inter', system-ui, -apple-system, sans-serif;

  --page-padding: clamp(1.25rem, 5vw, 2.5rem);
  --max-width:    1200px;
  --radius:       0.5rem;
  --radius-lg:    1rem;
  --transition:   0.3s ease;
  --nav-height:   68px;

  /* ---- Easings. The house curves were always here, just never named. ---- */
  --ease-expo:   cubic-bezier(0.22, 1, 0.36, 1);    /* the reveal: nav underline, modal, card zoom */
  --ease-servo:  cubic-bezier(0.77, 0, 0.175, 1);   /* NEW — the push split. Hydraulic. */
  --ease-pop:    cubic-bezier(0.34, 1.56, 0.64, 1); /* the back-out: map pins, and the seat pips */
  --dur-reveal:  0.75s;

  /* Highest z-index elsewhere in the sheet is 200 (.skip-link, .adv-modal).
     The preloader plate has to outrank all of it, including the fly bar (150). */
  --z-preloader: 9999;

  /* The ivory room (#spec). */
  --ink:  #0d1a0d;                /* text on cream */
  --rule: rgba(26, 46, 26, 0.13); /* hairlines on cream */

  /* ---- THE LIGHT ROOM (hero + preloader plate) ----
     Warm, earthy, product-lit. Three stops, top to bottom: a near-white haze, a
     cream body, a sand floor. The aircraft render is a studio product shot on a
     white field, and it only reads as hardware against a ground like this.
     --ink-warm is a warm near-black, NOT --ink (a cold near-black green): on
     sand, --ink reads as a mistake, the way black type reads on a warm print. */
  --hero-haze:  #fbfaf8;
  --hero-cream: #f1ece3;
  --hero-sand:  #e3d8c6;
  --ink-warm:   #2b2621;
  --rule-warm:  rgba(43, 38, 33, 0.22);
}

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

html {
  scroll-behavior: smooth;
  -webkit-text-size-adjust: 100%;
  /* SAFETY BELT FOR THE PRELOADER, not cosmetics. The plate sets
     overflow: hidden on <html> for its lifetime. On any platform with classic
     (non-overlay) scrollbars — Windows, Linux, macOS set to "Show scroll bars:
     Always" — that REMOVES the scrollbar, the viewport gets ~15px wider, every
     .journey__card reflows, and every document coordinate initJourneyScroll
     cached in measure() is now wrong. Teardown puts the scrollbar back and it
     all shifts again. Reserving the gutter permanently means overflow: hidden
     causes zero width change. Pairs with the 'aviatrix:preloaded' re-measure in
     initJourneyScroll — ship both or ship neither. */
  scrollbar-gutter: stable;
}

/* THE PAGE IS A LIGHT ROOM NOW. Warm near-black on cream, everywhere. --ink is a
   COLD near-black green and belongs only to the two rooms that predate the
   repaint (#spec's cream); on sand it reads as a mistake, the way black type
   reads on a warm print. Everything repainted uses --ink-warm. */
body {
  font-family: var(--font-sans);
  color: var(--ink-warm);
  background: var(--hero-cream);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* ---- Selection + scrollbars ----
   The two bits of browser chrome that stay on the OS palette unless you ask.
   A dark-mode scrollbar down the side of a cream page is the loudest tell that
   a light repaint was only skin deep. */
::selection {
  background: rgba(201, 137, 58, 0.30);
  color: var(--ink-warm);
}

html {
  scrollbar-color: rgba(43, 38, 33, 0.35) var(--hero-cream);
  scrollbar-width: thin;
}

::-webkit-scrollbar { width: 11px; height: 11px; }
::-webkit-scrollbar-track { background: var(--hero-cream); }
::-webkit-scrollbar-thumb {
  background: rgba(43, 38, 33, 0.28);
  border: 3px solid var(--hero-cream);
  border-radius: 999px;
}
::-webkit-scrollbar-thumb:hover { background: rgba(43, 38, 33, 0.45); }

img {
  display: block;
  max-width: 100%;
}

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

button {
  cursor: pointer;
  border: none;
  background: none;
  font: inherit;
}

input, select, textarea {
  font: inherit;
  border: none;
  outline: none;
  background: none;
}

ul, ol {
  list-style: none;
}

/* ---- Accessibility ---- */
:focus-visible {
  outline: 2px solid var(--amber);
  outline-offset: 3px;
  border-radius: 2px;
}

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

.skip-link {
  position: absolute;
  top: -100%;
  left: 1rem;
  background: var(--amber);
  /* Ink on amber is 5.1:1; white on amber is 2.9:1 and fails AA. The amber is
     the brand, so the TYPE is what moves. Same call on .btn--primary. */
  color: var(--ink-warm);
  padding: 0.75rem 1.5rem;
  border-radius: 0 0 var(--radius) var(--radius);
  font-weight: 600;
  z-index: 200;
  transition: top 0.2s ease;
}

.skip-link:focus {
  top: 0;
}

/* ---- Typography Utilities ---- */
.eyebrow {
  font-family: var(--font-sans);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--amber);
  margin-bottom: 0.75rem;
}

h1, h2, h3 {
  font-family: var(--font-serif);
  font-weight: 700;
  line-height: 1.15;
}

/* Northlake has no true italic — keep the gold accent words upright rather than
   letting the browser synthesize a slanted fake. Body <em> stays italic in Inter. */
h1 em, h2 em, h3 em {
  font-style: normal;
}

h1 { font-size: clamp(2.5rem, 8vw, 5rem); }
h2 { font-size: clamp(2rem, 5vw, 3.25rem); }
h3 { font-size: clamp(1.2rem, 3vw, 1.625rem); }

em {
  font-style: italic;
  color: var(--amber);
}

/* NOT --gray (#6b7280): on cream that token is ~4.1:1 and fails AA for body
   copy. Warm ink at 72% measures 5.5:1 and still reads as secondary. */
.section-sub {
  font-size: 1.0625rem;
  color: rgba(43, 38, 33, 0.72);
  max-width: 540px;
  margin-top: 0.75rem;
  line-height: 1.7;
}

.container {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--page-padding);
}

/* ---- Buttons ---- */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  padding: 0.875rem 2rem;
  border-radius: 3rem;
  transition: background var(--transition), border-color var(--transition), color var(--transition), transform var(--transition);
  white-space: nowrap;
  min-height: 52px;
  cursor: pointer;
}

.btn:active {
  transform: scale(0.97);
}

/* All three variants were drawn for a dark ground (cream borders, cream type,
   a white-on-amber fill). On a light page the outline and ghost buttons were
   literally invisible and the primary failed AA. They are ink-on-light now. */
.btn--primary {
  background: var(--amber);
  color: var(--ink-warm);   /* 5.1:1 — white on amber is 2.9:1 and fails */
}

.btn--primary:hover {
  background: var(--amber-light);   /* ink on gold: 7.2:1 */
}

.btn--outline {
  border: 2px solid var(--rule-warm);
  color: var(--ink-warm);
}

.btn--outline:hover {
  background: var(--ink-warm);
  color: var(--hero-cream);
  border-color: var(--ink-warm);
}

.btn--ghost {
  border: 2px solid rgba(43, 38, 33, 0.3);
  color: var(--ink-warm);
}

.btn--ghost:hover {
  border-color: var(--ink-warm);
  background: rgba(43, 38, 33, 0.06);
}

.btn--full {
  width: 100%;
}

/* ============================================================
   NAV
   ============================================================ */
.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  height: var(--nav-height);
  transition: background var(--transition), backdrop-filter var(--transition);
}

/* NO BAR. The nav is transparent at all times — the options float over the page
   with no strip behind them (client request). Kept the class so nothing that
   toggles it breaks; it just no longer paints a bar. */
.nav--scrolled {
  background: transparent;
  backdrop-filter: none;
  -webkit-backdrop-filter: none;
  border-bottom: none;
}

.nav__inner {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--page-padding);
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
}

/* THE MARK, NOT THE NAME. This was the words "Aviatrix Charters" set in
   Northlake; it is now the logo itself. `color` is still declared and still
   does the work — the SVG is INLINE and painted with currentColor, so every
   existing colour rule (.nav--light, .nav--on-dark, the scrolled states) keeps
   applying with no new selectors. That is the whole reason it is inlined
   rather than referenced as <img src>: an external SVG cannot see the host
   page's currentColor, so on the dark nav the mark would have stayed black and
   vanished into the background.
   The accessible name comes from aria-label on the <a>, and the <svg> is
   aria-hidden — the link is not left nameless by dropping the text. */
.nav__logo {
  display: block;
  color: var(--ink-warm);
  flex-shrink: 0;
  line-height: 0;
  transition: opacity var(--transition);
}

/* Height first: the artwork's own 924.06 x 1077.28 gives the width, so the
   mark is never distorted. 38px against a 68px nav leaves it sitting like a
   wordmark would, with air above and below. stroke-width 14 is baked into the
   markup — the vector's hairlines are drawn for print and go thin and grey at
   this size without it. */
.nav__logo-mark {
  display: block;
  height: 38px;
  width: auto;
  overflow: visible;
}
@media (max-width: 767px) {
  .nav__logo-mark { height: 32px; }
}

.nav__logo:hover {
  opacity: 0.8;
}

/* Desktop nav links — hidden on mobile */
.nav__links {
  display: none;
  gap: 2.25rem;
}

.nav__links a {
  position: relative;
  font-family: var(--font-sans);
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: rgba(43, 38, 33, 0.78);   /* 6.2:1 on the haze bar */
  padding-bottom: 4px;
  transition: color var(--transition);
}

/* Amber underline grows from the left on hover/focus */
.nav__links a::after {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  width: 100%;
  height: 1.5px;
  background: var(--amber);   /* the darker amber — --amber-light is a gold for
                                 dark grounds and goes to mush on the haze bar */
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.32s cubic-bezier(0.22, 1, 0.36, 1);
}

.nav__links a:hover,
.nav__links a:focus-visible {
  color: var(--ink-warm);
}

.nav__links a:hover::after,
.nav__links a:focus-visible::after {
  transform: scaleX(1);
}

.nav__cta {
  display: none;
  padding: 0.625rem 1.375rem;
  min-height: 44px;
  font-size: 0.875rem;
}

/* Hamburger */
.nav__hamburger {
  width: 44px;
  height: 44px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 6px;
  border-radius: var(--radius);
  transition: background var(--transition);
  flex-shrink: 0;
}

.nav__hamburger:hover {
  background: rgba(43, 38, 33, 0.07);
}

.nav__hamburger span {
  display: block;
  width: 22px;
  height: 2px;
  background: var(--ink-warm);
  border-radius: 1px;
  transition: transform var(--transition), opacity var(--transition);
}

.nav__hamburger.is-open span:nth-child(1) {
  transform: translateY(7px) rotate(45deg);
}

.nav__hamburger.is-open span:nth-child(2) {
  opacity: 0;
  transform: scaleX(0);
}

.nav__hamburger.is-open span:nth-child(3) {
  transform: translateY(-7px) rotate(-45deg);
}

/* Mobile full-screen nav */
.nav__links.is-open {
  display: flex;
  flex-direction: column;
  position: fixed;
  top: var(--nav-height);
  left: 0;
  right: 0;
  bottom: 0;
  /* Near-opaque haze: the sheet covers the map and the WebGL canvas, and a
     translucent white sheet over satellite imagery is unreadable. */
  background: rgba(251, 250, 248, 0.98);
  backdrop-filter: blur(16px);
  -webkit-backdrop-filter: blur(16px);
  padding: 2rem var(--page-padding) 3rem;
  gap: 0;
  border-top: 1px solid var(--rule-warm);
  z-index: 99;
  overflow-y: auto;
}

.nav__links.is-open a {
  display: block;
  padding: 1.25rem 0;
  font-size: 1.375rem;
  font-family: var(--font-serif);
  font-weight: 500;
  text-transform: none;
  letter-spacing: 0.01em;
  color: var(--ink-warm);
  border-bottom: 1px solid var(--rule-warm);
  transition: color var(--transition), padding-left var(--transition);
}

.nav__links.is-open a:hover {
  color: var(--amber);
  padding-left: 0.5rem;
}

.nav__links.is-open a:last-child {
  border-bottom: none;
}

@media (min-width: 900px) {
  .nav__links { display: flex; }
  .nav__cta   { display: inline-flex; }
  .nav__hamburger { display: none; }}

/* ============================================================
   HERO
   ============================================================ */
/* Hero is a tall scroll track; the sticky stage pins to the viewport
   while JS pushes the footage in (the "zoom through the sky" effect). */
/* The edge rails need road. Three viewports of it — Jesko's exact figure —
   so AVIATRIX and CHARTERS have room to open all the way to the edges before
   the sticky stage releases. (The reduced-motion block near the bottom of this
   sheet collapses the track to a single viewport; it wins, being later.) */
/* THE LIGHT ROOM. The hero is no longer a dark sky you fly through — it is a
   bright, still, warm field with the machine standing in the middle of it. The
   aircraft is the hero image, and a top-down render can only read as HARDWARE
   against a light ground (it is lit as a studio product shot, not a sky shot).
   Cream at the top, warmer sand at the bottom, so the frame has a horizon
   without ever drawing one. */
.hero {
  position: relative;
  height: 300vh;
  background: var(--hero-sand);
}

@media (max-width: 767px) {
  .hero { height: 200vh; }}

.hero__sticky {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  min-height: 600px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.hero__bg {
  position: absolute;
  inset: 0;
  background: linear-gradient(
    177deg,
    var(--hero-haze)  0%,
    var(--hero-cream) 38%,
    var(--hero-sand)  100%
  );
  /* main.js still writes transform/filter here on scroll. A gradient is happy to
     be scaled; a video was not the point any more. */
  will-change: transform;
  transform-origin: 50% 45%;
}

/* Warm floor bloom under the aircraft — it grounds the machine so it is standing
   in a room rather than pasted onto a swatch. Intensifies as you push in. */
.hero__atmos {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  opacity: 0;
  background:
    radial-gradient(90% 55% at 50% 88%,
      rgba(196, 168, 122, 0.34) 0%,
      rgba(196, 168, 122, 0.0)  70%);
  will-change: opacity;
}

/* ── THE CRAFT — THE FLY-IN ──────────────────────────────────────────────────
   THE MOVEMENT IS FROM THE REFERENCE (Site Refrence.mov), and the direction is
   the whole point: the aircraft RISES UP FROM BELOW, nose leading, and climbs
   the centre channel between the two halves of the name as you scroll — exactly
   as the jet rises through "Fly in / Luxury" on Jesko. It does NOT descend.

   At hero rest (--hp 0) the craft sits BELOW the fold: nose barely at the
   bottom edge, invisible. Match the reference frame 1 — no aircraft at rest.
   As --hp climbs it flies up the channel, growing, and by --hp ~0.7 it has
   climbed out the top and handed the moment to #spec, where the same top-down
   settles as the seating chart.

   multiply is load-bearing, not a filter: the render carries a white studio
   field, and on cream white multiplies to nothing. No matte, no halo — the same
   treatment #spec uses, so the two sections speak one hardware language.

   Two channels off the one --hp:
     • RISE  — from +78vh (below the fold) up to -46vh (climbed out the top).
     • GROW  — 0.82 → 1.28 as it comes toward you on the way up.
   The tail boom threads the channel between the words; the rotor rides above the
   wordline (the widest point of the image is ~45% down, i.e. the rotor disc, and
   it must clear the shared baseline or the blades saw through the type). */
.hero__craft {
  position: absolute;
  z-index: 2;              /* over the ground, UNDER the words — it flies behind
                              the name, which is what makes the channel read as
                              depth rather than as a gap in a layout. */
  left: 50%;
  top: -11vh;              /* the RESTING frame reference; the rise is all in the
                              transform below, off --hp. */
  width: clamp(320px, 37vw, 600px);
  margin: 0;
  pointer-events: none;
  mix-blend-mode: multiply;
  transform:
    translate3d(-50%, calc(78vh - var(--hp, 0) * 124vh), 0)
    scale(calc(0.82 + var(--hp, 0) * 0.46));
  /* Fades in as it enters from the bottom (invisible at rest), holds, then fades
     as it climbs out the top into #spec. */
  opacity: calc(clamp(0, var(--hp, 0) * 6, 1)
                * (1 - clamp(0, (var(--hp, 0) - 0.60) * 2.8, 1)));
  will-change: transform;
}

.hero__craft img {
  display: block;
  width: 100%;
  height: auto;
}

@media (max-width: 767px) {
  /* On a phone the channel is too narrow to hold a word either side of the
     aircraft — it still rises, but as a faint watermark behind the stacked
     lockup rather than the hero image. */
  .hero__craft {
    width: min(74vw, 360px);
    opacity: calc(clamp(0, var(--hp, 0) * 6, 0.14)
                  * (1 - clamp(0, (var(--hp, 0) - 0.60) * 2.8, 1)));
  }}

@media (prefers-reduced-motion: reduce) {
  /* No fly-in. The craft sits parked, centred, low in the channel — present but
     not moving. */
  .hero__craft {
    transform: translate3d(-50%, 8vh, 0) scale(1);
    opacity: 1;
  }}

/* The whole first screen is the wordmark: company name, nothing else.
   "Charters" sits on its own line, letterspaced and lighter, so the two
   words read as one lockup rather than a headline. */
/* Northlake ships 300 / 500 / 700 only — these must stay on those three values
   or the browser starts faux-bolding. */
/* NOTE: no color / no text-shadow here. text-shadow INHERITS, so a shadow left
   on the H1 lands on every .hw__word span underneath — which is how the words
   ended up wearing a dark-sky glow on a cream ground. The rails own their own
   color; see .hw__word. */
.hero__wordmark {
  font-size: clamp(3rem, 12vw, 9rem);
  font-weight: 300;
  line-height: 0.95;
  margin: 0;
}

/* Supporting line. Lives inside .hero__content — it is NOT a sibling of the
   wordmark any more (the H1 was lifted out into .hero__sticky so it could
   outlive the content fade), so do not reintroduce `.hero__wordmark + &`
   rules here: the combinator can never match. */
.hero__sub {
  font-size: clamp(1rem, 2.5vw, 1.1875rem);
  line-height: 1.7;
  color: rgba(245, 240, 232, 0.88);
  max-width: 620px;
  margin: 1.75rem auto 0;
}

/* TYPEWRITER — now driven by js/preloader.js on the PLATE, not by the hero.
   (initWordmarkType() is gone from main.js; two implementations of the same
   typewriter in one codebase is how you end up running both.) These .char /
   .is-typed / caret rules are kept because the preloader's lockup reuses this
   exact vocabulary. Characters are hidden with opacity rather than
   display/visibility so they still occupy their final space: the line never
   reflows as it types, and the caret lands exactly where the next glyph will
   appear. */
.hero__wordmark .char {
  opacity: 0;
  /* Each character is its own span, so the spaces in the descriptor line are
     spans holding a single space — which would otherwise collapse to nothing. */
  white-space: pre;
}

.hero__wordmark .char.is-typed {
  opacity: 1;
}

.hero__caret {
  display: inline-block;
  width: 0.06em;
  height: 0.78em;
  margin-left: 0.02em;
  vertical-align: baseline;
  background: var(--amber);
  animation: caretBlink 0.75s step-end infinite;
}

.hero__caret[hidden] {
  display: none;
}

@keyframes caretBlink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0; }}

@media (prefers-reduced-motion: reduce) {
  .hero__wordmark .char { opacity: 1; }
  .hero__caret { display: none; }}

.hero__scroll-hint {
  position: absolute;
  bottom: 2.5rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 2;
  opacity: 0.65;
  transition: opacity var(--transition);
}

.hero__scroll-hint:hover {
  opacity: 1;
}

.scroll-dot {
  display: block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--amber);
  animation: scrollBounce 2s ease-in-out infinite;
}

@keyframes scrollBounce {
  0%, 100% { transform: translateY(0);    opacity: 0.8; }
  50%       { transform: translateY(10px); opacity: 0.3; }}

/* ============================================================
   FLY BAR — sticky bottom CTA
   Collapsed: a single "Fly Now" pill. Expanded: leaving/going-to
   pickers. Fixed to the bottom of the viewport on every scroll
   position, above all page content.
   ============================================================ */
.flybar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 150; /* above nav (100) and page content, below the adventure modal (200) */
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.75rem;
  padding: 0.875rem var(--page-padding);
  padding-bottom: max(0.875rem, env(safe-area-inset-bottom));
  /* NO BAR. The strip is transparent — the "Fly Now" pill (and, when open, the
     contact card) float over the page with nothing behind them, matching the
     transparent nav. The card carries its own surface. --flybar-h, the ids,
     data-open and z-index 150 are unchanged. */
  background: transparent;
  pointer-events: none;   /* the transparent strip must not eat clicks; the pill
                             and card re-enable pointer events on themselves */
  /* Ride the same fake centre line as the machine and the words. AVIATRIX and
     CHARTERS are unequal widths, so the gap the helicopter sits in is --seam-shift
     LEFT of the raw viewport centre; nudge the fly-now pill (and the card it opens
     into) by the same amount so the whole lockup shares one centre. The var is only
     set on the home hero, so this is a no-op (0) on every other page. */
  transform: translateX(calc(var(--seam-shift, 0px) * -1));
}

.flybar__cta,
.flybar__panel { pointer-events: auto; }

.flybar__cta {
  display: inline-flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.8rem 2.5rem;
  border: 1px solid var(--rule-warm);
  border-radius: 999px;
  background: var(--white);
  color: var(--ink-warm);
  box-shadow: 0 10px 26px -14px rgba(43, 38, 33, 0.45);
  font-family: var(--font-sans);
  font-size: 1rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  cursor: pointer;
  transition: background var(--transition), transform var(--transition),
              border-color var(--transition);
}

.flybar__cta:hover {
  background: var(--white);
  border-color: var(--amber);
  transform: translateY(-1px);
}

/* Chevron points up when collapsed, flips down once the panel is open. */
.flybar__cta-chev {
  width: 0.5rem;
  height: 0.5rem;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-135deg);
  transition: transform var(--transition);
}

.flybar[data-open='true'] .flybar__cta-chev {
  transform: rotate(45deg);
}

/* ── THE CONTACT CARD ──────────────────────────────────────────────────────
   The Jesko contact bar, in our fonts and colours: a wide near-white rounded
   card that floats above the page. "Contact" in the display serif on the left,
   four underline fields across the middle, a round ink send button on the right,
   and the consent line beneath the fields. */
.flybar__panel {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-areas:
    'title fields send'
    'title consent send';
  align-items: center;
  column-gap: clamp(1.25rem, 3vw, 2.75rem);
  row-gap: 0.75rem;
  width: min(1180px, 100%);
  padding: clamp(1.25rem, 2.4vw, 2rem) clamp(1.5rem, 3vw, 2.75rem);
  background: var(--hero-haze);
  border: 1px solid var(--rule-warm);
  border-radius: clamp(1.25rem, 2vw, 1.75rem);
  box-shadow: 0 24px 60px -28px rgba(43, 38, 33, 0.5);
}

.flybar__panel[hidden] { display: none; }

.flybar__title {
  grid-area: title;
  margin: 0;
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: clamp(1.75rem, 3.4vw, 3rem);
  line-height: 1;
  color: var(--ink-warm);
}

.flybar__fields {
  grid-area: fields;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(1rem, 2.4vw, 2.5rem);
}

.flybar__field {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  min-width: 0;
}

.flybar__label {
  font-family: var(--font-sans);
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-warm);
}

/* Underline fields — a hairline, not a box, exactly like the reference. */
.flybar__input {
  width: 100%;
  padding: 0.35rem 0 0.5rem;
  border: none;
  border-bottom: 1.5px solid var(--rule-warm);
  border-radius: 0;
  background: transparent;
  color: var(--ink-warm);
  font-family: var(--font-sans);
  font-size: clamp(1rem, 1.4vw, 1.25rem);
  transition: border-color var(--transition);
}
.flybar__input::placeholder { color: rgba(43, 38, 33, 0.4); }
.flybar__input:focus {
  outline: none;
  border-bottom-color: var(--amber);
}

/* The round send button — ink disc, cream airplane. */
.flybar__submit {
  grid-area: send;
  align-self: center;
  justify-self: end;
  display: inline-grid;
  place-items: center;
  width: clamp(3rem, 4vw, 3.75rem);
  height: clamp(3rem, 4vw, 3.75rem);
  border: none;
  border-radius: 50%;
  background: var(--ink-warm);
  color: var(--hero-haze);
  cursor: pointer;
  transition: background var(--transition), transform var(--transition);
}
.flybar__submit:hover { background: var(--amber); transform: translateY(-1px); }
.flybar__submit svg { display: block; }

/* Consent line under the fields. */
.flybar__consent {
  grid-area: consent;
  display: flex;
  align-items: center;
  gap: 0.55rem;
  font-family: var(--font-sans);
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: rgba(43, 38, 33, 0.6);
}
.flybar__consent input { accent-color: var(--amber); width: 0.95rem; height: 0.95rem; }
.flybar__consent a { color: var(--ink-warm); text-decoration: underline; }

.flybar__done {
  grid-column: 1 / -1;
  margin: 0;
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: clamp(1.25rem, 2vw, 1.75rem);
  color: var(--ink-warm);
  text-align: center;
  padding: 0.5rem 0;
}

/* The submit handler hides the form rows with the `hidden` attribute, but each
   of those rows sets its own display (grid/flex/inline-grid), which out-ranks
   the UA `[hidden]{display:none}`. Force it inside the card. */
.flybar__panel [hidden] { display: none !important; }

/* The whole card shakes when submitted incomplete, instead of doing nothing. */
@keyframes flybarNudge {
  0%, 100% { transform: translateX(0); }
  25%      { transform: translateX(-5px); }
  75%      { transform: translateX(5px); }}
.flybar--nudge .flybar__panel { animation: flybarNudge 0.3s ease; }

/* MOBILE — the card stacks: title, then fields in a single column, consent,
   then a full-width send row. */
@media (max-width: 767px) {
  .flybar__panel {
    grid-template-columns: 1fr;
    grid-template-areas: 'title' 'fields' 'consent' 'send';
    row-gap: 1rem;
    max-height: 70vh;
    overflow-y: auto;
  }
  .flybar__fields { grid-template-columns: 1fr 1fr; }
  .flybar__submit { justify-self: stretch; width: 100%; border-radius: 999px; height: 3rem; }}

@media (prefers-reduced-motion: reduce) {
  .flybar__cta,
  .flybar__cta-chev,
  .flybar__submit,
  .flybar--nudge .flybar__panel {
    transition: none;
    animation: none;
  }}
@keyframes pinRipple {
  0%   { transform: translateX(-50%) scale(0.3); opacity: 0.85; }
  100% { transform: translateX(-50%) scale(2.6); opacity: 0; }}

/* ── Trail: fixed viewport overlay. viewBox 0–100 always maps to the
      viewport, so the path coordinates never break (no sticky un-stick). ── */
.trail {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  pointer-events: none;
  z-index: 4;
}

/* ============================================================
   ADVENTURES — CHOOSE YOUR EXPERIENCE
   ============================================================ */
.adventures {
  background: var(--cream);
  padding: 1.5rem 0 4rem;
  position: relative;
  z-index: 1;
}

.adventures .container {
  margin-bottom: 2.5rem;
}

/* ── Trail hand-off connector: the fixed trail line lands on the node,
      then a bar splits out to both sides ── */
.adv-intro {
  position: relative;
  height: 16px;            /* node + bar sit at the top */
  margin-bottom: 2.5rem;
  pointer-events: none;
  z-index: 3;
}

/* Node where the trail lands and splits */
.adv-intro__node {
  position: absolute;
  left: 50%;
  top: 8px;
  width: 13px;
  height: 13px;
  margin: -6px 0 0 -6px;
  border-radius: 50%;
  background: var(--amber);
  box-shadow: 0 0 10px rgba(232, 168, 78, 0.8);
  opacity: 0;
  transform: scale(0);
  transition: opacity 0.25s ease, transform 0.35s cubic-bezier(0.34, 1.56, 0.64, 1);
  transition-delay: 0.05s;
}

/* Horizontal bar splits outward from center to both sides */
.adv-intro__bar {
  position: absolute;
  left: 50%;
  top: 8px;
  width: min(760px, 86vw);
  height: 2px;
  margin-top: -1px;
  transform: translateX(-50%) scaleX(0);
  transform-origin: center;
  background: linear-gradient(
    to right,
    rgba(201, 137, 58, 0) 0%,
    var(--amber) 22%,
    var(--amber) 78%,
    rgba(201, 137, 58, 0) 100%
  );
  transition: transform 0.65s cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: 0.18s;
}

.adventures.is-unrolled .adv-intro__node {
  opacity: 1;
  transform: scale(1);
}
.adventures.is-unrolled .adv-intro__bar { transform: translateX(-50%) scaleX(1); }

/* ── Section unfurls downward beneath the split bar ── */
.js .adventures .container {
  clip-path: inset(0 0 100% 0);
  opacity: 0;
  transition: clip-path 0.8s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.8s ease;
  transition-delay: 0.42s;
}

.js .adventures.is-unrolled .container {
  clip-path: inset(0 0 0 0);
  opacity: 1;
}

.js .adventures__grid > .adv-card {
  opacity: 0;
  transform: translateY(34px);
  transition: opacity 0.55s ease, transform 0.55s cubic-bezier(0.22, 1, 0.36, 1);
}

.js .adventures.is-unrolled .adventures__grid > .adv-card {
  opacity: 1;
  transform: none;
}

.adventures.is-unrolled .adv-card:nth-child(1) { transition-delay: 0.58s; }
.adventures.is-unrolled .adv-card:nth-child(2) { transition-delay: 0.65s; }
.adventures.is-unrolled .adv-card:nth-child(3) { transition-delay: 0.72s; }
.adventures.is-unrolled .adv-card:nth-child(4) { transition-delay: 0.79s; }
.adventures.is-unrolled .adv-card:nth-child(5) { transition-delay: 0.86s; }
.adventures.is-unrolled .adv-card:nth-child(6) { transition-delay: 0.93s; }
.adventures.is-unrolled .adv-card:nth-child(7) { transition-delay: 1.00s; }
.adventures.is-unrolled .adv-card:nth-child(8) { transition-delay: 1.07s; }

/* Mobile: single-column stack */
.adventures__grid {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 0 var(--page-padding);
  max-width: var(--max-width);
  margin: 0 auto;
}

/* ── Adventure card base ── */
.adv-card {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius-lg);
  height: 280px;
  cursor: pointer;
  background: var(--ink-warm);   /* the plate under a photo that hasn't decoded */
}

.adv-card__bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  transition: transform 0.6s ease;
}

.adv-card:hover .adv-card__bg {
  transform: scale(1.06);
}

/* Gradient overlay */
/* The scrim over the photography stays DARK — it is what the white title sits
   on, and there is no light-page way to put type on an unknown photograph. It is
   warm-neutral now (the page's ink) rather than forest green: over a cream page
   a green wash is the single loudest leftover of the old palette. */
.adv-card::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(
    to top,
    rgba(28, 24, 20, 0.82) 0%,
    rgba(28, 24, 20, 0.30) 55%,
    rgba(28, 24, 20, 0.12) 100%
  );
  transition: background 0.35s ease;
  pointer-events: none;
  z-index: 1;
}

.adv-card:hover::after {
  background: linear-gradient(
    to top,
    rgba(28, 24, 20, 0.72) 0%,
    rgba(28, 24, 20, 0.20) 55%,
    rgba(28, 24, 20, 0.05) 100%
  );
}

/* Card body — sits above overlay */
.adv-card__body {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 1.5rem 1.75rem 1.75rem;
  z-index: 2;
  transition: transform 0.35s ease;
}

.adv-card:hover .adv-card__body {
  transform: translateY(-5px);
}

.adv-card__tag {
  display: block;
  font-family: var(--font-sans);
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--amber-light);
  margin-bottom: 0.45rem;
}

.adv-card__title {
  font-family: var(--font-serif);
  font-size: clamp(1.1rem, 2.5vw, 1.5rem);
  font-weight: 700;
  color: var(--white);
  line-height: 1.2;
  margin-bottom: 0.3rem;
  text-shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
  display: inline-block;
  position: relative;
}

/* Amber underline slide-in on hover */
.adv-card__title::after {
  content: '';
  position: absolute;
  bottom: -3px;
  left: 0;
  height: 2px;
  width: 100%;
  background: var(--amber);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 0.38s ease;
}

.adv-card:hover .adv-card__title::after {
  transform: scaleX(1);
}

.adv-card__tagline {
  display: block;
  font-size: 0.825rem;
  color: rgba(245, 240, 232, 0.72);
  line-height: 1.45;
}

/* Full-card invisible trigger button */
.adv-card__trigger {
  position: absolute;
  inset: 0;
  z-index: 3;
  background: none;
  border: none;
  cursor: pointer;
  display: flex;
  align-items: flex-end;
  justify-content: flex-end;
  padding: 1.25rem 1.5rem;
}

.adv-card__trigger span {
  opacity: 0;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--ink-warm);
  letter-spacing: 0.05em;
  background: rgba(251, 250, 248, 0.92);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  padding: 0.45rem 0.9rem;
  border-radius: 2rem;
  border: 1px solid rgba(201, 137, 58, 0.5);
  transition: opacity 0.25s ease, transform 0.25s ease;
  transform: translateY(6px);
}

.adv-card:hover .adv-card__trigger span,
.adv-card__trigger:focus span {
  opacity: 1;
  transform: translateY(0);
}

.adv-card__trigger:focus-visible {
  outline: 3px solid var(--amber);
  outline-offset: -4px;
  border-radius: var(--radius-lg);
}

/* ── Featured card (Crystal Mining — 2-col) ── */
.adv-card--featured {
  height: 340px;
}

/* Amber color warmth via CSS tint — richer than a border */
.adv-card--featured .adv-card__bg::after {
  content: '';
  position: absolute;
  inset: 0;
  background: rgba(201, 137, 58, 0.18);
  mix-blend-mode: multiply;
  pointer-events: none;
}

/* ── Wide card (Antebellum Mansion — full-width) ── */
.adv-card--wide {
  height: 260px;
}

/* Tablet: 2-column grid */
@media (min-width: 640px) {
  .adventures__grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0.875rem;
  }

  .adv-card--featured {
    grid-column: span 2;
    height: 380px;
  }

  .adv-card--wide {
    grid-column: span 2;
    height: 280px;
  }}

/* Desktop: 4-column grid */
@media (min-width: 1024px) {
  .adventures__grid {
    grid-template-columns: repeat(4, 1fr);
  }

  .adv-card {
    height: 340px;
  }

  .adv-card--featured {
    grid-column: span 2;
    height: 380px;
  }

  .adv-card--wide {
    grid-column: span 4;
    height: 260px;
  }}

/* ============================================================
   ADVENTURE MODAL
   ============================================================ */
.adv-modal {
  position: fixed;
  inset: 0;
  z-index: 200;
  display: flex;
  align-items: flex-end;
}

.adv-modal[hidden] {
  display: none;
}

.adv-modal__backdrop {
  position: absolute;
  inset: 0;
  background: rgba(43, 38, 33, 0.55);   /* warm ink, not forest */
  backdrop-filter: blur(4px);
  -webkit-backdrop-filter: blur(4px);
  opacity: 0;
  transition: opacity 0.35s ease;
}

.adv-modal.is-open .adv-modal__backdrop {
  opacity: 1;
}

/* Mobile: bottom sheet */
.adv-modal__dialog {
  position: relative;
  z-index: 1;
  width: 100%;
  max-height: 88vh;
  background: var(--white);
  border-radius: 1.375rem 1.375rem 0 0;
  overflow: hidden;
  display: flex;
  flex-direction: column;
  transform: translateY(100%);
  transition: transform 0.42s cubic-bezier(0.22, 1, 0.36, 1);
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}

/* Mobile drag-handle indicator */
.adv-modal__dialog::before {
  content: '';
  display: block;
  width: 40px;
  height: 4px;
  background: rgba(43, 38, 33, 0.18);
  border-radius: 2px;
  margin: 0.875rem auto 0;
  flex-shrink: 0;
}

.adv-modal.is-open .adv-modal__dialog {
  transform: translateY(0);
}

.adv-modal__close {
  position: absolute;
  top: 1rem;
  right: 1rem;
  z-index: 10;
  width: 38px;
  height: 38px;
  border-radius: 50%;
  background: rgba(43, 38, 33, 0.55);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  color: var(--white);
  border: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.2s ease;
}

.adv-modal__close:hover {
  background: rgba(43, 38, 33, 0.85);
}

.adv-modal__img-wrap {
  flex-shrink: 0;
  height: 260px;
  position: relative;
  overflow: hidden;
}

.adv-modal__img {
  width: 100%;
  height: 100%;
  background-size: cover;
  background-position: center;
  opacity: 0;
  transition: opacity 0.5s ease;
  transition-delay: 0ms;
}

.adv-modal.is-open .adv-modal__img {
  opacity: 1;
  transition-delay: 80ms;
}

.adv-modal__body {
  padding: 1.75rem 1.5rem 2.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.875rem;
  flex: 1;
}

.adv-modal__tag {
  display: block;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--amber);
}

.adv-modal__title {
  font-size: clamp(1.5rem, 4vw, 2.125rem);
  color: var(--ink-warm);
  line-height: 1.15;
  margin: 0;
}

/* Amber italic on white is 2.8:1 and this line is a sentence, not a flourish. */
.adv-modal__tagline {
  font-size: 1rem;
  color: rgba(43, 38, 33, 0.78);
  font-weight: 500;
  font-style: italic;
  margin: 0;
}

.adv-modal__desc {
  font-size: 0.9375rem;
  line-height: 1.75;
  color: rgba(43, 38, 33, 0.78);   /* --gray was 4.4:1 on white */
  margin: 0;
}

.adv-modal__highlights {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.55rem;
}

.adv-modal__highlights li {
  font-size: 0.9rem;
  color: var(--ink-warm);
  padding-left: 1.375rem;
  position: relative;
  line-height: 1.5;
}

.adv-modal__highlights li::before {
  content: '✦';
  position: absolute;
  left: 0;
  color: var(--amber);
  font-size: 0.6rem;
  top: 0.22em;
}

.adv-modal__actions {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding-top: 0.25rem;
}

.adv-modal__book-btn {
  text-align: center;
}

.adv-modal__back {
  background: none;
  border: none;
  color: rgba(43, 38, 33, 0.78);
  font-size: 0.875rem;
  cursor: pointer;
  text-align: left;
  padding: 0;
  font-family: var(--font-sans);
  transition: color 0.2s ease;
}

.adv-modal__back:hover {
  color: var(--ink-warm);
}

/* Desktop: centered modal with side-by-side layout */
@media (min-width: 768px) {
  .adv-modal {
    align-items: center;
    justify-content: center;
    padding: 2rem;
  }

  .adv-modal__dialog {
    width: min(80vw, 960px);
    max-height: 90vh;
    border-radius: 1.375rem;
    flex-direction: row;
    transform: scale(0.94);
    opacity: 0;
    transition: transform 0.42s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.38s ease;
    overflow-y: unset;
  }

  .adv-modal__dialog::before {
    display: none;
  }

  .adv-modal.is-open .adv-modal__dialog {
    transform: scale(1);
    opacity: 1;
  }

  .adv-modal__img-wrap {
    width: 42%;
    flex-shrink: 0;
    height: auto;
    min-height: 420px;
  }

  .adv-modal__body {
    flex: 1;
    padding: 2.5rem 2.25rem;
    overflow-y: auto;
  }

  .adv-modal__close {
    top: 1.125rem;
    right: 1.125rem;
    background: rgba(255, 255, 255, 0.88);
    color: var(--ink-warm);
  }

  .adv-modal__close:hover {
    background: var(--white);
  }

  .adv-modal__actions {
    flex-direction: row;
    align-items: center;
    flex-wrap: wrap;
  }}

/* ============================================================
   WHY OZARK TOURS — NUMBERED DIFFERENTIATORS
   ============================================================ */
/* NOTE — .why / .stats / .testimonial / .gallery / .ticker / .wave-divider /
   .more / .adv-* are not in index.html any more (the journey + spec rooms
   replaced them); main.js only reaches for them defensively. They are repainted
   anyway: leaving forest-green rules in the sheet means the day someone
   re-mounts one of these blocks, a slab of the old palette lands back on a cream
   page. Paint, not layout — none of them were re-designed. */
.why {
  background: var(--hero-sand);
  color: var(--ink-warm);
  padding: 5rem 0 4rem;
}

.why .container {
  margin-bottom: 3rem;
}

.why .container h2 {
  color: var(--ink-warm);
}

.why__list {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--page-padding);
  display: flex;
  flex-direction: column;
}

/* Initial hidden state — set by JS (progressive enhancement) */
.js .why__item {
  opacity: 0;
  transform: translateY(28px);
  transition: opacity 0.55s ease, transform 0.55s ease;
}

.js .why__item.is-visible {
  opacity: 1;
  transform: translateY(0);
}

.why__item {
  display: flex;
  gap: 2rem;
  align-items: flex-start;
  padding: 2.25rem 0;
  border-bottom: 1px solid rgba(43, 38, 33, 0.14);
}

.why__item:last-child {
  border-bottom: none;
}

.why__num {
  font-family: var(--font-serif);
  font-size: 2.5rem;
  font-weight: 900;
  color: var(--amber);
  line-height: 1;
  flex-shrink: 0;
  min-width: 3rem;
}

.why__text h3 {
  color: var(--ink-warm);
  font-size: 1.25rem;
  margin-bottom: 0.5rem;
}

.why__text p {
  font-size: 0.9375rem;
  line-height: 1.75;
  color: rgba(43, 38, 33, 0.75);
}

@media (min-width: 768px) {
  .why__item {
    gap: 3.5rem;
    padding: 2.75rem 0;
  }

  .why__num {
    font-size: 3.25rem;
    min-width: 4.5rem;
  }

  .why__text h3 {
    font-size: 1.5rem;
  }

  .why__text p {
    max-width: 580px;
    font-size: 1rem;
  }}

/* ============================================================
   STATS — BY THE NUMBERS
   ============================================================ */
.stats {
  background: var(--amber);
  padding: 4.5rem var(--page-padding);
}

.stats__inner {
  max-width: var(--max-width);
  margin: 0 auto;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 3rem 2rem;
  text-align: center;
}

.stats__value {
  display: flex;
  /* CENTRE, not baseline. align-items:baseline lines the two words up with each
     other but pins the row to the TOP of the cross axis, so the whole name
     typed itself against the top edge of the screen. The two words are the same
     font-size and the same line-height, so centring them aligns their baselines
     anyway — there is nothing baseline alignment was buying here. */
  align-items: center;
  justify-content: center;
  gap: 0.1em;
  line-height: 1;
  margin-bottom: 0.35rem;
}

.stats__num {
  font-family: var(--font-serif);
  font-size: clamp(2.75rem, 7vw, 4rem);
  font-weight: 900;
  color: var(--ink-warm);
  line-height: 1;
}

.stats__plus,
.stats__star {
  font-family: var(--font-serif);
  font-size: clamp(1.75rem, 4vw, 2.5rem);
  font-weight: 700;
  color: var(--ink-warm);
  opacity: 0.75;
}

/* Full ink on the amber band: ink at 60% measures 3.4:1 there and fails. */
.stats__item p {
  font-size: 0.8125rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-warm);
}

@media (min-width: 640px) {
  .stats__inner {
    grid-template-columns: repeat(4, 1fr);
    gap: 2rem;
  }}

/* ============================================================
   TESTIMONIAL
   ============================================================ */
.testimonial {
  background: var(--hero-haze);
  color: var(--ink-warm);
  padding: 6rem var(--page-padding);
}

.testimonial__inner {
  max-width: 820px;
  margin: 0 auto;
  text-align: center;
  position: relative;
}

.testimonial__mark {
  font-family: var(--font-serif);
  font-size: 8rem;
  line-height: 0.5;
  color: var(--amber);
  opacity: 0.55;
  display: block;
  margin-bottom: 1.5rem;
  user-select: none;
}

.testimonial blockquote p {
  font-family: var(--font-serif);
  font-size: clamp(1.2rem, 3.5vw, 2rem);
  font-style: italic;
  line-height: 1.6;
  color: var(--ink-warm);
}

.testimonial blockquote footer {
  margin-top: 1.75rem;
}

.testimonial blockquote cite {
  font-family: var(--font-sans);
  font-size: 0.9375rem;
  font-style: normal;
  color: rgba(43, 38, 33, 0.72);
  letter-spacing: 0.04em;
}

/* ============================================================
   GALLERY
   ============================================================ */
.gallery {
  background: var(--hero-cream);
  padding: 5rem 0 4rem;
}

.gallery .container {
  margin-bottom: 2.5rem;
}

/* Mobile: stacked, each image fills width */
.gallery__grid {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
  padding: 0 var(--page-padding);
  max-width: var(--max-width);
  margin: 0 auto;
}

.gallery__item {
  position: relative;
  border-radius: var(--radius-lg);
  overflow: hidden;
  background: var(--hero-sand);
}

.gallery__img {
  width: 100%;
  background-size: cover;
  background-position: center;
  transition: transform 0.6s ease;
}

/* Mobile aspect ratios */
.gallery__item     .gallery__img { aspect-ratio: 16 / 10; }
.gallery__item--tall .gallery__img { aspect-ratio: 4 / 5; }
.gallery__item--wide .gallery__img { aspect-ratio: 21 / 9; }

.gallery__caption {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 2.5rem 1.5rem 1.25rem;
  background: linear-gradient(to top, rgba(28, 24, 20, 0.78) 0%, transparent 100%);
  color: var(--white);
  font-size: 0.9375rem;
  font-weight: 500;
  letter-spacing: 0.02em;
}

/* Desktop: CSS grid layout
   [ tall item ] [ item 2  ]
   [ tall item ] [ item 3  ]
   [  wide item spanning 2 cols  ]
*/
@media (min-width: 768px) {
  .gallery__grid {
    display: grid;
    grid-template-columns: repeat(2, 1fr);
    gap: 0.75rem;
  }

  .gallery__item--tall {
    grid-row: span 2;
  }

  .gallery__item--tall .gallery__img {
    aspect-ratio: unset;
    height: 100%;
    min-height: 480px;
  }

  .gallery__item .gallery__img {
    aspect-ratio: 5 / 4;
  }

  .gallery__item--wide {
    grid-column: span 2;
  }

  .gallery__item--wide .gallery__img {
    aspect-ratio: 16 / 6;
  }

  .gallery__item:hover .gallery__img {
    transform: scale(1.04);
  }}

/* ============================================================
   BOOKING — FINAL CTA
   ============================================================ */
/* #f0f4ef was a GREEN-tinted off-white — the last cold surface on the page, and
   next to real cream it read as a colour cast rather than a choice. Plain cream,
   which also lets the white inputs sit up off the ground instead of vanishing
   into it (they would, on the haze). */
.book {
  background: var(--hero-cream);
  padding: 5rem 0 6rem;
  border-top: 1px solid var(--rule-warm);
}

.book__inner {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--page-padding);
  display: flex;
  flex-direction: column;
  gap: 3rem;
}

.book__copy .eyebrow {
  color: var(--amber);
}

.book__copy h2 {
  color: var(--ink-warm);
  margin-bottom: 1rem;
}

.book__copy > p {
  font-size: 1.0625rem;
  line-height: 1.7;
  color: rgba(43, 38, 33, 0.75);   /* was --gray: 4.1:1 on cream, under AA */
  max-width: 420px;
}

/* The phone number is the highest-intent link on the page and it was amber —
   2.5:1 on cream. Ink type, amber rule: the accent is under the number, not in
   it, and the number is now 12.7:1. */
.book__phone {
  display: inline-block;
  margin-top: 1.5rem;
  padding-bottom: 2px;
  font-size: 1.125rem;
  font-weight: 600;
  color: var(--ink-warm);
  border-bottom: 2px solid var(--amber);
  transition: border-color var(--transition);
}

.book__phone:hover {
  border-color: var(--ink-warm);
}

/* Form */
.book__form {
  display: flex;
  flex-direction: column;
  gap: 1.125rem;
}

.form-row {
  display: flex;
  flex-direction: column;
  gap: 1.125rem;
}

.form-group {
  display: flex;
  flex-direction: column;
  gap: 0.375rem;
}

.form-group label {
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--ink-warm);
  letter-spacing: 0.01em;
}

/* WHITE FIELDS ON CREAM. On a light page the inputs are the one thing that must
   NOT be the same value as the ground, or the form dissolves into the section.
   White wells, a warm hairline at 22%, and a solid amber edge on focus. */
.form-group input,
.form-group select,
.form-group textarea {
  padding: 0.875rem 1rem;
  border: 2px solid var(--rule-warm);
  border-radius: var(--radius);
  background: var(--white);
  font-size: 1rem;
  color: var(--ink-warm);
  transition: border-color var(--transition), box-shadow var(--transition);
  -webkit-appearance: none;
  appearance: none;
}

.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
  border-color: var(--amber);
  box-shadow: 0 0 0 3px rgba(201, 137, 58, 0.16);
}

/* Custom select arrow */
.form-group select {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' stroke='%23c9893a' stroke-width='2' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 1rem center;
  padding-right: 2.75rem;
  cursor: pointer;
}

.form-group textarea {
  resize: vertical;
  min-height: 110px;
}

/* 0.3 warm ink on white is 2.7:1 — a placeholder is exempt from AA, but at that
   value it also stopped being legible. 0.5 is 4.6:1 and still clearly a hint. */
.form-group input::placeholder,
.form-group textarea::placeholder {
  color: rgba(43, 38, 33, 0.5);
}

.book__form .btn--primary {
  margin-top: 0.25rem;
}

.form-note {
  font-size: 0.8125rem;
  color: rgba(43, 38, 33, 0.7);   /* 5.2:1 — --gray was 4.1:1 */
  text-align: center;
}

/* The success state. A warm amber wash and an amber rule, ink type — the same
   language as the focus ring, so a completed form and an active field read as
   the same system. (It was a green-on-green panel, which is now the only place
   forest could still have survived.) */
.form-success {
  background: rgba(201, 137, 58, 0.12);
  border: 1.5px solid var(--amber);
  border-radius: var(--radius);
  padding: 1.125rem 1.25rem;
  color: var(--ink-warm);
  font-weight: 600;
  text-align: center;
  font-size: 0.9375rem;
}

@media (min-width: 640px) {
  .form-row {
    flex-direction: row;
  }

  .form-row .form-group {
    flex: 1;
  }}

@media (min-width: 1024px) {
  .book__inner {
    flex-direction: row;
    align-items: flex-start;
    gap: 6rem;
  }

  .book__copy {
    flex: 0 0 360px;
    position: sticky;
    top: calc(var(--nav-height) + 2rem);
  }

  .book__form {
    flex: 1;
  }}

/* ============================================================
   FOOTER
   ============================================================ */
/* THE FLOOR. Sand — the warmest, deepest of the three light stops — so the page
   still LANDS on something instead of fading out. It is the same ground the hero
   ends on, which closes the loop: sand at the top of the scroll, sand at the
   bottom of it. */
.footer {
  background: var(--hero-sand);
  color: var(--ink-warm);
  padding: 4.5rem var(--page-padding) 2rem;
  border-top: 1px solid var(--rule-warm);
}

.footer__inner {
  max-width: var(--max-width);
  margin: 0 auto;
  display: grid;
  grid-template-columns: 1fr;
  gap: 2.5rem;
  padding-bottom: 3rem;
  border-bottom: 1px solid rgba(43, 38, 33, 0.18);
}

.footer__logo {
  font-family: var(--font-serif);
  font-size: 1.5rem;
  font-weight: 700;
  color: var(--ink-warm);
  display: block;
  margin-bottom: 0.875rem;
}

.footer__brand p {
  font-size: 0.9rem;
  line-height: 1.7;
  color: rgba(43, 38, 33, 0.72);   /* 5.0:1 on sand — 0.55 would be 3.2:1 */
}

.footer__serving {
  margin-top: 0.85rem;
  max-width: 28ch;
  font-size: 0.78rem;
  line-height: 1.55;
  color: rgba(43, 38, 33, 0.6);
}

.footer__phone {
  display: inline-block;
  margin-top: 1rem;
  padding-bottom: 2px;
  font-size: 1rem;
  font-weight: 600;
  color: var(--ink-warm);
  border-bottom: 2px solid var(--amber);
  transition: border-color var(--transition);
}

.footer__phone:hover {
  border-color: var(--ink-warm);
}

.footer__nav {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.footer__nav h4 {
  font-family: var(--font-sans);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-warm);          /* 10.6:1 — 0.35 alpha would be 1.9:1 */
  margin-bottom: 0.5rem;
}

.footer__nav a,
.footer__nav span {
  font-size: 0.9375rem;
  color: rgba(43, 38, 33, 0.75);   /* 5.4:1 on sand */
  transition: color var(--transition);
}

.footer__nav a:hover {
  color: var(--ink-warm);
}

/* Clear the fixed .flybar so it never covers the copyright line. --flybar-h is
   the bar's measured height, republished by JS whenever it expands or reflows;
   the fallback covers the collapsed bar if the script hasn't run. */
.footer__bottom {
  padding-bottom: calc(var(--flybar-h, 5rem) + 1.5rem);
  max-width: var(--max-width);
  margin: 2rem auto 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  align-items: center;
  text-align: center;
  font-size: 0.8rem;
  color: rgba(43, 38, 33, 0.7);   /* 4.8:1 — small print is still print */
}

@media (min-width: 640px) {
  .footer__inner {
    grid-template-columns: 2fr 1fr 1fr;
  }

  .footer__bottom {
    flex-direction: row;
    justify-content: space-between;
  }}

/* ============================================================
   GENERAL FADE-IN UTILITY (used for testimonial, etc.)
   ============================================================ */
.js .fade-in {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.6s ease, transform 0.6s ease;
}

.js .fade-in.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* ============================================================
   PARALLAX UTILITIES
   ============================================================ */
[data-par], [data-par-x] {
  will-change: transform;
}

/* Gallery items: contain overflow so parallax shift doesn't bleed */
.gallery__item {
  overflow: hidden;
}

/* ============================================================
   TICKER STRIP
   ============================================================ */
.ticker {
  background: var(--amber);
  height: 48px;
  overflow: hidden;
  display: flex;
  align-items: center;
  position: relative;
  z-index: 1;
}

.ticker__track {
  display: flex;
  align-items: center;
  gap: 2.5rem;
  animation: ticker-scroll 36s linear infinite;
  white-space: nowrap;
  padding-left: 2.5rem;
}

.ticker__track span {
  font-family: var(--font-sans);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-warm);
  flex-shrink: 0;
}

.ticker__sep {
  color: rgba(43, 38, 33, 0.5) !important;
  letter-spacing: 0 !important;
  font-size: 0.5rem !important;
}

@keyframes ticker-scroll {
  from { transform: translateX(0); }
  to   { transform: translateX(-50%); }}

/* Pause on hover */
.ticker:hover .ticker__track {
  animation-play-state: paused;
}

/* ============================================================
   WAVE DIVIDERS
   ============================================================ */
.wave-divider {
  position: relative;
  height: 80px;
  overflow: hidden;
  z-index: 1;
  /* Remove gap by pulling into the section above */
  margin-top: -2px;
}

.wave-divider svg {
  display: block;
  width: 100%;
  height: 100%;
}

.wave-divider--to-cream  { background: var(--hero-sand); }
.wave-divider--to-forest { background: var(--hero-cream); }

/* Blend divider: sand → cream. It used to run forest-dark through four greens
   into cream — a dusk gradient, and there is no dusk on this page any more. */
.wave-divider--blend {
  height: 240px;
  overflow: visible;
  margin-top: -1px;
  background: linear-gradient(
    to bottom,
    var(--hero-sand) 0%,
    var(--hero-cream) 100%
  );
}

/* Adjust section tops so waves sit flush */
.flights { margin-top: 0; }
.why { margin-top: 0; }

/* ============================================================
   WHY SECTION — DECORATIVE ORBS
   ============================================================ */
.why {
  position: relative;
  overflow: hidden;
}

.why__deco {
  position: absolute;
  inset: 0;
  pointer-events: none;
  z-index: 0;
}

.why__orb {
  position: absolute;
  border-radius: 50%;
  display: block;
}

.why__orb--1 {
  width: min(600px, 60vw);
  height: min(600px, 60vw);
  background: radial-gradient(circle, rgba(201,137,58,0.10) 0%, transparent 68%);
  top: -15%;
  right: -10%;
}

.why__orb--2 {
  width: min(400px, 50vw);
  height: min(400px, 50vw);
  background: radial-gradient(circle, rgba(201,137,58,0.07) 0%, transparent 65%);
  bottom: 5%;
  left: -8%;
}

.why__orb--3 {
  width: min(250px, 35vw);
  height: min(250px, 35vw);
  background: radial-gradient(circle, rgba(201,137,58,0.12) 0%, transparent 60%);
  top: 45%;
  left: 50%;
}

/* Ensure why content sits above orbs */
.why .container,
.why__list {
  position: relative;
  z-index: 1;
}

/* ============================================================
   AMBIENT FLOATING PARTICLES
   ============================================================ */
.ambient {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  overflow: hidden;
}

.ambient__orb {
  position: absolute;
  border-radius: 50%;
  display: block;
  animation: ambientFloat 18s ease-in-out infinite;
}

/* The orbs were amber glows tuned to bloom on a black page. On cream, additive
   warmth at 4% is invisible; the same amber at 8–10% instead reads as a warm
   cast in the paper, which is the light-room version of the same idea. */
.ambient__orb--1 {
  width: 320px;
  height: 320px;
  background: radial-gradient(circle, rgba(201,137,58,0.08) 0%, transparent 65%);
  top: 20%;
  left: -80px;
  animation-duration: 22s;
}

.ambient__orb--2 {
  width: 200px;
  height: 200px;
  background: radial-gradient(circle, rgba(201,137,58,0.10) 0%, transparent 60%);
  top: 55%;
  right: -40px;
  animation-duration: 16s;
  animation-delay: -8s;
}

.ambient__orb--3 {
  width: 150px;
  height: 150px;
  background: radial-gradient(circle, rgba(201,137,58,0.09) 0%, transparent 60%);
  bottom: 30%;
  left: 40%;
  animation-duration: 26s;
  animation-delay: -13s;
}

@keyframes ambientFloat {
  0%, 100% { transform: translateY(0) translateX(0); }
  25%       { transform: translateY(-30px) translateX(20px); }
  50%       { transform: translateY(-10px) translateX(-15px); }
  75%       { transform: translateY(20px) translateX(10px); }}

/* ============================================================
   TESTIMONIAL MARK parallax — needs relative context
   ============================================================ */
.testimonial__inner {
  position: relative;
}

.testimonial__mark {
  display: block;
  position: relative;
  z-index: 0;
}
@keyframes flightHintPulse {
  0%, 100% { opacity: 0.4; }
  50% { opacity: 0.85; }}

/* ============================================================
   MEET THE PILOT
   ============================================================ */
/* THE BRIGHT BAND. #pilot sits between #spec (cream) and #journey (cream), and
   if it is cream too the page runs 3,000px without taking a breath. It is the
   HAZE — the near-white — so the sequence reads cream / white / cream and the
   man gets his own light. The old three-stop forest gradient existed to hide a
   seam against a dark video that no longer exists; a flat field and two
   hairlines do the job now. */
.pilot {
  background: var(--hero-haze);
  color: var(--ink-warm);
  padding: clamp(4rem, 10vw, 7rem) var(--page-padding);
  border-top: 1px solid var(--rule-warm);
  border-bottom: 1px solid var(--rule-warm);
}

.pilot__inner {
  max-width: var(--max-width);
  margin: 0 auto;
  display: grid;
  gap: clamp(2rem, 5vw, 4rem);
  align-items: center;
}

.pilot__media {
  position: relative;
}

/* The headshot plate, until there is a headshot. A warm sand card with a hint of
   amber light across it — the same "empty product field" the hero and #spec use,
   not a black hole waiting for a photo. */
.pilot__photo {
  position: relative;
  aspect-ratio: 4 / 5;
  border-radius: var(--radius-lg);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(120% 90% at 30% 20%, rgba(201, 137, 58, 0.20), transparent 58%),
    linear-gradient(160deg, var(--hero-cream) 0%, var(--hero-sand) 100%);
  border: 1px solid var(--rule-warm);
}

.pilot__photo-note {
  font-size: 0.8rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(43, 38, 33, 0.7);   /* 5.2:1 on the sand plate */
}

.pilot__badge {
  position: absolute;
  left: 1rem;
  bottom: 1rem;
  background: rgba(251, 250, 248, 0.92);
  color: var(--ink-warm);
  font-size: 0.72rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 0.5rem 0.85rem;
  border-radius: 999px;
  backdrop-filter: blur(6px);
  border: 1px solid rgba(201, 137, 58, 0.5);
}

.pilot__copy .eyebrow { color: var(--amber); }
.pilot__copy h2 { color: var(--ink-warm); margin-bottom: 1.25rem; }

.pilot__lead {
  font-size: clamp(1rem, 2.2vw, 1.125rem);
  line-height: 1.75;
  color: rgba(43, 38, 33, 0.78);   /* 6.6:1 */
  margin-bottom: 1.75rem;
}

.pilot__creds {
  list-style: none;
  display: grid;
  gap: 0.75rem;
  margin-bottom: 2rem;
}

.pilot__creds li {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  font-size: 0.975rem;
  color: rgba(43, 38, 33, 0.78);
  padding-bottom: 0.75rem;
  border-bottom: 1px solid rgba(43, 38, 33, 0.12);
}

/* The cred KEY is the one amber note in the column, and it is display-sized
   serif — the same role .why__num used to play. The VALUE beside it is ink. */
.pilot__cred-k {
  font-family: var(--font-serif);
  font-size: 1.05rem;
  color: var(--ink-warm);
  min-width: 5.5rem;
  /* Rhymes with the #spec <dt> label voice (0.24em). The page now states the
     same typographic figure twice — once about the man, once about the machine.
     That rhyme is a large part of why the reorder works. */
  letter-spacing: 0.16em;
}

@media (min-width: 768px) {
  .pilot__inner {
    grid-template-columns: 0.85fr 1.15fr;
  }
  .pilot__photo { max-width: 420px; }}

/* ============================================================
   THE AIRCRAFT — AS350 3D scroll fly-through
   Tall track + sticky stage holding the WebGL canvas. Captions
   are absolutely positioned over the canvas and swapped by JS.
   ============================================================ */
/* The section ground has to MATCH the sky js/heli.js paints into the canvas, or
   the WebGL box shows as a rectangle of a different colour whenever the canvas
   is letterboxed or the context is lost. Same three stops, same order: haze at
   the zenith, cream through the body, sand at the floor. */
.aircraft {
  position: relative;
  height: 360vh;
  background:
    radial-gradient(140% 120% at 50% 8%,
      var(--hero-haze) 0%,
      var(--hero-cream) 55%,
      var(--hero-sand) 100%);
}

.aircraft__viz {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100svh;
  min-height: 600px;
  overflow: hidden;
}

.aircraft__canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* The stage is pinned at top:0, i.e. BEHIND the fixed bar — so the intro's offset
   has to be measured from the bar, not from the viewport. The old clamp() floor
   (4.5rem = 72px) left 4px of daylight under a 68px nav on a short window, and
   the eyebrow fouled the hairline. Anchor to --nav-height and the clearance is
   the same on every viewport. */
.aircraft__intro {
  position: absolute;
  top: calc(var(--nav-height) + clamp(1rem, 4vh, 2.5rem));
  left: 0;
  right: 0;
  text-align: center;
  padding: 0 var(--page-padding);
  z-index: 2;
  pointer-events: none;
  color: var(--white);
  transition: opacity 0.6s ease;
}

/* The heading is BLACK here (client request), so it needs a LIGHT ground to read
   — most of the time that is just the light 3D room, but the dark helicopter also
   scrolls up behind it. A soft CREAM radial glow sits behind the title only,
   fading out into the scene, so the black type keeps a clean light ground whether
   it is over the room or over the machine. Fades out with the intro. */
.aircraft__intro::before {
  content: '';
  position: absolute;
  inset: -45% 8% -90%;
  z-index: -1;
  pointer-events: none;
  background: radial-gradient(44% 76% at 50% 44%,
    rgba(241, 236, 227, 0.92) 0%,
    rgba(241, 236, 227, 0.55) 38%,
    rgba(241, 236, 227, 0) 66%);
}

/* The title has a job — announce the section — and then it is in the way. The
   camera flies INSIDE the cabin from p≈0.55 (see sampleCamera in js/heli.js),
   and from there the frame is cockpit: "A-Star." in amber landed on the tan
   door post and dissolved. So the intro holds for the opening beat and then
   gets out of the shot. js/heli.js adds .is-faded past the first step. */
.aircraft__intro.is-faded {
  opacity: 0;
}

.aircraft__intro .eyebrow { color: var(--ink-warm); }  /* black on the cream glow (client request) */
/* Both "Meet the" and the <em>A-Star.</em> go black. The em override beats the
   global `em { color: --amber }` so the whole heading reads as one black lockup. */
.aircraft__intro h2,
.aircraft__intro h2 em { color: var(--ink-warm); }

/* THE CAPTION RIDES A PLATE.
   The old rule bet that this copy would always land on the sand floor of the 3D
   sky ("no scrim needed"). That bet is wrong, and the screenshots say so: on the
   exterior orbit the block lands on the SKIDS, and through the cockpit dwell
   (p 0.66–0.86) the frame is entirely instrument panel — mid-grey dials, a red
   WARNING placard — so ink-on-"cream" was really ink-on-cockpit and the copy
   came apart. The model and the type were fighting for one band.
   The fix is not a dark scrim (that is the leftover-dirt look this repaint is
   getting rid of) — it is a LIGHT one. A frosted cream plate, the same card the
   journey section floats over the satellite map. Same house idiom, and the copy
   is now on a known ground in every frame instead of a hoped-for one. */
.aircraft__caption {
  position: absolute;
  left: 50%;
  bottom: clamp(3.25rem, 9vh, 5.5rem);
  transform: translateX(-50%) translateY(12px);
  width: min(560px, calc(100% - 2 * var(--page-padding)));
  padding: 1.5rem 1.875rem 1.625rem;
  background: rgba(251, 250, 248, 0.94);
  backdrop-filter: blur(14px);
  -webkit-backdrop-filter: blur(14px);
  border: 1px solid rgba(43, 38, 33, 0.12);
  border-radius: 1.25rem;
  box-shadow: 0 28px 60px -34px rgba(43, 38, 33, 0.75);
  text-align: center;
  z-index: 2;
  pointer-events: none;
  color: var(--ink-warm);
  opacity: 0;
  transition: opacity 0.5s ease, transform 0.5s ease;
}

.aircraft__caption.is-visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
}

/* Amber at 12px on the plate is 2.5:1 — the same trap .journey__card
   .step-location already walked out of. Small tracked labels go to ink; the
   amber stays where it is big enough to carry it (the progress bar, the em). */
.aircraft__caption-tag {
  display: inline-block;
  font-size: 0.75rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: rgba(43, 38, 33, 0.7);   /* 5.3:1 on the plate */
  margin-bottom: 0.75rem;
}

.aircraft__caption-title {
  font-family: var(--font-serif);
  font-size: clamp(1.5rem, 4vw, 2.25rem);
  color: var(--ink-warm);
  margin-bottom: 0.625rem;
}

.aircraft__caption-body {
  font-size: clamp(0.95rem, 2.2vw, 1.0625rem);
  line-height: 1.65;
  color: rgba(43, 38, 33, 0.8);   /* 7.6:1 on the plate */
}

.aircraft__progress {
  position: absolute;
  left: 50%;
  bottom: 2.25rem;
  transform: translateX(-50%);
  width: min(220px, 60%);
  height: 2px;
  border-radius: 2px;
  background: rgba(43, 38, 33, 0.16);
  z-index: 2;
  overflow: hidden;
}

.aircraft__progress-fill {
  display: block;
  height: 100%;
  width: 0%;
  background: linear-gradient(90deg, var(--amber), var(--amber-light));
  transition: width 0.1s linear;
}

@media (max-width: 767px) {
  /* Shorter runway on phones so the pinned stage isn't endless */
  .aircraft { height: 300vh; }}

/* ============================================================
   REDUCED MOTION — respect accessibility preference
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  .ticker__track { animation: none; }
  .ambient__orb  { animation: none; }
  [data-par], [data-par-x] { transform: none !important; }

  /* Hero: no sky-zoom track — collapse to a single static viewport */
  .hero { height: 100vh; height: 100svh; }
  .hero__bg { transform: none !important; filter: none !important; }
  .hero__content { transform: none !important; opacity: 1 !important; }
  .hero__atmos { opacity: 0 !important; }

  /* Aircraft: collapse the scroll runway; show a single static frame.
     The caption STAYS absolutely positioned. `position: static` dropped it into
     the flow of .aircraft__viz — whose only other children are absolute — so it
     rode up to the top of the stage and sat under the fixed nav. It just needs
     its motion removed, not its anchor. The intro never fades here: js/heli.js
     skips the .is-faded toggle under reduce, so the intro holds. */
  .aircraft { height: 100vh; height: 100svh; }
  .aircraft__caption {
    opacity: 1;
    transform: translateX(-50%);
    transition: none;
  }

  /* Adventures unroll: show immediately, no wipe/cascade/split */
  .js .adventures .container,
  .js .adventures__grid > .adv-card {
    clip-path: none;
    opacity: 1;
    transform: none;
    transition: none;
  }
  .adv-intro__node {
    transition: none;
    opacity: 1;
    transform: scale(1);
  }
  .adv-intro__bar {
    transition: none;
    transform: translateX(-50%) scaleX(1);
  }}

/* ============================================================
   EXPERIENCE SUB-PAGES  +  card→page View Transition
   The "big" adventures open as full immersive pages. The card's
   hero image morphs into the page hero via the cross-document
   View Transitions API (degrades to a normal nav where unsupported).
   ============================================================ */

/* Opt this document (and the experience pages) into MPA transitions. */
@view-transition { navigation: auto; }

/* Shared-element morph: every experience-page hero carries ONE name. The
   source element — the full-screen journey photo that's parked on screen
   when you tap "Learn More" — is tagged with the same name in JS only while
   a stop is active, so the photo expands seamlessly into the page hero.
   (Only one element ever holds the name per page, so there's no clash;
   cards elsewhere just cross-fade.) */
.exp-hero__bg { view-transition-name: exp-hero; }

::view-transition-group(exp-hero) { animation-duration: 0.55s; }
@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }}

/* ---- Experience page ---- */
.exp { background: var(--hero-cream); }

/* Hero */
.exp-hero {
  position: relative;
  min-height: 92vh;
  min-height: 92svh;
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  isolation: isolate;
}
.exp-hero__bg {
  position: absolute;
  inset: 0;
  background-size: cover;
  background-position: center;
  z-index: -2;
}
/* THE ONE DELIBERATELY DARK THING ON THESE PAGES. The exp hero is a full-bleed
   photograph and the title sits on it — there is no light-page way to put type
   over an arbitrary photo, and the reference does exactly this with its own
   photography. What changed is the COLOUR of the scrim: warm ink, not forest.
   The green wash was the tell; the darkness never was.
   The top stop also has to stay dark enough to hold the frosted nav bar's
   hairline, since the sub-pages carry .nav--scrolled from the first frame. */
.exp-hero__scrim {
  position: absolute;
  inset: 0;
  z-index: -1;
  background:
    linear-gradient(180deg, rgba(28,24,20,0.55) 0%, rgba(28,24,20,0) 32%),
    linear-gradient(0deg, rgba(28,24,20,0.92) 4%, rgba(28,24,20,0.35) 42%, rgba(28,24,20,0) 70%);
}
.exp-hero__inner {
  width: 100%;
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--page-padding) clamp(2.5rem, 6vw, 4.5rem);
  color: var(--cream);
}
.exp-hero .eyebrow { color: var(--amber-light); }
.exp-hero__title {
  font-size: clamp(2.75rem, 8vw, 5.5rem);
  color: var(--cream);
  margin: 0.25rem 0 0.5rem;
}
.exp-hero__tagline {
  font-size: clamp(1.0625rem, 2.4vw, 1.4rem);
  font-weight: 300;
  max-width: 36ch;
  color: rgba(245, 240, 232, 0.92);
}

/* Back link (top-left, over the hero) */
.exp-back {
  position: absolute;
  top: calc(var(--nav-height) + 1rem);
  left: var(--page-padding);
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 0.45rem;
  font-size: 0.85rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--white);
  padding: 0.5rem 1rem 0.5rem 0.85rem;
  border-radius: 3rem;
  background: rgba(28, 24, 20, 0.55);   /* stays dark: it is ON the photo */
  backdrop-filter: blur(6px);
  -webkit-backdrop-filter: blur(6px);
  transition: background var(--transition);
}
.exp-back:hover { background: rgba(28, 24, 20, 0.8); }
.exp-back span[aria-hidden] { transition: transform var(--transition); }
.exp-back:hover span[aria-hidden] { transform: translateX(-3px); }

/* Body sections */
.exp-section { padding: clamp(3.5rem, 8vw, 6rem) 0; }
.exp-section--tight { padding: clamp(2rem, 5vw, 3rem) 0; }
.exp-lede {
  font-family: var(--font-serif);
  font-size: clamp(1.5rem, 3.4vw, 2.25rem);
  line-height: 1.4;
  color: var(--ink-warm);
  max-width: 22ch;
}
.exp-lede em { color: var(--amber); }

.exp-grid {
  display: grid;
  gap: clamp(2rem, 5vw, 4rem);
  grid-template-columns: 1fr;
  align-items: start;
}
@media (min-width: 860px) {
  .exp-grid { grid-template-columns: 1.4fr 1fr; }}
/* #34432f is a green-grey. On cream it is the exact "grey body copy on a warm
   ground" failure the repaint exists to avoid (and it measured 6.8:1 only by
   luck). Warm ink at 78% — 6.6:1, and it belongs to the page. */
.exp-prose p {
  font-size: 1.0625rem;
  line-height: 1.8;
  color: rgba(43, 38, 33, 0.78);
  margin-bottom: 1.25rem;
  max-width: 60ch;
}
.exp-prose p:last-child { margin-bottom: 0; }

/* "Included" card — a SAND card, not a forest one. It still reads as a distinct
   panel beside the prose (sand against cream is a real step), and it keeps the
   page's light-and-shade without dropping a green slab into the middle of it. */
.exp-included {
  background: var(--hero-sand);
  color: var(--ink-warm);
  border: 1px solid var(--rule-warm);
  border-radius: var(--radius-lg);
  padding: clamp(1.75rem, 4vw, 2.5rem);
}
.exp-included h3 {
  color: var(--ink-warm);
  font-size: 1.35rem;
  margin-bottom: 1.25rem;
}
.exp-included ul { display: grid; gap: 0.85rem; }
.exp-included li {
  position: relative;
  padding-left: 1.75rem;
  font-size: 0.975rem;
  line-height: 1.5;
  color: rgba(43, 38, 33, 0.8);   /* 6.1:1 on sand */
}
.exp-included li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.45em;
  width: 0.55rem;
  height: 0.55rem;
  background: var(--amber);   /* --amber-light on sand is a gold on a gold */
  transform: rotate(45deg);
}

/* How-it-works steps */
.exp-steps {
  display: grid;
  gap: clamp(1.5rem, 4vw, 2.5rem);
  grid-template-columns: 1fr;
  counter-reset: step;
}
@media (min-width: 720px) {
  .exp-steps { grid-template-columns: repeat(4, 1fr); }}
.exp-step { counter-increment: step; }
.exp-step__num {
  font-family: var(--font-serif);
  font-size: 2.5rem;
  color: var(--amber);
  line-height: 1;
}
.exp-step__num::before { content: counter(step, decimal-leading-zero); }
.exp-step h4 {
  font-family: var(--font-serif);
  font-size: 1.2rem;
  margin: 0.6rem 0 0.4rem;
  color: var(--ink-warm);
}
.exp-step p { font-size: 0.95rem; line-height: 1.6; color: rgba(43, 38, 33, 0.75); }

/* Specs strip. The 1px grid gap IS the rule between cells: sand hairlines, haze
   cells — the same figure as #spec's row rules, at panel scale. */
.exp-specs {
  display: grid;
  gap: 1px;
  grid-template-columns: repeat(2, 1fr);
  background: var(--hero-sand);
  border: 1px solid var(--hero-sand);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
@media (min-width: 720px) { .exp-specs { grid-template-columns: repeat(4, 1fr); }}
.exp-spec { background: var(--hero-haze); padding: 1.5rem; text-align: center; }
/* A spec KEY is a label, not an eyebrow: "Duration" is the only thing that says
   what the number under it means. Amber at 11px is 2.8:1 here. Ink at 70%. */
.exp-spec__k {
  display: block;
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: rgba(43, 38, 33, 0.7);
  font-weight: 600;
  margin-bottom: 0.4rem;
}
.exp-spec__v { font-family: var(--font-serif); font-size: 1.2rem; color: var(--ink-warm); }

/* Gallery */
.exp-gallery {
  display: grid;
  gap: 0.75rem;
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 720px) { .exp-gallery { grid-template-columns: repeat(3, 1fr); }}
.exp-gallery__item {
  aspect-ratio: 4 / 3;
  background-size: cover;
  background-position: center;
  border-radius: var(--radius);
}
.exp-gallery__item--tall { grid-row: span 2; aspect-ratio: 3 / 5; }

/* CTA band. Haze — the brightest stop — so the page steps cream → white → sand
   (footer) on the way out and the closing ask is the lightest thing on it. */
.exp-cta {
  background: var(--hero-haze);
  color: var(--ink-warm);
  text-align: center;
  padding: clamp(3.5rem, 8vw, 6rem) var(--page-padding);
  border-top: 1px solid var(--rule-warm);
}
.exp-cta h2 { color: var(--ink-warm); margin-bottom: 0.75rem; }
.exp-cta p { color: rgba(43, 38, 33, 0.75); max-width: 46ch; margin: 0 auto 2rem; line-height: 1.7; }
.exp-cta .eyebrow { color: var(--amber); }

/* ============================================================
   PILOT — two-button action row
   ============================================================ */
.pilot__actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
}

/* ============================================================
   MORE WAYS TO FLY — private charter + corporate culture
   ============================================================ */
.more {
  background: var(--hero-cream);
  padding: 1rem 0 4.5rem;
  position: relative;
  z-index: 1;
}
.more .container { margin-bottom: 2.5rem; }

.more__grid {
  max-width: var(--max-width);
  margin: 0 auto;
  padding: 0 var(--page-padding);
  display: grid;
  grid-template-columns: 1fr;
  gap: 1.25rem;
}
@media (min-width: 768px) {
  .more__grid { grid-template-columns: 1fr 1fr; gap: 1.5rem; }}

.more__card {
  position: relative;
  min-height: 340px;
  border-radius: 1.25rem;
  overflow: hidden;
  display: flex;
  align-items: flex-end;
  isolation: isolate;
  box-shadow: 0 18px 40px -24px rgba(13, 26, 13, 0.55);
}
.more__card-bg {
  position: absolute;
  inset: 0;
  z-index: -2;
  background-size: cover;
  background-position: center;
  transition: transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
}
/* Warm-ink scrim so the copy stays legible over any photo (was forest) */
.more__card::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background: linear-gradient(
    to top,
    rgba(28, 24, 20, 0.92) 0%,
    rgba(28, 24, 20, 0.55) 42%,
    rgba(28, 24, 20, 0.10) 100%);
}
.more__card:hover .more__card-bg { transform: scale(1.06); }

.more__card-body { padding: 1.875rem 2rem 2rem; color: var(--cream); }
.more__card-tag {
  display: inline-block;
  font-family: var(--font-sans);
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--amber-light);
  margin-bottom: 0.75rem;
}
.more__card-title {
  font-family: var(--font-serif);
  font-size: clamp(1.35rem, 3vw, 1.75rem);
  font-weight: 700;
  color: var(--white);
  line-height: 1.2;
  margin-bottom: 0.6rem;
}
.more__card-copy {
  font-size: 0.95rem;
  line-height: 1.65;
  color: rgba(245, 240, 232, 0.82);
  max-width: 42ch;
}

/* ============================================================
   PRELOADER PLATE
   Types "Aviatrix Charters", then the empty flex spacer between the
   two words inflates and SHOVES them to opposite edges, where a FLIP
   transform lands them exactly on the hero's permanent edge rails.
   Then a soft gradient curtain wipes down and off, REVEALING the hero
   rather than erasing the loader.

   Gated on BOTH .js and .is-preloading, so:
     - JS off              → the plate never renders at all
     - preloader.js broken → the inline head script drops .is-preloading
                             at 6.5s and the plate disappears
   The plate can never trap the user. See js/preloader.js.
   ============================================================ */
.pre {
  display: none;
}

/* THE PLATE ROOT IS TRANSPARENT. Do not "tidy" a background back onto it.
   The forest field lives on .pre__tone, and .pre__tone is the ONLY thing the
   curtain wipes — if the root is opaque too, the mask uncovers an identically
   opaque parent, the hero is never revealed, and the whole handoff reads as
   "the words vanish and the screen sits blank for a second". */
html.js.is-preloading .pre {
  display: grid;
  position: fixed;
  inset: 0;
  z-index: var(--z-preloader);
  place-items: center;
  background: transparent;
  overflow: hidden;
}

/* Scroll is locked for the plate's lifetime: a user who scrolled during load
   would make the FLIP land into a mid-zoom hero. (Paired with
   scrollbar-gutter: stable on html — see the note up there.) */
html.is-preloading {
  overflow: hidden;
}

/* ---- The curtain ---- */
@property --mask-y {
  syntax: '<percentage>';
  inherits: false;
  initial-value: 0%;
}

.pre__tone {
  position: absolute;
  inset: 0;
  background: var(--forest-dark);
  -webkit-mask-image: linear-gradient(180deg, #000 0 42%, transparent 78%);
          mask-image: linear-gradient(180deg, #000 0 42%, transparent 78%);
  -webkit-mask-size: 100% 200%;
          mask-size: 100% 200%;
  /* NO-REPEAT IS LOAD-BEARING. The wipe drives the mask 200% off the top of the
     box; with the default `repeat` the next tile of the gradient wraps straight
     back in behind it and the forest field REAPPEARS over the hero a beat after
     it cleared. (You can only see this once .pre itself is transparent — which
     is why it hid behind the old opaque plate.) */
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: 0 var(--mask-y, 0%);
          mask-position: 0 var(--mask-y, 0%);
  transition: --mask-y 0.76s var(--ease-expo);
}

.pre.is-clearing .pre__tone {
  --mask-y: 200%;
  will-change: mask-position; /* only for the 1s of the wipe — the node is removed after */
}

/* FALLBACK: no @property support. JS feature-detects CSS.registerProperty and
   adds .pre--nomask. A plain fade. Nobody notices. */
.pre--nomask .pre__tone {
  transition: opacity 0.6s ease;
}
.pre--nomask.is-clearing .pre__tone {
  opacity: 0;
}

/* ---- The lockup ----
   font-family / font-weight / line-height / text-transform MUST stay in sync
   with .hw__word below: the FLIP scale is a pure font-size ratio and assumes
   identical metrics. If the words jump at the handoff, look here first. */
.pre__lockup {
  position: relative;
  display: flex;
  /* CENTRE, not baseline. align-items:baseline lines the two words up with each
     other but pins the row to the TOP of the cross axis, so the whole name
     typed itself against the top edge of the screen. The two words are the same
     font-size and the same line-height, so centring them aligns their baselines
     anyway — there is nothing baseline alignment was buying here. */
  align-items: center;
  justify-content: center;
  width: 100%;
  padding: 0 var(--page-padding);
  font-family: var(--font-serif);
  font-weight: 300;                 /* Northlake ships 300/500/700 only */
  font-size: clamp(1.75rem, 5.2vw, 4.5rem);
  line-height: 0.9;
  color: var(--white);
  text-shadow: 0 2px 32px rgba(0, 0, 0, 0.4);
  text-transform: uppercase;
}

.pre__word {
  flex: 0 0 auto;
  white-space: nowrap;
  letter-spacing: 0;
  /* Scaling from the OUTER corner is what makes the FLIP exact: word A maps its
     left edge onto the left rail, word B its right edge onto the right rail. */
  transform-origin: left top;
  transform: translate3d(var(--dx, 0px), var(--dy, 0px), 0) scale(var(--s, 1));
}

.pre__word[data-word='b'] {
  transform-origin: right top;
}

/* ★ THE PUSH ★ — an empty flex item. flex-basis is animatable; justify-content
   is not. That is the entire reason the spacer exists. At rest it is the typed
   space between the two words. Inflated to 100% it shoves them to the edges.

   flex-SHRINK must be 1 (see the same note on .spec__push): at basis 100% the
   spacer is already as wide as the row, and with shrink 0 the lockup would
   overflow both edges instead of parking the words against them — which would
   also make the FLIP measure a pair of off-screen boxes. */
.pre__push {
  flex: 0 1 0.32em;
}

/* Typewriter: REUSES .char / .is-typed / .hero__caret / caretBlink from the
   hero block above rather than inventing parallel names. Characters hide with
   opacity, never display, so the line never reflows as it types and the caret
   lands exactly where the next glyph will appear. */
.pre__word .char {
  opacity: 0;
  white-space: pre;
}

.pre__word .char.is-typed {
  opacity: 1;
}

.pre__lockup .hero__caret {
  transition: opacity 0.18s linear;
}

.pre.is-typed-out .hero__caret {
  opacity: 0;
}

/* MEASURE STATE — one frame, transitions suppressed. Applies the FINAL layout
   (push inflated, tracking open) so getBoundingClientRect() reads the box each
   word will actually animate INTO; the transform then only carries the residual
   delta to the hero rail. Measuring the pre-split box instead would make push
   and transform BOTH do the horizontal travel, and the words would fly clean
   off the edges of the screen. */
.pre--measure .pre__push,
.pre--measure .pre__word {
  transition: none !important;
}
.pre--measure .pre__push { flex-basis: 100%; }
/* MUST equal the hero word's resting tracking (.hw__word letter-spacing: 0.02em).
   The FLIP scales the plate word onto the hero word by font-size ratio ALONE, so
   any tracking difference survives the scale and the plate letters land a few px
   off the hero letters — the "title doesn't line up when it finishes" bug. This
   is the tracking the pre word is MEASURED at; the split value below must match. */
.pre--measure .pre__word { letter-spacing: 0.02em; }

/* ★ THE SPLIT ★ — push, tracking and the FLIP transform on one curve, one
   duration, so they read as a single hydraulic movement. */
.pre--split .pre__push {
  flex-basis: 100%;
  transition: flex-basis 0.72s var(--ease-servo);
}

.pre--split .pre__word {
  letter-spacing: 0.02em;   /* == .hw__word resting tracking, so the split word
                               lands exactly on the hero word (see .pre--measure) */
  transition: transform 0.72s var(--ease-servo),
              letter-spacing 0.72s var(--ease-servo);
}

/* Handoff: the real hero words switch on, the plate's copies switch off. They
   are pixel-coincident by construction, so the swap is invisible.

   THE DELAY IS LOAD-BEARING. The curtain reveals from the TOP DOWN and its
   soft edge means the plate is still effectively opaque for the first ~third of
   the wipe. Kill the plate's words on the same frame the curtain starts and you
   get exactly what shipped in the first cut: no words anywhere, on a blank
   field, for a third of a second. So the copies HOLD through the front of the
   wipe and only then cross-dissolve — onto the hero's own words, which by then
   are showing through the cleared band directly underneath them. */
.pre.is-handed .pre__lockup {
  opacity: 0;
  transition: opacity 0.4s linear 0.32s;
}

/* REDUCED MOTION — no typing, no flight. The finished lockup, a beat, a fade.
   The plate stays (deleting it exposes a flash of the mid-buffer hero video,
   which is worse than a loading screen). */
@media (prefers-reduced-motion: reduce) {
  .pre__word .char { opacity: 1; }
  .pre__lockup .hero__caret { display: none; }
  .pre__push { flex-basis: 0.32em; }
  .pre,
  .pre__tone,
  .pre__word,
  .pre__push { transition: none !important; }
  /* The whole plate — words and all — cross-dissolves onto the hero, whose own
     words are already landed underneath. Blanking the lockup first would leave
     a beat of empty forest, which is the bug this fade exists to avoid. */
  .pre.is-handed .pre__lockup { opacity: 1; transition: none !important; }
  .pre.is-clearing { opacity: 0; transition: opacity 0.3s ease !important; }}

/* ============================================================
   HERO EDGE RAILS
   AVIATRIX high-left, CHARTERS low-right. As the sky rushes at you
   they scale up and open to opposite edges — you fly BETWEEN the two
   halves of the company's own name.

   Zero new scroll listeners: updateHeroZoom (main.js) already computes
   hero progress and now publishes it as --hp on #hero. Everything
   below is CSS calc(). All values are LINEAR on purpose — this is
   scrubbed, and easing a scrub fights the scrollbar.
   ============================================================ */
/* Lives in .hero__sticky, NOT .hero__content — main.js fades .hero__content to
   opacity 0 as you push into the sky, and the words have to outlive it. */
.hero__wordmark {
  position: absolute;
  inset: 0;
  z-index: 3;                    /* above .hero__craft — the name is the plane the
                                    machine flies THROUGH */
  margin: 0;
  padding: 0 var(--page-padding);
  pointer-events: none;
  display: grid;
  /* THE CHANNEL. Three columns: word, aircraft, word. The middle column is dead
     space on purpose — it is the corridor the helicopter climbs, and it is what
     makes the two halves of the name read as one lockup split apart rather than
     as two headlines that happen to share a row.
     minmax() keeps the channel from collapsing at 1024px and from swallowing the
     words at 2560px.
     The outer columns are minmax(0, 1fr), NOT bare 1fr: at large sizes each word
     is WIDER than its 1fr share, and a bare 1fr (= minmax(auto,1fr)) expands the
     track to the word's min-content and blows the grid out to the right —
     CHARTERS then jams the right edge while AVIATRIX keeps its left margin, and
     the whole name reads shoved right. minmax(0,1fr) pins the flex size so the
     words overflow INWARD into the channel instead, landing symmetric edges. */
  grid-template-columns: minmax(0, 1fr) minmax(240px, 30vw) minmax(0, 1fr);
  /* ONE SHARED BASELINE. Both words sit in row 2; align-items: baseline is what
     makes AVIATRIX and CHARTERS agree, even though they are different lengths at
     different optical sizes. Row 3 holds the left blurb only. */
  grid-template-rows: 1fr auto auto 1.35fr;
  align-items: baseline;
}

[data-rail='a'].hw__word  { grid-area: 2 / 1; justify-self: start; text-align: left; }
[data-rail='b'].hw__word  { grid-area: 2 / 3; justify-self: end;   text-align: right; }
[data-rail='a'].hw__blurb { grid-area: 3 / 1; text-align: left; }

/* Northlake ships 300/500/700 and no italic — stay on those weights or the
   browser starts faux-bolding. line-height MUST match .pre__lockup. */
.hw__word {
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: clamp(2.5rem, 7.6vw, 7rem);
  line-height: 0.9;
  text-transform: uppercase;
  white-space: nowrap;
  color: var(--ink-warm);
  /* CREAM HALO. A soft, ground-coloured glow hugs each letter. On the bare page
     it is invisible (cream on cream); the instant the helicopter scrolls in
     behind the words, the halo reads as a clean cream ring that lifts the dark
     letters off the dark machine so the name stays legible on top of it. This is
     a backlight, not a drop-shadow — it matches the ground rather than faking one. */
  text-shadow:
    0 0 2px  var(--hero-cream),
    0 0 5px  var(--hero-cream),
    0 0 10px var(--hero-cream),
    0 0 17px var(--hero-cream);
  letter-spacing: calc(0.02em + var(--hp, 0) * 0.05em);
  opacity: calc(1 - clamp(0, (var(--hp, 0) - 0.55) * 2.5, 1));
}

/* will-change ONLY while the plate is up — the FLIP handoff is the one moment
   these two nodes have to be perfectly smooth, and the class goes away with the
   loader. A permanent will-change on two large text nodes is a compositor layer
   held for the life of the page for the sake of a drift that only runs while
   #hero is on screen. (The translate3d below is already composited.) */
html.is-preloading .hw__word {
  will-change: transform;
}

/* 0.22em of tracking on a 12px sans line is what turns copy into
   INSTRUMENTATION rather than body text. It is the label voice of the whole
   hardware layer, and it recurs verbatim on the #spec <dt>s. */
.hw__blurb {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 0.75rem;
  line-height: 1.6;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--amber);            /* the darker amber — --amber-light is a gold
                                     for dark grounds and goes to mush on cream */
  max-width: 24rem;
  /* The tagline runs to two lines at this width, and the natural break lands
     badly ("...AND PRIVATE / CHARTERS"). Balance evens the two lines instead.
     Progressive enhancement: engines without it just get the default wrap. */
  text-wrap: balance;
  margin-top: 1.35rem;
  opacity: calc(1 - clamp(0, (var(--hp, 0) - 0.30) * 2.2, 1));
}

/* THE DRIFT. The words already START at the edges, so they cannot open 52vw the
   way they did when the lockup sat in the middle of a dark sky — they would just
   leave. They ease OUT a further 9vw and grow, which widens the channel as the
   machine climbs it: the name gets out of the aircraft's way. The blurb trails
   at 5vw — the parallax between the two is the depth cue. Blurb retires first
   (gone by hp ≈ 0.75), the words by hp ≈ 0.95. */
[data-rail='a'].hw__word {
  transform: translate3d(calc(var(--hp, 0) * -9vw), calc(var(--hp, 0) * -2vh), 0)
             scale(calc(1 + var(--hp, 0) * 0.35));
}
[data-rail='b'].hw__word {
  transform: translate3d(calc(var(--hp, 0) *  9vw), calc(var(--hp, 0) * -2vh), 0)
             scale(calc(1 + var(--hp, 0) * 0.35));
}
[data-rail='a'].hw__blurb {
  transform: translate3d(calc(var(--hp, 0) * -5vw), 0, 0);
}

/* The words are held hidden while loading UNTIL the module reveals them for
   typing (is-typing on the wordmark). Keyed on .is-preloading — a class only JS
   adds, with a head failsafe — so no path can leave the hero hiding its own name
   (failsafe drops .is-preloading → this rule stops applying → words at rest).
   The tagline is handled separately (it blooms in on .is-spreading). */
html.is-preloading .hero__wordmark:not(.is-typing) .hw__word {
  opacity: 0;
}

/* BLURB BLOOM — on the INNER span, never the outer one.
   The outer .hw__blurb owns three channels the bloom must not touch: grid
   placement, the scroll-drift transform, and the --hp opacity calc. The bloom
   lives on .hw__blurb-i and multiplies with all of them.

   The separation is also what makes it safe. A keyframe with a `backwards` fill
   HOLDS its from-state until it runs — so if the animation never starts (a
   backgrounded tab, an odd engine, a script that died before adding
   .is-landed), a bloom bolted onto the outer span would hide the copy
   permanently. Here the animation only ever applies under .is-landed, and it
   ends at opacity 1 (`both`). If it never runs, the inner span is simply
   visible. Decoration must never be able to hide content — same principle as
   the .is-typing note in main.js. */
.hw__blurb-i {
  display: inline-block;
}

/* Longhands, not the shorthand: `animation:` would reset animation-delay to 0,
   and it out-specifies the two stagger rules below, so the stagger would be
   silently dropped. */
.hero__wordmark.is-landed .hw__blurb-i {
  animation-name: hwBlurbBloom;
  animation-duration: 0.7s;
  animation-timing-function: var(--ease-expo);
  animation-fill-mode: both;
}
[data-rail='a'] .hw__blurb-i { animation-delay: 0.2s; }
[data-rail='b'] .hw__blurb-i { animation-delay: 0.29s; }

@keyframes hwBlurbBloom {
  from { opacity: 0; filter: blur(6px); }
  to   { opacity: 1; filter: blur(0); }}

/* MOBILE — the words stack and centre; no drift. (Note the [data-rail]
   attribute selector: it has to out-specify the drift rules above, which are
   also attribute-qualified.) */
@media (max-width: 767px) {
  .hero__wordmark {
    grid-template-columns: 1fr;
    grid-template-rows: 16vh auto auto auto auto 1fr;
    text-align: center;
  }
  [data-rail='a'].hw__word  { grid-area: 2 / 1; justify-self: center; text-align: center; }
  [data-rail='a'].hw__blurb { grid-area: 3 / 1; text-align: center; margin: 0.75rem auto 0; }
  [data-rail='b'].hw__word  { grid-area: 4 / 1; justify-self: center; text-align: center; }
  [data-rail='b'].hw__blurb { grid-area: 5 / 1; text-align: center; margin: 0.75rem auto 0; }

  .hw__blurb { font-size: 0.625rem; letter-spacing: 0.18em; }

  [data-rail].hw__word,
  [data-rail].hw__blurb { transform: none; }}

/* REDUCED MOTION — updateHeroZoom early-returns, so --hp is never written and
   the words simply sit at their grid positions at rest. Belt and braces. */
@media (prefers-reduced-motion: reduce) {
  .hw__word,
  .hw__blurb,
  .hw__blurb-i {
    transform: none !important;
    opacity: 1 !important;
    filter: none !important;
    animation: none !important;
  }
  .hw__word { letter-spacing: 0.04em; }}

/* ============================================================
   #spec — THE LIGHT ROOM
   The site's one ivory section. A 440vh runway; .spec__stage pins for
   the duration. Nine spec rows arrive one at a time on the left while,
   in the centre, a rotor blade sweeps one full turn and everywhere it
   passes the photoreal airframe becomes a line drawing.

   THE RULE (see the same comment in js/aircraftspec.js):
   SCRUB THE ROOM, NEVER THE READING.
     Scrubbed 1:1 with the scrollbar — --tone, --sweep, --cut, the disc
     drift, the ring rotation. Anything that is a ROOM.
     Threshold class-flips, each playing its own eased CSS transition —
     the rows, the split title, the head, the caption, the pips, the
     sales line. Anything a human READS.
   Scrubbed text strobes on a fast trackpad flick and feels like dragging
   a slider. Threshold text feels like a page turning.
   ============================================================ */

/* No bar, no edge — the fly bar is a fully floating button. (The old hairline
   border-top that lived here was the last remnant of a strip; removed so the
   bottom is just a floating pill, matching the transparent nav on top.) */

/* The nav sits over cream for 440vh, so the LINKS need to be dark to read — but
   NO bar behind them (client request): the options float on the page background,
   same as the transparent nav elsewhere. Toggled by an IntersectionObserver in
   js/aircraftspec.js. */
.nav--light {
  background: transparent;
}
.nav--light .nav__logo,
.nav--light .nav__links a,
.nav--light .nav__cta {
  color: var(--ink);
  border-color: rgba(13, 26, 13, 0.35);
}
.nav--light .nav__hamburger span { background: var(--ink); }
.nav--light .nav__links a:hover,
.nav--light .nav__links a:focus-visible { color: var(--amber); }


/* ---- The runway ---- */
.spec {
  position: relative;
  /* Taller than it was (440vh): this section is now the WHOLE opening sequence —
     the title beat, the helicopter flying in and landing, the title scrolling
     out, the spec sheet arriving, and the x-ray. Each beat needs its own scroll.
     The helicopter lives in the sticky stage, so it stays LOCKED dead-centre the
     entire time while all of that moves around it. */
  height: 640vh;
  /* THE ONLY LINE TOUCHED IN #spec, AND IT HAD TO BE. --tone runs 0 → 1 across
     the first 10% of the runway (≈44vh of scroll) and this is what shows
     UNDERNEATH it at --tone 0. The stops were written when #aircraft — a dark
     WebGL dusk — was the section immediately above; the comment still said so.
     #spec's upstairs neighbour is now #hero, which ends on the sand floor, and
     #aircraft is light. Left as forest, the first 44vh of #spec was a
     full-viewport BLACK frame, one scroll-tick after a cream hero. Screenshot
     s-spec-top proved it. Sand → cream now: it enters on the exact colour the
     hero exits on, and the tone crossfade to ivory is invisible. */
  background: linear-gradient(180deg, var(--hero-sand) 0%, var(--hero-cream) 100%);
  /* CLIP THE DECORATION, NOT THE PAGE. .disc__ring is the amber rotor-disc glow:
     absolutely positioned, blurred, pointer-events:none, and sized off .disc —
     which is deliberately larger than a phone viewport, so on a 390px screen the
     ring reached x=446 and gave the whole document a 451px scrollWidth and a
     horizontal scrollbar. Nothing else was at fault; every other over-wide box
     (.ambient, the nav) was just a fixed element sized to an already-overflowed
     document.
     `clip`, NOT `hidden`: .spec__stage inside this section is position:sticky,
     and `hidden` would make this element a scroll container and take the pin with
     it. `clip` clips without becoming one — verified the stage still pins at
     top:0 mid-section. Clipping at the section's box is the same as clipping at
     the viewport here, so nothing visible is lost at any width. */
  overflow-x: clip;
  --sp: 0;         /* section progress 0..1, written by JS */
  --tone: 0;       /* dark → ivory */
  --sweep: 0turn;  /* rotor blade angle */
  --cut: 0;        /* 0 = photoreal, 1 = line drawing */
  --ring: 0deg;
}

/* ★★ THE ARM — READ THIS BEFORE TOUCHING ANY SELECTOR BELOW ★★

   EVERY hidden state, every pin, every mask in this section is gated on
   `.spec.is-armed`, and the ONLY thing in the codebase that adds that class is
   js/aircraftspec.js, on the one path where it is definitely about to start
   driving --sp. Nothing here is gated on `.js`.

   That is not a style preference, it is the whole safety contract. The first
   cut gated the hidden states on `.js`, and the day the module 404'd, threw, or
   simply never re-evaluated its breakpoint after a resize, #spec was 3,960px of
   blank ivory: nine spec rows sitting at opacity 0 with nobody left to reveal
   them. A decoration must never be able to hold content at zero.

   So the DEFAULT state of this section — no JS, dead module, reduced motion,
   any phone — is the plain thing: a static, fully legible spec sheet with both
   drawings stacked and captioned. The choreography is what you opt IN to. */
.spec:not(.is-armed) {
  height: auto;
  background: var(--cream);
}
.spec:not(.is-armed) .spec__tone { opacity: 1; }

.spec__tone {
  position: absolute;
  inset: 0;
  background: var(--cream);
  opacity: var(--tone, 0);
  pointer-events: none;
}

/* ---- The pinned stage (armed) ----
   The two pieces of fixed chrome on this site are the nav (--nav-height) and
   the fly bar (--flybar-h, republished live by main.js). The pin is 100svh, so
   anything the stage puts in those bands is UNDER them: at 1440x813 the eyebrow
   landed behind the nav and the footnote behind the "Fly Now" bar, descenders
   and all. Consume both variables — do not go back to bare vh padding.
   #aircraft already clears the bar the same way (.aircraft__caption). */
.spec__stage {
  position: sticky;
  top: 0;
  z-index: 1;
  height: 100vh;
  height: 100svh;
  /* Deliberately NOT 620px like .aircraft__viz: a min-height taller than the
     viewport pushes the footnote below the fold on a 1280x620 laptop, and the
     ninth row with it. The stage must never be taller than the pin. */
  min-height: 540px;
  display: grid;
  /* Four rows: the split rails, then the heading, then the body, then the
     footnote. The rails and the <h2> used to SHARE row 1, which put "FLY", "The
     A-Star, measured." and "LOW." in one 110px strip reading as three unrelated
     headings. They are separate bands now. */
  /* THE REFERENCE LAYOUT. Three columns: the aircraft's IDENTITY down the left
     (maker + model up top, the numbers settled at the bottom), the MACHINE in
     the middle running full height, and the BRAND's case on the right. The left
     column is the aircraft talking about itself; the right column is us talking
     about it; the centre is the thing itself. */
  grid-template-columns: minmax(300px, 32%) minmax(0, 1fr) minmax(220px, 25%);
  grid-template-rows: auto 1fr auto auto;
  align-items: start;
  /* Column gap can be generous; ROW gap is the scarce resource — nine rows plus
     a head plus a footnote have to clear a 100svh pin on a 1440x813 laptop. */
  column-gap: clamp(1.25rem, 3vw, 3rem);
  row-gap: clamp(0.5rem, 1.6vh, 1.25rem);
  padding: calc(var(--nav-height) + 2.5vh) var(--page-padding)
           calc(var(--flybar-h, 5rem) + 2vh);
  max-width: calc(var(--max-width) + 8vw);
  margin-inline: auto;
  /* EXIT: the ivory drains back to forest BEFORE the sticky stage unpins into
     #book. Without this the section un-sticks at full brightness and the seam
     is visible. */
  transform: translateY(calc(clamp(0, (var(--sp) - 0.88) * 8.33, 1) * -6vh))
             scale(calc(1 - clamp(0, (var(--sp) - 0.88) * 8.33, 1) * 0.02));
  opacity: calc(1 - clamp(0, (var(--sp) - 0.93) * 14, 1));
}

/* ---- The unarmed stage: one honest column. No pin, no overlay, no masks.
   A flex column rather than the grid, so none of the grid-column / grid-row
   placements below can leak into it and stack two things on top of each other
   (which is exactly what happened on mobile in the first cut). ---- */
.spec:not(.is-armed) .spec__stage {
  position: static;
  height: auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: clamp(1.75rem, 4vw, 2.75rem);
  max-width: 820px;
  padding: clamp(4rem, 10vw, 7rem) var(--page-padding);
  transform: none;
  opacity: 1;
}
/* The armed layout centres these in their grid cells (align-self: center). In
   the unarmed flex column that same declaration reads as CROSS-axis centring
   and shrink-to-fits the spec list to its widest row — 223px of a 350px phone,
   with the hairlines stopping short. Stretch them back out. */
.spec:not(.is-armed) .spec__rows,
.spec:not(.is-armed) .spec__note,
.spec:not(.is-armed) .disc        { align-self: stretch; }
.spec:not(.is-armed) .spec__head  { order: 0; }
.spec:not(.is-armed) .spec__title { order: 1; }
.spec:not(.is-armed) .disc        { order: 2; }
.spec:not(.is-armed) .spec__rows  { order: 3; }
.spec:not(.is-armed) .spec__note  { order: 4; }
.spec:not(.is-armed) .spec__foot  { order: 5; }
.spec:not(.is-armed) .spec__push  { flex-basis: 100%; transition: none; }

/* ---- Head: the maker's plate. Maker small and quiet, model enormous. ---- */
.spec__head {
  grid-column: 1;
  grid-row: 1;
  align-self: start;
  text-align: left;
}

.spec__make {
  margin: 0;
  font-family: var(--font-sans);
  font-size: clamp(1.1rem, 1.7vw, 1.5rem);
  font-weight: 500;
  letter-spacing: -0.01em;
  color: var(--ink-warm);
}

/* The model number is the biggest single piece of type in the section — bigger
   than the heading it replaced. It is a serial, not a sentence. */
.spec__model {
  margin: 0.1em 0 0;
  font-family: var(--font-sans);
  font-size: clamp(3.5rem, 7vw, 6.5rem);
  font-weight: 500;
  line-height: 0.92;
  letter-spacing: -0.035em;
  color: var(--ink-warm);
}

/* THE SPEC PANEL SCROLLS UP — masthead, numbers, blurb and footnote all ride the
   one scrubbed --reveal: translate up from below + fade, in step with the scroll
   and the x-ray. NOT a fade-in-place. See .spec.is-armed .spec__panel below. */
.spec.is-armed .spec__head,
.spec.is-armed .spec__rows,
.spec.is-armed .spec__note,
.spec.is-armed .spec__foot {
  transform: translateY(calc((1 - var(--reveal, 0)) * 42vh));
  opacity: var(--reveal, 0);
  will-change: transform, opacity;
}

/* ---- The split, echoed ---- */
.spec__title {
  grid-column: 1 / -1;
  grid-row: 1;
  display: flex;
  /* CENTRE, not baseline. align-items:baseline lines the two words up with each
     other but pins the row to the TOP of the cross axis, so the whole name
     typed itself against the top edge of the screen. The two words are the same
     font-size and the same line-height, so centring them aligns their baselines
     anyway — there is nothing baseline alignment was buying here. */
  align-items: center;
  justify-content: center;
  width: 100%;
  pointer-events: none;
  /* Retire the rails BEFORE the stage's exit translate carries them up into the
     nav — otherwise FLY and LOW. are sliced clean through the middle of the
     glyphs by the nav's bottom edge for a full second of scroll, which reads as
     a rendering bug. Gone by --sp 0.92; the stage starts moving at 0.88. */
  opacity: calc(1 - clamp(0, (var(--sp) - 0.84) * 12.5, 1));
}
/* Jets go OVER the Ozarks; a helicopter goes THROUGH them. */
.spec__title-w {
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: clamp(1.5rem, 3.4vw, 3rem);
  line-height: 0.95;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--ink);
  white-space: nowrap;
  /* Never let the words themselves absorb the shrink — only the push does. */
  flex: 0 0 auto;
}
/* The same empty flex spacer as the preloader lockup. One signature, used
   exactly twice — which is what makes the page read as designed rather than
   assembled. Three times would be a tic.

   flex-SHRINK must be 1. At `flex-basis: 100%` the spacer alone is already as
   wide as the whole row, so with shrink 0 the line is over-constrained and
   `justify-content: center` throws the two words off BOTH edges of the screen.
   Shrink 1 lets the spacer give back exactly the overflow, which parks the
   words hard against the container edges — which is the whole effect. */
.spec__push {
  flex: 0 1 0.4em;
  transition: flex-basis 1s var(--ease-servo);
}
.spec.is-armed.is-title .spec__push { flex-basis: 100%; }

/* per-character blur reveal; the spans are built by aircraftspec.js */
.spec.is-armed .spec__title .char {
  display: inline-block;
  white-space: pre;
  opacity: 0;
  filter: blur(14px);
  transition: opacity 0.7s var(--ease-expo), filter 0.7s var(--ease-expo);
  transition-delay: calc(var(--i, 0) * 24ms);
}
.spec.is-armed.is-title .spec__title .char { opacity: 1; filter: blur(0); }

/* ---- The nine rows ----
   Big quiet serif numeral, small sans unit. That single contrast is most of the
   Gulfstream feel — do not flatten it. */
.spec__rows {
  grid-column: 1;
  grid-row: 3;
  align-self: end;   /* the numbers SETTLE at the foot of the left column, under
                        the masthead — the reference reads top-to-bottom as
                        maker → model → (air) → numbers. */
  /* THE ROWS DO NOT DIM. They used to fade to opacity 0.42 as the schematic
     arrived, which put the value ink at ~2.7:1 on the ivory — under AA — and
     made the section's FINAL resting state its least readable one. You do not
     buy a moment of focus with an unreadable spec sheet; the artwork carries
     the transition on its own. */
}

/* The row rhythm is height-aware on purpose. Nine rows is a lot of vertical
   budget inside a 100svh pin, and a clipped ninth row is a real bug — so the
   padding and the numeral both scale off the viewport height rather than being
   fixed. Verified to fit at 1440x813 and at 1280x620. */
.spec__row {
  position: relative;
  padding: clamp(0.2rem, 0.68vh, 0.45rem) 0;
}
.spec__row::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: var(--rule);
  transform: scaleX(0);
  transform-origin: left;
}
/* The row hairlines are always drawn now — the whole panel slides up as one on
   --reveal, so there is no per-row draw-in to gate. */
.spec__row::before { transform: scaleX(1); }

/* The label voice. NOT --gray: at 11px on ivory that token is 4.1:1, under AA
   for body text. This is the same ink as the value at 68%, which measures ~5:1
   and still reads as a quiet label. */
.spec__row dt {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 0.6875rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(13, 26, 13, 0.68);
  margin-bottom: 0.15rem;
}
.spec__row dd {
  font-family: var(--font-serif);
  font-weight: 300;
  /* Height-aware, and it has to be: nine rows now share the pin with the nav
     band AND the fly bar band. Verified to fit at 1440x900, 1440x813 and
     1280x620 — if you grow this, re-check all three. */
  font-size: clamp(1rem, min(1.65vw, 2.9vh), 1.65rem);
  line-height: 1.1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
  /* One long unit string wrapping to a second line costs a whole row of budget
     and ragged-bottoms the list. Keep every value on one line. */
  white-space: nowrap;
}
/* The unit suffix. Small sans — so the NUMERAL carries the whole line. This one
   detail is 80% of it. */
.spec__u {
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 0.62em;
  letter-spacing: 0.02em;
  color: rgba(13, 26, 13, 0.68);
  margin-left: 0.5em;
}

/* Rows are always visible now — the whole .spec__rows container rides --reveal
   (slides up + fades as one block), so per-row fades would double up. */

/* Small print is a luxury signal, not a liability. */
.spec__foot {
  grid-column: 1;
  grid-row: 4;
  font-family: var(--font-sans);
  font-weight: 300;
  font-size: 0.6875rem;
  letter-spacing: 0.04em;
  color: rgba(13, 26, 13, 0.66);
  text-align: center;
}
/* .spec__foot reveal is handled by the shared --reveal rule above. */

/* ---- THE DISC ----
   Both images occupy the IDENTICAL box with identical object-fit. That is what
   makes the rotor sweep register pixel-for-pixel and read as seeing INTO the
   aircraft rather than swapping two pictures. Non-negotiable — and it is also
   why the payoff zoom lives on .disc (the PARENT) rather than on the schematic:
   scale the schematic alone and the two images come out of register mid-sweep.

   The studio field in both files is crushed to PURE WHITE, not keyed to alpha
   (see the long note in index.html). mix-blend-mode: multiply then does the
   matte for us: white multiplies to nothing, so the field vanishes against the
   cream, the antialiased edges survive, the airframe's own cast shadow
   multiplies in as a real shadow, and there is no key to go ragged.

   STRUCTURAL GOTCHA — do not "clean this up": .disc__plate must stay a CHILD of
   .disc. .disc carries a transform and an opacity, which create a stacking
   context, which isolates blending. Move the cream plate outside the group and
   the images multiply against transparent — i.e. two visible white rectangles.

   NO `contain: paint` HERE. It clipped the figure's own caption (bottom:
   -2.25rem) clean out of existence, took the blade's leading tip off at the
   rotor head, and ate the plate's bleed. The transform + opacity above already
   make the stacking context the blend needs. */
.disc {
  position: relative;
  grid-column: 2;
  grid-row: 1 / span 3;   /* the machine runs the FULL height of the stage —
                             it is the spine the two columns hang off. */
  justify-self: center;
  align-self: center;
  /* DEFAULT = the static stacked spread: photo, caption, schematic, caption. */
  display: flex;
  flex-direction: column;
  width: 100%;
  max-width: 620px;
  margin-inline: auto;
}

/* SIZE THE ARMED DISC FROM ITS HEIGHT, NOT ITS WIDTH.
   Every child is absolute once armed, so the figure has NO intrinsic content
   width; with `width: auto` + `margin-inline: auto` a shrink-to-fit box would
   collapse to 0 and nothing would render at all. Drive the height, let
   aspect-ratio derive the width, let max-width hand control back to the column.

   THE PAYOFF ZOOM: once the sweep has finished, the whole disc grows 14%. The
   schematic is the thing you came for and its seat callouts are the smallest
   type on the page — it has to get BIGGER at the reveal, not stay the size of
   the photograph. Scaling the parent keeps both images in register. */
.spec.is-armed .disc {
  display: block;
  /* CENTRE THE MACHINE ON THE VIEWPORT, not on the middle grid column. The
     stage's columns are asymmetric (32% identity / centre / 25% brand), so the
     middle column's centre sits ~44px right of the stage centre — and the heli,
     justify-self:center INSIDE that column, inherited the lean. Span the full
     track set instead; justify-self:center (from the base rule) then lands it on
     the true centre. Bonus: off the narrow column, max-width:100% no longer
     clips its height-derived width. */
  grid-column: 1 / -1;
  /* THE HEIGHT IS CAPPED BY ITS OWN GRID ROW — `min(…, calc(100% - 2.75rem))`.
     Sized off svh alone it grew TALLER than the row it sits in, and because
     .disc paints after .spec__head and carries an opaque cream plate, the
     overflow sliced the <h2> in half. The 2.75rem is the caption, which hangs
     below the box (bottom: -1.5rem) and must not land under the fly bar. */
  height: min(clamp(280px, 66svh, 800px), calc(100% - 2.75rem));
  aspect-ratio: 1400 / 1747;
  width: auto;
  max-width: 100%;
  /* ★★ THE SETTLE ★★
     The aircraft has ALREADY flown in — it rose up through the hero channel
     (see .hero__craft). By the time #spec pins, it is arriving at its resting
     centre. So this is NOT a second fly-in: it is the last of the same upward
     motion coming to rest. --land runs 0 → 1 with a small overshoot past 1 (the
     settle: at land ≈ 1.04 the craft is a hair ABOVE centre and eases back
     down onto it — a helicopter touching its hover).

     RISE, not descent: it comes up the last 26vh from below, matching the
     direction the hero established. A descent here (dropping from above) would
     reverse the motion the reference is built on. Grows the last few percent as
     it seats. Plus the slow 2.4deg life across the runway — a static PNG is dead.

     NO payoff zoom at the reveal: scaling the disc up pushed the caption through
     the fly bar and the plate through the heading. The schematic earns its
     legibility from bigger seat pips instead. */
  /* --seam-shift (set by js/aircraftspec.js) is HALF the width difference between
     CHARTERS and AVIATRIX. The words are block-centred, so their gap sits that far
     LEFT of the viewport centre; nudging the machine left by the same amount lands
     it dead-centre in the gap between the words instead of on the raw viewport
     centre. js/aircraftspec.js zeroes it under 768px, where the words stack and
     there is no seam. */
  transform:
    translate3d(calc(var(--seam-shift, 0px) * -1), calc((1 - var(--land, 0)) * 74vh), 0)
    scale(calc(0.84 + var(--land, 0) * 0.16))
    rotate(calc(-1.2deg + var(--sp) * 2.4deg));
  /* Fades in as it climbs into frame from below — invisible until it's actually
     flying. Once landed (--land 1) it never moves again: it is a child of the
     sticky stage, so it stays locked dead-centre while the title scrolls out and
     the spec sheet scrolls in around it. */
  opacity: clamp(0, calc(var(--land, 0) * 5), 1);
  z-index: 1;              /* BEHIND the title overlay (.spec__intro z2): the
                              machine lands in the channel while AVIATRIX and
                              CHARTERS stay fully legible in front of it. A wide
                              rotor over the top sliced the words to "AVIATR" /
                              "ARTERS". Words in front reads clean; the machine
                              still lands squarely in the middle of the name. */
}

/* THE PLATE IS GONE. It was a --cream rounded rectangle behind the images, meant
   to give the multiply blend a clean white ground — but --cream differs from the
   page's sand/cream, so it read as a visible box behind the helicopter (the
   "double square"). The images already crush their studio field to pure white,
   and white multiplied onto the page vanishes, so the helicopter sits on the
   page directly with no plate needed. */
.disc__plate { display: none; }

.disc picture { display: contents; }

.disc__img {
  display: block;
  width: 100%;
  height: auto;
  margin-inline: auto;
  max-width: 100%;
  /* The PNGs now carry a real alpha channel — the studio field is keyed out —
     so they drop onto the page with no blend trick. multiply is gone: it was
     only ever there to hide a white field that no longer exists, and on the
     transparent art it would just double-darken the soft cast shadow. */
}
.spec.is-armed .disc__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.spec.is-armed .disc__photo {
  transform: scale(calc(1.08 - clamp(0, calc(var(--sp) / 0.20), 1) * 0.08));
  /* Desaturate BEFORE it disappears, and never go to zero — the 12% ghost left
     registered under the line drawing is what sells this as an X-RAY rather
     than a slideshow. */
  filter: saturate(calc(1 - var(--cut, 0))) contrast(1.05);
  opacity: calc(1 - var(--cut, 0) * 0.88);
}

/* Unarmed, the schematic is simply the second plate on the page. */
.spec:not(.is-armed) .disc__plan {
  margin-top: 0.5rem;
  border-top: 1px solid var(--rule);
  padding-top: 2rem;
}

/* ★★ THE X-RAY ★★
   The cabin lights up from inside and the line drawing spreads OUTWARD through
   the airframe until the whole machine has gone to schematic.

   The origin is the CABIN (50% 32%) — not the image centre, and not the rotor
   head. That is the entire read: the reveal starts where the PEOPLE sit, and
   what it exposes is the seating. A reveal that started at the centre of the
   picture would just be a circle wipe.

   The mask is a radial gradient whose stop is scrubbed by --xray (0% → 100%),
   with a soft 14% shoulder so the boundary is a glow spreading through metal
   rather than a hard circle crawling across an image. 140% at full so the
   corners of a portrait box are actually reached. */
.spec.is-armed .disc__plan {
  -webkit-mask-image: radial-gradient(circle at 50% 32%,
                        #000 0 calc(var(--xray, 0%) * 1.4),
                        transparent calc(var(--xray, 0%) * 1.4 + 14%));
          mask-image: radial-gradient(circle at 50% 32%,
                        #000 0 calc(var(--xray, 0%) * 1.4),
                        transparent calc(var(--xray, 0%) * 1.4 + 14%));
  -webkit-mask-size: 100% 100%;
          mask-size: 100% 100%;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  /* HARD GATE ON THE CUT. The radial mask keeps a soft 14% shoulder open at its
     centre even at --xray:0, so a sliver of the plan (which stacks ABOVE the
     photo) bleeds through the intact airframe before the sweep starts. Multiply
     the whole plan by the cut so it is genuinely 0 until the transition begins —
     the mask still owns the spatial spread once it does. Reaches full opacity by
     --cut ≈ 0.125, well inside the reveal, so the finished schematic is unaffected. */
  opacity: clamp(0, calc(var(--cut, 0) * 8), 1);
}

/* The cabin glow — the light coming ON inside the aircraft, which is what makes
   the x-ray read as illumination from within rather than as a mask. It blooms at
   the start of the cut and is gone by the end: once the drawing is there, the
   light has done its job. */
.disc__blade,
.disc__ring,
.disc__pips { display: none; }

.spec.is-armed .disc__blade {
  display: block;
  position: absolute;
  left: 50%;
  top: 32%;
  width: 46%;
  aspect-ratio: 1;
  transform: translate(-50%, -50%) scale(calc(0.4 + var(--cut, 0) * 1.9));
  border-radius: 50%;
  background: radial-gradient(circle,
                rgba(232, 168, 78, 0.5) 0%,
                rgba(232, 168, 78, 0.16) 45%,
                transparent 70%);
  /* Up fast, gone by the end of the cut. */
  opacity: calc(clamp(0, var(--cut, 0) * 5, 1)
                * clamp(0, calc(1 - (var(--cut, 0) - 0.55) * 2.2), 1));
  pointer-events: none;
  mix-blend-mode: multiply;
}

/* The rotor spooling down as it arrives (JS eases --ring: linear rotation looks
   like a spinning logo; eased, it looks like a rotor coming to rest), and one
   last quarter-turn as the section leaves — spooling up to go. */
.spec.is-armed .disc__ring {
  display: block;
  position: absolute;
  inset: 6% 2%;
  border-radius: 50%;
  background: conic-gradient(from 0deg,
      rgba(201, 137, 58, 0) 0deg,
      rgba(201, 137, 58, 0.35) 60deg,
      rgba(201, 137, 58, 0) 130deg,
      rgba(201, 137, 58, 0) 180deg,
      rgba(201, 137, 58, 0.35) 240deg,
      rgba(201, 137, 58, 0) 310deg);
  -webkit-mask-image: radial-gradient(closest-side, transparent 42%, #000 60%, #000 99%, transparent 100%);
          mask-image: radial-gradient(closest-side, transparent 42%, #000 60%, #000 99%, transparent 100%);
  transform: rotate(var(--ring, 0deg));
  filter: blur(calc((1 - clamp(0, calc((var(--sp) - 0.05) / 0.15), 1)) * 14px));
  opacity: calc(0.42 - clamp(0, calc((var(--sp) - 0.05) / 0.15), 1) * 0.28);
  pointer-events: none;
}
/* The spinning amber ring is the ROTOR DISC — it reads as a helicopter's rotor
   spooling down as it lands. The jets have no rotor, so it makes no sense there:
   hide it entirely for the PC-12 and the Phenom. (Higher specificity than the
   .spec.is-armed .disc__ring rule above, so it wins.) */
[data-craft="pc12"]   .spec.is-armed .disc__ring,
[data-craft="phenom"] .spec.is-armed .disc__ring { display: none; }

/* (Seat pips and image captions removed — the sketch carries no labels,
   per client. is-cut / .disc__cap / .disc__pip are gone.) */

/* ---- The sales line. The only sentence in the room that sells, and it lands
   AFTER the numbers have earned it — but WITH the cut, not a beat behind it:
   held any later, the right-hand third of the composition is empty ivory for
   three quarters of the pin and the section reads as a two-column layout that
   lost its third column. ---- */
.spec__note {
  grid-column: 3;
  grid-row: 1 / span 2;
  align-self: start;    /* TOP of the column, level with the masthead — the two
                           outer columns start on the same line and the machine
                           runs between them. */
  color: var(--ink-warm);
}

/* The display sub-head. Sans, tight, two short lines — the reference's
   "Ultra-long-range Aircraft" slot. */
.spec__note-h {
  margin: 0 0 clamp(2rem, 6vh, 3.5rem);
  font-family: var(--font-sans);
  font-size: clamp(1.35rem, 1.9vw, 1.75rem);
  font-weight: 500;
  line-height: 1.18;
  letter-spacing: -0.015em;
  color: var(--ink-warm);
}

/* The tracked label above the paragraph. Same 0.22em instrumentation voice as
   the hero plate and the spec <dt>s — one label voice across the whole site. */
.spec__note-k {
  margin: 0 0 1.5rem;
  padding-top: 1rem;
  border-top: 1px solid var(--rule-warm);
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 0.7rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  line-height: 1.6;
  color: var(--ink-warm);
}

.spec__note-p {
  margin: 0;
  font-family: var(--font-sans);
  font-weight: 400;
  font-size: 0.9rem;
  line-height: 1.75;
  color: rgba(43, 38, 33, 0.74);
}
/* .spec__note reveal is the shared --reveal slide-up (above). */

/* ---- FALLBACKS ----
   No conic-mask support, or an image that failed to decode: a plain opacity
   crossfade over the same window. Nobody notices. JS adds .no-sweep. ---- */
.spec.is-armed.no-sweep .disc__plan {
  -webkit-mask-image: none;
          mask-image: none;
  opacity: var(--cut, 0);
}
.spec.is-armed.no-sweep .disc__blade { display: none; }

@supports not (mask-image: conic-gradient(#000, #000)) {
  .spec.is-armed .disc__plan {
    -webkit-mask-image: none;
            mask-image: none;
    opacity: var(--cut, 0);
  }
  .spec.is-armed .disc__blade { display: none; }}

/* ---- SHORT VIEWPORTS (a 1280x620 laptop is the real test case, not a phone).
   Nine rows, a head, a split title and a footnote all have to clear the pin —
   AND the nav and the fly bar. Buy the budget back from the chrome, never from
   the numbers. ---- */
/* 860px, not 800: a 13" laptop is 1440x813 with the browser chrome, and at the
   roomy density the nine rows' min-content forced the grid past its own bottom
   padding and pushed the footnote under the fly bar. Verified at 813 and 620. */
@media (min-width: 768px) and (max-height: 860px) {
  .spec__stage {
    padding: calc(var(--nav-height) + 1vh) var(--page-padding)
             calc(var(--flybar-h, 5rem) + 0.5vh);
    row-gap: clamp(0.3rem, 0.8vh, 0.6rem);
  }
  .spec__title-w    { font-size: clamp(1.5rem, 3.2vw, 2.25rem); }
  .spec__head h2    { font-size: clamp(1.35rem, 2.4vw, 2rem); }
  .spec__row        { padding: clamp(0.15rem, 0.5vh, 0.4rem) 0; }
  .spec__row dd     { font-size: clamp(0.95rem, min(1.5vw, 2.6vh), 1.5rem); }
  .spec__foot       { font-size: 0.625rem; }}

/* Under ~700px of viewport the nine rows, the heading, the rails, the footnote,
   the nav band AND the fly bar band cannot all coexist inside the pin. The
   rails are the only DECORATION in that list, so the rails are what goes. Never
   buy the budget back from the numbers. */
@media (min-width: 768px) and (max-height: 700px) {
  .spec__title { display: none; }}

/* ---- 768–991px: keep the pin and the rows; the note drops below them. ---- */
@media (min-width: 768px) and (max-width: 991px) {
  .spec__stage { grid-template-columns: minmax(240px, 40%) minmax(0, 1fr); }
  .spec__note  { grid-column: 1 / -1; grid-row: 4; text-align: center; }
  .spec__foot  { display: none; }}

/* ---- MOBILE (<768px) — no pin, no sweep, no 440vh scroll on a phone with two
   large images: initAircraftSpec() never arms below 768px, so the whole section
   is already the static stacked spread above. All that is left is to drop the
   split rails (they need width; do not fake it) and tighten the rhythm.
   (Jesko disables its whole scroll layer below 992px; same call.) ---- */
@media (max-width: 767px) {
  .spec:not(.is-armed) .spec__stage {
    gap: 2rem;
    padding: clamp(3.5rem, 10vw, 5rem) var(--page-padding);
  }
  .spec__title { display: none; }
  .disc { max-width: 100%; }
  .disc__cap span { letter-spacing: 0.16em; }}

/* ---- REDUCED MOTION — the section never arms, so it is already the static,
   fully legible spec spread, and it is honestly 90% as good. That is the test a
   real direction has to pass. Nothing left to undo but the one transition that
   lives outside .is-armed. ---- */
@media (prefers-reduced-motion: reduce) {
  .spec__push { transition: none; }}

/* ============================================================
   HERO — HARDWARE PLATE + BASE COPY
   The Gulfstream block, ours. A hairline rule, the maker hard left, the type
   hard right, and the machine in one paragraph beneath. It sits low-right,
   under CHARTERS, so the right rail reads top-to-bottom as: the word, the
   aircraft's name, the aircraft's case.
   ============================================================ */
.hero__plate {
  position: absolute;
  z-index: 3;
  right: var(--page-padding);
  bottom: clamp(5rem, 12vh, 8.5rem);   /* clears the fly bar — see --flybar-h */
  width: min(30rem, 34vw);
  pointer-events: none;
  /* Retires on the same curve as the blurb: by the time you are pushing into the
     page, the hero has said its piece. */
  opacity: calc(1 - clamp(0, (var(--hp, 0) - 0.22) * 2.4, 1));
  transform: translate3d(calc(var(--hp, 0) * 5vw), 0, 0);
}

/* Maker left, type right, hairline across the top — an instrument label, not a
   heading. The rule is what makes it read as a plate riveted to the machine. */
.hero__plate-rule {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin: 0 0 1.1rem;
  padding-top: 0.85rem;
  border-top: 1px solid var(--rule-warm);
  font-family: var(--font-sans);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.22em;      /* the label voice — same tracking as .hw__blurb
                                  and the #spec <dt>s. One instrumentation voice
                                  across the whole hardware layer. */
  text-transform: uppercase;
  color: var(--ink-warm);
}

.hero__plate-copy {
  margin: 0;
  font-family: var(--font-sans);
  font-size: 0.9rem;
  line-height: 1.75;
  color: rgba(43, 38, 33, 0.74);   /* ~5:1 on the sand floor */
}

/* Base copy — the crawlable "who and where" sentence, low-left, small. */
.hero__base {
  position: absolute;
  z-index: 3;
  left: var(--page-padding);
  bottom: clamp(5rem, 12vh, 8.5rem);
  width: min(26rem, 30vw);
  margin: 0;
  pointer-events: none;
  font-family: var(--font-sans);
  font-size: 0.82rem;
  line-height: 1.75;
  color: rgba(43, 38, 33, 0.66);
  opacity: calc(1 - clamp(0, (var(--hp, 0) - 0.22) * 2.4, 1));
  transform: translate3d(calc(var(--hp, 0) * -5vw), 0, 0);
}

.hero__base .eyebrow {
  display: block;
  margin-bottom: 0.6rem;
  color: var(--amber);
}

/* Both blocks are held back only while the plate is up, then bloom in behind the
   words. Keyed on .is-preloading — a class ONLY JS ever adds, with a failsafe —
   so no failure path can leave the hero hiding its own copy. */
.hero__plate,
.hero__base {
  transition: opacity 0.9s var(--ease-expo) 0.25s;
}
html.is-preloading .hero__plate,
html.is-preloading .hero__base {
  opacity: 0;
  transition: none;
}

/* MOBILE. There is no channel on a phone — a 390px viewport cannot hold a word,
   an aircraft and a word across one row. So the whole hero drops out of the
   absolute/grid overlay and becomes ONE ordinary stacked column: name, blurb,
   plate, base copy. The craft stays absolute BEHIND it as a ghost.
   Everything here is a deliberate un-doing of the desktop layout, not a tweak
   of it — hence position: static on the wordmark. */
@media (max-width: 767px) {
  .hero__sticky {
    display: block;
    padding: calc(var(--nav-height) + 2.5rem) var(--page-padding)
             calc(var(--flybar-h, 74px) + 1.5rem);
  }

  /* relative, NOT static: z-index is ignored on a static element, so a static
     wordmark loses its z-index: 3 and the absolutely-positioned .hero__craft
     (z-index: 2) paints straight over the top of the company name. The words
     were laid out correctly and simply buried under the aircraft. */
  .hero__wordmark {
    position: relative;
    display: block;
    padding: 0;
    text-align: center;
  }
  .hw__word {
    display: block;
    font-size: clamp(2.5rem, 15vw, 4.5rem);
    transform: none;              /* no opposing drift with nothing to drift past */
  }
  .hw__blurb {
    display: block;
    text-align: center;
    margin: 1.1rem auto 0;
    transform: none;
  }

  /* relative, not static — same z-index trap as the wordmark above. */
  .hero__plate,
  .hero__base {
    position: relative;
    z-index: 3;
    right: auto;
    bottom: auto;
    left: auto;
    width: auto;
    transform: none;
    margin-top: 1.75rem;
  }}

@media (prefers-reduced-motion: reduce) {
  .hero__plate,
  .hero__base { transform: none; }}

/* ============================================================
   NAV — FLOATING OPTIONS, NO BAR
   The nav is transparent at ALL times now, so the options are ink-on-light in
   every state (no more scrolled-bar flip). A whisper-soft light halo keeps them
   legible on the rare darker stretch the fixed nav floats over (the journey map,
   the aircraft heading glow).
   ============================================================ */
.nav__logo,
.nav__cta {
  color: var(--ink-warm);
}

.nav__links a {
  color: rgba(43, 38, 33, 0.82);
}

.nav__links a:hover,
.nav__links a:focus-visible {
  color: var(--ink-warm);
}

.nav__cta {
  border-color: var(--rule-warm);
}

.nav__cta:hover {
  background: var(--ink-warm);
  color: var(--hero-cream);
  border-color: var(--ink-warm);
}

.nav__hamburger span {
  background: var(--ink-warm);
}

/* Legibility over darker content the floating nav passes over. Invisible on the
   cream page; a faint light edge on the map. */
.nav__logo,
.nav__links a,
.nav__cta {
  text-shadow: 0 1px 12px rgba(251, 250, 248, 0.5);
}

/* ============================================================
   PRELOADER PLATE — THE LIGHT ROOM, TOO
   The plate must be the SAME ground the words land on, or the handoff reads as
   a scene change: dark plate wipes away, bright hero underneath, and the eye
   re-adjusts instead of following the words. Cream plate, ink words, and the
   wipe simply lifts a slightly denser cream off a lighter one.
   ============================================================ */
.pre__tone {
  background: linear-gradient(177deg,
    var(--hero-haze) 0%,
    var(--hero-cream) 45%,
    var(--hero-sand) 100%);
}

.pre__lockup {
  color: var(--ink-warm);
  text-shadow: none;   /* nothing to rescue the type off any more */
}

/* ============================================================
   PRELOADER — THE WAITING CURSOR
   The beat before the typing: a single caret blinking alone on an empty plate.
   The words are already in the DOM (they must be — they are the FLIP source),
   so they are collapsed to zero width rather than hidden, which keeps the caret
   optically centred instead of sitting where the finished lockup's left edge
   will be.
   ============================================================ */
.pre.is-waiting .pre__word,
.pre.is-waiting .pre__push {
  max-width: 0;
  overflow: hidden;
}

/* The caret must not inherit the collapse. */
.pre.is-waiting .hero__caret {
  max-width: none;
}

/* ============================================================
   #spec — THE OPENING BEAT (title overlay) + THE FLY-OUT
   The AVIATRIX/CHARTERS title, tagline and hardware plate now live INSIDE the
   spec sticky stage as a full-bleed overlay. At rest it is all you see. The
   helicopter (.disc) flies up over it and lands; then this whole group scrolls
   up and out (.spec.is-flown) while the machine stays locked and the spec sheet
   fades in around it. This is what makes it one continuous scene with no seam.
   ============================================================ */
.spec__intro {
  position: absolute;
  inset: 0;
  z-index: 2;                 /* above the spec text (which is hidden at rest),
                                 BELOW the helicopter (.disc z4). */
  pointer-events: none;
  /* The exit: on .is-flown the whole title group lifts up and dissolves — the
     "title scrolls up and off" beat. A transition, not a scrub, so it plays its
     own clean ease when the threshold flips (same idiom as .is-head below). */
  transition: transform 0.7s var(--ease-expo), opacity 0.55s var(--ease-expo);
}

.spec.is-flown .spec__intro {
  transform: translateY(-16vh);
  opacity: 0;
}

/* The title overlay reuses the .hero__wordmark / .hero__plate markup and their
   styles. Two things must be neutralised here, because those rules were written
   for the old #hero and keyed on --hp (which no longer exists — #hero is gone,
   so main.js never writes it and var(--hp,0) holds at 0):
     • the words sit STATIC (no drift, no scale) — exactly what the client asked
       for ("don't have the title expand");
     • the plate sits static too.
   var(--hp,0)=0 already gives that for free; these are just guards so a stray
   --hp can never reintroduce the drift. */
/* The words/tagline/plate sit static (no drift) because #hero is gone and --hp
   is never written, so their `var(--hp,0)` transforms resolve to identity. NO
   `transform: none !important` here on purpose — the preloader drives an inline
   transform on the words (centre lockup → spread to rest) and an !important
   would block it. */

/* The plate is absolute against .hero__sticky in its old life; here its
   positioning ancestor is .spec__intro (inset:0), which is the same box, so it
   still lands bottom-right. Nothing to change. */

/* THE SPEC SHEET SCROLLS UP FROM THE BOTTOM as you scroll down — masthead, rows,
   blurb and footnote all ride the shared --reveal rule (translate + fade) defined
   up in the #spec block. No per-element threshold fades any more. */

/* UNARMED (no JS / reduced-motion / mobile) — the stage is a static stacked
   column, so the title overlay must join the flow instead of covering it. It
   becomes the first block: the name, then the spec sheet beneath. */
.spec:not(.is-armed) .spec__intro {
  position: static;
  order: -1;
  transform: none;
  opacity: 1;
  margin-bottom: clamp(1.5rem, 4vw, 2.5rem);
}
.spec:not(.is-armed) .spec__intro .hero__wordmark {
  position: static;
  display: block;
  padding: 0;
  text-align: center;
}
.spec:not(.is-armed) .spec__intro .hw__word {
  display: block;
  font-size: clamp(2.5rem, 12vw, 5rem);
  line-height: 1.02;
}
.spec:not(.is-armed) .spec__intro .hw__blurb {
  display: block;
  text-align: center;
  margin: 0.9rem auto 0;
}
.spec:not(.is-armed) .spec__intro .hero__plate {
  position: static;
  width: auto;
  max-width: 34rem;
  margin: 1.5rem auto 0;
}

/* ============================================================
   #spec — MOBILE (≤767px), ARMED
   The full pinned choreography, restacked for a phone. Same scrubbed vars as
   desktop (--land / --reveal / --cut / --xray) — only the LAYOUT differs: the
   three flanking desktop columns collapse into one centred column.

   THE VERTICAL STACK, in the pinned 100svh stage:
     · .spec__intro — full-bleed title overlay: AVIATRIX over CHARTERS (big
       serif, clearing the nav), the tagline, then the Airbus/H125 plate. This
       is the opening beat AND the slot-out (rides .is-flown up-and-off, and the
       preloader spreads the real #wordA/#wordB into these resting boxes).
     · .disc — the heli rises from below (--land) and LOCKS dead-centre. z-flip:
       it flies in OVER the words, then settles BEHIND the readable words once
       .is-landed, with a soft fade at the handoff (discSettle).
     · .spec__head (masthead) sits ABOVE the locked heli, the 2-column spec grid
       BELOW it, the brand blurb below that — all sliding up on --reveal.

   Reduced motion never arms, so none of this touches the static stacked spread.
   ============================================================ */
@media (max-width: 767px) {

  /* THE PINNED STAGE — the machine is now a big low BACKDROP (absolute,
     bottom-anchored), and the text layers float in FRONT of it: the title at the
     top, then the masthead + number grid. Content flows from the top; the heli
     fills the lower two-thirds behind it. */
  .spec.is-armed .spec__stage {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    grid-template-columns: none;
    grid-template-rows: none;
    row-gap: clamp(0.3rem, 1vh, 0.65rem);
    column-gap: 0;
    max-width: none;
    padding: calc(var(--nav-height) + 0.5rem) var(--page-padding)
             calc(var(--flybar-h, 74px) + 0.4rem);
  }

  /* ---- 1 & 4. THE STACKED TITLE OVERLAY (opening beat + slot-out) ----
     Full-bleed over the pinned stage, anchored near the top so it clears the
     fixed nav. Left-aligned big serif per the reference. The preloader's centre
     lockup spreads into exactly these resting boxes, so the slot lands clean. */
  .spec.is-armed .spec__intro {
    display: flex;
    flex-direction: column;
    justify-content: flex-start;
    padding: calc(var(--nav-height) + clamp(1.5rem, 6vh, 3.25rem))
             var(--page-padding) 0;
  }
  .spec.is-armed .spec__intro .hero__wordmark {
    position: relative;
    display: block;
    padding: 0;
    text-align: left;
  }
  .spec.is-armed .spec__intro .hw__word {
    display: block;
    font-size: clamp(3rem, 17vw, 5rem);
    line-height: 0.98;
    letter-spacing: 0.01em;
  }
  .spec.is-armed .spec__intro .hw__blurb {
    display: block;
    text-align: left;
    max-width: none;
    margin: 1.1rem 0 0;
    font-size: 0.625rem;
    letter-spacing: 0.2em;
  }
  .spec.is-armed .spec__intro .hero__plate {
    position: relative;
    z-index: 3;
    right: auto; bottom: auto; left: auto;
    width: auto;
    max-width: 32rem;
    margin-top: clamp(1.5rem, 5vh, 2.5rem);
    transform: none;
  }
  .spec.is-armed .spec__intro .hero__plate-copy { font-size: 0.82rem; line-height: 1.65; }

  /* ---- 3 & 6. THE HELI (.disc) — BIG, lands LOW, tucks under the text ----
     Absolute and bottom-anchored so its tail tip sits just above the Fly Now
     pill; roughly the top quarter (rotor + cabin) tucks behind the title/stats.
     It does NOT shrink for the spec read — the numbers float in front and the
     x-ray to the seating sketch happens behind them. Sized by WIDTH (near
     full-bleed) with the frame's aspect ratio driving the height. */
  .spec.is-armed .disc {
    position: absolute;
    left: 50%;
    bottom: calc(var(--flybar-h, 74px) + 0.2rem);
    order: 0;
    align-self: auto;
    grid-column: auto;
    grid-row: auto;
    width: min(98vw, 460px);
    height: auto;
    aspect-ratio: 1400 / 1747;
    max-width: none;
    z-index: 3;              /* OVER the title during the fly-in */
    /* Rise from below to the low resting spot (--land carries the easeOutBack
       settle). No shrink afterwards. Overrides the desktop disc transform. */
    transform:
      translate3d(-50%, calc((1 - var(--land, 0)) * 66vh), 0)
      scale(calc(0.92 + var(--land, 0) * 0.08))
      rotate(calc(-1deg + var(--sp) * 1.8deg));
    opacity: clamp(0, calc(var(--land, 0) * 5), 1);
  }
  /* THE Z-FLIP + FADE. Once the machine has touched down (.is-landed, p≥LAND_TO)
     it drops BEHIND the text — title first, then the stats — so both read in
     front of it. A brief opacity dip carries the handoff ("fly in over the
     words, then fade behind them, sit, stay"). It never moves again. */
  .spec.is-armed.is-landed .disc {
    z-index: 1;
    animation: discSettle 0.5s var(--ease-expo) both;
  }

  /* ---- 5. THE SPEC PANEL — masthead + 2-col grid float IN FRONT of the big
     low heli (z2 over the heli's z1). They flow from the top and slide up on the
     shared --reveal. The heli x-rays to the seating sketch BEHIND them. ---- */
  .spec.is-armed .spec__head {
    order: 1;
    position: relative;
    z-index: 2;
    grid-column: auto;
    grid-row: auto;
    align-self: stretch;
    text-align: left;
  }
  .spec.is-armed .spec__rows {
    order: 2;
    position: relative;
    z-index: 2;
    grid-column: auto;
    grid-row: auto;
    align-self: stretch;
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: clamp(1rem, 5vw, 1.75rem);
    row-gap: 0.12rem;
  }

  /* ---- FAINT LIGHT BACKING ----
     Every text block that floats over the big low helicopter gets a soft cream
     halo behind it so the words read cleanly off the (dark, then line-drawing)
     machine. z-index:-1 keeps the halo behind the block's own text but inside
     the block's stacking context (which is above the heli), so it lifts the
     words off the airframe without a hard panel edge. The tagline and the intro
     plate get it too — the rotor tucks right behind them. */
  .spec.is-armed .spec__head,
  .spec.is-armed .spec__rows,
  .spec.is-armed .spec__intro .hw__blurb,
  .spec.is-armed .spec__intro .hero__plate {
    position: relative;
    z-index: 2;
  }
  .spec.is-armed .spec__intro .hero__plate { z-index: 3; }
  .spec.is-armed .spec__head::before,
  .spec.is-armed .spec__rows::before,
  .spec.is-armed .spec__intro .hw__blurb::before,
  .spec.is-armed .spec__intro .hero__plate::before {
    content: '';
    position: absolute;
    inset: -0.7rem -0.7rem;
    z-index: -1;
    pointer-events: none;
    border-radius: 0.9rem;
    background: radial-gradient(120% 128% at 50% 50%,
      rgba(251, 250, 248, 0.9) 0%,
      rgba(251, 250, 248, 0.72) 46%,
      rgba(251, 250, 248, 0) 100%);
  }
  /* The brand line is dropped on a phone — the hero tagline already says
     "Private Helicopter Charters", and dropping it buys the vertical room to run
     the helicopter big (client wanted it much larger on mobile). */
  .spec.is-armed .spec__note { display: none; }
  .spec.is-armed .spec__foot { order: 5; display: none; }

  .spec.is-armed .spec__make  { font-size: 0.9rem; }
  .spec.is-armed .spec__model { font-size: clamp(2.15rem, 9.5vw, 3rem); line-height: 0.95; }

  .spec.is-armed .spec__row    { padding: 0.06rem 0; }
  .spec.is-armed .spec__row dt { font-size: 0.5625rem; letter-spacing: 0.14em; margin-bottom: 0.04rem; }
  .spec.is-armed .spec__row dd { font-size: clamp(0.95rem, 4.6vw, 1.25rem); }
  /* Units STAY — a bare "340" is meaningless. They sit small under the numeral. */
  .spec.is-armed .spec__u {
    display: block;
    margin: 0.02rem 0 0;
    font-size: 0.6rem;
    letter-spacing: 0.01em;
    line-height: 1.25;
    color: rgba(43, 38, 33, 0.62);
  }

  .spec.is-armed .spec__note-h { display: none; }  /* the big pull-quote is desktop-only vertical budget */
  .spec.is-armed .spec__note-k { margin-bottom: 0; padding-top: 0.7rem; font-size: 0.6rem; }
  .spec.is-armed .spec__note-p { display: none; }  /* long prose won't fit the pin; the eyebrow carries the brand line */
}

/* The heli's fade-behind-the-words at the landing handoff (mobile z-flip). Runs
   once when .is-landed is added; ends at opacity 1 to match the scrubbed value. */
@keyframes discSettle {
  0%   { opacity: 1; }
  45%  { opacity: 0.4; }
  100% { opacity: 1; }}

/* THE APERTURE, open by default. Squashed into a C slot the ellipse is a closed
   loop and reads as an O, so a wedge mask bites its right side open. Default =
   scale(1) = open = a correct C: js/preloader.js only ever COLLAPSES it, for the
   split beat. If the script never runs, the letter is still a C. Decoration must
   never be able to break the word. */
.hw__aperture {
  transform-box: view-box;
  transform-origin: 50% 50%;
  /* 0.6s = SPLIT_C_MS, so the wedge is fully open on the frame the C lands. It
     was 0.62s against a 940ms flight, i.e. it finished 320ms early and the
     opening read as its own little event ahead of the arrival. */
  transition: transform 0.6s var(--ease-expo);
}

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

/* ============================================================
   THE LOGO INTRO — beat one: the compass draws itself
   ------------------------------------------------------------
   An animated WebP with real alpha, in --ink-warm, injected by js/preloader.js
   when the beat starts. It was a <video> matte until Safari refused to autoplay
   it (NotAllowedError, on a file that had loaded perfectly) — see the note on
   the element in index.html. An <img> is not subject to autoplay policy, so the
   intro plays for everyone.
   ============================================================ */
.logo-intro { display: none; }

html.js.is-preloading .logo-intro {
  display: grid;
  position: fixed;
  inset: 0;
  z-index: var(--z-preloader);
  place-items: center;
  pointer-events: none;
}

.logo-intro__img {
  /* Square, and capped against BOTH axes so the mark never crops on a short
     laptop window or a narrow phone. The 520px ceiling keeps a 560px-native
     asset from being upscaled on tall displays, where 46vh would otherwise ask
     for 700px+ and soften the artwork. */
  width:  min(46vh, 62vw, 520px);
  height: min(46vh, 62vw, 520px);
  display: block;
  opacity: 0;
  transition: opacity 0.2s linear;
}
.logo-intro.is-playing .logo-intro__img { opacity: 1; }
/* The handoff: the animation fades as the real marks take its pose. Short and
   overlapping on purpose — the two are registered to sub-pixel, so a long
   crossfade would just read as a double image. */
/* THE DRAWING LEAVES. It used to cross-fade under the two real marks coming up
   in its exact pose (.is-handoff, 160ms) — that mechanism is gone; the drawing
   now simply fades out on its own and the name types in afterwards, so this is
   a straight fade over nothing and can afford to be slower. Must stay in step
   with LOGO_FADE_MS in js/preloader.js, which is what decides when the node is
   actually taken out of the DOM. */
.logo-intro.is-fading .logo-intro__img { opacity: 0; transition: opacity 0.34s ease; }

/* ============================================================
   PRELOADER v2 — THE SAME-ELEMENT LOAD
   No plate, no FLIP, no second copy of the words. The loading screen types and
   spreads the REAL hero title (#wordA / #wordB in .spec__intro). Because it is
   the actual element, its final resting spot is exact by definition — the split
   words can never land misaligned. js/preloader.js drives an inline transform
   (centre lockup → translate(0) rest) and the char reveal.

   During the load the page is a still, warm field (the #spec section's own
   cream) with the title typing in the centre — everything else is held back and
   fades in as the words settle.
   ============================================================ */

/* Hold everything but the typing title back while loading. */
.is-preloading .spec__intro .hw__blurb,
.is-preloading #heroPlate {
  opacity: 0;
}
.is-preloading #nav,
.is-preloading #flybar,
/* THE MACHINE, held back too. #spec only becomes the pinned stage once main.js
   adds .is-armed; in the frames before that it is the plain stacked layout, and
   the aircraft sits high enough to peek over the fold — so the load opened on a
   slice of helicopter above the logo. It was survivable when the loading screen
   was ~2.5s of typing and the arm usually won the race; the logo draw makes the
   window several times longer and the flash became reliable. The machine is
   meant to arrive AFTER the name, by flying up the channel. Comes back with the
   nav at finish, not with the spread. */
.is-preloading #specDisc {
  opacity: 0;
  pointer-events: none;
}

/* The words are hidden until the module reveals them for typing (is-typing on
   the wordmark). 4-class specificity so it beats `.is-preloading .spec__intro
   .hw__word`. The per-char reveal reuses the existing .hero__wordmark .char /
   .char.is-typed vocabulary. */
.is-preloading .spec__intro .hw__word { opacity: 0; }
.spec__intro .hero__wordmark.is-typing .hw__word { opacity: 1; }

/* Smooth fade-in for everything that was held back, when the load ends (class
   removed) or the words start to spread (.is-spreading). */
#nav,
#flybar,
#specDisc,
.spec__intro .hw__blurb,
#heroPlate {
  transition: opacity 0.6s var(--ease-expo);
}
/* Tagline + plate bloom in AS the words spread; nav/flybar come back at finish. */
.is-spreading .spec__intro .hw__blurb,
.is-spreading #heroPlate {
  opacity: 1;
}

/* The typing caret — a slim amber bar that blinks, then walks with the glyphs.
   Created by the module and appended after the newest char. */
.hw__caret {
  /* THE CARET TAKES UP NO ROOM AT ALL — neither width nor height.
     It is only faded to opacity 0 and is not removed from the DOM until spread(),
     so anything it contributes to layout is still being contributed when the FLIP
     measures the lockup, and the spread then opens with a snap as it goes.
     Zeroing the ADVANCE (below) killed the horizontal half of that: the lockup
     grid is justify-content:center, so the caret's 6.41px re-centred the whole
     name by half of it and the spread jumped +3.20px sideways.
     Zeroing the HEIGHT kills the vertical half: an inline-block 0.92em tall
     raises the line box it sits in, so dropping the caret shortened row 1 and the
     spread jumped +3.35px downward as well. The visible bar is drawn by ::before,
     which is absolutely positioned and therefore contributes nothing.
     Net: the caret can never move layout, whenever it appears or goes. */
  display: inline-block;
  position: relative;
  width: 2px;
  height: 0;
  margin-left: 0.06em;
  margin-right: calc(-0.06em - 2px);
  vertical-align: baseline;   /* zero-height inline-block: bottom edge = baseline */
}

/* The bar itself, drawn upward from the baseline and out of flow. It overlaps the
   leading edge of the next glyph by 2px, which is invisible: that glyph is
   opacity:0 until it types, and by then the caret has walked on. */
.hw__caret::before {
  content: '';
  position: absolute;
  left: 0;
  bottom: 0;
  width: 2px;
  height: 0.92em;
  background: var(--amber);
  animation: hwCaretBlink 1.05s steps(1) infinite;
}
.hw__caret.is-fading { opacity: 0; transition: opacity 0.14s linear; }
.hw__caret.is-fading::before { animation: none; }
@keyframes hwCaretBlink { 0%, 50% { opacity: 1; } 50.01%, 100% { opacity: 0; }}

@media (prefers-reduced-motion: reduce) {
  .hw__caret::before { animation: none; }}

/* THE TYPING LOCKUP — a compact, centred "Aviatrix Charters" laid out natively
   by flexbox (no JS measurement, so the display font swapping in can't throw it
   off). The preloader FLIPs from this to the resting grid for the spread.

   PINNED TO THE VIEWPORT. justify-content:center only centres WITHIN the
   wordmark's own box — and that box is only full-bleed when #spec is armed on
   desktop. On mobile (and in the beat before #spec arms) the resting box is
   top-anchored and left-aligned, so the loading name sat high and off to one
   side. position:fixed + inset:0 makes the lockup the whole viewport, so the
   name types dead-centre on every device. The spread still measures live rects
   the instant .is-lockup drops, so it lands on the resting grid exactly. */
/* THE LOADING LOCKUP — the name, on ONE line, dead centre.
   It used to be a two-column grid: the assembled logo on the left, AVIATRIX
   over CHARTERS stacked beside it, with the marks flying out of the drawing
   into the A and C slots. That was removed on request — the drawing now fades
   out on its own and the name simply types, so all this has to do is hold the
   two words side by side while they fill in.
   SPECIFICITY IS LOAD-BEARING HERE. The mobile block up in #spec carries
   `.spec.is-armed .spec__intro .hero__wordmark` (four classes) and resets the
   title to position:relative / display:block for the stacked phone layout. A
   three-class `.spec__intro .hero__wordmark.is-lockup` LOSES to it, so on
   phones the lockup silently kept the resting layout and the whole intro sat
   jammed against the top of the screen. Every rule here is written to
   out-specify that one. Do not "simplify" these selectors back down.
   position:fixed + inset:0 makes the lockup the whole viewport, so the name
   types dead-centre on every device — the wordmark's own box is only
   full-bleed when #spec is armed on desktop. The spread still measures live
   rects the instant .is-lockup drops, so it lands on the resting grid exactly. */
.spec .spec__intro .hero__wordmark.is-lockup {
  position: fixed;
  inset: 0;
  z-index: var(--z-preloader);
  margin: 0;
  padding: 0 var(--page-padding);
  display: flex;
  /* CENTRE, not baseline. align-items:baseline lines the two words up with each
     other but pins the row to the TOP of the cross axis, so the whole name
     typed itself against the top edge of the screen. The two words are the same
     font-size and the same line-height, so centring them aligns their baselines
     anyway — there is nothing baseline alignment was buying here. */
  align-items: center;
  justify-content: center;
  /* The word space. A real space character would be typed like a glyph and sit
     in one of the two words; a gap belongs to neither and cannot be typed. */
  gap: 0.26em;
  font-size: var(--lockup-size);
}
/* One line of AVIATRIX CHARTERS is roughly 9.9em of advance, against the
   stacked version's ~5em, so this is deliberately smaller than the old lockup
   size (which was clamp(2.2rem, 6vw, 4.6rem) for two short rows). At 5.2vw the
   line is ~742px at 1440 and ~200px at 390 — comfortably inside the padding on
   both. Raise it and it will clip on narrow phones before it clips anywhere
   else. */
:root { --lockup-size: clamp(1.5rem, 5.2vw, 4.2rem); }

.spec .spec__intro .hero__wordmark.is-lockup .hw__word {
  transform: none;                 /* the FLIP sets an inline transform later */
  font-size: 1em;                  /* = --lockup-size, set on the flex row above */
  /* MUST MATCH THE RESTING .hw__word (0.9) — see the note there. The spread is a
     FLIP that pins each word's box BOTTOM, and how far the baseline sits above
     that bottom is set by line-height. At 0.98 here against 0.9 at rest the two
     boxes were not similar figures, so the inverse transform could not put the
     baseline back where it had been and the spread opened with a 3.35px vertical
     snap of the whole name. Not a style choice: the two states have to be the
     same shape for the FLIP to be exact. */
  line-height: 0.9;
  white-space: nowrap;
}
.spec .spec__intro .hero__wordmark.is-lockup .hw__blurb { display: none; }

/* NOTE ON UNTYPED GLYPHS. They are opacity:0 and KEEP their space — the lockup
   is deliberately laid out at its full final width from the first frame. An
   earlier version dropped them from layout (display:none) so a centred lockup
   could grow outward from the middle; here that would drag the logo column
   sideways on every keystroke, and the whole point of this beat is that the mark
   sits still while the name appears beside it. Do not reintroduce display:none. */

/* NAV OVER SOMETHING DARK — flip the floating options to light so they read on
   a photograph. js/main.js adds .nav--on-dark while the nav band overlaps any
   [data-nav-dark] section. A soft dark halo replaces the light one for the same
   reason in reverse. */

/* AND THE NAV CARRIES ITS OWN SCRIM, because recolouring the type is not enough
   on its own. These heroes are photographs: a bright patch behind the links —
   sky through the trees at Cassville, the lit quartz at Crystal Mining — comes
   through the hero's own top gradient and drops cream type under 4.5:1 on nine
   of the fourteen pages. Measured off the rendered pixels, not guessed.
   A short gradient in the NAV BAND ONLY, rather than a heavier hero scrim, so
   the photograph is untouched everywhere below the bar. Sits behind the nav's
   own content (the nav is a stacking context and has no background of its own)
   and disappears with the class, so the light pages never see it. */
.nav--on-dark::before {
  content: '';
  position: absolute;
  inset: 0 0 auto;
  height: calc(var(--nav-height) + 30px);
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(180deg,
    rgba(20, 17, 14, 0.62) 0%,
    rgba(20, 17, 14, 0.34) 55%,
    rgba(20, 17, 14, 0) 100%);
}
.nav--on-dark .nav__logo,
.nav--on-dark .nav__cta {
  color: var(--cream);
  border-color: rgba(245, 240, 232, 0.5);
}
.nav--on-dark .nav__links a { color: rgba(245, 240, 232, 0.9); }
.nav--on-dark .nav__links a:hover,
.nav--on-dark .nav__links a:focus-visible { color: var(--white); }
.nav--on-dark .nav__cta:hover {
  background: var(--cream);
  color: var(--ink-warm);
  border-color: var(--cream);
}
.nav--on-dark .nav__hamburger span { background: var(--cream); }
.nav--on-dark .nav__logo,
.nav--on-dark .nav__links a,
.nav--on-dark .nav__cta {
  text-shadow: 0 1px 14px rgba(10, 14, 24, 0.55);
}

/* ============================================================
   BOOK FORM — consent line + error (matches the fly bar contact card)
   ============================================================ */
.book__consent {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  font-family: var(--font-sans);
  font-size: 0.8125rem;
  line-height: 1.4;
  color: rgba(43, 38, 33, 0.72);
}
.book__consent input {
  margin-top: 0.15rem;
  accent-color: var(--amber);
  width: 1rem;
  height: 1rem;
  flex-shrink: 0;
}
.book__consent a { color: var(--ink-warm); text-decoration: underline; }

.form-error {
  margin: 0;
  padding: 0.9rem 1.1rem;
  border-radius: var(--radius);
  border: 1.5px solid rgba(180, 60, 40, 0.5);
  background: rgba(180, 60, 40, 0.08);
  color: #7a2b1c;
  font-family: var(--font-sans);
  font-size: 0.875rem;
  text-align: center;
}
.form-error[hidden] { display: none; }

/* ============================================================================
   THE AIRCRAFT SWITCH — added with the PC-12. Shared selector (twin instances
   in #spec and #pilot), per-craft content swap, the static PC-12 hero in
   #aircraft, and the 3-up pilot roster. See js/aircraft.js.
   ============================================================================ */

/* ---- The segmented selector ------------------------------------------------ */
.craft-switch {
  position: relative;
  display: inline-flex;
  align-items: stretch;
  padding: 4px;
  border-radius: 999px;
  background: rgba(251, 250, 248, 0.9);
  border: 1px solid var(--rule-warm);
  -webkit-backdrop-filter: blur(8px);
          backdrop-filter: blur(8px);
  box-shadow: 0 12px 34px -22px rgba(43, 38, 33, 0.7);
  font-family: var(--font-sans);
}
/* JS reveals the switches (they ship `hidden` so a no-JS / single-aircraft page
   never shows a dead control). */
.craft-switch[hidden] { display: none; }

.craft-switch__thumb {
  position: absolute;
  top: 4px; bottom: 4px; left: 4px;
  /* Three equal segments inside the 4px inset on each side. translateX in units
     of the thumb's OWN width then lands it exactly on segment 2 (100%) and 3 (200%). */
  width: calc((100% - 8px) / 3);
  border-radius: 999px;
  background: var(--ink-warm);
  transition: transform 0.5s var(--ease-expo);
  z-index: 0;
}
.craft-switch[data-active="pc12"]   .craft-switch__thumb { transform: translateX(100%); }
.craft-switch[data-active="phenom"] .craft-switch__thumb { transform: translateX(200%); }

.craft-switch__opt {
  position: relative;
  z-index: 1;
  flex: 1 1 33.333%;
  min-width: 6.25rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  padding: 0.5rem 1.1rem;
  border: 0;
  background: transparent;
  cursor: pointer;
  color: var(--ink-warm);
  transition: color 0.45s var(--ease-expo);
}
.craft-switch__opt[aria-pressed="true"] { color: var(--hero-haze); }
.craft-switch__opt:focus-visible { outline: 2px solid var(--amber); outline-offset: 3px; border-radius: 999px; }
.craft-switch__k {
  font-size: 0.56rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  opacity: 0.72;
}
.craft-switch__n {
  font-size: 0.9rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  white-space: nowrap;
}

/* ---- Placement A: pinned at the top of the #spec stage. It stays out of the
   pure opening beat and fades in once the title has flown out. --------------- */
.craft-switch--spec {
  position: absolute;
  top: calc(var(--nav-height) + clamp(0.6rem, 2.2vh, 1.4rem));
  left: 50%;
  /* RIDE THE SAME FAKE CENTRE LINE AS THE MACHINE. --seam-shift is half the
     width difference between CHARTERS and AVIATRIX; the two hero words are
     block-centred, so the gap between them sits that far LEFT of the raw
     viewport centre, and both the aircraft (.disc) and the fly bar are nudged
     left by it to land in that gap. This control was not, so it sat exactly on
     the viewport centre and therefore 39px to the RIGHT of the aircraft it
     belongs to — reads as crooked because everything around it agrees with
     everything except this. js/aircraftspec.js zeroes the variable under 768px,
     where the words stack and there is no seam, so this becomes a no-op there. */
  transform: translateX(-50%) translateX(calc(var(--seam-shift, 0px) * -1)) translateY(-8px);
  z-index: 6;
  opacity: 0;
  /* visibility (not just opacity) so the buttons are also out of the keyboard tab
     order while invisible at the top of the page — opacity:0 alone leaves them
     focusable, stranding a keyboard user on a control they cannot see. */
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.6s var(--ease-expo), transform 0.6s var(--ease-expo),
              visibility 0s linear 0.6s;
}
.spec.is-armed.is-flown .craft-switch--spec {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateX(calc(var(--seam-shift, 0px) * -1)) translateY(0);
  pointer-events: auto;
  transition: opacity 0.6s var(--ease-expo), transform 0.6s var(--ease-expo),
              visibility 0s linear 0s;
}

/* ---- Placement B: inline in the #pilot header (always available). ---------- */
.craft-switch--pilot { align-self: center; }

/* ---- THE CRAFT SWAP. On a switch, js/aircraft.js adds .is-craft-swapping for
   ~160ms: the featured machine and the sheet dip out, the content is replaced,
   and they come back on the new aircraft. The transition lives ONLY inside this
   class, so the scrubbed --reveal/--land values are never smeared in normal
   scroll. --land is pinned at 1 whenever the sheet is on screen (the only time
   the switch is reachable), so nothing scrubbed is moving underneath the fade. */
.spec.is-armed.is-craft-swapping .disc,
.spec.is-armed.is-craft-swapping .spec__head,
.spec.is-armed.is-craft-swapping .spec__rows,
.spec.is-armed.is-craft-swapping .spec__note,
.spec.is-armed.is-craft-swapping .spec__foot {
  opacity: 0 !important;
  transition: opacity 0.16s ease;
}
/* The fade-IN: same transition, no opacity:0, so the new craft eases up from 0
   to its pinned value instead of hard-cutting. js/aircraft.js removes this class
   ~240ms later, before the next scroll scrub, so nothing scrubbed is smeared. */
.spec.is-armed.is-craft-swapping-in .disc,
.spec.is-armed.is-craft-swapping-in .spec__head,
.spec.is-armed.is-craft-swapping-in .spec__rows,
.spec.is-armed.is-craft-swapping-in .spec__note,
.spec.is-armed.is-craft-swapping-in .spec__foot {
  transition: opacity 0.2s ease;
}

/* ---- #aircraft: the PC-12 static hero (no fly-through model). Floats on the
   same gradient ground as the CSS around it, so there is no seam. ------------ */
.aircraft__still {
  position: absolute;
  inset: 0;
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: calc(var(--nav-height) + 9vh) var(--page-padding) 15vh;
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.6s ease;
}
.aircraft__still img {
  max-width: min(660px, 80%);
  max-height: 100%;
  width: auto;
  height: auto;
  object-fit: contain;
  filter: drop-shadow(0 44px 60px rgba(43, 38, 33, 0.28));
}
/* NO MODEL, NO SECTION. #aircraft exists to show the aircraft off: the camera
   flies the real glTF for the A-Star and the Phenom (JET_MODELS in js/heli.js).
   The PC-12 has no model, so the whole tall pinned scroll track could only
   offer the same top-down photograph the spec sheet shows a screen later.
   Client asked for it gone rather than padded out.

   Safe to hide outright — nothing links to #aircraft, js/heli.js drives the
   section with plain getBoundingClientRect maths rather than a pinning library
   that would need re-measuring, and its render loop is gated on an
   IntersectionObserver a display:none section never fires. The rules below
   still cover the PC-12 for the no-JS case, where data-craft is never set and
   this rule cannot match.

   ⚠ PAIRED WITH js/heli.js. Add a pc12 entry to JET_MODELS and delete this rule
   in the same commit, or the model will load into a section nobody can see. */
[data-craft="pc12"] #aircraft { display: none; }

[data-craft="pc12"] .aircraft__still,
[data-craft="phenom"] .aircraft__still  { opacity: 1; visibility: visible; }
[data-craft="pc12"] .aircraft__canvas,
[data-craft="phenom"] .aircraft__canvas { opacity: 0; visibility: hidden; }
[data-craft="heli"] .aircraft__still  { opacity: 0; visibility: hidden; }
/* WebGL live (js/heli.js adds .craft-3d): the aircraft that HAVE a 3D model fly
   through the canvas, so retire their static stills. The PC-12 is deliberately
   excluded — its photoreal top-down beats the free CAD model, so it keeps the
   still. Higher specificity than the per-craft rules above, so it wins; absent
   (no WebGL / JS off) every still stays. */
html.craft-3d[data-craft="heli"]   .aircraft__still,
html.craft-3d[data-craft="phenom"] .aircraft__still  { opacity: 0; visibility: hidden; }
html.craft-3d[data-craft="heli"]   .aircraft__canvas,
html.craft-3d[data-craft="phenom"] .aircraft__canvas { opacity: 1; visibility: visible; }
@media (prefers-reduced-motion: no-preference) {
  [data-craft="pc12"] .aircraft__still img,
  [data-craft="phenom"] .aircraft__still img { animation: stillFloat 9s ease-in-out infinite; }}
@keyframes stillFloat {
  0%, 100% { transform: translateY(0) rotate(-0.5deg); }
  50%      { transform: translateY(-14px) rotate(0.5deg); }}

/* ---- #pilot: header + 3-up roster (replaces the old single-bio grid). ------ */
.pilot__inner { display: block; }
/* Copy left, aircraft switch CENTRED on its own row beneath it. This was a
   flex row with justify-content:space-between, which parked the switch hard
   against the right edge — 433px off centre at 1440, and further the wider the
   window got, so it read as a stray control rather than the section's own
   selector. A single-column grid puts it under the copy where it belongs;
   justify-self keeps the heading block left-aligned rather than dragging the
   whole header into the middle. */
.pilot__head {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  justify-items: center;
  gap: clamp(1.25rem, 3vw, 2rem);
  margin-bottom: clamp(2rem, 5vw, 3.25rem);
}
.pilot__head-copy { max-width: 46rem; justify-self: start; }
.pilot__head .eyebrow { color: var(--amber); }
.pilot__head h2 { color: var(--ink-warm); margin: 0.35rem 0 0.85rem; }
.pilot__sub {
  font-size: clamp(1rem, 2.2vw, 1.1rem);
  line-height: 1.7;
  color: rgba(43, 38, 33, 0.78);
  margin: 0;
  max-width: 42rem;
}

.pilot__cards {
  display: grid;
  gap: clamp(1.25rem, 2.5vw, 1.75rem);
  grid-template-columns: 1fr;
}
@media (min-width: 720px) { .pilot__cards { grid-template-columns: repeat(3, 1fr); }}

/* MOBILE: a swipeable left/right carousel instead of a tall stack. Each card
   takes ~80% of the width so the next one peeks, signalling there's more to
   scroll; scroll-snap holds each card on release. (js/aircraft.js no longer
   binds a craft-switch swipe here, so this horizontal scroll is unobstructed —
   the selector pill above the cards still switches aircraft.) */
@media (max-width: 719px) {
  .pilot__cards {
    display: flex;
    grid-template-columns: none;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scroll-padding-left: 2px;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;                 /* Firefox */
    padding-bottom: 4px;                   /* room so card shadows/borders aren't clipped */
  }
  .pilot__cards::-webkit-scrollbar { display: none; }   /* WebKit */
  .pilot__cards > .pilot-card {
    flex: 0 0 80%;
    scroll-snap-align: start;
  }}

.pilot-card {
  display: flex;
  flex-direction: column;
  background: rgba(251, 250, 248, 0.6);
  border: 1px solid var(--rule-warm);
  border-radius: var(--radius-lg);
  overflow: hidden;
}
.pilot-card--chief {
  border-color: rgba(201, 137, 58, 0.5);
  background: rgba(251, 250, 248, 0.85);
}
.pilot-card__media { position: relative; }
.pilot-card__photo {
  aspect-ratio: 4 / 5;
  display: flex;
  align-items: center;
  justify-content: center;
  background:
    radial-gradient(120% 90% at 30% 20%, rgba(201, 137, 58, 0.20), transparent 58%),
    linear-gradient(160deg, var(--hero-cream) 0%, var(--hero-sand) 100%);
  border-bottom: 1px solid var(--rule-warm);
}
.pilot-card__photo-note {
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(43, 38, 33, 0.6);
}
.pilot-card__badge {
  position: absolute;
  left: 0.85rem;
  bottom: 0.85rem;
  background: rgba(251, 250, 248, 0.92);
  color: var(--ink-warm);
  font-size: 0.64rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  padding: 0.4rem 0.7rem;
  border-radius: 999px;
  border: 1px solid rgba(201, 137, 58, 0.45);
  -webkit-backdrop-filter: blur(6px);
          backdrop-filter: blur(6px);
}
.pilot-card__body {
  display: flex;
  flex-direction: column;
  flex: 1;
  padding: clamp(1.1rem, 2vw, 1.5rem);
}
.pilot-card__role {
  font-family: var(--font-sans);
  font-size: 0.64rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--amber);
  margin: 0 0 0.3rem;
}
.pilot-card__name {
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: clamp(1.4rem, 2.4vw, 1.7rem);
  letter-spacing: 0.01em;
  color: var(--ink-warm);
  margin: 0 0 0.8rem;
}
.pilot-card__lead {
  font-size: 0.9rem;
  line-height: 1.62;
  color: rgba(43, 38, 33, 0.75);
  margin: 0 0 1.1rem;
}
.pilot-card__ph {
  display: block;
  margin-top: 0.45rem;
  font-size: 0.72rem;
  font-style: italic;
  letter-spacing: 0.03em;
  color: rgba(43, 38, 33, 0.5);
}
.pilot-card__creds {
  list-style: none;
  margin: 0 0 1.15rem;
  padding: 0;
  display: grid;
  gap: 0.5rem;
}
.pilot-card__creds li {
  display: flex;
  gap: 0.5rem;
  align-items: baseline;
  flex-wrap: wrap;
  font-size: 0.84rem;
  color: rgba(43, 38, 33, 0.78);
  padding-bottom: 0.5rem;
  border-bottom: 1px solid rgba(43, 38, 33, 0.1);
}
.pilot-card__cred-k {
  font-family: var(--font-serif);
  font-size: 0.92rem;
  letter-spacing: 0.06em;
  color: var(--ink-warm);
}
.pilot-card__actions { margin-top: auto; padding-top: 0.25rem; }

.btn--sm { padding: 0.6rem 1.2rem; font-size: 0.8rem; min-height: 40px; }

/* Roster stagger-in on each swap. The hidden initial state is gated on
   `.is-roster-js`, which js/aircraft.js adds to #pilotCards itself (in
   renderRoster) — NOT on `.js`. That distinction is the fallback: `.js` is set
   unconditionally in the head bootstrap, so gating on it would strand the static
   Stan card at opacity:0 if aircraft.js ever 404s or throws. Gating on the class
   the script sets means a page where the script never runs keeps the static card
   fully visible — the no-JS/script-failure fallback the Pilots section promises. */
.pilot__cards.is-roster-js .pilot-card { opacity: 0; transform: translateY(14px); }
.pilot__cards.is-roster-js.is-entered .pilot-card { animation: pilotIn 0.6s var(--ease-expo) forwards; }
.pilot__cards.is-roster-js.is-entered .pilot-card:nth-child(2) { animation-delay: 0.08s; }
.pilot__cards.is-roster-js.is-entered .pilot-card:nth-child(3) { animation-delay: 0.16s; }
@keyframes pilotIn { to { opacity: 1; transform: none; }}

@media (prefers-reduced-motion: reduce) {
  .pilot__cards.is-roster-js .pilot-card { opacity: 1; transform: none; animation: none; }
  .aircraft__still { transition: none; }}

/* Narrow phones: keep the pill from crowding its edges. */
@media (max-width: 420px) {
  .craft-switch__opt { min-width: 5.4rem; padding: 0.45rem 0.7rem; }
  .craft-switch__n { font-size: 0.82rem; }}

/* ---- HELI-ONLY ADD-ON BAND (camera gimbal). A standalone band between #spec
   and #pilot, gated purely on the shared [data-craft] state so it vanishes for
   the jets. Because it sits in normal flow (not the pinned spec sheet), it has
   room at every width — no 100svh budget to fight. ---------------------------- */
.addon-band {
  background: var(--hero-haze);
  border-top: 1px solid var(--rule-warm);
  padding: clamp(2rem, 5vw, 3.25rem) var(--page-padding);
}
/* Collapse entirely when a jet is featured — the gimbal is the A-Star's film rig. */
[data-craft="pc12"] .addon-band,
[data-craft="phenom"] .addon-band { display: none; }

.addon-band__inner {
  max-width: var(--max-width);
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: clamp(1rem, 3vw, 2rem);
  flex-wrap: wrap;
  padding: clamp(1.1rem, 2.5vw, 1.6rem) clamp(1.25rem, 3vw, 2rem);
  border: 1px solid rgba(201, 137, 58, 0.42);
  border-radius: var(--radius-lg);
  background: rgba(201, 137, 58, 0.06);
}
.addon-band__icon {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 3.1rem;
  height: 3.1rem;
  border-radius: 50%;
  color: var(--amber);
  background: rgba(201, 137, 58, 0.12);
  border: 1px solid rgba(201, 137, 58, 0.4);
}
.addon-band__copy { flex: 1 1 20rem; min-width: 0; }
.addon-band__k {
  margin: 0 0 0.35rem;
  font-family: var(--font-sans);
  font-weight: 600;
  font-size: 0.62rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--amber);
}
.addon-band__t {
  margin: 0 0 0.4rem;
  font-family: var(--font-serif);
  font-weight: 300;
  font-size: clamp(1.35rem, 2.4vw, 1.8rem);
  line-height: 1.1;
  color: var(--ink-warm);
}
.addon-band__p {
  margin: 0;
  font-family: var(--font-sans);
  font-size: clamp(0.88rem, 1.4vw, 0.95rem);
  line-height: 1.6;
  color: rgba(43, 38, 33, 0.75);
  max-width: 46rem;
}
.addon-band__cta { flex: 0 0 auto; }
@media (max-width: 620px) {
  .addon-band__cta { width: 100%; }}

/* ---- "Aircraft" nav link scrolls here; keep the landing clear of the fixed
   nav so #aircraft's opening beat isn't tucked under it. --------------------- */
#aircraft { scroll-margin-top: var(--nav-height); }

/* ---- FAQ. Plain native <details> accordions, on the ivory ground. Also feeds
   the FAQPage structured data in <head> for search + AI answer engines. ------ */
.faq {
  background: var(--hero-cream);
  border-top: 1px solid var(--rule-warm);
  padding: clamp(3rem, 7vw, 5.5rem) var(--page-padding);
  scroll-margin-top: var(--nav-height);
}
.faq__inner { max-width: 52rem; margin: 0 auto; }
.faq .eyebrow { color: var(--amber); }
.faq__inner h2 {
  font-family: var(--font-serif);
  color: var(--ink-warm);
  font-size: clamp(1.8rem, 4vw, 2.6rem);
  line-height: 1.1;
  margin: 0.35rem 0 1.75rem;
}
.faq__list { border-top: 1px solid var(--rule-warm); }
.faq__item { border-bottom: 1px solid var(--rule-warm); }
.faq__q {
  font-family: var(--font-serif);
  font-size: clamp(1.05rem, 2.4vw, 1.25rem);
  color: var(--ink-warm);
  cursor: pointer;
  list-style: none;
  padding: 1.15rem 2.75rem 1.15rem 0;
  position: relative;
}
.faq__q::-webkit-details-marker { display: none; }        /* Safari default triangle */
.faq__q::after {
  content: "+";
  position: absolute;
  right: 0.35rem;
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--font-sans);
  font-weight: 300;
  font-size: 1.6rem;
  line-height: 1;
  color: var(--amber);
  transition: transform 0.35s var(--ease-expo);
}
.faq__item[open] .faq__q::after { transform: translateY(-50%) rotate(45deg); }
.faq__q:focus-visible { outline: 2px solid var(--amber); outline-offset: 3px; border-radius: 4px; }
.faq__a { padding: 0 2.75rem 1.4rem 0; }
.faq__a p {
  font-family: var(--font-sans);
  font-size: 0.98rem;
  line-height: 1.65;
  color: rgba(43, 38, 33, 0.82);
  margin: 0;
  max-width: 44rem;
}

/* CC-BY attribution for the two Sketchfab jet models (required by the license). */
.footer__credits { opacity: 0.62; font-size: 0.7rem; line-height: 1.5; }
.footer__credits a { color: inherit; text-decoration: underline; text-underline-offset: 2px; }
/* ═══════════════════════════════════════════════════════════════════════════
   THE EXPERIENCES GRID  (/experiences)
   Replaced the scroll-driven MapLibre journey map. That page was a single
   linear flight through seven stops — beautiful, and structurally incapable of
   holding fourteen. A grid can grow; a flight path cannot.
   Card is deliberately quiet: photograph, name, place, and a read-more that is
   NOT a button. The whole card is the link (see a.xcard), so the "Read more"
   is a cue, not a second target — one destination, one hit area.
   ═══════════════════════════════════════════════════════════════════════ */

/* No heading any more — the grid is the whole page. The top padding therefore
   has to clear the fixed nav on its own rather than leaning on the old header
   block's margin. */
.xlist { padding: calc(var(--nav-height) + clamp(2rem, 5vw, 3.5rem)) 0 clamp(3rem, 8vw, 5rem); }


.xgrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 20rem), 1fr));
  gap: clamp(2rem, 4vw, 3.25rem) clamp(1.5rem, 3vw, 2.5rem);
}

a.xcard {
  display: block;
  text-decoration: none;
  color: inherit;
}

/* 3:2 via aspect-ratio, with a padding-top fallback for anything that predates
   it — a collapsed image would drop the whole grid into a text list. */
.xcard__img {
  display: block;
  width: 100%;
  aspect-ratio: 3 / 2;
  background: var(--cream-dark) center / cover no-repeat;
  border-radius: 0.35rem;
  transition: transform var(--transition), box-shadow var(--transition);
}
@supports not (aspect-ratio: 3 / 2) {
  .xcard__img { height: 0; padding-top: 66.667%; }}

/* PLACEHOLDER. Used where no licensed photograph exists yet. Brand ground with
   the compass ghosted into it, so an un-photographed card still reads as
   finished rather than broken — and so it is obvious at a glance which ones are
   still waiting on artwork. Delete this class when the real image lands. */
.xcard__img--ph {
  background:
    url("/icon-mark.svg") center / 34% no-repeat,
    linear-gradient(155deg, #efe9df 0%, #e2d9c9 100%);
  opacity: 0.92;
}

a.xcard:hover .xcard__img {
  transform: translateY(-4px);
  box-shadow: 0 22px 44px -26px rgba(43, 38, 33, 0.7);
}

.xcard__title {
  font-family: var(--font-serif);
  font-size: 1.32rem;
  font-weight: 400;
  letter-spacing: 0.012em;
  line-height: 1.22;
  margin: 1.15rem 0 0.4rem;
}

.xcard__loc {
  font-size: 0.735rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: rgba(43, 38, 33, 0.55);
  margin: 0 0 0.95rem;
}

/* Reads as a button, is not one — the card is the link. Kept as a span so it
   can never become a second tab stop for the same destination. */
.xcard__cta {
  display: inline-block;
  padding: 0.5rem 1.15rem;
  border-radius: 999px;
  background: var(--amber);
  color: #fff;
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  transition: background-color var(--transition);
}
a.xcard:hover .xcard__cta { background: var(--amber-light); }

a.xcard:focus-visible {
  outline: 2px solid var(--amber);
  outline-offset: 6px;
  border-radius: 0.5rem;
}

@media (prefers-reduced-motion: reduce) {
  .xcard__img, .xcard__cta { transition: none; }
  a.xcard:hover .xcard__img { transform: none; }}

/* Destination hero with no licensed photograph yet — same story as
   .xcard__img--ph, at hero scale.
   IT HAS TO BEHAVE LIKE A PHOTOGRAPH, NOT LIKE A BRAND PANEL. .exp-hero__scrim
   lays 55% black over the top third and 92% over the bottom, because every real
   hero here is a bright outdoor shot: the nav is dark text and relies on the
   top of the image being light. A dark placeholder put dark nav text on a dark
   ground and the whole header vanished. So this runs pale at the top, where the
   nav sits, into mid tone at the bottom, where the scrim takes over and the
   cream title needs something to sit on. */
.exp-hero__bg--ph {
  background:
    url("/icon-mark.svg") center 42% / clamp(80px, 12vw, 150px) no-repeat,
    linear-gradient(172deg,
      #ece5d9 0%,
      #d9cfbd 34%,
      #b3a691 68%,
      #8d8271 100%);
}
/* The homepage's tail-end link out to /experiences. (Was .journeymap-cta, when
   it pointed at the scroll-driven map.) Centring and rhythm only — the button
   itself is .btn. */
.experiences-cta {
  text-align: center;
  padding: clamp(3rem, 8vw, 5.5rem) var(--page-padding);
}
/* ═══════════════════════════════════════════════════════════════════════════
   EXPERIENCES CAROUSEL  (#experiences-carousel, homepage)
   A scroll-snap track, not a slider. Nothing moves unless the visitor moves it:
   this sits between a pinned, scroll-driven hero and a WebGL model, and a third
   thing animating on its own would be competing for attention with both.
   The cards are the SAME .xcard as /experiences — only the track around them
   is new — so the two cannot drift apart.
   ═══════════════════════════════════════════════════════════════════════ */

.xcar { padding: clamp(3.5rem, 9vw, 6rem) 0; }

.xcar__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1.5rem 2rem;
  flex-wrap: wrap;
  margin-bottom: clamp(1.75rem, 4vw, 2.5rem);
}
.xcar__head .eyebrow { color: var(--amber); }
.xcar__head h2 {
  color: var(--ink-warm);
  margin: 0.35rem 0 0;
  font-size: clamp(1.9rem, 4.6vw, 3rem);
  line-height: 1.06;
}

.xcar__nav { display: flex; gap: 0.6rem; flex: none; }
.xcar__btn {
  width: 46px;
  height: 46px;
  border-radius: 999px;
  border: 1px solid var(--rule-warm, rgba(43,38,33,0.18));
  background: transparent;
  color: var(--ink-warm);
  font-size: 1.05rem;
  line-height: 1;
  cursor: pointer;
  transition: background-color var(--transition), opacity var(--transition);
}
.xcar__btn:hover:not(:disabled) { background: rgba(43, 38, 33, 0.07); }
.xcar__btn:disabled { opacity: 0.3; cursor: default; }
.xcar__btn:focus-visible { outline: 2px solid var(--amber); outline-offset: 3px; }

/* THE TRACK. overflow-x on a flex row + scroll-snap is the whole mechanism —
   swipe on touch, trackpad sideways, arrow keys when focused, and the buttons
   just call scrollBy. No transform maths, so it cannot desync from what the
   user actually did. */
.xcar__track {
  display: flex;
  gap: clamp(1rem, 2.2vw, 1.75rem);
  margin: 0;
  padding: 0 0 0.5rem;
  list-style: none;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;            /* Firefox */
  -ms-overflow-style: none;
}
.xcar__track::-webkit-scrollbar { display: none; }
.xcar__track:focus-visible { outline: 2px solid var(--amber); outline-offset: 6px; border-radius: 0.5rem; }

.xcar__item {
  flex: 0 0 clamp(15rem, 26vw, 20rem);
  scroll-snap-align: start;
}

.xcar__all { margin: clamp(1.75rem, 4vw, 2.5rem) 0 0; }

@media (prefers-reduced-motion: reduce) {
  .xcar__track { scroll-behavior: auto; }
  .xcar__btn { transition: none; }
}
