Hamburger breakoutOne-line
Gemini 4 Argon
by Googlereasoning defaultClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 2 min 11 s
- Cost
- $0.105
- Output tokens
- 10,444
- Spent thinking
- 0 tokens
- Speed
- 82.3 tok/s
- Console errors
- 0
- First token after
- 4.0 s
- First visible text after
- 4.0 s
- Prompt tokens
- 402
- Code size
- 24.5 KB
- Finish reason
- stop
- Served by
- Model id
google/gemini-4-argon- Reasoning sent
model default- Max output
- 943,718 tokens
- Generated
- Nov 12, 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 · 909 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Burger Love Breakout</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; user-select: none; }
body {
background: radial-gradient(circle at center, #2a082e 0%, #110214 100%);
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
overflow: hidden;
color: #fff;
}
#game-wrapper {
position: relative;
width: 100%;
max-width: 920px;
padding: 12px;
display: flex;
flex-direction: column;
align-items: center;
}
.hud {
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
padding: 10px 20px;
background: rgba(255, 255, 255, 0.07);
border: 2px solid rgba(255, 105, 180, 0.35);
border-radius: 16px;
backdrop-filter: blur(8px);
box-shadow: 0 8px 25px rgba(0,0,0,0.4);
}
.hud-title {
font-weight: 900;
font-size: clamp(1rem, 2.5vw, 1.5rem);
background: linear-gradient(90deg, #ff7eb3, #ff758c, #ffb347);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
letter-spacing: 1px;
display: flex;
align-items: center;
gap: 8px;
}
.hud-stats {
display: flex;
gap: 24px;
font-weight: 800;
font-size: clamp(0.9rem, 2vw, 1.2rem);
}
.stat span {
color: #ff7eb3;
}
.canvas-box {
position: relative;
width: 100%;
aspect-ratio: 16 / 10;
background: linear-gradient(180deg, #1b0624 0%, #2d0b38 60%, #3d0e3b 100%);
border-radius: 20px;
border: 3px solid #ff6b9d;
box-shadow: 0 0 35px rgba(255, 65, 130, 0.35), inset 0 0 40px rgba(0,0,0,0.6);
overflow: hidden;
}
canvas {
width: 100%;
height: 100%;
display: block;
cursor: none;
}
#overlay {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
pointer-events: none;
transition: opacity 0.35s ease;
background: radial-gradient(circle, rgba(27,6,36,0.45) 0%, rgba(17,2,20,0.75) 100%);
}
#overlay.hidden {
opacity: 0;
}
.banner {
background: rgba(35, 8, 48, 0.88);
border: 3px solid #ff758c;
border-radius: 24px;
padding: 24px 38px;
text-align: center;
box-shadow: 0 15px 40px rgba(0,0,0,0.7), 0 0 25px rgba(255, 117, 140, 0.45);
animation: floatBanner 2.4s ease-in-out infinite;
}
@keyframes floatBanner {
0%, 100% { transform: translateY(0px) scale(1); }
50% { transform: translateY(-7px) scale(1.02); }
}
.banner h1 {
font-size: clamp(1.5rem, 4vw, 2.5rem);
margin-bottom: 8px;
color: #fff;
text-shadow: 0 3px 12px rgba(255, 60, 130, 0.8);
}
.banner p {
font-size: clamp(0.95rem, 2vw, 1.2rem);
color: #ffd1df;
font-weight: 600;
margin-bottom: 12px;
}
.pill {
display: inline-block;
background: linear-gradient(90deg, #ff416c, #ff4b2b);
color: #fff;
font-weight: 800;
padding: 8px 20px;
border-radius: 999px;
font-size: 0.95rem;
letter-spacing: 0.5px;
box-shadow: 0 4px 15px rgba(255, 65, 108, 0.5);
}
</style>
</head>
<body>
<div id="game-wrapper">
<div class="hud">
<div class="hud-title">🍔 BURGER LOVE BREAKOUT 💖</div>
<div class="hud-stats">
<div class="stat">HEARTS: <span id="hearts-count">0</span></div>
<div class="stat">SCORE: <span id="score">0</span></div>
<div class="stat">LIVES: <span id="lives">🍔🍔🍔</span></div>
</div>
</div>
<div class="canvas-box" id="canvas-box">
<canvas id="game" width="960" height="600"></canvas>
<div id="overlay">
<div class="banner">
<h1 id="overlay-title">🍔 BURGER CRUSH 💖</h1>
<p id="overlay-sub">Auto-Demo Running • Move with Mouse, Touch, or Arrow Keys</p>
<div class="pill">CLICK OR PRESS ANY KEY TO PLAY</div>
</div>
</div>
</div>
</div>
<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const overlay = document.getElementById('overlay');
const overlayTitle = document.getElementById('overlay-title');
const overlaySub = document.getElementById('overlay-sub');
const scoreEl = document.getElementById('score');
const heartsCountEl = document.getElementById('hearts-count');
const livesEl = document.getElementById('lives');
const W = canvas.width;
const H = canvas.height;
let audioCtx = null;
function playPopSound(freq = 440, type = 'sine', duration = 0.12) {
if (!audioCtx) return;
try {
const osc = audioCtx.createOscillator();
const gain = audioCtx.createGain();
osc.type = type;
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
osc.frequency.exponentialRampToValueAtTime(freq * 1.45, audioCtx.currentTime + duration);
gain.gain.setValueAtTime(0.12, audioCtx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + duration);
osc.connect(gain);
gain.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + duration);
} catch (e) {}
}
// Game state
let demoMode = true;
let score = 0;
let totalHeartsSpawned = 0;
let lives = 3;
let screenShake = 0;
let combo = 0;
// Paddle (Hamburger)
const paddle = {
w: 155,
h: 48,
x: W / 2 - 77.5,
y: H - 68,
vx: 0,
targetX: W / 2 - 77.5,
squish: 1,
tilt: 0,
seeds: [
{ rx: -0.32, ry: -0.25, rot: -0.3 },
{ rx: -0.18, ry: -0.38, rot: 0.1 },
{ rx: -0.05, ry: -0.22, rot: -0.1 },
{ rx: 0.08, ry: -0.36, rot: 0.2 },
{ rx: 0.22, ry: -0.24, rot: -0.2 },
{ rx: 0.34, ry: -0.30, rot: 0.35 },
{ rx: -0.25, ry: -0.12, rot: 0.15 },
{ rx: 0.14, ry: -0.14, rot: -0.25 }
]
};
// Balls
let balls = [];
function createBall(x, y, vx, vy) {
return {
x, y,
vx, vy,
r: 10,
trail: []
};
}
// Bricks
let bricks = [];
const BRICK_ROWS = 6;
const BRICK_COLS = 10;
const BRICK_W = 80;
const BRICK_H = 30;
const BRICK_GAP = 8;
const BRICK_OFFSET_TOP = 65;
const BRICK_OFFSET_LEFT = (W - (BRICK_COLS * (BRICK_W + BRICK_GAP) - BRICK_GAP)) / 2;
const ROW_PALETTES = [
{ fill: '#ff2a85', border: '#ff85b3', hearts: ['#ff2a85', '#ff66a3', '#ffb3d1', '#fff0f5'] },
{ fill: '#ff4757', border: '#ff8f99', hearts: ['#ff4757', '#ff6b81', '#ffa4b0', '#ffffff'] },
{ fill: '#ff6348', border: '#ffa596', hearts: ['#ff6348', '#ff7f50', '#ffbe76', '#ffeaa7'] },
{ fill: '#ff7eb3', border: '#ffc2dc', hearts: ['#ff7eb3', '#ff758c', '#ffb8d1', '#ffffff'] },
{ fill: '#e056fd', border: '#f09cff', hearts: ['#e056fd', '#be2edd', '#f3a6ff', '#ffe6ff'] },
{ fill: '#ff9ff3', border: '#ffd6fa', hearts: ['#ff9ff3', '#f368e0', '#ffccf9', '#ffffff'] }
];
// Heart Particles & Shockwaves
let hearts = [];
let floatingTexts = [];
let shockwaves = [];
let bgHearts = [];
// Populate ambient background hearts
for (let i = 0; i < 28; i++) {
bgHearts.push({
x: Math.random() * W,
y: Math.random() * H,
size: 8 + Math.random() * 18,
vy: -0.3 - Math.random() * 0.6,
alpha: 0.06 + Math.random() * 0.12,
phase: Math.random() * Math.PI * 2
});
}
function initBricks() {
bricks = [];
for (let r = 0; r < BRICK_ROWS; r++) {
for (let c = 0; c < BRICK_COLS; c++) {
const bx = BRICK_OFFSET_LEFT + c * (BRICK_W + BRICK_GAP);
const by = BRICK_OFFSET_TOP + r * (BRICK_H + BRICK_GAP);
bricks.push({
x: bx,
y: by,
w: BRICK_W,
h: BRICK_H,
row: r,
alive: true,
palette: ROW_PALETTES[r % ROW_PALETTES.length],
pulse: Math.random() * Math.PI * 2
});
}
}
}
function spawnHeartExplosion(cx, cy, palette, count = 42) {
totalHeartsSpawned += count;
heartsCountEl.textContent = totalHeartsSpawned.toLocaleString();
shockwaves.push({
x: cx,
y: cy,
r: 8,
maxR: 75,
alpha: 0.85,
color: palette.fill
});
for (let i = 0; i < count; i++) {
const angle = Math.random() * Math.PI * 2;
const speed = 1.5 + Math.random() * 8.5;
const size = 6 + Math.random() * 13;
hearts.push({
x: cx + (Math.random() - 0.5) * 35,
y: cy + (Math.random() - 0.5) * 14,
vx: Math.cos(angle) * speed,
vy: Math.sin(angle) * speed - (1.5 + Math.random() * 2.5),
gravity: 0.14 + Math.random() * 0.08,
drag: 0.985,
size: size,
color: palette.hearts[Math.floor(Math.random() * palette.hearts.length)],
alpha: 1,
decay: 0.011 + Math.random() * 0.014,
rot: (Math.random() - 0.5) * 1.2,
vRot: (Math.random() - 0.5) * 0.14,
wobble: Math.random() * Math.PI * 2,
wobbleSpeed: 0.08 + Math.random() * 0.12
});
}
}
function resetBallAndLaunch(aimImmediateHit = false) {
balls = [];
const startX = paddle.x + paddle.w / 2;
const startY = paddle.y - 16;
if (aimImmediateHit) {
// Launch fast right into the lower bricks so the screenshot at ~3s has exploding hearts
balls.push(createBall(startX, startY - 90, 4.2, -7.8));
balls.push(createBall(startX - 40, startY - 120, -4.6, -7.5));
} else {
balls.push(createBall(startX, startY, (Math.random() > 0.5 ? 1 : -1) * 4.8, -7.2));
}
}
function resetGame(isDemo = false) {
demoMode = isDemo;
score = 0;
lives = 3;
combo = 0;
updateHUD();
initBricks();
resetBallAndLaunch(isDemo);
if (isDemo) {
// Trigger an immediate celebratory brick pop right at start so first seconds are super vibrant
const centerBrick = bricks[bricks.length - 5];
if (centerBrick) {
centerBrick.alive = false;
spawnHeartExplosion(centerBrick.x + centerBrick.w / 2, centerBrick.y + centerBrick.h / 2, centerBrick.palette, 50);
}
}
}
function updateHUD() {
scoreEl.textContent = score.toLocaleString();
heartsCountEl.textContent = totalHeartsSpawned.toLocaleString();
livesEl.textContent = '🍔'.repeat(Math.max(0, lives));
}
function startHumanGame() {
if (!audioCtx) {
const AudioContext = window.AudioContext || window.webkitAudioContext;
if (AudioContext) audioCtx = new AudioContext();
}
if (demoMode || lives <= 0) {
overlay.classList.add('hidden');
resetGame(false);
}
}
// Controls
const keys = { left: false, right: false };
window.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') keys.left = true;
if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') keys.right = true;
if (demoMode || lives <= 0) startHumanGame();
});
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 handlePointerMove(clientX) {
const rect = canvas.getBoundingClientRect();
const relX = ((clientX - rect.left) / rect.width) * W;
paddle.targetX = Math.max(12, Math.min(W - paddle.w - 12, relX - paddle.w / 2));
}
canvas.addEventListener('mousemove', (e) => {
if (!demoMode) handlePointerMove(e.clientX);
});
canvas.addEventListener('mousedown', (e) => {
handlePointerMove(e.clientX);
startHumanGame();
});
canvas.addEventListener('touchstart', (e) => {
if (e.touches.length > 0) {
handlePointerMove(e.touches[0].clientX);
startHumanGame();
}
}, { passive: true });
canvas.addEventListener('touchmove', (e) => {
if (!demoMode && e.touches.length > 0) {
handlePointerMove(e.touches[0].clientX);
}
}, { passive: true });
// Draw a vector heart centered at (x, y)
function drawHeartPath(ctx, size) {
const s = size * 0.5;
ctx.beginPath();
ctx.moveTo(0, s * 0.35);
ctx.bezierCurveTo(0, -s * 0.45, -s * 1.15, -s * 0.45, -s * 1.15, s * 0.25);
ctx.bezierCurveTo(-s * 1.15, s * 0.85, 0, s * 1.25, 0, s * 1.6);
ctx.bezierCurveTo(0, s * 1.25, s * 1.15, s * 0.85, s * 1.15, s * 0.25);
ctx.bezierCurveTo(s * 1.15, -s * 0.45, 0, -s * 0.45, 0, s * 0.35);
ctx.closePath();
}
// Draw detailed juicy hamburger paddle
function drawHamburger(p) {
ctx.save();
const cx = p.x + p.w / 2;
const cy = p.y + p.h / 2;
ctx.translate(cx, cy);
ctx.rotate(p.tilt);
ctx.scale(2 - p.squish, p.squish);
const hw = p.w / 2;
const hh = p.h / 2;
// Drop shadow
ctx.fillStyle = 'rgba(0, 0, 0, 0.38)';
ctx.beginPath();
ctx.ellipse(0, hh + 6, hw * 0.92, 7, 0, 0, Math.PI * 2);
ctx.fill();
// 1. Bottom Bun
const botGrad = ctx.createLinearGradient(0, 6, 0, hh);
botGrad.addColorStop(0, '#f0a34b');
botGrad.addColorStop(1, '#c47420');
ctx.fillStyle = botGrad;
ctx.strokeStyle = '#6e3805';
ctx.lineWidth = 2.5;
ctx.beginPath();
ctx.roundRect(-hw + 4, 8, p.w - 8, hh - 6, [4, 4, 14, 14]);
ctx.fill();
ctx.stroke();
// 2. Juicy Beef Patty
const pattyGrad = ctx.createLinearGradient(-hw, 0, hw, 0);
pattyGrad.addColorStop(0, '#54230e');
pattyGrad.addColorStop(0.5, '#783519');
pattyGrad.addColorStop(1, '#4a1e0b');
ctx.fillStyle = pattyGrad;
ctx.strokeStyle = '#2e1003';
ctx.lineWidth = 2.5;
ctx.beginPath();
ctx.roundRect(-hw - 3, -2, p.w + 6, 13, 7);
ctx.fill();
ctx.stroke();
// Grill sear marks on patty
ctx.strokeStyle = 'rgba(30, 8, 0, 0.65)';
ctx.lineWidth = 2.5;
for (let gx = -hw + 22; gx <= hw - 22; gx += 22) {
ctx.beginPath();
ctx.moveTo(gx, 0);
ctx.lineTo(gx - 4, 9);
ctx.stroke();
}
// 3. Melted Cheddar Cheese (with triangles dripping over patty)
ctx.fillStyle = '#ffcc00';
ctx.strokeStyle = '#d48800';
ctx.lineWidth = 1.8;
ctx.beginPath();
ctx.moveTo(-hw + 2, -3);
ctx.lineTo(hw - 2, -3);
ctx.lineTo(hw - 4, 1);
ctx.lineTo(hw * 0.55, 1);
ctx.lineTo(hw * 0.45, 9);
ctx.lineTo(hw * 0.32, 1);
ctx.lineTo(0, 1);
ctx.lineTo(-hw * 0.12, 10);
ctx.lineTo(-hw * 0.25, 1);
ctx.lineTo(-hw * 0.58, 1);
ctx.lineTo(-hw * 0.68, 8);
ctx.lineTo(-hw * 0.78, 1);
ctx.lineTo(-hw + 2, 1);
ctx.closePath();
ctx.fill();
ctx.stroke();
// 4. Tomato Slices
ctx.fillStyle = '#ff3838';
ctx.strokeStyle = '#a81313';
ctx.lineWidth = 1.8;
ctx.beginPath();
ctx.roundRect(-hw + 12, -8, p.w * 0.42, 6, 3);
ctx.roundRect(4, -8, p.w * 0.42, 6, 3);
ctx.fill();
ctx.stroke();
// 5. Ruffled Crispy Lettuce
ctx.fillStyle = '#5cd833';
ctx.strokeStyle = '#257d0b';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.moveTo(-hw - 4, -6);
const waves = 11;
const step = (p.w + 8) / waves;
for (let i = 0; i <= waves; i++) {
const wx = -hw - 4 + i * step;
const wy = -6 + (i % 2 === 0 ? -4 : 3);
ctx.quadraticCurveTo(wx - step / 2, -12, wx, wy);
}
ctx.lineTo(hw + 4, -4);
ctx.lineTo(-hw - 4, -4);
ctx.closePath();
ctx.fill();
ctx.stroke();
// 6. Top Sesame Bun Dome
const topGrad = ctx.createRadialGradient(-hw * 0.25, -hh * 0.6, 5, 0, -4, hw);
topGrad.addColorStop(0, '#ffcb7d');
topGrad.addColorStop(0.55, '#eb983b');
topGrad.addColorStop(1, '#b86814');
ctx.fillStyle = topGrad;
ctx.strokeStyle = '#6e3805';
ctx.lineWidth = 2.5;
ctx.beginPath();
ctx.moveTo(-hw + 2, -7);
ctx.bezierCurveTo(-hw + 2, -hh - 12, hw - 2, -hh - 12, hw - 2, -7);
ctx.quadraticCurveTo(0, -4, -hw + 2, -7);
ctx.closePath();
ctx.fill();
ctx.stroke();
// Gloss highlight on top bun
ctx.fillStyle = 'rgba(255, 255, 255, 0.28)';
ctx.beginPath();
ctx.ellipse(-hw * 0.28, -hh * 0.55, hw * 0.36, 4.5, -0.12, 0, Math.PI * 2);
ctx.fill();
// Sesame Seeds
ctx.fillStyle = '#fff5d6';
ctx.strokeStyle = '#bd8b46';
ctx.lineWidth = 1;
for (const s of p.seeds) {
ctx.save();
ctx.translate(s.rx * p.w, s.ry * p.h);
ctx.rotate(s.rot);
ctx.beginPath();
ctx.ellipse(0, 0, 4.2, 2.1, 0, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
ctx.restore();
}
ctx.restore();
}
function update() {
// Screen shake decay
if (screenShake > 0) screenShake *= 0.85;
if (screenShake < 0.2) screenShake = 0;
// Keyboard movement
const keySpeed = 10.5;
if (!demoMode) {
if (keys.left) paddle.targetX = Math.max(12, paddle.targetX - keySpeed);
if (keys.right) paddle.targetX = Math.min(W - paddle.w - 12, paddle.targetX + keySpeed);
} else {
// AI Demo logic: track lowest downward-moving ball
let targetBall = balls[0];
let maxScore = -Infinity;
for (const b of balls) {
const weight = b.y + (b.vy > 0 ? 250 : 0);
if (weight > maxScore) {
maxScore = weight;
targetBall = b;
}
}
if (targetBall) {
const offset = Math.sin(performance.now() * 0.003) * 28;
paddle.targetX = Math.max(12, Math.min(W - paddle.w - 12, targetBall.x - paddle.w / 2 + offset));
}
}
// Smooth paddle follow & tilt physics
const prevX = paddle.x;
paddle.x += (paddle.targetX - paddle.x) * 0.28;
paddle.vx = paddle.x - prevX;
paddle.tilt = Math.max(-0.14, Math.min(0.14, paddle.vx * 0.008));
paddle.squish += (1 - paddle.squish) * 0.18;
// Update balls
for (let bi = balls.length - 1; bi >= 0; bi--) {
const b = balls[bi];
b.trail.push({ x: b.x, y: b.y });
if (b.trail.length > 9) b.trail.shift();
b.x += b.vx;
b.y += b.vy;
// Wall collisions
if (b.x - b.r < 0) {
b.x = b.r;
b.vx = Math.abs(b.vx);
playPopSound(320, 'triangle', 0.07);
} else if (b.x + b.r > W) {
b.x = W - b.r;
b.vx = -Math.abs(b.vx);
playPopSound(320, 'triangle', 0.07);
}
if (b.y - b.r < 0) {
b.y = b.r;
b.vy = Math.abs(b.vy);
playPopSound(360, 'triangle', 0.07);
}
// Hamburger Paddle collision
if (
b.vy > 0 &&
b.y + b.r >= paddle.y - 4 &&
b.y - b.r <= paddle.y + paddle.h &&
b.x + b.r >= paddle.x - 6 &&
b.x - b.r <= paddle.x + paddle.w + 6
) {
const hitFactor = ((b.x - (paddle.x + paddle.w / 2)) / (paddle.w / 2));
const clampedHit = Math.max(-0.95, Math.min(0.95, hitFactor));
const speed = Math.min(11.5, Math.hypot(b.vx, b.vy) * 1.015);
const angle = clampedHit * (Math.PI * 0.36);
b.vx = Math.sin(angle) * speed;
b.vy = -Math.cos(angle) * speed;
b.y = paddle.y - b.r - 4;
paddle.squish = 0.72; // Squish the burger!
combo = 0;
playPopSound(240, 'sine', 0.14);
// Tiny sesame/heart burst on burger bounce
spawnHeartExplosion(b.x, paddle.y, ROW_PALETTES[3], 10);
}
// Brick collisions
for (const br of bricks) {
if (!br.alive) continue;
if (
b.x + b.r > br.x &&
b.x - b.r < br.x + br.w &&
b.y + b.r > br.y &&
b.y - b.r < br.y + br.h
) {
br.alive = false;
const overlapLeft = (b.x + b.r) - br.x;
const overlapRight = (br.x + br.w) - (b.x - b.r);
const overlapTop = (b.y + b.r) - br.y;
const overlapBottom = (br.y + br.h) - (b.y - b.r);
const minOverlap = Math.min(overlapLeft, overlapRight, overlapTop, overlapBottom);
if (minOverlap === overlapLeft || minOverlap === overlapRight) {
b.vx = -b.vx;
} else {
b.vy = -b.vy;
}
combo++;
const pts = 100 * combo;
score += pts;
screenShake = 5.5;
const cx = br.x + br.w / 2;
const cy = br.y + br.h / 2;
// Explode into tons of little hearts!
spawnHeartExplosion(cx, cy, br.palette, 48);
floatingTexts.push({
x: cx,
y: cy,
text: combo > 1 ? `💖 +${pts} (${combo}x)` : `💖 +${pts}`,
alpha: 1,
vy: -1.6
});
playPopSound(420 + Math.min(combo * 45, 500), 'sine', 0.14);
updateHUD();
break;
}
}
// Bottom out
if (b.y - b.r > H) {
balls.splice(bi, 1);
}
}
// Check if all balls lost
if (balls.length === 0) {
if (demoMode) {
resetBallAndLaunch(false);
} else {
lives--;
updateHUD();
if (lives <= 0) {
overlayTitle.textContent = '💔 BURGER OVERCOOKED!';
overlaySub.textContent = `Final Score: ${score.toLocaleString()} • Hearts Unleashed: ${totalHeartsSpawned.toLocaleString()}`;
overlay.classList.remove('hidden');
demoMode = true;
resetBallAndLaunch(false);
} else {
resetBallAndLaunch(false);
}
}
}
// Check if level cleared
if (bricks.every(b => !b.alive)) {
initBricks();
resetBallAndLaunch(false);
spawnHeartExplosion(W / 2, H / 2, ROW_PALETTES[0], 90);
}
// Update Heart Particles
for (let i = hearts.length - 1; i >= 0; i--) {
const h = hearts[i];
h.vx *= h.drag;
h.vy = h.vy * h.drag + h.gravity;
h.wobble += h.wobbleSpeed;
h.x += h.vx + Math.sin(h.wobble) * 0.65;
h.y += h.vy;
h.rot += h.vRot;
h.alpha -= h.decay;
if (h.alpha <= 0 || h.y > H + 30) {
hearts.splice(i, 1);
}
}
// Update Shockwaves
for (let i = shockwaves.length - 1; i >= 0; i--) {
const sw = shockwaves[i];
sw.r += (sw.maxR - sw.r) * 0.18;
sw.alpha -= 0.045;
if (sw.alpha <= 0) shockwaves.splice(i, 1);
}
// Update Floating Texts
for (let i = floatingTexts.length - 1; i >= 0; i--) {
const ft = floatingTexts[i];
ft.y += ft.vy;
ft.alpha -= 0.022;
if (ft.alpha <= 0) floatingTexts.splice(i, 1);
}
// Update Background Ambient Hearts
for (const bh of bgHearts) {
bh.y += bh.vy;
bh.phase += 0.02;
if (bh.y < -30) {
bh.y = H + 30;
bh.x = Math.random() * W;
}
}
}
function draw() {
ctx.clearRect(0, 0, W, H);
ctx.save();
if (screenShake > 0) {
ctx.translate((Math.random() - 0.5) * screenShake, (Math.random() - 0.5) * screenShake);
}
// Ambient floating background hearts
for (const bh of bgHearts) {
ctx.save();
ctx.translate(bh.x + Math.sin(bh.phase) * 12, bh.y);
ctx.fillStyle = `rgba(255, 120, 180, ${bh.alpha})`;
drawHeartPath(ctx, bh.size);
ctx.fill();
ctx.restore();
}
// Draw Bricks with cute embedded heart emblems
const now = performance.now() * 0.004;
for (const br of bricks) {
if (!br.alive) continue;
ctx.save();
// Brick body
const grad = ctx.createLinearGradient(br.x, br.y, br.x, br.y + br.h);
grad.addColorStop(0, br.palette.border);
grad.addColorStop(0.25, br.palette.fill);
grad.addColorStop(1, '#590832');
ctx.fillStyle = grad;
ctx.strokeStyle = br.palette.border;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.roundRect(br.x, br.y, br.w, br.h, 8);
ctx.fill();
ctx.stroke();
// Top shine
ctx.fillStyle = 'rgba(255,255,255,0.28)';
ctx.beginPath();
ctx.roundRect(br.x + 4, br.y + 3, br.w - 8, 6, 4);
ctx.fill();
// Center pulsing heart icon on brick
const pulseScale = 1 + Math.sin(now + br.pulse) * 0.1;
ctx.translate(br.x + br.w / 2, br.y + br.h / 2 - 2);
ctx.scale(pulseScale, pulseScale);
ctx.fillStyle = 'rgba(255, 255, 255, 0.85)';
drawHeartPath(ctx, 11);
ctx.fill();
ctx.restore();
}
// Draw Shockwaves
for (const sw of shockwaves) {
ctx.save();
ctx.strokeStyle = sw.color;
ctx.globalAlpha = Math.max(0, sw.alpha);
ctx.lineWidth = 3;
ctx.beginPath();
ctx.arc(sw.x, sw.y, sw.r, 0, Math.PI * 2);
ctx.stroke();
ctx.restore();
}
// Draw Exploding Heart Particles
for (const h of hearts) {
ctx.save();
ctx.translate(h.x, h.y);
ctx.rotate(h.rot);
ctx.globalAlpha = Math.max(0, h.alpha);
ctx.fillStyle = h.color;
ctx.shadowColor = h.color;
ctx.shadowBlur = 8;
drawHeartPath(ctx, h.size);
ctx.fill();
ctx.restore();
}
// Draw Hamburger Paddle
drawHamburger(paddle);
// Draw Balls with Heart Trails
for (const b of balls) {
for (let i = 0; i < b.trail.length; i++) {
const t = b.trail[i];
const ratio = (i + 1) / b.trail.length;
ctx.save();
ctx.translate(t.x, t.y);
ctx.fillStyle = `rgba(255, 105, 180, ${ratio * 0.45})`;
drawHeartPath(ctx, b.r * 1.4 * ratio);
ctx.fill();
ctx.restore();
}
ctx.save();
const ballGrad = ctx.createRadialGradient(b.x - 3, b.y - 3, 2, b.x, b.y, b.r);
ballGrad.addColorStop(0, '#ffffff');
ballGrad.addColorStop(0.5, '#ff9ebb');
ballGrad.addColorStop(1, '#ff1f6a');
ctx.fillStyle = ballGrad;
ctx.shadowColor = '#ff2a85';
ctx.shadowBlur = 15;
ctx.beginPath();
ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
// Draw Floating Score Popups
for (const ft of floatingTexts) {
ctx.save();
ctx.font = '900 18px "Segoe UI", sans-serif';
ctx.textAlign = 'center';
ctx.globalAlpha = Math.max(0, ft.alpha);
ctx.fillStyle = '#fff';
ctx.shadowColor = '#ff1f6a';
ctx.shadowBlur = 8;
ctx.fillText(ft.text, ft.x, ft.y);
ctx.restore();
}
ctx.restore();
}
function loop() {
update();
draw();
requestAnimationFrame(loop);
}
// Start in lively self-playing demo mode
resetGame(true);
requestAnimationFrame(loop);
</script>
</body>
</html>