/* Subversive Effects — sbvrs
----------------------------------------
 * A namespaced layer for visual effects, kept deliberately separate from the
 * theme's structural CSS.
 *
 * What belongs here:
 *   Opt-in visual effects applied by adding a class to a block in the editor
 *   (Advanced > Additional CSS class(es)) — hovers, reveals, transforms,
 *   decorative motion.
 *
 * What does NOT belong here:
 *   style.css                    global defaults and element styling
 *   theme.json                   design tokens, layout, block defaults
 *   assets/css/blocks/           registered is-style-* block variants
 *   assets/css/overrides/        corrections to core block output
 *   assets/css/patterns/         styles scoped to a specific pattern
 *
 * Naming
 *   Every selector and custom property in this file is prefixed `sbvrs`, so
 *   nothing here can collide with core, the theme, or a plugin:
 *
 *     .sbvrs-<effect>              the effect itself
 *     .sbvrs-<effect>--<variant>   a variation of it
 *     .sbvrs-is-<state>            a state toggled at runtime
 *     --sbvrs-<name>               a custom property
 *
 * Loading
 *   Enqueued on enqueue_block_assets by inc/subversive_register_sbvrs_styles.php,
 *   so it applies on the front end and inside the editor canvas. Effects look
 *   the same while editing as they do when published.
 *
 * Motion
 *   style.css reduces every animation and transition to 0.01ms under
 *   prefers-reduced-motion, which covers effects driven by duration. It does
 *   NOT reliably cover scroll-driven animations, whose progress comes from a
 *   timeline rather than a duration — those need their own guard here.
 *
 * Cache
 *   This file is versioned with the theme version, so a change will not reach
 *   a browser holding a cached copy until the theme version is bumped. Hard
 *   reload while developing.
 */

/* Animate In
----------------------------------------
 * Scroll-driven entrance. The element starts offset and transparent, and
 * resolves to its natural position as it scrolls into view. Progress is tied
 * to the element's own position in the scrollport via animation-timeline:
 * view(), so there is no JavaScript, no IntersectionObserver, and no scroll
 * listener — the browser drives it on the compositor.
 *
 * Usage
 *   Add to a block under Advanced > Additional CSS class(es):
 *
 *     sbvrs-animate-in                        rises from below (default)
 *     sbvrs-animate-in sbvrs-animate-in--left enters from the left
 *     sbvrs-animate-in sbvrs-animate-in--zoom-in  scales up into place
 *
 *   Variants only set custom properties, so they combine freely:
 *
 *     sbvrs-animate-in sbvrs-animate-in--far sbvrs-animate-in--late
 *
 *   Above the fold, where there is no scrolling left to drive the effect:
 *
 *     sbvrs-animate-in sbvrs-animate-in--onload
 *
 *   Anything not covered by a variant can be set inline on the block:
 *
 *     style="--sbvrs-animate-in-y: 6rem"
 *
 * Easing
 *   Scroll-driven progress is normally left linear, so the element tracks the
 *   scroll position exactly. This eases instead, which front-loads the
 *   movement: it reads as more deliberate when scrolling forward, at the cost
 *   of feeling slightly detached if you scrub back and forth. Override with
 *   --sbvrs-animate-in-easing, or use .sbvrs-animate-in--linear.
 *
 * Tuning the range
 *   animation-range decides where in the element's pass through the viewport
 *   the animation runs. `entry 0%` is the moment its leading edge appears;
 *   `cover 40%` is 40% of the way through its full traversal. Widening the
 *   range makes the effect slower and more gradual relative to scrolling.
 *
 * Caveats
 *   view() resolves against the nearest scrollport, so an ancestor with
 *   overflow: hidden or auto becomes the reference instead of the page and the
 *   effect may never run. Elements already in view on load are past `entry`,
 *   so they appear already resolved — which is usually what you want, but it
 *   means this is not a substitute for an on-load animation.
 *
 * Support
 *   Wrapped in @supports, so browsers without scroll-driven animations skip
 *   the whole thing and render content normally. The hidden start state can
 *   never be applied somewhere it would not be animated away.
 */

