Models 44

DeepSeek V4.1 Flash 1017

by DeepSeekReasoning1 console error
Open in new window

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

How it went

Total time
43.2 s
Cost
$0.0063
Output tokens
11,520
Spent thinking
1 tokens
Speed
269.1 tok/s
Console errors
1
First token after
388 ms
First visible text after
388 ms
Prompt tokens
385
Code size
31.0 KB
Finish reason
stop
Served by
StepFun
Model id
deepseek/deepseek-v4.1-flash-1017
Reasoning sent
{"effort":"max"}
Max output
64,000 tokens
Generated
Nov 10, 2026

Console errors

Caught in the first 3 seconds after load, in a sandboxed iframe like the one above.

Failed to load resource: the server responded with a status of 404 (Not Found)

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 · 1003 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 {
    width: 100%; height: 100%;
    overflow: hidden;
    background: #1a0a2e;
    font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
    -webkit-user-select: none; user-select: none;
  }
  #game {
    display: block;
    width: 100%; height: 100%;
    cursor: none;
  }
  #overlay {
    position: absolute;
    inset: 0;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: rgba(26, 10, 46, 0.55);
    backdrop-filter: blur(3px);
    transition: opacity 0.4s;
    z-index: 10;
    pointer-events: none;
  }
  #overlay.hidden { opacity: 0; }
  #overlay .card {
    text-align: center;
    padding: 32px 44px;
    border-radius: 22px;
    background: rgba(40, 16, 68, 0.85);
    border: 2px solid rgba(255, 120, 170, 0.55);
    box-shadow: 0 0 60px rgba(255, 90, 150, 0.35), inset 0 0 30px rgba(255, 120, 170, 0.12);
    animation: floaty 3s ease-in-out infinite;
  }
  @keyframes floaty {
    0%, 100% { transform: translateY(0); }
    50% { transform: translateY(-8px); }
  }
  #overlay h1 {
    font-size: clamp(26px, 4.4vw, 48px);
    color: #ffd7e8;
    letter-spacing: 1px;
    text-shadow: 0 0 18px rgba(255, 90, 150, 0.8), 0 3px 0 #7a1f4b;
    margin-bottom: 10px;
  }
  #overlay h1 .emoji { font-size: 1.15em; }
  #overlay p {
    font-size: clamp(13px, 1.6vw, 19px);
    color: #ffb3d1;
    margin-bottom: 6px;
  }
  #overlay .hint {
    margin-top: 16px;
    font-size: clamp(11px, 1.3vw, 15px);
    color: #c9a5e8;
    animation: pulse 1.6s ease-in-out infinite;
  }
  @keyframes pulse {
    0%, 100% { opacity: 0.55; }
    50% { opacity: 1; }
  }
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="overlay">
  <div class="card">
    <h1>🍔 Burger Breakout 💕</h1>
    <p>The hamburger paddle. The heart bricks.</p>
    <p>Every brick explodes into a shower of little hearts.</p>
    <div class="hint">Click or press any key to start</div>
  </div>
