/* ============================================================
   BOOK MY TRIP — MOTION & ATMOSPHERE LAYER
   ============================================================
   Loaded LAST on every page, so it refines the base system
   without rewriting it. Delete the <link> to revert a page.

   Three ideas, drawn from the subject (air travel):
     1. Buttons that respond to where the cursor actually is
     2. Dotted flight-path arcs drifting behind the hero
     3. A slow aurora + film grain so flat fills read as chosen

   Everything here is decoration. Nothing here is required for
   the page to function, and all of it stops under
   prefers-reduced-motion (bottom of file).
   ============================================================ */

/* ---------- 1. BUTTONS ---------- */
/* A soft light follows the cursor across the face of the button.
   polish.js writes --mx/--my as percentages; the 50%/50% fallback
   keeps it centred for keyboard users and before first hover. */
/* inset:-40% so the glow visible at the edges is the tail of a
   much larger falloff, not a circle cut off mid-radius.
   overflow:hidden on .btn clips it — the excess never paints. */
.btn::before {
  content: "";
  position: absolute;
  inset: -40%;
  z-index: -1;
  background: radial-gradient(
    circle at var(--mx, 50%) var(--my, 50%),
    rgba(255, 255, 255, .34),
    transparent 55%
  );
  opacity: 0;
  transition: opacity var(--t-mid) var(--ease);
  pointer-events: none;
}
.btn:hover::before { opacity: 1; }

/* Ghost buttons sit on white, so a white glow is invisible —
   tint theirs navy instead. */
.btn-ghost::before {
  background: radial-gradient(
    circle at var(--mx, 50%) var(--my, 50%),
    rgba(var(--rgb-navy), .14),
    transparent 45%
  );
}

/* Press physics: the whole button dips toward the pointer.
   Overrides base.css :active, which only had translateY. */
.btn:active {
  transform: translateY(1px) scale(.975);
  transition-duration: .08s;
}

/* Click ripple. polish.js drops one span per click and removes it
   when the animation ends, so these never accumulate. */
.btn-ripple {
  position: absolute;
  border-radius: 50%;
  transform: scale(0);
  background: rgba(255, 255, 255, .45);
  pointer-events: none;
  animation: ripple .62s var(--ease) forwards;
}
.btn-ghost .btn-ripple { background: rgba(var(--rgb-navy), .2); }
@keyframes ripple {
  to { transform: scale(2.4); opacity: 0; }
}

/* Arrow nudge. In practice the arrows on this site sit in inline text
   links ("See packages →"), not buttons, so polish.js wraps those too
   and the hover is keyed off the link rather than only .btn. */
.btn__arw {
  display: inline-block;
  transition: transform var(--t-mid) var(--ease);
}
.btn:hover .btn__arw,
a:hover > .btn__arw { transform: translateX(4px); }

/* The primary CTA used to breathe once on load to pull the eye.
   That's now the shine ring's job on every button, and the two were
   animating the same box-shadow property — with .btn-primary.btn-lg
   outranking .btn-lg, cta-settle would simply have beaten the glow.
   Removed rather than reconciled: one system per property. */

/* ---------- 1b. SHINE RING ---------- */
/* A conic-gradient border that rotates continuously. The gradient
   is one bright arc in a full circle of transparency; masking it
   down to the border edge means what you actually see is a
   highlight travelling the perimeter — like light catching a metal
   rim.

   @property is what makes this work at all. To the animation engine
   an unregistered custom property is just a string, so it would
   snap 0deg to 360deg with nothing in between. Declaring it as
   <angle> is what lets it interpolate.

   Mauve rather than gold: the ring reads as light catching a rim,
   and the light on this site is dusk light. #E1CBD7 into near-white
   is the pale end of the supplied ramp, so on a red or navy fill it
   still separates cleanly while belonging to the palette — where
   the old gold sat outside it entirely.

   ON APPLYING THIS TO EVERY BUTTON
   Two things had to be solved for that to work rather than just
   be loud:

   1. Hierarchy. If every button shines identically, none of them
      leads. So the ring is a family trait set in three tiers —
      quiet on small/secondary buttons, standard on normal ones,
      and bright + glow-pulsing on the featured CTAs (.btn-lg and
      anything explicitly marked .btn-shine). The loudest member is
      still obvious.

   2. Cost. A rotating conic gradient repaints its ring every frame,
      and a pulsing box-shadow is the single most expensive thing
      you can animate. With 20+ buttons on a page that adds up on a
      mid-range phone, so: the glow pulse is limited to the featured
      tier only, and polish.js pauses any button scrolled out of
      view via .btn-idle. Nothing off-screen costs anything.        */
