/* ============================================================
   ANIMATIONS — scroll reveal

   Content is visible by default. Only when scripting is confirmed (an inline
   script stamps `.js` on <html> before first paint) do we hide the elements
   and let the IntersectionObserver in js/site.js reveal them by adding `.in`.
   This way a JS failure degrades to a plain, fully visible page instead of a
   blank one.
   ============================================================ */

.js .reveal {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 600ms var(--ease-out), transform 600ms var(--ease-out);
  will-change: opacity, transform;
}
.js .reveal.in {
  opacity: 1;
  transform: none;
}

/* Stagger children of a revealed group (cards in a grid). */
.js .reveal-group > * {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity 600ms var(--ease-out), transform 600ms var(--ease-out);
}
.js .reveal-group.in > * { opacity: 1; transform: none; }
.js .reveal-group.in > *:nth-child(1) { transition-delay: 0ms; }
.js .reveal-group.in > *:nth-child(2) { transition-delay: 80ms; }
.js .reveal-group.in > *:nth-child(3) { transition-delay: 160ms; }
.js .reveal-group.in > *:nth-child(4) { transition-delay: 240ms; }
.js .reveal-group.in > *:nth-child(5) { transition-delay: 320ms; }
.js .reveal-group.in > *:nth-child(6) { transition-delay: 400ms; }
.js .reveal-group.in > *:nth-child(7) { transition-delay: 480ms; }
.js .reveal-group.in > *:nth-child(n+8) { transition-delay: 560ms; }

@media (prefers-reduced-motion: reduce) {
  .js .reveal,
  .js .reveal-group > * {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}
