<?php require_once __DIR__ . '/includes/bootstrap.php';
$pageTitle = 'Games | Underground FX Radio';
$currentPage = basename(__FILE__);
$games = ugfx_games_for_public();
$allStats = ugfx_game_stats();
$totalGames = count(ugfx_games_catalog());
$onlineGames = count($games);
$featuredGames = array_filter($games, fn($g) => !empty($g['settings']['featured'] ?? false));
$featuredGames = $featuredGames ?: $games;
$spotlight = reset($featuredGames) ?: reset($games);
$engineOrder = ['reaction','memory','pattern','runner','quiz','burst'];
$engineLabels = ['reaction'=>'Reaction','memory'=>'Memory','pattern'=>'Pattern','runner'=>'Runner','quiz'=>'Quiz','burst'=>'Burst'];
$engineIcons = ['reaction'=>'bi-lightning-charge','memory'=>'bi-grid-3x3-gap','pattern'=>'bi-soundwave','runner'=>'bi-signpost-split','quiz'=>'bi-patch-question','burst'=>'bi-bullseye'];
$engineCounts = [];
foreach ($engineOrder as $engine) {
    $engineCounts[$engine] = count(array_filter($games, fn($g) => ($g['type'] ?? '') === $engine));
}
include __DIR__ . '/includes/header.php';
?>
<section class="ugfx-page-hero ugfx-arcade-hero">
  <div class="container-xxl position-relative">
    <div class="row g-4 align-items-center">
      <div class="col-lg-7">
        <div class="ugfx-kicker"><i class="bi bi-controller"></i> Underground FX Arcade</div>
        <h1 class="page-title">Arcade mode is live</h1>
        <p class="page-copy">A full browser arcade with reaction games, pattern tests, memory boards, runner challenges, quiz sets, and leaderboard battles. Station staff can take any game on or offline without touching the rest of the site.</p>
        <div class="d-flex flex-wrap gap-2 mt-4">
          <span class="ugfx-chip ugfx-chip-blue"><?= $onlineGames ?> online games</span>
          <span class="ugfx-chip"><?= $totalGames ?> total games</span>
          <span class="ugfx-chip"><?= count($featuredGames) ?> spotlight picks</span>
        </div>
      </div>
      <div class="col-lg-5">
        <div class="ugfx-card p-4 p-lg-5 ugfx-arcade-spotlight">
          <div class="ugfx-micro-label">ARCADE SPOTLIGHT</div>
          <?php if ($spotlight): ?>
          <h2 class="mb-2"><?= htmlspecialchars($spotlight['name']) ?></h2>
          <p class="text-secondary mb-3"><?= htmlspecialchars($spotlight['description']) ?></p>
          <div class="d-flex gap-2 flex-wrap mb-4">
            <span class="ugfx-chip"><?= htmlspecialchars(ucfirst($spotlight['type'])) ?></span>
            <span class="ugfx-chip"><?= htmlspecialchars(ucfirst($spotlight['difficulty'])) ?></span>
            <span class="ugfx-chip ugfx-chip-blue">Top <?= (int)($allStats[$spotlight['id']]['top_score'] ?? 0) ?> <?= htmlspecialchars($spotlight['unit']) ?></span>
          </div>
          <button class="btn ugfx-btn-primary" type="button" data-quick-open="<?= htmlspecialchars($spotlight['id']) ?>">Play spotlight game</button>
          <?php else: ?>
          <h2 class="mb-2">Arcade standby</h2>
          <p class="text-secondary mb-0">The game page is live, but every game has been taken offline from the admin area.</p>
          <?php endif; ?>
        </div>
      </div>
    </div>
  </div>
</section>

<section class="section-pad pt-3">
  <div class="container-xxl">
    <?php if (!$games): ?>
      <div class="ugfx-card p-5 text-center">
        <div class="ugfx-micro-label">ARCADE OFFLINE</div>
        <h2 class="mb-3">No games are online right now</h2>
        <p class="text-secondary mb-0">The station team has temporarily taken the arcade offline. Check back later for the next competition drop.</p>
      </div>
    <?php else: ?>
    <div class="row g-4 mb-4">
      <div class="col-lg-8">
        <div class="ugfx-card p-4 p-lg-5">
          <div class="d-flex justify-content-between gap-3 flex-wrap align-items-start mb-4">
            <div>
              <div class="ugfx-micro-label">SELECT A GAME ENGINE</div>
              <h2 class="mb-2">Choose your challenge</h2>
              <div class="text-secondary">Start by choosing one of the six arcade engines, then pick from the games inside it. The page stays clean on first load instead of dumping the full arcade list at once.</div>
            </div>
            <div class="d-flex flex-wrap gap-2">
              <button class="btn ugfx-btn-outline" type="button" data-clear-engine>Show all engines</button>
            </div>
          </div>
          <div class="ugfx-engine-grid mb-4" id="engineGrid">
            <?php foreach ($engineOrder as $engine): ?>
            <button class="ugfx-engine-card" type="button" data-engine-card="<?= htmlspecialchars($engine) ?>">
              <div class="d-flex justify-content-between align-items-start gap-3 mb-3">
                <div>
                  <div class="ugfx-micro-label">ENGINE</div>
                  <h3 class="mb-1"><?= htmlspecialchars($engineLabels[$engine]) ?></h3>
                </div>
                <i class="bi <?= htmlspecialchars($engineIcons[$engine]) ?> ugfx-game-icon"></i>
              </div>
              <div class="text-secondary small mb-3"><?= (int)$engineCounts[$engine] ?> games live in this lane</div>
              <div class="d-flex flex-wrap gap-2">
                <span class="ugfx-chip"><?= htmlspecialchars($engineLabels[$engine]) ?></span>
                <span class="ugfx-chip ugfx-chip-blue"><?= (int)$engineCounts[$engine] ?> games</span>
              </div>
            </button>
            <?php endforeach; ?>
          </div>
          <div class="d-flex justify-content-between gap-3 flex-wrap align-items-start mb-3">
            <div>
              <div class="ugfx-micro-label">ENGINE GAMES</div>
              <h3 class="mb-1" id="engineTitle">Pick an engine above</h3>
              <div class="text-secondary small" id="engineCopy">Once you choose an engine, its games will load here.</div>
            </div>
          </div>
          <div class="ugfx-game-grid" id="gameGrid"></div>
          <div class="text-secondary small mt-3" id="gameGridEmpty">Choose one of the six engines above to load its games.</div>
        </div>
      </div>
      <div class="col-lg-4">
        <div class="ugfx-card p-4 h-100">
          <div class="ugfx-micro-label">ARCADE CONTROL ROOM</div>
          <h3 class="mb-3">What is live right now</h3>
          <div class="d-grid gap-3">
            <div class="ugfx-mini-stat"><strong><?= $onlineGames ?></strong><span>Games online</span></div>
            <div class="ugfx-mini-stat"><strong><?= count(array_filter($allStats, fn($s) => !empty($s['leaderboard_enabled']))) ?></strong><span>Leaderboards live</span></div>
            <div class="ugfx-mini-stat"><strong><?= array_sum(array_map(fn($s) => (int)($s['plays'] ?? 0), $allStats)) ?></strong><span>Total saved scores</span></div>
          </div>
          <div class="ugfx-divider my-4"></div>
          <div class="text-secondary small">Need a fresh competition round? The admin side can now reset one board, all boards, hide a game, or take the whole arcade offline without touching the page code.</div>
        </div>
      </div>
    </div>

    <div class="row g-4 align-items-start">
      <div class="col-xl-8">
        <div class="ugfx-card p-4 p-lg-5" id="arcadePlayShell">
          <div class="d-flex justify-content-between gap-3 flex-wrap align-items-start mb-3">
            <div>
              <div class="ugfx-micro-label" id="activeGameType">GAME ENGINE</div>
              <h2 id="activeGameTitle">Loading arcade…</h2>
              <p class="text-secondary mb-0" id="activeGameDesc"></p>
            </div>
            <div class="d-flex gap-2 flex-wrap" id="activeGameBadges"></div>
          </div>
          <div id="arcadeMount"></div>
        </div>
      </div>
      <div class="col-xl-4">
        <div class="ugfx-card p-4 mb-4">
          <div class="d-flex justify-content-between align-items-start gap-3 mb-3">
            <div>
              <div class="ugfx-micro-label">LEADERBOARD</div>
              <h3 class="mb-0" id="leaderboardTitle">Top players</h3>
            </div>
            <span class="ugfx-chip" id="leaderboardUnit">pts</span>
          </div>
          <div class="text-secondary small mb-3" id="leaderboardDesc">Loading scores…</div>
          <div id="leaderboardBox"><div class="text-secondary">Loading leaderboard…</div></div>
        </div>
        <div class="ugfx-card p-4">
          <div class="ugfx-micro-label">HOW SCORING WORKS</div>
          <h3 class="mb-2">One arcade, many modes</h3>
          <div class="text-secondary small">Every game stores its own leaderboard. Save as many runs as you want. The admin panel can instantly reset a single board, clear everything, or hide a game while you set up new station events.</div>
        </div>
      </div>
    </div>
    <?php endif; ?>
  </div>
