Hamburger breakoutOne-line
Nemotron 3.5 Lightning (free)
by NVIDIAReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 4 min 40 s
- Cost
- $0
- Output tokens
- 20,341
- Spent thinking
- 18,250 tokens
- Speed
- 72.8 tok/s
- Console errors
- 0
- First token after
- 679 ms
- First visible text after
- 4 min 6 s
- Prompt tokens
- 378
- Code size
- 6.3 KB
- Finish reason
- stop
- Served by
- Nvidia
- Model id
nvidia/nemotron-3.5-lightning:free- Reasoning sent
model default- Max output
- 32,000 tokens
- Generated
- Nov 9, 2026
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 · 228 lines
<!DOCTYPE html>
<html lang="en">
<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; padding: 0; overflow: hidden; height: 100%; font-family: sans-serif; background: #111; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
let worldW, worldH;
function resize() {
worldW = Math.min(window.innerWidth, 1280);
worldH = Math.min(window.innerHeight, 800);
canvas.width = worldW;
canvas.height = worldH;
}
resize();
window.addEventListener('resize', resize);
const STATE = { START: 0, PLAY: 1 };
let state = STATE.START;
let score = 0;
// Paddle (hamburger)
let paddle = { x: 0, y: 0, w: 80, h: 15 };
// Ball
let ball = { x: 0, y: 0, r: 6, vx: 0, vy: 0 };
// Bricks
let bricks = [];
function initBricks() {
bricks = [];
const rows = 5, cols = 8;
const bw = (worldW - 20) / cols;
const bh = 15;
for (let c = 0; c < cols; c++) {
bricks[c] = [];
for (let r = 0; r < rows; r++) {
bricks[c][r] = {
x: 10 + c * (bw + 4),
y: 30 + r * (bh + 4),
w: bw,
h: bh,
status: 1,
color: `hsl(${r * 36}, 70%, 55%)`
};
}
}
}
initBricks();
// Hearts (emoji ♥)
let hearts = [];
function spawnHearts(x, y, count = 20) {
for (let i = 0; i < count; i++) {
hearts.push({
x: x + (Math.random() - 0.5) * 20,
y: y + (Math.random() - 0.5) * 20,
vx: (Math.random() - 0.5) * 3,
vy: (Math.random() - 0.5) * 3,
size: 1 + Math.random() * 2,
alpha: 1
});
}
}
function updateHearts() {
hearts = hearts.filter(h => h.alpha > 0 && h.y > 0 && h.y < worldH && h.x > 0 && h.x < worldW);
hearts.forEach(h => {
h.x += h.vx;
h.y += h.vy;
h.alpha -= 0.015;
h.size *= 0.98;
});
}
function drawHearts() {
hearts.forEach(h => {
ctx.save();
ctx.globalAlpha = Math.max(0, h.alpha);
ctx.fillStyle = '#ff69b4';
ctx.font = `${Math.max(1, h.size * 8)}px sans-serif`;
ctx.fillText('♥', h.x, h.y);
ctx.restore();
});
}
// Input – start game on click or keypress
canvas.addEventListener('mousedown', startGame);
window.addEventListener('keydown', e => {
if (e.code === 'Space' || e.code === 'Enter') startGame();
});
function startGame() {
state = STATE.PLAY;
ball.x = worldW / 2;
ball.y = worldH - 40;
ball.vx = (Math.random() > 0.5 ? 1 : -1) * 2;
ball.vy = -2;
paddle.x = worldW / 2 - 40;
paddle.y = worldH - 20;
}
function updatePaddle() {
if (state !== STATE.PLAY) return;
const target = ball.x - paddle.w / 2;
paddle.x += (target - paddle.x) * 0.15;
if (paddle.x < 0) paddle.x = 0;
if (paddle.x + paddle.w > worldW) paddle.x = worldW - paddle.w;
}
function ballBrickCollision() {
let hit = false;
for (let c = 0; c < bricks.length && !hit; c++) {
for (let r = 0; r < bricks[c].length && !hit; r++) {
const brick = bricks[c][r];
if (brick.status === 0) continue;
if (ball.x + ball.r > brick.x && ball.x - ball.r < brick.x + brick.w &&
ball.y + ball.r > brick.y && ball.y - ball.r < brick.y + brick.h) {
brick.status = 0;
score++;
spawnHearts(brick.x + brick.w / 2, brick.y + brick.h / 2, 15);
ball.vy = -ball.vy;
ball.vx *= 1.01;
ball.vy *= 1.01;
hit = true;
}
}
}
}
function gameLoop() {
ctx.clearRect(0, 0, worldW, worldH);
if (state === STATE.START) {
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.font = '24px sans-serif';
ctx.fillText('Hamburger Breakout', worldW / 2, worldH / 2 - 20);
ctx.fillText('Click or press a key to start', worldW / 2, worldH / 2 + 20);
requestAnimationFrame(gameLoop);
return;
}
// Update
updatePaddle();
// Move ball
ball.x += ball.vx;
ball.y += ball.vy;
// Wall collisions
if (ball.x - ball.r < 0) { ball.x = ball.r; ball.vx = -ball.vx; }
if (ball.x + ball.r > worldW) { ball.x = worldW - ball.r; ball.vx = -ball.vx; }
if (ball.y - ball.r < 0) { ball.y = ball.r; ball.vy = -ball.vy; }
// Paddle collision
if (ball.y + ball.r > paddle.y && ball.x > paddle.x && ball.x < paddle.x + paddle.w) {
ball.y = paddle.y - ball.r;
ball.vy = -Math.abs(ball.vy);
const hitX = (ball.x - paddle.x) / paddle.w;
ball.vx = (hitX - 0.5) * 4;
}
// Brick collision
ballBrickCollision();
// Heart update
updateHearts();
// Ball fell below – reset
if (ball.y + ball.r > worldH) {
ball.x = worldW / 2;
ball.y = worldH - 40;
ball.vx = (Math.random() > 0.5 ? 1 : -1) * 2;
ball.vy = -2;
}
// Draw bricks
for (let c = 0; c < bricks.length; c++) {
for (let r = 0; r < bricks[c].length; r++) {
const brick = bricks[c][r];
if (brick.status === 0) continue;
ctx.fillStyle = brick.color;
ctx.fillRect(brick.x, brick.y, brick.w, brick.h);
}
}
// Draw paddle (hamburger)
ctx.fillStyle = '#8B4513';
// top bun
ctx.fillRect(paddle.x, paddle.y - 12, paddle.w, 6);
// patty
ctx.fillStyle = '#A0522D';
ctx.fillRect(paddle.x + 8, paddle.y - 6, paddle.w - 16, 10);
// bottom bun
ctx.fillStyle = '#8B4513';
ctx.fillRect(paddle.x + 8, paddle.y + 4, paddle.w - 16, 6);
ctx.fillStyle = '#fff';
// Draw ball
ctx.fillStyle = '#ff6b6b';
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
ctx.fill();
// Draw hearts
drawHearts();
// UI
ctx.fillStyle = '#fff';
ctx.font = '14px sans-serif';
ctx.fillText(`Score: ${score}`, 10, 20);
requestAnimationFrame(gameLoop);
}
gameLoop();
</script>
</body>
</html>Reasoning trace (18,250 tokens)
Here's a thinking process:
1. **Analyze User Request:**
- Genre: Breakout game
- Unique twist: Paddle is a hamburger
- Visual effect: Bricks explode into hearts when broken
- Must be a self-contained web demo
- Output format: Single fenced code block with HTML (or SVG)
- Constraints: Inline CSS/JS, no external files/network (except allowed CDNs), no localStorage/alert/confirm/prompt, 1280x800 viewport, responsive down to 375px, visible on first paint, self-playing demo mode, "Click or press a key to start" message, zero console errors in latest Chrome
2. **Identify Key Components Needed:**
- HTML structure: canvas for game, start message
- CSS: styling, responsive layout, no horizontal scroll, centered content
- JavaScript: Breakout game logic, paddle as hamburger graphic, bricks that explode into hearts on break, self-playing demo AI, keyboard/touch support
- Assets: Need hamburger image/visual, heart graphic. Since no external images allowed, I'll use ASCII/CSS or inline SVG data URLs, or just draw them on canvas. Actually, I can draw the paddle and bricks as rectangles with colors, or use inline SVG paths. Better yet, I'll draw the hamburger and hearts using simple canvas drawing - but the user might expect visual fidelity. I can use inline data URLs for small PNGs, but that might be bulky. Alternatively, I can draw them with CSS/Canvas rectangles and label them, but that's not great. Let's think: I can create simple vector-like drawings using inline SVG paths embedded in the HTML, or just use colored rectangles with text labels "HB" for hamburger and "♥" for hearts. Given constraints, I'll draw everything on canvas using basic shapes and characters, or use tiny inline SVG data URLs for the hamburger and heart.
Actually, I can draw the hamburger as a simple shape on canvas: bottom bun, patty, top bun. Hearts can be drawn with quadratic Bezier curves on canvas. That's doable without external images.
Let's plan the canvas drawing:
- Paddle: hamburger shape (three rectangles: top bun, patty, bottom bun)
- Bricks: simple rectangles, colored
- Hearts: drawn on canvas with a heart path function
Game logic:
- Ball physics: bounce off walls, paddle, bricks
- Brick collision: when hit, brick disappears, spawn hearts that move outward and fade
- Self-play AI: simple AI that tracks ball x-position
- Start message: "Click or press a key to start"
- After start, auto-play
Constraints check:
- No external files: all inline
- No alert/confirm/prompt: avoid those
- No localStorage/sessionStorage/cookies/IndexedDB: avoid
- Responsive: canvas width/height relative, CSS viewport units
- 1280x800 first paint, adapt to 375px
- Visible on first paint: start screen with game area
- Self-playing demo: AI plays automatically after start
- "Click or press a key to start" message displayed before game starts
- Zero console errors: careful coding
Let's structure the code:
HTML:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hamburger Breakout</title>
<style>
body { margin: 0; overflow: hidden; background: #111; color: #fff; font-family: sans-serif; }
canvas { display: block; max-width: 100%; height: auto; }
#start-message { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="start-message">Click or press a key to start</div>
<script>
// game logic
</script>
</body>
</html>
```
Wait, need to ensure responsiveness and no horizontal scroll. Use viewport units, set canvas width to innerWidth, height to innerHeight, but constrained. Actually, I'll set canvas to fill the viewport, and make it responsive by resizing on window resize. But the task says "The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar." So I'll set body margin 0, overflow-x hidden, canvas width 100% but with a max width or use CSS to scale. I'll use `canvas { width: 100%; height: auto; }` and set a base size via CSS, or use `viewBox` if I use SVG. But it's a canvas game. I'll handle resize.
Actually, simpler: I'll set the canvas size via JS to Math.min(innerWidth, 1280) or something, but adapt down. Let's just make the canvas fill the screen, and the game world scales. I'll use a fixed virtual resolution maybe 1280x800 but scaled via CSS transform. Or I can set `canvas.width = window.innerWidth; canvas.height = window.innerHeight;` and draw everything scaled. But coordinate math gets messy. Better: use a fixed internal resolution, and scale the canvas element with CSS. I'll set canvas to `width: 100%; height: auto; max-width: 1280px; margin: 0 auto;` and let it scale. But need no horizontal scroll. So `body { overflow-x: hidden; }`.
Let's think about the game loop, bricks, hearts, paddle, ball, AI.
I'll write a compact but functional Breakout.
Key features to implement in ~50-60 lines of JS:
- State: "start", "play", "gameover"
- Input: keyboard (arrow keys or space to start, then move paddle with mouse or keys). Since it says "Click or press a key to start", and "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." So I'll use keyboard for paddle movement after click/keypress. I'll listen for mousedown/keydown to start.
- Paddle: hamburger shape drawn on canvas
- Bricks: array, rows, columns. Colors varying.
- Ball: physics, reset on miss
- On brick hit: remove brick, spawn hearts array. Hearts have position, velocity, size, alpha. Update/render each frame.
- AI: simple: paddle follows ball x position with slight lag/delay for demo mode.
- Score/lives maybe, but keep simple.
Let's draft the JS mentally:
```javascript
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
let width, height;
function resize() { width = Math.min(window.innerWidth, 1280); height = Math.min(window.innerHeight, 800); canvas.width = width; canvas.height = height; } resize(); window.onresize = resize;
const STATE = { START: 0, PLAY: 1, GAMEOVER: 2 };
let state = STATE.START;
let score = 0;
// Player
let paddle = { x: width/2 - 50, y: height - 30, w: 100, h: 20, dx: 6 };
let ball = { x: width/2, y: height/2, r: 8, vx: 3, vy: 3 };
// Bricks
let bricks = [];
function initBricks() {
bricks = [];
const brickRowCount = 5;
const brickColumnCount = 8;
const brickWidth = (width - 20) / brickColumnCount;
const brickHeight = 20;
for (let c = 0; c < brickColumnCount; c++) {
bricks[c] = [];
for (let r = 0; r < brickRowCount; r++) {
bricks[c][r] = { x: 10 + c * (brickWidth + 4), y: 40 + r * (brickHeight + 4), status: 1, color: `hsl(${r*30}, 70%, 60%)` };
}
}
}
initBricks();
// Hearts system
let hearts = [];
function spawnHearts(x, y, count = 20) {
for (let i = 0; i < count; i++) {
hearts.push({ x, y, vx: (Math.random() - 0.5) * 4, vy: (Math.random() - 0.5) * 4, size: 2 + Math.random() * 3, alpha: 1 });
}
}
function updateHearts() {
hearts = hearts.filter(h => h.alpha > 0);
hearts.forEach(h => { h.x += h.vx; h.y += h.vy; h.alpha -= 0.02; h.size *= 0.99; });
}
function drawHearts() {
hearts.forEach(h => {
ctx.save();
ctx.globalAlpha = h.alpha;
ctx.fillStyle = `#ff4da6`;
ctx.translate(h.x, h.y);
// simple heart shape via path
ctx.beginPath();
ctx.moveTo(0, -h.size);
ctx.beadCurveTo(h.size/2, -h.size/2, h.size, 0, h.size/2, h.size/2); // not exact but okay
// actually let's use a proper heart path
ctx.closePath();
ctx.fill();
ctx.restore();
});
}
// Hmm, heart path: better to use a known SVG path or canvas quadratic curves.
// I'll use a simple filled heart using bezier curves.
// Actually, I can just draw a red circle or use a pre-made small heart SVG as data URL, but that's an external resource risk. I'll draw with quadratics.
```
Let's get a proper heart path:
```javascript
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.bezierCurveTo(-Math.sqrt(2)*r, -Math.sqrt(2)*r, -r, -r, 0, -r/2); // no
```
Better: use the classic heart path from SVG: `M0,-16.76L0,-16.76`... Actually, I'll just use a simple approach: draw a red circle and call it a heart, or use an inline `<path>` inside `<svg>` but we're on canvas. I can use `ctx.arc` for simplicity, but the user wants hearts. I'll draw a decent heart with bezier curves. Let's look up a quick heart path:
A common path: `beginPath(); moveTo(x, y); bezierCurveTo(x-50, y-50, x+50, y-50, x, y+50);` not quite.
Actually, I can just use a filled rounded rectangle or use `ctx.fillStyle = 'red'; ctx.fillRect`. But the user specifically said "explodes into tons of little hearts". I'll draw a small heart using a path that's known to work. I can embed a tiny SVG heart as a data URL inside the JS, but data URLs are technically inline and allowed ("no external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task"). Data URLs are not external requests, they're data. But to be safe, I'll draw it with canvas.
Let's use this heart path (from various JS snippets):
```javascript
function drawHeart(x, y, size) {
ctx.save();
ctx.translate(x, y);
ctx.scale(size, size);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.bezierCurveTo(-Math.sqrt(2), -Math.sqrt(2), -2, -2, 0, -2); // meh
// Actually, let's use a simpler reliable heart:
ctx.moveTo(0, -0.5);
ctx.bezierCurveTo(-Math.sqrt(2)/2, -Math.sqrt(2)/2, -1, -1, -1, 0);
ctx.bezierCurveTo(-1, 1, 0, 1, 0, 1); // no
```
This is getting messy. I'll use a different strategy: I'll create a small HTML `<svg>` heart element and draw it on canvas using `drawImage`, but that requires an SVG source. I can just create an Image object with a data URL containing a tiny SVG heart. Data URL is inline. Let's do that: create a data URL string for a 10x10 red heart SVG. That's one line and guaranteed to look like a heart.
Data URL for a heart:
```
data:image/svg+xml;base64,PHN2ZyB3aWR0aD0gMTAuNSiBoZWlnaHQ9IDIwIiB2ZXJzaW9uPSIxLjEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgA...
```
I can generate a simple heart SVG:
```svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"><path fill="#ff4da6" d="M5 10C2.3 10 0 8.7 0 6.3C0 3.3 3.3 1 5 1c1.7 0 3.4.7 4.7 2.1L5 10z"/></svg>
```
Actually, a simple heart shape: `<path d="M5 10 C 2.3 10 0 8.7 0 6.3 C 0 3.3 3.3 1 5 1 C 6.7 1 8 3.3 8 6.3 C 8 8.7 5.7 10 5 10 Z"/>`. That's a heart-ish shape. I'll use that.
Data URL: base64 of `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"><path fill="#ff4da6" d="M5 10 C 2.3 10 0 8.7 0 6.3 C 0 3.3 3.3 1 5 1 C 6.7 1 8 3.3 8 6.3 C 8 8.7 5.7 10 5 10 Z"/></svg>`. I'll compute base64 mentally or just avoid it and draw with canvas. Given time, I'll just draw hearts using a simple filled circle with a smile? No, user wants hearts. I'll use the canvas path approach with a known working heart function. Let's derive a compact heart path that works in canvas:
```javascript
ctx.beginPath();
ctx.moveTo(x, y);
ctx.bezierCurveTo(x - 10, y - 10, x - 10, y + 10, x, y + 20);
ctx.bezierCurveTo(x + 10, y + 10, x + 10, y - 10, x, y);
```
That's more of a blob. Not great.
Let's use this standard heart path (from MDN/JS examples):
```javascript
function drawHeart(ctx, x, y, size) {
size = size || 1;
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.moveTo(x, y);
ctx.bezierCurveTo(x - size/2, y - size, x - size, y - size/2, x - size, y);
ctx.bezierCurveTo(x - size, y + size/2, x - size/2, y + size, x, y + size);
ctx.bezierCurveTo(x + size/2, y + size, x + size, y + size/2, x + size, y);
ctx.bezierCurveTo(x + size, y - size/2, x + size/2, y - size, x, y);
ctx.closePath();
ctx.fill();
}
```
Actually, I'll just use a pre-made small heart SVG as an Image src with data URL. It's the most reliable. I'll create the data URL once. The base64 of a minimal heart SVG. Let's compute:
SVG: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="#ff4da6" d="M6 12c-3.3 0-6-2.7-6-6s2.7-6 6-6 6 2.7 6 6-2.7 6-6 6z"/>` - no that's a circle with dimples. A heart usually has a point at bottom. Let's use: `<path fill="#ff4da6" d="M6 12.5L10 6h-4l-1.4 2.8z"/>` Not great.
Given the complexity, maybe I should just draw small red circles and call them "hearts" or use text "♥". But the task says "explodes into tons of little hearts". I think using the heart emoji "♥" or a simple shape is acceptable. I can draw a filled heart using `ctx.fillStyle = 'red'; ctx.fillRect` no.
Alternative: I can use `<canvas>` and draw an `<svg>` heart inside the canvas using `toDataURL`? No.
Let's reconsider: I can make the hearts be simple red circles with a notched bottom, or just use the heart emoji rendered by the browser if I set innerHTML, but that's not canvas. Actually, I can just use `ctx.fillStyle = '#ff69b4'; ctx.beginPath(); ctx.arc(x, y, r, 0, Math.PI*2); ctx.fill();` and call it a heart. The user might not inspect pixel-perfect hearts. But they said "tons of little hearts". I'll draw small red hearts using a path that's known: I'll use the "love" heart shape from common JS snippets.
After a quick mental search, a reliable 2D heart path in canvas:
```javascript
ctx.beginPath();
ctx.moveTo(x, y);
ctx.bezierCurveTo(x - 0.5*size, y - 1*size, x - 1*size, y - 0.5*size, x - 1*size, y);
ctx.bezierCurveTo(x - 1*size, y + 0.5*size, x - 0.5*size, y + 1*size, x, y + 1*size);
ctx.bezierCurveTo(x + 0.5*size, y + 1*size, x + 1*size, y + 0.5*size, x + 1*size, y);
ctx.bezierCurveTo(x + 1*size, y - 0.5*size, x + 0.5*size, y - 1*size, x, y);
ctx.closePath();
```
This might look like a blob. I'll just use a simple approach: draw a red circle, and that's fine. Or I can use an image. Let's use a data URL heart. I'll generate it now.
Actually, I can just use this: create an `Image` object, set its `src` to a data URL string of a small heart SVG. I'll compute the base64. Let's do a minimal heart SVG:
```
<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10" viewBox="0 0 10 10"><path fill="#ff4da6" d="M5 10 C 2.3 10 0 8.7 0 6.3 C 0 3.3 3.3 1 5 1 C 6.7 1 8 3.3 8 6.3 C 8 8.7 5.7 10 5 10 Z"/></svg>
```
Base64 of that: I can't compute exactly right now, but I can avoid the data URL and just draw hearts with a simple path that I know works. Let's use this one (from various CodePens):
```javascript
ctx.moveTo(80, 80);
ctx.bezierCurveTo(80, 10), (10, 10), (10, 80);
ctx.bezierCurveTo(10, 160), (80, 160), (80, 80);
```
No.
Given the time, I'll just use small red circles and add a "♥" text inside or just use the emoji. Actually, I can render the heart emoji by creating a canvas and drawing text "♥" with fill, but it'll be pixelated. Better: I'll just draw filled hearts using a simple method: draw two circles and a triangle, but on canvas it's easier to use `ctx.arc` for the top lobes and `ctx.moveTo` for the bottom point. Let's use this path that's commonly working:
```javascript
ctx.beginPath();
ctx.arc(x, y - size/2, size/2, 0, Math.PI*2); // top left? no
```
I'm overcomplicating. Let's just use a red circle and call it a heart. The task is about a demo, not a design contest. I'll draw small red filled circles and maybe add a notch. Or I'll use the heart emoji character: `ctx.fillText('♥', x, y);` but that's text.
Let's read the constraints again: "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." Heart emoji is a system font, fine. I can use `ctx.fillText('♥', ...)`. But "tons of little hearts" - I can draw many heart emojis at positions. That's actually perfect and easy! I'll just use `ctx.fillText('♥', x, y)` for each heart. That satisfies "hearts" and is trivial. The user will see hearts. I'll do that.
So hearts = array of {x, y, vx, vy, size, alpha}; I'll update positions and draw with `ctx.fillText('♥', x, y)`. Color can be set via `ctx.fillStyle = '#ff69b4'`. The heart emoji ♥ will appear. Perfect.
Now, paddle as hamburger: I'll draw three rectangles on canvas: top bun (brown), patty (red/brown), bottom bun (brown). I'll use colors: `#8B5A2B` for bun, `#A0522D` maybe. Or I can use.fillStyle colors. I'll draw:
- top bun: rect(x, y, width/5*2, height/5)
- patty: rect(x + width/10, y + height/5, width/5*3, height/3)
- bottom bun: rect(x + width/10, y + height/5 + height/3, width/5*2, height/5)
But since paddle moves, I'll draw it each frame.
Game flow:
- On start (click or key), hide start message, start game loop, ball moves, AI paddle follows ball.
- Bricks: 5 rows, 8 cols. Colors vary.
- On ball hitting brick: brick status = 0, remove, spawn hearts at brick center.
- Ball bounce: off walls, off paddle (with angle based on hit position), off nothing = lose life/life reset. I'll just reset ball to center and reduce lives, but keep it simple: maybe just 3 lives, game over when no lives. But to keep code short, I'll just have one life and restart on miss, or just loop. Since it's a demo, I'll make it so bricks keep resetting? No, I'll implement basic lives: let's say 3 lives, when ball falls, reset. But code length. I'll do simple: ball goes down, game over, restart with R key? But task says "Games must start in a self-playing demo mode behind a 'Click or press a key to start' message, so the game can be judged in action without playing." So I need self-playing demo. I'll make the AI play automatically, and if ball misses, it just resets and continues (infinite lives or just keep going). I'll set a lives counter but not game over, just reset ball. Or I'll make bricks endless by regenerating? Simpler: just play, bricks disappear, hearts fly, ball bounces, AI tracks. If ball misses bottom, reset ball to center, continue. Score increments per brick.
Let's design the loop:
```javascript
let state = 'start';
let score = 0;
let lives = 3;
let paddle = { x: width*0.5 - 40, y: height - 20, w: 80, h: 15 };
let ball = { x: width/2, y: height/2, r: 6, vx: 2, vy: 2 };
let bricks = [];
let hearts = [];
function init() {
// init bricks
bricks = [];
const rows = 5, cols = 8;
const bw = (width - 20) / cols;
const bh = 15;
for (let c = 0; c < cols; c++) {
bricks[c] = [];
for (let r = 0; r < rows; r++) {
bricks[c][r] = { x: 10 + c*(bw+4), y: 30 + r*(bh+4), w: bw, h: bh, status: 1, color: `hsl(${r*36}, 70%, 60%)` };
}
}
hearts = [];
ball.x = width/2; ball.y = height - 40;
ball.vx = (Math.random() > 0.5 ? 1 : -1) * 2;
ball.vy = -2;
state = 'play';
}
```
AI:
```javascript
function updateAI() {
if (state !== 'play') return;
// paddle center tracks ball x with slight lag
const target = ball.x - paddle.w/2;
paddle.x += (target - paddle.x) * 0.1;
// keep paddle in bounds
if (paddle.x < 0) paddle.x = 0;
if (paddle.x + paddle.w > width) paddle.x = width - paddle.w;
}
```
Input for paddle movement (player can also move it, but AI will drive it; I'll let player use left/right arrows, but AI overrides or combines). Since it's self-playing demo, I'll just have AI control paddle. But user might press keys. I'll listen for keydown to start, and maybe left/right to control, but AI will aim. To keep it simple, I'll have the AI control paddle, and the game starts on first click/keypress. The "Click or press a key to start" message will be displayed initially.
Let's structure the code carefully to avoid errors.
One important: "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." So I must display that message, and ensure that after click, keyboard events are received. I'll have the message, on mousedown or keydown, start the game.
Also: "No horizontal scrollbar." I'll set `html, body { margin: 0; padding: 0; overflow: hidden; }` and `canvas { display: block; }`.
Responsive: The canvas will resize to window inner width/height, but capped at 1280x800. I'll use `width = Math.min(window.innerWidth, 1280);` `height = Math.min(window.innerHeight, 800);` and set canvas width/height attributes. Then CSS `canvas { width: 100%; height: auto; }` will scale it, but the game world coordinates will be in virtual pixels. I'll map drawing to those virtual dimensions. Actually, if I set canvas width attribute to the capped size, and CSS width to 100%, the drawing will be at that resolution but scale down. To avoid blurriness, I'll just use the virtual size for all logic, and let CSS scale it. But if the viewport is 375px wide, the canvas element will be narrow, and the game world (1280 width) will be scaled down. That's fine. I need to make sure no horizontal scroll: since canvas is block element and body overflow-x hidden, it should be fine.
However, if I set `canvas.width = Math.min(innerWidth, 1280)`, and then `canvas.style.width = '100%'`, the canvas will be as wide as its container. The coordinate system will be from 0 to that width. That means the game resolution changes with viewport. That's okay for responsiveness, but the "1280×800 viewport" requirement means it should look good at that size. I'll just set the canvas size to the viewport size, and the game will adapt. To have a fixed virtual resolution, I'd need to use `scale()` transform, but that's more complex. I'll just make the canvas fill the screen, and design the game for a variable resolution. The task says "The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide." So I need to support both. I'll set the canvas `width` and `height` attributes to `innerWidth` and `innerHeight`, and not use CSS width, so the drawing is at native resolution. But then on mobile it'd be tiny. Better: I'll set canvas to `max-width: 1280px; margin: 0 auto;` and `width: 100%`? Let's do: canvas element with `width="1280" height="800"` fixed, and CSS `display: block; margin: 0 auto; max-width: 100%;`. But then it won't fill smaller screens and will have scroll. I need it responsive.
Let's use this approach: Set canvas attribute width = Math.min(innerWidth, 1280), height = Math.min(innerHeight, 800). Then in CSS, `canvas { width: 100%; height: auto; }`. The drawing will be at that resolution, and it will scale down. The game logic will use the attribute width/height as the world size. But if I set width attribute to 1280 when viewport is larger, and smaller when narrower, the world size changes. That's actually fine for a breakout game; I can just use the actual canvas pixel width as the game world width. I'll do: `let WORLD_W = canvas.width; let WORLD_H = canvas.height;` and init bricks based on that. On resize, I'll update canvas size and reinit bricks. That makes it fully responsive without coordinate transforms. I'll just keep all game coordinates relative to canvas width/height.
Let's outline the full code:
```html
<!DOCTYPE html>
<html lang="en">
<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; padding: 0; overflow: hidden; font-family: sans-serif; background: #111; color: #fff; }
canvas { display: block; }
#start-message { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; pointer-events: all; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="start-message">Click or press a key to start</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
let worldW, worldH;
function resize() {
worldW = Math.min(window.innerWidth, 1280);
worldH = Math.min(window.innerHeight, 800);
canvas.width = worldW;
canvas.height = worldH;
}
resize();
window.addEventListener('resize', resize);
const STATE = { START: 0, PLAY: 1 };
let state = STATE.START;
let score = 0;
// Paddle (hamburger)
let paddle = { x: worldW/2 - 40, y: worldH - 20, w: 80, h: 15 };
// Ball
let ball = { x: worldW/2, y: worldH/2, r: 6, vx: 0, vy: 0 };
// Bricks
let bricks = [];
function initBricks() {
bricks = [];
const rows = 5, cols = 8;
const bw = (worldW - 20) / cols;
const bh = 15;
for (let c = 0; c < cols; c++) {
bricks[c] = [];
for (let r = 0; r < rows; r++) {
bricks[c][r] = { x: 10 + c*(bw+4), y: 30 + r*(bh+4), w: bw, h: bh, status: 1, color: `hsl(${r*36}, 70%, 55%)` };
}
}
}
initBricks();
// Hearts (emoji ♥)
let hearts = [];
function spawnHearts(x, y, count = 20) {
for (let i = 0; i < count; i++) {
hearts.push({
x: x + (Math.random() - 0.5) * 20,
y: y + (Math.random() - 0.5) * 20,
vx: (Math.random() - 0.5) * 3,
vy: (Math.random() - 0.5) * 3,
size: 1 + Math.random() * 2,
alpha: 1
});
}
}
function updateHearts() {
hearts = hearts.filter(h => h.alpha > 0 && h.y > 0 && h.y < worldH && h.x > 0 && h.x < worldW);
hearts.forEach(h => {
h.x += h.vx;
h.y += h.vy;
h.alpha -= 0.015;
h.size *= 0.98;
});
}
function drawHearts() {
hearts.forEach(h => {
ctx.save();
ctx.globalAlpha = Math.max(0, h.alpha);
ctx.fillStyle = '#ff69b4';
ctx.font = `${h.size * 10}px sans-serif`;
ctx.fillText('♥', h.x, h.y);
ctx.restore();
});
}
// Input
let started = false;
canvas.addEventListener('mousedown', () => { if (state === STATE.START) startGame(); });
window.addEventListener('keydown', e => { if (e.code === 'Space' || e.code === 'Enter') { if (state === STATE.START) startGame(); } });
function startGame() {
state = STATE.PLAY;
started = true;
// hide start message
document.getElementById('start-message').style.display = 'none';
// reset ball
ball.x = worldW/2; ball.y = worldH - 40;
ball.vx = (Math.random() > 0.5 ? 1 : -1) * 2;
ball.vy = -2;
// reset paddle
paddle.x = worldW/2 - 40;
}
// AI paddle control (tracks ball)
function updatePaddle() {
if (state !== STATE.PLAY) return;
// simple AI: paddle center moves toward ball x
const target = ball.x - paddle.w/2;
paddle.x += (target - paddle.x) * 0.15;
if (paddle.x < 0) paddle.x = 0;
if (paddle.x + paddle.w > worldW) paddle.x = worldW - paddle.w;
}
// Collision detection
function ballBrickCollision() {
for (let c = 0; c < bricks.length; c++) {
for (let r = 0; r < bricks[c].length; r++) {
const brick = bricks[c][r];
if (brick.status === 0) continue;
if (ball.x + ball.r > brick.x && ball.x - ball.r < brick.x + brick.w &&
ball.y + ball.r > brick.y && ball.y - ball.r < brick.y + brick.h) {
// hit!
brick.status = 0;
score++;
spawnHearts(brick.x + brick.w/2, brick.y + brick.h/2, 15);
// bounce ball
ball.vy = -ball.vy;
// optional: increase speed slightly
ball.vx *= 1.02;
ball.vy *= 1.02;
break; // stop checking bricks for this frame? maybe continue to hit multiple
}
}
}
}
// Game loop
function loop() {
ctx.clearRect(0, 0, worldW, worldH);
if (state === STATE.START) {
ctx.fillStyle = '#fff';
ctx.font = '20px sans-serif';
ctx.textAlign = 'center';
ctx.fillText('Hamburger Breakout', worldW/2, worldH/2 - 20);
ctx.fillText('Click or press a key to start', worldW/2, worldH/2 + 20);
return;
}
// Update
updatePaddle();
// Move ball
ball.x += ball.vx;
ball.y += ball.vy;
// Wall collisions
if (ball.x - ball.r < 0) { ball.x = ball.r; ball.vx = -ball.vx; }
if (ball.x + ball.r > worldW) { ball.x = worldW - ball.r; ball.vx = -ball.vx; }
if (ball.y - ball.r < 0) { ball.y = ball.r; ball.vy = -ball.vy; }
// Paddle collision
if (ball.y + ball.r > paddle.y && ball.x > paddle.x && ball.x < paddle.x + paddle.w) {
// hit paddle
ball.y = paddle.y - ball.r;
ball.vy = -Math.abs(ball.vy);
// add angle based on where hit
const hitX = (ball.x - paddle.x) / paddle.w;
ball.vx = (hitX - 0.5) * 4;
}
// Brick collision
ballBrickCollision();
// Heart update
updateHearts();
// Ball fell below
if (ball.y + ball.r > worldH) {
// reset ball
ball.x = worldW/2; ball.y = worldH - 40;
ball.vx = (Math.random() > 0.5 ? 1 : -1) * 2;
ball.vy = -2;
// maybe reduce lives, but we'll just continue
}
// Draw everything
// Draw bricks
ctx.fillStyle = '#fff';
for (let c = 0; c < bricks.length; c++) {
for (let r = 0; r < bricks[c].length; r++) {
const brick = bricks[c][r];
if (brick.status === 0) continue;
ctx.fillStyle = brick.color;
ctx.fillRect(brick.x, brick.y, brick.w, brick.h);
}
}
// Draw paddle (hamburger)
ctx.fillStyle = '#8B4513'; // saddle brown
// top bun
ctx.fillRect(paddle.x, paddle.y - 12, paddle.w, 6);
// patty
ctx.fillStyle = '#A0522D'; // sienna
ctx.fillRect(paddle.x + 8, paddle.y - 6, paddle.w - 16, 10);
// bottom bun
ctx.fillStyle = '#8B4513';
ctx.fillRect(paddle.x + 8, paddle.y + 4, paddle.w - 16, 6);
ctx.fillStyle = '#fff'; // reset
// Draw ball
ctx.fillStyle = '#ff6b6b';
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI*2);
ctx.fill();
// Draw hearts
drawHearts();
// UI
ctx.fillStyle = '#fff';
ctx.font = '14px sans-serif';
ctx.fillText(`Score: ${score}`, 10, 20);
requestAnimationFrame(loop);
}
loop();
</script>
</body>
</html>
```
Let's review for errors and constraints.
- "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." I'll output just the code block.
- "Everything must be inline: CSS in <style>, JavaScript in <script>." Yes.
- "No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task." I'm not using any external resources. Heart emoji is system font, fine. Colors are inline. No CDNs needed.
- "Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw." I'm not using any of those.
- "The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar." I have `overflow: hidden` on html, body. Canvas resizes to min(innerWidth, 1280) and min(innerHeight, 800). On 375px wide, worldW = 375, worldH = min(innerHeight, 800). The game will scale down. No horizontal scroll because body overflow-x hidden and canvas is block element. I should also ensure the canvas doesn't cause scroll. I'll set `box-sizing: border-box` maybe not needed. I'll add `html, body { height: 100%; }` and `body { margin: 0; overflow: hidden; }`. Good.
- "Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction." The start message will be visible, showing "Hamburger Breakout" and "Click or press a key to start". That's meaningful. After 3 seconds, screenshot taken. The game state might be START, so it shows the title and start message. That's fine.
- "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." The game starts immediately on click/key, AI plays, bricks break, hearts explode. Visible right away.
- "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." I have that. The AI tracks the ball. On start, ball moves, AI paddle follows. Bricks are there. Hearts spawn on break.
- "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." I have the message, and I listen for mousedown and keydown to start. After click, keyboard events are received. I also listen for space/enter. Good.
- "The code must run with zero errors in the console of the latest Chrome." I need to check for any undefined, etc. Let's review.
Potential issues:
- In `updateHearts`, I filter hearts with `h.alpha > 0 && h.y > 0 && h.y < worldH && h.x > 0 && h.x < worldW`. That's fine.
- In `drawHearts`, I use `ctx.font = \`${h.size * 10}px sans-serif\``. If h.size is very small, font size might be 0 or negative. I'll ensure min size: `Math.max(1, h.size * 10)`. Also, the heart emoji ♥ might not render well at small sizes, but it's okay.
- In `ballBrickCollision`, I loop through all bricks and break after first hit, but I `break` inside the inner loop, which only breaks the inner loop. I should `break` out of both or use a flag. Actually, I have `break;` after `spawnHearts` and `ball.vy = -ball.vy;` which breaks the inner `for (let r ...)` loop. But the outer `for (let c ...)` continues, so the ball might hit multiple bricks in one frame. That's fine, but I need to make sure I don't modify the array while iterating in a way that causes issues. I'm just setting status=0, okay. But the `break` only breaks the r loop, then the c loop continues, and the ball might collide with another brick below. That's actually correct behavior for Breakout: ball can hit multiple bricks if they're aligned, but usually one per frame. To be safe, I'll add a `return` or `break` out of both loops. I'll use a flag `let hit = false;` and `if (hit) break;` after the c loop. Or I'll just let it hit all; it's fine for a demo.
- In paddle collision: `if (ball.y + ball.r > paddle.y && ball.x > paddle.x && ball.x < paddle.x + paddle.w)`. The paddle y is `worldH - 20`, and paddle h is 15. So paddle.y is bottom - 20, height 15, so bottom is at worldH - 5. Ball falls from top. The condition `ball.y + ball.r > paddle.y` means ball bottom > paddle top. That's correct. But I also set `ball.y = paddle.y - ball.r;` after collision, which pushes ball above paddle. Good.
- The AI: `paddle.x += (target - paddle.x) * 0.15;` This will smoothly follow. But I need to make sure paddle doesn't go out of bounds. I have the checks.
- The start message: I set `style.display = 'none'` on start. Initially it's visible. Good.
- Resize: I call `resize()` once on load, and on window resize. But the initial viewport might be 1280x800 or 375x667. The canvas size will adapt. The game world coordinates update. However, `initBricks()` uses `worldW` and `worldH` which are set in `resize()`. I call `initBricks()` after `resize()` at the top? Actually, I have `initBricks()` called after `resize()` in the global scope? In my outline, I have `resize();` then `initBricks();` but `worldW` and `worldH` are set in `resize()`. I need to make sure `initBricks()` runs after `resize()` sets `worldW`. I'll structure: `resize(); initBricks();` inside a window load or just after defining the function. Since I'm using `window.addEventListener('resize', resize);` and calling `resize()` immediately before, `worldW` will be set. Then `initBricks()` uses `worldW`. That's fine.
Let's refine the code to be more robust.
One thing: The "Click or press a key to start" message needs to be clickable/pressable. I have `canvas.addEventListener('mousedown', ...)` and `window.addEventListener('keydown', ...)`. But the message div is over the canvas. When user clicks the canvas, the event might not reach the message? Actually, the message is absolutely positioned over the canvas. Clicking the canvas will trigger mousedown on canvas, and also the div might receive event if pointer-events are default. I set `pointer-events: all` on the message in CSS? I didn't include that in the style snippet. I'll add `pointer-events: none;` to the message so clicks pass through to canvas, or `pointer-events: all` and handle both. Better: set `pointer-events: none;` on the message so mouse clicks go through to canvas underneath. But the task says "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." This implies that the user must click the iframe/canvas to enable keyboard events. So clicking the message or canvas should both work. I'll make the message clickable by setting `pointer-events: all;` and also have the canvas listener. Or I can just have the keydown listener on window, and mousedown on canvas. To ensure the click registers, I'll set the canvas to `cursor: pointer` and the message to `pointer-events: none;` so clicks fall on canvas. But then clicking the message does nothing, but user might click around. I'll just use the keydown listener as primary, and also mousedown on canvas. The message itself doesn't need to be clicked; the user just needs to click the iframe area. I'll add a style to the canvas: `cursor: pointer;` and the message is just text. I'll have the mousedown on canvas, and keydown on window. When either fires, start game. That satisfies "Click or press a key to start".
Let's adjust CSS:
```css
#start-message { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); text-align: center; pointer-events: none; }
canvas { cursor: pointer; }
```
`pointer-events: none;` on message means clicks go through to canvas, triggering mousedown. Good.
Also, I need to ensure that after start, keyboard events are received. The task says "The iframe only receives keyboard events after the user clicks it". So I should probably add a flag or just rely on the fact that after the first click/keypress, events are processed. In sandbox iframes, usually keyboard events are blocked until the iframe has focus or a user interaction. My code listens for keydown on window, which should work after any user interaction (click or keypress). Since I have both mousedown and keydown listeners, the first interaction will trigger start. After that, the game loop runs and AI plays. That should be fine.
Let's check for any `alert`, `confirm`, `prompt`. None.
`localStorage` etc. None.
Network requests? None. I'm not using any images or fonts externally. The heart emoji is system. The colors are hex strings. The SVG? I'm not using SVG. All good.
One thing: The canvas font rendering for hearts: `ctx.fillText('♥', h.x, h.y);` The heart emoji might be colored by the system, but I set `ctx.fillStyle = '#ff69b4'` which might override the emoji color to solid pink. That's fine, it'll be a pink heart.
Let's review the paddle drawing logic. I have:
```javascript
ctx.fillStyle = '#8B4513';
ctx.fillRect(paddle.x, paddle.y - 12, paddle.w, 6); // top bun
ctx.fillStyle = '#A0522D';
ctx.fillRect(paddle.x + 8, paddle.y - 6, paddle.w - 16, 10); // patty
ctx.fillStyle = '#8B4513';
ctx.fillRect(paddle.x + 8, paddle.y + 4, paddle.w - 16, 6); // bottom bun
ctx.fillStyle = '#fff';
```
This assumes paddle.w is at least 24 (since 8+16=24). paddle.w is 80, so fine. The positions: top bun sits at paddle.y - 12, height 6, so its bottom is at paddle.y - 6. Patty starts at paddle.y - 6, height 10, bottom at paddle.y + 4. Bottom bun starts at paddle.y + 4, height 6, bottom at paddle.y + 10. But paddle.y is `worldH - 20`. So paddle bottom is at worldH - 20 + 10 = worldH - 10. That's above the canvas bottom (worldH). The ball falls from y = worldH - 40, so there's space. The paddle sits near the bottom, not overlapping the very bottom edge. That's fine.
Ball start: `ball.y = worldH - 40;` with `vy = -2;` so it moves up initially. That's okay; it'll go up, then gravity? No gravity, ball vy will be affected by collisions. Actually, in Breakout, ball usually starts moving up towards bricks. Here, bricks are at y around 30+, ball starts at worldH - 40, vy = -2 (upwards). It'll go up, hit bricks, etc. When it falls below, it resets. That works.
Bricks: rows 5, cols 8. Colors hsl(r*36, 70%, 55%). r from 0 to 4, so colors vary. Brick height bh = 15, width bw = (worldW - 20)/8. For worldW=1280, bw = 1260/8 = 157.5. For worldW=375, bw = 355/8 = 44.375. That's fine.
Hearts spawn: 15 per brick. That's "tons of little hearts". Good.
Score display: `ctx.fillText(\`Score: ${score}\`, 10, 20);`
One potential error: In the loop, I have `if (state === STATE.START) { ... return; }` but the start message is already handled by the initial render and the startGame hiding it. Actually, I have the loop checking state and drawing text. But I also have the start message div initially visible. When startGame runs, it sets `style.display = 'none'` on the message div. Then the loop's state check might still draw text if state is START, but state will be PLAY after startGame. However, the loop runs on requestAnimationFrame, and the initial call is before any interaction. So initially state is START, it will draw the text inside the canvas, overlapping the div? The div is absolutely positioned over canvas. The canvas drawing of text at (worldW/2, worldH/2 ± 20) will appear on top of or behind the div depending on stack order. Since div is in body and canvas is also in body, canvas is after div in my HTML? I have canvas first, then div. In HTML, canvas is first, then div. So div is on top of canvas in rendering order. If I draw text on canvas, it might be hidden behind the div. I should either hide the div before drawing, or not draw the text on canvas when state is START, and rely on the div. I'll make the loop only draw when state is PLAY, and the START state is handled entirely by the div. So I'll remove the text drawing from the loop's START block, or set the div's innerHTML to the title/message and just have the loop run when PLAY. Let's do: initially state = START, loop runs, but I'll just have the loop clear and not draw anything if START, and the div is visible. When startGame runs, it hides the div and sets state = PLAY. Then loop draws the game. But the loop's first check `if (state === STATE.START) { ... return; }` will return early, clearing the canvas with `ctx.clearRect` at the top. That will clear the canvas, removing the title text. Then the div is hidden, so nothing is visible! That's bad. I need to ensure that on first paint, something is visible. The div with "Click or press a key to start" and "Hamburger Breakout" should be visible. I'll structure: the loop only runs after start, or I'll have the START state in the loop draw the title on canvas AND keep the div visible, but the div might overlap. Simpler: I'll not have a separate div for the start message; I'll just draw the start message on the canvas in the START state, and not use a div. The task says "display a clear 'Click or press a key to start' message". It doesn't specify it must be a div; it can be canvas text. And "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." So I'll just use canvas text. That avoids z-index issues and extra elements.
Let's remove the div entirely. I'll just have the canvas show the title and start message on first paint. On click/keypress, the game clears and starts. That's cleaner.
Revised plan:
- No start message div.
- In game loop, state = START: clear canvas, draw "Hamburger Breakout" and "Click or press a key to start" centered.
- On mousedown/keydown: state = PLAY, reset game vars.
- Loop then draws the game.
This satisfies "Something meaningful must be visible on first paint" and "Click or press a key to start" message.
Let's adjust code accordingly.
Also, responsive: with no div, full canvas. On 375px width, worldW = 375, worldH = min(innerHeight, 800). The game will be smaller but functional. No horizontal scroll because body overflow-x hidden.
Let's rewrite the full code mentally with these changes.
```html
<!DOCTYPE html>
<html lang="en">
<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; padding: 0; overflow: hidden; height: 100%; font-family: sans-serif; background: #111; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="game"></canvas>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
let worldW, worldH;
function resize() {
worldW = Math.min(window.innerWidth, 1280);
worldH = Math.min(window.innerHeight, 800);
canvas.width = worldW;
canvas.height = worldH;
}
resize();
window.addEventListener('resize', resize);
const STATE = { START: 0, PLAY: 1 };
let state = STATE.START;
let score = 0;
// Paddle (hamburger)
let paddle = { x: 0, y: 0, w: 80, h: 15 };
// Ball
let ball = { x: 0, y: 0, r: 6, vx: 0, vy: 0 };
// Bricks
let bricks = [];
function initBricks() {
bricks = [];
const rows = 5, cols = 8;
const bw = (worldW - 20) / cols;
const bh = 15;
for (let c = 0; c < cols; c++) {
bricks[c] = [];
for (let r = 0; r < rows; r++) {
bricks[c][r] = { x: 10 + c*(bw+4), y: 30 + r*(bh+4), w: bw, h: bh, status: 1, color: `hsl(${r*36}, 70%, 55%)` };
}
}
}
initBricks();
// Hearts
let hearts = [];
function spawnHearts(x, y, count = 20) {
for (let i = 0; i < count; i++) {
hearts.push({
x: x + (Math.random() - 0.5) * 20,
y: y + (Math.random() - 0.5) * 20,
vx: (Math.random() - 0.5) * 3,
vy: (Math.random() - 0.5) * 3,
size: 1 + Math.random() * 2,
alpha: 1
});
}
}
function updateHearts() {
hearts = hearts.filter(h => h.alpha > 0 && h.y > 0 && h.y < worldH && h.x > 0 && h.x < worldW);
hearts.forEach(h => {
h.x += h.vx;
h.y += h.vy;
h.alpha -= 0.015;
h.size *= 0.98;
});
}
function drawHearts() {
hearts.forEach(h => {
ctx.save();
ctx.globalAlpha = Math.max(0, h.alpha);
ctx.fillStyle = '#ff69b4';
ctx.font = `${Math.max(1, h.size * 8)}px sans-serif`;
ctx.fillText('♥', h.x, h.y);
ctx.restore();
});
}
// Input
canvas.addEventListener('mousedown', startGame);
window.addEventListener('keydown', e => { if (e.code === 'Space' || e.code === 'Enter') startGame(); });
function startGame() {
state = STATE.PLAY;
// reset ball
ball.x = worldW/2; ball.y = worldH - 40;
ball.vx = (Math.random() > 0.5 ? 1 : -1) * 2;
ball.vy = -2;
// reset paddle
paddle.x = worldW/2 - 40;
paddle.y = worldH - 20;
}
function updatePaddle() {
if (state !== STATE.PLAY) return;
const target = ball.x - paddle.w/2;
paddle.x += (target - paddle.x) * 0.15;
if (paddle.x < 0) paddle.x = 0;
if (paddle.x + paddle.w > worldW) paddle.x = worldW - paddle.w;
}
function ballBrickCollision() {
for (let c = 0; c < bricks.length; c++) {
for (let r = 0; r < bricks[c].length; r++) {
const brick = bricks[c][r];
if (brick.status === 0) continue;
if (ball.x + ball.r > brick.x && ball.x - ball.r < brick.x + brick.w &&
ball.y + ball.r > brick.y && ball.y - ball.r < brick.y + brick.h) {
brick.status = 0;
score++;
spawnHearts(brick.x + brick.w/2, brick.y + brick.h/2, 15);
ball.vy = -ball.vy;
ball.vx *= 1.01;
ball.vy *= 1.01;
// break out of both loops
c = bricks.length; // trick to outer loop exit
break;
}
}
}
}
function gameLoop() {
ctx.clearRect(0, 0, worldW, worldH);
if (state === STATE.START) {
ctx.fillStyle = '#fff';
ctx.textAlign = 'center';
ctx.font = '24px sans-serif';
ctx.fillText('Hamburger Breakout', worldW/2, worldH/2 - 20);
ctx.fillText('Click or press a key to start', worldW/2, worldH/2 + 20);
requestAnimationFrame(gameLoop);
return;
}
// Update
updatePaddle();
// Move ball
ball.x += ball.vx;
ball.y += ball.vy;
// Wall collisions
if (ball.x - ball.r < 0) { ball.x = ball.r; ball.vx = -ball.vx; }
if (ball.x + ball.r > worldW) { ball.x = worldW - ball.r; ball.vx = -ball.vx; }
if (ball.y - ball.r < 0) { ball.y = ball.r; ball.vy = -ball.vy; }
// Paddle collision
if (ball.y + ball.r > paddle.y && ball.x > paddle.x && ball.x < paddle.x + paddle.w) {
ball.y = paddle.y - ball.r;
ball.vy = -Math.abs(ball.vy);
const hitX = (ball.x - paddle.x) / paddle.w;
ball.vx = (hitX - 0.5) * 4;
}
// Brick collision
ballBrickCollision();
// Heart update
updateHearts();
// Ball fell below
if (ball.y + ball.r > worldH) {
ball.x = worldW/2; ball.y = worldH - 40;
ball.vx = (Math.random() > 0.5 ? 1 : -1) * 2;
ball.vy = -2;
}
// Draw bricks
for (let c = 0; c < bricks.length; c++) {
for (let r = 0; r < bricks[c].length; r++) {
const brick = bricks[c][r];
if (brick.status === 0) continue;
ctx.fillStyle = brick.color;
ctx.fillRect(brick.x, brick.y, brick.w, brick.h);
}
}
// Draw paddle (hamburger)
ctx.fillStyle = '#8B4513';
// top bun
ctx.fillRect(paddle.x, paddle.y - 12, paddle.w, 6);
// patty
ctx.fillStyle = '#A0522D';
ctx.fillRect(paddle.x + 8, paddle.y - 6, paddle.w - 16, 10);
// bottom bun
ctx.fillStyle = '#8B4513';
ctx.fillRect(paddle.x + 8, paddle.y + 4, paddle.w - 16, 6);
ctx.fillStyle = '#fff';
// Draw ball
ctx.fillStyle = '#ff6b6b';
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI*2);
ctx.fill();
// Draw hearts
drawHearts();
// UI
ctx.fillStyle = '#fff';
ctx.font = '14px sans-serif';
ctx.fillText(`Score: ${score}`, 10, 20);
requestAnimationFrame(gameLoop);
}
gameLoop();
</script>
</body>
</html>
```
Let's review for any console errors potential:
- `ctx.fillText('♥', h.x, h.y);` - heart emoji is a valid character, should work.
- `ctx.font = \`${Math.max(1, h.size * 8)}px sans-serif\`` - h.size starts at 1+Math.random()*2, so min 1, max ~3. Font size 8 to 24, fine.
- In `updateHearts`, `h.size *= 0.98;` each frame, will slowly decrease. After many frames, might underflow to 0, but `Math.max(1, ...)` in font size handles it. Also `h.alpha -= 0.015;` will go negative, but I filter `h.alpha > 0`. Good.
- In `ballBrickCollision`, I have `c = bricks.length;` to break outer loop, then `break;` inner. That works but is a bit tricky. Let's use a flag for clarity:
```javascript
let hit = false;
for (let c = 0; c < bricks.length && !hit; c++) {
for (let r = 0; r < bricks[c].length && !hit; r++) {
const brick = bricks[c][r];
if (brick.status && ...) {
// ...
hit = true;
}
}
}
```
That's cleaner. I'll adjust.
- In paddle collision: `if (ball.y + ball.r > paddle.y && ball.x > paddle.x && ball.x < paddle.x + paddle.w)`. paddle.y is `worldH - 20`. The paddle height is 15, so its bottom is at worldH - 5. The condition `ball.y + ball.r > paddle.y` means ball's bottom edge is below paddle's top edge. That's correct for detecting when ball hits from above. But I also need to ensure ball.x is within paddle width. Good. After collision, `ball.y = paddle.y - ball.r;` pushes ball above paddle. Good.
- Ball start vy = -2, so it moves up. Bricks are at y around 30+. So ball will go up, hit bricks, bounce down, etc. When it falls below worldH, it resets. That's fine for a demo.
- The AI paddle: `paddle.x += (target - paddle.x) * 0.15;` This will smoothly follow. Since ball vx is around ±2, and paddle moves with lerp, it should keep up. Might be a bit laggy but acceptable for demo.
- Resize: `worldW = Math.min(window.innerWidth, 1280);` if innerWidth is 375, worldW=375. Bricks will be narrow. The ball radius 6 might be large relative to brick width, but still playable. The paddle width 80 might be wider than canvas, but I have bounds check `if (paddle.x + paddle.w > worldW) paddle.x = worldW - paddle.w;`. If worldW < 80, paddle.x will be clamped to 0 or negative? Actually, if worldW=375, paddle.w=80, paddle.x can go up to 295. That's fine. If worldW=30, paddle.w=80, then paddle.x clamped to worldW - paddle.w = -50, which is < 0, then the other check `if (paddle.x < 0) paddle.x = 0;` will set it to 0, but then paddle extends beyond right edge. To prevent that, I should ensure paddle.w doesn't exceed worldW, or handle it. Since worldW min is 375 (from min(window.innerWidth, 1280)), and paddle.w=80, it's fine. 375 > 80. So safe.
- The start state: `ctx.clearRect(0, 0, worldW, worldH);` at the beginning of loop, then draw text. On first paint, state is START, it will clear (though canvas initially blank) and draw the title and start message. Good.
- The `gameLoop()` is called immediately. Initially canvas might be 0 size if DOM not ready, but we have `resize()` called before, and window load is not needed; the sandbox should have DOM ready.
- One missing thing: The task says "The iframe only receives keyboard events after the user clicks it". In my code, I have `canvas.addEventListener('mousedown', startGame);` and `window.addEventListener('keydown', ...)`. The keydown listener is on window, which will receive events after the iframe has focus and a user interaction has occurred. Since I have mousedown on canvas as the first interaction, after that, keydown events will be processed. That should satisfy the constraint. I'll also add `canvas.focus()` maybe not needed. I'll set `canvas.tabIndex = 1;` and `canvas.focus()` on load? But task says "Do not use ... window.open()", but focus is fine. Actually, I can just rely on the fact that the first mousedown or keydown will trigger start. The keydown listener on window might not fire until the page has focus. To be safe, I'll add `canvas.focus()` after resize, but that might not be necessary in sandbox. I'll skip it; the mousedown will start the game, and after that, the game has focus implicitly because the loop is running. But for the "press a key to start" part, if user presses key before clicking, the window keydown listener should fire if the page is focused. In many environments, the first keydown after page load is captured. I'll keep both.
Let's double-check the "No horizontal scrollbar" constraint. I have `html, body { overflow: hidden; }`. The canvas is `display: block;`. No elements have width wider than viewport. The canvas width is set to `worldW` which is `Math.min(innerWidth, 1280)`, so it fits. No horizontal scroll. Good.
"The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide." With `worldW = Math.min(window.innerWidth, 1280)`, on 1280px it's 1280, on 375px it's 375. The game elements scale down proportionally because all coordinates are relative to worldW/worldH. Bricks width scales, paddle scales, ball radius is fixed in world units (6 pixels at worldW resolution). Actually, ball.r = 6 is absolute in world pixels. On a 375px wide canvas, a ball radius of 6 is still 6 pixels, which is fine. The paddle width 80 is also absolute pixels. So the game will look smaller but proportions are maintained roughly. It should "adapt gracefully". I think it's acceptable.
One improvement: I could make the game elements scale relative to worldW, e.g., paddle.w = worldW * 0.0625 (so 80 when worldW=1280). But not necessary; fixed pixel sizes that happen to work at 1280 and shrink gracefully is fine. The task likely doesn't require perfect scaling, just "look right" and "adapt gracefully". I'll keep fixed sizes; on 375px it'll be a smaller version, which is fine.
Let's review the heart emoji rendering: `ctx.fillText('♥', h.x, h.y);` The heart emoji is U+2665. It should render. I set `ctx.fillStyle = '#ff69b4'` which will color the emoji solid pink. That's fine.
One potential issue: In the START state, I have `ctx.clearRect(0, 0, worldW, worldH);` then draw text. But the canvas might have default browser background; clearRect with no fill style will clear to transparent, showing body background #111. Then I draw white text. That's fine.
Let's ensure all variables are defined before use. `worldW` and `worldH` are set in `resize()`, called before `initBricks()`. Good.
Let's do a mental run-through of the code structure:
- HTML skeleton
- CSS: body margin 0, overflow hidden, background #111, canvas display block
- JS:
- get canvas, context
- resize function sets worldW/worldH, canvas attrs, called on load and on resize
- state const, init state START, score 0
- paddle, ball, bricks, hearts arrays/functions
- initBricks() uses worldW
- spawnHearts, updateHearts, drawHearts
- input listeners: mousedown on canvas -> startGame; keydown on window (space/enter) -> startGame
- startGame(): state=PLAY, reset ball pos/vel, reset paddle pos
- updatePaddle(): AI tracking ball
- ballBrickCollision(): hit detection, brick destroy, hearts spawn, ball bounce
- gameLoop():
- clearRect
- if START: draw title and start message, requestAnimationFrame loop, return
- else PLAY: update paddle, move ball, wall collisions, paddle collision, brick collision, update hearts, check ball below reset, draw bricks, draw paddle, draw ball, draw hearts, draw score, requestAnimationFrame loop
- gameLoop() called initially
Potential error: In the START block, I have `requestAnimationFrame(gameLoop);` and `return;`. That will create an infinite loop of animation frames doing nothing until user interaction. That's fine; it's just waiting. When interaction happens, startGame sets state=PLAY, and the loop will proceed to the PLAY section on the next frame. However, note that the clearRect at the top of the loop will clear the START text, and then the PLAY section will draw the game. But the startGame function is called from mousedown/keydown, which happens during a mouse event or key event, not necessarily during a gameLoop frame. The event listener will call startGame, which sets state=PLAY. The next time gameLoop runs (which will be soon, as the event loop continues), it will clear and draw PLAY. That's fine. But there's a subtle issue: the mousedown/keydown events might fire while the loop is in the START block, and after startGame, the loop's next iteration will handle the transition. I should make sure that the clearRect and state check happen after the event. Since event listeners are processed between timer events, it should work. To be safer, I could have the START block not clear the canvas every frame, but just draw once. But clearing each frame is fine; it will just redraw the text each frame until start.
Actually, I can optimize: in START block, I'll just draw the text once and not clear every frame, but it's okay.
Let's consider if the user clicks the canvas during the START text display. The mousedown listener will fire, call startGame(), state becomes PLAY. The next gameLoop iteration will clear the canvas (removing the text) and draw the game. The div is not there, so it's fine.
One more thing: The task says "Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction." My first paint will show "Hamburger Breakout" and "Click or press a key to start" centered. That's meaningful. The screenshot will capture that. Good.
"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." The game starts immediately with bricks, paddle, ball, AI plays. Bricks break, hearts explode. Visible right after start. Good.
"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." I have the AI paddle, the ball starts moving, bricks are there. The user doesn't need to play; the AI controls the paddle. The start message is there. After click/key, the demo plays automatically. Good.
I think the code is solid. Let's write the final output exactly as a single fenced code block with ```html.
I'll make sure there's no text before or after the code block. The code block should start with ```html and end with ```.
Let's produce the final answer.