/* ==========================================================================
   Animation system
   Driven by data-animate attributes (see js/motion.js). One shared observer
   adds .is-in; CSS does the rest.
   ========================================================================== */

/* --- Keyframes --- */
@keyframes gentleFloat {
    0%, 100% { transform: translateY(0); }
    50%      { transform: translateY(-8px); }
}

@keyframes drawLine { to { stroke-dashoffset: 0; } }

@keyframes pulseRing {
    0%   { transform: scale(1);   opacity: 0.45; }
    100% { transform: scale(1.7); opacity: 0; }
}

@keyframes drawerItemIn {
    to { opacity: 1; transform: translateY(0); }
}

@keyframes toastIn {
    from { opacity: 0; transform: translateY(12px) scale(0.97); }
    to   { opacity: 1; transform: translateY(0) scale(1); }
}

@keyframes toastOut {
    to { opacity: 0; transform: translateY(8px) scale(0.98); }
}

@keyframes shimmer {
    to { background-position: 200% 0; }
}

@keyframes marqueeScroll {
    from { transform: translateX(0); }
    to   { transform: translateX(-50%); }
}

@keyframes shake {
    10%, 90% { transform: translateX(-2px); }
    20%, 80% { transform: translateX(4px); }
    30%, 50%, 70% { transform: translateX(-6px); }
    40%, 60% { transform: translateX(6px); }
}


.float-el {
    animation: gentleFloat 4s ease-in-out infinite;
    transform-box: fill-box;
    transform-origin: center;
}

/* --- Legacy reveal (kept so existing .reveal markup still works) --- */
.js-motion .reveal {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity var(--dur-4) var(--ease-out), transform var(--dur-4) var(--ease-out);
}
.reveal.in-view { opacity: 1; transform: translateY(0); }

/* ==========================================================================
   data-animate primitives
   ========================================================================== */

[data-animate] { --_delay: 0ms; }

.js-motion [data-animate="fade-up"],
.js-motion [data-animate="fade-in"],
.js-motion [data-animate="fade-left"],
.js-motion [data-animate="fade-right"],
.js-motion [data-animate="scale-in"],
.js-motion [data-animate="blur-in"] {
    opacity: 0;
    transition: opacity var(--dur-4) var(--ease-out) var(--_delay),
                transform var(--dur-4) var(--ease-out) var(--_delay),
                filter var(--dur-4) var(--ease-out) var(--_delay);
    will-change: opacity, transform;
}

[data-animate="fade-up"]    { transform: translateY(12px); }
[data-animate="fade-left"]  { transform: translateX(12px); }
[data-animate="fade-right"] { transform: translateX(-12px); }
[data-animate="scale-in"]   { transform: scale(0.94); }
[data-animate="blur-in"]    { filter: blur(10px); }

[data-animate].is-in {
    opacity: 1;
    transform: none;
    filter: none;
    /* Release the compositor hint once the animation is done. Left standing on
       ~60 revealed elements it costs a needless layer each on mobile. */
    will-change: auto;
}

/* --- Mask reveal: content wipes in rather than fading. Used sparingly on
   the largest visual objects. --- */
.js-motion [data-animate="mask-reveal"] {
    clip-path: inset(0 100% 0 0);
    transition: clip-path var(--dur-4) var(--ease-out) var(--_delay);
}
[data-animate="mask-reveal"].is-in { clip-path: inset(0 0 0 0); }

.js-motion [data-animate="mask-up"] {
    clip-path: inset(100% 0 0 0);
    transition: clip-path var(--dur-4) var(--ease-out) var(--_delay);
}
[data-animate="mask-up"].is-in { clip-path: inset(0 0 0 0); }

/* --- Stagger: parent sets --i on children, CSS computes the delay.
   No per-child JS. --- */
