/* ==========================================================================
   Components introduced by the inner pages. Loaded after styles.css.
   Same tokens, same motion language — nothing here overrides the home page.
   ========================================================================== */

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

.container-bleed { width: 100%; }

.prose > * + * { margin-top: 1em; }
.prose p { margin: 0; }

/* --- Section heads ------------------------------------------------------- */
.section-head { max-width: 52rem; margin-bottom: var(--section-sm); }
.section-head--center { margin-inline: auto; text-align: center; }
.section-head .eyebrow { margin-bottom: var(--space-md); }
.section-head .h2,
.section-head .h3 { margin-bottom: var(--space-md); }
.section-head > :last-child { margin-bottom: 0; }

/* --- Photo hero ---------------------------------------------------------- */
.hero--photo { min-height: 78svh; }
.hero--photo.hero--tall { min-height: 88svh; }
.hero--photo.hero--short { min-height: 58svh; }

.hero--photo .hero__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  animation: kenburns 34s var(--ease-inout) infinite alternate;
}

.hero--photo .eyebrow { margin-bottom: var(--space-md); }

/* --- Split variants ------------------------------------------------------ */
.split--flip { }
.frames--flip { padding-right: 0; padding-left: 12%; }
.frames--flip .frames__lg { margin-left: auto; }
.frames--flip .frames__sm { right: auto; left: 0; }

.split__media img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
}

.section--bone .split__media img,
.section--dark .split__media img { aspect-ratio: 4 / 3; }

/* --- Tick lists ---------------------------------------------------------- */
.ticks { display: grid; gap: var(--space-xs); margin-top: var(--space-md); }

.ticks li {
  position: relative;
  padding-left: 1.75rem;
  font-size: var(--text-rg);
  line-height: 1.55;
}

/* LUX — a hairline check drawn in brand cyan rather than a bullet glyph. */
.ticks li::before {
  content: "";
  position: absolute;
  left: 0; top: 0.5em;
  width: 0.75rem; height: 0.4rem;
  border-left: 1px solid var(--brand-light);
  border-bottom: 1px solid var(--brand-light);
  transform: rotate(-45deg);
}

.ticks--grid { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-md) var(--space-xl); }
.section--dark .ticks li { color: var(--white-70); }

/* --- Pillars ------------------------------------------------------------- */
.section--brand-tint { background: var(--brand-dark); color: var(--white); }

.pillars {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-lg);
}

.pillar {
  padding: var(--space-xl) var(--space-lg);
  border-top: var(--hairline) solid var(--white-15);
}

.pillar__num {
  display: block;
  margin-bottom: var(--space-md);
  font-family: var(--font-accent);
  font-style: italic;
  font-size: 2.5rem;
  line-height: 1;
  color: var(--brand-light);
}

.pillar h3 { margin-bottom: var(--space-2xs); }
.pillar p { margin: 0; }

/* --- Tile grids ---------------------------------------------------------- */
.tiles--2 { grid-template-columns: repeat(2, 1fr); }
.tiles--4 { grid-template-columns: repeat(4, 1fr); }
.tiles--three { grid-template-columns: repeat(3, 1fr); }

/* --- Feature chips ------------------------------------------------------- */
.chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs);
}

.chip {
  padding: 0.625rem 1.125rem;
  border: var(--hairline) solid var(--dark-15);
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  letter-spacing: 0.01em;
  color: var(--dark-70);
  transition: border-color var(--dur-md) var(--ease-lux), color var(--dur-md) var(--ease-lux);
}

.chip:hover { border-color: var(--brand-light); color: var(--black); }

/* --- Horizontal gallery rail -------------------------------------------- */
.rail {
  overflow-x: auto;
  overflow-y: hidden;
  /* `proximity`, not `mandatory`: with mandatory snapping a smooth programmatic
     scroll is re-snapped to the current point before it can advance, so the
     arrow buttons silently do nothing. Proximity still snaps a manual swipe. */
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* Without this the first item's snap point lands on the container edge and
     eats the track's leading gutter, so the rail starts flush to the viewport
     while every other section is inset. */
  scroll-padding-inline: var(--gutter);
}

/* Snapping and continuous drift are incompatible: proximity snapping keeps
   tugging a slow programmatic scroll back to the nearest snap point, so the
   rail stutters instead of gliding. Arrow navigation on this rail is a tween
   over scrollLeft rather than a native snap scroll, so it loses nothing. */
.rail[data-rail-auto] {
  scroll-snap-type: none;
}

.rail::-webkit-scrollbar { display: none; }

.rail__track {
  display: flex;
  gap: var(--space-2xs);
  padding-inline: var(--gutter);
}

.rail__item {
  flex: 0 0 auto;
  width: min(46rem, 74vw);
  margin: 0;
  scroll-snap-align: start;
}

.rail__item img,
.rail__item video {
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
}

.rail__nav {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-2xs);
  margin-top: var(--space-md);
}

.rail__arrow {
  display: grid;
  place-items: center;
  width: 3.5rem; height: 3.5rem;
  border: var(--hairline) solid var(--dark-30);
  border-radius: 50%;
  transition: background var(--dur-md) var(--ease-lux),
              border-color var(--dur-md) var(--ease-lux),
              color var(--dur-md) var(--ease-lux);
}

.rail__arrow:hover { background: var(--brand-light); border-color: var(--brand-light); color: var(--white); }
.rail__arrow svg { width: 1.125rem; height: 1.125rem; }

.rail__arrow:disabled {
  opacity: 0.3;
  cursor: default;
  background: none;
  border-color: var(--dark-15);
  color: var(--black);
}

/* --- Destination routes -------------------------------------------------- */
.routes {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-2xs);
}

.route { position: relative; margin: 0; overflow: hidden; }

.route img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-lux);
}

.route:hover img { transform: scale(1.05); }

.route__label {
  position: absolute;
  inset: auto 0 0;
  padding: var(--space-lg);
  background: linear-gradient(to top, rgb(0 0 0 / 0.8), transparent);
  color: var(--white);
  font-size: var(--h5);
  font-weight: var(--w-display);
  letter-spacing: -0.01em;
}

/* --- Aircraft cards ------------------------------------------------------ */
/* Categories hold between one and four aircraft. A plain 1fr track would
   stretch a lone card across the whole container, so tracks are bounded at
   24rem and the row is centred instead. */
.ac-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 24rem));
  justify-content: center;
  gap: var(--space-lg);
}

/* Exactly four cards otherwise land 3 + 1. Squaring them off reads deliberate.
   Browsers without :has() keep the 3 + 1, which is still correct. */
.ac-cards:has(> :nth-child(4):last-child) {
  grid-template-columns: repeat(2, minmax(0, 24rem));
}

.ac-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  padding: var(--space-lg);
  background: var(--white);
  border: var(--hairline) solid var(--dark-10);
  transition: border-color var(--dur-md) var(--ease-lux),
              transform var(--dur-md) var(--ease-lux);
}

.ac-card:hover { border-color: var(--brand-light); transform: translateY(-4px); }

/* A miniature of the home page's aircraft stage, rather than a silhouette
   dropped into the card's text column.

   Before: the cutout sat inside the card's 32px padding and rendered ~318x94
   inside a 384x354 card — a thin strip floating in dead space, with the plane
   starting a third of the way down. Three changes fix that:

   1. Full-bleed. Negative margins pull the band out to the card's edges and up
      past its top padding, so the aircraft is the first thing in the card
      rather than something indented below the rim.
   2. A stage to sit on. The same top-down wash the home stage uses, so the
      cutout reads as lit on a surface instead of pasted onto white.
   3. Room and lift. A fixed band height with the jet anchored to the bottom and
      scaled past the band's width, so it fills the space and overhangs
      slightly — the depth cue the home stage gets from its own scale. */
