📄 +page.svelte
<script lang="ts">
  import { onMount, onDestroy } from "svelte";
  import { io, type Socket } from "socket.io-client";
  import { PLAYER_COLORS, type Player, type GameConfig } from "$lib/types";
  import GameScreen from "$lib/components/GameScreen.svelte";
  import ResultsScreen from "$lib/components/ResultsScreen.svelte";
  import QRCode from "qrcode";

  let socket: Socket | null = null;
  let roomCode = $state("");
  let qrCodeUrl = $state("");
  let connectedDisplays = $state(0);
  let displayIds = $state<string[]>([]);
  let masterSocketId = $state<string>("");

  // Game configuration
  let playerCount = $state(2);
  let gameDuration = $state(60);
  let players = $state<Player[]>([]);

  // Game state
  let gameState = $state<"setup" | "playing" | "results">("setup");
  let scores = $state<Player[]>([]);

  onMount(() => {
    // Connect to signaling server
    // In production, Socket.IO runs on same server as SvelteKit
    // In development, it runs on separate port (3001)
    const socketUrl = import.meta.env.DEV ? "http://localhost:3001" : window.location.origin;
    socket = io(socketUrl);

    socket.on("connect", () => {
      console.log("Connected to server");
      // Store master's socket ID
      if (socket?.id) {
        masterSocketId = socket.id;
      }
      // Create a room
      socket?.emit("create-room", (response: any) => {
        if (response.success) {
          roomCode = response.roomCode;
        }
      });
    });

    socket.on("display-joined", ({ displayId, totalDisplays }: any) => {
      console.log("Display joined:", displayId);
      connectedDisplays = totalDisplays;
      displayIds = [...displayIds, displayId];
    });

    socket.on("display-left", ({ displayId, totalDisplays }: any) => {
      console.log("Display left:", displayId);
      connectedDisplays = totalDisplays;
      displayIds = displayIds.filter((id) => id !== displayId);
    });

    socket.on("display-action", ({ from, action }: any) => {
      console.log("Action from display:", from, action);
      if (action.type === "tap") {
        handleTap(action.playerId);
      }
    });

    socket.on("room-closed", () => {
      console.log("Room closed");
      // Handle room closure
    });
  });

  onDestroy(() => {
    socket?.disconnect();
  });

  function updatePlayers() {
    players = Array.from({ length: playerCount }, (_, i) => ({
      id: i,
      color: PLAYER_COLORS[i % PLAYER_COLORS.length],
      score: 0,
    }));
  }

  $effect(() => {
    updatePlayers();
  });

  $effect(() => {
    if (roomCode) {
      // Generate QR code URL for joining
      const joinUrl = `${window.location.origin}/join?code=${roomCode}`;
      QRCode.toDataURL(joinUrl, { width: 200, margin: 2 })
        .then((url) => {
          qrCodeUrl = url;
        })
        .catch((err) => {
          console.error("QR code generation error:", err);
        });
    }
  });

  async function startGame() {
    // Initialize scores
    scores = players.map((p) => ({ ...p, score: 0 }));

    // Transition to game state
    gameState = "playing";

    // Request wake lock on this device
    try {
      await requestWakeLock();
    } catch (e) {
      console.error("Wake lock failed:", e);
    }

    // Tell all displays to start the game
    socket?.emit("game-broadcast", {
      roomCode,
      command: {
        type: "start-game",
        duration: gameDuration,
        players: players,
      },
    });

    // Start the game loop
    runGameLoop();
  }

  let wakeLock: WakeLockSentinel | null = null;

  async function requestWakeLock() {
    if ("wakeLock" in navigator) {
      try {
        wakeLock = await navigator.wakeLock.request("screen");
        wakeLock.addEventListener("release", () => {
          console.log("Wake lock released");
        });
      } catch (err) {
        console.error("Wake lock error:", err);
      }
    }
  }

  function releaseWakeLock() {
    if (wakeLock) {
      wakeLock.release();
      wakeLock = null;
    }
  }

  function runGameLoop() {
    setTimeout(() => {
      endGame();
    }, gameDuration * 1000);
  }

  function handleTap(playerId: number) {
    // Increment score
    const player = scores.find((p) => p.id === playerId);
    if (player) {
      player.score++;
      scores = [...scores];
    }
  }

  function endGame() {
    gameState = "results";
    releaseWakeLock();

    // Send results to all displays
    socket?.emit("game-broadcast", {
      roomCode,
      command: {
        type: "show-results",
        scores: scores,
      },
    });
  }

  function restartGame() {
    gameState = "setup";
    scores = [];
  }

  function quitGame() {
    socket?.disconnect();
    // Redirect to home
    window.location.href = "/";
  }
