Clone
play.html.heex
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
    <meta name="apple-mobile-web-app-capable" content="yes" />
    <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
    <meta name="csrf-token" content={get_csrf_token()} />
    <title>Pi Station</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
      html, body {
        width: 100%; height: 100%; overflow: hidden; background: #0a0a1a;
        touch-action: none; -webkit-touch-callout: none; -webkit-user-select: none;
        user-select: none; overscroll-behavior: none;
        position: fixed; inset: 0;
      }
      #game-container { width: 100%; height: 100%; position: relative; }
      #game-canvas { width: 100%; height: 100%; display: block; }

      /* Mini-game overlay */
      #mini-game-overlay {
        display: none; position: fixed; inset: 0;
        background: rgba(0,0,0,0.92); z-index: 100;
        flex-direction: column; align-items: center;
        justify-content: flex-start; padding: 1rem;
        overflow-y: auto; -webkit-overflow-scrolling: touch;
      }
      #mini-game-overlay.active { display: flex; }
      #mini-game-content {
        width: 100%; max-width: 500px; color: white;
        text-align: center; padding-bottom: 2rem; margin-top: 2rem;
      }

      .mg-title { font-size: 1.5rem; font-weight: bold; margin-bottom: 0.5rem; }
      .mg-subtitle { font-size: 0.9rem; color: #a78bfa; margin-bottom: 1.5rem; }
      .mg-close {
        position: fixed; top: 0.75rem; right: 0.75rem;
        background: rgba(255,255,255,0.15); border: none; color: white;
        width: 44px; height: 44px; border-radius: 50%; font-size: 1.5rem;
        cursor: pointer; z-index: 110;
      }
      .mg-btn {
        display: inline-block; padding: 0.75rem 2rem;
        background: linear-gradient(135deg, #06b6d4, #8b5cf6);
        color: white; border: none; border-radius: 0.75rem;
        font-size: 1.1rem; font-weight: bold; cursor: pointer;
        margin: 0.5rem; transition: transform 0.1s;
        -webkit-tap-highlight-color: transparent;
      }
      .mg-btn:active { transform: scale(0.95); }
      .mg-btn.secondary { background: rgba(255,255,255,0.15); }

      /* Pi Memory */
      .pi-display {
        font-family: monospace; font-size: 1.8rem; letter-spacing: 0.15em;
        color: #22d3ee; margin: 0.75rem 0; min-height: 2.5rem;
        word-break: break-all; line-height: 1.4;
      }
      .pi-numpad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0.4rem; max-width: 280px; margin: 0.75rem auto; }
      .pi-numpad button {
        padding: 0.9rem; font-size: 1.4rem; font-weight: bold;
        background: rgba(255,255,255,0.1); border: 1px solid rgba(255,255,255,0.2);
        border-radius: 0.75rem; color: white; cursor: pointer;
        -webkit-tap-highlight-color: transparent;
      }
      .pi-numpad button:active { background: rgba(139, 92, 246, 0.5); }
      .pi-numpad .zero { grid-column: span 3; }

      /* Monte Carlo */
      #mc-canvas { border-radius: 0.75rem; border: 2px solid rgba(255,255,255,0.2); touch-action: none; max-width: 100%; }
      .mc-stats { display: flex; gap: 0.75rem; justify-content: center; margin: 0.75rem 0; flex-wrap: wrap; }
      .mc-stat { background: rgba(255,255,255,0.1); padding: 0.4rem 0.75rem; border-radius: 0.5rem; }
      .mc-stat-value { font-size: 1.2rem; font-weight: bold; color: #22d3ee; }
      .mc-stat-label { font-size: 0.65rem; color: #a78bfa; }

      /* Slice the Pi */
      .slice-question { font-size: 1.1rem; margin: 0.75rem 0; color: #e2e8f0; min-height: 2.5rem; }
      .slice-choices { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; max-width: 400px; margin: 0.75rem auto; }
      .slice-choice {
        padding: 0.9rem; font-size: 1.1rem;
        background: rgba(255,255,255,0.1); border: 2px solid rgba(255,255,255,0.2);
        border-radius: 0.75rem; color: white; cursor: pointer;
        -webkit-tap-highlight-color: transparent;
      }
      .slice-choice.correct { background: rgba(34, 197, 94, 0.4); border-color: #22c55e; }
      .slice-choice.wrong { background: rgba(239, 68, 68, 0.4); border-color: #ef4444; }
      .slice-timer { font-size: 2.5rem; font-weight: bold; color: #22d3ee; }
      .slice-score-display { font-size: 1rem; color: #a78bfa; margin: 0.3rem 0; }

      /* Players sidebar */
      #players-list {
        position: fixed; top: 0.5rem; right: 0.5rem;
        background: rgba(0,0,0,0.7); border-radius: 0.75rem;
        padding: 0.5rem; max-width: 140px; z-index: 50;
        max-height: 40vh; overflow-y: auto;
        backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
      }
      .player-entry {
        display: flex; align-items: center; gap: 0.3rem;
        padding: 0.2rem 0.4rem; color: white; font-size: 0.65rem;
      }
      .player-avatar { font-size: 0.9rem; }
      .player-score { color: #22d3ee; margin-left: auto; font-size: 0.55rem; }

      /* Chat input */
      #chat-bar {
        position: fixed; bottom: 0; left: 0; right: 0;
        z-index: 60; display: flex; gap: 0.5rem;
        padding: 0.5rem; background: rgba(0,0,0,0.6);
        backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
        transform: translateY(100%); transition: transform 0.2s;
      }
      #chat-bar.visible { transform: translateY(0); }
      #chat-bar input {
        flex: 1; padding: 0.6rem 0.75rem; border-radius: 0.75rem;
        border: 1px solid rgba(255,255,255,0.2); background: rgba(255,255,255,0.1);
        color: white; font-size: 0.9rem; outline: none;
      }
      #chat-bar input::placeholder { color: rgba(255,255,255,0.4); }
      #chat-bar button {
        padding: 0.6rem 1rem; border-radius: 0.75rem;
        border: none; background: linear-gradient(135deg, #06b6d4, #8b5cf6);
        color: white; font-weight: bold; font-size: 0.9rem; cursor: pointer;
      }

      /* Chat toggle button */
      #chat-toggle {
        position: fixed; bottom: 0.75rem; left: 0.75rem; z-index: 55;
        width: 44px; height: 44px; border-radius: 50%;
        background: rgba(99,102,241,0.6); border: none; color: white;
        font-size: 1.2rem; cursor: pointer;
        backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
      }

      /* Station prompt */
      #station-prompt {
        position: fixed; bottom: 4rem; left: 50%; transform: translateX(-50%);
        z-index: 45; padding: 0.6rem 1.5rem; border-radius: 1rem;
        background: linear-gradient(135deg, rgba(6,182,212,0.8), rgba(139,92,246,0.8));
        color: white; font-weight: bold; font-size: 1rem;
        cursor: pointer; display: none; white-space: nowrap;
        backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
        animation: promptBounce 1.5s ease-in-out infinite;
        -webkit-tap-highlight-color: transparent;
      }
      #station-prompt.visible { display: block; }
      @keyframes promptBounce {
        0%, 100% { transform: translateX(-50%) translateY(0); }
        50% { transform: translateX(-50%) translateY(-5px); }
      }
    </style>
  </head>
  <body>
    <div id="game-container">
      <div id="game-canvas"></div>
    </div>

    <div id="players-list"></div>

    <button id="chat-toggle" onclick="toggleChat()">💬</button>

    <div id="chat-bar">
      <input id="chat-input" type="text" placeholder="Say something..." maxlength="100" autocomplete="off" />
      <button onclick="sendChat()">Send</button>
    </div>

    <div id="station-prompt"></div>

    <div id="mini-game-overlay">
      <button class="mg-close" onclick="window.piStation.closeMiniGame()">&times;</button>
      <div id="mini-game-content"></div>
    </div>

    <script>
      window.PLAYER_TOKEN = "<%= @player.session_token %>";
      window.PLAYER_ID = "<%= @player.id %>";
      window.PLAYER_NAME = "<%= @player.name %>";
      window.PLAYER_AVATAR = "<%= @player.avatar_key %>";
      window.AUTO_OPEN_GAME = "<%= @auto_open_game || "" %>";

      // Chat toggle
      let chatVisible = false;
      function toggleChat() {
        chatVisible = !chatVisible;
        document.getElementById('chat-bar').classList.toggle('visible', chatVisible);
        if (chatVisible) document.getElementById('chat-input').focus();
      }
      function sendChat() {
        const input = document.getElementById('chat-input');
        const msg = input.value.trim();
        if (msg && window.piStation && window.piStation.sendChat) {
          window.piStation.sendChat(msg);
          input.value = '';
        }
      }
      document.getElementById('chat-input').addEventListener('keydown', (e) => {
        if (e.key === 'Enter') sendChat();
        e.stopPropagation(); // Don't let WASD keys move player while typing
      });
      document.getElementById('chat-input').addEventListener('keyup', (e) => e.stopPropagation());
      document.getElementById('chat-input').addEventListener('keypress', (e) => e.stopPropagation());

      // Prevent iOS rubber-band scrolling
      document.addEventListener('touchmove', (e) => {
        if (!e.target.closest('#mini-game-overlay') && !e.target.closest('#chat-bar')) {
          e.preventDefault();
        }
      }, { passive: false });
    </script>
    <script src={~p"/assets/js/game.js"}></script>
  </body>
</html>