hypeorcode
Network offOpen alone (new tab)

Run details

Model
meta/muse-spark-open
Route
OpenRouter API, one user message
Reasoning
provider default, 3,378 reasoning tokens
Limits
60 min · 141,176 output tokens
Date
21 Oct 2026
Time
2 min 40 s
Size
39 kB
Tokens
221 in, 16,089 out
List price
$0.069 ($1.25 in / $4.25 out per M)
Paid
$0.069

Raw files: output · meta.json

Exact prompt

Make a Breakout game I can play right now with the keyboard or the mouse. Give it several levels, a score and some juice.

It must be one complete, self-contained HTML file: inline all CSS and JavaScript, and make no network requests of any kind (no CDNs, web fonts or remote images). Reply with the file in a single ```html code block.

Source

39 kB 1,061 lines Raw

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>BREAKOUT — Neon Shatter</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
:root{
  --bg:#070a14;
  --panel:#12182b;
  --line:rgba(255,255,255,.08);
  --accent:#7a5cff;
  --accent2:#00eaff;
  --text:#eef2ff;
  --muted:#9aa3c0;
}
html,body{height:100%}
body{
  background:
    radial-gradient(900px 600px at 15% -10%, #1a2b6b 0%, transparent 60%),
    radial-gradient(1000px 700px at 90% 0%, #4a1b6b 0%, transparent 60%),
    radial-gradient(800px 600px at 50% 120%, #0e3d5a 0%, transparent 70%),
    var(--bg);
  color:var(--text);
  font-family: ui-sans-system, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
  display:flex;align-items:center;justify-content:center;
  padding:18px;
  overflow-x:hidden;
}
.app{
  width:min(980px, 100%);
  display:flex;flex-direction:column;gap:14px;
}
header{
  display:flex;align-items:center;justify-content:space-between;gap:12px;
  background:linear-gradient(180deg, rgba(255,255,255,.07), rgba(255,255,255,.02));
  border:1px solid var(--line);
  border-radius:16px;
  padding:12px 14px;
  backdrop-filter: blur(8px);
  box-shadow: 0 10px 30px rgba(0,0,0,.35), inset 0 1px 0 rgba(255,255,255,.06);
}
.brand{
  display:flex;align-items:center;gap:12px;
}
.logo{
  width:40px;height:40px;border-radius:10px;
  background:
    linear-gradient(135deg, #7a5cff, #00eaff);
  display:grid;place-items:center;
  box-shadow:0 6px 18px rgba(122,92,255,.45);
  font-weight:900;letter-spacing:.04em;
}
.logo span{filter:drop-shadow(0 1px 4px rgba(0,0,0,.5));font-size:13px}
.brand h1{font-size:18px;line-height:1;letter-spacing:.14em;font-weight:900}
.brand p{font-size:12px;color:var(--muted);letter-spacing:.08em;margin-top:2px}
.stats{
  display:flex;align-items:center;gap:10px;flex-wrap:wrap;justify-content:flex-end;
}
.stat{
  background:rgba(0,0,0,.28);
  border:1px solid var(--line);
  border-radius:999px;
  padding:8px 12px;
  display:flex;align-items:center;gap:8px;
  font-size:12px;letter-spacing:.08em;font-weight:700;
  white-space:nowrap;
}
.stat b{font-size:14px;color:#fff}
.stat .dot{width:8px;height:8px;border-radius:50%}
.btn{
  appearance:none;border:1px solid rgba(255,255,255,.14);
  background:linear-gradient(180deg, rgba(255,255,255,.12), rgba(255,255,255,.04));
  color:var(--text);font-weight:800;letter-spacing:.08em;
  padding:10px 14px;border-radius:999px;cursor:pointer;
  transition:transform .08s ease, filter .15s, border-color .15s;
  font-size:12px;
}
.btn:hover{filter:brightness(1.15);border-color:rgba(255,255,255,.22)}
.btn:active{transform:scale(.97)}
.btn.primary{
  background:linear-gradient(180deg, #7a5cff, #5a36ff);
  border-color:rgba(122,92,255,.5);
  box-shadow:0 8px 20px rgba(122,92,255,.4);
}
.btn.ghost{background:transparent}
.stage{
  position:relative;
  border-radius:18px;
  overflow:hidden;
  border:1px solid rgba(255,255,255,.10);
  background:
    linear-gradient(180deg, rgba(255,255,255,.04), transparent 30%),
    linear-gradient(0deg, rgba(0,0,0,.45), transparent),
    #0c1120;
  box-shadow: 0 20px 60px rgba(0,0,0,.55), 0 0 0 1px rgba(255,255,255,.03) inset;
}
canvas{display:block;width:100%;height:auto;aspect-ratio: 960/540;background:transparent}
.overlay{
  position:absolute;inset:0;
  display:flex;align-items:center;justify-content:center;
  padding:22px;
  background: radial-gradient(600px 400px at 50% 20%, rgba(122,92,255,.18), transparent 60%),
              linear-gradient(180deg, rgba(7,10,20,.55), rgba(7,10,20,.78));
  backdrop-filter: blur(6px);
}
.hidden{display:none !important}
.panel{
  width:min(520px, 100%);
  background: linear-gradient(180deg, rgba(255,255,255,.10), rgba(255,255,255,.04));
  border:1px solid rgba(255,255,255,.12);
  border-radius:18px;
  padding:18px;
  box-shadow: 0 20px 50px rgba(0,0,0,.5), inset 0 1px 0 rgba(255,255,255,.08);
  text-align:center;
}
.kicker{font-size:11px;letter-spacing:.18em;color:#aab3d1;font-weight:800}
.panel h2{
  font-size:32px;letter-spacing:.06em;line-height:1;margin:8px 0 6px;
  text-shadow:0 6px 30px rgba(122,92,255,.55);
}
.panel p{color:#cbd2ea;font-size:13px;line-height:1.5}
.row{display:flex;gap:10px;justify-content:center;flex-wrap:wrap;margin-top:14px}
.badge{
  display:inline-flex;align-items:center;gap:8px;
  font-size:11px;letter-spacing:.08em;font-weight:800;
  padding:8px 10px;border-radius:999px;
  background:rgba(0,0,0,.35);border:1px solid rgba(255,255,255,.08);color:#d6dcf3
}
.gridKeys{
  display:grid;grid-template-columns:1fr 1fr;gap:8px;margin:14px 0;text-align:left;
}
.gridKeys div{
  background:rgba(0,0,0,.3);border:1px solid rgba(255,255,255,.06);
  border-radius:12px;padding:10px 11px;display:flex;align-items:center;gap:10px;
}
.gridKeys i{
  width:30px;height:30px;border-radius:8px;display:grid;place-items:center;
  background:linear-gradient(180deg, rgba(255,255,255,.14), rgba(255,255,255,.03));
  border:1px solid rgba(255,255,255,.12);font-style:normal;font-weight:900;font-size:12px
}
.gridKeys span{font-size:12px;line-height:1.2;color:#dfe6ff}
.gridKeys small{color:var(--muted);font-size:11px}
.levelDots{display:flex;gap:6px;justify-content:center;margin-top:12px}
.levelDots s{
  width:22px;height:6px;border-radius:999px;background:rgba(255,255,255,.15);display:block;
  border:1px solid rgba(255,255,255,.06)
}
.levelDots s.on{background:linear-gradient(90deg,#7a5cff,#00eaff);box-shadow:0 4px 12px rgba(122,92,255,.45)}
.hudbar{
  position:absolute;left:0;right:0;top:0;
  display:flex;justify-content:space-between;align-items:center;
  padding:10px 12px;pointer-events:none;
}
.hudchip{
  background:rgba(0,0,0,.45);border:1px solid rgba(255,255,255,.08);
  border-radius:999px;padding:6px 10px;font-size:11px;letter-spacing:.08em;font-weight:800;color:#dbe2ff;
  backdrop-filter: blur(6px);
}
.progress{
  position:absolute;left:12px;right:12px;bottom:10px;height:6px;
  background:rgba(255,255,255,.08);border-radius:999px;overflow:hidden;
  border:1px solid rgba(255,255,255,.06)
}
.progress i{display:block;height:100%;width:0%;background:linear-gradient(90deg,#7a5cff,#00eaff);box-shadow:0 0 12px rgba(0,234,255,.6)}
.controls{
  display:flex;justify-content:space-between;gap:10px;flex-wrap:wrap;
  color:var(--muted);font-size:11px;letter-spacing:.06em;
  padding:0 4px;
}
.controls b{color:#dde4ff}
.toast{
  position:absolute;left:50%;top:46%;transform:translate(-50%,-50%);
  font-weight:1000;letter-spacing:.08em;text-align:center;
  font-size:44px;line-height:1;
  text-shadow: 0 10px 30px rgba(0,0,0,.6), 0 0 20px rgba(122,92,255,.6);
  pointer-events:none;opacity:0;
}
.pulse{animation:pulse .6s ease}
@keyframes pulse{0%{transform:translate(-50%,-50%) scale(.9);opacity:0} 20%{opacity:1} 100%{transform:translate(-50%,-50%) scale(1.05);opacity:0}}
.shake{animation:shake .35s ease}
@keyframes shake{0%,100%{transform:translate(0,0)}20%{transform:translate(-6px,2px)}40%{transform:translate(5px,-2px)}60%{transform:translate(-3px,1px)}80%{transform:translate(4px,-1px)}}
.scorePop{position:absolute;pointer-events:none;font-weight:1000;font-size:14px;letter-spacing:.04em;text-shadow:0 4px 12px rgba(0,0,0,.6)}
@media (max-width:640px){
  header{flex-direction:column;align-items:stretch}
  .stats{justify-content:space-between}
  .panel h2{font-size:26px}
  .toast{font-size:32px}
}
</style>
</head>
<body>
<div class="app" id="app">
  <header>
    <div class="brand">
      <div class="logo"><span>◈</span></div>
      <div>
        <h1>NEON SHATTER</h1>
        <p>BREAKOUT • MOVE WITH MOUSE OR KEYS</p>
      </div>
    </div>
    <div class="stats">
      <div class="stat"><span class="dot" style="background:#42ff88;box-shadow:0 0 10px #42ff88"></span> SCORE <b id="score">0</b></div>
      <div class="stat">LIVES <b id="lives">● ● ●</b></div>
      <div class="stat">LEVEL <b id="level">1/6</b> <span id="mult" style="margin-left:6px;background:linear-gradient(90deg,#ffd23f,#ff8447);color:#1a1200;padding:3px 7px;border-radius:999px;font-size:10px;display:none">x1</span></div>
      <button class="btn ghost" id="muteBtn" title="Mute">🔊</button>
      <button class="btn" id="pauseBtn">⏸ PAUSE</button>
    </div>
  </header>

  <div class="stage" id="stage">
    <canvas id="c" width="960" height="540"></canvas>

    <div class="hudbar">
      <div class="hudchip" id="hudLeft">AIM • BREAK • SURVIVE</div>
      <div class="hudchip" id="hudRight">SPACE TO LAUNCH • P TO PAUSE</div>
    </div>
    <div class="progress"><i id="prog"></i></div>
    <div class="toast" id="toast"></div>

    <!-- MENU -->
    <div class="overlay" id="ovMenu">
      <div class="panel">
        <div class="kicker">ARCADE EDITION • 6 HANDCRAFTED LEVELS</div>
        <h2 style="background:linear-gradient(90deg,#fff 20%, #a99cff 45%, #5af3ff 80%);-webkit-background-clip:text;background-clip:text;color:transparent;">BREAK THE NEON</h2>
        <p>Move the paddle with <b>mouse</b> or <b>← → / A D</b>. Launch with <b>Space / Click / ↑</b>. Build combo streaks, catch power-ups, and shatter every brick. Don’t let the ball fall!</p>
        <div class="gridKeys">
          <div><i>🖱️</i><span>Move Mouse<br><small>Precision aiming</small></span></div>
          <div><i>⌨️</i><span>← → or A D<br><small>Keyboard control</small></span></div>
          <div><i>⎵</i><span>Space / Click / ↑<br><small>Launch ball</small></span></div>
          <div><i>P</i><span>P to Pause<br><small>M to Mute</small></span></div>
        </div>
        <div class="row">
          <button class="btn primary" id="playBtn">▶ PLAY NOW</button>
          <button class="btn" id="howBtn">HOW TO JUICE?</button>
        </div>
        <div class="levelDots" id="dotsMenu"></div>
        <p style="margin-top:10px;font-size:11px;color:#98a0bf;letter-spacing:.05em">Tip: Hit the edge of the paddle for sharp angles • Keep combos for 2× score</p>
      </div>
    </div>

    <!-- PAUSED -->
    <div class="overlay hidden" id="ovPause">
      <div class="panel">
        <div class="kicker">TAKE A BREATH</div>
        <h2>PAUSED</h2>
        <p>Game is frozen. Resume when ready.</p>
        <div class="row">
          <button class="btn primary" id="resumeBtn">▶ RESUME</button>
          <button class="btn" id="restartBtn2">↻ RESTART</button>
        </div>
      </div>
    </div>

    <!-- LEVEL CLEAR -->
    <div class="overlay hidden" id="ovClear">
      <div class="panel">
        <div class="kicker">LEVEL CLEARED</div>
        <h2 id="clearTitle">NICE SHOT!</h2>
        <p id="clearDesc">Preparing next wave…</p>
        <div class="row">
          <button class="btn primary" id="nextBtn">NEXT LEVEL →</button>
        </div>
      </div>
    </div>

    <!-- GAME OVER -->
    <div class="overlay hidden" id="ovOver">
      <div class="panel">
        <div class="kicker" id="overKicker">GAME OVER</div>
        <h2 id="overTitle">SHATTERED</h2>
        <p id="overDesc">You fought well. Try again?</p>
        <div class="row">
          <button class="btn primary" id="againBtn">↻ PLAY AGAIN</button>
          <button class="btn" id="menuBtn">MENU</button>
        </div>
        <div class="levelDots" id="dotsOver"></div>
      </div>
    </div>

  </div>

  <div class="controls">
    <div><b>Mouse:</b> move to steer • <b>Click:</b> launch &nbsp; | &nbsp; <b>Keys:</b> ← → / A D to move • <b>Space/↑</b> launch • <b>P</b> pause</div>
    <div>No installs • 60fps • Made with built-in sound</div>
  </div>
</div>

<script>
(() => {
  const canvas = document.getElementById('c');
  const ctx = canvas.getContext('2d');
  const W = 960, H = 540;
  const scoreEl = document.getElementById('score');
  const livesEl = document.getElementById('lives');
  const levelEl = document.getElementById('level');
  const prog = document.getElementById('prog');
  const multEl = document.getElementById('mult');
  const toast = document.getElementById('toast');
  const stage = document.getElementById('stage');

  const ovMenu = document.getElementById('ovMenu');
  const ovPause = document.getElementById('ovPause');
  const ovClear = document.getElementById('ovClear');
  const ovOver = document.getElementById('ovOver');

  let muted = false;
  let audioCtx = null;
  function ensureAudio(){
    if(muted) return;
    if(!audioCtx) audioCtx = new (window.AudioContext||window.webkitAudioContext)();
    if(audioCtx.state==='suspended') audioCtx.resume();
  }
  function beep(f1,f2,dur,type='sine',vol=0.18){
    if(muted) return;
    try{
      ensureAudio();
      const t0 = audioCtx.currentTime;
      const o = audioCtx.createOscillator();
      const g = audioCtx.createGain();
      const f = audioCtx.createBiquadFilter();
      o.type = type;
      o.frequency.setValueAtTime(f1, t0);
      if(f2) o.frequency.exponentialRampToValueAtTime(Math.max(30,f2), t0+dur*0.9);
      g.gain.setValueAtTime(vol, t0);
      g.gain.exponentialRampToValueAtTime(0.001, t0+dur);
      f.type='lowpass'; f.frequency.value=6000;
      o.connect(f).connect(g).connect(audioCtx.destination);
      o.start(t0); o.stop(t0+dur+0.02);
    }catch(e){}
  }
  const SFX = {
    paddle: ()=> beep(320,520,0.12,'square',0.16),
    wall: ()=> beep(220,260,0.09,'square',0.12),
    brick: (hi)=> {
      const base = 420 + Math.random()*260;
      beep(base, base+180, 0.12, 'square', 0.14);
      if(hi) setTimeout(()=>beep(base*1.5, base*2, 0.1,'sine',0.08), 40);
    },
    brickHard: ()=> beep(180,90,0.18,'triangle',0.22),
    launch: ()=> {beep(400,800,0.18,'sine',0.18); setTimeout(()=>beep(800,1200,0.12,'sine',0.12),90)},
    lose: ()=> {beep(300,110,0.5,'sawtooth',0.15); setTimeout(()=>beep(180,60,0.5,'sine',0.12),160)},
    power: ()=> {beep(600,900,0.2,'sine',0.16); setTimeout(()=>beep(900,1200,0.25,'sine',0.14),120)},
    clear: ()=> { [0,2,4,7].forEach((n,i)=> setTimeout(()=>beep(300+n*80, 500+n*80,0.3,'sine',0.16), i*110))},
    win: ()=> { [0,4,7,12,16].forEach((n,i)=> setTimeout(()=>beep(250+n*50, 400+n*50,0.45,'sine',0.18), i*130))},
  };

  // Levels as strings: ' '=empty, '1'=1hp, '2'=2hp, '3'=3hp, 'X'=unbreakable (metal)
  const LEVELS = [
    { name:"WARM-UP GRID", data:[
      "111111111111",
      "111111111111",
      "222211112222",
      "111111111111",
      "111111111111",
    ]},
    { name:"THE PYRAMID", data:[
      "-----11-----",
      "----1111----",
      "---122221---",
      "--11111111--",
      "-1122222211-",
      "111111111111",
    ]},
    { name:"NEON FORTRESS", data:[
      "12X111111X21",
      "111X1111X111",
      "111111111111",
      "11X111111X11",
      "122111122211",
      "11X 11  X11 ",
    ]},
    { name:"CHECKER VOID", data:[
      "121212121212",
      "212121212121",
      "1-1-1--1-1-1",
      "212121212121",
      "1212XXXX1212",
      "1-1-13311-1-1",
    ]},
    { name:"DIAMOND CORE", data:[
      "-----XX-----",
      "----1221----",
      "---1XXXX1---",
      "--12XXXX21--",
      "-1XX1221XX1-",
      "1111XXXX1111",
      "-1XX1221XX1-",
    ]},
    { name:"FINAL HEX", data:[
      "1X1122332211X1",
      "X111222222111X",
      "111XX111XX111",
      "12211XXXX11221",
      "X111XX11XX111X",
      "111122221111",
    ]},
  ];
  const PALETTE = ['#ff3b82','#ff7a3b','#ffd23f','#3be8ff','#7a5cff','#42ffa2','#ff9cf0'];
  const PALETTE2 = ['#ff4d6d','#ff8c42','#ffe066','#5ef0ff','#8a6cff','#5cff9a'];

  // Game state
  let state = 'menu'; // menu, playing, paused, clear, over, win
  let levelIndex = 0;
  let score = 0;
  let lives = 3;
  let combo = 0;
  let comboTimer = 0;
  let mult = 1;

  const paddle = { x: W/2 - 70, y: H - 38, w: 140, h: 16, tx: W/2, speed: 0, expandT:0 };
  let balls = [];
  let bricks = [];
  let particles = [];
  let texts = [];
  let powerups = [];
  let shake = 0;
  let mouseActive = false;
  let lastMouseMove = 0;

  const keys = {};
  const BRICK_W = 62, BRICK_H = 22, BRICK_GAP = 7;

  function hideAll(){ ovMenu.classList.add('hidden'); ovPause.classList.add('hidden'); ovClear.classList.add('hidden'); ovOver.classList.add('hidden'); }
  function showMenu(){
    state='menu'; hideAll(); ovMenu.classList.remove('hidden');
    buildDots();
  }
  function showPause(){ if(state==='playing'){ state='paused'; ovPause.classList.remove('hidden'); }}
  function resume(){ if(state==='paused'){ state='playing'; ovPause.classList.add('hidden'); }}
  function levelDotsHTML(idx){
    let h='';
    for(let i=0;i<LEVELS.length;i++) h+= `<s class="${i<=idx?'on':''}"></s>`;
    return h;
  }
  function buildDots(){
    document.getElementById('dotsMenu').innerHTML = levelDotsHTML(levelIndex-0.2);
    document.getElementById('dotsOver').innerHTML = levelDotsHTML(levelIndex);
  }

  function resetGame(fullReset=true){
    if(fullReset){ levelIndex=0; score=0; lives=3; combo=0; mult=1; }
    paddle.w = 140; paddle.expandT=0;
    balls=[]; particles=[]; texts=[]; powerups=[];
    loadLevel(levelIndex);
    spawnBall(true);
    updateHUD();
  }

  function loadLevel(idx){
    bricks=[];
    const lvl = LEVELS[idx];
    const rows = lvl.data.length;
    let maxCols = 0; lvl.data.forEach(r=> maxCols = Math.max(maxCols, r.length));
    const useW = BRICK_W, useH = BRICK_H, gap = BRICK_GAP;
    const totalW = maxCols*useW + (maxCols-1)*gap;
    const ox = (W - totalW)/2;
    const oy = 74;
    for(let r=0;r<rows;r++){
      const rowStr = lvl.data[r];
      for(let c=0;c<rowStr.length;c++){
        const ch=rowStr[c];
        if(ch===' '||ch==='-'||ch==='.' ) continue;
        let hp=1, unbreak=false;
        if(ch==='1') hp=1;
        else if(ch==='2') hp=2;
        else if(ch==='3') hp=3;
        else if(ch==='X'||ch==='x'){ hp=999; unbreak=true; }
        else continue;
        const col = unbreak ? '#6e768e' : PALETTE[r % PALETTE.length];
        const col2 = unbreak ? '#aab3cc' : PALETTE2[r % PALETTE2.length];
        bricks.push({
          x: ox + c*(useW+gap) + (maxCols - rowStr.length)*(useW+gap)/2,
          y: oy + r*(useH+gap),
          w: useW, h: useH,
          hp, maxHp: hp, unbreak,
          color: col, color2: col2,
          alive:true, hitAnim:0
        });
      }
    }
  }

  function spawnBall(stuck=false){
    const b = {
      x: paddle.x + paddle.w/2,
      y: paddle.y - 12,
      r: 8,
      vx: (Math.random()<0.5?-1:1) * (260 + Math.random()*60),
      vy: -360,
      stuck: stuck,
      trail: []
    };
    if(stuck && balls.length===0) balls=[b]; else if(!stuck) balls.push(b);
    else if(stuck) balls[0]=b;
    else balls.push(b);
  }

  function updateHUD(){
    scoreEl.textContent = score.toLocaleString();
    livesEl.innerHTML = Array.from({length:3},(_,i)=> i<lives? '●':'○').join(' ');
    livesEl.style.color = lives===1 ? '#ff5a6a' : lives===2 ? '#ffcc66' : '#a6ffb0';
    levelEl.textContent = `${levelIndex+1}/${LEVELS.length}`;
    document.getElementById('hudLeft').textContent = LEVELS[levelIndex].name;
    const total = bricks.filter(b=>!b.unbreak).length;
    const alive = bricks.filter(b=>!b.unbreak && b.alive).length;
    prog.style.width = (total? ((total-alive)/total*100):0) + '%';
    if(combo>=2){ multEl.style.display='inline-block'; multEl.textContent='x'+mult+' COMBO'; multEl.style.transform=`scale(${1+Math.min(0.18, combo*0.02)})`; }
    else multEl.style.display='none';
  }

  function triggerToast(txt, sub){
    toast.innerHTML = txt + (sub? `<div style="font-size:13px;letter-spacing:.16em;margin-top:8px;color:#ffe9a6">${sub}</div>`:'');
    toast.classList.remove('pulse'); void toast.offsetWidth; toast.classList.add('pulse');
    toast.style.opacity=1;
    setTimeout(()=> toast.style.opacity=0, 900);
    stage.classList.remove('shake'); void stage.offsetWidth; stage.classList.add('shake');
    shake = Math.min(10, shake+6);
  }

  function breakBrick(br){
    br.hitAnim = 1;
    if(br.unbreak){
      SFX.brickHard(); shake=4; br.hitAnim=1;
      spawnParticles(br.x+br.w/2, br.y+br.h/2, br.color2, 6);
      spawnText(br.x+br.w/2, br.y, 'CLANK', '#aab3cc');
      return; // not destroyed
    }
    br.hp--;
    if(br.hp<=0){
      br.alive=false;
      const pts = 100 * (br.maxHp) * mult;
      score += pts;
      combo++; comboTimer=1.2; mult = combo>=8?3 : combo>=4?2 : 1;
      SFX.brick(true); shake = 5 + mult*1.5;
      spawnParticles(br.x+br.w/2, br.y+br.h/2, br.color, 14);
      spawnText(br.x+br.w/2, br.y, '+'+pts, '#fff');
      // power-up chance
      if(Math.random()<0.12){
        const type = Math.random()<0.6 ? 'expand' : 'multi';
        powerups.push({x:br.x+br.w/2, y:br.y+br.h/2, vy:90, type, w:26,h:14, rot:0});
      }
      updateHUD();
    } else {
      SFX.brickHard();
      spawnParticles(br.x+br.w/2, br.y+br.h/2, br.color2, 8);
      spawnText(br.x+br.w/2, br.y+br.h/2, '−', br.color2);
      // color fade to indicate damage
    }
  }

  function spawnParticles(x,y,color,count){
    for(let i=0;i<count;i++){
      const ang = Math.random()*Math.PI*2;
      const sp = 120 + Math.random()*260;
      particles.push({
        x,y, vx: Math.cos(ang)*sp, vy: Math.sin(ang)*sp - Math.random()*60,
        life: 0.45+Math.random()*0.35, t:0, size: 2+Math.random()*3.5, color
      });
    }
  }
  function spawnText(x,y,txt,color){
    texts.push({x,y, txt, color, life:0.7, t:0, vy:-40});
  }

  function collideBallBrick(ball, br){
    // AABB circle collision
    const closestX = Math.max(br.x, Math.min(ball.x, br.x+br.w));
    const closestY = Math.max(br.y, Math.min(ball.y, br.y+br.h));
    const dx = ball.x - closestX, dy = ball.y - closestY;
    const dist2 = dx*dx+dy*dy;
    if(dist2 <= ball.r*ball.r){
      const dist = Math.sqrt(dist2) || 0.001;
      // Determine normal
      let nx=0, ny=0;
      if(dist===0){ // inside
        // choose smallest penetration
        const overlapL = (ball.x - br.x);
        const overlapR = (br.x+br.w - ball.x);
        const overlapT = (ball.y - br.y);
        const overlapB = (br.y+br.h - ball.y);
        const min = Math.min(overlapL, overlapR, overlapT, overlapB);
        if(min===overlapL){nx=-1;ny=0}
        else if(min===overlapR){nx=1;ny=0}
        else if(min===overlapT){nx=0;ny=-1}
        else {nx=0;ny=1}
      } else { nx=dx/dist; ny=dy/dist; }
      // push out
      const overlap = ball.r - dist;
      ball.x += nx * (overlap+1);
      ball.y += ny * (overlap+1);
      // reflect
      const dot = ball.vx*nx + ball.vy*ny;
      ball.vx = ball.vx - 2*dot*nx;
      ball.vy = ball.vy - 2*dot*ny;
      // add a little speed per hit
      const sp = Math.hypot(ball.vx, ball.vy);
      const target = Math.min(620, sp*1.004);
      const fac = target/sp;
      ball.vx*=fac; ball.vy*=fac;
      return true;
    }
    return false;
  }

  // Input
  canvas.addEventListener('mousemove', e=>{
    const rect = canvas.getBoundingClientRect();
    const x = (e.clientX - rect.left)/rect.width * W;
    paddle.tx = x - paddle.w/2;
    mouseActive=true; lastMouseMove=performance.now();
  });
  canvas.addEventListener('touchmove', e=>{
    const t=e.touches[0]; if(!t) return;
    const rect=canvas.getBoundingClientRect();
    const x=(t.clientX-rect.left)/rect.width*W;
    paddle.tx = x - paddle.w/2;
    mouseActive=true; lastMouseMove=performance.now();
  }, {passive:true});
  canvas.addEventListener('click', ()=>{
    if(state==='menu'){ startGame(); return; }
    if(state==='playing') launchBalls();
    if(state==='clear') nextLevel();
    if(state==='over' || state==='win') resetAndMenu();
  });
  window.addEventListener('keydown', e=>{
    const k=e.key.toLowerCase();
    if(k===' ') e.preventDefault();
    keys[k]=true;
    if(k==='p'){ if(state==='playing') showPause(); else if(state==='paused') resume(); }
    if(k==='m'){ muted=!muted; updateMute(); ensureAudio(); }
    if(k===' ' || k==='arrowup' || k==='w'){
      if(state==='menu') startGame();
      else if(state==='playing') launchBalls();
      else if(state==='clear') nextLevel();
      else if(state==='over' || state==='win') resetAndMenu();
    }
    if(k==='r' && (state==='over'||state==='win')){ resetAndMenu(); startGame(); }
    if(k==='escape'){ if(state==='paused') resume(); else if(state==='playing') showPause(); }
  });
  window.addEventListener('keyup', e=>{ keys[e.key.toLowerCase()]=false; });

  document.getElementById('playBtn').onclick = startGame;
  document.getElementById('howBtn').onclick = ()=> triggerToast('JUICE!', 'Particles • Shake • Trails • Slow-Mo • Combos');
  document.getElementById('pauseBtn').onclick = ()=> (state==='playing'? showPause(): (state==='paused'?resume():null));
  document.getElementById('resumeBtn').onclick = resume;
  document.getElementById('restartBtn2').onclick = ()=>{ hideAll(); resetGame(true); state='playing'; };
  document.getElementById('nextBtn').onclick = nextLevel;
  document.getElementById('againBtn').onclick = ()=>{ hideAll(); resetGame(true); state='playing'; };
  document.getElementById('menuBtn').onclick = ()=>{ resetAndMenu(); };
  document.getElementById('muteBtn').onclick = ()=>{ muted=!muted; updateMute(); };
  function updateMute(){
    document.getElementById('muteBtn').textContent = muted? '🔇':'🔊';
    document.getElementById('muteBtn').style.opacity = muted? .6:1;
  }
  function resetAndMenu(){ hideAll(); showMenu(); resetGame(true); }
  function startGame(){
    ensureAudio();
    hideAll();
    if(state==='menu'){ resetGame(true); }
    else if(state==='paused'){ hideAll();}
    state='playing';
    updateHUD();
  }
  function launchBalls(){
    let any=false;
    balls.forEach(b=>{ if(b.stuck){ b.stuck=false; b.vx = (Math.random()-0.5)*120; b.vy=-380; any=true; }});
    if(any) SFX.launch();
  }
  function nextLevel(){
    levelIndex++;
    if(levelIndex >= LEVELS.length){
      state='win';
      ovOver.classList.remove('hidden');
      document.getElementById('overKicker').textContent='YOU DID IT';
      document.getElementById('overTitle').textContent='NEON GOD';
      document.getElementById('overDesc').textContent=`Final Score ${score.toLocaleString()} • All 6 levels shattered. Play again for perfection?`;
      SFX.win(); triggerToast('YOU WIN!', `Score ${score.toLocaleString()}`);
      buildDots();
      return;
    }
    hideAll(); resetGame(false);
    state='playing';
    SFX.clear();
    triggerToast(LEVELS[levelIndex].name, `Level ${levelIndex+1} • GO!`);
  }

  // Update Loop
  let last = performance.now();
  function frame(now){
    requestAnimationFrame(frame);
    const dt = Math.min(0.032, (now-last)/1000);
    last = now;
    update(dt);
    render(dt);
  }

  function update(dt){
    if(state==='playing'){
      // combo timer
      if(combo>0){
        comboTimer -= dt;
        if(comboTimer<=0){ combo=0; mult=1; updateHUD(); }
      }
      // paddle control blending: keyboard dominant if held, else mouse lerp
      const kbLeft = keys['arrowleft']||keys['a'];
      const kbRight = keys['arrowright']||keys['d'];
      const kbMoving = kbLeft||kbRight;
      const recentMouse = (performance.now() - lastMouseMove) < 1200;
      if(kbMoving){
        const dir = (kbRight?1:0) - (kbLeft?1:0);
        paddle.x += dir * 520 * dt;
      } else if(mouseActive && recentMouse){
        // smooth follow
        paddle.x += (paddle.tx - paddle.x) * Math.min(1, 14*dt);
      } else {
        // drift to center slowly if idle? keep position
      }
      // clamp
      paddle.x = Math.max(4, Math.min(W - paddle.w -4, paddle.x));
      // expand timer
      if(paddle.expandT>0){
        paddle.expandT -= dt;
        if(paddle.expandT<=0){ paddle.w = 140; }
      }
      // balls
      let aliveBricks = bricks.filter(b=>b.alive && !b.unbreak).length;
      for(let i=balls.length-1;i>=0;i--){
        const b=balls[i];
        b.trail.unshift({x:b.x, y:b.y}); if(b.trail.length>10) b.trail.pop();
        if(b.stuck){
          b.x = paddle.x + paddle.w/2;
          b.y = paddle.y - b.r -2;
          continue;
        }
        // move
        b.x += b.vx*dt;
        b.y += b.vy*dt;
        // walls
        if(b.x - b.r <= 0){ b.x= b.r; b.vx *= -1; SFX.wall(); shake=2; spawnParticles(b.x,b.y,'#8ab4ff',4)}
        if(b.x + b.r >= W){ b.x= W-b.r; b.vx*=-1; SFX.wall(); shake=2; spawnParticles(b.x,b.y,'#8ab4ff',4)}
        if(b.y - b.r <= 0){ b.y= b.r; b.vy*=-1; SFX.wall(); shake=2; }
        // paddle
        if(b.vy>0 && b.y + b.r >= paddle.y && b.y - b.r <= paddle.y + paddle.h && b.x >= paddle.x -4 && b.x <= paddle.x + paddle.w +4){
          // reposition to top
          b.y = paddle.y - b.r -1;
          const hitPos = (b.x - (paddle.x + paddle.w/2)) / (paddle.w/2); // -1..1
          const angle = hitPos * 0.85; // radians factor
          const speed = Math.hypot(b.vx,b.vy);
          const newSpeed = Math.min(640, speed*1.015 + 6);
          b.vx = Math.sin(angle*1.15) * newSpeed + paddle.speed*0.2;
          b.vy = -Math.cos(angle*0.95) * newSpeed;
          // add paddle velocity influence
          SFX.paddle(); shake=3; spawnParticles(b.x, paddle.y, '#7a5cff', 6);
          // stretch paddle visual? handled via anim
          paddle.speed = b.vx*0.1;
          comboTimer = Math.max(comboTimer, 0.6);
        }
        // bottom death
        if(b.y - b.r > H+20){
          balls.splice(i,1);
          if(balls.length===0){
            lives--; SFX.lose(); shake=9;
            triggerToast('BALL LOST', `Lives ${lives}`);
            combo=0; mult=1; updateHUD();
            if(lives<=0){
              state='over';
              ovOver.classList.remove('hidden');
              document.getElementById('overKicker').textContent='GAME OVER';
              document.getElementById('overTitle').textContent='SHATTERED';
              document.getElementById('overDesc').textContent=`Score ${score.toLocaleString()} • Reached Level ${levelIndex+1}. Try again?`;
              buildDots();
            } else {
              // respawn single ball stuck after short delay
              paddle.w = 140; // reset temporary?
              spawnBall(true);
            }
          }
          continue;
        }
        // bricks
        for(let br of bricks){
          if(!br.alive) continue;
          if(collideBallBrick(b, br)){
            breakBrick(br);
            // if combo big, add time dilation illusion via particles?
            break;
          }
        }
      }
      // check level cleared
      if(bricks.filter(b=>!b.unbreak && b.alive).length===0){
        score += 1000 * (levelIndex+1) + lives*250;
        updateHUD();
        state='clear';
        ovClear.classList.remove('hidden');
        document.getElementById('clearTitle').textContent = (levelIndex===LEVELS.length-1? 'FINAL CLEARED!' : 'CLEAN SWEEP!');
        document.getElementById('clearDesc').textContent = `Bonus awarded • Score ${score.toLocaleString()}`;
        SFX.clear();
        triggerToast('LEVEL CLEAR!', '+1000 Bonus');
      }
      // powerups
      for(let i=powerups.length-1;i>=0;i--){
        const pu=powerups[i];
        pu.y += pu.vy*dt;
        pu.rot += dt*3;
        if(pu.y + pu.h >= paddle.y && pu.y <= paddle.y+paddle.h && pu.x >= paddle.x && pu.x <= paddle.x+paddle.w){
          // collect
          if(pu.type==='expand'){
            paddle.w = Math.min(220, paddle.w+42); paddle.expandT=10;
            triggerToast('WIDE PADDLE!', '10s Expand');
          } else if(pu.type==='multi'){
            // spawn 2 more balls
            for(let k=0;k<2;k++){
              const nb = {x:paddle.x+paddle.w/2, y:paddle.y-14, r:8, vx:(Math.random()-0.5)*340, vy:-360-Math.random()*80, stuck:false, trail:[]};
              balls.push(nb);
            }
            triggerToast('MULTIBALL!', '2 extra balls');
          }
          SFX.power();
          spawnParticles(pu.x, pu.y, pu.type==='expand' ? '#ffd23f' : '#00eaff', 12);
          powerups.splice(i,1);
        } else if(pu.y > H+30) powerups.splice(i,1);
      }

      // particles
      for(let i=particles.length-1;i>=0;i--){
        const p=particles[i];
        p.t+=dt; p.x+=p.vx*dt; p.y+=p.vy*dt; p.vy+= 520*dt; p.vx*= (1 - 0.8*dt);
        if(p.t > p.life) particles.splice(i,1);
      }
      for(let i=texts.length-1;i>=0;i--){
        const t=texts[i];
        t.t+=dt; t.y+=t.vy*dt; t.vy += 60*dt;
        if(t.t>t.life) texts.splice(i,1);
      }
      if(shake>0) shake = Math.max(0, shake - 14*dt);

      updateHUD();
    } else if(state==='menu'){
      // idle animate bricks? subtle
    }
  }

  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 render(dt){
    // adapt dpr? canvas fixed.
    ctx.save();
    // shake transform
    if(shake>0){
      const sx = (Math.random()-0.5)*shake;
      const sy = (Math.random()-0.5)*shake;
      ctx.translate(sx,sy);
    }
    // clear
    ctx.clearRect(0,0,W,H);
    // background grid + vignette
    ctx.fillStyle='#0a0f1f';
    ctx.fillRect(0,0,W,H);
    // subtle grid
    ctx.strokeStyle='rgba(255,255,255,.035)';
    ctx.lineWidth=1;
    for(let x=0;x<W;x+=48){ ctx.beginPath(); ctx.moveTo(x,0); ctx.lineTo(x,H); ctx.stroke(); }
    for(let y=0;y<H;y+=48){ ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(W,y); ctx.stroke(); }
    // top glow
    const g = ctx.createRadialGradient(W/2, -80, 40, W/2, -80, 680);
    g.addColorStop(0,'rgba(122,92,255,.22)'); g.addColorStop(1,'transparent');
    ctx.fillStyle=g; ctx.fillRect(0,0,W,220);
    // bottom glow under paddle
    const g2 = ctx.createLinearGradient(0,H-120,0,H);
    g2.addColorStop(0,'transparent'); g2.addColorStop(1,'rgba(0,234,255,.12)');
    ctx.fillStyle=g2; ctx.fillRect(0,H-120,W,120);

    // bricks
    for(let br of bricks){
      if(!br.alive) continue;
      // hit animation scale
      let s = 1;
      if(br.hitAnim>0){ br.hitAnim -= dt*6; s = 1 + Math.sin(br.hitAnim*Math.PI)*0.12; if(br.hitAnim<=0) br.hitAnim=0; }
      ctx.save();
      ctx.translate(br.x+br.w/2, br.y+br.h/2);
      ctx.scale(s,s);
      const x=-br.w/2, y=-br.h/2;
      // shadow
      ctx.fillStyle='rgba(0,0,0,.35)';
      roundRect(x+2,y+3,br.w,br.h,6); ctx.fill();
      // body
      const grad = ctx.createLinearGradient(0,y,0,y+br.h);
      if(br.unbreak){
        grad.addColorStop(0,'#8e95ac'); grad.addColorStop(1,'#5a647c');
      } else {
        grad.addColorStop(0, br.color2); grad.addColorStop(1, br.color);
      }
      ctx.fillStyle=grad;
      roundRect(x,y,br.w,br.h,6); ctx.fill();
      // highlight
      ctx.fillStyle='rgba(255,255,255,.18)';
      roundRect(x,y,br.w,5,6); ctx.fill();
      // hp indicator crack or dots
      if(!br.unbreak && br.maxHp>1){
        ctx.fillStyle='rgba(0,0,0,.35)';
        for(let k=0;k<br.maxHp;k++){
          const cx = x+10+k*14;
          ctx.beginPath(); ctx.arc(cx+br.w/2-14, y+br.h/2, 3,0,Math.PI*2); ctx.fill();
        }
        // remaining hp glowing
        ctx.fillStyle='#fff';
        for(let k=0;k<br.hp;k++){
          const cx = x+10+k*14;
          ctx.beginPath(); ctx.arc(cx+br.w/2-14, y+br.h/2, 2.2,0,Math.PI*2); ctx.fill();
        }
      }
      if(br.unbreak){
        ctx.strokeStyle='rgba(255,255,255,.25)'; ctx.lineWidth=1.2;
        roundRect(x+.6,y+.6,br.w-1.2,br.h-1.2,6); ctx.stroke();
        // diagonal hatch
        ctx.strokeStyle='rgba(255,255,255,.18)'; ctx.lineWidth=1;
        ctx.beginPath();
        ctx.moveTo(x+6,y+br.h-6); ctx.lineTo(x+br.w-6,y+6); ctx.stroke();
        ctx.beginPath(); ctx.moveTo(x+10,y+br.h-6); ctx.lineTo(x+br.w-6,y+10); ctx.stroke();
      } else {
        // outer glow for strong bricks
        if(br.hp>1){
          ctx.shadowColor=br.color; ctx.shadowBlur=12; roundRect(x,y,br.w,br.h,6); ctx.strokeStyle=br.color; ctx.lineWidth=1.4; ctx.stroke(); ctx.shadowBlur=0;
        }
      }
      ctx.restore();
    }

    // powerups
    for(let pu of powerups){
      ctx.save();
      ctx.translate(pu.x, pu.y);
      ctx.rotate(pu.rot*0.2);
      const isExp = pu.type==='expand';
      ctx.fillStyle = isExp ? '#ffd23f' : '#00eaff';
      ctx.shadowColor = ctx.fillStyle; ctx.shadowBlur=10;
      roundRect(-13,-7,26,14,7); ctx.fill();
      ctx.shadowBlur=0;
      ctx.fillStyle='#0a0f1f'; ctx.font='700 10px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle';
      ctx.fillText(isExp? 'WIDE' : 'x3', 0,0);
      // bob outline
      ctx.strokeStyle='rgba(255,255,255,.9)'; ctx.lineWidth=1; roundRect(-13,-7,26,14,7); ctx.stroke();
      ctx.restore();
    }

    // paddle
    {
      const px=paddle.x, py=paddle.y, pw=paddle.w, ph=paddle.h;
      ctx.save();
      // shadow
      ctx.fillStyle='rgba(0,0,0,.45)';
      roundRect(px, py+4, pw, ph, 8); ctx.fill();
      // body gradient
      const pg = ctx.createLinearGradient(0,py,0,py+ph);
      pg.addColorStop(0,'#ffffff'); pg.addColorStop(0.5,'#dcd7ff'); pg.addColorStop(1,'#7a5cff');
      ctx.fillStyle=pg;
      roundRect(px,py,pw,ph,8); ctx.fill();
      // inner highlight
      ctx.fillStyle='rgba(255,255,255,.65)';
      roundRect(px+6,py+2,pw-12,5,5); ctx.fill();
      // side caps neon
      ctx.fillStyle='#00eaff'; ctx.shadowColor='#00eaff'; ctx.shadowBlur=14;
      ctx.fillRect(px-1, py+4, 2, ph-8);
      ctx.fillRect(px+pw-1, py+4, 2, ph-8);
      ctx.shadowBlur=0;
      // expand timer ring
      if(paddle.expandT>0){
        ctx.strokeStyle='rgba(255,210,63,.9)'; ctx.lineWidth=2;
        const pct = paddle.expandT/10;
        roundRect(px-3, py-3, pw+6, ph+6, 10); ctx.globalAlpha=0.5*pct; ctx.stroke(); ctx.globalAlpha=1;
      }
      ctx.restore();
    }

    // balls
    for(let b of balls){
      // trail
      for(let i=0;i<b.trail.length;i++){
        const t=b.trail[i];
        const a = 1 - i/b.trail.length;
        ctx.fillStyle=`rgba(120,200,255,${a*0.22})`;
        ctx.beginPath(); ctx.arc(t.x,t.y, b.r*(1 - i*0.05),0,Math.PI*2); ctx.fill();
      }
      // ball shadow
      ctx.fillStyle='rgba(0,0,0,.35)';
      ctx.beginPath(); ctx.arc(b.x+2,b.y+4,b.r,0,Math.PI*2); ctx.fill();
      // ball body
      const bg = ctx.createRadialGradient(b.x-3,b.y-3,1,b.x,b.y,b.r+4);
      bg.addColorStop(0,'#ffffff'); bg.addColorStop(0.35,'#d9f5ff'); bg.addColorStop(1,'#3aa0ff');
      ctx.fillStyle=bg;
      ctx.shadowColor='#00eaff'; ctx.shadowBlur=12;
      ctx.beginPath(); ctx.arc(b.x,b.y,b.r,0,Math.PI*2); ctx.fill();
      ctx.shadowBlur=0;
      // highlight dot
      ctx.fillStyle='rgba(255,255,255,.95)';
      ctx.beginPath(); ctx.arc(b.x-2.5,b.y-2.5,2,0,Math.PI*2); ctx.fill();
    }

    // particles
    for(let p of particles){
      const a = 1 - p.t/p.life;
      ctx.globalAlpha = a;
      ctx.fillStyle = p.color;
      ctx.shadowColor=p.color; ctx.shadowBlur=8;
      ctx.beginPath(); ctx.arc(p.x,p.y, p.size*a,0,Math.PI*2); ctx.fill();
      ctx.shadowBlur=0;
    }
    ctx.globalAlpha=1;

    // floating texts
    for(let t of texts){
      const a = 1 - t.t/t.life;
      ctx.globalAlpha = Math.max(0,a);
      ctx.fillStyle=t.color;
      ctx.font='900 14px system-ui';
      ctx.textAlign='center';
      ctx.fillText(t.txt, t.x, t.y);
      // glow
      ctx.shadowColor=t.color; ctx.shadowBlur=8; ctx.fillText(t.txt, t.x, t.y); ctx.shadowBlur=0;
    }
    ctx.globalAlpha=1;

    // vignette
    const vig = ctx.createRadialGradient(W/2,H/2, Math.min(W,H)*0.5, W/2,H/2, Math.min(W,H));
    vig.addColorStop(0,'transparent'); vig.addColorStop(1,'rgba(0,0,0,.45)');
    ctx.fillStyle=vig; ctx.fillRect(0,0,W,H);

    ctx.restore();

    // update lives textual after render? already done
  }

  // init
  buildDots();
  updateHUD();
  resetGame(true);
  showMenu();
  updateMute();

  requestAnimationFrame(frame);

  // auto resize observer to keep crisp? canvas already fixed logical, no need.

  // prevent context menu on long press
  canvas.addEventListener('contextmenu', e=>e.preventDefault());

})();
</script>
</body>
</html>

Compare with

Muse Spark Open: other attempts