Models 44

GLM 5.3 Flash

by Z.aiReasoningClean
Open in new window

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

How it went

Total time
55.7 s
Cost
$0.0035
Output tokens
6,859
Spent thinking
1,280 tokens
Speed
124.3 tok/s
Console errors
0
First token after
478 ms
First visible text after
18.1 s
Prompt tokens
357
Code size
16.2 KB
Finish reason
stop
Served by
Cohere
Model id
z-ai/glm-5.3-flash
Reasoning sent
{"effort":"low"}
Max output
64,000 tokens
Generated
Nov 10, 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 · 524 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 { height:100%; overflow:hidden; }
  body {
    background: radial-gradient(circle at 50% 20%, #3a1d4e 0%, #1c0f2e 55%, #120820 100%);
    display:flex; align-items:center; justify-content:center;
    font-family: 'Segoe UI', 'Trebuchet MS', sans-serif;
    touch-action: none;
  }
  #wrap { position:relative; width:min(96vw, 900px); }
  canvas {
    display:block; width:100%; height:auto;
    border-radius:14px;
    box-shadow: 0 0 0 3px rgba(255,150,200,.25), 0 20px 60px rgba(0,0,0,.55);
    background:#241238;
    cursor:none;
  }
  #overlay {
    position:absolute; inset:0;
    display:flex; flex-direction:column; align-items:center; justify-content:center;
    text-align:center; pointer-events:none;
    transition: opacity .35s;
  }
  #overlay.hidden { opacity:0; }
  .title {
    font-size: clamp(28px, 6vw, 52px);
    font-weight: 900; letter-spacing: 2px;
    color:#ffd9ec;
    text-shadow: 0 0 18px rgba(255,80,150,.8), 0 4px 0 rgba(0,0,0,.4);
  }
  .sub {
    margin-top: 10px;
    font-size: clamp(14px, 2.4vw, 20px);
    color:#ffc4de;
  }
  .blink {
    margin-top: 26px;
    font-size: clamp(15px, 2.6vw, 22px);
    font-weight: 700;
    color:#fff;
    background: rgba(255,60,130,.85);
    padding: 12px 26px;
    border-radius: 999px;
    box-shadow: 0 0 24px rgba(255,60,130,.7);
    animation: pulse 1.1s ease-in-out infinite;
  }
  @keyframes pulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.07)} }
  .hint {
    margin-top: 18px; font-size: clamp(11px, 1.8vw, 14px);
    color: rgba(255,200,225,.75);
  }
</style>
</head>
<body>
<div id="wrap">
  <canvas id="c" width="900" height="640"></canvas>
  <div id="overlay">
    <div class="title">🍔 BURGER BREAKOUT 💕</div>
    <div class="sub">Smash the candy bricks — every one bursts into a shower of hearts!</div>
    <div class="blink" id="blinkMsg">🍔 Click or press a key to start</div>
    <div class="hint">Move: mouse / touch / ← → &nbsp;·&nbsp; the AI is demo-playing right now</div>
  </div>
