hypeorcode
Network offOpen alone (new tab)

Run details

Model
inception/mercury-3
Route
OpenRouter API, one user message
Reasoning
provider default, 13,610 reasoning tokens
Limits
60 min · 131,072 output tokens
Date
21 Oct 2026
Time
29 s
Size
16 kB
Tokens
75 in, 19,366 out
List price
$0.007 ($0.1 in / $0.35 out per M)
Paid
$0.007

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

16 kB 685 lines Raw

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Breakout</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  body {
    background: #0b0e14;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    font-family: Arial, Helvetica, sans-serif;
    overflow: hidden;
  }
  canvas {
    background: #000;
    border-radius: 14px;
    box-shadow: 0 0 40px rgba(34, 211, 238, 0.25);
    cursor: crosshair;
  }
</style>
</head>
<body>
<canvas id="game" width="800" height="600"></canvas>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');

// Fallback for older browsers
if (!ctx.roundRect) {
  CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) {
    if (w < 2 * r) r = w / 2;
    if (h < 2 * r) r = h / 2;
    this.moveTo(x + r, y);
    this.arcTo(x + w, y, x + w, y + h, r);
    this.arcTo(x + w, y + h, x, y + h, r);
    this.arcTo(x, y + h, x, y, r);
    this.arcTo(x, y, x + w, y, r);
    this.closePath();
    return this;
  };
}

// Constants
const W = 800, H = 600;
const PADDLE_W = 120;
const PADDLE_H = 14;
const PADDLE_Y = H - 40;
const BALL_R = 7;
const BASE_BALL_SPEED = 330;
const PADDLE_SPEED = 480;
const BRICK_GAP = 4;
const BRICK_H = 20;
const TOP_OFFSET = 70;
const SIDE_OFFSET = 30;
const MAX_LIVES = 3;

const COLORS = [
  '#ff6b6b', '#feca57', '#48dbfb', '#1dd1a1',
  '#f368e0', '#5f27cd', '#fd79a8'
];

const LEVELS = [
  [
    'XXXXXXXXXX',
    'XXXXXXXXXX',
    'XXXXXXXXXX',
    'XXXXXXXXXX',
    'XXXXXXXXXX'
  ],
  [
    'X.X.X.X.X.',
    '.X.X.X.X.X',
    'X.X.X.X.X.',
    '.X.X.X.X.X',
    'X.X.X.X.X.',
    '.X.X.X.X.X'
  ],
  [
    '....XX....',
    '...XXXX...',
    '..XXXXXX..',
    '.XXXXXXXX.',
    'XXXXXXXXXX',
    'XXXXXXXXXX',
    '.XXXXXXXX.',
    '..XXXXXX..',
    '...XXXX...',
    '....XX....'
  ],
  [
    'XX......XX',
    'XXX....XXX',
    'XXXX..XXXX',
    'XXXXXXXXXX',
    'XXXXXXXXXX',
    'XXXX..XXXX',
    'XXX....XXX',
    'XX......XX'
  ],
  [
    'XXXXXXXXXX',
    'X........X',
    'X.XXXXXX.X',
    'X.X....X.X',
    'X.X.XX.X.X',
    'X.X.XX.X.X',
    'X.X....X.X',
    'X.XXXXXX.X',
    'X........X',
    'XXXXXXXXXX'
  ]
];

// Game state
let state = 'menu'; // menu | playing | levelComplete | gameOver
let score = 0;
let lives = MAX_LIVES;
let level = 1;
let endTimer = 0;
let currentSpeed = BASE_BALL_SPEED;

const paddle = { x: W / 2 - PADDLE_W / 2, w: PADDLE_W, h: PADDLE_H };
const ball = {
  x: W / 2,
  y: PADDLE_Y - BALL_R - 2,
  vx: 0,
  vy: 0,
  r: BALL_R,
  launched: false
};

let bricks = [];
let particles = [];
let popups = [];
let rings = [];
let trail = [];
let shakeMag = 0, shakeDur = 0;
let flashAlpha = 0;
let keys = {};

// Audio
let audioCtx = null;
function initAudio() {
  if (!audioCtx) {
    audioCtx = new (window.AudioContext || window.webkitAudioContext)();
  }
  if (audioCtx.state === 'suspended') audioCtx.resume();
}

