// Page components

// Small double-sided coin that spins in 3D. Decorative — both faces use the
// same image; backface-visibility + a 180° back face keep it readable as it turns.
function SpinCoin({ size = 56, style = {} }) {
  return (
    <div className="coin-spin-wrap" style={{ width: size, height: size, ...style }} aria-hidden="true">
      <div className="coin-spin">
        <img className="coin-face coin-front" src="assets/coin.png" alt="" />
        <img className="coin-face coin-back" src="assets/coin.png" alt="" />
      </div>
    </div>
  );
}

// Match the mobile breakpoint used by the site chrome (820px). Games tagged
// `desktopOnly` (e.g. Dino Run — needs more screen real-estate + precise input)
// are filtered out of menus and the direct game URL on screens at or below this
// width, replaced with a friendly "play on a bigger screen" notice.
function useIsMobile() {
  const [mobile, setMobile] = React.useState(() => {
    if (typeof window === 'undefined') return false;
    return window.matchMedia('(max-width: 820px)').matches;
  });
  React.useEffect(() => {
    const mq = window.matchMedia('(max-width: 820px)');
    const onChange = (e) => setMobile(e.matches);
    // addEventListener is supported in all modern browsers; older Safari needs addListener
    if (mq.addEventListener) mq.addEventListener('change', onChange);
    else mq.addListener(onChange);
    return () => {
      if (mq.removeEventListener) mq.removeEventListener('change', onChange);
      else mq.removeListener(onChange);
    };
  }, []);
  return mobile;
}

// Filtered list of games for the current device. Hides desktopOnly entries
// on mobile so featured + library views stay accurate.
function useVisibleGames() {
  const mobile = useIsMobile();
  return React.useMemo(
    () => GAMES.filter(g => !(mobile && g.desktopOnly)),
    [mobile]
  );
}

function HeroA({ go }) {
  return (
    <div className="hero-a">
      <div>
        <span className="eyebrow">22 free games · no signup</span>
        <h1 className="hero-title" style={{ marginTop: 18 }}>
          Play, learn,<br/>
          <span className="pill">giggle</span> a lot.
        </h1>
        <p className="hero-sub">
          A cozy corner of the internet for 6–8 year olds. No ads, no accounts, no chat — just twenty bright, simple games made to be picked up and put down.
        </p>
        <div className="hero-ctas">
          <button className="btn btn-primary btn-lg" onClick={() => go('library')}>Pick a game →</button>
          <button className="btn btn-lg" onClick={() => go('parents')}>For parents</button>
        </div>
      </div>
      <div className="mascot-wrap">
        <Mascot size={260} color="var(--c-coral)" />
      </div>
    </div>
  );
}

function HeroB({ go }) {
  return (
    <div className="hero-b">
      <span className="eyebrow">Aydin's Game Lab</span>
      <h1 className="hero-title" style={{ marginTop: 18 }}>
        Twenty tiny games.<br/>
        Zero <span className="pill">ads.</span>
      </h1>
      <p className="hero-sub">A friendly playground of quick games for kids aged 6–8. Pick one, play for a few minutes, go ride your bike.</p>
      <div className="hero-ctas">
        <button className="btn btn-primary btn-lg" onClick={() => go('library')}>Browse all games</button>
        <button className="btn btn-green btn-lg" onClick={() => go('game:memory-zoo')}>Try Memory Zoo</button>
      </div>
      <div className="sticker-row">
        <Sticker kind="circle" color="var(--c-coral)" rotate={-6} />
        <Sticker kind="square" color="var(--c-sky)" rotate={4} />
        <Sticker kind="diamond" color="var(--c-grass)" rotate={0} />
        <Sticker kind="pill" color="var(--c-sun)" rotate={-3} />
        <Sticker kind="circle" color="var(--c-grape)" rotate={6} />
      </div>
    </div>
  );
}

