/* ==========================================================================
   ESH'S TRUCK DRIVING SCHOOL — animations.css
   Keyframes · reveal classes · reduced-motion block

   THE SAFETY CONTRACT, because getting this wrong hides the whole site:

   1. Every "hidden until revealed" starting state lives INSIDE
      @media (prefers-reduced-motion: no-preference) and is scoped to
      `html.js`. So content is visible by default in all three failure modes:
        · JavaScript disabled or errored  → no .js class → never hidden
        · prefers-reduced-motion: reduce  → media query never matches → never hidden
        · IntersectionObserver unsupported → main.js reveals everything at once

   2. Only transform and opacity are animated. Nothing animates width, height,
      top, left or box-shadow in a loop.

   3. Nothing runs longer than --dur-slow (560ms).
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. KEYFRAMES
   -------------------------------------------------------------------------- */

/* The red motion arc from the logo, drawn on rather than faded in. */
@keyframes arc-draw {
  from { stroke-dashoffset: var(--arc-length, 1200); }
  to   { stroke-dashoffset: 0; }
}

/* Lane markings running the length of a divider. */
@keyframes lane-run {
  from { background-position-x: 0; }
  to   { background-position-x: var(--lane-period, 48px); }
}

/* Floating call / WhatsApp buttons: a slow, quiet pulse. */
@keyframes soft-pulse {
  0%, 100% { transform: scale(1);    opacity: .55; }
  50%      { transform: scale(1.35); opacity: 0; }
}

/* Hero entrance primitives. */
@keyframes rise-in {
  from { opacity: 0; transform: translate3d(0, 1.25rem, 0); }
  to   { opacity: 1; transform: none; }
}
@keyframes wipe-in {
  from { opacity: 0; transform: translate3d(-1.5rem, 0, 0); }
  to   { opacity: 1; transform: none; }
}
@keyframes plate-in {
  from { opacity: 0; transform: scale(.97); }
  to   { opacity: 1; transform: none; }
}

/* Scroll cue at the foot of the hero. */
@keyframes cue-drift {
  0%, 100% { transform: translateY(0);      opacity: .45; }
  50%      { transform: translateY(.45rem); opacity: 1; }
}

