• 01 - Critter Models 02
  • 02 - VantaJS Shaders
  • 03 - App Bundle 02
  • 04 - Cathedrals
  • 05 - App Bundle 01
  • 06 - Retroscopic
  • 07 - Games
  • 08 - Voronoi
  • 09 - Terrain
  • 10 - Critter Models 01
  • 11 - Chromatophores
  • 12 - Conformal Maps
  • 13 - Echo Logix
  • 14 - Music
  • 15 - Colour Theory
MIKE'S NOTEBOOK
Notes
Number:
Project:
01Critter Models 02
└ Locust└ Butterfly└ Cockroach└ Spider└ Scorpion└ Dragon└ Brazilian Treehopper└ Tardigrade└ Diatoms└ Bumblebee└ Wasp└ Hammerhead Worm└ Giraffe Weevil└ Trilobite Beetle
02VantaJS Shaders
└ Clouds└ Waves└ Cells└ Rings└ Halo└ Globe└ Net└ Dots└ Birds└ Trunk└ Topology└ About VantaJS
03App Bundle 02
└ Gutindex└ Insect Builder└ OpenLayers
04Cathedrals
└ Lattice└ Lost└ Lucent└ Lumen└ Glass└ Disco└ Hyperion└ Latent
05App Bundle 01
└ Robohash└ PrismJS└ Horoscope└ Binary Jazz└ Morse Code└ URL Extractor└ Entity Escaper└ Phonetic Alphabet└ Toki Pona└ Semaphore└ Cut Up Machine└ Text Mixer└ Currency Freaks└ Host IO└ CrUX API└ Logo Dev└ Web Speech API└ GEO DB
06Retroscopic
└ Galaxy└ Ocean└ Slime└ 24-Cell└ Unicorn└ Polyp└ Oneiric 1└ Oneiric 2└ Node└ Torus Knot└ GLSL Grid└ Liquid Crystal└ Silky└ Butterfly└ Ocean 2└ Icosahedron└ Luma└ Swarm└ Broken Solid└ Hypersphere└ Constraints└ Psycho Killer
01 / 03

CONSTRAINTS - RETROSCOPIC CRT SHADER SOURCE CODE

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Constraints</title>
    <style>
        * { margin: 0; padding: 0; box-sizing: border-box; }
        body { background: #000; overflow: hidden; height: 100vh; width: 100vw; }
        #stage {
            position: fixed;
            inset: 0;
            filter: grayscale(1) sepia(1) hue-rotate(80deg) saturate(5) brightness(1.15) contrast(1.2);
            transform: scale(3);
            transform-origin: center center;
            z-index: 2;
        }
        canvas { display: block; width: 100%; height: 100%; }
        /* Retroscopic Overlay */
        #scope {
            position: fixed;
            inset: 0;
            z-index: 4;
            pointer-events: none;
            display: block;
        }
        #scope>div { position: absolute; inset: 0; }
        #scope .glow {
            background: radial-gradient(circle at 50% 46%, rgba(30, 255, 130, .08), rgba(0, 0, 0, 0) 62%);
            animation: nvflick 3.6s steps(24) infinite;
        }
        #scope .scan {
            background: repeating-linear-gradient(to bottom, rgba(0, 0, 0, 0) 0 2px, rgba(0, 25, 8, .45) 2px 3px);
            animation: nvscan 7s linear infinite;
        }
        #scope .vig { box-shadow: inset 0 0 200px 50px rgba(0, 0, 0, .8); }
        #scope .mask {
            background: radial-gradient(circle at 50% 50%, rgba(0, 0, 0, 0) 54%, rgba(0, 0, 0, .5) 72%, rgba(0, 0, 0, .94) 100%);
        }
        @keyframes nvscan {
            from { background-position: 0 0; }
            to { background-position: 0 240px; }
        }
        @keyframes nvflick {
            0%, 100% { opacity: .55; }
            40% { opacity: .9; }
            62% { opacity: .4; }
            80% { opacity: .75; }
        }
    </style>
