/* ============================================================
   LLAB v8 — EFFECTS LAYER
   ------------------------------------------------------------
   A standalone animation layer that sits on top of the static
   landing page (index.html, owned by the page session).

   Brave        → the question mask-reveals word-by-word, then the
                  letters settle into an ambient glitch.
   Experimental → a Bifrost splash: a giant LLAB wordmark over a
                  full-screen WebGL rainbow-energy background, the
                  wordmark carrying an ambient goo-wobble.

   Nothing here changes the page's resting appearance — every
   effect is gated behind a body state class set by js/effects.js.
   No-JS / reduced-motion both fall back to plain, legible text.
   ============================================================ */

/* SVG filter host — js/effects.js appends the <svg> of <filter>s. */
.llab-fx-defs { position: absolute; width: 0; height: 0; pointer-events: none; }

/* ============================================================
   BRAVE — word mask-reveal
   ------------------------------------------------------------
   text-reveal.js wraps each word of .intro__q in:
     <span class="rv-word"><span class="rv-word__inner">word</span></span>
   .rv-word is an overflow-hidden mask; .rv-word__inner is animated.
   The heading is fully visible at rest — the reveal only plays
   when Brave is engaged (body.brave-preview / body.brave-on).
   ============================================================ */
.rv-word {
  display: inline-block;
  overflow: hidden;
  vertical-align: top;
  line-height: 1.18;            /* descender room so overflow:hidden won't clip */
}
.rv-word__inner {
  display: inline-block;
  line-height: 1.18;
  will-change: transform;
}

/* Engaged: each word slides up out of its mask, staggered by --i.
   Using an animation (not a transition) so it always starts from
   the hidden state and cleanly replays on re-trigger. */
body.fx-ready.brave-preview .intro__q .rv-word__inner,
body.fx-ready.brave-on      .intro__q .rv-word__inner {
  animation: rv-reveal 620ms cubic-bezier(0.34, 1.4, 0.64, 1) both;
  animation-delay: calc(var(--i, 0) * 60ms);
}
@keyframes rv-reveal {
  from { transform: translateY(120%); }
  to   { transform: translateY(0); }
}

