Models 44

Gemini 3.8 Flash

by GoogleReasoningClean
Open in new window

Running live in a sandboxed frame. Click inside it before using the keyboard.

How it went

Total time
1 min 6 s
Cost
$0.041
Output tokens
10,915
Spent thinking
471 tokens
Speed
186.8 tok/s
Console errors
0
First token after
7.5 s
First visible text after
7.5 s
Prompt tokens
363
Code size
24.1 KB
Finish reason
stop
Served by
Google
Model id
google/gemini-3.8-flash
Reasoning sent
{"effort":"medium"}
Max output
65,536 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 · 961 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Burger Breakout - Heart Explosion Edition</title>
<style>
  * {
    box-sizing: border-box;
    user-select: none;
    -webkit-user-select: none;
    margin: 0;
    padding: 0;
  }
  body {
    background: radial-gradient(circle at 50% 30%, #2b1238, #11051b);
    color: #fff;
    font-family: 'Segoe UI', Roboto, -apple-system, BlinkMacSystemFont, sans-serif;
    overflow: hidden;
    height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
  }
  #game-container {
    position: relative;
    width: 100%;
    max-width: 900px;
    height: 100%;
    max-height: 720px;
    box-shadow: 0 10px 40px rgba(255, 60, 120, 0.25), 0 0 100px rgba(0,0,0,0.8);
    border-radius: 16px;
    overflow: hidden;
    border: 2px solid rgba(255, 120, 180, 0.2);
    background: #0d0214;
  }
  canvas {
    width: 100%;
    height: 100%;
    display: block;
  }
  #overlay {
    position: absolute;
    inset: 0;
    pointer-events: none;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
    padding: 16px 24px;
  }
  .hud {
    display: flex;
    justify-content: space-between;
    align-items: center;
    font-weight: 800;
    text-shadow: 0 2px 10px rgba(0,0,0,0.8);
  }
  .hud-stat {
    display: flex;
    align-items: center;
    gap: 8px;
    background: rgba(255, 255, 255, 0.08);
    padding: 6px 14px;
    border-radius: 20px;
    backdrop-filter: blur(8px);
    border: 1px solid rgba(255, 255, 255, 0.15);
    font-size: 15px;
    letter-spacing: 0.5px;
  }
  .hud-val {
    color: #ff528c;
    font-size: 18px;
  }
  #banner {
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    text-align: center;
    pointer-events: auto;
    cursor: pointer;
    background: rgba(20, 5, 28, 0.88);
    border: 2px solid rgba(255, 100, 170, 0.5);
    padding: 24px 36px;
    border-radius: 24px;
    box-shadow: 0 0 50px rgba(255, 60, 120, 0.4), inset 0 0 20px rgba(255, 60, 120, 0.2);
    transition: transform 0.2s ease, opacity 0.3s ease;
    backdrop-filter: blur(12px);
  }
  #banner:hover {
    transform: translate(-50%, -50%) scale(1.02);
  }
  #banner.hidden {
    opacity: 0;
    pointer-events: none;
    transform: translate(-50%, -50%) scale(0.9);
  }
  #banner h1 {
    font-size: 32px;
    margin-bottom: 8px;
    background: linear-gradient(45deg, #ffc048, #ff3377, #ff9ff3);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
    text-transform: uppercase;
    letter-spacing: 1px;
    font-weight: 900;
  }
  #banner p {
    color: #e0d0f0;
    font-size: 14px;
    line-height: 1.5;
    margin-bottom: 14px;
  }
  .tag-badge {
    display: inline-block;
    padding: 6px 14px;
    background: #ff2a70;
    color: #fff;
    border-radius: 12px;
    font-size: 13px;
    font-weight: 700;
    animation: pulse 1.5s infinite;
  }
  @keyframes pulse {
    0%, 100% { transform: scale(1); box-shadow: 0 0 10px rgba(255, 42, 112, 0.6); }
    50% { transform: scale(1.05); box-shadow: 0 0 20px rgba(255, 42, 112, 0.9); }
  }
</style>
</head>
<body>