function HeroC({ go }) {
  return (
    <div>
      <span className="eyebrow">Aydin's Game Lab</span>
      <h1 className="hero-title" style={{ marginTop: 18, maxWidth: 900 }}>
        A sticker book<br/>
        you can <span className="pill">play.</span>
      </h1>
      <p className="hero-sub">Twenty bright little games laid out like a collage. Tap any tile to jump in.</p>
      <div className="hero-c-grid">
        <div className="big" style={{ background: 'var(--c-coral)', color: 'white' }} onClick={() => go('game:memory-zoo')}>Memory Zoo</div>
        <div style={{ background: 'var(--c-sun)' }} onClick={() => go('game:math-catch')}>Catcher</div>
        <div style={{ background: 'var(--c-sky)' }} onClick={() => go('library')}>Maze</div>
        <div style={{ background: 'var(--c-grass)' }} onClick={() => go('library')}>Quiz</div>
        <div style={{ background: 'var(--c-grape)', color: 'white' }} onClick={() => go('library')}>Doodle</div>
        <div style={{ background: 'var(--c-sun)' }} onClick={() => go('library')}>Count</div>
        <div style={{ background: 'var(--c-coral)', color: 'white' }} onClick={() => go('library')}>ABC</div>
      </div>
    </div>
  );
}

function Home({ go, hero }) {
  const HeroComp = hero === 'a' ? HeroA : hero === 'b' ? HeroB : HeroC;
  const games = useVisibleGames();
  const featured = games.slice(0, 8);
  const total = games.length;
  return (
    <div>
      <div className="hero">
        <HeroComp go={go} />
      </div>

      <div className="section">
        <div className="section-head">
          <div>
            <span className="eyebrow">Featured</span>
            <h2 style={{ marginTop: 10 }}>Today's pick of games</h2>
          </div>
          <button className="btn" onClick={() => go('library')}>See all {total} →</button>
        </div>
        <div className="tiles">
          {featured.map(g => <GameTile key={g.id} game={g} onPlay={(id) => go('game:' + id)} />)}
        </div>
      </div>

      <div className="section">
        <div className="card card-yellow" style={{ display: 'grid', gridTemplateColumns: '1fr auto', gap: 24, alignItems: 'center' }}>
          <div>
            <h2>Made for kids. No tricks.</h2>
            <p style={{ marginTop: 10, maxWidth: 520 }}>
              No ads. No pop-ups. No accounts. No in-app purchases. No "watch this video to continue." Just games.
            </p>
            <button className="btn" style={{ marginTop: 18 }} onClick={() => go('parents')}>Read our promise →</button>
          </div>
          <Mascot size={140} color="var(--c-coral)" />
        </div>
      </div>
    </div>
  );
}

function Library({ go }) {
  const [cat, setCat] = React.useState('All');
  const games = useVisibleGames();
  const filtered = cat === 'All' ? games : games.filter(g => g.cat === cat);
  return (
    <div className="section" style={{ paddingTop: 40 }}>
      <span className="eyebrow">Games library</span>
      <h1 style={{ marginTop: 16, marginBottom: 8 }}>All {games.length} games</h1>
      <p style={{ color: 'var(--ink-soft)', maxWidth: 600, marginBottom: 28 }}>
        Pick a category or scroll through everything.
      </p>
      <div className="chips" style={{ marginBottom: 28 }}>
        {CATEGORIES.map(c => (
          <button key={c} className={`chip ${cat === c ? 'active' : ''}`} onClick={() => setCat(c)}>{c}</button>
        ))}
      </div>
      <div className="tiles">
        {filtered.map(g => <GameTile key={g.id} game={g} onPlay={(id) => go('game:' + id)} />)}
      </div>
    </div>
  );
}

