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, 12:14:22 PM

Initial version — all lines are new.

+<style>
+ html,body{height:100%;margin:0;background:#03040c;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:#cffafe;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:.6;letter-spacing:.14em;margin-bottom:.05rem;}
+ #hud .r{text-align:right;}
+ #time.low{color:#fb7185;}
+ #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(3,4,12,.86);color:#e0f2fe;cursor:pointer;-webkit-tap-highlight-color:transparent;touch-action:none;user-select:none;-webkit-user-select:none;}
+ #ov h1{margin:0;font-size:2.05rem;font-weight:900;letter-spacing:.02em;background:linear-gradient(90deg,#22d3ee,#38bdf8,#a3e635);-webkit-background-clip:text;background-clip:text;color:transparent;}
+ #ov .sub{margin:.1rem 0 0;font-size:.9rem;color:#7dd3fc;max-width:26ch;}
+ #ov .big{margin:.1rem 0 0;font-size:1.05rem;font-weight:800;color:#e0f2fe;}
+ #ov .pulse{margin-top:.5rem;font-size:.84rem;color:#67e8f9;animation:dcpulse 1.3s ease-in-out infinite;}
+ @keyframes dcpulse{0%,100%{opacity:.35}50%{opacity:1}}
+</style>
+<div id="wrap">
+ <canvas id="cv"></canvas>
+ <div id="hud">
+ <div><div class="lab">TIME</div><div id="time">60</div></div>
+ <div class="r"><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>
+ <div id="ov">
+ <h1>DEEP CATCH</h1>
+ <p class="sub" id="ovsub">Drag the lure to hook a fish, then drag it up to the boat. Dodge the jellyfish.</p>
+ <p class="big" id="ovbig"></p>
+ <p class="pulse">Tap to dive</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=ov.querySelector('h1');
+ var scoreEl=document.getElementById('score'), bestEl=document.getElementById('best'),
+ timeEl=document.getElementById('time');
+ var W=0,H=0,dpr=1;
+ var state='start', score=0, best=0, timeLeft=60, jellyTimer=0;
+ var hook=null, fish=[], jellies=[], parts=[], floaters=[];
+ var dragging=false, ptrX=0, ptrY=0;
+ var lastT=0, raf=0, running=false;
+
+ var TYPES=[
+ {key:'common',color:'#67e8f9',points:10,r:0.026,spd:0.010,wt:50,band:[0.22,0.46]},
+ {key:'silver',color:'#a5b4fc',points:25,r:0.034,spd:0.0072,wt:30,band:[0.44,0.66]},
+ {key:'deep', color:'#34d399',points:50,r:0.044,spd:0.0052,wt:15,band:[0.66,0.9]},
+ {key:'gold', color:'#fbbf24',points:100,r:0.03,spd:0.014,wt:5, band:[0.3,0.84]}
+ ];
+
+ function setCanvas(){
+ var r=cv.getBoundingClientRect();
+ W=Math.max(160,r.width); H=Math.max(220,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 anchorX(){ return W/2; }
+ function anchorY(){ return H*0.07; }
+ function bankY(){ return H*0.15; }
+ function rint(a,b){ return a+Math.floor(Math.random()*(b-a+1)); }
+ function pickType(){
+ var total=0,i; for(i=0;i<TYPES.length;i++) total+=TYPES[i].wt;
+ var n=Math.random()*total;
+ for(i=0;i<TYPES.length;i++){ n-=TYPES[i].wt; if(n<=0) return TYPES[i]; }
+ return TYPES[0];
+ }
+ function spawnFish(){
+ var t=pickType();
+ var dir=Math.random()<0.5?-1:1;
+ var y=H*(t.band[0]+Math.random()*(t.band[1]-t.band[0]));
+ fish.push({
+ t:t, x:dir<0?W+W*0.1:-W*0.1, y:y, vx:dir*t.spd*H,
+ w:W*t.r, h:W*t.r*0.62, color:t.color, points:t.points, caught:false,
+ phase:Math.random()*6.28
+ });
+ }
+ function spawnJelly(){
+ jellies.push({
+ x:Math.random()*W, y:H*(0.32+Math.random()*0.5),
+ vx:(Math.random()<0.5?-1:1)*H*0.0016, baseY:0, phase:Math.random()*6.28,
+ r:W*0.05
+ });
+ }
+ function newGame(){
+ score=0; timeLeft=60; jellyTimer=0;
+ fish=[]; jellies=[]; parts=[]; floaters=[];
+ hook={x:W/2,y:H*0.3,r:W*0.026,carry:null,stunUntil:0};
+ var i;
+ for(i=0;i<8;i++) spawnFish();
+ for(i=0;i<fish.length;i++) fish[i].x=Math.random()*W;
+ for(i=0;i<4;i++) spawnJelly();
+ state='playing'; syncHud();
+ }
+ function syncHud(){
+ scoreEl.textContent=score;
+ bestEl.textContent=best;
+ var s=Math.max(0,Math.ceil(timeLeft));
+ timeEl.textContent=s;
+ if(s<=10) timeEl.classList.add('low'); else timeEl.classList.remove('low');
+ }
+ function floater(x,y,text,color){
+ floaters.push({x:x,y:y,text:text,color:color,life:46});
+ }
+ function burst(x,y,color,n){
+ for(var i=0;i<n;i++){
+ var a=Math.random()*6.2832;
+ parts.push({x:x,y:y,vx:Math.cos(a)*(1+Math.random()*3),
+ vy:Math.sin(a)*(1+Math.random()*3),life:16+Math.random()*14,color:color});
+ }
+ }
+ function gameOver(){
+ state='over';
+ if(score>best){ best=score; saveBest(); }
+ syncHud();
+ ovtitle.textContent='TIME UP';
+ ovsub.textContent='Lures in for the day.';
+ ovbig.textContent='Score '+score+' · Best '+best;
+ ov.style.display='flex';
+ }
+ function step(dt){
+ timeLeft-=dt/60;
+ if(timeLeft<=0){ timeLeft=0; gameOver(); return; }
+ jellyTimer+=dt/60;
+ if(jellyTimer>=20 && jellies.length<6){ spawnJelly(); jellyTimer=0; }
+
+ // hook: follow the finger, or drift gently up when released
+ if(dragging){
+ hook.x+=(ptrX-hook.x)*Math.min(1,0.34*dt);
+ hook.y+=(ptrY-hook.y)*Math.min(1,0.34*dt);
+ } else {
+ hook.y-=H*0.0011*dt;
+ }
+ if(hook.x<W*0.03)hook.x=W*0.03; if(hook.x>W*0.97)hook.x=W*0.97;
+ if(hook.y<anchorY())hook.y=anchorY(); if(hook.y>H*0.97)hook.y=H*0.97;
+
+ var i,j;
+ // fish
+ for(i=0;i<fish.length;i++){
+ var f=fish[i];
+ if(f.caught) continue;
+ f.x+=f.vx*dt;
+ f.phase+=0.08*dt;
+ if(f.vx>0 && f.x>W+W*0.12) f.x=-W*0.12;
+ if(f.vx<0 && f.x<-W*0.12) f.x=W+W*0.12;
+ }
+ // jellies
+ for(i=0;i<jellies.length;i++){
+ var jl=jellies[i];
+ jl.x+=jl.vx*dt; jl.phase+=0.05*dt;
+ if(jl.x<-W*0.1) jl.x=W+W*0.1;
+ if(jl.x>W+W*0.1) jl.x=-W*0.1;
+ }
+ var stunned=performance.now()<hook.stunUntil;
+ // catch
+ if(!hook.carry && !stunned){
+ for(i=0;i<fish.length;i++){
+ var ff=fish[i]; if(ff.caught) continue;
+ var dx=ff.x-hook.x, dy=ff.y-hook.y;
+ if(dx*dx+dy*dy < (hook.r+ff.w*0.75)*(hook.r+ff.w*0.75)){
+ ff.caught=true; hook.carry=ff;
+ burst(hook.x,hook.y,ff.color,8);
+ break;
+ }
+ }
+ }
+ // jelly collision
+ for(i=0;i<jellies.length;i++){
+ var jj=jellies[i];
+ var jdx=jj.x-hook.x, jdy=jj.y-hook.y;
+ if(jdx*jdx+jdy*jdy < (hook.r+jj.r*0.8)*(hook.r+jj.r*0.8)){
+ if(hook.carry){
+ var lost=hook.carry; hook.carry=null;
+ lost.caught=false; lost.x=jj.x; lost.y=jj.y+jj.r;
+ floater(hook.x,hook.y,'Lost!','#fb7185');
+ } else if(!stunned){
+ hook.stunUntil=performance.now()+650;
+ burst(hook.x,hook.y,'#fb7185',6);
+ }
+ break;
+ }
+ }
+ // carried fish rides the hook; bank it at the surface
+ if(hook.carry){
+ hook.carry.x=hook.x; hook.carry.y=hook.y+hook.r+hook.carry.h;
+ if(hook.y<=bankY()){
+ var c=hook.carry;
+ score+=c.points;
+ floater(anchorX(),bankY(),'+'+c.points,c.color);
+ burst(anchorX(),bankY(),c.color,14);
+ var idx=fish.indexOf(c); if(idx>=0) fish.splice(idx,1);
+ hook.carry=null;
+ spawnFish();
+ syncHud();
+ }
+ }
+ // particles + floaters
+ for(i=parts.length-1;i>=0;i--){
+ var p=parts[i];
+ p.x+=p.vx*dt; p.y+=p.vy*dt; p.vy+=0.05*dt; p.life-=dt;
+ if(p.life<=0) parts.splice(i,1);
+ }
+ for(i=floaters.length-1;i>=0;i--){
+ var fl=floaters[i];
+ fl.y-=0.6*dt; fl.life-=dt;
+ if(fl.life<=0) floaters.splice(i,1);
+ }
+ }
+ function drawFish(f){
+ var dir=f.vx<0?-1:1, wob=Math.sin(f.phase)*f.h*0.3;
+ ctx.save();
+ ctx.translate(f.x,f.y);
+ ctx.scale(dir,1);
+ ctx.shadowColor=f.color; ctx.shadowBlur=12;
+ ctx.fillStyle=f.color;
+ ctx.beginPath(); ctx.ellipse(0,0,f.w,f.h,0,0,6.2832); ctx.fill();
+ ctx.beginPath();
+ ctx.moveTo(-f.w*0.75,0);
+ ctx.lineTo(-f.w*1.5,-f.h*0.95+wob);
+ ctx.lineTo(-f.w*1.5,f.h*0.95+wob);
+ ctx.closePath(); ctx.fill();
+ ctx.shadowBlur=0;
+ ctx.fillStyle='#03040c';
+ ctx.beginPath(); ctx.arc(f.w*0.5,-f.h*0.22,Math.max(1.2,f.h*0.2),0,6.2832); ctx.fill();
+ ctx.restore();
+ }
+ function drawJelly(jl){
+ ctx.save();
+ ctx.translate(jl.x,jl.y);
+ ctx.shadowColor='#fb7185'; ctx.shadowBlur=14;
+ ctx.fillStyle='rgba(251,113,133,0.82)';
+ ctx.beginPath(); ctx.arc(0,0,jl.r,Math.PI,Math.PI*2); ctx.closePath(); ctx.fill();
+ ctx.strokeStyle='rgba(251,113,133,0.6)'; ctx.lineWidth=2;
+ for(var k=0;k<4;k++){
+ var tx=-jl.r*0.62+k*(jl.r*0.41);
+ ctx.beginPath();
+ ctx.moveTo(tx,0);
+ ctx.quadraticCurveTo(tx+Math.sin(jl.phase+k)*5,jl.r*0.95,tx,jl.r*1.7);
+ ctx.stroke();
+ }
+ ctx.shadowBlur=0;
+ ctx.restore();
+ }
+ function render(){
+ var bg=ctx.createLinearGradient(0,0,0,H);
+ bg.addColorStop(0,'#0c3a52'); bg.addColorStop(0.45,'#082235'); bg.addColorStop(1,'#03040c');
+ ctx.fillStyle=bg; ctx.fillRect(0,0,W,H);
+ // light shafts
+ ctx.fillStyle='rgba(186,230,253,0.045)';
+ var s;
+ for(s=0;s<3;s++){
+ var lx=W*(0.2+s*0.32);
+ ctx.beginPath();
+ ctx.moveTo(lx-W*0.04,0); ctx.lineTo(lx+W*0.04,0);
+ ctx.lineTo(lx+W*0.16,H); ctx.lineTo(lx-W*0.16,H);
+ ctx.closePath(); ctx.fill();
+ }
+ // bank line
+ ctx.strokeStyle='rgba(103,232,249,0.25)'; ctx.lineWidth=1.5;
+ ctx.beginPath(); ctx.moveTo(0,bankY()); ctx.lineTo(W,bankY()); ctx.stroke();
+ var i;
+ for(i=0;i<fish.length;i++) if(!fish[i].caught) drawFish(fish[i]);
+ for(i=0;i<jellies.length;i++) drawJelly(jellies[i]);
+ if(hook){
+ // line
+ ctx.strokeStyle='rgba(226,232,240,0.5)'; ctx.lineWidth=1.5;
+ ctx.beginPath(); ctx.moveTo(anchorX(),anchorY()); ctx.lineTo(hook.x,hook.y); ctx.stroke();
+ // boat hull
+ ctx.fillStyle='#e2e8f0';
+ var bw=W*0.1, by=anchorY();
+ ctx.beginPath();
+ ctx.moveTo(anchorX()-bw,by-H*0.022);
+ ctx.lineTo(anchorX()+bw,by-H*0.022);
+ ctx.lineTo(anchorX()+bw*0.6,by);
+ ctx.lineTo(anchorX()-bw*0.6,by);
+ ctx.closePath(); ctx.fill();
+ // carried fish
+ if(hook.carry) drawFish(hook.carry);
+ // lure
+ var stunned=performance.now()<hook.stunUntil;
+ ctx.shadowColor=stunned?'#fb7185':'#fde68a'; ctx.shadowBlur=16;
+ ctx.fillStyle=stunned?'#fb7185':'#fde68a';
+ ctx.beginPath(); ctx.arc(hook.x,hook.y,hook.r,0,6.2832); ctx.fill();
+ ctx.shadowBlur=0;
+ ctx.strokeStyle='#cbd5e1'; ctx.lineWidth=2;
+ ctx.beginPath(); ctx.arc(hook.x,hook.y+hook.r*1.4,hook.r*0.62,0.1*Math.PI,0.9*Math.PI); ctx.stroke();
+ }
+ for(i=0;i<parts.length;i++){
+ var p=parts[i];
+ ctx.globalAlpha=Math.max(0,Math.min(1,p.life/18));
+ ctx.fillStyle=p.color;
+ ctx.fillRect(p.x-2,p.y-2,4,4);
+ }
+ ctx.globalAlpha=1;
+ for(i=0;i<floaters.length;i++){
+ var fl=floaters[i];
+ ctx.globalAlpha=Math.max(0,Math.min(1,fl.life/30));
+ ctx.fillStyle=fl.color;
+ ctx.font='900 '+Math.round(H*0.035)+'px system-ui';
+ ctx.textAlign='center'; ctx.textBaseline='middle';
+ ctx.fillText(fl.text,fl.x,fl.y);
+ }
+ 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') 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 ptr(e){
+ var r=cv.getBoundingClientRect();
+ ptrX=e.clientX-r.left; ptrY=e.clientY-r.top;
+ }
+ cv.addEventListener('pointerdown',function(e){ dragging=true; ptr(e); });
+ cv.addEventListener('pointermove',function(e){ if(dragging) ptr(e); });
+ cv.addEventListener('pointerup',function(){ dragging=false; });
+ cv.addEventListener('pointercancel',function(){ dragging=false; });
+ cv.addEventListener('pointerleave',function(){ dragging=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)){
+ var sx=W/oW, sy=H/oH, i;
+ if(hook){ hook.x*=sx; hook.y*=sy; hook.r*=sx; }
+ for(i=0;i<fish.length;i++){
+ var f=fish[i]; f.x*=sx; f.y*=sy; f.w*=sx; f.h*=sx; f.vx*=sx;
+ }
+ for(i=0;i<jellies.length;i++){
+ var jl=jellies[i]; jl.x*=sx; jl.y*=sy; jl.r*=sx; jl.vx*=sx;
+ }
+ for(i=0;i<parts.length;i++){ parts[i].x*=sx; parts[i].y*=sy; }
+ for(i=0;i<floaters.length;i++){ floaters[i].x*=sx; floaters[i].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, 12:14:22 PM

Initial version — all lines are new.

+<style>
+ html,body{height:100%;margin:0;background:#03040c;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:#cffafe;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:.6;letter-spacing:.14em;margin-bottom:.05rem;}
+ #hud .r{text-align:right;}
+ #time.low{color:#fb7185;}
+ #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(3,4,12,.86);color:#e0f2fe;cursor:pointer;-webkit-tap-highlight-color:transparent;touch-action:none;user-select:none;-webkit-user-select:none;}
+ #ov h1{margin:0;font-size:2.05rem;font-weight:900;letter-spacing:.02em;background:linear-gradient(90deg,#22d3ee,#38bdf8,#a3e635);-webkit-background-clip:text;background-clip:text;color:transparent;}
+ #ov .sub{margin:.1rem 0 0;font-size:.9rem;color:#7dd3fc;max-width:26ch;}
+ #ov .big{margin:.1rem 0 0;font-size:1.05rem;font-weight:800;color:#e0f2fe;}
+ #ov .pulse{margin-top:.5rem;font-size:.84rem;color:#67e8f9;animation:dcpulse 1.3s ease-in-out infinite;}
+ @keyframes dcpulse{0%,100%{opacity:.35}50%{opacity:1}}
+</style>
+<div id="wrap">
+ <canvas id="cv"></canvas>
+ <div id="hud">
+ <div><div class="lab">TIME</div><div id="time">60</div></div>
+ <div class="r"><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>
+ <div id="ov">
+ <h1>DEEP CATCH</h1>
+ <p class="sub" id="ovsub">Drag the lure to hook a fish, then drag it up to the boat. Dodge the jellyfish.</p>
+ <p class="big" id="ovbig"></p>
+ <p class="pulse">Tap to dive</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=ov.querySelector('h1');
+ var scoreEl=document.getElementById('score'), bestEl=document.getElementById('best'),
+ timeEl=document.getElementById('time');
+ var W=0,H=0,dpr=1;
+ var state='start', score=0, best=0, timeLeft=60, jellyTimer=0;
+ var hook=null, fish=[], jellies=[], parts=[], floaters=[];
+ var dragging=false, ptrX=0, ptrY=0;
+ var lastT=0, raf=0, running=false;
+
+ var TYPES=[
+ {key:'common',color:'#67e8f9',points:10,r:0.026,spd:0.010,wt:50,band:[0.22,0.46]},
+ {key:'silver',color:'#a5b4fc',points:25,r:0.034,spd:0.0072,wt:30,band:[0.44,0.66]},
+ {key:'deep', color:'#34d399',points:50,r:0.044,spd:0.0052,wt:15,band:[0.66,0.9]},
+ {key:'gold', color:'#fbbf24',points:100,r:0.03,spd:0.014,wt:5, band:[0.3,0.84]}
+ ];
+
+ function setCanvas(){
+ var r=cv.getBoundingClientRect();
+ W=Math.max(160,r.width); H=Math.max(220,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 anchorX(){ return W/2; }
+ function anchorY(){ return H*0.07; }
+ function bankY(){ return H*0.15; }
+ function rint(a,b){ return a+Math.floor(Math.random()*(b-a+1)); }
+ function pickType(){
+ var total=0,i; for(i=0;i<TYPES.length;i++) total+=TYPES[i].wt;
+ var n=Math.random()*total;
+ for(i=0;i<TYPES.length;i++){ n-=TYPES[i].wt; if(n<=0) return TYPES[i]; }
+ return TYPES[0];
+ }
+ function spawnFish(){
+ var t=pickType();
+ var dir=Math.random()<0.5?-1:1;
+ var y=H*(t.band[0]+Math.random()*(t.band[1]-t.band[0]));
+ fish.push({
+ t:t, x:dir<0?W+W*0.1:-W*0.1, y:y, vx:dir*t.spd*H,
+ w:W*t.r, h:W*t.r*0.62, color:t.color, points:t.points, caught:false,
+ phase:Math.random()*6.28
+ });
+ }
+ function spawnJelly(){
+ jellies.push({
+ x:Math.random()*W, y:H*(0.32+Math.random()*0.5),
+ vx:(Math.random()<0.5?-1:1)*H*0.0016, baseY:0, phase:Math.random()*6.28,
+ r:W*0.05
+ });
+ }
+ function newGame(){
+ score=0; timeLeft=60; jellyTimer=0;
+ fish=[]; jellies=[]; parts=[]; floaters=[];
+ hook={x:W/2,y:H*0.3,r:W*0.026,carry:null,stunUntil:0};
+ var i;
+ for(i=0;i<8;i++) spawnFish();
+ for(i=0;i<fish.length;i++) fish[i].x=Math.random()*W;
+ for(i=0;i<4;i++) spawnJelly();
+ state='playing'; syncHud();
+ }
+ function syncHud(){
+ scoreEl.textContent=score;
+ bestEl.textContent=best;
+ var s=Math.max(0,Math.ceil(timeLeft));
+ timeEl.textContent=s;
+ if(s<=10) timeEl.classList.add('low'); else timeEl.classList.remove('low');
+ }
+ function floater(x,y,text,color){
+ floaters.push({x:x,y:y,text:text,color:color,life:46});
+ }
+ function burst(x,y,color,n){
+ for(var i=0;i<n;i++){
+ var a=Math.random()*6.2832;
+ parts.push({x:x,y:y,vx:Math.cos(a)*(1+Math.random()*3),
+ vy:Math.sin(a)*(1+Math.random()*3),life:16+Math.random()*14,color:color});
+ }
+ }
+ function gameOver(){
+ state='over';
+ if(score>best){ best=score; saveBest(); }
+ syncHud();
+ ovtitle.textContent='TIME UP';
+ ovsub.textContent='Lures in for the day.';
+ ovbig.textContent='Score '+score+' · Best '+best;
+ ov.style.display='flex';
+ }
+ function step(dt){
+ timeLeft-=dt/60;
+ if(timeLeft<=0){ timeLeft=0; gameOver(); return; }
+ jellyTimer+=dt/60;
+ if(jellyTimer>=20 && jellies.length<6){ spawnJelly(); jellyTimer=0; }
+
+ // hook: follow the finger, or drift gently up when released
+ if(dragging){
+ hook.x+=(ptrX-hook.x)*Math.min(1,0.34*dt);
+ hook.y+=(ptrY-hook.y)*Math.min(1,0.34*dt);
+ } else {
+ hook.y-=H*0.0011*dt;
+ }
+ if(hook.x<W*0.03)hook.x=W*0.03; if(hook.x>W*0.97)hook.x=W*0.97;
+ if(hook.y<anchorY())hook.y=anchorY(); if(hook.y>H*0.97)hook.y=H*0.97;
+
+ var i,j;
+ // fish
+ for(i=0;i<fish.length;i++){
+ var f=fish[i];
+ if(f.caught) continue;
+ f.x+=f.vx*dt;
+ f.phase+=0.08*dt;
+ if(f.vx>0 && f.x>W+W*0.12) f.x=-W*0.12;
+ if(f.vx<0 && f.x<-W*0.12) f.x=W+W*0.12;
+ }
+ // jellies
+ for(i=0;i<jellies.length;i++){
+ var jl=jellies[i];
+ jl.x+=jl.vx*dt; jl.phase+=0.05*dt;
+ if(jl.x<-W*0.1) jl.x=W+W*0.1;
+ if(jl.x>W+W*0.1) jl.x=-W*0.1;
+ }
+ var stunned=performance.now()<hook.stunUntil;
+ // catch
+ if(!hook.carry && !stunned){
+ for(i=0;i<fish.length;i++){
+ var ff=fish[i]; if(ff.caught) continue;
+ var dx=ff.x-hook.x, dy=ff.y-hook.y;
+ if(dx*dx+dy*dy < (hook.r+ff.w*0.75)*(hook.r+ff.w*0.75)){
+ ff.caught=true; hook.carry=ff;
+ burst(hook.x,hook.y,ff.color,8);
+ break;
+ }
+ }
+ }
+ // jelly collision
+ for(i=0;i<jellies.length;i++){
+ var jj=jellies[i];
+ var jdx=jj.x-hook.x, jdy=jj.y-hook.y;
+ if(jdx*jdx+jdy*jdy < (hook.r+jj.r*0.8)*(hook.r+jj.r*0.8)){
+ if(hook.carry){
+ var lost=hook.carry; hook.carry=null;
+ lost.caught=false; lost.x=jj.x; lost.y=jj.y+jj.r;
+ floater(hook.x,hook.y,'Lost!','#fb7185');
+ } else if(!stunned){
+ hook.stunUntil=performance.now()+650;
+ burst(hook.x,hook.y,'#fb7185',6);
+ }
+ break;
+ }
+ }
+ // carried fish rides the hook; bank it at the surface
+ if(hook.carry){
+ hook.carry.x=hook.x; hook.carry.y=hook.y+hook.r+hook.carry.h;
+ if(hook.y<=bankY()){
+ var c=hook.carry;
+ score+=c.points;
+ floater(anchorX(),bankY(),'+'+c.points,c.color);
+ burst(anchorX(),bankY(),c.color,14);
+ var idx=fish.indexOf(c); if(idx>=0) fish.splice(idx,1);
+ hook.carry=null;
+ spawnFish();
+ syncHud();
+ }
+ }
+ // particles + floaters
+ for(i=parts.length-1;i>=0;i--){
+ var p=parts[i];
+ p.x+=p.vx*dt; p.y+=p.vy*dt; p.vy+=0.05*dt; p.life-=dt;
+ if(p.life<=0) parts.splice(i,1);
+ }
+ for(i=floaters.length-1;i>=0;i--){
+ var fl=floaters[i];
+ fl.y-=0.6*dt; fl.life-=dt;
+ if(fl.life<=0) floaters.splice(i,1);
+ }
+ }
+ function drawFish(f){
+ var dir=f.vx<0?-1:1, wob=Math.sin(f.phase)*f.h*0.3;
+ ctx.save();
+ ctx.translate(f.x,f.y);
+ ctx.scale(dir,1);
+ ctx.shadowColor=f.color; ctx.shadowBlur=12;
+ ctx.fillStyle=f.color;
+ ctx.beginPath(); ctx.ellipse(0,0,f.w,f.h,0,0,6.2832); ctx.fill();
+ ctx.beginPath();
+ ctx.moveTo(-f.w*0.75,0);
+ ctx.lineTo(-f.w*1.5,-f.h*0.95+wob);
+ ctx.lineTo(-f.w*1.5,f.h*0.95+wob);
+ ctx.closePath(); ctx.fill();
+ ctx.shadowBlur=0;
+ ctx.fillStyle='#03040c';
+ ctx.beginPath(); ctx.arc(f.w*0.5,-f.h*0.22,Math.max(1.2,f.h*0.2),0,6.2832); ctx.fill();
+ ctx.restore();
+ }
+ function drawJelly(jl){
+ ctx.save();
+ ctx.translate(jl.x,jl.y);
+ ctx.shadowColor='#fb7185'; ctx.shadowBlur=14;
+ ctx.fillStyle='rgba(251,113,133,0.82)';
+ ctx.beginPath(); ctx.arc(0,0,jl.r,Math.PI,Math.PI*2); ctx.closePath(); ctx.fill();
+ ctx.strokeStyle='rgba(251,113,133,0.6)'; ctx.lineWidth=2;
+ for(var k=0;k<4;k++){
+ var tx=-jl.r*0.62+k*(jl.r*0.41);
+ ctx.beginPath();
+ ctx.moveTo(tx,0);
+ ctx.quadraticCurveTo(tx+Math.sin(jl.phase+k)*5,jl.r*0.95,tx,jl.r*1.7);
+ ctx.stroke();
+ }
+ ctx.shadowBlur=0;
+ ctx.restore();
+ }
+ function render(){
+ var bg=ctx.createLinearGradient(0,0,0,H);
+ bg.addColorStop(0,'#0c3a52'); bg.addColorStop(0.45,'#082235'); bg.addColorStop(1,'#03040c');
+ ctx.fillStyle=bg; ctx.fillRect(0,0,W,H);
+ // light shafts
+ ctx.fillStyle='rgba(186,230,253,0.045)';
+ var s;
+ for(s=0;s<3;s++){
+ var lx=W*(0.2+s*0.32);
+ ctx.beginPath();
+ ctx.moveTo(lx-W*0.04,0); ctx.lineTo(lx+W*0.04,0);
+ ctx.lineTo(lx+W*0.16,H); ctx.lineTo(lx-W*0.16,H);
+ ctx.closePath(); ctx.fill();
+ }
+ // bank line
+ ctx.strokeStyle='rgba(103,232,249,0.25)'; ctx.lineWidth=1.5;
+ ctx.beginPath(); ctx.moveTo(0,bankY()); ctx.lineTo(W,bankY()); ctx.stroke();
+ var i;
+ for(i=0;i<fish.length;i++) if(!fish[i].caught) drawFish(fish[i]);
+ for(i=0;i<jellies.length;i++) drawJelly(jellies[i]);
+ if(hook){
+ // line
+ ctx.strokeStyle='rgba(226,232,240,0.5)'; ctx.lineWidth=1.5;
+ ctx.beginPath(); ctx.moveTo(anchorX(),anchorY()); ctx.lineTo(hook.x,hook.y); ctx.stroke();
+ // boat hull
+ ctx.fillStyle='#e2e8f0';
+ var bw=W*0.1, by=anchorY();
+ ctx.beginPath();
+ ctx.moveTo(anchorX()-bw,by-H*0.022);
+ ctx.lineTo(anchorX()+bw,by-H*0.022);
+ ctx.lineTo(anchorX()+bw*0.6,by);
+ ctx.lineTo(anchorX()-bw*0.6,by);
+ ctx.closePath(); ctx.fill();
+ // carried fish
+ if(hook.carry) drawFish(hook.carry);
+ // lure
+ var stunned=performance.now()<hook.stunUntil;
+ ctx.shadowColor=stunned?'#fb7185':'#fde68a'; ctx.shadowBlur=16;
+ ctx.fillStyle=stunned?'#fb7185':'#fde68a';
+ ctx.beginPath(); ctx.arc(hook.x,hook.y,hook.r,0,6.2832); ctx.fill();
+ ctx.shadowBlur=0;
+ ctx.strokeStyle='#cbd5e1'; ctx.lineWidth=2;
+ ctx.beginPath(); ctx.arc(hook.x,hook.y+hook.r*1.4,hook.r*0.62,0.1*Math.PI,0.9*Math.PI); ctx.stroke();
+ }
+ for(i=0;i<parts.length;i++){
+ var p=parts[i];
+ ctx.globalAlpha=Math.max(0,Math.min(1,p.life/18));
+ ctx.fillStyle=p.color;
+ ctx.fillRect(p.x-2,p.y-2,4,4);
+ }
+ ctx.globalAlpha=1;
+ for(i=0;i<floaters.length;i++){
+ var fl=floaters[i];
+ ctx.globalAlpha=Math.max(0,Math.min(1,fl.life/30));
+ ctx.fillStyle=fl.color;
+ ctx.font='900 '+Math.round(H*0.035)+'px system-ui';
+ ctx.textAlign='center'; ctx.textBaseline='middle';
+ ctx.fillText(fl.text,fl.x,fl.y);
+ }
+ 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') 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 ptr(e){
+ var r=cv.getBoundingClientRect();
+ ptrX=e.clientX-r.left; ptrY=e.clientY-r.top;
+ }
+ cv.addEventListener('pointerdown',function(e){ dragging=true; ptr(e); });
+ cv.addEventListener('pointermove',function(e){ if(dragging) ptr(e); });
+ cv.addEventListener('pointerup',function(){ dragging=false; });
+ cv.addEventListener('pointercancel',function(){ dragging=false; });
+ cv.addEventListener('pointerleave',function(){ dragging=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)){
+ var sx=W/oW, sy=H/oH, i;
+ if(hook){ hook.x*=sx; hook.y*=sy; hook.r*=sx; }
+ for(i=0;i<fish.length;i++){
+ var f=fish[i]; f.x*=sx; f.y*=sy; f.w*=sx; f.h*=sx; f.vx*=sx;
+ }
+ for(i=0;i<jellies.length;i++){
+ var jl=jellies[i]; jl.x*=sx; jl.y*=sy; jl.r*=sx; jl.vx*=sx;
+ }
+ for(i=0;i<parts.length;i++){ parts[i].x*=sx; parts[i].y*=sy; }
+ for(i=0;i<floaters.length;i++){ floaters[i].x*=sx; floaters[i].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