/* ==========================================================================
   GsmWhale — directional motion library
   assets/css/animations.css

   A single-file animation system covering every directional fade the site can
   use, plus zoom / flip / roll / blur / bounce / wipe variants and a set of
   looping idles.

   HOW IT WORKS
   ------------
   1. Markup carries no animation classes. Elements opt in with a data
      attribute:  <div data-anim="fade-up-right">.
      (assets/js/animations.js also stamps this attribute onto content blocks
      automatically, so pages do not have to be hand-tagged.)
   2. The element is hidden (opacity 0) only while it carries BOTH the
      `gw-anim-ready` marker on <html> and its own `gw-anim-pending` class —
      both applied by JS. If JS never runs, nothing is ever hidden.
   3. When the element scrolls into view the observer swaps `gw-anim-pending`
      for `gw-anim-in`, which starts the keyframes.
   4. On animationend both helper classes are removed, so the element keeps no
      residual transform. That matters: a leftover `transform` would create a
      containing block and silently break any `position: fixed/sticky`
      descendant.

   ONE KEYFRAME DRIVES MOST OF IT
   ------------------------------
   Rather than 40 near-identical @keyframes, the 2D family is a single
   `gwAnimIn` whose FROM state is composed entirely from custom properties
   (--gw-a-x / --gw-a-y / --gw-a-scale / --gw-a-rot / --gw-a-blur / ...).
   A direction class only sets the offsets, so `fade-up`, `zoom-in-left` and
   `blur-in-right` are three variable assignments, not three animations.
   3D moves (flips, rolls) use the parallel `gwAnimIn3D`, chosen by setting
   --gw-a-name rather than by fighting selector specificity.

   DIRECTION NAMING (AOS convention — the direction is where the element
   TRAVELS TO, not where it starts):
       fade-up        starts below,  moves up
       fade-down      starts above,  moves down
       fade-left      starts right,  moves left
       fade-right     starts left,   moves right
       fade-up-right  starts down-left,  moves up-right
       fade-up-left   starts down-right, moves up-left
       fade-down-right / fade-down-left  mirror the two above.
   ========================================================================== */

/* --------------------------------------------------------------------------
   1. Defaults. Every variable can be overridden per element with a data
      attribute (data-anim-distance / -duration / -delay / -ease) or with one
      of the utility classes at the bottom of this file.
   -------------------------------------------------------------------------- */
[data-anim],
[data-anim-loop] {
    --gw-a-name: gwAnimIn;
    --gw-a-x: 0px;
    --gw-a-y: 0px;
    --gw-a-scale: 1;
    --gw-a-rot: 0deg;
    --gw-a-blur: 0px;
    --gw-a-skew: 0deg;
    --gw-a-dist: 40px;
    --gw-a-dur: .62s;
    --gw-a-delay: 0s;
    --gw-a-ease: cubic-bezier(.22, .68, .36, 1);
    /* 3D axis (rx, ry, rz) + angle — only read by gwAnimIn3D */
    --gw-a-rx: 0;
    --gw-a-ry: 0;
    --gw-a-rz: 1;
    --gw-a-rang: 0deg;
    --gw-a-op0: 0
}

/* --------------------------------------------------------------------------
   2. Master keyframes
   -------------------------------------------------------------------------- */

/* 2D: translate + scale + rotate + skew + blur, all variable driven. */
@keyframes gwAnimIn {
    from {
        opacity: var(--gw-a-op0, 0);
        transform: translate3d(var(--gw-a-x, 0px), var(--gw-a-y, 0px), 0) scale(var(--gw-a-scale, 1)) rotate(var(--gw-a-rot, 0deg)) skewX(var(--gw-a-skew, 0deg));
        filter: blur(var(--gw-a-blur, 0px))
    }

    to {
        opacity: 1;
        transform: translate3d(0, 0, 0) scale(1) rotate(0deg) skewX(0deg);
        filter: blur(0px)
    }
}