</div>
<script>
(() => {
  'use strict';

  const canvas = document.getElementById('game');
  const ctx = canvas.getContext('2d');
  const overlay = document.getElementById('overlay');

  let W = 0, H = 0, DPR = 1;

  function resize() {
    DPR = Math.min(window.devicePixelRatio || 1, 2);
    W = window.innerWidth;
    H = window.innerHeight;
    canvas.width = Math.floor(W * DPR);
    canvas.height = Math.floor(H * DPR);
    canvas.style.width = W + 'px';
    canvas.style.height = H + 'px';
    ctx.setTransform(DPR, 0, 0, DPR, 0, 0);
    layout();
  }

  // ---------- Layout constants ----------
  const STATE = { DEMO: 0, PLAY: 1, DEAD: 2 };
  let state = STATE.DEMO;
  let started = false;

  const COLS = 10;
  let ROWS = 5;

  let playLeft, playTop, playW, playH;
  let brickGap = 6, brickW = 0, brickH = 0;
  let playAreaRight, playAreaBottom;

  let paddle = { x: 0, y: 0, w: 118, h: 26, targetX: 0, vx: 0 };
  let ball = { x: 0, y: 0, r: 8, vx: 0, vy: 0, speed: 0, stuck: true };
  let bricks = [];
  let hearts = [];
  let particles = [];
  let floatingTexts = [];
  let score = 0, lives = 3, level = 1;
  let shake = 0;
  let time = 0;
  let demoResetTimer = 0;

  const BRICK_COLORS = [
    ['#ff5f9e', '#c72c6b'],
    ['#ff8fb8', '#e04b86'],
    ['#c56bff', '#8a2be2'],
    ['#ffa94d', '#e07b1a'],
    ['#5fd4ff', '#1f9fd6'],
    ['#8affa1', '#2fbf55']
  ];

  function layout() {
    playTop = Math.max(64, H * 0.11);
    playW = Math.min(W * 0.94, 980);
    playLeft = (W - playW) / 2;
    playH = H - playTop - Math.max(50, H * 0.075);
    playAreaRight = playLeft + playW;
    playAreaBottom = playTop + playH;

    brickGap = Math.max(4, playW * 0.008);
    brickW = (playW - brickGap * (COLS - 1)) / COLS;
    brickH = Math.min(brickW * 0.42, playH * 0.055);

    paddle.w = Math.min(Math.max(96, playW * 0.16), 170);
    paddle.h = Math.max(20, paddle.w * 0.22);
    paddle.y = playAreaBottom - paddle.h - 8;
    if (!started) paddle.x = playLeft + playW / 2;

    ball.r = Math.max(6, Math.min(11, playW * 0.011));
    if (ball.stuck) {
      ball.x = paddle.x + paddle.w / 2;
      ball.y = paddle.y - ball.r - 2;
    }

    // Keep bricks aligned after a resize (only scale existing positions)
    rebuildBrickPositions();
  }

  function makeBricks() {
    bricks = [];
    ROWS = level >= 4 ? 6 : 5;
    for (let r = 0; r < ROWS; r++) {
      for (let c = 0; c < COLS; c++) {
        const pal = BRICK_COLORS[r % BRICK_COLORS.length];
        bricks.push({
          col: c, row: r,
          x: 0, y: 0, w: 0, h: 0,
          alive: true,
          hp: 1 + (level >= 3 && r === 0 ? 1 : 0),
          maxHp: 1 + (level >= 3 && r === 0 ? 1 : 0),
          color: pal[0], dark: pal[1],
          wob: Math.random() * Math.PI * 2
        });
      }
    }
    rebuildBrickPositions();
  }

  function rebuildBrickPositions() {
    for (const b of bricks) {
      b.x = playLeft + b.col * (brickW + brickGap);
      b.y = playTop + b.row * (brickH + brickGap);
      b.w = brickW;
      b.h = brickH;
    }
  }

  function resetBall(stuck) {
    ball.stuck = stuck;
    ball.speed = Math.min(730, 430 + level * 45);
    const ang = -Math.PI / 2 + (Math.random() - 0.5) * 0.7;
    ball.vx = Math.cos(ang) * ball.speed;
    ball.vy = Math.sin(ang) * ball.speed;
    ball.x = paddle.x + paddle.w / 2;
    ball.y = paddle.y - ball.r - 2;
    if (stuck) {
      ball.vx = 0; ball.vy = 0;
    }
  }

  function newGame() {
    score = 0; lives = 3; level = 1;
    makeBricks();
    paddle.x = playLeft + playW / 2 - paddle.w / 2;
    resetBall(true);
    hearts.length = 0; particles.length = 0; floatingTexts.length = 0;
  }

  function nextLevel() {
    level++;
    makeBricks();
    paddle.x = playLeft + playW / 2 - paddle.w / 2;
    resetBall(true);
    floatingTexts.push({ x: W / 2, y: H * 0.42, text: 'LEVEL ' + level, life: 1.6, max: 1.6, size: 38, color: '#ffd7e8' });
  }

  // ---------- Hearts particle system ----------
  const HEART_COLORS = ['#ff4d8d', '#ff7eb3', '#ff9ecb', '#ff2e6e', '#ffb3d1', '#ff5f9e'];

  function spawnHearts(x, y, w, h, amount) {
    for (let i = 0; i < amount; i++) {
      const a = Math.random() * Math.PI * 2;
      const sp = 60 + Math.random() * 340;
      hearts.push({
        x: x + Math.random() * w,
        y: y + Math.random() * h,
        vx: Math.cos(a) * sp,
        vy: Math.sin(a) * sp - 90,
        size: 3 + Math.random() * 9,
        rot: (Math.random() - 0.5) * 1.2,
        vrot: (Math.random() - 0.5) * 9,
        life: 1 + Math.random() * 1.1,
        maxLife: 2.1,
        color: HEART_COLORS[(Math.random() * HEART_COLORS.length) | 0],
        gravity: 260 + Math.random() * 200,
        drag: 0.985,
        wobble: Math.random() * Math.PI * 2
      });
    }
    // a few sparkle particles
    for (let i = 0; i < Math.floor(amount * 0.5); i++) {
      const a = Math.random() * Math.PI * 2;
      const sp = 40 + Math.random() * 260;
      particles.push({
        x: x + w / 2, y: y + h / 2,
        vx: Math.cos(a) * sp, vy: Math.sin(a) * sp,
        r: 1 + Math.random() * 2.4,
        life: 0.35 + Math.random() * 0.5, maxLife: 0.85,
        color: Math.random() < 0.5 ? '#fff5fa' : '#ffd7e8'
      });
    }
  }

  function drawHeart(cx, cy, size, rot, color, alpha) {
    ctx.save();
    ctx.globalAlpha = alpha;
    ctx.translate(cx, cy);
    ctx.rotate(rot);
    ctx.scale(size / 16, size / 16);
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.moveTo(0, 4);
    ctx.bezierCurveTo(-9, -4, -4, -12, 0, -7);
    ctx.bezierCurveTo(4, -12, 9, -4, 0, 4);
    ctx.closePath();
    ctx.fill();
    ctx.restore();
  }

  // ---------- Hamburger paddle drawing ----------
  function drawBurger(cx, cy, w, h) {
    const bw = w * 0.86;
    const x = cx - bw / 2;
    const s = h;

    ctx.save();
    // shadow
    ctx.fillStyle = 'rgba(0,0,0,0.35)';
    ctx.beginPath();
    ctx.ellipse(cx, cy + h * 0.62, bw * 0.5, h * 0.22, 0, 0, Math.PI * 2);
    ctx.fill();

    // bottom bun
    roundRect(x, cy + s * 0.18, bw, s * 0.45, s * 0.22, '#e8a55c');
    ctx.fillStyle = 'rgba(255,255,255,0.13)';
    roundRect(x + bw * 0.08, cy + s * 0.24, bw * 0.84, s * 0.12, s * 0.1, 'rgba(255,255,255,0.14)');

    // lettuce
    ctx.fillStyle = '#5fbf4b';
    ctx.beginPath();
    const ly = cy + s * 0.06;
    ctx.moveTo(x - bw * 0.02, ly);
    for (let i = 0; i <= 8; i++) {
      const px = x - bw * 0.02 + (bw * 1.04) * (i / 8);
      const dy = (i % 2 === 0 ? 1 : -1) * s * 0.09;
      ctx.lineTo(px, ly + dy);
    }
    ctx.lineTo(x + bw * 1.02, cy + s * 0.26);
    ctx.lineTo(x - bw * 0.02, cy + s * 0.26);
    ctx.closePath();
    ctx.fill();

    // cheese
    ctx.fillStyle = '#ffcc33';
    ctx.beginPath();
    ctx.moveTo(x - bw * 0.03, cy - s * 0.03);
    ctx.lineTo(x + bw * 1.03, cy - s * 0.03);
    ctx.lineTo(x + bw * 0.96, cy + s * 0.1);
    ctx.lineTo(x + bw * 0.78, cy + s * 0.02);
    ctx.lineTo(x + bw * 0.55, cy + s * 0.12);
    ctx.lineTo(x + bw * 0.3, cy + s * 0.01);
    ctx.lineTo(x + bw * 0.1, cy + s * 0.1);
    ctx.closePath();
    ctx.fill();

    // patty
    ctx.fillStyle = '#6b3a1e';
    roundRect(x + bw * 0.02, cy - s * 0.2, bw * 0.96, s * 0.22, s * 0.1, '#6b3a1e');
    ctx.fillStyle = 'rgba(0,0,0,0.22)';
    for (let i = 0; i < 5; i++) {
      const px = x + bw * (0.14 + i * 0.18);
      ctx.beginPath();
      ctx.arc(px, cy - s * 0.09, s * 0.028, 0, Math.PI * 2);
      ctx.fill();
    }

    // top bun
    const grad = ctx.createLinearGradient(0, cy - s * 0.62, 0, cy - s * 0.1);
    grad.addColorStop(0, '#ffcf7a');
    grad.addColorStop(1, '#e59a4a');
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.moveTo(x - bw * 0.02, cy - s * 0.16);
    ctx.quadraticCurveTo(x + bw * 0.5, cy - s * 0.95, x + bw * 1.02, cy - s * 0.16);
    ctx.closePath();
    ctx.fill();

    // sesame seeds
    ctx.fillStyle = 'rgba(255,248,226,0.92)';
    const seeds = [[0.2, -0.42], [0.36, -0.56], [0.5, -0.62], [0.64, -0.55], [0.8, -0.41], [0.28, -0.28], [0.72, -0.28], [0.5, -0.34]];
    for (const [sx, sy] of seeds) {
      ctx.save();
      ctx.translate(x + bw * sx, cy + s * sy);
      ctx.rotate(sx * 1.6);
      ctx.beginPath();
      ctx.ellipse(0, 0, bw * 0.026, bw * 0.014, 0, 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();
    }

    // eyes on the bun
    const eyeY = cy - s * 0.44;
    ctx.fillStyle = '#3a1e0d';
    ctx.beginPath();
    ctx.arc(x + bw * 0.36, eyeY, s * 0.075, 0, Math.PI * 2);
    ctx.arc(x + bw * 0.66, eyeY, s * 0.075, 0, Math.PI * 2);
    ctx.fill();
    ctx.fillStyle = '#fff';
    ctx.beginPath();
    ctx.arc(x + bw * 0.38, eyeY - s * 0.03, s * 0.03, 0, Math.PI * 2);
    ctx.arc(x + bw * 0.68, eyeY - s * 0.03, s * 0.03, 0, Math.PI * 2);
    ctx.fill();

    ctx.restore();
  }

  function roundRect(x, y, w, h, r, fill) {
    ctx.beginPath();
    const rr = Math.min(r, w / 2, h / 2);
    ctx.moveTo(x + rr, y);
    ctx.lineTo(x + w - rr, y);
    ctx.quadraticCurveTo(x + w, y, x + w, y + rr);
    ctx.lineTo(x + w, y + h - rr);
    ctx.quadraticCurveTo(x + w, y + h, x + w - rr, y + h);
    ctx.lineTo(x + rr, y + h);
    ctx.quadraticCurveTo(x, y + h, x, y + h - rr);
    ctx.lineTo(x, y + rr);
    ctx.quadraticCurveTo(x, y, x + rr, y);
    ctx.closePath();
    ctx.fillStyle = fill;
    ctx.fill();
  }

  // ---------- Input ----------
  const keys = { left: false, right: false };
  let pointerX = null;
  let inputActive = false;

  function beginPlay() {
    if (!started) {
      started = true;
      state = STATE.PLAY;
      newGame();
      overlay.classList.add('hidden');
      resetBall(true);
    } else if (state === STATE.DEAD) {
      state = STATE.PLAY;
      resetBall(true);
    } else if (ball.stuck) {
      launchBall();
    }
  }

  function launchBall() {
    if (!ball.stuck) return;
    ball.stuck = false;
    const ang = -Math.PI / 2 + (Math.random() - 0.5) * 0.5;
    ball.vx = Math.cos(ang) * ball.speed;
    ball.vy = Math.sin(ang) * ball.speed;
  }

  window.addEventListener('keydown', (e) => {
    if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') { keys.left = true; inputActive = true; }
    if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') { keys.right = true; inputActive = true; }
    if (e.key === ' ' || e.key === 'Enter') {
      e.preventDefault();
      inputActive = true;
      if (!started) beginPlay();
      else if (ball.stuck) launchBall();
    }
    if (!started && e.key.length === 1) { inputActive = true; beginPlay(); }
    if (['ArrowLeft', 'ArrowRight', ' '].includes(e.key)) e.preventDefault();
  });
  window.addEventListener('keyup', (e) => {
    if (e.key === 'ArrowLeft' || e.key === 'a' || e.key === 'A') keys.left = false;
    if (e.key === 'ArrowRight' || e.key === 'd' || e.key === 'D') keys.right = false;
  });

  function onPointer(x) {
    pointerX = x;
    if (!started) { inputActive = true; beginPlay(); }
    else if (ball.stuck) launchBall();
  }

  canvas.addEventListener('mousemove', (e) => {
    onPointer(e.clientX);
  });
  canvas.addEventListener('touchmove', (e) => {
    e.preventDefault();
    onPointer(e.touches[0].clientX);
  }, { passive: false });
  canvas.addEventListener('touchstart', (e) => {
    e.preventDefault();
    onPointer(e.touches[0].clientX);
  }, { passive: false });
  overlay.addEventListener('pointerdown', () => { inputActive = true; beginPlay(); });

  // ---------- Update ----------
  function update(dt) {
    time += dt;

    if (shake > 0) shake = Math.max(0, shake - dt * 60);

    // paddle control
    if (started && state === STATE.PLAY) {
      if (keys.left || keys.right) {
        const sp = playW * 1.15;
        if (keys.left) paddle.x -= sp * dt;
        if (keys.right) paddle.x += sp * dt;
      } else if (pointerX !== null && inputActive) {
        const targetCenter = pointerX;
        const desired = targetCenter - paddle.w / 2;
        paddle.x += (desired - paddle.x) * Math.min(1, dt * 16);
      }
    } else if (!started) {
      // demo AI in update of paddle below
    }

    // clamp paddle
    paddle.x = Math.max(playLeft - 6, Math.min(playAreaRight - paddle.w + 6, paddle.x));

    // Demo AI
    if (!started) {
      let target;
      if (ball.stuck || state === STATE.DEAD) {
        target = W / 2 - paddle.w / 2;
      } else {
        // predict ball x at paddle height (simple)
        let px = ball.x, py = ball.y, pvx = ball.vx, pvy = ball.vy;
        let guard = 0;
        while (py < paddle.y - 4 && guard < 400) {
          py += pvy * 0.01;
          px += pvx * 0.01;
          if (px < playLeft + ball.r) { px = playLeft + ball.r; pvx = Math.abs(pvx); }
          if (px > playAreaRight - ball.r) { px = playAreaRight - ball.r; pvx = -Math.abs(pvx); }
          guard++;
        }
        // add slight wobble for the hearts of the burger
        target = px + Math.sin(time * 3.4) * paddle.w * 0.16 - paddle.w / 2;
      }
      const diff = target - paddle.x;
      const maxStep = playW * 1.05 * dt;
      paddle.x += Math.max(-maxStep, Math.min(maxStep, diff * Math.min(1, dt * 9)));

      // auto launch after a moment
      if (ball.stuck && state === STATE.PLAY && started === false) {
        demoResetTimer += dt;
        if (demoResetTimer > 0.7) { demoResetTimer = 0; launchBall(); }
      }
    }

    // ball stuck to paddle
    if (ball.stuck) {
      ball.x = paddle.x + paddle.w / 2;
      ball.y = paddle.y - ball.r - 2;
      ball.vx = 0; ball.vy = 0;
      if (!started && state === STATE.PLAY) {
        demoResetTimer += dt;
        if (demoResetTimer > 0.55) { demoResetTimer = 0; launchBall(); }
      }
    } else {
      // physics with substeps to avoid tunneling
      const steps = 3;
      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 < playLeft) {
          ball.x = playLeft + ball.r;
          ball.vx = Math.abs(ball.vx);
          sparkWall(ball.x, ball.y);
        } else if (ball.x + ball.r > playAreaRight) {
          ball.x = playAreaRight - ball.r;
          ball.vx = -Math.abs(ball.vx);
          sparkWall(ball.x, ball.y);
        }
        if (ball.y - ball.r < playTop) {
          ball.y = playTop + ball.r;
          ball.vy = Math.abs(ball.vy);
          sparkWall(ball.x, ball.y);
        }

        // paddle collision
        if (ball.vy > 0 &&
            ball.y + ball.r >= paddle.y &&
            ball.y - ball.r <= paddle.y + paddle.h &&
            ball.x + ball.r >= paddle.x &&
            ball.x - ball.r <= paddle.x + paddle.w) {
          ball.y = paddle.y - ball.r;
          const hit = (ball.x - (paddle.x + paddle.w / 2)) / (paddle.w / 2);
          const clamped = Math.max(-1, Math.min(1, hit));
          const angle = clamped * 1.05; // max ~60deg
          const sp = Math.min(900, ball.speed * 1.012);
          ball.speed = sp;
          ball.vx = Math.sin(angle) * sp;
          ball.vy = -Math.cos(angle) * sp;
          shake = Math.max(shake, 3);
          for (let i = 0; i < 6; i++) {
            particles.push({
              x: ball.x, y: paddle.y,
              vx: (Math.random() - 0.5) * 260,
              vy: -Math.random() * 200,
              r: 1 + Math.random() * 2,
              life: 0.3 + Math.random() * 0.3, maxLife: 0.6,
              color: '#ffd7e8'
            });
          }
        }

        // brick collisions
        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) {
            // determine side
            const overlapL = (ball.x + ball.r) - b.x;
            const overlapR = (b.x + b.w) - (ball.x - ball.r);
            const overlapT = (ball.y + ball.r) - b.y;
            const overlapB = (b.y + b.h) - (ball.y - ball.r);
            const minH = Math.min(overlapL, overlapR);
            const minV = Math.min(overlapT, overlapB);
            if (minH < minV) {
              ball.vx = -ball.vx;
              ball.x += (overlapL < overlapR ? -overlapL : overlapR);
            } else {
              ball.vy = -ball.vy;
              ball.y += (overlapT < overlapB ? -overlapT : overlapB);
            }

            b.hp--;
            if (b.hp <= 0) {
              b.alive = false;
              // EXPLODE INTO TONS OF LITTLE HEARTS
              spawnHearts(b.x, b.y, b.w, b.h, 20);
              score += 100;
              shake = Math.max(shake, 6);
              floatingTexts.push({
                x: b.x + b.w / 2, y: b.y + b.h / 2,
                text: '+100', life: 0.85, max: 0.85, size: 15, color: '#ffd7e8'
              });
            } else {
              spawnHearts(b.x, b.y, b.w, b.h, 7);
              b.color = '#ffffff';
              setTimeout(() => {}, 0);
            }
            break;
          }
        }

        // bottom: ball lost
        if (ball.y - ball.r > playAreaBottom) {
          if (started) {
            lives--;
            if (lives <= 0) {
              state = STATE.DEAD;
              floatingTexts.push({ x: W / 2, y: H * 0.45, text: 'GAME OVER', life: 2.4, max: 2.4, size: 44, color: '#ff5f9e' });
              setTimeout(() => {
                if (started) {
                  newGame();
                  state = STATE.PLAY;
                  resetBall(true);
                  overlay.classList.remove('hidden');
                  overlay.querySelector('h1').innerHTML = '💔 Oops! 💔';
                  overlay.querySelector('p').textContent = 'You lost all your burgers. Click to play again!';
                  overlay.querySelector('.hint').textContent = 'Click or press any key to restart';
                  started = false;
                }
              }, 1600);
            } else {
              resetBall(true);
              shake = 10;
            }
          } else {
            // demo mode: reset the ball
            resetBall(false);
            ball.speed = Math.min(730, 430 + level * 45);
            const ang = -Math.PI / 2 + (Math.random() - 0.5) * 0.8;
            ball.vx = Math.cos(ang) * ball.speed;
            ball.vy = Math.sin(ang) * ball.speed;
          }
          break;
        }
      }
    }

    // level clear check
    if (started && state === STATE.PLAY && bricks.every(b => !b.alive)) {
      nextLevel();
    }
    if (!started && bricks.every(b => !b.alive)) {
      // demo mode: refresh the bricks
      nextLevel();
      level = 1;
    }

    // update hearts
    for (let i = hearts.length - 1; i >= 0; i--) {
      const p = hearts[i];
      p.vy += p.gravity * dt;
      p.vx *= Math.pow(p.drag, dt * 60);
      p.x += p.vx * dt;
      p.y += p.vy * dt;
      p.rot += p.vrot * dt;
      p.wobble += dt * 6;
      p.life -= dt;
      if (p.x - p.size < playLeft) { p.x = playLeft + p.size; p.vx = Math.abs(p.vx) * 0.6; }
      if (p.x + p.size > playAreaRight) { p.x = playAreaRight - p.size; p.vx = -Math.abs(p.vx) * 0.6; }
      if (p.y > playAreaBottom + 40 || p.life <= 0) hearts.splice(i, 1);
    }

    // update particles
    for (let i = particles.length - 1; i >= 0; i--) {
      const p = particles[i];
      p.x += p.vx * dt;
      p.y += p.vy * dt;
      p.vy += 300 * dt;
      p.life -= dt;
      if (p.life <= 0) particles.splice(i, 1);
    }

    // floating texts
    for (let i = floatingTexts.length - 1; i >= 0; i--) {
      const t = floatingTexts[i];
      t.life -= dt;
      t.y -= 34 * dt;
      if (t.life <= 0) floatingTexts.splice(i, 1);
    }
  }

  function sparkWall(x, y) {
    for (let i = 0; i < 3; i++) {
      particles.push({
        x, y,
        vx: (Math.random() - 0.5) * 180,
        vy: (Math.random() - 0.5) * 180,
        r: 1 + Math.random() * 2,
        life: 0.2 + Math.random() * 0.25, maxLife: 0.45,
        color: '#ffb3d1'
      });
    }
  }

  // ---------- Draw ----------
  function draw() {
    // background gradient
    const bg = ctx.createRadialGradient(W / 2, H * 0.35, 40, W / 2, H * 0.5, Math.max(W, H) * 0.8);
    bg.addColorStop(0, '#3b1450');
    bg.addColorStop(0.55, '#240b3a');
    bg.addColorStop(1, '#12041f');
    ctx.fillStyle = bg;
    ctx.fillRect(0, 0, W, H);

    // soft glowing orbs
    ctx.save();
    ctx.globalCompositeOperation = 'lighter';
    for (let i = 0; i < 5; i++) {
      const ox = W * (0.15 + 0.18 * i) + Math.sin(time * 0.4 + i) * 40;
      const oy = H * (0.2 + 0.15 * (i % 3)) + Math.cos(time * 0.3 + i * 1.3) * 30;
      const g = ctx.createRadialGradient(ox, oy, 0, ox, oy, 190);
      g.addColorStop(0, 'rgba(255,90,150,0.075)');
      g.addColorStop(1, 'rgba(255,90,150,0)');
      ctx.fillStyle = g;
      ctx.beginPath();
      ctx.arc(ox, oy, 190, 0, Math.PI * 2);
      ctx.fill();
    }
    ctx.restore();

    // floating faint hearts in background
    ctx.save();
    for (let i = 0; i < 14; i++) {
      const t = time * 0.22 + i * 0.91;
      const fx = ((i * 137.5 + Math.sin(t) * 24) % (W + 100)) - 50;
      const fy = H - ((t * 42 + i * 90) % (H + 160));
      const a = 0.05 + 0.045 * Math.sin(t * 2.3);
      drawHeart(fx, fy, 12 + (i % 4) * 7, Math.sin(t * 1.4) * 0.5, '#ff7eb3', Math.max(0.02, a));
    }
    ctx.restore();

    // play area border glow
    ctx.save();
    ctx.strokeStyle = 'rgba(255, 120, 170, 0.20)';
    ctx.lineWidth = 2;
    ctx.strokeRect(playLeft, playTop, playW, playH);
    ctx.restore();

    ctx.save();
    if (shake > 0.2) {
      ctx.translate((Math.random() - 0.5) * shake, (Math.random() - 0.5) * shake);
    }

    // bricks
    for (const b of bricks) {
      if (!b.alive) continue;
      const wob = Math.sin(time * 2.2 + b.wob) * 1.2;
      const bx = b.x, by = b.y + wob;
      // glow
      ctx.save();
      ctx.shadowColor = b.color;
      ctx.shadowBlur = 14;
      const grad = ctx.createLinearGradient(bx, by, bx, by + b.h);
      grad.addColorStop(0, b.color);
      grad.addColorStop(1, b.dark);
      ctx.fillStyle = grad;
      roundRectPath(bx, by, b.w, b.h, Math.min(7, b.h * 0.3));
      ctx.fill();
      ctx.restore();

      // glossy highlight
      ctx.save();
      const hl = ctx.createLinearGradient(bx, by, bx, by + b.h * 0.55);
      hl.addColorStop(0, 'rgba(255,255,255,0.42)');
      hl.addColorStop(1, 'rgba(255,255,255,0.02)');
      ctx.fillStyle = hl;
      roundRectPath(bx + 2, by + 2, b.w - 4, b.h * 0.5, Math.min(5, b.h * 0.22));
      ctx.fill();
      ctx.restore();

      // tiny heart emblem on the brick
      drawHeart(bx + b.w / 2, by + b.h / 2 + 1, Math.min(b.h * 0.52, b.w * 0.22), 0, 'rgba(255,255,255,0.55)', 0.85);

      if (b.hp > 1) {
        ctx.save();
        ctx.strokeStyle = 'rgba(255,255,255,0.9)';
        ctx.lineWidth = 2;
        roundRectPath(bx, by, b.w, b.h, Math.min(7, b.h * 0.3));
        ctx.stroke();
        ctx.restore();
      }
    }

    // hearts particles (behind paddle for depth? no, in front looks better)
    for (const p of hearts) {
      const a = Math.min(1, p.life / (p.maxLife * 0.5));
      const sc = p.size * (0.6 + 0.4 * Math.min(1, p.life));
      drawHeart(p.x, p.y, sc, p.rot + Math.sin(p.wobble) * 0.2, p.color, a);
    }

    // sparkle particles
    for (const p of particles) {
      const a = Math.max(0, p.life / p.maxLife);
      ctx.globalAlpha = a;
      ctx.fillStyle = p.color;
      ctx.beginPath();
      ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
      ctx.fill();
    }
    ctx.globalAlpha = 1;

    // paddle: the hamburger
    drawBurger(paddle.x + paddle.w / 2, paddle.y + paddle.h / 2 - 4, paddle.w * 1.07, paddle.h * 1.5);

    // ball: a little heart-shaped patty / glowing orb
    if (!ball.stuck || started) {
      ctx.save();
      ctx.shadowColor = '#fff0f6';
      ctx.shadowBlur = 22;
      const bg2 = ctx.createRadialGradient(ball.x - 2, ball.y - 3, 1, ball.x, ball.y, ball.r * 1.5);
      bg2.addColorStop(0, '#ffffff');
      bg2.addColorStop(0.5, '#ffd7e8');
      bg2.addColorStop(1, '#ff5f9e');
      ctx.fillStyle = bg2;
      ctx.beginPath();
      ctx.arc(ball.x, ball.y, ball.r, 0, Math.PI * 2);
      ctx.fill();
      ctx.restore();

      // trailing hearts
      if (Math.random() < 0.55) {
        hearts.push({
          x: ball.x + (Math.random() - 0.5) * 6,
          y: ball.y + (Math.random() - 0.5) * 6,
          vx: (Math.random() - 0.5) * 40,
          vy: 20 + Math.random() * 40,
          size: 2.5 + Math.random() * 4,
          rot: Math.random() * 2,
          vrot: (Math.random() - 0.5) * 4,
          life: 0.5 + Math.random() * 0.4,
          maxLife: 0.9,
          color: HEART_COLORS[(Math.random() * HEART_COLORS.length) | 0],
          gravity: 40,
          drag: 0.97,
          wobble: Math.random() * 6
        });
      }
    }

    // floating texts
    for (const t of floatingTexts) {
      const a = Math.max(0, Math.min(1, t.life / (t.max * 0.55)));
      ctx.save();
      ctx.globalAlpha = a;
      ctx.font = `800 ${t.size}px 'Segoe UI', system-ui, sans-serif`;
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.shadowColor = 'rgba(255,60,130,0.9)';
      ctx.shadowBlur = 18;
      ctx.fillStyle = t.color;
      ctx.fillText(t.text, t.x, t.y);
      ctx.restore();
    }

    ctx.restore();

    // HUD
    drawHUD();

    if (!started) {
      // small demo badge
      ctx.save();
      ctx.globalAlpha = 0.5 + 0.2 * Math.sin(time * 3);
      ctx.font = `600 ${Math.max(12, Math.min(16, W * 0.013))}px 'Segoe UI', system-ui, sans-serif`;
      ctx.textAlign = 'center';
      ctx.fillStyle = '#ffb3d1';
      ctx.fillText('DEMO MODE — the burger plays itself 🍔', W / 2, H - 14);
      ctx.restore();
    }
  }

  function roundRectPath(x, y, w, h, r) {
    const rr = Math.min(r, w / 2, h / 2);
    ctx.beginPath();
    ctx.moveTo(x + rr, y);
    ctx.lineTo(x + w - rr, y);
    ctx.quadraticCurveTo(x + w, y, x + w, y + rr);
    ctx.lineTo(x + w, y + h - rr);
    ctx.quadraticCurveTo(x + w, y + h, x + w - rr, y + h);
    ctx.lineTo(x + rr, y + h);
    ctx.quadraticCurveTo(x, y + h, x, y + h - rr);
    ctx.lineTo(x, y + rr);
    ctx.quadraticCurveTo(x, y, x + rr, y);
    ctx.closePath();
  }

  function drawHUD() {
    const fs = Math.max(13, Math.min(20, W * 0.016));
    ctx.save();
    ctx.font = `800 ${fs}px 'Segoe UI', system-ui, sans-serif`;
    ctx.textBaseline = 'middle';

    // score (left)
    ctx.textAlign = 'left';
    ctx.fillStyle = 'rgba(255,215,232,0.55)';
    ctx.fillText('SCORE', playLeft, playTop * 0.45);
    ctx.fillStyle = '#ffd7e8';
    ctx.shadowColor = 'rgba(255,90,150,0.8)';
    ctx.shadowBlur = 12;
    ctx.font = `900 ${fs * 1.55}px 'Segoe UI', system-ui, sans-serif`;
    ctx.fillText(String(score).padStart(4, '0'), playLeft, playTop * 0.72);
    ctx.shadowBlur = 0;

    // lives (right)
    ctx.textAlign = 'right';
    ctx.font = `800 ${fs}px 'Segoe UI', system-ui, sans-serif`;
    ctx.fillStyle = 'rgba(255,215,232,0.55)';
    ctx.fillText('BURGERS', playAreaRight, playTop * 0.45);
    for (let i = 0; i < lives; i++) {
      const lx = playAreaRight - 12 - i * (fs * 1.5);
      const ly = playTop * 0.72;
      drawMiniBurger(lx, ly, fs * 1.35);
    }

    // level (center)
    ctx.textAlign = 'center';
    ctx.font = `800 ${fs}px 'Segoe UI', system-ui, sans-serif`;
    ctx.fillStyle = 'rgba(255,215,232,0.55)';
    ctx.fillText('LEVEL ' + level, W / 2, playTop * 0.45);

    ctx.restore();
  }

  function drawMiniBurger(cx, cy, size) {
    const w = size, h = size * 0.78;
    ctx.save();
    ctx.translate(cx, cy);
    // bottom bun
    roundRect(-w * 0.5, h * 0.1, w, h * 0.42, h * 0.2, '#e8a55c');
    // lettuce
    ctx.fillStyle = '#5fbf4b';
    ctx.beginPath();
    ctx.moveTo(-w * 0.52, -h * 0.02);
    for (let i = 0; i <= 5; i++) {
      ctx.lineTo(-w * 0.52 + w * 1.04 * (i / 5), -h * 0.02 + (i % 2 === 0 ? -h * 0.11 : h * 0.11));
    }
    ctx.lineTo(w * 0.52, h * 0.16);
    ctx.lineTo(-w * 0.52, h * 0.16);
    ctx.closePath();
    ctx.fill();
    // patty
    roundRect(-w * 0.48, -h * 0.2, w * 0.96, h * 0.2, h * 0.1, '#6b3a1e');
    // top bun
    const g = ctx.createLinearGradient(0, -h * 0.6, 0, -h * 0.1);
    g.addColorStop(0, '#ffcf7a');
    g.addColorStop(1, '#e59a4a');
    ctx.fillStyle = g;
    ctx.beginPath();
    ctx.moveTo(-w * 0.5, -h * 0.14);
    ctx.quadraticCurveTo(0, -h * 0.95, w * 0.5, -h * 0.14);
    ctx.closePath();
    ctx.fill();
    // seeds
    ctx.fillStyle = 'rgba(255,248,226,0.9)';
    ctx.beginPath();
    ctx.ellipse(-w * 0.18, -h * 0.38, w * 0.05, h * 0.03, 0.4, 0, Math.PI * 2);
    ctx.ellipse(w * 0.14, -h * 0.42, w * 0.05, h * 0.03, -0.4, 0, Math.PI * 2);
    ctx.ellipse(0, -h * 0.24, w * 0.05, h * 0.03, 0, 0, Math.PI * 2);
    ctx.fill();
    ctx.restore();
  }

  // ---------- Main loop ----------
  let last = performance.now();
  function frame(now) {
    let dt = (now - last) / 1000;
    last = now;
    if (dt > 0.05) dt = 0.05;
    if (dt < 0) dt = 0;
    update(dt);
    draw();
    requestAnimationFrame(frame);
  }

  window.addEventListener('resize', resize);

  // init
  resize();
  newGame();
  state = STATE.PLAY;
  resetBall(false);
  ball.speed = 500;
  ball.vx = 240; ball.vy = -370;
  requestAnimationFrame((t) => { last = t; frame(t); });

  // Start demo immediately with the ball in flight
  setTimeout(() => {
    if (!started) {
      resetBall(false);
      const ang = -Math.PI / 2 + (Math.random() - 0.5) * 0.6;
      ball.vx = Math.cos(ang) * ball.speed;
      ball.vy = Math.sin(ang) * ball.speed;
    }
  }, 60);
})();
</script>
</body>
</html>