// ——— Local personal-best helpers (per-game) ———
function pbKey(gameId) { return 'gl_pb_' + gameId; }
function getLocalPB(gameId) {
  try {
    const v = localStorage.getItem(pbKey(gameId));
    if (v === null || v === '') return null;
    const n = parseInt(v, 10);
    return Number.isFinite(n) ? n : null;
  } catch { return null; }
}
function setLocalPB(gameId, score) {
  try { localStorage.setItem(pbKey(gameId), String(score)); } catch {}
}
function isImprovement(score, pb, lowerIsBetter) {
  if (score <= 0) return false;
  if (pb === null || pb === undefined) return true; // first time
  return lowerIsBetter ? (score < pb) : (score > pb);
}

// Compact status pill that replaces the manual "Save my score" button
// once the player has a profile saved on this device.
function AutoSaveStatus({ status, pb, liveScore, beatsPB, lowerIsBetter, unit, xpEarned }) {
  let label = '';
  let bg = 'var(--paper)';
  let color = 'var(--ink)';
  let icon = '💾';

  // " · +N XP" suffix when we know the real amount the server credited.
  const xpSuffix = (typeof xpEarned === 'number' && xpEarned > 0)
    ? ' · +' + xpEarned.toLocaleString() + ' XP'
    : '';

  if (status === 'pending' || status === 'saving') {
    icon = status === 'saving' ? '⏳' : (beatsPB ? '🎯' : '💾');
    label = status === 'saving'
      ? 'Saving…'
      : (beatsPB ? 'New best — auto-saving…' : 'Saving your play…');
    bg = beatsPB ? 'var(--c-sun)' : 'var(--bg-warm)';
  } else if (status === 'saved-best') {
    icon = '✓';
    label = 'New best saved!' + xpSuffix;
    bg = 'var(--c-grass)';
    color = 'white';
  } else if (status === 'saved') {
    icon = '✓';
    label = 'Score saved' + xpSuffix;
    bg = 'var(--bg-warm)';
  } else if (status === 'failed') {
    icon = '⚠️';
    label = 'Save failed — will retry';
    bg = 'var(--bg-warm)';
  } else if (liveScore > 0 && beatsPB) {
    icon = '🎯';
    label = 'New best!';
    bg = 'var(--c-sun)';
  } else if (pb !== null && pb !== undefined) {
    icon = '🏅';
    const u = unit ? ' ' + unit : '';
    label = `Your best: ${pb}${u}${lowerIsBetter ? ' (lower wins)' : ''}`;
  } else {
    icon = '💾';
    label = 'Scores auto-save';
  }

  return (
    <div
      title="Your scores save automatically — no need to click anything."
      style={{
        display: 'inline-flex', alignItems: 'center', gap: 8,
        padding: '10px 16px', borderRadius: 999,
        border: 'var(--border)', background: bg, color,
        fontFamily: 'var(--font-display)', fontWeight: 800, fontSize: 14,
        whiteSpace: 'nowrap',
      }}>
      <span style={{ fontSize: 16, lineHeight: 1 }}>{icon}</span>
      <span>{label}</span>
    </div>
  );
}

// Games that tally a "play" the moment the board is shuffled/started (via
// recordPlay) rather than on submit — so their solve must NOT count a play
// again, or it would double-count.
const PLAY_ON_START = ['piece-puzzle', 'shape-jigsaw'];

