/* ===========================================================================
   Motion system
   Everything here is CSS transforms and opacity only — no layout properties
   animate, so nothing triggers reflow. Total cost is this file plus about
   forty lines of IntersectionObserver.

   Three rules the whole system follows:
     1. Nothing moves more than 24px. Long travel reads as cheap.
     2. Nothing takes longer than 800ms. Waiting is not elegance.
     3. Every entrance runs once, then the observer stops watching.
   =========================================================================== */

/* --- the reveal primitive -------------------------------------------------
   `.rise` on any element, or `[data-stagger]` on a container to sequence its
   children. The JS sets --i on each child; the delay is derived from it. */

.js .rise,
.js [data-stagger] > * {
  opacity: 0;
  transform: translate3d(0, 22px, 0);
  transition:
    opacity 0.7s var(--ease-out),
    transform 0.7s var(--ease-out);
  transition-delay: calc(var(--i, 0) * 70ms);
  will-change: opacity, transform;
}

.js .rise.is-in,
.js [data-stagger].is-in > * {
  opacity: 1;
  transform: none;
}

/* Once it has landed, stop promising the compositor anything. */
.js .rise.is-done,
.js [data-stagger].is-done > * { will-change: auto; }

/* Directional variants, for sections that read better entering sideways. */
.js .rise-left { transform: translate3d(-26px, 0, 0); }
.js .rise-right { transform: translate3d(26px, 0, 0); }
.js .rise-scale { transform: scale(0.965); opacity: 0; }
.js .rise-scale.is-in { transform: none; }

/* Legacy delay classes, kept so existing markup keeps working. */
.js .rise-2 { --i: 1; }
.js .rise-3 { --i: 2; }
.js .rise-4 { --i: 3; }

/* --- headline line entrance ----------------------------------------------
   The JS wraps each line of a `[data-lines]` heading in a span. The lines rise
   from behind a mask, one after another. This is the single most expensive
   looking effect on the site and it costs nothing. */
.js [data-lines] .ln {
  display: block;
  overflow: hidden;
}

.js [data-lines] .ln > i {
  display: block;
  font-style: inherit;
  transform: translate3d(0, 105%, 0);
  transition: transform 0.9s var(--ease-out);
  transition-delay: calc(var(--i, 0) * 90ms);
}

.js [data-lines].is-in .ln > i { transform: none; }

/* --- scroll parallax ------------------------------------------------------
   Applied to an image inside a fixed-ratio frame. The JS writes --p (-1..1)
   from the element's position in the viewport; the image drifts against the
   scroll. The frame must clip, and the image is oversized so the drift never
   exposes an edge. */
.parallax { overflow: hidden; }

/* :where() so this carries zero specificity. .hero-media is already
   position:absolute; a plain `.parallax { position: relative }` would beat it
   on source order and collapse the hero image to its content height. */
:where(.parallax) { position: relative; }

/* Absolutely positioned so the -6% offset resolves against the frame HEIGHT.
   A percentage margin-top would resolve against its WIDTH, which is a
   different number entirely and leaves a gap at the top of tall frames. */
.parallax > img {
  position: absolute;
  top: -6%;
  left: 0;
  width: 100%;
  height: 112%;
  object-fit: cover;
  transform: translate3d(0, calc(var(--p, 0) * -5%), 0);
}

@media (prefers-reduced-motion: no-preference) {
  .parallax > img { transition: transform 0.12s linear; }
}

/* --- hero load sequence ---------------------------------------------------
   Runs once on load rather than on scroll. The image settles out of a slow
   zoom while the text arrives over it. */
.hero-media > img {
  animation: hero-settle 1.6s var(--ease-out) both;
}

@keyframes hero-settle {
  from { transform: scale(1.07); opacity: 0; }
  to { transform: scale(1); opacity: 1; }
}

.js .hero [data-hero-seq] > *,
.js .hero-form-float[data-hero-seq] > * {
  opacity: 0;
  transform: translate3d(0, 18px, 0);
  animation: hero-in 0.9s var(--ease-out) forwards;
  animation-delay: calc(220ms + var(--i, 0) * 110ms);
}

@keyframes hero-in {
  to { opacity: 1; transform: none; }
}

/* --- number count-up ------------------------------------------------------
   The JS animates the text; this only stops the width jumping around while
   the digits change. */
[data-count] { font-variant-numeric: tabular-nums; }

/* --- card and tile micro-interactions ------------------------------------- */
.lift {
  transition:
    transform 0.5s var(--ease-out),
    box-shadow 0.5s var(--ease-out),
    border-color 0.4s var(--ease);
}

.lift:hover { transform: translateY(-5px); }

/* A gold rule that draws itself across the top of a card on hover. */
.rule-draw { position: relative; }

.rule-draw::before {
  content: "";
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 2px;
  background: var(--gold);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.55s var(--ease-out);
  z-index: 3;
}

.rule-draw:hover::before,
.rule-draw:focus-within::before { transform: scaleX(1); }

/* Media inside a card breathes on hover without moving the card text. */
.zoom-frame { overflow: hidden; }

.zoom-frame > img {
  transition: transform 0.9s var(--ease-out), filter 0.6s var(--ease);
}

.lift:hover .zoom-frame > img,
.pkg:hover .zoom-frame > img { transform: scale(1.055); }

/* --- the eyebrow rule draws in with its section --------------------------- */
.js .rise .eyebrow::after,
.js [data-stagger] .eyebrow::after {
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.8s var(--ease-out) 0.25s;
}

.js .rise.is-in .eyebrow::after,
.js [data-stagger].is-in .eyebrow::after { transform: scaleX(1); }

/* --- respect the setting --------------------------------------------------
   Not a blanket kill switch: entrances resolve instantly and continuous or
   scroll-driven motion is removed entirely, but hover feedback stays, because
   losing it makes the interface feel broken rather than calm. */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  .js .rise,
  .js [data-stagger] > *,
  .js [data-lines] .ln > i,
  .js .hero [data-hero-seq] > *,
  .js .hero-form-float[data-hero-seq] > * {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
    animation: none !important;
  }

  .hero-media > img { animation: none; }
  .parallax > img { transform: none !important; top: 0; height: 100%; }
  .btn::before { display: none; }

  .lift:hover { transform: none; }
  .lift:hover .zoom-frame > img,
  .pkg:hover .zoom-frame > img { transform: none; }
}