/* 3D: flips and rolls. Perspective is baked in so the rotation reads as depth. */
@keyframes gwAnimIn3D {
    from {
        opacity: var(--gw-a-op0, 0);
        transform: perspective(1000px) translate3d(var(--gw-a-x, 0px), var(--gw-a-y, 0px), 0) scale(var(--gw-a-scale, 1)) rotate3d(var(--gw-a-rx, 0), var(--gw-a-ry, 0), var(--gw-a-rz, 1), var(--gw-a-rang, 0deg));
        filter: blur(var(--gw-a-blur, 0px))
    }

    to {
        opacity: 1;
        transform: perspective(1000px) translate3d(0, 0, 0) scale(1) rotate3d(0, 0, 1, 0deg);
        filter: blur(0px)
    }
}

/* Exit direction: same offsets, played backwards to opacity 0. Used only when
   an element opts in with data-anim-exit — by default content stays put once
   it has arrived, because re-hiding content that is still on screen reads as a
   bug rather than as polish. */
@keyframes gwAnimOut {
    from {
        opacity: 1;
        transform: translate3d(0, 0, 0) scale(1);
        filter: blur(0px)
    }

    to {
        opacity: 0;
        transform: translate3d(var(--gw-a-x, 0px), var(--gw-a-y, 0px), 0) scale(var(--gw-a-scale, 1));
        filter: blur(var(--gw-a-blur, 0px))
    }
}

/* --------------------------------------------------------------------------
   3. State hooks
   -------------------------------------------------------------------------- */

/* Hidden before reveal. The `.gw-anim-ready` ancestor is added by JS, so a
   failed script leaves every element at its natural opacity. */
.gw-anim-ready [data-anim].gw-anim-pending {
    opacity: 0
}

[data-anim].gw-anim-in {
    animation-name: var(--gw-a-name);
    animation-duration: var(--gw-a-dur);
    animation-timing-function: var(--gw-a-ease);
    animation-delay: var(--gw-a-delay);
    animation-fill-mode: both;
    /* Scoped to the animation only: the driver drops .gw-anim-in on
       animationend, so nothing keeps a permanent compositor hint. Only the
       handful of elements animating at any moment carry it. */
    will-change: opacity, transform
}

[data-anim].gw-anim-out {
    animation-name: gwAnimOut;
    animation-duration: var(--gw-a-dur);
    animation-timing-function: ease-in;
    animation-fill-mode: forwards
}

/* --------------------------------------------------------------------------
   4. Direction map — pure fades
   -------------------------------------------------------------------------- */
[data-anim="fade-in"] {
    --gw-a-op0: 0
}

[data-anim="fade-up"] {
    --gw-a-y: var(--gw-a-dist)
}

[data-anim="fade-down"] {
    --gw-a-y: calc(-1 * var(--gw-a-dist))
}

[data-anim="fade-left"] {
    --gw-a-x: var(--gw-a-dist)
}

[data-anim="fade-right"] {
    --gw-a-x: calc(-1 * var(--gw-a-dist))
}

[data-anim="fade-up-right"] {
    --gw-a-x: calc(-1 * var(--gw-a-dist));
    --gw-a-y: var(--gw-a-dist)
}

[data-anim="fade-up-left"] {
    --gw-a-x: var(--gw-a-dist);
    --gw-a-y: var(--gw-a-dist)
}

[data-anim="fade-down-right"] {
    --gw-a-x: calc(-1 * var(--gw-a-dist));
    --gw-a-y: calc(-1 * var(--gw-a-dist))
}

[data-anim="fade-down-left"] {
    --gw-a-x: var(--gw-a-dist);
    --gw-a-y: calc(-1 * var(--gw-a-dist))
}

/* Long-throw variants: same direction, roughly 2.4x the travel. Handy for
   tall sections or full-bleed blocks where 40px reads as nothing. */
[data-anim="fade-up-big"] {
    --gw-a-y: calc(var(--gw-a-dist) * 2.4)
}

[data-anim="fade-down-big"] {
    --gw-a-y: calc(-1 * var(--gw-a-dist) * 2.4)
}

[data-anim="fade-left-big"] {
    --gw-a-x: calc(var(--gw-a-dist) * 2.4)
}

