/* ─────────────────────────────────────────────────────────────────────────────
   Explicit Research — premium scroll effects
   v9: magnetic "outside-in" assembly (keyframe-based) + homepage hero entrance

   SAFETY: the hidden initial state applies ONLY when JS has added `.js-reveal` to <html>
   (js/effects.js). If JS fails or is disabled, nothing is ever hidden — products always render.

   WHY KEYFRAMES (not a transition from an offset resting-state): IntersectionObserver measures an
   element at its TRANSFORMED position. If the big fly-in offset lived in the resting state, a card
   shifted ~220px would sit off-screen on narrow (mobile) viewports, never intersect, and stay blank.
   So the resting hidden state is opacity-only (element stays at its natural spot → always detected),
   and the offset lives only inside the keyframe that plays on `.is-revealed`. 60fps, reduced-motion safe.
   ───────────────────────────────────────────────────────────────────────────── */

/* Resting hidden state — opacity only, NO transform (keeps the element at its natural position so
   IntersectionObserver can always see it). */
.js-reveal .er-reveal { opacity: 0; will-change: opacity, transform; }

/* Fly-in keyframes — the big travel + magnetic scale-settle live here, only while animating. */
@keyframes erInB  { from { opacity: 0; transform: translateY(200px)  scale(0.9); } to { opacity: 1; transform: none; } }
@keyframes erInT  { from { opacity: 0; transform: translateY(-200px) scale(0.9); } to { opacity: 1; transform: none; } }
@keyframes erInL  { from { opacity: 0; transform: translateX(-220px) scale(0.9); } to { opacity: 1; transform: none; } }
@keyframes erInR  { from { opacity: 0; transform: translateX(220px)  scale(0.9); } to { opacity: 1; transform: none; } }
@keyframes erInBL { from { opacity: 0; transform: translate(-150px,  150px) scale(0.9); } to { opacity: 1; transform: none; } }
@keyframes erInBR { from { opacity: 0; transform: translate( 150px,  150px) scale(0.9); } to { opacity: 1; transform: none; } }
@keyframes erInTL { from { opacity: 0; transform: translate(-150px, -150px) scale(0.9); } to { opacity: 1; transform: none; } }
@keyframes erInTR { from { opacity: 0; transform: translate( 150px, -150px) scale(0.9); } to { opacity: 1; transform: none; } }

/* Fallback resting state once revealed (in case a direction class is ever missing → never stuck). */
.js-reveal .er-reveal.is-revealed { opacity: 1; }

/* Magnetic snap-settle ease; the fast implicit opacity inside each keyframe means the piece is visible
   while it travels — you watch it get pulled in. Delay drives the batch stagger. */
.js-reveal .er-reveal.is-revealed.er-from-b  { animation: erInB  1.25s cubic-bezier(0.25, 1.1, 0.35, 1) var(--er-reveal-delay, 0ms) both; }
.js-reveal .er-reveal.is-revealed.er-from-t  { animation: erInT  1.25s cubic-bezier(0.25, 1.1, 0.35, 1) var(--er-reveal-delay, 0ms) both; }
.js-reveal .er-reveal.is-revealed.er-from-l  { animation: erInL  1.25s cubic-bezier(0.25, 1.1, 0.35, 1) var(--er-reveal-delay, 0ms) both; }
.js-reveal .er-reveal.is-revealed.er-from-r  { animation: erInR  1.25s cubic-bezier(0.25, 1.1, 0.35, 1) var(--er-reveal-delay, 0ms) both; }
.js-reveal .er-reveal.is-revealed.er-from-bl { animation: erInBL 1.25s cubic-bezier(0.25, 1.1, 0.35, 1) var(--er-reveal-delay, 0ms) both; }
.js-reveal .er-reveal.is-revealed.er-from-br { animation: erInBR 1.25s cubic-bezier(0.25, 1.1, 0.35, 1) var(--er-reveal-delay, 0ms) both; }
.js-reveal .er-reveal.is-revealed.er-from-tl { animation: erInTL 1.25s cubic-bezier(0.25, 1.1, 0.35, 1) var(--er-reveal-delay, 0ms) both; }
.js-reveal .er-reveal.is-revealed.er-from-tr { animation: erInTR 1.25s cubic-bezier(0.25, 1.1, 0.35, 1) var(--er-reveal-delay, 0ms) both; }

/* Sideways/diagonal reveals must never spawn a horizontal scrollbar (mobile especially). Clip the
   common content wrappers — `overflow-x: clip` clips the sliding pieces without becoming a scroll
   container (so it can't break the fixed header or vertical scrolling). */
.js-reveal .container,
.js-reveal .product-grid,
.js-reveal .page-content,
.js-reveal .article-content { overflow-x: clip; }

/* ── Homepage hero entrance (plays once on load) ──────────────────────────────
   Gated by html.er-anim-on, which the inline <head> script on index.html sets PRE-PAINT (only when
   animations should play). Setting it before the hero paints means the keyframe start state applies
   from frame one — no flash. If the class is never added (no-JS / error / suppressed) the hero is
   simply visible. */
/* One quick, unified entrance: the whole hero block eases DOWN from slightly above + fades in
   (replaced a staggered per-element sequence, which read as "trickling in" on slow connections).
   Time-based + `both`, so it always completes in ~0.6s regardless of network — it never waits on
   an image and never leaves anything stuck hidden. Vials just fade (they keep their own drift). */
@keyframes erHeroDrop     { from { opacity: 0; transform: translateY(-24px); } to { opacity: 1; transform: none; } }
@keyframes erHeroFadeVial { from { opacity: 0; } to { opacity: 1; } }
html.er-anim-on .hp-hero-inner { animation: erHeroDrop 0.6s cubic-bezier(0.16, 1, 0.3, 1) both; }
html.er-anim-on .hero-vial     { animation: erHeroFadeVial 0.7s ease both; }

/* Accessibility + comfort: reduced-motion users see everything immediately, no motion.
   (The JS/head-script also bail on reduced-motion — these are backups.) */
@media (prefers-reduced-motion: reduce) {
  .js-reveal .er-reveal { opacity: 1 !important; animation: none !important; transform: none !important; }
  html.er-anim-on .hp-hero-inner,
  html.er-anim-on .hero-vial { animation: none !important; }
}