</div>
<script>
(function(){
  const cv = document.getElementById('c');
  const ctx = cv.getContext('2d');
  const W = 900, H = 640;
  const overlay = document.getElementById('overlay');
  const blinkMsg = document.getElementById('blinkMsg');

  // ---------- audio ----------
  let actx = null;
  function beep(freq, dur, type, vol){
    try{
      if(!actx) return;
      const o = actx.createOscillator(), g = actx.createGain();
      o.type = type || 'sine'; o.frequency.value = freq;
      g.gain.setValueAtTime(vol||0.08, actx.currentTime);
      g.gain.exponentialRampToValueAtTime(0.0001, actx.currentTime + dur);
      o.connect(g); g.connect(actx.destination);
      o.start(); o.stop(actx.currentTime + dur);
    }catch(e){}
  }
  function initAudio(){
    if(actx) return;
    try{ actx = new (window.AudioContext || window.webkitAudioContext)(); }catch(e){}
  }

  // ---------- state ----------
  let state = 'demo'; // demo | play | over | win
  const paddle = { x: W/2, w: 118, h: 36, targetX: W/2 };
  const ball = { x: W/2, y: H/2, r: 8, vx: 3.4, vy: -4.4, speed: 5.6 };
  let lives = 3, score = 0, bricksLeft = 0, combo = 0;
  const keys = {};

  // hearts particles
  const hearts = [];
  const popups = [];
  const trail = [];

  // ---------- bricks ----------
  const COLS = 10, ROWS = 6;
  const BW = 76, BH = 24, GAP = 4;
  const BX0 = (W - (COLS*BW + (COLS-1)*GAP)) / 2;
  const BY0 = 78;
  const rowColors = [
    ['#ff5d8f','#c23b6b'], ['#ff8f5d','#c25b2b'], ['#ffd45d','#c29a2b'],
    ['#7ddf8f','#3ba04b'], ['#5db9ff','#2b6cc2'], ['#b47dff','#7b3bc2']
  ];
  const rowPts = [70, 60, 50, 40, 30, 20];
  let bricks = [];
  function buildBricks(){
    bricks = [];
    for(let r=0;r<ROWS;r++)
      for(let c=0;c<COLS;c++)
        bricks.push({ x: BX0 + c*(BW+GAP), y: BY0 + r*(BH+GAP), w: BW, h: BH, row: r, alive: true });
    bricksLeft = bricks.length;
  }

  function resetBall(demo){
    ball.x = paddle.x;
    ball.y = H - 92;
    ball.speed = demo ? 5.4 : 5.8;
    const a = -Math.PI/2 + (Math.random()*0.6 - 0.3);
    ball.vx = Math.cos(a) * ball.speed;
    ball.vy = Math.sin(a) * ball.speed;
    trail.length = 0;
  }

  function resetGame(){
    buildBricks();
    lives = 3; score = 0; combo = 0;
    paddle.x = W/2; paddle.targetX = W/2;
    resetBall(true);
    hearts.length = 0; popups.length = 0;
  }
  resetGame();

  // ---------- heart drawing ----------
  function heartPath(s){
    ctx.beginPath();
    ctx.moveTo(0, s*0.42);
    ctx.bezierCurveTo(-s*0.65, -s*0.08, -s*0.42, -s*0.72, 0, -s*0.28);
    ctx.bezierCurveTo(s*0.42, -s*0.72, s*0.65, -s*0.08, 0, s*0.42);
    ctx.closePath();
  }

  function burstHearts(x, y, n, big){
    const cols = ['#ff4d88','#ff7aa8','#ffa3c2','#ff2e6e','#ff9ecf','#ff5db0'];
    for(let i=0;i<n;i++){
      const a = Math.random()*Math.PI*2;
      const sp = (big?2.5:1.6) + Math.random()*(big?4.5:3.2);
      hearts.push({
        x, y,
        vx: Math.cos(a)*sp, vy: Math.sin(a)*sp - (big?2.5:1.8),
        s: (big?7:4) + Math.random()*(big?10:7),
        rot: Math.random()*Math.PI*2,
        vr: (Math.random()-0.5)*0.25,
        life: 1, decay: 0.008 + Math.random()*0.010,
        col: cols[(Math.random()*cols.length)|0]
      });
    }
    if(hearts.length > 900) hearts.splice(0, hearts.length - 900);
  }

  // ---------- burger paddle ----------
  function drawBurger(px, py, w, t){
    const x = px - w/2;
    const bob = Math.sin(t*0.006)*1.2;
    ctx.save();
    ctx.translate(0, bob);

    // shadow
    ctx.fillStyle = 'rgba(0,0,0,.35)';
    ctx.beginPath();
    ctx.ellipse(px, py + 34, w*0.5, 6, 0, 0, Math.PI*2);
    ctx.fill();

    // bottom bun
    ctx.fillStyle = '#e8a54b';
    roundRect(x+6, py+18, w-12, 15, 7); ctx.fill();
    ctx.fillStyle = 'rgba(255,255,255,.15)';
    roundRect(x+6, py+18, w-12, 6, 4); ctx.fill();

    // patty
    ctx.fillStyle = '#6b3f23';
    roundRect(x+2, py+10, w-4, 11, 5); ctx.fill();
    ctx.fillStyle = 'rgba(0,0,0,.2)';
    for(let i=0;i<5;i++){ ctx.beginPath(); ctx.arc(x+14+i*(w-28)/4, py+15.5, 1.6, 0, Math.PI*2); ctx.fill(); }

    // cheese drips
    ctx.fillStyle = '#ffc93c';
    ctx.beginPath();
    ctx.moveTo(x+4, py+11);
    ctx.lineTo(x+w-4, py+11);
    ctx.lineTo(x+w-4, py+13);
    for(let i=4;i>=0;i--){
      const dx = x+4 + i*(w-8)/4;
      ctx.lineTo(dx + (w-8)/8, py + 16 + (i%2?4:2));
      ctx.lineTo(dx, py+13);
    }
    ctx.closePath(); ctx.fill();

    // lettuce wave
    ctx.fillStyle = '#5fc94a';
    ctx.beginPath();
    ctx.moveTo(x+2, py+10);
    ctx.lineTo(x+w-2, py+10);
    ctx.lineTo(x+w-2, py+6);
    const n = 7;
    for(let i=0;i<=n;i++){
      const dx = x+w-2 - i*(w-4)/n;
      ctx.quadraticCurveTo(dx - (w-4)/(2*n), py + (i%2? 0.5 : 4.5), dx - (w-4)/n, py+6);
    }
    ctx.closePath(); ctx.fill();

    // top bun
    ctx.fillStyle = '#f2b45c';
    ctx.beginPath();
    ctx.moveTo(x+4, py+7);
    ctx.quadraticCurveTo(x+4, py-9, px, py-9);
    ctx.quadraticCurveTo(x+w-4, py-9, x+w-4, py+7);
    ctx.closePath(); ctx.fill();
    ctx.fillStyle = 'rgba(255,255,255,.28)';
    ctx.beginPath();
    ctx.ellipse(px - w*0.18, py-1, w*0.2, 3.5, -0.12, 0, Math.PI*2);
    ctx.fill();

    // sesame seeds
    ctx.fillStyle = '#fff2d6';
    const seeds = [[-0.3,-4],[-0.05,-7],[0.22,-4],[0.36,-1],[-0.17,-0.5],[0.05,-2]];
    for(const [sx, sy] of seeds){
      ctx.save();
      ctx.translate(px + sx*w, py + sy);
      ctx.rotate(sx*1.2);
      ctx.beginPath(); ctx.ellipse(0, 0, 2.6, 1.5, 0, 0, Math.PI*2); ctx.fill();
      ctx.restore();
    }
    ctx.restore();
  }

  function roundRect(x, y, w, h, r){
    ctx.beginPath();
    ctx.moveTo(x+r, y);
    ctx.arcTo(x+w, y, x+w, y+h, r);
    ctx.arcTo(x+w, y+h, x, y+h, r);
    ctx.arcTo(x, y+h, x, y, r);
    ctx.arcTo(x, y, x+w, y, r);
    ctx.closePath();
  }

  // ---------- physics ----------
  const PY = H - 74; // paddle top surface y

  function update(dt, t){
    // paddle control
    if(state === 'demo'){
      const bias = Math.sin(t*0.0011)*30;
      paddle.targetX = ball.x + bias;
      paddle.x += (paddle.targetX - paddle.x) * 0.08;
    } else {
      if(keys['ArrowLeft'] || keys['a']) paddle.x -= 9;
      if(keys['ArrowRight'] || keys['d']) paddle.x += 9;
      paddle.x += (paddle.targetX - paddle.x) * 0.35;
    }
    paddle.x = Math.max(paddle.w/2 + 6, Math.min(W - paddle.w/2 - 6, paddle.x));

    if(state === 'over' || state === 'win'){
      updateParticles(dt);
      return;
    }

    // ball
    ball.x += ball.vx * dt;
    ball.y += ball.vy * dt;

    trail.push({x:ball.x, y:ball.y});
    if(trail.length > 14) trail.shift();

    // walls
    if(ball.x < ball.r + 4){ ball.x = ball.r + 4; ball.vx = Math.abs(ball.vx); beep(300, .06, 'square', .04); }
    if(ball.x > W - ball.r - 4){ ball.x = W - ball.r - 4; ball.vx = -Math.abs(ball.vx); beep(300, .06, 'square', .04); }
    if(ball.y < ball.r + 4){ ball.y = ball.r + 4; ball.vy = Math.abs(ball.vy); beep(300, .06, 'square', .04); }

    // paddle bounce
    if(ball.vy > 0 && ball.y + ball.r >= PY && ball.y - ball.r <= PY + 30 &&
       ball.x >= paddle.x - paddle.w/2 - ball.r && ball.x <= paddle.x + paddle.w/2 + ball.r){
      ball.y = PY - ball.r;
      const off = (ball.x - paddle.x) / (paddle.w/2);
      const ang = -Math.PI/2 + off * 1.05;
      ball.vx = Math.cos(ang) * ball.speed;
      ball.vy = Math.sin(ang) * ball.speed;
      combo = 0;
      beep(180, .08, 'square', .05);
      burstHearts(ball.x, PY, 4, false);
    }

    // bottom
    if(ball.y > H + 30){
      lives--;
      beep(120, .3, 'sawtooth', .07);
      if(lives <= 0){
        state = 'over';
        blinkMsg.textContent = '💀 Game Over — click or press a key to retry';
        overlay.classList.remove('hidden');
        burstHearts(ball.x, H - 20, 60, true);
      } else {
        resetBall(state === 'demo');
      }
      return;
    }

    // brick collisions
    for(const b of bricks){
      if(!b.alive) continue;
      const nx = Math.max(b.x, Math.min(ball.x, b.x + b.w));
      const ny = Math.max(b.y, Math.min(ball.y, b.y + b.h));
      const dx = ball.x - nx, dy = ball.y - ny;
      if(dx*dx + dy*dy <= ball.r*ball.r){
        b.alive = false; bricksLeft--;
        combo++;
        const pts = rowPts[b.row] * Math.min(combo, 5);
        score += pts;
        popups.push({ x: b.x + b.w/2, y: b.y, txt: '+' + pts, life: 1, col: rowColors[b.row][0] });
        // bounce direction
        const hitVert = Math.abs(dy) > Math.abs(dx);
        if(hitVert) ball.vy = dy < 0 ? -Math.abs(ball.vy) : Math.abs(ball.vy);
        else ball.vx = dx < 0 ? -Math.abs(ball.vx) : Math.abs(ball.vx);
        ball.speed = Math.min(ball.speed + 0.06, 8.5);
        const sp = Math.hypot(ball.vx, ball.vy);
        ball.vx *= ball.speed/sp; ball.vy *= ball.speed/sp;
        burstHearts(b.x + b.w/2, b.y + b.h/2, 22, false);
        burstHearts(b.x + b.w/2, b.y + b.h/2, 3, true);
        beep(520 + b.row*60, .09, 'triangle', .06);
        if(bricksLeft <= 0){
          state = 'win';
          blinkMsg.textContent = '💖 YOU WIN! Click or press a key to play again';
          overlay.classList.remove('hidden');
          for(let i=0;i<10;i++) burstHearts(Math.random()*W, Math.random()*H*0.6, 14, true);
        }
        break;
      }
    }

    updateParticles(dt);
  }

  function updateParticles(dt){
    for(let i=hearts.length-1;i>=0;i--){
      const h = hearts[i];
      h.vy += 0.13*dt;
      h.vx *= 0.995;
      h.x += h.vx*dt; h.y += h.vy*dt;
      h.rot += h.vr*dt;
      h.life -= h.decay*dt;
      if(h.life <= 0 || h.y > H + 40) hearts.splice(i,1);
    }
    for(let i=popups.length-1;i>=0;i--){
      const p = popups[i];
      p.y -= 0.8*dt; p.life -= 0.02*dt;
      if(p.life <= 0) popups.splice(i,1);
    }
  }

  // ---------- render ----------
  function render(t){
    // background
    ctx.fillStyle = '#241238';
    ctx.fillRect(0, 0, W, H);
    const g = ctx.createRadialGradient(W/2, H*0.25, 40, W/2, H*0.25, 620);
    g.addColorStop(0, 'rgba(255,90,160,0.10)');
    g.addColorStop(1, 'rgba(0,0,0,0)');
    ctx.fillStyle = g;
    ctx.fillRect(0, 0, W, H);

    // subtle grid dots
    ctx.fillStyle = 'rgba(255,255,255,0.03)';
    for(let y=30;y<H;y+=40) for(let x=20;x<W;x+=40){ ctx.fillRect(x, y, 2, 2); }

    // bricks
    for(const b of bricks){
      if(!b.alive) continue;
      const [c1, c2] = rowColors[b.row];
      ctx.fillStyle = c2;
      roundRect(b.x+2, b.y+3, b.w, b.h, 6); ctx.fill();
      ctx.fillStyle = c1;
      roundRect(b.x, b.y, b.w, b.h, 6); ctx.fill();
      ctx.fillStyle = 'rgba(255,255,255,.35)';
      roundRect(b.x+4, b.y+3, b.w-8, 6, 4); ctx.fill();
      // tiny heart icon on brick
      ctx.save();
      ctx.translate(b.x + b.w/2, b.y + b.h/2 + 1);
      ctx.fillStyle = 'rgba(255,255,255,.55)';
      heartPath(5); ctx.fill();
      ctx.restore();
    }

    // ball trail
    for(let i=0;i<trail.length;i++){
      const p = trail[i], a = i/trail.length;
      ctx.fillStyle = `rgba(255,220,80,${a*0.35})`;
      ctx.beginPath(); ctx.arc(p.x, p.y, ball.r*a, 0, Math.PI*2); ctx.fill();
    }

    // ball (meatball)
    ctx.save();
    ctx.shadowColor = 'rgba(255,200,60,.9)'; ctx.shadowBlur = 14;
    ctx.fillStyle = '#b06a2c';
    ctx.beginPath(); ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI*2); ctx.fill();
    ctx.restore();
    ctx.fillStyle = 'rgba(255,255,255,.35)';
    ctx.beginPath(); ctx.arc(ball.x-2.5, ball.y-2.5, ball.r*0.4, 0, Math.PI*2); ctx.fill();

    // hearts particles
    for(const h of hearts){
      ctx.save();
      ctx.globalAlpha = Math.max(0, Math.min(1, h.life*1.4));
      ctx.translate(h.x, h.y);
      ctx.rotate(h.rot);
      ctx.fillStyle = h.col;
      heartPath(h.s); ctx.fill();
      ctx.restore();
    }

    // popups
    ctx.textAlign = 'center';
    for(const p of popups){
      ctx.globalAlpha = p.life;
      ctx.font = 'bold 17px Trebuchet MS, sans-serif';
      ctx.fillStyle = p.col;
      ctx.fillText(p.txt, p.x, p.y);
      ctx.globalAlpha = 1;
    }

    // burger paddle
    drawBurger(paddle.x, PY, paddle.w, t);

    // HUD
    ctx.textAlign = 'left';
    ctx.font = 'bold 20px Trebuchet MS, sans-serif';
    ctx.fillStyle = '#ffd9ec';
    ctx.fillText('SCORE ' + score, 20, 38);
    ctx.textAlign = 'right';
    ctx.fillText('LIVES', W - 110, 38);
    for(let i=0;i<3;i++){
      ctx.save();
      ctx.translate(W - 84 + i*30, 31);
      if(i < lives){ ctx.fillStyle = '#ff4d88'; ctx.shadowColor='#ff4d88'; ctx.shadowBlur=8; }
      else { ctx.fillStyle = 'rgba(255,255,255,.15)'; }
      heartPath(11); ctx.fill();
      ctx.restore();
    }
    if(state === 'demo'){
      ctx.textAlign = 'center';
      ctx.font = 'italic 15px Trebuchet MS, sans-serif';
      ctx.fillStyle = 'rgba(255,200,225,.65)';
      ctx.fillText('AI demo mode — grab a bite whenever you like 🍔', W/2, H - 14);
    }
  }

  // ---------- loop ----------
  let last = performance.now();
  function frame(t){
    const dt = Math.min(2.5, (t - last)/16.67);
    last = t;
    update(dt, t);
    render(t);
    requestAnimationFrame(frame);
  }
  requestAnimationFrame(frame);

  // ---------- input ----------
  function startPlay(){
    initAudio();
    if(actx && actx.state === 'suspended') actx.resume();
    resetGame();
    state = 'play';
    overlay.classList.add('hidden');
    resetBall(false);
    beep(660, .15, 'triangle', .07);
  }

  window.addEventListener('keydown', e => {
    keys[e.key] = true;
    if(state === 'demo' || state === 'over' || state === 'win'){
      if(['ArrowLeft','ArrowRight','a','d',' '].includes(e.key)) e.preventDefault();
      startPlay();
    }
  });
  window.addEventListener('keyup', e => { keys[e.key] = false; });

  function pointerX(e){
    const rect = cv.getBoundingClientRect();
    const cx = (e.touches ? e.touches[0].clientX : e.clientX);
    return (cx - rect.left) / rect.width * W;
  }
  cv.addEventListener('mousemove', e => {
    paddle.targetX = pointerX(e);
  });
  cv.addEventListener('mousedown', () => {
    if(state !== 'play') startPlay();
  });
  cv.addEventListener('touchstart', e => {
    e.preventDefault();
    paddle.targetX = pointerX(e);
    if(state !== 'play') startPlay();
  }, {passive:false});
  cv.addEventListener('touchmove', e => {
    e.preventDefault();
    paddle.targetX = pointerX(e);
  }, {passive:false});

  overlay.classList.remove('hidden');
})();
</script>
</body>
</html>
Reasoning trace (1,280 tokens)
Make a Breakout game: paddle is a hamburger (drawn with canvas or CSS), bricks explode into hearts particles. Self-playing demo mode behind "Click or press a key to start" overlay. Single HTML file, canvas-based.

