// Mini games: Quiz (5s timer), Slot, Daily wheel
const { useState: useStateG, useEffect: useEffectG, useRef: useRefG } = React;
const VOLLEY_LEADERBOARD_LIMIT = 5;

function sortVolleyLeaderboard(entries) {
  return [...entries]
    .sort((a, b) => (b.score - a.score) || (b.distance - a.distance) || ((a.createdAt || 0) - (b.createdAt || 0)))
    .slice(0, VOLLEY_LEADERBOARD_LIMIT);
}

function formatMeters(n) {
  return `${Math.max(0, Math.round(n))}m`;
}

function clampVolley(value, min, max) {
  return Math.max(min, Math.min(max, value));
}

function smoothVolleyStep(t) {
  const clamped = clampVolley(t, 0, 1);
  return clamped * clamped * (3 - 2 * clamped);
}

function mixVolley(a, b, t) {
  return a + (b - a) * t;
}

function QuizGame({ onExit, onEarnCoins, addToast, canPlay, countdownLabel, onMarkPlayed }) {
  const data = window.WC_DATA;
  const [idx, setIdx] = useStateG(0);
  const [q, setQ] = useStateG(null);
  const [sel, setSel] = useStateG(null);
  const [time, setTime] = useStateG(5);
  const [score, setScore] = useStateG(0);
  const [streak, setStreak] = useStateG(0);
  const [phase, setPhase] = useStateG('playing');
  const timerRef = useRefG(null);

  useEffectG(() => {
    setQ(data.TRIVIA[Math.floor(Math.random() * data.TRIVIA.length)]);
    setSel(null);
    setTime(5);
  }, [idx]);

  useEffectG(() => {
    if (sel !== null || phase !== 'playing') return;
    if (time <= 0) {
      handleAnswer(-1);
      return;
    }
    timerRef.current = setTimeout(() => setTime(t => +(t - 0.1).toFixed(1)), 100);
    return () => clearTimeout(timerRef.current);
  }, [time, sel, phase]);

  function handleAnswer(choice) {
    if (sel !== null || !canPlay || !q) return;
    setSel(choice);
    clearTimeout(timerRef.current);
    const correct = choice === q.correct;
    const timeBonus = Math.max(0, Math.floor(time * 4));
    const earn = correct ? 20 + timeBonus : 0;
    if (correct) {
      setScore(s => s + earn);
      setStreak(s => s + 1);
    } else {
      setStreak(0);
    }

    setTimeout(() => {
      if (idx >= 4) {
        const total = score + earn;
        setPhase('done');
        onEarnCoins(total);
        onMarkPlayed && onMarkPlayed();
        addToast(`Daily Quiz complete! +${total} gold`);
      } else {
        setIdx(i => i + 1);
      }
    }, 1200);
  }

  if (!q) return null;

  if (!canPlay && phase !== 'done') {
    return (
      <div className="game-stage" style={{ textAlign: 'center' }}>
        <div className="display" style={{ fontSize: 28, letterSpacing: '0.1em', marginBottom: 8 }}>Daily Quiz Complete</div>
        <div style={{ color: 'var(--ink-dim)', marginBottom: 20 }}>
          You have already cleared today's quiz. Reset at midnight GMT.
        </div>
        <div className="coin" style={{ fontSize: 24, fontFamily: 'var(--font-display)', justifyContent: 'center', margin: '10px 0 18px' }}>
          {countdownLabel}
        </div>
        <button className="btn btn-ghost" onClick={onExit}>Back</button>
      </div>
    );
  }

  const pct = (time / 5) * 100;
  const timerColor = time > 3 ? '#10b981' : time > 1.5 ? '#fbbf24' : '#ef4444';

  if (phase === 'done') {
    return (
      <div className="game-stage" style={{ textAlign: 'center' }}>
        <div className="display" style={{ fontSize: 28, letterSpacing: '0.1em', marginBottom: 8 }}>Daily Quiz Complete</div>
        <div className="coin" style={{ fontSize: 40, fontFamily: 'var(--font-display)', justifyContent: 'center', margin: '16px 0' }}>
          {score}
        </div>
        <div style={{ color: 'var(--ink-dim)', marginBottom: 20 }}>gold earned</div>
        <div className="row-wrap" style={{ justifyContent: 'center' }}>
          <button className="btn btn-ghost" onClick={onExit}>Back</button>
          <div style={{ color: 'var(--ink-dim)', alignSelf: 'center' }}>Resets in {countdownLabel}</div>
        </div>
      </div>
    );
  }

  return (
    <div className="game-stage">
      <div className="display" style={{ fontSize: 22, letterSpacing: '0.1em', marginBottom: 14, textAlign: 'center' }}>Daily Quiz</div>
      <div className="quiz__meta">
        <span>Q {idx + 1} / 5</span>
        <span>Streak: {streak}</span>
        <span className="coin">{score}</span>
      </div>
      <div className="quiz__timer">
        <svg viewBox="0 0 100 100">
          <circle cx="50" cy="50" r="45" fill="none" stroke="rgba(255,255,255,0.08)" strokeWidth="8" />
          <circle
            cx="50"
            cy="50"
            r="45"
            fill="none"
            stroke={timerColor}
            strokeWidth="8"
            strokeDasharray={2 * Math.PI * 45}
            strokeDashoffset={2 * Math.PI * 45 * (1 - pct / 100)}
            strokeLinecap="round"
            style={{ transition: 'stroke-dashoffset 0.1s linear' }}
          />
        </svg>
        <div className="quiz__timer__count" style={{ color: timerColor }}>{Math.ceil(time)}</div>
      </div>
      <div className="quiz__q">{q.q}</div>
      <div className="quiz__opts">
        {q.a.map((ans, i) => {
          let state = null;
          if (sel !== null) {
            if (i === q.correct) state = 'correct';
            else if (i === sel) state = 'wrong';
            else state = 'dim';
          }
          return (
            <button key={i} className="quiz__opt" data-state={state} onClick={() => handleAnswer(i)} disabled={sel !== null}>
              {ans}
            </button>
          );
        })}
      </div>
      <div className="quiz__feedback">
        {sel === q.correct ? 'Correct!' : sel !== null ? (sel === -1 ? 'Too slow!' : 'Wrong') : ''}
      </div>
      <div className="row" style={{ justifyContent: 'center', marginTop: 16 }}>
        <button className="btn btn-ghost btn-sm" onClick={onExit}>Quit</button>
      </div>
    </div>
  );
}