@supports (animation-timeline: view()) {

    .sbvrs-animate-in {

        /* Start state — overridden by the variants below, or per block */
        --sbvrs-animate-in-x: 0;
        --sbvrs-animate-in-y: 2rem;
        --sbvrs-animate-in-scale: 1;
        --sbvrs-animate-in-opacity: 0;

        /* Where in the element's pass through the viewport it animates */
        --sbvrs-animate-in-start: cover 10%;
        --sbvrs-animate-in-end: cover 40%;

        /* Eased rather than linear: progress is front-loaded, so the element
           becomes legible early and settles gently at the end of the range.
           Use the --linear variant to tie progress 1:1 to scroll instead. */
        --sbvrs-animate-in-easing: cubic-bezier(0.16, 1, 0.3, 1);

        animation: sbvrs-animate-in both;
        animation-timing-function: var(--sbvrs-animate-in-easing, cubic-bezier(0.16, 1, 0.3, 1));
        animation-timeline: view();
        animation-range: var(--sbvrs-animate-in-start) var(--sbvrs-animate-in-end);
    }

    /* Direction */
    .sbvrs-animate-in--down {
        --sbvrs-animate-in-y: -2rem;
    }

    .sbvrs-animate-in--left {
        --sbvrs-animate-in-x: -2rem;
        --sbvrs-animate-in-y: 0;
    }

    .sbvrs-animate-in--right {
        --sbvrs-animate-in-x: 2rem;
        --sbvrs-animate-in-y: 0;
    }

    /* Character */
    .sbvrs-animate-in--fade {
        --sbvrs-animate-in-x: 0;
        --sbvrs-animate-in-y: 0;
    }

    .sbvrs-animate-in--zoom-in {
        --sbvrs-animate-in-y: 0;
        --sbvrs-animate-in-scale: 0.92;
    }

    .sbvrs-animate-in--zoom-out {
        --sbvrs-animate-in-y: 0;
        --sbvrs-animate-in-scale: 1.08;
    }

    /* Distance */
    .sbvrs-animate-in--near {
        --sbvrs-animate-in-y: 1rem;
    }

    .sbvrs-animate-in--far {
        --sbvrs-animate-in-y: 5rem;
    }

    /* Pace — how much scrolling the animation is spread across */
    .sbvrs-animate-in--early {
        --sbvrs-animate-in-end: entry 100%;
    }

    .sbvrs-animate-in--late {
        --sbvrs-animate-in-end: cover 60%;
    }

    /* Easing */
    .sbvrs-animate-in--linear {
        --sbvrs-animate-in-easing: linear;
    }

    /* Timing mode
       Swaps the scroll timeline for a time-based one, so the element animates
       once on load instead of as it is scrolled to. Use it for content above
       the fold, which is already past its scroll range on arrival and would
       otherwise appear with no animation at all.
    
       Everything else is inherited from the base rule, so the direction,
       distance, character and easing variants all still apply. Must stay after
       that rule: the specificity is equal, so source order decides.
    
       Animating opacity from 0 above the fold delays when the content becomes
       legible and can affect LCP. Keep the duration short, or prefer --fade. */
    .sbvrs-animate-in--onload {
        animation-duration: var(--sbvrs-animate-in-duration, 0.6s);
        animation-delay: var(--sbvrs-animate-in-delay, 0s);
        animation-timeline: auto;
        animation-range: normal;
    }
}

/* Stagger
----------------------------------------
 * Runs Animate In across a container's direct children, offsetting each one a
 * little further along the scroll range than the last so they resolve in
 * sequence rather than together.
 *
 * The offset comes from sibling-index(), so the sequence is derived from the
 * DOM rather than hard-coded. Adding, removing or reordering children needs no
 * CSS change, and there is no per-item class and no :nth-child ladder.
 *
 * Usage
 *   Put the class on the CONTAINER — a Columns, Gallery, Row or Group block —
 *   and its direct children animate in turn:
 *
 *     sbvrs-stagger                             children rise in sequence
 *     sbvrs-stagger sbvrs-stagger--tight        tighter sequence
 *     sbvrs-stagger sbvrs-animate-in--left      each child enters from the left
 *
 *   Animate In's variants set inherited custom properties, so putting one on
 *   the container re-aims every child at once. The two effects compose without
 *   either knowing about the other.
 *
 *   This deliberately does not redeclare those properties. Defaults come from
 *   the fallbacks in the keyframes below, so a variant on the container always
 *   wins regardless of the order the classes appear in.
 *
 * On load instead of on scroll
 *   sbvrs-stagger--onload swaps the scroll timeline for a time-based one, the
 *   technique of animation-delay: calc(sibling-index() * step). Use it for
 *   content above the fold, which is already past its scroll range on arrival
 *   and would otherwise appear with no animation at all.
 *
 * Support
 *   Requires sibling-index(), so the @supports condition tests for it directly
 *   rather than assuming. Without it the whole block is skipped and children
 *   render normally.
 */

