/*
 * Refinements on the ported design.
 * Kept separate from style.css so each change stays reviewable.
 */

/* ==========================================================================
   Partner logos — noticeably larger
   ========================================================================== */

.partners__cell {
  aspect-ratio: 16 / 10;
  padding: clamp(14px, 1.4vw, 22px);
}

.partners__cell img {
  width: 100%;
  max-height: clamp(52px, 5.2vw, 84px);
  object-fit: contain;
  opacity: 1;
}

/* ==========================================================================
   Steps — smooth open
   The panel itself swaps display (it has to, for the flex-basis rail
   animation), so the content inside is what carries the motion.
   ========================================================================== */

@keyframes st-step-in {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

.step.is-open .step__panel > * {
  animation: st-step-in .55s cubic-bezier(.2, .7, .3, 1) both;
}

.step.is-open .step__panel > :nth-child(1) { animation-delay: .10s; }
.step.is-open .step__panel > :nth-child(2) { animation-delay: .17s; }
.step.is-open .step__panel > :nth-child(3) { animation-delay: .24s; }

/* Closed rails get a clearer affordance */
.step:not(.is-open) .step__spine { transition: background-color .3s ease; }
.step:not(.is-open):hover { background: rgba(255, 255, 255, .09); }

@media (prefers-reduced-motion: reduce) {
  .step.is-open .step__panel > * { animation: none; }
}

/* ==========================================================================
   Reviews — soft edge on the scroller, plus arrows
   The track bleeds past the section edge, so a hard cut looked like a crop.
   A mask fades the last card into the ground instead.
   ========================================================================== */

.reviews__track {
  --st-fade: clamp(28px, 5vw, 72px);
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--st-fade), #000 calc(100% - var(--st-fade)), transparent 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 var(--st-fade), #000 calc(100% - var(--st-fade)), transparent 100%);
  padding-inline: 2px;
}

