JIT
Grid - Untitled
Create a box on the screen filled with 50 different, random fruit emojis. The emojis must have physics (matter.js style logic, but written from scratch): they fall, bounce off each other, and stack up. The user can pick them up and throw them with the mouse. Add a 'Mix' button that spins the container like a washing machine.
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Fruit Physics</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; }

body { width: 100vw; height: 100vh; overflow: hidden; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); display: flex; justify-content: center; align-items: center; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; }

#container { position: relative; width: 80vw; max-width: 800px; height: 70vh; max-height: 700px; background: rgba(255, 255, 255, 0.95); border: 8px solid #333; border-radius: 20px; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); overflow: hidden; }

#canvas { width: 100%; height: 100%; cursor: grab; }

#canvas:active { cursor: grabbing; }

#mixButton { position: absolute; bottom: 20px; left: 50%; transform: translateX(-50%); padding: 15px 40px; font-size: 20px; font-weight: bold; color: white; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); border: none; border-radius: 50px; cursor: pointer; box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2); transition: transform 0.2s, box-shadow 0.2s; z-index: 10; }

#mixButton:hover { transform: translateX(-50%) translateY(-2px); box-shadow: 0 12px 30px rgba(0, 0, 0, 0.3); }

#mixButton:active { transform: translateX(-50%) translateY(0); box-shadow: 0 6px 15px rgba(0, 0, 0, 0.2); } </style>