<div id="game-container">
  <canvas id="gameCanvas" width="900" height="720"></canvas>
  <div id="overlay">
    <div class="hud">
      <div class="hud-stat">🍔 BURGER SCORE: <span class="hud-val" id="score-val">0</span></div>
      <div class="hud-stat">💖 COMBO: <span class="hud-val" id="combo-val">x1</span></div>
      <div class="hud-stat">❤️ LIVES: <span class="hud-val" id="lives-val">3</span></div>
    </div>
  </div>
  <div id="banner">
    <h1>🍔 BURGER BREAKOUT 💖</h1>
    <p>Bounce the Tomato with your Juicy Burger Paddle!<br>Every brick explodes into a shower of love!</p>
    <div class="tag-badge" id="banner-btn">CLICK OR PRESS ANY KEY TO PLAY</div>
  </div>
</div>

<script>
// --- AUDIO SYNTHESIS (Zero External Files) ---
const AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx = null;

function initAudio() {
  if (!audioCtx) {
    audioCtx = new AudioContext();
  }
  if (audioCtx.state === 'suspended') {
    audioCtx.resume();
  }
}

function playSound(type) {
  if (!audioCtx) return;
  try {
    const now = audioCtx.currentTime;
    const osc = audioCtx.createOscillator();
    const gain = audioCtx.createGain();
    osc.connect(gain);
    gain.connect(audioCtx.destination);

    if (type === 'paddle') {
      osc.type = 'triangle';
      osc.frequency.setValueAtTime(140, now);
      osc.frequency.exponentialRampToValueAtTime(320, now + 0.12);
      gain.gain.setValueAtTime(0.3, now);
      gain.gain.exponentialRampToValueAtTime(0.001, now + 0.15);
      osc.start(now);
      osc.stop(now + 0.15);
    } else if (type === 'brick') {
      osc.type = 'sine';
      const base = 440 + Math.random() * 200;
      osc.frequency.setValueAtTime(base, now);
      osc.frequency.exponentialRampToValueAtTime(base * 1.8, now + 0.15);
      gain.gain.setValueAtTime(0.25, now);
      gain.gain.exponentialRampToValueAtTime(0.001, now + 0.2);
      osc.start(now);
      osc.stop(now + 0.2);
    } else if (type === 'heart') {
      // Shimmering chime
      osc.type = 'sine';
      const notes = [659.25, 783.99, 987.77, 1174.66, 1318.51];
      const pitch = notes[Math.floor(Math.random() * notes.length)];
      osc.frequency.setValueAtTime(pitch, now);
      gain.gain.setValueAtTime(0.12, now);
      gain.gain.exponentialRampToValueAtTime(0.001, now + 0.25);
      osc.start(now);
      osc.stop(now + 0.25);
    } else if (type === 'lose') {
      osc.type = 'sawtooth';
      osc.frequency.setValueAtTime(280, now);
      osc.frequency.linearRampToValueAtTime(90, now + 0.4);
      gain.gain.setValueAtTime(0.2, now);
      gain.gain.exponentialRampToValueAtTime(0.001, now + 0.4);
      osc.start(now);
      osc.stop(now + 0.4);
    }
  } catch(e) {}
}

// --- CANVAS SETUP ---
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const scoreEl = document.getElementById('score-val');
const comboEl = document.getElementById('combo-val');
const livesEl = document.getElementById('lives-val');
const banner = document.getElementById('banner');

const W = 900;
const H = 720;

let isAutoplay = true;
let score = 0;
let lives = 3;
let combo = 1;
let comboTimer = 0;
let shakeTime = 0;
let shakeAmp = 0;

// --- BURGER PADDLE ---
const paddle = {
  w: 140,
  h: 42,
  x: W / 2 - 70,
  y: H - 75,
  targetX: W / 2 - 70,
  squishX: 1,
  squishY: 1,
  tilt: 0
};

// --- BALL (Cherry Tomato) ---
const balls = [];
function createBall(x, y, vx, vy) {
  return {
    x: x ?? W / 2,
    y: y ?? paddle.y - 20,
    r: 12,
    vx: vx ?? 4.5 * (Math.random() > 0.5 ? 1 : -1),
    vy: vy ?? -6.5,
    trail: []
  };
}
balls.push(createBall());

