<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, initial-scale=1, maximum-scale=1,
                 viewport-fit=cover">
  <title>Cozy Star Catcher</title>

  <style>
    /* 1. Theme and page layout */
    :root {
      --cream: #fff3dc;
      --muted: #c9c0df;
      --purple: #a99ae8;
      --night: #17152d;
      --panel: #24213f;
      --gold: #ffd98a;
    }

    * {
      box-sizing: border-box;
    }

    body {
      margin: 0;
      min-height: 100vh;
      min-height: 100dvh;
      display: flex;
      align-items: center;
      justify-content: center;
      padding: 14px;
      color: var(--cream);
      background:
        radial-gradient(ellipse at 50% 0%, #40375e 0%, transparent 55%),
        var(--night);
      font-family: system-ui, -apple-system, sans-serif;
      -webkit-tap-highlight-color: transparent;
    }

    button {
      min-height: 48px;
      padding: 12px 22px;
      border: 0;
      border-radius: 16px;
      background: var(--gold);
      color: #302543;
      font: inherit;
      font-weight: 800;
      cursor: pointer;
      touch-action: manipulation;
      transition: transform 0.15s, filter 0.15s;
    }

    button:active {
      transform: scale(0.96);
    }

    button:focus-visible {
      outline: 3px solid white;
      outline-offset: 4px;
    }

    /* 2. Main game card */
    .game {
      width: min(100%, 440px);
      overflow: hidden;
      border: 1px solid #51486f;
      border-radius: 28px;
      background: var(--panel);
      box-shadow: 0 24px 80px #09081799;
    }

    .header {
      padding: 20px 20px 14px;
      text-align: center;
    }

    .eyebrow {
      margin: 0 0 5px;
      color: var(--gold);
      font-size: 11px;
      font-weight: 800;
      letter-spacing: 3px;
      text-transform: uppercase;
    }

    h1 {
      margin: 0;
      font-size: clamp(25px, 7vw, 34px);
      letter-spacing: -1px;
    }

    .subtitle {
      margin: 7px 0 0;
      color: var(--muted);
      font-size: 13px;
    }

    /* 3. Score and timer */
    .stats {
      display: grid;
      grid-template-columns: 1fr 1fr 1fr;
      gap: 8px;
      padding: 0 16px 14px;
    }

    .stat {
      padding: 10px 5px;
      border: 1px solid #494263;
      border-radius: 14px;
      background: #19172e;
      text-align: center;
    }

    .stat-label {
      display: block;
      margin-bottom: 3px;
      color: var(--muted);
      font-size: 10px;
      letter-spacing: 1px;
      text-transform: uppercase;
    }

    .stat-value {
      font-size: 21px;
      font-weight: 800;
      font-variant-numeric: tabular-nums;
    }

    /* 4. Canvas play area */
    .stage {
      position: relative;
      margin: 0 12px;
      overflow: hidden;
      border: 1px solid #50466f;
      border-radius: 20px;
      background: #1b1935;
    }

    canvas {
      display: block;
      width: 100%;
      height: auto;
      aspect-ratio: 340 / 430;
      touch-action: none;
      cursor: crosshair;
    }

    /* 5. Start and end overlays */
    .overlay {
      position: absolute;
      inset: 0;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      padding: 24px;
      background: #17152dcc;
      text-align: center;
      backdrop-filter: blur(5px);
    }

    .overlay[hidden] {
      display: none;
    }

    .overlay-emoji {
      margin-bottom: 12px;
      font-size: 54px;
    }

    .overlay h2 {
      margin: 0 0 10px;
      font-size: 27px;
    }

    .overlay p {
      max-width: 260px;
      margin: 0 0 20px;
      color: var(--muted);
      font-size: 14px;
      line-height: 1.7;
    }

    /* 6. Touch controls and instructions */
    .controls {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 12px;
      padding: 14px 16px 8px;
    }

    .move-btn {
      min-height: 54px;
      border: 1px solid #625782;
      background: #393252;
      color: var(--cream);
      font-size: 23px;
      user-select: none;
      -webkit-user-select: none;
      touch-action: none;
    }

    .help {
      margin: 4px 16px 18px;
      color: var(--muted);
      font-size: 11px;
      line-height: 1.7;
      text-align: center;
    }

    .help strong {
      color: var(--cream);
    }

    /* 7. Larger screens */
    @media (min-width: 700px) {
      body {
        padding: 30px;
      }

      .game {
        width: 440px;
      }
    }

    @media (prefers-reduced-motion: reduce) {
      button {
        transition: none;
      }
    }
  </style>
</head>

<body>
  <main class="game">
    <!-- Game title -->
    <header class="header">
      <p class="eyebrow">A little night adventure</p>
      <h1>Cozy Star Catcher <span aria-hidden="true">🌙</span></h1>
      <p class="subtitle">A quiet little game beneath the stars.</p>
    </header>

    <!-- Scoreboard -->
    <section class="stats" aria-label="Game statistics">
      <div class="stat">
        <span class="stat-label">Stars</span>
        <span class="stat-value" id="score">0 / 15</span>
      </div>
      <div class="stat">
        <span class="stat-label">Time</span>
        <span class="stat-value" id="time">45</span>
      </div>
      <div class="stat">
        <span class="stat-label">Best</span>
        <span class="stat-value" id="best">0</span>
      </div>
    </section>

    <!-- Canvas and screen overlays -->
    <section class="stage" aria-label="Game area">
      <canvas id="gameCanvas" width="340" height="430"
              aria-label="Move the basket to catch stars and avoid rocks">
      </canvas>

      <div class="overlay" id="overlay">
        <div class="overlay-emoji" id="overlayEmoji">🌙</div>
        <h2 id="overlayTitle">Ready to catch stars?</h2>
        <p id="overlayText">
          Catch 15 glowing stars in 45 seconds.
          Watch out for the little rocks!
        </p>
        <button id="startBtn">Start playing ✨</button>
      </div>
    </section>

    <!-- Large touch-friendly buttons -->
    <section class="controls" aria-label="Movement controls">
      <button class="move-btn" id="leftBtn"
              aria-label="Move basket left">←</button>
      <button class="move-btn" id="rightBtn"
              aria-label="Move basket right">→</button>
    </section>

    <p class="help">
      <strong>Phone:</strong> Drag on the sky or hold ← →<br>
      <strong>Computer:</strong> Move your mouse or use arrow keys<br>
      Catch ⭐ stars · Avoid ☄️ rocks
    </p>
  </main>

  <script>
    (() => {
      "use strict";

      // 1. Find the page elements
      const canvas = document.getElementById("gameCanvas");
      const ctx = canvas.getContext("2d");

      const scoreEl = document.getElementById("score");
      const timeEl = document.getElementById("time");
      const bestEl = document.getElementById("best");

      const overlay = document.getElementById("overlay");
      const overlayEmoji = document.getElementById("overlayEmoji");
      const overlayTitle = document.getElementById("overlayTitle");
      const overlayText = document.getElementById("overlayText");
      const startBtn = document.getElementById("startBtn");

      const leftBtn = document.getElementById("leftBtn");
      const rightBtn = document.getElementById("rightBtn");

      // 2. Game settings
      const W = canvas.width;
      const H = canvas.height;
      const GOAL = 15;
      const ROUND_TIME = 45;
      const BASKET_SPEED = 270;

      let score = 0;
      let timeLeft = ROUND_TIME;
      let best = loadBest();
      let running = false;
      let ended = false;

      let basket = {
        x: W / 2,
        y: H - 45,
        w: 66,
        h: 28
      };

      let objects = [];
      let particles = [];
      let stars = [];
      let spawnTimer = 0;
      let elapsed = 0;
      let lastFrame = 0;
      let animationId = 0;
      let leftHeld = false;
      let rightHeld = false;
      let pointerDown = false;
      let targetX = null;

      // 3. Load and save the best score
      function loadBest() {
        try {
          return Number(localStorage.getItem("cozyStarBest")) || 0;
        } catch {
          return 0;
        }
      }

      function saveBest() {
        try {
          localStorage.setItem("cozyStarBest", String(best));
        } catch {
          // The game still works if browser storage is unavailable.
        }
      }

      bestEl.textContent = best;

      // 4. Create the decorative night sky
      function makeSky() {
        stars = Array.from({ length: 65 }, () => ({
          x: Math.random() * W,
          y: Math.random() * H,
          r: 0.5 + Math.random() * 1.5,
          phase: Math.random() * Math.PI * 2
        }));
      }

      function drawBackground() {
        const gradient = ctx.createLinearGradient(0, 0, 0, H);
        gradient.addColorStop(0, "#252044");
        gradient.addColorStop(1, "#17172f");

        ctx.fillStyle = gradient;
        ctx.fillRect(0, 0, W, H);

        // Tiny twinkling stars
        stars.forEach(star => {
          const alpha = 0.35 +
            0.45 * (0.5 + 0.5 * Math.sin(elapsed * 2 + star.phase));

          ctx.globalAlpha = alpha;
          ctx.fillStyle = "#fff0c9";
          ctx.beginPath();
          ctx.arc(star.x, star.y, star.r, 0, Math.PI * 2);
          ctx.fill();
        });

        ctx.globalAlpha = 1;

        // Moon and its glow
        ctx.save();
        ctx.shadowColor = "#ffe6a3";
        ctx.shadowBlur = 22;
        ctx.fillStyle = "#ffe8ae";
        ctx.beginPath();
        ctx.arc(278, 60, 23, 0, Math.PI * 2);
        ctx.fill();

        ctx.shadowBlur = 0;
        ctx.fillStyle = "#252044";
        ctx.beginPath();
        ctx.arc(288, 51, 21, 0, Math.PI * 2);
        ctx.fill();
        ctx.restore();

        // Distant rolling hills
        ctx.fillStyle = "#292747";
        ctx.beginPath();
        ctx.moveTo(0, H - 55);
        ctx.quadraticCurveTo(85, H - 115, 170, H - 55);
        ctx.quadraticCurveTo(255, H - 5, W, H - 70);
        ctx.lineTo(W, H);
        ctx.lineTo(0, H);
        ctx.fill();

        ctx.fillStyle = "#333052";
        ctx.beginPath();
        ctx.moveTo(0, H - 24);
        ctx.quadraticCurveTo(130, H - 75, 230, H - 25);
        ctx.quadraticCurveTo(290, H - 5, W, H - 42);
        ctx.lineTo(W, H);
        ctx.lineTo(0, H);
        ctx.fill();
      }

      // 5. Draw stars, rocks, and the player's basket
      function drawStar(x, y, radius, color) {
        ctx.save();
        ctx.translate(x, y);
        ctx.rotate(Math.sin(elapsed * 2 + y) * 0.12);
        ctx.shadowColor = color;
        ctx.shadowBlur = 13;
        ctx.fillStyle = color;
        ctx.beginPath();

        for (let i = 0; i < 10; i++) {
          const r = i % 2 === 0 ? radius : radius * 0.45;
          const a = -Math.PI / 2 + i * Math.PI / 5;
          const px = Math.cos(a) * r;
          const py = Math.sin(a) * r;

          if (i === 0) ctx.moveTo(px, py);
          else ctx.lineTo(px, py);
        }

        ctx.closePath();
        ctx.fill();
        ctx.restore();
      }

      function drawRock(obj) {
        ctx.save();
        ctx.translate(obj.x, obj.y);
        ctx.rotate(obj.rotation);

        ctx.fillStyle = "#a7a1ba";
        ctx.strokeStyle = "#d6cfe6";
        ctx.lineWidth = 1.5;
        ctx.beginPath();

        for (let i = 0; i < 8; i++) {
          const angle = i * Math.PI / 4;
          const r = obj.r * (i % 2 === 0 ? 1 : 0.78);
          const x = Math.cos(angle) * r;
          const y = Math.sin(angle) * r;

          if (i === 0) ctx.moveTo(x, y);
          else ctx.lineTo(x, y);
        }

        ctx.closePath();
        ctx.fill();
        ctx.stroke();

        ctx.restore();
      }

      function drawBasket() {
        const x = basket.x;
        const y = basket.y;

        ctx.save();
        ctx.translate(x, y);

        // Warm glowing basket
        ctx.shadowColor = "#ffc879";
        ctx.shadowBlur = 14;
        ctx.fillStyle = "#dca46f";
        ctx.beginPath();
        ctx.moveTo(-basket.w / 2, -basket.h / 2);
        ctx.lineTo(basket.w / 2, -basket.h / 2);
        ctx.lineTo(basket.w / 2 - 8, basket.h / 2);
        ctx.lineTo(-basket.w / 2 + 8, basket.h / 2);
        ctx.closePath();
        ctx.fill();

        ctx.shadowBlur = 0;
        ctx.strokeStyle = "#ffe2a8";
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.moveTo(-basket.w / 2 - 3, -basket.h / 2);
        ctx.lineTo(basket.w / 2 + 3, -basket.h / 2);
        ctx.stroke();

        // Basket weave
        ctx.strokeStyle = "#9f704f";
        ctx.lineWidth = 2;

        for (let x = -18; x <= 18; x += 12) {
          ctx.beginPath();
          ctx.moveTo(x, -5);
          ctx.lineTo(x, 9);
          ctx.stroke();
        }

        // Little handle
        ctx.strokeStyle = "#ffe2a8";
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.arc(0, -basket.h / 2, 17, Math.PI, 0);
        ctx.stroke();

        ctx.restore();
      }

      // 6. Particle effects when an object is caught
      function burst(x, y, color) {
        for (let i = 0; i < 10; i++) {
          const angle = Math.random() * Math.PI * 2;
          const speed = 35 + Math.random() * 100;

          particles.push({
            x,
            y,
            vx: Math.cos(angle) * speed,
            vy: Math.sin(angle) * speed,
            life: 0.45 + Math.random() * 0.3,
            maxLife: 0.75,
            color
          });
        }
      }

      function drawParticles(dt) {
        particles = particles.filter(p => p.life > 0);

        particles.forEach(p => {
          p.x += p.vx * dt;
          p.y += p.vy * dt;
          p.vy += 60 * dt;
          p.life -= dt;

          ctx.globalAlpha = Math.max(0, p.life / p.maxLife);
          ctx.fillStyle = p.color;
          ctx.beginPath();
          ctx.arc(p.x, p.y, 3, 0, Math.PI * 2);
          ctx.fill();
        });

        ctx.globalAlpha = 1;
      }

      // 7. Spawn falling objects at random positions
      function spawnObject() {
        const rock = Math.random() < 0.24;

        objects.push({
          x: 20 + Math.random() * (W - 40),
          y: -20,
          r: rock ? 12 : 11,
          type: rock ? "rock" : "star",
          speed: 95 + Math.random() * 65 + score * 1.5,
          rotation: Math.random() * Math.PI
        });
      }

      // 8. Update positions, collisions, and countdown
      function update(dt) {
        elapsed += dt;

        if (leftHeld) {
          basket.x -= BASKET_SPEED * dt;
          targetX = null;
        }

        if (rightHeld) {
          basket.x += BASKET_SPEED * dt;
          targetX = null;
        }

        // Smooth mouse and touch dragging
        if (targetX !== null) {
          basket.x += (targetX - basket.x) *
            Math.min(1, dt * 16);
        }

        basket.x = Math.max(
          basket.w / 2 + 4,
          Math.min(W - basket.w / 2 - 4, basket.x)
        );

        spawnTimer -= dt;

        if (spawnTimer <= 0) {
          spawnObject();
          spawnTimer = Math.max(0.34, 0.8 - score * 0.015);
        }

        // Move falling objects and check for catches
        objects = objects.filter(obj => {
          obj.y += obj.speed * dt;
          obj.rotation += dt * 1.2;

          const caught =
            obj.y + obj.r >= basket.y - basket.h / 2 &&
            obj.y - obj.r <= basket.y + basket.h / 2 &&
            obj.x + obj.r >= basket.x - basket.w / 2 &&
            obj.x - obj.r <= basket.x + basket.w / 2;

          if (caught) {
            if (obj.type === "star") {
              score++;
              burst(obj.x, obj.y, "#ffe08a");
            } else {
              score = Math.max(0, score - 2);
              burst(obj.x, obj.y, "#c8bbef");
            }

            updateStats();
            return false;
          }

          return obj.y < H + 30;
        });

        drawParticles(dt);

        timeLeft -= dt;
        updateStats();

        // Reaching the target ends the round immediately
        if (score >= GOAL) {
          finishGame(true);
        } else if (timeLeft <= 0) {
          finishGame(false);
        }
      }

      // 9. Refresh the scoreboard
      function updateStats() {
        scoreEl.textContent = `${score} / ${GOAL}`;
        timeEl.textContent = Math.max(0, Math.ceil(timeLeft));
        bestEl.textContent = best;
      }

      // 10. Draw one complete frame
      function draw(dt = 0) {
        drawBackground();

        objects.forEach(obj => {
          if (obj.type === "star") {
            drawStar(obj.x, obj.y, obj.r, "#ffdc86");
          } else {
            drawRock(obj);
          }
        });

        drawBasket();

        if (dt > 0) {
          drawParticles(dt);
        }
      }

      // 11. Main animation loop
      function loop(timestamp) {
        if (!running) return;

        const dt = Math.min(
          (timestamp - lastFrame) / 1000 || 0,
          0.04
        );

        lastFrame = timestamp;
        update(dt);
        draw();

        if (running) {
          animationId = requestAnimationFrame(loop);
        }
      }

      // 12. Start a fresh round
      function startGame() {
        cancelAnimationFrame(animationId);

        score = 0;
        timeLeft = ROUND_TIME;
        elapsed = 0;
        spawnTimer = 0.4;
        objects = [];
        particles = [];

        basket.x = W / 2;
        targetX = null;
        leftHeld = false;
        rightHeld = false;
        pointerDown = false;

        ended = false;
        running = true;

        updateStats();
        overlay.hidden = true;

        lastFrame = performance.now();
        animationId = requestAnimationFrame(loop);
      }

      // 13. Finish the round and show the result
      function finishGame(won) {
        if (!running) return;

        running = false;
        ended = true;
        cancelAnimationFrame(animationId);

        if (score > best) {
          best = score;
          saveBest();
        }

        updateStats();

        overlayEmoji.textContent = won ? "🌟" : "🌙";
        overlayTitle.textContent = won
          ? "You caught them all!"
          : "The night is over";

        overlayText.textContent = won
          ? `Amazing! You caught ${score} stars. Your little sky is sparkling!`
          : `You caught ${score} of ${GOAL} stars. Take a breath and try again!`;

        startBtn.textContent = "Play again ↻";
        overlay.hidden = false;
      }

      // 14. Convert screen coordinates to canvas coordinates
      function pointerToCanvas(event) {
        const rect = canvas.getBoundingClientRect();

        return {
          x: (event.clientX - rect.left) * W / rect.width,
          y: (event.clientY - rect.top) * H / rect.height
        };
      }

      canvas.addEventListener("pointerdown", event => {
        if (!running) return;

        event.preventDefault();
        pointerDown = true;

        const point = pointerToCanvas(event);
        targetX = point.x;

        try {
          canvas.setPointerCapture(event.pointerId);
        } catch {
          // Pointer capture may not be available in every browser.
        }
      });

      canvas.addEventListener("pointermove", event => {
        if (!running || !pointerDown) return;

        event.preventDefault();
        targetX = pointerToCanvas(event).x;
      });

      function releasePointer() {
        pointerDown = false;
        targetX = null;
      }

      canvas.addEventListener("pointerup", releasePointer);
      canvas.addEventListener("pointercancel", releasePointer);
      canvas.addEventListener("lostpointercapture", releasePointer);

      // 15. Hold-to-move buttons for phones and tablets
      function bindHoldButton(button, direction) {
        const press = event => {
          event.preventDefault();

          if (!running) return;

          if (direction === "left") {
            leftHeld = true;
            rightHeld = false;
          } else {
            rightHeld = true;
            leftHeld = false;
          }

          targetX = null;

          try {
            button.setPointerCapture(event.pointerId);
          } catch {
            // The buttons still work without pointer capture.
          }
        };

        const release = event => {
          event.preventDefault();

          if (direction === "left") leftHeld = false;
          else rightHeld = false;
        };

        button.addEventListener("pointerdown", press);
        button.addEventListener("pointerup", release);
        button.addEventListener("pointercancel", release);
        button.addEventListener("lostpointercapture", release);
      }

      bindHoldButton(leftBtn, "left");
      bindHoldButton(rightBtn, "right");

      // 16. Keyboard controls for desktop
      document.addEventListener("keydown", event => {
        if (!running) return;

        if (event.key === "ArrowLeft" || event.key === "a") {
          event.preventDefault();
          leftHeld = true;
          targetX = null;
        }

        if (event.key === "ArrowRight" || event.key === "d") {
          event.preventDefault();
          rightHeld = true;
          targetX = null;
        }
      });

      document.addEventListener("keyup", event => {
        if (event.key === "ArrowLeft" || event.key === "a") {
          leftHeld = false;
        }

        if (event.key === "ArrowRight" || event.key === "d") {
          rightHeld = false;
        }
      });

      // Stop movement if the browser tab becomes hidden
      document.addEventListener("visibilitychange", () => {
        if (document.hidden) {
          leftHeld = false;
          rightHeld = false;
          releasePointer();
        }
      });

      // 17. Start and replay buttons
      startBtn.addEventListener("click", startGame);

      // 18. Show the initial scene
      makeSky();
      draw();
      updateStats();
    })();
  </script>
</body>
</html>