Show code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Sloshing Water</title>
<style>
html,body{margin:0;height:100%;background:#05070f;display:flex;align-items:center;justify-content:center;font-family:system-ui,sans-serif}
canvas{max-width:100vw;max-height:100vh;border-radius:10px;box-shadow:0 0 60px #0a2a5580}
</style>
</head>
<body>
<canvas id="c" width="760" height="480"></canvas>
<script>
const cv=document.getElementById('c'),g=cv.getContext('2d');
const W=760,H=480,L=34,R=W-34,T=44,B=H-30,N=200,rr=6,h=20,hc=h*1.75,G=1500;
const sp=document.createElement('canvas');sp.width=sp.height=64;
const sc=sp.getContext('2d'),rg=sc.createRadialGradient(32,32,0,32,32,32);
rg.addColorStop(0,'rgba(140,215,255,.85)');rg.addColorStop(.35,'rgba(45,135,240,.45)');
rg.addColorStop(1,'rgba(10,50,150,0)');sc.fillStyle=rg;sc.fillRect(0,0,64,64);
let P,t,per,amp,ph,dur,ax=[],ay=[];
function init(){
P=[];
for(let i=0;i<N;i++)P.push({x:L+50+Math.random()*(R-L-100),y:T+70+Math.random()*(B-T-130),vx:(Math.random()-.5)*60,vy:(Math.random()-.5)*60});
t=0;per=2.6+Math.random()*2.2;amp=.42+Math.random()*.38;ph=Math.random()*6.283;dur=20+Math.random()*12;
}
let ang=0;
function step(dt){
ang=amp*Math.sin(t*6.283/per+ph)+amp*.22*Math.sin(t*6.283/(per*.41)+ph*2);
const gx=Math.sin(ang)*G,gy=Math.cos(ang)*G,s=dt/2;
for(let k=0;k<2;k++){
for(let i=0;i<N;i++){ax[i]=gx;ay[i]=gy;}
for(let i=0;i<N;i++){const a=P[i];
for(let j=i+1;j<N;j++){const b=P[j];
let dx=b.x-a.x,dy=b.y-a.y,d2=dx*dx+dy*dy;
if(d2<hc*hc&&d2>.01){
const d=Math.sqrt(d2),nx=dx/d,ny=dy/d;
const f=d<h?3000*(1-d/h)*(1-d/h*.5):-300*(1-(d-h)/(hc-h));
ax[i]-=nx*f;ay[i]-=ny*f;ax[j]+=nx*f;ay[j]+=ny*f;
const u=((b.vx-a.vx)*nx+(b.vy-a.vy)*ny)*7*(1-d/hc);
ax[i]+=nx*u;ay[i]+=ny*u;ax[j]-=nx*u;ay[j]-=ny*u;
}}}
for(let i=0;i<N;i++){const p=P[i];
p.vx=(p.vx+ax[i]*s)*.9985;p.vy=(p.vy+ay[i]*s)*.9985;
const v2=p.vx*p.vx+p.vy*p.vy;
if(v2>810000){const m=900/Math.sqrt(v2);p.vx*=m;p.vy*=m;}
p.x+=p.vx*s;p.y+=p.vy*s;
if(p.x<L+rr){p.x=L+rr;if(p.vx<0)p.vx*=-.34;p.vy*=.97;}
if(p.x>R-rr){p.x=R-rr;if(p.vx>0)p.vx*=-.34;p.vy*=.97;}
if(p.y<T+rr){p.y=T+rr;if(p.vy<0)p.vy*=-.34;p.vx*=.97;}
if(p.y>B-rr){p.y=B-rr;if(p.vy>0)p.vy*=-.34;p.vx*=.96;}
}
}
}
function draw(){
g.fillStyle='#070b16';g.fillRect(0,0,W,H);
g.save();
g.beginPath();g.rect(L-2,T-2,R-L+4,B-T+4);g.clip();
g.globalCompositeOperation='lighter';
for(let i=0;i<N;i++){const p=P[i];g.drawImage(sp,p.x-23,p.y-23,46,46);}
for(let i=0;i<N;i++){const p=P[i];
g.fillStyle='rgba(190,240,255,.16)';g.beginPath();g.arc(p.x,p.y,4.5,0,6.283);g.fill();}
g.restore();
g.strokeStyle='rgba(150,200,255,.4)';g.lineWidth=3;
g.strokeRect(L-4,T-4,R-L+8,B-T+8);
const deg=ang*180/Math.PI;
g.fillStyle='#d3e8ff';g.font='600 17px system-ui,sans-serif';
g.fillText('Tilt: '+(deg>=0?'+':'')+deg.toFixed(1)+'°',L-4,T-16);
g.font='400 12px system-ui,sans-serif';g.fillStyle='rgba(150,190,230,.7)';
g.fillText('gravity',R-74,T-16);
const cx=R-24,cy=T-22;
g.strokeStyle='rgba(120,200,255,.85)';g.lineWidth=2;g.beginPath();
g.moveTo(cx-Math.sin(ang)*9,cy-Math.cos(ang)*9);
g.lineTo(cx+Math.sin(ang)*9,cy+Math.cos(ang)*9);g.stroke();
g.beginPath();g.arc(cx+Math.sin(ang)*9,cy+Math.cos(ang)*9,3,0,6.283);
g.fillStyle='rgba(120,200,255,.9)';g.fill();
}
let last=0;
function loop(ts){
const dt=Math.min(.033,(ts-last)/1000||.016);last=ts;
t+=dt;step(dt);draw();
if(t>dur)init();
requestAnimationFrame(loop);
}
init();requestAnimationFrame(loop);
</script>
</body>
</html>