/* ==========================================================================
   AffiNinja — motion layer
   Additive. `html.reveal-on` is added by JS *inside* the observer setup, so
   the hidden state can never outlive the thing that un-hides it.
   ========================================================================== */
:root{
  --ease-out:cubic-bezier(.22,1,.36,1);
  --ease-spring:cubic-bezier(.34,1.56,.64,1);
  --dur:.8s;
}

/* ---- Reveal ------------------------------------------------------------- */
.reveal-on [data-reveal]{
  opacity:0;will-change:opacity,transform;
  transition:opacity var(--dur) var(--ease-out),transform var(--dur) var(--ease-out);
  transition-delay:var(--reveal-delay,0ms);
}
.reveal-on [data-reveal="up"]   {transform:translate3d(0,42px,0)}
.reveal-on [data-reveal="left"] {transform:translate3d(-44px,0,0)}
.reveal-on [data-reveal="right"]{transform:translate3d(44px,0,0)}
.reveal-on [data-reveal="scale"]{transform:scale(.9)}
.reveal-on [data-reveal].is-in{opacity:1;transform:none}

/* ---- Word-by-word headline --------------------------------------------- */
.split-on .word{display:inline-block;overflow:hidden;vertical-align:bottom;
  padding-bottom:.14em;margin-bottom:-.14em}
.split-on .word > i{
  display:inline-block;font-style:inherit;transform:translate3d(0,110%,0);
  transition:transform .95s var(--ease-out);transition-delay:var(--word-delay,0ms);
}
.split-on .is-in .word > i{transform:none}

/* ---- Scroll progress ---------------------------------------------------- */
.scroll-progress{
  position:fixed;top:0;left:0;height:3px;width:100%;z-index:500;
  transform:scaleX(0);transform-origin:0 50%;background:var(--accent);
  pointer-events:none;
}

/* ---- Header ------------------------------------------------------------- */
.site-header{transition:transform .45s var(--ease-out)}

/* ---- Buttons ------------------------------------------------------------ */
.btn:active{transform:scale(.97)}
.btn{transition:background .3s var(--ease),color .3s var(--ease),border-color .3s var(--ease)}

/* ---- Cards -------------------------------------------------------------- */
.card,.quote{transition:transform .5s var(--ease-out),border-color .4s var(--ease-out)}
.card:hover,.quote:hover{transform:translateY(-8px);border-color:var(--accent)}

.card__art img{transition:transform .6s var(--ease-spring)}
.card:hover .card__art img{transform:translateY(-10px) rotate(-3deg) scale(1.05)}

/* ---- Equation ----------------------------------------------------------- */
.equation__item{transition:transform .45s var(--ease-spring)}
.equation__item:hover{transform:translateY(-8px)}
.equation__icon{transition:transform .5s var(--ease-spring)}
.equation__item:hover .equation__icon{transform:rotate(-14deg) scale(1.12)}

/* ---- Marquee ------------------------------------------------------------ */
.marquee__track{will-change:transform}
.marquee:hover .marquee__track{animation-play-state:paused}

/* ---- Form --------------------------------------------------------------- */
.form__msg{animation:aff-pop .45s var(--ease-spring)}
@keyframes aff-pop{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:none}}

/* ---- Reduced motion ----------------------------------------------------- */
/* Reduce Motion only switches off the things that MOVE CONTENT — scroll reveals,
   the word-split headline and parallax. It used to also blanket-kill every
   animation via `*{animation-duration:.01ms}`, and iOS users very often have
   Reduce Motion on, so the tape and the flames simply froze on iPhone. The hero's
   ambient loops (tape, flame) stay running. */
@media (prefers-reduced-motion:reduce){
  .reveal-on [data-reveal]{opacity:1 !important;transform:none !important}
  .split-on .word > i{transform:none !important}
  .scroll-progress{display:none}
}
