/* ===========================================================================
   motion.css — the animation vocabulary.

   Reveal classes are driven by an IntersectionObserver in js/motion.js, which
   only ever toggles `.is-in`. Keeping the *what* here and the *when* in JS is
   why the whole site can be made still by one media query.
   =========================================================================== */

/* ---------------------------------------------------------------------------
   Scroll reveals
   --------------------------------------------------------------------------- */

[data-reveal] {
    opacity: 0;
    transition: opacity var(--d-4) var(--e-out),
                transform var(--d-5) var(--e-expo),
                filter var(--d-4) var(--e-out);
    transition-delay: var(--delay, 0ms);
}
/* No will-change here: running transitions are composited anyway, and a
   permanent hint kept every reveal target (each card, its body, every header)
   on its own GPU layer for the whole visit, dragging overlapping content up. */
[data-reveal].is-in { opacity: 1; transform: none; filter: none; }

[data-reveal="up"]     { transform: translateY(30px); }
[data-reveal="down"]   { transform: translateY(-24px); }
[data-reveal="left"]   { transform: translateX(34px); }
[data-reveal="right"]  { transform: translateX(-34px); }
[data-reveal="scale"]  { transform: scale(.94); }
[data-reveal="blur"]   { filter: blur(12px); transform: translateY(20px); }
[data-reveal="fade"]   { transform: none; }

/* A card entering the grid: rises, unblurs and un-skews at once. */
[data-reveal="card"] {
    transform: translateY(38px) scale(.97);
    filter: blur(6px);
}

/* Rule that draws itself left-to-right. */
[data-reveal="rule"] {
    opacity: 1;
    transform: scaleX(0);
    transform-origin: left;
    transition: transform var(--d-5) var(--e-expo);
    transition-delay: var(--delay, 0ms);
}
[data-reveal="rule"].is-in { transform: scaleX(1); }

/* ---------------------------------------------------------------------------
   Split-text reveal — js/motion.js wraps each word in .sp > i
   --------------------------------------------------------------------------- */

/* The mask has to be noticeably taller and wider than the glyphs it hides.
   Fraunces has deep descenders and, in italic, real horizontal overhang — a
   tight mask clips the tail of a "g" and the exit stroke of a "y". Padding
   grows the clip box; the matching negative margin keeps layout unchanged. */
.sp {
    display: inline-block;
    overflow: hidden;
    vertical-align: bottom;
    padding: .14em .08em .3em;
    margin: -.14em -.08em -.3em;
}
.sp > i {
    display: inline-block;
    font-style: inherit;
    /* Must exceed the padded mask height, not just the glyph height. */
    transform: translateY(150%) rotate(3deg);
    opacity: 0;
    filter: blur(5px);
    transition: transform var(--d-5) var(--e-expo),
                opacity var(--d-3) var(--e-out),
                filter var(--d-4) var(--e-out);
    transition-delay: var(--d, 0ms);
}
.is-split-in .sp > i { transform: none; opacity: 1; filter: none; }

/* ---------------------------------------------------------------------------
   Scroll-linked illumination

   JS writes one number (--p, 0→1) on the paragraph. Each word derives its own
   opacity from that plus its index, so the highlight sweeps through the line
   as you scroll without JS ever touching an individual word.
   --------------------------------------------------------------------------- */

.is-lit .w {
    /* Word i lights when progress passes i/n, with a soft two-word ramp. */
    opacity: clamp(.26, calc((var(--p, 0) * (var(--n) + 2) - var(--i)) / 2), 1);
    transition: opacity .18s linear;
}

/* ---------------------------------------------------------------------------
   Magnetic + tilt hooks (transforms written by JS, easing owned here)
   --------------------------------------------------------------------------- */

.magnetic {
    will-change: transform;
    transition: transform var(--d-4) var(--e-expo);
}
.magnetic.is-pulled { transition: transform .1s linear; }

/* ---------------------------------------------------------------------------
   Keyframes
   --------------------------------------------------------------------------- */