@supports (animation-timeline: view()) and (animation-range-start: entry calc(sibling-index() * 1%)) {

    .sbvrs-stagger {
        /* How much further along the range each successive child starts */
        --sbvrs-stagger-step: 50%;
    }

    .sbvrs-stagger>* {
        animation: sbvrs-animate-in both;
        animation-timing-function: var(--sbvrs-animate-in-easing, cubic-bezier(0.16, 1, 0.3, 1));
        animation-timeline: view();
        animation-range:
            entry calc(sibling-index() * var(--sbvrs-stagger-step, 25%)) var(--sbvrs-animate-in-end, cover 40%);
    }

    /* Pace of the sequence */
    .sbvrs-stagger--tight {
        --sbvrs-stagger-step: 25%;
    }

    .sbvrs-stagger--loose {
        --sbvrs-stagger-step: 75%;
    }

    /* Time-based variant for above-the-fold content. Overrides the scroll
	   timeline, so it must stay after the rule above. */
    .sbvrs-stagger--onload>* {
        animation: sbvrs-animate-in both;
        animation-duration: var(--sbvrs-stagger-duration, 0.4s);
        animation-timing-function: var(--sbvrs-animate-in-easing, cubic-bezier(0.16, 1, 0.3, 1));
        animation-delay: calc(sibling-index() * var(--sbvrs-stagger-delay, 100ms));
        animation-timeline: auto;
        animation-range: normal;
    }
}

/* Only a `from` is declared, so the animation resolves to whatever the element
   already is. Nothing here dictates the element's final appearance. */
@keyframes sbvrs-animate-in {
    from {
        opacity: var(--sbvrs-animate-in-opacity, 0);
        transform:
            translate3d(var(--sbvrs-animate-in-x, 0), var(--sbvrs-animate-in-y, 2rem), 0) scale(var(--sbvrs-animate-in-scale, 1));
    }
}

/* Scroll-driven animations take progress from a timeline, not a duration, so
   the global duration clamp in style.css does not switch them off. Remove the
   animation outright: elements keep their natural, fully visible state. This
   also covers sbvrs-stagger--onload, whose delay the clamp leaves untouched,
   so items would otherwise still pop in one after another. */
@media (prefers-reduced-motion: reduce) {

    .sbvrs-animate-in,
    .sbvrs-stagger>* {
        animation: none;
    }
}

/* Button Personality
----------------------------------------
 * Retunes the motion every button already has. The default personality, a
 * spring on hover, a squish on press and a ripple from the centre, is set by
 * the btn tokens in theme.json (settings.custom.btn) and applied in style.css.
 * These variants only swap those values, so how a button moves is defined in
 * one place and nothing here duplicates it.
 *
 * Usage
 *   Add to a Button, a Buttons block, or any container around them under
 *   Advanced > Additional CSS class(es). The properties inherit, so one class
 *   on a Group retunes every button inside it:
 *
 *     sbvrs-btn--tight    professional: smaller, quicker, no overshoot
 *     sbvrs-btn--loose    casual: bigger, bouncier and slower
 *
 *   To set the personality for a whole site, override settings.custom.btn in
 *   the child theme's theme.json rather than adding a class everywhere.
 *
 * Easing
 *   cubic-bezier() can overshoot only once. The loose spring is a linear()
 *   curve sampled from a damped spring (damping ratio 0.545): it overshoots by
 *   about 13% and settles after one small rebound. Browsers without linear()
 *   keep the single-overshoot fallback.
 *
 * Motion
 *   Every value here is a duration or a transition target, so the clamp in
 *   style.css covers them, and style.css drops the scale under
 *   prefers-reduced-motion.
 */

.sbvrs-btn--tight {
    --sbvrs-btn-duration: 75ms;
    --sbvrs-btn-easing: cubic-bezier(0.2, 0, 0, 1);
    --sbvrs-btn-hover-glow: 0 10px 24px -7px rgba(13, 13, 13, 0.65);
    --sbvrs-btn-hover-scale: 1.01;
    --sbvrs-btn-press-scale: 0.99;
    --sbvrs-btn-ripple-duration: 270ms;
    --sbvrs-btn-ripple-opacity: 0.12;
}

.sbvrs-btn--loose {
    --sbvrs-btn-duration: 160ms;
    --sbvrs-btn-easing: cubic-bezier(0.34, 1.68, 0.64, 1);
    --sbvrs-btn-hover-glow: 0 14px 32px -6px rgba(13, 13, 13, 0.72);
    --sbvrs-btn-hover-scale: 1.035;
    --sbvrs-btn-press-scale: 0.965;
    --sbvrs-btn-ripple-duration: 500ms;
}

@supports (transition-timing-function: linear(0, 1)) {

    .sbvrs-btn--loose {
        --sbvrs-btn-easing: linear(0, 0.05 4%, 0.175 8%, 0.339 12%, 0.515 16%, 0.683 20%, 0.861 25%, 0.993 30%, 1.078 35%, 1.12 40%, 1.13 44.4%, 1.118 50%, 1.065 60%, 1.015 70%, 0.989 80%, 0.983 88.7%, 1);
    }
}