</script>

{#if gameState === "setup"}
  <div class="setup-container">
    <h1>Master Device</h1>

    {#if roomCode}
      <div class="room-code">
        <h2>Room Code</h2>
        {#if qrCodeUrl}
          <img src={qrCodeUrl} alt="QR Code to join room" class="qr-code" />
        {/if}
        <div class="code">{roomCode}</div>
      </div>

      <div class="display-count">
        <h3>Connected Displays: {connectedDisplays}</h3>
      </div>

      <div class="config">
        <h3>Game Configuration</h3>

        <div class="form-group">
          <label for="numPlayers">Number of Players:</label>
          <input type="number" id="numPlayers" bind:value={playerCount} min="1" max="10" />
        </div>

        <div class="form-group">
          <label for="duration">Game Duration (seconds):</label>
          <input type="number" id="duration" bind:value={gameDuration} min="10" max="300" />
        </div>

        <div class="players-preview">
          <h4>Players:</h4>
          <div class="player-colors">
            {#each players as player}
              <div class="player-color" style="background-color: {player.color}">
                Player {player.id + 1}
              </div>
            {/each}
          </div>
        </div>

        <button class="start-button" onclick={startGame}>Start Game</button>
      </div>
    {:else}
      <p>Connecting to server...</p>
    {/if}
  </div>
{:else if gameState === "playing"}
  <GameScreen {playerCount} onTap={handleTap} />
{:else if gameState === "results"}
  <ResultsScreen {scores} isMaster={true} onRestart={restartGame} onQuit={quitGame} />
{/if}

<style>
  :global(body) {
    margin: 0;
    padding: 0;
    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
  }

  .setup-container {
    max-width: 600px;
    margin: 0 auto;
    padding: 2rem;
    text-align: center;
  }

  h1 {
    color: #333;
    margin-bottom: 2rem;
  }

  .room-code {
    background: #f0f0f0;
    padding: 2rem;
    border-radius: 8px;
    margin-bottom: 2rem;
  }

  .qr-code {
    margin: 1.5rem auto 1rem;
    display: block;
    border: 4px solid white;
    border-radius: 8px;
    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  }

  .code {
    font-size: 3rem;
    font-weight: bold;
    letter-spacing: 0.5rem;
    color: #007bff;
    margin-top: 1rem;
  }

  .display-count {
    margin-bottom: 2rem;
  }

  .display-count h3 {
    color: #666;
  }

  .config {
    background: #fff;
    border: 2px solid #ddd;
    border-radius: 8px;
    padding: 2rem;
    margin-top: 2rem;
  }

  .form-group {
    margin-bottom: 1.5rem;
    text-align: left;
  }

  label {
    display: block;
    margin-bottom: 0.5rem;
    font-weight: bold;
    color: #555;
  }

  input[type="number"] {
    width: 100%;
    padding: 0.75rem;
    font-size: 1rem;
    border: 2px solid #ddd;
    border-radius: 4px;
    box-sizing: border-box;
  }

  .players-preview {
    margin-top: 2rem;
    text-align: left;
  }

  .player-colors {
    display: flex;
    flex-wrap: wrap;
    gap: 0.5rem;
    margin-top: 1rem;
  }

  .player-color {
    padding: 0.75rem 1.5rem;
    border-radius: 4px;
    color: white;
    font-weight: bold;
    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
  }

  .start-button {
    width: 100%;
    padding: 1rem;
    font-size: 1.25rem;
    font-weight: bold;
    background: #28a745;
    color: white;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    margin-top: 2rem;
    transition: background 0.2s;
  }

  .start-button:hover {
    background: #218838;
  }

  .start-button:active {
    background: #1e7e34;
  }
</style>