library / retro & cyberpunk / plasma-tunnel
live - 60fps 1920 x 1080
plasma-tunnel.html - self-contained
<!DOCTYPE html>
<html>
<head>
<title>Plasma Tunnel</title>
<style>
    :root {
        --bg-color: #000008;
        --glow-1: #7c3aed;
        --glow-2: #22d3ee;
    }
    * { margin: 0; padding: 0; }
    body { height: 100vh; background: var(--bg-color); overflow: hidden; }
    canvas { display: block; width: 100vw; height: 100vh; image-rendering: pixelated; }
</style>
</head>
<body>
    <canvas id="c"></canvas>
<script>
    const cv = document.getElementById('c');
    const ctx = cv.getContext('2d');
    ctx.imageSmoothingEnabled = false;
    const css = getComputedStyle(document.documentElement);
    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 g1 = hex(css.getPropertyValue('--glow-1') || '#7c3aed');
    const g2 = hex(css.getPropertyValue('--glow-2') || '#22d3ee');
    const SCALE = 4;
    let lw, lh, img, buf, rad, ang;
    function build() {
        cv.width = Math.ceil(innerWidth / SCALE);
        cv.height = Math.ceil(innerHeight / SCALE);
        lw = cv.width; lh = cv.height;
        img = ctx.createImageData(lw, lh); buf = img.data;
        rad = new Float32Array(lw * lh);
        ang = new Float32Array(lw * lh);
        const cx = lw / 2, cy = lh / 2;
        for (let y = 0; y < lh; y++) {
            for (let x = 0; x < lw; x++) {
                const dx = x - cx, dy = y - cy;
                const idx = y * lw + x;
                rad[idx] = Math.sqrt(dx * dx + dy * dy);
                ang[idx] = Math.atan2(dy, dx);
            }
        }
    }
    addEventListener('resize', build);
    build();
    function step(t) {
        for (let i = 0; i < rad.length; i++) {
            const dist = rad[i];
            const d = 34 / (dist + 1.2);          // depth coord — grows toward the centre
            const a = ang[i];
            // tunnel wall: concentric rings scrolling toward the viewer + angular flutes
            const rings = Math.sin(d * 3.2 - t * 3);
            const flutes = Math.sin(a * 8 + d * 0.6 + t * 0.4);
            let tex = 0.5 + 0.5 * rings * flutes;
            const light = Math.min(1, dist * 0.013);  // lit walls at the edge, dark vanishing point
            let s = (0.22 + 0.78 * tex) * light;
            if (s < 0) s = 0; else if (s > 1) s = 1;
            const mix = 0.5 + 0.5 * Math.sin(d * 2 + a * 3 - t * 1.5);
            const j = i * 4;
            buf[j] = (g1[0] + (g2[0] - g1[0]) * mix) * s | 0;
            buf[j + 1] = (g1[1] + (g2[1] - g1[1]) * mix) * s | 0;
            buf[j + 2] = (g1[2] + (g2[2] - g1[2]) * mix) * s | 0;
            buf[j + 3] = 255;
        }
        ctx.putImageData(img, 0, 0);
    }
    if (matchMedia('(prefers-reduced-motion: reduce)').matches) {
        step(1.0);
    } else {
        let t = 0;
        (function loop() { t += 0.03; step(t); requestAnimationFrame(loop); })();
    }
</script>
</body>
</html>

About this animation

A hypnotic demoscene tunnel — spiralling plasma flutes rush inward toward a dark vanishing point in an endless psychedelic zoom.

Source Code
Copied to clipboard