/* FINTRA motion: the intro, the scroll reveals, and the hero settle on inner pages.
   The press lives with each component in neo.css. */

/* ---------- 0. The intro: the camera enters the R ----------
   The wordmark is six spans. Each letter stamps in on a stagger, then the camera pushes into the R:
   the R scales until its strokes fill the viewport and the other five letters drop away. The site
   arrives through it, so the last thing the reader sees before the page is the inside of a letter.

   An overlay like this is the one element that can take a site hostage, because its only exit is an
   animation. Four guards, and step 13 of neo-check.mjs fails if any goes missing:
     1. pointer-events: none, so even a stuck layer cannot eat a click.
     2. aria-hidden, so a screen reader never reads it or waits on it.
     3. display: none under reduced motion, because the global override kills animations and this
        layer would then sit over the page forever.
     4. visibility: hidden in the last keyframe, so it leaves the paint tree rather than sitting there
        at zero opacity.
   The residual risk is CSS failing to load at all, which unsticks the whole site anyway.

   ONE KNOB. Every duration and delay below is a base value multiplied by --intro-speed, so the whole
   sequence can be stretched or tightened by editing one number without disturbing the rhythm between
   its phases. At 1 the intro runs 1.75s; shipped at 1.7 it runs just under 3s.

   The letters were quantised, and it was wrong. letter-in ran on steps(6, end) and letter-out on
   steps(5, end), on the theory that holding each step near 95ms is what the eye reads as a hard
   stamp rather than as lag. Sampled frame by frame at a true 60fps, each step held five to seven
   identical frames and then jumped: six freeze-frames, a jump of one sixth, six more. That is a 10Hz
   animation running inside a 60Hz page, which is not a stamp, it is a dropped frame rate, and it is
   what the start of this page felt like. Both now run on curves the rest of the file already uses.
   letter-in is the arrival curve and is front-loaded, covering most of its travel inside the first
   sixth of its duration, which keeps the landing hard without the stair; letter-out is the exit
   curve. The durations are untouched, so the stagger and every phase boundary downstream are
   unchanged: the letters still land at 0.96s and the camera still moves at 1.19s. */
:root { --intro-speed: 1.7; }
.intro {
  display: grid; place-content: center; gap: 16px; background: var(--ink); color: var(--paper);
  overflow: hidden; /* the zoomed R is 4507px wide; clip it so it cannot ever set page width */
  animation: intro-leave calc(420ms * var(--intro-speed)) cubic-bezier(.7, 0, .3, 1) calc(1330ms * var(--intro-speed)) forwards;
}
.intro__word {
  display: flex; justify-content: center; margin: 0; color: var(--paper);
  font: 700 clamp(56px, 13vw, 176px)/1 var(--font-display); letter-spacing: -.05em;
}
.intro__word span { display: inline-block; animation: letter-in calc(340ms * var(--intro-speed)) cubic-bezier(.2, .9, .2, 1) both; }
.intro__word span:nth-child(1) { animation-delay: calc(0ms * var(--intro-speed)); }
.intro__word span:nth-child(2) { animation-delay: calc(45ms * var(--intro-speed)); }
.intro__word span:nth-child(3) { animation-delay: calc(90ms * var(--intro-speed)); }
.intro__word span:nth-child(4) { animation-delay: calc(135ms * var(--intro-speed)); }
.intro__word span:nth-child(5) { animation-delay: calc(180ms * var(--intro-speed)); }
.intro__word span:nth-child(6) { animation-delay: calc(225ms * var(--intro-speed)); }
/* The five that are not the R fall away as the camera moves in, so the R is the only thing left. */
.intro__word span:not(.intro__key) {
  animation: letter-in calc(340ms * var(--intro-speed)) cubic-bezier(.2, .9, .2, 1) both,
             letter-out calc(300ms * var(--intro-speed)) cubic-bezier(.7, 0, .3, 1) calc(700ms * var(--intro-speed)) forwards;
}
/* Selector note: this must out-specify `.intro__word span` above, which is 0,1,1 against 0,1,0 for a
   bare `.intro__key`. Written bare, the zoom was silently dropped and the R never scaled, so the whole
   effect was a fade. `.intro__word span.intro__key` is 0,2,1 and wins. */
