Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Virtual Spring Simulation</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/js/all.min.js"></script> | |
| <style> | |
| .spring-container { | |
| position: relative; | |
| height: 400px; | |
| overflow: hidden; | |
| } | |
| .spring { | |
| position: absolute; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| width: 8px; | |
| background-color: #3b82f6; | |
| border-radius: 4px; | |
| transition: height 0.1s ease-out; | |
| } | |
| .mass { | |
| position: absolute; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| width: 80px; | |
| height: 80px; | |
| background-color: #10b981; | |
| border-radius: 8px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: white; | |
| font-weight: bold; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| } | |
| .controls { | |
| background-color: #f3f4f6; | |
| border-radius: 12px; | |
| padding: 1.5rem; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); | |
| } | |
| .slider-container { | |
| display: flex; | |
| align-items: center; | |
| gap: 1rem; | |
| margin-bottom: 1rem; | |
| } | |
| .slider { | |
| flex-grow: 1; | |
| -webkit-appearance: none; | |
| height: 8px; | |
| border-radius: 4px; | |
| background: #d1d5db; | |
| outline: none; | |
| } | |
| .slider::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| appearance: none; | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: #3b82f6; | |
| cursor: pointer; | |
| } | |
| .slider::-moz-range-thumb { | |
| width: 20px; | |
| height: 20px; | |
| border-radius: 50%; | |
| background: #3b82f6; | |
| cursor: pointer; | |
| } | |
| .spring-anchor { | |
| position: absolute; | |
| top: 0; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| width: 100px; | |
| height: 20px; | |
| background-color: #6b7280; | |
| border-radius: 0 0 10px 10px; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-gray-100 min-h-screen"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <header class="text-center mb-8"> | |
| <h1 class="text-4xl font-bold text-indigo-700 mb-2">Virtual Spring Simulator</h1> | |
| <p class="text-gray-600">Explore Hooke's Law with this interactive spring-mass system</p> | |
| </header> | |
| <div class="grid grid-cols-1 lg:grid-cols-3 gap-8"> | |
| <div class="lg:col-span-2"> | |
| <div class="bg-white rounded-xl shadow-lg overflow-hidden"> | |
| <div class="spring-container relative"> | |
| <div class="spring-anchor"></div> | |
| <div id="spring" class="spring bg-blue-500"></div> | |
| <div id="mass" class="mass bg-emerald-500">1 kg</div> | |
| </div> | |
| <div class="p-4 bg-gray-50 border-t border-gray-200"> | |
| <button id="startBtn" class="bg-blue-600 hover:bg-blue-700 text-white px-4 py-2 rounded-lg mr-3 transition"> | |
| <i class="fas fa-play mr-2"></i> Start Oscillation | |
| </button> | |
| <button id="stopBtn" class="bg-gray-600 hover:bg-gray-700 text-white px-4 py-2 rounded-lg transition"> | |
| <i class="fas fa-stop mr-2"></i> Stop | |
| </button> | |
| <button id="resetBtn" class="bg-red-600 hover:bg-red-700 text-white px-4 py-2 rounded-lg float-right transition"> | |
| <i class="fas fa-undo mr-2"></i> Reset | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="controls"> | |
| <h2 class="text-xl font-semibold text-gray-800 mb-4">Spring Parameters</h2> | |
| <div class="slider-container"> | |
| <span class="text-gray-700 w-24">Mass (kg)</span> | |
| <input type="range" id="massSlider" class="slider" min="0.1" max="5" step="0.1" value="1"> | |
| <span id="massValue" class="text-gray-700 w-12">1.0</span> | |
| </div> | |
| <div class="slider-container"> | |
| <span class="text-gray-700 w-24">Spring Constant (N/m)</span> | |
| <input type="range" id="kSlider" class="slider" min="1" max="50" step="1" value="10"> | |
| <span id="kValue" class="text-gray-700 w-12">10</span> | |
| </div> | |
| <div class="slider-container"> | |
| <span class="text-gray-700 w-24">Damping</span> | |
| <input type="range" id="dampingSlider" class="slider" min="0" max="0.5" step="0.01" value="0.05"> | |
| <span id="dampingValue" class="text-gray-700 w-12">0.05</span> | |
| </div> | |
| <div class="slider-container"> | |
| <span class="text-gray-700 w-24">Initial Displacement (cm)</span> | |
| <input type="range" id="displacementSlider" class="slider" min="0" max="50" step="1" value="20"> | |
| <span id="displacementValue" class="text-gray-700 w-12">20</span> | |
| </div> | |
| <div class="mt-6 p-4 bg-indigo-50 rounded-lg"> | |
| <h3 class="font-medium text-indigo-800 mb-2">Physics Information</h3> | |
| <p class="text-sm text-gray-700 mb-1"><span class="font-medium">Natural Frequency:</span> <span id="frequency">1.58</span> Hz</p> | |
| <p class="text-sm text-gray-700 mb-1"><span class="font-medium">Period:</span> <span id="period">3.97</span> s</p> | |
| <p class="text-sm text-gray-700"><span class="font-medium">Current Position:</span> <span id="position">20.0</span> cm</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="mt-8 bg-white rounded-xl shadow-lg p-6"> | |
| <h2 class="text-2xl font-bold text-gray-800 mb-4">About Spring Systems</h2> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
| <div> | |
| <h3 class="text-lg font-semibold text-indigo-700 mb-2">Hooke's Law</h3> | |
| <p class="text-gray-600 mb-4">Hooke's Law states that the force exerted by a spring is proportional to its displacement from equilibrium: F = -kx, where k is the spring constant and x is the displacement.</p> | |
| <div class="bg-gray-100 p-3 rounded-lg text-center"> | |
| <p class="font-mono text-gray-800">F = -k × x</p> | |
| </div> | |
| </div> | |
| <div> | |
| <h3 class="text-lg font-semibold text-indigo-700 mb-2">Simple Harmonic Motion</h3> | |
| <p class="text-gray-600 mb-4">When a mass is attached to a spring, it exhibits simple harmonic motion. The period T of oscillation depends on the mass m and spring constant k:</p> | |
| <div class="bg-gray-100 p-3 rounded-lg text-center"> | |
| <p class="font-mono text-gray-800">T = 2π√(m/k)</p> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // DOM elements | |
| const spring = document.getElementById('spring'); | |
| const mass = document.getElementById('mass'); | |
| const startBtn = document.getElementById('startBtn'); | |
| const stopBtn = document.getElementById('stopBtn'); | |
| const resetBtn = document.getElementById('resetBtn'); | |
| // Sliders and values | |
| const massSlider = document.getElementById('massSlider'); | |
| const kSlider = document.getElementById('kSlider'); | |
| const dampingSlider = document.getElementById('dampingSlider'); | |
| const displacementSlider = document.getElementById('displacementSlider'); | |
| const massValue = document.getElementById('massValue'); | |
| const kValue = document.getElementById('kValue'); | |
| const dampingValue = document.getElementById('dampingValue'); | |
| const displacementValue = document.getElementById('displacementValue'); | |
| // Physics info | |
| const frequencyEl = document.getElementById('frequency'); | |
| const periodEl = document.getElementById('period'); | |
| const positionEl = document.getElementById('position'); | |
| // Simulation variables | |
| let animationId; | |
| let isOscillating = false; | |
| let equilibriumY = 150; // Equilibrium position in pixels | |
| let currentY = equilibriumY; | |
| let velocity = 0; | |
| let lastTime = 0; | |
| let initialDisplacement = 20; // cm | |
| // Constants | |
| const pixelsPerCm = 3; // Conversion factor | |
| const g = 9.81; // gravity in m/s² | |
| // Initialize the simulation | |
| function init() { | |
| updateParameters(); | |
| updatePhysicsInfo(); | |
| render(); | |
| } | |
| // Update parameters from sliders | |
| function updateParameters() { | |
| const mass = parseFloat(massSlider.value); | |
| const k = parseFloat(kSlider.value); | |
| const damping = parseFloat(dampingSlider.value); | |
| initialDisplacement = parseFloat(displacementSlider.value); | |
| massValue.textContent = mass.toFixed(1); | |
| kValue.textContent = k; | |
| dampingValue.textContent = damping.toFixed(2); | |
| displacementValue.textContent = initialDisplacement; | |
| // Reset position to initial displacement | |
| currentY = equilibriumY + initialDisplacement * pixelsPerCm; | |
| velocity = 0; | |
| updatePhysicsInfo(); | |
| render(); | |
| } | |
| // Update physics information display | |
| function updatePhysicsInfo() { | |
| const mass = parseFloat(massSlider.value); | |
| const k = parseFloat(kSlider.value); | |
| // Calculate natural frequency (rad/s) | |
| const omega = Math.sqrt(k / mass); | |
| // Convert to Hz | |
| const freq = omega / (2 * Math.PI); | |
| // Calculate period | |
| const period = 2 * Math.PI / omega; | |
| // Current position in cm (relative to equilibrium) | |
| const positionCm = (currentY - equilibriumY) / pixelsPerCm; | |
| frequencyEl.textContent = freq.toFixed(2); | |
| periodEl.textContent = period.toFixed(2); | |
| positionEl.textContent = positionCm.toFixed(1); | |
| } | |
| // Render the spring and mass | |
| function render() { | |
| // Spring height is from anchor to mass top | |
| const springHeight = currentY - 20; // 20 is anchor height | |
| spring.style.height = `${springHeight}px`; | |
| spring.style.top = '20px'; // Below anchor | |
| mass.style.top = `${currentY}px`; | |
| mass.textContent = `${parseFloat(massSlider.value).toFixed(1)} kg`; | |
| } | |
| // Animation loop | |
| function animate(timestamp) { | |
| if (!lastTime) lastTime = timestamp; | |
| const deltaTime = (timestamp - lastTime) / 1000; // Convert to seconds | |
| lastTime = timestamp; | |
| if (isOscillating) { | |
| // Get current parameters | |
| const mass = parseFloat(massSlider.value); | |
| const k = parseFloat(kSlider.value); | |
| const damping = parseFloat(dampingSlider.value); | |
| // Calculate acceleration (F = ma = -kx - damping*v) | |
| const displacement = (currentY - equilibriumY) / pixelsPerCm / 100; // Convert to meters | |
| const acceleration = (-k * displacement - damping * velocity) / mass; | |
| // Update velocity and position | |
| velocity += acceleration * deltaTime; | |
| currentY += velocity * pixelsPerCm * 100 * deltaTime; // Convert back to pixels | |
| updatePhysicsInfo(); | |
| render(); | |
| } | |
| animationId = requestAnimationFrame(animate); | |
| } | |
| // Event listeners | |
| startBtn.addEventListener('click', () => { | |
| if (!isOscillating) { | |
| isOscillating = true; | |
| lastTime = 0; | |
| animationId = requestAnimationFrame(animate); | |
| } | |
| }); | |
| stopBtn.addEventListener('click', () => { | |
| isOscillating = false; | |
| if (animationId) { | |
| cancelAnimationFrame(animationId); | |
| } | |
| }); | |
| resetBtn.addEventListener('click', () => { | |
| isOscillating = false; | |
| if (animationId) { | |
| cancelAnimationFrame(animationId); | |
| } | |
| updateParameters(); | |
| }); | |
| // Slider event listeners | |
| massSlider.addEventListener('input', updateParameters); | |
| kSlider.addEventListener('input', updateParameters); | |
| dampingSlider.addEventListener('input', updateParameters); | |
| displacementSlider.addEventListener('input', updateParameters); | |
| // Initialize | |
| init(); | |
| </script> | |
| <p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=amirpoorazima/virtual-spring" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> | |
| </html> |