@property --shine-angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

.btn {
  /* Per-variant knobs, so one ::after rule serves every button.
     --shine-rest re-states whatever resting shadow the variant
     already had, so the glow keyframes add to it instead of
     wiping it out. */
  --shine-w: 1.25px;
  --shine-spin: 4.6s;
  --shine-peak: var(--mauve-100);
  --shine-hi: #FFFFFF;
  --shine-fade: .62;
  --shine-rest: 0 0 #0000;
}

.btn::after {
  content: "";
  position: absolute;
  /* base.css sets overflow:hidden on .btn (for the ripple), which
     clips anything sitting exactly on the border box — the ring's
     outer half would be shaved off. Insetting by its own width
     pulls the whole ring inside the clip rectangle. */
  inset: var(--shine-w);
  z-index: -1;
  border-radius: inherit;
  padding: var(--shine-w);
  opacity: var(--shine-fade);
  background: conic-gradient(
    from var(--shine-angle),
    transparent          0%,
    var(--shine-peak)    9%,
    var(--shine-hi)     15%,
    var(--shine-peak)   21%,
    transparent         33%,
    transparent        100%
  );
  /* Two identical fills clipped to different boxes. Excluding the
     content-box one from the border-box one leaves exactly the
     padding width — i.e. the ring, and nothing else. */
  mask-image: linear-gradient(#fff 0 0), linear-gradient(#fff 0 0);
  mask-clip: content-box, border-box;
  mask-composite: exclude;
  -webkit-mask-image: linear-gradient(#fff 0 0), linear-gradient(#fff 0 0);
  -webkit-mask-clip: content-box, border-box;
  -webkit-mask-composite: xor;
  animation: shine-rotate var(--shine-spin) linear infinite;
  pointer-events: none;
}

/* Hover brightens and speeds up rather than changing character —
   the motion is already running, it just gains presence. */
.btn:hover::after { opacity: 1; animation-duration: 1.9s; }

/* --- Tier 1: quiet. Small and utility buttons. --- */
.btn-sm  { --shine-w: 1px; --shine-fade: .5; --shine-spin: 5.4s; }

/* --- Tier 3: featured. The one that leads the page. --- */
.btn-lg,
.btn-shine {
  --shine-w: 1.5px;
  --shine-spin: 3.2s;
  --shine-fade: 1;
  animation: shine-glow 3.2s ease-in-out infinite;
}
.btn-primary         { --shine-rest: var(--sh-red); }
.btn-navy            { --shine-rest: var(--sh-md); }
.btn-outline-light   { --shine-rest: 0 0 #0000; }

/* WhatsApp keeps its own identity — a mauve ring on the green
   button fights the one colour people actually recognise it by,
   so its highlight is white. */
.btn-wa {
  --shine-peak: rgba(255, 255, 255, .9);
  --shine-hi: #fff;
  --shine-fade: .5;
}

/* The glow pulse belongs to filled buttons only.
   Ghost and outline buttons exist to be the quiet half of a pair —
   "Honeymoon Trips" beside "Holiday Tours", "Visit Our Office"
   beside "Request a Quote". At .btn-lg they were inheriting the
   same pulse as the primary next to them, at the same intensity,
   which left the pair with no rank at all. They keep the ring and
   lose the pulse; their ring also sits back slightly, so the
   primary is unambiguously the one being offered.

   Two class selectors (0,2,0) to outrank .btn-lg (0,1,0). */
.btn-ghost.btn-lg,         .btn-ghost.btn-shine,
.btn-outline-light.btn-lg, .btn-outline-light.btn-shine,
.btn-wa.btn-lg,            .btn-wa.btn-shine {
  animation: none;
}
.btn-ghost.btn-lg,         .btn-ghost.btn-shine,
.btn-outline-light.btn-lg, .btn-outline-light.btn-shine {
  --shine-fade: .72;
}

/* Bordered variants already draw a 1.5px line of their own, and
   `inset: 0` resolves to the padding box — so the ring would land
   just inside that border and read as a doubled edge. Dropping
   their border to transparent on hover lets the ring take over the
   edge instead of competing with it. */
.btn-ghost::after,
.btn-outline-light::after { --shine-w: 1px; }
.btn-ghost:hover          { border-color: transparent; }
.btn-outline-light:hover  { border-color: rgba(255, 255, 255, .25); }

/* Scrolled out of view — polish.js toggles this. Paused rather
   than removed, so it resumes mid-cycle instead of restarting. */
/* Paused rather than removed: `animation: none` restarts the
   sweep from 0deg on every scroll-back, which flickers. Pausing
   resumes mid-cycle, so re-entry is invisible. Applied by
   initButtonIdling() in polish.js. */
.btn-idle::after { animation-play-state: paused; }
.btn-idle        { animation-play-state: paused; }

@keyframes shine-rotate { to { --shine-angle: 360deg; } }
@keyframes shine-glow {
  0%, 100% { box-shadow: var(--shine-rest), 0 0 12px rgba(var(--rgb-mauve), .45); }
  50%      { box-shadow: var(--shine-rest), 0 0 24px rgba(var(--rgb-mauve), .7); }
}

/* No @property (older Safari/Firefox): the angle can't animate, so
   the ring would sit frozen at one arbitrary position. A static
   mauve hairline is a better resting state than a stalled sweep. */
@supports not (background: conic-gradient(from var(--shine-angle), red, blue)) {
  .btn::after,
  .chat-fab::after,
  .chat-send::after {
    background: var(--mauve-100);
    opacity: .55;
  }
}

/* ---------- 2. FLIGHT PATHS ---------- */
/* Sits behind the hero copy. polish.js injects the markup once,
   into .hero and .page-hero, so no page HTML changes. */
.atmos {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}
.atmos svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
/* .atmos is a positioned layer at z-index 0, so it paints above
   normal-flow content. components.css already lifts .hero > *;
   .page-hero has no such rule, so without this the arcs would
   cross the headings on all 12 inner pages. */
.page-hero > .wrap { position: relative; z-index: 1; }
.atmos__arc {
  fill: none;
  stroke: var(--navy-400);
  stroke-width: 1.6;
  stroke-dasharray: 5 9;
  stroke-linecap: round;
  opacity: .32;
  animation: arc-drift 22s linear infinite;
}
.atmos__arc--2 { opacity: .22; animation-duration: 30s; }
.atmos__arc--3 { opacity: .16; animation-duration: 38s; }
@keyframes arc-drift {
  to { stroke-dashoffset: -140; }
}

/* The travelling marker. animateMotion would need the path
   duplicated in markup; offset-path keeps the geometry in CSS. */
.atmos__plane {
  position: absolute;
  top: 0; left: 0;
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--red-500);
  box-shadow: 0 0 0 4px rgba(var(--rgb-red), .16),
              0 0 14px 2px rgba(var(--rgb-red), .5);
  offset-path: path("M -60 300 C 240 130, 620 96, 1000 190 S 1400 300, 1560 210");
  offset-rotate: 0deg;
  animation: fly 15s cubic-bezier(.5, 0, .5, 1) infinite;
}
.atmos__plane--2 {
  width: 5px; height: 5px;
  background: var(--mauve-500);
  box-shadow: 0 0 0 3px rgba(var(--rgb-mauve), .2),
              0 0 12px 2px rgba(var(--rgb-mauve), .55);
  offset-path: path("M -60 430 C 300 350, 700 400, 1100 300 S 1420 210, 1560 250");
  animation-duration: 21s;
  animation-delay: 3.5s;
}
@keyframes fly {
  0%        { offset-distance: 0%;   opacity: 0; }
  8%, 88%   { opacity: 1; }
  100%      { offset-distance: 100%; opacity: 0; }
}

/* No offset-path (older Safari/Firefox): the arcs still drift,
   the markers simply never appear rather than parking at 0,0. */
@supports not (offset-path: path("M 0 0 L 1 1")) {
  .atmos__plane { display: none; }
}
/* ---------- 3. AURORA (dark sections) ---------- */
/* .bg-navy is now a dusk gradient rather than a flat fill, and
   these two blooms drift across it. Mauve and blue — the same two
   mid-ramp colours used on the light sections, so the dark bands
   are the same sky seen later in the evening rather than a
   different palette. Mauve reads clearly at 22% over indigo, which
   the old red never did.

   Under 24% opacity and ~30s per cycle, so they stay atmosphere
   and never resolve into a gradient blob. */
.bg-navy { position: relative; overflow: hidden; isolation: isolate; }
.bg-navy > .wrap { position: relative; z-index: 1; }
.bg-navy::before {
  content: "";
  position: absolute;
  z-index: 0;
  width: 60vw; height: 60vw;
  max-width: 780px; max-height: 780px;
  top: -22%; left: -12%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(var(--rgb-mauve), .55), transparent 68%);
  filter: blur(46px);
  opacity: .3;
  animation: aurora-a 30s var(--ease-io) infinite alternate;
  pointer-events: none;
}
.bg-navy::after {
  content: "";
  position: absolute;
  z-index: 0;
  width: 52vw; height: 52vw;
  max-width: 680px; max-height: 680px;
  right: -14%; bottom: -26%;
  border-radius: 50%;
  background: radial-gradient(circle, rgba(var(--rgb-blue), .6), transparent 66%);
  filter: blur(52px);
  opacity: .26;
  animation: aurora-b 38s var(--ease-io) infinite alternate;
  pointer-events: none;
}
@keyframes aurora-a {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(9vw, 5vh, 0) scale(1.18); }
}
@keyframes aurora-b {
  from { transform: translate3d(0, 0, 0) scale(1.1); }
  to   { transform: translate3d(-7vw, -5vh, 0) scale(1); }
}