/* ---- Ambient glitch — js adds .is-glitch once the reveal lands ---- */
.intro__q.is-glitch {
  filter: url(#llab-glitch);
  animation: llab-jitter 0.34s steps(3) infinite;
}
@keyframes llab-jitter {
  0%   { transform: translate(0, 0); }
  25%  { transform: translate(-0.9px, 0.5px); }
  50%  { transform: translate(0.7px, -0.6px); }
  75%  { transform: translate(-0.5px, 0.9px); }
  100% { transform: translate(0, 0); }
}

/* ---- Brave pill — "fall away" transition (js/effects.js) ----
   The landing elements drop off-screen one by one; an ASCII matrix
   fills the white screen behind them; then it hands to brave.html.
   No black frame anywhere. ---- */

/* the ASCII matrix layer — fixed behind .intro; brave-ascii.js stacks
   the field up from the bottom (its buildMs option) once it's shown */
.bfall-ascii {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  background: #ffffff;
  opacity: 0;
  pointer-events: none;
}
body.brave-falling .bfall-ascii { opacity: 1; }
.bfall-ascii pre {
  margin: 0;
  width: 100%;
  height: 100%;
  overflow: hidden;
  white-space: pre;
  color: #0a0a0a;
  font-family: "SFMono-Regular", "SF Mono", Menlo, Consolas,
               "Liberation Mono", monospace;
  font-size: 1.5rem;
}

/* each landing element falls — gravity-accelerated drop + tumble.
   --i staggers the start, --rot varies the tumble (set by effects.js) */
.bfall-item {
  animation: bfall 850ms cubic-bezier(0.5, 0, 0.9, 0.35) both;
  animation-delay: calc(var(--i, 0) * 90ms);
  overflow: visible;            /* heading-word masks must not clip the fall */
  will-change: transform;
}
@keyframes bfall {
  to {
    transform: translateY(125vh) rotate(var(--rot, 14deg));
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .bfall-item { animation: none; }
}

/* ============================================================
   LANDING INTRO — black → LLAB fades in
   ------------------------------------------------------------
   js/effects.js runs the show; an inline <head> script adds
   html.intro-armed before first paint so there is no flash.
   Reduced-motion never arms — the page renders normally.
   ============================================================ */

/* armed (pre-paint) — black the page out and hold the heading,
   pills and tagline hidden while the logo fades in */
html.intro-armed { background: #000; }
html.intro-armed body { background: #000; }
html.intro-armed .intro__q,
html.intro-armed .pills,
html.intro-armed .intro__tag { visibility: hidden; }

/* the LLAB logo fades in after a brief black hold; js/effects.js
   hands off on animationend. `both` keeps it hidden through the delay. */
html.intro-armed .intro__logo {
  animation: logo-fade 1200ms ease 800ms both;
}
@keyframes logo-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* spawn-in — once the intro hands off (body.intro-spawn): the heading
   words mask-reveal, the pills rise, the tagline fades up */
body.intro-spawn .intro__q .rv-word__inner {
  animation: rv-reveal 620ms cubic-bezier(0.34, 1.4, 0.64, 1) both;
  animation-delay: calc(200ms + var(--i, 0) * 60ms);
}
body.intro-spawn .pill {
  animation: intro-rise 560ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
body.intro-spawn .pill--brave { animation-delay: 360ms; }
body.intro-spawn .pill--exp   { animation-delay: 450ms; }
body.intro-spawn .intro__tag {
  animation: intro-fade 640ms ease 560ms both;
}
@keyframes intro-rise {
  from { opacity: 0; transform: translateY(24px); }
  to   { opacity: 1; transform: translateY(0); }
}
@keyframes intro-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}

/* ============================================================
   EXPERIMENTAL — Bifrost splash
   ------------------------------------------------------------
   A fixed full-viewport overlay, hidden until body.exp-on.
   .exp-splash__bg  — WebGL canvas (js/bifrost.js)
   .exp-splash__mark — giant LLAB wordmark, ambient goo-wobble
   ============================================================ */
.exp-splash {
  position: fixed;
  inset: 0;
  z-index: 90;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #04050a;
  visibility: hidden;
  /* Circular reveal — the colour field blooms out of the Experimental
     pill in one motion. effects.js sets the origin (--exp-ox/--exp-oy)
     and the cover radius (--exp-r) from the pill's live position.
     Closing reverses it: the colour collapses back into the pill. */
  clip-path: circle(0px at var(--exp-ox, 50%) var(--exp-oy, 50%));
  transition: clip-path 1400ms cubic-bezier(0.16, 1, 0.3, 1),
              visibility 0s linear 1400ms;
}
body.exp-on .exp-splash {
  visibility: visible;
  clip-path: circle(var(--exp-r, 150%) at var(--exp-ox, 50%) var(--exp-oy, 50%));
  transition: clip-path 1400ms cubic-bezier(0.16, 1, 0.3, 1);
}

.exp-splash__bg {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
  display: block;
}

/* Soft dark scrim behind the wordmark — guarantees legible white type
   over the background regardless of what colour the shader lands on. */
.exp-splash__scrim {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background: radial-gradient(ellipse 60% 46% at 50% 50%,
              rgba(4, 5, 10, 0.74) 0%,
              rgba(4, 5, 10, 0.46) 48%,
              transparent 80%);
}

.exp-splash__mark {
  position: relative;
  z-index: 2;
  font-family: var(--display, "Bahnschrift Condensed", "Archivo Narrow", sans-serif);
  font-weight: 700;
  font-size: clamp(4.5rem, 24vw, 20rem);
  line-height: 1;
  letter-spacing: -0.045em;
  color: #ffffff;
  /* ambient goo-wobble: #llab-liquid self-animates its baseFrequency,
     so the wordmark oozes continuously with no JS loop. */
  filter: url(#llab-goo-text) url(#llab-liquid);
  /* eases in once the splash opens */
  opacity: 0;
  transform: scale(0.94);
  transition: opacity 700ms ease 200ms,
              transform 900ms cubic-bezier(0.16, 1, 0.3, 1) 200ms;
}
body.exp-on .exp-splash__mark {
  opacity: 1;
  transform: scale(1);
}

.exp-splash__cue {
  position: absolute;
  z-index: 2;
  bottom: clamp(20px, 5vh, 48px);
  left: 50%;
  transform: translateX(-50%);
  font-family: var(--display, sans-serif);
  font-weight: 600;
  font-size: 0.8rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.62);
  opacity: 0;
  transition: opacity 600ms ease 900ms;
  pointer-events: none;
}
body.exp-on .exp-splash__cue { opacity: 1; }

/* ============================================================
   PHONE — the priority target. Lighten the heaviest filter:
   drop the goo blur, keep the cheaper liquid wobble.
   ============================================================ */
@media (max-width: 720px) {
  .exp-splash__mark { filter: url(#llab-liquid); }
}

/* ============================================================
   REDUCED MOTION — everything resolves to plain, static, legible.
   ============================================================ */
@media (prefers-reduced-motion: reduce) {
  body.fx-ready .intro__q .rv-word__inner { animation: none !important; }
  .intro__q.is-glitch { filter: none; animation: none; }
  .exp-splash, .exp-splash__mark, .exp-splash__cue { transition: none; }
  .exp-splash__mark { filter: none; opacity: 1; transform: none; }
}