.ac-card__media {
  display: block;
  position: relative;
  /* Cancels the card's padding on three sides. */
  margin: calc(var(--space-lg) * -1) calc(var(--space-lg) * -1) var(--space-md);
  height: clamp(7rem, 11vw, 9rem);
  overflow: hidden;
  /* --neutral-lightest is the same wash .section--bone uses behind these cards,
     so the stage reads as a continuation of the section rather than a tinted box. */
  background: linear-gradient(180deg, var(--neutral-lightest) 0%, rgb(255 255 255 / 0) 78%);
}

.ac-card__media img {
  position: absolute;
  /* Bottom-anchored like .ac-slide__jet, so every aircraft sits on the same
     line no matter how tall its cutout is. */
  inset: auto 0 8%;
  width: 112%;
  margin-inline: -6%;
  filter: drop-shadow(0 18px 26px rgb(0 0 0 / 0.16));
  transition: transform var(--dur-md) var(--ease-lux);
}

/* The card already lifts on hover; the jet lifts a little further so the two
   move at different rates, which is the same parallax idea as the home stage. */
.ac-card:hover .ac-card__media img { transform: translateY(-3px) scale(1.03); }

@media (prefers-reduced-motion: reduce) {
  .ac-card__media img { transition: none; }
  .ac-card:hover .ac-card__media img { transform: none; }
}

.ac-card__name { margin-bottom: var(--space-3xs); }

.ac-card__specs {
  font-size: var(--text-sm);
  color: var(--dark-70);
  font-variant-numeric: tabular-nums;
  margin-bottom: var(--space-sm);
}

.ac-card .link { margin-top: auto; }

/* --- Category cards ------------------------------------------------------ */
/* Seven categories. auto-fit packed 5+2 at desktop, which reads like an
   accident; a fixed 4-wide grid gives a deliberate 4+3. */
.cat-cards {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-2xs);
}

@media (max-width: 1200px) {
  .cat-cards { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.cat-card {
  position: relative;
  display: block;
  min-height: 22rem;
  overflow: hidden;
  background: var(--neutral-dark);
  color: var(--white);
}

.cat-card__media { position: absolute; inset: 0; }

.cat-card__media img {
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0.5;
  transition: opacity var(--dur-slow) var(--ease-lux), transform var(--dur-slow) var(--ease-lux);
}

.cat-card:hover .cat-card__media img { opacity: 0.65; transform: scale(1.06); }

.cat-card::after {
  content: "";
  position: absolute; inset: 0;
  background: linear-gradient(to top, rgb(0 0 0 / 0.78), rgb(0 0 0 / 0.15) 55%, transparent);
}

.cat-card__body {
  position: absolute;
  inset: auto 0 0;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  padding: var(--space-lg);
}

.cat-card__specs {
  font-size: var(--text-sm);
  color: var(--white-70);
  font-variant-numeric: tabular-nums;
}

/* --- Fleet index rows ---------------------------------------------------- */
.fleet-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.15fr);
  gap: var(--space-4xl);
  align-items: center;
}

.fleet-row__copy .eyebrow { margin-bottom: var(--space-sm); }
.fleet-row__copy .h3 { margin-bottom: var(--space-md); }
.fleet-row__copy .specs { margin-top: var(--space-lg); }

.fleet-row__jets {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(12rem, 100%), 18rem));
  gap: var(--space-md);
}

.jet-chip {
  display: block;
  padding: var(--space-md);
  border: var(--hairline) solid var(--dark-10);
  transition: border-color var(--dur-md) var(--ease-lux), transform var(--dur-md) var(--ease-lux);
}

.jet-chip:hover { border-color: var(--brand-light); transform: translateY(-3px); }
.jet-chip img { width: 100%; margin-bottom: var(--space-sm); }

.jet-chip__name {
  display: block;
  font-size: var(--text-md);
  font-weight: var(--w-light);
}

.jet-chip__spec {
  display: block;
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--dark-70);
  margin-top: var(--space-3xs);
}

/* --- Quote bar ----------------------------------------------------------- */
/* The site's primary conversion surface. Every competitor of consequence puts a
   From/To/Date/Passengers bar within a screen of the hero; this is ours. */
.quotebar-wrap { position: relative; z-index: 5; padding-block: var(--space-2xl); }

/* Lifted so it straddles the hero edge — reads as part of the hero, not a
   separate band, and guarantees it is above the fold. Zeroed on both sides:
   padding-top would double up with the hero above it, and padding-bottom
   would double up with the padding-top of whatever section always follows
   this component — at wide viewports that stacked to 100px+ of dead space
   between the card and the next section's own content. */
.quotebar-wrap--float {
  margin-top: calc(var(--space-3xl) * -1);
  padding-block: 0;
}

.quotebar {
  background: var(--white);
  border: var(--hairline) solid var(--dark-10);
  box-shadow: 0 30px 70px rgb(0 0 0 / 0.13);
  padding: var(--space-lg) var(--space-xl);
}

.quotebar-wrap--dark .quotebar {
  background: rgb(61 60 68 / 0.96);
  border-color: var(--white-15);
  color: var(--white);
  backdrop-filter: blur(14px);
}

.quotebar__eyebrow {
  margin: 0 0 var(--space-md);
  font-size: var(--text-xs);
  font-weight: var(--w-regular);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--dark-70);
}

.quotebar-wrap--dark .quotebar__eyebrow { color: var(--white-50); }

.quotebar__fields {
  display: grid;
  grid-template-columns: 1fr 1fr 0.8fr 0.7fr max-content;
  align-items: end;
  gap: var(--space-lg);
}

.qb-field { display: grid; gap: var(--space-3xs); min-width: 0; }

.qb-field__label {
  font-size: var(--text-xs);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--dark-70);
}

.quotebar-wrap--dark .qb-field__label { color: var(--white-50); }

/* Hairline-underlined inputs, matching the contact form rather than boxed
   fields — boxes would read as a booking engine, not a private desk. */
.qb-field__input {
  width: 100%;
  min-width: 0;
  padding: 0.5rem 0;
  background: transparent;
  border: 0;
  border-bottom: var(--hairline) solid var(--dark-30);
  border-radius: 0;
  font: inherit;
  font-size: var(--text-rg);
  color: inherit;
  transition: border-color var(--dur-md) var(--ease-lux);
}

.qb-field__input::placeholder { color: var(--dark-30); }
.qb-field__input:focus { outline: none; border-bottom-color: var(--brand-light); }

.quotebar-wrap--dark .qb-field__input { border-bottom-color: var(--white-30); }
.quotebar-wrap--dark .qb-field__input::placeholder { color: var(--white-40); }

/* Safari renders date inputs at a different intrinsic size; pin the box. */
.qb-field--date .qb-field__input { min-height: 2.25rem; }

.qb-stepper {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
  border-bottom: var(--hairline) solid var(--dark-30);
  padding-bottom: 0.5rem;
}

.quotebar-wrap--dark .qb-stepper { border-bottom-color: var(--white-30); }

.qb-stepper__btn {
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border: var(--hairline) solid var(--dark-30);
  border-radius: 50%;
  font-size: var(--text-rg);
  line-height: 1;
  color: inherit;
  transition: border-color var(--dur-md) var(--ease-lux),
              background var(--dur-md) var(--ease-lux),
              color var(--dur-md) var(--ease-lux);
}

/* 1.75rem (28px) is fine for a mouse pointer but under the 44px touch-target
   floor (WCAG 2.5.5 / Apple HIG) — this is the passenger count on the
   homepage's primary lead form, so a mis-tap here is a lost lead, not just an
   annoyance. Sized off `pointer: coarse` rather than a width breakpoint: a
   touch laptop or a wide tablet needs the bigger target just as much as a
   phone does. The fleet arrows already run 4-5.25rem, so a 44px circle here
   reads as consistent with the rest of the site's touch controls, not oversized. */
@media (pointer: coarse) {
  .qb-stepper__btn { width: 2.75rem; height: 2.75rem; }
}

.qb-stepper__btn:hover {
  border-color: var(--brand-light);
  background: var(--brand-light);
  color: var(--white);
}

.quotebar-wrap--dark .qb-stepper__btn { border-color: var(--white-30); }