[data-anim="fade-right-big"] {
    --gw-a-x: calc(-1 * var(--gw-a-dist) * 2.4)
}

/* Slides: fades with a longer throw and a flatter curve, so they read as
   "travel" rather than "settle". */
[data-anim="slide-up"],
[data-anim="slide-in-up"] {
    --gw-a-y: calc(var(--gw-a-dist) * 2.2);
    --gw-a-ease: cubic-bezier(.16, .84, .28, 1)
}

[data-anim="slide-down"],
[data-anim="slide-in-down"] {
    --gw-a-y: calc(-1 * var(--gw-a-dist) * 2.2);
    --gw-a-ease: cubic-bezier(.16, .84, .28, 1)
}

[data-anim="slide-left"],
[data-anim="slide-in-left"] {
    --gw-a-x: calc(var(--gw-a-dist) * 2.2);
    --gw-a-ease: cubic-bezier(.16, .84, .28, 1)
}

[data-anim="slide-right"],
[data-anim="slide-in-right"] {
    --gw-a-x: calc(-1 * var(--gw-a-dist) * 2.2);
    --gw-a-ease: cubic-bezier(.16, .84, .28, 1)
}

/* --------------------------------------------------------------------------
   5. Zoom family — scale combined with an optional direction
   -------------------------------------------------------------------------- */
[data-anim="zoom-in"] {
    --gw-a-scale: .82
}

[data-anim="zoom-out"] {
    --gw-a-scale: 1.18
}

[data-anim="zoom-in-up"] {
    --gw-a-scale: .88;
    --gw-a-y: var(--gw-a-dist)
}

[data-anim="zoom-in-down"] {
    --gw-a-scale: .88;
    --gw-a-y: calc(-1 * var(--gw-a-dist))
}

[data-anim="zoom-in-left"] {
    --gw-a-scale: .88;
    --gw-a-x: var(--gw-a-dist)
}

[data-anim="zoom-in-right"] {
    --gw-a-scale: .88;
    --gw-a-x: calc(-1 * var(--gw-a-dist))
}

[data-anim="zoom-out-up"] {
    --gw-a-scale: 1.14;
    --gw-a-y: calc(var(--gw-a-dist) * .6)
}

[data-anim="zoom-out-down"] {
    --gw-a-scale: 1.14;
    --gw-a-y: calc(-1 * var(--gw-a-dist) * .6)
}

[data-anim="zoom-out-left"] {
    --gw-a-scale: 1.14;
    --gw-a-x: calc(var(--gw-a-dist) * .6)
}

[data-anim="zoom-out-right"] {
    --gw-a-scale: 1.14;
    --gw-a-x: calc(-1 * var(--gw-a-dist) * .6)
}

/* Diagonal zooms */
[data-anim="zoom-in-up-left"] {
    --gw-a-scale: .88;
    --gw-a-x: var(--gw-a-dist);
    --gw-a-y: var(--gw-a-dist)
}

[data-anim="zoom-in-up-right"] {
    --gw-a-scale: .88;
    --gw-a-x: calc(-1 * var(--gw-a-dist));
    --gw-a-y: var(--gw-a-dist)
}

[data-anim="zoom-in-down-left"] {
    --gw-a-scale: .88;
    --gw-a-x: var(--gw-a-dist);
    --gw-a-y: calc(-1 * var(--gw-a-dist))
}

[data-anim="zoom-in-down-right"] {
    --gw-a-scale: .88;
    --gw-a-x: calc(-1 * var(--gw-a-dist));
    --gw-a-y: calc(-1 * var(--gw-a-dist))
}

/* --------------------------------------------------------------------------
   6. Rotate / blur / skew
   -------------------------------------------------------------------------- */
[data-anim="rotate-in"] {
    --gw-a-rot: -9deg;
    --gw-a-scale: .92
}

[data-anim="rotate-in-left"] {
    --gw-a-rot: -14deg;
    --gw-a-x: var(--gw-a-dist)
}

[data-anim="rotate-in-right"] {
    --gw-a-rot: 14deg;
    --gw-a-x: calc(-1 * var(--gw-a-dist))
}

