<script>
const fruitEmojis = ['🍎', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🫐', '🍈', '🍒',
'🍑', '🥭', '🍍', '🥥', '🥝', '🍅', '🍆', '🥑', '🥦', '🥬',
'🥒', '🌶️', '🫑', '🌽', '🥕', '🫒', '🧄', '🧅', '🍄', '🥜',
'🌰', '🍞', '🥐', '🥖', '🫓', '🥨', '🥯', '🥞', '🧇', '🧀',
'🍖', '🍗', '🥩', '🥓', '🍔', '🍟', '🍕', '🌭', '🥪', '🌮'];
class Fruit {
constructor(x, y, emoji) {
this.x = x;
this.y = y;
this.vx = (Math.random() - 0.5) * 2;
this.vy = 0;
this.emoji = emoji;
this.radius = 20;
this.mass = 1;
this.element = null;
this.isDragging = false;
this.dragOffsetX = 0;
this.dragOffsetY = 0;
this.lastMouseX = 0;
this.lastMouseY = 0;
this.mouseVelocityX = 0;
this.mouseVelocityY = 0;
}
createElement() {
const div = document.createElement('div');
div.className = 'fruit';
div.textContent = this.emoji;
div.style.left = this.x + 'px';
div.style.top = this.y + 'px';
this.element = div;
return div;
}
update(gravity, damping, bounds) {
if (!this.isDragging) {
// Apply gravity
this.vy += gravity;
// Apply damping
this.vx *= damping;
this.vy *= damping;
// Update position
this.x += this.vx;
this.y += this.vy;
// Bounce off walls
if (this.x - this.radius < 0) {
this.x = this.radius;
this.vx = Math.abs(this.vx) * 0.8;
}
if (this.x + this.radius > bounds.width) {
this.x = bounds.width - this.radius;
this.vx = -Math.abs(this.vx) * 0.8;
}
if (this.y + this.radius > bounds.height) {
this.y = bounds.height - this.radius;
this.vy = -Math.abs(this.vy) * 0.7;
// Stop tiny bounces
if (Math.abs(this.vy) < 0.5) {
this.vy = 0;
}
}
if (this.y - this.radius < 0) {
this.y = this.radius;
this.vy = Math.abs(this.vy) * 0.8;
}
}
// Update DOM element
if (this.element) {
this.element.style.left = (this.x - this.radius) + 'px';
this.element.style.top = (this.y - this.radius) + 'px';
}
}
checkCollision(other) {
const dx = other.x - this.x;
const dy = other.y - this.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const minDistance = this.radius + other.radius;
if (distance < minDistance) {
// Collision detected
const angle = Math.atan2(dy, dx);
const targetX = this.x + Math.cos(angle) * minDistance;
const targetY = this.y + Math.sin(angle) * minDistance;
const ax = (targetX - other.x) * 0.05;
const ay = (targetY - other.y) * 0.05;
// Apply forces
if (!this.isDragging) {
this.vx -= ax;
this.vy -= ay;
}
if (!other.isDragging) {
other.vx += ax;
other.vy += ay;
}
// Separate overlapping fruits
const overlap = minDistance - distance;
const separationX = (dx / distance) * overlap * 0.5;
const separationY = (dy / distance) * overlap * 0.5;
if (!this.isDragging) {
this.x -= separationX;
this.y -= separationY;
}
if (!other.isDragging) {
other.x += separationX;
other.y += separationY;
}
}
}
startDrag(mouseX, mouseY) {
this.isDragging = true;
this.dragOffsetX = mouseX - this.x;
this.dragOffsetY = mouseY - this.y;
this.lastMouseX = mouseX;
this.lastMouseY = mouseY;
this.element.classList.add('dragging');
}
drag(mouseX, mouseY) {
if (this.isDragging) {
// Calculate mouse velocity
this.mouseVelocityX = mouseX - this.lastMouseX;
this.mouseVelocityY = mouseY - this.lastMouseY;
// Update position
this.x = mouseX - this.dragOffsetX;
this.y = mouseY - this.dragOffsetY;
// Store last mouse position
this.lastMouseX = mouseX;
this.lastMouseY = mouseY;
}
}
endDrag() {
if (this.isDragging) {
this.isDragging = false;
this.vx = this.mouseVelocityX * 0.5;
this.vy = this.mouseVelocityY * 0.5;
this.element.classList.remove('dragging');
}
}
}
class PhysicsSimulation {
constructor(container) {
this.container = container;
this.fruits = [];
this.gravity = 0.3;
this.damping = 0.99;
this.bounds = { width: 0, height: 0 };
this.animationId = null;
this.draggedFruit = null;
this.isSpinning = false;
this.init();
}
init() {
this.updateBounds();
this.createFruits();
this.setupEventListeners();
this.animate();
}
updateBounds() {
const rect = this.container.getBoundingClientRect();
this.bounds.width = rect.width;
this.bounds.height = rect.height;
}
createFruits() {
this.container.innerHTML = '';
this.fruits = [];
for (let i = 0; i < 50; i++) {
const x = Math.random() * (this.bounds.width - 40) + 20;
const y = Math.random() * (this.bounds.height / 2);
const emoji = fruitEmojis[Math.floor(Math.random() * fruitEmojis.length)];
const fruit = new Fruit(x, y, emoji);
const element = fruit.createElement();
this.container.appendChild(element);
this.fruits.push(fruit);
}
}
setupEventListeners() {
// Mouse events
this.container.addEventListener('mousedown', (e) => this.handleMouseDown(e));
document.addEventListener('mousemove', (e) => this.handleMouseMove(e));
document.addEventListener('mouseup', () => this.handleMouseUp());
// Touch events
this.container.addEventListener('touchstart', (e) => this.handleTouchStart(e));
document.addEventListener('touchmove', (e) => this.handleTouchMove(e));
document.addEventListener('touchend', () => this.handleTouchEnd());
// Window resize
window.addEventListener('resize', () => this.updateBounds());
}
handleMouseDown(e) {
const rect = this.container.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
for (let fruit of this.fruits) {
const dx = mouseX - fruit.x;
const dy = mouseY - fruit.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < fruit.radius) {
this.draggedFruit = fruit;
fruit.startDrag(mouseX, mouseY);
break;
}
}
}
handleMouseMove(e) {
if (this.draggedFruit) {
const rect = this.container.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
this.draggedFruit.drag(mouseX, mouseY);
}
}
handleMouseUp() {
if (this.draggedFruit) {
this.draggedFruit.endDrag();
this.draggedFruit = null;
}
}
handleTouchStart(e) {
e.preventDefault();
const rect = this.container.getBoundingClientRect();
const touch = e.touches[0];
const touchX = touch.clientX - rect.left;
const touchY = touch.clientY - rect.top;
for (let fruit of this.fruits) {
const dx = touchX - fruit.x;
const dy = touchY - fruit.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < fruit.radius) {
this.draggedFruit = fruit;
fruit.startDrag(touchX, touchY);
break;
}
}
}
handleTouchMove(e) {
e.preventDefault();
if (this.draggedFruit) {
const rect = this.container.getBoundingClientRect();
const touch = e.touches[0];
const touchX = touch.clientX - rect.left;
const touchY = touch.clientY - rect.top;
this.draggedFruit.drag(touchX, touchY);
}
}
handleTouchEnd() {
if (this.draggedFruit) {
this.draggedFruit.endDrag();
this.draggedFruit = null;
}
}
animate() {
// Update physics
for (let fruit of this.fruits) {
fruit.update(this.gravity, this.damping, this.bounds);
}
// Check collisions
for (let i = 0; i < this.fruits.length; i++) {
for (let j = i + 1; j < this.fruits.length; j++) {
this.fruits[i].checkCollision(this.fruits[j]);
}
}
this.animationId = requestAnimationFrame(() => this.animate());
}
mix() {
if (this.isSpinning) return;
this.isSpinning = true;
this.container.classList.add('spinning');
// Add random forces to all fruits
this.fruits.forEach(fruit => {
fruit.vx = (Math.random() - 0.5) * 20;
fruit.vy = (Math.random() - 0.5) * 20;
});
setTimeout(() => {
this.container.classList.remove('spinning');
this.isSpinning = false;
}, 2000);
}
reset() {
this.createFruits();
}
destroy() {
if (this.animationId) {
cancelAnimationFrame(this.animationId);
}
}
}
// Initialize simulation
const container = document.getElementById('container');
const simulation = new PhysicsSimulation(container);
// Button controls
document.getElementById('mixBtn').addEventListener('click', () => {
simulation.mix();
});
document.getElementById('resetBtn').addEventListener('click', () => {
simulation.reset();
});
</script>
<script>
const fruitEmojis = ['🍎', '🍊', '🍋', '🍌', '🍉', '🍇', '🍓', '🫐', '🍈', '🍒',
'🍑', '🥭', '🍍', '🥥', '🥝', '🍅', '🍆', '🥑', '🥦', '🥬',
'🥒', '🌶️', '🫑', '🌽', '🥕', '🫒', '🧄', '🧅', '🍄', '🥜',
'🌰', '🍞', '🥐', '🥖', '🫓', '🥨', '🥯', '🥞', '🧇', '🧀',
'🍖', '🍗', '🥩', '🥓', '🍔', '🍟', '🍕', '🌭', '🥪', '🌮'];
class Fruit {
constructor(x, y, emoji) {
this.x = x;
this.y = y;
this.vx = (Math.random() - 0.5) * 2;
this.vy = 0;
this.emoji = emoji;
this.radius = 20;
this.mass = 1;
this.element = null;
this.isDragging = false;
this.dragOffsetX = 0;
this.dragOffsetY = 0;
this.lastMouseX = 0;
this.lastMouseY = 0;
this.mouseVelocityX = 0;
this.mouseVelocityY = 0;
}
createElement() {
const div = document.createElement('div');
div.className = 'fruit';
div.textContent = this.emoji;
div.style.left = this.x + 'px';
div.style.top = this.y + 'px';
this.element = div;
return div;
}
update(gravity, damping, bounds) {
if (!this.isDragging) {
// Apply gravity
this.vy += gravity;
// Apply damping
this.vx *= damping;
this.vy *= damping;
// Update position
this.x += this.vx;
this.y += this.vy;
// Bounce off walls
if (this.x - this.radius < 0) {
this.x = this.radius;
this.vx = Math.abs(this.vx) * 0.8;
}
if (this.x + this.radius > bounds.width) {
this.x = bounds.width - this.radius;
this.vx = -Math.abs(this.vx) * 0.8;
}
if (this.y + this.radius > bounds.height) {
this.y = bounds.height - this.radius;
this.vy = -Math.abs(this.vy) * 0.7;
// Stop tiny bounces
if (Math.abs(this.vy) < 0.5) {
this.vy = 0;
}
}
if (this.y - this.radius < 0) {
this.y = this.radius;
this.vy = Math.abs(this.vy) * 0.8;
}
}
// Update DOM element
if (this.element) {
this.element.style.left = (this.x - this.radius) + 'px';
this.element.style.top = (this.y - this.radius) + 'px';
}
}
checkCollision(other) {
const dx = other.x - this.x;
const dy = other.y - this.y;
const distance = Math.sqrt(dx * dx + dy * dy);
const minDistance = this.radius + other.radius;
if (distance < minDistance) {
// Collision detected
const angle = Math.atan2(dy, dx);
const targetX = this.x + Math.cos(angle) * minDistance;
const targetY = this.y + Math.sin(angle) * minDistance;
const ax = (targetX - other.x) * 0.05;
const ay = (targetY - other.y) * 0.05;
// Apply forces
if (!this.isDragging) {
this.vx -= ax;
this.vy -= ay;
}
if (!other.isDragging) {
other.vx += ax;
other.vy += ay;
}
// Separate overlapping fruits
const overlap = minDistance - distance;
const separationX = (dx / distance) * overlap * 0.5;
const separationY = (dy / distance) * overlap * 0.5;
if (!this.isDragging) {
this.x -= separationX;
this.y -= separationY;
}
if (!other.isDragging) {
other.x += separationX;
other.y += separationY;
}
}
}
startDrag(mouseX, mouseY) {
this.isDragging = true;
this.dragOffsetX = mouseX - this.x;
this.dragOffsetY = mouseY - this.y;
this.lastMouseX = mouseX;
this.lastMouseY = mouseY;
this.element.classList.add('dragging');
}
drag(mouseX, mouseY) {
if (this.isDragging) {
// Calculate mouse velocity
this.mouseVelocityX = mouseX - this.lastMouseX;
this.mouseVelocityY = mouseY - this.lastMouseY;
// Update position
this.x = mouseX - this.dragOffsetX;
this.y = mouseY - this.dragOffsetY;
// Store last mouse position
this.lastMouseX = mouseX;
this.lastMouseY = mouseY;
}
}
endDrag() {
if (this.isDragging) {
this.isDragging = false;
this.vx = this.mouseVelocityX * 0.5;
this.vy = this.mouseVelocityY * 0.5;
this.element.classList.remove('dragging');
}
}
}
class PhysicsSimulation {
constructor(container) {
this.container = container;
this.fruits = [];
this.gravity = 0.3;
this.damping = 0.99;
this.bounds = { width: 0, height: 0 };
this.animationId = null;
this.draggedFruit = null;
this.isSpinning = false;
this.init();
}
init() {
this.updateBounds();
this.createFruits();
this.setupEventListeners();
this.animate();
}
updateBounds() {
const rect = this.container.getBoundingClientRect();
this.bounds.width = rect.width;
this.bounds.height = rect.height;
}
createFruits() {
this.container.innerHTML = '';
this.fruits = [];
for (let i = 0; i < 50; i++) {
const x = Math.random() * (this.bounds.width - 40) + 20;
const y = Math.random() * (this.bounds.height / 2);
const emoji = fruitEmojis[Math.floor(Math.random() * fruitEmojis.length)];
const fruit = new Fruit(x, y, emoji);
const element = fruit.createElement();
this.container.appendChild(element);
this.fruits.push(fruit);
}
}
setupEventListeners() {
// Mouse events
this.container.addEventListener('mousedown', (e) => this.handleMouseDown(e));
document.addEventListener('mousemove', (e) => this.handleMouseMove(e));
document.addEventListener('mouseup', () => this.handleMouseUp());
// Touch events
this.container.addEventListener('touchstart', (e) => this.handleTouchStart(e));
document.addEventListener('touchmove', (e) => this.handleTouchMove(e));
document.addEventListener('touchend', () => this.handleTouchEnd());
// Window resize
window.addEventListener('resize', () => this.updateBounds());
}
handleMouseDown(e) {
const rect = this.container.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
for (let fruit of this.fruits) {
const dx = mouseX - fruit.x;
const dy = mouseY - fruit.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < fruit.radius) {
this.draggedFruit = fruit;
fruit.startDrag(mouseX, mouseY);
break;
}
}
}
handleMouseMove(e) {
if (this.draggedFruit) {
const rect = this.container.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const mouseY = e.clientY - rect.top;
this.draggedFruit.drag(mouseX, mouseY);
}
}
handleMouseUp() {
if (this.draggedFruit) {
this.draggedFruit.endDrag();
this.draggedFruit = null;
}
}
handleTouchStart(e) {
e.preventDefault();
const rect = this.container.getBoundingClientRect();
const touch = e.touches[0];
const touchX = touch.clientX - rect.left;
const touchY = touch.clientY - rect.top;
for (let fruit of this.fruits) {
const dx = touchX - fruit.x;
const dy = touchY - fruit.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < fruit.radius) {
this.draggedFruit = fruit;
fruit.startDrag(touchX, touchY);
break;
}
}
}
handleTouchMove(e) {
e.preventDefault();
if (this.draggedFruit) {
const rect = this.container.getBoundingClientRect();
const touch = e.touches[0];
const touchX = touch.clientX - rect.left;
const touchY = touch.clientY - rect.top;
this.draggedFruit.drag(touchX, touchY);
}
}
handleTouchEnd() {
if (this.draggedFruit) {
this.draggedFruit.endDrag();
this.draggedFruit = null;
}
}
animate() {
// Update physics
for (let fruit of this.fruits) {
fruit.update(this.gravity, this.damping, this.bounds);
}
// Check collisions
for (let i = 0; i < this.fruits.length; i++) {
for (let j = i + 1; j < this.fruits.length; j++) {
this.fruits[i].checkCollision(this.fruits[j]);
}
}
this.animationId = requestAnimationFrame(() => this.animate());
}
mix() {
if (this.isSpinning) return;
this.isSpinning = true;
this.container.classList.add('spinning');
// Add random forces to all fruits
this.fruits.forEach(fruit => {
fruit.vx = (Math.random() - 0.5) * 20;
fruit.vy = (Math.random() - 0.5) * 20;
});
setTimeout(() => {
this.container.classList.remove('spinning');
this.isSpinning = false;
}, 2000);
}
reset() {
this.createFruits();
}
destroy() {
if (this.animationId) {
cancelAnimationFrame(this.animationId);
}
}
}
// Initialize simulation
const container = document.getElementById('container');
const simulation = new PhysicsSimulation(container);
// Button controls
document.getElementById('mixBtn').addEventListener('click', () => {
simulation.mix();
});
document.getElementById('resetBtn').addEventListener('click', () => {
simulation.reset();
});
</script>