.qb-stepper__input {
  width: 2.5rem;
  padding: 0;
  background: transparent;
  border: 0;
  font: inherit;
  font-size: var(--text-rg);
  font-variant-numeric: tabular-nums;
  text-align: center;
  color: inherit;
  -moz-appearance: textfield;
}

.qb-stepper__input::-webkit-outer-spin-button,
.qb-stepper__input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.qb-stepper__input:focus { outline: none; }

.qb-submit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  padding: 0.9rem 1.75rem;
  border: var(--hairline) solid var(--brand-light);
  border-radius: var(--radius-pill);
  background: var(--brand-light);
  color: var(--white);
  font-size: var(--text-rg);
  line-height: 1.2;
  white-space: nowrap;
  transition: background var(--dur-md) var(--ease-lux),
              border-color var(--dur-md) var(--ease-lux);
}

.qb-submit:hover { background: var(--brand-medium); border-color: var(--brand-medium); }
.qb-submit svg { width: 1rem; height: 1rem; flex: none; }

.quotebar__note {
  margin: var(--space-md) 0 0;
  font-size: var(--text-xs);
  color: var(--dark-70);
}

.quotebar-wrap--dark .quotebar__note { color: var(--white-50); }

@media (max-width: 1100px) {
  .quotebar__fields { grid-template-columns: 1fr 1fr; }
  .qb-submit { grid-column: 1 / -1; width: 100%; }
}

@media (max-width: 767px) {
  .quotebar-wrap--float { margin-top: calc(var(--space-xl) * -1); }
  .quotebar { padding: var(--space-lg); }
  .quotebar__fields { grid-template-columns: 1fr; gap: var(--space-md); }
}

/* --- Featured aircraft stage (home) ------------------------------------- */
/* Composition follows the live build: a rolling-cloud sky behind the whole
   section, the aircraft name centred over it, and the aircraft pinned wide
   across the bottom crossing over the name. Slides move horizontally. */
.ac-section {
  position: relative;
  overflow: hidden;
  background: var(--white);
}

/* The live section runs a looping cloud video full-height and centred. Masked to
   white top and bottom so it reads as sky rather than a pasted-in panel. */
.ac-sky {
  position: absolute;
  inset: 0;
  z-index: 0;
  overflow: hidden;
}

.ac-sky video {
  width: auto;
  min-width: 100%;
  height: 100%;
  margin-inline: auto;
  object-fit: cover;
  /* Mirrored so the roll reads as travelling the other way — clouds have no
     fixed orientation, so flipping the footage is free, no re-encode needed. */
  transform: scaleX(-1);
}

.ac-sky::after {
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(to bottom, var(--white) 0%, rgb(255 255 255 / 0) 18%,
                    rgb(255 255 255 / 0) 80%, var(--white) 100%),
    linear-gradient(to right, var(--white) 0%, rgb(255 255 255 / 0) 9%,
                    rgb(255 255 255 / 0) 91%, var(--white) 100%);
}

.ac-inner { position: relative; z-index: 1; }

/* The shared .section-head caps at 52rem, which wraps this headline onto two
   lines; the live build runs it full width on one line. */
.ac-head {
  max-width: none;
  margin-bottom: var(--space-lg);
}

.ac-stage {
  position: relative;
  height: clamp(20rem, 34vw, 30rem);
  margin-bottom: var(--space-2xl);
}

.ac-stage:focus-visible { outline: 1px solid var(--brand-light); outline-offset: 8px; }

/* Horizontal slide, same direction and geometry as the live build, but the
   track, the jet and the name each travel on their own timing. That offset is
   the whole effect: the track leads, the jet trails and overshoots slightly
   into place, the name arrives last. Moving all three together is what made the
   original read as a stock carousel. */
.ac-viewport { position: relative; height: 100%; overflow: hidden; }

/* A glint of light sweeps the stage in the direction of travel while the
   track is mid-transition — main.js toggles `.is-flying` on transitionrun /
   transitionend, so it only ever plays alongside an actual slide change. */
.ac-viewport::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: linear-gradient(100deg,
    transparent 42%, rgb(255 255 255 / 0.5) 50%, transparent 58%);
  transform: translate3d(calc(var(--dir, 1) * -130%), 0, 0);
  opacity: 0;
}

.ac-viewport.is-flying::after {
  opacity: 1;
  transform: translate3d(calc(var(--dir, 1) * 130%), 0, 0);
  transition: transform var(--dur-slide) var(--ease-glide), opacity var(--dur-slide) linear;
}

.ac-track {
  display: flex;
  height: 100%;
  transform: translate3d(calc(var(--i, 0) * -100%), 0, 0);
  transition: transform var(--dur-slide) var(--ease-slide);
}

.ac-slide {
  position: relative;
  flex: 0 0 100%;
  height: 100%;
}

.ac-slide__label {
  position: absolute;
  inset: 0 0 auto;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2xs);
  text-align: center;
  padding-inline: var(--gutter);
}

.ac-slide__cat {
  margin: 0;
  font-size: var(--text-rg);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--dark-70);
}

/* The live build sets the name at 3.5rem with slight negative tracking. */
.ac-slide__name {
  font-size: clamp(2.25rem, 4.4vw, 3.5rem);
  line-height: 1;
  letter-spacing: -0.05rem;
  margin: 0;
}

/* Wide and bottom-anchored, as on the live site. The slide is the positioning
   context, so the jet can overlap the name without pushing layout around. */
.ac-slide__jet {
  display: block;
  position: absolute;
  inset: auto 0 0;
  z-index: 1;
  width: min(95%, 74rem);
  margin-inline: auto;
}

.ac-slide__photo { display: block; width: 100%; }

/* --- Slide choreography --- */
/* `--n` is each slide's own fixed index (set once, in the markup); `--i` is
   the current slide, held on the track. Their difference is the jet's
   distance from center, so the outgoing jet and the incoming jet resolve to
   opposite signs automatically — one flies off the trailing edge, the next
   flies in from the leading edge — without main.js tracking which literal
   direction is which. */
.ac-slide__jet {
  opacity: 0;
  /* A wide bank and a clearing motion blur read as a jet actually crossing
     the frame, not a photo sliding into place. */
  transform: translate3d(calc((var(--n, 0) - var(--i, 0)) * 65%), 2.5%, 0)
             scale(0.85)
             rotate(calc((var(--n, 0) - var(--i, 0)) * -3.5deg));
  filter: blur(9px);
  transition: transform var(--dur-jet) var(--ease-glide),
              filter var(--dur-jet) var(--ease-glide),
              opacity var(--dur-md) var(--ease-inout);
  will-change: transform, filter;
}

.ac-slide.is-active .ac-slide__jet {
  opacity: 1;
  transform: none;
  filter: blur(0);
  /* Starts after the track is already moving — that gap is the parallax. */
  transition-delay: 70ms;
}

.ac-slide__label > * {
  opacity: 0;
  transform: translateY(0.85rem);
  transition: opacity var(--dur-ac-label) var(--ease-lux),
              transform var(--dur-ac-label) var(--ease-lux);
}

.ac-slide.is-active .ac-slide__cat { opacity: 1; transform: none; transition-delay: 200ms; }
.ac-slide.is-active .ac-slide__name { opacity: 1; transform: none; transition-delay: 280ms; }

/* Idle float on the active jet only — it sits on the <img>, inside the wrapper
   that owns the entrance transform, so the two never collide. Long, tiny, and
   out of phase with nothing else on the page. */
@keyframes ac-float {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-0.9%) rotate(-0.22deg); }
}

.ac-slide.is-active .ac-slide__photo {
  animation: ac-float 9s var(--ease-inout) infinite;
}

/* --- Aircraft thumbnails --- */
/* A small cutout of each jet rather than a bare dot, so the row also answers
   "what else is in the fleet" — same click-to-jump behaviour as the dots
   they replaced (main.js still treats them as the same tablist). */
.ac-thumbs {
  display: flex;
  justify-content: center;
  align-items: flex-end;
  flex-wrap: wrap;
  gap: var(--space-sm);
  margin-bottom: var(--space-xl);
}