/* At the very start there is nothing to the left to fade into. */
.reviews__track.is-at-start {
  -webkit-mask-image: linear-gradient(to right, #000 0, #000 calc(100% - var(--st-fade)), transparent 100%);
  mask-image: linear-gradient(to right, #000 0, #000 calc(100% - var(--st-fade)), transparent 100%);
}

.reviews__track.is-at-end {
  -webkit-mask-image: linear-gradient(to right, transparent 0, #000 var(--st-fade), #000 100%);
  mask-image: linear-gradient(to right, transparent 0, #000 var(--st-fade), #000 100%);
}

.reviews__track.is-at-start.is-at-end {
  -webkit-mask-image: none;
  mask-image: none;
}

.reviews__arrows {
  margin-top: clamp(18px, 2vw, 28px);
}

/* ==========================================================================
   FAQ — the whole card is the hit area
   ========================================================================== */

.faq__item { cursor: pointer; }
.faq__item.is-open { cursor: default; }
.faq__item:not(.is-open):hover { background: var(--bronze-25); }

.faq__q { cursor: pointer; }
.faq__item.is-open .faq__q { cursor: default; }

/* The button no longer needs to be the only target, but it must still show
   focus for keyboard users. */
.faq__q:focus-visible {
  outline: 2px solid var(--bronze);
  outline-offset: -4px;
}

/* ==========================================================================
   Hero type
   The ported scale capped the headline at 3.95rem, so past roughly 1400px the
   photograph kept growing while the words did not — leaving them stranded in
   the bottom-left corner. The cap is now high enough that the type keeps pace
   with the image, and the headline column is wide enough to carry it.
   ========================================================================== */

.hero__content {
  grid-template-columns: minmax(0, 1.5fr) minmax(0, .6fr);
  gap: clamp(18px, 3.4vw, 56px);
  padding-right: calc(var(--gutter) + clamp(0px, 2.5vw, 44px));
  padding-bottom: clamp(96px, 12vh, 150px);
}

.hero__title {
  /* Conservative no-JS fallback. The fitter in main.js replaces this with a
     measured value that fills the column; until then it must be small enough
     to never clip, and it is allowed to wrap. */
  font-size: clamp(2.3rem, 5.4vw, 6rem);
  line-height: .97;
  letter-spacing: -.038em;
}

/* Once measured, the lines are guaranteed to fit, so they can stop wrapping. */
.hero__title.is-fitted { text-wrap: nowrap; white-space: nowrap; }

.hero__lede {
  max-width: 38ch;
  /* Eases off as the viewport widens, matching the headline: the vw term keeps
     it growing, the ceiling stops it becoming oversized body copy. */
  font-size: clamp(1rem, 0.62vw + 0.62rem, 1.22rem);
  line-height: 1.6;
  margin-bottom: .5em;
}

/* Below the two-column breakpoint the lede sits under the headline, so it can
   have the full measure back. */
@media (max-width: 900px) {
  .hero__content {
    grid-template-columns: minmax(0, 1fr);
    padding-bottom: clamp(84px, 14vh, 120px);
  }
  .hero__title { font-size: clamp(2.4rem, 11vw, 4.4rem); }
  .hero__lede { max-width: 46ch; font-size: clamp(1rem, 3.4vw, 1.08rem); }
}

/* Very wide screens: hold the line length, keep the block off the far edge. */
@media (min-width: 1800px) {
  .hero__content {
    grid-template-columns: minmax(0, 1.45fr) minmax(0, .55fr);
    padding-inline: clamp(76px, 5vw, 130px);
  }
}

/* ==========================================================================
   Hero scrim — colour grade
   The ported scrim was three flat passes of near-black. It darkened the
   photograph without putting any colour into it, so the hero read as grey
   film over a warm image. This is deeper than before, but the shadow is
   indigo rather than neutral and warm light is allowed to bloom through the
   top half. Shadow and highlight now disagree in hue, which is the whole
   reason it stops looking like a grey wash.

   Order matters: the blooms are painted on the element itself and the
   darkening pass sits above them on ::after. That way raising contrast for
   the headline never costs the colour — the two are not competing for the
   same layer.
   ========================================================================== */

.hero__scrim {
  background:
    /* champagne — top-right, the brightest point */
    radial-gradient(96% 74% at 90% 6%,    rgba(247, 235, 155, .40) 0%, rgba(247, 235, 155, 0) 58%),
    /* saffron — the warm core of the bloom */
    radial-gradient(82% 60% at 64% 20%,   rgba(230, 126, 42, .44) 0%,  rgba(230, 126, 42, 0) 62%),
    /* bronze — carries the brand hue across the top-left */
    radial-gradient(70% 54% at 24% 6%,    rgba(183, 131, 63, .34) 0%,  rgba(183, 131, 63, 0) 60%),
    /* bougainvillea — a cooler counterpoint in the bottom-right corner */
    radial-gradient(78% 58% at 99% 84%,   rgba(168, 58, 98, .30) 0%,   rgba(168, 58, 98, 0) 62%),
    /* Majorelle blue — tints the deepest shadow under the headline */
    radial-gradient(130% 104% at 2% 100%, rgba(31, 98, 140, .50) 0%,   rgba(31, 98, 140, 0) 66%);
}

.hero__scrim::after {
  content: "";
  position: absolute;
  inset: 0;
  /* Bottom-left resolves to ~90% coverage (was ~84%), so the headline gains
     contrast even though the image behind it got more colourful. */
  background:
    linear-gradient(to top,    rgba(12, 15, 30, .80) 0%, rgba(12, 15, 30, .40) 46%, rgba(12, 15, 30, .04) 76%),
    linear-gradient(to right,  rgba(12, 15, 30, .52) 0%, rgba(12, 15, 30, .10) 58%, transparent 80%),
    linear-gradient(to bottom, rgba(12, 15, 30, .40) 0%, transparent 30%);
}

/* ==========================================================================
   Large screens — let the page keep growing past 1400px
   --------------------------------------------------------------------------
   An audit of style.css turned up ~410 declarations that stop responding
   somewhere between 1220px and 1650px of viewport. Two things follow from
   that: five section wrappers freeze at max-width 1360px, so a 2560px screen
   renders the whole site in a 1360px strip with 1200px of dead margin; and
   every rem-based clamp hits its ceiling at roughly the same point, so the
   type stops growing while the screen does not. Together that is why the
   page reads tiny on a large monitor.

   Patching 410 declarations individually would be both enormous and fragile,
   and it would break the relationships between them — headings lifted, their
   sibling body copy not. Most of those ceilings are expressed in rem, so
   scaling the root instead lifts them proportionally in one move and keeps
   the existing rhythm intact. What is left after that is the handful of
   values written in px, which the root cannot reach; those are listed out
   below individually.

   Everything is inside min-width:1400px on purpose. Below that boundary not
   one declaration here applies, so every layout already signed off at phone,
   tablet and laptop width renders exactly as it did before.
   ========================================================================== */

@media (min-width: 1400px) {

  /* --- the systemic half: scale the root -------------------------------- */

  /* 16px at 1400 (continuous with the old value — no jump at the boundary),
     easing to 20px by ~2360. Every rem-based clamp ceiling in the theme
     rises with it. Media queries are unaffected: they resolve against the
     initial 16px, not this, so no breakpoint moves. */
  :root { font-size: clamp(16px, 0.42vw + 10.1px, 20px); }

  /* style.css pins this to 16px, which would otherwise anchor all inherited
     text below the root and cancel the line above. */
  body { font-size: 1rem; }

  /* --- content width ---------------------------------------------------- */

  /* The dead-margin fix. Safe to widen because every prose block in the
     theme already carries its own ch-based measure (.services__text 42ch,
     .steps__text 48ch, .faq__a 56ch and so on), so line length stays put and
     only the grids — cards, experiences, partners, proof — actually spread. */
  .services__inner,
  .experiences__inner,
  .steps__inner,
  .proof__inner,
  .cta-band__inner,
  .sec-inner { max-width: clamp(1360px, 88vw, 1800px); }

  .st-container { max-width: clamp(1180px, 82vw, 1560px); }
  .st-archive .st-container { max-width: clamp(1240px, 84vw, 1640px); }

  /* --- the hero is signed off: hold it exactly where it is -------------- */

  /* Both were rem, so the root change above would have quietly resized them.
     Restated in px to reproduce today's rendering at every width. The JS
     fitter still owns the title once it runs; this is only the fallback. */
  .hero__title { font-size: clamp(36.8px, 5.4vw, 96px); }
  .hero__lede  { font-size: clamp(16px, 0.62vw + 9.92px, 19.52px); }

  /* --- the px half: values the root cannot reach ------------------------ */

  /* Nav pill. At 2560 it stretched to ~2440px wide while staying 84px tall
     and holding 15.5px links — a very elongated bar full of small text. */
  .nav { min-height: clamp(84px, 4.6vw + 19.6px, 104px); }
  .nav__links a { font-size: clamp(15.5px, 0.72vw + 5.4px, 19px); }
  .brand__logo { height: clamp(56px, 2.2vw + 25.2px, 70px); }

  /* Buttons — px font and px padding, so they were frozen outright. */
  .btn--light,
  .btn--dark,
  .btn--ghost,
  .btn--accent { font-size: clamp(17px, 0.6vw + 8.6px, 20px); }

  .btn--light,
  .btn--dark { padding: clamp(19px, 0.5vw + 12px, 24px) clamp(36px, 1vw + 22px, 48px); }

  .btn__icon { width: clamp(42px, 1.1vw + 26.6px, 54px); height: clamp(42px, 1.1vw + 26.6px, 54px); }

  /* Partner logos. The ceiling here is the one set earlier in this file
     (52→84px), not style.css's original 34px — picked up from 84px so the
     boundary stays continuous. */
  .partners__cell img { max-height: clamp(72.8px, 2.2vw + 42px, 96px); }

  /* Steps accordion. The band height and the closed rails are both hard px,
     so on a wide screen three thin slivers sat beside one very wide panel.
     The rail width appears twice in style.css and the two must move together
     or the closed and open rails desynchronise. */
  .steps__accordion { height: clamp(540px, 14vw + 344px, 700px); }
  .step { flex: 0 0 clamp(96px, 2vw + 68px, 132px); }
  .step.is-open .step__spine { width: clamp(96px, 2vw + 68px, 132px); }

  /* FAQ — same shape of problem, and the shortest frozen band on the page. */
  .faq__accordion { height: clamp(400px, 11vw + 246px, 520px); }
  .faq__item { flex: 0 0 clamp(240px, 6vw + 156px, 320px); }

  /* Review cards sized off a percentage of a track that was itself frozen,
     so they never even reached their own 308px ceiling. */
  .review { flex: 0 0 clamp(308px, 32%, 420px); }

  /* Form controls: the text is 1rem so it rides the root, but the padding is
     px and would have tightened around it as the text grew. */
  .field input,
  .field select,
  .field textarea { padding: clamp(14px, 0.4vw + 8.4px, 18px) clamp(16px, 0.45vw + 9.7px, 21px); }

  .quotebar__field input,
  .quotebar__field select { padding: clamp(13px, 0.4vw + 7.4px, 17px) clamp(14px, 0.4vw + 8.4px, 18px); }

  /* --- transfers wizard + site forms ------------------------------------ */

  /* Most of _wizard.css and _forms.css is written in rem and rode the root up
     on its own — .st-btn, the step labels, the field labels all scaled. The
     field controls did not: they are pinned to 16px so that iOS does not zoom
     on focus. Left alone that inverted the pairing, putting a 17.6px label
     above a 16px input. Restated here to track the root instead. The iOS
     reason still holds where it matters, because none of this applies below
     1400px, which no phone reaches. */
  /* The [type] in these selectors is doing real work, not decoration.
     _forms.css styles the text inputs as `.st-field input[type="text"]`, and
     an attribute selector counts toward specificity — that rule scores
     (0,2,1), so a plain `.st-field input` at (0,1,1) loses to it no matter
     how late it is loaded. Matching the attribute matches the score, and
     source order then settles it. The bare `.st-field input` line stays as a
     floor for any input rendered without an explicit type. */
  .st-field input,
  .st-field input[type],
  .st-field select,
  .st-field textarea,
  .st-place__control input,
  .st-place__control input[type],
  .st-picker__trigger { font-size: clamp(16px, 0.42vw + 10.1px, 20px); }

  /* textarea is deliberately min-height:auto in _forms.css — left out. */
  .st-field input,
  .st-field input[type],
  .st-field select,
  .st-place__control input,
  .st-place__control input[type],
  .st-picker__trigger { min-height: clamp(48px, 0.9vw + 35.4px, 60px); }

  .st-field input,
  .st-field input[type],
  .st-field select,
  .st-field textarea,
  .st-picker__trigger { padding: clamp(12px, 0.35vw + 7.1px, 16px) clamp(14px, 0.4vw + 8.4px, 18px); }

  /* Keeps the pin icon clear of the text in the location fields. */
  .st-place__control input,
  .st-place__control input[type] { padding-left: clamp(40px, 0.7vw + 30.2px, 48px); }

  .st-btn { padding: clamp(13px, 0.35vw + 8.1px, 17px) clamp(26px, 0.7vw + 16.2px, 34px); }

  .st-form__actions .st-btn,
  .st-wizard__nav .st-btn {
    min-height: clamp(50px, 0.9vw + 37.4px, 62px);
    padding-inline: clamp(30px, 0.8vw + 18.8px, 40px);
  }

  .st-steps__dot {
    width: clamp(40px, 0.8vw + 28.8px, 50px);
    height: clamp(40px, 0.8vw + 28.8px, 50px);
  }

  .st-place__list { max-height: clamp(268px, 6vw + 184px, 340px); }

  .st-fieldset,
  .st-mapcard { padding: clamp(24px, 0.9vw + 11.4px, 34px); }
}

/* ==========================================================================
   Page ground — warm parchment instead of near-white
   --------------------------------------------------------------------------
   --paper was #fbf8f1: about 3% off pure white, light enough that it read as
   white rather than as a deliberate shade. Both grounds move down together so
   the alternation between them survives — the step between paper and paper-2
   is kept at roughly its original size (8/12/18 per channel, was 7/12/20).

   Because everything references var(--paper), this is a two-token change that
   reaches all ~30 usages without editing any of them.

   --muted has to move with the ground, and that is not optional. At #7b6f64
   the secondary text was already only 4.63:1 on the old paper — barely AA —
   so deepening the ground at all pushes it under. #6b6055 restores it to
   ~5.2:1 on paper and ~4.7:1 on paper-2.

   Every element carrying --muted resolves onto one of these two grounds,
   including .step__text: the steps band is dark, but .step.is-open paints
   itself --paper, so the open panel is a light card and its text is on
   paper like everything else. (Closed steps do sit on the dark band, but
   their panels are not displayed, so no visible text is affected.)

   Cards stay pure white on purpose. They were made white to sit on a light
   ground, and a deeper ground only sharpens that separation.
   ========================================================================== */

:root {
  --paper:   #f2ece0;   /* warm parchment ground   (was #fbf8f1) */
  --paper-2: #eae0ce;   /* deeper alternating tint (was #f4ecdd) */
  --muted:   #6b6055;   /* secondary text          (was #7b6f64) */
}

/* ==========================================================================
   Inner-page header clearance
   --------------------------------------------------------------------------
   The nav pill is positioned, so it takes no space in flow and every page had
   to reserve its own room underneath. They each guessed differently: the
   listing archive cleared it by 76px, the transfers page by 20px, and the
   tour and page heads by two more values again.

   Deriving the padding from the pill instead of hand-picking a number makes
   the visible gap identical everywhere, and keeps it that way when the pill
   resizes. --page-gap is the space you actually see; the other two terms just
   reproduce the pill's own height so we clear it exactly.

   Home is untouched: front-page.php builds its own sections and never uses
   .st-section, and the hero deliberately sits under the pill.
   ========================================================================== */

:root {
  --nav-h:    clamp(68px, 5.4vw, 84px);   /* mirrors .nav min-height */
  --page-gap: clamp(56px, 4vw, 92px);     /* the gap that actually shows */
  --page-top: calc(var(--pill-top) + var(--nav-h) + var(--page-gap));
}

/* Every inner template opens with .st-section — archive, single, contact,
   transfers and the generic page/index fallbacks all route through it. */
.st-section,
.page-head,
.tour-head { padding-top: var(--page-top); }

/* One size for every inner-page title.
   The clearance above them was already identical, but the titles themselves
   were not: the archives, transfers and contact all rendered at 33px while a
   single listing rendered at 42px, so moving between them the heading visibly
   changed rank. They now share one scale, set at the more confident of the
   two — 33px read undersized for a page title once the ground and containers
   had been opened up. */
.st-archive__header h1,
.st-pagehead h1,
.st-single__titlebar h1 {
  font-size: clamp(1.75rem, 0.9rem + 1.9vw, 2.7rem);
  line-height: 1.1;
  letter-spacing: -.022em;
}

/* ==========================================================================
   Proof mosaic — stat tiles
   The tiles were --champagne-50 (#fbf5cd) on the paper ground. Both are pale
   warm neutrals, so the tiles read as slightly-yellow paper rather than as
   cards, and deepening the ground closed the gap further. Key Black turns
   them into the anchor of the mosaic instead: they now contrast with both the
   ground and the photographs, and the champagne moves to the numeral where it
   carries the brand at full strength.
   ========================================================================== */

.proof__stat {
  background: var(--key-black);
  color: #fff;
}

.proof__num   { color: var(--champagne); }
.proof__label { color: rgba(255, 255, 255, .74); }   /* 9.6:1 on Key Black */

/* Both were tuned to sit on a pale tile and disappear on a dark one. */
.proof__stat::after  { opacity: .22; }
.proof__stat-mark    { opacity: .14; }

/* ==========================================================================
   Listing card scrims — deeper, so the type on the image holds
   ========================================================================== */

/* Archive cards had no scrim at all: the tag and place pills were sitting
   straight on the photograph and relying on their own backgrounds. */
.st-card__media::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;                 /* under .st-card__tag / __place, which are z-index 2 */
  pointer-events: none;
  background: linear-gradient(to top,
    rgba(18, 15, 16, .74) 0%,
    rgba(18, 15, 16, .28) 34%,
    rgba(18, 15, 16, 0)   62%);
}

/* The homepage service and experience cards carry their titles directly on
   the image, so these go deeper still and start fading higher up. */
.card::after {
  background: linear-gradient(to top,
    rgba(18, 15, 16, .90) 0%,
    rgba(18, 15, 16, .42) 42%,
    rgba(18, 15, 16, .06) 72%);
}

.exp-card::after {
  background: linear-gradient(to top,
    rgba(18, 15, 16, .92) 0%,
    rgba(18, 15, 16, .50) 46%,
    rgba(18, 15, 16, .12) 78%);
}

/* ==========================================================================
   Motion
   --------------------------------------------------------------------------
   Built on the reveal engine already in main.js rather than beside it, so
   there is still exactly one IntersectionObserver and one set of fallbacks
   for the whole site. Everything here animates transform, opacity or
   clip-path only — no layout properties — so none of it can trigger reflow
   while scrolling.

   The safety property of the existing system is preserved throughout: an
   element is only ever hidden by .is-pending, and .is-pending is only ever
   added by JS. If the script fails to run, nothing is hidden and the page
   renders as plain static content.
   ========================================================================== */

/* ---- Motion tokens ------------------------------------------------------
   One rhythm for the whole site. Entrances were 750ms, which reads as
   sluggish rather than considered — long fades feel like waiting. These sit
   in the range that reads as responsive, with a firm ease-out so movement
   arrives quickly and settles gently. Exits are deliberately shorter than
   entrances: a control that leaves slowly feels unresponsive. */
:root {
  --st-enter:      440ms;
  --st-enter-slow: 560ms;   /* larger surfaces only — featured card, mosaic */
  --st-hover:      260ms;
  --st-press:      120ms;
  --st-ease-out:   cubic-bezier(.22, .70, .24, 1);
  --st-ease-soft:  cubic-bezier(.33, .68, .38, 1);
  --st-step:       42ms;    /* per-item stagger */
}

/* ---- Reveal variants ----------------------------------------------------
   Each says something about the content it carries, rather than decorating
   it: lists arrive along their own axis, mosaics assemble, headings are
   uncovered. Plain [data-reveal] keeps the original rise. */

[data-reveal].is-revealed {
  transition: opacity var(--st-enter) var(--st-ease-out),
              transform var(--st-enter) var(--st-ease-out);
  transition-delay: calc(var(--i, 0) * var(--st-step));
}

[data-reveal="left"].is-pending  { transform: translate3d(-32px, 0, 0); }
[data-reveal="right"].is-pending { transform: translate3d(32px, 0, 0); }
[data-reveal="scale"].is-pending { transform: scale(.955); }

/* Cards: rise a little further and settle out of a slight recess, so they
   read as coming toward the reader rather than just fading up. */
[data-reveal="lift"].is-pending  { transform: translate3d(0, 34px, 0) scale(.972); }
[data-reveal="lift"].is-revealed {
  transition-duration: var(--st-enter-slow);
}

/* Logos and small tiles: a quick, even cadence. */
[data-reveal="pop"].is-pending   { transform: scale(.9); }
[data-reveal="pop"].is-revealed  { transition-duration: 340ms; }

/* Images: the frame stays put and the picture is uncovered. Opacity stays at
   1 so it reads as a reveal, not a fade. */
[data-reveal="mask"].is-pending {
  opacity: 1;
  transform: none;
  clip-path: inset(0 0 100% 0);
}
[data-reveal="mask"].is-revealed {
  clip-path: inset(0 0 0 0);
  transition: clip-path var(--st-enter-slow) var(--st-ease-out);
  transition-delay: calc(var(--i, 0) * var(--st-step));
}

/* Headings: uncovered from below rather than moved, which separates them
   from the body content around them. The negative insets keep descenders and
   the gold-sweep gradient from being clipped at the extremes. */
[data-reveal="wipe"].is-pending {
  opacity: 1;
  transform: none;
  clip-path: inset(-0.3em 0 100% 0);
}
[data-reveal="wipe"].is-revealed {
  clip-path: inset(-0.3em 0 -0.3em 0);
  transition: clip-path 480ms var(--st-ease-out);
  transition-delay: calc(var(--i, 0) * var(--st-step));
}

/* ---- Hero entrance ------------------------------------------------------
   An orchestrated load rather than everything arriving at once: the photo
   settles out of a slow push-in while the nav, headline, lede and scroll cue
   step in behind it, each picking up as the one before is still moving.

   The whole thing is gated on .st-hero-ready, which main.js only adds once
   the headline has been measured and the webfont has settled — so the
   sequence begins from a finished headline instead of resizing mid-flight.
   Nothing here hides anything by default; .st-hero-priming is added by
   script and removed by the same script's timeout, so a failed or blocked
   main.js leaves the hero plainly visible.

   Only translate and opacity are animated on the text. Never scale: the
   fitter measures rendered line widths, and a scaled headline would hand it
   a wrong number if a resize landed mid-animation. */

@keyframes st-hero-in {
  from { opacity: 0; transform: translate3d(0, 26px, 0); }
  to   { opacity: 1; transform: none; }
}

/* The photo uses the independent `scale` property, NOT transform — the
   parallax engine writes transform on this same element every frame, and the
   two would fight over it. `scale` composites separately. */
@keyframes st-hero-settle {
  from { scale: 1.06; }
  to   { scale: 1; }
}

@keyframes st-hero-scrim {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* Held back until measured.

   This has to come from the stylesheet, not from script: main.js is loaded in
   the footer, so by the time it could add a class the browser has already
   painted the headline at its fallback size — the reader would see it appear,
   vanish, then return. The stylesheet is render-blocking, so hiding here means
   it is never painted at the wrong size in the first place.

   Hiding from CSS would normally risk content stranded invisible if the
   script never runs, so each element also carries a pure-CSS failsafe that
   fades it in at 2.2s regardless. When main.js does its job the .st-hero-ready
   rules below outrank the failsafe and it never fires.

   Scoped to the front page. .nav is a sibling of .hero and lives on every
   page, so hiding it unscoped would blank the navigation site-wide on any
   page that has no hero to reveal it. */
.home .hero__title,
.home .hero__lede,
.home .hero__cta,
.home .hero__actions,
.home .hero__scroll,
.home .nav {
  opacity: 0;
  animation: st-hero-failsafe .5s ease 2.2s both;
}

.home .hero__mark {
  opacity: 0;
  animation: st-hero-mark-failsafe .5s ease 2.2s both;
}

@keyframes st-hero-failsafe      { to { opacity: 1; } }
@keyframes st-hero-mark-failsafe { to { opacity: .07; } }

.st-hero-ready .hero__photo  { animation: st-hero-settle 2s   cubic-bezier(.22, .68, .24, 1) both; }
.st-hero-ready .hero__scrim  { animation: st-hero-scrim  1.2s ease both; }
.st-hero-ready .nav          { animation: st-hero-in .80s cubic-bezier(.2, .7, .3, 1) .10s both; }
.st-hero-ready .hero__title  { animation: st-hero-in .95s cubic-bezier(.2, .7, .3, 1) .26s both; }
.st-hero-ready .hero__lede   { animation: st-hero-in .95s cubic-bezier(.2, .7, .3, 1) .44s both; }
.st-hero-ready .hero__cta,
.st-hero-ready .hero__actions { animation: st-hero-in .95s cubic-bezier(.2, .7, .3, 1) .58s both; }
.st-hero-ready .hero__scroll { animation: st-hero-in .80s cubic-bezier(.2, .7, .3, 1) .72s both; }

/* The brandmark rests at opacity .07, so it needs its own keyframe — the
   shared one ends at 1 and would blow the watermark out to full strength. */
@keyframes st-hero-mark-in {
  from { opacity: 0;    transform: translate3d(0, 26px, 0); }
  to   { opacity: .07;  transform: none; }
}
.st-hero-ready .hero__mark { animation: st-hero-mark-in 1.4s cubic-bezier(.2, .7, .3, 1) .34s both; }

/* ---- Micro-interactions ------------------------------------------------- */

/* Press feedback. Exit is quicker than enter, which is what makes a control
   feel responsive rather than springy. */
.btn, .st-btn, .st-veh__card, .faq__q, .step__spine {
  transition: transform var(--st-hover) var(--st-ease-out),
              background-color var(--st-hover) ease,
              border-color var(--st-hover) ease,
              color var(--st-hover) ease,
              box-shadow 300ms var(--st-ease-out);
}

.btn:active, .st-btn:active {
  transform: translateY(1px) scale(.985);
  transition-duration: var(--st-press);
}

/* The arrow leans toward where it is about to take you. */
.card__arrow, .st-card__go {
  transition: transform 320ms var(--st-ease-out);
}
.card:hover .card__arrow    { transform: translate3d(3px, -3px, 0); }
.st-card:hover .st-card__go { transform: translate3d(4px, 0, 0); }

/* ---- Listing card hover -------------------------------------------------
   Layered rather than simultaneous: the card settles first and the image
   keeps travelling for a moment afterwards. That small difference in
   duration is what separates a considered hover from a uniform one — the
   card feels like a surface with a photograph behind it, not a flat sprite.

   Only transform and box-shadow move, so nothing reflows on hover, and the
   whole thing is suppressed on touch where there is no hover to speak of. */
.st-card__media { overflow: hidden; }

.st-card {
  transition: transform var(--st-hover) var(--st-ease-out),
              box-shadow 380ms var(--st-ease-soft);
}

.st-card__img {
  transition: transform 720ms var(--st-ease-soft);
  will-change: transform;
}

.st-card__title {
  transition: color var(--st-hover) ease;
}

@media (hover: hover) {
  .st-card:hover {
    transform: translate3d(0, -4px, 0);
    box-shadow: 0 2px 6px -2px rgba(35, 31, 32, .10),
                0 22px 42px -18px rgba(35, 31, 32, .30);
  }
  .st-card:hover .st-card__img  { transform: scale(1.045); }
  .st-card:hover .st-card__title { color: var(--bronze, #b7833f); }

  /* Pressing settles it back toward the page — the surface accepts the tap. */
  .st-card:active {
    transform: translate3d(0, -1px, 0) scale(.994);
    transition-duration: var(--st-press);
  }

  .card:hover, .exp-card:hover { transition-duration: var(--st-hover); }
}

/* Keyboard users get the same affordance as pointer users. */
.st-card:focus-within {
  transform: translate3d(0, -4px, 0);
  box-shadow: 0 2px 6px -2px rgba(35, 31, 32, .10),
              0 22px 42px -18px rgba(35, 31, 32, .30);
}

/* Count-up numerals must not reflow the tile as the digits change. */
.proof__num { font-variant-numeric: tabular-nums; }

@media (prefers-reduced-motion: reduce) {
  [data-reveal="mask"].is-pending   { clip-path: none; }
  [data-reveal].is-revealed         { transition-delay: 0s; }
  .btn:active, .st-btn:active       { transform: none; }
  .card:hover .card__arrow,
  .st-card:hover .st-card__go       { transform: none; }

  /* Nothing is held back and nothing moves: the hero is simply present from
     first paint. Both the hidden state and the ready state are neutralised,
     because the script still announces readiness either way. */
  .home .hero__title,
  .home .hero__lede,
  .home .hero__cta,
  .home .hero__actions,
  .home .hero__scroll,
  .home .nav,
  .st-hero-ready .hero__title,
  .st-hero-ready .hero__lede,
  .st-hero-ready .hero__cta,
  .st-hero-ready .hero__actions,
  .st-hero-ready .hero__scroll,
  .st-hero-ready .nav { opacity: 1; animation: none; }

  .home .hero__mark,
  .st-hero-ready .hero__mark { opacity: .07; animation: none; }

  .st-hero-ready .hero__photo,
  .st-hero-ready .hero__scrim { animation: none; scale: 1; opacity: 1; }
}

/* ==========================================================================
   Hero image — phones
   --------------------------------------------------------------------------
   <picture> must not generate a box of its own: .hero__photo is absolutely
   positioned against .hero, and an inline wrapper in normal flow would add a
   stray line box above it.
   ========================================================================== */

.hero__picture { display: contents; }

@media (max-width: 900px) {
  /* The 150% height and -25% inset exist to give the parallax something to
     travel through. Parallax is switched off on phones (see main.js), so the
     overscan is pure cost: it made the browser render a 390x1266 image for an
     844px-tall hero, demanding half again as many pixels as are ever visible.
     Matching the box to the hero is worth about a third of the sharpness on
     its own, before the portrait source is even considered. */
  .hero__photo {
    inset: 0;
    height: 100%;
    will-change: auto;
  }
}

/* ==========================================================================
   Picker footer
   Selections no longer dismiss the popover, so it needs an explicit way out.
   ========================================================================== */

.st-pop__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  margin-top: 10px;
  padding-top: 10px;
  border-top: 1px solid var(--hairline, rgba(35, 31, 32, .12));
}

.st-pop__foot--end { justify-content: flex-end; }

.st-pop__done {
  padding: 9px 20px;
  font-family: inherit;
  font-size: .86rem;
  font-weight: 700;
  color: #fff;
  background: var(--key-black, #231f20);
  border: 0;
  border-radius: 999px;
  cursor: pointer;
  transition: background-color 200ms ease, transform 150ms cubic-bezier(.2, .7, .3, 1);
}

.st-pop__done:hover  { background: var(--bronze, #b7833f); }
.st-pop__done:active { transform: scale(.97); }
.st-pop__done:focus-visible { outline: 2px solid var(--bronze, #b7833f); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  .st-pop__done:active { transform: none; }
}

/* ==========================================================================
   Section rhythm
   --------------------------------------------------------------------------
   Every section padded both edges independently, on values between 6vw and
   8vw, so wherever two met the reader saw the sum of both. Between the
   service cards and "Most Booked Experiences" that came to 192px at 1280 —
   102px from the bottom of one section and 90px from the top of the next,
   both on the same paper ground, so it read as one large empty band rather
   than as separation.

   One token now drives all of them, at roughly half the previous value.
   Adjacent sections still total a little under 100px at 1280, which reads as
   a deliberate break instead of a gap.
   ========================================================================== */

:root { --sec-y: clamp(36px, 3.6vw, 68px); }

/* ==========================================================================
   Side padding
   --------------------------------------------------------------------------
   Two different values were in play: the section inners and .st-container sat
   on clamp(20px, 4.4vw, 60px), while the hero ran on --gutter at
   clamp(22px, 5.6vw, 76px). So the hero copy started 16px further in than
   everything beneath it, which reads as a misalignment down the left edge of
   the page rather than as a deliberate difference.

   One token now drives both, at a wider setting than either. --gutter is
   redefined rather than replaced because the hero's scroll cue is positioned
   off it too, and both should move together.

   Note this widens the hero headline's column, so the fitter re-measures and
   the type resolves a little smaller — that is the fitter working correctly,
   not a regression.
   ========================================================================== */

:root {
  --pad-x:  clamp(24px, 5.2vw, 96px);
  --gutter: var(--pad-x);
}

.services__inner,
.experiences__inner,
.steps__inner,
.proof__inner,
.cta-band__inner,
.sec-inner,
.st-container { padding-inline: var(--pad-x); }

.services,
.experiences,
.steps,
.proof,
.partners,
.reviews,
.faq,
.psec,
.cta-band,
.blocks,
.tour-related,
.tour-layout { padding-block: var(--sec-y); }

/* .st-section is excluded on purpose: its padding-top is the nav-pill
   clearance set above, and overriding it here would put the inner-page
   headings back under the floating nav. Only its foot is touched. */
.st-section { padding-bottom: var(--sec-y); }

/* The dark steps band already carries its own internal padding, so the paper
   section that follows it does not need a full band of its own on top. */
.steps + .proof { padding-top: calc(var(--sec-y) * .55); }

/* Large screens raise the nav pill, so the clearance token has to follow it.
   This has to sit at the very end of the file: a media query adds no
   specificity, so the plain :root block above would otherwise win on source
   order and the pill would grow while the gap below it did not. */
@media (min-width: 1400px) {
  :root { --nav-h: clamp(84px, 4.6vw + 19.6px, 104px); }
}

/* ==========================================================================
   Hero scroll cue — sit it lower
   It was pinned to --gutter on both axes, which is now 96px at the top end.
   That put it well up into the frame rather than at the foot of the fold,
   where a scroll affordance reads. The horizontal inset stays on --gutter so
   it still lines up with the right edge of everything else.
   ========================================================================== */

.hero__scroll { bottom: clamp(14px, 1.6vw, 28px); }
