← Mike Higgins Home
Custom Code Experiment •

Calabi-Yau 1

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>Calabi-Yau Manifold Animation</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background: #0a0a0a;
            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
            overflow: hidden;
            padding-top: 80px;
        }

        #animation-container {
            position: relative;
            width: 100%;
            height: calc(100vh - 80px);
        }

        canvas {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            display: block;
        }

        .manifold-selector {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            background: rgba(15, 15, 20, 0.95);
            backdrop-filter: blur(20px);
            -webkit-backdrop-filter: blur(20px);
            border-bottom: 1px solid rgba(255, 255, 255, 0.1);
            padding: 15px;
            z-index: 100;
            display: flex;
            justify-content: center;
            align-items: center;
        }

        .selector-container {
            display: flex;
            gap: 8px;
            max-width: 500px;
            width: 100%;
            flex-wrap: wrap;
            justify-content: center;
        }

        .manifold-btn {
            flex: 1;
            min-width: 100px;
            max-width: 120px;
            background: rgba(255, 255, 255, 0.08);
            color: rgba(255, 255, 255, 0.8);
            border: none;
            padding: 12px 16px;
            border-radius: 12px;
            cursor: pointer;
            font-size: 14px;
            font-weight: 500;
            transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 4px;
            letter-spacing: 0.3px;
        }

        .manifold-btn:hover {
            background: rgba(255, 255, 255, 0.12);
            transform: translateY(-1px);
        }

        .manifold-btn.active {
            background: linear-gradient(135deg, #4a4af0 0%, #5a5aff 100%);
            color: white;
            box-shadow: 0 4px 20px rgba(74, 74, 240, 0.3);
        }

        .manifold-btn.active:hover {
            background: linear-gradient(135deg, #5a5aff 0%, #6a6aff 100%);
            box-shadow: 0 6px 24px rgba(74, 74, 240, 0.4);
        }

        .manifold-indicator {
            width: 6px;
            height: 6px;
            border-radius: 50%;
            background: rgba(255, 255, 255, 0.3);
            transition: background 0.3s;
        }

        .manifold-btn.active .manifold-indicator {
            background: rgba(255, 255, 255, 0.9);
            box-shadow: 0 0 8px rgba(255, 255, 255, 0.5);
        }

        .reset-notice {
            position: fixed;
            bottom: 20px;
            right: 20px;
            background: rgba(255, 74, 141, 0.2);
            color: #ff4a8d;
            padding: 10px 16px;
            border-radius: 8px;
            font-size: 13px;
            opacity: 0;
            transition: opacity 0.3s, transform 0.3s;
            transform: translateY(10px);
            backdrop-filter: blur(10px);
            -webkit-backdrop-filter: blur(10px);
            border: 1px solid rgba(255, 74, 141, 0.2);
            z-index: 100;
        }

        .reset-notice.visible {
            opacity: 1;
            transform: translateY(0);
        }

        /* Mobile optimizations */
        @media (max-width: 768px) {
            body {
                padding-top: 70px;
            }

            #animation-container {
                height: calc(100vh - 70px);
            }

            .manifold-selector {
                padding: 12px;
            }

            .selector-container {
                gap: 6px;
            }

            .manifold-btn {
                min-width: calc(50% - 6px);
                max-width: calc(50% - 6px);
                padding: 10px 12px;
                font-size: 13px;
                border-radius: 10px;
            }

            .manifold-indicator {
                width: 5px;
                height: 5px;
            }
        }

        @media (max-width: 480px) {
            .manifold-selector {
                padding: 10px;
            }

            .selector-container {
                gap: 5px;
            }

            .manifold-btn {
                min-width: calc(50% - 5px);
                max-width: calc(50% - 5px);
                padding: 10px 8px;
                font-size: 12px;
            }

            .reset-notice {
                bottom: 10px;
                right: 10px;
                left: 10px;
                text-align: center;
                font-size: 12px;
                padding: 8px 12px;
            }
        }

        @media (max-width: 320px) {
            .manifold-btn {
                min-width: 100%;
                max-width: 100%;
                flex-direction: row;
                justify-content: center;
                gap: 8px;
            }

            .manifold-indicator {
                order: -1;
            }
        }
    </style>
</head>
<body>
    <div class="manifold-selector">
        <div class="selector-container">
            <button class="manifold-btn active" data-manifold="1">
                <span class="manifold-indicator"></span>
                <span>Manifold 1</span>
            </button>
            <button class="manifold-btn" data-manifold="2">
                <span class="manifold-indicator"></span>
                <span>Manifold 2</span>
            </button>
            <button class="manifold-btn" data-manifold="3">
                <span class="manifold-indicator"></span>
                <span>Manifold 3</span>
            </button>
            <button class="manifold-btn" data-manifold="4">
                <span class="manifold-indicator"></span>
                <span>Manifold 4</span>
            </button>
        </div>
    </div>

    <div id="animation-container">
        <canvas id="calabi-canvas"></canvas>
    </div>

    <div class="reset-notice" id="reset-notice">Animation Reset</div>

    <script>
        // Canvas setup
        const canvas = document.getElementById('calabi-canvas');
        const ctx = canvas.getContext('2d');
        let animationId = null;

        // Animation parameters
        let time = 0;
        let manifoldType = 1;

        // Resize management
        let resizeTimeout;
        let isResetting = false;

        // Set canvas size
        function setCanvasSize() {
            const container = document.getElementById('animation-container');
            canvas.width = container.clientWidth * window.devicePixelRatio;
            canvas.height = container.clientHeight * window.devicePixelRatio;

            // Scale context for retina displays
            ctx.scale(window.devicePixelRatio, window.devicePixelRatio);

            console.log(`Canvas resized to: ${canvas.width / window.devicePixelRatio}x${canvas.height / window.devicePixelRatio}`);
        }

        // Show reset notice
        function showResetNotice() {
            const notice = document.getElementById('reset-notice');
            notice.classList.add('visible');
            setTimeout(() => {
                notice.classList.remove('visible');
            }, 1000);
        }

        // Clear and reset animation
        function resetAnimation() {
            if (isResetting) return;

            isResetting = true;

            // Clear any existing animation frame
            if (animationId) {
                cancelAnimationFrame(animationId);
            }

            // Reset canvas size
            setCanvasSize();

            // Clear canvas
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            // Reset time for animation continuity
            time = 0;

            // Restart animation
            animate();

            // Show notice
            showResetNotice();

            console.log('Animation reset complete');

            isResetting = false;
        }

        // Animation loop
        function animate() {
            // Clear canvas
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            // Draw Calabi-Yau manifold based on selected type
            drawManifold();

            // Increment time
            time += 0.01;

            // Continue animation loop
            animationId = requestAnimationFrame(animate);
        }

        // Draw Calabi-Yau manifold
        function drawManifold() {
            const centerX = canvas.width / (2 * window.devicePixelRatio);
            const centerY = canvas.height / (2 * window.devicePixelRatio);
            const scale = Math.min(centerX, centerY) * 0.4;

            // Common parameters
            const detail = 100;
            const radius = scale * 0.8;

            // Set line style
            ctx.lineWidth = 2;
            ctx.strokeStyle = '#4a4af0';
            ctx.fillStyle = 'rgba(74, 74, 240, 0.1)';

            // Draw based on manifold type
            switch(manifoldType) {
                case 1:
                    drawManifold1(centerX, centerY, radius, detail);
                    break;
                case 2:
                    drawManifold2(centerX, centerY, radius, detail);
                    break;
                case 3:
                    drawManifold3(centerX, centerY, radius, detail);
                    break;
                case 4:
                    drawManifold4(centerX, centerY, radius, detail);
                    break;
            }
        }

        // Manifold 1: Basic torus with ripple
        function drawManifold1(centerX, centerY, radius, detail) {
            const tubeRadius = radius * 0.3;

            ctx.beginPath();
            for (let i = 0; i <= detail; i++) {
                const u = (i / detail) * Math.PI * 2;
                const ripple = Math.sin(u * 5 + time * 2) * 0.2;

                for (let j = 0; j <= detail; j++) {
                    const v = (j / detail) * Math.PI * 2;

                    const x = (radius + tubeRadius * Math.cos(v + time)) * Math.cos(u + time);
                    const y = (radius + tubeRadius * Math.cos(v + time)) * Math.sin(u + time);
                    const z = tubeRadius * Math.sin(v + time) + ripple * radius;

                    const scale = 1 + z / (radius * 2);
                    const screenX = centerX + x * scale;
                    const screenY = centerY + y * scale;

                    if (j === 0) {
                        ctx.moveTo(screenX, screenY);
                    } else {
                        ctx.lineTo(screenX, screenY);
                    }
                }
            }
            ctx.stroke();
        }

        // Manifold 2: Klein bottle-like surface
        function drawManifold2(centerX, centerY, radius, detail) {
            ctx.strokeStyle = '#ff4a8d';
            ctx.fillStyle = 'rgba(255, 74, 141, 0.1)';

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

                for (let j = 0; j <= detail; j++) {
                    const v = (j / detail) * Math.PI * 2;

                    const x = radius * (Math.cos(u) * (1 + Math.cos(v)) +
                                       Math.sin(u) * Math.cos(u) * Math.sin(v));
                    const y = radius * (Math.sin(u) * (1 + Math.cos(v)) -
                                       Math.cos(u) * Math.cos(u) * Math.sin(v));
                    const z = radius * Math.sin(v) * Math.sin(u/2) + Math.sin(time) * radius * 0.3;

                    const rotation = time * 0.5;
                    const rotatedX = x * Math.cos(rotation) - z * Math.sin(rotation);
                    const rotatedZ = x * Math.sin(rotation) + z * Math.cos(rotation);

                    const screenX = centerX + rotatedX;
                    const screenY = centerY + y + rotatedZ * 0.5;

                    if (j === 0) {
                        ctx.moveTo(screenX, screenY);
                    } else {
                        ctx.lineTo(screenX, screenY);
                    }
                }
            }
            ctx.stroke();
        }

        // Manifold 3: Complex harmonic surface
        function drawManifold3(centerX, centerY, radius, detail) {
            ctx.strokeStyle = '#4affa3';
            ctx.fillStyle = 'rgba(74, 255, 163, 0.1)';

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

                for (let j = 0; j <= detail; j++) {
                    const v = (j / detail) * Math.PI * 2;

                    // Complex harmonic function
                    const harmonic = Math.sin(3 * u + time) * Math.cos(2 * v + time) *
                                   Math.sin(u + v + time * 0.5);

                    const x = radius * Math.cos(u) * (1 + 0.5 * harmonic);
                    const y = radius * Math.sin(u) * (1 + 0.5 * harmonic);
                    const z = radius * Math.sin(v) * (1 + 0.3 * Math.cos(2 * u + time));

                    const scale = 1.2 + Math.sin(time + u) * 0.3;
                    const screenX = centerX + (x + z * 0.3) * scale;
                    const screenY = centerY + (y + z * 0.5) * scale;

                    if (j === 0) {
                        ctx.moveTo(screenX, screenY);
                    } else {
                        ctx.lineTo(screenX, screenY);
                    }
                }
            }
            ctx.stroke();
        }

        // Manifold 4: Quantum foam-like structure
        function drawManifold4(centerX, centerY, radius, detail) {
            ctx.strokeStyle = '#ffa34a';
            ctx.fillStyle = 'rgba(255, 163, 74, 0.1)';

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

                for (let j = 0; j <= detail; j++) {
                    const v = (j / detail) * Math.PI * 2;

                    // Quantum foam perturbation
                    const foam = Math.sin(5 * u + time * 3) *
                               Math.cos(4 * v + time * 2) *
                               Math.sin(2 * u * v + time);

                    const x = radius * Math.cos(u + foam * 0.3) *
                            (1 + 0.4 * Math.sin(2 * v + time));
                    const y = radius * Math.sin(u + foam * 0.3) *
                            (1 + 0.4 * Math.cos(2 * v + time));
                    const z = radius * foam * 0.5;

                    const twist = time * 0.3;
                    const twistedX = x * Math.cos(twist) - y * Math.sin(twist);
                    const twistedY = x * Math.sin(twist) + y * Math.cos(twist);

                    const screenX = centerX + twistedX + z * Math.cos(time);
                    const screenY = centerY + twistedY + z * Math.sin(time);

                    if (j === 0) {
                        ctx.moveTo(screenX, screenY);
                    } else {
                        ctx.lineTo(screenX, screenY);
                    }
                }
            }
            ctx.stroke();
        }

        // Debounced resize handler
        function handleResize() {
            clearTimeout(resizeTimeout);
            resizeTimeout = setTimeout(() => {
                resetAnimation();
            }, 250); // Debounce delay of 250ms
        }

        // Initialize
        function init() {
            // Set initial canvas size
            setCanvasSize();

            // Start animation
            animate();

            // Add event listeners
            window.addEventListener('resize', handleResize);

            // Control buttons
            document.querySelectorAll('.manifold-btn').forEach(button => {
                button.addEventListener('click', function() {
                    // Update active button
                    document.querySelectorAll('.manifold-btn').forEach(btn => {
                        btn.classList.remove('active');
                    });
                    this.classList.add('active');

                    // Update manifold type
                    manifoldType = parseInt(this.dataset.manifold);
                });
            });

            // Handle page visibility change
            document.addEventListener('visibilitychange', () => {
                if (document.hidden) {
                    // Page is hidden, stop animation to save resources
                    if (animationId) {
                        cancelAnimationFrame(animationId);
                        animationId = null;
                    }
                } else {
                    // Page is visible again, restart animation
                    if (!animationId) {
                        animate();
                    }
                }
            });

            console.log('Calabi-Yau manifold animation initialized');
        }

        // Start everything
        window.addEventListener('load', init);
    </script>
</body>
</html>