• Updated 30 Oct, 21:11 UTC
  • Next run 31 Oct, 05:17 UTCNext 31 Oct 05:17
  • Last 7 days: 4 new models · 475 runs7 d: 4 models · 475 runs
Playable Breakout

Kimi K3.1 ProMoonshot AI

Network offOpen alone (new tab)

Run details

Model
moonshotai/kimi-k3.1-pro
Route
OpenRouter API, one user message
Served by
Moonshot AI
Effort
max, OpenRouter's default for this model
Limits
60 min · 300,000 output tokens (our $3.00 stop per request at $10/M)
Date
23 Oct 2026
Time
8 min 18 s
Size
44 kB
Tokens
162 in, 34,869 out (22,394 of them thinking)
List price
$0.35 at today's price ($2 in / $10 out per M)
Paid
$0.35

Raw files: output · meta.json

Exact prompt

Make a Breakout game I can play right now with the keyboard or the mouse. Give it several levels, a score and some juice.

It must be one complete, self-contained HTML file: inline all CSS and JavaScript, and make no network requests of any kind (no CDNs, web fonts or remote images). Reply with the file in a single ```html code block.

Source

44 kB 1,575 lines Raw

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="theme-color" content="#070817">
  <link rel="icon" href="data:,">
  <title>Neon Breakout</title>
  <style>
    * { box-sizing: border-box; }

    :root {
      color-scheme: dark;
      --cyan: #44e7ff;
      --pink: #ff4fa3;
      --violet: #8b6cff;
      --ink: #070817;
      --muted: #7f89ad;
    }

    html, body {
      margin: 0;
      min-height: 100%;
      background: #070817;
    }

    body {
      min-height: 100vh;
      min-height: 100dvh;
      display: grid;
      place-items: center;
      overflow: hidden;
      padding: 12px;
      color: #edf3ff;
      font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
      user-select: none;
      background:
        radial-gradient(circle at 15% 15%, rgba(71, 52, 170, .18), transparent 36%),
        radial-gradient(circle at 85% 85%, rgba(0, 194, 255, .10), transparent 34%),
        #070817;
    }

    #app {
      width: min(1080px, 96vw, calc(160vh - 190px));
    }

    .topline {
      min-height: 25px;
      margin-bottom: 9px;
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 16px;
      font-size: 11px;
      letter-spacing: .16em;
    }

    .brand {
      display: flex;
      align-items: center;
      gap: 9px;
      font-weight: 800;
    }

    .brand-dot {
      width: 8px;
      height: 8px;
      border-radius: 50%;
      background: var(--cyan);
      box-shadow: 0 0 14px var(--cyan);
    }

    .tag {
      color: var(--muted);
      font-size: 9px;
    }

    #stage {
      position: relative;
      width: 100%;
      aspect-ratio: 8 / 5;
      overflow: hidden;
      line-height: 0;
      border: 1px solid rgba(119, 149, 255, .28);
      border-radius: 14px;
      background: #090b1c;
      box-shadow:
        0 28px 90px rgba(0, 0, 0, .55),
        0 0 45px rgba(81, 77, 255, .10),
        inset 0 0 30px rgba(0, 0, 0, .4);
    }

    canvas {
      display: block;
      width: 100%;
      height: 100%;
      border-radius: inherit;
      outline: none;
      touch-action: none;
      cursor: crosshair;
    }

    canvas:focus-visible {
      box-shadow: inset 0 0 0 2px rgba(68, 231, 255, .6);
    }

    .scanlines {
      position: absolute;
      inset: 0;
      pointer-events: none;
      border-radius: inherit;
      opacity: .16;
      background: repeating-linear-gradient(
        to bottom,
        transparent 0,
        transparent 3px,
        rgba(255, 255, 255, .025) 4px
      );
      box-shadow: inset 0 0 70px rgba(0, 0, 0, .45);
    }

    .help {
      margin-top: 10px;
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 16px;
      color: #8993b5;
      font-size: 10px;
      line-height: 22px;
    }

    kbd {
      display: inline-block;
      min-width: 20px;
      padding: 0 5px;
      margin: 0 1px;
      border: 1px solid rgba(143, 159, 207, .28);
      border-bottom-color: rgba(143, 159, 207, .5);
      border-radius: 4px;
      background: rgba(255, 255, 255, .04);
      color: #cbd5f3;
      font: inherit;
      line-height: 17px;
      text-align: center;
    }

    @media (max-width: 620px) {
      .tag, .help span:last-child { display: none; }
      .help { justify-content: center; font-size: 9px; }
    }

    @media (max-height: 560px) {
      body { padding: 8px; }
      .topline, .help { display: none; }
      #app { width: min(1080px, 96vw, calc(160vh - 26px)); }
    }
  </style>
</head>
<body>
  <main id="app">
    <div class="topline">
      <div class="brand"><span class="brand-dot"></span>NEON // BREAKOUT</div>
      <div class="tag">FIVE LEVELS · ONE VERY RESPONSIBLE PADDLE</div>
    </div>

    <div id="stage">
      <canvas
        id="game"
        width="960"
        height="600"
        tabindex="0"
        aria-label="Neon Breakout. Move with the mouse, arrow keys, or A and D. Press Space to launch, P to pause, M to toggle sound, and R to restart."
      ></canvas>
      <div class="scanlines"></div>
    </div>

    <div class="help">
      <span><kbd>←</kbd><kbd>→</kbd> / <kbd>A</kbd><kbd>D</kbd> or mouse to move</span>
      <span><kbd>Space</kbd> launch · <kbd>P</kbd> pause · <kbd>M</kbd> sound · <kbd>R</kbd> restart</span>
    </div>
  </main>

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

      const W = 960;
      const H = 600;
      const TOP = 62;
      const WALL_LEFT = 13;
      const WALL_RIGHT = W - 13;

      const canvas = document.getElementById("game");
      const ctx = canvas.getContext("2d");

      const levels = [
        {
          name: "IGNITION",
          speed: 380,
          palette: ["#22d3ee", "#6366f1", "#a855f7"],
          rows: [
            "111111111111",
            "111111111111",
            "111111111111"
          ]
        },
        {
          name: "PYRAMID",
          speed: 400,
          palette: ["#f472b6", "#a855f7", "#6366f1"],
          rows: [
            "....2222....",
            "...111111...",
            "..22222222..",
            ".1111111111."
          ]
        },
        {
          name: "FORTRESS",
          speed: 425,
          palette: ["#fbbf24", "#fb7185", "#f472b6"],
          rows: [
            "222222222222",
            "2..........2",
            "2.11111111.2",
            "2..........2",
            "222222222222"
          ]
        },
        {
          name: "CHECKER",
          speed: 450,
          palette: ["#34d399", "#22d3ee", "#818cf8"],
          rows: [
            "3.3.3.3.3.3.",
            ".2.2.2.2.2.2",
            "1.1.1.1.1.1.",
            ".2.2.2.2.2.2",
            "3.3.3.3.3.3."
          ]
        },
        {
          name: "THE CORE",
          speed: 475,
          palette: ["#ff4d8d", "#a855f7", "#22d3ee"],
          rows: [
            "....3333....",
            "..22222222..",
            ".3333333333.",
            "111111111111"
          ]
        }
      ];

      const POWERS = {
        wide:  { color: "#42f5ff", icon: "W",  name: "WIDE PADDLE" },
        slow:  { color: "#8292ff", icon: "S",  name: "SLOW BALL" },
        multi: { color: "#ff4fd8", icon: "×3", name: "MULTIBALL" },
        life:  { color: "#ffd166", icon: "+",  name: "EXTRA LIFE" }
      };

      const clamp = (value, min, max) => Math.max(min, Math.min(max, value));

      let dpr = 1;
      let state = "ready";
      let serveType = "start";
      let levelIndex = 0;
      let score = 0;
      let lives = 3;
      let best = loadBest();
      let combo = 0;
      let levelBonus = 0;

      let bricks = [];
      let balls = [];
      let particles = [];
      let powerups = [];
      let floaters = [];

      let wideTimer = 0;
      let shake = 0;
      let flash = 0;
      let time = 0;
      let bannerTimer = 0;
      let muted = false;

      const paddle = {
        x: W / 2,
        y: H - 43,
        w: 122,
        h: 14,
        vx: 0
      };

      const keys = { left: false, right: false };
      const pointer = { x: W / 2, active: false };

      let audio = null;
      let master = null;
      let lastFrame = performance.now();

      const stars = Array.from({ length: 90 }, () => ({
        x: 18 + Math.random() * (W - 36),
        y: TOP + Math.random() * (H - TOP),
        size: .4 + Math.random() * 1.4,
        phase: Math.random() * Math.PI * 2,
        speed: .4 + Math.random() * 1.5
      }));

      function loadBest() {
        try {
          return Number(localStorage.getItem("neon-breakout-best")) || 0;
        } catch (_) {
          return 0;
        }
      }

      function saveBest() {
        try {
          localStorage.setItem("neon-breakout-best", String(best));
        } catch (_) {}
      }

      function resizeCanvas() {
        dpr = Math.min(window.devicePixelRatio || 1, 2);
        canvas.width = Math.round(W * dpr);
        canvas.height = Math.round(H * dpr);
      }

      function makeBall(x, y, stuck = false) {
        return {
          x, y,
          r: 7,
          vx: 0,
          vy: 0,
          stuck,
          dead: false,
          trail: []
        };
      }

      function resetBall() {
        balls = [makeBall(paddle.x, paddle.y - 10, true)];
      }

      function createBricks(config) {
        const result = [];
        const cols = 12;
        const gap = 6;
        const boardX = 48;
        const boardW = 864;
        const brickW = (boardW - gap * (cols - 1)) / cols;
        const brickH = 24;

        config.rows.forEach((row, r) => {
          for (let c = 0; c < cols; c++) {
            const char = row[c] || ".";
            const hp = Number(char);
            if (!Number.isFinite(hp) || hp <= 0) continue;

            result.push({
              x: boardX + c * (brickW + gap),
              y: 92 + r * 32,
              w: brickW,
              h: brickH,
              row: r,
              col: c,
              hp,
              maxHp: hp,
              alive: true,
              flash: 0,
              color: config.palette[(r + c) % config.palette.length]
            });
          }
        });

        return result;
      }

      function loadLevel(index) {
        levelIndex = index;
        bricks = createBricks(levels[index]);
        powerups = [];
        floaters = [];
        combo = 0;
        wideTimer = 0;
        paddle.x = W / 2;
        paddle.w = 122;
        paddle.vx = 0;
        resetBall();
        bannerTimer = 2.2;
      }

      function resetGame() {
        if (score > 0) saveBest();
        score = 0;
        lives = 3;
        combo = 0;
        levelBonus = 0;
        particles = [];
        shake = 0;
        flash = .25;
        keys.left = false;
        keys.right = false;
        loadLevel(0);
        state = "ready";
        serveType = "start";
      }

      function addScore(points) {
        score += points;
        if (score > best) best = score;
      }

      function launchBall() {
        if (state !== "ready") return;
        const ball = balls[0];
        if (!ball) return;

        state = "playing";
        const speed = levels[levelIndex].speed;
        const angle = (Math.random() - .5) * .55;
        ball.stuck = false;
        ball.vx = Math.sin(angle) * speed;
        ball.vy = -Math.cos(angle) * speed;

        spawnBurst(ball.x, ball.y, "#55edff", 12, 135);
        tone(240, .12, "triangle", .05, 1.8);
      }

      function completeLevel() {
        if (state !== "playing") return;

        levelBonus = 500 * (levelIndex + 1) + lives * 100;
        addScore(levelBonus);
        saveBest();
        powerups = [];
        shake = 10;
        flash = .3;

        if (levelIndex === levels.length - 1) {
          state = "won";
          sfxClear(true);
        } else {
          state = "levelclear";
          sfxClear(false);
        }
      }

      function nextLevel() {
        if (levelIndex >= levels.length - 1) return;
        loadLevel(levelIndex + 1);
        state = "ready";
        launchBall();
      }

      function loseLife() {
        lives = Math.max(0, lives - 1);
        combo = 0;
        wideTimer = 0;
        powerups = [];
        bannerTimer = 0;
        shake = 14;
        flash = .5;

        tone(220, .22, "sawtooth", .045, .45);
        tone(130, .28, "triangle", .04, .55, .09);

        if (lives === 0) {
          state = "gameover";
          balls = [];
          saveBest();
        } else {
          resetBall();
          state = "ready";
          serveType = "lost";
        }
      }

      function handleAction() {
        initAudio();

        if (state === "ready") {
          launchBall();
        } else if (state === "paused") {
          state = "playing";
        } else if (state === "levelclear") {
          nextLevel();
        } else if (state === "gameover" || state === "won") {
          resetGame();
          launchBall();
        }
      }

      function togglePause() {
        if (state === "playing") {
          state = "paused";
        } else if (state === "paused") {
          state = "playing";
        }
      }

      function initAudio() {
        if (audio) {
          if (audio.state === "suspended") audio.resume().catch(() => {});
          return;
        }

        try {
          const AudioContext = window.AudioContext || window.webkitAudioContext;
          if (!AudioContext) return;
          audio = new AudioContext();
          master = audio.createGain();
          master.gain.value = .5;
          master.connect(audio.destination);
        } catch (_) {}
      }

      function tone(freq, duration = .08, type = "sine", volume = .04, slide = 1, delay = 0) {
        if (muted || !audio || !master) return;

        try {
          const start = audio.currentTime + delay;
          const oscillator = audio.createOscillator();
          const gain = audio.createGain();

          oscillator.type = type;
          oscillator.frequency.setValueAtTime(Math.max(30, freq), start);
          if (slide !== 1) {
            oscillator.frequency.exponentialRampToValueAtTime(
              Math.max(30, freq * slide),
              start + duration
            );
          }

          gain.gain.setValueAtTime(.0001, start);
          gain.gain.exponentialRampToValueAtTime(volume, start + .008);
          gain.gain.exponentialRampToValueAtTime(.0001, start + duration);

          oscillator.connect(gain);
          gain.connect(master);
          oscillator.start(start);
          oscillator.stop(start + duration + .025);
        } catch (_) {}
      }

      function sfxClear(won) {
        const notes = won ? [523, 659, 784, 1047] : [440, 554, 659];
        notes.forEach((note, i) => {
          tone(note, .2, "triangle", .05, 1, i * .09);
        });
      }

      function spawnBurst(x, y, color, count = 12, power = 170) {
        for (let i = 0; i < count; i++) {
          const angle = Math.random() * Math.PI * 2;
          const speed = power * (.25 + Math.random() * .85);
          const life = .3 + Math.random() * .45;

          particles.push({
            x, y,
            vx: Math.cos(angle) * speed,
            vy: Math.sin(angle) * speed,
            gravity: 75,
            life,
            maxLife: life,
            size: 1 + Math.random() * 2.2,
            color
          });
        }

        if (particles.length > 650) {
          particles.splice(0, particles.length - 650);
        }
      }

      function addFloater(x, y, text, color = "#ffffff") {
        floaters.push({
          x, y, text, color,
          life: 1,
          maxLife: 1
        });
      }

      function comboMultiplier() {
        return Math.min(5, 1 + Math.floor(combo / 5));
      }

      function setBallSpeed(ball, speed) {
        const current = Math.hypot(ball.vx, ball.vy) || 1;
        ball.vx = ball.vx / current * speed;
        ball.vy = ball.vy / current * speed;
      }

      function tameBall(ball) {
        const speed = Math.hypot(ball.vx, ball.vy);
        if (speed === 0 || Math.abs(ball.vy) >= speed * .24) return;

        const verticalDirection = ball.vy <= 0 ? -1 : 1;
        const horizontalDirection = ball.vx < 0 ? -1 : 1;
        ball.vy = verticalDirection * speed * .24;
        ball.vx = horizontalDirection * Math.sqrt(speed * speed - ball.vy * ball.vy);
      }

      function damageBrick(brick, ball) {
        brick.hp--;
        brick.flash = 1;

        if (brick.hp > 0) {
          addScore(15 * brick.maxHp);
          spawnBurst(ball.x, ball.y, brick.color, 5, 95);
          shake = Math.min(8, shake + 1.3);
          tone(190 + brick.hp * 45, .055, "square", .025, .8);
          return;
        }

        brick.alive = false;
        combo++;
        const multiplier = comboMultiplier();
        const points = 100 * brick.maxHp * multiplier;
        addScore(points);

        const cx = brick.x + brick.w / 2;
        const cy = brick.y + brick.h / 2;
        addFloater(cx, cy, `+${points}`, brick.color);
        spawnBurst(cx, cy, brick.color, 15, 190);
        shake = Math.min(11, shake + 3.2);
        flash = Math.max(flash, .08);

        tone(310 + Math.min(combo, 15) * 16, .075, "square", .032, 1.55);

        if (combo > 0 && combo % 5 === 0) {
          addFloater(cx, cy - 20, `COMBO ×${multiplier}`, "#ffffff");
          tone(660, .12, "triangle", .04, 1.3, .04);
        }

        if (Math.random() < .15) {
          spawnPowerup(cx, cy);
        }

        if (bricks.every(item => !item.alive)) {
          completeLevel();
        }
      }

      function spawnPowerup(x, y) {
        const roll = Math.random();
        const type =
          roll < .34 ? "wide" :
          roll < .58 ? "slow" :
          roll < .92 ? "multi" : "life";

        powerups.push({
          x, y,
          vy: 125,
          type,
          rot: Math.random() * Math.PI * 2,
          dead: false
        });
      }

      function applyPowerup(type) {
        const info = POWERS[type];
        let label = info.name;

        if (type === "wide") {
          wideTimer = 14;
        } else if (type === "slow") {
          balls.forEach(ball => {
            const speed = Math.hypot(ball.vx, ball.vy);
            if (speed > 0) setBallSpeed(ball, Math.max(290, speed * .8));
          });
        } else if (type === "multi") {
          const source = balls.find(ball => !ball.dead);
          if (source) {
            const baseAngle = Math.atan2(source.vy, source.vx);
            const speed = Math.hypot(source.vx, source.vy) || levels[levelIndex].speed;

            [-.48, .48].forEach(offset => {
              if (balls.length >= 6) return;
              const ball = makeBall(source.x, source.y);
              ball.vx = Math.cos(baseAngle + offset) * speed;
              ball.vy = Math.sin(baseAngle + offset) * speed;
              balls.push(ball);
            });
          }
        } else if (type === "life") {
          if (lives < 5) {
            lives++;
          } else {
            addScore(500);
            label = "LIFE MAX · +500";
          }
        }

        addScore(50);
        addFloater(paddle.x, paddle.y - 28, label, info.color);
        spawnBurst(paddle.x, paddle.y, info.color, 18, 180);
        shake = Math.min(9, shake + 2);

        tone(440, .1, "triangle", .04, 1.15);
        tone(660, .12, "triangle", .04, 1.1, .07);
        tone(880, .14, "triangle", .035, 1.05, .14);
      }

      function updatePaddle(dt) {
        const oldX = paddle.x;
        const targetWidth = wideTimer > 0 ? 170 : 122;
        paddle.w += (targetWidth - paddle.w) * Math.min(1, dt * 8);

        const direction = (keys.right ? 1 : 0) - (keys.left ? 1 : 0);

        if (direction !== 0) {
          paddle.x += direction * 690 * dt;
        } else if (pointer.active) {
          paddle.x += (pointer.x - paddle.x) * Math.min(1, dt * 22);
        }

        paddle.x = clamp(
          paddle.x,
          paddle.w / 2 + 17,
          W - paddle.w / 2 - 17
        );
        paddle.vx = dt > 0 ? (paddle.x - oldX) / dt : 0;
      }

      function updatePowerups(dt) {
        for (const powerup of powerups) {
          if (powerup.dead) continue;

          powerup.y += powerup.vy * dt;
          powerup.rot += dt * 3;

          const caught =
            powerup.y + 13 >= paddle.y &&
            powerup.y - 13 <= paddle.y + paddle.h &&
            Math.abs(powerup.x - paddle.x) <= paddle.w / 2 + 14;

          if (caught) {
            powerup.dead = true;
            applyPowerup(powerup.type);
          } else if (powerup.y > H + 30) {
            powerup.dead = true;
          }
        }

        powerups = powerups.filter(powerup => !powerup.dead);
      }

      function stepBall(ball, dt) {
        const previousX = ball.x;
        const previousY = ball.y;

        ball.x += ball.vx * dt;
        ball.y += ball.vy * dt;

        let wallHit = false;

        if (ball.x - ball.r < WALL_LEFT) {
          ball.x = WALL_LEFT + ball.r;
          ball.vx = Math.abs(ball.vx);
          wallHit = true;
        } else if (ball.x + ball.r > WALL_RIGHT) {
          ball.x = WALL_RIGHT - ball.r;
          ball.vx = -Math.abs(ball.vx);
          wallHit = true;
        }

        if (ball.y - ball.r < TOP) {
          ball.y = TOP + ball.r;
          ball.vy = Math.abs(ball.vy);
          wallHit = true;
        }

        if (wallHit) {
          spawnBurst(ball.x, ball.y, "#7186ff", 3, 70);
          tone(150, .035, "square", .014, 1.1);
        }

        const paddleHit =
          ball.vy > 0 &&
          ball.y + ball.r >= paddle.y &&
          ball.y - ball.r <= paddle.y + paddle.h &&
          ball.y < paddle.y + paddle.h + 4 &&
          ball.x >= paddle.x - paddle.w / 2 - ball.r &&
          ball.x <= paddle.x + paddle.w / 2 + ball.r;

        if (paddleHit) {
          ball.y = paddle.y - ball.r;

          const english = clamp(paddle.vx / 1800, -.12, .12);
          const offset = clamp(
            (ball.x - paddle.x) / (paddle.w / 2) + english,
            -1,
            1
          );
          const angle = offset * 1.08;
          const speed = clamp(
            Math.hypot(ball.vx, ball.vy) * 1.008,
            levels[levelIndex].speed,
            640
          );

          ball.vx = Math.sin(angle) * speed;
          ball.vy = -Math.cos(angle) * speed;
          combo = 0;

          spawnBurst(ball.x, paddle.y, "#55edff", 7, 105);
          shake = Math.min(7, shake + 1.2);
          tone(250 + Math.abs(offset) * 80, .065, "triangle", .032, 1.35);
          return;
        }

        for (const brick of bricks) {
          if (!brick.alive) continue;

          const nearestX = clamp(ball.x, brick.x, brick.x + brick.w);
          const nearestY = clamp(ball.y, brick.y, brick.y + brick.h);
          const dx = ball.x - nearestX;
          const dy = ball.y - nearestY;

          if (dx * dx + dy * dy > ball.r * ball.r) continue;

          if (previousY + ball.r <= brick.y + 1 && ball.vy > 0) {
            ball.y = brick.y - ball.r;
            ball.vy = -Math.abs(ball.vy);
          } else if (previousY - ball.r >= brick.y + brick.h - 1 && ball.vy < 0) {
            ball.y = brick.y + brick.h + ball.r;
            ball.vy = Math.abs(ball.vy);
          } else if (previousX + ball.r <= brick.x + 1 && ball.vx > 0) {
            ball.x = brick.x - ball.r;
            ball.vx = -Math.abs(ball.vx);
          } else if (previousX - ball.r >= brick.x + brick.w - 1 && ball.vx < 0) {
            ball.x = brick.x + brick.w + ball.r;
            ball.vx = Math.abs(ball.vx);
          } else {
            const positions = [
              { side: "left",   value: Math.abs(ball.x - (brick.x - ball.r)) },
              { side: "right",  value: Math.abs(ball.x - (brick.x + brick.w + ball.r)) },
              { side: "top",    value: Math.abs(ball.y - (brick.y - ball.r)) },
              { side: "bottom", value: Math.abs(ball.y - (brick.y + brick.h + ball.r)) }
            ].sort((a, b) => a.value - b.value);

            const side = positions[0].side;
            if (side === "left") {
              ball.x = brick.x - ball.r;
              ball.vx = -Math.abs(ball.vx);
            } else if (side === "right") {
              ball.x = brick.x + brick.w + ball.r;
              ball.vx = Math.abs(ball.vx);
            } else if (side === "top") {
              ball.y = brick.y - ball.r;
              ball.vy = -Math.abs(ball.vy);
            } else {
              ball.y = brick.y + brick.h + ball.r;
              ball.vy = Math.abs(ball.vy);
            }
          }

          damageBrick(brick, ball);
          setBallSpeed(
            ball,
            Math.min(640, Math.hypot(ball.vx, ball.vy) + 2.5)
          );
          tameBall(ball);
          return;
        }

        if (ball.y - ball.r > H + 25) {
          ball.dead = true;
        }
      }

      function updateBalls(dt) {
        for (const ball of balls) {
          if (state !== "playing") break;
          if (ball.dead) continue;

          if (ball.stuck) {
            ball.x = paddle.x;
            ball.y = paddle.y - ball.r - 3;
            continue;
          }

          ball.trail.unshift({ x: ball.x, y: ball.y });
          if (ball.trail.length > 15) ball.trail.pop();

          const speed = Math.hypot(ball.vx, ball.vy);
          const steps = clamp(Math.ceil(speed * dt / 5), 1, 10);
          const stepDt = dt / steps;

          for (let i = 0; i < steps; i++) {
            if (state !== "playing" || ball.dead) break;
            stepBall(ball, stepDt);
          }
        }

        balls = balls.filter(ball => !ball.dead);
      }

      function updateEffects(dt) {
        for (let i = particles.length - 1; i >= 0; i--) {
          const particle = particles[i];
          particle.life -= dt;

          if (particle.life <= 0) {
            particles.splice(i, 1);
            continue;
          }

          particle.vy += particle.gravity * dt;
          particle.x += particle.vx * dt;
          particle.y += particle.vy * dt;
          particle.vx *= Math.pow(.97, dt * 60);
          particle.vy *= Math.pow(.985, dt * 60);
        }

        for (let i = floaters.length - 1; i >= 0; i--) {
          const floater = floaters[i];
          floater.life -= dt;
          floater.y -= 30 * dt;
          if (floater.life <= 0) floaters.splice(i, 1);
        }

        for (const brick of bricks) {
          brick.flash = Math.max(0, brick.flash - dt * 5);
        }

        shake = Math.max(0, shake - dt * 25);
        flash = Math.max(0, flash - dt * 1.8);
      }

      function update(dt) {
        time += dt;

        if (state !== "paused") {
          updateEffects(dt);
        }

        if (state === "ready" || state === "playing") {
          updatePaddle(dt);
        }

        if (state === "ready") {
          for (const ball of balls) {
            if (ball.stuck) {
              ball.x = paddle.x;
              ball.y = paddle.y - ball.r - 3;
            }
          }
        }

        if (state === "playing") {
          wideTimer = Math.max(0, wideTimer - dt);
          updatePowerups(dt);
          updateBalls(dt);

          if (state === "playing" && balls.length === 0) {
            loseLife();
          }

          bannerTimer = Math.max(0, bannerTimer - dt);
        }
      }

      function roundedPath(x, y, w, h, radius) {
        const r = Math.min(radius, w / 2, h / 2);
        ctx.beginPath();
        ctx.moveTo(x + r, y);
        ctx.lineTo(x + w - r, y);
        ctx.quadraticCurveTo(x + w, y, x + w, y + r);
        ctx.lineTo(x + w, y + h - r);
        ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
        ctx.lineTo(x + r, y + h);
        ctx.quadraticCurveTo(x, y + h, x, y + h - r);
        ctx.lineTo(x, y + r);
        ctx.quadraticCurveTo(x, y, x + r, y);
        ctx.closePath();
      }

      function drawText(text, x, y, size, color, align = "left", weight = 600, mono = true) {
        ctx.save();
        ctx.font = `${weight} ${size}px ${
          mono
            ? "ui-monospace, SFMono-Regular, Menlo, Consolas, monospace"
            : "system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", sans-serif"
        }`;
        ctx.textAlign = align;
        ctx.textBaseline = "middle";
        ctx.fillStyle = color;
        ctx.fillText(text, x, y);
        ctx.restore();
      }

      function drawBackground() {
        const gradient = ctx.createLinearGradient(0, 0, 0, H);
        gradient.addColorStop(0, "#0d1028");
        gradient.addColorStop(.55, "#090c20");
        gradient.addColorStop(1, "#080917");
        ctx.fillStyle = gradient;
        ctx.fillRect(0, 0, W, H);

        const glow = ctx.createRadialGradient(
          W * .5, H * .85, 0,
          W * .5, H * .85, W * .68
        );
        glow.addColorStop(0, "rgba(73, 67, 190, .12)");
        glow.addColorStop(1, "rgba(20, 20, 60, 0)");
        ctx.fillStyle = glow;
        ctx.fillRect(0, TOP, W, H - TOP);

        ctx.save();
        for (const star of stars) {
          const alpha = .09 + (.5 + .5 * Math.sin(time * star.speed + star.phase)) * .23;
          ctx.globalAlpha = alpha;
          ctx.fillStyle = star.size > 1.2 ? "#8fdcff" : "#a7b5ef";
          ctx.fillRect(star.x, star.y, star.size, star.size);
        }
        ctx.restore();

        ctx.save();
        ctx.strokeStyle = "rgba(109, 129, 205, .045)";
        ctx.lineWidth = 1;
        ctx.beginPath();

        for (let x = 0; x <= W; x += 60) {
          ctx.moveTo(x, TOP);
          ctx.lineTo(x, H);
        }
        for (let y = TOP; y <= H; y += 40) {
          ctx.moveTo(0, y);
          ctx.lineTo(W, y);
        }

        ctx.stroke();
        ctx.restore();

        const danger = ctx.createLinearGradient(0, H - 90, 0, H);
        danger.addColorStop(0, "rgba(255, 45, 110, 0)");
        danger.addColorStop(1, "rgba(255, 45, 110, .065)");
        ctx.fillStyle = danger;
        ctx.fillRect(0, H - 90, W, 90);
      }

      function drawBounds() {
        ctx.save();
        ctx.strokeStyle = "rgba(91, 191, 255, .18)";
        ctx.lineWidth = 1;
        ctx.beginPath();
        ctx.moveTo(WALL_LEFT, H);
        ctx.lineTo(WALL_LEFT, TOP);
        ctx.lineTo(WALL_RIGHT, TOP);
        ctx.lineTo(WALL_RIGHT, H);
        ctx.stroke();

        ctx.strokeStyle = "rgba(82, 232, 255, .45)";
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.moveTo(WALL_LEFT, TOP + 20);
        ctx.lineTo(WALL_LEFT, TOP);
        ctx.lineTo(WALL_LEFT + 20, TOP);
        ctx.moveTo(WALL_RIGHT - 20, TOP);
        ctx.lineTo(WALL_RIGHT, TOP);
        ctx.lineTo(WALL_RIGHT, TOP + 20);
        ctx.stroke();
        ctx.restore();
      }

      function drawBricks() {
        for (const brick of bricks) {
          if (!brick.alive) continue;

          const ratio = brick.hp / brick.maxHp;
          ctx.save();

          roundedPath(brick.x, brick.y, brick.w, brick.h, 5);
          ctx.shadowColor = brick.color;
          ctx.shadowBlur = 9;
          ctx.globalAlpha = .28 + ratio * .43;
          ctx.fillStyle = brick.color;
          ctx.fill();

          ctx.shadowBlur = 0;
          ctx.globalAlpha = .85;
          ctx.strokeStyle = brick.color;
          ctx.lineWidth = 1;
          ctx.stroke();

          ctx.globalAlpha = .26;
          ctx.strokeStyle = "#ffffff";
          ctx.beginPath();
          ctx.moveTo(brick.x + 7, brick.y + 5);
          ctx.lineTo(brick.x + brick.w - 7, brick.y + 5);
          ctx.stroke();

          if (brick.maxHp > 1) {
            ctx.globalAlpha = .75;
            ctx.fillStyle = "#ffffff";
            for (let i = 0; i < brick.hp; i++) {
              ctx.fillRect(
                brick.x + brick.w - 8 - i * 6,
                brick.y + brick.h - 6,
                3,
                2
              );
            }
          }

          if (brick.flash > 0) {
            roundedPath(brick.x, brick.y, brick.w, brick.h, 5);
            ctx.globalAlpha = brick.flash * .75;
            ctx.fillStyle = "#ffffff";
            ctx.fill();
          }

          ctx.restore();
        }
      }

      function drawParticles() {
        ctx.save();
        ctx.globalCompositeOperation = "lighter";
        ctx.lineCap = "round";

        for (const particle of particles) {
          const alpha = clamp(particle.life / particle.maxLife, 0, 1);
          ctx.globalAlpha = alpha;
          ctx.strokeStyle = particle.color;
          ctx.lineWidth = particle.size;
          ctx.beginPath();
          ctx.moveTo(particle.x, particle.y);
          ctx.lineTo(
            particle.x - particle.vx * .024,
            particle.y - particle.vy * .024
          );
          ctx.stroke();
        }

        ctx.restore();
      }

      function drawPowerups() {
        for (const powerup of powerups) {
          const info = POWERS[powerup.type];

          ctx.save();
          ctx.translate(powerup.x, powerup.y);
          ctx.rotate(Math.sin(powerup.rot) * .16);
          ctx.shadowColor = info.color;
          ctx.shadowBlur = 18;

          roundedPath(-14, -14, 28, 28, 8);
          ctx.globalAlpha = .25;
          ctx.fillStyle = info.color;
          ctx.fill();

          ctx.globalAlpha = 1;
          ctx.strokeStyle = info.color;
          ctx.lineWidth = 1.5;
          ctx.stroke();
          ctx.shadowBlur = 0;

          drawText(
            info.icon,
            0,
            1,
            info.icon.length > 1 ? 11 : 15,
            "#ffffff",
            "center",
            800
          );
          ctx.restore();
        }
      }

      function drawPaddle() {
        const x = paddle.x - paddle.w / 2;
        const gradient = ctx.createLinearGradient(x, 0, x + paddle.w, 0);
        gradient.addColorStop(0, "#35e7ff");
        gradient.addColorStop(.52, "#8174ff");
        gradient.addColorStop(1, "#ff50bf");

        ctx.save();
        ctx.shadowColor = wideTimer > 0 ? "#45f3ff" : "#756cff";
        ctx.shadowBlur = wideTimer > 0 ? 24 : 16;
        roundedPath(x, paddle.y, paddle.w, paddle.h, 7);
        ctx.fillStyle = gradient;
        ctx.fill();

        ctx.shadowBlur = 0;
        roundedPath(x + 4, paddle.y + 3, paddle.w - 8, 3, 2);
        ctx.globalAlpha = .45;
        ctx.fillStyle = "#ffffff";
        ctx.fill();

        ctx.globalAlpha = .9;
        ctx.fillStyle = "#ffffff";
        ctx.fillRect(paddle.x - 1, paddle.y + 3, 2, paddle.h - 6);
        ctx.restore();
      }

      function drawBalls() {
        for (const ball of balls) {
          ctx.save();
          ctx.globalCompositeOperation = "lighter";

          for (let i = ball.trail.length - 1; i >= 0; i--) {
            const point = ball.trail[i];
            const strength = 1 - i / ball.trail.length;
            ctx.globalAlpha = strength * .18;
            ctx.fillStyle = i % 2 ? "#6b8cff" : "#43eaff";
            ctx.beginPath();
            ctx.arc(
              point.x,
              point.y,
              ball.r * (.25 + strength * .55),
              0,
              Math.PI * 2
            );
            ctx.fill();
          }

          ctx.restore();

          ctx.save();
          ctx.shadowColor = "#54dcff";
          ctx.shadowBlur = 20;
          const gradient = ctx.createRadialGradient(
            ball.x - 2, ball.y - 2, 1,
            ball.x, ball.y, ball.r * 1.8
          );
          gradient.addColorStop(0, "#ffffff");
          gradient.addColorStop(.4, "#d8fbff");
          gradient.addColorStop(1, "#55bfff");
          ctx.fillStyle = gradient;
          ctx.beginPath();
          ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
          ctx.fill();
          ctx.restore();
        }
      }

      function drawFloaters() {
        ctx.save();
        for (const floater of floaters) {
          const alpha = clamp(floater.life / floater.maxLife, 0, 1);
          ctx.globalAlpha = Math.min(1, alpha * 2);
          drawText(
            floater.text,
            floater.x,
            floater.y,
            12 + (1 - alpha) * 2,
            floater.color,
            "center",
            800
          );
        }
        ctx.restore();
      }

      function drawHUD() {
        ctx.save();
        ctx.fillStyle = "rgba(5, 7, 22, .74)";
        ctx.fillRect(0, 0, W, TOP);
        ctx.strokeStyle = "rgba(112, 137, 218, .18)";
        ctx.beginPath();
        ctx.moveTo(0, TOP - .5);
        ctx.lineTo(W, TOP - .5);
        ctx.stroke();
        ctx.restore();

        drawText("SCORE", 24, 17, 9, "#7481aa", "left", 700);
        drawText(String(score).padStart(6, "0"), 24, 39, 20, "#edf6ff", "left", 800);

        drawText("BEST", 190, 17, 9, "#7481aa", "left", 700);
        drawText(String(best).padStart(6, "0"), 190, 39, 15, "#9aa8d2", "left", 700);

        drawText(
          `LEVEL ${levelIndex + 1} / ${levels.length}`,
          W / 2, 17, 9, "#7481aa", "center", 700
        );
        drawText(
          levels[levelIndex].name,
          W / 2, 39, 15, "#b9c8ff", "center", 800
        );

        drawText("LIVES", 716, 39, 9, "#7481aa", "left", 700);

        ctx.save();
        for (let i = 0; i < lives; i++) {
          const x = 780 + i * 22;
          ctx.shadowColor = "#ff5ca8";
          ctx.shadowBlur = 9;
          ctx.fillStyle = "#ff70b4";
          ctx.beginPath();
          ctx.arc(x, 39, 4.5, 0, Math.PI * 2);
          ctx.fill();
        }
        ctx.restore();

        drawText(
          muted ? "M · SOUND OFF" : "M · SOUND ON",
          W - 24, 17, 8,
          muted ? "#626b8b" : "#66b9c9",
          "right",
          700
        );

        if (wideTimer > 0 && state === "playing") {
          drawText(
            `WIDE ${wideTimer.toFixed(1)}s`,
            24, H - 17, 10, "#54e9ff", "left", 700
          );
        }

        if (combo >= 2 && state === "playing") {
          drawText(
            `${combo} HIT COMBO · ×${comboMultiplier()}`,
            W - 24, H - 17, 10, "#ff88ca", "right", 800
          );
        }
      }

      function drawBanner() {
        if (state !== "playing" || bannerTimer <= 0) return;

        const elapsed = 2.2 - bannerTimer;
        const alpha = clamp(Math.min(elapsed * 3.5, bannerTimer * 1.4), 0, 1);

        ctx.save();
        ctx.globalAlpha = alpha * .75;
        drawText(
          `LEVEL ${levelIndex + 1}`,
          W / 2, 466, 10, "#7f8fb9", "center", 700
        );
        drawText(
          levels[levelIndex].name,
          W / 2, 491, 25, "#c6d2ff", "center", 900, false
        );
        ctx.restore();
      }

      function drawOverlay() {
        if (state === "playing") return;

        let kicker = "";
        let title = "";
        let line1 = "";
        let line2 = "";
        let action = "";

        if (state === "ready" && serveType === "start") {
          kicker = "NO COIN REQUIRED";
          title = "NEON BREAKOUT";
          line1 = "Clear five levels. Catch power-ups. Chase the high score.";
          line2 = "Move with mouse, touch, ← →, or A / D";
          action = "CLICK OR PRESS SPACE TO LAUNCH";
        } else if (state === "ready") {
          kicker = `LEVEL ${levelIndex + 1} · ${levels[levelIndex].name}`;
          title = "BALL LOST";
          line1 = `${lives} ${lives === 1 ? "life" : "lives"} remaining`;
          line2 = "The next ball is ready when you are.";
          action = "CLICK OR PRESS SPACE TO RELAUNCH";
        } else if (state === "paused") {
          kicker = "TAKE A BREATH";
          title = "PAUSED";
          line1 = "Your run is safe.";
          line2 = "Press P or Escape to jump back in.";
          action = "CLICK OR PRESS SPACE TO RESUME";
        } else if (state === "levelclear") {
          kicker = `LEVEL ${levelIndex + 1} COMPLETE`;
          title = "NICE WORK";
          line1 = `Clear bonus +${levelBonus} · Score ${score}`;
          line2 = `Next up: ${levels[levelIndex + 1].name}`;
          action = "CLICK OR PRESS SPACE FOR NEXT LEVEL";
        } else if (state === "gameover") {
          kicker = "THE GRID WINS THIS ROUND";
          title = "GAME OVER";
          line1 = `Score ${score} · Best ${best}`;
          line2 = `You reached level ${levelIndex + 1}: ${levels[levelIndex].name}`;
          action = "CLICK OR PRESS SPACE TO TRY AGAIN";
        } else if (state === "won") {
          kicker = "ALL FIVE LEVELS CLEARED";
          title = "GRID CLEARED!";
          line1 = `Final score ${score} · Best ${best}`;
          line2 = "You are officially a brick-breaking menace.";
          action = "CLICK OR PRESS SPACE TO PLAY AGAIN";
        } else {
          return;
        }

        ctx.save();
        ctx.fillStyle = "rgba(4, 5, 18, .69)";
        ctx.fillRect(0, TOP, W, H - TOP);

        const panelX = W / 2 - 300;
        const panelY = 196;
        const panelW = 600;
        const panelH = 242;

        ctx.shadowColor = "rgba(75, 100, 255, .35)";
        ctx.shadowBlur = 35;
        roundedPath(panelX, panelY, panelW, panelH, 18);
        ctx.fillStyle = "rgba(10, 13, 36, .94)";
        ctx.fill();

        ctx.shadowBlur = 0;
        ctx.strokeStyle = "rgba(119, 150, 255, .3)";
        ctx.lineWidth = 1;
        ctx.stroke();
        ctx.restore();

        drawText(kicker, W / 2, 224, 9, "#6ed8e8", "center", 800);
        drawText(title, W / 2, 272, 41, "#f2f5ff", "center", 900, false);
        drawText(line1, W / 2, 318, 13, "#b1bddc", "center", 500);
        drawText(line2, W / 2, 342, 11, "#7785ac", "center", 500);

        const pulse = .65 + Math.sin(time * 4) * .15;
        ctx.save();
        ctx.globalAlpha = pulse;
        roundedPath(W / 2 - 210, 365, 420, 42, 10);
        ctx.fillStyle = "rgba(56, 221, 255, .10)";
        ctx.fill();
        ctx.strokeStyle = "rgba(73, 226, 255, .62)";
        ctx.stroke();
        ctx.restore();

        drawText(action, W / 2, 386, 11, "#a9f4ff", "center", 800);
      }

      function render() {
        ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
        ctx.clearRect(0, 0, W, H);

        drawBackground();

        ctx.save();
        if (shake > 0) {
          ctx.translate(
            (Math.random() * 2 - 1) * shake,
            (Math.random() * 2 - 1) * shake
          );
        }

        drawBounds();
        drawBricks();
        drawParticles();
        drawPowerups();
        drawPaddle();
        drawBalls();
        drawFloaters();
        ctx.restore();

        drawHUD();
        drawBanner();
        drawOverlay();

        if (flash > 0) {
          ctx.fillStyle = `rgba(190, 225, 255, ${flash * .16})`;
          ctx.fillRect(0, 0, W, H);
        }
      }

      function frame(now) {
        const dt = Math.min(.033, Math.max(0, (now - lastFrame) / 1000));
        lastFrame = now;
        update(dt);
        render();
        requestAnimationFrame(frame);
      }

      function pointerXFromEvent(event) {
        const rect = canvas.getBoundingClientRect();
        return clamp((event.clientX - rect.left) / rect.width * W, 0, W);
      }

      canvas.addEventListener("pointermove", event => {
        pointer.x = pointerXFromEvent(event);
        pointer.active = true;
      });

      canvas.addEventListener("pointerdown", event => {
        event.preventDefault();
        canvas.focus({ preventScroll: true });
        pointer.x = pointerXFromEvent(event);
        pointer.active = true;

        if (state === "ready") {
          paddle.x = clamp(
            pointer.x,
            paddle.w / 2 + 17,
            W - paddle.w / 2 - 17
          );
          for (const ball of balls) {
            if (ball.stuck) {
              ball.x = paddle.x;
              ball.y = paddle.y - ball.r - 3;
            }
          }
        }

        handleAction();
      });

      canvas.addEventListener("contextmenu", event => event.preventDefault());

      window.addEventListener("keydown", event => {
        if (event.ctrlKey || event.metaKey || event.altKey) return;

        const controlledKeys = [
          "ArrowLeft", "ArrowRight", "Space",
          "KeyA", "KeyD", "KeyP", "KeyM", "KeyR",
          "Escape", "Enter"
        ];

        if (controlledKeys.includes(event.code)) {
          event.preventDefault();
        }

        if (event.code === "ArrowLeft" || event.code === "KeyA") {
          keys.left = true;
          pointer.active = false;
        }

        if (event.code === "ArrowRight" || event.code === "KeyD") {
          keys.right = true;
          pointer.active = false;
        }

        if (event.repeat) return;

        if (event.code === "Space" || event.code === "Enter") {
          handleAction();
        } else if (event.code === "KeyP" || event.code === "Escape") {
          togglePause();
        } else if (event.code === "KeyM") {
          muted = !muted;
          if (!muted) {
            initAudio();
            tone(660, .09, "triangle", .04, 1.25);
          }
        } else if (event.code === "KeyR") {
          resetGame();
        }
      });

      window.addEventListener("keyup", event => {
        if (event.code === "ArrowLeft" || event.code === "KeyA") {
          keys.left = false;
        }
        if (event.code === "ArrowRight" || event.code === "KeyD") {
          keys.right = false;
        }
      });

      document.addEventListener("visibilitychange", () => {
        if (document.hidden && state === "playing") {
          state = "paused";
        }
      });

      window.addEventListener("blur", () => {
        if (state === "playing") state = "paused";
      });

      window.addEventListener("resize", resizeCanvas);

      resizeCanvas();
      resetGame();
      requestAnimationFrame(frame);
    })();
  </script>
</body>
</html>

Compare with

Kimi K3.1 Pro: other attempts