/* ============================================================
   Brainflood marketing — special-effects library  (v2: perf pass)
   ============================================================
   Universal effect classes + keyframes used across the marketing
   site, brainflood login (AuthLayout.vue), and joinplay landing.

   v2 changes (focus: zero-churn long-running stability)
   ─────────────────────────────────────────────────────────────
   - Persistent DOM model: a single hook element (or two alternating
     for crossfade) lives in the page. textContent updates per cycle
     instead of destroying / rebuilding ~30+ per-letter spans.
   - 7 whole-element entrance effects (was 14 per-letter): drift,
     zoom-pop, tilt, shimmer, prism, glitch, blur-clear. Each is
     ONE animation per cycle instead of 30+. Browser tracks one
     active animation, not a swarm.
   - Animations re-trigger via remove-class / force-reflow / add-class
     pattern from the JS engine — no destroy/recreate.
   - All hardcoded color values use theme CSS vars (--accent-rgb,
     --secondary-rgb, --text-primary) — auto-tints to whichever
     skin the visitor picked.

   Engine: marketing/js/marketing-fx.js
   CSP-safe: pure CSS, no inline JS.
   ============================================================ */


/* ─── HOOK ROTATOR STAGE ─────────────────────────────────────
   Reserves vertical space, isolates per-element animations from
   invalidating layout outside the stage. */
.bf-fx-stage {
  position: relative;
  display: grid;
  place-items: center;
  /* Fits a 2-line hook (line-height 1.05 × 2 + small margin) without
     reserving wasted space. */
  min-height: 2.15em;
  perspective: 800px;
  overflow: visible;
  isolation: isolate;
  contain: layout paint style;
}

/* Static halo behind the text. Painted ONCE and cached by the GPU
   — zero per-frame cost. */
.bf-fx-halo {
  position: absolute;
  inset: -4% 18%;
  border-radius: 999px;
  pointer-events: none;
  z-index: 0;
  filter: blur(10px);
  background: radial-gradient(ellipse at 50% 50%,
    rgba(var(--accent-rgb, 168, 85, 247), 0.22),
    rgba(var(--accent-rgb, 168, 85, 247), 0.06) 45%,
    transparent 70%);
  opacity: 0.55;
}

/* TWO alternating hook elements (slot a / slot b). Both stacked
   in the same grid cell (1 / 1). On each rotation: the engine
   updates the next slot's textContent, fades the active slot out,
   and triggers an entrance animation on the new slot. Zero DOM
   creation per cycle. */