.js-motion [data-animate="stagger"] > * {
    opacity: 0;
    transform: translateY(12px);
    transition: opacity var(--dur-4) var(--ease-out), transform var(--dur-4) var(--ease-out);
    transition-delay: calc(var(--i, 0) * var(--_stagger, var(--stagger)));
}
[data-animate="stagger"].is-in > * { opacity: 1; transform: none; }

/* --- Split text (word level) --- */
.js-motion .split-word {
    display: inline-block;
    opacity: 0;
    transform: translateY(0.6em);
    transition: opacity var(--dur-3) var(--ease-out), transform var(--dur-3) var(--ease-out);
    transition-delay: calc(var(--i, 0) * 60ms);
}
[data-animate="split-text"].is-in .split-word { opacity: 1; transform: none; }

/* --- Parallax: JS writes --py, compositor does the work --- */
[data-animate="parallax"] { transform: translate3d(0, var(--py, 0px), 0); will-change: transform; }

/* --- Tilt with specular sheen --- */
[data-animate="tilt"] {
    transform: perspective(900px) rotateX(var(--rx, 0deg)) rotateY(var(--ry, 0deg));
    transition: transform var(--dur-3) var(--ease-spring);
    transform-style: preserve-3d;
    position: relative;
}
[data-animate="tilt"]::after {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: inherit;
    background: radial-gradient(circle at var(--mx, 50%) var(--my, 50%), rgba(255, 255, 255, 0.35), transparent 45%);
    opacity: 0;
    transition: opacity var(--dur-2) var(--ease-out);
    pointer-events: none;
}
[data-animate="tilt"]:hover::after { opacity: 1; }

/* --- Magnetic --- */
[data-animate="magnetic"] {
    transform: translate3d(var(--tx, 0px), var(--ty, 0px), 0);
    transition: transform var(--dur-3) var(--ease-spring);
}

/* --- SVG line drawing --- */
.draw-path {
    stroke-dasharray: var(--len, 320);
    stroke-dashoffset: var(--len, 320);
    animation: drawLine 2.2s ease forwards 0.3s;
}
[data-animate="draw"]:not(.is-in) .draw-path { animation-play-state: paused; }

/* --- Marquee --- */
.marquee { overflow: hidden; position: relative; -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
.marquee-track { display: flex; width: max-content; animation: marqueeScroll var(--marquee-dur, 40s) linear infinite; }
.marquee-track > * { flex-shrink: 0; }
/* :focus-within as well as :hover — the twin-selector precedent from 04-nav.css.
   Without it, tabbing to a link inside a moving strip leaves the focus ring
   sliding off-screen. */
.marquee:hover .marquee-track,
.marquee:focus-within .marquee-track { animation-play-state: paused; }
.marquee.is-reverse .marquee-track { animation-direction: reverse; }

/* --- Skeleton loading --- */
.skeleton {
    background: linear-gradient(90deg, var(--slate-200) 25%, var(--slate-100) 37%, var(--slate-200) 63%);
    background-size: 200% 100%;
    animation: shimmer 1.4s ease-in-out infinite;
    border-radius: var(--radius-sm);
}

/* --- Form validation shake --- */
.is-shaking { animation: shake 0.5s var(--ease-in-out); }

/* ==========================================================================
   Reduced motion — genuinely off, not merely faster.
   ========================================================================== */
@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.001ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.001ms !important;
        scroll-behavior: auto !important;
    }

    .reveal,
    [data-animate],
    [data-animate="stagger"] > *,
    .split-word {
        opacity: 1 !important;
        transform: none !important;
        filter: none !important;
    }

    [data-animate="mask-reveal"], [data-animate="mask-up"] { clip-path: none !important; }

    .draw-path { stroke-dashoffset: 0 !important; }

    /* .float-el is decorative styling, not a reveal state, so only its
       animation is stopped. Resetting transform would move SVG groups that
       are positioned with a transform attribute back to the origin. */
    .float-el { animation: none !important; }

    .marquee-track { animation: none !important; }
    .skeleton { animation: none !important; }
}