[data-anim="rotate-in-up"] {
    --gw-a-rot: -7deg;
    --gw-a-y: var(--gw-a-dist)
}

[data-anim="rotate-in-down"] {
    --gw-a-rot: 7deg;
    --gw-a-y: calc(-1 * var(--gw-a-dist))
}

[data-anim="rotate-in-down-left"] {
    --gw-a-rot: -45deg;
    --gw-a-x: var(--gw-a-dist);
    --gw-a-y: calc(-1 * var(--gw-a-dist))
}

[data-anim="rotate-in-down-right"] {
    --gw-a-rot: 45deg;
    --gw-a-x: calc(-1 * var(--gw-a-dist));
    --gw-a-y: calc(-1 * var(--gw-a-dist))
}

[data-anim="rotate-in-up-left"] {
    --gw-a-rot: 45deg;
    --gw-a-x: var(--gw-a-dist);
    --gw-a-y: var(--gw-a-dist)
}

[data-anim="rotate-in-up-right"] {
    --gw-a-rot: -45deg;
    --gw-a-x: calc(-1 * var(--gw-a-dist));
    --gw-a-y: var(--gw-a-dist)
}

[data-anim="blur-in"] {
    --gw-a-blur: 12px
}

[data-anim="blur-in-up"] {
    --gw-a-blur: 12px;
    --gw-a-y: var(--gw-a-dist)
}

[data-anim="blur-in-down"] {
    --gw-a-blur: 12px;
    --gw-a-y: calc(-1 * var(--gw-a-dist))
}

[data-anim="blur-in-left"] {
    --gw-a-blur: 12px;
    --gw-a-x: var(--gw-a-dist)
}

[data-anim="blur-in-right"] {
    --gw-a-blur: 12px;
    --gw-a-x: calc(-1 * var(--gw-a-dist))
}

[data-anim="skew-in-left"] {
    --gw-a-skew: -8deg;
    --gw-a-x: var(--gw-a-dist)
}

[data-anim="skew-in-right"] {
    --gw-a-skew: 8deg;
    --gw-a-x: calc(-1 * var(--gw-a-dist))
}

[data-anim="skew-in-up"] {
    --gw-a-skew: -6deg;
    --gw-a-y: var(--gw-a-dist)
}

[data-anim="skew-in-down"] {
    --gw-a-skew: 6deg;
    --gw-a-y: calc(-1 * var(--gw-a-dist))
}

/* --------------------------------------------------------------------------
   7. 3D flips and rolls
   -------------------------------------------------------------------------- */
[data-anim="flip-left"] {
    --gw-a-name: gwAnimIn3D;
    --gw-a-ry: 1;
    --gw-a-rang: -92deg
}

[data-anim="flip-right"] {
    --gw-a-name: gwAnimIn3D;
    --gw-a-ry: 1;
    --gw-a-rang: 92deg
}

[data-anim="flip-up"] {
    --gw-a-name: gwAnimIn3D;
    --gw-a-rx: 1;
    --gw-a-rang: 88deg
}

[data-anim="flip-down"] {
    --gw-a-name: gwAnimIn3D;
    --gw-a-rx: 1;
    --gw-a-rang: -88deg
}

[data-anim="flip-diag-left"] {
    --gw-a-name: gwAnimIn3D;
    --gw-a-rx: 1;
    --gw-a-ry: 1;
    --gw-a-rang: -92deg
}

[data-anim="flip-diag-right"] {
    --gw-a-name: gwAnimIn3D;
    --gw-a-rx: 1;
    --gw-a-ry: 1;
    --gw-a-rang: 92deg
}

[data-anim="tilt-in"] {
    --gw-a-name: gwAnimIn3D;
    --gw-a-rx: 1;
    --gw-a-rang: 26deg;
    --gw-a-y: var(--gw-a-dist)
}

[data-anim="roll-in-left"] {
    --gw-a-name: gwRollInLeft
}

[data-anim="roll-in-right"] {
    --gw-a-name: gwRollInRight
}