.bf-fx-hook {
  display: inline-block;
  grid-area: 1 / 1;
  position: relative;
  z-index: 2;
  text-align: center;
  color: var(--text-primary, #fff);
  -webkit-text-fill-color: var(--text-primary, #fff);
  text-shadow:
    0 0 4px rgba(var(--accent-rgb, 168, 85, 247), 0.4),
    0 1px 2px rgba(0, 0, 0, 0.45);
  /* Default state: visible but no entrance — JS adds an fx-* class
     to trigger the animation per cycle. */
  opacity: 1;
  /* No transition — outgoing slot SNAPS invisible. The new slot's
     fx-* entrance animation provides the visual continuity. With
     a slow transition here the two slots crossfade through each
     other for the transition duration → both texts partially
     visible at the same grid cell → readable mush. Snap fixes it. */
}
/* Light themes: softer glow + softer black shadow. */
[data-theme="arctic"] .bf-fx-hook,
[data-theme="ivory"]  .bf-fx-hook,
[data-theme="rose"]   .bf-fx-hook,
[data-theme="sage"]   .bf-fx-hook {
  text-shadow:
    0 0 4px  rgba(var(--accent-rgb), 0.22),
    0 1px 2px rgba(0, 0, 0, 0.10);
}
/* Slot B starts hidden. The engine flips visibility / opacity
   between slots on each cycle. */
.bf-fx-hook[data-slot="b"] {
  opacity: 0;
  visibility: hidden;
}
.bf-fx-hook.is-leaving {
  opacity: 0;
  /* visibility: hidden guarantees the outgoing slot can't paint
     even if some animation tries to re-set its opacity mid-cycle. */
  visibility: hidden;
}
.bf-fx-hook.is-active {
  opacity: 1;
  visibility: visible;
}

/* Line 2 stays a single plain-text element so the global brand
   gradient text fill (.bf-h1 .lit { background-clip: text; … })
   renders correctly. The gradient WOULDN'T work if line 2 were
   split into nested inline-blocks for per-letter animation —
   which is the whole reason we ditched per-letter in v2. */
.bf-fx-hook .lit {
  display: inline-block;
  /* Cinematic delayed fade-up on line 2 — lands AFTER line 1's
     entrance completes, so the gradient is the punchline. */
  animation: bf-fx-lit-enter 0.55s cubic-bezier(0.16, 1, 0.3, 1) 0.35s both;
}
@keyframes bf-fx-lit-enter {
  0%   { opacity: 0; transform: translateY(8px) scale(0.97); }
  100% { opacity: 1; transform: translateY(0)   scale(1); }
}


/* ═══════════════════════════════════════════════════════════════
   ENTRANCE EFFECTS  (7 — whole-element, one animation per cycle)
   ═══════════════════════════════════════════════════════════════ */

/* DRIFT — gentle slide-down + fade. Quiet but premium. */
.bf-fx-hook.fx-drift {
  animation: bf-fx-drift 0.7s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes bf-fx-drift {
  0%   { opacity: 0; transform: translateY(-20px) scale(1.04); }
  100% { opacity: 1; transform: translateY(0)     scale(1); }
}

/* ZOOM-POP — scale up from small with a spring overshoot. */
.bf-fx-hook.fx-zoompop {
  animation: bf-fx-zoompop 0.8s cubic-bezier(0.34, 1.56, 0.64, 1) both;
}
@keyframes bf-fx-zoompop {
  0%   { opacity: 0; transform: scale(0.5) rotate(-3deg); }
  55%  { opacity: 1; transform: scale(1.08) rotate(1deg); }
  100% { opacity: 1; transform: scale(1) rotate(0); }
}

/* TILT — 3D rotateX entrance, like a panel folding into place. */
.bf-fx-hook.fx-tilt {
  animation: bf-fx-tilt 0.85s cubic-bezier(0.22, 1.4, 0.55, 1) both;
  transform-origin: 50% 100%;
}
@keyframes bf-fx-tilt {
  0%   { opacity: 0; transform: rotateX(-55deg) translateY(12px) scale(0.92); }
  60%  { opacity: 1; transform: rotateX(8deg)   translateY(-2px) scale(1.02); }
  100% { opacity: 1; transform: rotateX(0)      translateY(0)    scale(1); }
}

/* SHIMMER — clip-path wipe reveal left-to-right. */
.bf-fx-hook.fx-shimmer {
  animation: bf-fx-shimmer 0.95s cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes bf-fx-shimmer {
  0%   { clip-path: inset(0 100% 0 0); opacity: 0; }
  20%  { opacity: 1; }
  100% { clip-path: inset(0 0 0 0); opacity: 1; }
}

/* PRISM — Dark Side of the Moon. RGB chromatic ghosts converge to
   one. Whole-element scope (was per-letter in v1; whole-element
   reads just as dramatic with vastly less compositing cost). */
.bf-fx-hook.fx-prism {
  animation: bf-fx-prism 1s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes bf-fx-prism {
  0%   {
    opacity: 0;
    transform: scale(1.4);
    text-shadow:
      -30px 0 12px rgba(255, 30, 80, 0.9),
        0 0 12px rgba(80, 255, 120, 0.7),
       30px 0 12px rgba(40, 110, 255, 0.9);
  }
  55%  {
    opacity: 1;
    transform: scale(1.06);
    text-shadow:
      -10px 0 8px rgba(255, 30, 80, 0.7),
        0 0 10px rgba(80, 255, 120, 0.6),
       10px 0 8px rgba(40, 110, 255, 0.7);
  }
  100% { opacity: 1; transform: scale(1); }
}

/* GLITCH — translate + skew jitter, settles to position. */
.bf-fx-hook.fx-glitch {
  animation: bf-fx-glitch 0.6s steps(8, end) both;
}
@keyframes bf-fx-glitch {
  0%   { opacity: 0; transform: translate(6px, -4px) skewX(-8deg); }
  20%  { opacity: 1; transform: translate(-8px, 3px) skewX(6deg); }
  40%  { transform: translate(5px, 4px) skewX(-4deg); }
  60%  { transform: translate(-4px, -2px) skewX(2deg); }
  80%  { transform: translate(2px, 1px) skewX(0); }
  100% { opacity: 1; transform: translate(0) skewX(0); }
}

/* BLUR-CLEAR — start blurred + bigger, sharpen to position.
   Cinematic "focus pull" effect. */
.bf-fx-hook.fx-blurclear {
  animation: bf-fx-blurclear 0.9s cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes bf-fx-blurclear {
  0%   { opacity: 0; transform: scale(1.25); filter: blur(12px); }
  50%  { opacity: 1; filter: blur(4px); }
  100% { opacity: 1; transform: scale(1); filter: blur(0); }
}


/* ═══════════════════════════════════════════════════════════════
   CYCLE BURST  (persistent layers, re-triggered via class toggle)
   ═══════════════════════════════════════════════════════════════ */

/* Three persistent burst layers live in the DOM forever; on each
   rotation the engine removes their .play class, forces reflow,
   re-adds it. Animation replays. Zero DOM creation. */
.cycle-streak,
.cycle-burst,
.cycle-sparkle {
  position: absolute;
  pointer-events: none;
  z-index: 3;
  opacity: 0;
  visibility: hidden;
}

.cycle-streak {
  top: 50%; left: 0;
  width: 35%; height: 3px;
  background: linear-gradient(90deg,
    transparent,
    rgba(var(--accent-rgb, 168, 85, 247), 1) 35%,
    rgba(255, 255, 255, 1) 50%,
    rgba(var(--secondary-rgb, 244, 114, 182), 1) 65%,
    transparent);
  filter: blur(1px);
  box-shadow:
    0 0 10px rgba(var(--accent-rgb, 168, 85, 247), 0.7),
    0 0 20px rgba(var(--secondary-rgb, 244, 114, 182), 0.4);
  border-radius: 999px;
  transform: translate3d(-180%, -50%, 0);
}
.cycle-streak.play {
  visibility: visible;
  animation: bf-streak-fly 1.1s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
@keyframes bf-streak-fly {
  0%   { opacity: 0; transform: translate3d(-180%, -50%, 0); }
  15%  { opacity: 1; }
  100% { opacity: 0; transform: translate3d(380%, -50%, 0); }
}

.cycle-burst {
  top: 50%; left: 50%;
  width: 20px; height: 20px;
  border-radius: 50%;
  border: 2px solid rgba(var(--accent-rgb, 168, 85, 247), 0.9);
  box-shadow:
    0 0 12px rgba(var(--accent-rgb, 168, 85, 247), 0.6),
    inset 0 0 12px rgba(var(--secondary-rgb, 244, 114, 182), 0.4);
  transform: translate(-50%, -50%) scale(0);
}
.cycle-burst.play {
  visibility: visible;
  animation: bf-burst-expand 0.85s ease-out forwards;
}
@keyframes bf-burst-expand {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0); }
  20%  { opacity: 1; }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(7); border-width: 0.5px; }
}

.cycle-sparkle {
  top: 22%; right: 16%;
  width: 6px; height: 6px;
  border-radius: 50%;
  background: #fff;
  box-shadow:
    0 0 10px rgba(var(--accent-rgb, 168, 85, 247), 0.85),
    0 0 20px rgba(var(--secondary-rgb, 244, 114, 182), 0.5);
}
.cycle-sparkle.play {
  visibility: visible;
  animation: bf-sparkle-burst 1s ease-out forwards;
}
@keyframes bf-sparkle-burst {
  0%   { opacity: 0; transform: scale(0); }
  25%  { opacity: 1; transform: scale(2); }
  60%  { opacity: 0.7; transform: scale(1) translate(12px, -8px); }
  100% { opacity: 0; transform: scale(0.4) translate(24px, -18px); }
}


/* ═══════════════════════════════════════════════════════════════
   SCROLL-REVEAL / STAGGER / COUNTER / SPOTLIGHT / CTA SHIMMER
   ═══════════════════════════════════════════════════════════════ */

/* Initial-hidden state gated behind html.bf-fx-ready — if the JS
   never runs, content shows at full opacity instead of being
   stuck hidden. */
html.bf-fx-ready .bf-fx-reveal {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
  will-change: transform, opacity;
}
html.bf-fx-ready .bf-fx-reveal.bf-fx-revealed {
  opacity: 1;
  transform: translateY(0);
  will-change: auto;
}
html.bf-fx-ready .bf-fx-stagger > * {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity 0.6s cubic-bezier(0.16, 1, 0.3, 1),
              transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);
  transition-delay: calc(var(--bf-i, 0) * 70ms);
  will-change: transform, opacity;
}
html.bf-fx-ready .bf-fx-stagger.bf-fx-revealed > * {
  opacity: 1;
  transform: translateY(0);
  will-change: auto;
}

/* One-shot hero entrance for sub-pages — fades+slides the whole H1
   in once on page load, with a static halo behind it. */
.bf-fx-hero-wrap {
  position: relative;
  display: inline-block;
  isolation: isolate;
  contain: layout paint style;
}
.bf-fx-hero-wrap .bf-fx-halo { inset: -20% -5%; }
.bf-fx-hero-wrap .bf-fx-hero-text {
  position: relative;
  z-index: 1;
  display: inline-block;
  opacity: 0;
  transform: translateY(20px);
}
.bf-fx-hero-wrap.bf-fx-hero-play .bf-fx-hero-text {
  animation: bf-fx-hero-in 0.85s cubic-bezier(0.16, 1, 0.3, 1) both;
}
@keyframes bf-fx-hero-in {
  0%   { opacity: 0; transform: translateY(20px); }
  100% { opacity: 1; transform: translateY(0); }
}

/* Counter — count-up + landing halo flash */
.bf-fx-counter { position: relative; display: inline-block; }
.bf-fx-counter::after {
  content: '';
  position: absolute;
  inset: -20%;
  border-radius: 18px;
  background: radial-gradient(ellipse at center,
    rgba(var(--secondary-rgb, 244, 114, 182), 0.4),
    rgba(var(--accent-rgb, 168, 85, 247), 0.2) 45%,
    transparent 75%);
  filter: blur(14px);
  opacity: 0;
  pointer-events: none;
  z-index: -1;
}
.bf-fx-counter.bf-fx-landed::after {
  animation: bf-fx-counter-flash 0.7s ease-out;
}
@keyframes bf-fx-counter-flash {
  0%   { opacity: 0;   transform: scale(0.6); }
  35%  { opacity: 0.9; transform: scale(1.15); }
  100% { opacity: 0;   transform: scale(1.3); }
}

/* CTA shimmer sweep — one-shot per ~7s cycle, idle in between */
.bf-fx-cta-shimmer { position: relative; overflow: hidden; }
.bf-fx-cta-shimmer::after {
  content: '';
  position: absolute;
  inset: 0;
  background: linear-gradient(110deg,
    transparent 30%,
    rgba(255, 255, 255, 0.28) 50%,
    transparent 70%);
  transform: translateX(-120%);
  pointer-events: none;
  animation: bf-fx-cta-sweep 7s ease-in-out infinite;
  animation-delay: 3s;
}
@keyframes bf-fx-cta-sweep {
  0%, 88%  { transform: translateX(-120%); }
  94%      { transform: translateX(0%); }
  100%     { transform: translateX(120%); }
}

/* Card hover spotlight — desktop only */
@media (hover: hover) and (pointer: fine) {
  [data-fx-spotlight] {
    position: relative;
    overflow: hidden;
    isolation: isolate;
  }
  [data-fx-spotlight]::before {
    content: '';
    position: absolute;
    inset: 0;
    background: radial-gradient(
      circle at var(--bf-x, 50%) var(--bf-y, 50%),
      rgba(var(--accent-rgb, 168, 85, 247), 0.18),
      transparent 45%);
    opacity: 0;
    transition: opacity 0.25s ease;
    pointer-events: none;
    z-index: 0;
  }
  [data-fx-spotlight]:hover::before { opacity: 1; }
  [data-fx-spotlight] > * { position: relative; z-index: 1; }
}


/* ═══════════════════════════════════════════════════════════════
   MOBILE + REDUCED-MOTION
   ═══════════════════════════════════════════════════════════════ */

@media (max-width: 640px) {
  .bf-fx-stage  { min-height: 3em; perspective: 600px; }
  .bf-fx-halo   { inset: -3% 12%; filter: blur(8px); }
  html.bf-fx-ready .bf-fx-stagger > * { transition-delay: calc(var(--bf-i, 0) * 50ms); }
  .bf-fx-cta-shimmer::after { animation-duration: 10s; }
}
@media (max-width: 420px) {
  .bf-fx-stage  { min-height: 3.6em; }
  .bf-fx-halo   { display: none; }
}

@media (prefers-reduced-motion: reduce) {
  .bf-fx-hook,
  .bf-fx-hook[class*="fx-"],
  .bf-fx-hook .lit,
  .bf-fx-hero-wrap.bf-fx-hero-play .bf-fx-hero-text,
  .cycle-streak.play,
  .cycle-burst.play,
  .cycle-sparkle.play,
  .bf-fx-cta-shimmer::after,
  .bf-fx-counter.bf-fx-landed::after {
    animation: none !important;
    opacity: 1 !important;
    transform: none !important;
    filter: none !important;
    clip-path: none !important;
  }
  .bf-fx-hook[data-slot="b"] { visibility: hidden; }
  .bf-fx-halo,
  .cycle-streak,
  .cycle-burst,
  .cycle-sparkle { display: none; }
  html.bf-fx-ready .bf-fx-reveal,
  html.bf-fx-ready .bf-fx-stagger > * {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
  }
}