function SubmitScorePanel({ gameId, lowerIsBetter, metric }) {
  const [liveScore, setLive] = React.useState(0);
  // Lock the save button / auto-save against re-saving the same score.
  // Reset whenever the score changes (player replays, gets a new score).
  const [lastSavedScore, setLastSavedScore] = React.useState(-1);
  const [showModal, setShowModal] = React.useState(false);
  const [refreshKey, setRefreshKey] = React.useState(0);
  // Local personal best, used to decide whether to auto-save.
  const [pb, setPb] = React.useState(() => getLocalPB(gameId));
  // 'idle' | 'pending' (debouncing) | 'saving' (network) | 'saved' | 'failed'
  const [autoStatus, setAutoStatus] = React.useState('idle');
  // XP earned on the most recent successful save (from the server response),
  // so the status pill can show the real amount instead of a hardcoded value.
  const [lastXp, setLastXp] = React.useState(null);
  // Re-render when credentials change (switch profile)
  const [, bumpCreds] = React.useReducer(x => x + 1, 0);
  const debounceRef = React.useRef(null);
  // Hard lock against repeated saves of the same score within one play cycle.
  // Game components dispatch a 0 score when starting a fresh play; on that
  // transition we reset this ref so a new positive score is eligible to save.
  // Without this, transient 0→N event sequences (e.g. useReportScore's cleanup
  // dispatching 0 between re-renders) can cause `alreadySaved` to flicker
  // false and re-trigger the debounced save.
  const savedScoreRef = React.useRef(0);

  // Reset everything when the player switches to a different game.
  React.useEffect(() => {
    setLive(0);
    setLastSavedScore(-1);
    setPb(getLocalPB(gameId));
    setAutoStatus('idle');
    setLastXp(null);
    savedScoreRef.current = 0;
    if (debounceRef.current) { clearTimeout(debounceRef.current); debounceRef.current = null; }
  }, [gameId]);

  React.useEffect(() => {
    const onScore = (e) => {
      const v = e.detail || 0;
      // Only react when the score actually changes — spurious same-value
      // dispatches (from useReportScore's cleanup + new-effect cycle) would
      // otherwise reset lastSavedScore and re-trigger the save useEffect.
      setLive(prev => {
        if (prev === v) return prev;
        if (v === 0) {
          // Player started a fresh game cycle. Clear the save lock so a new
          // positive score is eligible for auto-save.
          savedScoreRef.current = 0;
          setLastSavedScore(-1);
        }
        setAutoStatus(s => (s === 'saved' || s === 'saved-best' || s === 'failed') ? 'idle' : s);
        return v;
      });
    };
    window.addEventListener('gl-score', onScore);
    setLive(window.__glCurrentScore || 0);
    return () => window.removeEventListener('gl-score', onScore);
  }, [gameId]);

  React.useEffect(() => {
    const onCreds = () => bumpCreds();
    window.addEventListener('gl-credentials-changed', onCreds);
    return () => window.removeEventListener('gl-credentials-changed', onCreds);
  }, []);

  const hasCreds = hasStoredCredentials();
  const storedName = getStoredName();
  const unit = metric || '';
  const alreadySaved = lastSavedScore === liveScore && liveScore > 0;
  const beatsPB = isImprovement(liveScore, pb, lowerIsBetter);

  // ——— Auto-save: debounced save on every completed score ———
  // We submit every play (not just personal-best improvements) so the
  // server can credit +50 XP per play and re-award the +1000 world-top
  // bonus on ties (e.g. another perfect 8-move Memory Zoo run).
  // 2.2s debounce avoids spamming during a run-up — for end-of-game
  // scores (memory/maze) the score flips non-zero once and saves once.
  React.useEffect(() => {
    if (debounceRef.current) { clearTimeout(debounceRef.current); debounceRef.current = null; }
    if (!hasCreds) return;
    if (liveScore <= 0) return;
    // Hard guard: if we've already initiated a save for this exact score in
    // this play cycle, do not re-fire. The lock is cleared in onScore when
    // liveScore drops to 0 (next play cycle).
    if (savedScoreRef.current === liveScore) return;

    // Claim the slot immediately so any re-renders during the debounce or
    // network round-trip can't schedule a duplicate save for the same value.
    savedScoreRef.current = liveScore;
    setAutoStatus('pending');
    debounceRef.current = setTimeout(async () => {
      setAutoStatus('saving');
      const res = await submitScoreSmart(
        gameId, getStoredName(), getStoredEmail(),
        liveScore, { lowerIsBetter, countPlay: !PLAY_ON_START.includes(gameId) }
      );
      if (res && !res.error) {
        const newPB = (typeof res.currentBest === 'number') ? res.currentBest : liveScore;
        setLocalPB(gameId, newPB);
        setPb(newPB);
        setLastSavedScore(liveScore);
        // Show the real XP the server credited for this play (skip the
        // per-device local fallback, which only assumes a flat amount).
        setLastXp(res.local ? null : (typeof res.xpEarned === 'number' ? res.xpEarned : null));
        // 'saved-best' if this submission improved the player's PB,
        // 'saved' if it was a tie/lower play (still earns play XP).
        setAutoStatus(res.improved ? 'saved-best' : 'saved');
        setRefreshKey(k => k + 1);
      } else {
        setAutoStatus('failed');
        // Release the lock so the next score event can retry.
        savedScoreRef.current = 0;
      }
    }, 2200);

    return () => {
      if (debounceRef.current) { clearTimeout(debounceRef.current); debounceRef.current = null; }
    };
  }, [liveScore, hasCreds, gameId, lowerIsBetter]);

  // The manual save button is only needed when we don't yet have stored
  // credentials — clicking it opens the modal which collects name + email.
  const saveDisabled = liveScore <= 0 || alreadySaved;

  const switchProfile = () => {
    if (confirm('Clear your saved profile? Next time you save a score, you\'ll be asked for name + email again.')) {
      clearStoredCredentials();
    }
  };

  return (
    <div style={{ marginTop: 26, padding: 20, background: 'var(--bg-warm)',
      border: 'var(--border)', borderRadius: 18 }}>
      <h3 style={{ textAlign: 'center', marginBottom: 14 }}>🏆 Top Players</h3>
      <LeaderboardList key={refreshKey} gameId={gameId}
        highlightName={storedName}
        lowerIsBetter={lowerIsBetter}
        metric={metric} />
      <div style={{ display: 'flex', gap: 12, marginTop: 16, flexWrap: 'wrap',
        justifyContent: 'center', alignItems: 'center' }}>
        <div style={{ padding: '10px 18px', border: 'var(--border)', borderRadius: 999,
          fontFamily: 'var(--font-display)', fontWeight: 800, fontSize: 15,
          background: 'var(--paper)' }}>
          Your {unit || 'score'}: <span style={{ color: 'var(--c-coral)' }}>{liveScore}</span>
        </div>
        {hasCreds ? (
          <AutoSaveStatus
            status={autoStatus}
            pb={pb}
            liveScore={liveScore}
            beatsPB={beatsPB}
            lowerIsBetter={lowerIsBetter}
            xpEarned={lastXp}
            unit={unit}
          />
        ) : (
          <button className="btn btn-primary" disabled={saveDisabled}
            onClick={() => setShowModal(true)}
            style={{ padding: '10px 18px', fontSize: 14, opacity: saveDisabled ? 0.5 : 1, cursor: saveDisabled ? 'default' : 'pointer' }}>
            {alreadySaved ? '✓ Score saved' : 'Save my score'}
          </button>
        )}
      </div>
      {lowerIsBetter && (
        <p style={{ textAlign: 'center', marginTop: 8, fontSize: 12, color: 'var(--ink-soft)', fontStyle: 'italic' }}>
          Fewer {metric || 'moves'} ranks higher
        </p>
      )}
      {hasCreds && (
        <p style={{ textAlign: 'center', marginTop: 10, fontSize: 13, color: 'var(--ink-soft)' }}>
          Playing as <strong>{storedName}</strong>
          {' · '}
          <a onClick={switchProfile}
            style={{ textDecoration: 'underline', cursor: 'pointer', color: 'var(--ink-soft)' }}>
            not you?
          </a>
        </p>
      )}
      {showModal && (
        <ScoreSubmitModal gameId={gameId} score={liveScore}
          metric={metric}
          lowerIsBetter={lowerIsBetter}
          onClose={() => setShowModal(false)}
          onSubmitted={(res) => {
            setShowModal(false);
            setLastSavedScore(liveScore); // lock this score against re-saves
            savedScoreRef.current = liveScore; // also lock the auto-save path
            // Mirror auto-save: keep local PB in sync with the manual flow,
            // so future runs trigger auto-save against the right benchmark.
            const newPB = (res && typeof res.currentBest === 'number')
              ? res.currentBest
              : liveScore;
            setLocalPB(gameId, newPB);
            setPb(newPB);
            setRefreshKey(k => k + 1);
          }} />
      )}
    </div>
  );
}

