/* ═══════════════════════════════════════════════════════════════════════
   MOTION — content arrives as you scroll to it.

   The restraint is the point. Everything moves a short distance, over a
   generous duration, with a soft ease, once. Nothing loops, nothing bounces,
   nothing moves unless the reader is scrolling.

   Two engines. Browsers that support scroll-driven animations run this with
   no JavaScript at all, tied directly to the scroll position. Older browsers
   fall back to motion.js, which adds .is-in when an element enters view.
   ═══════════════════════════════════════════════════════════════════════ */

:root{
  --rise: 22px;
  --ease: cubic-bezier(.22,.61,.36,1);
  --dur: .85s;
}

/* ── the elements that participate ── */
.reveal{
  opacity:0;
  transform:translateY(var(--rise));
  will-change:opacity, transform;
}

/* ── engine 1: scroll-driven, no JavaScript ── */
@supports (animation-timeline: view()){
  .reveal{
    animation:rise-in linear both;
    animation-timeline:view();
    animation-range:entry 5% cover 26%;
  }
  /* a grid staggers, so the eye is led across rather than hit all at once */
  .reveal-stagger > *{
    opacity:0;
    animation:rise-in linear both;
    animation-timeline:view();
    animation-range:entry 2% cover 24%;
  }
  .reveal-stagger > *:nth-child(2){animation-range:entry 2% cover 29%}
  .reveal-stagger > *:nth-child(3){animation-range:entry 2% cover 34%}
  .reveal-stagger > *:nth-child(4){animation-range:entry 2% cover 39%}

  /* the hero settles as the page leaves it: the picture eases back a touch
     and the words drift, which is what gives the sense of depth */
  .hero-photo{
    animation:hero-settle linear both;
    animation-timeline:view();
    animation-range:exit 0% exit 100%;
  }
  .hero-photo-inner{
    animation:hero-drift linear both;
    animation-timeline:view();
    animation-range:exit 0% exit 100%;
  }

  /* editorial photographs breathe very slightly as they pass */
  figure.site-photo img{
    animation:photo-breathe linear both;
    animation-timeline:view();
    animation-range:entry 0% exit 100%;
  }
}

@keyframes rise-in{
  to{opacity:1; transform:none}
}
@keyframes hero-settle{
  to{transform:scale(1.06); filter:brightness(.82)}
}
@keyframes hero-drift{
  to{transform:translateY(-34px); opacity:.35}
}
@keyframes photo-breathe{
  from{transform:scale(1.04)}
  to{transform:scale(1)}
}

/* ── engine 2: the fallback, driven by motion.js ── */
@supports not (animation-timeline: view()){
  .reveal{transition:opacity var(--dur) var(--ease), transform var(--dur) var(--ease)}
  .reveal.is-in{opacity:1; transform:none}
  .reveal-stagger > *{
    opacity:0; transform:translateY(var(--rise));
    transition:opacity var(--dur) var(--ease), transform var(--dur) var(--ease);
  }
  .reveal-stagger.is-in > *{opacity:1; transform:none}
  .reveal-stagger.is-in > *:nth-child(2){transition-delay:.08s}
  .reveal-stagger.is-in > *:nth-child(3){transition-delay:.16s}
  .reveal-stagger.is-in > *:nth-child(4){transition-delay:.24s}
}

/* ── anchor jumps and the search box should not fight the reader ── */
html{scroll-behavior:smooth}

/* ── nobody who has asked for stillness gets any of this ── */
@media (prefers-reduced-motion: reduce){
  html{scroll-behavior:auto}
  .reveal,.reveal-stagger > *{
    opacity:1 !important;
    transform:none !important;
    animation:none !important;
    transition:none !important;
  }
  .hero-photo,.hero-photo-inner,figure.site-photo img{animation:none !important}
}

/* ── if JavaScript never runs, nothing should be left invisible ── */
.no-js .reveal,.no-js .reveal-stagger > *{opacity:1;transform:none}
