<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Enhanced Gravity Simulation</title>
  <style>
    body {
      margin: 0;
      overflow: hidden;
      display: flex;
      flex-direction: column;
      align-items: center;
      background-color: #222;
      color: #fff;
    }
    #canvas {
      background-color: #000;
      cursor: crosshair;
    }
    #controls {
      display: flex;
      flex-wrap: wrap;
      justify-content: center;
      margin: 10px;
    }
    #controls > * {
      margin: 5px;
      padding: 5px;
      font-size: 16px;
      color: #fff;
      background-color: #333;
      border: none;
      border-radius: 3px;
    }
    #controls > input[type="range"], #controls > input[type="number"] {
      width: 150px;
    }
    #controls > label {
      margin-right: 0;
    }
  </style>
</head>
<body>

<div id="controls">
  <label for="gravityDirection">Gravity Direction:</label>
  <select id="gravityDirection">
    <option value="down">Down</option>
    <option value="up">Up</option>
    <option value="left">Left</option>
    <option value="right">Right</option>
  </select>
  <label for="gravityStrength">Gravity Strength:</label>
  <input type="range" id="gravityStrength" min="0" max="2" step="0.1" value="0.5">
  <label for="windResistance">Wind Resistance:</label>
  <input type="range" id="windResistance" min="0" max="0.05" step="0.005" value="0">

  <label for="elasticity">Elasticity:</label>
  <input type="range" id="elasticity" min="0" max="1" step="0.05" value="0.9">
  <label for="ballSize">Ball Size:</label>
  <input type="number" id="ballSize" min="10" max="50" value="20">
  <label for="ballColor">Ball Color:</label>
  <input type="color" id="ballColor" value="#ff0000">
  <button id="randomSize">Random Size</button>
  <button id="randomColor">Random Color</button>
  <button id="resetButton">Reset Simulation</button>
  <button id="saveConfig">Save Config</button>
  <button id="loadConfig">Load Config</button>
</div>
<canvas id="canvas"></canvas>