function GamePage({ gameId, go }) {
  const game = GAMES.find(g => g.id === gameId);
  const isMobile = useIsMobile();

  if (!game) return <div className="section"><h2>Game not found</h2></div>;

  // Block desktop-only games on small screens with a friendly notice. The
  // tile is already hidden from menus, but a deep-link / shared URL can land
  // a phone user here — give them a graceful redirect instead of a broken
  // experience.
  if (isMobile && game.desktopOnly) {
    return (
      <div className="game-page">
        <button className="btn" style={{ marginBottom: 20 }} onClick={() => go('library')}>← All games</button>
        <div style={{
          display: 'grid', placeItems: 'center', minHeight: 460, textAlign: 'center',
          padding: '24px 16px',
        }}>
          <div>
            <div style={{ fontSize: 72, lineHeight: 1, marginBottom: 8 }}>🖥️</div>
            <h2 style={{ marginTop: 10 }}>Best played on a bigger screen</h2>
            <p style={{ color: 'var(--ink-soft)', marginTop: 12, maxWidth: 420, marginInline: 'auto' }}>
              <strong>{game.title}</strong> needs more room to run. Open this page on a
              tablet or computer to play — we'll see you out there!
            </p>
            <button className="btn btn-primary btn-lg" style={{ marginTop: 22 }} onClick={() => go('library')}>
              Browse other games
            </button>
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="game-page">
      <button className="btn" style={{ marginBottom: 20 }} onClick={() => go('library')}>← All games</button>
      <div className="game-header">
        <div>
          <span className="eyebrow">{game.cat}</span>
          <h1 style={{ marginTop: 10 }}>{game.title}</h1>
          <div style={{ display: 'flex', gap: 16, marginTop: 12, color: 'var(--ink-soft)', fontWeight: 700 }}>
            <span>Age {game.age}</span>
            <span>•</span>
            <span>~{game.time}</span>
          </div>
        </div>
      </div>

      <div className="game-stage">
        {game.id === 'chess' && <ChessGame />}
        {game.id === 'dig-bomb' && <DigBombGame />}
        {game.id === 'dino-run' && <DinoRunGame />}
        {game.id === 'shuffle-quest' && <ShuffleQuestGame />}
        {game.id === 'memory-zoo' && <MemoryGame />}
        {game.id === 'math-catch' && <CatchGame />}
        {game.id === 'alphabet-safari' && <AlphabetSafariGame />}
        {game.id === 'add-it-up' && <AddItUpGame />}
        {(game.id === 'doodle-pad' || game.id === 'color-studio') && <DoodlePadGame />}
        {game.id === 'maze-runner' && <BubbleMazeGame />}
        {game.id === 'shape-maze' && <ShapeMazeGame />}
        {game.id === 'count-garden' && <CountGardenGame />}
        {game.id === 'quiz-quest' && <QuizQuestGame />}
        {game.id === 'find-the-pair' && <FindThePairGame />}
        {game.id === 'memory-match-hard' && <SuperMemoryGame />}
        {game.id === 'word-builder' && <WordBuilderGame />}
        {game.id === 'letter-hop' && <LetterHopGame />}
        {game.id === 'rainbow-tap' && <RainbowTapGame />}
        {game.id === 'space-dodge' && <SpaceDodgeGame />}
        {game.id === 'times-table' && <TimesTableGame />}
        {game.id === 'animal-facts' && <AnimalFactsGame />}
        {game.id === 'shape-jigsaw' && <SlidingPuzzleGame size={3} gameId="shape-jigsaw" />}
        {game.id === 'piece-puzzle' && <SlidingPuzzleGame size={4} picture={true} gameId="piece-puzzle" />}
        {game.playable && <SubmitScorePanel gameId={game.id} lowerIsBetter={game.lowerIsBetter} metric={game.metric} />}
        {!game.playable && (
          <div style={{ display: 'grid', placeItems: 'center', minHeight: 460, textAlign: 'center' }}>
            <div>
              <div style={{ display: 'inline-block', padding: '30px 40px', background: game.color, border: 'var(--border)', borderRadius: 24, fontFamily: 'ui-monospace, monospace', fontSize: 14 }}>
                [{game.art}]<br/>placeholder art
              </div>
              <h2 style={{ marginTop: 24 }}>Coming soon</h2>
              <p style={{ color: 'var(--ink-soft)', marginTop: 8, maxWidth: 420 }}>
                This game is on the workbench. Check back soon!
              </p>
              <button className="btn btn-primary" style={{ marginTop: 20 }} onClick={() => go('library')}>Try another game</button>
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

function About({ go }) {
  return (
    <div className="section" style={{ maxWidth: 900 }}>
      <span className="eyebrow">About</span>
      <h1 style={{ marginTop: 14 }}>A quiet place<br/>to play on the internet.</h1>
      <div className="two-col" style={{ marginTop: 40 }}>
        <div>
          <p style={{ fontSize: 18 }}>
            Aydin's Game Lab started as a weekend project — a small collection of games made for one kid, then another, then a whole classroom. Today it's twenty quick, simple, educational games that you can play in a browser, on any device, without signing up for anything.
          </p>
          <p style={{ fontSize: 18, marginTop: 16 }}>
            Everything is made by hand: the art, the sounds, the code, the design. If you have a game idea you'd like to see here, we'd love to hear about it.
          </p>
          <button className="btn btn-primary" style={{ marginTop: 22 }} onClick={() => go('library')}>See the games</button>
        </div>
        <div style={{ display: 'grid', placeItems: 'center' }}>
          <Mascot size={220} color="var(--c-sky)" />
          <p style={{ fontFamily: 'var(--font-display)', fontWeight: 800, fontSize: 22, marginTop: 16 }}>Meet Ziggy</p>
          <p style={{ color: 'var(--ink-soft)', textAlign: 'center', marginTop: 4 }}>Our Lab mascot. Loves puzzles.</p>
        </div>
      </div>
    </div>
  );
}

function Parents() {
  const promises = [
    { n: '1', t: 'No ads, ever.', d: 'No banner ads, no video ads, no "rewarded" ads. Nothing for sale inside games.' },
    { n: '2', t: 'No accounts.', d: "Kids don't sign in. We don't ask for names, emails, birthdays or photos." },
    { n: '3', t: 'No tracking.', d: "No analytics pixels, no third-party cookies, no data sold. Game progress is stored on your device only." },
    { n: '4', t: 'No chat.', d: "There's no way for kids to message each other or anyone else. It's a single-player playground." },
    { n: '5', t: 'Age-appropriate.', d: 'Games are designed for 6-8 year olds. Rules are simple, failure is gentle, success is noisy.' },
    { n: '6', t: 'Short sessions.', d: "Most games take 2–5 minutes. No endless scrolls or skinner-box loops." },
  ];
  return (
    <div className="section" style={{ maxWidth: 980 }}>
      <span className="eyebrow">For parents & guardians</span>
      <h1 style={{ marginTop: 14 }}>Our promise.</h1>
      <p style={{ fontSize: 19, color: 'var(--ink-soft)', maxWidth: 620, marginTop: 14 }}>
        We built this site because we couldn't find a corner of the internet where our kid could play for 5 minutes without being sold something. Here's what that means in practice:
      </p>
      <div className="feature-list" style={{ marginTop: 32 }}>
        {promises.map(p => (
          <div className="feature-item" key={p.n}>
            <div className="feature-num">{p.n}</div>
            <div>
              <h3>{p.t}</h3>
              <p style={{ marginTop: 6, color: 'var(--ink-soft)' }}>{p.d}</p>
            </div>
          </div>
        ))}
      </div>
    </div>
  );
}

function Achievements() {
  // Stickers are SERVER-DRIVEN: we fetch the signed-in player's saved scores
  // from the leaderboard backend and compute every badge from them
  // (components/Stickers.jsx). Nothing here reads sticker state from localStorage.
  const [state, setState] = React.useState({ loading: true, noProfile: false, badges: [] });
  // Re-fetch when the player signs in / switches profile.
  const [credTick, setCredTick] = React.useState(0);
  React.useEffect(() => {
    const bump = () => setCredTick(t => t + 1);
    window.addEventListener('gl-credentials-changed', bump);
    window.addEventListener('gl-xp-changed', bump); // a fresh save may unlock a sticker
    return () => {
      window.removeEventListener('gl-credentials-changed', bump);
      window.removeEventListener('gl-xp-changed', bump);
    };
  }, []);

  React.useEffect(() => {
    let alive = true;
    setState(s => ({ ...s, loading: true }));
    (async () => {
      const res = (typeof window.fetchMyStickers === 'function')
        ? await window.fetchMyStickers()
        : { ok: false, noProfile: true, rows: [] };
      if (!alive) return;
      const badges = (typeof window.computeBadges === 'function')
        ? window.computeBadges(res.rows, res.totalXp)
        : [];
      setState({ loading: false, noProfile: !!res.noProfile, badges });
    })();
    return () => { alive = false; };
  }, [credTick]);

  const { loading, noProfile, badges } = state;
  const done = badges.filter(b => b.unlocked).length;

  const openProfile = () => window.dispatchEvent(new Event('gl-open-profile'));

  return (
    <div className="section" style={{ maxWidth: 1000 }}>
      <span className="eyebrow">Achievements</span>
      <h1 style={{ marginTop: 14 }}>Your sticker book.</h1>
      <p style={{ color: 'var(--ink-soft)', maxWidth: 600, marginTop: 12 }}>
        Collect badges as you play. Your stickers are saved to your player profile, so they
        follow you on every device you sign in on.
      </p>

      {noProfile ? (
        <div style={{
          marginTop: 28, padding: '28px 26px', background: 'var(--paper)',
          border: 'var(--border-thick)', borderRadius: 22, boxShadow: 'var(--shadow-lg)',
          maxWidth: 520,
        }}>
          <div style={{ fontSize: 44, marginBottom: 8 }}>⭐</div>
          <h3 style={{ fontSize: 20, marginBottom: 8 }}>Sign in to start collecting</h3>
          <p style={{ color: 'var(--ink-soft)', fontSize: 14, marginBottom: 18 }}>
            Stickers unlock from your saved scores. Set up a player profile (name + email)
            and your badges will appear here automatically.
          </p>
          <button className="btn btn-primary" onClick={openProfile}>Set up my profile</button>
        </div>
      ) : (
        <>
          <div style={{ display: 'flex', gap: 12, margin: '28px 0' }}>
            <div className="pill-stat">{loading ? 'Loading…' : `${done} / ${badges.length} earned`}</div>
            {!loading && (
              <div className="pill-stat" style={{ background: 'var(--c-sun)' }}>
                {done === badges.length ? 'All done! 🎉' : 'Keep going!'}
              </div>
            )}
          </div>
          <div className="badges-grid" style={{ opacity: loading ? 0.55 : 1, transition: 'opacity .2s' }}>
            {badges.map(b => (
              <div key={b.n} className={`badge ${b.unlocked ? '' : 'locked'}`}>
                <div className="badge-icon" style={{ background: b.unlocked ? b.color : 'var(--paper)' }}>
                  {b.unlocked ? b.glyph : '?'}
                </div>
                <h3 style={{ fontSize: 18 }}>{b.n}</h3>
                <p style={{ fontSize: 13, color: 'var(--ink-soft)', marginTop: 6 }}>{b.d}</p>
              </div>
            ))}
          </div>
        </>
      )}
    </div>
  );
}

Object.assign(window, { Home, Library, GamePage, About, Parents, Achievements });