.ac-thumb {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2xs);
  width: 7.5rem;
  padding: var(--space-2xs) var(--space-2xs) 0;
  border: 0;
  background: none;
  cursor: pointer;
  opacity: 0.4;
  transition: opacity var(--dur-fast) var(--ease-inout);
}

.ac-thumb img {
  display: block;
  width: 100%;
  height: 3.75rem;
  object-fit: contain;
}

/* The underline is the selected-state indicator, in the same brand-light
   accent the dots used, so the row still reads at a glance which aircraft
   is current — not just which thumbnail is brightest. */
.ac-thumb::after {
  content: "";
  display: block;
  width: 1.4rem;
  height: 2px;
  border-radius: var(--radius-pill);
  background: transparent;
  transition: background var(--dur-fast) var(--ease-inout);
}

.ac-thumb:hover { opacity: 0.7; }

.ac-thumb.is-on { opacity: 1; }

.ac-thumb.is-on::after { background: var(--brand-light); }

/* --- Spec values swapping --- */
/* The numbers are the content that actually changes between aircraft, so they
   get their own beat rather than blinking. Staggered left to right via --n. */
.ac-spec__value {
  transition: opacity 200ms var(--ease-inout), transform 200ms var(--ease-inout);
  transition-delay: calc(var(--n, 0) * 35ms);
}

.ac-specs.is-swapping .ac-spec__value {
  opacity: 0;
  transform: translateY(-0.35rem);
}

/* Next sits ABOVE previous, matching the live build — its `is-bottom-next` rule
   resolves to top:0 while `is-bottom-previous` sits 120px lower. */
.ac-stage__nav {
  position: absolute;
  inset: 0 var(--gutter) auto auto;
  z-index: 3;
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  pointer-events: none;
}

.ac-stage__arrow {
  pointer-events: auto;
  display: grid;
  place-items: center;
  width: 5.25rem;
  height: 5.25rem;
  border: var(--hairline) solid var(--dark-30);
  border-radius: 50%;
  background: var(--white-70);
  backdrop-filter: blur(6px);
  color: var(--black);
  transition: background var(--dur-md) var(--ease-lux),
              border-color var(--dur-md) var(--ease-lux),
              color var(--dur-md) var(--ease-lux);
}

.ac-stage__arrow:hover {
  background: var(--brand-light);
  border-color: var(--brand-light);
  color: var(--white);
}

.ac-stage__arrow svg { width: 1.25rem; height: 1.25rem; }

/* The live slider is data-infinite="false", so the ends are hard stops. */
.ac-stage__arrow:disabled {
  opacity: 0.28;
  cursor: default;
  background: var(--white-70);
  border-color: var(--dark-15);
  color: var(--black);
}

/* Hairline-ruled spec bar: five icon specs, CTA pushed to the far end. */
.ac-specbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-lg);
  padding-block: 1.25rem;
  border-top: var(--hairline) solid var(--dark-15);
  border-bottom: var(--hairline) solid var(--dark-15);
}

/* Five fixed columns rather than a wrapping flex row: a long value like
   "7,500–8,200 nautical miles" would otherwise push Cargo Capacity onto a second
   row. Equal columns keep all five in line and let the text wrap in place, which
   is what the live bar does. */
.ac-specs {
  flex: 1;
  display: grid;
  grid-template-columns: repeat(5, minmax(0, 1fr));
  align-items: start;
  gap: var(--space-md) var(--space-lg);
  margin: 0;
}

.ac-spec {
  display: flex;
  align-items: flex-start;
  gap: 0.625rem;
  color: var(--neutral-medium);
}

.ac-spec__icon { flex: none; }

.ac-spec__icon svg {
  width: 2rem;
  height: auto;
  display: block;
}

.ac-spec__text { display: block; }

.ac-spec__label {
  margin-bottom: 0.125rem;
  font-size: var(--text-rg);
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--dark-70);
}

.ac-spec__value {
  margin: 0;
  font-size: var(--text-rg);
  font-weight: var(--w-semibold);
  color: var(--black);
  font-variant-numeric: tabular-nums;
}

.ac-specbar__cta { flex: none; }

/* --- Aircraft detail hero panel ------------------------------------------ */
.ac-hero img {
  width: 100%;
  margin-bottom: var(--space-lg);
  filter: drop-shadow(0 24px 40px rgb(0 0 0 / 0.16));
}

/* --- Program cards ------------------------------------------------------- */
.prog-cards {
  display: grid
;  grid-template-columns: repeat(auto-fit, minmax(min(15rem, 100%), 1fr));
  gap: var(--space-lg);
}

.prog-card {
  display: flex;
  flex-direction: column;
  border-top: var(--hairline) solid var(--white-15);
  padding-top: var(--space-md);
  transition: border-color var(--dur-md) var(--ease-lux);
}

.prog-card:hover { border-color: var(--brand-light); }

.prog-card__media { display: block; overflow: hidden; margin-bottom: var(--space-md); }

.prog-card__media img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-lux);
}

.prog-card:hover .prog-card__media img { transform: scale(1.05); }

.prog-card__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
  flex: 1;
}

.prog-card__body .link { margin-top: auto; padding-top: var(--space-sm); }

/* --- Category chips (program pages) ------------------------------------- */
.cat-chips {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(11rem, 100%), 1fr));
  gap: var(--space-md);
}

.cat-chip {
  display: block;
  padding: var(--space-md);
  text-align: center;
  border: var(--hairline) solid var(--dark-10);
  transition: border-color var(--dur-md) var(--ease-lux), transform var(--dur-md) var(--ease-lux);
}

.cat-chip:hover { border-color: var(--brand-light); transform: translateY(-3px); }
.cat-chip img { width: 100%; margin-bottom: var(--space-sm); }

.cat-chip__name {
  font-size: var(--text-sm);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--dark-70);
}

/* --- Numbered service grid ---------------------------------------------- */
.svc-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
  gap: var(--space-xl);
}

.svc-grid--three { grid-template-columns: repeat(3, minmax(0, 1fr)); }

.svc {
  padding-top: var(--space-md);
  border-top: var(--hairline) solid var(--dark-15);
}

.svc__num {
  display: block;
  margin-bottom: var(--space-sm);
  font-family: var(--font-accent);
  font-style: italic;
  font-size: 1.75rem;
  line-height: 1;
  color: var(--brand-light);
}

.svc h3 { margin-bottom: var(--space-2xs); }
.svc p { margin: 0; }

.section--dark .svc { border-top-color: var(--white-15); }
.section--dark .svc h3 { color: var(--white); }

/* --- Sectors ------------------------------------------------------------ */
.sectors {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
  gap: var(--space-lg);
}

.sector {
  padding-top: var(--space-sm);
  border-top: var(--hairline) solid var(--dark-15);
  font-size: var(--text-md);
  font-weight: var(--w-light);
}

.sector__num {
  display: block;
  margin-bottom: var(--space-2xs);
  font-size: var(--text-xs);
  letter-spacing: var(--track-eyebrow);
  color: var(--brand-light);
  font-variant-numeric: tabular-nums;
}

/* LUX — the brand line set large in the italic serif, centred on brand navy. */
.accent--kicker {
  font-size: var(--h3);
  line-height: 1.2;
  color: var(--white);
}

/* --- Timeline ----------------------------------------------------------- */
.timeline-wrap {
  display: grid;
  grid-template-columns: minmax(0, 1.5fr) minmax(0, 1fr);
  gap: var(--space-4xl);
  align-items: start;
}

.timeline {
  counter-reset: tl;
  border-top: var(--hairline) solid var(--white-15);
}

.tl {
  display: grid;
  grid-template-columns: 4.5rem 1fr;
  gap: var(--space-lg);
  padding-block: var(--space-lg);
  border-bottom: var(--hairline) solid var(--white-15);
}

.tl__time {
  font-family: var(--font-accent);
  font-style: italic;
  font-size: 1.75rem;
  line-height: 1;
  color: var(--brand-light);
}

.tl__body h3 { margin-bottom: var(--space-2xs); }
.tl__body p { margin: 0; }

