/* ═══════════════════════════════════════════════════════════════
   KS666 — Retro Arcade Intro (fire wipe → cast reveal → START)
   Mobile-first: base = ~375px, scale UP with min-width queries.
   Palette mirrors admin/admin.css (LOCKED — do not alter).
   Motion rules: transform/opacity only. Cast stagger is tuned so at most
   3 members (6 animations) are in flight at once — see .cast-member.
═══════════════════════════════════════════════════════════════ */

@font-face {
  font-family: 'Press Start 2P';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../fonts/PressStart2P-Regular.woff2') format('woff2');
}

/* Lock page scroll while the intro is up (removed by dismissIntro) */
body.intro-active { overflow: hidden; }

/* Form gate — .container starts hidden + inert; intro.js reveals it */
.container.form-gated { display: none; }

/* ── Overlay root ───────────────────────────────────────────── */
#introOverlay {
  --obsidian: #090909;
  --crimson:  #bf2218;
  --lava:     #e84118;
  --ember:    #ff6820;
  --gold:     #e8a020;
  --text:     #ede4dc;

  position: fixed;
  inset: 0;
  z-index: 2000; /* above the form popover (z 1000) */
  overflow: hidden;
  background:
    radial-gradient(ellipse 130% 55% at 50% 100%, rgba(120, 20, 5, 0.22) 0%, transparent 60%),
    radial-gradient(ellipse 60% 30% at 50% 100%, rgba(70, 8, 2, 0.38) 0%, transparent 45%),
    var(--obsidian);
  padding: env(safe-area-inset-top) env(safe-area-inset-right)
           env(safe-area-inset-bottom) env(safe-area-inset-left);
  font-family: 'Press Start 2P', monospace;
  user-select: none;
  -webkit-user-select: none;
  touch-action: manipulation; /* kill iOS double-tap zoom on rapid skip taps */
}

/* Fade-out on dismiss; intro.js removes the node on transitionend */
#introOverlay.is-leaving {
  opacity: 0;
  pointer-events: none;
  transition: opacity 420ms ease-out;
}

/* ── Stage 1: fire wipe ─────────────────────────────────────────
   Wrap carries the bloom-up transform so the same choreography
   drives both the Phase-1 gradient fallback and the Phase-3 clip. */
.intro-fire-wrap {
  position: absolute;
  inset: 0;
  transform: translateY(70%) scaleY(0.35);
  transform-origin: 50% 100%;
  opacity: 0;
  transition: transform 900ms cubic-bezier(0.19, 0.62, 0.28, 1),
              opacity 500ms ease-out;
  will-change: transform, opacity;
}
.is-fire .intro-fire-wrap { transform: translateY(0) scaleY(1); opacity: 1; }

/* Real fire clip (Phase 3). No src in Phase 1 → intro.js removes it. */
.intro-fire {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* Gradient fire — Phase-1 stand-in AND permanent under-layer so a
   blocked/slow video never shows black */
.intro-fire-fx {
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 140% 68% at 50% 108%, rgba(232, 65, 24, 0.50) 0%, rgba(191, 34, 24, 0.30) 38%, transparent 66%),
    radial-gradient(ellipse 85% 42% at 50% 106%, rgba(255, 104, 32, 0.75) 0%, rgba(232, 65, 24, 0.35) 45%, transparent 70%),
    radial-gradient(ellipse 46% 22% at 50% 103%, rgba(232, 160, 32, 0.85) 0%, rgba(255, 104, 32, 0.40) 55%, transparent 75%);
}
.intro-fire-fx::after {
  content: '';
  position: absolute;
  inset: 0;
  background:
    radial-gradient(ellipse 60% 30% at 32% 104%, rgba(255, 104, 32, 0.5) 0%, transparent 70%),
    radial-gradient(ellipse 55% 26% at 70% 103%, rgba(232, 160, 32, 0.45) 0%, transparent 72%);
  opacity: 0.4;
}
.is-fire .intro-fire-fx::after {
  animation: fire-flicker 640ms ease-in-out infinite alternate;
}
@keyframes fire-flicker {
  from { opacity: 0.35; }
  to   { opacity: 0.85; }
}

/* ── Stage 2: cast reveal — overlapping roster lineup ───────────
   --cw sizes everything; margin-inline creates the 40% overlap.
   Depth: edges back, center front (fighting-game roster). */
.intro-cast {
  position: absolute;
  left: 0;
  right: 0;
  bottom: max(9vh, calc(env(safe-area-inset-bottom) + 6vh));
  display: flex;
  align-items: flex-end;
  justify-content: center;
  /* Purely decorative (aria-hidden). Must never intercept taps: the cast
     paints above START (flex items honour z-index at position:static), and a
     swallowed tap on START leaves the form permanently gated. */
  pointer-events: none;
}
.cast-member {
  --cw: clamp(74px, 24vw, 170px);
  display: flex;
  flex-direction: column;
  align-items: center;
  width: var(--cw);
  /* -0.26 pulls adjacent BOXES together by 0.52*--cw. The painted figures are
     narrower than their boxes (measured 55%-82% of box width across the six),
     so the real figure-to-figure overlap is ~41px of ~130px = ~31%. At the old
     -0.20 it was 18px / 14% and the lineup read as six separate cutouts.
     Re-derive this if the art's width-in-box changes — see encode_all.py. */
  margin-inline: calc(var(--cw) * -0.26);
  opacity: 0;
  transform: translateY(14vh) scale(var(--s, 1));
  transform-origin: 50% 100%;
  /* 420ms rise against a 150ms stagger => at most ceil(420/150) = 3 members
     in flight (measured: 3 members / 7 active animations). The old 620ms/120ms
     pair overlapped all six (6 members / 11 active) and cost dropped frames at
     6x CPU throttle. Last member lands at 5*150+420 = 1170ms after is-cast,
     still inside the freeze beat before is-start.
     Measuring this is a trap: getAnimations() reports playState 'running'
     during transition-delay, and CSS transitions fill BACKWARDS so
     getComputedTiming().progress is 0 (not null) in the delay phase. Both
     naive metrics report 6 members regardless of stagger. Compare localTime
     against delay/activeDuration instead. */
  transition: transform 420ms cubic-bezier(0.18, 0.7, 0.24, 1.02),
              opacity 360ms ease-out;
  transition-delay: calc(var(--i) * 150ms);
}
.is-cast .cast-member { opacity: 1; transform: translateY(0) scale(var(--s, 1)); }