Plan: Canvas 2D. Paddle drawn as a burger (bun top with sesame seeds, lettuce, patty, bun bottom). Bricks colored rows, on break spawn many heart particles (drawn hearts via bezier or two circles + triangle). Ball. Demo AI controls paddle. On click/keydown start real play (or restart). Keep everything visible quickly.

Write compact but polished code. Handle resize, 1280x800 and 375px. Use fixed logical size scaled via canvas.

Implementation details:
- Logical canvas e.g. 800x600, scale to fit with CSS.
- Bricks grid ~ 10 cols x 5 rows.
- Hearts particles: x,y,vx,vy,rot,vr,size,life; gravity; drawn with path: heart shape function.
- Burger paddle: draw at paddle position; width ~110. Layers: bottom bun (rounded rect tan), patty (brown), lettuce (wavy green), top bun (dome tan with sesame seeds white ellipses). Ball could be a tomato slice? Fun: ball is a red meatball or cheese? Keep ball simple glowing white/cheese. Let's make ball a cheese ball (yellow circle). On paddle hit, small bounce.
- Score, lives, title overlay.
- Demo mode: AI tracks ball x with lerp.
- Start overlay: "🍔 BREAKOUT DELUXE — Click or press a key to start". Behind it demo runs.
- After game over/ win: message; click to restart into play mode.
- Mouse/touch controls paddle too.

