Hamburger breakoutOne-line
Nemotron 3 Nano Omni (free)
by NVIDIAReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 1 min 48 s
- Cost
- $0
- Output tokens
- 10,795
- Spent thinking
- 9,307 tokens
- Speed
- 100.1 tok/s
- Console errors
- 0
- First token after
- 470 ms
- First visible text after
- 1 min 34 s
- Prompt tokens
- 378
- Code size
- 4.4 KB
- Finish reason
- stop
- Served by
- Nvidia
- Model id
nvidia/nemotron-3-nano-omni-30b-a3b-reasoning: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 · 147 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Breakout Hamburger</title>
<style>
html,body{margin:0;padding:0;overflow:hidden;background:#111;color:#fff;font-family:sans-serif;}
#startMsg{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:24px;text-align:center;pointer-events:auto;z-index:10;}
canvas{position:relative;display:block;margin:auto;width:100%;height:auto;background:#222;}
</style>
</head>
<body>
<div id="startMsg">Click or press a key to start</div>
<canvas id="game"></canvas>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const startMsg = document.getElementById('startMsg');
function resizeCanvas(){
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
const ball = {r:8, x:canvas.width/2, y:canvas.height-30, vx:3, vy:-3};
const paddle = {w:80, h:10, x:canvas.width/2-40, y:canvas.height-20};
const bricks = [];
const hearts = [];
// create bricks
for (let row=0; row<5; row++) {
for (let col=0; col<6; col++) {
const x = 20 + col*(50+10);
const y = 20 + row*(20+10);
bricks.push({x, y, w:50, h:20, alive:true});
}
}
function startGame(){
startMsg.style.display='none';
requestAnimationFrame(gameLoop);
}
canvas.addEventListener('click',()=>{ if(startMsg.style.display!=='none') startGame(); });
document.addEventListener('keydown',e=>{ if(startMsg.style.display!=='none') startGame(); });
function spawnHearts(brick){
const cx = brick.x + brick.w/2;
const cy = brick.y + brick.h/2;
for(let i=0;i<30;i++){
const angle = Math.random()*Math.PI*2;
const speed = 2+Math.random()*3;
const vx = Math.cos(angle)*speed;
const vy = Math.sin(angle)*speed;
const size = 5+Math.random()*10;
const svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
svg.setAttribute('viewBox','0 0 32 29.6');
svg.innerHTML = '<path d="M0 29.6C0 15.5 16 0 32 0C32 0 16 15.5 0 29.6z" fill="#ff69b4"/>';
svg.style.position='absolute';
svg.style.left = (cx) + 'px';
svg.style.top = (cy) + 'px';
svg.style.width = size + 'px';
svg.style.height = size + 'px';
svg.style.opacity = '1';
document.body.appendChild(svg);
hearts.push({elem:svg, x:cx, y:cy, vx, vy, size, life:1});
}
}
function gameLoop(){
// update ball
ball.x += ball.vx;
ball.y += ball.vy;
// wall collisions
if (ball.x+ball.r > canvas.width){ ball.x = canvas.width-ball.r; ball.vx=-ball.vx; }
if (ball.x-ball.r < 0){ ball.x = 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 = -ball.vy;
}
// brick collisions
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){
b.alive = false;
spawnHearts(b);
ball.vy = -ball.vy;
}
}
// AI paddle movement
const step = 4;
if (ball.vx > 0) paddle.x += step;
else if (ball.vx < 0) paddle.x -= step;
paddle.x = Math.max(0, Math.min(canvas.width-paddle.w, paddle.x));
// update hearts
for(let i=hearts.length-1;i>=0;i--){
const h = hearts[i];
h.x += h.vx;
h.y += h.vy;
h.life -= 0.02;
h.elem.style.opacity = h.life;
if (h.life <= 0){
h.elem.remove();
hearts.splice(i,1);
}
}
// render
ctx.clearRect(0,0,canvas.width,canvas.height);
// background
ctx.fillStyle = '#111';
ctx.fillRect(0,0,canvas.width,canvas.height);
// bricks
ctx.fillStyle = '#ff69b4';
for(const b of bricks){
if(b.alive){
ctx.fillRect(b.x,b.y,b.w,b.h);
}
}
// paddle (hamburger)
ctx.fillStyle = '#fff';
const ph = 10, pw = 80, py = paddle.y;
const hx = paddle.x + (pw-40)/2;
ctx.fillRect(hx, py-12, 40, 6); // top bar
ctx.fillRect(hx, py-4, 40, 6); // middle bar
ctx.fillRect(paddle.x, py, pw, ph); // bottom bar (paddle)
// ball
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI*2);
ctx.fillStyle = '#fff';
ctx.fill();
requestAnimationFrame(gameLoop);
}
</script>
</body>
</html>Reasoning trace (9,307 tokens)
We need to output a single fenced code block with HTML containing CSS and JS inline. It's a Breakout game. Paddle is a hamburger (maybe an image or CSS shape). Bricks explode into hearts (SVG hearts). Must be self-contained, no external resources except CDN if allowed. We can use simple shapes via CSS/HTML canvas. Use canvas for game logic. We'll need to load hearts as SVG data URIs.
We need to ensure page looks right in 1280x800 and adapt down to 375px. Use responsive canvas scaling: set canvas width/height attributes to match CSS size (maybe 800x600). Use CSS to make canvas responsive (max-width: 100%; height: auto). Ensure no scrollbars.
Start with a message "Click or press a key to start". After click/keypress, start demo mode: a simple AI plays automatically (the paddle moves automatically to follow ball). That satisfies "self-playing demo mode". The game should be visible within first seconds.
We need to avoid any external files. Use canvas element. Use CSS to style. Use JavaScript to handle input and game loop.
Implement simple Breakout: ball bounces off walls, paddle (hamburger) at bottom, bricks arranged in rows. When ball hits a brick, we replace brick with an explosion of hearts (SVG hearts). Hearts should be many small hearts (maybe 10-20). We can generate heart shapes via SVG path and embed as data URIs, then create many <div> or <svg> elements that animate outward.
Simplify: When brick is hit, we create a burst of heart elements positioned at brick location, with random velocities, and fade out.
Implement paddle as a hamburger: maybe a simple shape: three horizontal bars (CSS). Could be a <div> with class "paddle hamburger". Use CSS to draw three bars.
But we need paddle to be controlled? In demo mode, AI will move paddle automatically to follow ball. So we need to detect ball direction and move paddle accordingly.
Simplify: Use keyboard input for user (click or press key to start). After start, we can let AI automatically move paddle: compute ball x speed, move paddle to center of ball's x.
Implement game loop using requestAnimationFrame.
Responsive: set canvas CSS width: 100%; height: auto; and set canvas width/height attributes to e.g., 800 and 600 for default, but CSS will scale.
Need to ensure no scrollbars: body margin 0; overflow hidden.
Now hearts: we can embed a small heart SVG as data URI. Let's create a heart path: "M0 16A8 8 0 0 1 8 0A8 8 0 0 1 16 16H0z". That's a simple heart shape. We'll use that.
We need many hearts: maybe 30 hearts per brick. We'll create <svg> elements with class "heart" and set their transform to position and animate via CSS keyframes (scale and opacity). Or we can use canvas to draw hearts; but easier to use <svg> elements.
But we must keep everything inline, no external files. Using data URIs for hearts is okay.
Implement brick array: each brick object with x, y, width, height, alive. When hit, set alive false and spawn hearts.
Hearts: we can create <svg class="heart"> with width/height 20 and position absolute; we need to place them at the brick location and then animate outward. Use CSS animation: keyframes moving outward (translate) and scaling, fading.
Simplify: For each heart, set its style: position absolute; top/left based on brick center; then apply animation that moves it outward with random angle and distance, and fade.
We'll need a container for hearts? maybe body.
Implementation steps:
- HTML: <canvas id="game"></canvas>
- <style> set canvas size, body margin 0, display flex center, etc. Also style paddle (hamburger) as a div with class "paddle". Use ::before etc to create three bars.
- <script> define constants: canvas width 800, height 600; scale for responsiveness.
- Initialize variables: ball = {x: canvas.width/2, y: canvas.height-30, vx: 3, vy: -3}; bricks = []; create bricks rows.
- Paddle = {x: canvas.width/2 - 40, y: canvas.height-20, width: 80, height: 10, vx: 0};
- hearts = []; each heart object {svg: element, x, y, vx, vy, size, alpha}.
- Function spawnHearts(brick): create many heart elements (maybe 30). For each, set its position to brick center, assign random velocity direction (angle) and speed (e.g., 2-5). Add to hearts array.
- In each frame: move ball, handle collisions with walls (reverse vx/vy), collisions with paddle (if ball.y > paddle.y and ball.x within paddle.x to paddle.x+width) -> reset ball y and vx positive, vy negative.
- Collision with bricks: check if ball overlaps brick rect; if so, set brick alive false, spawn hearts at brick center, and maybe reverse ball vy.
- Move paddle: In demo mode, AI: if ball.vx > 0, move paddle right; else left; clamp within bounds.
- Move hearts: update each heart's position (apply velocity), update its transform: translate based on its vx,vy and also maybe scale. Also decrease alpha over time; remove when alpha <=0.
- Rendering: clear canvas; draw bricks (maybe simple rectangles). Draw paddle (hamburger). Draw ball (maybe a circle). Draw hearts (draw each heart SVG at its position). Could use drawImage with heart SVG? But easiest: set each heart's element's style left/top to its position and let it render as SVG.
But hearts are inline SVG elements; we can create them as <svg class="heart"> with viewBox and path; set style position absolute; then in script set style.left = (x) + "px"; style.top = (y) + "px"; Also set transform: translate(...) maybe.
Simplify: For each heart, we can set its "transform" attribute to move outward: e.g., setAttribute('transform', `translate(${dx} ${dy}) scale(${scale})`); but easier: we can set left/top and also apply CSS animation that moves it outward.
Maybe we can just set the heart's CSS transform: `transform: translate(${dx} ${dy}) scale(${scale});` and also set opacity.
Simplify further: Instead of many dynamic hearts, we can create a single heart element and clone it for each burst, setting its CSS transform to random angle and distance, and animate via CSS keyframes (e.g., move outward and fade). But we need many hearts simultaneously.
Simplify: Use canvas to draw hearts (simple path). But we need heart shape; we could draw with canvas using fillStyle and path. That may be easier: in each frame, we draw hearts as part of the canvas. But hearts need to be many and animated; we can store heart objects with position and velocity and draw them on canvas each frame.
Let's do canvas drawing for hearts: create a function drawHearts(ctx). For each heart, compute its position (x,y) based on initial location and velocity; also maybe scale based on distance. But we need heart shape; we can draw a path.
Heart path: "M0 16A8 8 0 0 1 8 0A8 8 0 0 1 16 16H0z". We can scale to size.
Implementation details:
- When brick is hit, we generate heart objects: for i in 0..30: x0 = brick.x + brick.width/2; y0 = brick.y + brick.height/2; assign dx = Math.cos(angle) * speed; dy = Math.sin(angle) * speed; size = 5 + Math.random()*10; add to hearts array.
- In update: for each heart: x += vx; y += vy; maybe also scale: factor = 1 + (distance / maxDist) * 0.5? Not necessary.
- In render: clear canvas; draw bricks (if alive). Draw paddle (hamburger). Draw ball (circle). Draw hearts: for each heart, draw heart shape at (x,y) with size (maybe radius). Use ctx.save/translate.
But drawing hearts with canvas may be heavy but fine.
Let's decide: Use canvas for everything: bricks as rectangles; paddle as hamburger shape (draw three bars). Hearts as small heart shapes drawn with canvas.
We need to draw hamburger: three rectangles: top bar centered, middle bar (the "bun"?). Actually hamburger icon is three horizontal lines. We can draw three rectangles: top, middle, bottom. Use ctx.fillRect.
Implement paddle as a <div> with class "paddle". But we can also draw paddle on canvas: a rectangle representing the paddle. But requirement: paddle is a hamburger. So maybe we draw the hamburger shape on canvas (three bars). That satisfies requirement.
Let's design paddle: width 80, height 10 (the bottom bar). Then top two bars maybe half height each? Actually typical hamburger: three lines: top line, middle line, bottom line. We'll draw three rectangles each width 30, height 6, spaced vertically. But we can just draw a simple shape: three horizontal lines.
Simplify: Paddle as a rectangle 80x10 at bottom; but that wouldn't be hamburger. Let's draw three lines: top line at y = paddle.y, middle at y = paddle.y + 8, bottom at y = paddle.y + 16. Use width 40 maybe. But we need to represent hamburger shape. Let's design: paddle width 80, height 20. Then we draw three lines each width 40, height 6, positions: y1 = 4, y2 = 12, y3 = 20. Centered.
Implement: In render, draw paddle: for each line, ctx.fillRect(paddleCenterX - 20, lineY, 40, 6).
Now game loop:
- On start (click or keypress), set a flag demo = true; start loop.
- In loop: update ball position; check collisions; update paddle position (AI). If ball hits paddle, reverse vy and set ball.y = paddle.y - ball.radius.
- If ball hits wall: reverse vx or vy accordingly.
- If ball hits brick: mark brick dead, spawn hearts, reverse ball vy (maybe also vx). Also maybe increase score.
- If ball goes below canvas (miss), maybe reset ball (but demo mode continues).
- Hearts update: for each heart, move position (apply velocity). Also maybe apply slight gravity? Not needed.
- Render: clear canvas; draw background (maybe light color). Draw bricks (if alive). Draw paddle. Draw ball. Draw hearts.
- Ensure canvas size responsive: set canvas width/height attributes to 800 and 600; CSS width: 100%; height: auto; So on small screens it scales down.
- Ensure no scrollbars: body overflow hidden.
- The "Click or press a key to start" message: overlay text centered, maybe <div id="startMsg">Click or press a key to start</div>. When user clicks anywhere or presses any key, hide the message and start the game.
- Also need to ensure that after start the demo mode automatically plays (AI). So we don't need user to control paddle.
Potential issues: Keyboard events may be blocked until canvas is focused. We'll add event listener for click on canvas to focus, and keypress to start.
Implementation details:
HTML:
<div id="startMsg">Click or press a key to start</div>
<canvas id="game"></canvas>
CSS:
html,body {margin:0; padding:0; overflow:hidden; background:#111;}
#startMsg {position:absolute; top:50%; left:50%; transform:translate(-50%,-50%); color:#fff; font-size:24px; text-align:center; pointer-events:auto;}
canvas {display:block; width:800px; height:600px; background:#222; margin:0 auto; }
But need responsive: set canvas width: 100%; height: auto; but also set width/height attributes to 800 and 600 for drawing. However if we set width:100% and height:auto, the canvas's internal pixel size remains 800x600, but CSS scaling may blur. Better: we can set canvas width/height attributes to window.innerWidth and window.innerHeight? But requirement: look right in 1280x800 and adapt down to 375px. So we can set canvas CSS width: 100%; height: auto; and set canvas width/height to 800 and 600, then scale via CSS. That should be fine.
But to adapt to smaller screens, we may need to adjust canvas size via JS to match CSS size. Simpler: set canvas width/height to window.innerWidth and window.innerHeight, but then drawing coordinate system must be scaled. That's more complex.
Alternative: Keep canvas size fixed at 800x600, and set CSS to make it fill width while preserving aspect ratio. For 1280x800 viewport, the canvas width 800px will be 800/1280 = 0.625 of viewport width; that's okay. For 375px width, canvas will be 800px but CSS will shrink it to 375px width, causing pixelation but still visible. Acceptable.
Thus we set:
.canvas { width: 100%; height: auto; max-width: 800px; } maybe.
Let's set:
canvas { width: 100%; height: auto; }
But we also need to set canvas's actual drawing buffer size: we can set canvas.width = 800; canvas.height = 600; then CSS width: 100%; height: auto; This will scale drawing to fit screen width.
Now hearts: We'll draw them on canvas using path.
Heart shape path: "M0 16A8 8 0 0 1 8 0A8 8 0 0 1 16 16H0z". We'll scale based on size parameter.
In drawHearts function:
ctx.save();
ctx.translate(x, y);
ctx.scale(size, size);
ctx.beginPath();
ctx.moveTo(0,16);
ctx.quadic? Actually we need to draw the path. Let's define a function drawHeart(ctx, x, y, size) that draws the heart shape.
Simplify: Use the path data: "M0 16A8 8 0 0 1 8 0A8 8 0 0 1 16 16H0z". We'll set transform to move to x,y then scale.
Implementation:
function drawHeart(ctx, x, y, size) {
ctx.save();
ctx.translate(x, y);
ctx.scale(size, size);
ctx.beginPath();
ctx.moveTo(0,16);
ctx.quadic? Actually we need to use the path: "M0 16A8 8 0 0 1 8 0A8 8 0 0 1 16 16H0z". This is a combination of arcs and line. We'll just use ctx.arc for the two circles and lineTo.
Simplify: Use the path: "M0 16A8 8 0 0 1 8 0A8 8 0 0 1 16 16H0z". We can draw as:
ctx.arc(0,8,8, Math.PI, 0, true); // right side? Hmm.
Better to use a known heart path: "M0 16A8 8 0 0 1 8 0A8 8 0 0 1 16 16H0z". Let's break: start at (0,16). Draw a semi-circle to (8,0) using arc with radius 8, sweeping clockwise? Actually the arc from (0,16) to (8,0) with radius 8, center at (0,8). That's a 180-degree arc. Then another arc from (8,0) to (16,16) with center at (8,8). Then line back to (0,16). Let's implement:
ctx.moveTo(0,16);
ctx.arc(0,8,8, Math.PI, 0, true); // from angle PI to 0 (clockwise) => from left top to right bottom.
ctx.arc(8,8,8, -Math.PI/2, -Math.PI/2 + Math.PI); // Actually we need from (8,0) to (16,16). The center is (8,8). The angle from -PI/2 (down) to 0 (right) then to? Wait.
Let's search typical heart path: "M0 16A8 8 0 0 1 8 0A8 8 0 0 1 16 16H0z". This can be drawn as:
ctx.moveTo(0,16);
ctx.arc(0,8,8, Math.PI, 0, true); // from (0,16) to (8,0) (right side)
ctx.arc(8,8,8, -Math.PI/2, -Math.PI/2 + Math.PI); // from (8,0) to (16,16) (left side)?? Not sure.
Maybe easier: Use path "M0 16L0 0A8 8 0 0 1 8 0A8 8 0 0 1 16 16L0 16z". That's more straightforward: line down, then arc right, then arc left, then line up.
Let's adopt: "M0 16L0 0A8 8 0 0 1 8 0A8 8 0 0 1 16 16L0 16z". We'll implement that.
So drawHeart:
ctx.moveTo(0,16);
ctx.lineTo(0,0);
ctx.arc(0,8,8, Math.PI, 0, true); // from (0,0) to (8,0) (right half)
ctx.arc(8,8,8, -Math.PI/2, -Math.PI/2 + Math.PI); // from (8,0) to (16,16) (left side) maybe using angles -PI/2 to -PI/2+PI = -PI/2 to PI/2? Actually we need from (8,0) to (16,16) which is upper right quadrant. The center is (8,8). Starting at (8,0) which is angle -PI/2 (down). To go to (16,16) which is angle 0 (right). So we need to sweep clockwise? Let's just use ctx.arc(8,8,8, -Math.PI/2, 0, true); which goes from down to right (clockwise). Then line back to start.
Then closePath and fill.
Simplify: Use ctx.fillRect for paddle, circle for ball, and for hearts we can just use ctx.fillRect? But requirement is hearts shape, so we need heart shape.
Given time, maybe we can cheat: use <svg> heart elements positioned absolutely and animate via CSS. That may be easier than drawing on canvas.
Let's go with SVG hearts: create a <svg class="heart"> with viewBox and path, set its style to position absolute; then in JS we create many such elements, set left/top based on brick position, assign random transform: translate(dx, dy) and maybe rotate. Also set animation via CSS: keyframes that move outward and fade.
Implementation steps for hearts using SVG:
- Define CSS:
.heart {
position: absolute;
width: 20px; height: 20px;
opacity: 0;
animation: burst 1s forwards;
}
@keyframes burst {
0% { transform: translate(0,0) scale(0.5); opacity: 0; }
10% { opacity: 1; }
100% { transform: translate(var(--dx), var(--dy)) scale(1.5); opacity: 0; }
}
But we need to set custom properties for each heart: --dx, --dy, maybe --size.
Simplify: we can set inline style: transform: translate(x,y) rotate(angle); and animation that moves and fades.
But we need to create many hearts per brick; we can set each heart's initial position (left/top) to brick center, then assign a random direction vector (dx, dy) and a speed, and then in each frame update its position (move) and also apply CSS animation? That may be heavy.
Maybe easier: In each frame, we move hearts manually (update their left/top) and also apply CSS transition for fading. But we can just update their style directly each frame, no CSS animation needed.
Let's do: For each heart element, store its current position (x,y) and velocity (vx,vy). In update loop, for each heart: x += vx; y += vy; set heart.style.left = (x) + "px"; heart.style.top = (y) + "px"; Also maybe scale: heart.style.transform = "scale(" + (1 + distance/10) + ")"; but we can skip scaling.
Hearts should be removed when they go off-screen or alpha <=0. To simulate fade, we can decrease opacity based on age.
Simplify: hearts have a life property that decreases each frame; set style.opacity = life; and remove when life <=0.
Implementation:
function spawnHearts(brick) {
const cx = brick.x + brick.w/2;
const cy = brick.y + brick.h/2;
for (let i=0;i<30;i++) {
const angle = Math.random()*Math.PI*2;
const speed = 2 + Math.random()*3;
const vx = Math.cos(angle)*speed;
const vy = Math.sin(angle)*speed;
const size = 5 + Math.random()*10;
const heart = document.createElementNS('http://www.w3.org/2000/svg','svg');
heart.classList.add('heart');
heart.setAttribute('viewBox','0 0 32 29.6');
// path data for heart: "M0 29.6C0 15.5 16 0 32 0C32 0 16 15.5 0 29.6z"
heart.innerHTML = '<path d="M0 29.6C0 15.5 16 0 32 0C32 0 16 15.5 0 29.6z" fill="#ff69b4"/>';
heart.style.position = 'absolute';
heart.style.left = cx + 'px';
heart.style.top = cy + 'px';
heart.style.width = size + 'px';
heart.style.height = size + 'px';
heart.style.opacity = 1;
heart.style.transition = 'opacity 0.5s linear';
// add to DOM
document.body.appendChild(heart);
hearts.push({elem: heart, x: cx, y: cy, vx: vx, vy: vy, size: size, life: 1});
}
}
But we need to update positions each frame: for each heart, update x+=vx*some factor (maybe multiply by 0.5 per frame). Also update left/top accordingly. Also reduce life.
Implementation details: Since hearts are SVG elements, setting left/top may affect their viewBox scaling; but we set width/height to size, so they scale accordingly.
Better: Instead of using SVG, we can use <div> with background-image: url('data:image/svg+xml;utf8,<svg ...>'); but that's more complex. Using <svg> element with class 'heart' and set its style left/top works.
But we need to ensure hearts are positioned relative to viewport, not to the canvas. Since canvas is centered, we need to compute absolute positions based on canvas location.
Simplify: Place canvas at (0,0) for simplicity (no margin). Then hearts positioned absolute relative to canvas's top-left. So we need to get canvas bounding rect.
Let's compute canvasRect = canvas.getBoundingClientRect(); Then hearts left = cx - canvasRect.left + vx etc.
Simplify further: set canvas style position: relative; then hearts positioned absolute inside canvas? Or set canvas as position: relative; and hearts as position: absolute; then we can set hearts.style.left = (cx) + 'px'; but need to offset by canvasRect.left.
Simplify: set canvas style position: relative; then hearts are positioned relative to canvas (top-left 0). So we can set hearts.style.left = (cx) + 'px'; hearts.style.top = (cy) + 'px'; That works if canvas is positioned with no offset.
Let's set canvas {position: relative; display:block; margin:auto;}. Then hearts absolute inside.
Now we need to ensure hearts are visible on first paint (i.e., after start). The start message will hide after click, then hearts will be drawn.
Now game loop: we need requestAnimationFrame.
Implementation steps in JS:
- const canvas = document.getElementById('game');
- const ctx = canvas.getContext('2d');
- const startMsg = document.getElementById('startMsg');
- function startGame() {
startMsg.style.display = 'none';
// start loop
requestAnimationFrame(gameLoop);
}
- Add event listeners: canvas.addEventListener('click', () => { if (startMsg.style.display !== 'none') startGame(); });
- document.addEventListener('keydown', e => { if (startMsg.style.display !== 'none') startGame(); });
- In gameLoop:
update();
render();
requestAnimationFrame(gameLoop);
- update():
// move ball
ball.x += ball.vx;
ball.y += ball.vy;
// wall collisions
if (ball.x + ball.r > canvas.width) { ball.x = canvas.width - ball.r; ball.vx = -ball.vx; }
if (ball.x - ball.r < 0) { ball.x = 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 = -ball.vy;
// optional: add some angle based on where hit
}
// brick collisions
for (let i=0;i<bricks.length;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) {
// hit
b.alive = false;
spawnHearts(b);
ball.vy = -ball.vy; // bounce
// maybe adjust vx based on where hit
break;
}
}
// move paddle (AI)
if (ball.vx > 0) {
paddle.x += 4; // speed
} else if (ball.vx < 0) {
paddle.x -= 4;
}
// clamp paddle
paddle.x = Math.max(0, Math.min(canvas.width - paddle.w, paddle.x));
// remove off-screen hearts
for (let i=hearts.length-1; i>=0; i--) {
const h = hearts[i];
h.x += h.vx;
h.y += h.vy;
h.life -= 0.02;
h.elem.style.opacity = h.life;
if (h.life <= 0) {
h.elem.remove();
hearts.splice(i,1);
}
}
- render():
ctx.clearRect(0,0,canvas.width,canvas.height);
// draw background
ctx.fillStyle = '#111';
ctx.fillRect(0,0,canvas.width,canvas.height);
// draw bricks
ctx.fillStyle = '#ff69b4';
for (let b of bricks) {
if (b.alive) {
ctx.fillRect(b.x, b.y, b.w, b.h);
}
}
// draw paddle (hamburger)
ctx.fillStyle = '#fff';
const ph = 10; // height of paddle
const pw = 80;
const px = paddle.x;
const py = canvas.height - 20; // y position
// top bar
ctx.fillRect(px + (pw-40)/2, py - 12, 40, 6);
// middle bar
ctx.fillRect(px + (pw-40)/2, py - 4, 40, 6);
// bottom bar (paddle)
ctx.fillRect(px, py, pw, ph);
// draw ball
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI*2);
ctx.fillStyle = '#fff';
ctx.fill();
// draw hearts (SVG elements) - we just need to ensure they are positioned; no drawing needed on canvas.
// request next frame
requestAnimationFrame(gameLoop);
Now we need to define constants:
- ball: radius 8; x = canvas.width/2; y = canvas.height - 30; vx = 3; vy = -3;
- bricks: create rows. For simplicity, 5 rows, each row 6 bricks. Brick size 50x20. Spacing 10px. Starting x = 20, y = 20.
- paddle: w = 80; h = 10; y = canvas.height - 20; x = canvas.width/2 - 40;
- hearts: array.
Now ensure that the page loads with the start message visible. After click/keypress, startGame hides message and starts demo mode (AI paddle). The ball will start moving automatically.
Potential issues: The canvas may not be focused; clicking on it will focus and also start. Ensure that after start, the message disappears.
Also ensure that the page looks right in 1280x800 and adapt down to 375px. Since canvas CSS width:100%; height:auto; it will scale.
Now we need to embed everything in a single fenced code block with type html.
Let's craft final HTML:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Breakout Hamburger</title>
<style>
html,body{margin:0;padding:0;overflow:hidden;background:#111;color:#fff;font-family:sans-serif;}
#startMsg{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);font-size:24px;text-align:center;pointer-events:auto;z-index:10;}
canvas{position:relative;display:block;margin:auto;width:100%;height:auto;background:#222;}
</style>
</head>
<body>
<div id="startMsg">Click or press a key to start</div>
<canvas id="game"></canvas>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const startMsg = document.getElementById('startMsg');
// Resize canvas to match CSS size
function resizeCanvas() {
const rect = canvas.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
}
window.addEventListener('resize', resizeCanvas);
resizeCanvas();
const ball = {r:8, x:canvas.width/2, y:canvas.height-30, vx:3, vy:-3};
const paddle = {w:80, h:10, x:canvas.width/2-40, y:canvas.height-20};
const bricks = [];
const hearts = [];
// create bricks
for (let row=0; row<5; row++) {
for (let col=0; col<6; col++) {
const x = 20 + col*(50+10);
const y = 20 + row*(20+10);
bricks.push({x, y, w:50, h:20, alive:true});
}
}
// start game
function startGame(){
startMsg.style.display='none';
requestAnimationFrame(gameLoop);
}
canvas.addEventListener('click',()=>{ if(startMsg.style.display!=='none') startGame(); });
document.addEventListener('keydown',e=>{ if(startMsg.style.display!=='none') startGame(); });
function spawnHearts(brick){
const cx = brick.x + brick.w/2;
const cy = brick.y + brick.h/2;
for(let i=0;i<30;i++){
const angle = Math.random()*Math.PI*2;
const speed = 2+Math.random()*3;
const vx = Math.cos(angle)*speed;
const vy = Math.sin(angle)*speed;
const size = 5+Math.random()*10;
const svg = document.createElementNS('http://www.w3.org/2000/svg','svg');
svg.setAttribute('viewBox','0 0 32 29.6');
svg.innerHTML = '<path d="M0 29.6C0 15.5 16 0 32 0C32 0 16 15.5 0 29.6z" fill="#ff69b4"/>';
svg.style.position='absolute';
svg.style.left = (cx) + 'px';
svg.style.top = (cy) + 'px';
svg.style.width = size + 'px';
svg.style.height = size + 'px';
svg.style.opacity = '1';
document.body.appendChild(svg);
hearts.push({elem:svg, x:cx, y:cy, vx, vy, size, life:1});
}
}
function gameLoop(){
// update ball
ball.x += ball.vx;
ball.y += ball.vy;
// wall collisions
if (ball.x+ball.r > canvas.width){ ball.x = canvas.width-ball.r; ball.vx=-ball.vx; }
if (ball.x-ball.r < 0){ ball.x = 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 = -ball.vy;
}
// brick collisions
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){
b.alive = false;
spawnHearts(b);
ball.vy = -ball.vy;
}
}
// AI paddle movement
const step = 4;
if (ball.vx > 0) paddle.x += step;
else if (ball.vx < 0) paddle.x -= step;
paddle.x = Math.max(0, Math.min(canvas.width-paddle.w, paddle.x));
// update hearts
for(let i=hearts.length-1;i>=0;i--){
const h = hearts[i];
h.x += h.vx;
h.y += h.vy;
h.life -= 0.02;
h.elem.style.opacity = h.life;
if (h.life <= 0){
h.elem.remove();
hearts.splice(i,1);
}
}
// render
ctx.clearRect(0,0,canvas.width,canvas.height);
// background
ctx.fillStyle = '#111';
ctx.fillRect(0,0,canvas.width,canvas.height);
// bricks
ctx.fillStyle = '#ff69b4';
for(const b of bricks){
if(b.alive){
ctx.fillRect(b.x,b.y,b.w,b.h);
}
}
// paddle (hamburger)
ctx.fillStyle = '#fff';
const ph = 10, pw = 80, py = paddle.y;
const hx = paddle.x + (pw-40)/2;
ctx.fillRect(hx, py-12, 40, 6); // top bar
ctx.fillRect(hx, py-4, 40, 6); // middle bar
ctx.fillRect(paddle.x, py, pw, ph); // bottom bar (the paddle)
// ball
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI*2);
ctx.fillStyle = '#fff';
ctx.fill();
requestAnimationFrame(gameLoop);
}
</script>
</body>
</html>
```
Check constraints:
- All inline CSS and JS. No external files.
- Canvas size responsive: width:100%; height:auto; also we resize on window resize to match CSS size (so drawing coordinates match). That ensures correct scaling.
- The start message appears initially, then after click or keypress, game starts. Demo mode AI moves paddle automatically.
- Hearts appear as SVG elements positioned absolutely; they fade out.
- No scrollbars: body overflow hidden; canvas is relative and fills width.
- Should run without errors.
Potential issues: The resizeCanvas may be called before canvas is sized; but we set canvas width/height to CSS size after layout. Since we set canvas style width:100%; height:auto; the actual rendered size may be based on parent container; but we call resizeCanvas after page load (window load). Might need to call on resize and also after initial load. We'll add window.addEventListener('load', resizeCanvas); but we already have resize on window resize; initial call may be fine.
Also need to ensure that the hearts are visible on first paint: after start, hearts are spawned when bricks are hit; but initially there are no hits, so hearts not visible at start. However requirement: "Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction." So we need something visible at first paint, i.e., before user interaction. The start message is visible initially, which is meaningful. So that's okay.
But maybe they want something like the game already in action (ball moving). However the start message is displayed for 3 seconds automatically? The requirement says screenshot taken about 3 seconds after load, without interaction. So we need something visible at that time. The start message is visible initially; after 3 seconds maybe the game auto starts? But we need to start after click or keypress. However the screenshot is taken automatically after 3 seconds, but if the user hasn't clicked, the game hasn't started, so maybe nothing interesting is visible. To satisfy, we could auto-start after 3 seconds without requiring click, but the rule says "Click or press a key to start". So we need a start message that remains until user interacts. However screenshot is taken automatically after 3 seconds; if user hasn't clicked, the game hasn't started, so maybe they consider the start message as the visible content. That's acceptable.
But maybe they want the game to be in demo mode automatically after 3 seconds, without user interaction. However the rule says "Games must start in a self-playing demo mode (a simple AI plays) behind a 'Click or press a key to start' message". So we need the message, then after click/keypress, the demo mode runs. The screenshot is taken 3 seconds after load; if the user hasn't clicked, the game hasn't started, but maybe the screenshot will capture the start message. That's okay.
But maybe we can make the demo start automatically after 3 seconds, but still show the message until user interacts? The rule says "Click or press a key to start". So we must wait for user input. So screenshot will capture the message, which is "meaningful". That satisfies "something meaningful must be visible on first paint". So okay.
Now ensure that there are no console errors. Use const, let, etc. Should be fine.
Potential minor bug: The paddle drawing uses fillRect for bottom bar; but we also draw top and middle bars as part of hamburger. That's okay.
Now ensure that the canvas CSS width:100% and height:auto; and we also set canvas width/height attributes to match CSS size via resizeCanvas (which uses getBoundingClientRect). That ensures drawing resolution matches displayed size.
Now we need to ensure that the page looks right in 1280x800 and adapts down to 375px. Since canvas width is set to CSS width, which is 100% of container, and container width is the viewport width, it should adapt.
Now final answer: Provide exactly one fenced code block with html. No extra text.