/* ===========================================================================
   Motion
   ===========================================================================
   Scroll-driven entrances and scroll-linked transforms — the vocabulary
   Framer Motion gives a React app, written here as CSS custom properties a
   plain page can use. Framer Motion itself is a React library and this site
   is Blade and jQuery; importing a renderer to animate a heading would be
   several hundred kilobytes to move something twenty pixels.

   TWO KINDS OF MOTION, AND THEY ARE NOT THE SAME THING

   1. Entrances. An element crosses into view once and arrives — from a
      direction, out of a zoom, unsqueezing, or wiped in behind a mask. Set by
      [data-m] and triggered by adding .is-in. Runs once.

   2. Scroll-linked. A value tracks scroll position continuously, so the
      element keeps moving while the page does: parallax, a squeeze that
      tightens as the section leaves, a photograph that scales as it passes.
      Set by [data-m-scroll]; the script writes --p, 0 to 1, and these rules
      spend it.

   Everything below is inert until the script marks it. If the JavaScript
   fails, every element is at its final state and the page is exactly the page
   — no content is hidden waiting for an animation that will not come, which
   is the way scroll-reveal libraries usually break.
   ========================================================================= */

/* The starting states. Only applied once the script has said it is running,
   so a page with no JS never hides anything. */
.hww-motion [data-m] {
    opacity: 0;
    transform: var(--m-from, translateY(24px));
    filter: var(--m-blur, none);
    transition:
        opacity var(--m-dur, .7s) var(--m-ease, cubic-bezier(.22, .61, .36, 1)) var(--m-delay, 0ms),
        transform var(--m-dur, .7s) var(--m-ease, cubic-bezier(.22, .61, .36, 1)) var(--m-delay, 0ms),
        filter var(--m-dur, .7s) var(--m-ease, cubic-bezier(.22, .61, .36, 1)) var(--m-delay, 0ms),
        clip-path var(--m-dur, .7s) var(--m-ease, cubic-bezier(.22, .61, .36, 1)) var(--m-delay, 0ms);
    will-change: opacity, transform;
}

.hww-motion [data-m].is-in {
    opacity: 1;
    transform: none;
    filter: none;
    clip-path: inset(0 0 0 0);
    will-change: auto;
}

/* --- the directions ----------------------------------------------------- */
.hww-motion [data-m="up"]     { --m-from: translateY(28px); }
.hww-motion [data-m="down"]   { --m-from: translateY(-28px); }
.hww-motion [data-m="left"]   { --m-from: translateX(-36px); }
.hww-motion [data-m="right"]  { --m-from: translateX(36px); }

/* Zoom in from slightly small, which reads as approaching rather than as
   growing — the difference is the 8%, more than that and it lunges. */
.hww-motion [data-m="zoom"]   { --m-from: scale(.92); }
.hww-motion [data-m="pop"]    { --m-from: scale(.86); --m-ease: cubic-bezier(.34, 1.56, .64, 1); }

/* Squeeze: flattened on its own base, springing back up. Cards and figures
   take this well; text does not — squeezed type is unreadable for the frames
   it is squeezed. */
.hww-motion [data-m="squeeze"] {
    --m-from: scaleY(.72);
    transform-origin: 50% 100%;
}
.hww-motion [data-m="squeeze-x"] {
    --m-from: scaleX(.8);
    transform-origin: 0 50%;
}

/* A wipe. The element is whole the entire time and a mask uncovers it, which
   is the one entrance that does not move the thing being read. */
.hww-motion [data-m="wipe"] {
    --m-from: none;
    opacity: 1;
    clip-path: inset(0 100% 0 0);
}
.hww-motion [data-m="wipe-up"] {
    --m-from: none;
    opacity: 1;
    clip-path: inset(100% 0 0 0);
}

.hww-motion [data-m="blur"] { --m-from: translateY(14px); --m-blur: blur(10px); }

/* Stagger. Children of a group land a few frames apart; --i is written by the
   script from each child's position, so nothing has to be numbered by hand. */
.hww-motion [data-m] { --m-delay: calc(var(--i, 0) * var(--m-step, 80ms)); }

/* --- scroll-linked ------------------------------------------------------ */
/* --p runs 0 → 1 across the element's travel through the viewport. */
.hww-motion [data-m-scroll] { will-change: transform; }

.hww-motion [data-m-scroll="parallax"] {
    transform: translate3d(0, calc((var(--p, .5) - .5) * var(--m-range, 60px) * -1), 0);
}
.hww-motion [data-m-scroll="parallax-slow"] { --m-range: 30px; }
.hww-motion [data-m-scroll="parallax-fast"] { --m-range: 110px; }

/* A photograph that keeps growing while its section is on screen. Starts
   already a little large so the crop never shows an edge. */
.hww-motion [data-m-scroll="grow"] {
    transform: scale(calc(1.06 + var(--p, 0) * .1));
}

/* The squeeze on the way past: full height in the middle of the viewport,
   compressed at both ends. --p is remapped by the script to 0 at the edges
   and 1 in the middle for this one. */
.hww-motion [data-m-scroll="squeeze"] {
    transform: scaleY(calc(.94 + var(--p, 1) * .06));
    transform-origin: 50% 50%;
}

/* A rule or bar that draws itself as the section arrives. */
.hww-motion [data-m-scroll="draw"] {
    transform: scaleX(var(--p, 1));
    transform-origin: 0 50%;
}

/*
 | Reduced motion means no motion, not less of it.
 |
 | Everything is at its final state and stays there. The script also stops
 | registering scroll-linked elements, so nothing is being computed either.
 */
@media (prefers-reduced-motion: reduce) {
    .hww-motion [data-m],
    .hww-motion [data-m].is-in,
    .hww-motion [data-m-scroll] {
        opacity: 1 !important;
        transform: none !important;
        filter: none !important;
        clip-path: none !important;
        transition: none !important;
    }
}