/* --------------------------------------------------------------------------
   2. SCROLL REVEALS
   Base state is "visible". The hidden state is applied only when we know
   animation is both wanted and possible.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: no-preference) {
  .js .reveal {
    opacity: 0;
    transform: translate3d(0, 1.5rem, 0);
    transition:
      opacity   var(--dur-slow) var(--ease-out) var(--reveal-delay, 0ms),
      transform var(--dur-slow) var(--ease-out) var(--reveal-delay, 0ms);
    will-change: opacity, transform;
  }

  /* Horizontal reveal offsets are desktop-only. On a phone the starting
     transform pushes the element past the right edge, and although the body
     clips it, it is a needless overflow risk for no visual gain at that size. */
  .js .reveal--left,
  .js .reveal--right { transform: translate3d(0, 1.5rem, 0); }
  @media (min-width: 48em) {
    .js .reveal--left  { transform: translate3d(-1.75rem, 0, 0); }
    .js .reveal--right { transform: translate3d(1.75rem, 0, 0); }
  }
  .js .reveal--scale { transform: scale(.96); }

  .js .reveal.is-visible {
    opacity: 1;
    transform: none;
    will-change: auto;
  }

  /* Staggered children. The delay is capped so a long list never leaves the
     last item waiting; anything past the 8th arrives with the 8th. */
  .js .reveal-group > * {
    opacity: 0;
    transform: translate3d(0, 1.25rem, 0);
    transition:
      opacity   var(--dur-mid) var(--ease-out),
      transform var(--dur-mid) var(--ease-out);
  }
  .js .reveal-group.is-visible > * {
    opacity: 1;
    transform: none;
  }
  .js .reveal-group.is-visible > *:nth-child(1) { transition-delay: 0ms; }
  .js .reveal-group.is-visible > *:nth-child(2) { transition-delay: 60ms; }
  .js .reveal-group.is-visible > *:nth-child(3) { transition-delay: 120ms; }
  .js .reveal-group.is-visible > *:nth-child(4) { transition-delay: 180ms; }
  .js .reveal-group.is-visible > *:nth-child(5) { transition-delay: 240ms; }
  .js .reveal-group.is-visible > *:nth-child(6) { transition-delay: 300ms; }
  .js .reveal-group.is-visible > *:nth-child(n + 7) { transition-delay: 360ms; }

  /* ---- Hero load cascade ------------------------------------------------
     Headline, sub-line, CTAs and trust plate arrive in sequence rather than
     all at once. Runs on page load, not on scroll. */
  .js .hero__eyebrow { animation: wipe-in var(--dur-mid) var(--ease-out) 120ms both; }
  .js .hero__title span { animation: rise-in var(--dur-slow) var(--ease-out) both; }
  .js .hero__title span:nth-child(1) { animation-delay: 180ms; }
  .js .hero__title span:nth-child(2) { animation-delay: 280ms; }
  .js .hero__title span:nth-child(3) { animation-delay: 380ms; }
  .js .hero__sub     { animation: rise-in var(--dur-mid) var(--ease-out) 500ms both; }
  .js .hero__actions { animation: rise-in var(--dur-mid) var(--ease-out) 580ms both; }
  .js .hero__plate   { animation: plate-in var(--dur-mid) var(--ease-out) 660ms both; }
  .js .hero__cue     { animation: cue-drift 2.6s var(--ease-in-out) 1.2s infinite; }

  .js .hero__arc path {
    stroke-dasharray: var(--arc-length, 1200);
    animation: arc-draw var(--dur-slow) var(--ease-out) 240ms both;
  }

  /* ---- The Ladder -------------------------------------------------------
     The road draws itself station by station when the block comes into view. */
  .js .ladder__road::after {
    transform: scaleX(0);
    transform-origin: left center;
    transition: transform var(--dur-slow) var(--ease-out);
  }
  .js .ladder.is-visible .ladder__road::after { transform: scaleX(1); }

  .js .ladder__express path {
    stroke-dasharray: var(--arc-length, 600);
    stroke-dashoffset: var(--arc-length, 600);
    transition: stroke-dashoffset var(--dur-slow) var(--ease-out) 320ms;
  }
  .js .ladder.is-visible .ladder__express path { stroke-dashoffset: 0; }

  .js .station { opacity: 0; transform: translate3d(0, 1rem, 0); }
  .js .ladder.is-visible .station {
    opacity: 1;
    transform: none;
    transition:
      opacity   var(--dur-mid) var(--ease-out),
      transform var(--dur-mid) var(--ease-out);
  }
  .js .ladder.is-visible .station:nth-child(1) { transition-delay: 80ms; }
  .js .ladder.is-visible .station:nth-child(2) { transition-delay: 160ms; }
  .js .ladder.is-visible .station:nth-child(3) { transition-delay: 240ms; }
  .js .ladder.is-visible .station:nth-child(4) { transition-delay: 320ms; }
  .js .ladder.is-visible .station:nth-child(5) { transition-delay: 400ms; }

  /* ---- Lane-marking dividers -------------------------------------------- */
  .lane-rule::before { animation: lane-run 1.6s linear infinite; }

  /* ---- Floating buttons -------------------------------------------------- */
  .float-btn::after { animation: soft-pulse 2.8s var(--ease-in-out) infinite; }
}

/* --------------------------------------------------------------------------
   3. REDUCED MOTION
   The only !important declarations in the codebase. They are here because
   this block must beat every component-level transition and animation
   regardless of specificity — a user asking for less motion is not a
   suggestion.
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
    transition-delay: 0ms !important;
    scroll-behavior: auto !important;
  }

  html { scroll-behavior: auto !important; }

  /* Everything that would have animated in is simply already here. */
  .reveal,
  .reveal-group > *,
  .station {
    opacity: 1 !important;
    transform: none !important;
  }

  .ladder__road::after      { transform: scaleX(1) !important; }
  .ladder__express path,
  .hero__arc path           { stroke-dashoffset: 0 !important; stroke-dasharray: none !important; }
  .float-btn::after         { display: none !important; }
  .hero__cue                { opacity: .7 !important; }

  /* Parallax is a transform driven by scroll; it is switched off in JS too. */
  .parallax__media { transform: none !important; }
}