@keyframes gwRollInLeft {
    0% {
        opacity: 0;
        transform: translate3d(calc(var(--gw-a-dist) * -1.7), 0, 0) rotate3d(0, 0, 1, -120deg)
    }

    100% {
        opacity: 1;
        transform: translate3d(0, 0, 0) rotate3d(0, 0, 1, 0deg)
    }
}

@keyframes gwRollInRight {
    0% {
        opacity: 0;
        transform: translate3d(calc(var(--gw-a-dist) * 1.7), 0, 0) rotate3d(0, 0, 1, 120deg)
    }

    100% {
        opacity: 1;
        transform: translate3d(0, 0, 0) rotate3d(0, 0, 1, 0deg)
    }
}

/* --------------------------------------------------------------------------
   8. Overshoot family — dedicated multi-stop keyframes, because a bounce
   cannot be expressed as a single FROM state.
   -------------------------------------------------------------------------- */
[data-anim="bounce-in"] {
    --gw-a-name: gwBounceIn;
    --gw-a-ease: cubic-bezier(.28, .84, .42, 1)
}

[data-anim="bounce-in-up"] {
    --gw-a-name: gwBounceInUp;
    --gw-a-ease: linear
}

[data-anim="bounce-in-down"] {
    --gw-a-name: gwBounceInDown;
    --gw-a-ease: linear
}

[data-anim="bounce-in-left"] {
    --gw-a-name: gwBounceInLeft;
    --gw-a-ease: linear
}

[data-anim="bounce-in-right"] {
    --gw-a-name: gwBounceInRight;
    --gw-a-ease: linear
}

[data-anim="pop-in"] {
    --gw-a-name: gwPopIn;
    --gw-a-dur: .42s;
    --gw-a-ease: cubic-bezier(.34, 1.5, .64, 1)
}

[data-anim="spring-up"] {
    --gw-a-name: gwSpringUp;
    --gw-a-ease: linear
}

[data-anim="swing-in"] {
    --gw-a-name: gwSwingIn;
    --gw-a-ease: ease-out
}

[data-anim="drop-in"] {
    --gw-a-name: gwDropIn;
    --gw-a-ease: cubic-bezier(.3, .9, .4, 1)
}

[data-anim="rise-up"] {
    --gw-a-name: gwRiseUp
}

[data-anim="elastic-x"] {
    --gw-a-name: gwElasticX;
    --gw-a-ease: linear
}

@keyframes gwBounceIn {
    0% {
        opacity: 0;
        transform: scale3d(.32, .32, .32)
    }

    22% {
        opacity: 1;
        transform: scale3d(1.12, 1.12, 1.12)
    }

    44% {
        transform: scale3d(.9, .9, .9)
    }

    66% {
        transform: scale3d(1.04, 1.04, 1.04)
    }

    84% {
        transform: scale3d(.98, .98, .98)
    }

    to {
        opacity: 1;
        transform: scale3d(1, 1, 1)
    }
}

@keyframes gwBounceInUp {
    0% {
        opacity: 0;
        transform: translate3d(0, var(--gw-a-dist), 0)
    }

    58% {
        opacity: 1;
        transform: translate3d(0, calc(var(--gw-a-dist) * -.18), 0)
    }

    80% {
        transform: translate3d(0, calc(var(--gw-a-dist) * .07), 0)
    }

    to {
        opacity: 1;
        transform: translate3d(0, 0, 0)
    }
}

@keyframes gwBounceInDown {
    0% {
        opacity: 0;
        transform: translate3d(0, calc(-1 * var(--gw-a-dist)), 0)
    }

    58% {
        opacity: 1;
        transform: translate3d(0, calc(var(--gw-a-dist) * .18), 0)
    }

    80% {
        transform: translate3d(0, calc(var(--gw-a-dist) * -.07), 0)
    }

    to {
        opacity: 1;
        transform: translate3d(0, 0, 0)
    }
}

@keyframes gwBounceInLeft {
    0% {
        opacity: 0;
        transform: translate3d(var(--gw-a-dist), 0, 0)
    }

    58% {
        opacity: 1;
        transform: translate3d(calc(var(--gw-a-dist) * -.18), 0, 0)
    }

    80% {
        transform: translate3d(calc(var(--gw-a-dist) * .07), 0, 0)
    }

    to {
        opacity: 1;
        transform: translate3d(0, 0, 0)
    }
}