<script>
  // Get DOM elements
  const canvas = document.getElementById('canvas');
  const ctx = canvas.getContext('2d');
  const gravityDirectionSelect = document.getElementById('gravityDirection');
  const gravityStrengthInput = document.getElementById('gravityStrength');
  const windResistanceInput = document.getElementById('windResistance');
  const elasticityInput = document.getElementById('elasticity');
  const ballSizeInput = document.getElementById('ballSize');
  const ballColorInput = document.getElementById('ballColor');
  const randomSizeButton = document.getElementById('randomSize');
  const randomColorButton = document.getElementById('randomColor');
  const resetButton = document.getElementById('resetButton');
  const saveConfigButton = document.getElementById('saveConfig');
  const loadConfigButton = document.getElementById('loadConfig');
  const controls = document.getElementById('controls');

  // Set up canvas size
  function resizeCanvas() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight - controls.offsetHeight;
  }
  resizeCanvas();
  window.addEventListener('resize', resizeCanvas);

  // Variables
  let gravity = { x: 0, y: 0.5 }; // Default gravity direction: down
  let balls = [];

  // Ball class
  class Ball {
    constructor(x, y, radius, color, dx = 0, dy = 0, elasticity = 0.9) {
      this.x = x;
      this.y = y;
      this.radius = radius;
      this.color = color;
      this.dx = dx; // Horizontal velocity
      this.dy = dy; // Vertical velocity
      this.friction = 0.99; // Energy retention on collision
      this.elasticity = elasticity; // Bounciness
      this.mass = radius; // Mass proportional to radius
      this.windResistance = parseFloat(windResistanceInput.value); // Wind resistance
    }

    draw() {
      ctx.beginPath();
      ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
      ctx.fillStyle = this.color;
      ctx.fill();
      ctx.closePath();
    }

    update(balls) {
      // Apply wind resistance
      this.dx *= 1 - this.windResistance;
      this.dy *= 1 - this.windResistance;

      // Apply gravity
      this.dx += gravity.x * parseFloat(gravityStrengthInput.value);
      this.dy += gravity.y * parseFloat(gravityStrengthInput.value);

      // Next position
      this.x += this.dx;
      this.y += this.dy;

      // Collision with walls
      if (this.x + this.radius > canvas.width) {
        this.x = canvas.width - this.radius;
        this.dx = -this.dx * this.elasticity;
      }

      if (this.x - this.radius < 0) {
        this.x = this.radius;
        this.dx = -this.dx * this.elasticity;
      }

      // Collision with floor/ceiling
      if (this.y + this.radius > canvas.height) {
        this.y = canvas.height - this.radius;
        this.dy = -this.dy * this.elasticity;
      }

      if (this.y - this.radius < 0) {
        this.y = this.radius;
        this.dy = -this.dy * this.elasticity;
      }

      // Update wind resistance value
      this.windResistance = parseFloat(windResistanceInput.value);

      // Check collisions with other balls
      for (let i = 0; i < balls.length; i++) {
        if (this === balls[i]) continue;
        if (distanceBetween(this, balls[i]) < this.radius + balls[i].radius) {
          resolveCollision(this, balls[i]);
        }
      }

      this.draw();
    }

    isClicked(x, y) {
      return distanceBetween(this, { x, y }) <= this.radius;
    }
  }

  function distanceBetween(ball1, ball2) {
    const dx = ball2.x - ball1.x;
    const dy = ball2.y - ball1.y;
    return Math.hypot(dx, dy);
  }

  // Initialize balls
  function initBalls() {
    balls = [];
    for (let i = 0; i < 10; i++) {
      let radius = parseInt(ballSizeInput.value);
      let x = Math.random() * (canvas.width - radius * 2) + radius;
      let y = Math.random() * (canvas.height - radius * 2) + radius;
      let color = ballColorInput.value;
      let dx = (Math.random() - 0.5) * 6;
      let dy = (Math.random() - 0.5) * 6;
      let elasticity = parseFloat(elasticityInput.value);

      // Ensure balls are not overlapping
      if (i !== 0) {
        for (let j = 0; j < balls.length; j++) {
          if (distanceBetween({ x, y }, balls[j]) - radius * 2 < 0) {
            x = Math.random() * (canvas.width - radius * 2) + radius;
            y = Math.random() * (canvas.height - radius * 2) + radius;
            j = -1;
          }
        }
      }

      balls.push(new Ball(x, y, radius, color, dx, dy, elasticity));
    }
  }
  initBalls();

  // Animate
  function animate() {
    requestAnimationFrame(animate);

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

    // Update balls
    for (let i = 0; i < balls.length; i++) {
      balls[i].update(balls);
    }
  }
  animate();

  // UI Event Listeners
  gravityDirectionSelect.addEventListener('change', updateGravityDirection);
  gravityStrengthInput.addEventListener('input', () => {});
  windResistanceInput.addEventListener('input', () => {});
  elasticityInput.addEventListener('input', () => {});
  randomSizeButton.addEventListener('click', () => {
    ballSizeInput.value = Math.floor(Math.random() * 41) + 10; // Random size between 10 and 50
  });
  randomColorButton.addEventListener('click', () => {
    ballColorInput.value = '#' + Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0');
  });
  resetButton.addEventListener('click', () => {
    initBalls();
  });
  saveConfigButton.addEventListener('click', saveConfig);
  loadConfigButton.addEventListener('click', loadConfig);

  function updateGravityDirection() {
    let direction = gravityDirectionSelect.value;
    switch (direction) {
      case 'down':
        gravity = { x: 0, y: 1 };
        break;
      case 'up':
        gravity = { x: 0, y: -1 };
        break;
      case 'left':
        gravity = { x: -1, y: 0 };
        break;
      case 'right':
        gravity = { x: 1, y: 0 };
        break;
    }
  }
  updateGravityDirection();

  // Add balls on click
  let isMouseDown = false;
  canvas.addEventListener('mousedown', (e) => {
    isMouseDown = true;
    handleClick(e);
  });
  canvas.addEventListener('mousemove', (e) => {
    if (isMouseDown) {
      handleClick(e);
    }
  });
  canvas.addEventListener('mouseup', () => {
    isMouseDown = false;
  });

  function handleClick(e) {
    const ctrlKey = e.ctrlKey || e.metaKey;
    if (ctrlKey) {
      // Remove ball if Ctrl key is held
      removeBall(e);
    } else {
      // Add ball
      addBall(e);
    }
  }

  function addBall(e) {
    let rect = canvas.getBoundingClientRect();
    let x = e.clientX - rect.left;
    let y = e.clientY - rect.top;
    let radius = parseInt(ballSizeInput.value);
    let color = ballColorInput.value;
    let dx = (Math.random() - 0.5) * 6;
    let dy = (Math.random() - 0.5) * 6;
    let elasticity = parseFloat(elasticityInput.value);

    balls.push(new Ball(x, y, radius, color, dx, dy, elasticity));
  }

  function removeBall(e) {
    let rect = canvas.getBoundingClientRect();
    let x = e.clientX - rect.left;
    let y = e.clientY - rect.top;

    for (let i = balls.length - 1; i >= 0; i--) {
      if (balls[i].isClicked(x, y)) {
        balls.splice(i, 1);
        break;
      }
    }
  }

  // Collision resolution between balls
  function resolveCollision(ball1, ball2) {
    const xVelocityDiff = ball1.dx - ball2.dx;
    const yVelocityDiff = ball1.dy - ball2.dy;

    const xDist = ball2.x - ball1.x;
    const yDist = ball2.y - ball1.y;

    // Prevent accidental overlap of balls
    if (xVelocityDiff * xDist + yVelocityDiff * yDist >= 0) {

      // Calculate angle of collision
      const angle = -Math.atan2(ball2.y - ball1.y, ball2.x - ball1.x);

      // Store mass
      const m1 = ball1.mass;
      const m2 = ball2.mass;

      // Velocity before equation
      const u1 = rotate({ x: ball1.dx, y: ball1.dy }, angle);
      const u2 = rotate({ x: ball2.dx, y: ball2.dy }, angle);

      // Velocity after collision (one-dimensional)
      const v1 = { x: (u1.x * (m1 - m2) + 2 * m2 * u2.x) / (m1 + m2), y: u1.y };
      const v2 = { x: (u2.x * (m2 - m1) + 2 * m1 * u1.x) / (m1 + m2), y: u2.y };

      // Final velocity after rotating back to original coordinate system
      const vFinal1 = rotate(v1, -angle);
      const vFinal2 = rotate(v2, -angle);

      // Update velocities
      ball1.dx = vFinal1.x * ball1.elasticity;
      ball1.dy = vFinal1.y * ball1.elasticity;

      ball2.dx = vFinal2.x * ball2.elasticity;
      ball2.dy = vFinal2.y * ball2.elasticity;
    }
  }

  function rotate(velocity, angle) {
    return {
      x: velocity.x * Math.cos(angle) - velocity.y * Math.sin(angle),
      y: velocity.x * Math.sin(angle) + velocity.y * Math.cos(angle)
    };
  }

  // Save and Load Configurations
  function saveConfig() {
    const config = {
      gravityDirection: gravityDirectionSelect.value,
      gravityStrength: gravityStrengthInput.value,
      windResistance: windResistanceInput.value,
      elasticity: elasticityInput.value,
      ballSize: ballSizeInput.value,
      ballColor: ballColorInput.value,
      ballsData: balls.map(ball => ({
        x: ball.x,
        y: ball.y,
        radius: ball.radius,
        color: ball.color,
        dx: ball.dx,
        dy: ball.dy,
        elasticity: ball.elasticity
      }))
    };
    localStorage.setItem('simulationConfig', JSON.stringify(config));
    alert('Configuration saved!');
  }

  function loadConfig() {
    const config = JSON.parse(localStorage.getItem('simulationConfig'));
    if (config) {
      gravityDirectionSelect.value = config.gravityDirection;
      gravityStrengthInput.value = config.gravityStrength;
      windResistanceInput.value = config.windResistance;
      elasticityInput.value = config.elasticity;
      ballSizeInput.value = config.ballSize;
      ballColorInput.value = config.ballColor;
      balls = config.ballsData.map(data => new Ball(
        data.x, data.y, data.radius, data.color, data.dx, data.dy, data.elasticity
      ));
      updateGravityDirection();
      alert('Configuration loaded!');
    } else {
      alert('No saved configuration found.');
    }
  }

</script>

</body>
</html>