function SlotGame({ onExit, onEarnCoins, addToast, canPlay, countdownLabel, onMarkPlayed }) {
  const data = window.WC_DATA;
  const sym = data.SLOT_SYMBOLS;
  const [reels, setReels] = useStateG([0, 0, 0]);
  const [offsets, setOffsets] = useStateG([0, 0, 0]);
  const [spinning, setSpinning] = useStateG(false);
  const [animateReels, setAnimateReels] = useStateG(false);
  const [result, setResult] = useStateG(null);

  function weightedPick() {
    const total = sym.reduce((a, s) => a + s.weight, 0);
    let r = Math.random() * total;
    for (let i = 0; i < sym.length; i++) {
      r -= sym[i].weight;
      if (r <= 0) return i;
    }
    return 0;
  }

  function spin() {
    if (spinning || !canPlay) return;
    setResult(null);
    setSpinning(true);
    const picks = [weightedPick(), weightedPick(), weightedPick()];
    const newOffsets = picks.map((p, i) => -(8 + i * 2) * sym.length * 140 - p * 140);
    setAnimateReels(false);
    setOffsets([0, 0, 0]);
    requestAnimationFrame(() => {
      requestAnimationFrame(() => {
        setAnimateReels(true);
        setOffsets(newOffsets);
      });
    });

    setTimeout(() => {
      setReels(picks);
      setSpinning(false);
      setAnimateReels(false);
      const [a, b, c] = picks;
      let payout = 0;
      let text = 'No win this time';
      if (a === b && b === c) {
        payout = sym[a].payout[3];
        text = `JACKPOT! +${payout}`;
      } else if (a === b || b === c || a === c) {
        payout = 75;
        text = `2-match! +${payout}`;
      }
      if (payout > 0) {
        onEarnCoins(payout);
        addToast(`Daily Slots paid +${payout} gold`);
      }
      setResult({ text, payout });
      onMarkPlayed && onMarkPlayed();
    }, 3100);
  }

  if (!canPlay && !result) {
    return (
      <div className="game-stage" style={{ textAlign: 'center' }}>
        <div className="display" style={{ fontSize: 28, letterSpacing: '0.1em', marginBottom: 8 }}>Daily Slots Used</div>
        <div style={{ color: 'var(--ink-dim)', marginBottom: 20 }}>
          Your free daily roll has already been used. Reset at midnight GMT.
        </div>
        <div className="coin" style={{ fontSize: 24, fontFamily: 'var(--font-display)', justifyContent: 'center', margin: '10px 0 18px' }}>
          {countdownLabel}
        </div>
        <button className="btn btn-ghost" onClick={onExit}>Back</button>
      </div>
    );
  }

  return (
    <div className="game-stage slot">
      <div className="display" style={{ fontSize: 22, letterSpacing: '0.1em', marginBottom: 14, textAlign: 'center' }}>Daily Slots</div>
      <div style={{ color: 'var(--ink-dim)', fontSize: 12, marginBottom: 16, textAlign: 'center' }}>
        {result ? `Quest complete. Resets in ${countdownLabel}.` : 'One free roll each day. No betting required.'}
      </div>
      <div className="slot__reels">
        {[0, 1, 2].map(i => (
          <div className="slot__reel" key={i}>
            <div
              className="slot__strip"
              style={{
                transform: `translateY(${offsets[i]}px)`,
                transition: animateReels ? 'transform 3s cubic-bezier(0.17, 0.67, 0.24, 1)' : 'none',
              }}
            >
              {Array.from({ length: 20 }).flatMap((_, rep) => sym.map((s, si) => (
                <div key={`${rep}-${si}`}>{s.glyph}</div>
              )))}
              <div>{sym[reels[i]].glyph}</div>
            </div>
          </div>
        ))}
      </div>
      <div className="slot__lever">
        <div className="slot__bet">Daily roll: <b>FREE</b></div>
        <button className="btn btn-gold btn-lg" onClick={spin} disabled={spinning || !canPlay || !!result}>
          {spinning ? 'Spinning...' : result ? 'ROLLED TODAY' : 'ROLL'}
        </button>
      </div>
      <div className={`slot__result ${result?.payout > 0 ? 'is-win' : ''}`}>{result?.text || ''}</div>
      <div className="row" style={{ justifyContent: 'center', marginTop: 16, gap: 16, fontSize: 11, color: 'var(--ink-dim)' }}>
        <span>3x Ball = 50</span>
        <span>3x Boot = 80</span>
        <span>3x Trophy = 200</span>
        <span>3x Star = 300</span>
        <span style={{ color: 'var(--gold)' }}>3x Seven = 777</span>
      </div>
      <div className="row" style={{ justifyContent: 'center', marginTop: 20 }}>
        <button className="btn btn-ghost btn-sm" onClick={onExit}>Exit</button>
      </div>
    </div>
  );
}

function WheelGame({ onExit, onEarnCoins, onRewardPack, addToast, canPlay, countdownLabel, onMarkPlayed }) {
  const data = window.WC_DATA;
  const segs = data.WHEEL_SEGMENTS;
  const [rotation, setRotation] = useStateG(0);
  const [spinning, setSpinning] = useStateG(false);
  const [result, setResult] = useStateG(null);
  const settleTimerRef = useRefG(null);

  const total = segs.reduce((a, s) => a + s.weight, 0);
  const segAngle = 360 / segs.length;

  useEffectG(() => () => {
    if (settleTimerRef.current) clearTimeout(settleTimerRef.current);
  }, []);

  function pickIdx() {
    let r = Math.random() * total;
    for (let i = 0; i < segs.length; i++) {
      r -= segs[i].weight;
      if (r <= 0) return i;
    }
    return 0;
  }

  function spin() {
    if (spinning || !canPlay) return;
    setResult(null);
    const winIdx = pickIdx();
    const targetAngle = winIdx * segAngle + segAngle / 2;
    const currentAngle = ((rotation % 360) + 360) % 360;
    const deltaToTarget = (360 - ((currentAngle + targetAngle) % 360)) % 360;
    const newRot = rotation + 360 * 5 + deltaToTarget;
    setRotation(newRot);
    setSpinning(true);

    if (settleTimerRef.current) clearTimeout(settleTimerRef.current);
    settleTimerRef.current = setTimeout(() => {
      const seg = segs[winIdx];
      if (seg.rewardType === 'pack') {
        const pack = data.PACKS.find(p => p.id === seg.packId);
        if (pack) {
          onRewardPack && onRewardPack(pack);
          addToast(`Daily Spin won a ${pack.name}!`);
          setResult({ text: `${pack.name} won!`, isPack: true });
        } else {
          setResult({ text: 'Prize unavailable', isPack: false });
        }
      } else {
        const payout = seg.coins || 0;
        onEarnCoins(payout);
        addToast(`Daily Spin paid +${payout} gold`);
        setResult({ text: `+${payout} gold!`, isPack: false });
      }
      setSpinning(false);
      onMarkPlayed && onMarkPlayed();
      settleTimerRef.current = null;
    }, 4600);
  }

  return (
    <div className="game-stage" style={{ textAlign: 'center' }}>
      <div className="display" style={{ fontSize: 22, letterSpacing: '0.1em', marginBottom: 8 }}>Daily Spin</div>
      <div style={{ color: 'var(--ink-dim)', fontSize: 12, marginBottom: 16 }}>
        {canPlay ? 'One spin each day for gold or a pack.' : `Already used today. Resets in ${countdownLabel}.`}
      </div>
      <div className="wheel-wrap">
        <div className="wheel__pointer" />
        <div className="wheel" style={{ transform: `rotate(${rotation}deg)` }}>
          <svg viewBox="0 0 200 200" style={{ width: '100%', height: '100%' }}>
            {segs.map((s, i) => {
              const start = i * segAngle;
              const end = (i + 1) * segAngle;
              const sRad = (start - 90) * Math.PI / 180;
              const eRad = (end - 90) * Math.PI / 180;
              const x1 = 100 + 100 * Math.cos(sRad);
              const y1 = 100 + 100 * Math.sin(sRad);
              const x2 = 100 + 100 * Math.cos(eRad);
              const y2 = 100 + 100 * Math.sin(eRad);
              const large = segAngle > 180 ? 1 : 0;
              const midRad = ((start + end) / 2 - 90) * Math.PI / 180;
              const tx = 100 + 65 * Math.cos(midRad);
              const ty = 100 + 65 * Math.sin(midRad);
              const label = s.label;
              return (
                <g key={i}>
                  <path d={`M 100 100 L ${x1} ${y1} A 100 100 0 ${large} 1 ${x2} ${y2} Z`} fill={s.color} stroke="rgba(0,0,0,0.3)" strokeWidth="1" />
                  <text x={tx} y={ty} textAnchor="middle" fontFamily="var(--font-display)" fontSize="11" fill="#1a0d00" fontWeight="700"
                    transform={`rotate(${(start + end) / 2}, ${tx}, ${ty})`}>{label}</text>
                </g>
              );
            })}
          </svg>
        </div>
        <div className="wheel__hub">SPIN</div>
      </div>
      <button className="btn btn-gold btn-lg" onClick={spin} disabled={spinning || !canPlay || !!result}>
        {spinning ? 'Spinning...' : canPlay && !result ? 'SPIN THE WHEEL' : countdownLabel.toUpperCase()}
      </button>
      {result && (
        <div style={{ marginTop: 14, fontFamily: 'var(--font-display)', fontSize: 20, color: 'var(--gold)' }}>
          {result.text}
        </div>
      )}
      <div className="row" style={{ justifyContent: 'center', marginTop: 20 }}>
        <button className="btn btn-ghost btn-sm" onClick={onExit}>Back</button>
      </div>
    </div>
  );
}

