JIT
Grid - Untitled
In one html file create a triple pendulum animation that follows the laws of gravity. No controls, no interactions, simply the animation. Make sure each intercept from one pendulum to another leaves a trail.
<!DOCTYPE html> <html> <head> <title>Triple Pendulum Animation</title> <meta charset="utf-8"> <style> body, html { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background-color: #0d0d0d; } canvas { display: block; } </style> </head> <body> <canvas id="pendulumCanvas"></canvas> <script> const canvas = document.getElementById('pendulumCanvas'); const ctx = canvas.getContext('2d');
    // --- Configuration ---
    const g = 9.81; // Gravity
    
    // Pendulum Properties
    const l1 = 160, l2 = 160, l3 = 160; // Lengths of arms
    const m1 = 15, m2 = 15, m3 = 15;   // Masses of bobs

    // Initial State (angles in radians, velocities in rad/s)
    let theta1 = Math.PI / 1.5;
    let theta2 = Math.PI / 1.2;
    let theta3 = Math.PI;
    let omega1 = 0, omega2 = 0, omega3 = 0; // Angular velocities

    // Animation settings
    const substeps = 8; // Number of physics updates per frame for stability
    const maxTrailLength = 800;
    const trailFade = 0.04; // Lower is longer lasting trails

    // Trail arrays
    const trail1 = [], trail2 = [], trail3 = [];

    let pivotX, pivotY;

    function resizeCanvas() {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight;
        pivotX = canvas.width / 2;
        pivotY = canvas.height / 2 - Math.min(canvas.width, canvas.height) * 0.2;
    }

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

    function updatePhysics(timeStep) {
        // Shorthands for sin/cos and other terms
        const c1 = Math.cos(theta1), s1 = Math.sin(theta1);
        const c2 = Math.cos(theta2), s2 = Math.sin(theta2);
        const c3 = Math.cos(theta3), s3 = Math.sin(theta3);
        const c12 = Math.cos(theta1 - theta2), s12 = Math.sin(theta1 - theta2);
        const c13 = Math.cos(theta1 - theta3), s13 = Math.sin(theta1 - theta3);
        const c23 = Math.cos(theta2 - theta3), s23 = Math.sin(theta2 - theta3);
        
        const l1_2 = l1 * l1, l2_2 = l2 * l2, l3_2 = l3 * l3;
        const w1_2 = omega1 * omega1, w2_2 = omega2 * omega2, w3_2 = omega3 * omega3;

        // Define the M matrix and F vector for the system M * alpha = F
        const M11 = (m1 + m2 + m3) * l1_2;
        const M12 = (m2 + m3) * l1 * l2 * c12;
        const M13 = m3 * l1 * l3 * c13;
        const M21 = M12;
        const M22 = (m2 + m3) * l2_2;
        const M23 = m3 * l2 * l3 * c23;
        const M31 = M13;
        const M32 = M23;
        const M33 = m3 * l3_2;

        const F1 = -(m2 + m3) * l1 * l2 * w2_2 * s12 - m3 * l1 * l3 * w3_2 * s13 - (m1 + m2 + m3) * g * l1 * s1;
        const F2 = (m2 + m3) * l1 * l2 * w1_2 * s12 - m3 * l2 * l3 * w3_2 * s23 - (m2 + m3) * g * l2 * s2;
        const F3 = m3 * l1 * l3 * w1_2 * s13 + m3 * l2 * l3 * w2_2 * s23 - m3 * g * l3 * s3;

        // Solve for angular accelerations (alpha) using Cramer's rule
        const detM = M11 * (M22 * M33 - M23 * M32) - M12 * (M21 * M33 - M23 * M31) + M13 * (M21 * M32 - M22 * M31);

        if (Math.abs(detM) < 1e-12) return; // Avoid division by near-zero determinant

        const detM1 = F1 * (M22 * M33 - M23 * M32) - M12 * (F2 * M33 - M23 * F3) + M13 * (F2 * M32 - M22 * F3);
        const detM2 = M11 * (F2 * M33 - M23 * F3) - F1 * (M21 * M33 - M23 * M31) + M13 * (M21 * F3 - F2 * M31);
        const detM3 = M11 * (M22 * F3 - F2 * M32) - M12 * (M21 * F3 - F2 * M31) + F1 * (M21 * M32 - M22 * M31);

        const alpha1 = detM1 / detM;
        const alpha2 = detM2 / detM;
        const alpha3 = detM3 / detM;

        // Update velocities and positions using Euler integration
        omega1 += alpha1 * timeStep;
        omega2 += alpha2 * timeStep;
        omega3 += alpha3 * timeStep;

        theta1 += omega1 * timeStep;
        theta2 += omega2 * timeStep;
        theta3 += omega3 * timeStep;
    }

    function draw() {
        // Apply a semi-transparent overlay to create fading trails
        ctx.fillStyle = `rgba(13, 13, 13, ${trailFade})`;
        ctx.fillRect(0, 0, canvas.width, canvas.height);

        // Convert polar coordinates (angle, length) to Cartesian (x, y)
        const x1 = pivotX + l1 * Math.sin(theta1);
        const y1 = pivotY + l1 * Math.cos(theta1);
        const x2 = x1 + l2 * Math.sin(theta2);
        const y2 = y1 + l2 * Math.cos(theta2);
        const x3 = x2 + l3 * Math.sin(theta3);
        const y3 = y2 + l3 * Math.cos(theta3);

        // Add new points to the trail arrays
        trail1.push({ x: x1, y: y1 });
        trail2.push({ x: x2, y: y2 });
        trail3.push({ x: x3, y: y3 });
        if (trail1.length > maxTrailLength) trail1.shift();
        if (trail2.length > maxTrailLength) trail2.shift();
        if (trail3.length > maxTrailLength) trail3.shift();

        // Draw trails
        drawTrail(trail1, '#ff4545');
        drawTrail(trail2, '#45ff75');
        drawTrail(trail3, '#45a5ff');

        // Draw pendulum arms
        ctx.strokeStyle = '#cccccc';
        ctx.lineWidth = 2;
        ctx.beginPath();
        ctx.moveTo(pivotX, pivotY);
        ctx.lineTo(x1, y1);
        ctx.lineTo(x2, y2);
        ctx.lineTo(x3, y3);
        ctx.stroke();

        // Draw bobs
        drawBob(x1, y1, m1, '#ff4545');
        drawBob(x2, y2, m2, '#45ff75');
        drawBob(x3, y3, m3, '#45a5ff');
        
        // Draw pivot point
        drawBob(pivotX, pivotY, 5, '#ffffff');
    }

    function drawTrail(trail, color) {
        ctx.beginPath();
        if (trail.length > 0) {
            ctx.moveTo(trail[0].x, trail[0].y);
            for (let i = 1; i < trail.length; i++) {
                ctx.lineTo(trail[i].x, trail[i].y);
            }
        }
        ctx.strokeStyle = color;
        ctx.lineWidth = 1.5;
        ctx.stroke();
    }

    function drawBob(x, y, mass, color) {
        const radius = Math.cbrt(mass) * 3;
        ctx.beginPath();
        ctx.arc(x, y, radius, 0, 2 * Math.PI);
        ctx.fillStyle = color;
        ctx.fill();
    }

    let lastTime = 0;
    function animate(currentTime) {
        const deltaTime = (currentTime - lastTime) / 1000; // time in seconds
        lastTime = currentTime;

        // Use a fixed time step for physics stability, running multiple steps if needed
        const timeStep = 1 / 60; // Simulate at a consistent 60 FPS
        const steps = Math.max(1, Math.round(deltaTime / timeStep));

        for (let i = 0; i < steps; i++) {
            for (let j = 0; j < substeps; j++) {
                updatePhysics(timeStep / substeps);
            }
        }
        
        draw();
        requestAnimationFrame(animate);
    }

    requestAnimationFrame(animate);
</script>
</body> </html>*_
Updated files
v1