// --- BRICKS ---
const BRICK_ROWS = 6;
const BRICK_COLS = 10;
const BRICK_W = 74;
const BRICK_H = 26;
const BRICK_GAP = 12;
const BRICK_OFFSET_X = (W - (BRICK_COLS * BRICK_W + (BRICK_COLS - 1) * BRICK_GAP)) / 2;
const BRICK_OFFSET_Y = 70;

const BRICK_THEMES = [
  { name: 'Berry Glaze', top: '#ff3377', bot: '#c0134f', light: '#ff8fb2' },
  { name: 'Cheddar Gold', top: '#ffb930', bot: '#d97b0a', light: '#ffe17d' },
  { name: 'Crisp Lettuce', top: '#38ef7d', bot: '#11998e', light: '#a8ff78' },
  { name: 'Sweet Ketchup', top: '#ff4b4b', bot: '#b81212', light: '#ffa3a3' },
  { name: 'Pickle Lime', top: '#8ee53f', bot: '#4c9b13', light: '#c8f792' },
  { name: 'Toasted Sesame', top: '#e6a86c', bot: '#a06830', light: '#ffddaa' }
];

let bricks = [];

function initBricks() {
  bricks = [];
  for (let r = 0; r < BRICK_ROWS; r++) {
    const theme = BRICK_THEMES[r % BRICK_THEMES.length];
    for (let c = 0; c < BRICK_COLS; c++) {
      bricks.push({
        x: BRICK_OFFSET_X + c * (BRICK_W + BRICK_GAP),
        y: BRICK_OFFSET_Y + r * (BRICK_H + BRICK_GAP),
        w: BRICK_W,
        h: BRICK_H,
        active: true,
        theme: theme,
        hp: (r === 0 ? 2 : 1),
        maxHp: (r === 0 ? 2 : 1),
        wobble: 0
      });
    }
  }
}
initBricks();

// --- HEART PARTICLES ---
const hearts = [];
const heartGradients = [
  ['#ff1493', '#ff69b4'],
  ['#ff0055', '#ff4d88'],
  ['#ff3377', '#ffa3c2'],
  ['#ff4081', '#ffd1dc'],
  ['#e0115f', '#ff729f'],
  ['#ffc0cb', '#ffffff']
];

function spawnHearts(bx, by, count = 35) {
  playSound('brick');
  playSound('heart');
  for (let i = 0; i < count; i++) {
    const angle = Math.random() * Math.PI * 2;
    const speed = 2 + Math.random() * 7.5;
    const size = 7 + Math.random() * 11;
    const palette = heartGradients[Math.floor(Math.random() * heartGradients.length)];
    hearts.push({
      x: bx,
      y: by,
      vx: Math.cos(angle) * speed + (Math.random() - 0.5) * 2,
      vy: Math.sin(angle) * speed - 2.5,
      size: size,
      rot: Math.random() * Math.PI * 2,
      vRot: (Math.random() - 0.5) * 0.25,
      alpha: 1,
      decay: 0.012 + Math.random() * 0.018,
      wobbleSpeed: 4 + Math.random() * 6,
      wobbleAmp: Math.random() * 1.5,
      colorA: palette[0],
      colorB: palette[1],
      sparkle: Math.random() > 0.4
    });
  }
}

// Floaty ambient hearts rising from bottom occasionally
let ambientTimer = 0;
function spawnAmbientHeart() {
  const palette = heartGradients[Math.floor(Math.random() * heartGradients.length)];
  hearts.push({
    x: Math.random() * W,
    y: H + 10,
    vx: (Math.random() - 0.5) * 1.2,
    vy: -(1.5 + Math.random() * 2),
    size: 6 + Math.random() * 8,
    rot: Math.random() * Math.PI,
    vRot: (Math.random() - 0.5) * 0.08,
    alpha: 0.65,
    decay: 0.004,
    wobbleSpeed: 2,
    wobbleAmp: 1,
    colorA: palette[0],
    colorB: palette[1],
    sparkle: false
  });
}

