<?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','card','dice','mahjong'];
$engineLabels = ['reaction'=>'Reaction','memory'=>'Memory','pattern'=>'Pattern','runner'=>'Runner','quiz'=>'Quiz','burst'=>'Burst','card'=>'Cards','dice'=>'Dice','mahjong'=>'Mahjong'];
$engineIcons = ['reaction'=>'bi-lightning-charge','memory'=>'bi-grid-3x3-gap','pattern'=>'bi-soundwave','runner'=>'bi-signpost-split','quiz'=>'bi-patch-question','burst'=>'bi-bullseye','card'=>'bi-suit-spade-fill','dice'=>'bi-dice-5-fill','mahjong'=>'bi-grid-3x3'];
$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">Premium arcade mode is live</h1>
        <p class="page-copy">A premium browser arcade with deep game packs, richer variety, futuristic styling, and a much bigger score-chasing catalog. Station staff can take any game or whole engine lanes on and offline without touching the rest of the site, including cards, dice, and mahjong tables.</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 lane</h2>
              <div class="text-secondary">Jump into one of the nine arcade engines, or use All to browse the full premium catalog without the oversized control cards.</div>
            </div>
          </div>
          <div class="row g-3 mb-4">
            <div class="col-lg-8">
              <input class="form-control form-control-lg ugfx-search-input" type="search" id="arcadeSearch" placeholder="Search all arcade titles, themes, or engines">
            </div>
            <div class="col-lg-4 text-secondary small d-flex align-items-center">Search pulls matching games from the full catalog without flooding the page.</div>
          </div>
          <div class="d-flex justify-content-between gap-3 flex-wrap align-items-start mb-4">
          </div>
          <div class="d-flex flex-wrap gap-2 mb-4" id="engineGrid">
            <button class="ugfx-filter-chip is-active" type="button" data-engine-card="all">All</button>
            <?php foreach ($engineOrder as $engine): ?>
            <button class="ugfx-filter-chip" type="button" data-engine-card="<?= htmlspecialchars($engine) ?>"><?= htmlspecialchars($engineLabels[$engine]) ?></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">All premium arcade games</h3>
              <div class="text-secondary small" id="engineCopy">Browse the full catalog or filter by engine above.</div>
            </div>
          </div>
          <div class="ugfx-game-grid" id="gameGrid"></div>
          <div class="text-secondary small mt-3" id="gameGridEmpty">Choose one of the nine 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">Pick a game to start</h2>
              <p class="text-secondary mb-0" id="activeGameDesc">Choose a game card above to load its board here.</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>

<div class="modal fade" id="scoreSaveModal" tabindex="-1" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered">
    <div class="modal-content bg-dark text-white border border-warning-subtle rounded-4">
      <div class="modal-header border-secondary-subtle">
        <h5 class="modal-title">Save your score</h5>
        <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        <div class="small text-secondary mb-2" id="scoreSaveGame">Arcade leaderboard</div>
        <div class="mb-3">
          <label class="form-label" for="scoreSaveName">Display name</label>
          <input type="text" class="form-control bg-black text-white border-warning-subtle" id="scoreSaveName" maxlength="32" placeholder="Enter your name">
        </div>
        <div class="small text-secondary" id="scoreSaveMeta">Your score will be added to this game's leaderboard.</div>
      </div>
      <div class="modal-footer border-secondary-subtle">
        <button type="button" class="btn ugfx-btn-outline" data-bs-dismiss="modal">Cancel</button>
        <button type="button" class="btn ugfx-btn-primary" id="scoreSaveConfirm">Save score</button>
      </div>
    </div>
  </div>
</div>