</head> <body> <div id="container"> <canvas id="canvas"></canvas> <button id="mixButton">πŸŒͺ️ Mix</button> </div> <script> const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); const container = document.getElementById('container'); const mixButton = document.getElementById('mixButton'); // Set canvas size function resizeCanvas() { canvas.width = container.clientWidth; canvas.height = container.clientHeight; } resizeCanvas(); window.addEventListener('resize', resizeCanvas); // Fruit emojis const fruits = ['🍎', '🍊', 'πŸ‹', '🍌', 'πŸ‰', 'πŸ‡', 'πŸ“', '🍈', 'πŸ’', 'πŸ‘', 'πŸ₯­', '🍍', 'πŸ₯₯', 'πŸ₯', 'πŸ…', 'πŸ₯‘']; // Physics constants const GRAVITY = 0.5; const DAMPING = 0.98; const RESTITUTION = 0.6; const FRICTION = 0.99; class Fruit { constructor(x, y) { this.x = x; this.y = y; this.vx = (Math.random() - 0.5) * 4; this.vy = Math.random() * -2; this.radius = 20 + Math.random() * 15; this.emoji = fruits[Math.floor(Math.random() * fruits.length)]; this.rotation = Math.random() * Math.PI * 2; this.angularVelocity = (Math.random() - 0.5) * 0.1; this.mass = this.radius / 20; this.isDragging = false; } update(width, height, gravity = GRAVITY) { if (this.isDragging) return; // Apply gravity this.vy += gravity; // Apply velocity this.x += this.vx; this.y += this.vy; // Apply damping this.vx *= DAMPING; this.vy *= DAMPING; // Rotation this.rotation += this.angularVelocity; this.angularVelocity *= FRICTION; // Wall collisions if (this.x - this.radius < 0) { this.x = this.radius; this.vx = Math.abs(this.vx) * RESTITUTION; this.angularVelocity = -this.vx * 0.1; } if (this.x + this.radius > width) { this.x = width - this.radius; this.vx = -Math.abs(this.vx) * RESTITUTION; this.angularVelocity = -this.vx * 0.1; } if (this.y - this.radius < 0) { this.y = this.radius; this.vy = Math.abs(this.vy) * RESTITUTION; } if (this.y + this.radius > height) { this.y = height - this.radius; this.vy = -Math.abs(this.vy) * RESTITUTION; this.vx *= FRICTION; this.angularVelocity *= 0.8; } } draw(ctx) { ctx.save(); ctx.translate(this.x, this.y); ctx.rotate(this.rotation); ctx.font = `${this.radius * 2}px Arial`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(this.emoji, 0, 0); ctx.restore(); } checkCollision(other) { const dx = other.x - this.x; const dy = other.y - this.y; const distance = Math.sqrt(dx * dx + dy * dy); const minDist = this.radius + other.radius; if (distance < minDist) { // Collision detected const angle = Math.atan2(dy, dx); const overlap = minDist - distance; // Separate fruits const separation = overlap / 2; this.x -= Math.cos(angle) * separation; this.y -= Math.sin(angle) * separation; other.x += Math.cos(angle) * separation; other.y += Math.sin(angle) * separation; // Calculate relative velocity const dvx = other.vx - this.vx; const dvy = other.vy - this.vy; // Velocity along collision normal const dvn = dvx * Math.cos(angle) + dvy * Math.sin(angle); // Don't resolve if velocities are separating if (dvn > 0) return; // Calculate impulse const impulse = (2 * dvn) / (this.mass + other.mass); // Apply impulse this.vx += impulse * other.mass * Math.cos(angle) * RESTITUTION; this.vy += impulse * other.mass * Math.sin(angle) * RESTITUTION; other.vx -= impulse * this.mass * Math.cos(angle) * RESTITUTION; other.vy -= impulse * this.mass * Math.sin(angle) * RESTITUTION; // Add some angular velocity this.angularVelocity += dvn * 0.05; other.angularVelocity -= dvn * 0.05; } } } // Create fruits const fruits_array = []; for (let i = 0; i < 50; i++) { const x = Math.random() * (canvas.width - 80) + 40; const y = Math.random() * (canvas.height / 2); fruits_array.push(new Fruit(x, y)); } // Mouse interaction let draggedFruit = null; let mouseX = 0; let mouseY = 0; let prevMouseX = 0; let prevMouseY = 0; canvas.addEventListener('mousedown', (e) => { const rect = canvas.getBoundingClientRect(); mouseX = e.clientX - rect.left; mouseY = e.clientY - rect.top; prevMouseX = mouseX; prevMouseY = mouseY; // Find fruit under mouse for (let i = fruits_array.length - 1; i >= 0; i--) { const fruit = fruits_array[i]; const dx = mouseX - fruit.x; const dy = mouseY - fruit.y; const distance = Math.sqrt(dx * dx + dy * dy); if (distance < fruit.radius) { draggedFruit = fruit; fruit.isDragging = true; fruit.vx = 0; fruit.vy = 0; break; } } }); canvas.addEventListener('mousemove', (e) => { const rect = canvas.getBoundingClientRect(); prevMouseX = mouseX; prevMouseY = mouseY; mouseX = e.clientX - rect.left; mouseY = e.clientY - rect.top; if (draggedFruit) { draggedFruit.x = mouseX; draggedFruit.y = mouseY; } }); canvas.addEventListener('mouseup', () => { if (draggedFruit) { draggedFruit.isDragging = false; // Apply throw velocity draggedFruit.vx = (mouseX - prevMouseX) * 1.5; draggedFruit.vy = (mouseY - prevMouseY) * 1.5; draggedFruit.angularVelocity = draggedFruit.vx * 0.05; draggedFruit = null; } }); canvas.addEventListener('mouseleave', () => { if (draggedFruit) { draggedFruit.isDragging = false; draggedFruit = null; } }); // Mix button let isMixing = false; let mixTime = 0; let mixRotation = 0; mixButton.addEventListener('click', () => { isMixing = true; mixTime = 0; mixRotation = 0; }); // Animation loop function animate() { ctx.clearRect(0, 0, canvas.width, canvas.height); let currentGravity = GRAVITY; if (isMixing) { mixTime++; mixRotation += 0.15; // Apply centrifugal forces const centerX = canvas.width / 2; const centerY = canvas.height / 2; fruits_array.forEach(fruit => { if (!fruit.isDragging) { const angle = Math.atan2(fruit.y - centerY, fruit.x - centerX) + mixRotation; const distance = Math.sqrt((fruit.x - centerX) ** 2 + (fruit.y - centerY) ** 2); const force = 15; fruit.vx += Math.cos(angle) * force; fruit.vy += Math.sin(angle) * force; } }); if (mixTime > 120) { isMixing = false; } } // Update fruits fruits_array.forEach(fruit => { fruit.update(canvas.width, canvas.height, currentGravity); }); // Check collisions for (let i = 0; i < fruits_array.length; i++) { for (let j = i + 1; j < fruits_array.length; j++) { fruits_array[i].checkCollision(fruits_array[j]); } } // Draw fruits fruits_array.forEach(fruit => { fruit.draw(ctx); }); requestAnimationFrame(animate); } animate(); </script> </body> </html>_
Updated files
v1