// --- DRAWING UTILS ---

// Draw a smooth cute bezier heart
function drawHeart(c, x, y, size, rot, colorA, colorB, alpha) {
  c.save();
  c.translate(x, y);
  c.rotate(rot);
  c.globalAlpha = Math.max(0, Math.min(1, alpha));

  const grad = c.createLinearGradient(0, -size, 0, size);
  grad.addColorStop(0, colorA);
  grad.addColorStop(1, colorB);
  c.fillStyle = grad;

  c.beginPath();
  const topCurveHeight = size * 0.35;
  c.moveTo(0, size * 0.4);
  // top left
  c.bezierCurveTo(-size * 0.8, -size * 0.2, -size * 0.8, -size * 0.8, 0, -size * 0.35);
  // top right
  c.bezierCurveTo(size * 0.8, -size * 0.8, size * 0.8, -size * 0.2, 0, size * 0.4);
  c.closePath();
  c.fill();

  // Gloss highlight
  c.fillStyle = 'rgba(255,255,255,0.45)';
  c.beginPath();
  c.arc(-size * 0.25, -size * 0.4, size * 0.12, 0, Math.PI * 2);
  c.fill();

  c.restore();
}

// Draw realistic cute hamburger paddle
function drawBurger(c, p) {
  c.save();
  c.translate(p.x + p.w / 2, p.y + p.h / 2);
  c.rotate(p.tilt);
  c.scale(p.squishX, p.squishY);

  const hw = p.w / 2;
  const hh = p.h / 2;

  // Shadow under burger
  c.shadowColor = 'rgba(255, 60, 130, 0.35)';
  c.shadowBlur = 18;
  c.fillStyle = 'rgba(0,0,0,0.3)';
  c.beginPath();
  c.ellipse(0, hh + 6, hw * 0.9, 8, 0, 0, Math.PI * 2);
  c.fill();
  c.shadowBlur = 0;

  // 1. BOTTOM BUN
  const botBunGrad = c.createLinearGradient(0, 4, 0, hh);
  botBunGrad.addColorStop(0, '#e29b52');
  botBunGrad.addColorStop(1, '#b56828');
  c.fillStyle = botBunGrad;
  c.beginPath();
  c.roundRect(-hw + 8, 4, p.w - 16, hh - 2, [4, 4, 14, 14]);
  c.fill();

  // 2. BEEF PATTY
  const pattyGrad = c.createLinearGradient(0, -2, 0, 8);
  pattyGrad.addColorStop(0, '#532314');
  pattyGrad.addColorStop(0.5, '#3b170c');
  pattyGrad.addColorStop(1, '#270c04');
  c.fillStyle = pattyGrad;
  c.beginPath();
  c.roundRect(-hw + 4, -2, p.w - 8, 11, 5);
  c.fill();

  // Grill marks
  c.strokeStyle = '#1e0701';
  c.lineWidth = 2.5;
  for (let i = -hw + 18; i < hw - 10; i += 18) {
    c.beginPath();
    c.moveTo(i, -2);
    c.lineTo(i + 8, 9);
    c.stroke();
  }

  // 3. MELTED CHEESE (Points dripping down)
  c.fillStyle = '#ffb300';
  c.beginPath();
  c.moveTo(-hw + 6, -1);
  c.lineTo(hw - 6, -1);
  c.lineTo(hw - 10, 6);
  // Drippy cheese triangles
  const drips = 7;
  const step = (p.w - 20) / drips;
  for (let i = 0; i <= drips; i++) {
    const cx = (hw - 10) - i * step;
    const cy = (i % 2 === 0) ? 9 : 3;
    c.lineTo(cx, cy);
  }
  c.closePath();
  c.fill();

  // 4. JUICY TOMATO SLICES (2 slices)
  c.fillStyle = '#ff2b2b';
  c.beginPath();
  c.roundRect(-hw + 14, -8, hw - 10, 7, 3);
  c.roundRect(0, -8, hw - 14, 7, 3);
  c.fill();

  // 5. WAVY LETTUCE
  c.fillStyle = '#4cd137';
  c.beginPath();
  c.moveTo(-hw + 2, -7);
  for (let lx = -hw + 2; lx <= hw - 2; lx += 10) {
    c.quadraticCurveTo(lx + 5, -13, lx + 10, -7);
  }
  c.lineTo(hw - 2, -3);
  c.lineTo(-hw + 2, -3);
  c.closePath();
  c.fill();

  // 6. TOP BUN (Golden plump dome)
  const topBunGrad = c.createLinearGradient(0, -hh - 4, 0, -6);
  topBunGrad.addColorStop(0, '#f8b467');
  topBunGrad.addColorStop(0.6, '#e08c35');
  topBunGrad.addColorStop(1, '#c06b1d');
  c.fillStyle = topBunGrad;
  c.beginPath();
  c.moveTo(-hw + 6, -6);
  c.quadraticCurveTo(0, -hh - 12, hw - 6, -6);
  c.quadraticCurveTo(0, -4, -hw + 6, -6);
  c.fill();

  // Top bun shiny specular highlight
  c.fillStyle = 'rgba(255, 255, 255, 0.4)';
  c.beginPath();
  c.ellipse(-hw * 0.25, -hh - 1, hw * 0.35, 4, -0.15, 0, Math.PI * 2);
  c.fill();

  // 7. SESAME SEEDS (Cute little white/cream ovals)
  c.fillStyle = '#fff4db';
  const seeds = [
    [-0.6, -11, 0.2], [-0.35, -16, -0.1], [-0.1, -19, 0.1],
    [0.15, -18, -0.2], [0.4, -14, 0.3], [0.6, -10, -0.15],
    [-0.45, -8, 0.1], [-0.2, -12, -0.3], [0.0, -14, 0.2],
    [0.25, -11, -0.1], [0.45, -7, 0.2]
  ];
  for (let s of seeds) {
    c.save();
    c.translate(s[0] * (hw - 15), s[1]);
    c.rotate(s[2]);
    c.beginPath();
    c.ellipse(0, 0, 3, 1.6, 0, 0, Math.PI * 2);
    c.fill();
    c.restore();
  }

  c.restore();
}

