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

  let socket: Socket | null = null;
  let roomCodeInput = $state("");

  // State
  let gameState = $state<"join" | "waiting" | "playing" | "results">("join");
  let playerCount = $state(0);
  let scores = $state<Player[]>([]);
  let errorMessage = $state("");

  onMount(() => {
    // Connect to 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");

      // Check for room code in query parameter and auto-join
      const codeParam = $page.url.searchParams.get("code");
      if (codeParam) {
        roomCodeInput = codeParam.toUpperCase();
        // Auto-join after a brief delay to ensure socket is ready
        setTimeout(() => joinRoom(), 100);
      }
    });

    socket.on("game-command", (command: any) => {
      handleCommand(command);
    });

    socket.on("room-closed", () => {
      console.log("Room closed by master");
      gameState = "join";
      releaseWakeLock();
      alert("Room closed by master device");
    });
  });

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

  function joinRoom() {
    if (!roomCodeInput.trim()) {
      errorMessage = "Please enter a room code";
      return;
    }

    const code = roomCodeInput.toUpperCase().trim();

    socket?.emit("join-room", code, (response: any) => {
      if (response.success) {
        console.log("Joined room successfully");
        errorMessage = "";
        gameState = "waiting";
      } else {
        errorMessage = response.error || "Failed to join room";
      }
    });
  }

  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;
    }
  }

  async function handleCommand(command: any) {
    console.log("Received command:", command);

    switch (command.type) {
      case "start-game":
        gameState = "playing";
        playerCount = command.players.length;
        await requestWakeLock();
        break;

      case "show-results":
        gameState = "results";
        scores = command.scores;
        releaseWakeLock();
        break;
    }
  }

  function handleTap(playerId: number) {
    // Send tap event to master
    socket?.emit("display-action", {
      type: "tap",
      playerId,
    });
  }
</script>

{#if gameState === "join"}
  <div class="join-container">
    <h1>Join Game</h1>

    <div class="join-form">
      <div class="form-group">
        <label for="roomCode">Enter Room Code:</label>
        <input
          type="text"
          id="roomCode"
          bind:value={roomCodeInput}
          placeholder="ABC123"
          maxlength="6"
          onkeydown={(e) => e.key === "Enter" && joinRoom()}
        />
      </div>

      {#if errorMessage}
        <div class="error">{errorMessage}</div>
      {/if}

      <button class="join-button" onclick={joinRoom}>Join Room</button>
    </div>
  </div>
{:else if gameState === "waiting"}
  <div class="waiting-container">
    <h1>Waiting for Game to Start</h1>
    <p>Connected to room. Waiting for master to start the game...</p>
    <div class="spinner"></div>
  </div>
{:else if gameState === "playing"}
  <GameScreen {playerCount} onTap={handleTap} />
{:else if gameState === "results"}
  <ResultsScreen {scores} isMaster={false} />
{/if}

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

  .join-container {
    max-width: 400px;
    margin: 0 auto;
    padding: 2rem;
    text-align: center;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
  }

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

  .join-form {
    background: #fff;
    border: 2px solid #ddd;
    border-radius: 8px;
    padding: 2rem;
  }

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

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

  input[type="text"] {
    width: 100%;
    padding: 1rem;
    font-size: 1.5rem;
    text-align: center;
    text-transform: uppercase;
    border: 2px solid #ddd;
    border-radius: 4px;
    box-sizing: border-box;
    letter-spacing: 0.25rem;
  }

  .error {
    color: #dc3545;
    margin-bottom: 1rem;
    padding: 0.75rem;
    background: #f8d7da;
    border: 1px solid #f5c6cb;
    border-radius: 4px;
  }

  .join-button {
    width: 100%;
    padding: 1rem;
    font-size: 1.25rem;
    font-weight: bold;
    background: #007bff;
    color: white;
    border: none;
    border-radius: 8px;
    cursor: pointer;
    transition: background 0.2s;
  }

  .join-button:hover {
    background: #0056b3;
  }

  .join-button:active {
    background: #004085;
  }

  .waiting-container {
    text-align: center;
    padding: 4rem 2rem;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
  }

  .waiting-container h1 {
    margin-bottom: 1rem;
  }

  .waiting-container p {
    color: #666;
    font-size: 1.1rem;
    margin-bottom: 2rem;
  }

  .spinner {
    border: 4px solid #f3f3f3;
    border-top: 4px solid #007bff;
    border-radius: 50%;
    width: 50px;
    height: 50px;
    animation: spin 1s linear infinite;
  }

  @keyframes spin {
    0% {
      transform: rotate(0deg);
    }
    100% {
      transform: rotate(360deg);
    }
  }
</style>