@keyframes gwBounceInRight {
    0% {
        opacity: 0;
        transform: translate3d(calc(-1 * var(--gw-a-dist)), 0, 0)
    }

    58% {
        opacity: 1;
        transform: translate3d(calc(var(--gw-a-dist) * .18), 0, 0)
    }

    80% {
        transform: translate3d(calc(var(--gw-a-dist) * -.07), 0, 0)
    }

    to {
        opacity: 1;
        transform: translate3d(0, 0, 0)
    }
}

@keyframes gwPopIn {
    0% {
        opacity: 0;
        transform: scale(.62)
    }

    72% {
        opacity: 1;
        transform: scale(1.06)
    }

    to {
        opacity: 1;
        transform: scale(1)
    }
}

@keyframes gwSpringUp {
    0% {
        opacity: 0;
        transform: translate3d(0, var(--gw-a-dist), 0) scale(.96)
    }

    55% {
        opacity: 1;
        transform: translate3d(0, calc(var(--gw-a-dist) * -.16), 0) scale(1.015)
    }

    to {
        opacity: 1;
        transform: translate3d(0, 0, 0) scale(1)
    }
}

@keyframes gwSwingIn {
    0% {
        opacity: 0;
        transform: rotate3d(0, 0, 1, -13deg)
    }

    62% {
        opacity: 1;
        transform: rotate3d(0, 0, 1, 4.5deg)
    }

    to {
        opacity: 1;
        transform: rotate3d(0, 0, 1, 0deg)
    }
}

@keyframes gwDropIn {
    0% {
        opacity: 0;
        transform: translate3d(0, calc(var(--gw-a-dist) * -4), 0)
    }

    62% {
        opacity: 1;
        transform: translate3d(0, calc(var(--gw-a-dist) * .2), 0)
    }

    84% {
        transform: translate3d(0, calc(var(--gw-a-dist) * -.07), 0)
    }

    to {
        opacity: 1;
        transform: translate3d(0, 0, 0)
    }
}

@keyframes gwRiseUp {
    0% {
        opacity: 0;
        transform: translate3d(0, var(--gw-a-dist), 0) scale(.975)
    }

    to {
        opacity: 1;
        transform: translate3d(0, 0, 0) scale(1)
    }
}

@keyframes gwElasticX {
    0% {
        opacity: 0;
        transform: scaleX(.42)
    }

    46% {
        opacity: 1;
        transform: scaleX(1.06)
    }

    72% {
        transform: scaleX(.97)
    }

    to {
        opacity: 1;
        transform: scaleX(1)
    }
}

/* --------------------------------------------------------------------------
   9. Mask / wipe reveals — clip-path based, good for hero images and banners
   -------------------------------------------------------------------------- */
[data-anim="wipe-right"] {
    --gw-a-name: gwWipeRight;
    --gw-a-ease: cubic-bezier(.4, 0, .2, 1)
}

[data-anim="wipe-left"] {
    --gw-a-name: gwWipeLeft;
    --gw-a-ease: cubic-bezier(.4, 0, .2, 1)
}

[data-anim="wipe-up"] {
    --gw-a-name: gwWipeUp;
    --gw-a-ease: cubic-bezier(.4, 0, .2, 1)
}

[data-anim="wipe-down"] {
    --gw-a-name: gwWipeDown;
    --gw-a-ease: cubic-bezier(.4, 0, .2, 1)
}

[data-anim="curtain-up"] {
    --gw-a-name: gwCurtainUp;
    --gw-a-ease: cubic-bezier(.22, 1, .36, 1)
}

@keyframes gwWipeRight {
    from {
        clip-path: inset(0 100% 0 0);
        opacity: .35
    }

    to {
        clip-path: inset(0 0 0 0);
        opacity: 1
    }
}

@keyframes gwWipeLeft {
    from {
        clip-path: inset(0 0 0 100%);
        opacity: .35
    }

    to {
        clip-path: inset(0 0 0 0);
        opacity: 1
    }
}