// Draw bouncing Cherry Tomato Ball
function drawBall(c, b) {
  // Trail
  for (let i = 0; i < b.trail.length; i++) {
    const t = b.trail[i];
    const progress = (i + 1) / b.trail.length;
    c.fillStyle = `rgba(255, 60, 100, ${progress * 0.25})`;
    c.beginPath();
    c.arc(t.x, t.y, b.r * progress * 0.8, 0, Math.PI * 2);
    c.fill();
  }

  c.save();
  c.translate(b.x, b.y);

  // Glow
  c.shadowColor = '#ff2a6d';
  c.shadowBlur = 15;

  // Tomato Body
  const grad = c.createRadialGradient(-3, -3, 2, 0, 0, b.r);
  grad.addColorStop(0, '#ff7070');
  grad.addColorStop(0.4, '#ff1a3c');
  grad.addColorStop(1, '#ab051f');
  c.fillStyle = grad;
  c.beginPath();
  c.arc(0, 0, b.r, 0, Math.PI * 2);
  c.fill();
  c.shadowBlur = 0;

  // Tomato stem (little green leaves on top)
  c.fillStyle = '#4cd137';
  for (let ang = 0; ang < Math.PI * 2; ang += (Math.PI * 2) / 4) {
    c.save();
    c.rotate(ang + 0.2);
    c.beginPath();
    c.ellipse(0, -b.r + 2, 2, 5, 0.4, 0, Math.PI * 2);
    c.fill();
    c.restore();
  }
  // Center stem pip
  c.fillStyle = '#2ed573';
  c.beginPath();
  c.arc(0, -b.r + 2, 2.5, 0, Math.PI * 2);
  c.fill();

  // Glossy shine highlight
  c.fillStyle = 'rgba(255, 255, 255, 0.75)';
  c.beginPath();
  c.ellipse(-3.5, -3.5, 3.5, 2, -Math.PI / 4, 0, Math.PI * 2);
  c.fill();

  c.restore();
}

