Physics Engine Demo

HTML

<div class="ui">
        Click to spawn balls<br>
        Move mouse to interact
    </div>
    <canvas id="canvas"></canvas>

CSS

body {
            margin: 0;
            padding: 0;
            overflow: hidden;
            background-color: #1a1a1a;
            font-family: sans-serif;
        }
        canvas {
            display: block;
        }
        .ui {
            position: absolute;
            top: 20px;
            left: 20px;
            color: rgba(255, 255, 255, 0.5);
            pointer-events: none;
            user-select: none;
        }

JavaScript

/**
 * PHYSICS ENGINE CONSTANTS
 */
const GRAVITY = 0.25;
const FRICTION = 0.99; // Air resistance
const WALL_BOUNCE = 0.7; // Energy kept after hitting a wall
const SUB_STEPS = 8; // Physics sub-steps for stability

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

let width, height;
let balls = [];
const mouse = { x: -1000, y: -1000, radius: 150 };

// Resize handler
function resize() {
    width = window.innerWidth;
    height = window.innerHeight;
    canvas.width = width;
    canvas.height = height;
}

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

/**
 * BALL CLASS
 */
class Ball {
    constructor(x, y, radius, color, mass) {
        this.x = x;
        this.y = y;
        this.radius = radius;
        this.mass = mass || radius; // Mass proportional to size
        this.color = color;
        this.vx = (Math.random() - 0.5) * 10;
        this.vy = (Math.random() - 0.5) * 10;
    }

    draw() {
        ctx.beginPath();
        ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
        ctx.fillStyle = this.color;
        ctx.fill();
        // Shine effect
        ctx.beginPath();
        ctx.arc(this.x - this.radius * 0.3, this.y - this.radius * 0.3, this.radius * 0.1, 0, Math.PI * 2);
        ctx.fillStyle = 'rgba(255,255,255,0.5)';
        ctx.fill();
    }

    update() {
        // Apply gravity
        this.vy += GRAVITY;
        
        // Apply air friction
        this.vx *= FRICTION;
        this.vy *= FRICTION;

        // Update position
        this.x += this.vx;
        this.y += this.vy;

        // Wall Collisions
        if (this.x + this.radius > width) {
            this.x = width - this.radius;
            this.vx *= -WALL_BOUNCE;
        } else if (this.x - this.radius < 0) {
            this.x = this.radius;
            this.vx *= -WALL_BOUNCE;
        }

        if (this.y + this.radius > height) {
           ...