function beep(freq, dur, type = 'square', vol = 0.1) {
  if (!audioCtx) return;
  const osc = audioCtx.createOscillator();
  const gain = audioCtx.createGain();
  osc.type = type;
  osc.frequency.value = freq;
  osc.connect(gain);
  gain.connect(audioCtx.destination);
  const t = audioCtx.currentTime;
  gain.gain.setValueAtTime(vol, t);
  gain.gain.exponentialRampToValueAtTime(0.001, t + dur);
  osc.start(t);
  osc.stop(t + dur);
}

// Level building
function buildLevel(lvl) {
  const pattern = LEVELS[(lvl - 1) % LEVELS.length];
  bricks = [];
  const rows = pattern.length;
  const cols = pattern[0].length;
  const brickW = (W - 2 * SIDE_OFFSET - (cols - 1) * BRICK_GAP) / cols;
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      if (pattern[r][c] === 'X') {
        const x = SIDE_OFFSET + c * (brickW + BRICK_GAP);
        const y = TOP_OFFSET + r * (BRICK_H + BRICK_GAP);
        bricks.push({
          x,
          y,
          w: brickW,
          h: BRICK_H,
          alive: true,
          color: COLORS[r % COLORS.length],
          points: (r + 1) * 10,
          row: r,
          col: c
        });
      }
    }
  }
  currentSpeed = BASE_BALL_SPEED + (lvl - 1) * 30;
}

// Ball helpers
function resetBall() {
  ball.launched = false;
  ball.vx = 0;
  ball.vy = 0;
  ball.x = paddle.x + paddle.w / 2;
  ball.y = paddle.y - ball.r - 2;
}

function launchBall() {
  if (state !== 'playing' || ball.launched) return;
  ball.launched = true;
  const angle = -Math.PI / 2 + (Math.random() - 0.5) * 0.4;
  ball.vx = currentSpeed * Math.cos(angle);
  ball.vy = currentSpeed * Math.sin(angle);
  beep(440, 0.1, 'triangle', 0.15);
}

// Juice helpers
function shake(mag, dur) {
  shakeMag = Math.max(shakeMag, mag);
  shakeDur = Math.max(shakeDur, dur);
}

function spawnBrickParticles(b) {
  const n = 14;
  for (let i = 0; i < n; i++) {
    const angle = Math.random() * Math.PI * 2;
    const speed = 100 + Math.random() * 250;
    particles.push({
      x: b.x + b.w / 2,
      y: b.y + b.h / 2,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed,
      size: 2 + Math.random() * 3,
      color: b.color,
      life: 0.4 + Math.random() * 0.6,
      maxLife: 1
    });
  }
  rings.push({
    x: b.x + b.w / 2,
    y: b.y + b.h / 2,
    radius: 0,
    life: 0.4,
    color: b.color
  });
}

function spawnPaddleParticles(x, y) {
  for (let i = 0; i < 6; i++) {
    const angle = Math.PI + Math.random() * Math.PI;
    const speed = 50 + Math.random() * 150;
    particles.push({
      x,
      y,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed,
      size: 2 + Math.random() * 2,
      color: '#fff',
      life: 0.3 + Math.random() * 0.3,
      maxLife: 0.5
    });
  }
}

// Game start / next level
function startGame() {
  score = 0;
  lives = MAX_LIVES;
  level = 1;
  buildLevel(level);
  resetBall();
  particles = [];
  popups = [];
  rings = [];
  trail = [];
  state = 'playing';
}

function nextLevel() {
  level++;
  buildLevel(level);
  resetBall();
  state = 'playing';
}

// Update
function updateEffects(dt) {
  for (let i = particles.length - 1; i >= 0; i--) {
    const p = particles[i];
    p.life -= dt;
    if (p.life <= 0) { particles.splice(i, 1); continue; }
    p.vy += 300 * dt;
    p.x += p.vx * dt;
    p.y += p.vy * dt;
  }

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

  for (let i = rings.length - 1; i >= 0; i--) {
    const r = rings[i];
    r.life -= dt;
    r.radius += 200 * dt;
    if (r.life <= 0) rings.splice(i, 1);
  }

  if (ball.launched && state === 'playing') {
    trail.unshift({ x: ball.x, y: ball.y, life: 0.25 });
    if (trail.length > 20) trail.pop();
  } else if (trail.length) {
    trail.shift();
  }
  for (let i = trail.length - 1; i >= 0; i--) {
    trail[i].life -= dt;
    if (trail[i].life <= 0) trail.splice(i, 1);
  }

  if (shakeDur > 0) {
    shakeDur -= dt;
    if (shakeDur < 0) { shakeDur = 0; shakeMag = 0; }
  }

  if (flashAlpha > 0) flashAlpha = Math.max(0, flashAlpha - dt * 2);
}