</section>

<style>
.ugfx-arcade-hero{position:relative;overflow:hidden}
.ugfx-arcade-hero::after{content:"";position:absolute;inset:0;background:radial-gradient(circle at top right, rgba(255,123,0,.14), transparent 45%);pointer-events:none}
.ugfx-arcade-spotlight{min-height:100%}
.ugfx-engine-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}
.ugfx-game-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:16px}
.ugfx-engine-card,.ugfx-arcade-card{padding:22px;border-radius:26px;border:1px solid rgba(255,123,0,.14);background:linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,123,0,.05));text-align:left;color:#fff;transition:transform .18s ease, border-color .18s ease, box-shadow .18s ease}
.ugfx-engine-card:hover,.ugfx-engine-card.is-active,.ugfx-arcade-card:hover,.ugfx-arcade-card.is-active{transform:translateY(-2px);border-color:rgba(255,145,80,.42);box-shadow:0 24px 60px rgba(0,0,0,.25),0 0 0 1px rgba(255,145,80,.18) inset}
.ugfx-game-icon{font-size:1.75rem;color:#ff9a55}
.ugfx-mini-stat{display:flex;justify-content:space-between;align-items:center;padding:14px 16px;border:1px solid rgba(255,123,0,.14);border-radius:18px;background:rgba(255,255,255,.02)}
.ugfx-mini-stat strong{font-size:1.2rem}
.ugfx-mini-stat span{color:var(--ugfx-muted);font-size:.92rem}
.ugfx-divider{height:1px;background:rgba(255,255,255,.06)}
.ugfx-arcade-stage{border-radius:30px;border:1px solid rgba(255,123,0,.16);background:radial-gradient(circle at top, rgba(255,123,0,.16), rgba(8,9,18,.96));padding:24px;position:relative;overflow:hidden}
.ugfx-stage-shell{position:relative;min-height:340px;border-radius:26px;border:1px solid rgba(255,123,0,.18);background:rgba(6,7,16,.72);overflow:hidden}
.ugfx-stage-overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;text-align:center;padding:24px;color:#f8efe8;font-weight:700;letter-spacing:.04em;background:rgba(10,11,22,.12)}
.ugfx-target{position:absolute;width:72px;height:72px;border:none;border-radius:50%;background:radial-gradient(circle at 30% 30%, #ffd39d, #ff6a00 55%, #341300 100%);box-shadow:0 0 0 6px rgba(255,120,35,.14),0 0 36px rgba(255,120,35,.72);transform:translate(-50%,-50%)}
.ugfx-memory-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:14px}
.ugfx-memory-card{aspect-ratio:1/1;border-radius:22px;border:1px solid rgba(255,123,0,.15);background:linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,123,0,.08));color:#fff;font-size:1.6rem;font-weight:800}
.ugfx-memory-card.is-open,.ugfx-memory-card.is-matched{background:linear-gradient(180deg, rgba(255,123,0,.28), rgba(255,123,0,.12));box-shadow:0 0 0 1px rgba(255,145,80,.2) inset}
.ugfx-memory-card.is-matched{opacity:.82}
.ugfx-beat-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}
.ugfx-beat-pad{aspect-ratio:1/1;border-radius:26px;border:1px solid rgba(255,123,0,.15);background:linear-gradient(180deg, rgba(255,255,255,.04), rgba(255,123,0,.08));box-shadow:inset 0 0 0 1px rgba(255,255,255,.03)}
.ugfx-beat-pad.active{transform:scale(.985);box-shadow:0 0 0 2px rgba(255,182,114,.62) inset,0 0 34px rgba(255,120,35,.4)}
.ugfx-runner-track{display:grid;grid-template-columns:repeat(3,1fr);gap:12px;height:340px;padding:14px}
.ugfx-runner-lane{position:relative;border-radius:22px;background:linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,123,0,.05));overflow:hidden}
 .ugfx-runner-player,.ugfx-runner-block{position:absolute;left:50%;transform:translateX(-50%);width:56px;height:56px;border-radius:18px}
.ugfx-runner-player{bottom:14px;background:radial-gradient(circle at 30% 30%, #ffd39d, #ff6a00 60%, #3c1800 100%);box-shadow:0 0 30px rgba(255,123,0,.4)}
.ugfx-runner-block{top:-60px;background:linear-gradient(180deg, rgba(255,255,255,.85), rgba(255,255,255,.32))}
.ugfx-runner-controls{display:flex;gap:12px;justify-content:center;flex-wrap:wrap}
.ugfx-runner-controls button{min-width:110px}
.ugfx-quiz-answers{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}
.ugfx-quiz-answer{padding:16px;border-radius:18px;border:1px solid rgba(255,123,0,.15);background:rgba(255,255,255,.03);color:#fff;text-align:left}
.ugfx-burst-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:14px}
.ugfx-burst-pad{aspect-ratio:1/1;border-radius:22px;border:1px solid rgba(255,123,0,.15);background:linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,123,0,.05));opacity:.45}
.ugfx-burst-pad.live{opacity:1;box-shadow:0 0 0 2px rgba(255,182,114,.62) inset,0 0 32px rgba(255,123,0,.4)}
@media (max-width: 1199px){.ugfx-game-grid{grid-template-columns:repeat(2,minmax(0,1fr))}}
@media (max-width: 767px){.ugfx-game-grid,.ugfx-quiz-answers,.ugfx-memory-grid,.ugfx-burst-grid,.ugfx-beat-grid{grid-template-columns:1fr 1fr}.ugfx-stage-shell{min-height:280px}.ugfx-runner-track{height:280px}.ugfx-runner-controls{gap:8px}}
@media (max-width: 575px){.ugfx-game-grid,.ugfx-quiz-answers,.ugfx-memory-grid,.ugfx-burst-grid{grid-template-columns:1fr}.ugfx-beat-grid{grid-template-columns:1fr 1fr}}
</style>

