Liveloop

An interactive timeline for social media. Every post is a tiny app you can play, save, and remix.

Product

  • Feed
  • Create
  • Claude Code plugin
  • Blog

Legal

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • DMCA

Project

  • Templates
© 2026 Liveloop. All rights reserved.
LiveloopVersion history

v1Current

@liveloop · 5/18/2026, 10:15:39 AM

Initial version — all lines are new.

+<style>
+ html,body{height:100%;margin:0;background:#06060f;overflow:hidden;}
+ *{box-sizing:border-box;}
+ #wrap{position:relative;height:100%;width:100%;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;}
+ canvas{display:block;width:100%;height:100%;touch-action:none;}
+ #hud{position:absolute;top:0;left:0;right:0;display:flex;justify-content:space-between;align-items:flex-start;padding:.7rem .95rem;color:#dbeafe;font-size:.86rem;font-weight:800;letter-spacing:.04em;pointer-events:none;text-shadow:0 0 10px rgba(0,0,0,.95);}
+ #hud .lab{font-size:.56rem;opacity:.55;letter-spacing:.14em;margin-bottom:.05rem;}
+ #hud .r{text-align:right;}
+ #lives{color:#f472b6;font-size:1rem;letter-spacing:.1em;}
+ #ov{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;text-align:center;padding:1.6rem;background:rgba(6,6,15,.85);color:#e9d5ff;cursor:pointer;-webkit-tap-highlight-color:transparent;touch-action:none;user-select:none;-webkit-user-select:none;}
+ #ov h1{margin:0;font-size:2.15rem;font-weight:900;letter-spacing:.02em;background:linear-gradient(90deg,#22d3ee,#c084fc,#f472b6);-webkit-background-clip:text;background-clip:text;color:transparent;}
+ #ov .sub{margin:.1rem 0 0;font-size:.92rem;color:#a5b4fc;}
+ #ov .big{margin:.1rem 0 0;font-size:1.05rem;font-weight:800;color:#e9d5ff;}
+ #ov .pulse{margin-top:.5rem;font-size:.84rem;color:#67e8f9;animation:nbpulse 1.3s ease-in-out infinite;}
+ @keyframes nbpulse{0%,100%{opacity:.35}50%{opacity:1}}
+</style>
+<div id="wrap">
+ <canvas id="cv"></canvas>
+ <div id="hud">
+ <div><div class="lab">SCORE</div><div id="score">0</div></div>
+ <div class="r"><div class="lab">BEST</div><div id="best">0</div></div>
+ <div class="r"><div class="lab">BALLS</div><div id="lives">|||</div></div>
+ </div>
+ <div id="ov">
+ <h1 id="ovtitle">GLOW BREAKER</h1>
+ <p class="sub" id="ovsub">Break every block. Don't drop the ball.</p>
+ <p class="big" id="ovbig"></p>
+ <p class="pulse">Tap to play</p>
+ </div>
+</div>
+<script>
+(function(){
+ var LL=window.liveloop||null;
+ var cv=document.getElementById('cv'), ctx=cv.getContext('2d');
+ var ov=document.getElementById('ov'), ovsub=document.getElementById('ovsub'),
+ ovbig=document.getElementById('ovbig'), ovtitle=document.getElementById('ovtitle');
+ var scoreEl=document.getElementById('score'), bestEl=document.getElementById('best'),
+ livesEl=document.getElementById('lives');
+ var COLORS=['#f472b6','#fb923c','#facc15','#a3e635','#22d3ee'];
+ var W=0,H=0,dpr=1;
+ var state='start', score=0, best=0, lives=3, level=1;
+ var paddle=null, ball=null, bricks=[], parts=[];
+ var dockUntil=0, flashText='', flashUntil=0;
+ var lastT=0, raf=0, running=false, keyL=false, keyR=false;
+
+ function setCanvas(){
+ var r=cv.getBoundingClientRect();
+ W=Math.max(140,r.width); H=Math.max(180,r.height);
+ dpr=Math.min(window.devicePixelRatio||1,2.5);
+ cv.width=Math.round(W*dpr); cv.height=Math.round(H*dpr);
+ ctx.setTransform(dpr,0,0,dpr,0,0);
+ }
+ function aliveCount(){
+ var n=0; for(var i=0;i<bricks.length;i++) if(bricks[i].alive) n++;
+ return n;
+ }
+ function buildBricks(){
+ bricks=[];
+ var cols=6, rows=Math.min(3+level,7);
+ var mx=W*0.045, top=H*0.115, gap=W*0.014;
+ var bw=(W-2*mx-(cols-1)*gap)/cols, bh=H*0.03;
+ for(var r=0;r<rows;r++){
+ for(var c=0;c<cols;c++){
+ bricks.push({x:mx+c*(bw+gap),y:top+r*(bh+gap),w:bw,h:bh,
+ color:COLORS[r%COLORS.length],alive:true});
+ }
+ }
+ }
+ function dockBall(){
+ var sp=H*0.0118*(1+(level-1)*0.11);
+ ball={x:paddle.x,y:paddle.y-H*0.035,r:Math.max(5,W*0.019),vx:0,vy:0,sp:sp,docked:true};
+ dockUntil=performance.now()+700;
+ }
+ function launch(){
+ var a=(-Math.PI/2)+(Math.random()*0.5-0.25);
+ ball.vx=Math.cos(a)*ball.sp; ball.vy=Math.sin(a)*ball.sp; ball.docked=false;
+ }
+ function newGame(){
+ score=0; lives=3; level=1; parts=[];
+ paddle={w:W*0.23,h:Math.max(9,H*0.016),x:W/2,y:H*0.9};
+ buildBricks(); dockBall(); state='playing'; syncHud();
+ }
+ function flash(t){ flashText=t; flashUntil=performance.now()+1100; }
+ function syncHud(){
+ scoreEl.textContent=score; bestEl.textContent=best;
+ var s=''; for(var i=0;i<lives;i++) s+='|';
+ livesEl.textContent=s||'-';
+ }
+ function pop(b){
+ var cx=b.x+b.w/2, cy=b.y+b.h/2, i;
+ for(i=0;i<8;i++){
+ var a=Math.random()*6.283;
+ parts.push({x:cx,y:cy,vx:Math.cos(a)*(1+Math.random()*2.4),
+ vy:Math.sin(a)*(1+Math.random()*2.4),life:14+Math.random()*12,color:b.color});
+ }
+ }
+ function normSpeed(){
+ var m=Math.sqrt(ball.vx*ball.vx+ball.vy*ball.vy);
+ if(m>0){ ball.vx=ball.vx/m*ball.sp; ball.vy=ball.vy/m*ball.sp; }
+ var minv=ball.sp*0.34;
+ if(Math.abs(ball.vy)<minv){
+ ball.vy=(ball.vy<0?-1:1)*minv;
+ normSpeedXOnly();
+ }
+ }
+ function normSpeedXOnly(){
+ var rem=ball.sp*ball.sp-ball.vy*ball.vy;
+ if(rem<0) rem=0;
+ ball.vx=(ball.vx<0?-1:1)*Math.sqrt(rem);
+ }
+ function gameOver(){
+ state='over';
+ if(score>best){ best=score; saveBest(); }
+ syncHud();
+ ovtitle.textContent='GAME OVER';
+ ovsub.textContent='You reached level '+level;
+ ovbig.textContent='Score '+score+' · Best '+best;
+ ov.style.display='flex';
+ }
+ function substep(dt){
+ ball.x+=ball.vx*dt; ball.y+=ball.vy*dt;
+ var r=ball.r;
+ if(ball.x-r<0){ ball.x=r; ball.vx=Math.abs(ball.vx); }
+ else if(ball.x+r>W){ ball.x=W-r; ball.vx=-Math.abs(ball.vx); }
+ if(ball.y-r<0){ ball.y=r; ball.vy=Math.abs(ball.vy); }
+ var px=paddle.x-paddle.w/2;
+ if(ball.vy>0 && ball.y+r>=paddle.y && ball.y-r<=paddle.y+paddle.h &&
+ ball.x>=px-r && ball.x<=px+paddle.w+r){
+ ball.y=paddle.y-r;
+ var rel=(ball.x-paddle.x)/(paddle.w/2);
+ if(rel>1)rel=1; if(rel<-1)rel=-1;
+ ball.vx=ball.sp*0.8*rel;
+ ball.vy=-Math.sqrt(Math.max(ball.sp*ball.sp-ball.vx*ball.vx,ball.sp*ball.sp*0.16));
+ }
+ for(var k=0;k<bricks.length;k++){
+ var b=bricks[k]; if(!b.alive) continue;
+ var nx=Math.max(b.x,Math.min(ball.x,b.x+b.w));
+ var ny=Math.max(b.y,Math.min(ball.y,b.y+b.h));
+ var ddx=ball.x-nx, ddy=ball.y-ny;
+ if(ddx*ddx+ddy*ddy<=r*r){
+ b.alive=false; score+=10*level; pop(b);
+ ball.sp*=1.014; if(ball.sp>H*0.032) ball.sp=H*0.032;
+ var rcx=(ball.x-(b.x+b.w/2))/(b.w/2);
+ var rcy=(ball.y-(b.y+b.h/2))/(b.h/2);
+ if(Math.abs(rcx)>=Math.abs(rcy)) ball.vx=(rcx>0?1:-1)*Math.abs(ball.vx);
+ else ball.vy=(rcy>0?1:-1)*Math.abs(ball.vy);
+ normSpeed(); syncHud();
+ break;
+ }
+ }
+ if(ball.y-r>H){
+ lives--; syncHud();
+ if(lives<=0) gameOver(); else dockBall();
+ return;
+ }
+ if(aliveCount()===0){
+ level++; buildBricks(); dockBall(); flash('LEVEL '+level);
+ }
+ }
+ function step(dt){
+ if(keyL) paddle.x-=W*0.026*dt;
+ if(keyR) paddle.x+=W*0.026*dt;
+ var hw=paddle.w/2;
+ if(paddle.x<hw) paddle.x=hw;
+ if(paddle.x>W-hw) paddle.x=W-hw;
+ if(ball.docked){
+ ball.x=paddle.x; ball.y=paddle.y-H*0.035;
+ if(performance.now()>=dockUntil) launch();
+ } else {
+ var steps=Math.ceil(dt), i;
+ for(i=0;i<steps && state==='playing';i++) substep(dt/steps);
+ }
+ for(var j=parts.length-1;j>=0;j--){
+ var p=parts[j];
+ p.x+=p.vx*dt; p.y+=p.vy*dt; p.vy+=0.07*dt; p.life-=dt;
+ if(p.life<=0) parts.splice(j,1);
+ }
+ }
+ function rr(x,y,w,h,rad){
+ var r=Math.min(rad,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 render(){
+ var g=ctx.createLinearGradient(0,0,0,H);
+ g.addColorStop(0,'#100c26'); g.addColorStop(1,'#06060f');
+ ctx.fillStyle=g; ctx.fillRect(0,0,W,H);
+ var i;
+ for(i=0;i<bricks.length;i++){
+ var b=bricks[i]; if(!b.alive) continue;
+ ctx.shadowColor=b.color; ctx.shadowBlur=10;
+ ctx.fillStyle=b.color; rr(b.x,b.y,b.w,b.h,b.h*0.32); ctx.fill();
+ ctx.shadowBlur=0;
+ ctx.fillStyle='rgba(255,255,255,0.28)';
+ rr(b.x+b.w*0.12,b.y+b.h*0.2,b.w*0.76,b.h*0.22,b.h*0.12); ctx.fill();
+ }
+ for(i=0;i<parts.length;i++){
+ var p=parts[i];
+ ctx.globalAlpha=Math.max(0,Math.min(1,p.life/16));
+ ctx.fillStyle=p.color;
+ ctx.fillRect(p.x-2,p.y-2,4,4);
+ }
+ ctx.globalAlpha=1;
+ if(paddle){
+ var pg=ctx.createLinearGradient(paddle.x-paddle.w/2,0,paddle.x+paddle.w/2,0);
+ pg.addColorStop(0,'#22d3ee'); pg.addColorStop(1,'#c084fc');
+ ctx.shadowColor='#22d3ee'; ctx.shadowBlur=14;
+ ctx.fillStyle=pg;
+ rr(paddle.x-paddle.w/2,paddle.y,paddle.w,paddle.h,paddle.h/2); ctx.fill();
+ ctx.shadowBlur=0;
+ }
+ if(ball){
+ ctx.shadowColor='#67e8f9'; ctx.shadowBlur=16;
+ ctx.fillStyle='#f0fdff';
+ ctx.beginPath(); ctx.arc(ball.x,ball.y,ball.r,0,6.2832); ctx.fill();
+ ctx.shadowBlur=0;
+ }
+ if(performance.now()<flashUntil){
+ ctx.globalAlpha=Math.max(0,Math.min(1,(flashUntil-performance.now())/700));
+ ctx.fillStyle='#e9d5ff';
+ ctx.font='900 '+Math.round(H*0.06)+'px system-ui';
+ ctx.textAlign='center'; ctx.textBaseline='middle';
+ ctx.shadowColor='#c084fc'; ctx.shadowBlur=18;
+ ctx.fillText(flashText,W/2,H*0.42);
+ ctx.shadowBlur=0; ctx.globalAlpha=1;
+ }
+ }
+ function frame(now){
+ if(!running) return;
+ var dt=(now-lastT)/16.667; lastT=now;
+ if(dt<0.2)dt=0.2; if(dt>2.6)dt=2.6;
+ if(state==='playing' && paddle) step(dt);
+ render();
+ raf=requestAnimationFrame(frame);
+ }
+ function startLoop(){
+ if(running) return;
+ running=true; lastT=performance.now(); raf=requestAnimationFrame(frame);
+ }
+ function stopLoop(){
+ running=false; if(raf) cancelAnimationFrame(raf); raf=0;
+ }
+ function saveBest(){
+ if(LL && LL.storage && LL.storage.set){ try{ LL.storage.set({best:best}); }catch(e){} }
+ }
+ function loadBest(){
+ if(LL && LL.storage && LL.storage.get){
+ LL.storage.get().then(function(s){
+ if(s && typeof s.best==='number' && s.best>best){ best=s.best; syncHud(); }
+ },function(){});
+ }
+ }
+ function moveTo(clientX){
+ if(!paddle) return;
+ var r=cv.getBoundingClientRect(), x=clientX-r.left, hw=paddle.w/2;
+ if(x<hw)x=hw; if(x>W-hw)x=W-hw;
+ paddle.x=x;
+ }
+ cv.addEventListener('pointerdown',function(e){ moveTo(e.clientX); });
+ cv.addEventListener('pointermove',function(e){ moveTo(e.clientX); });
+ window.addEventListener('keydown',function(e){
+ if(e.key==='ArrowLeft')keyL=true; else if(e.key==='ArrowRight')keyR=true;
+ });
+ window.addEventListener('keyup',function(e){
+ if(e.key==='ArrowLeft')keyL=false; else if(e.key==='ArrowRight')keyR=false;
+ });
+ ov.addEventListener('pointerdown',function(e){
+ e.preventDefault();
+ ov.style.display='none';
+ newGame();
+ });
+ function onResize(){
+ var oW=W,oH=H;
+ setCanvas();
+ if(oW>0 && oH>0 && (oW!==W||oH!==H) && paddle){
+ var sx=W/oW, sy=H/oH;
+ paddle.x*=sx; paddle.w*=sx; paddle.h*=sy; paddle.y*=sy;
+ ball.x*=sx; ball.y*=sy; ball.r*=sx; ball.vx*=sx; ball.vy*=sy; ball.sp*=sx;
+ for(var k=0;k<bricks.length;k++){
+ var b=bricks[k]; b.x*=sx; b.y*=sy; b.w*=sx; b.h*=sy;
+ }
+ for(k=0;k<parts.length;k++){ parts[k].x*=sx; parts[k].y*=sy; }
+ }
+ }
+ window.addEventListener('resize',onResize);
+ if(LL && LL.onResize) LL.onResize(onResize);
+ if(LL && LL.onVisibility) LL.onVisibility(function(v){ if(v) startLoop(); else stopLoop(); });
+ document.addEventListener('visibilitychange',function(){
+ if(document.hidden) stopLoop(); else startLoop();
+ });
+
+ setCanvas(); syncHud(); loadBest(); startLoop();
+})();
+</script>

v1Current

@liveloop · 5/18/2026, 10:15:39 AM

Initial version — all lines are new.

+<style>
+ html,body{height:100%;margin:0;background:#06060f;overflow:hidden;}
+ *{box-sizing:border-box;}
+ #wrap{position:relative;height:100%;width:100%;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;}
+ canvas{display:block;width:100%;height:100%;touch-action:none;}
+ #hud{position:absolute;top:0;left:0;right:0;display:flex;justify-content:space-between;align-items:flex-start;padding:.7rem .95rem;color:#dbeafe;font-size:.86rem;font-weight:800;letter-spacing:.04em;pointer-events:none;text-shadow:0 0 10px rgba(0,0,0,.95);}
+ #hud .lab{font-size:.56rem;opacity:.55;letter-spacing:.14em;margin-bottom:.05rem;}
+ #hud .r{text-align:right;}
+ #lives{color:#f472b6;font-size:1rem;letter-spacing:.1em;}
+ #ov{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.5rem;text-align:center;padding:1.6rem;background:rgba(6,6,15,.85);color:#e9d5ff;cursor:pointer;-webkit-tap-highlight-color:transparent;touch-action:none;user-select:none;-webkit-user-select:none;}
+ #ov h1{margin:0;font-size:2.15rem;font-weight:900;letter-spacing:.02em;background:linear-gradient(90deg,#22d3ee,#c084fc,#f472b6);-webkit-background-clip:text;background-clip:text;color:transparent;}
+ #ov .sub{margin:.1rem 0 0;font-size:.92rem;color:#a5b4fc;}
+ #ov .big{margin:.1rem 0 0;font-size:1.05rem;font-weight:800;color:#e9d5ff;}
+ #ov .pulse{margin-top:.5rem;font-size:.84rem;color:#67e8f9;animation:nbpulse 1.3s ease-in-out infinite;}
+ @keyframes nbpulse{0%,100%{opacity:.35}50%{opacity:1}}
+</style>
+<div id="wrap">
+ <canvas id="cv"></canvas>
+ <div id="hud">
+ <div><div class="lab">SCORE</div><div id="score">0</div></div>
+ <div class="r"><div class="lab">BEST</div><div id="best">0</div></div>
+ <div class="r"><div class="lab">BALLS</div><div id="lives">|||</div></div>
+ </div>
+ <div id="ov">
+ <h1 id="ovtitle">GLOW BREAKER</h1>
+ <p class="sub" id="ovsub">Break every block. Don't drop the ball.</p>
+ <p class="big" id="ovbig"></p>
+ <p class="pulse">Tap to play</p>
+ </div>
+</div>
+<script>
+(function(){
+ var LL=window.liveloop||null;
+ var cv=document.getElementById('cv'), ctx=cv.getContext('2d');
+ var ov=document.getElementById('ov'), ovsub=document.getElementById('ovsub'),
+ ovbig=document.getElementById('ovbig'), ovtitle=document.getElementById('ovtitle');
+ var scoreEl=document.getElementById('score'), bestEl=document.getElementById('best'),
+ livesEl=document.getElementById('lives');
+ var COLORS=['#f472b6','#fb923c','#facc15','#a3e635','#22d3ee'];
+ var W=0,H=0,dpr=1;
+ var state='start', score=0, best=0, lives=3, level=1;
+ var paddle=null, ball=null, bricks=[], parts=[];
+ var dockUntil=0, flashText='', flashUntil=0;
+ var lastT=0, raf=0, running=false, keyL=false, keyR=false;
+
+ function setCanvas(){
+ var r=cv.getBoundingClientRect();
+ W=Math.max(140,r.width); H=Math.max(180,r.height);
+ dpr=Math.min(window.devicePixelRatio||1,2.5);
+ cv.width=Math.round(W*dpr); cv.height=Math.round(H*dpr);
+ ctx.setTransform(dpr,0,0,dpr,0,0);
+ }
+ function aliveCount(){
+ var n=0; for(var i=0;i<bricks.length;i++) if(bricks[i].alive) n++;
+ return n;
+ }
+ function buildBricks(){
+ bricks=[];
+ var cols=6, rows=Math.min(3+level,7);
+ var mx=W*0.045, top=H*0.115, gap=W*0.014;
+ var bw=(W-2*mx-(cols-1)*gap)/cols, bh=H*0.03;
+ for(var r=0;r<rows;r++){
+ for(var c=0;c<cols;c++){
+ bricks.push({x:mx+c*(bw+gap),y:top+r*(bh+gap),w:bw,h:bh,
+ color:COLORS[r%COLORS.length],alive:true});
+ }
+ }
+ }
+ function dockBall(){
+ var sp=H*0.0118*(1+(level-1)*0.11);
+ ball={x:paddle.x,y:paddle.y-H*0.035,r:Math.max(5,W*0.019),vx:0,vy:0,sp:sp,docked:true};
+ dockUntil=performance.now()+700;
+ }
+ function launch(){
+ var a=(-Math.PI/2)+(Math.random()*0.5-0.25);
+ ball.vx=Math.cos(a)*ball.sp; ball.vy=Math.sin(a)*ball.sp; ball.docked=false;
+ }
+ function newGame(){
+ score=0; lives=3; level=1; parts=[];
+ paddle={w:W*0.23,h:Math.max(9,H*0.016),x:W/2,y:H*0.9};
+ buildBricks(); dockBall(); state='playing'; syncHud();
+ }
+ function flash(t){ flashText=t; flashUntil=performance.now()+1100; }
+ function syncHud(){
+ scoreEl.textContent=score; bestEl.textContent=best;
+ var s=''; for(var i=0;i<lives;i++) s+='|';
+ livesEl.textContent=s||'-';
+ }
+ function pop(b){
+ var cx=b.x+b.w/2, cy=b.y+b.h/2, i;
+ for(i=0;i<8;i++){
+ var a=Math.random()*6.283;
+ parts.push({x:cx,y:cy,vx:Math.cos(a)*(1+Math.random()*2.4),
+ vy:Math.sin(a)*(1+Math.random()*2.4),life:14+Math.random()*12,color:b.color});
+ }
+ }
+ function normSpeed(){
+ var m=Math.sqrt(ball.vx*ball.vx+ball.vy*ball.vy);
+ if(m>0){ ball.vx=ball.vx/m*ball.sp; ball.vy=ball.vy/m*ball.sp; }
+ var minv=ball.sp*0.34;
+ if(Math.abs(ball.vy)<minv){
+ ball.vy=(ball.vy<0?-1:1)*minv;
+ normSpeedXOnly();
+ }
+ }
+ function normSpeedXOnly(){
+ var rem=ball.sp*ball.sp-ball.vy*ball.vy;
+ if(rem<0) rem=0;
+ ball.vx=(ball.vx<0?-1:1)*Math.sqrt(rem);
+ }
+ function gameOver(){
+ state='over';
+ if(score>best){ best=score; saveBest(); }
+ syncHud();
+ ovtitle.textContent='GAME OVER';
+ ovsub.textContent='You reached level '+level;
+ ovbig.textContent='Score '+score+' · Best '+best;
+ ov.style.display='flex';
+ }
+ function substep(dt){
+ ball.x+=ball.vx*dt; ball.y+=ball.vy*dt;
+ var r=ball.r;
+ if(ball.x-r<0){ ball.x=r; ball.vx=Math.abs(ball.vx); }
+ else if(ball.x+r>W){ ball.x=W-r; ball.vx=-Math.abs(ball.vx); }
+ if(ball.y-r<0){ ball.y=r; ball.vy=Math.abs(ball.vy); }
+ var px=paddle.x-paddle.w/2;
+ if(ball.vy>0 && ball.y+r>=paddle.y && ball.y-r<=paddle.y+paddle.h &&
+ ball.x>=px-r && ball.x<=px+paddle.w+r){
+ ball.y=paddle.y-r;
+ var rel=(ball.x-paddle.x)/(paddle.w/2);
+ if(rel>1)rel=1; if(rel<-1)rel=-1;
+ ball.vx=ball.sp*0.8*rel;
+ ball.vy=-Math.sqrt(Math.max(ball.sp*ball.sp-ball.vx*ball.vx,ball.sp*ball.sp*0.16));
+ }
+ for(var k=0;k<bricks.length;k++){
+ var b=bricks[k]; if(!b.alive) continue;
+ var nx=Math.max(b.x,Math.min(ball.x,b.x+b.w));
+ var ny=Math.max(b.y,Math.min(ball.y,b.y+b.h));
+ var ddx=ball.x-nx, ddy=ball.y-ny;
+ if(ddx*ddx+ddy*ddy<=r*r){
+ b.alive=false; score+=10*level; pop(b);
+ ball.sp*=1.014; if(ball.sp>H*0.032) ball.sp=H*0.032;
+ var rcx=(ball.x-(b.x+b.w/2))/(b.w/2);
+ var rcy=(ball.y-(b.y+b.h/2))/(b.h/2);
+ if(Math.abs(rcx)>=Math.abs(rcy)) ball.vx=(rcx>0?1:-1)*Math.abs(ball.vx);
+ else ball.vy=(rcy>0?1:-1)*Math.abs(ball.vy);
+ normSpeed(); syncHud();
+ break;
+ }
+ }
+ if(ball.y-r>H){
+ lives--; syncHud();
+ if(lives<=0) gameOver(); else dockBall();
+ return;
+ }
+ if(aliveCount()===0){
+ level++; buildBricks(); dockBall(); flash('LEVEL '+level);
+ }
+ }
+ function step(dt){
+ if(keyL) paddle.x-=W*0.026*dt;
+ if(keyR) paddle.x+=W*0.026*dt;
+ var hw=paddle.w/2;
+ if(paddle.x<hw) paddle.x=hw;
+ if(paddle.x>W-hw) paddle.x=W-hw;
+ if(ball.docked){
+ ball.x=paddle.x; ball.y=paddle.y-H*0.035;
+ if(performance.now()>=dockUntil) launch();
+ } else {
+ var steps=Math.ceil(dt), i;
+ for(i=0;i<steps && state==='playing';i++) substep(dt/steps);
+ }
+ for(var j=parts.length-1;j>=0;j--){
+ var p=parts[j];
+ p.x+=p.vx*dt; p.y+=p.vy*dt; p.vy+=0.07*dt; p.life-=dt;
+ if(p.life<=0) parts.splice(j,1);
+ }
+ }
+ function rr(x,y,w,h,rad){
+ var r=Math.min(rad,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 render(){
+ var g=ctx.createLinearGradient(0,0,0,H);
+ g.addColorStop(0,'#100c26'); g.addColorStop(1,'#06060f');
+ ctx.fillStyle=g; ctx.fillRect(0,0,W,H);
+ var i;
+ for(i=0;i<bricks.length;i++){
+ var b=bricks[i]; if(!b.alive) continue;
+ ctx.shadowColor=b.color; ctx.shadowBlur=10;
+ ctx.fillStyle=b.color; rr(b.x,b.y,b.w,b.h,b.h*0.32); ctx.fill();
+ ctx.shadowBlur=0;
+ ctx.fillStyle='rgba(255,255,255,0.28)';
+ rr(b.x+b.w*0.12,b.y+b.h*0.2,b.w*0.76,b.h*0.22,b.h*0.12); ctx.fill();
+ }
+ for(i=0;i<parts.length;i++){
+ var p=parts[i];
+ ctx.globalAlpha=Math.max(0,Math.min(1,p.life/16));
+ ctx.fillStyle=p.color;
+ ctx.fillRect(p.x-2,p.y-2,4,4);
+ }
+ ctx.globalAlpha=1;
+ if(paddle){
+ var pg=ctx.createLinearGradient(paddle.x-paddle.w/2,0,paddle.x+paddle.w/2,0);
+ pg.addColorStop(0,'#22d3ee'); pg.addColorStop(1,'#c084fc');
+ ctx.shadowColor='#22d3ee'; ctx.shadowBlur=14;
+ ctx.fillStyle=pg;
+ rr(paddle.x-paddle.w/2,paddle.y,paddle.w,paddle.h,paddle.h/2); ctx.fill();
+ ctx.shadowBlur=0;
+ }
+ if(ball){
+ ctx.shadowColor='#67e8f9'; ctx.shadowBlur=16;
+ ctx.fillStyle='#f0fdff';
+ ctx.beginPath(); ctx.arc(ball.x,ball.y,ball.r,0,6.2832); ctx.fill();
+ ctx.shadowBlur=0;
+ }
+ if(performance.now()<flashUntil){
+ ctx.globalAlpha=Math.max(0,Math.min(1,(flashUntil-performance.now())/700));
+ ctx.fillStyle='#e9d5ff';
+ ctx.font='900 '+Math.round(H*0.06)+'px system-ui';
+ ctx.textAlign='center'; ctx.textBaseline='middle';
+ ctx.shadowColor='#c084fc'; ctx.shadowBlur=18;
+ ctx.fillText(flashText,W/2,H*0.42);
+ ctx.shadowBlur=0; ctx.globalAlpha=1;
+ }
+ }
+ function frame(now){
+ if(!running) return;
+ var dt=(now-lastT)/16.667; lastT=now;
+ if(dt<0.2)dt=0.2; if(dt>2.6)dt=2.6;
+ if(state==='playing' && paddle) step(dt);
+ render();
+ raf=requestAnimationFrame(frame);
+ }
+ function startLoop(){
+ if(running) return;
+ running=true; lastT=performance.now(); raf=requestAnimationFrame(frame);
+ }
+ function stopLoop(){
+ running=false; if(raf) cancelAnimationFrame(raf); raf=0;
+ }
+ function saveBest(){
+ if(LL && LL.storage && LL.storage.set){ try{ LL.storage.set({best:best}); }catch(e){} }
+ }
+ function loadBest(){
+ if(LL && LL.storage && LL.storage.get){
+ LL.storage.get().then(function(s){
+ if(s && typeof s.best==='number' && s.best>best){ best=s.best; syncHud(); }
+ },function(){});
+ }
+ }
+ function moveTo(clientX){
+ if(!paddle) return;
+ var r=cv.getBoundingClientRect(), x=clientX-r.left, hw=paddle.w/2;
+ if(x<hw)x=hw; if(x>W-hw)x=W-hw;
+ paddle.x=x;
+ }
+ cv.addEventListener('pointerdown',function(e){ moveTo(e.clientX); });
+ cv.addEventListener('pointermove',function(e){ moveTo(e.clientX); });
+ window.addEventListener('keydown',function(e){
+ if(e.key==='ArrowLeft')keyL=true; else if(e.key==='ArrowRight')keyR=true;
+ });
+ window.addEventListener('keyup',function(e){
+ if(e.key==='ArrowLeft')keyL=false; else if(e.key==='ArrowRight')keyR=false;
+ });
+ ov.addEventListener('pointerdown',function(e){
+ e.preventDefault();
+ ov.style.display='none';
+ newGame();
+ });
+ function onResize(){
+ var oW=W,oH=H;
+ setCanvas();
+ if(oW>0 && oH>0 && (oW!==W||oH!==H) && paddle){
+ var sx=W/oW, sy=H/oH;
+ paddle.x*=sx; paddle.w*=sx; paddle.h*=sy; paddle.y*=sy;
+ ball.x*=sx; ball.y*=sy; ball.r*=sx; ball.vx*=sx; ball.vy*=sy; ball.sp*=sx;
+ for(var k=0;k<bricks.length;k++){
+ var b=bricks[k]; b.x*=sx; b.y*=sy; b.w*=sx; b.h*=sy;
+ }
+ for(k=0;k<parts.length;k++){ parts[k].x*=sx; parts[k].y*=sy; }
+ }
+ }
+ window.addEventListener('resize',onResize);
+ if(LL && LL.onResize) LL.onResize(onResize);
+ if(LL && LL.onVisibility) LL.onVisibility(function(v){ if(v) startLoop(); else stopLoop(); });
+ document.addEventListener('visibilitychange',function(){
+ if(document.hidden) stopLoop(); else startLoop();
+ });
+
+ setCanvas(); syncHud(); loadBest(); startLoop();
+})();
+</script>
← Version history