Models 44

Qwen3.8 Flash

by QwenReasoningClean
Open in new window

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

How it went

Total time
1 min 37 s
Cost
$0.0039
Output tokens
8,161
Spent thinking
0 tokens
Speed
84.7 tok/s
Console errors
0
First token after
748 ms
First visible text after
748 ms
Prompt tokens
376
Code size
21.0 KB
Finish reason
stop
Served by
Alibaba
Model id
qwen/qwen3.8-flash
Reasoning sent
{"effort":"none"}
Max output
64,000 tokens
Generated
Nov 11, 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 · 531 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 — Hearts Everywhere</title>
<style>
  *{margin:0;padding:0;box-sizing:border-box}
  :root{--amber:#ffc247;--cream:#fff4e2;--red:#ff5b6a}
  html,body{height:100%}
  body{
    font-family:"Trebuchet MS","Segoe UI",system-ui,sans-serif;
    color:var(--cream);
    background:radial-gradient(130% 90% at 50% -12%,#3d2a63 0%,#271a40 42%,#160f26 100%);
    display:flex;flex-direction:column;align-items:center;justify-content:center;
    min-height:100vh;overflow-x:hidden;padding:14px;user-select:none;-webkit-tap-highlight-color:transparent;
  }
  .bg{position:fixed;inset:0;z-index:-1;overflow:hidden;pointer-events:none}
  .bg span{position:absolute;font-size:26px;opacity:.14;color:#ffd9c0;animation:drift linear infinite;text-shadow:0 0 12px rgba(255,150,150,.4)}
  @keyframes drift{0%{transform:translateY(108vh) rotate(0deg)}100%{transform:translateY(-12vh) rotate(360deg)}}

  .wrap{display:flex;flex-direction:column;align-items:center;gap:12px;width:min(96vw,640px)}
  .title{font-weight:800;letter-spacing:.5px;font-size:clamp(20px,4.4vw,32px);color:var(--amber);text-shadow:0 2px 0 #a35a12,0 0 22px rgba(255,180,60,.35);display:flex;align-items:center;gap:10px}
  .title b{color:var(--red);text-shadow:0 2px 0 #7a1220,0 0 22px rgba(255,90,110,.4)}

  .hud{display:flex;align-items:center;justify-content:space-between;gap:10px;width:100%;max-width:520px}
  .stat{background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.13);border-radius:14px;padding:7px 14px;display:flex;flex-direction:column;align-items:center;line-height:1.05;min-width:66px}
  .stat .k{font-size:10px;letter-spacing:1.5px;text-transform:uppercase;opacity:.7}
  .stat .v{font-size:22px;font-weight:800;color:var(--amber)}
  .lives{display:flex;gap:5px;align-items:center;margin-top:2px;font-size:15px}
  .heart{filter:drop-shadow(0 0 4px rgba(255,90,110,.7));transition:transform .2s}
  .heart.lost{opacity:.22;filter:grayscale(1);transform:scale(.8)}

  .stage{position:relative;border-radius:18px;overflow:hidden;background:#120a20;
    box-shadow:0 22px 60px rgba(0,0,0,.55),inset 0 0 0 2px rgba(255,255,255,.05)}
  canvas{display:block;touch-action:none}

  .overlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:12px;text-align:center;padding:22px;
    background:linear-gradient(rgba(16,10,30,.62),rgba(16,10,30,.86));backdrop-filter:blur(2px);z-index:5;cursor:pointer}
  .overlay.hidden{display:none}
  .bigemoji{font-size:64px;animation:bob 1.6s ease-in-out infinite}
  @keyframes bob{0%,100%{transform:translateY(0) rotate(-4deg)}50%{transform:translateY(-12px) rotate(4deg)}}
  .ov-title{font-size:clamp(24px,6vw,40px);font-weight:800;color:var(--amber);text-shadow:0 3px 0 #a35a12}
  .ov-sub{font-size:14px;opacity:.9;max-width:340px;line-height:1.5}
  .prompt{font-size:15px;font-weight:700;color:var(--cream);padding:9px 20px;border-radius:999px;background:rgba(255,90,110,.18);border:1px solid rgba(255,90,110,.5);animation:pulse 1.4s ease-in-out infinite}
  @keyframes pulse{0%,100%{transform:scale(1);opacity:.85}50%{transform:scale(1.06);opacity:1}}
  .foot{font-size:12px;opacity:.6}
  .foot kbd{background:rgba(255,255,255,.12);border-radius:5px;padding:1px 6px;font-family:inherit}
</style>
</head>
<body>
<div class="bg" id="bg"></div>
<div class="wrap">
  <div class="title">🍔 Hamburger <b>Breakout</b></div>
  <div class="hud">
    <div class="stat"><span class="k">Score</span><span class="v" id="score">0</span></div>
    <div class="stat"><span class="k">Lives</span><div class="lives" id="lives"></div></div>
    <div class="stat"><span class="k">Level</span><span class="v" id="level">1</span></div>
  </div>
  <div class="stage" id="stage">
    <canvas id="game"></canvas>
    <div class="overlay" id="overlay">
      <div class="bigemoji" id="ovEmoji">🍔</div>
      <div class="ov-title" id="ovTitle">Hamburger Breakout</div>
      <div class="ov-sub" id="ovSub">Smash every brick with the burger — they explode into a shower of hearts! 💖</div>
      <div class="prompt" id="ovPrompt">Click or press any key to start</div>
      <div class="foot">Move with your mouse / finger · paddle follows · <kbd>P</kbd> pause · <kbd>R</kbd> restart</div>
    </div>
  </div>
</div>

<script>
(function(){
"use strict";
const W=520,H=720;
const cvs=document.getElementById('game');
const ctx=cvs.getContext('2d');
const stage=document.getElementById('stage');
const overlay=document.getElementById('overlay');
const scoreEl=document.getElementById('score');
const levelEl=document.getElementById('level');
const livesEl=document.getElementById('lives');
const ovEmoji=document.getElementById('ovEmoji');
const ovTitle=document.getElementById('ovTitle');
const ovSub=document.getElementById('ovSub');
const ovPrompt=document.getElementById('ovPrompt');

let dpr=Math.min(window.devicePixelRatio||1,2);
function fit(){
  const availW=Math.min(document.querySelector('.wrap').clientWidth,W);
  const scale=availW/W;
  const dispW=W*scale, dispH=H*scale;
  stage.style.width=dispW+'px'; stage.style.height=dispH+'px';
  cvs.style.width=dispW+'px'; cvs.style.height=dispH+'px';
  dpr=Math.min(window.devicePixelRatio||1,2);
  cvs.width=Math.round(dispW*dpr); cvs.height=Math.round(dispH*dpr);
}
fit(); window.addEventListener('resize',fit);

// ---------- helpers ----------
function clamp(v,a,b){return v<a?a:v>b?b:v;}
function rand(a,b){return a+Math.random()*(b-a);}
function rrp(g,x,y,w,h,r){r=Math.min(r,w/2,h/2);g.beginPath();g.moveTo(x+r,y);g.arcTo(x+w,y,x+w,y+h,r);g.arcTo(x+w,y+h,x,y+h,r);g.arcTo(x,y+h,x,y,r);g.arcTo(x,y,x+w,y,r);g.closePath();}
function heartPath(g,cx,cy,s){g.save();g.translate(cx,cy);g.scale(s,s);g.beginPath();g.moveTo(0,-0.5);g.bezierCurveTo(0.5,-1.1,1.3,-0.4,0,0.9);g.bezierCurveTo(-1.3,-0.4,-0.5,-1.1,0,-0.5);g.closePath();g.restore();}

// ---------- palette ----------
const COL={
  bunTop:'#f2b04a',bunBot:'#e0a24a',crust:'#c98a34',seed:'#fdf0cf',
  lettuce:'#7cc24b',tomato:'#e24b4b',cheese:'#f6c64b',patty:'#6b3f24',
  onion:'#e7dcef',ball:'#ffe08a'
};
const BRICK_COLORS=[COL.tomato,COL.lettuce,COL.cheese,COL.onion,'#e88fb0','#f0a35e'];
const HEART_COLORS=['#ff5b6a','#ff8fa3','#ff2d55','#ffb3c1','#ff6b81','#e63950'];

// ---------- state ----------
let state='demo'; // demo | playing | paused | gameover | win
let score=0,lives=3,level=1;
let shake=0,flash=0;
let bricks=[],particles=[],floaters=[];
let combo=0,lastBrickTime=0;
let ball,paddle;
let pointerX=null;
let lastTs=performance.now(),acc=0;const STEP=1000/60;

const PW_BASE=100,PH_BASE=34,BR=9;
const BALL_SPEEDS=[350,380,410,440,470];

function initEntities(){
  ball={x:W/2,y:H-140,vx:0,vy:0,r:BR,trail:[],speed:BALL_SPEEDS[Math.min(level-1,4)]};
  paddle={w:PW_BASE-(level-1)*6,x:W/2,y:H-58,targetX:W/2};
  if(paddle.w<70)paddle.w=70;
}
initEntities();

function serveBall(fromLeft){
  ball.speed=BALL_SPEEDS[Math.min(level-1,4)];
  let ang=(fromLeft?-1:1)*(rand(Math.PI*0.16,Math.PI*0.34))+Math.PI/2;
  ball.vx=Math.cos(ang)*ball.speed;
  ball.vy=-Math.abs(Math.sin(ang))*ball.speed;
  ball.x=paddle.x;ball.y=paddle.y-paddle.h/2-ball.r-2;
  ball.trail.length=0;
}

function buildLevel(lv){
  bricks=[];
  const cols=8,gap=6,top=66,left=24,right=24;
  const bw=(W-left-right-gap*(cols-1))/cols;
  const bh=26;
  const rows=Math.min(3+lv,7);
  for(let r=0;r<rows;r++){
    for(let c=0;c<cols;c++){
      const hp=(r%2===0&&lv>1)?2:1;
      const cx=left+c*(bw+gap)+bw/2;
      const cy=top+r*(bh+gap)+bh/2;
      bricks.push({x:cx-bw/2,y:cy-bh/2,w:bw,h:bh,hp,maxhp:hp,color:BRICK_COLORS[(r+c)%BRICK_COLORS.length],alive:true});
    }
  }
}

function resetGame(){
  score=0;lives=3;level=1;combo=0;
  particles=[];floaters=[];shake=0;flash=0;
  buildLevel(level);initEntities();serveBall(true);updateHUD();
}
function nextLevel(){
  level++;buildLevel(level);initEntities();serveBall(true);updateHUD();
}

// ---------- particles ----------
function spawnHearts(x,y,n,opts){
  opts=opts||{};
  for(let i=0;i<n;i++){
    const a=rand(0,Math.PI*2);
    const sp=rand(opts.spMin||120,opts.spMax||380);
    particles.push({
      type:'heart',x:x+rand(-6,6),y:y+rand(-6,6),
      vx:Math.cos(a)*sp,vy:Math.sin(a)*sp-rand(40,160),
      rot:rand(0,Math.PI*2),vr:rand(-6,6),
      size:rand(8,18),life:1,decay:rand(0.008,0.016),
      grav:rand(120,260),drag:0.992,
      color:HEART_COLORS[i%HEART_COLORS.length]
    });
  }
}
function spawnBurst(x,y,n,color){
  for(let i=0;i<n;i++){
    const a=rand(0,Math.PI*2),sp=rand(60,240);
    particles.push({type:'spark',x,y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp,
      life:1,decay:rand(0.03,0.06),size:rand(2,4),color,grav:60,drag:0.9});
  }
}
function spawnCrumbs(x,y,color){
  for(let i=0;i<10;i++){
    const a=rand(-Math.PI*0.9,-Math.PI*0.1),sp=rand(80,220);
    particles.push({type:'crumb',x:x+rand(-10,10),y:y+rand(-8,8),
      vx:Math.cos(a)*sp,vy:Math.sin(a)*sp,size:rand(3,6),rot:rand(0,6),vr:rand(-8,8),
      life:1,decay:rand(0.01,0.02),grav:420,drag:0.99,color});
  }
}
function updateParticles(dt){
  for(let i=particles.length-1;i>=0;i--){
    const p=particles[i];
    p.vy+=p.grav*dt;p.vx*=p.drag;p.vy*=p.drag;
    p.x+=p.vx*dt;p.y+=p.vy*dt;
    if(p.rot!==undefined)p.rot+=p.vr*dt;
    p.life-=p.decay;
    if(p.life<=0||p.y>H+40)particles.splice(i,1);
  }
  for(let i=floaters.length-1;i>=0;i--){
    const f=floaters[i];f.y-=38*dt;f.life-=dt;
    if(f.life<=0)floaters.splice(i,1);
  }
}

// ---------- gameplay events ----------
function onBrickHit(b){
  const now=performance.now();
  combo=(now-lastBrickTime<650)?combo+1:1;lastBrickTime=now;
  b.hp--;
  const pts=10*combo;score+=pts;
  floaters.push({x:b.x+b.w/2,y:b.y,text:'+'+pts+(combo>1?' x'+combo:''),life:0.9,color:combo>1?'#ffd24a':'#fff'});
  flash=Math.max(flash,0.12);shake=Math.max(shake,combo>2?7:4);
  spawnCrumbs(b.x+b.w/2,b.y+b.h/2,b.color);
  spawnBurst(b.x+b.w/2,b.y+b.h/2,14,'#ffffff');
  if(b.hp<=0){
    b.alive=false;
    spawnHearts(b.x+b.w/2,b.y+b.h/2,26,{spMin:140,spMax:420});
    spawnHearts(b.x+b.w/2,b.y+b.h/2,10,{spMin:60,spMax:160});
    shake=Math.max(shake,9);
  } else {
    spawnHearts(b.x+b.w/2,b.y+b.h/2,8,{spMin:80,spMax:220});
  }
  updateHUD();
}
function loseLife(){
  lives--;updateHUD();
  shake=Math.max(shake,14);flash=Math.max(flash,0.25);
  spawnHearts(ball.x,H-20,20,{spMin:120,spMax:300});
  if(lives<=0){state='gameover';showOverlay('💀','Game Over','The burger ran out of lives.','Final Score: '+score,'Click or press a key to play again');}
  else serveBall(true);
}
function checkWin(){
  if(bricks.every(b=>!b.alive)){
    if(state==='playing'){state='win';showOverlay('🎉','Level Cleared!','All bricks exploded into hearts!','Score: '+score,'Click or press a key for Level '+(level+1));}
    else{nextLevel();}
  }
}

// ---------- physics ----------
function step(dt){
  // paddle follow
  if(state==='playing'||state==='demo'){
    paddle.targetX=clamp(pointerX!=null?pointerX:(ball.x),paddle.w/2,W-paddle.w/2);
    paddle.x+=(paddle.targetX-paddle.x)*Math.min(1,dt*16);
  }
  // ball move + substeps
  const steps=Math.max(1,Math.ceil((Math.hypot(ball.vx,ball.vy)*dt)/(ball.r*0.8)));
  const sdt=dt/steps;
  for(let s=0;s<steps;s++){
    ball.x+=ball.vx*sdt;ball.y+=ball.vy*sdt;
    // walls
    if(ball.x-ball.r<0){ball.x=ball.r;ball.vx=Math.abs(ball.vx);spawnSparkWall(ball.x,ball.y);}
    if(ball.x+ball.r>W){ball.x=W-ball.r;ball.vx=-Math.abs(ball.vx);spawnSparkWall(ball.x,ball.y);}
    if(ball.y-ball.r<0){ball.y=ball.r;ball.vy=Math.abs(ball.vy);spawnSparkWall(ball.x,ball.y);}
    // bricks
    for(const b of bricks){
      if(!b.alive)continue;
      if(ball.x+ball.r>b.x&&ball.x-ball.r<b.x+b.w&&ball.y+ball.r>b.y&&ball.y-ball.r<b.y+b.h){
        const overlapL=ball.x+ball.r-b.x, overlapR=b.x+b.w-(ball.x-ball.r);
        const overlapT=ball.y+ball.r-b.y, overlapB=b.y+b.h-(ball.y-ball.r);
        const m=Math.min(overlapL,overlapR,overlapT,overlapB);
        if(m===overlapL){ball.x=b.x-ball.r;ball.vx=-Math.abs(ball.vx);}
        else if(m===overlapR){ball.x=b.x+b.w+ball.r;ball.vx=Math.abs(ball.vx);}
        else if(m===overlapT){ball.y=b.y-ball.r;ball.vy=-Math.abs(ball.vy);}
        else{ball.y=b.y+b.h+ball.r;ball.vy=Math.abs(ball.vy);}
        onBrickHit(b);break;
      }
    }
    // paddle
    if(ball.vy>0&&ball.y+ball.r>paddle.y-paddle.h/2&&ball.y-ball.r<paddle.y+paddle.h/2&&
       ball.x>paddle.x-paddle.w/2-ball.r&&ball.x<paddle.x+paddle.w/2+ball.r){
      const off=clamp((ball.x-paddle.x)/(paddle.w/2),-1,1);
      const maxAng=Math.PI*0.38;
      const ang=off*maxAng;
      const speed=Math.hypot(ball.vx,ball.vy);
      ball.vx=Math.sin(ang)*speed;
      ball.vy=-Math.abs(Math.cos(ang)*speed);
      ball.y=paddle.y-paddle.h/2-ball.r;
      if(state==='playing')combo=0;
      spawnHearts(ball.x,paddle.y-paddle.h/2,5,{spMin:60,spMax:140});
    }
    // bottom
    if(ball.y-ball.r>H){loseLife();if(state!=='playing'&&state!=='demo')break;}
  }
  // trail
  ball.trail.push({x:ball.x,y:ball.y});
  if(ball.trail.length>14)ball.trail.shift();
  checkWin();
  // decay fx
  shake*=Math.pow(0.86,dt*60);
  flash*=Math.pow(0.85,dt*60);
}
function spawnSparkWall(x,y){spawnBurst(x,y,6,'#ffe08a');}

// ---------- drawing ----------
function drawBackground(t){
  const g=ctx.createLinearGradient(0,0,0,H);
  g.addColorStop(0,'#2c1d4d');g.addColorStop(0.5,'#241738');g.addColorStop(1,'#180f28');
  ctx.fillStyle=g;ctx.fillRect(0,0,W,H);
  // soft glow
  const rg=ctx.createRadialGradient(W/2,H*0.35,20,W/2,H*0.35,W*0.7);
  rg.addColorStop(0,'rgba(255,150,120,0.10)');rg.addColorStop(1,'rgba(255,150,120,0)');
  ctx.fillStyle=rg;ctx.fillRect(0,0,W,H);
  // faint drifting hearts in bg (behind everything)
  ctx.save();ctx.globalAlpha=0.06;ctx.fillStyle='#ffb0c0';
  for(let i=0;i<10;i++){
    const x=(i*97+t*0.02*((i%3)+1))%(W+60)-30;
    const y=((i*131+t*0.03*((i%4)+1))%(H+60))-30;
    heartPath(ctx,x,y,26);ctx.fill();
  }
  ctx.restore();
}

function drawBrick(b,t){
  const dmg=b.hp/b.maxhp;
  ctx.save();
  ctx.shadowColor='rgba(0,0,0,.35)';ctx.shadowBlur=6;ctx.shadowOffsetY=3;
  rrp(ctx,b.x,b.y,b.w,b.h,7);ctx.fillStyle=b.color;ctx.fill();
  ctx.shadowColor='transparent';
  // top highlight
  rrp(ctx,b.x+2,b.y+2,b.w-4,b.h*0.42,5);ctx.fillStyle='rgba(255,255,255,.22)';ctx.fill();
  // bottom shade
  rrp(ctx,b.x+2,b.y+b.h*0.6,b.w-4,b.h*0.32,5);ctx.fillStyle='rgba(0,0,0,.16)';ctx.fill();
  // little seed dot
  ctx.fillStyle='rgba(255,255,255,.35)';
  ctx.beginPath();ctx.arc(b.x+b.w*0.5,b.y+b.h*0.4,2,0,7);ctx.fill();
  // damage crack
  if(dmg<1){
    ctx.strokeStyle='rgba(0,0,0,.4)';ctx.lineWidth=1.4;
    ctx.beginPath();
    ctx.moveTo(b.x+b.w*0.5,b.y+3);
    ctx.lineTo(b.x+b.w*0.42,b.y+b.h*0.5);
    ctx.lineTo(b.x+b.w*0.58,b.y+b.h*0.6);
    ctx.lineTo(b.x+b.w*0.5,b.y+b.h-3);
    ctx.stroke();
  }
  ctx.restore();
}

function drawPaddle(t){
  const px=paddle.x,py=paddle.y,pw=paddle.w,ph=paddle.h;
  ctx.save();
  ctx.translate(px,py);
  ctx.rotate(clamp((paddle.targetX-paddle.x)/pw,-0.12,0.12));
  const hw=pw/2,hh=ph/2;
  ctx.shadowColor='rgba(0,0,0,.4)';ctx.shadowBlur=10;ctx.shadowOffsetY=5;
  // bottom bun
  rrp(ctx,-hw,hh-12,pw,12,6);ctx.fillStyle=COL.bunBot;ctx.fill();
  ctx.shadowColor='transparent';
  // patty
  rrp(ctx,-hw+3,hh-20,pw-6,9,4);ctx.fillStyle=COL.patty;ctx.fill();
  // cheese (dripping zigzag)
  ctx.fillStyle=COL.cheese;
  ctx.beginPath();
  ctx.moveTo(-hw+2,hh-22);ctx.lineTo(hw-2,hh-22);ctx.lineTo(hw-2,hh-16);
  for(let x=hw-2;x>-hw+2;x-=10){ctx.lineTo(x-5,hh-11);ctx.lineTo(x-10,hh-16);}
  ctx.lineTo(-hw+2,hh-16);ctx.closePath();ctx.fill();
  // lettuce
  ctx.fillStyle=COL.lettuce;
  ctx.beginPath();
  const ly=hh-24;
  ctx.moveTo(-hw+1,ly+6);
  for(let x=-hw+1;x<hw;x+=8){ctx.quadraticCurveTo(x+4,ly-4,x+8,ly+6);}
  ctx.lineTo(hw-1,ly+9);ctx.lineTo(-hw+1,ly+9);ctx.closePath();ctx.fill();
  // tomato slices
  ctx.fillStyle=COL.tomato;
  ctx.beginPath();ctx.ellipse(-pw*0.22,ly-2,11,6,0,0,7);ctx.fill();
  ctx.beginPath();ctx.ellipse(pw*0.22,ly-2,11,6,0,0,7);ctx.fill();
  ctx.fillStyle='rgba(255,255,255,.35)';
  ctx.beginPath();ctx.ellipse(-pw*0.22,ly-3,5,2.4,0,0,7);ctx.fill();
  ctx.beginPath();ctx.ellipse(pw*0.22,ly-3,5,2.4,0,0,7);ctx.fill();
  // top bun dome
  rrp(ctx,-hw,-hh,pw,ph*0.55,[hw,hw,10,10]);ctx.fillStyle=COL.bunTop;ctx.fill();
  // sesame seeds
  ctx.fillStyle=COL.seed;
  const seeds=[[-0.55,-0.7],[-0.2,-0.85],[0.15,-0.8],[0.5,-0.65],[-0.35,-0.55],[0.35,-0.5],[0.02,-0.6]];
  for(const s of seeds){ctx.save();ctx.translate(s[0]*hw,s[1]*hh);ctx.rotate(s[0]);rrp(ctx,-2.4,-1.4,4.8,2.8,1.4);ctx.fill();ctx.restore();}
  ctx.restore();
}

function drawBall(t){
  // trail
  for(let i=0;i<ball.trail.length;i++){
    const p=ball.trail[i],a=i/ball.trail.length;
    ctx.save();ctx.globalAlpha=a*0.35;ctx.fillStyle=COL.ball;
    ctx.beginPath();ctx.arc(p.x,p.y,ball.r*a,0,7);ctx.fill();ctx.restore();
  }
  ctx.save();
  ctx.shadowColor='rgba(255,200,90,.8)';ctx.shadowBlur=14;
  const g=ctx.createRadialGradient(ball.x-3,ball.y-3,1,ball.x,ball.y,ball.r);
  g.addColorStop(0,'#fff6d8');g.addColorStop(1,COL.ball);
  ctx.fillStyle=g;ctx.beginPath();ctx.arc(ball.x,ball.y,ball.r,0,7);ctx.fill();
  ctx.restore();
}

function drawParticles(){
  for(const p of particles){
    const a=clamp(p.life,0,1);
    if(p.type==='heart'){
      ctx.save();ctx.globalAlpha=a;
      ctx.shadowColor='rgba(255,90,120,.6)';ctx.shadowBlur=8;
      ctx.fillStyle=p.color;
      heartPath(ctx,p.x,p.y,p.size);ctx.fill();
      ctx.restore();
    } else if(p.type==='spark'){
      ctx.save();ctx.globalAlpha=a;ctx.fillStyle=p.color;
      ctx.beginPath();ctx.arc(p.x,p.y,p.size,0,7);ctx.fill();ctx.restore();
    } else { // crumb
      ctx.save();ctx.globalAlpha=a;ctx.fillStyle=p.color;
      ctx.translate(p.x,p.y);ctx.rotate(p.rot);
      ctx.fillRect(-p.size/2,-p.size/2,p.size,p.size);ctx.restore();
    }
  }
}

function drawFloaters(){
  ctx.textAlign='center';ctx.font='800 18px "Trebuchet MS",sans-serif';
  for(const f of floaters){
    ctx.save();ctx.globalAlpha=clamp(f.life/0.9,0,1);
    ctx.fillStyle=f.color;ctx.shadowColor='rgba(0,0,0,.5)';ctx.shadowBlur=4;
    ctx.fillText(f.text,f.x,f.y);ctx.restore();
  }
}

function render(t){
  ctx.setTransform(dpr,0,0,dpr,0,0);
  ctx.clearRect(0,0,W,H);
  ctx.save();
  if(shake>0.3)ctx.translate(rand(-shake,shake),rand(-shake,shake));
  drawBackground(t);
  for(const b of bricks)if(b.alive)drawBrick(b,t);
  drawPaddle(t);
  drawBall(t);
  drawParticles();
  drawFloaters();
  ctx.restore();
  if(flash>0.02){ctx.fillStyle='rgba(255,255,255,'+(flash*0.5)+')';ctx.fillRect(0,0,W,H);}
}

// ---------- HUD ----------
function updateHUD(){
  scoreEl.textContent=score;
  levelEl.textContent=level;
  livesEl.innerHTML='';
  for(let i=0;i<3;i++){
    const s=document.createElement('span');
    s.className='heart'+(i<lives?'':' lost');
    s.textContent='❤️';livesEl.appendChild(s);
  }
}

// ---------- overlay ----------
function showOverlay(emoji,title,sub,extra,prompt){
  overlay.classList.remove('hidden');
  ovEmoji.textContent=emoji;
  ovTitle.textContent=title;
  ovSub.innerHTML=sub+(extra?'<br><strong style="color:#ffc247">'+extra+'</strong>':'');
  ovPrompt.textContent=prompt;
}
function hideOverlay(){overlay.classList.add('hidden');}

function handleStart(){
  if(state==='gameover'){resetGame();state='playing';hideOverlay();return;}
  if(state==='win'){nextLevel();state='playing';hideOverlay();return;}
  if(state==='paused'){state='playing';hideOverlay();return;}
  if(state==='demo'){state='playing';hideOverlay();}
}

// ---------- input ----------
function setPointer(clientX){
  const rect=cvs.getBoundingClientRect();
  pointerX=clamp((clientX-rect.left)/rect.width*W,0,W);
}
cvs.addEventListener('pointermove',e=>setPointer(e.clientX));
cvs.addEventListener('pointerdown',e=>{setPointer(e.clientX);handleStart();});
window.addEventListener('keydown',e=>{
  if(e.key==='p'||e.key==='P'){
    if(state==='playing'){state='paused';showOverlay('⏸️','Paused','Take a breather.','Score: '+score,'Press P to resume');}
    else if(state==='paused'){state='playing';hideOverlay();}
    return;
  }
  if(e.key==='r'||e.key==='R'){resetGame();state='playing';hideOverlay();return;}
  if(!['ArrowLeft','ArrowRight'].includes(e.key))handleStart();
});
document.addEventListener('visibilitychange',()=>{if(document.hidden&&(state==='playing')){state='paused';showOverlay('⏸️','Paused','You tabbed away.','Score: '+score,'Press P to resume');}});

// ---------- loop ----------
function frame(ts){
  let dt=(ts-lastTs)/1000;lastTs=ts;
  if(dt>0.05)dt=0.05;
  acc+=dt*1000;
  let guard=0;
  while(acc>=STEP&&guard<5){step(STEP/1000);acc-=STEP;guard++;}
  updateParticles(dt);
  render(ts);
  requestAnimationFrame(frame);
}

// ---------- boot ----------
buildLevel(level);
updateHUD();
requestAnimationFrame(frame);

// decorative floating emoji behind page
(function(){
  const bg=document.getElementById('bg');
  const em=['💖','🍔','❤️','🍟','💕','🧅','🥓','💗'];
  for(let i=0;i<16;i++){
    const s=document.createElement('span');
    s.textContent=em[i%em.length];
    s.style.left=(Math.random()*100)+'%';
    s.style.animationDuration=(14+Math.random()*20)+'s';
    s.style.animationDelay=(-Math.random()*30)+'s';
    s.style.fontSize=(18+Math.random()*22)+'px';
    bg.appendChild(s);
  }
})();
})();
</script>
</body>
</html>