/* ================================================================
   ANIMATIONS — Keyframes + scroll reveal system
   Progressive enhancement: elements visible by default.
   JS adds .js-ready to <body>, THEN hides + reveals elements.
   ================================================================ */

/* -- Keyframes -------------------------------------------------- */
@keyframes wordReveal {
  from { transform: translateY(110%); opacity: 0; }
  to   { transform: translateY(0%);   opacity: 1; }
}

@keyframes fadeUp {
  from { opacity: 0; transform: translateY(24px); }   /* 3×8 */
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes fadeIn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes grainShift {
  0%,  100% { transform: translate(0, 0); }
  10%        { transform: translate(-2%, -3%); }
  20%        { transform: translate(3%, 2%); }
  30%        { transform: translate(-1%, 3%); }
  40%        { transform: translate(3%, -1%); }
  50%        { transform: translate(-3%, 2%); }
  60%        { transform: translate(1%, -2%); }
  70%        { transform: translate(-2%, 1%); }
  80%        { transform: translate(2%, 3%); }
  90%        { transform: translate(-1%, -3%); }
}

/* -- Hero entrance animations — THE FALLBACK -------------------- */
/* Gated on `html:not(.gsap-motion)`: when GSAP is driving,
   `hero-entrance.js` owns these elements and this whole block must not
   exist. Gating the rules OFF is deliberate — an earlier version left
   them on and cancelled them afterwards with an override, which is the
   mistake documented in the GSAP HANDOVER note at the bottom. */
.hero-anim-tag {
  display: inline-flex;   /* layout, not motion — never gated */
}

html:not(.gsap-motion) .hero-anim-nav {
  animation: fadeIn var(--dur-slow) var(--ease-out) 0.03s both;
}
.hero-anim-heading {
  /* word-inner children get wordReveal via hero.css per-word stagger */
}
html:not(.gsap-motion) .hero-anim-sub {
  animation: fadeUp 0.85s var(--ease-editorial) 0.50s both;
}
html:not(.gsap-motion) .hero-anim-tags {
  animation: fadeIn var(--dur-slow) var(--ease-out) 0.68s both;
}
html:not(.gsap-motion) .hero-anim-tag {
  animation: fadeUp var(--dur-slow) var(--ease-spring) both;
}
html:not(.gsap-motion) .hero-anim-tag:nth-child(1) { animation-delay: 0.72s; }
html:not(.gsap-motion) .hero-anim-tag:nth-child(2) { animation-delay: 0.82s; }
html:not(.gsap-motion) .hero-anim-tag:nth-child(3) { animation-delay: 0.92s; }
html:not(.gsap-motion) .hero-anim-tag:nth-child(4) { animation-delay: 1.02s; }

/* -- Section divider grow --------------------------------------- */
.section__divider {
  transform-origin: left;
}
html:not(.gsap-motion) .section__divider {
  transition: transform 0.9s var(--ease-editorial);
}


/* ================================================================
   SCROLL REVEAL SYSTEM

   Step 1: JS adds .js-ready to <body> — this arms the system.
   Step 2: [data-reveal] become invisible once .js-ready exists.
   Step 3: IntersectionObserver adds .is-visible → elements appear.
   Without JS: elements are always visible (progressive enhancement).
   ================================================================ */

/* -- Base: UNFOLD — every revealed block hinges open from its top
   crease, like a sheet of paper tipping flat. This is the page-wide
   Warm Precision reveal language (headings, dividers, content, cards). */
html:not(.gsap-motion) body.js-ready [data-reveal] {
  opacity: 0;
  transform-origin: top center;
  transform: perspective(1500px) rotateX(-20deg) translateY(30px);
  transition:
    opacity   0.9s  var(--ease-editorial),
    transform 1.15s var(--ease-editorial);
}

html:not(.gsap-motion) body.js-ready [data-reveal].is-visible {
  opacity: 1;
  transform: none;
}

/* -- Delay variants (stagger siblings entering together) --------- */
html:not(.gsap-motion) body.js-ready [data-reveal][data-delay="1"] { transition-delay: 0.15s; }
html:not(.gsap-motion) body.js-ready [data-reveal][data-delay="2"] { transition-delay: 0.38s; }
html:not(.gsap-motion) body.js-ready [data-reveal][data-delay="3"] { transition-delay: 0.55s; }
html:not(.gsap-motion) body.js-ready [data-reveal][data-delay="4"] { transition-delay: 0.70s; }
html:not(.gsap-motion) body.js-ready [data-reveal][data-delay="5"] { transition-delay: 0.85s; }

/* Section headings now unfold as whole blocks via the base reveal
   above (their `data-split` was removed) — the per-word clip system
   remains only on the hero, driven by hero.css. */

/* -- Scale variant (project cards, footer aside) ---------------- */
html:not(.gsap-motion) body.js-ready [data-reveal="scale"] {
  transform: translateY(48px) scale(0.97);
}
html:not(.gsap-motion) body.js-ready [data-reveal="scale"].is-visible {
  transform: translateY(0) scale(1);
}


/* -- Fade only (no translateY) ---------------------------------- */
html:not(.gsap-motion) body.js-ready [data-reveal="fade"] {
  transform: none;
}
html:not(.gsap-motion) body.js-ready [data-reveal="fade"].is-visible {
  transform: none;
}

/* `data-reveal="fold"` (project cards) now resolves to the base unfold
   above — kept as an explicit alias so the markup still reads "fold". */

/* -- Divider: scaleX wipe from left ----------------------------- */
html:not(.gsap-motion) body.js-ready .section__divider[data-reveal="fade"] {
  transform: scaleX(0);
  transform-origin: left;
  transition-duration: 1s;
}
html:not(.gsap-motion) body.js-ready .section__divider[data-reveal="fade"].is-visible {
  transform: scaleX(1);
}

/* ================================================================
   GSAP HANDOVER

   `js/modules/motion.js` adds `.gsap-motion` to <html> when GSAP +
   ScrollTrigger are present and motion is welcome. Everything above is
   then the FALLBACK, and is GATED OFF at the selector
   (`html:not(.gsap-motion)`) rather than cancelled by an override here.

   WHY THE GATE AND NOT AN OVERRIDE — a bug worth not repeating.
   The first version left the fallback rules live and neutralised them
   with `html.gsap-motion body.js-ready [data-reveal] { opacity: 1;
   transform: none; transition: none }`. Two things went wrong:

     1. `transition: none` is not scoped in TIME. It was meant to stop a
        double-ease during the reveal, but it applied forever — so every
        `[data-reveal]` element lost its own component transitions for
        the life of the page. `.btn-pill` and `.social-link` stopped
        easing their hover colour entirely.
     2. `transform: none` at specificity (0,3,2) outranks
        `.social-link:hover` (0,2,0), so the social icons' hover lift
        never rendered at all.

   An override has to out-specify the rule it cancels, and that same
   weight then lands on every unrelated rule for the element. A `:not()`
   gate has no weight to spend — the fallback simply is not there.

   The one thing GSAP genuinely needs suppressed is the element's OWN
   component transition while a tween is in flight. That is scoped to
   `.is-revealing`, which `scroll-reveal.js` adds with the start state
   and removes on completion — so it covers exactly the tween and
   nothing after it.
   ================================================================ */

html.gsap-motion [data-reveal].is-revealing {
  transition: none;
}


/* ================================================================
   PAGE TRANSITION
   The cross-document View Transition that used to live here has been
   REMOVED. Measured on real Chrome, the outgoing page reported a
   transition while the incoming page got none — the browser abandoned
   it between documents, so the fold never ran, with no API to observe
   why. It is also skipped outright for hidden documents, so it cannot
   be verified in a background tab.

   The page swap is now covered by a CSS-animated curtain whose motion
   owes nothing to the browser capture machinery — see
   css/components/curtain.css.

   Do NOT reintroduce @view-transition or ::view-transition-* rules.
   ================================================================ */
