/* ==========================================================================
   MOTION
   Entrance states, scroll-scene defaults, and a per-effect reduced-motion
   policy. Everything here animates transform / opacity / stroke-dashoffset
   only — nothing that triggers layout.
   ========================================================================== */

@layer motion {

    /* ------------------------------------------------------------------
       Scroll scenes

       JS writes --p (0..1) onto [data-scene] from a single rAF loop.
       Without JS there is no writer, so scenes must default to complete —
       otherwise every trace line and wire would sit permanently undrawn.
       ------------------------------------------------------------------ */

    html:not(.js) [data-scene] {
        --p: 1;
    }

    /* Scopes layout recalc from the per-frame --p write. Deliberately NOT
       `paint`: #experience contains a position: sticky column and #contact an
       expanding ring, and neither needs the clipping that paint containment
       brings along. */
    [data-scene] {
        contain: layout;
    }

    /* ------------------------------------------------------------------
       Entrance reveals

       Hidden state is scoped to .js so a failed script can never leave the
       page blank. --i drives the stagger.
       ------------------------------------------------------------------ */

    .js .reveal {
        opacity: 0;
        translate: 0 14px;
    }

    .js .reveal.is-in {
        opacity: 1;
        translate: 0 0;
        transition: opacity var(--d-4) var(--ease-out-expo),
            translate var(--d-4) var(--ease-out-expo);
        transition-delay: calc(var(--i, 0) * 55ms);
    }

    /* Hero name — per-word mask reveal. Words, never characters: character
       splitting breaks screen-reader word boundaries. */
    .js [data-split] .w {
        display: inline-block;
        overflow: hidden;
        vertical-align: bottom;
        padding-bottom: .08em;
    }

    .js [data-split] .w-i {
        display: inline-block;
        translate: 0 110%;
        transition: translate var(--d-5) var(--ease-out-expo);
        transition-delay: calc(var(--i, 0) * 70ms);
    }

    .js [data-split].is-in .w-i {
        translate: 0 0;
    }

    /* ------------------------------------------------------------------
       Scroll cue
       ------------------------------------------------------------------ */

    .scroll-cue-line {
        animation: cue-draw 2.4s var(--ease-in-out-quint) infinite;
    }

    @keyframes cue-draw {

        0%,
        100% {
            transform: scaleY(.2);
            opacity: .4;
        }

        45% {
            transform: scaleY(1);
            opacity: 1;
        }
    }

    /* ------------------------------------------------------------------
       Contact terminal node — the trace completes on a successful send
       ------------------------------------------------------------------ */

    .contact.is-complete .trace-terminal {
        background: var(--accent);
    }

    .contact.is-complete .trace-terminal::after {
        animation: terminal-ring 900ms var(--ease-out-expo) forwards;
    }

    @keyframes terminal-ring {
        from {
            opacity: .9;
            scale: 1;
        }

        to {
            opacity: 0;
            scale: 3.2;
        }
    }

    /* ------------------------------------------------------------------
       Reduced motion — per effect, not a blanket kill.

       Colour and opacity transitions under ~250ms stay: they are not
       vestibular triggers, and removing them makes the UI feel broken.
       Everything spatial, looping, or scroll-linked goes.
       ------------------------------------------------------------------ */

    @media (prefers-reduced-motion: reduce) {
        html {
            scroll-behavior: auto;
        }

        /* Scenes render complete rather than mid-draw */
        [data-scene] {
            --p: 1 !important;
        }

        .js .reveal,
        .js .reveal.is-in {
            opacity: 1 !important;
            translate: none !important;
            transition: none !important;
        }

        .js [data-split] .w-i,
        .js [data-split].is-in .w-i {
            translate: none !important;
            transition: none !important;
        }

        .js [data-split] .w {
            overflow: visible;
        }

        .scroll-cue-line,
        .trace-terminal::after {
            animation: none !important;
        }

        .hero-grid {
            --shift: 0px;
        }

        /* Keep the hard shadow — drop the displacement */
        .work-card:hover,
        .footer-links a:hover,
        .btn:hover {
            translate: none !important;
        }

        .exp-index-item {
            translate: none !important;
        }

        .btn-wipe::before {
            transition: none !important;
        }

        .site-nav {
            transition: background-color var(--d-2) linear, border-color var(--d-2) linear;
        }

        .site-nav[data-hidden="true"] {
            translate: none;
        }
    }
}
