• Updated 22 Oct, 10:10 UTC
  • Next run 23 Oct, 05:17 UTCNext 23 Oct 05:17
  • Last 7 days: 7 new models, 157 runs7 d: 7 models, 157 runs
Network offOpen alone (new tab)

Run details

Model
meta/muse-wonder
Route
OpenRouter API, one user message
Served by
Meta
Reasoning
provider default, 1,398 reasoning tokens
Limits
60 min · 30,000 output tokens ($0.60 at $20/M)
Date
22 Oct 2026
Time
74 s
Size
35 kB
Tokens
85 in, 13,161 out
List price
$0.26 ($4 in / $20 out per M)
Paid
$0.26

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

35 kB 864 lines Raw

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NEON BREAKOUT</title>
<style>
  * { box-sizing: border-box; margin: 0; padding: 0; }
  :root{
    --bg:#070812; --panel:#12142a; --cyan:#38f5ff; --pink:#ff4fd8;
    --yellow:#ffe75a; --text:#f4f6ff; --muted:#9aa3c7;
  }
  html,body{
    min-height:100%;
    background:
      radial-gradient(900px 500px at 20% -10%, rgba(83,48,255,.35), transparent 60%),
      radial-gradient(800px 500px at 90% 0%, rgba(255,55,180,.22), transparent 55%),
      radial-gradient(700px 700px at 50% 110%, rgba(0,220,255,.18), transparent 60%),
      var(--bg);
    color:var(--text);
    font-family: system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
    overflow-x:hidden;
  }
  body{ display:flex; align-items:center; justify-content:center; padding:18px 12px 24px; }
  .wrap{ width:min(940px,100%); }
  header{
    display:flex; align-items:flex-end; justify-content:space-between; gap:14px; flex-wrap:wrap;
    margin-bottom:12px;
  }
  h1{
    font-size:clamp(28px,5vw,50px); line-height:.95; letter-spacing:.06em; font-weight:900;
    background:linear-gradient(90deg,#fff,var(--cyan) 35%,var(--pink) 70%,var(--yellow));
    -webkit-background-clip:text; background-clip:text; color:transparent;
    text-shadow:0 0 28px rgba(56,245,255,.12);
  }
  .sub{ color:var(--muted); font-size:14px; margin-top:6px; }
  .buttons{ display:flex; gap:8px; flex-wrap:wrap; }
  button{
    appearance:none; border:1px solid rgba(255,255,255,.18); color:var(--text);
    background:linear-gradient(180deg,#23264a,#151733); padding:10px 14px; border-radius:999px;
    font-weight:800; letter-spacing:.04em; cursor:pointer; box-shadow:0 6px 18px rgba(0,0,0,.35);
  }
  button:hover{ filter:brightness(1.18); transform:translateY(-1px); }
  button:active{ transform:translateY(1px); }
  .primary{ background:linear-gradient(90deg,#00d6ff,#ff4fd8); border:none; color:white; }
  .hud{
    display:grid; grid-template-columns:repeat(5,1fr); gap:8px; margin-bottom:10px;
  }
  .card{
    background:linear-gradient(180deg,rgba(255,255,255,.08),rgba(255,255,255,.03));
    border:1px solid rgba(255,255,255,.12); border-radius:14px; padding:9px 12px;
    min-height:58px; box-shadow: inset 0 1px 0 rgba(255,255,255,.08);
  }
  .label{ font-size:11px; letter-spacing:.16em; color:var(--muted); font-weight:800; }
  .value{ font-size:clamp(19px,3vw,28px); font-weight:900; margin-top:2px; white-space:nowrap; }
  #lives{ color:#ff6b81; letter-spacing:.08em; }
  #powerStatus{ font-size:13px; color:var(--yellow); font-weight:800; white-space:normal; line-height:1.25; }
  .stage{
    position:relative; border-radius:20px; overflow:hidden;
    border:1px solid rgba(255,255,255,.16);
    background:#090a1c;
    box-shadow:0 24px 70px rgba(0,0,0,.55), 0 0 0 6px rgba(255,255,255,.03), 0 0 55px rgba(64,80,255,.18);
  }
  canvas{ display:block; width:100%; height:auto; aspect-ratio: 900 / 620; touch-action:none; cursor:none; }
  .overlay{
    position:absolute; inset:0; display:flex; align-items:center; justify-content:center; text-align:center;
    background:radial-gradient(circle at center, rgba(10,10,30,.28), rgba(4,4,14,.78));
    backdrop-filter: blur(2px); padding:24px; z-index:5;
  }
  .overlay.hidden{ display:none; }
  .panel{
    max-width:620px; background:rgba(14,16,38,.82); border:1px solid rgba(255,255,255,.16);
    border-radius:22px; padding:26px 28px; box-shadow:0 20px 60px rgba(0,0,0,.5);
  }
  .panel h2{ font-size:clamp(30px,6vw,54px); letter-spacing:.05em; line-height:1; margin-bottom:10px; }
  .panel p{ color:#d9def7; line-height:1.55; margin:8px 0; }
  .panel .small{ color:var(--muted); font-size:13px; }
  .keys{ display:flex; gap:7px; justify-content:center; flex-wrap:wrap; margin:14px 0; }
  kbd{
    background:#22254a; border:1px solid rgba(255,255,255,.22); border-bottom-width:3px;
    padding:4px 8px; border-radius:7px; font-family:inherit; font-weight:800; font-size:12px;
  }
  .legend{ display:flex; justify-content:center; gap:8px; flex-wrap:wrap; margin-top:12px; }
  .pill{ padding:6px 9px; border-radius:999px; font-size:12px; font-weight:900; color:#07101c; }
  .w{background:#5aff8a}.m{background:#ffdf5a}.s{background:#62c9ff}.l{background:#ff7d9c}
  footer{ display:flex; justify-content:space-between; gap:12px; flex-wrap:wrap; color:var(--muted); font-size:13px; margin-top:10px; line-height:1.5; }
  .toast{
    position:absolute; left:50%; top:16px; transform:translateX(-50%) translateY(-8px);
    background:rgba(10,12,30,.86); border:1px solid rgba(255,255,255,.2); color:#fff;
    padding:8px 13px; border-radius:999px; font-weight:900; letter-spacing:.05em; opacity:0;
    transition:.25s; pointer-events:none; z-index:6; white-space:nowrap;
  }
  .toast.show{ opacity:1; transform:translateX(-50%) translateY(0); }
  @media(max-width:650px){
    .hud{ grid-template-columns:repeat(2,1fr); }
    .hud .card:last-child{ grid-column:span 2; }
    canvas{ cursor:auto; }
    footer{ justify-content:center; text-align:center; }
  }
</style>
</head>
<body>
<main class="wrap">
  <header>
    <div>
      <h1>NEON BREAKOUT</h1>
      <div class="sub">Smash bricks, catch power-ups, chain combos. 5 handcrafted levels — then the speed keeps rising.</div>
    </div>
    <div class="buttons">
      <button id="startBtn" class="primary">▶ START</button>
      <button id="pauseBtn">⏸ PAUSE</button>
      <button id="soundBtn">🔊 SOUND</button>
      <button id="restartBtn">↻ RESTART</button>
    </div>
  </header>

  <section class="hud" aria-label="Game stats">
    <div class="card"><div class="label">SCORE</div><div class="value" id="score">0</div></div>
    <div class="card"><div class="label">BEST</div><div class="value" id="best">0</div></div>
    <div class="card"><div class="label">LEVEL</div><div class="value" id="level">1 / 5</div></div>
    <div class="card"><div class="label">LIVES</div><div class="value" id="lives">♥♥♥</div></div>
    <div class="card"><div class="label">ACTIVE POWER</div><div class="value" id="powerStatus">—</div></div>
  </section>

  <section class="stage">
    <canvas id="game" width="900" height="620"></canvas>
    <div class="toast" id="toast"></div>
    <div class="overlay" id="overlay">
      <div class="panel">
        <h2 id="ovTitle">READY?</h2>
        <p id="ovText">Move with your mouse or arrow keys. Launch the ball, clear every colored brick, and don't let the ball fall.</p>
        <div class="keys">
          <span><kbd>←</kbd> <kbd>→</kbd> / <kbd>A</kbd> <kbd>D</kbd> Move</span>
          <span><kbd>SPACE</kbd> Launch</span>
          <span><kbd>P</kbd> Pause</span>
          <span><kbd>M</kbd> Mute</span>
        </div>
        <button class="primary" id="bigBtn" style="font-size:17px;padding:13px 22px;">START GAME</button>
        <div class="legend">
          <span class="pill w">W = WIDE</span>
          <span class="pill m">M = MULTI-BALL</span>
          <span class="pill s">S = SLOW-MO</span>
          <span class="pill l">L = EXTRA LIFE</span>
        </div>
        <p class="small">Orange bricks explode. Gray steel bricks can't be broken. Cracked bricks need extra hits.</p>
      </div>
    </div>
  </section>

  <footer>
    <div>🖱️ Mouse / touch: move paddle &nbsp;•&nbsp; Click or Space: launch</div>
    <div>Tip: hit with the edge of the paddle for sharp angles. Build combos for up to ×5 points!</div>
  </footer>
</main>

<script>
(() => {
  "use strict";

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

  const el = id => document.getElementById(id);
  const scoreEl = el('score'), bestEl = el('best'), levelEl = el('level'),
        livesEl = el('lives'), powerEl = el('powerStatus'),
        overlay = el('overlay'), ovTitle = el('ovTitle'), ovText = el('ovText'),
        bigBtn = el('bigBtn'), toast = el('toast');

  const clamp = (v,a,b)=>Math.max(a,Math.min(b,v));
  const rand = (a,b)=>a+Math.random()*(b-a);
  const TAU = Math.PI*2;

  let best = 0;
  try { best = +localStorage.getItem('neonBreakoutBest') || 0; } catch(e) {}
  bestEl.textContent = best.toLocaleString();

  // ---------- Audio (generated, no files) ----------
  let audioCtx = null, muted = false;
  function ensureAudio(){
    if(!audioCtx){
      try { audioCtx = new (window.AudioContext || window.webkitAudioContext)(); } catch(e) {}
    }
    if(audioCtx && audioCtx.state === 'suspended') audioCtx.resume();
  }
  function tone(freq=440, dur=.07, type='square', vol=.045, slide=0){
    if(muted || !audioCtx) return;
    const t = audioCtx.currentTime;
    const o = audioCtx.createOscillator(), g = audioCtx.createGain();
    o.type = type; o.frequency.setValueAtTime(freq, t);
    if(slide) o.frequency.exponentialRampToValueAtTime(Math.max(30,freq+slide), t+dur);
    g.gain.setValueAtTime(vol, t);
    g.gain.exponentialRampToValueAtTime(.0001, t+dur);
    o.connect(g).connect(audioCtx.destination);
    o.start(t); o.stop(t+dur+.02);
  }
  const sfx = {
    paddle(){ tone(210,.055,'square',.05,120); },
    wall(){ tone(170,.04,'square',.032,40); },
    brick(combo=0){ tone(330+Math.min(combo,20)*18,.07,'square',.055,260); },
    hard(){ tone(125,.06,'sawtooth',.05,-30); },
    boom(){ tone(95,.22,'sawtooth',.09,-55); tone(55,.28,'triangle',.08,-20); },
    power(){ [523,659,784,1046].forEach((f,i)=>setTimeout(()=>tone(f,.09,'triangle',.055),i*45)); },
    lose(){ tone(320,.28,'sawtooth',.06,-220); },
    launch(){ tone(420,.09,'triangle',.05,240); },
    clear(){ [392,523,659,784,1046,1318].forEach((f,i)=>setTimeout(()=>tone(f,.11,'triangle',.055),i*55)); },
    over(){ [440,349,293,220].forEach((f,i)=>setTimeout(()=>tone(f,.16,'sawtooth',.05),i*130)); }
  };

  // ---------- Levels ----------
  // 1,2,3 = HP | E = explosive | X = steel | P = power brick | . = empty
  const LEVELS = [
    {
      name:"First Contact", speed:390,
      map:[
        "1111111111",
        "1111111111",
        "2222222222",
        "1111111111",
        "1P111111P1",
        "1111111111"
      ]
    },
    {
      name:"Checker Storm", speed:425,
      map:[
        "1.1.11.1.1",
        ".2.2..2.2.",
        "1.1.EE.1.1",
        ".2.2..2.2.",
        "1.1.11.1.1",
        "..P....P..",
        "1111111111"
      ]
    },
    {
      name:"The Fortress", speed:455,
      map:[
        "..111111..",
        ".12222221.",
        "12X3333X21",
        "123E22E321",
        ".12222221.",
        "..1P11P1..",
        "1111111111"
      ]
    },
    {
      name:"Diamond Mine", speed:490,
      map:[
        "....11....",
        "...1221...",
        "..123321..",
        ".12E33E21.",
        "112X33X211",
        ".12P22P21.",
        "..111111..",
        "1111111111"
      ]
    },
    {
      name:"Neon Core", speed:530,
      map:[
        "3X111111X3",
        "122E22E221",
        "1123333211",
        "111X22X111",
        "12P1331P21",
        "1E111111E1",
        "2222222222",
        "1111111111"
      ]
    }
  ];

  const COLORS = {
    1:['#37f5ff','#087f9e'], 2:['#ffe75a','#d68a00'], 3:['#ff4fd8','#8d1590'],
    E:['#ff9b3d','#c43d00'], X:['#aeb7cc','#4c5268'], P:['#63ff8f','#109447']
  };

  // ---------- State ----------
  let state = 'menu'; // menu, ready, playing, paused, between, gameover, win
  let score=0, lives=3, levelIndex=0, loop=0;
  let combo=0, maxCombo=0, shake=0, flash=0, time=0;
  let bricks=[], balls=[], particles=[], powerups=[], floaters=[];
  let wideTimer=0, slowTimer=0, paddleSquash=0, levelIntroTimer=0;
  let keys = {left:false,right:false};
  let mouseX = W/2, usingMouse=false;

  const paddle = { x:W/2, y:H-42, baseW:118, w:118, h:16, speed:760 };
  const BRICK = { cols:10, gap:7, h:25, x:34, y:78 };
  BRICK.w = (W - BRICK.x*2 - BRICK.gap*(BRICK.cols-1)) / BRICK.cols;

  function currentSpeed(){
    const base = LEVELS[levelIndex].speed + loop*35;
    return base * (slowTimer>0 ? .68 : 1);
  }
  function breakablesLeft(){ return bricks.filter(b=>b.hp>0).length; }

  function buildLevel(idx){
    bricks=[]; powerups=[]; particles=[]; floaters=[];
    combo=0; wideTimer=0; slowTimer=0;
    paddle.w=paddle.baseW; paddle.x=W/2;
    const lvl=LEVELS[idx];
    lvl.map.forEach((row,r)=>{
      [...row].forEach((ch,c)=>{
        if(ch==='.'||ch===' ') return;
        let hp=1, type=ch;
        if(ch==='2') hp=2; if(ch==='3') hp=3; if(ch==='X') hp=Infinity;
        bricks.push({
          x:BRICK.x+c*(BRICK.w+BRICK.gap), y:BRICK.y+r*(BRICK.h+BRICK.gap),
          w:BRICK.w, h:BRICK.h, hp, maxHp:hp, type, dead:false, pop:0
        });
      });
    });
    resetBall();
  }

  function makeBall(x,y,vx,vy){
    return { x,y,vx,vy,r:9, trail:[], stuck:false };
  }
  function resetBall(){
    balls=[makeBall(paddle.x, paddle.y-14, 0,0)];
    balls[0].stuck=true;
  }
  function launchBalls(){
    if(state!=='ready' && state!=='playing') return;
    let launched=false;
    balls.forEach(b=>{
      if(b.stuck){
        const a = rand(-.5,-.22) * (Math.random()<.5?-1:1) - Math.PI/2;
        // angle around upward direction
        const ang = -Math.PI/2 + rand(-.45,.45);
        const sp=currentSpeed();
        b.vx=Math.cos(ang)*sp; b.vy=Math.sin(ang)*sp; b.stuck=false; launched=true;
      }
    });
    if(launched){ state='playing'; hideOverlay(); sfx.launch(); showToast('GO!'); }
  }

  function resetGame(){
    score=0; lives=3; levelIndex=0; loop=0; maxCombo=0;
    buildLevel(0);
    state='ready';
    updateHUD();
    showOverlay('LEVEL 1 — '+LEVELS[0].name.toUpperCase(),
      'Clear all colored bricks to advance. Steel bricks are indestructible — use them to your advantage!',
      'LAUNCH BALL');
  }

  // ---------- Juice ----------
  function burst(x,y,color,n=18,speed=280){
    for(let i=0;i<n;i++){
      const a=Math.random()*TAU, s=rand(40,speed);
      particles.push({x,y,vx:Math.cos(a)*s,vy:Math.sin(a)*s-50,life:rand(.35,.85),max:.85,
        size:rand(2,6),color,shape:Math.random()<.25?'rect':'circle',rot:rand(0,TAU),vr:rand(-8,8)});
    }
    if(particles.length>550) particles.splice(0, particles.length-550);
  }
  function floater(x,y,text,color='#fff',size=18){
    floaters.push({x,y,text,color,size,life:1,vy:-48});
  }
  function showToast(txt,ms=950){
    toast.textContent=txt; toast.classList.add('show');
    clearTimeout(showToast._t);
    showToast._t=setTimeout(()=>toast.classList.remove('show'),ms);
  }

  // ---------- Power-ups ----------
  const POWER_TYPES=[
    {k:'W',label:'WIDE',color:'#5aff8a'},
    {k:'M',label:'MULTI',color:'#ffdf5a'},
    {k:'S',label:'SLOW',color:'#62c9ff'},
    {k:'L',label:'LIFE',color:'#ff7d9c'}
  ];
  function maybeDrop(x,y,guaranteed){
    if(!guaranteed && Math.random()>.17) return;
    const p=POWER_TYPES[Math.floor(Math.random()*POWER_TYPES.length)];
    powerups.push({x,y,w:36,h:20,vy:150,k:p.k,label:p.label,color:p.color,rot:0});
  }
  function applyPower(p){
    sfx.power(); showToast(p.label+'!');
    burst(p.x,p.y,p.color,24,240);
    if(p.k==='W'){ wideTimer=12; paddle.w=paddle.baseW*1.55; floater(p.x,p.y-12,'WIDE PADDLE','#5aff8a',20); }
    if(p.k==='S'){ slowTimer=9; normalizeBalls(); floater(p.x,p.y-12,'SLOW-MO','#62c9ff',20); }
    if(p.k==='L'){ lives=Math.min(9,lives+1); floater(p.x,p.y-12,'+1 LIFE','#ff7d9c',22); toneLife(); }
    if(p.k==='M'){
      const src=[...balls].filter(b=>!b.stuck);
      src.forEach(b=>{
        if(balls.length>=6) return;
        const sp=Math.hypot(b.vx,b.vy)||currentSpeed(), a=Math.atan2(b.vy,b.vx)+rand(.45,.75)*(Math.random()<.5?-1:1);
        balls.push(makeBall(b.x,b.y,Math.cos(a)*sp,Math.sin(a)*sp));
      });
      floater(p.x,p.y-12,'MULTI-BALL','#ffdf5a',20);
    }
    updateHUD();
  }
  function toneLife(){ setTimeout(()=>tone(880,.12,'triangle',.06,220),60); }
  function normalizeBalls(){
    const sp=currentSpeed();
    balls.forEach(b=>{
      if(b.stuck) return;
      const m=Math.hypot(b.vx,b.vy)||1;
      b.vx=b.vx/m*sp; b.vy=b.vy/m*sp;
      if(Math.abs(b.vy)<sp*.22){ b.vy=(b.vy<0?-1:1)*sp*.22; const s2=Math.hypot(b.vx,b.vy); b.vx*=sp/s2; b.vy*=sp/s2; }
    });
  }

  // ---------- Brick damage / explosions ----------
  function damageBrick(br, ball){
    if(br.dead) return;
    if(br.hp===Infinity){ sfx.hard(); shake=Math.max(shake,2); burst(br.x+br.w/2,br.y+br.h/2,'#aeb7cc',5,130); return; }
    if(br.type==='E'){ explodeBrick(br); return; }
    br.hp--; br.pop=1;
    if(br.hp>0){ sfx.hard(); burst(br.x+br.w/2,br.y+br.h/2,COLORS[br.type][0],7,160); return; }
    destroyBrick(br, true);
  }
  function destroyBrick(br, drop=true){
    if(br.dead || br.hp===Infinity) return;
    br.dead=true; combo++; maxCombo=Math.max(maxCombo,combo);
    const mult=1+Math.min(4,Math.floor(combo/8));
    const pts=(br.type==='P'?100:50)*mult;
    score+=pts;
    const col=COLORS[br.type][0];
    burst(br.x+br.w/2,br.y+br.h/2,col, br.type==='P'?30:18, 300);
    floater(br.x+br.w/2,br.y, '+'+pts, col, mult>1?20:16);
    if(mult>1 && combo%8===0) floater(W/2,150,'COMBO ×'+mult,'#ffe75a',30);
    shake=Math.max(shake, br.type==='P'?5:2.5);
    sfx.brick(combo);
    if(drop) maybeDrop(br.x+br.w/2,br.y+br.h/2, br.type==='P');
    updateHUD();
    checkLevelClear();
  }
  function explodeBrick(br){
    if(br.dead) return;
    br.dead=true; score+=75; combo++;
    shake=Math.max(shake,11); flash=.22;
    sfx.boom();
    burst(br.x+br.w/2,br.y+br.h/2,'#ff9b3d',46,430);
    burst(br.x+br.w/2,br.y+br.h/2,'#ffe75a',22,300);
    floater(br.x+br.w/2,br.y,'BOOM +75','#ff9b3d',21);
    const cx=br.x+br.w/2, cy=br.y+br.h/2;
    bricks.forEach(o=>{
      if(o===br||o.dead) return;
      const ox=o.x+o.w/2, oy=o.y+o.h/2;
      if(Math.hypot(ox-cx,oy-cy) < 105){
        if(o.hp===Infinity) return;
        if(o.type==='E' && !o.dead){ setTimeout(()=>explodeBrick(o),45); }
        else { o.hp=0; destroyBrick(o,true); }
      }
    });
    updateHUD(); checkLevelClear();
  }
  function checkLevelClear(){
    if(state!=='playing') return;
    if(breakablesLeft()<=0){
      state='between'; levelIntroTimer=1.35;
      score += 250 + lives*25;
      sfx.clear();
      for(let i=0;i<5;i++) setTimeout(()=>burst(rand(100,W-100),rand(100,300),
        ['#38f5ff','#ff4fd8','#ffe75a','#63ff8f'][i%4],35,380), i*110);
      showToast('LEVEL CLEAR!  BONUS +'+(250+lives*25),1400);
      updateHUD();
    }
  }
  function nextLevel(){
    levelIndex++;
    if(levelIndex>=LEVELS.length){
      if(loop===0){
        state='win';
        saveBest();
        showOverlay('🏆 YOU WIN!','You smashed the Neon Core! Final score: <b>'+score.toLocaleString()+'</b><br>Max combo: '+maxCombo+'<br><br>Press play again for Endless Mode — faster balls, same 5 arenas.','PLAY ENDLESS MODE');
        return;
      }
      levelIndex=0;
    }
    loop += (levelIndex===0 && state!=='win') ? 0 : 0; // loop incremented on endless start below
    buildLevel(levelIndex);
    state='ready';
    showOverlay('LEVEL '+(levelIndex+1)+' — '+LEVELS[levelIndex].name.toUpperCase(),
      loop>0 ? 'Endless round '+(loop+1)+'. The ball is faster now. Good luck!' : 'Get ready. Click or press Space to launch.',
      'LAUNCH BALL');
    updateHUD();
  }

  // ---------- HUD / overlay ----------
  function updateHUD(){
    scoreEl.textContent=score.toLocaleString();
    bestEl.textContent=Math.max(best,score).toLocaleString();
    levelEl.textContent=(levelIndex+1)+' / '+LEVELS.length+(loop>0?' ∞':'');
    livesEl.textContent='♥'.repeat(clamp(lives,0,9))||'—';
    let p=[];
    if(wideTimer>0) p.push('WIDE '+Math.ceil(wideTimer)+'s');
    if(slowTimer>0) p.push('SLOW '+Math.ceil(slowTimer)+'s');
    if(combo>=3) p.push('COMBO ×'+(1+Math.min(4,Math.floor(combo/8)))+' ('+combo+')');
    powerEl.textContent=p.length?p.join(' • '):'—';
  }
  function showOverlay(title,html,btn){
    ovTitle.innerHTML=title; ovText.innerHTML=html; bigBtn.textContent=btn;
    overlay.classList.remove('hidden');
  }
  function hideOverlay(){ overlay.classList.add('hidden'); }
  function saveBest(){
    if(score>best){ best=score; try{ localStorage.setItem('neonBreakoutBest',String(best)); }catch(e){} }
    bestEl.textContent=best.toLocaleString();
  }
  function gameOver(){
    state='gameover'; saveBest(); sfx.over(); shake=14;
    showOverlay('GAME OVER','Score: <b>'+score.toLocaleString()+'</b> &nbsp;•&nbsp; Best: <b>'+best.toLocaleString()+'</b><br>Level reached: '+(levelIndex+1)+' &nbsp;•&nbsp; Max combo: '+maxCombo,'TRY AGAIN');
  }

  // ---------- Update ----------
  function update(dt){
    time+=dt;
    shake=Math.max(0,shake-dt*28);
    flash=Math.max(0,flash-dt);
    paddleSquash=Math.max(0,paddleSquash-dt*7);
    if(wideTimer>0){ wideTimer-=dt; if(wideTimer<=0) paddle.w=paddle.baseW; }
    if(slowTimer>0){ const was=slowTimer; slowTimer-=dt; if(was>0&&slowTimer<=0) normalizeBalls(); }
    if(state==='between'){
      levelIntroTimer-=dt;
      updateParticles(dt);
      if(levelIntroTimer<=0) nextLevel();
      updateHUD(); return;
    }
    if(state!=='playing' && state!=='ready' && state!=='paused') { updateParticles(dt); return; }
    if(state==='paused') return;

    // Paddle keyboard
    const dir=(keys.right?1:0)-(keys.left?1:0);
    if(dir!==0){ paddle.x += dir*paddle.speed*dt; usingMouse=false; }
    else if(usingMouse){ paddle.x += (mouseX-paddle.x)*Math.min(1,dt*22); }
    paddle.x=clamp(paddle.x,paddle.w/2+8,W-paddle.w/2-8);

    // Stuck balls follow paddle
    balls.forEach(b=>{ if(b.stuck){ b.x=paddle.x; b.y=paddle.y-b.r-3; b.trail.length=0; } });

    if(state==='playing'){
      // Move balls in sub-steps to avoid tunneling
      balls.forEach(b=>{
        if(b.stuck) return;
        // Maintain intended speed (keeps slow-mo consistent)
        const target=currentSpeed(), m=Math.hypot(b.vx,b.vy);
        if(m>0 && Math.abs(m-target)>2){ b.vx*=target/m; b.vy*=target/m; }
        const dist=Math.hypot(b.vx,b.vy)*dt, steps=Math.max(1,Math.ceil(dist/(b.r*.75)));
        const sdt=dt/steps;
        for(let s=0;s<steps && !b.dead;s++){
          b.x+=b.vx*sdt; b.y+=b.vy*sdt;
          collideWalls(b); collidePaddle(b); collideBricks(b);
        }
        b.trail.unshift({x:b.x,y:b.y});
        if(b.trail.length>13) b.trail.pop();
        if(b.y-b.r>H+30) b.dead=true;
      });
      const before=balls.length;
      balls=balls.filter(b=>!b.dead);
      if(before>0 && balls.length===0){
        lives--; combo=0; sfx.lose(); shake=12; flash=.18;
        burst(paddle.x,H-20,'#ff6b81',35,320);
        updateHUD();
        if(lives<=0){ gameOver(); return; }
        resetBall(); state='ready';
        showToast('BALL LOST — '+lives+(lives===1?' LIFE':' LIVES')+' LEFT');
        setTimeout(()=>{ if(state==='ready') showOverlay('BALL READY','Lives left: '+'♥'.repeat(lives)+'<br>Click or press Space to launch.','LAUNCH'); },350);
      }

      // Power-ups
      powerups.forEach(p=>{
        p.y+=p.vy*dt; p.rot+=dt*3;
        if(p.y+p.h>=paddle.y && p.y<=paddle.y+paddle.h+8 &&
           p.x+p.w/2>=paddle.x-paddle.w/2 && p.x-p.w/2<=paddle.x+paddle.w/2){
          p.caught=true; applyPower(p);
        }
      });
      powerups=powerups.filter(p=>!p.caught && p.y<H+40);
    }
    bricks.forEach(b=>b.pop=Math.max(0,b.pop-dt*6));
    updateParticles(dt);
    updateHUD();
  }
  function updateParticles(dt){
    particles.forEach(p=>{ p.life-=dt; p.x+=p.vx*dt; p.y+=p.vy*dt; p.vy+=420*dt; p.vx*=.99; p.rot+=p.vr*dt; });
    particles=particles.filter(p=>p.life>0);
    floaters.forEach(f=>{ f.life-=dt*.75; f.y+=f.vy*dt; });
    floaters=floaters.filter(f=>f.life>0);
  }

  function collideWalls(b){
    if(b.x-b.r<8){ b.x=8+b.r; b.vx=Math.abs(b.vx); sfx.wall(); }
    if(b.x+b.r>W-8){ b.x=W-8-b.r; b.vx=-Math.abs(b.vx); sfx.wall(); }
    if(b.y-b.r<8){ b.y=8+b.r; b.vy=Math.abs(b.vy); sfx.wall(); }
  }
  function collidePaddle(b){
    if(b.vy<=0) return;
    const top=paddle.y, left=paddle.x-paddle.w/2, right=paddle.x+paddle.w/2;
    if(b.y+b.r>=top && b.y-b.r<=top+paddle.h+10 && b.x>=left-b.r && b.x<=right+b.r){
      const rel=clamp((b.x-paddle.x)/(paddle.w/2),-1,1);
      const maxAngle=1.12; // ~64°
      const ang=-Math.PI/2 + rel*maxAngle;
      const sp=currentSpeed();
      b.vx=Math.cos(ang)*sp; b.vy=Math.sin(ang)*sp;
      b.y=top-b.r-1;
      combo=0; paddleSquash=1;
      shake=Math.max(shake, Math.abs(rel)*2);
      burst(b.x,top, '#38f5ff', 8, 150);
      sfx.paddle(); updateHUD();
    }
  }
  function collideBricks(b){
    for(const br of bricks){
      if(br.dead) continue;
      const cx=clamp(b.x,br.x,br.x+br.w), cy=clamp(b.y,br.y,br.y+br.h);
      const dx=b.x-cx, dy=b.y-cy;
      if(dx*dx+dy*dy <= b.r*b.r){
        // Resolve by smallest penetration axis
        const overlapL=Math.abs((b.x+b.r)-br.x), overlapR=Math.abs((br.x+br.w)-(b.x-b.r));
        const overlapT=Math.abs((b.y+b.r)-br.y), overlapB=Math.abs((br.y+br.h)-(b.y-b.r));
        const min=Math.min(overlapL,overlapR,overlapT,overlapB);
        if(min===overlapT){ b.y=br.y-b.r; b.vy=-Math.abs(b.vy); }
        else if(min===overlapB){ b.y=br.y+br.h+b.r; b.vy=Math.abs(b.vy); }
        else if(min===overlapL){ b.x=br.x-b.r; b.vx=-Math.abs(b.vx); }
        else { b.x=br.x+br.w+b.r; b.vx=Math.abs(b.vx); }
        // Prevent near-horizontal stalls
        const sp=currentSpeed(), m=Math.hypot(b.vx,b.vy)||sp;
        b.vx*=sp/m; b.vy*=sp/m;
        if(Math.abs(b.vy)<sp*.18){ b.vy=(b.vy<0?-1:1)*sp*.18; const m2=Math.hypot(b.vx,b.vy); b.vx*=sp/m2; b.vy*=sp/m2; }
        damageBrick(br,b);
        break;
      }
    }
  }

  // ---------- Draw ----------
  function roundRect(x,y,w,h,r){
    r=Math.min(r,w/2,h/2);
    ctx.beginPath();
    ctx.moveTo(x+r,y); ctx.arcTo(x+w,y,x+w,y+h,r); ctx.arcTo(x+w,y+h,x,y+h,r);
    ctx.arcTo(x,y+h,x,y,r); ctx.arcTo(x,y,x+w,y,r); ctx.closePath();
  }
  function draw(){
    ctx.save();
    ctx.clearRect(0,0,W,H);
    // Background
    const g=ctx.createLinearGradient(0,0,0,H);
    g.addColorStop(0,'#0b0d26'); g.addColorStop(.55,'#0a1230'); g.addColorStop(1,'#12081f');
    ctx.fillStyle=g; ctx.fillRect(0,0,W,H);
    // Grid + stars
    ctx.strokeStyle='rgba(90,120,255,.07)'; ctx.lineWidth=1;
    for(let x=0;x<=W;x+=45){ ctx.beginPath(); ctx.moveTo(x,0); ctx.lineTo(x,H); ctx.stroke(); }
    for(let y=0;y<=H;y+=45){ ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(W,y); ctx.stroke(); }
    ctx.fillStyle='rgba(255,255,255,.35)';
    for(let i=0;i<45;i++){
      const sx=(i*167.3)%W, sy=(i*89.7)%H, tw=.35+.65*Math.abs(Math.sin(time*1.4+i));
      ctx.globalAlpha=.12*tw; ctx.fillRect(sx,sy,2,2);
    }
    ctx.globalAlpha=1;

    if(shake>0) ctx.translate(rand(-shake,shake),rand(-shake,shake));

    // Arena border
    ctx.strokeStyle='rgba(56,245,255,.35)'; ctx.lineWidth=2;
    ctx.strokeRect(8.5,8.5,W-17,H-17);
    ctx.shadowBlur=18; ctx.shadowColor='rgba(56,245,255,.45)';
    ctx.strokeRect(8.5,8.5,W-17,H-17); ctx.shadowBlur=0;

    drawBricks(); drawPowerups(); drawPaddle(); drawBalls(); drawParticles(); drawFloaters();

    // Ready hint arrow
    if(state==='ready'){
      ctx.fillStyle='rgba(255,255,255,.9)';
      ctx.font='800 15px system-ui,sans-serif'; ctx.textAlign='center';
      const bob=Math.sin(time*5)*5;
      ctx.fillText('CLICK OR PRESS SPACE TO LAUNCH', W/2, H-92+bob);
      ctx.beginPath();
      ctx.moveTo(W/2-9,H-78+bob); ctx.lineTo(W/2+9,H-78+bob); ctx.lineTo(W/2,H-66+bob); ctx.closePath();
      ctx.fillStyle='#ffe75a'; ctx.fill();
    }
    if(state==='paused'){
      ctx.fillStyle='rgba(4,5,18,.62)'; ctx.fillRect(0,0,W,H);
      ctx.fillStyle='#fff'; ctx.font='900 52px system-ui,sans-serif'; ctx.textAlign='center';
      ctx.fillText('PAUSED',W/2,H/2-8);
      ctx.font='700 17px system-ui,sans-serif'; ctx.fillStyle='#bdc6ee';
      ctx.fillText('Press P or click Pause to resume',W/2,H/2+26);
    }
    if(flash>0){ ctx.fillStyle=`rgba(255,120,60,${flash*.55})`; ctx.fillRect(0,0,W,H); }
    ctx.restore();

    // Vignette
    const v=ctx.createRadialGradient(W/2,H/2,H*.35,W/2,H/2,H*.85);
    v.addColorStop(0,'rgba(0,0,0,0)'); v.addColorStop(1,'rgba(0,0,0,.38)');
    ctx.fillStyle=v; ctx.fillRect(0,0,W,H);
  }
  function drawBricks(){
    bricks.forEach(br=>{
      if(br.dead) return;
      const [main,dark]=COLORS[br.type];
      const scale=1+br.pop*.12, w=br.w*scale, h=br.h*scale;
      const x=br.x+(br.w-w)/2, y=br.y+(br.h-h)/2;
      const g=ctx.createLinearGradient(x,y,x,y+h);
      g.addColorStop(0,main); g.addColorStop(1,dark);
      ctx.save();
      ctx.shadowBlur=br.type==='X'?0:13; ctx.shadowColor=main;
      ctx.fillStyle=g; roundRect(x,y,w,h,7); ctx.fill();
      ctx.shadowBlur=0;
      ctx.fillStyle='rgba(255,255,255,.28)'; roundRect(x+4,y+3,w-8,h*.36,5); ctx.fill();
      ctx.strokeStyle='rgba(255,255,255,.35)'; ctx.lineWidth=1; roundRect(x+.5,y+.5,w-1,h-1,7); ctx.stroke();
      if(br.type==='E'){
        ctx.fillStyle='#fff'; ctx.font='900 15px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle';
        ctx.fillText('✹', br.x+br.w/2, br.y+br.h/2+1);
      } else if(br.type==='P'){
        ctx.fillStyle='#062b13'; ctx.font='900 13px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle';
        ctx.fillText('★', br.x+br.w/2, br.y+br.h/2+1);
      } else if(br.hp!==Infinity && br.hp<br.maxHp){
        // cracks
        ctx.strokeStyle='rgba(0,0,0,.45)'; ctx.lineWidth=1.4;
        ctx.beginPath();
        ctx.moveTo(br.x+10,br.y+4); ctx.lineTo(br.x+br.w*.42,br.y+br.h/2); ctx.lineTo(br.x+br.w*.3,br.y+br.h-4);
        ctx.moveTo(br.x+br.w-12,br.y+5); ctx.lineTo(br.x+br.w*.62,br.y+br.h*.55); ctx.lineTo(br.x+br.w-22,br.y+br.h-3);
        ctx.stroke();
        ctx.fillStyle='rgba(0,0,0,.55)'; ctx.font='900 12px system-ui'; ctx.textAlign='right'; ctx.textBaseline='top';
        ctx.fillText(br.hp, br.x+br.w-5, br.y+3);
      }
      ctx.restore();
    });
  }
  function drawPaddle(){
    const squash=1+paddleSquash*.12, w=paddle.w*squash, h=paddle.h*(2-squash);
    const x=paddle.x-w/2, y=paddle.y+(paddle.h-h);
    ctx.save();
    ctx.shadowBlur=24; ctx.shadowColor='#38f5ff';
    const g=ctx.createLinearGradient(x,y,x,y+h);
    g.addColorStop(0,'#ffffff'); g.addColorStop(.35,'#5ff7ff'); g.addColorStop(1,'#7b3dff');
    ctx.fillStyle=g; roundRect(x,y,w,h,9); ctx.fill();
    ctx.shadowBlur=0;
    ctx.fillStyle='rgba(255,255,255,.75)'; roundRect(x+8,y+3,w-16,4,3); ctx.fill();
    // end caps
    ctx.fillStyle='#ff4fd8';
    roundRect(x+2,y+2,10,h-4,5); ctx.fill();
    roundRect(x+w-12,y+2,10,h-4,5); ctx.fill();
    ctx.restore();
  }
  function drawBalls(){
    balls.forEach(b=>{
      // trail
      b.trail.forEach((t,i)=>{
        ctx.globalAlpha=.16*(1-i/b.trail.length);
        ctx.fillStyle='#38f5ff';
        ctx.beginPath(); ctx.arc(t.x,t.y,b.r*(1-i/(b.trail.length*1.4)),0,TAU); ctx.fill();
      });
      ctx.globalAlpha=1;
      ctx.save();
      ctx.shadowBlur=22; ctx.shadowColor='#fff';
      const g=ctx.createRadialGradient(b.x-3,b.y-4,1,b.x,b.y,b.r+2);
      g.addColorStop(0,'#ffffff'); g.addColorStop(.55,'#aefcff'); g.addColorStop(1,'#1599ff');
      ctx.fillStyle=g; ctx.beginPath(); ctx.arc(b.x,b.y,b.r,0,TAU); ctx.fill();
      ctx.restore();
    });
  }
  function drawPowerups(){
    powerups.forEach(p=>{
      ctx.save();
      ctx.translate(p.x,p.y);
      ctx.shadowBlur=16; ctx.shadowColor=p.color;
      ctx.fillStyle=p.color; roundRect(-p.w/2,-p.h/2,p.w,p.h,10); ctx.fill();
      ctx.shadowBlur=0;
      ctx.fillStyle='rgba(255,255,255,.45)'; roundRect(-p.w/2+5,-p.h/2+3,p.w-10,5,3); ctx.fill();
      ctx.fillStyle='#101426'; ctx.font='900 14px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle';
      ctx.fillText(p.k,0,1);
      ctx.restore();
    });
  }
  function drawParticles(){
    particles.forEach(p=>{
      ctx.save(); ctx.globalAlpha=clamp(p.life/p.max,0,1);
      ctx.translate(p.x,p.y); ctx.rotate(p.rot); ctx.fillStyle=p.color;
      if(p.shape==='rect') ctx.fillRect(-p.size/2,-p.size/2,p.size,p.size*.65);
      else { ctx.beginPath(); ctx.arc(0,0,p.size/2,0,TAU); ctx.fill(); }
      ctx.restore();
    });
    ctx.globalAlpha=1;
  }
  function drawFloaters(){
    floaters.forEach(f=>{
      ctx.save(); ctx.globalAlpha=clamp(f.life,0,1);
      ctx.font='900 '+f.size+'px system-ui,sans-serif'; ctx.textAlign='center'; ctx.textBaseline='middle';
      ctx.shadowBlur=12; ctx.shadowColor=f.color; ctx.fillStyle=f.color;
      ctx.fillText(f.text,f.x,f.y); ctx.restore();
    });
  }

  // ---------- Loop ----------
  let last=performance.now();
  function frame(now){
    const dt=clamp((now-last)/1000,0,.033);
    last=now;
    update(dt); draw();
    requestAnimationFrame(frame);
  }

  // ---------- Input ----------
  function canvasPos(e){
    const r=canvas.getBoundingClientRect();
    const cx=(e.touches?e.touches[0].clientX:e.clientX);
    return (cx-r.left)/r.width*W;
  }
  canvas.addEventListener('mousemove',e=>{ mouseX=canvasPos(e); usingMouse=true; ensureAudio(); });
  canvas.addEventListener('mousedown',e=>{ ensureAudio(); mouseX=canvasPos(e); usingMouse=true; handlePrimaryAction(); });
  canvas.addEventListener('touchstart',e=>{ e.preventDefault(); ensureAudio(); mouseX=canvasPos(e); usingMouse=true; },{passive:false});
  canvas.addEventListener('touchmove',e=>{ e.preventDefault(); mouseX=canvasPos(e); usingMouse=true; },{passive:false});
  canvas.addEventListener('touchend',e=>{ e.preventDefault(); if(state==='ready') launchBalls(); },{passive:false});

  window.addEventListener('keydown',e=>{
    if(['ArrowLeft','ArrowRight','Space','ArrowUp','ArrowDown'].includes(e.code)) e.preventDefault();
    ensureAudio();
    if(e.code==='ArrowLeft'||e.code==='KeyA') keys.left=true;
    if(e.code==='ArrowRight'||e.code==='KeyD') keys.right=true;
    if(e.code==='Space'||e.code==='Enter'){
      if(state==='menu'||state==='gameover') resetGame();
      else if(state==='win'){ loop++; levelIndex=0; buildLevel(0); state='ready'; hideOverlay(); showToast('ENDLESS MODE — ROUND '+(loop+1)); }
      else if(state==='paused') togglePause();
      else launchBalls();
    }
    if(e.code==='KeyP') togglePause();
    if(e.code==='KeyM') toggleSound();
    if(e.code==='KeyR') resetGame();
  });
  window.addEventListener('keyup',e=>{
    if(e.code==='ArrowLeft'||e.code==='KeyA') keys.left=false;
    if(e.code==='ArrowRight'||e.code==='KeyD') keys.right=false;
  });
  window.addEventListener('blur',()=>{ if(state==='playing'){ state='paused'; syncPauseBtn(); } });

  function handlePrimaryAction(){
    if(state==='menu'||state==='gameover') resetGame();
    else if(state==='win'){ loop++; levelIndex=0; buildLevel(0); state='ready'; hideOverlay(); showToast('ENDLESS MODE'); }
    else if(state==='paused') togglePause();
    else launchBalls();
  }
  function togglePause(){
    if(state==='playing'){ state='paused'; }
    else if(state==='paused'){ state='playing'; hideOverlay(); }
    syncPauseBtn();
  }
  function syncPauseBtn(){ el('pauseBtn').textContent = state==='paused' ? '▶ RESUME' : '⏸ PAUSE'; }
  function toggleSound(){
    muted=!muted;
    el('soundBtn').textContent=muted?'🔇 MUTED':'🔊 SOUND';
    if(!muted){ ensureAudio(); tone(660,.08,'triangle',.05); }
  }

  el('startBtn').addEventListener('click',()=>{ ensureAudio(); if(state==='menu'||state==='gameover'||state==='win') handlePrimaryAction(); else launchBalls(); });
  el('restartBtn').addEventListener('click',()=>{ ensureAudio(); resetGame(); });
  el('pauseBtn').addEventListener('click',()=>{ ensureAudio(); togglePause(); syncPauseBtn(); });
  el('soundBtn').addEventListener('click',()=>{ ensureAudio(); toggleSound(); });
  bigBtn.addEventListener('click',()=>{ ensureAudio(); handlePrimaryAction(); });

  // ---------- Init ----------
  buildLevel(0);
  updateHUD();
  requestAnimationFrame(frame);
})();
</script>
</body>
</html>

Compare with

Muse Wonder: other attempts

  • Muse Wonder, Pelican: Queued · #1 of 11 paid · not before 23 Oct
  • Muse Wonder, Watch: Queued · #1 of 11 paid · not before 23 Oct
  • Muse Wonder, Landing: Queued · #1 of 11 paid · not before 23 Oct
  • Muse Wonder, Logo: Queued · #1 of 11 paid · not before 23 Oct
  • Muse Wonder, About: Queued · #1 of 11 paid · not before 23 Oct