/* A mauve hairline where a dark section meets a light one. It reads
   as the last of the light on the horizon rather than as a border,
   which is exactly the join the dusk ramp is describing. */
.sec.bg-navy { border-top: 1px solid rgba(var(--rgb-mauve), .3); }

/* ---------- 4. FILM GRAIN ---------- */
/* One fixed, tiled SVG noise layer over the whole page at 3.5%.
   Kills the plastic look of large flat fills. Fixed position means
   it never repaints on scroll. */
body::after {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 9998;
  pointer-events: none;
  opacity: .035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)'/%3E%3C/svg%3E");
}

/* ---------- 5. SCROLL PROGRESS ---------- */
/* Sits above the header (z 900) but below the drawer and chat. */
.scroll-bar {
  position: fixed;
  top: 0; left: 0;
  height: 2.5px;
  width: 100%;
  z-index: 901;
  transform: scaleX(0);
  transform-origin: 0 50%;
  background: linear-gradient(90deg, var(--navy-600), var(--mauve-500) 48%, var(--red-500));
  pointer-events: none;
}

/* ---------- 6. CARD SPOTLIGHT ---------- */
/* Same cursor-tracking idea as the buttons, dialled right down —
   a card is content, so the effect has to stay near-subliminal. */
.card-hover, .step, .quote {
  position: relative;
  isolation: isolate;
}
.card-hover::after, .step::after, .quote::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  background: radial-gradient(
    380px circle at var(--mx, 50%) var(--my, 50%),
    rgba(var(--rgb-navy), .07),
    transparent 62%
  );
  opacity: 0;
  transition: opacity var(--t-mid) var(--ease);
  pointer-events: none;
}
.card-hover:hover::after, .step:hover::after, .quote:hover::after { opacity: 1; }