function updatePlaying(dt) {
  // Paddle movement
  if (keys['arrowleft'] || keys['a']) paddle.x -= PADDLE_SPEED * dt;
  if (keys['arrowright'] || keys['d']) paddle.x += PADDLE_SPEED * dt;
  paddle.x = Math.max(0, Math.min(W - paddle.w, paddle.x));

  // Ball attached to paddle
  if (!ball.launched) {
    ball.x = paddle.x + paddle.w / 2;
    ball.y = paddle.y - ball.r - 2;
  } else {
    ball.x += ball.vx * dt;
    ball.y += ball.vy * dt;

    // Wall collisions
    if (ball.x - ball.r < 0) {
      ball.x = ball.r;
      ball.vx = -ball.vx;
      beep(220, 0.03, 'square', 0.08);
    }
    if (ball.x + ball.r > W) {
      ball.x = W - ball.r;
      ball.vx = -ball.vx;
      beep(220, 0.03, 'square', 0.08);
    }
    if (ball.y - ball.r < 0) {
      ball.y = ball.r;
      ball.vy = -ball.vy;
      beep(260, 0.03, 'square', 0.08);
    }

    // Paddle collision
    if (ball.vy > 0 && ball.y + ball.r >= paddle.y && ball.y + ball.r <= paddle.y + paddle.h + 10) {
      if (ball.x >= paddle.x - ball.r && ball.x <= paddle.x + paddle.w + ball.r) {
        const rel = (ball.x - (paddle.x + paddle.w / 2)) / (paddle.w / 2);
        const angle = rel * (Math.PI / 3);
        const speed = Math.sqrt(ball.vx * ball.vx + ball.vy * ball.vy);
        ball.vx = speed * Math.sin(angle);
        ball.vy = -speed * Math.cos(angle);
        ball.y = paddle.y - ball.r;
        beep(520, 0.05, 'triangle', 0.12);
        shake(2, 0.1);
        spawnPaddleParticles(ball.x, ball.y);
      }
    }

    // Brick collision
    for (let i = 0; i < bricks.length; i++) {
      const b = bricks[i];
      if (!b.alive) continue;
      const bx = Math.max(b.x, Math.min(ball.x, b.x + b.w));
      const by = Math.max(b.y, Math.min(ball.y, b.y + b.h));
      const dx = ball.x - bx;
      const dy = ball.y - by;
      const dist2 = dx * dx + dy * dy;
      if (dist2 < ball.r * ball.r) {
        const overlapLeft = ball.x + ball.r - b.x;
        const overlapRight = b.x + b.w - (ball.x - ball.r);
        const overlapTop = ball.y + ball.r - b.y;
        const overlapBottom = b.y + b.h - (ball.y - ball.r);
        const minX = Math.min(overlapLeft, overlapRight);
        const minY = Math.min(overlapTop, overlapBottom);
        if (minX < minY) {
          ball.vx = -ball.vx;
        } else {
          ball.vy = -ball.vy;
        }
        b.alive = false;
        score += b.points;
        spawnBrickParticles(b);
        popups.push({ x: b.x + b.w / 2, y: b.y, text: '+' + b.points, life: 0.8, color: '#fff' });
        beep(400 + b.row * 80, 0.06, 'square', 0.12);
        shake(2 + b.row * 0.5, 0.12);
        break;
      }
    }

    // All bricks cleared
    if (!bricks.some(b => b.alive)) {
      state = 'levelComplete';
      endTimer = 2;
      beep(660, 0.2, 'triangle', 0.2);
      beep(880, 0.2, 'triangle', 0.2);
    }

    // Ball lost
    if (ball.y - ball.r > H) {
      lives--;
      flashAlpha = 0.5;
      shake(8, 0.5);
      beep(120, 0.4, 'sawtooth', 0.2);
      if (lives <= 0) {
        state = 'gameOver';
      } else {
        resetBall();
      }
    }
  }
}

