← Mike Higgins Home
Custom Code Experiment •

Webpage Background

Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Fluid Background</title>
    <style>
        body {
            margin: 0;
            overflow: hidden;
            background: #000;
        }
        canvas {
            display: block;
        }
    </style>
</head>
<body>
    <canvas id="canvas"></canvas>
    <script>
        const canvas = document.getElementById('canvas');
        const ctx = canvas.getContext('2d');

        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;

        window.addEventListener('resize', () => {
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
        });

        class GlassBlob {
            constructor(x, y, size) {
                this.x = x;
                this.y = y;
                this.size = size;
                this.baseSize = size;
                this.vx = (Math.random() - 0.5) * 0.3;
                this.vy = (Math.random() - 0.5) * 0.3;
                this.angle = Math.random() * Math.PI * 2;
                this.rotationSpeed = (Math.random() - 0.5) * 0.01;
                this.pulsePhase = Math.random() * Math.PI * 2;
                this.pulseSpeed = 0.01 + Math.random() * 0.01;
                this.hueOffset = Math.random() * 360;
            }

            update() {
                this.x += this.vx;
                this.y += this.vy;
                this.angle += this.rotationSpeed;
                this.pulsePhase += this.pulseSpeed;
                this.size = this.baseSize + Math.sin(this.pulsePhase) * 10;

                if (this.x < -200) this.x = canvas.width + 200;
                if (this.x > canvas.width + 200) this.x = -200;
                if (this.y < -200) this.y = canvas.height + 200;
                if (this.y > canvas.height + 200) this.y = -200;
            }

            draw() {
                ctx.save();
                ctx.translate(this.x, this.y);
                ctx.rotate(this.angle);

                const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, this.size);
                gradient.addColorStop(0, `hsla(${this.hueOffset}, 90%, 70%, 0.7)`);
                gradient.addColorStop(0.3, `hsla(${this.hueOffset + 80}, 85%, 65%, 0.6)`);
                gradient.addColorStop(0.6, `hsla(${this.hueOffset + 160}, 80%, 60%, 0.4)`);
                gradient.addColorStop(1, 'rgba(255, 255, 255, 0)');

                ctx.fillStyle = gradient;
                ctx.filter = 'blur(30px)';
                ctx.beginPath();

                const points = 8;
                for (let i = 0; i < points; i++) {
                    const a = (i / points) * Math.PI * 2;
                    const r = this.size * (0.8 + Math.sin(a * 3 + this.pulsePhase) * 0.2);
                    const x = Math.cos(a) * r;
                    const y = Math.sin(a) * r;

                    if (i === 0) ctx.moveTo(x, y);
                    else ctx.lineTo(x, y);
                }
                ctx.closePath();
                ctx.fill();
                ctx.filter = 'none';
                ctx.restore();
            }
        }

        class WaveLine {
            constructor(startY) {
                this.points = [];
                this.startY = startY;
                this.offset = 0;
                this.speed = 0.02;
                this.amplitude = 50 + Math.random() * 50;
                this.frequency = 0.01 + Math.random() * 0.01;
                this.numLines = 20;
                this.spacing = 8;
            }

            update() {
                this.offset += this.speed;
            }

            draw() {
                for (let line = 0; line < this.numLines; line++) {
                    ctx.strokeStyle = `rgba(255, 255, 255, ${0.15 - line * 0.005})`;
                    ctx.lineWidth = 1.5;
                    ctx.beginPath();

                    for (let x = 0; x <= canvas.width; x += 5) {
                        const y = this.startY +
                                Math.sin(x * this.frequency + this.offset) * this.amplitude +
                                line * this.spacing;

                        if (x === 0) ctx.moveTo(x, y);
                        else ctx.lineTo(x, y);
                    }
                    ctx.stroke();

                    if (line % 3 === 0) {
                        for (let x = 0; x <= canvas.width; x += 80) {
                            const y = this.startY +
                                    Math.sin(x * this.frequency + this.offset) * this.amplitude +
                                    line * this.spacing;

                            ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';
                            ctx.beginPath();
                            ctx.arc(x, y, 3, 0, Math.PI * 2);
                            ctx.fill();
                        }
                    }
                }
            }
        }

        class DiagonalStreak {
            constructor() {
                this.reset();
            }

            reset() {
                this.x = Math.random() * canvas.width;
                this.y = -100;
                this.length = 100 + Math.random() * 150;
                this.speed = 2 + Math.random() * 3;
                this.opacity = 0.1 + Math.random() * 0.2;
            }

            update() {
                this.x += this.speed * 0.5;
                this.y += this.speed;

                if (this.y > canvas.height + 100) {
                    this.reset();
                }
            }

            draw() {
                const gradient = ctx.createLinearGradient(
                    this.x, this.y,
                    this.x + this.length * 0.5, this.y + this.length
                );
                gradient.addColorStop(0, `rgba(255, 255, 255, 0)`);
                gradient.addColorStop(0.5, `rgba(255, 255, 255, ${this.opacity})`);
                gradient.addColorStop(1, `rgba(255, 255, 255, 0)`);

                ctx.strokeStyle = gradient;
                ctx.lineWidth = 2;
                ctx.beginPath();
                ctx.moveTo(this.x, this.y);
                ctx.lineTo(this.x + this.length * 0.5, this.y + this.length);
                ctx.stroke();
            }
        }

        class Star {
            constructor() {
                this.x = Math.random() * canvas.width;
                this.y = Math.random() * canvas.height;
                this.size = 1 + Math.random() * 2;
                this.opacity = Math.random();
                this.twinkleSpeed = 0.02 + Math.random() * 0.02;
            }

            update() {
                this.opacity += this.twinkleSpeed;
                if (this.opacity > 1 || this.opacity < 0) {
                    this.twinkleSpeed *= -1;
                }
            }

            draw() {
                ctx.fillStyle = `rgba(255, 255, 255, ${this.opacity * 0.5})`;
                ctx.beginPath();
                ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
                ctx.fill();
            }
        }

        function drawCornerGrids() {
            const dotSize = 4;
            const spacing = 20;
            const rows = 2;
            const cols = 8;

            ctx.fillStyle = 'rgba(255, 255, 255, 0.6)';

            for (let r = 0; r < rows; r++) {
                for (let c = 0; c < cols; c++) {
                    ctx.beginPath();
                    ctx.arc(
                        canvas.width - 50 - c * spacing,
                        50 + r * spacing,
                        dotSize,
                        0,
                        Math.PI * 2
                    );
                    ctx.fill();
                }
            }

            for (let r = 0; r < rows; r++) {
                for (let c = 0; c < cols; c++) {
                    ctx.beginPath();
                    ctx.arc(
                        50 + c * spacing,
                        canvas.height - 50 - r * spacing,
                        dotSize,
                        0,
                        Math.PI * 2
                    );
                    ctx.fill();
                }
            }
        }

        const blobs = [];
        for (let i = 0; i < 8; i++) {
            blobs.push(new GlassBlob(
                Math.random() * canvas.width,
                Math.random() * canvas.height,
                60 + Math.random() * 80
            ));
        }

        const waveLines = [
            new WaveLine(canvas.height * 0.5)
        ];

        const streaks = [];
        for (let i = 0; i < 8; i++) {
            streaks.push(new DiagonalStreak());
        }

        const stars = [];
        for (let i = 0; i < 80; i++) {
            stars.push(new Star());
        }

        function animate() {
            const gradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
            gradient.addColorStop(0, '#0f0a1f');
            gradient.addColorStop(0.3, '#1a0f2e');
            gradient.addColorStop(0.6, '#1f0a33');
            gradient.addColorStop(1, '#0a1628');
            ctx.fillStyle = gradient;
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            stars.forEach(star => {
                star.update();
                star.draw();
            });

            streaks.forEach(streak => {
                streak.update();
                streak.draw();
            });

            blobs.forEach(blob => {
                blob.update();
                blob.draw();
            });

            waveLines.forEach(wave => {
                wave.update();
                wave.draw();
            });

            drawCornerGrids();

            requestAnimationFrame(animate);
        }

        animate();
    </script>
</body>
</html>