Ensure no errors, requestAnimationFrame loop.

Heart drawing: use classic param: ctx.moveTo(0, r*0.3); bezier curves. Standard:
function drawHeart(ctx,s){ ctx.beginPath(); ctx.moveTo(0,s*0.35); ctx.bezierCurveTo(s,-s*0.6, s*0.7,-s, 0,-s*0.35)... } Simpler known formula:
ctx.moveTo(0, s*0.3);
ctx.bezierCurveTo(0, 0, -s*0.5, -s*0.3, -s*0.5, 0)? Let's just use well-known heart path scaled:

Heart with size s:
beginPath();
moveTo(x, y + s*0.25);
bezierCurveTo(x, y, x - s*0.5, y - s*0.35, x - s*0.5, y + s*0.05)? Getting fiddly; use the common one:

function heartPath(c, s){
  c.beginPath();
  c.moveTo(0, s*0.4);
  c.bezierCurveTo(-s*0.6, -s*0.1, -s*0.4, -s*0.7, 0, -s*0.25);
  c.bezierCurveTo(s*0.4, -s*0.7, s*0.6, -s*0.1, 0, s*0.4);
  c.closePath();
}
That's a decent heart with cusp at bottom (0,0.4s) and lobes up top, dip at (0,-0.25s). Good.

