Show code
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Chaotic Double Pendulum</title>
<style>
html,body{margin:0;height:100%;background:#06080f;overflow:hidden}
canvas{display:block}
#hud{position:fixed;top:16px;left:18px;font:600 20px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace;color:#a9dcff;text-shadow:0 0 14px rgba(40,140,220,.7);pointer-events:none}
#hud b{display:block;font:600 10px/1.6 ui-monospace,monospace;color:#5d7796;letter-spacing:.24em;text-transform:uppercase}
</style>
<canvas id=c></canvas>
<div id=hud><b>elapsed</b><span id=s>0.00 s</span></div>
<script>
const c=document.getElementById('c'),x=c.getContext('2d'),S=document.getElementById('s');
let W,H;
function fit(){const d=Math.min(2,devicePixelRatio||1);W=innerWidth;H=innerHeight;c.width=W*d;c.height=H*d;c.style.width=W+'px';c.style.height=H+'px';x.setTransform(d,0,0,d,0,0)}
addEventListener('resize',fit);fit();
const G=9.81;
let a1,a2,w1,w2,L1,L2,m1,m2,tr,t0,hue,len;
function reset(f){
a1=f?2.2:(1.0+Math.random()*1.9)*(Math.random()<.5?-1:1);
a2=f?1.8:(1.0+Math.random()*2.0)*(Math.random()<.5?-1:1);
w1=w2=0;L1=1;L2=.85+Math.random()*.3;m1=1+Math.random();m2=.8+Math.random();
tr=[];t0=0;len=0;hue=Math.random()*360;
}
reset(1);
function der(s){
const A=s[0],B=s[1],U=s[2],V=s[3],d=A-B,sd=Math.sin(d),cd=Math.cos(d),
D=2*m1+m2-m2*Math.cos(2*d);
return[U,V,
(-G*(2*m1+m2)*Math.sin(A)-m2*G*Math.sin(A-2*B)-2*sd*m2*(V*V*L2+U*U*L1*cd))/(L1*D),
(2*sd*(U*U*L1*(m1+m2)+G*(m1+m2)*Math.cos(A)+V*V*L2*m2*cd))/(L2*D)];
}
function step(h){
const s=[a1,a2,w1,w2],k1=der(s),
k2=der(s.map((v,i)=>v+h/2*k1[i])),
k3=der(s.map((v,i)=>v+h/2*k2[i])),
k4=der(s.map((v,i)=>v+h*k3[i])),
o=s.map((v,i)=>v+h/6*(k1[i]+2*k2[i]+2*k3[i]+k4[i]));
a1=o[0];a2=o[1];w1=o[2];w2=o[3];
}
function bob(px,py,r,h){
const g=x.createRadialGradient(px-r*.35,py-r*.35,r*.15,px,py,r);
g.addColorStop(0,'#fff');g.addColorStop(.4,'hsl('+h+',95%,68%)');g.addColorStop(1,'hsl('+h+',85%,32%)');
x.fillStyle=g;x.beginPath();x.arc(px,py,r,0,7);x.fill();
}
let last=0;
function frame(now){
requestAnimationFrame(frame);
if(!last)last=now;
let dt=Math.min(.04,(now-last)/1000);last=now;t0+=dt;
const R=Math.min(W,H)*.2,cx=W/2,cy=H*.36,N=10,h=dt/N;
for(let i=0;i<N;i++){
step(h);
const px=cx+R*L1*Math.sin(a1)+R*L2*Math.sin(a2),
py=cy+R*L1*Math.cos(a1)+R*L2*Math.cos(a2),
p=tr[tr.length-1];
if(p)len+=Math.hypot(px-p[0],py-p[1]);
tr.push([px,py]);
}
while(tr.length>2200)tr.shift();
x.fillStyle='#06080f';x.fillRect(0,0,W,H);
x.lineJoin=x.lineCap='round';
const n=tr.length,CH=24;
for(let i=0;i<n-1;i+=CH){
const e=Math.min(n-1,i+CH),a=i/n;
x.beginPath();x.moveTo(tr[i][0],tr[i][1]);
for(let j=i+1;j<=e;j++)x.lineTo(tr[j][0],tr[j][1]);
x.strokeStyle='hsla('+((hue+a*90)%360)+',96%,'+(42+a*26)+'%,'+(a*a*.8+.03)+')';
x.lineWidth=.6+a*2.1;x.stroke();
}
const x1=cx+R*L1*Math.sin(a1),y1=cy+R*L1*Math.cos(a1),
x2=x1+R*L2*Math.sin(a2),y2=y1+R*L2*Math.cos(a2);
x.strokeStyle='rgba(190,220,255,.28)';x.lineWidth=7;
x.beginPath();x.moveTo(cx,cy);x.lineTo(x1,y1);x.lineTo(x2,y2);x.stroke();
x.strokeStyle='rgba(225,240,255,.92)';x.lineWidth=2.2;
x.beginPath();x.moveTo(cx,cy);x.lineTo(x1,y1);x.lineTo(x2,y2);x.stroke();
x.fillStyle='#7f95b4';x.beginPath();x.arc(cx,cy,5,0,7);x.fill();
bob(x1,y1,6+m1*3,(hue+20)%360);
bob(x2,y2,6+m2*4,(hue+95)%360);
S.textContent=t0.toFixed(2)+' s';
if(len>R*46||t0>26)reset(0);
}
requestAnimationFrame(frame);
</script>
</html>