/* Shared reveal contract for the three marketing pages: /, /beta, /elevate.
   Linked from landing/index.html, beta2/index.html and elevate/index.html, so a
   change here governs every page at once. The three copies of the Reveal
   component may drift; this file may not.

   Two mechanisms, deliberately different, chosen by where the block sits:

   .reveal      Below the fold. Starts hidden and becomes visible when the
                IntersectionObserver in useReveal adds .in as the block scrolls
                into view. Correct here: the scroll gesture is the trigger.

   .reveal-now  Above the fold, where there is no scroll to act as a trigger.
                Never gated on a callback. A CSS animation runs on the document
                timeline from the moment the element enters the DOM: it needs no
                observer, no queued task and no rendering opportunity in order to
                be scheduled, and if frames are suppressed it simply catches up.
                A transition gated on a JS-added class cannot catch up, because
                the class was never added. That asymmetry is the whole fix for
                PUB-001, where the hero stayed at opacity 0 for seconds because
                the observer callback was never delivered.

   Note on prefers-reduced-motion: the rule in each index.html forces both
   animation-duration and transition-duration to .001ms, so reduced-motion users
   get the hero instantly. Before this file they got a permanently blank hero,
   since a zero-duration transition on a class that is never added is still
   opacity: 0. */

.reveal     { opacity: 0; transform: translateY(28px); transition: opacity .9s ease, transform .9s cubic-bezier(.2,.7,.2,1); }
.reveal.in  { opacity: 1; transform: none; }

.reveal-now { animation: revealNow .9s cubic-bezier(.2,.7,.2,1) both; }

@keyframes revealNow {
  from { opacity: 0; transform: translateY(28px); }
  to   { opacity: 1; transform: none; }
}
