Models 44

Muse Ember

Open in new window

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

How it went

Total time
10.4 s
Cost
$0.0036
Output tokens
7,136
Spent thinking
120 tokens
Speed
746.2 tok/s
Console errors
0
First token after
881 ms
First visible text after
881 ms
Prompt tokens
375
Code size
19.8 KB
Finish reason
stop
Served by
Meta
Model id
meta/muse-ember
Reasoning sent
{"effort":"minimal"}
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 · 488 lines

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hamburger Breakout — Heart Explosion</title>
<style>
  *{margin:0;padding:0;box-sizing:border-box}
  body{
    background:#1a0f14;
    background-image:radial-gradient(ellipse at 50% 0%, #3d1e2e 0%, #1a0f14 65%);
    color:#fff;font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
    min-height:100vh;display:flex;flex-direction:column;align-items:center;
    overflow-x:hidden;padding:14px 10px 18px;
  }
  header{text-align:center;max-width:860px;width:100%}
  h1{font-size:clamp(26px,5vw,44px);font-weight:900;letter-spacing:-.5px;line-height:1}
  h1 .ham{filter:drop-shadow(0 4px 0 rgba(0,0,0,.35))}
  h1 .heartword{color:#ff4d7a;text-shadow:0 0 22px rgba(255,77,122,.7)}
  .subtitle{margin-top:6px;color:#f8c8d0;font-size:clamp(12px,2vw,15px);font-weight:600}
  .subtitle b{color:#ffd166}
  .hud{
    width:min(860px,100%);display:flex;justify-content:space-between;align-items:center;gap:10px;
    margin:12px 0 10px;flex-wrap:wrap;
  }
  .pill{
    background:rgba(255,255,255,.09);border:1.5px solid rgba(255,255,255,.14);
    border-radius:999px;padding:7px 14px;font-weight:800;font-size:14px;backdrop-filter:blur(6px);
    display:flex;align-items:center;gap:7px;white-space:nowrap;
  }
  .pill small{opacity:.7;font-weight:700;font-size:11px;letter-spacing:.08em;text-transform:uppercase}
  .pill.score{border-color:#ffb3c6;color:#ffe5ec}
  .pill.lives{border-color:#ffd166}
  .btn{
    cursor:pointer;border:none;border-radius:999px;padding:9px 18px;font-weight:900;font-size:14px;
    background:linear-gradient(180deg,#ff6b91,#e6395f);color:#fff;box-shadow:0 6px 0 #9e1b3e,0 10px 20px rgba(0,0,0,.35);
    transition:transform .08s, box-shadow .08s;
  }
  .btn:active{transform:translateY(4px);box-shadow:0 2px 0 #9e1b3e}
  .btn.secondary{background:linear-gradient(180deg,#ffd166,#f59e0b);box-shadow:0 6px 0 #92400e;color:#431407}
  .stage{
    width:min(860px,100%);position:relative;
    border-radius:22px;overflow:hidden;
    border:3px solid rgba(255,255,255,.16);
    box-shadow:0 24px 60px rgba(0,0,0,.55), 0 0 0 6px rgba(255,77,122,.12), 0 0 50px rgba(255,77,122,.18);
    background:#120a12;
  }
  canvas{display:block;width:100%;height:auto;aspect-ratio:860/560;background:linear-gradient(180deg,#241126 0%,#180c1d 55%,#100818 100%)}
  .overlay{
    position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;
    text-align:center;padding:20px;background:radial-gradient(ellipse at center, rgba(20,8,20,.25), rgba(10,4,14,.72));
    z-index:5;transition:opacity .25s;
  }
  .overlay.hidden{opacity:0;pointer-events:none}
  .overlay .big{font-size:clamp(42px,8vw,72px);line-height:1;filter:drop-shadow(0 8px 0 rgba(0,0,0,.4))}
  .overlay h2{font-size:clamp(26px,5vw,40px);font-weight:900;margin-top:10px;letter-spacing:-.5px}
  .overlay p{margin-top:8px;color:#ffd9e2;font-weight:700;font-size:clamp(13px,2.5vw,16px);max-width:520px}
  .overlay .hint{margin-top:16px;background:#fff;color:#111;border-radius:999px;padding:10px 20px;font-weight:900;font-size:15px;box-shadow:0 6px 0 #bbb;animation:pulse 1.2s infinite}
  @keyframes pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.05)}}
  .controls{
    width:min(860px,100%);display:flex;flex-wrap:wrap;gap:8px;justify-content:center;margin-top:12px;
  }
  .controls span{background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.12);padding:6px 12px;border-radius:999px;font-size:12.5px;font-weight:700;color:#f3dbe0}
  .touchbar{display:none;width:min(860px,100%);gap:10px;margin-top:10px}
  .touchbar button{flex:1;padding:14px;border-radius:16px;border:none;font-weight:900;font-size:18px;background:#fff;color:#111;box-shadow:0 5px 0 #bbb}
  .touchbar button:active{transform:translateY(3px);box-shadow:0 2px 0 #bbb}
  @media (pointer:coarse){ .touchbar{display:flex} }
  .legend{display:flex;gap:6px;flex-wrap:wrap;justify-content:center;margin-top:10px}
  .legend span{font-size:12px;font-weight:800;padding:4px 9px;border-radius:999px}
</style>
</head>
<body>
<header>
  <h1><span class="ham">🍔</span> HAMBURGER <span class="heartword">♥ BREAKOUT ♥</span></h1>
  <div class="subtitle">Smash bricks with the burger ball — every brick <b>explodes into hearts!</b> Catch hearts for bonus points.</div>
</header>

<div class="hud">
  <div class="pill score"><span>♥</span><small>Score</small><span id="score">0</span></div>
  <div class="pill"><small>Level</small><span id="level">1</span></div>
  <div class="pill"><small>Hearts</small><span id="hearts">0</span> <span style="color:#ff4d7a">♥</span></div>
  <div class="pill lives"><small>Lives</small><span id="lives">🍔🍔🍔</span></div>
  <button class="btn" id="restartBtn">↺ Restart</button>
</div>

<div class="stage" id="stage">
  <canvas id="game" width="860" height="560"></canvas>
  <div class="overlay" id="overlay">
    <div class="big">🍔💥♥</div>
    <h2 id="ovTitle">Hamburger Breakout</h2>
    <p id="ovText">Watch the burger auto-play &amp; burst bricks into hearts.<br>Click or press a key to take control!</p>
    <div class="hint" id="ovHint">▶ &nbsp;CLICK OR PRESS A KEY TO START</div>
  </div>
</div>

<div class="touchbar">
  <button id="leftBtn">◀ LEFT</button>
  <button id="rightBtn">RIGHT ▶</button>
</div>

<div class="controls">
  <span>🖱️ Move mouse / drag to move burger</span>
  <span>⬅️ ➡️ Arrow keys</span>
  <span>Space — launch ball</span>
  <span>💡 Catch falling ♥ for +50 pts</span>
</div>
<div class="legend">
  <span style="background:#ff5a7a">♥ Brick = 10 pts</span>
  <span style="background:#ffb703;color:#111">★ Golden = 30 pts</span>
  <span style="background:#06d6a0;color:#052e22">❤️ Heart catch = +50</span>
  <span style="background:#9b5de5">💜 Unbreakable = bounce!</span>
</div>

<script>
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const W = canvas.width, H = canvas.height;
const $ = id => document.getElementById(id);

let score=0, heartsCaught=0, lives=3, level=1;
let demo=true, playing=false, gameOver=false, won=false;
let keys={};
let shake=0, flash=0;

const paddle = {x:W/2-62, y:H-38, w:124, h:26, speed:9, tx:W/2-62};
const ball = {x:W/2, y:H-70, r:10, vx:0, vy:0, stuck:true, speed:7.2};

let bricks=[], particles=[], floaters=[], pickups=[], bgHearts=[];

// background floating hearts
for(let i=0;i<28;i++) bgHearts.push({x:Math.random()*W,y:Math.random()*H,s:6+Math.random()*14,v:.2+Math.random()*.5,a:.04+Math.random()*.08,ph:Math.random()*6.28});

const BRICK_COLORS=['#ff5a7a','#ff8fa3','#ffb703','#06d6a0','#4cc9f0','#9b5de5','#f15bb5','#00f5d4'];

function buildLevel(lv){
  bricks=[];
  const rows = Math.min(5+lv, 8);
  const cols = 10;
  const bw = (W-40)/cols, bh=22, gap=6, top=58;
  for(let r=0;r<rows;r++){
    for(let c=0;c<cols;c++){
      // leave some gaps pattern
      if((r+c)%13==5 && lv>1) continue;
      let type='normal'; let color=BRICK_COLORS[r%BRICK_COLORS.length];
      // unbreakable every level 2+
      if(lv>=2 && (r==2 && (c==2||c==7))) {type='hard'; color='#9b5de5';}
      if(lv>=3 && (r==0 && c==4)) {type='hard'; color='#9b5de5';}
      if(Math.random()<0.10){type='gold'; color='#ffd166';}
      bricks.push({x:20+c*bw+gap/2, y:top+r*(bh+gap), w:bw-gap, h:bh, color, type, hp: type==='hard'?999:1, alive:true, seed:Math.random()*6.28});
    }
  }
}

function resetBall(){
  ball.x=paddle.x+paddle.w/2; ball.y=paddle.y-ball.r-4; ball.vx=0; ball.vy=0; ball.stuck=true;
}
function launch(){
  if(!ball.stuck) return;
  ball.stuck=false;
  const a = -Math.PI/2 + (Math.random()-0.5)*0.7;
  ball.vx=Math.cos(a)*ball.speed; ball.vy=Math.sin(a)*ball.speed;
}
function resetGame(){
  score=0; heartsCaught=0; lives=3; level=1; gameOver=false; won=false;
  paddle.x=W/2-paddle.w/2; paddle.tx=paddle.x;
  buildLevel(1); resetBall(); particles=[]; pickups=[]; floaters=[];
  updateHUD();
}
function updateHUD(){
  $('score').textContent=score;
  $('level').textContent=level;
  $('hearts').textContent=heartsCaught;
  $('lives').textContent='🍔'.repeat(Math.max(0,lives))+'🤍'.repeat(Math.max(0,3-lives));
}
function addFloater(x,y,txt,color='#ff4d7a'){
  floaters.push({x,y,txt,color,life:1,vy:-1.2});
}
function burstHearts(x,y,color,count=26,power=1){
  for(let i=0;i<count;i++){
    const a=Math.random()*Math.PI*2, sp=(1+Math.random()*5)*power;
    particles.push({
      x,y, vx:Math.cos(a)*sp, vy:Math.sin(a)*sp-1.5,
      size:4+Math.random()*10, rot:Math.random()*6.28, vr:(Math.random()-.5)*.3,
      life:1, decay:.008+Math.random()*.012,
      color: Math.random()<.7?'#ff4d7a':(Math.random()<.5?'#ff8fa3':'#ffb3c6'),
      shape:'heart'
    });
  }
  // sparks
  for(let i=0;i<10;i++){
    const a=Math.random()*Math.PI*2, sp=2+Math.random()*4;
    particles.push({x,y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp,size:2+Math.random()*2,rot:0,vr:0,life:1,decay:.03,color:'#fff',shape:'spark'});
  }
  shake=Math.min(10, shake+4); flash=.35;
  // pickup chance
  if(Math.random()<0.35){
    pickups.push({x,y,vx:(Math.random()-.5)*2,vy:1.2,size:18+Math.random()*6,rot:0,heart:true});
  }
}

function drawHeart(x,y,size,color,rot=0){
  ctx.save(); ctx.translate(x,y); ctx.rotate(rot); ctx.scale(size/20,size/20);
  ctx.fillStyle=color; ctx.shadowColor=color; ctx.shadowBlur=8;
  ctx.beginPath();
  ctx.moveTo(0,6);
  ctx.bezierCurveTo(-10,-4,-6,-12,0,-6);
  ctx.bezierCurveTo(6,-12,10,-4,0,6);
  ctx.closePath(); ctx.fill();
  ctx.shadowBlur=0;
  // highlight
  ctx.fillStyle='rgba(255,255,255,.55)';
  ctx.beginPath(); ctx.ellipse(-3,-4,2.2,3,.6,0,6.28); ctx.fill();
  ctx.restore();
}

function drawBurger(x,y,w){
  const h=26, cx=x, cy=y;
  ctx.save();
  // shadow
  ctx.fillStyle='rgba(0,0,0,.35)'; ctx.beginPath(); ctx.ellipse(cx+w/2,y+h+6,w/2,7,0,0,6.28); ctx.fill();
  // bottom bun
  ctx.fillStyle='#e8a04a'; roundRect(cx+4,y+16,w-8,9,5); ctx.fill();
  ctx.fillStyle='#c47a2b'; roundRect(cx+4,y+22,w-8,3,2); ctx.fill();
  // patty
  ctx.fillStyle='#5c2e0e'; roundRect(cx+2,y+11,w-4,7,4); ctx.fill();
  ctx.fillStyle='#7a4218'; roundRect(cx+2,y+11,w-4,3,3); ctx.fill();
  // cheese
  ctx.fillStyle='#ffb703'; ctx.beginPath();
  ctx.moveTo(cx+4,y+11); ctx.lineTo(cx+w-4,y+11); ctx.lineTo(cx+w-10,y+16); ctx.lineTo(cx+w/2+6,y+13); ctx.lineTo(cx+w/2-8,y+17); ctx.lineTo(cx+10,y+15); ctx.closePath(); ctx.fill();
  // lettuce
  ctx.fillStyle='#2ecc71';
  ctx.beginPath();
  for(let i=0;i<=10;i++){ const lx=cx+2+(w-4)*i/10; const ly=y+10+Math.sin(i*1.2)*2; i===0?ctx.moveTo(lx,ly):ctx.lineTo(lx,ly); }
  ctx.lineTo(cx+w-2,y+13); ctx.lineTo(cx+2,y+13); ctx.closePath(); ctx.fill();
  // tomato
  ctx.fillStyle='#ff4757'; roundRect(cx+10,y+8,w-20,4,2); ctx.fill();
  // top bun
  ctx.fillStyle='#f2b04e';
  ctx.beginPath(); ctx.ellipse(cx+w/2,y+9,w/2,11,0,Math.PI,0); ctx.closePath(); ctx.fill();
  ctx.fillStyle='#e8a04a'; ctx.beginPath(); ctx.ellipse(cx+w/2,y+9,w/2,4,0,0,Math.PI); ctx.fill();
  // sesame
  ctx.fillStyle='#fff3d6';
  [[.25,.28],[.5,.18],[.72,.3],[.38,.38],[.62,.4]].forEach(p=>{
    ctx.beginPath(); ctx.ellipse(cx+w*p[0], y+9*p[1]*2, 3,1.8,.5,0,6.28); ctx.fill();
  });
  // face on burger (cute)
  ctx.fillStyle='#3a1c00';
  ctx.beginPath(); ctx.arc(cx+w/2-14,y+6,2.4,0,6.28); ctx.arc(cx+w/2+14,y+6,2.4,0,6.28); ctx.fill();
  ctx.strokeStyle='#3a1c00'; ctx.lineWidth=1.6; ctx.beginPath(); ctx.arc(cx+w/2,y+7,5,0.2,Math.PI-0.2); ctx.stroke();
  // cheeks
  ctx.fillStyle='rgba(255,100,100,.5)'; ctx.beginPath(); ctx.arc(cx+w/2-20,y+10,3,0,6.28); ctx.arc(cx+w/2+20,y+10,3,0,6.28); ctx.fill();
  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();
}

function draw(){
  ctx.clearRect(0,0,W,H);
  ctx.save();
  if(shake>0.3){ ctx.translate((Math.random()-.5)*shake,(Math.random()-.5)*shake); shake*=.88; } else shake=0;

  // bg hearts
  bgHearts.forEach(b=>{
    b.y-=b.v; b.ph+=.02; if(b.y<-20){b.y=H+20;b.x=Math.random()*W}
    drawHeart(b.x+Math.sin(b.ph)*6,b.y,b.s,`rgba(255,77,122,${b.a})`,Math.sin(b.ph)*.2);
  });

  // bricks
  bricks.forEach(br=>{
    if(!br.alive) return;
    ctx.save();
    ctx.shadowColor=br.color; ctx.shadowBlur= br.type==='gold'?14:6;
    ctx.fillStyle=br.color; roundRect(br.x,br.y,br.w,br.h,6); ctx.fill();
    ctx.shadowBlur=0;
    ctx.fillStyle='rgba(255,255,255,.35)'; roundRect(br.x+3,br.y+3,br.w-6,6,3); ctx.fill();
    ctx.fillStyle='rgba(0,0,0,.18)'; roundRect(br.x+3,br.y+br.h-6,br.w-6,3,2); ctx.fill();
    if(br.type==='gold'){
      ctx.fillStyle='#fff'; ctx.font='bold 13px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle';
      ctx.fillText('★ ★ ★', br.x+br.w/2, br.y+br.h/2+1);
    } else if(br.type==='hard'){
      ctx.fillStyle='#fff'; ctx.font='bold 12px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle';
      ctx.fillText('💜', br.x+br.w/2, br.y+br.h/2+1);
    } else {
      ctx.fillStyle='rgba(255,255,255,.95)';
      const hx=br.x+br.w/2, hy=br.y+br.h/2+1;
      ctx.font='11px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle'; ctx.fillText('♥',hx,hy);
    }
    ctx.restore();
  });

  // pickups
  pickups.forEach(p=>{ drawHeart(p.x,p.y,p.size,'#ff2e63',Math.sin(Date.now()/200+p.x)*.2); });

  // paddle (burger)
  drawBurger(paddle.x,paddle.y,paddle.w);

  // ball - heart ball
  if(!gameOver){
    ctx.save();
    ctx.shadowColor='#ff4d7a'; ctx.shadowBlur=16;
    drawHeart(ball.x,ball.y, ball.r*1.9, '#ff2e63', Math.sin(Date.now()/150)*.15);
    ctx.restore();
    ctx.fillStyle='#fff'; ctx.beginPath(); ctx.arc(ball.x-3,ball.y-4,3,0,6.28); ctx.fill();
  }

  // particles
  particles.forEach(p=>{
    if(p.shape==='heart') drawHeart(p.x,p.y,p.size*p.life+2,p.color,p.rot);
    else { ctx.fillStyle=p.color; ctx.globalAlpha=p.life; ctx.fillRect(p.x,p.y,p.size,p.size); ctx.globalAlpha=1; }
  });

  // floaters
  floaters.forEach(f=>{
    ctx.save(); ctx.globalAlpha=f.life; ctx.font='900 16px system-ui'; ctx.textAlign='center';
    ctx.fillStyle=f.color; ctx.shadowColor='#000'; ctx.shadowBlur=4; ctx.fillText(f.txt,f.x,f.y); ctx.restore();
  });

  ctx.restore();

  if(flash>0){ ctx.fillStyle=`rgba(255,255,255,${flash})`; ctx.fillRect(0,0,W,H); flash-=.04; }

  // progress bar
  const total=bricks.filter(b=>b.type!=='hard').length;
  const left=bricks.filter(b=>b.alive&&b.type!=='hard').length;
  const pct= total? (1-left/total):1;
  ctx.fillStyle='rgba(255,255,255,.15)'; roundRect(20,18,W-40,10,5); ctx.fill();
  ctx.fillStyle='#ff4d7a'; if(pct>0){ roundRect(20,18,(W-40)*pct,10,5); ctx.fill(); }
  ctx.fillStyle='#fff'; ctx.font='bold 11px system-ui'; ctx.textAlign='left'; ctx.fillText('♥ PROGRESS',22,42);
}

function step(){
  // paddle control
  if(keys['ArrowLeft']||keys['a']) paddle.tx-=paddle.speed;
  if(keys['ArrowRight']||keys['d']) paddle.tx+=paddle.speed;
  if(demo){
    // AI track ball
    const target = ball.stuck? W/2 : ball.x;
    // predict slight
    paddle.tx += (target - (paddle.x+paddle.w/2))*0.18;
    if(ball.stuck && Math.random()<0.02) launch();
    if(ball.stuck===false && ball.y>H-100 && Math.random()<.02) {}
  }
  paddle.tx=Math.max(6,Math.min(W-paddle.w-6,paddle.tx));
  // smooth
  if(demo) paddle.x += (paddle.tx-paddle.x)*0.35;
  else paddle.x = paddle.tx;

  if(ball.stuck){ ball.x=paddle.x+paddle.w/2; ball.y=paddle.y-ball.r-4; }

  if(!ball.stuck && !gameOver){
    ball.x+=ball.vx; ball.y+=ball.vy;
    // walls
    if(ball.x<ball.r){ball.x=ball.r; ball.vx*=-1;}
    if(ball.x>W-ball.r){ball.x=W-ball.r; ball.vx*=-1;}
    if(ball.y<ball.r+30){ball.y=ball.r+30; ball.vy*=-1;}
    // paddle collision
    if(ball.vy>0 && ball.y+ball.r>=paddle.y && ball.y+ball.r<=paddle.y+14 && ball.x>=paddle.x-4 && ball.x<=paddle.x+paddle.w+4){
      const rel=(ball.x-(paddle.x+paddle.w/2))/(paddle.w/2);
      const ang=rel*1.05; // -60..60 deg
      const sp=Math.min(ball.speed+0.05,9.5);
      ball.vx=Math.sin(ang)*sp; ball.vy=-Math.cos(ang)*sp;
      ball.y=paddle.y-ball.r-1;
      burstHearts(ball.x,paddle.y, '#ff4d7a', 8, .5);
    }
    // bricks
    for(const br of bricks){
      if(!br.alive) continue;
      if(ball.x+ball.r>br.x && ball.x-ball.r<br.x+br.w && ball.y+ball.r>br.y && ball.y-ball.r<br.y+br.h){
        // determine bounce axis by overlap
        const overlapX = Math.min(ball.x+ball.r-br.x, br.x+br.w-(ball.x-ball.r));
        const overlapY = Math.min(ball.y+ball.r-br.y, br.y+br.h-(ball.y-ball.r));
        if(overlapX<overlapY) ball.vx*=-1; else ball.vy*=-1;

        if(br.type==='hard'){
          burstHearts(ball.x,ball.y,'#9b5de5',6,.4);
          addFloater(br.x+br.w/2,br.y,'CLANG!','#c77dff');
        } else {
          br.alive=false;
          const pts= br.type==='gold'?30:10;
          score+=pts; updateHUD();
          burstHearts(br.x+br.w/2,br.y+br.h/2,br.color, br.type==='gold'?42:28, br.type==='gold'?1.4:1);
          addFloater(br.x+br.w/2,br.y, `+${pts} ♥`, br.type==='gold'?'#ffd166':'#ff8fa3');
          // check win
          if(bricks.every(b=>!b.alive||b.type==='hard')){
            level++; buildLevel(level); resetBall();
            addFloater(W/2,200,`LEVEL ${level}!`,'#fff');
            burstHearts(W/2,180,'#ffd166',50,1.5);
            score+=100; updateHUD();
          }
        }
        break;
      }
    }
    // bottom - lose life
    if(ball.y-ball.r>H){
      lives--; updateHUD();
      burstHearts(ball.x,H-20,'#ff4d7a',20,1);
      if(lives<=0){
        gameOver=true; showOverlay(true);
      } else {
        resetBall();
        if(demo) setTimeout(launch,600);
      }
    }
  }

  // particles physics
  particles=particles.filter(p=>{
    p.x+=p.vx; p.y+=p.vy; p.vy+=0.06; p.vx*=.99; p.rot+=p.vr; p.life-=p.decay;
    return p.life>0 && p.y<H+30;
  });
  floaters=floaters.filter(f=>{f.y+=f.vy; f.life-=.018; return f.life>0});
  pickups=pickups.filter(p=>{
    p.y+=p.vy; p.vy+=.04; p.x+=p.vx;
    if(p.y>paddle.y && p.y<paddle.y+22 && p.x>paddle.x && p.x<paddle.x+paddle.w){
      heartsCaught++; score+=50; updateHUD();
      addFloater(p.x,p.y,'+50 ♥','#ff2e63');
      burstHearts(p.x,p.y,'#ff4d7a',16,.8);
      return false;
    }
    return p.y<H+30;
  });
}

function loop(){ step(); draw(); requestAnimationFrame(loop); }

function showOverlay(over){
  const ov=$('overlay');
  if(over){
    ov.classList.remove('hidden');
    $('ovTitle').textContent= gameOver?'🍔 Game Over!':'🎉 You Win!';
    $('ovText').innerHTML=`Final Score: <b>${score}</b> &nbsp;•&nbsp; Hearts caught: <b>${heartsCaught} ♥</b><br>Click restart to play again — or keep watching demo!`;
    $('ovHint').textContent='↺ CLICK RESTART TO PLAY AGAIN';
  } else {
    ov.classList.add('hidden');
  }
}
function startPlaying(){
  if(demo){
    demo=false; playing=true; showOverlay(false);
    if(ball.stuck) launch();
  }
}

$('stage').addEventListener('pointerdown', e=>{
  if(gameOver) return;
  startPlaying();
  // move paddle to pointer
  const rect=canvas.getBoundingClientRect();
  const x=(e.clientX-rect.left)*(W/rect.width);
  paddle.tx=x-paddle.w/2;
});
canvas.addEventListener('pointermove', e=>{
  if(demo) return;
  const rect=canvas.getBoundingClientRect();
  const x=(e.clientX-rect.left)*(W/rect.width);
  paddle.tx=x-paddle.w/2;
});
window.addEventListener('keydown', e=>{
  keys[e.key]=true;
  if(['ArrowLeft','ArrowRight',' ','Spacebar'].includes(e.key)) e.preventDefault();
  if(e.key===' '){ startPlaying(); launch(); }
  else startPlaying();
});
window.addEventListener('keyup', e=>{ keys[e.key]=false; });

$('restartBtn').addEventListener('click', ()=>{
  resetGame(); demo=false; playing=true; showOverlay(false); launch();
  // if game over overlay was shown, hide it
  $('overlay').classList.add('hidden');
});
// touch buttons
function bindHold(id, dir){
  const el=$(id);
  const down=e=>{e.preventDefault(); keys[dir]=true; startPlaying();};
  const up=()=>{keys[dir]=false};
  el.addEventListener('pointerdown',down); el.addEventListener('pointerup',up); el.addEventListener('pointerleave',up);
}
bindHold('leftBtn','ArrowLeft'); bindHold('rightBtn','ArrowRight');

resetGame();
loop();
// auto launch in demo after 800ms
setTimeout(()=>{ if(demo) launch(); },800);
</script>
</body>
</html>