.timeline__aside {
  position: sticky;
  top: calc(var(--nav-h) + var(--space-lg));
  padding: var(--space-xl);
  border: var(--hairline) solid var(--white-15);
}

.timeline__aside .eyebrow { margin-bottom: var(--space-sm); }
.timeline__aside .h4 { margin-bottom: var(--space-sm); }
.timeline__aside p { margin: 0; }

/* --- Comparison table --------------------------------------------------- */
/* The table is wider than a phone, so it scrolls sideways. The feature column
   is pinned: without it, scrolling to reach the later programmes carried the row
   labels off-screen and left a grid of unlabelled ticks and dashes. */
.table-scroll {
  overflow-x: auto;
  /* Fade on the right edge as a hint there is more to scroll to. */
  -webkit-overflow-scrolling: touch;
}

.compare {
  width: 100%;
  min-width: 44rem;
  /* `separate` is required for sticky cells to keep their background — with
     `collapse` the borders are owned by the table and the pinned column shows
     the scrolling content through it. */
  border-collapse: separate;
  border-spacing: 0;
  text-align: left;
}

.compare th[scope="row"],
.compare thead th:first-child {
  position: sticky;
  left: 0;
  z-index: 1;
  background: var(--neutral-lightest);
  /* Hairline on the pinned edge so the split reads as deliberate. */
  box-shadow: 1px 0 0 0 var(--dark-15);
}

/* Bottom borders move to the cells now that collapse is off. */
.compare th,
.compare td { border-bottom: var(--hairline) solid var(--dark-15); }
.compare tbody tr:last-child th,
.compare tbody tr:last-child td { border-bottom: 0; }

.compare th,
.compare td {
  padding: var(--space-md);
  vertical-align: middle;
}

.compare thead th {
  font-size: var(--text-sm);
  font-weight: var(--w-regular);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--dark-70);
  border-bottom-color: var(--dark-30);
}

.compare thead th a { transition: color var(--dur-md) var(--ease-lux); }
.compare thead th a:hover { color: var(--brand-medium); }

.compare tbody th {
  font-size: var(--text-rg);
  font-weight: var(--w-light);
}

.compare td { text-align: center; }
.tick { color: var(--brand-light); font-size: 1.125rem; }
.dash { color: var(--dark-30); }

/* --- Fleet compare/filter tool -------------------------------------------- */
.fleet-compare__controls {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: var(--space-lg);
  margin-bottom: var(--space-xl);
}

.fleet-compare__field {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  font-size: var(--text-sm);
  color: var(--dark-70);
}

.fleet-compare__field select {
  padding: var(--space-2xs) var(--space-sm);
  border: var(--hairline) solid var(--dark-30);
  border-radius: var(--radius-sm, 0.25rem);
  background: var(--white);
  color: var(--black);
  font: inherit;
  min-width: 10rem;
}

/* Aircraft/category/passengers/range are the columns a trip requirement
   actually maps to — sortable. Speed and cabin volume are supporting detail
   once you're already looking at a short-listed row, not something anyone
   filters a fleet by, so they stay plain headers. */
.fleet-compare__table thead th button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-3xs);
  font: inherit;
  letter-spacing: inherit;
  text-transform: inherit;
  color: inherit;
  cursor: pointer;
}

.fleet-compare__table thead th button::after {
  content: "";
  width: 0;
  height: 0;
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
  border-top: 5px solid currentColor;
  opacity: 0.35;
}

.fleet-compare__table thead th button[data-fx-dir="asc"]::after { transform: rotate(180deg); opacity: 1; }
.fleet-compare__table thead th button[data-fx-dir="desc"]::after { opacity: 1; }

.fleet-compare__table tbody tr[hidden] { display: none; }

.fleet-compare__empty {
  text-align: center;
  color: var(--dark-70);
  margin-top: var(--space-lg);
}

/* --- Article cards ------------------------------------------------------ */
.articles {
  display: grid;
  /* minmax(20rem, 1fr) alone forces every track to at least 20rem regardless
     of how much room is actually available -- at a 320px viewport, after the
     container's own gutter padding there's only ~280px to work with, so the
     20rem (320px) floor overflowed the page. min(20rem, 100%) caps the floor
     at whichever is smaller. */
  grid-template-columns: repeat(auto-fit, minmax(min(20rem, 100%), 1fr));
  gap: var(--space-2xl);
}

.article__media { display: block; overflow: hidden; margin-bottom: var(--space-md); }

.article__media img {
  width: 100%;
  aspect-ratio: 16 / 10;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-lux);
}

.article:hover .article__media img { transform: scale(1.04); }
.article__title { margin-bottom: var(--space-2xs); }
.article__body { margin: 0; }

/* --- Offices ------------------------------------------------------------ */
.offices {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--space-2xs);
}

.office { position: relative; margin: 0; overflow: hidden; }

.office img {
  width: 100%;
  aspect-ratio: 4 / 5;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-lux);
}

.office:hover img { transform: scale(1.05); }

.office figcaption {
  position: absolute;
  inset: auto 0 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  padding: var(--space-lg);
  background: linear-gradient(to top, rgb(0 0 0 / 0.8), transparent);
  color: var(--white);
}

.office figcaption .eyebrow { color: var(--white-70); }

/* --- Contact form ------------------------------------------------------- */
.contact {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr);
  gap: var(--space-4xl);
  align-items: start;
}

.contact__copy .h2 { margin-bottom: var(--space-md); }

.contact__meta {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(8rem, 100%), 1fr));
  gap: var(--space-lg);
  margin-top: var(--space-2xl);
}

.contact__meta dt {
  font-size: var(--text-md);
  font-weight: var(--w-light);
  margin-bottom: var(--space-3xs);
}

.contact__meta dd {
  margin: 0;
  font-size: var(--text-xs);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--dark-70);
}

.form { display: grid; gap: var(--space-md); }
.form__row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-md); }

.field { display: grid; gap: var(--space-2xs); }

/* Honeypot — off-screen, not display:none/visibility:hidden. Some spam bots
   specifically skip filling fields hidden that way, since it's the known
   honeypot tell; a field that's merely positioned off-canvas still looks
   fillable to a naive scraper while staying invisible and untabbable for a
   real visitor (tabindex="-1" beside the aria-hidden on the label). */
.field--trap {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
}

.field > span {
  font-size: var(--text-xs);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--dark-70);
}

.field input,
.field select,
.field textarea {
  width: 100%;
  padding: 0.875rem 0;
  background: transparent;
  border: 0;
  border-bottom: var(--hairline) solid var(--dark-30);
  border-radius: 0;
  font: inherit;
  color: var(--black);
  transition: border-color var(--dur-md) var(--ease-lux);
}

.field textarea { resize: vertical; }
.field input:focus,
.field select:focus,
.field textarea:focus { outline: none; border-bottom-color: var(--brand-light); }

.form .btn { justify-self: start; margin-top: var(--space-sm); }

.form__note {
  margin: 0;
  font-size: var(--text-xs);
  color: var(--dark-70);
}