@keyframes drift-a {
    0%   { transform: translate3d(0, 0, 0) scale(1); }
    100% { transform: translate3d(14vw, 12vh, 0) scale(1.22); }
}
@keyframes drift-b {
    0%   { transform: translate3d(0, 0, 0) scale(1.1); }
    100% { transform: translate3d(-16vw, 16vh, 0) scale(.88); }
}
@keyframes drift-c {
    0%   { transform: translate3d(0, 0, 0) scale(.92); }
    100% { transform: translate3d(12vw, -14vh, 0) scale(1.18); }
}

/* Grain jitters so the texture never reads as a static PNG. */
@keyframes grain {
    0%,100% { transform: translate(0, 0); }
    10% { transform: translate(-3%, -4%); }
    20% { transform: translate(-8%, 2%); }
    30% { transform: translate(4%, -7%); }
    40% { transform: translate(-3%, 6%); }
    50% { transform: translate(-8%, 3%); }
    60% { transform: translate(6%, 0%); }
    70% { transform: translate(0%, 6%); }
    80% { transform: translate(3%, -3%); }
    90% { transform: translate(-4%, 3%); }
}

@keyframes ping {
    0%   { transform: scale(1); opacity: .7; }
    70%  { transform: scale(2.6); opacity: 0; }
    100% { transform: scale(2.6); opacity: 0; }
}

@keyframes rule-sweep {
    0%   { transform: translateX(-100%); }
    55%  { transform: translateX(0); }
    100% { transform: translateX(100%); }
}

@keyframes shimmer {
    0%   { background-position: 140% 0; }
    100% { background-position: -40% 0; }
}

@keyframes grow-x {
    from { transform: scaleX(0); }
    to   { transform: scaleX(1); }
}

@keyframes letter-rise {
    to { transform: translateY(0); }
}

@keyframes toast-in {
    from { opacity: 0; transform: translateY(20px) scale(.94); }
    to   { opacity: 1; transform: none; }
}
@keyframes toast-out {
    to { opacity: 0; transform: translateY(10px) scale(.96); }
}

@keyframes spin { to { transform: rotate(360deg); } }
.is-spinning { animation: spin .8s linear infinite; }

@keyframes pop {
    0%   { transform: scale(1); }
    45%  { transform: scale(1.28); }
    100% { transform: scale(1); }
}
.is-popping { animation: pop var(--d-3) var(--e-spring); }

/* ---------------------------------------------------------------------------
   Cross-document View Transitions.

   Grid re-layouts are handled by FLIP in js/motion.js, which animates each
   card individually — better than a crossfade. What View Transitions are used
   for here is *navigation*: index → case study → back, which becomes a single
   continuous move instead of a white flash. Browsers without support simply
   navigate normally.
   --------------------------------------------------------------------------- */

@view-transition { navigation: auto; }

::view-transition-group(*) {
    animation-duration: var(--d-4);
    animation-timing-function: var(--e-expo);
}
::view-transition-old(root) { animation: vt-out var(--d-2) var(--e-out) forwards; }
::view-transition-new(root) { animation: vt-in var(--d-4) var(--e-expo) forwards; }

@keyframes vt-out { to { opacity: 0; transform: scale(.99); } }
@keyframes vt-in  { from { opacity: 0; transform: translateY(14px); } }

/* The nav is the one element that should stay put across a navigation. */
.nav { view-transition-name: site-nav; }
::view-transition-group(site-nav) { animation-duration: var(--d-3); }

@media (prefers-reduced-motion: reduce) {
    @view-transition { navigation: none; }
}

/* ---------------------------------------------------------------------------
   Reduced motion — kill ambience, keep state changes legible.
   --------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
    [data-reveal], .sp > i { opacity: 1 !important; transform: none !important; filter: none !important; }
    .is-lit .w { opacity: 1 !important; }
    .card-plaque, .card-head, .card-actions { transform: none !important; }
    .bd-wash span, .bd-grain, .hero-scroll .rule::after, .hero-badge .dot::after { animation: none !important; }
    .marquee-track { transform: none !important; }
    .cur-dot, .cur-ring { display: none !important; }
    .curtain { display: none !important; }
}