Particles: per brick ~ 18 hearts, colors pink/red variations, gravity 0.15, fade out.

Also combo text? Keep simple: score popups "+" maybe. Keep it manageable.

Sounds: WebAudio beeps optional — small blip on brick break. Wrap in try, create AudioContext on first user gesture. Could skip to avoid errors; add safe small sound with lazy init on start click.

Brick colors per row: candy palette.

Lives: 3 hearts displayed as icons.

Game over → show overlay "Game Over — click to play again", demo resumes? Simpler: after game over, restart demo mode after overlay click restarts play.

Structure:
state = 'demo' | 'play' | 'over' | 'win'
Overlay div (HTML) with pointer-events none except message? Use HTML overlay centered text; canvas behind. Click anywhere / keydown: if state demo or over/win → start play (reset). Overlay hides during play.

Ball-brick collision: simple AABB vs circle approx: find nearest point.

Ball speed increase slightly per brick. Ball reset when falls below bottom; lose life; in demo AI just continues.

Win: all bricks cleared → confetti hearts burst everywhere, overlay "You win!".

Write the code.

Paddle burger drawing at (px, py), width w, height ~34. Draw centered on paddle.x, bottom area y = H-40.

Layers top to bottom: top bun dome height ~14, sesame; lettuce wave ~5; patty ~8; tomato? bottom bun ~10 with rounded. Total ~36. Collision top of paddle = py (top surface) for bounce.

Paddle movement: keyboard arrows, mouse move, touch. AI in demo: paddle.x += clamp(ball.x - paddle.x)*speed.

Ball collision with paddle: if ball moving down and intersects paddle rect → reflect vy upward with angle based on hit offset.

Code sizes fine. Let's write.

Canvas logical: W=900, H=640; CSS: canvas max-width 100%, aspect ratio preserved via CSS width: min(96vw, 900px), height auto. Compute scale for pointer coords via getBoundingClientRect.

Bricks: cols=10, rows=6, brick w=76, h=24, top offset 70, gap 4. 10*76+9*4=796 fits in 900 with margin 52.

Hearts particles count per brick: 16 plus 4 bigger. Cap total particles ~600 for perf.

Score popups: small "+50" text floating.

Ball trail: store last positions, draw fading circles — nice.

Write final HTML.