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 18 s
Cost
$0.044
Output tokens
11,691
Spent thinking
708 tokens
Speed
154.7 tok/s
Console errors
0
First token after
2.2 s
First visible text after
8.9 s
Prompt tokens
363
Code size
28.1 KB
Finish reason
stop
Served by
Google
Model id
google/gemini-3.8-flash
Reasoning sent
model default
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 · 1030 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 - Heart Explosion Edition</title>
  <style>
    * {
      box-sizing: border-box;
      margin: 0;
      padding: 0;
      user-select: none;
      -webkit-user-select: none;
    }

    body {
      background: radial-gradient(circle at center, #2b1329 0%, #150918 100%);
      font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
      overflow: hidden;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      min-height: 100vh;
      color: #fff;
    }

    #game-container {
      position: relative;
      width: 100%;
      max-width: 900px;
      aspect-ratio: 4 / 3;
      max-height: 94vh;
      display: flex;
      justify-content: center;
      align-items: center;
      box-shadow: 0 20px 50px rgba(0, 0, 0, 0.7), 0 0 30px rgba(255, 105, 180, 0.2);
      border-radius: 16px;
      overflow: hidden;
      border: 3px solid rgba(255, 182, 193, 0.3);
      background: #110515;
    }

    canvas {
      width: 100%;
      height: 100%;
      display: block;
    }

    #overlay-banner {
      position: absolute;
      bottom: 24px;
      left: 50%;
      transform: translateX(-50%);
      background: rgba(255, 20, 147, 0.85);
      backdrop-filter: blur(8px);
      border: 2px solid #fff;
      padding: 10px 24px;
      border-radius: 30px;
      font-size: 16px;
      font-weight: 800;
      letter-spacing: 1px;
      text-transform: uppercase;
      box-shadow: 0 4px 20px rgba(255, 20, 147, 0.6);
      pointer-events: none;
      animation: pulseBanner 1.6s ease-in-out infinite;
      z-index: 20;
      display: flex;
      align-items: center;
      gap: 10px;
      white-space: nowrap;
    }

    #overlay-banner span.tag {
      background: #ffe600;
      color: #331500;
      padding: 3px 8px;
      border-radius: 12px;
      font-size: 12px;
      font-weight: 900;
    }

    @keyframes pulseBanner {
      0%, 100% {
        transform: translateX(-50%) scale(1);
        box-shadow: 0 0 15px rgba(255, 105, 180, 0.7);
      }
      50% {
        transform: translateX(-50%) scale(1.05);
        box-shadow: 0 0 28px rgba(255, 20, 147, 1);
      }
    }

    @media (max-width: 600px) {
      #overlay-banner {
        font-size: 12px;
        padding: 6px 16px;
        bottom: 12px;
      }
    }
  </style>
</head>
<body>

  <div id="game-container">
    <canvas id="gameCanvas" width="800" height="600"></canvas>
    <div id="overlay-banner">
      <span class="tag">AI AUTO-PLAY</span>
      <span id="banner-text">CLICK OR PRESS ANY KEY TO PLAY</span>
    </div>
  </div>