.cast-member:nth-child(1), .cast-member:nth-child(6) { --s: 0.86; z-index: 1; }
.cast-member:nth-child(2), .cast-member:nth-child(5) { --s: 0.93; z-index: 2; }
.cast-member:nth-child(3), .cast-member:nth-child(4) { --s: 1;    z-index: 3; }

/* No drop-shadow filter here: 6 filtered+transforming layers is a
   mid-range-Android jank risk. Shadows get baked into Phase-3 art. */
.cast-art {
  width: 100%;
  aspect-ratio: 2 / 3;
  display: block;
}

.cast-tag {
  margin-top: 6px;
  font-size: 8px;
  letter-spacing: 1px;
  color: var(--gold);
  text-shadow: 0 0 6px rgba(232, 160, 32, 0.5);
}

/* ── Stage 3: freeze ────────────────────────────────────────────
   A held beat — no motion change. Class kept as a tuning hook. */

/* ── Stage 4: START prompt ──────────────────────────────────────
   Pixel-resolve = steps() snap from scale 2.6 → 1 (transform+opacity
   only). Blink starts at is-ready, on the inner text span. */
.intro-start {
  position: absolute;
  left: 50%;
  top: 30%;
  z-index: 10; /* above .cast-member (z 1-3) — they overlap in short landscape */
  transform: translate(-50%, -50%);
  min-width: 178px;
  min-height: 58px; /* ≥44px tap target */
  padding: 18px 26px;
  visibility: hidden;
  opacity: 0;
  pointer-events: none;
  border: 3px solid rgba(255, 104, 32, 0.85);
  border-radius: 4px;
  background: rgba(20, 8, 4, 0.35);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px); /* glassmorphism; static, never animated */
  box-shadow: 0 0 24px rgba(232, 65, 24, 0.35), inset 0 0 18px rgba(232, 65, 24, 0.18);
  color: var(--text);
  font-family: inherit;
  font-size: 16px;
  letter-spacing: 2px;
  cursor: pointer;
  touch-action: manipulation;
}
.is-start .intro-start {
  visibility: visible;
  animation: start-resolve 480ms steps(4, end) forwards;
}
.is-ready .intro-start { opacity: 1; pointer-events: auto; }
@keyframes start-resolve {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(2.6); }
  100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}

.is-ready .intro-start-text { animation: start-blink 1.06s steps(1, end) infinite; }
@keyframes start-blink {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0; }
}

.intro-start:focus-visible {
  outline: 3px solid var(--gold);
  outline-offset: 3px;
}

/* ── Skip (tap-anywhere): jump every entrance to its final state.
   Blink (.intro-start-text) intentionally survives. ───────────── */
.is-skip .intro-fire-wrap,
.is-skip .cast-member { transition: none; }
.is-skip .intro-start { animation: none; }

/* ── Reduced motion: final frame immediately, nothing animates —
   including the infinite blink and flicker ────────────────────── */
@media (prefers-reduced-motion: reduce) {
  #introOverlay .intro-fire-wrap,
  #introOverlay .cast-member { transition: none !important; }
  #introOverlay .intro-fire-fx::after,
  #introOverlay .intro-start,
  #introOverlay .intro-start-text { animation: none !important; }
  .is-start .intro-start { visibility: visible; opacity: 1; }
}

/* ── Tablet / desktop scale-up ──────────────────────────────── */
@media (min-width: 768px) {
  .cast-member {
    --cw: clamp(120px, 15vw, 190px);
    /* Keep in step with the base value — the three margin-inline declarations
       in this file must always match, or the fan tightens/loosens on rotate. */
    margin-inline: calc(var(--cw) * -0.26);
  }
  .cast-tag { font-size: 10px; margin-top: 10px; }
  .intro-start { font-size: 20px; padding: 22px 34px; }
}

/* ── Short landscape (phone rotated) ─────────────────────────────
   MUST stay last: a rotated phone matches BOTH this and min-width:768px
   (844/852/932 wide), and at equal specificity the later block wins.
   Ordered above, the desktop sizes leaked into short viewports and pushed
   the cast over the START button. */
@media (orientation: landscape) and (max-height: 520px) {
  .cast-member {
    /* Was 13vw/110px. Measured START-bottom -> cast-top clearance was only
       23.9px @844x390 and 25.5px @852x393 (187-368px everywhere else).
       Placeholder art survives that because the figure sits low in its 2:3
       box; Phase-3 art that fills the box would collide. */
    --cw: clamp(52px, 11.5vw, 100px);
    margin-inline: calc(var(--cw) * -0.26);
  }
  .intro-cast { bottom: 4vh; }
  .cast-tag { font-size: 8px; margin-top: 6px; }
  .intro-start { top: 26%; font-size: 16px; padding: 16px 24px; }
}