// Draw fancy Brick
function drawBrick(c, b) {
  if (!b.active) return;

  c.save();
  const wobX = Math.sin(b.wobble) * 2;
  const wobY = Math.cos(b.wobble) * 2;
  c.translate(b.x + wobX, b.y + wobY);

  // Gradient body
  const grad = c.createLinearGradient(0, 0, 0, b.h);
  grad.addColorStop(0, b.theme.top);
  grad.addColorStop(1, b.theme.bot);

  c.fillStyle = grad;
  c.shadowColor = b.theme.top;
  c.shadowBlur = b.hp > 1 ? 12 : 6;
  c.beginPath();
  c.roundRect(0, 0, b.w, b.h, 6);
  c.fill();
  c.shadowBlur = 0;

  // Top highlight border
  c.fillStyle = b.theme.light;
  c.beginPath();
  c.roundRect(2, 2, b.w - 4, 4, 2);
  c.fill();

  // Mini heart stamp in center of brick
  c.save();
  c.translate(b.w / 2, b.h / 2);
  c.fillStyle = 'rgba(255, 255, 255, 0.28)';
  c.beginPath();
  const hs = 5;
  c.moveTo(0, hs * 0.4);
  c.bezierCurveTo(-hs * 0.8, -hs * 0.2, -hs * 0.8, -hs * 0.8, 0, -hs * 0.35);
  c.bezierCurveTo(hs * 0.8, -hs * 0.8, hs * 0.8, -hs * 0.2, 0, hs * 0.4);
  c.fill();
  c.restore();

  // Double health indicator
  if (b.hp > 1) {
    c.strokeStyle = 'rgba(255,255,255,0.7)';
    c.lineWidth = 1.5;
    c.strokeRect(4, 4, b.w - 8, b.h - 8);
  }

  c.restore();
}

// --- GAME LOGIC & COLLISION ---

