/* =============================================================================
   animations.css — the motion layer (optional). Copy alongside base.css only if
   the site uses animations. base.css stays layout-only; motion lives here.
   Start-states apply ONLY under .reveal-ready (set on <html> by JS), so JS-less
   visitors see everything at rest. Reveal rules are gated the SAME way plus the
   toggle class, so they always out-specify the start-state (never use an `html`
   element selector on the start-state — it inflates specificity and the reveal
   can't win). Every effect no-ops under prefers-reduced-motion (base.css).
   ============================================================================= */

/* ── fade-up — default card/section entrance (add .d2/.d3 for row stagger) ───── */
.reveal-ready .fade-up { opacity: 0; transform: translateY(30px); transition: opacity .7s var(--ease), transform .7s var(--ease); }
.reveal-ready .fade-up.in { opacity: 1; transform: none; }
.reveal-ready .fade-up.d2 { transition-delay: .12s; }
.reveal-ready .fade-up.d3 { transition-delay: .24s; }
.reveal-ready .fade-up.d4 { transition-delay: .36s; }
.reveal-ready .fade-up.no-move { transform: none; }

/* ── slide-in-from-side — alternate direction per card with .from-right ──────── */
.reveal-ready .slide-in-from-side { opacity: 0; transform: translateX(-48px); transition: opacity .7s var(--ease), transform .7s var(--ease); }
.reveal-ready .slide-in-from-side.from-right { transform: translateX(48px); }
.reveal-ready .slide-in-from-side.in { opacity: 1; transform: none; }
/* Mobile: side entrances become rise-ups — the ±48px X offsets widen the scrollable
   area on iOS and let the viewport wobble sideways */
@media (max-width: 720px) {
  .reveal-ready .slide-in-from-side,
  .reveal-ready .slide-in-from-side.from-right { transform: translateY(28px); }
}

/* ── reveal-block — whole-element fade+rise (for things that can't be split) ──── */
.reveal-ready .reveal-block { opacity: 0; transform: translateY(24px); transition: opacity .7s var(--ease), transform .7s var(--ease); }
.reveal-ready .reveal-block.in { opacity: 1; transform: none; }

/* ── text-write — word-by-word reveal (JS wraps words in .rw with --rw-i) ────── */
.reveal-ready [data-text-write] .rw { opacity: .12; transform: translateY(.18em); display: inline-block;
  transition: opacity .55s var(--ease), transform .55s var(--ease); transition-delay: calc(var(--rw-i, 0) * 55ms); }
.reveal-ready [data-text-write].reveal-on .rw { opacity: 1; transform: none; }

/* ── letter-fade — small headline/eyebrow lift-in (CSS-only, self-completing) ── */
.reveal-ready .letter-fade { opacity: 0; transform: translateY(10px); animation: letter-fade .7s var(--ease) .1s forwards; }
@keyframes letter-fade { to { opacity: 1; transform: none; } }

/* ── zoom-in — banner titles grow from nothing at the centre on page load ────── */
.reveal-ready .zoom-in { animation: zoom-in 1s var(--ease) .15s both; }
@keyframes zoom-in { from { opacity: 0; transform: scale(0); } to { opacity: 1; transform: scale(1); } }

/* Scroll-driven transforms below apply ONLY under html.fx-on — a flag animations.js
   sets when it's actually driving them. So with JS off, or under reduced-motion (where
   animations.js bails), these elements sit at their natural rest state (fully visible),
   never stuck at opacity:0 or a half-applied transform. */

/* ── parallax-band / parallax-drift — JS sets --par (−1…1); media drifts ─────── */
.parallax-band { position: relative; overflow: hidden; }
.parallax-band__media { position: absolute; inset: -12% 0; width: 100%; height: 124%; object-fit: cover; }
/* 8% (not the kit's 22%): drift must stay inside the media's 12% bleed on tall bands,
   or the image lags behind its box and shows a gap at the band's top edge. */
.fx-on .parallax-band__media { transform: translateY(calc(var(--par, 0) * 8%)); will-change: transform; }
.fx-on [data-parallax-drift] { transform: translateY(calc(var(--par, 0) * 20px)); will-change: transform; }

/* ── scroll-grow — feature image scales+rises with scroll (JS sets --grow 0→1) ─ */
.fx-on [data-scroll-grow] { transform: scale(calc(.93 + var(--grow, 0) * .07)) translateY(calc((1 - var(--grow, 0)) * 40px));
  transform-origin: center; will-change: transform; }

/* ── gallery-auto-scroll (hover gallery) — image zooms behind a fixed frame ──── */
.gallery-frame img { transition: transform .5s var(--ease); }
.gallery-frame:hover img, .gallery-frame:focus-within img { transform: scale(1.06); }
.gallery-frame { transition: transform .25s var(--ease), box-shadow .25s var(--ease); }
.gallery-frame:hover, .gallery-frame:focus-within { transform: translateY(-4px); box-shadow: 0 14px 34px rgba(0,0,0,.18); }
@media (prefers-reduced-motion: reduce) { .gallery-frame:hover, .gallery-frame:focus-within { transform: none; } .gallery-frame img { transition: none; } }

/* ── card-slide-in — cluster of frames each sliding in from a chosen edge ─────── */
/* JS sets --p 0→1 per frame; data-axis (x|y) + data-sign (-1|1) choose the edge. */
.fx-on [data-slide-frame] { opacity: var(--p, 0);
  transform: translate(calc(var(--dx, 0) * 1px), calc(var(--dy, 0) * 1px)); will-change: transform, opacity; }

/* ── logo-marquee — seamless infinite strip (pure-CSS baseline; JS adds drag) ── */
.marquee { overflow: hidden; }
.marquee__track { display: flex; width: max-content; gap: clamp(24px, 5vw, 64px); align-items: center; animation: marquee 55s linear infinite; }
.marquee.dragging .marquee__track, .marquee:hover .marquee__track { animation-play-state: paused; }
@keyframes marquee { from { transform: translateX(0); } to { transform: translateX(-50%); } }
@media (prefers-reduced-motion: reduce) { .marquee__track { animation: none; } }