@keyframes gwWipeUp {
    from {
        clip-path: inset(100% 0 0 0);
        opacity: .35
    }

    to {
        clip-path: inset(0 0 0 0);
        opacity: 1
    }
}

@keyframes gwWipeDown {
    from {
        clip-path: inset(0 0 100% 0);
        opacity: .35
    }

    to {
        clip-path: inset(0 0 0 0);
        opacity: 1
    }
}

@keyframes gwCurtainUp {
    from {
        clip-path: inset(100% 0 0 0)
    }

    to {
        clip-path: inset(0 0 0 0)
    }
}

/* --------------------------------------------------------------------------
   10. Exit variants (opt-in via data-anim-exit)
   -------------------------------------------------------------------------- */
[data-anim="fade-out"],
[data-anim-exit="fade-out"] {
    --gw-a-op0: 1
}

[data-anim="fade-out-up"],
[data-anim-exit="fade-out-up"] {
    --gw-a-y: calc(-1 * var(--gw-a-dist))
}

[data-anim="fade-out-down"],
[data-anim-exit="fade-out-down"] {
    --gw-a-y: var(--gw-a-dist)
}

[data-anim="fade-out-left"],
[data-anim-exit="fade-out-left"] {
    --gw-a-x: calc(-1 * var(--gw-a-dist))
}

[data-anim="fade-out-right"],
[data-anim-exit="fade-out-right"] {
    --gw-a-x: var(--gw-a-dist)
}

[data-anim="fade-out-up-right"],
[data-anim-exit="fade-out-up-right"] {
    --gw-a-x: var(--gw-a-dist);
    --gw-a-y: calc(-1 * var(--gw-a-dist))
}

[data-anim="fade-out-up-left"],
[data-anim-exit="fade-out-up-left"] {
    --gw-a-x: calc(-1 * var(--gw-a-dist));
    --gw-a-y: calc(-1 * var(--gw-a-dist))
}

/* --------------------------------------------------------------------------
   11. Looping idles — data-anim-loop="name". Independent of the reveal pass:
       these run forever and never hide anything.
   -------------------------------------------------------------------------- */
[data-anim-loop="float"] {
    animation: gwFloat 3.4s ease-in-out infinite
}

[data-anim-loop="bob"] {
    animation: gwBob 2.4s ease-in-out infinite
}

[data-anim-loop="pulse-soft"] {
    animation: gwPulseSoft 2.8s ease-in-out infinite
}

[data-anim-loop="heartbeat"] {
    animation: gwHeartbeat 1.6s ease-in-out infinite
}

[data-anim-loop="wiggle"] {
    animation: gwWiggle 1.4s ease-in-out infinite
}

[data-anim-loop="shake-x"] {
    animation: gwShakeX 2.6s ease-in-out infinite
}

[data-anim-loop="spin-slow"] {
    animation: gwSpinSlow 9s linear infinite
}

[data-anim-loop="glow"] {
    animation: gwGlowPulse 3.2s ease-in-out infinite
}

[data-anim-loop="sheen"] {
    animation: gwSheenSweep 4.6s ease-in-out infinite
}

@keyframes gwFloat {

    0%,
    100% {
        transform: translateY(0)
    }

    50% {
        transform: translateY(-11px)
    }
}

@keyframes gwBob {

    0%,
    100% {
        transform: translateY(0)
    }

    50% {
        transform: translateY(-5px)
    }
}

@keyframes gwPulseSoft {

    0%,
    100% {
        transform: scale(1);
        opacity: 1
    }

    50% {
        transform: scale(1.045);
        opacity: .88
    }
}

@keyframes gwHeartbeat {
    0% {
        transform: scale(1)
    }

    14% {
        transform: scale(1.09)
    }

    28% {
        transform: scale(1)
    }

    42% {
        transform: scale(1.06)
    }

    70% {
        transform: scale(1)
    }
}

@keyframes gwWiggle {

    0%,
    100% {
        transform: rotate(-2.2deg)
    }

    50% {
        transform: rotate(2.2deg)
    }
}