.intro__word span.intro__key {
  color: var(--red);
  animation: letter-in calc(340ms * var(--intro-speed)) cubic-bezier(.2, .9, .2, 1) calc(180ms * var(--intro-speed)) both,
             key-zoom calc(820ms * var(--intro-speed)) cubic-bezier(.72, 0, .16, 1) calc(700ms * var(--intro-speed)) forwards;
}
.intro__sub {
  margin: 0; text-align: center; color: var(--paper);
  font: 800 12px/1 var(--font-mono); letter-spacing: .34em; text-transform: uppercase;
  animation: letter-in calc(340ms * var(--intro-speed)) cubic-bezier(.2, .9, .2, 1) calc(260ms * var(--intro-speed)) both,
             letter-out calc(240ms * var(--intro-speed)) cubic-bezier(.7, 0, .3, 1) calc(700ms * var(--intro-speed)) forwards;
}
@keyframes letter-in { from { opacity: 0; translate: 0 14px; } to { opacity: 1; translate: 0 0; } }
@keyframes letter-out { to { opacity: 0; scale: .88; } }
/* 44x on a 176px glyph is 7744px: the stroke band is wider than any viewport, so the screen becomes
   the inside of the letter. Accelerating easing, because a camera push gains speed; a linear zoom
   reads as a rectangle getting bigger instead of as travel. */
@keyframes key-zoom { to { scale: 44; } }
/* A plain fade at the end. The layer no longer needs its own scale: by this point the R is the whole
   screen, so scaling the layer as well would fight the zoom. */
@keyframes intro-leave {
  from { opacity: 1; visibility: visible; }
  to   { opacity: 0; visibility: hidden; }
}
@media (prefers-reduced-motion: reduce) {
  .intro { display: none; }
}
/* The site arrives on the same beat as the layer leaving, which is what makes the two read as one
   move. `.hero` and not `.hero__copy`, because the copy already owns a parallax animation and an
   element gets one `animation` list: a second rule on the child would silently replace the parallax. */
.hero { animation: site-in calc(850ms * var(--intro-speed)) cubic-bezier(.2, .9, .2, 1) calc(1180ms * var(--intro-speed)) both; }
@keyframes site-in {
  from { opacity: 0; scale: .93; }
  to   { opacity: 1; scale: 1; }
}
@media (prefers-reduced-motion: reduce) { .hero { animation: none; } }

/* ---------- The reveal ----------
   Blocks rise into place as they scroll into view, in every browser: neo.js watches .reveal elements
   with an IntersectionObserver and adds .is-in. The animation sits paused on its first frame until
   then, so the hidden start state lives only inside @keyframes (step 11 of neo-check). The head script
   adds .js-reveal only when IntersectionObserver exists, and drops it again on load if neo.js never
   ran, so a blocked script can never leave content hidden. Translate and opacity only. */
@media (prefers-reduced-motion: no-preference) {
  html.js-reveal .reveal { animation: rise .7s cubic-bezier(.2, .8, .2, 1) both; animation-play-state: paused; }
  html.js-reveal .reveal.is-in { animation-play-state: running; }
}
@keyframes rise {
  from { opacity: 0; translate: 0 40px; }
  to { opacity: 1; translate: 0 0; }
}

/* Inner pages have no intro; their head settles once on load. */
@media (prefers-reduced-motion: no-preference) {
  .page-head .wrap { animation: settle .6s cubic-bezier(.2, .8, .2, 1) both; }
}
@keyframes settle {
  from { opacity: 0; translate: 0 20px; }
  to { opacity: 1; translate: 0 0; }
}