.form__note--error { color: #b3261e; }
.footer .form__note--error { color: #ff6b61; }

/* Replaces the whole form on success — there's nothing left to fill in, so
   nothing left to show but the confirmation. Default colour assumes the
   contact page's light section; the footer newsletter form sits on
   .footer's dark background instead and needs the override below, same
   reason the Bloomberg band needed one there. */
.form__success {
  grid-column: 1 / -1;
  font-size: var(--text-md);
  color: var(--black);
}

.footer .form__success { color: var(--white); }

/* --- Legal -------------------------------------------------------------- */
.section--legal { padding-top: calc(var(--nav-h) + var(--section-md)); }

.prose--legal { margin-top: var(--space-2xl); color: var(--dark-70); }
.prose--legal h2 { margin-top: var(--space-2xl); margin-bottom: var(--space-sm); color: var(--black); }
.prose--legal h2:first-child { margin-top: 0; }
.prose--legal p { margin-top: var(--space-sm); line-height: 1.7; }
.prose--legal a { color: var(--brand-medium); text-decoration: underline; text-underline-offset: 0.15em; }
.prose--legal a:hover { color: var(--brand-dark); }

.legal-toc {
  margin-top: var(--space-xl);
  padding: var(--space-lg);
  border: var(--hairline) solid var(--dark-15);
  border-radius: var(--radius-md);
}

.legal-toc .eyebrow { margin-bottom: var(--space-sm); }

.legal-toc ol {
  display: grid;
  gap: var(--space-2xs) var(--space-lg);
  margin: 0;
  padding-left: 1.25rem;
}

@media (min-width: 640px) {
  .legal-toc ol { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

.legal-toc a { color: var(--brand-medium); line-height: 1.5; }
.legal-toc a:hover { color: var(--brand-dark); }

/* --- 404 ---------------------------------------------------------------- */
.nf { padding-top: calc(var(--nav-h) + var(--section-lg)); }

/* --- Responsive --------------------------------------------------------- */
@media (max-width: 991px) {

  .pillars,
  .svc-grid--three,
  .offices,
  .routes { grid-template-columns: 1fr; }

  .routes,
  .offices { gap: var(--space-2xs); }

  .fleet-row,
  .timeline-wrap,
  .contact { grid-template-columns: 1fr; gap: var(--space-2xl); }

  .timeline__aside { position: static; }

  .frames--flip { padding-left: 0; }

  .tiles--4,
  .tiles--three,
  .tiles--2 { grid-template-columns: 1fr; }

  .ticks--grid { grid-template-columns: 1fr; }

  .route img,
  .office img { aspect-ratio: 16 / 10; }

  .rail__item { width: min(30rem, 86vw); }
}

@media (max-width: 991px) {
  .cat-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); }

  /* No room to flank the aircraft at this width, so the arrows drop below it and
     the aircraft lifts to clear them. The offset goes on the photo, not the
     slide: slides are flex items now, so `bottom` on the slide would shift the
     whole thing up and the viewport would clip the name off the top. */
  .ac-stage { height: clamp(20rem, 56vw, 26rem); }
  /* On the wrapper, not the <img> — the image is a plain in-flow child now, and
     the wrapper is what is absolutely positioned. */
  .ac-slide__jet { bottom: 5rem; }
  .ac-stage__arrow { width: 3.5rem; height: 3.5rem; }

  /* The horizontal white fade eats too much of a narrow viewport. */
  .ac-sky::after {
    background: linear-gradient(to bottom, var(--white) 0%, rgb(255 255 255 / 0) 14%,
                                rgb(255 255 255 / 0) 84%, var(--white) 100%);
  }

  .ac-stage__nav {
    inset: auto 0 0;
    flex-direction: row;
    justify-content: center;
    gap: var(--space-2xs);
  }

  /* The column layout on desktop puts next above previous on purpose, but in
     a row that DOM order reads as next-then-previous left to right — the
     wrong way round. Reorder visually only, so IDs/JS are untouched. */
  #fleetPrev { order: -1; }

  .ac-specbar { flex-direction: column; align-items: flex-start; gap: var(--space-lg); }
  .ac-specs { grid-template-columns: repeat(2, minmax(0, 1fr)); width: 100%; }
  .ac-specbar__cta { width: 100%; }
}

@media (max-width: 767px) {
  .form__row { grid-template-columns: 1fr; }
  .cat-cards { grid-template-columns: 1fr; }
  /* Shorter in portrait, and the reason is framing rather than taste. These
     photographs are 3:2. object-fit:cover shows a slice whose width is
     (box aspect / 1.5) of the frame — at 72svh on a 402x738 phone that is a
     0.76 box and about half the picture, which cuts the nose and tail off every
     side-profile aircraft. At 52svh the box is ~1.05 and roughly 70% of the
     frame survives, which is the difference between a whole aeroplane and a
     length of fuselage. Desktop keeps its full-height heroes. */
  .hero--photo { min-height: 50svh; }
  .hero--photo.hero--tall { min-height: 52svh; }

  /* kenburns has to go with it. It scales to 1.12, so it was cropping a further
     12% on top of the above — and on a subject that is already touching the
     frame edges, a slow push-in reads as the aeroplane drifting out of shot
     rather than as movement. */
  .hero--photo .hero__media img { animation: none; }
  .rail__nav { justify-content: center; }

  /* Seven thumbnails wrapped to three cramped rows at this width — a swipeable
     single row (the same pattern as .rail elsewhere) reads far better. */
  .ac-thumbs {
    flex-wrap: nowrap;
    justify-content: flex-start;
    overflow-x: auto;
    scrollbar-width: none;
    -webkit-overflow-scrolling: touch;
  }
  .ac-thumbs::-webkit-scrollbar { display: none; }
  .ac-thumb { flex: 0 0 auto; }
}

/* Confirmation that quote-bar details survived the page change. */
.form__prefill {
  margin: 0;
  padding: var(--space-sm) var(--space-md);
  border-left: 2px solid var(--brand-light);
  background: var(--dark-05);
  font-size: var(--text-sm);
  color: var(--dark-70);
}

/* --- Instagram Reels ----------------------------------------------------- */
.ig__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-lg);
  margin-bottom: var(--section-sm);
}

.ig__head .section-head { margin-bottom: 0; }
.ig__follow { flex: none; }

/* Four portrait tiles across. An explicit column count rather than auto-fit,
   which packed them unevenly and orphaned the last one. */
.ig__grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-2xs);
}

/* The whole tile is the link — the loop is a preview, not a player, so there are
   no controls to fight with and one tap goes straight to the real reel. */
.ig-reel {
  position: relative;
  display: block;
  overflow: hidden;
  background: var(--neutral-dark);
}

/* 9:16 — reels are portrait; anything else letterboxes them. */
.ig-reel__media {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 9 / 16;
}

.ig-reel__media video,
.ig-reel__media img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The poster sits above the video until the loop is actually playing, so a tile
   is never blank while the file is still being fetched. */
.ig-reel__media img {
  z-index: 1;
  transition: opacity var(--dur-md) var(--ease-lux);
}

.ig-reel.is-playing .ig-reel__media img { opacity: 0; }

.ig-reel::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 2;
  background: linear-gradient(to top, rgb(0 0 0 / 0.78), rgb(0 0 0 / 0.1) 46%, transparent);
  pointer-events: none;
}

.ig-reel__media video,
.ig-reel__media img { transform: scale(1.001); transition: transform var(--dur-slow) var(--ease-lux), opacity var(--dur-md) var(--ease-lux); }
.ig-reel:hover .ig-reel__media video,
.ig-reel:hover .ig-reel__media img { transform: scale(1.05); }

.ig-reel__meta {
  position: absolute;
  inset: auto 0 0;
  z-index: 3;
  display: block;
  padding: var(--space-md);
  color: var(--white);
}

.ig-reel__caption {
  display: block;
  font-size: var(--text-sm);
  line-height: 1.4;
}

/* LUX — the prompt to leave the site stays hidden until the tile is engaged. */
.ig-reel__cue {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  margin-top: var(--space-2xs);
  font-size: var(--text-xs);
  letter-spacing: var(--track-eyebrow);
  text-transform: uppercase;
  color: var(--white-70);
  opacity: 0;
  transform: translateY(0.35rem);
  transition: opacity var(--dur-md) var(--ease-lux), transform var(--dur-md) var(--ease-lux);
}

.ig-reel:hover .ig-reel__cue,
.ig-reel:focus-visible .ig-reel__cue { opacity: 1; transform: none; }

.ig__empty {
  margin: 0;
  padding: var(--space-lg);
  border: var(--hairline) dashed var(--white-30);
  font-size: var(--text-sm);
  color: var(--white-70);
}

.section--bone .ig__empty { border-color: var(--dark-30); color: var(--dark-70); }
.ig__empty code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.9em; }

/* Four portrait reels across a tablet gave 171px-wide columns at 768px. Two is
   the readable minimum for 9:16 video, so the switch happens for the whole
   tablet band rather than only on phones. */
