Models 44

Flint (free)

by Recursivereasoning default1 console error
Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
2 min 15 s
Cost
$0
Output tokens
16,200
Spent thinking
12,172 tokens
Speed
120.7 tok/s
Console errors
1
First token after
466 ms
First visible text after
1 min 47 s
Prompt tokens
349
Code size
8.3 KB
Finish reason
stop
Served by
Recursive
Model id
recursive/flint-v1.0:free
Reasoning sent
model default
Max output
32,000 tokens
Generated
Nov 9, 2026

Console errors

Caught in the first 3 seconds after load, in a sandboxed iframe like the one above.

Unexpected identifier 'bulging'

What it was asked

The prompt
“i want a breakout game where the paddle is a hamburger and every brick you break explodes into tons of little hearts”
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task.

Output rules:
- Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block.
- Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task.
- Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw.
- The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar.
- Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction.
- Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session.
- Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing.
- The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message.
- The code must run with zero errors in the console of the latest Chrome.

Code run-1.html · 335 lines

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hamburger Breakout</title>
<style>
  html, body {
    margin: 0;
    height: 100%;
    background: #0b0b12;
    overflow: hidden;
  }
  #game {
    display proudruct: block;
    width: 100vw;
    height: 100vh;
  }
  #ui {
    position: fixed;
    top: 10px;
    left: 0;
    right: 0;
    text-align: center;
    color: #fff;
    font-family: monospace;
    font-size: 18px;
    z-index: 10;
    pointer-events: none;
  }
  #overlay {
    position: fixed;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #fff;
    font-family: monospace;
    font-size: clamp(28px, 7vw, 64px);
    font-weight: 700;
    text-align: center;
    z-index: 20;
    pointer-events: auto;
    text-shadow: 0 0 18px rgba(255,77,109,.9), 0 6px 0 rgba(122,15,46,.9);
  }
  #overlay.hidden {
    display: none;
  }
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="ui">SCORE Television: <span id="score">0</span> | LIVES: <span id="lives">3</span></div>
 simultaneously<div id="overlay" tabindex="0">Click or press a key to start</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const livesEl = document.getElementById('lives');
const overlay = document.getElementById('overlay');

let gameWidth = window.innerWidth;
let gameHeight = window.innerHeight;
let dpr = window.devicePixelRatio || 1;

const paddle = { x: 0, y: 0, w: Math.min(110, window.innerWidth * 0.25), h: 70 };
const ball = { x: window.innerWidth / 2, y: window.innerHeight / 2, vx: 3, vy: -3, r: 9, speed: 4.2 };
let bricks = [];
let particles = [];
let score = 0;
let lives = 3;
let started = false;

function resize() {
  dpr = window.devicePixelRatio || 1;
  canvas.width = window.innerWidth * dpr;
  canvas.height = window.innerHeight * dpr;
  canvas.style.width = '100%';
  canvas.style.height = '100%';
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  gameWidth = window.innerWidth;
  gameHeight = window.innerHeight;
  paddle.x = (gameWidth - paddle.w) / 2;
  paddle.y = gameHeight / 2 - paddle.h / 2;
}
window.addEventListener('resize', resize);
resize();