<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(5,minmax(0,1fr));gap:12px}
.ugfx-engine-card,.ugfx-arcade-card{padding:12px 13px;border-radius:18px;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 18px 44px rgba(0,0,0,.22),0 0 0 1px rgba(255,145,80,.18) inset}
.ugfx-arcade-card h3{font-size:.88rem;line-height:1.1;margin-bottom:.15rem}
.ugfx-arcade-card .text-secondary.small{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden;min-height:2rem;font-size:.78rem;line-height:1.25}
.ugfx-game-icon{font-size:1.2rem;color:#ff9a55}
.ugfx-filter-chip{appearance:none;-webkit-appearance:none;display:inline-flex;align-items:center;justify-content:center;padding:.72rem 1rem;border-radius:18px;border:1px solid rgba(255,145,80,.24);background:linear-gradient(180deg, rgba(255,255,255,.03), rgba(255,123,0,.05));color:#f8efe8;font-weight:700;line-height:1;text-decoration:none;transition:all .18s ease;box-shadow:inset 0 0 0 1px rgba(255,255,255,.02)}
.ugfx-filter-chip:hover,.ugfx-filter-chip.is-active{background:linear-gradient(180deg, rgba(255,123,0,.28), rgba(255,123,0,.12));border-color:rgba(255,145,80,.5);color:#fff;box-shadow:0 0 0 1px rgba(255,145,80,.16) inset, 0 10px 24px rgba(0,0,0,.18)}
.ugfx-search-input{background:rgba(255,255,255,.04);border:1px solid rgba(255,145,80,.24);border-radius:20px;color:#fff}.ugfx-search-input:focus{background:rgba(255,255,255,.06);color:#fff;border-color:rgba(255,145,80,.55);box-shadow:0 0 0 .25rem rgba(255,123,0,.12)}
.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);z-index:1;pointer-events:none}.ugfx-stage-overlay.is-hidden{display:none}
.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%);z-index:3;cursor:pointer;touch-action:none;user-select:none;-webkit-user-select:none}
.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)}
.ugfx-save-ready{box-shadow:0 0 0 2px rgba(255,182,114,.35) inset,0 0 28px rgba(255,123,0,.24);animation:ugfxPulse 1.2s ease-in-out infinite}
@keyframes ugfxPulse{0%,100%{transform:translateY(0)}50%{transform:translateY(-1px)}}
.ugfx-status-line{margin-top:12px;color:#f8efe8;font-weight:600}
.ugfx-card-grid{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:12px}
.ugfx-playing-card,.ugfx-mahjong-tile{border:none;border-radius:18px;padding:14px;background:linear-gradient(180deg, rgba(255,255,255,.06), rgba(255,123,0,.08));color:#fff;min-height:96px;display:flex;flex-direction:column;justify-content:space-between;align-items:flex-start;border:1px solid rgba(255,123,0,.14)}
.ugfx-arcade-card .ugfx-chip{padding:.34rem .62rem;font-size:.74rem}
.ugfx-arcade-card .d-flex.justify-content-between.align-items-start.mb-3{margin-bottom:.6rem!important}
.ugfx-game-grid .ugfx-arcade-card{min-height:unset}
.ugfx-playing-card small,.ugfx-mahjong-tile small{color:rgba(255,255,255,.72)}
.ugfx-dice-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:16px}
.ugfx-die{border-radius:24px;min-height:140px;display:flex;flex-direction:column;gap:8px;align-items:center;justify-content:center;font-size:4rem;border:1px solid rgba(255,123,0,.16);background:linear-gradient(180deg, rgba(255,255,255,.05), rgba(255,123,0,.08));color:#fff}.ugfx-die small{font-size:.75rem;letter-spacing:.08em;color:rgba(255,255,255,.76)}.ugfx-die.is-held{box-shadow:0 0 0 2px rgba(255,182,114,.62) inset,0 0 26px rgba(255,123,0,.35)}
.ugfx-card-table{display:grid;grid-template-columns:1.2fr .8fr;gap:16px}
.ugfx-card-hand{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px}
.ugfx-mahjong-board{display:grid;grid-template-columns:repeat(6,minmax(0,1fr));gap:10px}.ugfx-mahjong-wall{position:relative;height:290px}.ugfx-mahjong-3d{position:absolute;width:68px;min-height:82px;box-shadow:0 10px 22px rgba(0,0,0,.28)}.ugfx-mahjong-3d.is-free{border-color:rgba(255,182,114,.48)}
.ugfx-mahjong-tile.is-selected{box-shadow:0 0 0 2px rgba(255,182,114,.62) inset,0 0 22px rgba(255,123,0,.35)}
.ugfx-mahjong-tile.is-cleared{opacity:.2;pointer-events:none}
@media (max-width: 991px){.ugfx-card-grid{grid-template-columns:repeat(3,minmax(0,1fr))}.ugfx-card-table{grid-template-columns:1fr}.ugfx-mahjong-board{grid-template-columns:repeat(4,minmax(0,1fr))}}
@media (max-width: 575px){.ugfx-card-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.ugfx-mahjong-board{grid-template-columns:repeat(3,minmax(0,1fr))}.ugfx-card-hand{grid-template-columns:repeat(3,minmax(0,1fr))}.ugfx-dice-grid{grid-template-columns:1fr}}
@media (max-width: 1399px){.ugfx-game-grid{grid-template-columns:repeat(4,minmax(0,1fr))}}
@media (max-width: 1199px){.ugfx-game-grid{grid-template-columns:repeat(3,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 searchInput = document.getElementById('arcadeSearch');
  const playShell = document.getElementById('arcadePlayShell');
  const leaderboards = {};
  let activeGame = games[0] || null;
  let activeEngine = null;
  let cleanup = null;

  const arcadeName = () => localStorage.getItem('ugfx_arcade_name') || '';
  const scoreModalEl = document.getElementById('scoreSaveModal');
  const scoreSaveGame = document.getElementById('scoreSaveGame');
  const scoreSaveMeta = document.getElementById('scoreSaveMeta');
  const scoreSaveName = document.getElementById('scoreSaveName');
  const scoreSaveConfirm = document.getElementById('scoreSaveConfirm');
  const scoreModal = (window.bootstrap && scoreModalEl) ? new bootstrap.Modal(scoreModalEl) : null;
  function askName(game, score){
    if (!scoreModalEl || !scoreModal) {
      const raw = prompt('Name for the leaderboard:', arcadeName());
      if (raw === null) return Promise.resolve(null);
      const clean = (raw.trim() || 'Guest').slice(0, 32);
      localStorage.setItem('ugfx_arcade_name', clean);
      return Promise.resolve(clean);
    }
    scoreSaveGame.textContent = `${game.name} leaderboard`;
    scoreSaveMeta.textContent = `Score: ${score} ${game.unit}. Save your run to the leaderboard.`;
    scoreSaveName.value = arcadeName();
    scoreModal.show();
    setTimeout(() => { scoreSaveName.focus(); scoreSaveName.select(); }, 120);
    return new Promise(resolve => {
      const done = value => {
        scoreSaveConfirm.removeEventListener('click', onConfirm);
        scoreSaveName.removeEventListener('keydown', onKey);
        scoreModalEl.removeEventListener('hidden.bs.modal', onHidden);
        resolve(value);
      };
      const onConfirm = () => {
        const clean = (scoreSaveName.value.trim() || 'Guest').slice(0, 32);
        localStorage.setItem('ugfx_arcade_name', clean);
        scoreModal.hide();
        done(clean);
      };
      const onKey = e => { if (e.key === 'Enter') { e.preventDefault(); onConfirm(); } };
      const onHidden = () => done(null);
      scoreSaveConfirm.addEventListener('click', onConfirm);
      scoreSaveName.addEventListener('keydown', onKey);
      scoreModalEl.addEventListener('hidden.bs.modal', onHidden, {once:true});
    });
  }
  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 scrollToPlayArea(){ playShell?.scrollIntoView({behavior:'smooth', block:'start'}); }
  function activeEngines(type){engineButtons.forEach(card => card.classList.toggle('is-active', card.dataset.engineCard === type));}
  function gameCardMarkup(game){
    return `
      <button class="ugfx-arcade-card ${activeGame?.id === game.id ? 'is-active' : ''}" type="button" data-game-card="${escapeHtml(game.id)}" data-game-open="${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>`;
  }
  function renderGameCards(type, query=''){
    activeEngine = type;
    activeEngines(type);
    const q = (query || '').trim().toLowerCase();
    const filtered = games.filter(game => (type === 'all' || game.type === type) && (!q || (game.name + ' ' + game.description + ' ' + game.type + ' ' + game.difficulty).toLowerCase().includes(q)));
    engineTitle.textContent = type === 'all' ? 'All premium arcade games' : `${type.charAt(0).toUpperCase()+type.slice(1)} engine`;
    engineCopy.textContent = type === 'all' ? `${filtered.length} games live across the full arcade catalog.` : `${filtered.length} games live in this engine. Pick one to load it below.`;
    gameGrid.innerHTML = filtered.map(gameCardMarkup).join('');
    gameGridEmpty.style.display = filtered.length ? 'none' : '';
    gameGrid.querySelectorAll('[data-game-card]').forEach(card => card.addEventListener('click', () => { activate(card.dataset.gameCard); setTimeout(scrollToPlayArea, 120); }));
  }
  function clearGameCards(){
    renderGameCards('all', searchInput?.value || '');
  }
  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>`;
  }
  function finishRound(game, saveBtn, score, message='Round over'){
    if (!saveBtn) return;
    saveBtn.disabled = score <= 0;
    saveBtn.classList.toggle('ugfx-save-ready', score > 0);
    if (score > 0) {
      boardDesc.textContent = `${message} Save ${game.name} to the leaderboard when you're ready.`;
      setTimeout(() => { saveBtn.scrollIntoView({behavior:'smooth', block:'center'}); saveBtn.focus({preventScroll:true}); }, 160);
    }
  }
  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 = await askName(game, score);
    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(); const half = Math.max(28, Math.round(targetSize / 2)); target.style.left = `${randomRange(half, Math.max(half, rect.width - half))}px`; target.style.top = `${randomRange(half, Math.max(half, rect.height - half))}px`; }
    function hitTarget(e){ if (e) e.preventDefault(); if (!running) return; score += pace; scoreEl.textContent = String(score); move(); }
    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=''; overlay.classList.add('is-hidden'); 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`; overlay.classList.remove('is-hidden'); finishRound(game, saveBtn, score, `Reaction round complete on ${game.name}.`); } }, 1000); };
    target.onclick = hitTarget;
    target.onpointerdown = hitTarget;
    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); if (matched === cards.length / 2) finishRound(game, saveBtn, score, `Board cleared on ${game.name}.`); } }, 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); finishRound(game, saveBtn, score, `Memory board complete on ${game.name}.`); } } 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; finishRound(game, saveBtn, best, `Pattern run complete on ${game.name}.`); }
    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); finishRound(game, saveBtn, Math.floor(score), `Runner session complete on ${game.name}.`); }
    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); finishRound(game, saveBtn, score, `Quiz round complete on ${game.name}.`); }
    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')); finishRound(game, saveBtn, score, `Burst wave complete on ${game.name}.`); } }, 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 handValue(cards){
    const ranks = cards.map(card => card.rank).sort((a,b) => a - b);
    const counts = {};
    ranks.forEach(r => counts[r] = (counts[r] || 0) + 1);
    const groups = Object.values(counts).sort((a,b) => b - a);
    const isFlush = cards.every(card => card.suit === cards[0].suit);
    const isStraight = ranks.every((rank, idx) => idx === 0 || rank === ranks[idx - 1] + 1) || JSON.stringify(ranks) === JSON.stringify([2,3,4,5,14]);
    let score = ranks.reduce((sum, rank) => sum + rank, 0);
    let label = 'High card';
    if (isStraight && isFlush) { score += 160; label = 'Straight flush'; }
    else if (groups[0] === 4) { score += 130; label = 'Four of a kind'; }
    else if (groups[0] === 3 && groups[1] === 2) { score += 110; label = 'Full house'; }
    else if (isFlush) { score += 90; label = 'Flush'; }
    else if (isStraight) { score += 80; label = 'Straight'; }
    else if (groups[0] === 3) { score += 65; label = 'Three of a kind'; }
    else if (groups[0] === 2 && groups[1] === 2) { score += 50; label = 'Two pair'; }
    else if (groups[0] === 2) { score += 30; label = 'Pair'; }
    return {score, label};
  }
  function cardMarkup(card){
    const color = ['♥','♦'].includes(card.suit) ? '#ff8f8f' : '#f8efe8';
    return `<div class="ugfx-playing-card"><div style="font-size:1.1rem;font-weight:800;color:${color}">${card.face}${card.suit}</div><small>${card.label}</small></div>`;
  }
  function drawDeckHand(count=5){
    const suits = ['♠','♥','♦','♣']; const faces = {11:'J',12:'Q',13:'K',14:'A'};
    const deck = [];
    suits.forEach(suit => { for (let rank = 2; rank <= 14; rank++) deck.push({rank, suit, face: faces[rank] || String(rank), label: `${faces[rank] || rank} of ${({'♠':'Spades','♥':'Hearts','♦':'Diamonds','♣':'Clubs'})[suit]}`}); });
    return shuffle(deck).slice(0, count);
  }
  function mountCard(game){
    const id = game.id;
    const isBlackjack = id.includes('blackjack') || id.includes('ace-chase');
    const isHoldem = id.includes('holdem') || id.includes('river-kings');
    const isDraw = id.includes('draw') || id.includes('flush') || id.includes('full-house');
    const isWar = id.includes('war');
    const isBaccarat = id.includes('baccarat');
    mount.innerHTML = `<div class="ugfx-arcade-stage"><div class="d-flex justify-content-between flex-wrap gap-2 mb-3"><span class="ugfx-chip">Rounds: <strong id="cgRound">0</strong>/5</span><span class="ugfx-chip ugfx-chip-blue">Score: <strong id="cgScore">0</strong></span></div><div class="ugfx-card-table"><div class="ugfx-stage-shell p-4"><div class="ugfx-micro-label mb-2">YOUR SIDE</div><div class="ugfx-card-hand mb-3" id="cardPlayer"></div><div class="ugfx-micro-label mb-2">TABLE / HOUSE</div><div class="ugfx-card-hand" id="cardDealer"></div></div><div class="ugfx-stage-shell p-4 d-flex flex-column justify-content-between"><div><div class="ugfx-micro-label">TABLE STATUS</div><h3 class="mb-2" id="cardResult">Table ready</h3><div class="text-secondary small" id="cardMeta">Play a full round, then save your run.</div><div class="ugfx-status-line" id="cardStatus"></div></div><div class="d-flex gap-2 flex-wrap mt-4" id="cardActions"></div></div></div></div>`;
    const pEl=document.getElementById('cardPlayer'), dEl=document.getElementById('cardDealer'), resultEl=document.getElementById('cardResult'), metaEl=document.getElementById('cardMeta'), roundEl=document.getElementById('cgRound'), scoreEl=document.getElementById('cgScore'), statusEl=document.getElementById('cardStatus'), actions=document.getElementById('cardActions');
    let rounds=0, score=0, player=[], dealer=[], table=[], deck=[], selected=[];
    function newDeck(){ const suits=['♠','♥','♦','♣']; const faces={11:'J',12:'Q',13:'K',14:'A'}; deck=[]; suits.forEach(suit=>{ for(let rank=2; rank<=14; rank++) deck.push({rank,suit,face:faces[rank]||String(rank),label:`${faces[rank]||rank} of ${({ '♠':'Spades','♥':'Hearts','♦':'Diamonds','♣':'Clubs' })[suit]}`}); }); deck=shuffle(deck); }
    function draw(n=1){ if(deck.length<n) newDeck(); return deck.splice(0,n); }
    function score21(cards){ let total=cards.reduce((sum,c)=>sum+Math.min(c.rank,10),0); let aces=cards.filter(c=>c.rank===14).length; while(aces-- && total+10<=21) total+=10; return total; }
    function setActions(html){ actions.innerHTML = html; const saveBtn = actions.querySelector('[data-save-score]'); if (saveBtn) saveBtn.onclick = () => submitScore(game, score, {rounds}); return saveBtn; }
    function renderCards(){
      pEl.innerHTML = player.map((card, idx) => {
        const holdable = isDraw && rounds>0 && selected !== null;
        return `<button class="ugfx-playing-card ${selected.includes(idx)?'is-selected':''}" ${holdable?'':'disabled'} data-player-card="${idx}" type="button"><div style="font-size:1.1rem;font-weight:800;color:${['♥','♦'].includes(card.suit)?'#ff8f8f':'#f8efe8'}">${card.face}${card.suit}</div><small>${card.label}${holdable?' · tap to hold':''}</small></button>`;
      }).join('');
      dEl.innerHTML = [...table, ...dealer].map(cardMarkup).join('');
      pEl.querySelectorAll('[data-player-card]').forEach(btn => btn.onclick = () => {
        if (!isDraw) return;
        const idx = Number(btn.dataset.playerCard);
        if (selected.includes(idx)) selected = selected.filter(v => v !== idx); else if (selected.length < 3) selected.push(idx);
        renderCards();
      });
      roundEl.textContent=String(rounds); scoreEl.textContent=String(score);
    }
    function finishSeries(saveBtn, message){ scoreEl.textContent=String(score); roundEl.textContent=String(rounds); if(rounds>=5){ finishRound(game, saveBtn, score, message); return true; } return false; }
    function showdownLabel(cardsA, cardsB){ const a=handValue(cardsA), b=handValue(cardsB); return {a,b, win:a.score>b.score, tie:a.score===b.score}; }
    function startBlackjack(){
      newDeck(); rounds++; player=draw(2); dealer=draw(2); table=[]; selected=[]; metaEl.textContent=`Blackjack hand ${rounds} of 5.`; resultEl.textContent='Blackjack table live'; statusEl.textContent=`Your total ${score21(player)}. Hit or stand.`; renderCards();
      const saveBtn = setActions(`<button class="btn ugfx-btn-primary" id="cardHit" type="button">Hit</button><button class="btn ugfx-btn-outline" id="cardStand" type="button">Stand</button><button class="btn ugfx-btn-outline" data-save-score type="button" disabled>Save score</button>`);
      actions.querySelector('#cardHit').onclick=()=>{ player.push(...draw(1)); renderCards(); const total=score21(player); statusEl.textContent=`Your total ${total}.`; if(total>21){ resultEl.textContent='Bust'; statusEl.textContent='House wins this hand.'; score += 5; actions.querySelector('#cardHit').disabled=true; actions.querySelector('#cardStand').disabled=true; if(!finishSeries(saveBtn, `Card table complete on ${game.name}.`)) setTimeout(startBlackjack, 800); } };
      actions.querySelector('#cardStand').onclick=()=>{ while(score21(dealer)<17) dealer.push(...draw(1)); renderCards(); const p=score21(player), d=score21(dealer); if(d>21 || p>d){ resultEl.textContent='You win the hand'; score += Math.max(25, 40 - (21-p)); } else if(p===d){ resultEl.textContent='Push hand'; score += 12; } else { resultEl.textContent='House wins'; score += 6; } statusEl.textContent=`You ${p} · House ${d}`; actions.querySelector('#cardHit').disabled=true; actions.querySelector('#cardStand').disabled=true; if(!finishSeries(saveBtn, `Card table complete on ${game.name}.`)) setTimeout(startBlackjack, 1000); };
    }
    function startWar(){
      newDeck(); rounds++; player=draw(1); dealer=draw(1); table=[]; selected=[]; resultEl.textContent='High card duel'; metaEl.textContent=`War draw ${rounds} of 5.`; const p=player[0].rank, d=dealer[0].rank; statusEl.textContent=`${player[0].face}${player[0].suit} vs ${dealer[0].face}${dealer[0].suit}`; if(p>d) score += 30+p; else if(p<d) score += 8; else score += 16; renderCards(); const saveBtn = setActions(`<button class="btn ugfx-btn-primary" id="cardDeal" type="button">Draw next card</button><button class="btn ugfx-btn-outline" data-save-score type="button" ${rounds<5?'disabled':''}>Save score</button>`); if(finishSeries(saveBtn, `Card table complete on ${game.name}.`)) actions.querySelector('#cardDeal').disabled=true; else actions.querySelector('#cardDeal').onclick=()=>startWar();
    }
    function startBaccarat(){
      newDeck(); rounds++; player=draw(2); dealer=draw(2); table=[]; selected=[]; resultEl.textContent='Choose your side'; metaEl.textContent=`Baccarat round ${rounds} of 5.`; const p=(player.reduce((s,c)=>s+Math.min(c.rank,10),0))%10; const d=(dealer.reduce((s,c)=>s+Math.min(c.rank,10),0))%10; statusEl.textContent=`Player ${p} · Banker ${d}. Pick the side you think wins.`; renderCards(); const saveBtn=setActions(`<button class="btn ugfx-btn-primary" id="pickPlayer" type="button">Back player</button><button class="btn ugfx-btn-outline" id="pickBanker" type="button">Back banker</button><button class="btn ugfx-btn-outline" data-save-score type="button" ${rounds<5?'disabled':''}>Save score</button>`); const settle=choice=>{ if((choice==='player' && p>d) || (choice==='banker' && d>p)){ score += 28; resultEl.textContent='Correct call'; } else if(p===d){ score += 14; resultEl.textContent='Tie hand'; } else { score += 6; resultEl.textContent='House read you'; } if(!finishSeries(saveBtn, `Card table complete on ${game.name}.`)) setTimeout(startBaccarat, 900); }; actions.querySelector('#pickPlayer').onclick=()=>settle('player'); actions.querySelector('#pickBanker').onclick=()=>settle('banker');
    }
    function startDrawPoker(){
      newDeck(); rounds++; player=draw(5); dealer=draw(5); table=[]; selected=[]; resultEl.textContent='Select up to 3 cards to hold'; metaEl.textContent=`Draw poker round ${rounds} of 5.`; statusEl.textContent='Tap cards you want to keep, then draw replacements.'; renderCards();
      const saveBtn=setActions(`<button class="btn ugfx-btn-primary" id="drawCards" type="button">Draw replacements</button><button class="btn ugfx-btn-outline" data-save-score type="button" ${rounds<5?'disabled':''}>Save score</button>`);
      actions.querySelector('#drawCards').onclick=()=>{ const held = new Set(selected); player = player.map((card, idx) => held.has(idx) ? card : draw(1)[0]); selected=[]; const outcome=showdownLabel(player,dealer); renderCards(); resultEl.textContent = outcome.win ? 'You win the showdown' : (outcome.tie ? 'Split pot' : 'House takes the pot'); statusEl.textContent = `${outcome.a.label} vs ${outcome.b.label}`; score += outcome.win ? outcome.a.score : (outcome.tie ? Math.max(12, Math.floor(outcome.a.score/2)) : Math.max(8, Math.floor(outcome.a.score/4))); actions.querySelector('#drawCards').disabled=true; if(!finishSeries(saveBtn, `Card table complete on ${game.name}.`)) setTimeout(startDrawPoker, 1000); };
    }
    function startHoldem(){
      newDeck(); rounds++; player=draw(2); dealer=draw(2); table=[]; selected=[]; let street=0; resultEl.textContent='Hold’em table live'; metaEl.textContent=`Hold’em round ${rounds} of 5.`; statusEl.textContent='Advance the board to the flop.'; renderCards();
      const saveBtn=setActions(`<button class="btn ugfx-btn-primary" id="nextStreet" type="button">Deal flop</button><button class="btn ugfx-btn-outline" id="showdownBtn" type="button" disabled>Showdown</button><button class="btn ugfx-btn-outline" data-save-score type="button" ${rounds<5?'disabled':''}>Save score</button>`);
      actions.querySelector('#nextStreet').onclick=()=>{
        if(street===0){ table = draw(3); street=1; statusEl.textContent='Flop is live. Deal the turn.'; actions.querySelector('#nextStreet').textContent='Deal turn'; }
        else if(street===1){ table.push(...draw(1)); street=2; statusEl.textContent='Turn is live. Deal the river.'; actions.querySelector('#nextStreet').textContent='Deal river'; }
        else if(street===2){ table.push(...draw(1)); street=3; statusEl.textContent='Board complete. Run the showdown.'; actions.querySelector('#nextStreet').disabled=true; actions.querySelector('#showdownBtn').disabled=false; }
        renderCards();
      };
      actions.querySelector('#showdownBtn').onclick=()=>{
        const playerBest = handValue([...player, ...table].slice(0,5));
        const dealerBest = handValue([...dealer, ...table].slice(0,5));
        renderCards();
        resultEl.textContent = playerBest.score>dealerBest.score ? 'You win the pot' : (playerBest.score===dealerBest.score ? 'Chopped pot' : 'House wins the pot');
        statusEl.textContent = `${playerBest.label} vs ${dealerBest.label}`;
        score += playerBest.score>dealerBest.score ? playerBest.score + 20 : (playerBest.score===dealerBest.score ? Math.max(15, Math.floor(playerBest.score/2)) : Math.max(8, Math.floor(playerBest.score/4)));
        actions.querySelector('#showdownBtn').disabled=true;
        if(!finishSeries(saveBtn, `Card table complete on ${game.name}.`)) setTimeout(startHoldem, 1100);
      };
    }
    const begin = () => { score=0; rounds=0; scoreEl.textContent='0'; roundEl.textContent='0'; if(isBlackjack) startBlackjack(); else if(isWar) startWar(); else if(isBaccarat) startBaccarat(); else if(isHoldem) startHoldem(); else startDrawPoker(); };
    setActions(`<button class="btn ugfx-btn-primary" id="cardBegin" type="button">Start table</button><button class="btn ugfx-btn-outline" data-save-score type="button" disabled>Save score</button>`);
    actions.querySelector('#cardBegin').onclick = begin;
    actions.querySelector('#cardBegin').click();
    return () => {};
  }
  function dieFace(value){ return ['⚀','⚁','⚂','⚃','⚄','⚅'][value - 1] || '⚀'; }
  function mountDice(game){
    mount.innerHTML = `<div class="ugfx-arcade-stage"><div class="d-flex justify-content-between flex-wrap gap-2 mb-3"><span class="ugfx-chip">Rolls left: <strong id="dgRolls">3</strong></span><span class="ugfx-chip ugfx-chip-blue">Score: <strong id="dgScore">0</strong></span></div><div class="ugfx-dice-grid mb-3" id="diceGrid"></div><div class="ugfx-stage-shell p-4"><div class="ugfx-micro-label">TABLE STATUS</div><h3 class="mb-2" id="diceResult">Choose dice to hold, then roll</h3><div class="text-secondary small" id="diceMeta">Three rolls per round. Hold strong dice and bank the best combo.</div><div class="ugfx-status-line" id="diceStatus"></div><div class="d-flex gap-2 flex-wrap mt-4"><button class="btn ugfx-btn-primary" id="diceRoll" type="button">Roll dice</button><button class="btn ugfx-btn-outline" id="diceBank" type="button">Bank round</button><button class="btn ugfx-btn-outline" id="diceSave" type="button" disabled>Save score</button></div></div></div>`;
    const grid=document.getElementById('diceGrid'), resultEl=document.getElementById('diceResult'), metaEl=document.getElementById('diceMeta'), statusEl=document.getElementById('diceStatus'), rollsEl=document.getElementById('dgRolls'), scoreEl=document.getElementById('dgScore'), saveBtn=document.getElementById('diceSave');
    let dice=[1,1,1,1,1], held=[false,false,false,false,false], rolls=3, rounds=0, score=0;
    function renderDice(){ grid.innerHTML = dice.map((v,i)=>`<button class="ugfx-die ${held[i]?'is-held':''}" data-die="${i}" type="button">${dieFace(v)}<small>${held[i]?'HELD':'LIVE'}</small></button>`).join(''); grid.querySelectorAll('[data-die]').forEach(btn => btn.onclick=()=>{ if(rolls===3 && rounds===0) return; const i=Number(btn.dataset.die); if(rolls<3 && rolls>0){ held[i]=!held[i]; renderDice(); } }); rollsEl.textContent=String(rolls); scoreEl.textContent=String(score); }
    function evaluateDice(){ const counts={}; dice.forEach(v=>counts[v]=(counts[v]||0)+1); const vals=Object.values(counts).sort((a,b)=>b-a); const total=dice.reduce((a,b)=>a+b,0); let gained=total, label='High total'; const uniq=Object.keys(counts).map(Number).sort((a,b)=>a-b); const straight = uniq.length===5 && uniq[4]-uniq[0]===4; if(vals[0]===5){ gained=120; label='Five of a kind'; } else if(vals[0]===4){ gained=90; label='Four of a kind'; } else if(vals[0]===3 && vals[1]===2){ gained=75; label='Full house'; } else if(straight){ gained=65; label='Straight'; } else if(vals[0]===3){ gained=48; label='Three of a kind'; } else if(vals[0]===2 && vals[1]===2){ gained=36; label='Two pair'; } else if(vals[0]===2){ gained=24; label='Pair'; } return {gained, label}; }
    function newRound(){ rounds++; rolls=3; held=[false,false,false,false,false]; dice=[1,1,1,1,1]; renderDice(); resultEl.textContent='Choose dice to hold, then roll'; metaEl.textContent=`Round ${rounds} of 5 on ${game.name}.`; statusEl.textContent='Roll up to three times, then bank the result.'; }
    document.getElementById('diceRoll').onclick = () => { if(rounds===0) newRound(); if(rolls<=0) return; for(let i=0;i<5;i++){ if(!held[i]) dice[i]=randomRange(1,6); } rolls--; const evald=evaluateDice(); renderDice(); resultEl.textContent=evald.label; statusEl.textContent=`Current combo scores ${evald.gained} pts.`; if(rolls===0) document.getElementById('diceBank').classList.add('ugfx-save-ready'); };
    document.getElementById('diceBank').onclick = () => { if(rounds===0) return; const evald=evaluateDice(); score += evald.gained; scoreEl.textContent=String(score); resultEl.textContent=`${evald.label} banked`; statusEl.textContent=`+${evald.gained} points added.`; document.getElementById('diceBank').classList.remove('ugfx-save-ready'); if(rounds>=5){ finishRound(game, saveBtn, score, `Dice table complete on ${game.name}.`); } else { newRound(); } };
    saveBtn.onclick = () => submitScore(game, score, {rounds});
    newRound();
    return () => {};
  }
  function mountMahjong(game){
    const symbolPools = {
      easy:['🀙','🀚','🀛','🀜','🀝','🀞'],
      medium:['🀇','🀈','🀉','🀊','🀋','🀌','🀍','🀎'],
      hard:['🀐','🀑','🀒','🀓','🀔','🀕','🀖','🀗','🀘','🀀']
    };
    const layouts = {
      easy:[{x:0,y:1,z:0},{x:1,y:1,z:0},{x:2,y:1,z:0},{x:3,y:1,z:0},{x:4,y:1,z:0},{x:5,y:1,z:0},{x:1,y:0,z:0},{x:2,y:0,z:0},{x:3,y:0,z:0},{x:4,y:0,z:0},{x:2,y:0,z:1},{x:3,y:0,z:1}],
      medium:[{x:0,y:1,z:0},{x:1,y:1,z:0},{x:2,y:1,z:0},{x:3,y:1,z:0},{x:4,y:1,z:0},{x:5,y:1,z:0},{x:0,y:0,z:0},{x:1,y:0,z:0},{x:2,y:0,z:0},{x:3,y:0,z:0},{x:4,y:0,z:0},{x:5,y:0,z:0},{x:1,y:0,z:1},{x:2,y:0,z:1},{x:3,y:0,z:1},{x:4,y:0,z:1}],
      hard:[{x:0,y:2,z:0},{x:1,y:2,z:0},{x:2,y:2,z:0},{x:3,y:2,z:0},{x:4,y:2,z:0},{x:5,y:2,z:0},{x:0,y:1,z:0},{x:1,y:1,z:0},{x:2,y:1,z:0},{x:3,y:1,z:0},{x:4,y:1,z:0},{x:5,y:1,z:0},{x:1,y:0,z:0},{x:2,y:0,z:0},{x:3,y:0,z:0},{x:4,y:0,z:0},{x:2,y:1,z:1},{x:3,y:1,z:1},{x:2,y:0,z:1},{x:3,y:0,z:1}]
    };
    const symbols = symbolPools[game.difficulty] || symbolPools.easy;
    const layout = layouts[game.difficulty] || layouts.easy;
    mount.innerHTML = `<div class="ugfx-arcade-stage"><div class="d-flex justify-content-between flex-wrap gap-2 mb-3"><span class="ugfx-chip">Pairs cleared: <strong id="mjClear">0</strong>/${layout.length/2}</span><span class="ugfx-chip ugfx-chip-blue">Score: <strong id="mjScore">0</strong></span></div><div class="ugfx-stage-shell p-4"><div class="ugfx-mahjong-wall" id="mahjongBoard"></div></div><div class="d-flex gap-2 flex-wrap mt-3"><button class="btn ugfx-btn-primary" id="mahjongStart" type="button">New wall</button><button class="btn ugfx-btn-outline" id="mahjongSave" type="button" disabled>Save score</button></div><div class="ugfx-status-line" id="mahjongStatus">Select two matching free tiles. A free tile has no tile above it and one side open.</div></div>`;
    const board=document.getElementById('mahjongBoard'), clearEl=document.getElementById('mjClear'), scoreEl=document.getElementById('mjScore'), saveBtn=document.getElementById('mahjongSave'), statusEl=document.getElementById('mahjongStatus');
    let tiles=[], selected=[], cleared=0, score=0;
    function build(){
      const pairsNeeded = layout.length/2;
      const source = shuffle([...symbols]).slice(0,pairsNeeded);
      const pool = shuffle([...source, ...source]);
      tiles = layout.map((pos, idx) => ({...pos, symbol:pool[idx], cleared:false}));
      selected=[]; cleared=0; score=0; clearEl.textContent='0'; scoreEl.textContent='0'; saveBtn.disabled=true; saveBtn.classList.remove('ugfx-save-ready');
      render(); statusEl.textContent='Select two matching free tiles. A free tile has no tile above it and one side open.';
    }
    function hasTop(tile){ return tiles.some(other => !other.cleared && other.z>tile.z && other.x===tile.x && other.y===tile.y); }
    function isFree(idx){ const tile=tiles[idx]; if(!tile || tile.cleared) return false; if(hasTop(tile)) return false; const leftBlocked = tiles.some(other => !other.cleared && other.z===tile.z && other.y===tile.y && other.x===tile.x-1); const rightBlocked = tiles.some(other => !other.cleared && other.z===tile.z && other.y===tile.y && other.x===tile.x+1); return !(leftBlocked && rightBlocked); }
    function render(){ board.innerHTML = tiles.map((tile, idx) => { const free=isFree(idx); const left=tile.x*74 + tile.z*8; const top=tile.y*84 - tile.z*8; return `<button class="ugfx-mahjong-tile ugfx-mahjong-3d ${selected.includes(idx)?'is-selected':''} ${tile.cleared?'is-cleared':''} ${free?'is-free':''}" style="left:${left}px;top:${top}px;z-index:${tile.z+1}" data-tile="${idx}" type="button" ${tile.cleared?'disabled':''}><div style="font-size:2rem">${tile.cleared?'':tile.symbol}</div><small>${tile.cleared?'cleared':(free?'free':'covered')}</small></button>`; }).join(''); board.querySelectorAll('[data-tile]').forEach(btn => btn.onclick = () => pick(Number(btn.dataset.tile))); }
    function pick(idx){ if(!isFree(idx)){ statusEl.textContent='That tile is blocked. Pick a free tile from an open edge.'; return; } if(selected.includes(idx)){ selected=selected.filter(v=>v!==idx); render(); return; } selected.push(idx); if(selected.length<2){ render(); return; } const [a,b]=selected; if(tiles[a].symbol===tiles[b].symbol){ tiles[a].cleared=true; tiles[b].cleared=true; cleared++; score += 55; clearEl.textContent=String(cleared); scoreEl.textContent=String(score); statusEl.textContent='Pair cleared.'; } else { score=Math.max(0, score-8); scoreEl.textContent=String(score); statusEl.textContent='No match. Free another route.'; } selected=[]; render(); if(cleared>=layout.length/2){ finishRound(game, saveBtn, score, `Mahjong wall complete on ${game.name}.`); statusEl.textContent='Wall cleared — save your score.'; } }
    document.getElementById('mahjongStart').onclick = build; saveBtn.onclick = () => submitScore(game, score, {pairs: cleared}); build();
    return () => {};
  }

  function activate(gameId){
    const game = lookup[gameId]; if (!game) return;
    activeGame = game; activeCards(gameId); playShell?.classList.remove('d-none'); 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,card:mountCard,dice:mountDice,mahjong:mountMahjong}[game.type] || mountReaction)(game);
    setTimeout(scrollToPlayArea, 120);
  }

  engineButtons.forEach(btn => btn.addEventListener('click', () => renderGameCards(btn.dataset.engineCard, searchInput?.value || '')));
  document.querySelector('[data-clear-engine]')?.addEventListener('click', () => { if (searchInput) searchInput.value=''; clearGameCards(); });
  searchInput?.addEventListener('input', () => {
    const q = searchInput.value || '';
    renderGameCards(activeEngine || 'all', q);
  });
  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 (playShell) playShell.classList.add('d-none'); });
})();
</script>
<?php include __DIR__ . '/includes/footer.php'; ?>
