Hamburger breakoutOne-line
DeepSeek V4.1 Flash 1017
by DeepSeekReasoning1 console error
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 43.2 s
- Cost
- $0.0063
- Output tokens
- 11,520
- Spent thinking
- 1 tokens
- Speed
- 269.1 tok/s
- Console errors
- 1
- First token after
- 388 ms
- First visible text after
- 388 ms
- Prompt tokens
- 385
- Code size
- 31.0 KB
- Finish reason
- stop
- Served by
- StepFun
- Model id
deepseek/deepseek-v4.1-flash-1017- Reasoning sent
{"effort":"max"}- Max output
- 64,000 tokens
- Generated
- Nov 10, 2026
Console errors
Caught in the first 3 seconds after load, in a sandboxed iframe like the one above.
Failed to load resource: the server responded with a status of 404 (Not Found)
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 · 1003 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Burger Breakout</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body {
width: 100%; height: 100%;
overflow: hidden;
background: #1a0a2e;
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
-webkit-user-select: none; user-select: none;
}
#game {
display: block;
width: 100%; height: 100%;
cursor: none;
}
#overlay {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: rgba(26, 10, 46, 0.55);
backdrop-filter: blur(3px);
transition: opacity 0.4s;
z-index: 10;
pointer-events: none;
}
#overlay.hidden { opacity: 0; }
#overlay .card {
text-align: center;
padding: 32px 44px;
border-radius: 22px;
background: rgba(40, 16, 68, 0.85);
border: 2px solid rgba(255, 120, 170, 0.55);
box-shadow: 0 0 60px rgba(255, 90, 150, 0.35), inset 0 0 30px rgba(255, 120, 170, 0.12);
animation: floaty 3s ease-in-out infinite;
}
@keyframes floaty {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-8px); }
}
#overlay h1 {
font-size: clamp(26px, 4.4vw, 48px);
color: #ffd7e8;
letter-spacing: 1px;
text-shadow: 0 0 18px rgba(255, 90, 150, 0.8), 0 3px 0 #7a1f4b;
margin-bottom: 10px;
}
#overlay h1 .emoji { font-size: 1.15em; }
#overlay p {
font-size: clamp(13px, 1.6vw, 19px);
color: #ffb3d1;
margin-bottom: 6px;
}
#overlay .hint {
margin-top: 16px;
font-size: clamp(11px, 1.3vw, 15px);
color: #c9a5e8;
animation: pulse 1.6s ease-in-out infinite;
}
@keyframes pulse {
0%, 100% { opacity: 0.55; }
50% { opacity: 1; }
}
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="overlay">
<div class="card">
<h1>🍔 Burger Breakout 💕</h1>
<p>The hamburger paddle. The heart bricks.</p>
<p>Every brick explodes into a shower of little hearts.</p>
<div class="hint">Click or press any key to start</div>
</div>
</div>
<script>
(() => {
'use strict';
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const overlay = document.getElementById('overlay');
let W = 0, H = 0, DPR = 1;
function resize() {
DPR = Math.min(window.devicePixelRatio || 1, 2);
W = window.innerWidth;
H = window.innerHeight;
canvas.width = Math.floor(W * DPR);
canvas.height = Math.floor(H * DPR);
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
layout();
}
// ---------- Layout constants ----------
const STATE = { DEMO: 0, PLAY: 1, DEAD: 2 };
let state = STATE.DEMO;
let started = false;
const COLS = 10;
let ROWS = 5;
let playLeft, playTop, playW, playH;
let brickGap = 6, brickW = 0, brickH = 0;
let playAreaRight, playAreaBottom;
let paddle = { x: 0, y: 0, w: 118, h: 26, targetX: 0, vx: 0 };
let ball = { x: 0, y: 0, r: 8, vx: 0, vy: 0, speed: 0, stuck: true };
let bricks = [];
let hearts = [];
let particles = [];
let floatingTexts = [];
let score = 0, lives = 3, level = 1;
let shake = 0;
let time = 0;
let demoResetTimer = 0;
const BRICK_COLORS = [
['#ff5f9e', '#c72c6b'],
['#ff8fb8', '#e04b86'],
['#c56bff', '#8a2be2'],
['#ffa94d', '#e07b1a'],
['#5fd4ff', '#1f9fd6'],
['#8affa1', '#2fbf55']
];
function layout() {
playTop = Math.max(64, H * 0.11);
playW = Math.min(W * 0.94, 980);
playLeft = (W - playW) / 2;
playH = H - playTop - Math.max(50, H * 0.075);
playAreaRight = playLeft + playW;
playAreaBottom = playTop + playH;
brickGap = Math.max(4, playW * 0.008);
brickW = (playW - brickGap * (COLS - 1)) / COLS;
brickH = Math.min(brickW * 0.42, playH * 0.055);
paddle.w = Math.min(Math.max(96, playW * 0.16), 170);
paddle.h = Math.max(20, paddle.w * 0.22);
paddle.y = playAreaBottom - paddle.h - 8;
if (!started) paddle.x = playLeft + playW / 2;
ball.r = Math.max(6, Math.min(11, playW * 0.011));
if (ball.stuck) {
ball.x = paddle.x + paddle.w / 2;
ball.y = paddle.y - ball.r - 2;
}
// Keep bricks aligned after a resize (only scale existing positions)
rebuildBrickPositions();
}
function makeBricks() {
bricks = [];
ROWS = level >= 4 ? 6 : 5;
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
const pal = BRICK_COLORS[r % BRICK_COLORS.length];
bricks.push({
col: c, row: r,
x: 0, y: 0, w: 0, h: 0,
alive: true,
hp: 1 + (level >= 3 && r === 0 ? 1 : 0),
maxHp: 1 + (level >= 3 && r === 0 ? 1 : 0),
color: pal[0], dark: pal[1],
wob: Math.random() * Math.PI * 2
});
}
}
rebuildBrickPositions();
}
function rebuildBrickPositions() {
for (const b of bricks) {
b.x = playLeft + b.col * (brickW + brickGap);
b.y = playTop + b.row * (brickH + brickGap);
b.w = brickW;
b.h = brickH;
}
}
function resetBall(stuck) {
ball.stuck = stuck;
ball.speed = Math.min(730, 430 + level * 45);
const ang = -Math.PI / 2 + (Math.random() - 0.5) * 0.7;
ball.vx = Math.cos(ang) * ball.speed;
ball.vy = Math.sin(ang) * ball.speed;
ball.x = paddle.x + paddle.w / 2;
ball.y = paddle.y - ball.r - 2;
if (stuck) {
ball.vx = 0; ball.vy = 0;
}
}
function newGame() {
score = 0; lives = 3; level = 1;
makeBricks();
paddle.x = playLeft + playW / 2 - paddle.w / 2;
resetBall(true);
hearts.length = 0; particles.length = 0; floatingTexts.length = 0;
}
function nextLevel() {
level++;
makeBricks();
paddle.x = playLeft + playW / 2 - paddle.w / 2;
resetBall(true);
floatingTexts.push({ x: W / 2, y: H * 0.42, text: 'LEVEL ' + level, life: 1.6, max: 1.6, size: 38, color: '#ffd7e8' });
}
// ---------- Hearts particle system ----------
const HEART_COLORS = ['#ff4d8d', '#ff7eb3', '#ff9ecb', '#ff2e6e', '#ffb3d1', '#ff5f9e'];
function spawnHearts(x, y, w, h, amount) {
for (let i = 0; i < amount; i++) {
const a = Math.random() * Math.PI * 2;
const sp = 60 + Math.random() * 340;
hearts.push({
x: x + Math.random() * w,
y: y + Math.random() * h,
vx: Math.cos(a) * sp,
vy: Math.sin(a) * sp - 90,
size: 3 + Math.random() * 9,
rot: (Math.random() - 0.5) * 1.2,
vrot: (Math.random() - 0.5) * 9,
life: 1 + Math.random() * 1.1,
maxLife: 2.1,
color: HEART_COLORS[(Math.random() * HEART_COLORS.length) | 0],
gravity: 260 + Math.random() * 200,
drag: 0.985,
wobble: Math.random() * Math.PI * 2
});
}
// a few sparkle particles
for (let i = 0; i < Math.floor(amount * 0.5); i++) {
const a = Math.random() * Math.PI * 2;
const sp = 40 + Math.random() * 260;
particles.push({
x: x + w / 2, y: y + h / 2,
vx: Math.cos(a) * sp, vy: Math.sin(a) * sp,
r: 1 + Math.random() * 2.4,
life: 0.35 + Math.random() * 0.5, maxLife: 0.85,
color: Math.random() < 0.5 ? '#fff5fa' : '#ffd7e8'
});
}
}
function drawHeart(cx, cy, size, rot, color, alpha) {
ctx.save();
ctx.globalAlpha = alpha;
ctx.translate(cx, cy);
ctx.rotate(rot);
ctx.scale(size / 16, size / 16);
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(0, 4);
ctx.bezierCurveTo(-9, -4, -4, -12, 0, -7);
ctx.bezierCurveTo(4, -12, 9, -4, 0, 4);
ctx.closePath();
ctx.fill();
ctx.restore();
}
// ---------- Hamburger paddle drawing ----------
function drawBurger(cx, cy, w, h) {
const bw = w * 0.86;
const x = cx - bw / 2;
const s = h;
ctx.save();
// shadow
ctx.fillStyle = 'rgba(0,0,0,0.35)';
ctx.beginPath();
ctx.ellipse(cx, cy + h * 0.62, bw * 0.5, h * 0.22, 0, 0, Math.PI * 2);
ctx.fill();
// bottom bun
roundRect(x, cy + s * 0.18, bw, s * 0.45, s * 0.22, '#e8a55c');
ctx.fillStyle = 'rgba(255,255,255,0.13)';
roundRect(x + bw * 0.08, cy + s * 0.24, bw * 0.84, s * 0.12, s * 0.1, 'rgba(255,255,255,0.14)');
// lettuce
ctx.fillStyle = '#5fbf4b';
ctx.beginPath();
const ly = cy + s * 0.06;
ctx.moveTo(x - bw * 0.02, ly);
for (let i = 0; i <= 8; i++) {
const px = x - bw * 0.02 + (bw * 1.04) * (i / 8);
const dy = (i % 2 === 0 ? 1 : -1) * s * 0.09;
ctx.lineTo(px, ly + dy);
}
ctx.lineTo(x + bw * 1.02, cy + s * 0.26);
ctx.lineTo(x - bw * 0.02, cy + s * 0.26);
ctx.closePath();
ctx.fill();
// cheese
ctx.fillStyle = '#ffcc33';
ctx.beginPath();
ctx.moveTo(x - bw * 0.03, cy - s * 0.03);
ctx.lineTo(x + bw * 1.03, cy - s * 0.03);
ctx.lineTo(x + bw * 0.96, cy + s * 0.1);
ctx.lineTo(x + bw * 0.78, cy + s * 0.02);
ctx.lineTo(x + bw * 0.55, cy + s * 0.12);
ctx.lineTo(x + bw * 0.3, cy + s * 0.01);
ctx.lineTo(x + bw * 0.1, cy + s * 0.1);
ctx.closePath();
ctx.fill();
// patty
ctx.fillStyle = '#6b3a1e';
roundRect(x + bw * 0.02, cy - s * 0.2, bw * 0.96, s * 0.22, s * 0.1, '#6b3a1e');
ctx.fillStyle = 'rgba(0,0,0,0.22)';
for (let i = 0; i < 5; i++) {
const px = x + bw * (0.14 + i * 0.18);
ctx.beginPath();
ctx.arc(px, cy - s * 0.09, s * 0.028, 0, Math.PI * 2);
ctx.fill();
}
// top bun
const grad = ctx.createLinearGradient(0, cy - s * 0.62, 0, cy - s * 0.1);
grad.addColorStop(0, '#ffcf7a');
grad.addColorStop(1, '#e59a4a');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.moveTo(x - bw * 0.02, cy - s * 0.16);
ctx.quadraticCurveTo(x + bw * 0.5, cy - s * 0.95, x + bw * 1.02, cy - s * 0.16);
ctx.closePath();
ctx.fill();
// sesame seeds
ctx.fillStyle = 'rgba(255,248,226,0.92)';
const seeds = [[0.2, -0.42], [0.36, -0.56], [0.5, -0.62], [0.64, -0.55], [0.8, -0.41], [0.28, -0.28], [0.72, -0.28], [0.5, -0.34]];
for (const [sx, sy] of seeds) {
ctx.save();
ctx.translate(x + bw * sx, cy + s * sy);
ctx.rotate(sx * 1.6);
ctx.beginPath();
ctx.ellipse(0, 0, bw * 0.026, bw * 0.014, 0, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
// eyes on the bun
const eyeY = cy - s * 0.44;
ctx.fillStyle = '#3a1e0d';
ctx.beginPath();
ctx.arc(x + bw * 0.36, eyeY, s * 0.075, 0, Math.PI * 2);
ctx.arc(x + bw * 0.66, eyeY, s * 0.075, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(x + bw * 0.38, eyeY - s * 0.03, s * 0.03, 0, Math.PI * 2);
ctx.arc(x + bw * 0.68, eyeY - s * 0.03, s * 0.03, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
function roundRect(x, y, w, h, r, fill) {
ctx.beginPath();
const rr = Math.min(r, w / 2, h / 2);
ctx.moveTo(x + rr, y);
ctx.lineTo(x + w - rr, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + rr);
ctx.lineTo(x + w, y + h - rr);
ctx.quadraticCurveTo(x + w, y + h, x + w - rr, y + h);
ctx.lineTo(x + rr, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - rr);
ctx.lineTo(x, y + rr);
ctx.quadraticCurveTo(x, y, x + rr, y);
ctx.closePath();
ctx.fillStyle = fill;
ctx.fill();
}
// ---------- Input ----------
const keys = { left: false, right: false };
let pointerX = null;
let inputActive = false;
function beginPlay() {
if (!started) {
started = true;
state = STATE.PLAY;
newGame();
overlay.classList.add('hidden');
resetBall(true);
} else if (state === STATE.DEAD) {
state = STATE.PLAY;
resetBall(true);
} else if (ball.stuck) {
launchBall();
}
}
function launchBall() {
if (!ball.stuck) return;
ball.stuck = false;
const ang = -Math.PI / 2 + (Math.random() - 0.5) * 0.5;
ball.vx = Math.cos(ang) * ball.speed;
ball.vy = Math.sin(ang) * ball.speed;
}
window.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') { keys.left = true; inputActive = true; }
if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') { keys.right = true; inputActive = true; }
if (e.key === ' ' || e.key === 'Enter') {
e.preventDefault();
inputActive = true;
if (!started) beginPlay();
else if (ball.stuck) launchBall();
}
if (!started && e.key.length === 1) { inputActive = true; beginPlay(); }
if (['ArrowLeft', 'ArrowRight', ' '].includes(e.key)) e.preventDefault();
});
window.addEventListener('keyup', (e) => {
if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') keys.left = false;
if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') keys.right = false;
});
function onPointer(x) {
pointerX = x;
if (!started) { inputActive = true; beginPlay(); }
else if (ball.stuck) launchBall();
}
canvas.addEventListener('mousemove', (e) => {
onPointer(e.clientX);
});
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
onPointer(e.touches[0].clientX);
}, { passive: false });
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
onPointer(e.touches[0].clientX);
}, { passive: false });
overlay.addEventListener('pointerdown', () => { inputActive = true; beginPlay(); });
// ---------- Update ----------
function update(dt) {
time += dt;
if (shake > 0) shake = Math.max(0, shake - dt * 60);
// paddle control
if (started && state === STATE.PLAY) {
if (keys.left || keys.right) {
const sp = playW * 1.15;
if (keys.left) paddle.x -= sp * dt;
if (keys.right) paddle.x += sp * dt;
} else if (pointerX !== null && inputActive) {
const targetCenter = pointerX;
const desired = targetCenter - paddle.w / 2;
paddle.x += (desired - paddle.x) * Math.min(1, dt * 16);
}
} else if (!started) {
// demo AI in update of paddle below
}
// clamp paddle
paddle.x = Math.max(playLeft - 6, Math.min(playAreaRight - paddle.w + 6, paddle.x));
// Demo AI
if (!started) {
let target;
if (ball.stuck || state === STATE.DEAD) {
target = W / 2 - paddle.w / 2;
} else {
// predict ball x at paddle height (simple)
let px = ball.x, py = ball.y, pvx = ball.vx, pvy = ball.vy;
let guard = 0;
while (py < paddle.y - 4 && guard < 400) {
py += pvy * 0.01;
px += pvx * 0.01;
if (px < playLeft + ball.r) { px = playLeft + ball.r; pvx = Math.abs(pvx); }
if (px > playAreaRight - ball.r) { px = playAreaRight - ball.r; pvx = -Math.abs(pvx); }
guard++;
}
// add slight wobble for the hearts of the burger
target = px + Math.sin(time * 3.4) * paddle.w * 0.16 - paddle.w / 2;
}
const diff = target - paddle.x;
const maxStep = playW * 1.05 * dt;
paddle.x += Math.max(-maxStep, Math.min(maxStep, diff * Math.min(1, dt * 9)));
// auto launch after a moment
if (ball.stuck && state === STATE.PLAY && started === false) {
demoResetTimer += dt;
if (demoResetTimer > 0.7) { demoResetTimer = 0; launchBall(); }
}
}
// ball stuck to paddle
if (ball.stuck) {
ball.x = paddle.x + paddle.w / 2;
ball.y = paddle.y - ball.r - 2;
ball.vx = 0; ball.vy = 0;
if (!started && state === STATE.PLAY) {
demoResetTimer += dt;
if (demoResetTimer > 0.55) { demoResetTimer = 0; launchBall(); }
}
} else {
// physics with substeps to avoid tunneling
const steps = 3;
const sdt = dt / steps;
for (let s = 0; s < steps; s++) {
ball.x += ball.vx * sdt;
ball.y += ball.vy * sdt;
// walls
if (ball.x - ball.r < playLeft) {
ball.x = playLeft + ball.r;
ball.vx = Math.abs(ball.vx);
sparkWall(ball.x, ball.y);
} else if (ball.x + ball.r > playAreaRight) {
ball.x = playAreaRight - ball.r;
ball.vx = -Math.abs(ball.vx);
sparkWall(ball.x, ball.y);
}
if (ball.y - ball.r < playTop) {
ball.y = playTop + ball.r;
ball.vy = Math.abs(ball.vy);
sparkWall(ball.x, ball.y);
}
// paddle collision
if (ball.vy > 0 &&
ball.y + ball.r >= paddle.y &&
ball.y - ball.r <= paddle.y + paddle.h &&
ball.x + ball.r >= paddle.x &&
ball.x - ball.r <= paddle.x + paddle.w) {
ball.y = paddle.y - ball.r;
const hit = (ball.x - (paddle.x + paddle.w / 2)) / (paddle.w / 2);
const clamped = Math.max(-1, Math.min(1, hit));
const angle = clamped * 1.05; // max ~60deg
const sp = Math.min(900, ball.speed * 1.012);
ball.speed = sp;
ball.vx = Math.sin(angle) * sp;
ball.vy = -Math.cos(angle) * sp;
shake = Math.max(shake, 3);
for (let i = 0; i < 6; i++) {
particles.push({
x: ball.x, y: paddle.y,
vx: (Math.random() - 0.5) * 260,
vy: -Math.random() * 200,
r: 1 + Math.random() * 2,
life: 0.3 + Math.random() * 0.3, maxLife: 0.6,
color: '#ffd7e8'
});
}
}
// brick collisions
for (const b of bricks) {
if (!b.alive) continue;
if (ball.x + ball.r > b.x && ball.x - ball.r < b.x + b.w &&
ball.y + ball.r > b.y && ball.y - ball.r < b.y + b.h) {
// determine side
const overlapL = (ball.x + ball.r) - b.x;
const overlapR = (b.x + b.w) - (ball.x - ball.r);
const overlapT = (ball.y + ball.r) - b.y;
const overlapB = (b.y + b.h) - (ball.y - ball.r);
const minH = Math.min(overlapL, overlapR);
const minV = Math.min(overlapT, overlapB);
if (minH < minV) {
ball.vx = -ball.vx;
ball.x += (overlapL < overlapR ? -overlapL : overlapR);
} else {
ball.vy = -ball.vy;
ball.y += (overlapT < overlapB ? -overlapT : overlapB);
}
b.hp--;
if (b.hp <= 0) {
b.alive = false;
// EXPLODE INTO TONS OF LITTLE HEARTS
spawnHearts(b.x, b.y, b.w, b.h, 20);
score += 100;
shake = Math.max(shake, 6);
floatingTexts.push({
x: b.x + b.w / 2, y: b.y + b.h / 2,
text: '+100', life: 0.85, max: 0.85, size: 15, color: '#ffd7e8'
});
} else {
spawnHearts(b.x, b.y, b.w, b.h, 7);
b.color = '#ffffff';
setTimeout(() => {}, 0);
}
break;
}
}
// bottom: ball lost
if (ball.y - ball.r > playAreaBottom) {
if (started) {
lives--;
if (lives <= 0) {
state = STATE.DEAD;
floatingTexts.push({ x: W / 2, y: H * 0.45, text: 'GAME OVER', life: 2.4, max: 2.4, size: 44, color: '#ff5f9e' });
setTimeout(() => {
if (started) {
newGame();
state = STATE.PLAY;
resetBall(true);
overlay.classList.remove('hidden');
overlay.querySelector('h1').innerHTML = '💔 Oops! 💔';
overlay.querySelector('p').textContent = 'You lost all your burgers. Click to play again!';
overlay.querySelector('.hint').textContent = 'Click or press any key to restart';
started = false;
}
}, 1600);
} else {
resetBall(true);
shake = 10;
}
} else {
// demo mode: reset the ball
resetBall(false);
ball.speed = Math.min(730, 430 + level * 45);
const ang = -Math.PI / 2 + (Math.random() - 0.5) * 0.8;
ball.vx = Math.cos(ang) * ball.speed;
ball.vy = Math.sin(ang) * ball.speed;
}
break;
}
}
}
// level clear check
if (started && state === STATE.PLAY && bricks.every(b => !b.alive)) {
nextLevel();
}
if (!started && bricks.every(b => !b.alive)) {
// demo mode: refresh the bricks
nextLevel();
level = 1;
}
// update hearts
for (let i = hearts.length - 1; i >= 0; i--) {
const p = hearts[i];
p.vy += p.gravity * dt;
p.vx *= Math.pow(p.drag, dt * 60);
p.x += p.vx * dt;
p.y += p.vy * dt;
p.rot += p.vrot * dt;
p.wobble += dt * 6;
p.life -= dt;
if (p.x - p.size < playLeft) { p.x = playLeft + p.size; p.vx = Math.abs(p.vx) * 0.6; }
if (p.x + p.size > playAreaRight) { p.x = playAreaRight - p.size; p.vx = -Math.abs(p.vx) * 0.6; }
if (p.y > playAreaBottom + 40 || p.life <= 0) hearts.splice(i, 1);
}
// update particles
for (let i = particles.length - 1; i >= 0; i--) {
const p = particles[i];
p.x += p.vx * dt;
p.y += p.vy * dt;
p.vy += 300 * dt;
p.life -= dt;
if (p.life <= 0) particles.splice(i, 1);
}
// floating texts
for (let i = floatingTexts.length - 1; i >= 0; i--) {
const t = floatingTexts[i];
t.life -= dt;
t.y -= 34 * dt;
if (t.life <= 0) floatingTexts.splice(i, 1);
}
}
function sparkWall(x, y) {
for (let i = 0; i < 3; i++) {
particles.push({
x, y,
vx: (Math.random() - 0.5) * 180,
vy: (Math.random() - 0.5) * 180,
r: 1 + Math.random() * 2,
life: 0.2 + Math.random() * 0.25, maxLife: 0.45,
color: '#ffb3d1'
});
}
}
// ---------- Draw ----------
function draw() {
// background gradient
const bg = ctx.createRadialGradient(W / 2, H * 0.35, 40, W / 2, H * 0.5, Math.max(W, H) * 0.8);
bg.addColorStop(0, '#3b1450');
bg.addColorStop(0.55, '#240b3a');
bg.addColorStop(1, '#12041f');
ctx.fillStyle = bg;
ctx.fillRect(0, 0, W, H);
// soft glowing orbs
ctx.save();
ctx.globalCompositeOperation = 'lighter';
for (let i = 0; i < 5; i++) {
const ox = W * (0.15 + 0.18 * i) + Math.sin(time * 0.4 + i) * 40;
const oy = H * (0.2 + 0.15 * (i % 3)) + Math.cos(time * 0.3 + i * 1.3) * 30;
const g = ctx.createRadialGradient(ox, oy, 0, ox, oy, 190);
g.addColorStop(0, 'rgba(255,90,150,0.075)');
g.addColorStop(1, 'rgba(255,90,150,0)');
ctx.fillStyle = g;
ctx.beginPath();
ctx.arc(ox, oy, 190, 0, Math.PI * 2);
ctx.fill();
}
ctx.restore();
// floating faint hearts in background
ctx.save();
for (let i = 0; i < 14; i++) {
const t = time * 0.22 + i * 0.91;
const fx = ((i * 137.5 + Math.sin(t) * 24) % (W + 100)) - 50;
const fy = H - ((t * 42 + i * 90) % (H + 160));
const a = 0.05 + 0.045 * Math.sin(t * 2.3);
drawHeart(fx, fy, 12 + (i % 4) * 7, Math.sin(t * 1.4) * 0.5, '#ff7eb3', Math.max(0.02, a));
}
ctx.restore();
// play area border glow
ctx.save();
ctx.strokeStyle = 'rgba(255, 120, 170, 0.20)';
ctx.lineWidth = 2;
ctx.strokeRect(playLeft, playTop, playW, playH);
ctx.restore();
ctx.save();
if (shake > 0.2) {
ctx.translate((Math.random() - 0.5) * shake, (Math.random() - 0.5) * shake);
}
// bricks
for (const b of bricks) {
if (!b.alive) continue;
const wob = Math.sin(time * 2.2 + b.wob) * 1.2;
const bx = b.x, by = b.y + wob;
// glow
ctx.save();
ctx.shadowColor = b.color;
ctx.shadowBlur = 14;
const grad = ctx.createLinearGradient(bx, by, bx, by + b.h);
grad.addColorStop(0, b.color);
grad.addColorStop(1, b.dark);
ctx.fillStyle = grad;
roundRectPath(bx, by, b.w, b.h, Math.min(7, b.h * 0.3));
ctx.fill();
ctx.restore();
// glossy highlight
ctx.save();
const hl = ctx.createLinearGradient(bx, by, bx, by + b.h * 0.55);
hl.addColorStop(0, 'rgba(255,255,255,0.42)');
hl.addColorStop(1, 'rgba(255,255,255,0.02)');
ctx.fillStyle = hl;
roundRectPath(bx + 2, by + 2, b.w - 4, b.h * 0.5, Math.min(5, b.h * 0.22));
ctx.fill();
ctx.restore();
// tiny heart emblem on the brick
drawHeart(bx + b.w / 2, by + b.h / 2 + 1, Math.min(b.h * 0.52, b.w * 0.22), 0, 'rgba(255,255,255,0.55)', 0.85);
if (b.hp > 1) {
ctx.save();
ctx.strokeStyle = 'rgba(255,255,255,0.9)';
ctx.lineWidth = 2;
roundRectPath(bx, by, b.w, b.h, Math.min(7, b.h * 0.3));
ctx.stroke();
ctx.restore();
}
}
// hearts particles (behind paddle for depth? no, in front looks better)
for (const p of hearts) {
const a = Math.min(1, p.life / (p.maxLife * 0.5));
const sc = p.size * (0.6 + 0.4 * Math.min(1, p.life));
drawHeart(p.x, p.y, sc, p.rot + Math.sin(p.wobble) * 0.2, p.color, a);
}
// sparkle particles
for (const p of particles) {
const a = Math.max(0, p.life / p.maxLife);
ctx.globalAlpha = a;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
ctx.fill();
}
ctx.globalAlpha = 1;
// paddle: the hamburger
drawBurger(paddle.x + paddle.w / 2, paddle.y + paddle.h / 2 - 4, paddle.w * 1.07, paddle.h * 1.5);
// ball: a little heart-shaped patty / glowing orb
if (!ball.stuck || started) {
ctx.save();
ctx.shadowColor = '#fff0f6';
ctx.shadowBlur = 22;
const bg2 = ctx.createRadialGradient(ball.x - 2, ball.y - 3, 1, ball.x, ball.y, ball.r * 1.5);
bg2.addColorStop(0, '#ffffff');
bg2.addColorStop(0.5, '#ffd7e8');
bg2.addColorStop(1, '#ff5f9e');
ctx.fillStyle = bg2;
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
// trailing hearts
if (Math.random() < 0.55) {
hearts.push({
x: ball.x + (Math.random() - 0.5) * 6,
y: ball.y + (Math.random() - 0.5) * 6,
vx: (Math.random() - 0.5) * 40,
vy: 20 + Math.random() * 40,
size: 2.5 + Math.random() * 4,
rot: Math.random() * 2,
vrot: (Math.random() - 0.5) * 4,
life: 0.5 + Math.random() * 0.4,
maxLife: 0.9,
color: HEART_COLORS[(Math.random() * HEART_COLORS.length) | 0],
gravity: 40,
drag: 0.97,
wobble: Math.random() * 6
});
}
}
// floating texts
for (const t of floatingTexts) {
const a = Math.max(0, Math.min(1, t.life / (t.max * 0.55)));
ctx.save();
ctx.globalAlpha = a;
ctx.font = `800 ${t.size}px 'Segoe UI', system-ui, sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.shadowColor = 'rgba(255,60,130,0.9)';
ctx.shadowBlur = 18;
ctx.fillStyle = t.color;
ctx.fillText(t.text, t.x, t.y);
ctx.restore();
}
ctx.restore();
// HUD
drawHUD();
if (!started) {
// small demo badge
ctx.save();
ctx.globalAlpha = 0.5 + 0.2 * Math.sin(time * 3);
ctx.font = `600 ${Math.max(12, Math.min(16, W * 0.013))}px 'Segoe UI', system-ui, sans-serif`;
ctx.textAlign = 'center';
ctx.fillStyle = '#ffb3d1';
ctx.fillText('DEMO MODE — the burger plays itself 🍔', W / 2, H - 14);
ctx.restore();
}
}
function roundRectPath(x, y, w, h, r) {
const rr = Math.min(r, w / 2, h / 2);
ctx.beginPath();
ctx.moveTo(x + rr, y);
ctx.lineTo(x + w - rr, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + rr);
ctx.lineTo(x + w, y + h - rr);
ctx.quadraticCurveTo(x + w, y + h, x + w - rr, y + h);
ctx.lineTo(x + rr, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - rr);
ctx.lineTo(x, y + rr);
ctx.quadraticCurveTo(x, y, x + rr, y);
ctx.closePath();
}
function drawHUD() {
const fs = Math.max(13, Math.min(20, W * 0.016));
ctx.save();
ctx.font = `800 ${fs}px 'Segoe UI', system-ui, sans-serif`;
ctx.textBaseline = 'middle';
// score (left)
ctx.textAlign = 'left';
ctx.fillStyle = 'rgba(255,215,232,0.55)';
ctx.fillText('SCORE', playLeft, playTop * 0.45);
ctx.fillStyle = '#ffd7e8';
ctx.shadowColor = 'rgba(255,90,150,0.8)';
ctx.shadowBlur = 12;
ctx.font = `900 ${fs * 1.55}px 'Segoe UI', system-ui, sans-serif`;
ctx.fillText(String(score).padStart(4, '0'), playLeft, playTop * 0.72);
ctx.shadowBlur = 0;
// lives (right)
ctx.textAlign = 'right';
ctx.font = `800 ${fs}px 'Segoe UI', system-ui, sans-serif`;
ctx.fillStyle = 'rgba(255,215,232,0.55)';
ctx.fillText('BURGERS', playAreaRight, playTop * 0.45);
for (let i = 0; i < lives; i++) {
const lx = playAreaRight - 12 - i * (fs * 1.5);
const ly = playTop * 0.72;
drawMiniBurger(lx, ly, fs * 1.35);
}
// level (center)
ctx.textAlign = 'center';
ctx.font = `800 ${fs}px 'Segoe UI', system-ui, sans-serif`;
ctx.fillStyle = 'rgba(255,215,232,0.55)';
ctx.fillText('LEVEL ' + level, W / 2, playTop * 0.45);
ctx.restore();
}
function drawMiniBurger(cx, cy, size) {
const w = size, h = size * 0.78;
ctx.save();
ctx.translate(cx, cy);
// bottom bun
roundRect(-w * 0.5, h * 0.1, w, h * 0.42, h * 0.2, '#e8a55c');
// lettuce
ctx.fillStyle = '#5fbf4b';
ctx.beginPath();
ctx.moveTo(-w * 0.52, -h * 0.02);
for (let i = 0; i <= 5; i++) {
ctx.lineTo(-w * 0.52 + w * 1.04 * (i / 5), -h * 0.02 + (i % 2 === 0 ? -h * 0.11 : h * 0.11));
}
ctx.lineTo(w * 0.52, h * 0.16);
ctx.lineTo(-w * 0.52, h * 0.16);
ctx.closePath();
ctx.fill();
// patty
roundRect(-w * 0.48, -h * 0.2, w * 0.96, h * 0.2, h * 0.1, '#6b3a1e');
// top bun
const g = ctx.createLinearGradient(0, -h * 0.6, 0, -h * 0.1);
g.addColorStop(0, '#ffcf7a');
g.addColorStop(1, '#e59a4a');
ctx.fillStyle = g;
ctx.beginPath();
ctx.moveTo(-w * 0.5, -h * 0.14);
ctx.quadraticCurveTo(0, -h * 0.95, w * 0.5, -h * 0.14);
ctx.closePath();
ctx.fill();
// seeds
ctx.fillStyle = 'rgba(255,248,226,0.9)';
ctx.beginPath();
ctx.ellipse(-w * 0.18, -h * 0.38, w * 0.05, h * 0.03, 0.4, 0, Math.PI * 2);
ctx.ellipse(w * 0.14, -h * 0.42, w * 0.05, h * 0.03, -0.4, 0, Math.PI * 2);
ctx.ellipse(0, -h * 0.24, w * 0.05, h * 0.03, 0, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
// ---------- Main loop ----------
let last = performance.now();
function frame(now) {
let dt = (now - last) / 1000;
last = now;
if (dt > 0.05) dt = 0.05;
if (dt < 0) dt = 0;
update(dt);
draw();
requestAnimationFrame(frame);
}
window.addEventListener('resize', resize);
// init
resize();
newGame();
state = STATE.PLAY;
resetBall(false);
ball.speed = 500;
ball.vx = 240; ball.vy = -370;
requestAnimationFrame((t) => { last = t; frame(t); });
// Start demo immediately with the ball in flight
setTimeout(() => {
if (!started) {
resetBall(false);
const ang = -Math.PI / 2 + (Math.random() - 0.5) * 0.6;
ball.vx = Math.cos(ang) * ball.speed;
ball.vy = Math.sin(ang) * ball.speed;
}
}, 60);
})();
</script>
</body>
</html>