function update(dt) {
  if (state === 'playing') {
    updatePlaying(dt);
  } else if (state === 'levelComplete') {
    endTimer -= dt;
    if (endTimer <= 0) nextLevel();
  }
  updateEffects(dt);
}

// Drawing
function drawBackground() {
  const grad = ctx.createLinearGradient(0, 0, 0, H);
  grad.addColorStop(0, '#0b0e14');
  grad.addColorStop(1, '#141a29');
  ctx.fillStyle = grad;
  ctx.fillRect(0, 0, W, H);

  ctx.save();
  ctx.strokeStyle = 'rgba(255,255,255,0.03)';
  ctx.lineWidth = 1;
  for (let y = 0; y < H; y += 30) {
    ctx.beginPath();
    ctx.moveTo(0, y);
    ctx.lineTo(W, y);
    ctx.stroke();
  }
  for (let x = 0; x < W; x += 30) {
    ctx.beginPath();
    ctx.moveTo(x, 0);
    ctx.lineTo(x, H);
    ctx.stroke();
  }
  ctx.restore();
}

function drawBricks() {
  for (const b of bricks) {
    if (!b.alive) continue;
    ctx.save();
    ctx.shadowColor = b.color;
    ctx.shadowBlur = 10;
    ctx.fillStyle = b.color;
    ctx.beginPath();
    ctx.roundRect(b.x, b.y, b.w, b.h, 4);
    ctx.fill();
    ctx.shadowBlur = 0;
    ctx.fillStyle = 'rgba(255,255,255,0.25)';
    ctx.fillRect(b.x + 2, b.y + 2, b.w - 4, b.h / 2 - 1);
    ctx.restore();
  }
}

function drawPaddle() {
  const grad = ctx.createLinearGradient(0, paddle.y, 0, paddle.y + paddle.h);
  grad.addColorStop(0, '#a5f3fc');
  grad.addColorStop(1, '#22d3ee');
  ctx.save();
  ctx.shadowColor = '#22d3ee';
  ctx.shadowBlur = 15;
  ctx.fillStyle = grad;
  ctx.beginPath();
  ctx.roundRect(paddle.x, paddle.y, paddle.w, paddle.h, 7);
  ctx.fill();
  ctx.shadowBlur = 0;
  ctx.fillStyle = 'rgba(255,255,255,0.6)';
  ctx.fillRect(paddle.x + paddle.w / 2 - 2, paddle.y + 3, 4, paddle.h - 6);
  ctx.restore();
}

function drawBall() {
  for (let i = 0; i < trail.length; i++) {
    const t = trail[i];
    const alpha = (i / trail.length) * 0.4;
    ctx.fillStyle = `rgba(255,255,255,${alpha})`;
    ctx.beginPath();
    ctx.arc(t.x, t.y, BALL_R * (i / trail.length), 0, Math.PI * 2);
    ctx.fill();
  }
  ctx.save();
  ctx.shadowColor = '#fff';
  ctx.shadowBlur = 20;
  ctx.fillStyle = '#fff';
  ctx.beginPath();
  ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
  ctx.fill();
  ctx.restore();
}

function drawParticles() {
  for (const p of particles) {
    const alpha = Math.max(0, p.life / p.maxLife);
    ctx.globalAlpha = alpha;
    ctx.fillStyle = p.color;
    ctx.beginPath();
    ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
    ctx.fill();
  }
  ctx.globalAlpha = 1;
}

function drawRings() {
  for (const r of rings) {
    const alpha = Math.max(0, r.life / 0.4);
    ctx.strokeStyle = r.color;
    ctx.globalAlpha = alpha;
    ctx.lineWidth = 3;
    ctx.beginPath();
    ctx.arc(r.x, r.y, r.radius, 0, Math.PI * 2);
    ctx.stroke();
  }
  ctx.globalAlpha = 1;
}

function drawPopups() {
  ctx.font = 'bold 18px Arial';
  ctx.textAlign = 'center';
  for (const p of popups) {
    const alpha = Math.max(0, p.life / 0.8);
    ctx.globalAlpha = alpha;
    ctx.fillStyle = p.color;
    ctx.fillText(p.text, p.x, p.y);
  }
  ctx.globalAlpha = 1;
}