/* ---------- 7. HEADER ---------- */
/* The logo already shrinks on scroll; give the bar a tint shift
   to match so the two read as one movement. Denser and cooler
   once stuck, because by then there's content sliding under it. */
.hdr.stuck {
  background: linear-gradient(180deg, rgba(255,255,255,.88) 0%, rgba(231,237,246,.82) 100%);
}

.nav a::after { transition: transform var(--t-mid) var(--ease); }

/* ---------- 8. IMAGE REVEAL ---------- */
/* Photos in cards ease up as well as scale, which reads as
   deliberate where scale alone reads as a stock hover. */
.country-card img,
.split__media img {
  transition: transform 1.1s var(--ease), filter var(--t-slow) var(--ease);
}
.country-card:hover img { transform: scale(1.09) translateY(-4px); }
.split__media:hover img { transform: scale(1.045); }

/* ---------- 9. FLOATS ---------- */
/* The two round buttons that aren't .btn but are unmistakably
   buttons: the chat launcher and the send key inside the panel.
   They get the same ring so the family is consistent everywhere.

   Both need setting up first — .btn carries `isolation: isolate`,
   which is what lets a z-index:-1 pseudo-element paint above its
   own background instead of dropping behind it. Neither of these
   had it, and .chat-send wasn't positioned at all.

   Deliberately NOT included:
     .wa-float   — already pulses its own green halo on ::after;
                   a second ring would be two animations arguing
                   over one 54px circle.
     .chat-close — a 30px X. Decoration on a dismiss control just
                   draws the eye to the way out.
     .burger     — three lines, no button surface to rim.
     .copt       — <button> by markup, but they're the 15 country
                   tiles in a selection grid. Ringing all of them
                   would turn a form into a light display, and
                   they already have their own selected state.     */
