JSFiddle - React, Tailwind, and code Playground

HTML

<!DOCTYPE html>
<html>
<head>
    <title>Bouncing Ball in Spinning Hexagon</title>
    <style>
        canvas { border: 1px solid black; }
        .controls { margin: 10px; }
        .control-group { margin-bottom: 10px; }
        label { display: inline-block; width: 120px; }
    </style>
</head>
<body>
    <div class="controls">
        <div class="control-group">
            <label>Rotation Speed: </label>
            <input type="range" id="rotationSpeed" min="-5" max="5" step="0.1" value="2">
            <span id="rotationSpeedValue">2</span>
        </div>
        <div class="control-group">
            <label>Gravity: </label>
            <input type="range" id="gravity" min="0" max="1000" step="10" value="500">
            <span id="gravityValue">500</span>
        </div>
        <div class="control-group">
            <label>Bounciness: </label>
            <input type="range" id="bounciness" min="0" max="1.5" step="0.1" value="0.8">
            <span id="bouncinessValue">0.8</span>
        </div>
    </div>
    <canvas id="canvas" width="600" height="600"></canvas>
    <script src="dist/main.js"></script>
</body>
</html>

TypeScript

interface Ball {
    x: number;
    y: number;
    vx: number;
    vy: number;
}

interface Vertex {
    x: number;
    y: number;
}

interface Normal {
    x: number;
    y: number;
}

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

// Canvas management object
const canvasContext = {
    clear() {
        ctx.save();
        ctx.setTransform(1, 0, 0, 1, 0, 0);
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        ctx.restore();
    },

    drawHexagon(vertices: Vertex[]) {
        ctx.save();
        ctx.translate(canvas.width / 2, canvas.height / 2);
        ctx.beginPath();
        ctx.moveTo(vertices[0].x, vertices[0].y);
        vertices.slice(1).forEach(v => ctx.lineTo(v.x, v.y));
        ctx.closePath();
        ctx.strokeStyle = '#000';
        ctx.stroke();
        ctx.restore();
    },

    drawBall(ball: Ball) {
        ctx.save();
        ctx.translate(canvas.width / 2, canvas.height / 2);
        ctx.beginPath();
        ctx.arc(ball.x, ball.y, BALL_RADIUS, 0, Math.PI * 2);
        ctx.fillStyle = '#f00';
        ctx.fill();
        ctx.restore();
    }
};

// Constants
const HEX_RADIUS = 200;
const BALL_RADIUS = 10;
const FRICTION = 0.3;

// State
let hexAngle = 0;
const ball: Ball = {
    x: 0,
    y: -HEX_RADIUS + BALL_RADIUS * 2,
    vx: 50,
    vy: -200
};

// Control elements
const rotationSpeedInput = document.getElementById('rotationSpeed') as HTMLInputElement;
const gravityInput = document.getElementById('gravity') as HTMLInputElement;
const bouncinessInput = document.getElementById('bounciness') as HTMLInputElement;
const rotationSpeedValue = document.getElementById('rotationSpeedValue')!;
const gravityValue = document.getElementById('gravityValue')!;
const bouncinessValue = document.getElementById('bouncinessValue')!;

// Event listeners
rotationSpeedInput.addEventListener('input', () => {
    rotationSpeedValue.textContent =...