<script>
(() => {
  const games = <?= json_encode(array_values($games), JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE) ?>;
  const lookup = Object.fromEntries(games.map(game => [game.id, game]));
  const stats = <?= json_encode($allStats, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE) ?>;
  const endpoint = 'api/games.php';
  const questionsByDifficulty = {
    easy:[
      {q:'Which page shows who is on air next?', a:['Schedule','News','Friends','Chat'], correct:0},
      {q:'What does a DJ usually do live on Underground FX?', a:['Hosts a show','Repairs browsers','Edits ZIP headers','Deletes streams'], correct:0},
      {q:'Which of these fits a radio request page?', a:['Song shout-out','Car tyre size','Tax form','Wi-Fi password'], correct:0},
      {q:'What does “live now” usually mean?', a:['Broadcast is active','Site is offline','No one is listening','Charts are empty'], correct:0},
      {q:'Which page would most likely list upcoming presenters?', a:['Schedule','Games','Friends','Join'], correct:0},
      {q:'What is the safest meaning of AutoDJ?', a:['Automated playout','Broken microphone','Offline website','Disabled requests'], correct:0},
      {q:'Which action belongs on a request page?', a:['Ask for a song','Reset server logs','Rename a PHP file','Move a DNS record'], correct:0},
      {q:'Which button would start listening fastest?', a:['Listen Live','Join','News','Contact'], correct:0},
      {q:'What does a leaderboard celebrate?', a:['Best scores','Largest image','Oldest browser','Longest URL'], correct:0},
      {q:'Which area is most likely to show station stories?', a:['News','Chat','Schedule','Games'], correct:0},
      {q:'Why do stations show a current show box?', a:['To tell listeners who is on air','To hide the logo','To disable requests','To rename the stream'], correct:0},
      {q:'Which item would fit a DJ profile best?', a:['Presenter photo','Server crash dump','Tax invoice','Router password'], correct:0},
    ],
    medium:[
      {q:'A popup player is mainly useful for what?', a:['Listening while browsing','Making coffee','Uploading PHP','Editing DNS'], correct:0},
      {q:'What does a leaderboard sort by?', a:['Highest score','Longest title','Newest browser','Biggest image'], correct:0},
      {q:'What does AutoDJ usually mean?', a:['Automated playout','Muted microphone','Broken CSS','Closed chat'], correct:0},
      {q:'Which area would fans use to submit a song request?', a:['Requests','Admin backup','Charts reset','404 page'], correct:0},
      {q:'Why can a browser audio player fail on a secure site?', a:['Mixed-content or HTTPS issues','Too many menu links','Large logos','A short DJ name'], correct:0},
      {q:'Which thing should update from live metadata?', a:['Current track title','Footer copyright year','Admin password','Theme color'], correct:0},
      {q:'What should a station site use when a live DJ is active?', a:['The live mount first','Only the oldest cache','A placeholder artist','The robots file'], correct:0},
      {q:'What makes a quiz leaderboard fairer?', a:['Separate boards per game','One board for everything','Alphabetical winners','No saved times'], correct:0},
      {q:'Why might a page briefly show the wrong DJ label?', a:['Cached API data','Too many images','A long news post','A missing favicon'], correct:0},
      {q:'What should happen when a game is taken offline in admin?', a:['New scores are blocked','The whole site crashes','The logo disappears','News posts delete themselves'], correct:0},
      {q:'Which feature helps a station arcade feel fresh?', a:['Different question pools','One repeated round','No score saving','A blank game shell'], correct:0},
      {q:'Why is a backup stream link useful?', a:['It gives listeners a fallback','It replaces the homepage','It edits metadata','It clears the cache'], correct:0},
    ],
    hard:[
      {q:'Which score should rank higher?', a:['Higher score, then newer time','Older name alphabetically','Shortest player name','Lowest browser memory'], correct:0},
      {q:'A cached API response can briefly show what?', a:['Old labels','More bass','Bigger logos','New DNS'], correct:0},
      {q:'What is the safest way to hide a game from players?', a:['Take it offline in admin','Delete the homepage','Rename CSS','Turn off images'], correct:0},
      {q:'What should a game API reject when a title is offline?', a:['New score submissions','Images','Schedule entries','Audio'], correct:0},
      {q:'Which source should win when a live mount is truly active?', a:['The live mount metadata','The AutoDJ fallback always','A random playlist row','The footer text'], correct:0},
      {q:'Why would two pages show different now-playing labels?', a:['They are using different source logic','The logo is too bright','Chat is disabled','The sitemap is wrong'], correct:0},
      {q:'What is the cleanest way to stop quiz repetition?', a:['Use larger pools and per-game sets','Use the same 4 questions','Remove the timer','Hide the answers'], correct:0},
      {q:'Why should a score API return fresh leaderboard rows?', a:['So the UI can update instantly','To enlarge the buttons','To change the background','To mute the stream'], correct:0},
      {q:'Which admin control is best for competitions?', a:['Reset one board without touching others','Delete all game code','Disable every page','Rename the domain'], correct:0},
      {q:'What is better than hardcoding one mount forever?', a:['Shared source-selection logic','Copying text by hand','Refreshing twice','Using placeholder titles'], correct:0},
      {q:'Why would a popup player need fallback links?', a:['To keep listening possible when one route fails','To replace the navbar','To hide the DJ name','To change the chart page'], correct:0},
      {q:'What makes one quiz board feel distinct from another?', a:['Its own themed question bank','The same 4 repeated prompts','A smaller heading','No leaderboard'], correct:0},
    ]
  };
  const quizPoolsByGame = {
    'quiz-blitz': [
      {q:'Which button should a new listener hit first?', a:['Listen Live','Contact','Friends','Join'], correct:0},
      {q:'What does a live pulse usually signal?', a:['Broadcast activity','Broken images','Offline admin','No schedule'], correct:0},
      {q:'Where would shout-outs normally go?', a:['Requests','Charts','Robots','Backups'], correct:0},
      {q:'Which word best fits a station homepage hero?', a:['Live','Compile','Archive-only','Hidden'], correct:0},
      {q:'What should the now-playing box show?', a:['Current song','Old password','ZIP filename','Visitor IP'], correct:0},
      {q:'Which area is meant for station updates?', a:['News','Games admin','Backups','404 logs'], correct:0},
      {q:'Why use a sticky player?', a:['Keep audio controls visible','Hide the header','Delete the cache','Reset scores'], correct:0},
      {q:'What usually belongs on a DJ card?', a:['Name and image','SQL errors','Token secrets','Zone IDs'], correct:0},
    ],
    'music-trivia': [
      {q:'Which of these is a country artist?', a:['Merle Haggard','Mozart','Da Vinci','Hawking'], correct:0},
      {q:'What does x_icy_title usually contain?', a:['Artist and song title','Admin username','Cache key','Listener IP'], correct:0},
      {q:'A playlist row usually shows what?', a:['Recent tracks','Chat bans','CSS variables','Meta tags'], correct:0},
      {q:'Which role fits a live presenter?', a:['Hosts the show','Writes DNS records','Compiles PHP','Resets routers'], correct:0},
      {q:'Why would listeners care about now playing?', a:['To know the current song','To see the sitemap','To inspect JSON','To rename pages'], correct:0},
      {q:'Which page title sounds music-focused?', a:['Vinyl Vault','Security Notes','Backup Export','Admin Users'], correct:0},
      {q:'What should a good music quiz avoid?', a:['Repeating the same few prompts','Having answers','Keeping score','Using timers'], correct:0},
      {q:'Which thing makes a music round feel fresher?', a:['Bigger question banks','Repeated question order','No variety','One fixed pool'], correct:0},
    ],
    'request-roulette': [
      {q:'Where do listeners normally ask for songs?', a:['Requests page','Sitemap file','Vendor folder','Error log'], correct:0},
      {q:'A request queue is most useful to whom?', a:['Presenters and admins','Search bots','CD drives','Printers'], correct:0},
      {q:'What is a shout-out?', a:['A message on air','A broken image','A cache file','A game reset'], correct:0},
      {q:'What should happen after a request is sent?', a:['It goes into the queue','The homepage deletes','The chart resets','The stream stops'], correct:0},
      {q:'Which page would likely include request controls?', a:['Admin requests','Games public','Friends page','Robots'], correct:0},
      {q:'Why might a request game use speed scoring?', a:['Fast answers feel like a live desk rush','To hide the timer','To remove leaderboards','To disable names'], correct:0},
      {q:'Which label best fits a request-heavy round?', a:['Quickfire','Offline only','Silent mode','Hidden config'], correct:0},
      {q:'What keeps a request-themed quiz from going stale?', a:['Lots of different prompts','The same four questions','No answer buttons','Static text'], correct:0},
    ],
    'station-smarts': [
      {q:'Why should source-selection logic be shared?', a:['So all pages agree on the current track','To enlarge the logo','To hide games','To change the footer'], correct:0},
      {q:'What is safer than hardcoding one old fallback label?', a:['Recomputing from live data','Guessing from memory','Using a placeholder forever','Copying text manually'], correct:0},
      {q:'Which page is most likely to need live metadata?', a:['Homepage now-playing section','Robots.txt','Manifest icons only','Vendor README'], correct:0},
      {q:'Why can a stale cache confuse listeners?', a:['It shows old songs or DJs','It increases image quality','It shortens URLs','It speeds up typing'], correct:0},
      {q:'Which admin action is best before a new tournament?', a:['Reset a single board','Delete every game','Hide the homepage','Remove CSS'], correct:0},
      {q:'What makes a hard quiz board feel premium?', a:['Distinct themed questions','Repeated filler','No timer','No scoring'], correct:0},
      {q:'When live /stream is active, which track should win?', a:['The live stream track','AutoDJ by default','The oldest playlist row','The next scheduled title'], correct:0},
      {q:'Which thing should stay writable for saved scores?', a:['The data storage path','The favicon PNG','The robots file','The header logo'], correct:0},
    ],
    'airwave-academy': [
      {q:'What is a browser game leaderboard for?', a:['Tracking top scores','Streaming audio','Posting news','Editing pages'], correct:0},
      {q:'What does a schedule page normally show?', a:['Who is on and when','Server certificates','Game code','File backups'], correct:0},
      {q:'What might a popup player let you do?', a:['Listen while browsing','Reset chat','Edit admins','Delete scores'], correct:0},
      {q:'Why show listener counts?', a:['It reflects live audience activity','It fixes images','It changes colors','It edits metadata'], correct:0},
      {q:'What belongs on a team page?', a:['Presenter profiles','Cache dumps','Cron output','ZIP checksums'], correct:0},
      {q:'What should an arcade admin page be able to do?', a:['Take games offline','Delete the stream host','Change the DNS provider','Hide the schedule forever'], correct:0},
      {q:'Which phrase best fits a station arcade?', a:['Play, score, repeat','Archive, delete, reboot','Compile, deploy, restart','Mount, bind, proxy'], correct:0},
      {q:'Why would a station add games?', a:['Fun and engagement','To replace music','To remove requests','To hide events'], correct:0},
    ],
    'vinyl-vault': [
      {q:'Which game name sounds record-themed?', a:['Vinyl Vault','Signal Smash','Booth Breakout','Night Drive'], correct:0},
      {q:'What does crate digging relate to?', a:['Searching records','Editing charts','Testing CSS','Clearing caches'], correct:0},
      {q:'Which detail helps a themed quiz feel different?', a:['Record-room questions','The same exact prompts','No title','No timer'], correct:0},
      {q:'Why use per-game question banks?', a:['To give each quiz its own flavour','To duplicate every round','To remove scores','To break the API'], correct:0},
      {q:'What would fit a vinyl-themed leaderboard subtitle?', a:['Record-room challenge','Error log review','Token export','SSL repair'], correct:0},
      {q:'Which item belongs to music culture most?', a:['Albums and tracks','Router ports','Apache modules','PHP notices'], correct:0},
      {q:'Why is variety important in quizzes?', a:['Players notice repeats fast','It changes DNS','It reduces images','It hides buttons'], correct:0},
      {q:'Which storage idea helps keep a game fresh?', a:['Large prompt pools','One fixed prompt list','No data files','Hardcoded winners'], correct:0},
    ],
    'dj-decision': [
      {q:'What should a DJ-facing game reward?', a:['Fast correct choices','Longest username','Most browser tabs','Biggest screenshot'], correct:0},
      {q:'Which live detail matters on air?', a:['Current track and current show','Vendor folder size','Sitemap date only','Image EXIF'], correct:0},
      {q:'Why might “AutoDJ” be wrong sometimes?', a:['A live DJ is actually on schedule','The logo changed','A quiz ended','CSS loaded'], correct:0},
      {q:'What should replace an old DJ label?', a:['The real scheduled presenter','A random fallback','The admin username','The request count'], correct:0},
      {q:'What is a good quiz trait for repeat visits?', a:['Different prompts each round','Identical order every time','No save option','No timer'], correct:0},
      {q:'Which page likely benefits from consistent labels?', a:['Homepage and sticky player','Robots only','Manifest only','404 only'], correct:0},
      {q:'What helps score saving feel immediate?', a:['Instant leaderboard refresh','A page reload requirement','Removing names','Disabling totals'], correct:0},
      {q:'What does “decision” imply in a quiz?', a:['Quick choices under pressure','Manual file editing','Chart exporting','Changing domains'], correct:0},
    ],
    'encore-expert': [
      {q:'Which logic bug is hardest for users to trust?', a:['Wrong now-playing info','Rounded corners','Chip spacing','Button labels'], correct:0},
      {q:'What should happen if /stream has a real title?', a:['It overrides AutoDJ metadata','It gets ignored','The page hides it','The chart resets'], correct:0},
      {q:'Why can a single cached label be dangerous?', a:['It appears across multiple widgets','It changes your password','It deletes images','It renames files'], correct:0},
      {q:'What keeps an expert quiz worth replaying?', a:['Large themed banks and randomization','The same easy set','No timer','No score saving'], correct:0},
      {q:'Which board should admins reset before a special event?', a:['Only the event game board','Every page on the site','The sitemap','The manifest'], correct:0},
      {q:'What is best for a live metadata parser?', a:['Clear mount priority','A hardcoded artist','One old cache','Static text'], correct:0},
      {q:'When a game is offline, what should the API do?', a:['Reject new submissions','Fake success','Delete data','Hide the navbar'], correct:0},
      {q:'Why are per-game variants useful in an arcade?', a:['Less repetition and more replay value','Smaller logos','More 500 errors','Fewer controls'], correct:0},
    ],
    'request-desk-masters': [
      {q:'What should a request desk admin see?', a:['Queues and status controls','Logo gradients only','Sitemap links only','Manifest icons'], correct:0},
      {q:'What makes a long-form quiz fun?', a:['Different topics and deeper pools','One repeated prompt','No score saving','Static headings'], correct:0},
      {q:'Why should a station arcade have many pools?', a:['Players replay often','It changes robots.txt','It fixes SSL','It loads fonts faster'], correct:0},
      {q:'Which thing helps a big quiz feel master-level?', a:['Distinct themes across boards','Copy-paste repeats','No variation','No branding'], correct:0},
      {q:'What does a save score action need most?', a:['A game id and a score','A favicon','A CSS map','A chart image'], correct:0},
      {q:'What should stay separate per game?', a:['Leaderboard entries','Header include','Site URL','Footer logo'], correct:0},
      {q:'How do you avoid stale challenge boards?', a:['Refresh the prompt pool and randomize it','Reuse the same four lines','Remove admin controls','Hide the game page'], correct:0},
      {q:'What should a request-themed master quiz feel like?', a:['Busy and varied like a live desk','Static and repeated','Silent and blank','Offline and hidden'], correct:0},
    ],
  };
  const memoryThemesByGame = {
    'memory-match':['🎙️','🔥','🎧','📻','🎚️','⭐'],
    'vinyl-match':['💿','📀','🎵','🪩','🎚️','🎤','📻','⭐'],
    'request-match':['📩','💌','🎶','📻','🎧','🎤','🔥','⭐'],
    'late-night-match':['🌙','✨','🎙️','🎧','🌃','📻','🎶','💿','🎤','🔥'],
    'backstage-pairs':['🎫','🎟️','🎤','🎚️','🔦','⭐'],
    'crate-digger':['📦','💿','📀','🎶','🎵','🪩','🎤','📻'],
    'signal-pairs':['📡','📻','🔊','🎚️','⚡','🎧','🔥','⭐','🎶','🎤'],
    'golden-grooves':['🏆','💎','💿','🎵','🎧','📻','⭐','🔥','🎙️','🎤'],
  };
  function hashString(str){ let h=0; for(let i=0;i<str.length;i++){ h=((h<<5)-h)+str.charCodeAt(i); h|=0; } return Math.abs(h); }
  function rotateList(list, shift){ if(!list.length) return []; const offset = shift % list.length; return list.slice(offset).concat(list.slice(0, offset)); }
  function shuffledQuestion(question){
    const answers = question.a.map((text, idx) => ({text, idx}));
    const mixed = shuffle(answers);
    return {q:question.q, a:mixed.map(item => item.text), correct:mixed.findIndex(item => item.idx === question.correct)};
  }
  const mount = document.getElementById('arcadeMount');
  const boardBox = document.getElementById('leaderboardBox');
  const boardTitle = document.getElementById('leaderboardTitle');
  const boardDesc = document.getElementById('leaderboardDesc');
  const boardUnit = document.getElementById('leaderboardUnit');
  const activeTitle = document.getElementById('activeGameTitle');
  const activeDesc = document.getElementById('activeGameDesc');
  const activeType = document.getElementById('activeGameType');
  const activeBadges = document.getElementById('activeGameBadges');
  const gameGrid = document.getElementById('gameGrid');
  const gameGridEmpty = document.getElementById('gameGridEmpty');
  const engineTitle = document.getElementById('engineTitle');
  const engineCopy = document.getElementById('engineCopy');
  const engineButtons = Array.from(document.querySelectorAll('[data-engine-card]'));
  const leaderboards = {};
  let activeGame = games[0] || null;
  let activeEngine = null;
  let cleanup = null;

  const arcadeName = () => localStorage.getItem('ugfx_arcade_name') || '';
  function askName(){
    const raw = prompt('Name for the leaderboard:', arcadeName());
    if (raw === null) return null;
    const clean = (raw.trim() || 'Guest').slice(0, 32);
    localStorage.setItem('ugfx_arcade_name', clean);
    return clean;
  }
  function escapeHtml(str){return String(str).replace(/[&<>"']/g, s => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[s]));}
  function shuffle(list){return [...list].sort(() => Math.random() - 0.5);}
  function randomRange(min,max){return Math.floor(Math.random()*(max-min+1))+min;}
  function activeCards(gameId){Array.from(document.querySelectorAll('[data-game-card]')).forEach(card => card.classList.toggle('is-active', card.dataset.gameCard === gameId));}
  function activeEngines(type){engineButtons.forEach(card => card.classList.toggle('is-active', card.dataset.engineCard === type));}
  function renderGameCards(type){
    activeEngine = type;
    activeEngines(type);
    const filtered = games.filter(game => game.type === type);
    engineTitle.textContent = `${type.charAt(0).toUpperCase()+type.slice(1)} engine`;
    engineCopy.textContent = `${filtered.length} games live in this engine. Pick one to load it below.`;
    gameGrid.innerHTML = filtered.map(game => `
      <button class="ugfx-arcade-card ${activeGame?.id === game.id ? 'is-active' : ''}" type="button" data-game-card="${escapeHtml(game.id)}" data-type="${escapeHtml(game.type)}">
        <div class="d-flex justify-content-between align-items-start gap-3 mb-3">
          <div>
            <div class="ugfx-micro-label">${escapeHtml(game.difficulty.toUpperCase())}</div>
            <h3 class="mb-1">${escapeHtml(game.name)}</h3>
          </div>
          <i class="bi ${escapeHtml(game.icon)} ugfx-game-icon"></i>
        </div>
        <div class="text-secondary small mb-3">${escapeHtml(game.tagline)}</div>
        <div class="d-flex flex-wrap gap-2">
          <span class="ugfx-chip">${escapeHtml(game.type.charAt(0).toUpperCase()+game.type.slice(1))}</span>
          <span class="ugfx-chip ugfx-chip-blue">Top ${Number(stats[game.id]?.top_score || 0)}</span>
        </div>
      </button>`).join('');
    gameGridEmpty.style.display = filtered.length ? 'none' : '';
    gameGrid.querySelectorAll('[data-game-card]').forEach(card => card.addEventListener('click', () => activate(card.dataset.gameCard)));
  }
  function clearGameCards(){
    activeEngine = null;
    activeEngines(null);
    engineTitle.textContent = 'Pick an engine above';
    engineCopy.textContent = 'Once you choose an engine, its games will load here.';
    gameGrid.innerHTML = '';
    gameGridEmpty.style.display = '';
  }
  function setBadges(game){
    activeBadges.innerHTML = `
      <span class="ugfx-chip">${escapeHtml(game.type)}</span>
      <span class="ugfx-chip">${escapeHtml(game.difficulty)}</span>
      <span class="ugfx-chip ugfx-chip-blue">Top ${Number(stats[game.id]?.top_score || 0)} ${escapeHtml(game.unit)}</span>`;
  }
  async function loadBoards(){
    const res = await fetch(endpoint, {cache:'no-store'});
    const data = await res.json();
    Object.assign(leaderboards, data.leaderboards || {});
    renderBoard(activeGame?.id);
  }
  function renderBoard(gameId){
    const game = lookup[gameId];
    if (!game) return;
    boardTitle.textContent = game.name + ' leaderboard';
    boardUnit.textContent = game.unit;
    boardDesc.textContent = game.description;
    const rows = leaderboards[gameId] || [];
    if (!rows.length) {
      boardBox.innerHTML = '<div class="text-secondary">No scores saved yet. Jump in and claim the board.</div>';
      return;
    }
    boardBox.innerHTML = rows.slice(0,10).map((row, index) => `<div class="d-flex justify-content-between align-items-center border rounded-4 p-3 mb-2"><div><div class="fw-semibold">#${index+1} ${escapeHtml(row.name || 'Guest')}</div><div class="text-secondary small">${escapeHtml(row.submitted_at || '')}</div></div><span class="ugfx-chip ugfx-chip-blue">${Number(row.score || 0)}</span></div>`).join('');
  }
  async function submitScore(game, score, extra = {}){
    const name = askName();
    if (name === null) return;
    const res = await fetch(endpoint, {method:'POST', headers:{'Content-Type':'application/json'}, body:JSON.stringify({action:'submit', game: game.id, name, score, extra})});
    const data = await res.json();
    if (!data.ok) { alert(data.message || 'Could not save score.'); return; }
    leaderboards[game.id] = data.leaderboard || [];
    stats[game.id] = Object.assign({}, stats[game.id] || {}, {top_score: Math.max(Number(stats[game.id]?.top_score || 0), Number(score || 0)), plays: Number(stats[game.id]?.plays || 0) + 1});
    renderBoard(game.id);
    setBadges(game);
    const card = document.querySelector(`[data-game-card="${game.id}"] .ugfx-chip-blue`);
    if (card) card.textContent = `Top ${Number(stats[game.id]?.top_score || 0)}`;
    alert('Score saved.');
  }

  function mountReaction(game){
    const variant = hashString(game.id);
    const duration = game.difficulty === 'easy' ? 20 + (variant % 3) : game.difficulty === 'medium' ? 18 + (variant % 2) : 16 + (variant % 2);
    const pace = game.difficulty === 'easy' ? 1 + (variant % 2) : game.difficulty === 'medium' ? 2 + (variant % 2) : 3 + (variant % 2);
    const targetSize = game.difficulty === 'hard' ? 62 - (variant % 6) : game.difficulty === 'medium' ? 68 - (variant % 4) : 72 - (variant % 2);
    mount.innerHTML = `<div class="ugfx-arcade-stage"><div class="d-flex justify-content-between flex-wrap gap-2 mb-3"><span class="ugfx-chip">Time: <strong id="rgTime">${duration}</strong>s</span><span class="ugfx-chip ugfx-chip-blue">Score: <strong id="rgScore">0</strong></span></div><div class="ugfx-stage-shell" id="reactionShell"><button class="ugfx-target d-none" id="reactionTarget" type="button"></button><div class="ugfx-stage-overlay" id="reactionOverlay">Press start to begin</div></div><div class="d-flex gap-2 flex-wrap mt-3"><button class="btn ugfx-btn-primary" id="reactionStart" type="button">Start round</button><button class="btn ugfx-btn-outline" id="reactionSave" type="button" disabled>Save score</button></div></div>`;
    const shell = document.getElementById('reactionShell'); const target = document.getElementById('reactionTarget'); const overlay = document.getElementById('reactionOverlay'); const timeEl = document.getElementById('rgTime'); const scoreEl = document.getElementById('rgScore'); const saveBtn = document.getElementById('reactionSave'); target.style.width = `${targetSize}px`; target.style.height = `${targetSize}px`; overlay.textContent = `Press start to begin ${game.name}`;
    let score = 0, time = duration, timer = null, running = false;
    function move(){ const rect = shell.getBoundingClientRect(); target.style.left = `${randomRange(44, Math.max(44, rect.width - 44))}px`; target.style.top = `${randomRange(44, Math.max(44, rect.height - 44))}px`; }
    document.getElementById('reactionStart').onclick = () => { clearInterval(timer); score = 0; time = duration; running = true; scoreEl.textContent='0'; timeEl.textContent=String(duration); target.classList.remove('d-none'); overlay.textContent=''; saveBtn.disabled = true; move(); timer = setInterval(() => { time--; timeEl.textContent=String(time); if (time <= 0) { clearInterval(timer); running = false; target.classList.add('d-none'); overlay.textContent = `Round over — ${score} points`; saveBtn.disabled = score <= 0; } }, 1000); };
    target.onclick = () => { if (!running) return; score += pace; scoreEl.textContent = String(score); move(); };
    saveBtn.onclick = () => submitScore(game, score, {duration});
    return () => clearInterval(timer);
  }

  function mountMemory(game){
    const difficultyMap = {easy:60, medium:55, hard:50};
    const icons = memoryThemesByGame[game.id] || memorySets[game.difficulty] || memorySets.easy;
    const columns = icons.length >= 10 ? 5 : icons.length >= 8 ? 4 : 3;
    mount.innerHTML = `<div class="ugfx-arcade-stage"><div class="d-flex justify-content-between flex-wrap gap-2 mb-3"><span class="ugfx-chip">Time: <strong id="mmTime">${difficultyMap[game.difficulty]}</strong>s</span><span class="ugfx-chip ugfx-chip-blue">Moves: <strong id="mmMoves">0</strong></span></div><div class="ugfx-memory-grid" id="memoryGrid"></div><div class="d-flex gap-2 flex-wrap mt-3"><button class="btn ugfx-btn-primary" id="memoryStart" type="button">New board</button><button class="btn ugfx-btn-outline" id="memorySave" type="button" disabled>Save score</button></div></div>`;
    const grid = document.getElementById('memoryGrid'); const timeEl = document.getElementById('mmTime'); const movesEl = document.getElementById('mmMoves'); const saveBtn = document.getElementById('memorySave'); grid.style.gridTemplateColumns = `repeat(${columns}, minmax(0,1fr))`;
    let cards = [], open = [], matched = 0, moves = 0, timer = null, time = difficultyMap[game.difficulty], score = 0;
    function build(){ clearInterval(timer); open=[]; matched=0; moves=0; time=difficultyMap[game.difficulty]; score=0; timeEl.textContent=String(time); movesEl.textContent='0'; saveBtn.disabled=true; cards = shuffle([...icons, ...icons]).map((icon, index) => ({icon, index, open:false, matched:false})); render(); timer = setInterval(() => { time--; timeEl.textContent=String(time); if (time <= 0) { clearInterval(timer); score = Math.max(0, matched * 20 - moves); saveBtn.disabled = matched !== cards.length / 2; } }, 1000); }
    function render(){ grid.innerHTML = cards.map(card => `<button class="ugfx-memory-card ${card.open?'is-open':''} ${card.matched?'is-matched':''}" data-index="${card.index}" type="button">${card.open || card.matched ? escapeHtml(card.icon) : '?'}</button>`).join(''); grid.querySelectorAll('[data-index]').forEach(btn => btn.addEventListener('click', onFlip)); }
    function onFlip(e){ const idx = Number(e.currentTarget.dataset.index); const card = cards.find(c => c.index === idx); if (!card || card.open || card.matched || open.length >= 2 || time <= 0) return; card.open = true; open.push(card); render(); if (open.length === 2) { moves++; movesEl.textContent = String(moves); const [a,b] = open; if (a.icon === b.icon) { a.matched = b.matched = true; matched++; open=[]; render(); if (matched === cards.length/2) { clearInterval(timer); score = Math.max(50, time * 10 - moves * 2 + matched * 15); saveBtn.disabled = false; } } else { setTimeout(() => { a.open = false; b.open = false; open=[]; render(); }, 700); } } }
    document.getElementById('memoryStart').onclick = build; saveBtn.onclick = () => submitScore(game, score, {moves, time_left: time}); build();
    return () => clearInterval(timer);
  }

  function mountPattern(game){
    const speed = game.difficulty === 'easy' ? 650 : game.difficulty === 'medium' ? 520 : 420;
    mount.innerHTML = `<div class="ugfx-arcade-stage"><div class="d-flex justify-content-between flex-wrap gap-2 mb-3"><span class="ugfx-chip">Round: <strong id="pmRound">0</strong></span><span class="ugfx-chip ugfx-chip-blue">Best run: <strong id="pmBest">0</strong></span></div><div class="ugfx-beat-grid" id="patternGrid">${[0,1,2,3].map(i=>`<button class="ugfx-beat-pad" data-pad="${i}" type="button"></button>`).join('')}</div><div class="d-flex gap-2 flex-wrap mt-3"><button class="btn ugfx-btn-primary" id="patternStart" type="button">Start set</button><button class="btn ugfx-btn-outline" id="patternSave" type="button" disabled>Save score</button></div></div>`;
    const pads = Array.from(document.querySelectorAll('.ugfx-beat-pad')); const roundEl = document.getElementById('pmRound'); const bestEl = document.getElementById('pmBest'); const saveBtn = document.getElementById('patternSave');
    let seq = [], input = [], round = 0, best = 0, playingBack = false, playbackTimer = null;
    function light(index){ pads[index]?.classList.add('active'); setTimeout(() => pads[index]?.classList.remove('active'), Math.min(260, speed-120)); }
    function nextRound(){ input=[]; round++; best=Math.max(best, round-1); roundEl.textContent=String(round); bestEl.textContent=String(best); seq.push(randomRange(0,3)); playingBack=true; let i=0; clearInterval(playbackTimer); playbackTimer=setInterval(() => { light(seq[i]); i++; if (i >= seq.length) { clearInterval(playbackTimer); setTimeout(() => { playingBack=false; }, speed); } }, speed); }
    function fail(){ playingBack=true; saveBtn.disabled = best <= 0; }
    pads.forEach(pad => pad.onclick = () => { if (playingBack) return; const idx = Number(pad.dataset.pad); input.push(idx); light(idx); const pos = input.length - 1; if (seq[pos] !== idx) { fail(); return; } if (input.length === seq.length) { setTimeout(nextRound, 500); } });
    document.getElementById('patternStart').onclick = () => { seq=[]; input=[]; round=0; best=0; roundEl.textContent='0'; bestEl.textContent='0'; saveBtn.disabled=true; nextRound(); };
    saveBtn.onclick = () => submitScore(game, best, {round: best});
    return () => clearInterval(playbackTimer);
  }

  function mountRunner(game){
    const spawnRate = game.difficulty === 'easy' ? 900 : game.difficulty === 'medium' ? 700 : 550;
    const fallStep = game.difficulty === 'easy' ? 7 : game.difficulty === 'medium' ? 8.5 : 10;
    mount.innerHTML = `<div class="ugfx-arcade-stage"><div class="d-flex justify-content-between flex-wrap gap-2 mb-3"><span class="ugfx-chip">Survival: <strong id="rnTime">0</strong>s</span><span class="ugfx-chip ugfx-chip-blue">Lane: <strong id="rnLane">2</strong></span></div><div class="ugfx-stage-shell"><div class="ugfx-runner-track" id="runnerTrack">${[0,1,2].map(i=>`<div class="ugfx-runner-lane" data-lane="${i}"></div>`).join('')}</div></div><div class="ugfx-runner-controls mt-3"><button class="btn ugfx-btn-outline" id="runnerLeft" type="button">Move left</button><button class="btn ugfx-btn-primary" id="runnerStart" type="button">Start run</button><button class="btn ugfx-btn-outline" id="runnerRight" type="button">Move right</button><button class="btn ugfx-btn-outline" id="runnerSave" type="button" disabled>Save score</button></div></div>`;
    const track = document.getElementById('runnerTrack'); const lanes = Array.from(track.querySelectorAll('.ugfx-runner-lane')); const timeEl = document.getElementById('rnTime'); const laneEl = document.getElementById('rnLane'); const saveBtn = document.getElementById('runnerSave');
    let lane = 1, blocks = [], running = false, score = 0, interval = null, spawnTimer = null, player = document.createElement('div'); player.className='ugfx-runner-player';
    function renderPlayer(){ laneEl.textContent=String(lane+1); lanes.forEach((laneEl, idx) => { if (idx === lane && !laneEl.contains(player)) laneEl.appendChild(player); }); }
    function spawnBlock(){ const laneIdx = randomRange(0,2); const block = document.createElement('div'); block.className='ugfx-runner-block'; block.dataset.y='-60'; lanes[laneIdx].appendChild(block); blocks.push({lane: laneIdx, el: block, y: -60}); }
    function endRun(){ running=false; clearInterval(interval); clearInterval(spawnTimer); saveBtn.disabled = score <= 0; }
    function tick(){ if (!running) return; score += 0.1; timeEl.textContent = String(Math.floor(score)); blocks.forEach(block => { block.y += fallStep; block.el.style.top = `${block.y}px`; if (block.y > 250 && block.lane === lane) endRun(); }); blocks = blocks.filter(block => { if (block.y > 360) { block.el.remove(); return false; } return true; }); }
    document.getElementById('runnerStart').onclick = () => { blocks.forEach(b => b.el.remove()); blocks=[]; score=0; lane=1; renderPlayer(); saveBtn.disabled=true; timeEl.textContent='0'; running=true; clearInterval(interval); clearInterval(spawnTimer); interval=setInterval(tick, 50); spawnTimer=setInterval(spawnBlock, spawnRate); };
    document.getElementById('runnerLeft').onclick = () => { if (!running) return; lane = Math.max(0, lane-1); renderPlayer(); };
    document.getElementById('runnerRight').onclick = () => { if (!running) return; lane = Math.min(2, lane+1); renderPlayer(); };
    saveBtn.onclick = () => submitScore(game, Math.floor(score), {duration: Math.floor(score)});
    window.addEventListener('keydown', keyHandler = (e) => { if (e.key === 'ArrowLeft') document.getElementById('runnerLeft').click(); if (e.key === 'ArrowRight') document.getElementById('runnerRight').click(); });
    renderPlayer();
    return () => { clearInterval(interval); clearInterval(spawnTimer); window.removeEventListener('keydown', keyHandler); };
  }

  function mountQuiz(game){
    const basePool = quizPoolsByGame[game.id] || questionsByDifficulty[game.difficulty] || questionsByDifficulty.easy;
    const rotatedPool = rotateList(basePool, hashString(game.id + ':' + new Date().toDateString()));
    const questionCount = Math.min(game.difficulty === 'hard' ? 7 : 6, rotatedPool.length);
    const pool = shuffle(rotatedPool).slice(0, questionCount).map(shuffledQuestion);
    const duration = game.difficulty === 'easy' ? 60 : game.difficulty === 'medium' ? 50 : 42;
    mount.innerHTML = `<div class="ugfx-arcade-stage"><div class="d-flex justify-content-between flex-wrap gap-2 mb-3"><span class="ugfx-chip">Time: <strong id="qzTime">${duration}</strong>s</span><span class="ugfx-chip ugfx-chip-blue">Score: <strong id="qzScore">0</strong></span></div><div class="ugfx-stage-shell p-4 d-flex flex-column justify-content-between"><div><div class="ugfx-micro-label">QUESTION</div><h3 class="mb-3" id="quizQuestion"></h3><div class="ugfx-quiz-answers" id="quizAnswers"></div></div><div class="d-flex gap-2 flex-wrap mt-4"><button class="btn ugfx-btn-primary" type="button" id="quizStart">Start quiz</button><button class="btn ugfx-btn-outline" type="button" id="quizSave" disabled>Save score</button></div></div></div>`;
    const timeEl = document.getElementById('qzTime'); const scoreEl = document.getElementById('qzScore'); const qEl = document.getElementById('quizQuestion'); const answersEl = document.getElementById('quizAnswers'); const saveBtn = document.getElementById('quizSave');
    let index = 0, score = 0, timer = null, time = duration, running = false;
    function end(){ running=false; clearInterval(timer); saveBtn.disabled = score <= 0; }
    function render(){ const current = pool[index]; if (!current) { qEl.textContent='Round complete'; answersEl.innerHTML=''; end(); return; } qEl.textContent = current.q; answersEl.innerHTML = current.a.map((ans, idx) => `<button class="ugfx-quiz-answer" data-answer="${idx}" type="button">${escapeHtml(ans)}</button>`).join(''); answersEl.querySelectorAll('[data-answer]').forEach(btn => btn.onclick = () => { if (!running) return; if (Number(btn.dataset.answer) === current.correct) score += Math.max(10, time); scoreEl.textContent = String(score); index++; render(); }); }
    document.getElementById('quizStart').onclick = () => { index=0; score=0; time=duration; running=true; scoreEl.textContent='0'; timeEl.textContent=String(time); saveBtn.disabled=true; render(); clearInterval(timer); timer=setInterval(() => { time--; timeEl.textContent=String(time); if (time <= 0) end(); }, 1000); };
    saveBtn.onclick = () => submitScore(game, score, {correct: index});
    return () => clearInterval(timer);
  }

  function mountBurst(game){
    const variant = hashString(game.id);
    const totalPads = game.difficulty === 'hard' ? (variant % 2 ? 12 : 9) : game.difficulty === 'medium' ? (variant % 3 === 0 ? 12 : 9) : 9;
    const liveCount = game.difficulty === 'easy' ? 2 + (variant % 2) : game.difficulty === 'medium' ? 3 + (variant % 2) : 4 + (variant % 2);
    const duration = game.difficulty === 'easy' ? 25 : game.difficulty === 'medium' ? 22 : 18;
    mount.innerHTML = `<div class="ugfx-arcade-stage"><div class="d-flex justify-content-between flex-wrap gap-2 mb-3"><span class="ugfx-chip">Time: <strong id="btTime">${duration}</strong>s</span><span class="ugfx-chip ugfx-chip-blue">Score: <strong id="btScore">0</strong></span></div><div class="ugfx-burst-grid" id="burstGrid">${Array.from({length:totalPads}, (_,i) => `<button class="ugfx-burst-pad" data-pad="${i}" type="button"></button>`).join('')}</div><div class="d-flex gap-2 flex-wrap mt-3"><button class="btn ugfx-btn-primary" type="button" id="burstStart">Start wave</button><button class="btn ugfx-btn-outline" type="button" id="burstSave" disabled>Save score</button></div></div>`;
    const pads = Array.from(document.querySelectorAll('.ugfx-burst-pad')); const timeEl = document.getElementById('btTime'); const scoreEl = document.getElementById('btScore'); const saveBtn = document.getElementById('burstSave'); document.getElementById('burstGrid').style.gridTemplateColumns = totalPads > 9 ? 'repeat(4,minmax(0,1fr))' : 'repeat(3,minmax(0,1fr))';
    let timer = null, waveTimer = null, running = false, time = duration, score = 0, combo = 0, live = new Set();
    function setLive(){ live.clear(); shuffle(pads).slice(0, liveCount).forEach((pad, idx) => { live.add(Number(pad.dataset.pad)); pad.classList.add('live'); }); pads.forEach(p => { if (!live.has(Number(p.dataset.pad))) p.classList.remove('live'); }); }
    document.getElementById('burstStart').onclick = () => { clearInterval(timer); clearInterval(waveTimer); running=true; time=duration; score=0; combo=0; scoreEl.textContent='0'; timeEl.textContent=String(time); saveBtn.disabled=true; setLive(); timer=setInterval(() => { time--; timeEl.textContent=String(time); if (time <= 0) { clearInterval(timer); clearInterval(waveTimer); running=false; pads.forEach(p => p.classList.remove('live')); saveBtn.disabled = score <= 0; } }, 1000); waveTimer=setInterval(setLive, game.difficulty === 'hard' ? 650 : 850); };
    pads.forEach(pad => pad.onclick = () => { if (!running) return; const id = Number(pad.dataset.pad); if (!live.has(id)) { combo = 0; return; } combo += 1; score += 5 * combo; scoreEl.textContent = String(score); pad.classList.remove('live'); live.delete(id); if (!live.size) setLive(); });
    saveBtn.onclick = () => submitScore(game, score, {combo});
    return () => { clearInterval(timer); clearInterval(waveTimer); };
  }

  function activate(gameId){
    const game = lookup[gameId]; if (!game) return;
    activeGame = game; activeCards(gameId); activeType.textContent = `${game.type.toUpperCase()} ENGINE`; activeTitle.textContent = game.name; activeDesc.textContent = game.description; setBadges(game); renderBoard(game.id); if (cleanup) cleanup();
    cleanup = ({reaction:mountReaction,memory:mountMemory,pattern:mountPattern,runner:mountRunner,quiz:mountQuiz,burst:mountBurst}[game.type] || mountReaction)(game);
  }

  engineButtons.forEach(btn => btn.addEventListener('click', () => renderGameCards(btn.dataset.engineCard)));
  document.querySelector('[data-clear-engine]')?.addEventListener('click', clearGameCards);
  document.querySelectorAll('[data-quick-open]').forEach(btn => btn.addEventListener('click', () => { const game = lookup[btn.dataset.quickOpen]; if (game) { renderGameCards(game.type); activate(game.id); } }));
  loadBoards().then(() => { clearGameCards(); if (activeGame) activate(activeGame.id); });
})();
</script>
<?php include __DIR__ . '/includes/footer.php'; ?>