function QuizStreakGame({ onExit, addToast, leaderboard, onSubmitScore }) {
  const data = window.WC_DATA;
  const [round, setRound] = useStateG(0);
  const [q, setQ] = useStateG(null);
  const [sel, setSel] = useStateG(null);
  const [time, setTime] = useStateG(5);
  const [score, setScore] = useStateG(0);
  const [phase, setPhase] = useStateG('playing');
  const [saveMessage, setSaveMessage] = useStateG('5 seconds per question. One mistake ends the run.');
  const timerRef = useRefG(null);
  const sortedLeaderboard = sortVolleyLeaderboard(leaderboard || []);

  useEffectG(() => {
    if (phase !== 'playing') return;
    setQ(data.TRIVIA[Math.floor(Math.random() * data.TRIVIA.length)]);
    setSel(null);
    setTime(5);
  }, [round, phase]);

  useEffectG(() => {
    if (sel !== null || phase !== 'playing') return;
    if (time <= 0) {
      handleAnswer(-1);
      return;
    }
    timerRef.current = setTimeout(() => setTime(t => +(t - 0.1).toFixed(1)), 100);
    return () => clearTimeout(timerRef.current);
  }, [time, sel, phase]);

  function finishRun(finalScore) {
    setPhase('done');
    if (!finalScore) {
      setSaveMessage('No score saved this time. Get one right to post a streak.');
      return;
    }
    setSaveMessage('Checking leaderboard...');
    Promise.resolve(onSubmitScore && onSubmitScore({ score: finalScore })).then(result => {
      if (!result) {
        setSaveMessage('Run complete.');
        return;
      }
      if (result.saved) {
        setSaveMessage(result.scope === 'global'
          ? 'New best saved to the global leaderboard.'
          : 'New best saved locally. Sign in to publish globally.');
        addToast(`Quiz Streak best updated: ${finalScore}`);
      } else {
        setSaveMessage(`Best on file: ${result.best}. Beat it to update the leaderboard.`);
      }
    });
  }

  function handleAnswer(choice) {
    if (sel !== null || phase !== 'playing' || !q) return;
    setSel(choice);
    clearTimeout(timerRef.current);
    const correct = choice === q.correct;
    const nextScore = correct ? score + 1 : score;
    setTimeout(() => {
      if (correct) {
        setScore(nextScore);
        setRound(r => r + 1);
      } else {
        finishRun(nextScore);
      }
    }, 900);
  }

  function restart() {
    clearTimeout(timerRef.current);
    setRound(0);
    setQ(null);
    setSel(null);
    setTime(5);
    setScore(0);
    setPhase('playing');
    setSaveMessage('5 seconds per question. One mistake ends the run.');
  }

  if (!q && phase === 'playing') return null;

  const pct = (time / 5) * 100;
  const timerColor = time > 3 ? '#10b981' : time > 1.5 ? '#fbbf24' : '#ef4444';

  return (
    <div className="game-stage">
      <div className="display" style={{ fontSize: 22, letterSpacing: '0.1em', marginBottom: 14, textAlign: 'center' }}>Quiz Streak</div>
      {phase === 'playing' ? (
        <>
          <div className="quiz__meta">
            <span>Current streak: {score}</span>
            <span>World best: {sortedLeaderboard[0]?.score ?? 0}</span>
            <span>Sudden death</span>
          </div>
          <div className="quiz__timer">
            <svg viewBox="0 0 100 100">
              <circle cx="50" cy="50" r="45" fill="none" stroke="rgba(255,255,255,0.08)" strokeWidth="8" />
              <circle
                cx="50"
                cy="50"
                r="45"
                fill="none"
                stroke={timerColor}
                strokeWidth="8"
                strokeDasharray={2 * Math.PI * 45}
                strokeDashoffset={2 * Math.PI * 45 * (1 - pct / 100)}
                strokeLinecap="round"
                style={{ transition: 'stroke-dashoffset 0.1s linear' }}
              />
            </svg>
            <div className="quiz__timer__count" style={{ color: timerColor }}>{Math.ceil(time)}</div>
          </div>
          <div className="quiz__q">{q.q}</div>
          <div className="quiz__opts">
            {q.a.map((ans, i) => {
              let state = null;
              if (sel !== null) {
                if (i === q.correct) state = 'correct';
                else if (i === sel) state = 'wrong';
                else state = 'dim';
              }
              return (
                <button key={i} className="quiz__opt" data-state={state} onClick={() => handleAnswer(i)} disabled={sel !== null}>
                  {ans}
                </button>
              );
            })}
          </div>
          <div className="quiz__feedback">
            {sel === q.correct ? 'Correct! Keep it alive.' : sel !== null ? (sel === -1 ? 'Time up! Run over.' : 'Wrong! Run over.') : ''}
          </div>
          <div className="row" style={{ justifyContent: 'center', marginTop: 16 }}>
            <button className="btn btn-ghost btn-sm" onClick={onExit}>Quit</button>
          </div>
        </>
      ) : (
        <div style={{ textAlign: 'center', marginBottom: 20 }}>
          <div className="display" style={{ fontSize: 28, letterSpacing: '0.1em', marginBottom: 8 }}>Full Time</div>
          <div className="coin" style={{ fontSize: 40, fontFamily: 'var(--font-display)', justifyContent: 'center', margin: '16px 0' }}>
            {score}
          </div>
          <div style={{ color: 'var(--ink-dim)', marginBottom: 12 }}>final streak</div>
          <div className="golden-volley__leaderboard-entry" style={{ marginBottom: 20 }}>{saveMessage}</div>
          <div className="row-wrap" style={{ justifyContent: 'center' }}>
            <button className="btn btn-primary" onClick={restart}>Play Again</button>
            <button className="btn btn-ghost" onClick={onExit}>Back</button>
          </div>
        </div>
      )}

      <div className="golden-volley__leaderboard" style={{ marginTop: 20 }}>
        <div className="display golden-volley__board-title">Leaderboard</div>
        {sortedLeaderboard.length ? (
          <div className="golden-volley__board-list">
            {sortedLeaderboard.map((entry, idx) => (
              <div key={`${entry.name}-${entry.score}-${idx}`} className="golden-volley__board-row">
                <span className="golden-volley__board-rank">#{idx + 1}</span>
                <span className="golden-volley__board-name">{entry.name}</span>
                <span className="golden-volley__board-distance">Streak</span>
                <b className="golden-volley__board-score">{entry.score}</b>
              </div>
            ))}
          </div>
        ) : (
          <div className="golden-volley__board-empty">No scores on the board yet. Set the opening record.</div>
        )}
      </div>
    </div>
  );
}
function BouncemastersGame({ onExit, addToast, leaderboard, onSubmitScore }) {
  const KICK_DURATION = 0.44;
  const VOLLEY_GROUND_Y = 290;
  const VOLLEY_START_Y = VOLLEY_GROUND_Y - 16;
  const VOLLEY_BALL_RADIUS = 17;
  const VOLLEY_ENEMY_FLOOR_OFFSET = 40;
  const VOLLEY_ENEMY_VISUAL_FLOOR_Y = 352;
  const VOLLEY_POWERUP_RADIUS = 18;
  const rafRef = useRefG(0);
  const lastTsRef = useRefG(0);
  const meterDirRef = useRefG(1);
  const simRef = useRefG(null);
  const [phase, setPhase] = useStateG('aiming');
  const [power, setPower] = useStateG(44);
  const [feedback, setFeedback] = useStateG('Set the strike power, launch off his boot, then time each running volley with the swing.');
  const [saveMessage, setSaveMessage] = useStateG('');
  const [runResult, setRunResult] = useStateG(null);
  const [renderState, setRenderState] = useStateG({
    playerScreenX: 56,
    bodyLean: -8,
    frontLeg: -16,
    backLeg: 16,
    frontArm: 10,
    backArm: -16,
    runBob: 0,
    isKicking: false,
    screenX: 96,
    screenY: VOLLEY_START_Y,
    distance: 0,
    combo: 0,
    bestCombo: 0,
    bounces: 0,
    camera: 0,
    velocity: 0,
    obstacles: [],
    powerups: [],
    ballCharged: false,
    impactAge: -1,
    impactX: 0,
    impactY: 0,
  });

  const sortedLeaderboard = sortVolleyLeaderboard(leaderboard || []);

  function getKickState(sim) {
    if (!sim || sim.kickAnim < 0) return null;
    const kickProgress = Math.min(1, sim.kickAnim / KICK_DURATION);
    const windup = Math.min(1, kickProgress / 0.42);
    const strike = Math.max(0, (kickProgress - 0.42) / 0.58);
    const legRootX = sim.playerX + (kickProgress < 0.42 ? 42 - windup * 10 : 38 - strike * 18);
    const legRootY = sim.ground - (kickProgress < 0.42 ? 60 + windup * 4 : 64 - strike * 4);
    const footX = sim.playerX + (kickProgress < 0.42 ? 52 - windup * 28 : 24 + strike * 86);
    const footY = sim.ground - (kickProgress < 0.42
      ? 16 + windup * 14
      : 24 + Math.sin(strike * Math.PI) * 72 + strike * 10);
    return { kickProgress, windup, strike, legRootX, legRootY, footX, footY };
  }

  function getSweepPoint(px, py, x1, y1, x2, y2) {
    const segX = x2 - x1;
    const segY = y2 - y1;
    const segLenSq = segX * segX + segY * segY;
    const t = segLenSq > 0
      ? Math.max(0, Math.min(1, ((px - x1) * segX + (py - y1) * segY) / segLenSq))
      : 1;
    const hitX = x1 + segX * t;
    const hitY = y1 + segY * t;
    return {
      t,
      x: hitX,
      y: hitY,
      distance: Math.hypot(px - hitX, py - hitY),
    };
  }

  function buildObstacle(sim) {
    const type = Math.random() < 0.52 ? 'slide' : 'header';
    const gap = 420 + Math.random() * 260;
    const width = type === 'slide' ? 132 : 118;
    const height = type === 'slide' ? 156 : 154;

    sim.nextObstacleX += gap;
    return {
      id: sim.nextObstacleId++,
      x: sim.nextObstacleX,
      y: sim.ground - height,
      width,
      height,
      type,
      hitAt: -1,
    };
  }

  function buildPowerup(sim) {
    const gap = 360 + Math.random() * 260;
    sim.nextPowerupX += gap;
    return {
      id: sim.nextPowerupId++,
      x: sim.nextPowerupX,
      y: sim.ground - (124 + Math.random() * 118),
      radius: VOLLEY_POWERUP_RADIUS,
      collectedAt: -1,
    };
  }

  function getObstacleTargetWorldY(obstacle, localY) {
    return VOLLEY_ENEMY_VISUAL_FLOOR_Y - obstacle.height + localY;
  }

  function getObstacleMotion(obstacle, sim) {
    const distanceToBall = obstacle.x - sim.x;
    const runClock = sim.t * (7.2 + Math.min(2.6, sim.playerSpeed / 160)) + obstacle.id * 0.85;
    const stride = Math.sin(runClock);
    const bob = Math.abs(Math.sin(runClock));

    let motion = {
      phase: 'run',
      attack: 0,
      attackPhase: 0,
      bodyLean: 10,
      frontLeg: 14 + stride * 26,
      backLeg: 18 - stride * 24,
      frontArm: -14 - stride * 18,
      backArm: 16 + stride * 18,
      runBob: bob * 3.4,
      coreShiftX: 0,
      coreShiftY: 0,
      rootX: 46,
      rootY: 90,
      headX: 34,
      headY: 10,
      armBackX: 22,
      armBackY: 40,
      armFrontX: 62,
      armFrontY: 40,
      legBackX: 37,
      legBackY: 82,
      legFrontX: 52,
      legFrontY: 82,
      legBackLength: 58,
      legFrontLength: 58,
      target: obstacle.type === 'slide'
        ? {
            kind: 'boot',
            x: obstacle.x + 72,
            localX: 72,
            localY: obstacle.height - 40,
            y: getObstacleTargetWorldY(obstacle, obstacle.height - 40),
            radius: 12,
            active: false,
          }
        : {
            kind: 'head',
            x: obstacle.x + 52,
            localX: 52,
            localY: obstacle.height - 126,
            y: getObstacleTargetWorldY(obstacle, obstacle.height - 126),
            radius: 13,
            active: false,
          },
    };

    if (obstacle.type === 'slide') {
      const attackIn = smoothVolleyStep((220 - distanceToBall) / 130);
      const attackOut = smoothVolleyStep((70 - distanceToBall) / 160);
      const attack = Math.max(0, attackIn - attackOut);
      const attackPhase = clampVolley((220 - distanceToBall) / 290, 0, 1);
      const slideLift = Math.sin(attackPhase * Math.PI);

      motion = {
        ...motion,
        phase: attack > 0.08 ? (distanceToBall < 28 ? 'recover' : 'attack') : 'run',
        attack,
        attackPhase,
        bodyLean: mixVolley(motion.bodyLean, 54 + slideLift * 12, attack),
        frontLeg: mixVolley(motion.frontLeg, 114 - attackPhase * 18, attack),
        backLeg: mixVolley(motion.backLeg, 34 + attackPhase * 22, attack),
        frontArm: mixVolley(motion.frontArm, 96 - attackPhase * 12, attack),
        backArm: mixVolley(motion.backArm, 38 + attackPhase * 16, attack),
        runBob: mixVolley(motion.runBob, 0.8 + slideLift * 1.2, attack),
        coreShiftX: mixVolley(0, -4 - slideLift * 10, attack),
        coreShiftY: mixVolley(0, 10 + slideLift * 18, attack),
        rootX: mixVolley(46, 44, attack),
        rootY: mixVolley(90, 104, attack),
        headX: mixVolley(34, 14 - attackPhase * 2, attack),
        headY: mixVolley(10, 56 - slideLift * 4, attack),
        armBackX: mixVolley(22, 24, attack),
        armBackY: mixVolley(40, 58, attack),
        armFrontX: mixVolley(62, 46 + attackPhase * 6, attack),
        armFrontY: mixVolley(40, 64, attack),
        legBackX: mixVolley(37, 58, attack),
        legBackY: mixVolley(82, 64, attack),
        legFrontX: mixVolley(52, 84, attack),
        legFrontY: mixVolley(82, 62, attack),
        legBackLength: mixVolley(58, 38, attack),
        legFrontLength: mixVolley(58, 42, attack),
      };

      const localX = mixVolley(70, 98 + attackPhase * 4, attack);
      const localY = obstacle.height - mixVolley(40, 16 + slideLift * 4, attack);
      motion.target = {
          kind: 'boot',
          x: obstacle.x + localX,
          y: getObstacleTargetWorldY(obstacle, localY),
          localX,
          localY,
          radius: mixVolley(12, 17, attack),
          active: attack > 0.18,
        };
    } else {
      const attackIn = smoothVolleyStep((280 - distanceToBall) / 140);
      const attackOut = smoothVolleyStep((88 - distanceToBall) / 175);
      const attack = Math.max(0, attackIn - attackOut);
      const attackPhase = clampVolley((280 - distanceToBall) / 315, 0, 1);
      const diveLift = Math.sin(attackPhase * Math.PI);

      motion = {
        ...motion,
        phase: attack > 0.08 ? (distanceToBall < 46 ? 'recover' : 'attack') : 'run',
        attack,
        attackPhase,
        bodyLean: mixVolley(motion.bodyLean, -28 - diveLift * 12, attack),
        frontLeg: mixVolley(motion.frontLeg, -18 + attackPhase * 10, attack),
        backLeg: mixVolley(motion.backLeg, 26 + attackPhase * 8, attack),
        frontArm: mixVolley(motion.frontArm, -124 + attackPhase * 12, attack),
        backArm: mixVolley(motion.backArm, -78 - attackPhase * 10, attack),
        runBob: mixVolley(motion.runBob, 1.2 + diveLift * 1.6, attack),
        coreShiftX: mixVolley(0, 8 + attackPhase * 14, attack),
        coreShiftY: mixVolley(0, -8 - diveLift * 44, attack),
        rootX: mixVolley(46, 50, attack),
        rootY: mixVolley(90, 94, attack),
        headX: mixVolley(34, 50 + attackPhase * 6, attack),
        headY: mixVolley(10, 18 - diveLift * 4, attack),
        armBackX: mixVolley(22, 20, attack),
        armBackY: mixVolley(40, 52, attack),
        armFrontX: mixVolley(62, 62, attack),
        armFrontY: mixVolley(40, 38, attack),
        legBackX: mixVolley(37, 36, attack),
        legBackY: mixVolley(82, 82, attack),
        legFrontX: mixVolley(52, 60, attack),
        legFrontY: mixVolley(82, 78, attack),
        legBackLength: mixVolley(58, 42, attack),
        legFrontLength: mixVolley(58, 44, attack),
      };

      const localX = mixVolley(48, 58 + attackPhase * 8, attack);
      const localY = obstacle.height - mixVolley(126, 162 + diveLift * 22, attack);
      motion.target = {
          kind: 'head',
          x: obstacle.x + localX,
          y: getObstacleTargetWorldY(obstacle, localY),
          localX,
          localY,
          radius: mixVolley(13, 18, attack),
          active: attack > 0.16,
        };
    }

    return motion;
  }

  function getCircleCircleHit(cx, cy, radius, tx, ty, tr) {
    let dx = cx - tx;
    let dy = cy - ty;
    let distance = Math.hypot(dx, dy);
    const totalRadius = radius + tr;

    if (distance < 0.001) {
      dx = 1;
      dy = -0.2;
      distance = 1;
    }

    if (distance >= totalRadius) return null;

    return {
      distance,
      normalX: dx / distance,
      normalY: dy / distance,
      penetration: totalRadius - distance,
      contactX: tx,
      contactY: ty,
    };
  }

  function getMovingCircleHit(x1, y1, x2, y2, radius, tx, ty, tr, padding = 0) {
    const sweep = getSweepPoint(tx, ty, x1, y1, x2, y2);
    if (sweep.distance >= radius + tr + padding) return null;
    const hit = getCircleCircleHit(sweep.x, sweep.y, radius, tx, ty, tr + padding);
    return hit ? { ...hit, sweepX: sweep.x, sweepY: sweep.y } : null;
  }

  function buildPose(sim, aiming = false) {
    if (!sim) {
      return {
        playerScreenX: 56,
        bodyLean: -8,
        frontLeg: -16,
        backLeg: 16,
        frontArm: 10,
        backArm: -16,
        runBob: 0,
        isKicking: false,
      };
    }

    const kickState = getKickState(sim);
    const kickProgress = kickState?.kickProgress ?? -1;
    const stride = sim.launched ? Math.sin(sim.runCycle) : 0;
    let frontLeg = 10 + stride * 26;
    let backLeg = 18 - stride * 24;
    let frontArm = -12 - stride * 18;
    let backArm = 14 + stride * 18;
    let bodyLean = sim.launched ? Math.min(15, sim.playerSpeed / 95) : -6;
    let runBob = sim.launched ? Math.abs(Math.sin(sim.runCycle)) * 4 : 0;

    if (aiming) {
      frontLeg = -20;
      backLeg = 18;
      frontArm = 14;
      backArm = -20;
      bodyLean = -10;
      runBob = 0;
    }

    if (kickProgress >= 0) {
      const { windup, strike } = kickState;
      // Negative rotation swings the hanging leg forward/right on screen.
      frontLeg = strike > 0 ? -(34 + strike * 146) : 12 + windup * 48;
      backLeg = 12 + windup * 12 - strike * 34;
      frontArm = 34 - kickProgress * 60;
      backArm = -34 + kickProgress * 52;
      bodyLean = -(18 + windup * 10 + strike * 18);
      runBob = 0;
    }

    return {
      playerScreenX: sim.playerX - sim.camera + 54,
      bodyLean,
      frontLeg,
      backLeg,
      frontArm,
      backArm,
      runBob,
      isKicking: kickProgress >= 0,
    };
  }

  function resetRenderState() {
    setRenderState({
      ...buildPose(null, true),
      screenX: 96,
      screenY: VOLLEY_START_Y,
      distance: 0,
      combo: 0,
      bestCombo: 0,
      bounces: 0,
      camera: 0,
      velocity: 0,
      obstacles: [],
      powerups: [],
      ballCharged: false,
      impactAge: -1,
      impactX: 0,
      impactY: 0,
    });
  }

  function triggerKick() {
    const sim = simRef.current;
    if (!sim || phase !== 'flying') return;
    if (sim.kickAnim >= 0) return;
    sim.kickAnim = 0;
    sim.kickResolved = false;
    sim.kickMode = 'volley';
    sim.lastFootX = null;
    sim.lastFootY = null;
    sim.lastKickStartedAt = sim.t;
    setFeedback('Kick swinging. Catch the descending ball on the boot.');
  }

  function handleArenaTap() {
    if (phase === 'aiming') {
      startRun();
      return;
    }
    if (phase === 'flying') {
      triggerKick();
    }
  }

  useEffectG(() => {
    if (phase !== 'aiming') return undefined;
    const id = setInterval(() => {
      setPower(prev => {
        let next = prev + meterDirRef.current * 3;
        if (next >= 100) {
          next = 100;
          meterDirRef.current = -1;
        } else if (next <= 24) {
          next = 24;
          meterDirRef.current = 1;
        }
        return next;
      });
    }, 30);
    return () => clearInterval(id);
  }, [phase]);

  useEffectG(() => {
    if (phase !== 'flying') return undefined;

    function frame(ts) {
      if (!lastTsRef.current) lastTsRef.current = ts;
      const dt = Math.min(0.03, (ts - lastTsRef.current) / 1000 || 0.016);
      lastTsRef.current = ts;
      const sim = simRef.current;
      if (!sim) return;

      sim.t += dt;

      if (sim.kickAnim >= 0) {
        sim.kickAnim += dt;
      }

      if (sim.impactAge >= 0) {
        sim.impactAge += dt;
        if (sim.impactAge > 0.28) sim.impactAge = -1;
      }

      if (sim.powerSprintTimer > 0) {
        sim.powerSprintTimer = Math.max(0, sim.powerSprintTimer - dt);
      }

      const prevBallX = sim.x;
      const prevBallY = sim.y;

      if (sim.launched) {
        sim.x += sim.vx * dt;
        sim.y += sim.vy * dt;
        sim.vy += sim.gravity * dt;
        sim.vx *= Math.pow(0.992, dt * 60);

        const sprintingAfterPowerShot = sim.powerSprintTimer > 0;
        const chaseTarget = Math.max(0, sim.x - (sprintingAfterPowerShot ? 30 : 58));
        const chaseDelta = chaseTarget - sim.playerX;
        const chaseSpeed = 340
          + Math.min(sprintingAfterPowerShot ? 860 : 420, sim.vx * (sprintingAfterPowerShot ? 0.92 : 0.5))
          + sim.combo * 28
          + (sprintingAfterPowerShot ? 460 : 0);
        const chaseStep = Math.sign(chaseDelta) * Math.min(Math.abs(chaseDelta), chaseSpeed * dt);
        sim.playerX += chaseStep;
        sim.playerSpeed = Math.abs(chaseStep) / Math.max(dt, 0.001);
        sim.runCycle += dt * (7 + Math.min(4, sim.playerSpeed / 110));

        while (sim.nextObstacleX < sim.x + 1800) {
          sim.obstacles.push(buildObstacle(sim));
        }
        while (sim.nextPowerupX < sim.x + 1500) {
          sim.powerups.push(buildPowerup(sim));
        }
        sim.obstacles = sim.obstacles.filter(obstacle => obstacle.x + obstacle.width > sim.camera - 220);
        sim.powerups = sim.powerups.filter(powerup => powerup.collectedAt >= 0 || powerup.x + powerup.radius > sim.camera - 160);
      }

      const kickState = getKickState(sim);
      let footVelX = 0;
      let footVelY = 0;
      let footSweep = null;

      if (kickState) {
        const prevFootX = sim.lastFootX ?? kickState.footX;
        const prevFootY = sim.lastFootY ?? kickState.footY;
        footVelX = (kickState.footX - prevFootX) / Math.max(dt, 0.001);
        footVelY = (kickState.footY - prevFootY) / Math.max(dt, 0.001);
        footSweep = getSweepPoint(sim.x, sim.y, prevFootX, prevFootY, kickState.footX, kickState.footY);
        sim.lastFootX = kickState.footX;
        sim.lastFootY = kickState.footY;
      } else {
        sim.lastFootX = null;
        sim.lastFootY = null;
      }

      if (!sim.launched && sim.kickMode === 'launch' && kickState && kickState.strike >= 0.08) {
        sim.launched = true;
        sim.kickResolved = true;
        sim.vx = 520 + power * 5.8 + Math.max(0, footVelX) * 0.16;
        sim.vy = -(300 + power * 4.6 + Math.max(0, -footVelY) * 0.12);
        sim.impactAge = 0;
        sim.impactX = kickState.footX;
        sim.impactY = kickState.footY;
        setFeedback('He is off after it. Kick again as the dropping ball reaches his boot.');
      }

      const powerupHit = sim.powerups.find(powerup => (
        powerup.collectedAt < 0 &&
        getMovingCircleHit(prevBallX, prevBallY, sim.x, sim.y, VOLLEY_BALL_RADIUS, powerup.x, powerup.y, powerup.radius, 4)
      ));

      if (powerupHit) {
        powerupHit.collectedAt = sim.t;
        sim.powerShotReady = true;
        sim.impactAge = 0;
        sim.impactX = powerupHit.x;
        sim.impactY = powerupHit.y;
        setFeedback('Power star collected. The next kick is charged for a 5x golden blast.');
      }

      if (kickState && sim.kickMode === 'volley' && !sim.kickResolved && sim.launched) {
        const legContact = getSweepPoint(sim.x, sim.y, kickState.legRootX, kickState.legRootY, kickState.footX, kickState.footY);
        const directFootDx = sim.x - kickState.footX;
        const directFootDy = sim.y - kickState.footY;
        const footDistance = footSweep?.distance ?? Math.hypot(directFootDx, directFootDy);
        const nearFoot = footDistance < 24;
        const onLeg = legContact.distance < 28;
        const contactWindow = kickState.strike > 0.02 && kickState.strike < 0.96 && footVelX > 50;
        const hasOverlap = sim.y >= sim.ground - 126 && sim.y <= sim.ground + 14 && (nearFoot || onLeg);

        if (hasOverlap) {
          const contactT = Math.max(0.24, Math.min(1, legContact.t));
          const footWins = footDistance <= legContact.distance + 2;
          const contactX = footWins ? kickState.footX : legContact.x;
          const contactY = footWins ? kickState.footY : legContact.y;
          const rawNormalX = sim.x - contactX;
          const rawNormalY = sim.y - contactY;
          const overlapRadius = footWins ? 24 : 28;
          const overlapDistance = footWins ? footDistance : legContact.distance;
          const overlapDepth = Math.max(0, overlapRadius - overlapDistance);
          let normalX = rawNormalX;
          let normalY = rawNormalY;

          if (Math.hypot(normalX, normalY) < 0.001) {
            normalX = 0.42 + Math.max(0, footVelX) / 1400;
            normalY = sim.y > sim.ground - 34 ? -0.95 : -0.48;
          }

          if (sim.y > sim.ground - 34) {
            normalY = Math.min(normalY, -0.42);
          }

          const normalLen = Math.max(0.001, Math.hypot(normalX, normalY));
          normalX /= normalLen;
          normalY /= normalLen;

          // Always separate the ball away from the striking leg so the boot never visually clips through it.
          if (overlapDepth > 0) {
            sim.x = contactX + normalX * (overlapRadius + 1.5);
            sim.y = Math.min(sim.ground - 17, contactY + normalY * (overlapRadius + 1.5));
          }

          const timingQuality = contactWindow ? 1 : 0.58;
          const bootQuality = Math.max(0, (contactT - 0.78) / 0.22) * timingQuality;
          const undercut = Math.max(-1.1, Math.min(1.4, (contactY - sim.y) / 26));
          const sweetSpot = Math.max(0, 1 - Math.abs(kickState.strike - 0.54) / 0.54);
          const forwardBias = Math.max(-0.45, Math.min(1.2, (sim.x - contactX) / 24));
          const rawDirX =
            0.34 +
            contactT * 0.52 +
            bootQuality * 0.28 +
            kickState.strike * 0.24 +
            forwardBias * 0.18 +
            Math.max(0, footVelX) / 6200;
          const rawDirY =
            -0.72 -
            undercut * 0.7 -
            Math.max(0, -footVelY) / 1500 +
            (0.5 - kickState.strike) * 0.14 -
            bootQuality * 0.1 -
            (contactWindow ? 0 : 0.08);
          const dirLen = Math.max(0.001, Math.hypot(rawDirX, rawDirY));
          const dirX = rawDirX / dirLen;
          const dirY = rawDirY / dirLen;
          const baseShotSpeed = Math.max(430, Math.min(1520,
            Math.hypot(sim.vx, sim.vy) * (contactWindow ? 0.22 : 0.16) +
            Math.max(0, footVelX) * ((contactWindow ? 0.48 : 0.22) + bootQuality * 0.34) +
            Math.max(0, -footVelY) * 0.12 +
            (contactWindow ? 210 : 150) +
            power * (contactWindow ? 2.8 : 1.8) +
            sim.combo * 28 +
            sweetSpot * 90 +
            contactT * 140 +
            bootQuality * 120
          ));
          const powerMultiplier = sim.powerShotReady ? 5 : 1;
          const shotSpeed = Math.max(430, Math.min(sim.powerShotReady ? 5200 : 1520, baseShotSpeed * powerMultiplier));

          sim.kickResolved = true;
          sim.combo += 1;
          sim.touches += 1;
          sim.bestCombo = Math.max(sim.bestCombo, sim.combo);
          sim.vx = Math.max(250, Math.min(1480, dirX * shotSpeed));
          sim.vy = Math.max(-920, Math.min(-190, dirY * shotSpeed));
          sim.x = contactX + dirX * 20;
          sim.y = Math.min(sim.ground - 20, contactY + dirY * 20);
          sim.impactAge = 0;
          sim.impactX = contactX;
          sim.impactY = contactY;
          const powerShotUsed = sim.powerShotReady;
          sim.powerShotReady = false;
          if (powerShotUsed) {
            sim.powerSprintTimer = 1.9;
          }

          const shotCall = contactWindow
            ? (bootQuality > 0.72 ? 'Sweet boot volley' : contactT > 0.58 ? 'Shin volley' : 'Scrappy save')
            : 'Rescue touch';
          setFeedback(powerShotUsed
            ? `Golden power shot x${sim.combo}. 5x force fired off the boot.`
            : `${shotCall} x${sim.combo}. Strike phase ${Math.round(kickState.strike * 100)}%, boot quality ${Math.round((0.35 + bootQuality * 0.65) * 100)}%.`);
        }
      }

      if (sim.kickAnim >= 0 && sim.kickAnim > KICK_DURATION) {
        if (sim.kickMode === 'volley' && !sim.kickResolved) {
          setFeedback('Whiff. Let him close the gap, then swing as the ball drops onto the boot.');
        }
        sim.kickAnim = -1;
        sim.kickResolved = false;
        if (sim.kickMode !== 'launch') sim.kickMode = 'idle';
      }

      const obstacleHit = sim.obstacles.find(obstacle => {
        if (sim.t - obstacle.hitAt < 0.16) return false;
        const target = getObstacleMotion(obstacle, sim).target;
        if (!target.active) return false;
        return !!getMovingCircleHit(prevBallX, prevBallY, sim.x, sim.y, VOLLEY_BALL_RADIUS, target.x, target.y, target.radius, 6);
      });

      if (obstacleHit) {
        const target = getObstacleMotion(obstacleHit, sim).target;
        const hit = getMovingCircleHit(prevBallX, prevBallY, sim.x, sim.y, VOLLEY_BALL_RADIUS, target.x, target.y, target.radius, 6)
          || getCircleCircleHit(sim.x, sim.y, VOLLEY_BALL_RADIUS, target.x, target.y, target.radius + 6);
        if (hit) {
          obstacleHit.hitAt = sim.t;
          sim.combo = 0;
          sim.x = hit.contactX + hit.normalX * (VOLLEY_BALL_RADIUS + 1.5);
          sim.y = Math.min(sim.ground - 18, hit.contactY + hit.normalY * (VOLLEY_BALL_RADIUS + 1.5));

          if (obstacleHit.type === 'slide') {
            sim.vx = Math.max(190, sim.vx * 0.78 + 80);
            sim.vy = -Math.max(240, Math.abs(sim.vy) * 0.66 + 110);
          } else {
            sim.vx = Math.max(170, sim.vx * 0.74 + 60);
            sim.vy = Math.max(-40, Math.abs(sim.vy) * 0.44 + 70);
          }

          sim.impactAge = 0;
          sim.impactX = target.x;
          sim.impactY = target.y;
          setFeedback(obstacleHit.type === 'slide'
            ? 'Slide tackle nicked it. Lift the next touch earlier to clear the boot.'
            : 'Diving header got there. Keep the next bounce lower or drive through before the head rises.');
        }
      }

      if (sim.launched && prevBallY < sim.ground && sim.y >= sim.ground) {
        sim.y = sim.ground;
        sim.bounces += 1;
        sim.combo = 0;
        finishRun(sim);
        return;
      }

      sim.camera = Math.max(0, Math.max(sim.x - 188, sim.playerX - 80));
      const pose = buildPose(sim);
      setRenderState({
        ...pose,
        screenX: sim.x - sim.camera + 72,
        screenY: sim.y,
        distance: sim.x / 6,
        combo: sim.combo,
        bestCombo: sim.bestCombo,
        bounces: sim.bounces,
        camera: sim.camera,
        velocity: sim.vx,
        obstacles: sim.obstacles
          .filter(obstacle => obstacle.x + obstacle.width > sim.camera - 120 && obstacle.x < sim.camera + 820)
          .map(obstacle => {
            const motion = getObstacleMotion(obstacle, sim);
            return {
              ...obstacle,
              left: obstacle.x - sim.camera + 72,
              motion,
              target: {
                ...motion.target,
                left: motion.target.x - sim.camera + 72,
              },
              isHit: sim.t - obstacle.hitAt < 0.18,
            };
          }),
        powerups: sim.powerups
          .filter(powerup => powerup.collectedAt < 0 && powerup.x + powerup.radius > sim.camera - 120 && powerup.x - powerup.radius < sim.camera + 840)
          .map(powerup => ({
            ...powerup,
            left: powerup.x - sim.camera + 72,
          })),
        ballCharged: !!sim.powerShotReady,
        impactAge: sim.impactAge,
        impactX: sim.impactX - sim.camera + 72,
        impactY: sim.impactY,
      });

      rafRef.current = requestAnimationFrame(frame);
    }

    rafRef.current = requestAnimationFrame(frame);
    return () => {
      cancelAnimationFrame(rafRef.current);
      lastTsRef.current = 0;
    };
  }, [phase]);

  useEffectG(() => () => cancelAnimationFrame(rafRef.current), []);

  function startRun() {
    cancelAnimationFrame(rafRef.current);
    lastTsRef.current = 0;
    setRunResult(null);
    setSaveMessage('');
    setPhase('flying');
    setFeedback('Launch is loading in his kicking leg, then he will sprint after the ball.');
    simRef.current = {
      x: 36,
      y: VOLLEY_START_Y,
      vx: 0,
      vy: 0,
      gravity: 900,
      ground: VOLLEY_GROUND_Y,
      playerX: 0,
      playerSpeed: 0,
      runCycle: 0,
      camera: 0,
      launched: false,
      obstacles: [],
      powerups: [],
      nextPowerupX: 280,
      nextPowerupId: 1,
      nextObstacleX: 520,
      nextObstacleId: 1,
      combo: 0,
      bestCombo: 0,
      touches: 0,
      bounces: 0,
      t: 0,
      lastKickStartedAt: 0,
      lastFootX: null,
      lastFootY: null,
      impactAge: -1,
      impactX: 0,
      impactY: 0,
      powerShotReady: false,
      powerSprintTimer: 0,
      kickAnim: 0,
      kickResolved: false,
      kickMode: 'launch',
    };
    setRenderState({
      ...buildPose({
        playerX: 0,
        playerSpeed: 0,
        runCycle: 0,
        kickAnim: 0,
        camera: 0,
        launched: false,
      }, true),
      screenX: 96,
      screenY: VOLLEY_START_Y,
      distance: 0,
      combo: 0,
      bestCombo: 0,
      bounces: 0,
      camera: 0,
      velocity: 0,
      obstacles: [],
      powerups: [],
      ballCharged: false,
      impactAge: -1,
      impactX: 0,
      impactY: 0,
    });
  }

  async function finishRun(sim) {
    cancelAnimationFrame(rafRef.current);
    const distance = Math.round(sim.x / 6);
    const score = Math.max(50, Math.round(distance + sim.bestCombo * 140 + sim.touches * 55 - sim.bounces * 8));
    const nextResult = {
      id: Date.now(),
      score,
      distance,
      bestCombo: sim.bestCombo,
      touches: sim.touches,
    };
    setPhase('done');
    setRunResult(nextResult);
    setSaveMessage('Checking leaderboard result...');
    setFeedback(sim.bestCombo > 0 ? `Full-time: ${formatMeters(distance)} with a best volley chain of ${sim.bestCombo}.` : `Full-time: ${formatMeters(distance)}. Chain a volley next run for a much bigger score.`);

    if (!onSubmitScore) {
      setSaveMessage('Leaderboard is unavailable right now.');
      return;
    }

    try {
      const result = await onSubmitScore({
        score,
        distance,
        bestCombo: sim.bestCombo,
        touches: sim.touches,
      });

      if (result?.saved && result.scope === 'global') {
        setSaveMessage('New best saved to the global leaderboard.');
        addToast(`Golden Volley best updated: ${score}`);
        return;
      }

      if (result?.saved && result.scope === 'local') {
        setSaveMessage('New best saved locally. Sign in to publish it globally.');
        addToast(`Golden Volley best updated: ${score}`);
        return;
      }

      if (result?.scope === 'global') {
        setSaveMessage(`Global best to beat: ${result.best}.`);
        return;
      }

      if (result?.scope === 'local') {
        setSaveMessage(`Local best to beat: ${result.best}. Sign in to publish globally.`);
        return;
      }

      setSaveMessage('Leaderboard result could not be confirmed.');
    } catch (error) {
      console.error('Golden Volley leaderboard save failed', error);
      setSaveMessage('Leaderboard save failed.');
    }
  }

  function resetToAiming() {
    cancelAnimationFrame(rafRef.current);
    simRef.current = null;
    lastTsRef.current = 0;
    setPhase('aiming');
    setRunResult(null);
    setSaveMessage('');
    setFeedback('Set the strike power, launch off his boot, then time each running volley with the swing.');
    resetRenderState();
  }

  const meterGradient = `linear-gradient(90deg, #22c55e 0%, #facc15 ${Math.max(40, power)}%, #ef4444 100%)`;
  const markerStart = Math.floor(renderState.distance / 50) * 50;
  const markers = Array.from({ length: 7 }, (_, idx) => markerStart + idx * 50);

  return (
    <div className="game-stage golden-volley">
      <div className="golden-volley__header">
        <div>
          <div className="display golden-volley__eyebrow">Golden Volley</div>
          <div className="golden-volley__lead">A World Cup-flavored bounce run. Launch the match ball, then time your volleys to keep it alive.</div>
        </div>
        <div className="golden-volley__scorebox">
          <span>Top score</span>
          <b>{sortedLeaderboard[0]?.score ?? 0}</b>
        </div>
      </div>

      <div className="golden-volley__scoreboard">
        <div>
          <span>Distance</span>
          <b>{formatMeters(renderState.distance)}</b>
        </div>
        <div>
          <span>Combo</span>
          <b>x{renderState.combo}</b>
        </div>
        <div>
          <span>Best chain</span>
          <b>x{renderState.bestCombo}</b>
        </div>
        <div>
          <span>Speed</span>
          <b>{Math.round(renderState.velocity)}</b>
        </div>
      </div>

      <div
        className={`golden-volley__arena ${phase === 'aiming' || phase === 'flying' ? 'is-tappable' : ''}`}
        onPointerDown={handleArenaTap}
      >
        <div className="golden-volley__sky" />
        <div className="golden-volley__crowd" />
        <div className="golden-volley__pitch">
          <div className="golden-volley__lines">
            {markers.map(marker => (
              <div key={marker} className="golden-volley__marker" style={{ left: `${marker * 6 - renderState.camera + 32}px` }}>
                <span>{marker}m</span>
              </div>
            ))}
          </div>
          <div className="golden-volley__obstacles">
            {renderState.obstacles.map(obstacle => (
              <div
                key={obstacle.id}
                className={`golden-volley__obstacle is-${obstacle.type} ${obstacle.isHit ? 'is-hit' : ''}`}
                style={{
                  left: `${obstacle.left}px`,
                  bottom: `${VOLLEY_ENEMY_FLOOR_OFFSET}px`,
                  width: `${obstacle.width}px`,
                  height: `${obstacle.height}px`,
                  '--enemy-body-lean': `${obstacle.motion.bodyLean}deg`,
                  '--enemy-front-leg': `${obstacle.motion.frontLeg}deg`,
                  '--enemy-back-leg': `${obstacle.motion.backLeg}deg`,
                  '--enemy-front-arm': `${obstacle.motion.frontArm}deg`,
                  '--enemy-back-arm': `${obstacle.motion.backArm}deg`,
                  '--enemy-run-bob': `${obstacle.motion.runBob}px`,
                  '--enemy-core-shift-x': `${obstacle.motion.coreShiftX}px`,
                  '--enemy-core-shift-y': `${obstacle.motion.coreShiftY}px`,
                  '--enemy-root-x': `${obstacle.motion.rootX}px`,
                  '--enemy-root-y': `${obstacle.motion.rootY}px`,
                  '--enemy-head-x': `${obstacle.motion.headX}px`,
                  '--enemy-head-y': `${obstacle.motion.headY}px`,
                  '--enemy-arm-back-x': `${obstacle.motion.armBackX}px`,
                  '--enemy-arm-back-y': `${obstacle.motion.armBackY}px`,
                  '--enemy-arm-front-x': `${obstacle.motion.armFrontX}px`,
                  '--enemy-arm-front-y': `${obstacle.motion.armFrontY}px`,
                  '--enemy-leg-back-x': `${obstacle.motion.legBackX}px`,
                  '--enemy-leg-back-y': `${obstacle.motion.legBackY}px`,
                  '--enemy-leg-front-x': `${obstacle.motion.legFrontX}px`,
                  '--enemy-leg-front-y': `${obstacle.motion.legFrontY}px`,
                  '--enemy-leg-back-length': `${obstacle.motion.legBackLength}px`,
                  '--enemy-leg-front-length': `${obstacle.motion.legFrontLength}px`,
                }}
              >
                <span className="golden-volley__enemy-shadow" />
                <span className="golden-volley__enemy-core">
                  <span className="golden-volley__enemy-head" />
                  <span className="golden-volley__enemy-arm is-back" />
                  <span className="golden-volley__enemy-arm is-front" />
                  <span className="golden-volley__enemy-torso" />
                  <span className="golden-volley__enemy-shorts" />
                  <span className="golden-volley__enemy-leg is-back" />
                  <span className="golden-volley__enemy-leg is-front" />
                </span>
                <span
                  className={`golden-volley__enemy-target is-${obstacle.target.kind}`}
                  style={{
                    left: `${obstacle.target.localX}px`,
                    top: `${obstacle.target.localY}px`,
                    opacity: obstacle.target.active ? 0.72 : 0,
                  }}
                />
              </div>
            ))}
          </div>
          <div className="golden-volley__powerups">
            {renderState.powerups.map(powerup => (
              <div
                key={powerup.id}
                className="golden-volley__powerup"
                style={{
                  left: `${powerup.left}px`,
                  top: `${powerup.y}px`,
                }}
              >
                <span />
              </div>
            ))}
          </div>
          <div
            className={`golden-volley__player ${renderState.isKicking ? 'is-kicking' : ''}`}
            style={{
              left: `${renderState.playerScreenX}px`,
              '--body-lean': `${renderState.bodyLean}deg`,
              '--front-leg': `${renderState.frontLeg}deg`,
              '--back-leg': `${renderState.backLeg}deg`,
              '--front-arm': `${renderState.frontArm}deg`,
              '--back-arm': `${renderState.backArm}deg`,
              '--run-bob': `${renderState.runBob}px`,
            }}
          >
            <div className="golden-volley__player-shadow" />
            <div className="golden-volley__player-core">
              <div className="golden-volley__player-head" />
              <div className="golden-volley__player-arm is-back" />
              <div className="golden-volley__player-arm is-front" />
              <div className="golden-volley__player-torso" />
              <div className="golden-volley__player-shorts" />
              <div className="golden-volley__player-leg is-back" />
              <div className="golden-volley__player-leg is-front" />
            </div>
          </div>
          <div className={`golden-volley__ball ${phase === 'flying' ? 'is-live' : ''} ${renderState.ballCharged ? 'is-charged' : ''}`} style={{ left: `${renderState.screenX}px`, top: `${renderState.screenY}px` }}>
            <span />
          </div>
          {renderState.impactAge >= 0 && (
            <div
              className="golden-volley__impact"
              style={{
                left: `${renderState.impactX}px`,
                top: `${renderState.impactY}px`,
                '--impact-age': renderState.impactAge,
                '--impact-opacity': `${Math.max(0, 1 - renderState.impactAge / 0.28)}`,
                '--impact-scale': `${1 + renderState.impactAge * 3.4}`,
              }}
            >
              <span className="golden-volley__impact-ring" />
              <span className="golden-volley__impact-flash" />
            </div>
          )}
          {phase === 'done' && runResult && (
            <div className="golden-volley__gameover">
              <div className="golden-volley__gameover-card">
                <div className="display golden-volley__gameover-title">Full Time</div>
                <div className="golden-volley__gameover-copy">The ball hit the turf. This run is over.</div>
                <div className="golden-volley__gameover-stats">
                  <div><span>Score</span><b>{runResult.score}</b></div>
                  <div><span>Distance</span><b>{formatMeters(runResult.distance)}</b></div>
                </div>
                <div className="row-wrap golden-volley__gameover-actions" style={{ justifyContent: 'center' }}>
                  <button className="btn btn-primary btn-lg" onClick={resetToAiming}>Play Again</button>
                  <button className="btn btn-ghost" onClick={onExit}>Back</button>
                </div>
              </div>
            </div>
          )}
        </div>
      </div>

      <div className="golden-volley__feedback">{feedback}</div>

      {phase === 'aiming' && (
        <div className="golden-volley__controls">
          <div className="golden-volley__meter">
            <div className="golden-volley__meter-fill" style={{ width: `${power}%`, background: meterGradient }} />
          </div>
          <div className="golden-volley__meter-meta">
            <span>Tap the pitch or press Launch</span>
            <b>{Math.round(power)}</b>
          </div>
          <div className="row-wrap golden-volley__actions" style={{ justifyContent: 'center' }}>
            <button className="btn btn-primary btn-lg" onClick={startRun}>Launch</button>
            <button className="btn btn-ghost" onClick={onExit}>Back</button>
          </div>
        </div>
      )}

      {phase === 'flying' && (
        <div className="golden-volley__controls">
          <div className="golden-volley__hint">He chases automatically. Tap anywhere on the pitch, or hit KICK, to meet the dropping ball and clear the defenders.</div>
          <div className="row-wrap golden-volley__actions" style={{ justifyContent: 'center' }}>
            <button className="btn btn-gold btn-lg" onClick={triggerKick}>KICK</button>
            <button className="btn btn-ghost" onClick={resetToAiming}>Restart</button>
          </div>
        </div>
      )}

      {phase === 'done' && runResult && (
        <div className="golden-volley__summary">
          <div className="golden-volley__result-grid">
            <div><span>Score</span><b>{runResult.score}</b></div>
            <div><span>Distance</span><b>{formatMeters(runResult.distance)}</b></div>
            <div><span>Best combo</span><b>x{runResult.bestCombo}</b></div>
            <div><span>Touches</span><b>{runResult.touches}</b></div>
          </div>

          <div className={`golden-volley__leaderboard-entry ${saveMessage.includes('saved') ? 'is-saved' : ''}`}>
            {saveMessage || `Top ${VOLLEY_LEADERBOARD_LIMIT} scores are shown below.`}
          </div>

          <div className="row-wrap" style={{ justifyContent: 'center' }}>
            <button className="btn btn-primary" onClick={resetToAiming}>Play Again</button>
            <button className="btn btn-ghost" onClick={onExit}>Back</button>
          </div>
        </div>
      )}

      <div className="golden-volley__leaderboard">
        <div className="display golden-volley__board-title">Leaderboard</div>
        {sortedLeaderboard.length ? (
          <div className="golden-volley__board-list">
            {sortedLeaderboard.map((entry, idx) => (
              <div key={`${entry.name}-${entry.score}-${idx}`} className="golden-volley__board-row">
                <span className="golden-volley__board-rank">#{idx + 1}</span>
                <span className="golden-volley__board-name">{entry.name}</span>
                <span className="golden-volley__board-distance">{formatMeters(entry.distance || 0)}</span>
                <b className="golden-volley__board-score">{entry.score}</b>
              </div>
            ))}
          </div>
        ) : (
          <div className="golden-volley__board-empty">No scores on the board yet. Set the opening record.</div>
        )}
      </div>
    </div>
  );
}

window.QuizGame = QuizGame;
window.SlotGame = SlotGame;
window.WheelGame = WheelGame;
window.QuizStreakGame = QuizStreakGame;
window.BouncemastersGame = BouncemastersGame;