@keyframes gwShakeX {

    0%,
    100% {
        transform: translateX(0)
    }

    18% {
        transform: translateX(-4px)
    }

    38% {
        transform: translateX(4px)
    }

    58% {
        transform: translateX(-3px)
    }

    78% {
        transform: translateX(2px)
    }
}

@keyframes gwSpinSlow {
    to {
        transform: rotate(360deg)
    }
}

@keyframes gwGlowPulse {

    0%,
    100% {
        opacity: .45
    }

    50% {
        opacity: 1
    }
}

@keyframes gwSheenSweep {
    0% {
        background-position: -180% 0
    }

    100% {
        background-position: 180% 0
    }
}

/* --------------------------------------------------------------------------
   12. Utility modifiers — hand-authoring hooks. JS writes the same values as
       inline custom properties when it reads data-anim-* attributes, so the
       two mechanisms never conflict.
   -------------------------------------------------------------------------- */
.gw-a-near {
    --gw-a-dist: 14px
}

.gw-a-mid {
    --gw-a-dist: 40px
}

.gw-a-far {
    --gw-a-dist: 88px
}

.gw-a-fast {
    --gw-a-dur: .34s
}

.gw-a-slow {
    --gw-a-dur: 1.05s
}

.gw-a-slower {
    --gw-a-dur: 1.4s
}

.gw-a-ease-soft {
    --gw-a-ease: cubic-bezier(.4, 0, .2, 1)
}

.gw-a-ease-spring {
    --gw-a-ease: cubic-bezier(.34, 1.42, .64, 1)
}

.gw-a-ease-linear {
    --gw-a-ease: linear
}

.gw-a-ease-snap {
    --gw-a-ease: cubic-bezier(.16, .84, .28, 1)
}

.gw-a-d1 {
    --gw-a-delay: .06s
}

.gw-a-d2 {
    --gw-a-delay: .12s
}

.gw-a-d3 {
    --gw-a-delay: .18s
}

.gw-a-d4 {
    --gw-a-delay: .24s
}

.gw-a-d5 {
    --gw-a-delay: .3s
}

.gw-a-d6 {
    --gw-a-delay: .36s
}

.gw-a-d7 {
    --gw-a-delay: .42s
}

.gw-a-d8 {
    --gw-a-delay: .48s
}

/* --------------------------------------------------------------------------
   13. Accessibility and fallback
   -------------------------------------------------------------------------- */

/* Reduced motion: the JS driver bails out before stamping anything, and this
   rule guarantees that any hand-written data-anim in the markup still renders
   at full opacity with no movement. */
@media (prefers-reduced-motion: reduce) {

    [data-anim],
    [data-anim-loop] {
        animation: none !important;
        opacity: 1 !important;
        transform: none !important;
        clip-path: none !important;
        filter: none !important
    }
}

/* Printed output must never depend on a scroll observer. */
@media print {
    [data-anim] {
        animation: none !important;
        opacity: 1 !important;
        transform: none !important;
        clip-path: none !important
    }
}

/* If the driver script never executes, no element ever receives
   `gw-anim-pending`, so nothing can be stranded invisible. This is the
   belt-and-braces version of the same guarantee. */
html:not(.gw-anim-ready) [data-anim] {
    opacity: 1;
    animation: none
}

/* ── column 3: live storefront search results ───────────────────────────
   Result cards are created/destroyed by Alpine as the query types, so the
   entrance replays on every change; the per-card delay staggers them.
   Reuses gwRiseUp / gwPulseSoft from this same file. */
.gw-live-card {
    --gw-a-dist: 14px;
    animation: gwRiseUp .42s cubic-bezier(.22, .68, .36, 1) both
}

.gw-live-dot {
    display: inline-block;
    width: 6px;
    height: 6px;
    border-radius: 999px;
    background: #03D9F0;
    box-shadow: 0 0 8px rgba(3, 217, 240, .7);
    animation: gwPulseSoft 1.8s ease-in-out infinite
}

@media (prefers-reduced-motion: reduce) {

    .gw-live-card,
    .gw-live-dot {
        animation: none
    }
}