@media (max-width: 991px) {
  .ig__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 767px) {
  .ig__head { flex-direction: column; align-items: flex-start; }
  .ig-reel__caption { font-size: var(--text-xs); }
  .ig-reel__meta { padding: var(--space-sm); }
  /* No hover on touch, so the cue is always shown. */
  .ig-reel__cue { opacity: 1; transform: none; }
}

/* --- Bloomberg interview: click-to-play video card ---------------------- */
/* The card is a link until main.js upgrades it, so everything here has to read
   correctly as a link first and as a player second. */

.vidcard {
  display: block;
  position: relative;
  text-decoration: none;
  color: inherit;
}

.vidcard__media {
  display: block;
  position: relative;
  overflow: hidden;
  aspect-ratio: 16 / 9;
  background: var(--black);
}

.vidcard__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform var(--dur-slow) var(--ease-lux),
              opacity var(--dur-md) var(--ease-inout);
}

.vidcard:hover .vidcard__media img,
.vidcard:focus-visible .vidcard__media img { transform: scale(1.03); }

/* Play button. A ring plus a triangle built from a single border trick, so no
   SVG request and no icon font. */
.vidcard__play {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 5.5rem;
  height: 5.5rem;
  display: grid;
  place-items: center;
  border: var(--hairline) solid var(--white-70);
  border-radius: var(--radius-pill);
  background: rgb(0 0 0 / 0.25);
  backdrop-filter: blur(2px);
  transition: background var(--dur-fast) var(--ease-inout),
              border-color var(--dur-fast) var(--ease-inout),
              transform var(--dur-md) var(--ease-lux);
}

.vidcard__tri {
  width: 0;
  height: 0;
  /* Optically centred: the triangle's mass sits left of its box centre. */
  margin-left: 0.35rem;
  border-top: 0.5rem solid transparent;
  border-bottom: 0.5rem solid transparent;
  border-left: 0.85rem solid var(--white);
}

.vidcard:hover .vidcard__play,
.vidcard:focus-visible .vidcard__play {
  background: var(--brand-light);
  border-color: var(--brand-light);
  transform: scale(1.08);
}

.vidcard__time {
  position: absolute;
  right: var(--space-sm);
  bottom: var(--space-sm);
  padding: 0.2rem var(--space-2xs);
  background: rgb(0 0 0 / 0.7);
  color: var(--white);
  font-size: var(--text-xs);
  letter-spacing: 0.04em;
  font-variant-numeric: tabular-nums;
}

.vidcard--sm .vidcard__play { width: 3.25rem; height: 3.25rem; }
.vidcard--sm .vidcard__tri {
  margin-left: 0.25rem;
  border-top-width: 0.35rem;
  border-bottom-width: 0.35rem;
  border-left-width: 0.6rem;
}

/* The iframe main.js swaps in. It replaces the <img>/play/time inside
   .vidcard__media, so the 16/9 box and background are already correct. */
.vidcard__media iframe {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  border: 0;
}

/* --- Media page feature block --- */
/* Deliberately wider than the container it sits in: the player breaks out to
   near-full-bleed so the interview reads as the page's main event, not an inset
   figure. The 4vw inset keeps a sliver of margin at every width rather than
   running hard into the screen edge. */
.videofeat__stage {
  width: calc(100vw - 8vw);
  max-width: 110rem;
  margin-inline: calc(50% - 50vw + 4vw);
}

/* The container's own gutter is already gone at this width, so put the caption
   back in line with the player rather than the text above it. */
.videofeat__caption { padding-inline: var(--space-3xs); }

.videofeat__caption {
  display: flex;
  flex-direction: column;
  gap: var(--space-3xs);
  margin-top: var(--space-md);
  font-size: var(--text-sm);
}

.videofeat__caption strong { color: var(--white); letter-spacing: 0.02em; }
.videofeat__caption span { color: var(--white-70); }
.section--bone .videofeat__caption strong { color: var(--black); }
.section--bone .videofeat__caption span { color: var(--dark-70); }

/* --- Home page band, sitting under the As Seen On strip --- */
.bloomberg-band__inner {
  display: grid;
  /* The body column was 1fr, stretching to fill whatever the container had
     left over. Grid tracks pack to the start by default with no 1fr track
     in the mix, so switching it to a fixed width was the right idea — but
     30rem + 26rem is, on its own, already most of a typical viewport, so
     the row still reached almost to the right edge on its own content's
     account, not because anything was stretching it. Narrower columns,
     not a width/margin fix on the row itself, are what actually move it
     left. */
  grid-template-columns: minmax(0, 22rem) minmax(0, 18rem) auto;
  align-items: center;
  gap: var(--space-lg);
  padding-top: var(--space-xl);
  border-top: var(--hairline) solid var(--dark-15);
}

.bloomberg-band__title { margin: 0 0 var(--space-3xs); }
.bloomberg-band__body p { margin: 0; }
.bloomberg-band__body .eyebrow { margin-bottom: var(--space-2xs); }

.bloomberg-band__cue {
  white-space: nowrap;
  font-size: var(--text-sm);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--brand-medium);
}

.bloomberg-band__inner:hover .bloomberg-band__cue .link-arrow { transform: translateX(0.35rem); }

/* The thumbnail is large enough that the three-column row only works on a wide
   screen. Below that it starves the copy column and the title wraps to three
   lines, so drop the cue first, then stack. */
@media (max-width: 1199px) {
  .bloomberg-band__inner {
    grid-template-columns: minmax(0, 24rem) minmax(0, 1fr);
    gap: var(--space-lg);
  }
  .bloomberg-band__cue { display: none; }
}

@media (max-width: 767px) {
  .bloomberg-band__inner {
    grid-template-columns: minmax(0, 1fr);
    gap: var(--space-md);
  }
  .vidcard--sm { max-width: 28rem; }
}

/* Article cards that are links */
.article--link { display: block; text-decoration: none; color: inherit; }

.article__cue {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  margin-top: var(--space-xs);
  font-size: var(--text-sm);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--brand-medium);
}

.article--link:hover .article__media img { transform: scale(1.04); }
.article--link:hover .article__cue .link-arrow { transform: translateX(0.35rem); }

/* Once playing, the card is no longer interactive chrome. */
.vidcard.is-playing { cursor: default; }
.vidcard.is-playing .vidcard__media img { transform: none; }

/* The caveat under any spec table. Deliberately quiet — it has to be readable
   without competing with the figures it qualifies. */
.spec-note {
  max-width: 44rem;
  margin: var(--space-md) 0 0;
  font-size: var(--text-sm);
  line-height: 1.5;
  color: var(--dark-70);
}

.specs--light + .spec-note,
.section--dark .spec-note { color: var(--white-50); }

/* Press item date. Sits above the headline, where a wire story would carry it. */
.article__date {
  display: block;
  margin-bottom: var(--space-2xs);
  font-size: var(--text-xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--dark-70);
}


/* --- Tablet band ---------------------------------------------------------- */
/* The journey tiles collapsed straight from three columns to one, so at 1112px
   each tile was 1023px wide and 420px tall — three of those is a lot of scroll
   for very little information. Two across uses the width the band actually has. */
@media (min-width: 768px) and (max-width: 1150px) {
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  /* An odd last tile spans the row rather than leaving a hole beside it. */
  .tiles > :last-child:nth-child(odd) { grid-column: 1 / -1; }
}

/* ==========================================================================
   Closer-look lightbox
   Opens a gallery image at full size. The rails cap images at 46rem and now
   drift on their own, so studying an aircraft needed somewhere still to do it.
   ========================================================================== */

.rail__zoom {
  display: block;
  width: 100%;
  padding: 0;
  border: 0;
  background: none;
  cursor: zoom-in;
  /* The rail is a scroll container; a focus ring drawn outside the button
     would be clipped at the rail's edge, so it is inset instead. */
  outline-offset: -3px;
}