<script>
(() => {
  const canvas = document.getElementById('gameCanvas');
  const ctx = canvas.getContext('2d');
  const banner = document.getElementById('overlay-banner');
  const bannerText = document.getElementById('banner-text');

  // --- Audio Synthesis (Web Audio API) ---
  let audioCtx = null;
  function initAudio() {
    if (!audioCtx) {
      audioCtx = new (window.AudioContext || window.webkitAudioContext)();
    }
  }

  function playSound(type, pitchModifier = 1) {
    if (!audioCtx || audioCtx.state !== 'running') return;
    try {
      const now = audioCtx.currentTime;
      const osc = audioCtx.createOscillator();
      const gain = audioCtx.createGain();
      osc.connect(gain);
      gain.connect(audioCtx.destination);

      if (type === 'bounce') {
        osc.type = 'triangle';
        osc.frequency.setValueAtTime(260 * pitchModifier, now);
        osc.frequency.exponentialRampToValueAtTime(120 * pitchModifier, now + 0.12);
        gain.gain.setValueAtTime(0.25, now);
        gain.gain.linearRampToValueAtTime(0.01, now + 0.12);
        osc.start(now);
        osc.stop(now + 0.13);
      } else if (type === 'brick') {
        osc.type = 'sine';
        const startFreq = (400 + Math.random() * 200) * pitchModifier;
        osc.frequency.setValueAtTime(startFreq, now);
        osc.frequency.exponentialRampToValueAtTime(startFreq * 2.2, now + 0.15);
        gain.gain.setValueAtTime(0.22, now);
        gain.gain.exponentialRampToValueAtTime(0.005, now + 0.18);
        osc.start(now);
        osc.stop(now + 0.19);
      } else if (type === 'chime') {
        osc.type = 'sine';
        osc.frequency.setValueAtTime((800 + Math.random() * 400) * pitchModifier, now);
        gain.gain.setValueAtTime(0.12, now);
        gain.gain.exponentialRampToValueAtTime(0.001, now + 0.3);
        osc.start(now);
        osc.stop(now + 0.31);
      } else if (type === 'cheer') {
        osc.type = 'triangle';
        osc.frequency.setValueAtTime(523.25, now);
        osc.frequency.setValueAtTime(659.25, now + 0.08);
        osc.frequency.setValueAtTime(783.99, now + 0.16);
        osc.frequency.setValueAtTime(1046.5, now + 0.24);
        gain.gain.setValueAtTime(0.25, now);
        gain.gain.linearRampToValueAtTime(0.01, now + 0.45);
        osc.start(now);
        osc.stop(now + 0.46);
      }
    } catch(e) {}
  }

  // --- Game State ---
  let isDemo = true;
  let score = 0;
  let combo = 0;
  let heartsCollected = 0;
  let lives = 3;
  let level = 1;
  let screenShake = 0;

  // Paddle (The Burger)
  const paddle = {
    x: 350,
    y: 530,
    width: 120,
    height: 38,
    vx: 0,
    targetX: 350,
    squash: 1, // squash/stretch deformation
    stretch: 1,
    tilt: 0
  };

  // Ball (Juicy Cherry Tomato / Love Ball)
  const ball = {
    x: 400,
    y: 400,
    radius: 10,
    vx: 4.5,
    vy: -5.5,
    speed: 7.2,
    trail: []
  };

  // Particle systems
  const heartParticles = [];
  const floatingTexts = [];

  // Brick Grid
  const BRICK_ROWS = 5;
  const BRICK_COLS = 9;
  const BRICK_WIDTH = 74;
  const BRICK_HEIGHT = 26;
  const BRICK_PADDING = 9;
  const BRICK_OFFSET_TOP = 65;
  const BRICK_OFFSET_LEFT = 31;

  const ROW_PALETTES = [
    { fill: '#ff4d79', stroke: '#ff1a53', glow: '#ff3366', name: 'Strawberry' },
    { fill: '#ff85b3', stroke: '#ff4d94', glow: '#ff66a3', name: 'Bubblegum' },
    { fill: '#b86bff', stroke: '#9d3bff', glow: '#a84dff', name: 'Grape' },
    { fill: '#ffb347', stroke: '#ff941a', glow: '#ffa32b', name: 'Peach' },
    { fill: '#ffd166', stroke: '#ffbf2b', glow: '#ffc947', name: 'Honey' }
  ];

  let bricks = [];

  function createBricks() {
    bricks = [];
    for (let r = 0; r < BRICK_ROWS; r++) {
      bricks[r] = [];
      const pal = ROW_PALETTES[r % ROW_PALETTES.length];
      for (let c = 0; c < BRICK_COLS; c++) {
        bricks[r][c] = {
          x: BRICK_OFFSET_LEFT + c * (BRICK_WIDTH + BRICK_PADDING),
          y: BRICK_OFFSET_TOP + r * (BRICK_HEIGHT + BRICK_PADDING),
          w: BRICK_WIDTH,
          h: BRICK_HEIGHT,
          alive: true,
          color: pal.fill,
          stroke: pal.stroke,
          glow: pal.glow,
          hp: 1,
          pulse: Math.random() * Math.PI * 2
        };
      }
    }
  }

  createBricks();

  // Floating ambient background hearts
  const bgHearts = [];
  for (let i = 0; i < 25; i++) {
    bgHearts.push({
      x: Math.random() * 800,
      y: Math.random() * 600,
      size: 6 + Math.random() * 12,
      speed: 0.3 + Math.random() * 0.7,
      wobble: Math.random() * Math.PI * 2,
      alpha: 0.15 + Math.random() * 0.25
    });
  }

  // --- Helper: Draw Heart Shape ---
  function drawHeartPath(ctx, x, y, size) {
    const topCurveHeight = size * 0.3;
    ctx.beginPath();
    ctx.moveTo(x, y + topCurveHeight);
    // Left curve
    ctx.bezierCurveTo(
      x, y,
      x - size / 2, y,
      x - size / 2, y + topCurveHeight
    );
    ctx.bezierCurveTo(
      x - size / 2, y + (size + topCurveHeight) / 2,
      x, y + (size + topCurveHeight) / 1.3,
      x, y + size
    );
    // Right curve
    ctx.bezierCurveTo(
      x, y + (size + topCurveHeight) / 1.3,
      x + size / 2, y + (size + topCurveHeight) / 2,
      x + size / 2, y + topCurveHeight
    );
    ctx.bezierCurveTo(
      x + size / 2, y,
      x, y,
      x, y + topCurveHeight
    );
    ctx.closePath();
  }

  // --- Explode Brick Into Tons of Hearts ---
  function explodeBrick(brick) {
    const heartCount = 35 + Math.floor(Math.random() * 16); // 35 to 50 hearts per brick!
    heartsCollected += heartCount;
    combo++;
    const points = 100 * combo;
    score += points;

    // Trigger Screen Shake
    screenShake = Math.min(screenShake + 5, 12);

    // Audio
    playSound('brick', 1 + (combo % 8) * 0.12);
    playSound('chime', 1 + (combo % 6) * 0.15);

    const heartColors = [
      '#ff1493', '#ff69b4', '#ff3366', '#ff85b3',
      '#ff0055', '#ffffff', '#ffd1dc', '#ff4081', '#ffcc00'
    ];

    const praiseWords = ['SWEET!', 'DELICIOUS!', 'YUM!', 'BURGER LOVE!', 'TASTY!', 'CRUNCH!', 'HEARTY!'];
    const word = (combo > 2 && Math.random() < 0.6) ? praiseWords[Math.floor(Math.random() * praiseWords.length)] : null;

    if (word) {
      floatingTexts.push({
        text: `${word} +${points}`,
        x: brick.x + brick.w / 2,
        y: brick.y,
        alpha: 1.0,
        scale: 1.4,
        vy: -1.6
      });
    } else {
      floatingTexts.push({
        text: `+${points}`,
        x: brick.x + brick.w / 2,
        y: brick.y,
        alpha: 1.0,
        scale: 1.1,
        vy: -1.3
      });
    }

    // Spawn massive cloud of hearts
    for (let i = 0; i < heartCount; i++) {
      const angle = Math.random() * Math.PI * 2;
      const speed = 1.5 + Math.random() * 7.5;
      const size = 6 + Math.random() * 16;
      heartParticles.push({
        x: brick.x + brick.w / 2 + (Math.random() - 0.5) * (brick.w * 0.6),
        y: brick.y + brick.h / 2 + (Math.random() - 0.5) * (brick.h * 0.6),
        vx: Math.cos(angle) * speed,
        vy: Math.sin(angle) * speed - (2 + Math.random() * 2), // initial upward pop
        gravity: 0.13 + Math.random() * 0.08,
        friction: 0.965,
        size: size,
        rotation: Math.random() * Math.PI * 2,
        rotSpeed: (Math.random() - 0.5) * 0.25,
        color: heartColors[Math.floor(Math.random() * heartColors.length)],
        life: 1.0,
        decay: 0.012 + Math.random() * 0.014,
        pulseSpeed: 3 + Math.random() * 4,
        shimmer: Math.random() > 0.4
      });
    }
  }

  // --- Render Hamburger Paddle ---
  function drawHamburger(x, y, width, height, squash, stretch, tilt) {
    ctx.save();
    ctx.translate(x + width / 2, y + height / 2);
    ctx.rotate(tilt);
    ctx.scale(squash, stretch);
    ctx.translate(-(width / 2), -(height / 2));

    const w = width;
    const h = height;

    // Shadow underneath burger
    ctx.fillStyle = 'rgba(0, 0, 0, 0.35)';
    ctx.beginPath();
    ctx.ellipse(w / 2, h + 8, w * 0.5, 7, 0, 0, Math.PI * 2);
    ctx.fill();

    // 1. Bottom Bun
    const bottomBunH = h * 0.24;
    const bottomBunY = h - bottomBunH;
    const botGrad = ctx.createLinearGradient(0, bottomBunY, 0, h);
    botGrad.addColorStop(0, '#f2a953');
    botGrad.addColorStop(1, '#b86618');
    ctx.fillStyle = botGrad;
    ctx.beginPath();
    ctx.roundRect(4, bottomBunY, w - 8, bottomBunH, [2, 2, 9, 9]);
    ctx.fill();
    ctx.lineWidth = 1.5;
    ctx.strokeStyle = '#7c3905';
    ctx.stroke();

    // 2. Patty (Meat)
    const pattyH = h * 0.28;
    const pattyY = bottomBunY - pattyH + 3;
    const pattyGrad = ctx.createLinearGradient(0, pattyY, 0, pattyY + pattyH);
    pattyGrad.addColorStop(0, '#592913');
    pattyGrad.addColorStop(0.5, '#401a08');
    pattyGrad.addColorStop(1, '#2c1003');
    ctx.fillStyle = pattyGrad;
    ctx.beginPath();
    ctx.roundRect(1, pattyY, w - 2, pattyH, 6);
    ctx.fill();

    // Patty grill marks
    ctx.strokeStyle = '#1e0900';
    ctx.lineWidth = 2.5;
    for (let gx = 12; gx < w - 10; gx += 16) {
      ctx.beginPath();
      ctx.moveTo(gx, pattyY + 2);
      ctx.lineTo(gx + 6, pattyY + pattyH - 2);
      ctx.stroke();
    }

    // 3. Melted Cheese
    const cheeseY = pattyY - 2;
    ctx.fillStyle = '#ffc800';
    ctx.beginPath();
    ctx.moveTo(3, cheeseY);
    ctx.lineTo(w - 3, cheeseY);
    ctx.lineTo(w - 7, cheeseY + 9);
    // Cheese drips/drapes over patty
    ctx.lineTo(w * 0.78, cheeseY + 12);
    ctx.lineTo(w * 0.70, cheeseY + 4);
    ctx.lineTo(w * 0.52, cheeseY + 14);
    ctx.lineTo(w * 0.40, cheeseY + 5);
    ctx.lineTo(w * 0.24, cheeseY + 13);
    ctx.lineTo(w * 0.14, cheeseY + 6);
    ctx.lineTo(5, cheeseY + 8);
    ctx.closePath();
    ctx.fill();
    ctx.strokeStyle = '#e69500';
    ctx.lineWidth = 1;
    ctx.stroke();

    // 4. Tomato Slices (2 slices peeking through)
    const tomatoY = cheeseY - 4;
    ctx.fillStyle = '#ff3333';
    ctx.beginPath();
    ctx.ellipse(w * 0.32, tomatoY + 2, w * 0.22, 6, 0.05, 0, Math.PI * 2);
    ctx.ellipse(w * 0.68, tomatoY + 2, w * 0.22, 6, -0.05, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#ff6666'; // Tomato gloss
    ctx.beginPath();
    ctx.ellipse(w * 0.32 - 4, tomatoY, w * 0.12, 2.5, 0.05, 0, Math.PI * 2);
    ctx.ellipse(w * 0.68 - 4, tomatoY, w * 0.12, 2.5, -0.05, 0, Math.PI * 2);
    ctx.fill();

    // 5. Crispy Wavy Lettuce
    const lettuceY = tomatoY - 2;
    ctx.fillStyle = '#4ade80';
    ctx.strokeStyle = '#22c55e';
    ctx.lineWidth = 1.5;
    ctx.beginPath();
    ctx.moveTo(0, lettuceY + 4);
    for (let lx = 0; lx <= w; lx += 10) {
      const ly = lettuceY + Math.sin(lx * 0.45) * 4.5;
      ctx.lineTo(lx, ly);
    }
    ctx.lineTo(w, lettuceY + 7);
    ctx.lineTo(0, lettuceY + 7);
    ctx.closePath();
    ctx.fill();
    ctx.stroke();

    // 6. Plump Top Bun
    const topBunH = h * 0.52;
    const topBunY = lettuceY - topBunH + 4;
    const topGrad = ctx.createLinearGradient(0, topBunY, 0, topBunY + topBunH);
    topGrad.addColorStop(0, '#f9be6d');
    topGrad.addColorStop(0.4, '#e79438');
    topGrad.addColorStop(1, '#b96515');

    ctx.fillStyle = topGrad;
    ctx.beginPath();
    // Round bun dome
    ctx.moveTo(2, topBunY + topBunH);
    ctx.bezierCurveTo(
      -2, topBunY - 4,
      w + 2, topBunY - 4,
      w - 2, topBunY + topBunH
    );
    ctx.closePath();
    ctx.fill();
    ctx.lineWidth = 1.5;
    ctx.strokeStyle = '#8a4004';
    ctx.stroke();

    // Bun Gloss Highlight
    ctx.fillStyle = 'rgba(255, 255, 255, 0.35)';
    ctx.beginPath();
    ctx.ellipse(w * 0.48, topBunY + topBunH * 0.3, w * 0.32, 5, -0.08, 0, Math.PI * 2);
    ctx.fill();

    // 7. Sesame Seeds on Bun
    const seeds = [
      { x: 0.22, y: 0.35, rot: 0.2 },
      { x: 0.35, y: 0.22, rot: -0.3 },
      { x: 0.50, y: 0.16, rot: 0.1 },
      { x: 0.65, y: 0.24, rot: 0.4 },
      { x: 0.78, y: 0.36, rot: -0.2 },
      { x: 0.28, y: 0.50, rot: 0.5 },
      { x: 0.44, y: 0.42, rot: -0.1 },
      { x: 0.58, y: 0.44, rot: 0.3 },
      { x: 0.72, y: 0.52, rot: -0.4 }
    ];

    ctx.fillStyle = '#fff9db';
    ctx.strokeStyle = '#d4aa55';
    ctx.lineWidth = 0.5;
    for (let s of seeds) {
      ctx.save();
      ctx.translate(w * s.x, topBunY + topBunH * s.y);
      ctx.rotate(s.rot);
      ctx.beginPath();
      ctx.ellipse(0, 0, 3.2, 1.8, 0, 0, Math.PI * 2);
      ctx.fill();
      ctx.stroke();
      ctx.restore();
    }

    ctx.restore();
  }

  // --- Reset Ball After Loss ---
  function resetBall() {
    ball.x = paddle.x + paddle.width / 2;
    ball.y = paddle.y - 18;
    const angle = -Math.PI / 2 + (Math.random() - 0.5) * 0.8;
    ball.speed = 7.2;
    ball.vx = Math.cos(angle) * ball.speed;
    ball.vy = Math.sin(angle) * ball.speed;
    ball.trail = [];
    combo = 0;
  }

  // --- Start Real Player Mode ---
  function enterPlayerMode() {
    if (isDemo) {
      isDemo = false;
      banner.style.display = 'none';
      initAudio();
      score = 0;
      heartsCollected = 0;
      combo = 0;
      lives = 3;
      createBricks();
      resetBall();
    }
  }

  window.addEventListener('keydown', (e) => {
    initAudio();
    if (isDemo) {
      enterPlayerMode();
    }
    if (e.key === 'ArrowLeft' || e.key === 'a') {
      paddle.targetX = Math.max(0, paddle.targetX - 35);
    } else if (e.key === 'ArrowRight' || e.key === 'd') {
      paddle.targetX = Math.min(canvas.width - paddle.width, paddle.targetX + 35);
    }
  });

  window.addEventListener('mousedown', () => {
    initAudio();
    if (isDemo) enterPlayerMode();
  });

  window.addEventListener('touchstart', () => {
    initAudio();
    if (isDemo) enterPlayerMode();
  }, { passive: true });

  canvas.addEventListener('mousemove', (e) => {
    if (isDemo) return;
    const rect = canvas.getBoundingClientRect();
    const scaleX = canvas.width / rect.width;
    const mouseX = (e.clientX - rect.left) * scaleX;
    paddle.targetX = Math.max(0, Math.min(canvas.width - paddle.width, mouseX - paddle.width / 2));
  });

  canvas.addEventListener('touchmove', (e) => {
    if (isDemo) return;
    if (e.touches.length > 0) {
      const rect = canvas.getBoundingClientRect();
      const scaleX = canvas.width / rect.width;
      const touchX = (e.touches[0].clientX - rect.left) * scaleX;
      paddle.targetX = Math.max(0, Math.min(canvas.width - paddle.width, touchX - paddle.width / 2));
    }
  }, { passive: true });

  // --- Main Update Loop ---
  let lastTime = performance.now();

  function update() {
    // 1. AI Logic (for Demo Mode)
    if (isDemo) {
      // Natural prediction of ball X
      const destX = ball.x - paddle.width / 2;
      paddle.targetX += (destX - paddle.targetX) * 0.16;
    }

    // Smooth Paddle Movement
    const prevPaddleX = paddle.x;
    paddle.x += (paddle.targetX - paddle.x) * 0.28;
    paddle.vx = paddle.x - prevPaddleX;
    paddle.tilt = Math.max(-0.25, Math.min(0.25, paddle.vx * 0.02));

    // Restore squash / stretch gradually
    paddle.squash += (1 - paddle.squash) * 0.15;
    paddle.stretch += (1 - paddle.stretch) * 0.15;

    // Constrain paddle
    paddle.x = Math.max(0, Math.min(canvas.width - paddle.width, paddle.x));

    // Ball Trails
    ball.trail.push({ x: ball.x, y: ball.y, alpha: 0.6 });
    if (ball.trail.length > 8) ball.trail.shift();
    ball.trail.forEach(t => t.alpha *= 0.82);

    // Ball Movement
    ball.x += ball.vx;
    ball.y += ball.vy;

    // Ball Wall Collisions
    if (ball.x - ball.radius <= 0) {
      ball.x = ball.radius;
      ball.vx = Math.abs(ball.vx);
      playSound('bounce', 1.4);
    } else if (ball.x + ball.radius >= canvas.width) {
      ball.x = canvas.width - ball.radius;
      ball.vx = -Math.abs(ball.vx);
      playSound('bounce', 1.4);
    }

    if (ball.y - ball.radius <= 0) {
      ball.y = ball.radius;
      ball.vy = Math.abs(ball.vy);
      playSound('bounce', 1.4);
    }

    // Ball Bottom Out
    if (ball.y - ball.radius > canvas.height) {
      if (isDemo) {
        resetBall();
      } else {
        lives--;
        screenShake = 15;
        if (lives <= 0) {
          // Game Over -> Reset
          score = 0;
          lives = 3;
          createBricks();
        }
        resetBall();
      }
    }

    // Paddle Collision (The Burger Bounce!)
    const burgerTop = paddle.y;
    const burgerBottom = paddle.y + paddle.height;
    const burgerLeft = paddle.x;
    const burgerRight = paddle.x + paddle.width;

    if (
      ball.y + ball.radius >= burgerTop &&
      ball.y - ball.radius <= burgerBottom &&
      ball.x + ball.radius >= burgerLeft &&
      ball.x - ball.radius <= burgerRight &&
      ball.vy > 0
    ) {
      ball.y = burgerTop - ball.radius;
      // Hit point on burger determines angle (-1 to 1)
      const hitOffset = (ball.x - (paddle.x + paddle.width / 2)) / (paddle.width / 2);
      const angle = hitOffset * (Math.PI / 3); // Max 60 deg angle

      ball.speed = Math.min(10.5, ball.speed + 0.08); // Slight speed boost
      ball.vx = Math.sin(angle) * ball.speed + paddle.vx * 0.25;
      ball.vy = -Math.cos(angle) * ball.speed;

      // Squash burger on impact!
      paddle.squash = 1.25;
      paddle.stretch = 0.72;

      combo = 0; // Combo resets on paddle hit
      playSound('bounce', 1.1);

      // Cute burger sparkle bursts
      for (let s = 0; s < 7; s++) {
        heartParticles.push({
          x: ball.x,
          y: burgerTop,
          vx: (Math.random() - 0.5) * 5,
          vy: -Math.random() * 3.5 - 1,
          gravity: 0.1,
          friction: 0.94,
          size: 5 + Math.random() * 7,
          rotation: Math.random() * Math.PI,
          rotSpeed: 0.1,
          color: '#ffe066',
          life: 0.8,
          decay: 0.03,
          pulseSpeed: 4
        });
      }
    }

    // Brick Collisions
    let aliveCount = 0;
    for (let r = 0; r < BRICK_ROWS; r++) {
      for (let c = 0; c < BRICK_COLS; c++) {
        const b = bricks[r][c];
        if (!b.alive) continue;
        aliveCount++;

        b.pulse += 0.05;

        // AABB vs Circle
        const closestX = Math.max(b.x, Math.min(ball.x, b.x + b.w));
        const closestY = Math.max(b.y, Math.min(ball.y, b.y + b.h));
        const distX = ball.x - closestX;
        const distY = ball.y - closestY;
        const distSq = distX * distX + distY * distY;

        if (distSq < ball.radius * ball.radius) {
          // Explode the brick!
          b.alive = false;
          explodeBrick(b);

          // Determine bounce direction
          const prevX = ball.x - ball.vx;
          const prevY = ball.y - ball.vy;

          if (prevX <= b.x || prevX >= b.x + b.w) {
            ball.vx = -ball.vx;
          } else {
            ball.vy = -ball.vy;
          }
          break;
        }
      }
    }

    // Recreate bricks if all cleared
    if (aliveCount === 0) {
      playSound('cheer');
      level++;
      createBricks();
      resetBall();
    }

    // Update Hearts Particles
    for (let i = heartParticles.length - 1; i >= 0; i--) {
      const p = heartParticles[i];
      p.x += p.vx;
      p.y += p.vy;
      p.vx *= p.friction;
      p.vy *= p.friction;
      p.vy += p.gravity;
      p.rotation += p.rotSpeed;
      p.life -= p.decay;

      if (p.life <= 0) {
        heartParticles.splice(i, 1);
      }
    }

    // Update Floating Text
    for (let i = floatingTexts.length - 1; i >= 0; i--) {
      const t = floatingTexts[i];
      t.y += t.vy;
      t.alpha -= 0.02;
      t.scale += 0.005;
      if (t.alpha <= 0) {
        floatingTexts.splice(i, 1);
      }
    }

    // Background floating hearts
    for (let h of bgHearts) {
      h.y -= h.speed;
      h.wobble += 0.02;
      h.x += Math.sin(h.wobble) * 0.4;
      if (h.y < -20) {
        h.y = canvas.height + 20;
        h.x = Math.random() * canvas.width;
      }
    }

    if (screenShake > 0) {
      screenShake *= 0.88;
      if (screenShake < 0.2) screenShake = 0;
    }
  }

  // --- Main Render Loop ---
  function render() {
    ctx.save();

    // Screen Shake effect
    if (screenShake > 0) {
      const sx = (Math.random() - 0.5) * screenShake;
      const sy = (Math.random() - 0.5) * screenShake;
      ctx.translate(sx, sy);
    }

    // Clear & Dynamic gradient background
    const bgGrad = ctx.createRadialGradient(
      canvas.width / 2, canvas.height / 2, 80,
      canvas.width / 2, canvas.height / 2, 500
    );
    bgGrad.addColorStop(0, '#270a2a');
    bgGrad.addColorStop(1, '#0e0312');
    ctx.fillStyle = bgGrad;
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // Subtle background grid
    ctx.strokeStyle = 'rgba(255, 105, 180, 0.04)';
    ctx.lineWidth = 1;
    for (let x = 0; x < canvas.width; x += 32) {
      ctx.beginPath();
      ctx.moveTo(x, 0);
      ctx.lineTo(x, canvas.height);
      ctx.stroke();
    }
    for (let y = 0; y < canvas.height; y += 32) {
      ctx.beginPath();
      ctx.moveTo(0, y);
      ctx.lineTo(canvas.width, y);
      ctx.stroke();
    }

    // Draw ambient floating background hearts
    for (let h of bgHearts) {
      ctx.save();
      ctx.fillStyle = `rgba(255, 105, 180, ${h.alpha})`;
      drawHeartPath(ctx, h.x, h.y, h.size);
      ctx.fill();
      ctx.restore();
    }

    // Draw Bricks
    for (let r = 0; r < BRICK_ROWS; r++) {
      for (let c = 0; c < BRICK_COLS; c++) {
        const b = bricks[r][c];
        if (!b.alive) continue;

        const pulseScale = 1 + Math.sin(b.pulse) * 0.02;
        const bw = b.w * pulseScale;
        const bh = b.h * pulseScale;
        const bx = b.x + (b.w - bw) / 2;
        const by = b.y + (b.h - bh) / 2;

        ctx.save();
        // Brick Glow
        ctx.shadowColor = b.glow;
        ctx.shadowBlur = 10;

        // Brick Body Gradient
        const bGrad = ctx.createLinearGradient(bx, by, bx, by + bh);
        bGrad.addColorStop(0, '#ffffff');
        bGrad.addColorStop(0.2, b.color);
        bGrad.addColorStop(1, b.stroke);
        ctx.fillStyle = bGrad;

        ctx.beginPath();
        ctx.roundRect(bx, by, bw, bh, 6);
        ctx.fill();

        ctx.shadowBlur = 0;
        ctx.strokeStyle = 'rgba(255, 255, 255, 0.6)';
        ctx.lineWidth = 1.5;
        ctx.stroke();

        // Brick mini heart emblem
        ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
        drawHeartPath(ctx, bx + bw / 2, by + bh * 0.22, 10);
        ctx.fill();

        ctx.restore();
      }
    }

    // Draw Ball Trail
    for (let i = 0; i < ball.trail.length; i++) {
      const t = ball.trail[i];
      ctx.save();
      ctx.fillStyle = `rgba(255, 65, 115, ${t.alpha * 0.5})`;
      ctx.beginPath();
      ctx.arc(t.x, t.y, ball.radius * (i / ball.trail.length), 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();
    }

    // Draw Ball (Cherry Tomato / Sweet Berry)
    ctx.save();
    ctx.shadowColor = '#ff1493';
    ctx.shadowBlur = 15;

    // Tomato Sphere
    const tomatoGrad = ctx.createRadialGradient(
      ball.x - 3, ball.y - 3, 2,
      ball.x, ball.y, ball.radius
    );
    tomatoGrad.addColorStop(0, '#ff99aa');
    tomatoGrad.addColorStop(0.35, '#ff1a53');
    tomatoGrad.addColorStop(0.9, '#a6002c');
    tomatoGrad.addColorStop(1, '#66001a');

    ctx.fillStyle = tomatoGrad;
    ctx.beginPath();
    ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
    ctx.fill();

    // Stem / green calyx top
    ctx.fillStyle = '#48bb78';
    ctx.beginPath();
    ctx.arc(ball.x, ball.y - ball.radius + 1.5, 3.2, 0, Math.PI * 2);
    ctx.fill();

    // Highlight sheen
    ctx.fillStyle = 'rgba(255, 255, 255, 0.85)';
    ctx.beginPath();
    ctx.arc(ball.x - 3.5, ball.y - 3.5, 2.8, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();

    // Draw Heart Particles (Exploding Bricks)
    for (let p of heartParticles) {
      ctx.save();
      ctx.translate(p.x, p.y);
      ctx.rotate(p.rotation);
      ctx.globalAlpha = Math.max(0, p.life);

      if (p.shimmer) {
        ctx.shadowColor = p.color;
        ctx.shadowBlur = 12;
      }

      ctx.fillStyle = p.color;
      drawHeartPath(ctx, 0, -p.size / 2, p.size);
      ctx.fill();

      // Inner sparkle highlight
      if (p.size > 11) {
        ctx.fillStyle = 'rgba(255, 255, 255, 0.65)';
        drawHeartPath(ctx, -1, -p.size / 2 + 1, p.size * 0.45);
        ctx.fill();
      }

      ctx.restore();
    }

    // Draw Floating Score & Words
    for (let t of floatingTexts) {
      ctx.save();
      ctx.font = `bold ${Math.floor(18 * t.scale)}px sans-serif`;
      ctx.fillStyle = `rgba(255, 230, 240, ${t.alpha})`;
      ctx.shadowColor = '#ff007f';
      ctx.shadowBlur = 10;
      ctx.textAlign = 'center';
      ctx.fillText(t.text, t.x, t.y);
      ctx.restore();
    }

    // Draw Burger Paddle
    drawHamburger(
      paddle.x,
      paddle.y,
      paddle.width,
      paddle.height,
      paddle.squash,
      paddle.stretch,
      paddle.tilt
    );

    // Draw HUD (Score, Hearts, Lives)
    ctx.save();
    ctx.font = '800 18px sans-serif';
    ctx.fillStyle = '#ffffff';
    ctx.shadowColor = 'rgba(255, 20, 147, 0.8)';
    ctx.shadowBlur = 8;

    // Score & Hearts Counter
    ctx.textAlign = 'left';
    ctx.fillText(`SCORE: ${score}`, 24, 34);

    // Hearts Icon + Count
    ctx.fillStyle = '#ff4d79';
    drawHeartPath(ctx, 190, 21, 16);
    ctx.fill();
    ctx.fillStyle = '#ffffff';
    ctx.fillText(`HEARTS: ${heartsCollected}`, 206, 34);

    if (combo > 1) {
      ctx.fillStyle = '#ffea00';
      ctx.fillText(`${combo}x COMBO!`, 370, 34);
    }

    // Lives as cute mini-burgers or hearts
    ctx.textAlign = 'right';
    ctx.fillStyle = '#ffffff';
    ctx.fillText(`LIVES:`, canvas.width - 90, 34);
    for (let i = 0; i < lives; i++) {
      ctx.save();
      ctx.fillStyle = '#ff2b66';
      drawHeartPath(ctx, canvas.width - 70 + (i * 22), 20, 16);
      ctx.fill();
      ctx.restore();
    }

    ctx.restore();
    ctx.restore(); // for screen shake
  }

  // Main Animation Loop
  function gameLoop() {
    update();
    render();
    requestAnimationFrame(gameLoop);
  }

  requestAnimationFrame(gameLoop);
})();
</script>
</body>
</html>
Reasoning trace (708 tokens)
**Designing Engaging Breakout Elements**

I'm currently refining the visual flair for the breakout experience. My focus is on bringing the hamburger paddle to life with intricate detail and ensuring the heart particle explosions are truly captivating and dynamic, creating a sense of joyous destruction. The AI demo is also being tuned.

**Developing Immersive Breakout Mechanics**

I'm currently focused on the finer points of gameplay feel. The AI is being tuned for immediate engagement, and I'm iterating on visual elements like the ball's trail and the hamburger paddle's procedural generation to ensure a truly "juicy" experience. The theme "Burger Breakout: Love at First Bite" is shaping the aesthetic.

**Enhancing Breakout Visuals**

I'm now focusing on the finer details of brick and explosion visuals. I'm planning pastel candy-like colors with gloss and depth for the bricks, and envisioning heart particles in various colors and physics for the explosions, accompanied by shockwaves and celebratory text. Audio integration is also in progress.