live - 60fps 1920 x 1080
<!DOCTYPE html>
<html>
<head>
<title>Event Horizon</title>
<style>
:root { --bg-color: #02020a; --hot: #fff1c9; --cool: #f97316; --rim: #a855f7; }
* { margin: 0; padding: 0; }
body { height: 100vh; background: var(--bg-color); overflow: hidden; }
canvas { display: block; }
</style>
</head>
<body>
<canvas id="c"></canvas>
<script>
const cv = document.getElementById('c'), ctx = cv.getContext('2d');
const css = getComputedStyle(document.documentElement);
const bg = (css.getPropertyValue('--bg-color') || '#02020a').trim();
function hex(h){h=(h||'').trim().replace('#','');if(h.length===3)h=h.split('').map(c=>c+c).join('');const n=parseInt(h,16);return[(n>>16)&255,(n>>8)&255,n&255];}
const hot = hex(css.getPropertyValue('--hot') || '#fff1c9');
const cool = hex(css.getPropertyValue('--cool') || '#f97316');
const rim = (css.getPropertyValue('--rim') || '#a855f7').trim();
let w, h, cx, cy, horizon, parts;
const N = 1400;
function spawn(s) { s.a = Math.random() * Math.PI * 2; s.r = horizon + 40 + Math.random() * (Math.min(w, h) * 0.5); s.spin = 0.6 + Math.random() * 0.6; }
function build() {
w = cv.width = innerWidth; h = cv.height = innerHeight; cx = w / 2; cy = h / 2;
horizon = Math.min(w, h) * 0.08;
parts = []; for (let i = 0; i < N; i++) { const s = {}; spawn(s); s.r = horizon + Math.random() * (Math.min(w, h) * 0.5); parts.push(s); }
ctx.fillStyle = bg; ctx.fillRect(0, 0, w, h);
}
addEventListener('resize', build); build();
function step() {
ctx.fillStyle = bg.length === 7 ? bg + '30' : bg; ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = 'lighter';
for (const s of parts) {
s.r -= (horizon * 22) / s.r * 0.14; // faster infall closer in
s.a += s.spin / s.r * 26;
if (s.r <= horizon) { spawn(s); continue; }
// squash orbit into a tilted accretion disk
const x = cx + Math.cos(s.a) * s.r;
const y = cy + Math.sin(s.a) * s.r * 0.42;
const heat = Math.max(0, Math.min(1, 1 - (s.r - horizon) / (Math.min(w, h) * 0.42)));
const r = cool[0] + (hot[0] - cool[0]) * heat | 0;
const g = cool[1] + (hot[1] - cool[1]) * heat | 0;
const b = cool[2] + (hot[2] - cool[2]) * heat | 0;
ctx.fillStyle = `rgba(${r},${g},${b},${0.35 + heat * 0.6})`;
const sz = 0.6 + heat * 1.8;
ctx.fillRect(x, y, sz, sz);
}
ctx.globalCompositeOperation = 'source-over';
// black hole + glowing photon ring
const g = ctx.createRadialGradient(cx, cy, horizon * 0.6, cx, cy, horizon * 1.6);
g.addColorStop(0, '#000'); g.addColorStop(0.72, '#000'); g.addColorStop(0.9, rim); g.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = g; ctx.beginPath(); ctx.arc(cx, cy, horizon * 1.6, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = '#000'; ctx.beginPath(); ctx.arc(cx, cy, horizon, 0, Math.PI * 2); ctx.fill();
}
if (matchMedia('(prefers-reduced-motion: reduce)').matches) { for (let i = 0; i < 120; i++) step(); }
else (function loop() { step(); requestAnimationFrame(loop); })();
</script>
</body>
</html> About this animation
Glowing matter spirals into a black hole, an accretion disk of hot particles swirling ever faster before crossing the photon ring into darkness.
Related