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 · 6/9/2026, 7:20:23 PM

Initial version — all lines are new.

+<style>
+ html,body{height:100%;margin:0;overflow:hidden;background:#F4EEE3;}
+ *{box-sizing:border-box;}
+ #wrap{position:relative;height:100%;width:100%;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;-webkit-tap-highlight-color:transparent;touch-action:none;user-select:none;-webkit-user-select:none;cursor:pointer;}
+ canvas{display:block;width:100%;height:100%;position:absolute;inset:0;}
+ #hud{position:absolute;top:0;left:0;z-index:2;display:flex;justify-content:flex-start;gap:1.8rem;align-items:flex-start;padding:1.1rem 1.25rem;pointer-events:none;}
+ #hud .lab{font-size:.56rem;letter-spacing:.16em;text-transform:uppercase;color:#8F857B;font-weight:700;margin-bottom:.1rem;}
+ #score{font-family:"Instrument Serif",Georgia,serif;font-size:2.6rem;line-height:.9;color:#16110D;}
+ #best{font-size:.92rem;font-weight:800;color:#6B4EFE;text-align:left;}
+ #combo{position:absolute;top:3.6rem;left:1.3rem;z-index:2;font-size:.8rem;font-weight:800;color:#FF5A1F;opacity:0;transition:opacity .2s;pointer-events:none;}
+ #ov{position:absolute;inset:0;z-index:3;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.3rem;text-align:center;padding:1.6rem;background:rgba(248,243,234,.7);backdrop-filter:blur(3px);}
+ #ov h1{margin:0;font-family:"Instrument Serif",Georgia,serif;font-weight:400;font-size:3rem;line-height:1;color:#16110D;}
+ #ov .sub{margin:.15rem 0 0;font-size:.95rem;color:#5C544C;}
+ #ov .big{margin:.2rem 0 0;font-size:1.05rem;font-weight:800;color:#6B4EFE;}
+ #ov .pulse{margin-top:.7rem;font-size:.85rem;font-weight:700;color:#FF5A1F;animation:p 1.3s ease-in-out infinite;}
+ @keyframes p{0%,100%{opacity:.4}50%{opacity:1}}
+ .hide{display:none!important;}
+</style>
+<div id="wrap">
+ <canvas id="orbs"></canvas>
+ <canvas id="cv"></canvas>
+ <div id="hud">
+ <div><div class="lab">Score</div><div id="score">0</div></div>
+ <div><div class="lab">Best</div><div id="best">0</div></div>
+ </div>
+ <div id="combo"></div>
+ <div id="ov">
+ <h1 id="ovt">Zen Pop</h1>
+ <p class="sub" id="ovs">Tap the bubbles before they drift away.<br>Quick pops build a combo. Gold is worth more.</p>
+ <p class="big" id="ovb"></p>
+ <p class="pulse">Tap to play</p>
+ </div>
+</div>
+<script>
+(function(){
+ var LL=window.liveloop||null;
+ // --- sound: short tap SFX gated on the feed-wide mute; declareMedia so the
+ // platform draws the sound control (carries across loops); SDK auto-unlocks. ---
+ var muted = LL ? LL.muted : true;
+ if(LL && LL.onMute){ try{ LL.onMute(function(m){ muted=m; }); }catch(e){} }
+ if(LL && LL.declareMedia){ try{ LL.declareMedia({sound:true}); }catch(e){} }
+ var _ac=null;
+ function actx(){ if(!_ac){ try{ _ac=new (window.AudioContext||window.webkitAudioContext)(); }catch(e){ _ac=null; } } return _ac; }
+ function tone(freq,dur,type,vol,delay){
+ if(muted) return; var c=actx(); if(!c) return;
+ var t=c.currentTime+(delay||0), o=c.createOscillator(), g=c.createGain();
+ o.type=type||'sine'; o.frequency.setValueAtTime(freq,t);
+ g.gain.setValueAtTime(0.0001,t); g.gain.exponentialRampToValueAtTime(vol||0.16,t+0.012);
+ g.gain.exponentialRampToValueAtTime(0.0001,t+dur);
+ o.connect(g); g.connect(c.destination); o.start(t); o.stop(t+dur+0.03);
+ }
+ var SCALE=[523.25,587.33,659.25,783.99,880.0,1046.5];
+ function pluck(){ tone(SCALE[Math.min(combo,SCALE.length-1)],0.16,'triangle',0.16); }
+ function sparkle(){ var s=[659.25,830.61,987.77,1318.5]; for(var i=0;i<s.length;i++) tone(s[i],0.2,'sine',0.1,i*0.05); }
+ function lowtone(){ tone(174.6,0.32,'sine',0.12); }
+ var wrap=document.getElementById('wrap');
+ var orbC=document.getElementById('orbs'), oc=orbC.getContext('2d');
+ var cv=document.getElementById('cv'), ctx=cv.getContext('2d');
+ var scoreEl=document.getElementById('score'), bestEl=document.getElementById('best'), comboEl=document.getElementById('combo');
+ var ov=document.getElementById('ov'), ovt=document.getElementById('ovt'), ovs=document.getElementById('ovs'), ovb=document.getElementById('ovb');
+ var W=0,H=0,dpr=1,T=0,raf=0,paused=false;
+ var state='start', score=0, best=0, combo=0, comboT=0, spawnT=0, lives=3;
+ var bubbles=[], parts=[];
+ var COLS=[['#6B4EFE','#E8E2FF'],['#FF5A1F','#FFE3D2'],['#E14C8F','#FFD9EC'],['#3C9A6A','#D6F0E0'],['#7C5CFF','#EBE4FF']];
+
+ function size(){
+ var r=wrap.getBoundingClientRect();
+ W=Math.max(180,r.width); H=Math.max(260,r.height);
+ dpr=Math.min(window.devicePixelRatio||1,2.5);
+ [orbC,cv].forEach(function(c){ c.width=Math.round(W*dpr); c.height=Math.round(H*dpr); });
+ oc.setTransform(dpr,0,0,dpr,0,0); ctx.setTransform(dpr,0,0,dpr,0,0);
+ drawOrbs();
+ }
+ function blob(x,y,r,col){ var g=oc.createRadialGradient(x,y,0,x,y,r); g.addColorStop(0,col); g.addColorStop(1,'rgba(244,238,227,0)'); oc.fillStyle=g; oc.beginPath(); oc.arc(x,y,r,0,Math.PI*2); oc.fill(); }
+ function drawOrbs(){
+ oc.clearRect(0,0,W,H);
+ var bg=oc.createLinearGradient(0,0,0,H); bg.addColorStop(0,'#F8F3EA'); bg.addColorStop(1,'#F1E7D7');
+ oc.fillStyle=bg; oc.fillRect(0,0,W,H);
+ blob(W*0.84,H*0.16,W*0.5,'rgba(232,226,255,0.8)');
+ blob(W*0.12,H*0.78,W*0.5,'rgba(255,227,210,0.75)');
+ blob(W*0.7,H*0.92,W*0.5,'rgba(255,212,226,0.45)');
+ }
+ function rand(a,b){ return a+Math.random()*(b-a); }
+
+ function reset(){ score=0; combo=0; lives=3; bubbles=[]; parts=[]; spawnT=0; scoreEl.textContent='0'; comboEl.style.opacity='0'; }
+ function start(){ ov.classList.add('hide'); state='play'; reset(); T=performance.now(); }
+
+ function spawnBubble(){
+ var U=Math.min(W,H);
+ var gold=Math.random()<0.12;
+ var rr=rand(U*0.055,U*0.085)*(gold?1.05:1);
+ bubbles.push({
+ x:rand(rr+10,W-rr-10), y:H+rr, r:rr,
+ vy:-rand(U*0.0016,U*0.0032)*60/60, vx:rand(-0.3,0.3),
+ wob:rand(0,Math.PI*2), ws:rand(0.8,1.6),
+ gold:gold, col:gold?['#F6A93B','#FFEFC9']:COLS[Math.floor(rand(0,COLS.length))]
+ });
+ }
+ function burst(x,y,col){ for(var i=0;i<12;i++){ var a=Math.random()*Math.PI*2, sp=rand(1,4); parts.push({x:x,y:y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp,life:rand(20,40),r:rand(1.5,3.5),c:col}); } }
+
+ function pop(b,i){
+ bubbles.splice(i,1);
+ burst(b.x,b.y,b.col[0]);
+ combo++; comboT=70;
+ var pts=(b.gold?5:1)*(1+Math.floor(combo/4));
+ score+=pts; scoreEl.textContent=score;
+ if(combo>=3){ comboEl.textContent='combo ×'+(1+Math.floor(combo/4)); comboEl.style.opacity='1'; }
+ if(score>best){ best=score; bestEl.textContent=best; persist(); }
+ if(b.gold){ sparkle(); if(LL && LL.confetti){ try{ LL.confetti(); }catch(e){} } } else { pluck(); }
+ }
+ function persist(){ if(LL&&LL.storage){ try{ LL.storage.set({best:best}); }catch(e){} } }
+
+ function miss(){ // a bubble drifted off the top
+ lives--; combo=0; comboEl.style.opacity='0'; lowtone();
+ if(lives<=0) gameOver();
+ }
+ function gameOver(){
+ state='over';
+ ovt.textContent='Calm cleared'; ovs.innerHTML='That was a nice little flow.';
+ ovb.textContent='Score '+score+(best>0?' · best '+best:'');
+ ov.classList.remove('hide');
+ }
+
+ function drawBubble(b){
+ var g=ctx.createRadialGradient(b.x-b.r*0.3,b.y-b.r*0.3,b.r*0.1,b.x,b.y,b.r);
+ g.addColorStop(0,b.col[1]); g.addColorStop(0.7,b.col[0]); g.addColorStop(1,b.col[0]);
+ ctx.globalAlpha=0.92; ctx.fillStyle=g; ctx.beginPath(); ctx.arc(b.x,b.y,b.r,0,Math.PI*2); ctx.fill();
+ ctx.globalAlpha=0.5; ctx.fillStyle='rgba(255,255,255,.8)'; ctx.beginPath(); ctx.arc(b.x-b.r*0.32,b.y-b.r*0.34,b.r*0.22,0,Math.PI*2); ctx.fill();
+ ctx.globalAlpha=1;
+ if(b.gold){ ctx.strokeStyle='rgba(255,255,255,.7)'; ctx.lineWidth=2; ctx.beginPath(); ctx.arc(b.x,b.y,b.r*0.78,0,Math.PI*2); ctx.stroke(); }
+ }
+
+ function frame(now){
+ raf=requestAnimationFrame(frame);
+ if(paused) return;
+ var dt=Math.min(2.4,(now-T)/16.67); T=now;
+ ctx.clearRect(0,0,W,H);
+ if(state==='play'){
+ spawnT-=dt; if(spawnT<=0){ spawnBubble(); spawnT=Math.max(22,52-score*0.25); }
+ if(comboT>0){ comboT-=dt; if(comboT<=0){ combo=0; comboEl.style.opacity='0'; } }
+ }
+ for(var i=bubbles.length-1;i>=0;i--){
+ var b=bubbles[i];
+ b.wob+=0.04*dt;
+ b.x+=Math.sin(b.wob)*b.ws*0.4*dt;
+ b.y+=b.vy*dt; // rise (vy is negative)
+ if(b.y< -b.r){ bubbles.splice(i,1); if(state==='play') miss(); continue; }
+ drawBubble(b);
+ }
+ for(var p=parts.length-1;p>=0;p--){ var q=parts[p]; q.x+=q.vx*dt; q.y+=q.vy*dt; q.vy+=0.12*dt; q.life-=dt; if(q.life<=0){ parts.splice(p,1); continue; } ctx.globalAlpha=Math.max(0,q.life/30); ctx.fillStyle=q.c; ctx.beginPath(); ctx.arc(q.x,q.y,q.r,0,Math.PI*2); ctx.fill(); }
+ ctx.globalAlpha=1;
+ if(state==='play'){ // lives as small dots bottom-right
+ for(var L=0;L<lives;L++){ ctx.fillStyle='rgba(225,76,143,.8)'; ctx.beginPath(); ctx.arc(W-16-L*16,H-16,5,0,Math.PI*2); ctx.fill(); }
+ }
+ }
+
+ function at(e){ var r=cv.getBoundingClientRect(), t=e.touches?e.touches[0]:e; return {x:t.clientX-r.left,y:t.clientY-r.top}; }
+ wrap.addEventListener('pointerdown',function(e){
+ e.preventDefault();
+ if(state==='start'||state==='over'){ start(); return; }
+ var p=at(e), hit=false;
+ for(var i=bubbles.length-1;i>=0;i--){ var b=bubbles[i]; var dx=p.x-b.x,dy=p.y-b.y; if(dx*dx+dy*dy<=(b.r+8)*(b.r+8)){ pop(b,i); hit=true; break; } }
+ if(!hit){ combo=0; comboEl.style.opacity='0'; }
+ });
+
+ if(LL&&LL.onPause){ try{ LL.onPause(function(p){ paused=p; if(!p) T=performance.now(); }); }catch(e){} }
+ document.addEventListener('visibilitychange',function(){ paused=document.hidden; if(!paused) T=performance.now(); });
+ window.addEventListener('resize',size);
+
+ size();
+ if(LL&&LL.storage){ try{ LL.storage.get().then(function(s){ if(s&&typeof s.best==='number'){ best=s.best; bestEl.textContent=best; } }).catch(function(){}); }catch(e){} }
+ state='start'; T=performance.now(); raf=requestAnimationFrame(frame);
+})();
+</script>

v1Current

@liveloop · 6/9/2026, 7:20:23 PM

Initial version — all lines are new.

+<style>
+ html,body{height:100%;margin:0;overflow:hidden;background:#F4EEE3;}
+ *{box-sizing:border-box;}
+ #wrap{position:relative;height:100%;width:100%;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;-webkit-tap-highlight-color:transparent;touch-action:none;user-select:none;-webkit-user-select:none;cursor:pointer;}
+ canvas{display:block;width:100%;height:100%;position:absolute;inset:0;}
+ #hud{position:absolute;top:0;left:0;z-index:2;display:flex;justify-content:flex-start;gap:1.8rem;align-items:flex-start;padding:1.1rem 1.25rem;pointer-events:none;}
+ #hud .lab{font-size:.56rem;letter-spacing:.16em;text-transform:uppercase;color:#8F857B;font-weight:700;margin-bottom:.1rem;}
+ #score{font-family:"Instrument Serif",Georgia,serif;font-size:2.6rem;line-height:.9;color:#16110D;}
+ #best{font-size:.92rem;font-weight:800;color:#6B4EFE;text-align:left;}
+ #combo{position:absolute;top:3.6rem;left:1.3rem;z-index:2;font-size:.8rem;font-weight:800;color:#FF5A1F;opacity:0;transition:opacity .2s;pointer-events:none;}
+ #ov{position:absolute;inset:0;z-index:3;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.3rem;text-align:center;padding:1.6rem;background:rgba(248,243,234,.7);backdrop-filter:blur(3px);}
+ #ov h1{margin:0;font-family:"Instrument Serif",Georgia,serif;font-weight:400;font-size:3rem;line-height:1;color:#16110D;}
+ #ov .sub{margin:.15rem 0 0;font-size:.95rem;color:#5C544C;}
+ #ov .big{margin:.2rem 0 0;font-size:1.05rem;font-weight:800;color:#6B4EFE;}
+ #ov .pulse{margin-top:.7rem;font-size:.85rem;font-weight:700;color:#FF5A1F;animation:p 1.3s ease-in-out infinite;}
+ @keyframes p{0%,100%{opacity:.4}50%{opacity:1}}
+ .hide{display:none!important;}
+</style>
+<div id="wrap">
+ <canvas id="orbs"></canvas>
+ <canvas id="cv"></canvas>
+ <div id="hud">
+ <div><div class="lab">Score</div><div id="score">0</div></div>
+ <div><div class="lab">Best</div><div id="best">0</div></div>
+ </div>
+ <div id="combo"></div>
+ <div id="ov">
+ <h1 id="ovt">Zen Pop</h1>
+ <p class="sub" id="ovs">Tap the bubbles before they drift away.<br>Quick pops build a combo. Gold is worth more.</p>
+ <p class="big" id="ovb"></p>
+ <p class="pulse">Tap to play</p>
+ </div>
+</div>
+<script>
+(function(){
+ var LL=window.liveloop||null;
+ // --- sound: short tap SFX gated on the feed-wide mute; declareMedia so the
+ // platform draws the sound control (carries across loops); SDK auto-unlocks. ---
+ var muted = LL ? LL.muted : true;
+ if(LL && LL.onMute){ try{ LL.onMute(function(m){ muted=m; }); }catch(e){} }
+ if(LL && LL.declareMedia){ try{ LL.declareMedia({sound:true}); }catch(e){} }
+ var _ac=null;
+ function actx(){ if(!_ac){ try{ _ac=new (window.AudioContext||window.webkitAudioContext)(); }catch(e){ _ac=null; } } return _ac; }
+ function tone(freq,dur,type,vol,delay){
+ if(muted) return; var c=actx(); if(!c) return;
+ var t=c.currentTime+(delay||0), o=c.createOscillator(), g=c.createGain();
+ o.type=type||'sine'; o.frequency.setValueAtTime(freq,t);
+ g.gain.setValueAtTime(0.0001,t); g.gain.exponentialRampToValueAtTime(vol||0.16,t+0.012);
+ g.gain.exponentialRampToValueAtTime(0.0001,t+dur);
+ o.connect(g); g.connect(c.destination); o.start(t); o.stop(t+dur+0.03);
+ }
+ var SCALE=[523.25,587.33,659.25,783.99,880.0,1046.5];
+ function pluck(){ tone(SCALE[Math.min(combo,SCALE.length-1)],0.16,'triangle',0.16); }
+ function sparkle(){ var s=[659.25,830.61,987.77,1318.5]; for(var i=0;i<s.length;i++) tone(s[i],0.2,'sine',0.1,i*0.05); }
+ function lowtone(){ tone(174.6,0.32,'sine',0.12); }
+ var wrap=document.getElementById('wrap');
+ var orbC=document.getElementById('orbs'), oc=orbC.getContext('2d');
+ var cv=document.getElementById('cv'), ctx=cv.getContext('2d');
+ var scoreEl=document.getElementById('score'), bestEl=document.getElementById('best'), comboEl=document.getElementById('combo');
+ var ov=document.getElementById('ov'), ovt=document.getElementById('ovt'), ovs=document.getElementById('ovs'), ovb=document.getElementById('ovb');
+ var W=0,H=0,dpr=1,T=0,raf=0,paused=false;
+ var state='start', score=0, best=0, combo=0, comboT=0, spawnT=0, lives=3;
+ var bubbles=[], parts=[];
+ var COLS=[['#6B4EFE','#E8E2FF'],['#FF5A1F','#FFE3D2'],['#E14C8F','#FFD9EC'],['#3C9A6A','#D6F0E0'],['#7C5CFF','#EBE4FF']];
+
+ function size(){
+ var r=wrap.getBoundingClientRect();
+ W=Math.max(180,r.width); H=Math.max(260,r.height);
+ dpr=Math.min(window.devicePixelRatio||1,2.5);
+ [orbC,cv].forEach(function(c){ c.width=Math.round(W*dpr); c.height=Math.round(H*dpr); });
+ oc.setTransform(dpr,0,0,dpr,0,0); ctx.setTransform(dpr,0,0,dpr,0,0);
+ drawOrbs();
+ }
+ function blob(x,y,r,col){ var g=oc.createRadialGradient(x,y,0,x,y,r); g.addColorStop(0,col); g.addColorStop(1,'rgba(244,238,227,0)'); oc.fillStyle=g; oc.beginPath(); oc.arc(x,y,r,0,Math.PI*2); oc.fill(); }
+ function drawOrbs(){
+ oc.clearRect(0,0,W,H);
+ var bg=oc.createLinearGradient(0,0,0,H); bg.addColorStop(0,'#F8F3EA'); bg.addColorStop(1,'#F1E7D7');
+ oc.fillStyle=bg; oc.fillRect(0,0,W,H);
+ blob(W*0.84,H*0.16,W*0.5,'rgba(232,226,255,0.8)');
+ blob(W*0.12,H*0.78,W*0.5,'rgba(255,227,210,0.75)');
+ blob(W*0.7,H*0.92,W*0.5,'rgba(255,212,226,0.45)');
+ }
+ function rand(a,b){ return a+Math.random()*(b-a); }
+
+ function reset(){ score=0; combo=0; lives=3; bubbles=[]; parts=[]; spawnT=0; scoreEl.textContent='0'; comboEl.style.opacity='0'; }
+ function start(){ ov.classList.add('hide'); state='play'; reset(); T=performance.now(); }
+
+ function spawnBubble(){
+ var U=Math.min(W,H);
+ var gold=Math.random()<0.12;
+ var rr=rand(U*0.055,U*0.085)*(gold?1.05:1);
+ bubbles.push({
+ x:rand(rr+10,W-rr-10), y:H+rr, r:rr,
+ vy:-rand(U*0.0016,U*0.0032)*60/60, vx:rand(-0.3,0.3),
+ wob:rand(0,Math.PI*2), ws:rand(0.8,1.6),
+ gold:gold, col:gold?['#F6A93B','#FFEFC9']:COLS[Math.floor(rand(0,COLS.length))]
+ });
+ }
+ function burst(x,y,col){ for(var i=0;i<12;i++){ var a=Math.random()*Math.PI*2, sp=rand(1,4); parts.push({x:x,y:y,vx:Math.cos(a)*sp,vy:Math.sin(a)*sp,life:rand(20,40),r:rand(1.5,3.5),c:col}); } }
+
+ function pop(b,i){
+ bubbles.splice(i,1);
+ burst(b.x,b.y,b.col[0]);
+ combo++; comboT=70;
+ var pts=(b.gold?5:1)*(1+Math.floor(combo/4));
+ score+=pts; scoreEl.textContent=score;
+ if(combo>=3){ comboEl.textContent='combo ×'+(1+Math.floor(combo/4)); comboEl.style.opacity='1'; }
+ if(score>best){ best=score; bestEl.textContent=best; persist(); }
+ if(b.gold){ sparkle(); if(LL && LL.confetti){ try{ LL.confetti(); }catch(e){} } } else { pluck(); }
+ }
+ function persist(){ if(LL&&LL.storage){ try{ LL.storage.set({best:best}); }catch(e){} } }
+
+ function miss(){ // a bubble drifted off the top
+ lives--; combo=0; comboEl.style.opacity='0'; lowtone();
+ if(lives<=0) gameOver();
+ }
+ function gameOver(){
+ state='over';
+ ovt.textContent='Calm cleared'; ovs.innerHTML='That was a nice little flow.';
+ ovb.textContent='Score '+score+(best>0?' · best '+best:'');
+ ov.classList.remove('hide');
+ }
+
+ function drawBubble(b){
+ var g=ctx.createRadialGradient(b.x-b.r*0.3,b.y-b.r*0.3,b.r*0.1,b.x,b.y,b.r);
+ g.addColorStop(0,b.col[1]); g.addColorStop(0.7,b.col[0]); g.addColorStop(1,b.col[0]);
+ ctx.globalAlpha=0.92; ctx.fillStyle=g; ctx.beginPath(); ctx.arc(b.x,b.y,b.r,0,Math.PI*2); ctx.fill();
+ ctx.globalAlpha=0.5; ctx.fillStyle='rgba(255,255,255,.8)'; ctx.beginPath(); ctx.arc(b.x-b.r*0.32,b.y-b.r*0.34,b.r*0.22,0,Math.PI*2); ctx.fill();
+ ctx.globalAlpha=1;
+ if(b.gold){ ctx.strokeStyle='rgba(255,255,255,.7)'; ctx.lineWidth=2; ctx.beginPath(); ctx.arc(b.x,b.y,b.r*0.78,0,Math.PI*2); ctx.stroke(); }
+ }
+
+ function frame(now){
+ raf=requestAnimationFrame(frame);
+ if(paused) return;
+ var dt=Math.min(2.4,(now-T)/16.67); T=now;
+ ctx.clearRect(0,0,W,H);
+ if(state==='play'){
+ spawnT-=dt; if(spawnT<=0){ spawnBubble(); spawnT=Math.max(22,52-score*0.25); }
+ if(comboT>0){ comboT-=dt; if(comboT<=0){ combo=0; comboEl.style.opacity='0'; } }
+ }
+ for(var i=bubbles.length-1;i>=0;i--){
+ var b=bubbles[i];
+ b.wob+=0.04*dt;
+ b.x+=Math.sin(b.wob)*b.ws*0.4*dt;
+ b.y+=b.vy*dt; // rise (vy is negative)
+ if(b.y< -b.r){ bubbles.splice(i,1); if(state==='play') miss(); continue; }
+ drawBubble(b);
+ }
+ for(var p=parts.length-1;p>=0;p--){ var q=parts[p]; q.x+=q.vx*dt; q.y+=q.vy*dt; q.vy+=0.12*dt; q.life-=dt; if(q.life<=0){ parts.splice(p,1); continue; } ctx.globalAlpha=Math.max(0,q.life/30); ctx.fillStyle=q.c; ctx.beginPath(); ctx.arc(q.x,q.y,q.r,0,Math.PI*2); ctx.fill(); }
+ ctx.globalAlpha=1;
+ if(state==='play'){ // lives as small dots bottom-right
+ for(var L=0;L<lives;L++){ ctx.fillStyle='rgba(225,76,143,.8)'; ctx.beginPath(); ctx.arc(W-16-L*16,H-16,5,0,Math.PI*2); ctx.fill(); }
+ }
+ }
+
+ function at(e){ var r=cv.getBoundingClientRect(), t=e.touches?e.touches[0]:e; return {x:t.clientX-r.left,y:t.clientY-r.top}; }
+ wrap.addEventListener('pointerdown',function(e){
+ e.preventDefault();
+ if(state==='start'||state==='over'){ start(); return; }
+ var p=at(e), hit=false;
+ for(var i=bubbles.length-1;i>=0;i--){ var b=bubbles[i]; var dx=p.x-b.x,dy=p.y-b.y; if(dx*dx+dy*dy<=(b.r+8)*(b.r+8)){ pop(b,i); hit=true; break; } }
+ if(!hit){ combo=0; comboEl.style.opacity='0'; }
+ });
+
+ if(LL&&LL.onPause){ try{ LL.onPause(function(p){ paused=p; if(!p) T=performance.now(); }); }catch(e){} }
+ document.addEventListener('visibilitychange',function(){ paused=document.hidden; if(!paused) T=performance.now(); });
+ window.addEventListener('resize',size);
+
+ size();
+ if(LL&&LL.storage){ try{ LL.storage.get().then(function(s){ if(s&&typeof s.best==='number'){ best=s.best; bestEl.textContent=best; } }).catch(function(){}); }catch(e){} }
+ state='start'; T=performance.now(); raf=requestAnimationFrame(frame);
+})();
+</script>
← Version history