Hamburger breakoutOne-line
MiniMax M3.1 Flash
by MiniMaxReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 31.4 s
- Cost
- $0.0049
- Output tokens
- 8,127
- Spent thinking
- 829 tokens
- Speed
- 264.2 tok/s
- Console errors
- 0
- First token after
- 620 ms
- First visible text after
- 6.8 s
- Prompt tokens
- 528
- Code size
- 21.2 KB
- Finish reason
- stop
- Served by
- DigitalOcean
- Model id
minimax/minimax-m3.1-flash- Reasoning sent
{"effort":"medium"}- Max output
- 64,000 tokens
- Generated
- Nov 11, 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 · 628 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Heartbreaker Breakout</title>
<style>
:root {
--cream: #fff8df;
--pink: #ff4f91;
--hot: #ff6b6b;
--yellow: #ffd166;
--purple: #241033;
}
* { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; }
body {
background:
radial-gradient(circle at 15% 10%, rgba(255,79,145,.25), transparent 35%),
radial-gradient(circle at 85% 90%, rgba(255,209,102,.14), transparent 35%),
#12091e;
color: var(--cream);
font-family: ui-rounded, "Arial Rounded MT Bold", system-ui, sans-serif;
display: flex;
justify-content: center;
overflow-x: hidden;
}
.shell { width: min(1100px, 100%); padding: 18px 18px 24px; }
header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 14px;
}
.brand { display: flex; align-items: center; gap: 13px; min-width: 0; }
.logo {
width: 54px; height: 54px; flex: 0 0 auto;
display: grid; place-items: center;
background: linear-gradient(145deg, #ff6aa8, #df2675);
border-radius: 17px;
font-size: 29px;
transform: rotate(-6deg);
box-shadow: 0 8px 24px rgba(255,79,145,.35), inset 0 2px 0 rgba(255,255,255,.3);
}
h1 {
margin: 0;
font-size: clamp(19px, 3.3vw, 33px);
letter-spacing: .055em;
line-height: 1;
text-shadow: 0 3px 0 #6b1747;
}
.subtitle { margin-top: 6px; color: #c7a8cc; font-size: 12px; letter-spacing: .12em; text-transform: uppercase; }
.stats { display: flex; gap: 8px; }
.stat {
min-width: 78px;
padding: 8px 12px;
text-align: center;
border: 1px solid rgba(255,255,255,.12);
border-radius: 13px;
background: rgba(255,255,255,.065);
backdrop-filter: blur(8px);
}
.stat small { display: block; color: #bfa9c8; font-size: 9px; letter-spacing: .14em; text-transform: uppercase; }
.stat strong { font-size: 18px; color: var(--yellow); }
.stage {
position: relative;
border: 2px solid rgba(255,255,255,.14);
border-radius: 23px;
overflow: hidden;
background: #1d0d2d;
box-shadow: 0 24px 70px rgba(0,0,0,.5), 0 0 0 6px rgba(255,255,255,.025);
}
canvas { display: block; width: 100%; height: min(68vh, 650px); min-height: 470px; touch-action: none; }
.overlay {
position: absolute;
inset: 0;
display: grid;
place-items: center;
padding: 20px;
background: radial-gradient(circle, rgba(29,13,45,.3), rgba(10,4,18,.72));
transition: opacity .35s, visibility .35s;
z-index: 3;
}
.overlay.hidden { opacity: 0; visibility: hidden; pointer-events: none; }
.card {
width: min(450px, 94%);
text-align: center;
padding: 30px 25px;
border-radius: 26px;
border: 1px solid rgba(255,255,255,.18);
background: linear-gradient(150deg, rgba(67,28,85,.94), rgba(31,12,48,.94));
box-shadow: 0 18px 60px rgba(0,0,0,.45);
backdrop-filter: blur(12px);
}
.card .heart { font-size: 44px; filter: drop-shadow(0 6px 10px rgba(255,79,145,.5)); }
.card h2 { margin: 8px 0 10px; font-size: clamp(22px, 4vw, 32px); }
.card p { color: #d9c4dd; line-height: 1.5; margin: 8px 0 20px; font-size: 14px; }
button {
border: 0;
border-radius: 999px;
padding: 14px 24px;
color: #421025;
background: linear-gradient(#ffe38b, #ffb84f);
font: inherit;
font-weight: 900;
letter-spacing: .06em;
cursor: pointer;
box-shadow: 0 6px 0 #a9592b, 0 10px 24px rgba(255,184,79,.25);
transition: transform .15s, box-shadow .15s;
}
button:hover { transform: translateY(-2px); box-shadow: 0 8px 0 #a9592b, 0 12px 28px rgba(255,184,79,.3); }
button:active { transform: translateY(3px); box-shadow: 0 3px 0 #a9592b; }
.hint { margin-top: 16px; color: #b99abd; font-size: 11px; letter-spacing: .08em; }
.footer {
display: flex; justify-content: space-between; gap: 15px;
margin-top: 13px; color: #aa91b4; font-size: 12px;
}
.demo-dot { color: #ffd166; }
@media (max-width: 700px) {
.shell { padding: 12px 10px 18px; }
header { align-items: flex-start; flex-wrap: wrap; }
.stats { width: 100%; }
.stat { flex: 1; min-width: 0; padding: 7px 4px; }
.logo { width: 46px; height: 46px; font-size: 25px; }
canvas { height: 65vh; min-height: 430px; }
.footer { flex-direction: column; gap: 5px; }
}
</style>
</head>
<body>
<main class="shell">
<header>
<div class="brand">
<div class="logo">🍔</div>
<div>
<h1>HEARTBREAKER</h1>
<div class="subtitle">Breakout with a very delicious paddle</div>
</div>
</div>
<div class="stats">
<div class="stat"><small>Score</small><strong id="score">0</strong></div>
<div class="stat"><small>Hearts</small><strong id="lives">♥ 3</strong></div>
<div class="stat"><small>Combo</small><strong id="combo">x1</strong></div>
<div class="stat"><small>Level</small><strong id="level">1</strong></div>
</div>
</header>
<section class="stage">
<canvas id="game" aria-label="Heartbreaker Breakout game"></canvas>
<div class="overlay" id="overlay">
<div class="card">
<div class="heart">💖</div>
<h2>CLICK OR PRESS A KEY TO START</h2>
<p>The burger chef bot is already playing in the background.<br>Take over with your mouse, arrow keys, or touch!</p>
<button id="startBtn">LET'S BREAK SOME HEARTS</button>
<div class="hint">Move the burger • Catch the sundae ball • Smash every brick</div>
</div>
</div>
</section>
<div class="footer">
<span><span class="demo-dot">●</span> <span id="mode">DEMO MODE</span> — the chef bot is warming up the grill</span>
<span>Every brick explodes into a burst of tiny hearts.</span>
</div>
</main>
<script>
(() => {
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const overlay = document.getElementById('overlay');
const scoreEl = document.getElementById('score');
const livesEl = document.getElementById('lives');
const comboEl = document.getElementById('combo');
const levelEl = document.getElementById('level');
const modeEl = document.getElementById('mode');
let W = 1000, H = 600, dpr = 1;
let score = 0, lives = 3, level = 1, combo = 1, comboTimer = 0;
let manual = false, started = false, gameOver = false;
let bricks = [], particles = [], floaters = [], stars = [];
let shake = 0, flash = 0;
const paddle = { x: 440, y: 540, w: 126, h: 27, targetX: 440 };
const ball = { x: 500, y: 500, vx: 4.5, vy: -6, r: 11, speed: 7.5 };
const palettes = [
['#ff5d91', '#ff8f70', '#ffbf69', '#f06bd8', '#a66cff', '#ff7597'],
['#ff477e', '#ff6e58', '#ffad42', '#d75eff', '#7a6cff', '#ff8d70'],
['#ef3f7f', '#ff7043', '#f8c945', '#b95cff', '#4e7cff', '#ff5fa2']
];
function resize() {
const r = canvas.getBoundingClientRect();
dpr = Math.min(window.devicePixelRatio || 1, 2);
W = r.width; H = r.height;
canvas.width = Math.round(W * dpr);
canvas.height = Math.round(H * dpr);
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
paddle.w = Math.min(126, W * .25);
paddle.h = 27;
paddle.y = H - 52;
if (!started) {
paddle.x = paddle.targetX = W / 2 - paddle.w / 2;
ball.x = W / 2; ball.y = paddle.y - 45;
}
buildBricks();
stars = Array.from({length: 65}, () => ({
x: Math.random() * W, y: Math.random() * H,
r: Math.random() * 1.7 + .3, a: Math.random() * .55 + .15,
tw: Math.random() * Math.PI * 2
}));
}
function buildBricks() {
const cols = W < 600 ? 7 : 9;
const rows = 6;
const gap = Math.max(5, W * .007);
const top = 56;
const side = Math.max(24, W * .045);
const bw = (W - side * 2 - gap * (cols - 1)) / cols;
const bh = Math.max(20, Math.min(34, H * .052));
const palette = palettes[(level - 1) % palettes.length];
bricks = [];
for (let row = 0; row < rows; row++) {
for (let col = 0; col < cols; col++) {
bricks.push({
x: side + col * (bw + gap),
y: top + row * (bh + gap),
w: bw, h: bh,
color: palette[(row + col) % palette.length],
alive: true,
shine: Math.random() * Math.PI * 2
});
}
}
}
function resetBall() {
ball.x = paddle.x + paddle.w / 2;
ball.y = paddle.y - ball.r - 5;
const angle = (Math.random() * .7 - .35);
ball.speed = 7.3 + Math.min(level, 8) * .28;
ball.vx = Math.sin(angle) * ball.speed;
ball.vy = -Math.cos(angle) * ball.speed;
combo = 1;
comboTimer = 0;
}
function resetGame() {
score = 0; lives = 3; level = 1; gameOver = false;
particles = []; floaters = []; shake = 0; flash = 0;
buildBricks();
paddle.x = paddle.targetX = W / 2 - paddle.w / 2;
resetBall();
updateHud();
}
function updateHud() {
scoreEl.textContent = score.toLocaleString();
livesEl.textContent = '♥ ' + lives;
comboEl.textContent = 'x' + combo;
levelEl.textContent = level;
}
function startManual() {
if (gameOver) resetGame();
manual = true;
started = true;
overlay.classList.add('hidden');
modeEl.textContent = 'PLAYER MODE';
document.querySelector('.footer').firstElementChild.innerHTML =
'<span style="color:#ff5d91">●</span> PLAYER MODE — move the burger to catch the ball';
}
document.getElementById('startBtn').addEventListener('click', startManual);
overlay.addEventListener('click', startManual);
window.addEventListener('keydown', e => {
if (['ArrowLeft','ArrowRight','Space'].includes(e.code)) e.preventDefault();
startManual();
});
canvas.addEventListener('pointermove', e => {
const r = canvas.getBoundingClientRect();
manual = true;
paddle.targetX = e.clientX - r.left - paddle.w / 2;
});
canvas.addEventListener('pointerdown', e => {
startManual();
const r = canvas.getBoundingClientRect();
paddle.targetX = e.clientX - r.left - paddle.w / 2;
});
window.addEventListener('resize', resize);
function drawHeart(x, y, size, color, alpha = 1, rotation = 0) {
ctx.save();
ctx.translate(x, y);
ctx.rotate(rotation);
ctx.globalAlpha = alpha;
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(0, size * .35);
ctx.bezierCurveTo(-size * .58, -size * .04, -size * .42, -size * .53, 0, -size * .27);
ctx.bezierCurveTo(size * .42, -size * .53, size * .58, -size * .04, 0, size * .35);
ctx.closePath();
ctx.fill();
ctx.restore();
}
function explode(brick, hitX, hitY) {
const count = 10 + Math.floor(Math.random() * 7);
const colors = ['#ff3f83', '#ff6fae', '#ff91c3', '#ffd0e2', '#ff4d68'];
for (let i = 0; i < count; i++) {
const a = Math.random() * Math.PI * 2;
const speed = 1.4 + Math.random() * 4.2;
particles.push({
x: hitX, y: hitY,
vx: Math.cos(a) * speed,
vy: Math.sin(a) * speed - 1.4,
size: 4 + Math.random() * 7,
color: colors[Math.floor(Math.random() * colors.length)],
rot: Math.random() * Math.PI,
vr: (Math.random() - .5) * .25,
life: 1,
decay: .008 + Math.random() * .012
});
}
for (let i = 0; i < 7; i++) {
particles.push({
x: hitX, y: hitY,
vx: (Math.random() - .5) * 5,
vy: (Math.random() - .5) * 5,
size: 2 + Math.random() * 4,
color: '#ffe08a',
rot: 0, vr: 0, life: 1, decay: .025 + Math.random() * .025,
spark: true
});
}
floaters.push({x: hitX, y: hitY - 8, text: '+' + (100 * combo), life: 1, color: '#ffe08a'});
shake = Math.max(shake, 5);
flash = .16;
}
function breakBrick(brick) {
brick.alive = false;
combo = Math.min(8, combo + 1);
comboTimer = 2.2;
score += 100 * combo;
const cx = brick.x + brick.w / 2, cy = brick.y + brick.h / 2;
explode(brick, cx, cy);
updateHud();
if (bricks.every(b => !b.alive)) {
score += 1000;
flash = .6;
floaters.push({x: W/2, y: H/2, text: 'LEVEL CLEARED! +1000', life: 2, color: '#ffdf8a', big: true});
level++;
buildBricks();
paddle.w = Math.min(126, W * .25);
resetBall();
updateHud();
}
}
function circleRectCollision(b, r) {
const closestX = Math.max(r.x, Math.min(b.x, b.x + b.w));
const closestY = Math.max(r.y, Math.min(b.y, b.y + b.h));
const dx = b.x + b.w / 2 - closestX;
const dy = b.y + b.h / 2 - closestY;
return dx * dx + dy * dy < r.r * r.r;
}
function update(dt) {
if (!gameOver) {
// Demo chef bot gently tracks the ball.
if (!manual) {
const predicted = ball.vx > 0 ? ball.x + ball.vx * 14 : ball.x + ball.vx * 14;
paddle.targetX = predicted - paddle.w / 2 + Math.sin(performance.now() / 900) * 8;
}
const maxMove = 9;
const diff = paddle.targetX - paddle.x;
paddle.x += Math.max(-maxMove, Math.min(maxMove, diff * .15));
ball.x += ball.vx;
ball.y += ball.vy;
if (ball.x - ball.r < 0) { ball.x = ball.r; ball.vx = Math.abs(ball.vx); }
if (ball.x + ball.r > W) { ball.x = W - ball.r; ball.vx = -Math.abs(ball.vx); }
if (ball.y - ball.r < 0) { ball.y = ball.r; ball.vy = Math.abs(ball.vy); }
if (ball.vy > 0 && ball.y + ball.r >= paddle.y &&
ball.y - ball.r <= paddle.y + paddle.h &&
ball.x >= paddle.x - 8 && ball.x <= paddle.x + paddle.w + 8) {
const hit = (ball.x - (paddle.x + paddle.w / 2)) / (paddle.w / 2);
const speed = Math.min(11, Math.hypot(ball.vx, ball.vy) + .08);
const angle = hit * 1.12;
ball.vx = Math.sin(angle) * speed;
ball.vy = -Math.cos(angle) * speed;
ball.y = paddle.y - ball.r - 1;
if (Math.abs(ball.vx) < 1.2) ball.vx = 1.2 * Math.sign(hit || 1);
}
for (const brick of bricks) {
if (!brick.alive || !circleRectCollision(brick, ball)) continue;
const closestX = Math.max(brick.x, Math.min(ball.x, brick.x + brick.w));
const closestY = Math.max(brick.y, Math.min(ball.y, brick.y + brick.h));
const dx = ball.x - closestX, dy = ball.y - closestY;
if (Math.abs(dx) > Math.abs(dy)) ball.vx *= -1;
else ball.vy *= -1;
breakBrick(brick);
break;
}
if (ball.y - ball.r > H + 20) {
lives--;
shake = 10;
updateHud();
if (lives <= 0) {
gameOver = true;
manual = false;
overlay.classList.remove('hidden');
overlay.querySelector('.card').innerHTML =
'<div class="heart">🍔💔</div><h2>OUT OF HEARTS!</h2><p>Your score: <strong>' +
score.toLocaleString() + '</strong><br>The bricks won this round.</p>' +
'<button id="startBtn">ORDER ANOTHER BURGER</button>' +
'<div class="hint">Press any key or click to play again</div>';
document.getElementById('startBtn').addEventListener('click', startManual);
} else {
resetBall();
}
}
if (comboTimer > 0) {
comboTimer -= dt;
if (comboTimer <= 0) combo = 1;
}
}
for (const p of particles) {
p.x += p.vx; p.y += p.vy;
p.vy += p.spark ? .08 : .12;
p.vx *= .992;
p.rot += p.vr;
p.life -= p.decay;
}
particles = particles.filter(p => p.life > 0).slice(-700);
for (const f of floaters) { f.y -= dt * 34; f.life -= dt * .75; }
floaters = floaters.filter(f => f.life > 0);
shake = Math.max(0, shake - dt * 24);
flash = Math.max(0, flash - dt);
}
function roundedRect(x, y, w, h, r) {
ctx.beginPath();
ctx.roundRect(x, y, w, h, r);
}
function drawBurger(x, y, w, h) {
ctx.save();
ctx.translate(x + w / 2, y);
const bunW = w * .92, bunH = h * .38;
// shadow
ctx.fillStyle = 'rgba(0,0,0,.28)';
ctx.beginPath(); ctx.ellipse(0, h + 4, w * .42, 5, 0, 0, Math.PI * 2); ctx.fill();
// bottom bun
ctx.fillStyle = '#e99137';
roundedRect(-bunW/2, h*.42, bunW, h*.32, 7); ctx.fill();
ctx.fillStyle = '#ffc45f';
roundedRect(-bunW/2, h*.38, bunW, h*.17, 6); ctx.fill();
// patty
ctx.fillStyle = '#6d2a1f';
roundedRect(-w/2, h*.28, w, h*.25, 7); ctx.fill();
// cheese
ctx.fillStyle = '#ffd447';
ctx.beginPath();
ctx.moveTo(-w*.48, h*.29); ctx.lineTo(w*.48, h*.29);
ctx.lineTo(w*.40, h*.43); ctx.lineTo(w*.25, h*.31);
ctx.lineTo(w*.07, h*.45); ctx.lineTo(-w*.08, h*.32);
ctx.lineTo(-w*.30, h*.45); ctx.closePath(); ctx.fill();
// lettuce
ctx.fillStyle = '#5fc45b';
ctx.beginPath();
ctx.moveTo(-w*.49, h*.27);
for (let i = 0; i <= 8; i++) {
const px = -w*.49 + i * w*.98/8;
ctx.quadraticCurveTo(px + w*.05, h*.18, px + w*.12, h*.28);
}
ctx.lineTo(w*.49, h*.34); ctx.lineTo(-w*.49, h*.34); ctx.closePath(); ctx.fill();
// top bun
const grad = ctx.createLinearGradient(0, -h*.4, 0, h*.3);
grad.addColorStop(0, '#ffd879'); grad.addColorStop(1, '#e99a3b');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.ellipse(0, h*.16, bunW/2, bunH, 0, Math.PI, 0);
ctx.closePath(); ctx.fill();
// sesame seeds
ctx.fillStyle = '#fff1bd';
const seeds = [[-.24,-.15],[-.08,-.23],[.12,-.24],[.29,-.12],[0,-.08],[.19,.02],[-.17,.01]];
seeds.forEach(([sx, sy]) => {
ctx.save(); ctx.translate(sx*w, sy*h); ctx.rotate(sx*4);
ctx.beginPath(); ctx.ellipse(0,0,2.1,3.4,0,0,Math.PI*2); ctx.fill(); ctx.restore();
});
ctx.restore();
}
function draw() {
ctx.clearRect(0, 0, W, H);
const bg = ctx.createLinearGradient(0, 0, W, H);
bg.addColorStop(0, '#28113d'); bg.addColorStop(.55, '#170b29'); bg.addColorStop(1, '#341039');
ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H);
// soft colored corner glows
const glow = ctx.createRadialGradient(W*.15, 0, 0, W*.15, 0, W*.45);
glow.addColorStop(0, 'rgba(255,79,145,.13)'); glow.addColorStop(1, 'rgba(255,79,145,0)');
ctx.fillStyle = glow; ctx.fillRect(0, 0, W, H);
// grid
ctx.strokeStyle = 'rgba(255,255,255,.035)';
ctx.lineWidth = 1;
const size = 42;
const ox = (performance.now() * .006) % size;
for (let x = -size + ox; x < W; x += size) {
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, H); ctx.stroke();
}
for (let y = 0; y < H; y += size) {
ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(W, y); ctx.stroke();
}
ctx.fillStyle = '#fff';
stars.forEach(s => {
ctx.globalAlpha = s.a * (.65 + Math.sin(performance.now()/700 + s.tw) * .35);
ctx.beginPath(); ctx.arc(s.x, s.y, s.r, 0, Math.PI*2); ctx.fill();
});
ctx.globalAlpha = 1;
ctx.save();
if (shake > 0) ctx.translate((Math.random()-.5)*shake, (Math.random()-.5)*shake);
// bricks
for (const b of bricks) {
if (!b.alive) continue;
ctx.save();
ctx.shadowColor = b.color; ctx.shadowBlur = 10;
const g = ctx.createLinearGradient(b.x, b.y, b.x, b.y+b.h);
g.addColorStop(0, b.color); g.addColorStop(1, b.color + 'aa');
ctx.fillStyle = g;
roundedRect(b.x, b.y, b.w, b.h, 6); ctx.fill();
ctx.shadowBlur = 0;
ctx.fillStyle = 'rgba(255,255,255,.23)';
roundedRect(b.x+4, b.y+3, b.w-8, 3, 2); ctx.fill();
ctx.fillStyle = 'rgba(255,255,255,.7)';
ctx.font = 'bold 12px system-ui';
ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
if (b.w > 48) ctx.fillText('♥', b.x+b.w/2, b.y+b.h/2+1);
ctx.restore();
}
// ball trail
for (let i = 4; i >= 1; i--) {
ctx.globalAlpha = .07 * (5-i);
ctx.fillStyle = '#fff2c9';
ctx.beginPath();
ctx.arc(ball.x - ball.vx*i*1.7, ball.y - ball.vy*i*1.7, ball.r*(1-i*.11), 0, Math.PI*2);
ctx.fill();
}
ctx.globalAlpha = 1;
// sundae ball
ctx.save();
ctx.shadowColor = '#ffdf84'; ctx.shadowBlur = 22;
const bg2 = ctx.createRadialGradient(ball.x-3, ball.y-4, 1, ball.x, ball.y, ball.r);
bg2.addColorStop(0, '#fffef0'); bg2.addColorStop(.5, '#ffe6a4'); bg2.addColorStop(1, '#f49a5a');
ctx.fillStyle = bg2;
ctx.beginPath(); ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI*2); ctx.fill();
ctx.shadowBlur = 0;
ctx.fillStyle = '#e85d7b';
ctx.beginPath(); ctx.arc(ball.x-3, ball.y-3, 3, 0, Math.PI*2); ctx.fill();
ctx.restore();
// particles: tiny hearts
for (const p of particles) {
if (p.spark) {
ctx.globalAlpha = p.life;
ctx.fillStyle = p.color;
ctx.fillRect(p.x-p.size/2, p.y-p.size/2, p.size, p.size);
} else {
drawHeart(p.x, p.y, p.size, p.color, p.life, p.rot);
}
}
ctx.globalAlpha = 1;
// paddle
drawBurger(paddle.x, paddle.y, paddle.w, paddle.h);
// floating scores
ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
for (const f of floaters) {
ctx.globalAlpha = Math.min(1, f.life * 2);
ctx.fillStyle = f.color;
ctx.font = `900 ${f.big ? 25 : 16}px system-ui`;
ctx.shadowColor = f.color; ctx.shadowBlur = 10;
ctx.fillText(f.text, f.x, f.y);
}
ctx.globalAlpha = 1; ctx.shadowBlur = 0;
ctx.restore();
if (flash > 0) {
ctx.fillStyle = `rgba(255,255,255,${flash * .45})`;
ctx.fillRect(0, 0, W, H);
}
}
let last = performance.now();
function loop(now) {
const dt = Math.min(.033, (now - last) / 1000);
last = now;
update(dt);
draw();
requestAnimationFrame(loop);
}
resize();
resetGame();
requestAnimationFrame(loop);
})();
</script>
</body>
</html>Reasoning trace (829 tokens)
We need produce single HTML code block, complete breakout game hamburger paddle, bricks explode into hearts. Need self-playing demo behind click/key message. Make polished. Canvas game. Attractive visuals. Hearts particles. Burger paddle. Instructions overlay. AI plays automatically after start? Requirement says "Games must start in a self-playing demo mode (a simple AI plays) behind a 'Click or press a key to start' message". Interpret show overlay "Click or press a key to start", maybe game already runs AI behind overlay? Say message on start overlay and AI plays automatically upon load; clicking enables player control. We can have AI playing always, click toggles to player control, overlay fades. The message displayed prominently on first paint with gameplay visible behind. Implement canvas responsive. Need exactly one fenced code block. Let's build rich HTML: title header "HEARTBREAKER BREAKOUT", score, lives, combo, level, stage. Canvas with gradient, neon-ish aesthetic, burger paddle drawing, bricks with cute patterns, ball trail, particle hearts on brick break, screen shake, floating score text. Mouse/arrow control, touch. AI tracks ball. Start overlay card with "CLICK OR PRESS A KEY TO START" and subtext "Demo mode: chef bot is warming up the grill" and button maybe. But clicking overlay starts manual. It says behind a message. Good. Ensure no external assets/fonts. Use system fonts. Emoji hearts rendered as text might vary but fine; could draw hearts with canvas path for consistent. Let's draw heart shape with bezier path scaled, colored pink/red. Game loop: paddle x,y w=120,h=26. Ball radius 10. Bricks grid 9 cols x 6 rows, spacing, offsets. Ball speed ~ 6. Collision robust. Level progression. Score, combo multiplier, lives. AI mode auto plays (bot target ball x with slight error). When life lost reset. Walls. Top. Bricks exploding into 8-14 hearts with velocities and gravity; hearts fade. Also floating "+150 HEARTS!" text. Win level regenerate with new hue. Responsive: canvas internal size scaled by devicePixelRatio; fixed logical width based on container? Better canvas CSS width 100%, height min(68vh, 640px). Resize sets canvas.width = rect.width*dpr and height. Game coordinates use actual logical width/height from rect (CSS pixels) with ctx.setTransform(dpr...). On resize, rescale positions? Simplify use fixed logical design width 1000, height 600 and scale with letterbox? Responsive easier: canvas fills wrapper, logical W = 1000, H=600, use transform scaling based on aspect with contain, background. But on mobile portrait, contain leaves small. Could make W dynamic = rect.width, H=rect.height and reset positions proportionally on resize. Resizing infrequent. Use W=canvas.clientWidth, H=canvas.clientHeight. Store normalized? Just set paddle.x = W/2 - w/2; ball center. Bricks layout computed. On resize rebuild brick layout preserving scores. fine. Draw background: deep purple gradient, grid, glow. Header outside canvas. Let's write carefully. Collision: ball moving; check walls. paddle collision when vy>0 and ball.y+r >= paddle.y and ball.y-r <= paddle.y+paddle.h and x within paddle. reflect with angle based on hit offset, clamp speed. Bricks: for each alive, AABB circle collision; determine overlap x/y, reflect accordingly. Use previous position for accuracy. Simpler circle-rect closest point. Compute overlapX etc. Reflect based on min penetration. Mark dead, score. Explode hearts. Particles many. Cap ~600. Overlay HTML absolute over canvas. Button start. Keydown anywhere. AI paddle: target = ball.x + sin drift; move toward. Demo mode true. But behind overlay it plays. Clicking sets manual=false? Actually manual = true so player controls; demo overlay says bot. Maybe click starts manual control. Prompt wording clear. Let's code.