.chat-fab,
.chat-send {
  isolation: isolate;
  --shine-w: 1.25px;
  --shine-spin: 4.6s;
  --shine-peak: var(--mauve-100);
  --shine-hi: #FFFFFF;
  --shine-fade: .62;
}
/* Only .chat-send needs a containing block introduced — .chat-fab
   is position:fixed already, and setting `relative` here would
   unpin it from the corner and drop it into the page flow. */
.chat-send { position: relative; }
.chat-fab::after,
.chat-send::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  border-radius: inherit;
  padding: var(--shine-w);
  opacity: var(--shine-fade);
  background: conic-gradient(
    from var(--shine-angle),
    transparent          0%,
    var(--shine-peak)    9%,
    var(--shine-hi)     15%,
    var(--shine-peak)   21%,
    transparent         33%,
    transparent        100%
  );
  mask-image: linear-gradient(#fff 0 0), linear-gradient(#fff 0 0);
  mask-clip: content-box, border-box;
  mask-composite: exclude;
  -webkit-mask-image: linear-gradient(#fff 0 0), linear-gradient(#fff 0 0);
  -webkit-mask-clip: content-box, border-box;
  -webkit-mask-composite: xor;
  animation: shine-rotate var(--shine-spin) linear infinite;
  pointer-events: none;
}
.chat-fab:hover::after,
.chat-send:hover::after { opacity: 1; animation-duration: 1.9s; }

/* The launcher is a persistent floating CTA, so it takes the
   featured tier — but no glow pulse: it already carries a heavy
   drop shadow and sits over live page content. */
.chat-fab { --shine-w: 1.5px; --shine-spin: 3.2s; --shine-fade: 1; }

/* Hidden state must take the ring with it, or a gold circle stays
   floating after the launcher scales away. */
.chat-fab.hidden::after { opacity: 0; }

.wa-float::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  border: 1.5px solid rgba(37, 211, 102, .55);
  animation: halo 2.6s var(--ease-io) infinite;
  pointer-events: none;
}
@keyframes halo {
  0%        { transform: scale(1);    opacity: .8; }
  70%, 100% { transform: scale(1.75); opacity: 0; }
}

/* ---------- 10. REDUCED MOTION ---------- */
/* base.css already flattens durations globally. These are the
   pieces that need to be switched off rather than sped up:
   an .01ms aurora still lands mid-drift, and a 0s ripple still
   flashes. Grain is static, so it stays. */
@media (prefers-reduced-motion: reduce) {
  .atmos,
  .btn::before,
  .btn-ripple,
  .bg-navy::before,
  .bg-navy::after,
  .wa-float::after,
  .card-hover::after,
  .step::after,
  .quote::after { display: none; }

  .scroll-bar { display: none; }

  /* Buttons keep their pale mauve edge — that's the visual, and
     it's worth keeping — but nothing rotates and nothing pulses. */
  .btn, .btn-lg, .btn-shine { animation: none; }
  .btn::after,
  .chat-fab::after,
  .chat-send::after {
    animation: none;
    background: var(--mauve-100);
    opacity: .5;
  }
}

/* ---------- 11. MOBILE ---------- */
/* Touch has no hover, so the cursor-tracked layers are dead
   weight — drop them and keep the page light. */
@media (hover: none) {
  .btn::before,
  .card-hover::after,
  .step::after,
  .quote::after { display: none; }
}
@media (max-width: 700px) {
  .atmos__plane { display: none; }        /* arcs stay, markers go */
  body::after { opacity: .028; }
}

/* ---------- 12. THE DESCENT ---------- */
/* The one idea the palette is actually about: the page goes from
   late afternoon at the top to dusk at the bottom, and scrolling
   is what moves it.

   Two fixed layers. html::before is the deeper sky and never
   moves. body::before is the paler sky on top of it, and its
   opacity is tied to scroll position rather than to a clock — so
   the light doesn't fade on a timer while you read, it fades
   because you went down the page. Reach the footer and you're
   looking at the bottom of the ramp.

   Opacity, not filter. A hue-rotate across the whole viewport
   recomputes every scroll frame; a cross-fade between two static
   layers is two textures the compositor already has. */