</head>
<body>
    <div id="scope">
        <div class="glow"></div>
        <div class="scan"></div>
        <div class="vig"></div>
        <div class="mask"></div>
    </div>
    <div id="stage">
        <canvas id="phiCanvas"></canvas>
    </div>

    <script>
        const PHI = (1 + Math.sqrt(5)) / 2;
        let animationId = null;
        let time = 0;

        const animationSpeed = 1;
        const maxDepth = 1;
        const glowIntensity = 100;

        const canvas = document.getElementById('phiCanvas');
        const ctx = canvas.getContext('2d');

        function resizeCanvas() {
            canvas.width = window.innerWidth;
            canvas.height = window.innerHeight;
        }

        resizeCanvas();
        window.addEventListener('resize', resizeCanvas);

        function drawVortex(x, y, radius, rotation, color, isInward, depth = 0) {
            if (depth > maxDepth) return;

            ctx.save();
            ctx.translate(x, y);
            ctx.rotate(rotation);

            const gradient = ctx.createRadialGradient(0, 0, 0, 0, 0, radius);
            if (isInward) {
                gradient.addColorStop(0, `rgba(100, 150, 255, ${0.9 - depth * 0.15})`);
                gradient.addColorStop(0.5, `rgba(50, 100, 200, ${0.6 - depth * 0.1})`);
                gradient.addColorStop(1, `rgba(20, 50, 150, ${0.2 - depth * 0.05})`);
            } else {
                gradient.addColorStop(0, `rgba(255, 150, 50, ${0.9 - depth * 0.15})`);
                gradient.addColorStop(0.5, `rgba(255, 100, 30, ${0.6 - depth * 0.1})`);
                gradient.addColorStop(1, `rgba(200, 50, 10, ${0.2 - depth * 0.05})`);
            }

            const numArms = 8;
            for (let i = 0; i < numArms; i++) {
                const angle = (i * Math.PI * 2) / numArms;
                ctx.beginPath();

                for (let r = 0; r < radius; r += 2) {
                    const spiralAngle = angle + (isInward ? -r : r) / (radius / (PHI * 3));
                    const dist = r * (isInward ? (1 - r / radius * 0.3) : 1);
                    const xPos = Math.cos(spiralAngle) * dist;
                    const yPos = Math.sin(spiralAngle) * dist;

                    if (r === 0) {
                        ctx.moveTo(xPos, yPos);
                    } else {
                        ctx.lineTo(xPos, yPos);
                    }
                }

                ctx.strokeStyle = gradient;
                ctx.lineWidth = 3 - depth * 0.5;
                ctx.stroke();
            }

            if (depth < maxDepth) {
                const nestedRadius = radius / PHI;
                const nestedDistance = radius / (PHI * 1.5);

                for (let i = 0; i < 5; i++) {
                    const nestedAngle = (i * Math.PI * 2) / 5;
                    const nx = Math.cos(nestedAngle) * nestedDistance;
                    const ny = Math.sin(nestedAngle) * nestedDistance;

                    drawVortex(nx, ny, nestedRadius, rotation + nestedAngle, color, !isInward, depth + 1);
                }
            }

            ctx.restore();
        }

        function drawPhiSpiral(centerX, centerY, maxRadius, color, clockwise = true) {
            ctx.beginPath();
            ctx.strokeStyle = color;
            ctx.lineWidth = 2;

            let angle = 0;
            const angleStep = 0.1;
            const maxAngle = Math.PI * 6;

            while (angle < maxAngle) {
                const radius = maxRadius * Math.pow(PHI, angle / (Math.PI * 2) - 3);
                const x = centerX + Math.cos(angle * (clockwise ? 1 : -1)) * radius;
                const y = centerY + Math.sin(angle * (clockwise ? 1 : -1)) * radius;

                if (angle === 0) {
                    ctx.moveTo(x, y);
                } else {
                    ctx.lineTo(x, y);
                }
                angle += angleStep;
            }

            ctx.stroke();
        }

        function animate() {
            const centerX = canvas.width / 2;
            const centerY = canvas.height / 2;

            ctx.fillStyle = 'rgba(5, 5, 15, 0.15)';
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            const spiralRadius = Math.min(canvas.width, canvas.height) * 0.35;
            drawPhiSpiral(centerX, centerY, spiralRadius, 'rgba(100, 150, 255, 0.2)', true);
            drawPhiSpiral(centerX, centerY, spiralRadius, 'rgba(255, 150, 50, 0.2)', false);

            const glowSize = 50 * (glowIntensity / 100);
            const glowGradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, glowSize);
            glowGradient.addColorStop(0, 'rgba(255, 255, 255, 0.9)');
            glowGradient.addColorStop(0.5, 'rgba(200, 200, 255, 0.3)');
            glowGradient.addColorStop(1, 'rgba(100, 100, 200, 0)');
            ctx.fillStyle = glowGradient;
            ctx.beginPath();
            ctx.arc(centerX, centerY, glowSize, 0, Math.PI * 2);
            ctx.fill();

            const numPairs = 6;
            const distance = Math.min(canvas.width, canvas.height) * 0.25;

            for (let i = 0; i < numPairs; i++) {
                const pairAngle = (i * Math.PI * 2) / numPairs + time * 0.2;

                const blueX = centerX + Math.cos(pairAngle) * distance;
                const blueY = centerY + Math.sin(pairAngle) * distance;
                const blueRotation = time * (1 / PHI) - pairAngle;
                drawVortex(blueX, blueY, 80, blueRotation, 'blue', true, 0);

                const orangeAngle = pairAngle + Math.PI;
                const orangeX = centerX + Math.cos(orangeAngle) * distance;
                const orangeY = centerY + Math.sin(orangeAngle) * distance;
                const orangeRotation = time * PHI + orangeAngle;
                drawVortex(orangeX, orangeY, 80, orangeRotation, 'orange', false, 0);
            }

            ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
            ctx.lineWidth = 1;
            for (let i = 0; i < 12; i++) {
                const angle = (i * Math.PI * 2) / 12;
                const radius = 150 + Math.sin(time + i) * 30;
                ctx.beginPath();
                ctx.arc(centerX, centerY, radius * Math.pow(PHI, i % 3 - 1), 0, Math.PI * 2);
                ctx.stroke();
            }

            time += 0.02 * animationSpeed;

            animationId = requestAnimationFrame(animate);
        }

        function startAnimation() {
            if (animationId) {
                cancelAnimationFrame(animationId);
            }

            ctx.fillStyle = 'rgb(5, 5, 15)';
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            animate();
        }

        startAnimation();

        document.addEventListener('visibilitychange', function() {
            if (document.hidden) {
                if (animationId) {
                    cancelAnimationFrame(animationId);
                    animationId = null;
                }
            } else {
                startAnimation();
            }
        });
    </script>
</body>
</html>
MikeHiggins.me.uk LinkedIn GitHub Bluesky Contra Upwork
© 2026 Mike Higgins. All rights reserved.