function update(dt) {
  // Combo cooldown
  if (comboTimer > 0) {
    comboTimer -= dt;
    if (comboTimer <= 0) {
      combo = 1;
      comboEl.innerText = 'x1';
    }
  }

  // Screenshake decay
  if (shakeTime > 0) {
    shakeTime -= dt;
  }

  // Paddle squish relaxation
  paddle.squishX += (1 - paddle.squishX) * 0.15;
  paddle.squishY += (1 - paddle.squishY) * 0.15;
  paddle.tilt += (0 - paddle.tilt) * 0.1;

  // Autoplay or user smoothing
  if (isAutoplay) {
    // Intelligent AI tracking nearest ball
    let targetBall = balls[0];
    for (let b of balls) {
      if (b.vy > 0 && b.y > (targetBall ? targetBall.y : 0)) {
        targetBall = b;
      }
    }
    if (targetBall) {
      // Predict ball position slightly
      const predX = targetBall.x + targetBall.vx * 3;
      paddle.targetX = predX - paddle.w / 2;
    }
  }

  // Bound paddle
  paddle.targetX = Math.max(10, Math.min(W - paddle.w - 10, paddle.targetX));
  const prevX = paddle.x;
  paddle.x += (paddle.targetX - paddle.x) * (isAutoplay ? 0.22 : 0.32);
  paddle.tilt = ((paddle.x - prevX) * 0.008);

  // Ball update
  for (let i = balls.length - 1; i >= 0; i--) {
    const b = balls[i];

    // Trail
    b.trail.push({ x: b.x, y: b.y });
    if (b.trail.length > 8) b.trail.shift();

    b.x += b.vx;
    b.y += b.vy;

    // Walls
    if (b.x - b.r < 0) {
      b.x = b.r;
      b.vx = Math.abs(b.vx);
      playSound('paddle');
    } else if (b.x + b.r > W) {
      b.x = W - b.r;
      b.vx = -Math.abs(b.vx);
      playSound('paddle');
    }
    if (b.y - b.r < 0) {
      b.y = b.r;
      b.vy = Math.abs(b.vy);
      playSound('paddle');
    }

    // Paddle hit
    if (
      b.y + b.r >= paddle.y &&
      b.y - b.r <= paddle.y + paddle.h &&
      b.x >= paddle.x - b.r * 0.5 &&
      b.x <= paddle.x + paddle.w + b.r * 0.5 &&
      b.vy > 0
    ) {
      b.vy = -Math.abs(b.vy);
      b.y = paddle.y - b.r;

      // Angled bounce based on paddle hit location
      const hitOffset = (b.x - (paddle.x + paddle.w / 2)) / (paddle.w / 2);
      const speed = Math.hypot(b.vx, b.vy);
      const angle = hitOffset * (Math.PI / 3.2); // max 56 deg
      b.vx = speed * Math.sin(angle);
      b.vy = -speed * Math.cos(angle);

      // Squish burger paddle
      paddle.squishX = 1.25;
      paddle.squishY = 0.75;
      playSound('paddle');

      // Heart burst on paddle bounce
      for (let k = 0; k < 6; k++) {
        hearts.push({
          x: b.x + (Math.random() - 0.5) * 10,
          y: paddle.y,
          vx: (Math.random() - 0.5) * 4,
          vy: -(2 + Math.random() * 3),
          size: 6 + Math.random() * 5,
          rot: Math.random() * Math.PI,
          vRot: (Math.random() - 0.5) * 0.2,
          alpha: 0.9,
          decay: 0.03,
          wobbleSpeed: 4,
          wobbleAmp: 1,
          colorA: '#ffc048',
          colorB: '#ff3377',
          sparkle: true
        });
      }
    }

    // Brick collisions
    for (let br of bricks) {
      if (!br.active) continue;

      // Closest point on brick to ball center
      const closestX = Math.max(br.x, Math.min(b.x, br.x + br.w));
      const closestY = Math.max(br.y, Math.min(b.y, br.y + br.h));
      const distX = b.x - closestX;
      const distY = b.y - closestY;
      const distSq = distX * distX + distY * distY;

      if (distSq < b.r * b.r) {
        // Collision!
        br.hp--;
        br.wobble = 1.0;

        // Bounce response
        const overlapX = (br.w / 2 + b.r) - Math.abs(b.x - (br.x + br.w / 2));
        const overlapY = (br.h / 2 + b.r) - Math.abs(b.y - (br.y + br.h / 2));
        if (overlapX < overlapY) {
          b.vx *= -1;
        } else {
          b.vy *= -1;
        }

        // COMBO & SCORE
        score += 100 * combo;
        combo = Math.min(10, combo + 1);
        comboTimer = 2.4;
        scoreEl.innerText = score;
        comboEl.innerText = `x${combo} 🔥`;

        // Explode into hearts!
        const heartCount = (br.hp <= 0) ? 35 : 12;
        spawnHearts(br.x + br.w / 2, br.y + br.h / 2, heartCount);

        shakeTime = 0.12;
        shakeAmp = 4;

        if (br.hp <= 0) {
          br.active = false;
        }
        break; // One brick hit per ball frame
      }
    }

    // Ball fell below screen
    if (b.y - b.r > H) {
      balls.splice(i, 1);
    }
  }

  // Check if all balls lost
  if (balls.length === 0) {
    lives--;
    livesEl.innerText = lives;
    playSound('lose');
    if (lives <= 0) {
      // Reset
      lives = 3;
      score = 0;
      combo = 1;
      scoreEl.innerText = score;
      comboEl.innerText = 'x1';
      livesEl.innerText = lives;
      initBricks();
    }
    balls.push(createBall());
  }

  // Check if all bricks cleared
  const remaining = bricks.filter(b => b.active).length;
  if (remaining === 0) {
    initBricks();
    balls.push(createBall(W / 2, paddle.y - 20, 5, -7));
  }

  // Update Hearts
  for (let i = hearts.length - 1; i >= 0; i--) {
    const h = hearts[i];
    h.x += h.vx + Math.sin(h.rot * h.wobbleSpeed) * h.wobbleAmp;
    h.y += h.vy;
    h.vy += 0.14; // gentle gravity
    h.vx *= 0.985;
    h.rot += h.vRot;
    h.alpha -= h.decay;

    if (h.alpha <= 0 || h.y > H + 50) {
      hearts.splice(i, 1);
    }
  }

  // Ambient floaters
  ambientTimer += dt;
  if (ambientTimer > 0.4) {
    ambientTimer = 0;
    spawnAmbientHeart();
  }
}

