/* ---------- scroll-triggered reveal ----------
   Generic, reusable on any grid: mark items .reveal-item. js/app.js
   observes each item independently and adds .in-view to it once its
   leading edge crosses the activation line at 85% of the viewport
   height (implemented via IntersectionObserver rootMargin). Items are
   observed individually rather than grouped by parent so a multi-row
   grid reveals row by row as the user scrolls; items sharing a row
   sit at the same scroll offset, so they still fire together and
   fade + rise in with a 100ms stagger, grid-flow order (nth-child,
   cycling every 3 columns).

   Gated behind the .js class on <html> (swapped from the default
   .no-js very early in <head>) so that if JS never runs, content is
   just visible immediately instead of stuck hidden. */

.js .reveal-item{
  opacity: 0;
  transform: translateY(var(--reveal-offset));
  transition: opacity var(--reveal-duration) var(--reveal-ease),
              transform var(--reveal-duration) var(--reveal-ease);
}

.js .reveal-item.in-view{
  opacity: 1;
  transform: translateY(0);
}

.js .reveal-item.in-view:nth-child(3n+1){ transition-delay: calc(var(--reveal-stagger) * 0); }
.js .reveal-item.in-view:nth-child(3n+2){ transition-delay: calc(var(--reveal-stagger) * 1); }
.js .reveal-item.in-view:nth-child(3n){ transition-delay: calc(var(--reveal-stagger) * 2); }

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