.lightbox {
  position: fixed;
  inset: 0;
  z-index: var(--z-modal);
  display: grid;
  place-items: center;
  padding: clamp(1rem, 4vw, 3rem);
  background: rgb(8 10 14 / 0.94);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--dur-md) var(--ease-inout), visibility var(--dur-md);
}

.lightbox.is-open { opacity: 1; visibility: visible; }

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

.lightbox__img {
  max-width: 100%;
  /* Leaves room for the caption below without the image ever overflowing. */
  max-height: calc(100vh - 9rem);
  /* iOS Safari's 100vh ignores its own toolbars, so the tall-image case ran
     under them. dvh tracks the actually-visible height. */
  max-height: calc(100dvh - 9rem);
  object-fit: contain;
  border-radius: 4px;
}

.lightbox__cap {
  position: absolute;
  bottom: clamp(1rem, 3vw, 2rem);
  left: 50%;
  transform: translateX(-50%);
  font-size: 0.85rem;
  letter-spacing: 0.04em;
  color: rgb(255 255 255 / 0.62);
}

.lightbox__close,
.lightbox__nav {
  position: absolute;
  display: grid;
  place-items: center;
  width: 3rem;
  height: 3rem;
  border: 1px solid rgb(255 255 255 / 0.22);
  border-radius: 50%;
  background: rgb(255 255 255 / 0.06);
  color: #fff;
  cursor: pointer;
  transition: background var(--dur-md) var(--ease-lux), border-color var(--dur-md) var(--ease-lux);
}

.lightbox__close:hover,
.lightbox__nav:hover {
  background: rgb(255 255 255 / 0.14);
  border-color: rgb(255 255 255 / 0.4);
}

.lightbox__close { top: clamp(1rem, 3vw, 2rem); right: clamp(1rem, 3vw, 2rem); font-size: 1.1rem; }
.lightbox__nav--prev { left: clamp(0.5rem, 2vw, 2rem); }
.lightbox__nav--next { right: clamp(0.5rem, 2vw, 2rem); }
.lightbox__nav[disabled] { opacity: 0.3; cursor: default; }

@media (max-width: 40rem) {
  /* Bottom-anchored on phones so both arrows sit in the thumb zone — plus the
     home-indicator inset, or they land under it. */
  .lightbox__nav { bottom: calc(clamp(1rem, 3vw, 2rem) + env(safe-area-inset-bottom, 0px)); top: auto; }
  .lightbox__cap { display: none; }
}

/* ==========================================================================
   Fleet explorer
   Replaces the bordered aircraft cards. No boxes: each aircraft sits on a soft
   wash with the silhouette given real room, which is the same treatment the
   home page's stage uses — the boxes read as a spec sheet rather than a fleet.
   ========================================================================== */

.fx__tabs {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs) var(--space-xs);
  margin: var(--space-lg) 0 var(--space-2xl);
}

.fx__tab {
  padding: 0.55rem 1.1rem;
  font-size: var(--text-sm);
  letter-spacing: 0.04em;
  color: var(--dark-70);
  text-decoration: none;
  border: var(--hairline) solid var(--dark-10);
  border-radius: 99px;
  white-space: nowrap;
  transition: color var(--dur-md) var(--ease-lux),
              border-color var(--dur-md) var(--ease-lux),
              background var(--dur-md) var(--ease-lux);
}

.fx__tab:hover { color: var(--dark); border-color: var(--dark-30, var(--dark-10)); }

.fx__tab.is-on {
  color: var(--white);
  background: var(--brand-dark);
  border-color: var(--brand-dark);
}

/* auto-FILL with a hard 24rem cap, not auto-fit with 1fr. auto-fit collapses
   the empty tracks, so the Light Jet class — which has a single aircraft — got
   one 1232px-wide track and rendered a silhouette 1232px across in a 144px
   band. The cap is what keeps a lone aircraft the same size as one of four.
   Same pattern the cards it replaced used, for the same reason. */
.fx__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(17rem, 100%), 24rem));
  justify-content: center;
  gap: var(--space-xl) var(--space-lg);
}

.fx__item {
  display: flex;
  flex-direction: column;
  text-decoration: none;
  color: inherit;
}

/* Nothing is clipped. An earlier version fixed the band's height and absolutely
   positioned the jet at 116% width with overflow:hidden, which cropped nose and
   wingtips. The image is now a normal block at its own aspect, so the whole
   aircraft is always visible; the cutouts all share ~3.4:1 proportions, so every
   jet in a class still lands on the same baseline without being forced. */
/* No panel behind the aircraft. A filled wash — even a gradient one — still
   draws a rectangle with visible top and side edges, which is the boxed look
   this was meant to get away from. Grounding comes from an elliptical contact
   shadow under the fuselage instead: it fades out on every side, so there is no
   edge anywhere for the eye to read as a container. */
.fx__jet {
  display: block;
  position: relative;
  padding: var(--space-md) 0 var(--space-sm);
  margin-bottom: var(--space-sm);
}

/* Sits behind the jet via z-index rather than a negative one, which would punch
   it through the link's own stacking context and out the back of the section. */
.fx__jet::after {
  content: '';
  position: absolute;
  z-index: 0;
  left: 14%;
  right: 14%;
  bottom: calc(var(--space-sm) - 2px);
  height: 16px;
  background: radial-gradient(50% 50% at 50% 50%,
              rgb(0 0 0 / 0.18) 0%, rgb(0 0 0 / 0) 70%);
  transition: opacity var(--dur-slow) var(--ease-glide),
              transform var(--dur-slow) var(--ease-glide);
}

.fx__jet img {
  display: block;
  position: relative;
  z-index: 1;
  width: 100%;
  filter: drop-shadow(0 10px 16px rgb(0 0 0 / 0.10));
  transition: transform var(--dur-slow) var(--ease-glide),
              filter var(--dur-slow) var(--ease-glide);
}

/* The jet lifts and its shadow spreads and softens — the two together are what
   make it read as leaving the ground rather than sliding on it. */
.fx__item:hover .fx__jet::after { transform: scaleX(1.06); opacity: 0.72; }

.fx__item:hover .fx__jet img { transform: translateY(-5px) scale(1.03); }

.fx__name { font-size: var(--text-h4, 1.35rem); margin-bottom: var(--space-3xs); }

.fx__meta {
  font-size: var(--text-sm);
  color: var(--dark-70);
  font-variant-numeric: tabular-nums;
  margin-bottom: var(--space-xs);
}

.fx__cue { font-size: var(--text-sm); color: var(--brand); }
.fx__cue .link-arrow { display: inline-block; transition: transform var(--dur-md) var(--ease-lux); }
.fx__item:hover .fx__cue .link-arrow { transform: translateX(4px); }

/* --- Class swap -----------------------------------------------------------
   Two-part move, so a class change reads as aircraft leaving and new ones
   arriving rather than a list redrawing. Outgoing items sink and fade together;
   incoming ones rise, and their jets glide in from the left out of a slight
   blur — the same "a jet crossing the frame" idea as the home page stage.
   main.js drives both by adding a class and releasing it next frame. */
.js .fx__item {
  transition: opacity var(--dur-slow) var(--ease-lux),
              transform var(--dur-slow) var(--ease-lux);
}

.fx__grid.is-leaving .fx__item {
  opacity: 0;
  transform: translateY(14px) scale(0.985);
  transition-duration: var(--dur-md);
  transition-delay: 0ms;
}

.fx__item.fx-in { opacity: 0; transform: translateY(18px); }

.fx__item.fx-in .fx__jet img {
  transform: translateX(-26px);
  filter: blur(3px) drop-shadow(0 18px 28px rgb(0 0 0 / 0));
}

@media (prefers-reduced-motion: reduce) {
  .js .fx__item,
  .fx__jet img,
  .fx__jet::after,
  .fx__cue .link-arrow { transition: none; }
  .fx__item:hover .fx__jet img { transform: none; }
  .fx__item:hover .fx__jet::after { transform: none; opacity: 1; }
  .fx__item.fx-in { opacity: 1; transform: none; }
  .fx__item.fx-in .fx__jet img { transform: none; filter: none; }
}