html::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: -3;
  pointer-events: none;
  background:
    radial-gradient(120vw 90vh at 14% 8%,  #E3D3E2, transparent 62%),
    radial-gradient(110vw 85vh at 88% 66%, #CFDCF0, transparent 60%),
    linear-gradient(172deg, #DCE5F4 0%, #E6D9E6 58%, #D6DFF0 100%);
}

/* Every stop in that deep layer is still above 85% lightness. The
   descent has to be visible without ever becoming the reason a
   paragraph is hard to read — the veils over it are only 50-66%
   opaque, so whatever is down here shows through to the text. */
@supports (animation-timeline: scroll()) {
  @media (prefers-reduced-motion: no-preference) {
    body::before {
      animation: sky-drift 90s var(--ease-io) infinite alternate,
                 sky-descend linear both;
      animation-timeline: auto, scroll(root block);
    }
  }
}
@keyframes sky-descend {
  from { opacity: 1; }
  to   { opacity: .2; }
}

/* ---------- 13. FOCUS PULL ---------- */
/* Section headings resolve out of blur as they arrive, which is
   the same gesture as the frosted panels everywhere else: the
   page is glass, and glass comes into focus.

   Only on [data-reveal="rise"], which layout.js puts on .sec-head
   and nothing else — six or so per page. Blur is a real paint
   cost and this is the one place it earns it. */
@media (min-width: 701px) {
  [data-reveal="rise"] {
    filter: blur(7px);
    transition: opacity .9s var(--ease),
                transform .9s var(--ease),
                filter .9s var(--ease);
  }
  [data-reveal="rise"].in { filter: blur(0); }
}

/* ---------- 14. HEADER ARRIVAL ---------- */
/* The header is injected by layout.js after DOMContentLoaded, so
   it pops into an already-painted page. Dropping it in gives the
   load a first beat, and the logo and nav follow it down rather
   than arriving with it.

   `both` matters: the transform must land on `none`, or a fixed
   header with a live transform becomes the containing block for
   every absolutely-positioned dropdown inside it. */
.hdr { animation: hdr-drop .62s var(--ease) both; }
@keyframes hdr-drop {
  from { transform: translateY(-100%); opacity: 0; }
  to   { transform: none; opacity: 1; }
}
.hdr__logo,
.nav > *,
.hdr__cta > * { animation: hdr-settle .5s var(--ease) both; }
.hdr__logo      { animation-delay: .16s; }
.nav > *:nth-child(1) { animation-delay: .22s; }
.nav > *:nth-child(2) { animation-delay: .26s; }
.nav > *:nth-child(3) { animation-delay: .30s; }
.nav > *:nth-child(4) { animation-delay: .34s; }
.nav > *:nth-child(5) { animation-delay: .38s; }
.nav > *:nth-child(6) { animation-delay: .42s; }
.nav > *:nth-child(7) { animation-delay: .46s; }
.hdr__cta > *   { animation-delay: .5s; }
@keyframes hdr-settle {
  from { opacity: 0; transform: translateY(-7px); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  [data-reveal="rise"] { filter: none; }
  .hdr,
  .hdr__logo,
  .nav > *,
  .hdr__cta > * { animation: none; opacity: 1; transform: none; }
}

/* ============================================================
   14. SMOOTH SCROLL (Lenis)
   ------------------------------------------------------------
   Scoped to .lenis-on, which smooth-scroll.js puts on <html>
   only once Lenis is actually running. That matters: on reduced
   motion or a touch device Lenis never starts, and the native
   scrolling these rules override has to stay intact.
   ============================================================ */

/* base.css sets scroll-behavior:smooth on <html>. Native smooth
   and Lenis animate the same property from different starting
   points, so a hash jump stutters between the two. Lenis owns
   the animation while it is running. */
html.lenis-on { scroll-behavior: auto; }

/* Lenis drives scroll from a transform on the wrapper, so the
   document must not also scroll natively. */
html.lenis-on,
html.lenis-on body { height: auto; }

.lenis.lenis-smooth { scroll-behavior: auto !important; }

/* While a programmatic scrollTo is in flight, swallow pointer
   events so a click mid-animation cannot start a second one. */
.lenis.lenis-stopped { overflow: hidden; }
.lenis.lenis-smooth [data-lenis-prevent] { overscroll-behavior: contain; }