// --- RENDER LOOP ---
let lastTime = performance.now();

function render() {
  const now = performance.now();
  const dt = Math.min((now - lastTime) / 1000, 0.1);
  lastTime = now;

  update(dt);

  ctx.save();

  // Screen shake
  if (shakeTime > 0) {
    const rx = (Math.random() - 0.5) * shakeAmp;
    const ry = (Math.random() - 0.5) * shakeAmp;
    ctx.translate(rx, ry);
  }

  // Background
  ctx.fillStyle = '#0f0217';
  ctx.fillRect(0, 0, W, H);

  // Subtle background heart pattern/grid
  ctx.fillStyle = 'rgba(255, 60, 140, 0.02)';
  for (let x = 30; x < W; x += 60) {
    for (let y = 30; y < H; y += 60) {
      ctx.fillRect(x, y, 2, 2);
    }
  }

  // Draw Bricks
  for (let b of bricks) {
    drawBrick(ctx, b);
  }

  // Draw Hearts (explosions & sparkles)
  for (let h of hearts) {
    drawHeart(ctx, h.x, h.y, h.size, h.rot, h.colorA, h.colorB, h.alpha);
    if (h.sparkle && h.alpha > 0.3) {
      ctx.fillStyle = `rgba(255, 255, 255, ${h.alpha * 0.8})`;
      ctx.beginPath();
      ctx.arc(h.x + (Math.sin(h.rot) * h.size * 0.6), h.y, 1.5, 0, Math.PI * 2);
      ctx.fill();
    }
  }

  // Draw Balls
  for (let b of balls) {
    drawBall(ctx, b);
  }

  // Draw Hamburger Paddle
  drawBurger(ctx, paddle);

  ctx.restore();

  requestAnimationFrame(render);
}

// --- USER INPUT & DEMO TOGGLE ---

function startGame() {
  initAudio();
  if (isAutoplay) {
    isAutoplay = false;
    banner.classList.add('hidden');
    // Fresh ball bounce on user start
    if (balls.length > 0) {
      balls[0].vy = -Math.abs(balls[0].vy);
    }
  }
}

// Mouse / Pointer Controls
window.addEventListener('mousemove', (e) => {
  const rect = canvas.getBoundingClientRect();
  const scaleX = W / rect.width;
  const mouseX = (e.clientX - rect.left) * scaleX;
  if (!isAutoplay) {
    paddle.targetX = mouseX - paddle.w / 2;
  }
});

// Touch Controls
window.addEventListener('touchmove', (e) => {
  if (e.touches.length > 0) {
    const rect = canvas.getBoundingClientRect();
    const scaleX = W / rect.width;
    const touchX = (e.touches[0].clientX - rect.left) * scaleX;
    if (!isAutoplay) {
      paddle.targetX = touchX - paddle.w / 2;
    }
  }
}, { passive: true });

// Keyboard Controls
const keys = {};
window.addEventListener('keydown', (e) => {
  initAudio();
  keys[e.code] = true;
  startGame();
});
window.addEventListener('keyup', (e) => {
  keys[e.code] = false;
});

// Key movement interval
setInterval(() => {
  if (!isAutoplay) {
    if (keys['ArrowLeft'] || keys['KeyA']) {
      paddle.targetX -= 18;
    }
    if (keys['ArrowRight'] || keys['KeyD']) {
      paddle.targetX += 18;
    }
  }
}, 16);

// Banner / Canvas click
banner.addEventListener('click', startGame);
canvas.addEventListener('click', startGame);

// Start render loop immediately (Autoplay demo ready on frame 1)
requestAnimationFrame(render);

</script>
</body>
</html>