function drawHUD() {
  ctx.fillStyle = '#fff';
  ctx.font = 'bold 20px Arial';
  ctx.textAlign = 'left';
  ctx.fillText(`Score: ${score}`, 10, 30);
  ctx.fillText(`Level: ${level}`, 200, 30);
  ctx.textAlign = 'right';
  ctx.fillText(`Lives: ${lives}`, W - 10, 30);
}

function drawMenu() {
  ctx.fillStyle = 'rgba(0,0,0,0.6)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';
  ctx.fillStyle = '#fff';
  ctx.font = 'bold 64px Arial';
  ctx.fillText('BREAKOUT', W / 2, H / 2 - 80);
  ctx.font = '20px Arial';
  ctx.fillStyle = '#aaa';
  ctx.fillText('Move mouse or use arrow keys / A D', W / 2, H / 2 - 20);
  ctx.fillText('Click or press Space to launch / start', W / 2, H / 2 + 20);
  ctx.fillStyle = '#22d3ee';
  ctx.font = 'bold 28px Arial';
  ctx.fillText('Press Space to Start', W / 2, H / 2 + 80);
}

function drawGameOver() {
  ctx.fillStyle = 'rgba(0,0,0,0.7)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';
  ctx.fillStyle = '#ff6b6b';
  ctx.font = 'bold 60px Arial';
  ctx.fillText('GAME OVER', W / 2, H / 2 - 60);
  ctx.fillStyle = '#fff';
  ctx.font = '24px Arial';
  ctx.fillText(`Score: ${score}`, W / 2, H / 2 + 10);
  ctx.fillText('Press Space to Play Again', W / 2, H / 2 + 60);
}

function drawLevelComplete() {
  ctx.fillStyle = 'rgba(0,0,0,0.5)';
  ctx.fillRect(0, 0, W, H);
  ctx.textAlign = 'center';
  ctx.fillStyle = '#ffd740';
  ctx.font = 'bold 52px Arial';
  ctx.fillText('LEVEL COMPLETE!', W / 2, H / 2 - 20);
  ctx.fillStyle = '#fff';
  ctx.font = '22px Arial';
  ctx.fillText('Next level incoming...', W / 2, H / 2 + 30);
}

function draw() {
  drawBackground();

  ctx.save();
  if (shakeDur > 0) {
    const mag = shakeMag * (shakeDur / 0.5);
    const ox = (Math.random() - 0.5) * mag * 2;
    const oy = (Math.random() - 0.5) * mag * 2;
    ctx.translate(ox, oy);
  }

  drawBricks();
  drawPaddle();
  drawBall();
  drawParticles();
  drawRings();
  drawPopups();
  ctx.restore();

  drawHUD();

  if (state === 'menu') drawMenu();
  else if (state === 'gameOver') drawGameOver();
  else if (state === 'levelComplete') drawLevelComplete();

  if (flashAlpha > 0) {
    ctx.fillStyle = `rgba(255,0,0,${flashAlpha})`;
    ctx.fillRect(0, 0, W, H);
  }
}

// Main loop
let lastTime = performance.now();
function loop(now) {
  const dt = Math.min((now - lastTime) / 1000, 0.05);
  lastTime = now;
  update(dt);
  draw();
  requestAnimationFrame(loop);
}

// Input
canvas.addEventListener('mousemove', e => {
  const rect = canvas.getBoundingClientRect();
  const scaleX = W / rect.width;
  const mx = (e.clientX - rect.left) * scaleX;
  paddle.x = mx - paddle.w / 2;
  paddle.x = Math.max(0, Math.min(W - paddle.w, paddle.x));
});

canvas.addEventListener('mousedown', () => {
  initAudio();
  if (state === 'menu' || state === 'gameOver') {
    startGame();
  } else if (state === 'playing') {
    if (!ball.launched) launchBall();
  }
});

window.addEventListener('keydown', e => {
  initAudio();
  keys[e.key.toLowerCase()] = true;
  if (e.key === ' ') {
    e.preventDefault();
    if (state === 'menu' || state === 'gameOver') {
      startGame();
    } else if (state === 'playing') {
      if (!ball.launched) launchBall();
    }
  }
});

window.addEventListener('keyup', e => {
  keys[e.key.toLowerCase()] = false;
});

requestAnimationFrame(loop);
</script>
</body>
</html>

Compare with

Mercury 3: other attempts