function roundRect(ctx, x, y, w, h, r) {
  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 drawHeart(ctx, x, y, size, color) {
  ctx.save();
  ctx.translate(x, y);
  ctx.scale(size / 20, size / 20);
  ctx.beginPath();
  ctx.moveTo(0, 5);
  ctx.bezierCurveTo(-6, -2, -10, -6, 0, -1);
  ctx.bezierCurveTo(10, -6, 6, -2, 0, 5);
  ctx.fillStyle = color;
  ctx.fill();
  ctx.restore();
}

function createBricks() {
  bricks = [];
  const cols = 8;
  const rows = 5;
  const brickW = (gameWidth - 80) / cols;
  const brickH = 26;
  const gap = 10;
  const top = 90;
  const startX = (gameWidth - (cols * brickW + (cols - 1) * gap)) / 2;
  const startY = top;
  const colors = ['#ff4d6d', '#ff8a5c', '#ffd166', '#06d6a0', '#4cc9f0'];
  for (let r = 0; r < rows; r++) {
    for (let c = 0; c < cols; c++) {
      bricks.push({
        x: startX + c * (brickW + gap),
        y: startY + r * (brickH + gap),
        w: brickW,
        h: brickH,
        alive: true,
        color bulging: colors[r % colors.length]
      });
    }
  }
  return bricks;
}

function explode(x, y) {
  const colors = ['#ff4d6d', '#ff8a5c', '#ffd166', '#06d6a0', '#4cc9f0'];
  for (let i = 0; i < 16; i++) {
    const angle = Math.random() * Math.PI * 2;
    const speed = 2 + Math.random() * 5;
    particles.push({
      x: x,
      y: y,
      vx: Math.cos(angle) * speed,
      vy: Math.sin(angle) * speed - 2,
      life: 0,
      maxLife: 60,
      size: 6 + Math.random() * 6,
      color: colors[Math.floor(Math.random() * colors.length)]
    });
  }
}

function resetBall() {
  ball.x = paddle.x + paddle.w / 2;
  ball.y = paddle.y - ball.r;
  ball.vx = (Math.random() * 2 - 1) * ball.speed;
  ball.vy = -ball.speed;
}

function resetLevel() {
  bricks = createBricks();
  score = 0;
  scoreEl.textContent = score;
  lives = 3;
  livesEl.textContent = lives;
  resetBall();
}

function loseLife() {
  if (lives > 0) {
    lives--;
    livesEl.textContent = lives;
    resetBall();
  } else {
    resetLevel();
  }
}

function update() {
  paddle.y = Math.max(paddle.h / 2, Math.min(gameHeight - paddle.h / 2, ball.y - paddle.h / 2));
  paddle.x = (gameWidth - paddle.w) / 2;

  const prevX = ball.x;
  const prevY = ball.y;
  ball.x += ball.vx;
  ball.y += ball.vy;

  if (ball.y - ball.r < 0) {
    ball.y = ball.r;
    ball.vy = -ball.vy;
  }
  if (ball.y + ball.r > gameHeight) {
    ball.y = gameHeight - ball.r;
    ball.vy = -ball.vy;
  }

  if (ball.vy > 0 && ball.y - ball.r <= paddle.y + paddle.h && ball.y + ball.r >= paddle.y && ball.x >= paddle.x - ball.r && ball.x <= paddle.x + paddle.w + ball.r) {
    const hitPos = (ball.x - paddle.x) / paddle.w;
    const angle = (hitPos - 0.5) * Math.PI * 0.8;
    ball.vx = Math.sin(angle) * ball.speed;
    ball.vy = -Math.abs(Math.cos(angle)) * ball.speed;
    ball.x = paddle x + paddle.w / 2;
    ball.y = paddle.y - ball.r;
  }

  for (let i = bricks.length - 1; i >= 0; i--) {
    const b = bricks[i];
    if (!b.alive) continue;
    if (ball.x + ball.r > b.x && ball.x - ball.r < b.x + b.w && ball.y + ball.r > b.y && ball.y - ball.r < b.y + b.h) {
      const dx = ball.x - prevX;
      const dy = ball.y - prevY;
      const len = Math.hypot(dx, dy) || 1;
      const nx = dx / len;
      const ny = dy / len;
      if (Math.abs(nx) > Math.abs(ny)) {
        ball.vx = -ball.vx;
      } else {
        ball.vy = -ball.vy;
      }
      const speed = Math.hypot(ball.vx, ball.vy) || 1;
      ball.vx = (ball.vx / speed) * ball.speed;
      ball.vy = (ball.vy / speed) * ball.speed;
      b.alive = false;
      score += 10;
      scoreEl.textContent = score;
      explode(b.x + b.w / 2, b.y + b.h / 2);
      break;
    }
  }

  if (bricks.length > 0 && bricks.every(b => !b.alive)) {
    resetLevel();
  }

  if (ball.y - ball.r > gameHeight) {
    loseLife();
  }

  for (let i = particles.length - 1; i >= 0; i--) {
    const p = particles[i];
    p.life++;
    p.x += p.vx;
    p.y += p.vy;
    p.vy += 0.05;
    p.vx *= 0.98;
    if (p.life > p.maxLife) {
      particles.splice(i, 1);
    }
  }
}

function drawPaddle(ctx, p) {
  ctx.fillStyle = 'rgba(0,0,0,0.35)';
  roundRect(ctx, p.x + 3, p.y + 5, p.w, p.h, 14);
  ctx.fill();
  ctx.fillStyle = '#7a4426';
  roundRect(ctx, p.x, p.y + 8, p.w, p.h - 14, 12);
  ctx.fill();
  ctx.fillStyle = '#f2c14e';
  roundRect(ctx, p.x - 4, p.y, p.w + 8, 16, 8);
  ctx.fill();
  ctx.fillStyle = '#6ad96a';
  roundRect(ctx, p.x - 3, p.y + 10, p.w + 6, 8, 4);
  ctx.fill();
  ctx.fillStyle = '#ffd166';
  ctx.fillRect(p.x, p.y + 18, p.w, 5);
}

function drawBall(ctx) {
  ctx.fillStyle = '#fff';
  ctx.beginPath();
  ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
  ctx.fill();
  ctx.fillStyle = '#ff4d6d';
  ctx.beginPath();
  ctx.arc(ball.x, ball.y, ball.r * 0.6, 0, Math.PI * 2);
  ctx.fill();
}

function render() {
  ctx.fillStyle = '#0b0b12';
  ctx.fillRect(0, 0, gameWidth, gameHeight);

  for (const b of bricks) {
    if (!b.alive) continue;
    ctx.fillStyle = b.color;
    roundRect(ctx, b.x, b.y, b.w, b.h, 8);
    ctx.fill();
    ctx.fillStyle = 'rgba(255,255,255,0.25)';
    roundRect(ctx, b.x + 4, b.y + 4, b.w - 8, b.h * 0.4, 4);
    ctx.fill();
  }

  drawPaddle(ctx, paddle);
  drawBall(ctx);

  for (const p of particles) {
    ctx.globalAlpha = 1 - p.life / p.maxLife;
    drawHeart(ctx, p.x, p.y, p.size, p.color);
  }
  ctx.globalAlpha = 1;

  ctx.fillStyle = '#fff';
  ctx.font = '16px monospace';
  ctx.textAlign = 'center';
  ctx.fillText('BURGER BREAKOUT', gameWidth / 2, 30);
}

function loop() {
  update();
  render();
  requestAnimationFrame(loop);
}
loop();

function start() {
  started = true;
  overlay.classList.add('hidden');
}
overlay.addEventListener('click', start);
window.addEventListener('keydown', (e) => {
  if (!started && (e.key === 'Enter' || e.key === ' ' || e.key.toLowerCase() === 'a')) {
    start();
  }
});
</script>
</body>
</html>
Reasoning trace (12,172 tokens)
We need answer with one fenced code block containing complete HTML file. Need implement breakout game hamburger paddle, bricks explode into hearts. Must follow constraints. We need craft code. No text before/after except code block. Must be self-contained. No external files/fonts/network except allowed CDN. Better no external. Inline CSS/JS. Runs zero console errors. Viewport 1280x800, adapt 375. First paint meaningful, screenshot 3s after load no interaction. Games start in self-playing demo mode (simple AI) behind "Click or press a key to start" message, so judged in action without playing. Iframe only receives keyboard events after user clicks it: if demo uses keyboard, display clear message.

We need decide implementation. A breakout game in HTML canvas. Paddle is hamburger. Bricks when hit explode into hearts. Need hearts particles. Self-playing demo mode behind start message. Could have start overlay "Click or press a key to start". On click/keydown start. Then AI paddle moves toward ball. Game loop. Score maybe not hidden. Everything meaningful visible first seconds: ball, paddle, bricks, hearts maybe. Start overlay must be clear but game starts on click/key. Screenshot after 3s without interaction? Hmm task says screenshot taken automatically about 3 seconds after load, without any interaction. But also games must start in self-playing demo mode behind "Click or press a key to start" message. Contradiction? Probably start overlay visible initially, but self-playing demo mode? Maybe show start message, but game world behind it? To satisfy screenshot, maybe start overlay covers? If screenshot after 3s no interaction, if overlay visible, maybe not judged. But requirement: Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing. Could have overlay semi-transparent? Or start message but demo AI already playing? "behind a message" means maybe game canvas behind, overlay message. Screenshot might capture message. But they want game in action. Could auto-start demo after short delay? But must be behind "Click or press a key to start" message. Maybe start overlay with message, but also start game automatically? Hmm.

Maybe initial state: overlay message centered. But to be self-playing behind, we could start game immediately with AI, and overlay message "Click or press a key to start" fades? But screenshot at 3s might catch overlay. Better: start overlay is visible, but game starts automatically? Actually "behind a 'Click or press a key to start' message" means show that message, likely overlay. But for screenshot, meaningful visible. Could make overlay a translucent banner at top: "Click or press a key to start" and game already auto-playing with AI. That satisfies behind message? Maybe not. Or start overlay with backdrop but game world visible? Could have message as HTML absolutely positioned, semi-transparent. Auto demo mode starts immediately. On click/keydown, hide message, maybe toggle pause. That might satisfy screenshot and start mode.

Need code robust.

Let's design:

- Canvas fills window, resize handler. Use logical size maybe 1280x800, scale to fit? For responsive, canvas width=clientWidth, height=clientHeight. But for 1280x800 initial, maybe fixed 1280x800. Adapt down to 375. Use canvas width 1280 height 800 and CSS width 100%, height auto? Or set canvas.width = window.innerWidth, height = window.innerHeight, style width/height 100%. At 1280x800 okay, at 375 small. No horizontal scrollbar.

- Game state: ball, paddle, bricks. Ball speed. Paddle hamburger: rounded rect with bun top, patty, lettuce, cheese? Simple.

- Bricks: rows/cols. Colors. When ball hits brick, destroy, spawn explosion of hearts. Hearts: particles with x,y, vx, vy, life, size. They float/fade. Maybe hearts burst from brick position and trail.

- Ball physics: angle based on hit position. Speed increases slightly per brick? Maybe not.

- Paddle AI: if self-playing, AI tracks ball y with max speed. If player mode? We can just AI always, but user can move with arrows/W/S? Requirement self-playing demo. Could have AI controls by default after start; keyboard arrows maybe manual? To keep simple, AI.

- Start overlay: HTML div #overlay with text "Click or press a key to start". Semi-transparent, pointer-events? If pointer-events auto, click starts. But if user clicks, start. To allow AI, maybe overlay starts hidden after first click/keydown. But initial screenshot before interaction might see overlay. Maybe set overlay opacity 0.85, visible. Screenshot at 3s no interaction will capture it. Not ideal. Could auto start after 2s? But no.

Maybe initial state: overlay visible. But also game auto starts? That might be weird. Could set overlay visible and game running. On click/keydown hide overlay. The screenshot might catch overlay. The task says behind a "Click or press a key to start" message. So overlay is okay.

- Keyboard: iframe only receives after click. We can listen keydown on window after start? But initial message says click or press key. If no click, keydown might not reach iframe? They said iframe only receives keyboard events after user clicks it. So overlay should have tabindex? Or click focuses. We can add overlay tabIndex=0 and click listener. But simpler: overlay covers screen with button? But no alert. "Click or press a key to start" - click anywhere or press key. We can set overlay tabIndex=0, then keydown on overlay. But if iframe not focused? Click should focus. Add click on overlay to start.

- Start: hide overlay, start loop. Maybe if not started, still render game with AI? We can start game immediately but overlay shown. On start, set started=true, hide overlay.

- Self-playing demo: AI moves paddle. Ball bounces. If lose all lives, reset ball/paddle? Or game over overlay? Must keep interesting visible first seconds. Start with few bricks maybe. Lives 3. Score.

- Hamburger paddle: draw rounded patty etc.

- Bricks explode into tons of little hearts: when brick destroyed, create e.g. 12-20 hearts at brick position with random velocities, gravity? Hearts float upward? They should be particles. Draw heart path. Life fade. Maybe hearts burst outward and fade.

- Need "tons of little hearts" maybe explosion. Use particles array.

- Visual: dark background, neon. Paddle hamburger. Bricks with numbers? no.

- Canvas rendering: clear, background, bricks, paddle, ball, particles, maybe score UI in DOM or canvas. Keep score in DOM top.

- Game loop with requestAnimationFrame. Fixed timestep? Simple.

- Ball-paddle collision: if ball.y+radius >= paddle.y && ball.y+radius <= paddle.y+height && ball.x between paddle.x and paddle.x+width, reflect. AI: paddle.y = clamp(ball.y - paddle.height/2, min, max).

- Ball-brick collision: check ball intersects brick rects. Since brick grid, can check ball position. Better use grid collision: for each brick alive, if ball moving and intersects, determine side. But simple: check if ball center within brick rect expanded radius. Then resolve by changing angle based on overlap. Need avoid sticking. Could compute previous position. Simpler: for each brick, if ball collides, set ball direction based on which side hit. Use ball pos and prev. Compute overlapX/Y. If overlapX > overlapY, flip x else flip y. Then remove brick, spawn hearts, speed up.

- Paddle collision: reflect y.

- Ball out of bounds: lose life, reset ball to paddle. If lives zero, maybe reset level after delay? Keep simple: lives=3, game over overlay? But must be interesting. Could reset ball and keep bricks? Better: if lives lost, reset ball. If all bricks destroyed, win, maybe restart level. Not critical.

- Start overlay: HTML/CSS. No external. Use font-family system. No alert. Use div. Text: "Click or press a key to start". Style centered, pointer events auto, z-index. On click/keydown start. But if overlay has pointer events, canvas not clickable? It's overlay. On start hide.

- Keyboard: after started, listen keydown for arrows? But AI. Maybe not needed. But for start, listen on document. Since iframe click focuses, keydown works. Add window keydown listener for Enter/Space/Arrow? If not started, start. If started, maybe manual control? But self-playing demo. Could ignore.

- No localStorage etc.

- No console errors.

- Must be exactly one fenced code block. No text before/after. The final answer should be just ```html ... ```.

Let's write code carefully.

HTML structure:
```
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hamburger Breakout</title>
<style>
  html, body { margin:0; height:100%; background:#0b0b12; overflow:hidden; }
  #game { display:block; width:100vw; height:100vh; }
  #ui { position:fixed; top:10px; left:0; right:0; text-align:center; color:#fff; font-family: monospace; font-size:18px; z-index:10; pointer-events:none; }
  #overlay { position:fixed; inset:0; display:flex; align-items:center; justify-content:center; background:rgba(10,10,20,0.55); color:#fff; font-family: monospace; font-size:42px; text-align:center; z-index:20; cursor:pointer; }
  #overlay.hidden { display:none; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="ui">SCORE: <span id="score">0</span> | LIVES: <span id="lives">3</span></div>
<div id="overlay" tabindex="0">Click or press a key to start</div>
<script>
...
</script>
</body>
</html>
```

Canvas resize: set canvas.width = window.innerWidth * devicePixelRatio? For crisp. But simpler: canvas.width = window.innerWidth; canvas.height = window.innerHeight; ctx.scale? If set width/height and style 100%, coordinate space matches CSS pixels. At devicePixelRatio might be blurry. Use width = window.innerWidth * dpr; height = window.innerHeight * dpr; ctx.setTransform(dpr,0,0,dpr,0,0). Then logical coords based on CSS pixels? Could be confusing. Simpler: canvas.width = window.innerWidth; canvas.height = window.innerHeight; ctx.scale(window.devicePixelRatio, window.devicePixelRatio)? If canvas.width is actual pixels, style width 100%, then logical coordinate = CSS pixels? Actually if canvas.width=1280, style width=100% (could be 1280), ctx.scale(dpr) means drawing coordinates in CSS px. But if window resized, need set canvas.width = clientWidth * dpr; canvas.height = clientHeight * dpr; canvas.style.width = clientWidth+'px'; canvas.style.height = clientHeight+'px'; ctx.setTransform(dpr,0,0,dpr,0,0). Then logical coords are CSS px. At 1280x800, dpr=1. At retina, scale. Fine.

Game logical: width = canvas.clientWidth? But after resize, need update. Use gameWidth = window.innerWidth, gameHeight = window.innerHeight? For consistency, use canvas.clientWidth/clientHeight. On resize, update. But if using ctx.setTransform(dpr), logical coords = CSS pixels. So gameWidth = canvas.clientWidth, gameHeight = canvas.clientHeight.

Initial: paddle width 110, height 70. Hamburger style.

Bricks: rows=6, cols=8? At 1280x800, top area maybe 100-250. Brick width = (gameWidth - padding*2)/cols. Let's compute: cols=8, rows=5. brickW = (gameWidth - 60)/8 ≈ 150. brickH=28. Top margin=80, bottom margin=40. Total height = rows*(brickH+10) = 5*(38)=190. Start y=100, okay.

Ball radius 10. Speed 4.5.

Lives 3.

Score.

Particles: array. Heart particle: x,y,vx,vy,life,maxLife,size,color. On brick break: for i<14: angle random, speed 2-7, life 40-80 frames, size 6-12. Update: vy += gravity? Hearts float maybe vy += 0.05, vx*0.98, x+=vx, y+=vy. Draw heart.

Heart path function: standard bezier. Draw at x,y with size. Could use two arcs? Use path:
```
function heart(ctx,x,y,s){
  ctx.beginPath();
  ctx.moveTo(x, y+0.3*s);
  ctx.bezierCurveTo(x-s*0.5, y-s*0.4, x-s*0.8, y-s*1.1, x, y+s*0.9);
  ctx.bezierCurveTo(x+s*0.8, y-s*1.1, x+s*0.5, y-s*0.4, x, y+0.3*s);
  ctx.fill();
}
```
Maybe simpler: draw two circles and triangle? Bezier okay.

Brick drawing: rounded rect. Fill color based on row. Slight shine.

Paddle hamburger: draw rounded patty, bun top, lettuce wavy? Simple: patty rect rounded, bun top arc, cheese stripe, lettuce green blob. Use colors.

Ball: white with glow? Use radial gradient maybe. Simple circle.

Background: dark gradient? Canvas fill solid. Add subtle grid? Not needed.

HUD: DOM score/lives. Update textContent.

Overlay: div. When started false, show. On click or keydown, set started true, overlay.classList.add('hidden'). But keydown listener should be on window once? If before started, listen. After started, maybe not. We can just listen once: if (!started) start(). After started, ignore.

Need to handle start: set started=true, hide overlay, maybe reset game if not started. Initialize game on load.

Self-playing: if started, AI controls paddle. If not started, maybe AI still controls? But overlay covers. Could have AI always control paddle when started. Before started, maybe no ball? But for screenshot, overlay. After start, AI.

Actually to have self-playing demo behind message, maybe start game immediately (started=true?) but overlay visible. Hmm. Let's set started = false. Game loop runs always? If started false, AI controls paddle and ball moves? Overlay covers. But screenshot might catch game behind overlay? Overlay semi-transparent. Could be okay. But better: game state initialized, started=false, overlay visible. Game loop runs, AI moves paddle, ball bounces. Overlay semi-transparent. On click/keydown, hide overlay. This means behind overlay, self-playing demo. Good.

But if overlay has background rgba(10,10,20,0.55), screenshot will show overlay. But game visible through? Maybe not. Could make overlay background transparent? But then text readable? Use background: rgba(10,10,20,0.35); backdrop-filter? Not necessary. Maybe no background, just text with text-shadow. But to be clear, semi-transparent. The screenshot might show "Click or press a key to start" which is okay? The task says behind message. Hmm.

Maybe start overlay is just text, no background, z-index 20, pointer-events auto. Then game visible behind. Text with big bold, text-shadow. On screenshot, text visible, game behind. That satisfies. Use:
```
#overlay { position:fixed; inset:0; display:flex; align-items:center; justify-content:center; color:#fff; font-family: monospace; font-size:56px; font-weight:700; text-align:center; z-index:20; pointer-events:auto; text-shadow: 0 0 20px rgba(255,80,120,.8), 0 4px 0 #7a0f2e; }
#overlay.hidden { display:none; }
```
No background, so game behind. But pointer events on overlay means clicks start, but text is visible. The overlay covers screen but transparent. Good.

Need click or press key. Overlay tabindex=0. Click anywhere on overlay starts. Keydown on overlay or window. Since iframe receives after click, clicking overlay focuses it? tabindex=0 on div makes it focusable, so keydown works. Add keydown listener on overlay? Or window. If window, after click focus might be on overlay, but keydown bubbles. Fine.

Game loop: requestAnimationFrame. Use delta time? Simple frame-based. Ball speed in px/frame. At 60fps. Could use dt. Simpler: constant speed per frame. Might be fast. Use speed = 4.2. At 60fps, moves ~4px/frame.

Collision ball-brick: need check each brick. Since ball moves, check if intersects. To avoid multiple, break after one. Compute previous position? We can just check current position and radius. If intersects, resolve. Might cause sticking if same side. Use previous position to determine. Store ball.prevX, prevY? Or compute direction and move accordingly.

Simpler: for each brick, if ball moving and distance < radius*2, resolve. Compute overlapX = (ball.x + r) - brick.x, overlapY = (ball.y + r) - brick.y, etc. Actually use ball center. Let prevX, prevY store before move. Then on collision:
```
let dx = ball.x - prevX;
let dy = ball.y - prevY;
let len = Math.hypot(dx,dy);
let nx = dx/len, ny=dy/len;
let overlapX = (ball.x + r) - brick.x;
let overlapY = (ball.y + r) - brick.y;
let overlapR = r*2 - Math.hypot(overlapX, overlapY)? 
```
Maybe easier: use axis-aligned based on velocity. If |nx| > |ny|, flip nx; else flip ny. Then normalize speed. Remove brick, spawn hearts, score++. This works.

But ball might tunnel at high speed. Speed low enough.

Paddle collision: if ball.vy > 0 and ball.y - r < paddle.y + paddle.height and ball.y + r > paddle.y and ball.x > paddle.x - r and ball.x < paddle.x + paddle.width + r, set ball.y = paddle.y - r, reflect vy = -vy. Also maybe adjust vx based on hit position.

AI: paddle.y = clamp(ball.y - paddle.height/2, paddle.height/2, gameHeight - paddle.height/2). If manual? Not needed.

Reset ball: ball.x = paddle.x + paddle.width/2, ball.y = paddle.y - r, ball.vy = -3, vx = (Math.random()*2 -1). If stuck, set.

Lose life: lives--, update DOM, if lives <=0 maybe reset? Let's do: lives = 3. If lose life, reset ball. If lives < 0? Actually when lose, lives--. If lives <=0, maybe game over overlay? But requirement no hidden later. Could just reset lives to 3 and clear bricks? Better: if lives <= 0, show game over? Might be fine. But to keep demo, maybe reset level after short delay. Simpler: lives starts 3; when lose life, lives--; if lives < 0, lives = 3? No. Let's do game over state? Not needed for demo. But if all hearts lost, ball resets and lives decremented. If lives becomes -1? Set lives=3? Hmm.

Let's implement: lives = 3. On ball out: lives--; update HUD; if lives < 0: reset level? Actually if lives reaches 0, maybe start next level? For demo, just reset ball and keep lives? Could be confusing. Let's do: if lives > 0, reset ball. Else, reset all bricks and lives=3, score=0. That gives continuous demo.

Win condition: all bricks destroyed, maybe reset level.

Score: +10 per brick.

Hearts particles: when brick broken, create 16 hearts. Each with random angle, speed 2-7, life 50, size random 6-12, color pink/red. Update and draw.

HUD: score and lives. Update via textContent.

Resize: update canvas width/height, ctx.setTransform, gameWidth/gameHeight. Paddle positions recalc.

DPR handling:
```
function resize(){
  const dpr = window.devicePixelRatio || 1;
  canvas.width = window.innerWidth * dpr;
  canvas.height = window.innerHeight * dpr;
  canvas.style.width = window.innerWidth + 'px';
  canvas.style.height = window.innerHeight + 'px';
  ctx.setTransform(dpr,0,0,dpr,0,0);
  gameWidth = window.innerWidth;
  gameHeight = window.innerHeight;
}
window.addEventListener('resize', resize);
```
Then logical coordinates in CSS px.

But if using ctx.setTransform, fillRect etc use logical. Good.

Initial gameWidth = window.innerWidth, gameHeight = window.innerHeight.

Paddle: width = min(110, gameWidth*0.2)? At 375, width 75. height = 60? Hamburger maybe height 70. Keep paddle width 100, height 70. At small screens, maybe scale. Use paddleWidth = Math.min(110, gameWidth*0.25); paddleHeight = 65.

Bricks: cols=8, rows=5. brickW = (gameWidth - 80)/cols; brickH = 26; gap=10; top = 90; bottom = gameHeight*0.2? Let's compute total height = rows*(brickH+gap) - gap = 5*(36)-10=170. top = (gameHeight - totalHeight)/2? But at 800, top=315? That's too low. Better top=90. At 375 height=667, top=90, total height=170, okay.

Actually at 1280x800, top=90, bricks from 90 to 260. Good.

Ball radius 9.

Particles: hearts. Draw heart path centered at x,y. Use size as scale.

Heart path:
```
function drawHeart(ctx, x, y, size, color){
  ctx.save();
  ctx.translate(x,y);
  ctx.scale(size/20, size/20);
  ctx.beginPath();
  ctx.moveTo(0, 4);
  ctx.bezierCurveTo(-8, -2, -12, -8, 0, -2);
  ctx.bezierCurveTo(12, -8, 8, -2, 0, 4);
  ctx.fillStyle = color;
  ctx.fill();
  ctx.restore();
}
```
Hmm not sure. Let's use a known heart path:
```
ctx.moveTo(x, y + 0.3 * s);
ctx.bezierCurveTo(x - 0.5*s, y - 0.2*s, x - 0.8*s, y - 0.8*s, x, y - 0.2*s);
ctx.bezierCurveTo(x + 0.8*s, y - 0.8*s, x + 0.5*s, y - 0.2*s, x, y + 0.3*s);
```
Actually that's a heart? Maybe. Let's design: top dip at y, two lobes. Standard:
```
ctx.moveTo(x, y + s*0.3);
ctx.bezierCurveTo(x - s*0.4, y - s*0.2, x - s*0.7, y - s*0.9, x, y - s*0.3);
ctx.bezierCurveTo(x + s*0.7, y - s*0.9, x + s*0.4, y - s*0.2, x, y + s*0.3);
```
Might look okay. Or simpler: draw two circles and a triangle:
```
ctx.beginPath();
ctx.arc(x - s*0.35, y - s*0.1, s*0.35, 0, Math.PI*2);
ctx.arc(x + s*0.35, y - s*0.1, s*0.35, 0, Math.PI*2);
ctx.moveTo(x, y + s*0.6);
ctx.lineTo(x, y + s*1.4);
ctx.closePath();
ctx.fill();
```
That's a heart-ish. Better use bezier.

Let's use:
```
function drawHeart(ctx, x, y, size, color) {
  ctx.save();
  ctx.translate(x, y);
  ctx.scale(size / 20, size / 20);
  ctx.beginPath();
  ctx.moveTo(0, 5);
  ctx.bezierCurveTo(-6, -2, -10, -6, 0, -1);
  ctx.bezierCurveTo(10, -6, 6, -2, 0, 5);
  ctx.fillStyle = color;
  ctx.fill();
  ctx.restore();
}
```
This should draw a heart shape. Fine.

Brick colors: rows alternate? Use palette: #ff4d6d, #ff8a5c, #ffd166, #06d6a0, #4cc9f0. Or by row.

Paddle hamburger: draw at paddle.x, paddle.y, width, height.
```
function drawPaddle(ctx, p) {
  // patty
  roundRect(ctx, p.x, p.y+10, p.w, p.h-20, 12);
  fill #6b3a1f;
  // bun top
  roundRect(ctx, p.x-5, p.y, p.w+10, 14, 7);
  fill #e8b36a;
  // lettuce
  ctx.fillStyle = '#7ed957';
  ctx.beginPath();
  ctx.ellipse(p.x+p.w/2, p.y+12, p.w/2, 8, 0, 0, Math.PI*2);
  ctx.fill();
  // cheese
  ctx.fillStyle = '#ffd166';
  ctx.fillRect(p.x, p.y+18, p.w, 5);
}
```
Maybe simpler.

RoundRect helper: use ctx.roundRect if available? Chrome supports. But to be safe, write helper or use fillRect with rounded? Chrome latest supports ctx.roundRect. Use it? Might be okay. But to be safe, implement roundRect.

```
function roundRect(ctx,x,y,w,h,r){
  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();
}
```

Ball: circle with glow. Use ctx.shadowBlur? Might be okay. Draw white circle, maybe shadow.

Background: fill #0b0b12. Add subtle top gradient? Not necessary.

HUD: update score/lives. Use spans.

Overlay: div. On click start. On keydown start. When started, hide overlay. Also maybe set a flag to prevent multiple.

Start function:
```
function start(){
  started = true;
  overlay.classList.add('hidden');
}
overlay.addEventListener('click', start);
overlay.addEventListener('keydown', (e) => { if(e.key === 'Enter' || e.key === ' ' || e.key.toLowerCase() === 'a') start(); });
```
But if overlay has tabindex, keydown works. Also clicking canvas? Overlay covers, so click on overlay. Good. But overlay has no pointer-events? Set auto.

Since iframe only receives keyboard after click, clicking overlay will focus it? tabindex=0 makes it focusable. Good.

Game loop:
```
function update(){
  if (started) { // or always? Let's always AI control if started, else maybe no ball? For screenshot, overlay. Let's run always, AI controls. 
    // move ball
  }
}
```
If started false, maybe ball still moves? For screenshot, overlay. But if overlay covers, okay. To be safe, run game always, AI controls paddle, ball moves. Overlay just hides on start.

Update ball: ball.x += ball.vx; ball.y += ball.vy; check bounds. Check paddle collision. Check bricks.

Brick check: loop through bricks array. If alive, check collision. Since ball may pass, check previous position? We'll store prevX, prevY. On each frame, before moving, set prevX = ball.x, prevY = ball.y. Then move. Then check collisions.

Collision resolution:
```
function checkCollisions(){
  for (let i = bricks.length-1; i>=0; i--) {
    const b = bricks[i];
    if (!b.alive) continue;
    if (ball.x + ball.r > b.x && ball.x - ball.r < b.x + b.w && ball.y + ball.r > b.y && ball.y - ball.r < b.y + b.h) {
      // determine side
      const dx = ball.x - prevX;
      const dy = ball.y - prevY;
      const len = Math.hypot(dx, dy) || 1;
      const nx = dx/len, ny = dy/len;
      if (Math.abs(nx) > Math.abs(ny)) {
        ball.vx = -ball.vx;
      } else {
        ball.vy = -ball.vy;
      }
      // normalize speed
      const speed = Math.hypot(ball.vx, ball.vy);
      ball.vx = (ball.vx / speed) * ball.speed;
      ball.vy = (ball.vy / speed) * ball.speed;
      b.alive = false;
      score += 10;
      updateScore();
      explode(b.x + b.w/2, b.y + b.h/2);
      break;
    }
  }
}
```
But if ball hits corner, might flip both? Fine.

Paddle collision:
```
if (ball.vy > 0 && ball.y - ball.r <= paddle.y + paddle.h && ball.y + ball.r >= paddle.y && ball.x >= paddle.x - ball.r && ball.x <= paddle.x + paddle.w + ball.r) {
  const hitPos = (ball.x - paddle.x) / paddle.w; // 0..1
  const angle = (hitPos - 0.5) * Math.PI * 0.8;
  ball.vx = Math.sin(angle) * ball.speed;
  ball.vy = -Math.abs(Math.cos(angle)) * ball.speed;
  ball.y = paddle.y - ball.r;
}
```
But AI paddle y set to ball.y - h/2, so hit always center? For AI, maybe just reflect. For manual, angle. Since AI, set directly. But paddle follows ball, so collision rare. Still implement.

AI update:
```
paddle.y = clamp(ball.y - paddle.h/2, paddle.h/2, gameHeight - paddle.h/2);
```

Ball update:
```
ball.x += ball.vx;
ball.y += ball.vy;
if (ball.y - ball.r < 0) { ball.y = ball.r; ball.vy = -ball.vy; }
if (ball.y + ball.r > gameHeight) { ball.y = gameHeight - ball.r; ball.vy = -ball.vy; }
if (ball.x - ball.r < 0) { ball.x = ball.r; ball.vx = -ball.vx; }
if (ball.x + ball.r > gameWidth) { ball.x = gameWidth - ball.r; ball.vx = -ball.vx; }
```
Then check paddle collision, bricks.

If no collision, move. Need prev before move.

Order: set prev, move, bounds, paddle, bricks.

If lose life? Ball shouldn't go out top if paddle catches? If ball goes below paddle, lose life? In breakout, if ball passes paddle, lose. We can check if ball.y - r > gameHeight and no paddle? Actually paddle at bottom. If ball.y - r > paddle.y + paddle.h, it's lost. But paddle moves to ball, so maybe not. Add: if (ball.y - ball.r > gameHeight) loseLife().

Lose life:
```
function loseLife(){
  lives--;
  updateLives();
  if (lives < 0) { // game over? reset
    lives = 3;
    resetLevel();
  } else {
    resetBall();
  }
}
```
Actually if lives=3, lose -> 2. If lose -> -1? Set lives to 3? Better: if (lives <= 0) { lives = 3; resetLevel(); } else resetBall. But initial lives=3. First lose -> 2. Second ->1. Third ->0. Fourth lose -> reset. So condition if (lives <= 0) reset. Or set lives=3 after reset.

Let's do:
```
function loseLife(){
  lives--;
  updateLives();
  if (lives < 0) {
    lives = 3;
    resetLevel();
  } else {
    resetBall();
  }
}
```
Wait if lives=3, lose ->2 (not <0) reset ball. Lose again ->1 reset. Lose again ->0 reset ball? That's wrong. Should be lives <=0 for game over. Let's set lives=3. When lose, lives--. If lives <= 0: reset level. Else reset ball. So first lose: 2 -> reset ball. Second: 1 -> reset. Third: 0 -> reset level? Actually losing third life should maybe game over? In breakout, you have 3 lives, lose all -> game over. So when lives becomes 0, reset level? Hmm. Let's just do: if (lives > 0) resetBall(); else resetLevel(). But initial lives=3. Lose -> lives=2 >0 reset ball. Lose ->1 reset. Lose ->0 not >0 reset level. That's okay.

Simpler: lives = 3. On lose: lives--; update; if (lives < 0) { lives = 3; resetLevel(); } else { resetBall(); } This means after third lose, lives=0, reset ball? Actually start 3. Lose1: lives=2, reset ball. Lose2: lives=1, reset ball. Lose3: lives=0, reset ball? That's only 3 losses? No, third loss sets lives=0, then reset ball? That means you can lose again with lives=0 -> lose -> -1 reset level. Confusing.

Let's do clean:
- lives = 3.
- loseLife: if (lives > 0) { lives--; update; resetBall(); } else { resetLevel(); }
But initial lives=3, first lose -> lives=2 >0, reset ball. Second ->1. Third ->0, not >0, reset level. So you get 3 losses then reset. Good.

Reset level: clear hearts? Keep hearts? Clear bricks, create new bricks, score=0, updateScore, resetBall.

Win: if all bricks dead, resetLevel after delay? Just resetLevel immediately? Might be jarring. But okay.

Now code.

Need to avoid console errors. Ensure all functions defined. Use const/let.

Let's write final code.

Check constraints: no external files, no network. Inline CSS/JS. No localStorage etc. No alert/confirm/prompt/window.open. We use none. No storage. Good.

No horizontal scrollbar: body overflow hidden, canvas full.

Adapt down to 375: paddle width min, bricks maybe cols 8 with width (375-80)/8=36, okay. Overlay font 56 might be too big but okay. Maybe use clamp? Use font-size: clamp(24px, 8vw, 56px). Good.

First paint: canvas with bricks, paddle, ball, overlay text. Good.

Screenshot 3s: overlay text visible, game behind. If overlay has no background, text readable? Use text-shadow. Maybe add background? To see game behind, no background. But text might be hard. Add slight background? The task says behind message. I'll use transparent background with text shadow and border? Maybe:
```
#overlay { position:fixed; inset:0; display:flex; align-items:center; justify-content:center; color:#fff; font-family: monospace; font-size: clamp(28px, 7vw, 64px); font-weight:700; text-align:center; z-index:20; pointer-events:auto; text-shadow: 0 0 18px rgba(255,77,109,.9), 0 6px 0 rgba(122,15,46,.9); }
```
No background, so game visible. But text might be over bricks. Fine.

On click/press start, hide overlay.

Game loop starts on load. started false. Overlay visible. AI controls paddle. Ball moves. Self-playing demo behind overlay.

Now final code. Need one fenced code block. No text before/after.

Let's compose carefully.

Potential issues:
- ctx.roundRect not used.
- Device pixel ratio: canvas.width = window.innerWidth * dpr; canvas.height = window.innerHeight * dpr; canvas.style.width = window.innerWidth + 'px'; canvas.style.height = window.innerHeight + 'px'; ctx.setTransform(dpr,0,0,dpr,0,0); gameWidth = window.innerWidth; gameHeight = window.innerHeight; But if we set canvas.style.width to window.innerWidth, it might stretch if CSS? Better set canvas.style.width = '100%'; canvas.style.height = '100%'; and canvas.width = clientWidth*dpr etc. Simpler: canvas.style.width = '100%'; canvas.style.height = '100%'; canvas.width = window.innerWidth * dpr; canvas.height = window.innerHeight * dpr; ctx.setTransform(dpr,0,0,dpr,0,0); gameWidth = window.innerWidth; gameHeight = window.innerHeight; On resize, same. This makes canvas fill. Good.

- ctx.setTransform after resize. Good.

- HUD elements: #ui fixed top, left 0 right 0, text-align center, z-index 10, pointer-events none. Score/lives update.

- Overlay: pointer-events auto, z-index 20. When hidden display none. If display none, no pointer events.

- Keyboard: overlay.addEventListener('keydown', (e) => { if (e.key === 'Enter' || e.key === ' ' || e.key.toLowerCase() === 'a') start(); }); But e.key for space is ' '. Fine. Also click.

- Since overlay covers screen, click will start. But if user presses key, overlay must be focused. tabindex=0. Good. But if iframe not focused? After click, it should be. The keydown listener on overlay will only fire if focused. To be safe, add window keydown listener when not started? But after click, window works. Let's add:
```
window.addEventListener('keydown', (e) => {
  if (!started && (e.key === 'Enter' || e.key === ' ' || e.key.toLowerCase() === 'a')) start();
});
overlay.addEventListener('click', start);
```
This works even if focus on body? Window keydown receives if target is body? Yes, keydown bubbles to window. So okay.

- Game loop: function loop() { update(); render(); requestAnimationFrame(loop); } call once.

- Update only if started? Let's run always, but overlay covers. AI controls paddle. Ball moves. So started flag only for overlay. Actually self-playing demo: AI controls paddle regardless. So update always. On start, just hide overlay.

- resetBall: set ball.x = paddle.x + paddle.w/2; ball.y = paddle.y - ball.r; ball.vx = (Math.random()*2-1)*ball.speed; ball.vy = -ball.speed; ball.speed = 4.2.

- resetLevel: clear bricks, create new. score=0, updateScore, lives=3? Wait if reset level after game over, set lives=3. But loseLife logic. Let's define resetLevel: bricks = createBricks(); score=0; updateScore(); lives=3; updateLives(); resetBall(); But if called after game over, good. If called after win, good.

- loseLife: if (lives > 0) { lives--; updateLives(); resetBall(); } else { resetLevel(); }

- Check win: if (bricks.length > 0 && bricks.every(b => !b.alive)) { resetLevel(); } But maybe not needed.

- Ball speed: maybe increase when brick broken? Not necessary.

- Hearts particles: array of objects. On explode: for i<16, angle random, speed 2-7, life 40, maxLife 60, size 6+Math.random()*6, color ['#ff4d6d','#ff8a5c','#ffd166','#06d6a0'][Math.floor(Math.random()*4)]. Update: p.x += p.vx; p.y += p.vy; p.vy += 0.05; p.vx *= 0.98; p.life--; draw if life>0.

- Draw hearts with alpha = p.life / p.maxLife.

- Render: clear, draw bricks, paddle, ball, particles, maybe background.

- HUD: score textContent = score; lives = lives. Update in update.

- Canvas resize: update paddle positions? Paddle x centered. paddle.x = (gameWidth - paddle.w)/2. paddle.y set by AI. On resize, recalc paddle.x and paddle.w. gameWidth changes.

Let's write code.

Detailed code:

```
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const livesEl = document.getElementById('lives');
const overlay = document.getElementById('overlay');

let gameWidth = window.innerWidth;
let gameHeight = window.innerHeight;
let dpr = window.devicePixelRatio || 1;

function resize() {
  dpr = window.devicePixelRatio || 1;
  canvas.width = window.innerWidth * dpr;
  canvas.height = window.innerHeight * dpr;
  canvas.style.width = '100%';
  canvas.style.height = '100%';
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  gameWidth = window.innerWidth;
  gameHeight = window.innerHeight;
  paddle.x = (gameWidth - paddle.w) / 2;
}
window.addEventListener('resize', resize);
resize();

const paddle = { x: (gameWidth - 100) / 2, y: gameHeight/2 - 35, w: Math.min(110, gameWidth*0.25), h: 70 };
```
Wait paddle height 70, y should be center. Set paddle.y = gameHeight/2 - paddle.h/2.

```
const paddle = { x: (gameWidth - Math.min(110, gameWidth*0.25))/2, y: gameHeight/2 - 35, w: Math.min(110, gameWidth*0.25), h: 70 };
```

Ball:
```
const ball = { x: gameWidth/2, y: gameHeight/2, vx: 3, vy: -3, r: 9, speed: 4.2 };
```

Bricks:
```
let bricks = [];
function createBricks() {
  bricks = [];
  const cols = 8;
  const rows = 5;
  const brickW = (gameWidth - 80) / cols;
  const brickH = 26;
  const gap = 10;
  const top = 90;
  const totalH = rows*(brickH+gap)-gap;
  const startY = top;
  for (let r=0; r<rows; r++) {
    for (let c=0; c<cols; c++) {
      bricks.push({
        x: 40 + c*(brickW+gap),
        y: startY + r*(brickH+gap),
        w: brickW,
        h: brickH,
        alive: true,
        color: ['#ff4d6d','#ff8a5c','#ffd166','#06d6a0','#4cc9f0'][r % 5]
      });
    }
  }
  return bricks;
}
createBricks();
```
Wait x start: 40 + c*(brickW+gap). Last brick may exceed width? gameWidth - 40 - brickW? Better: x = (gameWidth - (cols*brickW + (cols-1)*gap))/2 + c*(brickW+gap). But simpler: x = 40 + c*(brickW+gap). At gameWidth=1280, brickW=(1200)/8=150, gap=10, last x=40+7*160=1140, w=150, ends 1290 > 1280. Slight overflow. Use padding 20. Let's compute: total width = cols*brickW + (cols-1)*gap = 8*150+70=1270. Start x = (gameWidth - totalWidth)/2 = (1280-1270)/2=5. Then x = startX + c*(brickW+gap). Good.

```
const startX = (gameWidth - (cols*brickW + (cols-1)*gap)) / 2;
```
For small screens, brickW may be small but okay.

Rows: total height = rows*(brickH+gap)-gap = 5*36-10=170. top=90. startY=90. Last ends 260. Good.

Particles:
```
let particles = [];
function explode(x,y){
  for (let i=0; i<16; i++){
    const angle = Math.random()*Math.PI*2;
    const speed = 2 + Math.random()*5;
    particles.push({
      x, y,
      vx: Math.cos(angle)*speed,
      vy: Math.sin(angle)*speed - 2,
      lifeブル: 0,
      ...
    });
  }
}
```
Let's use life and maxLife.

```
function explode(x,y){
  for (let i=0; i<16; i++){
    const angle = Math.random()*Math.PI*2;
    const speed = 2 + Math.random()*5;
    particles: {
      x, y,
      vx: Math.cos(angle)*speed,
      vy: Math similar Math.sin(angle)*speed - 2,
      life: 0,
      maxLife Television: 60,
      size: 6 + Math.random()*6,
      color: ...
    }
  }
}
```
Avoid weird names.

Update particles:
```
for (let i=particles.length-1; i>=0; i--) {
  const p = particles[i];
  p.life++;
  p.x += p.vx;
  p.y += p.vy;
  p.vy += 0.05;
  p.vx *= 0.98;
  if (p.life > p.maxLife) particles.splice(i,1);
}
```

Render particles:
```
for (let p of particles) {
  ctx.globalAlpha = 1 - p.life/p.maxLife;
  drawHeart(ctx, p.x, p.y, p.size, p.color);
}
ctx.globalAlpha = 1;
```

Heart function:
```
function drawHeart(ctx, x, y, size, color) {
  ctx.save();
  ctx.translate(x, y);
  ctx.scale(size/20, size/20);
  ctx.beginPath();
  ctx.moveTo(0, 5);
  ctx.bezierCurveTo(-6, -2, -10, -6, 0, -1);
  ctx.bezierCurveTo(10, -6, 6, -2, 0, 5);
  ctx.fillStyle = color;
  ctx.fill();
  ctx.restore();
}
```
This might not be perfect but okay.

RoundRect:
```
function roundRect(ctx, x, y, w, h, r) {
  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();
}
```

Draw paddle:
```
function drawPaddle(ctx, p) {
  // shadow dkotauste
  ctx.fillStyle = 'rgba(0,0,0,0.35)';
  roundRect(ctx, p.x+3, p.y+5, p.w, p.h, 14);
  ctx.fill();
  // patty
  ctx.fillStyle = '#7a4426';
  roundRect(ctx, p.x, p.y+8, p.w, p.h-14, 12);
  ctx.fill();
  // bun top
  ctx.fillStyle = '#f2c14e';
  roundRect(ctx, p.x-4, p.y, p.w+8, 16, 8);
  ctx.fill();
  // lettuce
  ctx.fillStyle = '#6ad96a';
  ctx.beginPath();
  ctx.ellipse(p.x+p.w/2, p.y+14, p.w/2, 8, 0, Math.PI, 0);
  ctx.fill();
  // cheese appealedalus
  ctx.fillStyle = '#ffd166';
  ctx.fillRect(p.x, p.y+18, p.w, 5);
}
```
Hmm ellipse with start Pi end 0 draws bottom half? Actually ellipse startAngle Pi, end 0 draws lower half? Might be okay.

Better: lettuce as green rounded rect wavy? Use ellipse at y+12, radius y? Let's just use fillRect with green and maybe ellipse. Simpler:
```
ctx.fillStyle = '#6ad96a';
roundRect(ctx, p.x-3, p.y+10, p.w+6, 8, 4);
ctx.fill();
```
Okay.

Ball draw:
```
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI*2);
ctx.fill();
ctx.fillStyle = '#ff4d6d';
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r*0.6, 0, Math.PI*2);
ctx.fill();
```

Bricks draw:
```
for (const b of bricks) {
  if (!b.alive) continue;
  ctx.fillStyle = b.color;
  roundRect(ctx, b.x, b.y, b.w, b.h, 8);
  ctx.fill();
  ctx.fillStyle = 'rgba(255,255,255,0.25)';
  roundRect(ctx, b.x+4, b.y+4, b.w-8, b.h*0.4, 4);
  ctx.fill();
}
```

Background: fill #0b0b12.

Update function:
```
function update() {
  // AI paddle
  paddle.y = Math.max(paddle.h/2, Math.min(gameHeight - paddle.h/2, ball.y - paddle.h/2));
  paddle.x = (gameWidth - paddle.w)/2; // keep centered

  // ball
  const prevX = ball.x;
  const prevY = ball.y;
  ball enough? 
  ball.x += ball.vx;
  ball.y += ball.vy;

  if (ball.y - ball.r < 0) { ball.y = ball.r; ball.vy = -ball.vy; persist? }
  if (ball.y + ball.r > gameHeight) { ball.y = gameHeight - ball.r; ball.vy = -ball.vy; }

  // paddle collision
  if (ball.vy > 0 && ball.y - ball.r <= paddle.y + paddle.h && ball.y + ball.r >= paddle.y && ball.x >= paddle.x - ball.r && ball.x <= paddle.x + paddle.w + ball.r) {
    const hitPos = (ball.x - paddle.x) / paddle.w;
    const angle = (hitPos - 0.5) * Math.PI * 0.8;
    ball.vx Atan2? 
  }
```
Since AI, paddle.y = ball.y - h/2, so ball should always be above paddle. But if ball moving down, collision. Compute:
```
    const angle = (hitPos - 0.5) * Math.PI * 0.8;
    ball.vx = Math.sin(angle) * ball.speed;
    ball.vy = -Math.abs(Math.cos(angle)) * ball.speed;
    ball.x = paddle.x + paddle.w/2;
    ball.y = paddle.y - ball.r;
```
But if AI, hitPos may be 0.5. Fine.

Then check bricks:
```
  for (let i = bricks.length - 1; i >= 0; i--) {
    const b = bricks[i];
    if (!b.alive) continue;
    if (ball.x + ball.r > b.x && ball.x - ball.r < b.x + b.w && ball.y + ball.r > b.y && ball.y - ball.r < b.y + b.h) {
      const dx = ball.x - prevX;
      const dy = ball.y - prevY;
      const len = Math.hypot(dx, dy) || 1;
      const nx = dx / len;
      const ny = dy / len;
      if (Math.abs(nx) > Math.abs(ny)) {
        ball.vx = -ball.vx;
      } else {
        ball.vy = -ball.vy;
      }
      const speed = Math.hypot(ball.vx, ball.vy);
      ball.vx = (ball.vx / speed) * ball.speed;
      ball.vy = (ball.vy / speed) * ball.speed;
      b.alive = false;
      score += 10;
      scoreEl.textContent = score;
      explode(b.x + b.w/2, b.y + b.h/2);
      break;
    }
  }
```
But ball speed might be 0? No.

Check win:
```
  if (bricks.length > 0 && bricks.every(b => !b.alive)) {
    resetLevel();
  }
```
Check lose: if (ball.y - ball.r > gameHeight) { loseLife(); } But ball might be above paddle? Since AI, paddle moves, so unlikely. But if it goes below paddle? Actually paddle at bottom, ball moving down would hit paddle. If not, it might go below. Add check.

Particles update.

Render:
```
function render() {
  ctx.fillStyle = '#0b0b12';
  ctx.fillRect(0, 0, gameWidth, gameHeight);
  // draw bricks
  ...
  // draw paddle Bats? 
  drawPaddle(ctx, paddle);
  // ball
  drawBall(ctx);
  // particles
  ...
}
```

Loop:
```
function loop() {
  update();
  render();
  requestAnimationFrame(loop);
}
loop();
```

Start:
```
let started = false;
function start() {
  started = true;
  overlay.classList.add('hidden');
}
overlay.addEventListener('click', start);
window.addEventListener('keydown', (e) => {
  if (!started && (e.key === 'Enter' || e.key === ' ' || e.key.toLowerCase() === 'a')) {
    start();
  }
});
```

HUD update:
```
function updateLives() { livesEl.textContent = lives; }
function updateScore() { scoreEl.textContent = score; }
```

Reset ball:
```
function resetBall() {
  ball.x = paddle.x + paddle.w/2;
  ball.y = paddle.y - ball.r;
  ball.vx = (Math.random()*2-1) * ball.speed;
  ball.vy = -ball.speed;
}
```

Reset level:
```
function resetLevel() {
  bricks = createBricks();
  score = 0;
  scoreEl.textContent = score;
  lives = 3;
  livesEl.textContent = lives;
  resetBall();
}
```

Lose life:
```
function loseLife() {
  if (lives > 0) {
    lives--;
    livesEl.textContent = lives;
    resetBall();
  } else {
    resetLevel();
  }
}
```

Initial lives = 3, livesEl.textContent = 3.

Now code final.

Check no external. Good.

Potential issue: ctx.setTransform(dpr,0,0,dpr,0,0) after resize, but then ctx.fillRect uses logical. Good.

Potential issue: canvas.style.width = '100%'; canvas.style.height = '100%'; and canvas.width = window.innerWidth * dpr; canvas.height = window.innerHeight * dpr; This might cause canvas internal size not matching style? It's fine.

Potential issue: window.innerWidth includes scrollbar? body overflow hidden, so no.

Potential issue: no horizontal scrollbar. body overflow hidden.

Potential issue Television. I'll write clean.

Let's write final code now. Ensure one fenced code block. No text before/after.

We need to be careful with quotes and backticks. The code contains backticks? No. Contains `${}`? No. Good.

Let's draft final HTML.

```html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hamburger Breakout</title>
<style>
  html, body { margin:0; height:100%; background:#0b0b12; overflow:hidden; }
  #game { display:block; width:100vw; height:100vh; }
  #ui { position:fixed; top:10px; left:0; right:0; text-align:center; color:#fff; font-family:monospace; font-size proud? 
...
```

Let's finalize CSS.

```
#ui {
  position: fixed;
  top: 10px;
  left: 0;
  right: 0;
  text-align: center;
  color: #fff;
  font-family: monospace;
  font-size: 18px;
  z-index rolling systematically? 10;
  pointer-events: none;
}
#overlay {
  position: fixed;
  inset: 0;
  display flex? 
```
Use `display: flex;`.

Font size: `font-size: clamp(28px, 7vw, 64px);`

Overlay hidden: `display: none;`

Now JS.

Let's write all code in final answer.

Check for any accidental use of prohibited APIs. No alert etc. Good.

Check for localStorage etc. No.

Check for external requests. No.

Check for network. No.

Check for fonts. No.

Check for images. No.

Check for canvas2d. Good.

Check for requestAnimationFrame. Good.

Check for devicePixelRatio. Good.

Check for Math.random. Good.

Check for console. No.

Check for errors. Potential error: `roundRect` not defined? I define it. `drawHeart` uses ctx. Good.

Potential error: `paddle` used before declaration in resize? I declare paddle before resize? Need order. Let's order: canvas, ctx, elements, state vars, functions, then paddle? But resize uses paddle. So declare paddle before resize or use function. Let's declare paddle before resize. But paddle needs gameWidth. We can set initial paddle after gameWidth. Let's do:

```
let gameWidth = window.innerWidth;
let gameHeight = window. 800 Shard? no.
let dpr = window.devicePixelRatio || 1;
const paddle = { x: 0, y: 0, w Newton's? ... };
```
Better:

```
let gameWidth = window.innerWidth;
let gameHeight = window.innerHeight;
let dpr = window.devicePixelRatio || 1;
const paddle = { x: (window.innerWidth - Math.min(110, window.innerWidth*0 tenure Peek? 0.25)) / 2, y: window.innerHeight/2 - 35, w America's? ... };
```
This is messy. Let's just declare game vars, then paddle after resize? But resize function uses paddle. We can define paddle as `const paddle = { x: 0, y: 0, w America's? ... };` and set in resize. But update uses paddle. So declare before functions.

Let's write:

```
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score');
const livesEl = document.getElementById('lives');
const overlay = document.getElementById('overlay');

let gameWidth = window.innerWidth;
let gameHeight = window.innerHeight;
let dpr = window.devicePixelRatio || 1;
const paddle = { x: 0, y: 0, w: Math.min(110, window.innerWidth*0.25), h: 70 };
const ball = { x: window.innerWidth/2, y: window.innerHeight/2, vx: 3, vy: -3, r: 9, speed: 4.2 };
let bricks = [];
let particles = [];
let score = 0;
let lives = 3;
let started = false;
```

Then resize:
```
function resize() {
  dpr = window.devicePixelRatio || 1;
  canvas.width = window.innerWidth * dpr;
  canvas.height = window.innerHeight * dpr;
  canvas.style.width = '100%';
  canvas.style.height = '100%';
  ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
  gameWidth = window.innerWidth;
  gameHeight = window.innerHeight;
  paddle.x = (gameWidth - paddle.w) / 2;
  paddle.y = gameHeight/2 - paddle.h/2;
}
window.addEventListener('resize', resize);
resize();
```

Good.

Now final answer.

We must ensure no text before/after code block. So final message should start with ```html and end with ```.

Let's produce final.