simulation-de-gravit / index.html
SaidAmchghal's picture
Add 2 files
6255610 verified
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gravity Simulator - Comparaison interplanétaire</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.planet {
transition: transform 0.3s ease, box-shadow 0.3s ease;
will-change: transform;
}
.planet:hover {
transform: scale(1.05);
box-shadow: 0 10px 25px rgba(0,0,0,0.2);
}
.gravity-well {
position: absolute;
border-radius: 50%;
opacity: 0.2;
transform: translate(-50%, -50%);
}
#simulationCanvas {
background: radial-gradient(circle at center, #1a202c 0%, #0f172a 100%);
border-radius: 12px;
box-shadow: inset 0 0 20px rgba(0,0,0,0.5);
}
.object {
position: absolute;
border-radius: 50%;
transform: translate(-50%, -50%);
}
@keyframes float {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-10px); }
}
.floating {
animation: float 3s ease-in-out infinite;
}
.planet-stats {
position: absolute;
background: rgba(30, 41, 59, 0.8);
border-radius: 8px;
padding: 8px;
font-size: 12px;
pointer-events: none;
transform: translateX(-50%);
}
.fall-data {
position: absolute;
bottom: 10px;
left: 50%;
transform: translateX(-50%);
background: rgba(30, 41, 59, 0.8);
border-radius: 8px;
padding: 8px;
font-size: 12px;
text-align: center;
min-width: 120px;
}
</style>
</head>
<body class="bg-gray-900 text-gray-100 min-h-screen">
<div class="container mx-auto px-4 py-8">
<header class="mb-10 text-center">
<h1 class="text-4xl md:text-5xl font-bold mb-2 bg-gradient-to-r from-blue-400 to-purple-600 bg-clip-text text-transparent">
Gravity Simulator
</h1>
<p class="text-xl text-gray-300 max-w-2xl mx-auto">
Comparez les effets de la gravité sur différentes planètes en temps réel
</p>
</header>
<div class="grid grid-cols-1 lg:grid-cols-3 gap-8 mb-10">
<!-- Contrôles -->
<div class="bg-gray-800 p-6 rounded-xl shadow-lg col-span-1">
<h2 class="text-2xl font-semibold mb-4 flex items-center">
<i class="fas fa-sliders-h mr-2 text-blue-400"></i> Paramètres
</h2>
<div class="mb-6">
<label class="block text-gray-300 mb-2">Planètes à comparer</label>
<div class="grid grid-cols-2 gap-2">
<button id="earthBtn" class="planet-btn bg-blue-600 hover:bg-blue-700 text-white py-2 px-4 rounded flex items-center justify-center">
<i class="fas fa-globe-americas mr-2"></i> Terre
</button>
<button id="moonBtn" class="planet-btn bg-gray-400 hover:bg-gray-500 text-gray-900 py-2 px-4 rounded flex items-center justify-center">
<i class="fas fa-moon mr-2"></i> Lune
</button>
<button id="marsBtn" class="planet-btn bg-red-600 hover:bg-red-700 text-white py-2 px-4 rounded flex items-center justify-center">
<i class="fas fa-globe mr-2"></i> Mars
</button>
<button id="jupiterBtn" class="planet-btn bg-yellow-600 hover:bg-yellow-700 text-white py-2 px-4 rounded flex items-center justify-center">
<i class="fas fa-globe mr-2"></i> Jupiter
</button>
</div>
</div>
<div class="mb-6">
<label class="block text-gray-300 mb-2">Objet à lâcher</label>
<select id="objectType" class="w-full bg-gray-700 border border-gray-600 rounded py-2 px-3 text-white focus:outline-none focus:ring-2 focus:ring-blue-500">
<option value="ball">Balle (1kg)</option>
<option value="feather">Plume (0.01kg)</option>
<option value="hammer">Marteau (5kg)</option>
<option value="car">Voiture (1000kg)</option>
</select>
</div>
<div class="mb-6">
<label class="block text-gray-300 mb-2">Hauteur de chute</label>
<input id="heightSlider" type="range" min="50" max="300" value="150" class="w-full h-2 bg-gray-700 rounded-lg appearance-none cursor-pointer">
<div class="flex justify-between text-sm text-gray-400">
<span>50m</span>
<span id="heightValue">150m</span>
<span>300m</span>
</div>
</div>
<div class="flex space-x-3">
<button id="startBtn" class="flex-1 bg-green-600 hover:bg-green-700 text-white py-2 px-4 rounded flex items-center justify-center">
<i class="fas fa-play mr-2"></i> Démarrer
</button>
<button id="resetBtn" class="flex-1 bg-gray-600 hover:bg-gray-700 text-white py-2 px-4 rounded flex items-center justify-center">
<i class="fas fa-redo mr-2"></i> Réinitialiser
</button>
</div>
</div>
<!-- Simulation -->
<div class="bg-gray-800 p-6 rounded-xl shadow-lg col-span-2">
<div class="flex justify-between items-center mb-4">
<h2 class="text-2xl font-semibold flex items-center">
<i class="fas fa-atom mr-2 text-purple-400"></i> Simulation
</h2>
<div class="text-sm bg-gray-700 px-3 py-1 rounded-full flex items-center">
<i class="fas fa-info-circle mr-1 text-blue-400"></i>
<span id="timeScale">Temps réel</span>
</div>
</div>
<div class="relative h-96 w-full" id="simulationContainer">
<canvas id="simulationCanvas" class="w-full h-full"></canvas>
<!-- Conteneurs pour les planètes sélectionnées -->
<div id="planetsContainer" class="absolute inset-0"></div>
<!-- Affichage des résultats -->
<div id="resultsPanel" class="absolute bottom-4 left-4 bg-gray-900 bg-opacity-80 p-3 rounded-lg hidden">
<div class="grid grid-cols-3 gap-4 text-sm">
<div>
<div class="text-gray-400">Temps de chute:</div>
<div id="fallTime" class="font-mono">0.00s</div>
</div>
<div>
<div class="text-gray-400">Vitesse finale:</div>
<div id="finalSpeed" class="font-mono">0.00 m/s</div>
</div>
<div>
<div class="text-gray-400">Force gravitationnelle:</div>
<div id="gravForce" class="font-mono">0.00 N</div>
</div>
</div>
</div>
</div>
<div class="mt-4 grid grid-cols-1 md:grid-cols-3 gap-4" id="planetStats">
<!-- Les statistiques des planètes seront ajoutées ici dynamiquement -->
</div>
</div>
</div>
<!-- Tableau comparatif -->
<div class="bg-gray-800 p-6 rounded-xl shadow-lg mb-10">
<h2 class="text-2xl font-semibold mb-4 flex items-center">
<i class="fas fa-table mr-2 text-green-400"></i> Données planétaires
</h2>
<div class="overflow-x-auto">
<table class="w-full text-left">
<thead class="bg-gray-700">
<tr>
<th class="py-3 px-4">Planète</th>
<th class="py-3 px-4">Gravité (m/s²)</th>
<th class="py-3 px-4">Masse (×10²⁴ kg)</th>
<th class="py-3 px-4">Rayon (km)</th>
<th class="py-3 px-4">Temps de chute de 100m</th>
<th class="py-3 px-4">Vitesse finale (100m)</th>
</tr>
</thead>
<tbody id="planetData">
<!-- Les données seront ajoutées dynamiquement -->
</tbody>
</table>
</div>
</div>
<!-- Explications scientifiques -->
<div class="bg-gray-800 p-6 rounded-xl shadow-lg">
<h2 class="text-2xl font-semibold mb-4 flex items-center">
<i class="fas fa-book-open mr-2 text-yellow-400"></i> Science de la gravité
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="bg-gray-700 p-4 rounded-lg">
<h3 class="text-lg font-medium mb-2 text-blue-300">Loi de la gravitation universelle</h3>
<p class="text-gray-300 mb-3">
La force gravitationnelle entre deux objets est donnée par la formule:
</p>
<div class="bg-gray-800 p-3 rounded font-mono text-center mb-3">
F = G × (m₁ × m₂) / r²
</div>
<p class="text-gray-300">
Où F est la force, G la constante gravitationnelle (6.674×10⁻¹¹ N·m²/kg²),
m₁ et m₂ les masses des objets, et r la distance entre leurs centres.
</p>
</div>
<div class="bg-gray-700 p-4 rounded-lg">
<h3 class="text-lg font-medium mb-2 text-purple-300">Mouvement des corps en chute libre</h3>
<p class="text-gray-300 mb-3">
Pour un objet en chute libre près de la surface d'une planète:
</p>
<div class="bg-gray-800 p-3 rounded font-mono text-center mb-3">
v = √(2 × g × h)<br>
t = √(2 × h / g)
</div>
<p class="text-gray-300">
Où v est la vitesse finale, g l'accélération gravitationnelle,
h la hauteur de chute, et t le temps de chute.
</p>
</div>
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Constantes physiques
const G = 6.67430e-11; // Constante gravitationnelle
const PLANETS = {
earth: { name: "Terre", color: "#3b82f6", g: 9.81, mass: 5.972, radius: 6371, emoji: "🌍" },
moon: { name: "Lune", color: "#9ca3af", g: 1.62, mass: 0.07346, radius: 1737, emoji: "🌕" },
mars: { name: "Mars", color: "#ef4444", g: 3.71, mass: 0.64171, radius: 3389, emoji: "♂️" },
jupiter: { name: "Jupiter", color: "#f59e0b", g: 24.79, mass: 1898.19, radius: 69911, emoji: "♃" }
};
const OBJECTS = {
ball: { name: "Balle", mass: 1, color: "#ffffff", radius: 10 },
feather: { name: "Plume", mass: 0.01, color: "#f3f4f6", radius: 5 },
hammer: { name: "Marteau", mass: 5, color: "#a1a1aa", radius: 12 },
car: { name: "Voiture", mass: 1000, color: "#60a5fa", radius: 15 }
};
// État de la simulation
let selectedPlanets = [];
let simulationRunning = false;
let animationId = null;
let startTime = null;
let objects = [];
let currentHeight = 150;
// Éléments DOM
const simulationContainer = document.getElementById('simulationContainer');
const simulationCanvas = document.getElementById('simulationCanvas');
const planetsContainer = document.getElementById('planetsContainer');
const planetStats = document.getElementById('planetStats');
const planetData = document.getElementById('planetData');
const heightSlider = document.getElementById('heightSlider');
const heightValue = document.getElementById('heightValue');
const objectType = document.getElementById('objectType');
const startBtn = document.getElementById('startBtn');
const resetBtn = document.getElementById('resetBtn');
const resultsPanel = document.getElementById('resultsPanel');
const fallTime = document.getElementById('fallTime');
const finalSpeed = document.getElementById('finalSpeed');
const gravForce = document.getElementById('gravForce');
// Initialisation du canvas
const ctx = simulationCanvas.getContext('2d');
simulationCanvas.width = simulationContainer.clientWidth;
simulationCanvas.height = simulationContainer.clientHeight;
// Gestionnaires d'événements
document.querySelectorAll('.planet-btn').forEach(btn => {
btn.addEventListener('click', function() {
const planetId = this.id.replace('Btn', '');
togglePlanetSelection(planetId);
});
});
heightSlider.addEventListener('input', function() {
currentHeight = parseInt(this.value);
heightValue.textContent = `${currentHeight}m`;
updateFallData();
});
startBtn.addEventListener('click', startSimulation);
resetBtn.addEventListener('click', resetSimulation);
// Fonctions
function togglePlanetSelection(planetId) {
const index = selectedPlanets.indexOf(planetId);
const btn = document.getElementById(`${planetId}Btn`);
if (index === -1) {
if (selectedPlanets.length < 3) {
selectedPlanets.push(planetId);
btn.classList.add('ring-2', 'ring-blue-400');
renderPlanetSelection();
updatePlanetDataTable();
updateFallData();
}
} else {
selectedPlanets.splice(index, 1);
btn.classList.remove('ring-2', 'ring-blue-400');
renderPlanetSelection();
updatePlanetDataTable();
updateFallData();
}
}
function renderPlanetSelection() {
planetsContainer.innerHTML = '';
planetStats.innerHTML = '';
if (selectedPlanets.length === 0) {
planetsContainer.innerHTML = `
<div class="absolute inset-0 flex items-center justify-center text-gray-500">
<div class="text-center">
<i class="fas fa-globe-europe text-5xl mb-4"></i>
<p>Sélectionnez au moins une planète pour commencer</p>
</div>
</div>
`;
return;
}
const containerWidth = simulationContainer.clientWidth;
const planetWidth = containerWidth / selectedPlanets.length;
selectedPlanets.forEach((planetId, index) => {
const planet = PLANETS[planetId];
const left = (index * planetWidth) + (planetWidth / 2);
// Création de la représentation visuelle de la planète
const planetEl = document.createElement('div');
planetEl.className = `planet absolute top-1/2 flex flex-col items-center`;
planetEl.style.left = `${left}px`;
planetEl.style.transform = 'translate(-50%, -50%)';
planetEl.innerHTML = `
<div class="w-16 h-16 rounded-full shadow-lg mb-2" style="background-color: ${planet.color}">
<div class="w-full h-full flex items-center justify-center text-2xl">
${planet.emoji}
</div>
</div>
<div class="text-sm font-medium">${planet.name}</div>
<div class="text-xs text-gray-400">${planet.g}m/s²</div>
`;
planetsContainer.appendChild(planetEl);
// Création du puits de gravité (effet visuel)
const gravityWell = document.createElement('div');
gravityWell.className = 'gravity-well';
gravityWell.style.width = `${planetWidth * 0.8}px`;
gravityWell.style.height = `${planetWidth * 0.8}px`;
gravityWell.style.left = `${left}px`;
gravityWell.style.top = '50%';
gravityWell.style.backgroundColor = planet.color;
planetsContainer.appendChild(gravityWell);
// Ajout des données de chute sous chaque planète
const fallDataEl = document.createElement('div');
fallDataEl.className = 'fall-data';
fallDataEl.style.left = `${left}px`;
fallDataEl.style.color = planet.color;
fallDataEl.innerHTML = `
<div>Hauteur: ${currentHeight}m</div>
<div>Temps: ${calculateFallTime(currentHeight, planet.g).toFixed(2)}s</div>
<div>Vitesse: ${calculateFinalSpeed(currentHeight, planet.g).toFixed(2)}m/s</div>
`;
fallDataEl.id = `fall-data-${planetId}`;
planetsContainer.appendChild(fallDataEl);
// Ajout des statistiques de la planète
const statsEl = document.createElement('div');
statsEl.className = 'bg-gray-700 p-4 rounded-lg';
statsEl.innerHTML = `
<div class="flex items-center mb-2">
<div class="w-6 h-6 rounded-full mr-2" style="background-color: ${planet.color}"></div>
<h3 class="font-medium">${planet.name}</h3>
</div>
<div class="grid grid-cols-2 gap-2 text-sm">
<div>
<div class="text-gray-400">Gravité:</div>
<div>${planet.g} m/s²</div>
</div>
<div>
<div class="text-gray-400">Masse:</div>
<div>${planet.mass} ×10²⁴ kg</div>
</div>
<div>
<div class="text-gray-400">Rayon:</div>
<div>${planet.radius} km</div>
</div>
<div>
<div class="text-gray-400">Poids de l'objet:</div>
<div id="weight-${planetId}">0 N</div>
</div>
</div>
`;
planetStats.appendChild(statsEl);
});
}
function calculateFallTime(height, gravity) {
return Math.sqrt(2 * height / gravity);
}
function calculateFinalSpeed(height, gravity) {
return Math.sqrt(2 * gravity * height);
}
function updateFallData() {
selectedPlanets.forEach(planetId => {
const planet = PLANETS[planetId];
const fallDataEl = document.getElementById(`fall-data-${planetId}`);
if (fallDataEl) {
fallDataEl.innerHTML = `
<div>Hauteur: ${currentHeight}m</div>
<div>Temps: ${calculateFallTime(currentHeight, planet.g).toFixed(2)}s</div>
<div>Vitesse: ${calculateFinalSpeed(currentHeight, planet.g).toFixed(2)}m/s</div>
`;
}
});
}
function updatePlanetDataTable() {
planetData.innerHTML = '';
Object.keys(PLANETS).forEach(planetId => {
const planet = PLANETS[planetId];
const isSelected = selectedPlanets.includes(planetId);
const fallTime100m = calculateFallTime(100, planet.g);
const finalSpeed100m = calculateFinalSpeed(100, planet.g);
const row = document.createElement('tr');
row.className = isSelected ? 'bg-gray-700' : 'border-b border-gray-700';
row.innerHTML = `
<td class="py-3 px-4 flex items-center">
${isSelected ? `<span class="w-2 h-2 rounded-full mr-2" style="background-color: ${planet.color}"></span>` : ''}
${planet.name}
</td>
<td class="py-3 px-4">${planet.g}</td>
<td class="py-3 px-4">${planet.mass}</td>
<td class="py-3 px-4">${planet.radius}</td>
<td class="py-3 px-4">${fallTime100m.toFixed(2)} s</td>
<td class="py-3 px-4">${finalSpeed100m.toFixed(2)} m/s</td>
`;
planetData.appendChild(row);
});
}
function startSimulation() {
if (selectedPlanets.length === 0 || simulationRunning) return;
simulationRunning = true;
startBtn.disabled = true;
startTime = Date.now();
objects = [];
const height = currentHeight;
const object = OBJECTS[objectType.value];
// Créer un objet pour chaque planète sélectionnée
selectedPlanets.forEach((planetId, index) => {
const planet = PLANETS[planetId];
const containerWidth = simulationContainer.clientWidth;
const planetWidth = containerWidth / selectedPlanets.length;
const planetX = (index * planetWidth) + (planetWidth / 2);
const obj = {
planetId,
x: planetX,
y: 50,
vy: 0,
mass: object.mass,
radius: object.radius,
color: object.color,
planetX,
planetY: simulationContainer.clientHeight / 2,
g: planet.g,
height,
falling: false,
startTime: null,
fallDuration: null,
finalSpeed: null,
statsEl: null
};
// Créer l'élément pour afficher les stats en temps réel
obj.statsEl = document.createElement('div');
obj.statsEl.className = 'planet-stats';
obj.statsEl.style.left = `${planetX}px`;
obj.statsEl.style.top = '20px';
obj.statsEl.style.color = planet.color;
obj.statsEl.innerHTML = `
<div>Temps: <span class="time-value">0.00</span>s</div>
<div>Vitesse: <span class="speed-value">0.00</span>m/s</div>
`;
planetsContainer.appendChild(obj.statsEl);
objects.push(obj);
// Mettre à jour le poids affiché
const weightEl = document.getElementById(`weight-${planetId}`);
if (weightEl) {
weightEl.textContent = `${(object.mass * planet.g).toFixed(2)} N`;
}
});
// Démarrer l'animation
animate();
// Afficher le panneau de résultats après un délai
setTimeout(() => {
resultsPanel.classList.remove('hidden');
}, 500);
}
function animate() {
animationId = requestAnimationFrame(animate);
const now = Date.now();
const elapsedTime = (now - startTime) / 1000;
// Effacer le canvas
ctx.clearRect(0, 0, simulationCanvas.width, simulationCanvas.height);
// Dessiner les étoiles en arrière-plan
drawStars();
// Mettre à jour et dessiner chaque objet
objects.forEach(obj => {
if (!obj.falling && elapsedTime > 1) {
obj.falling = true;
obj.startTime = now;
}
if (obj.falling) {
// Calculer la position en fonction de la gravité
const fallTime = (now - obj.startTime) / 1000;
// Formule de chute libre: y = y0 + 0.5 * g * t^2
// On ajuste l'échelle pour que la chute soit visible à l'écran
const scaleFactor = (simulationCanvas.height / 2 - 50) / obj.height;
obj.y = 50 + 0.5 * obj.g * fallTime * fallTime * scaleFactor;
// Vitesse: v = g * t
obj.vy = obj.g * fallTime;
// Mettre à jour les stats en temps réel
if (obj.statsEl) {
obj.statsEl.querySelector('.time-value').textContent = fallTime.toFixed(2);
obj.statsEl.querySelector('.speed-value').textContent = obj.vy.toFixed(2);
}
// Vérifier si l'objet a atteint le sol
if (obj.y >= obj.planetY - 30) {
obj.y = obj.planetY - 30;
obj.falling = false;
obj.fallDuration = fallTime;
obj.finalSpeed = obj.vy;
// Mettre à jour l'affichage des résultats
updateResults();
}
}
// Dessiner l'objet
ctx.beginPath();
ctx.arc(obj.x, obj.y, obj.radius, 0, Math.PI * 2);
ctx.fillStyle = obj.color;
ctx.fill();
ctx.closePath();
// Dessiner une ombre
ctx.beginPath();
ctx.ellipse(obj.x, obj.y + obj.radius + 2, obj.radius, obj.radius/3, 0, 0, Math.PI * 2);
ctx.fillStyle = 'rgba(0,0,0,0.3)';
ctx.fill();
ctx.closePath();
});
}
function drawStars() {
ctx.fillStyle = 'white';
// Dessiner des étoiles aléatoires
for (let i = 0; i < 50; i++) {
const x = Math.random() * simulationCanvas.width;
const y = Math.random() * simulationCanvas.height;
const radius = Math.random() * 1.5;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fill();
}
}
function updateResults() {
if (objects.length === 0) return;
// Trouver l'objet avec le temps de chute le plus long
const longestFall = objects.reduce((prev, current) => {
return (prev.fallDuration || 0) > (current.fallDuration || 0) ? prev : current;
});
if (longestFall.fallDuration) {
fallTime.textContent = `${longestFall.fallDuration.toFixed(2)}s`;
finalSpeed.textContent = `${longestFall.finalSpeed.toFixed(2)} m/s`;
// Calculer la force gravitationnelle (poids)
const planet = PLANETS[longestFall.planetId];
const object = OBJECTS[objectType.value];
const force = object.mass * planet.g;
gravForce.textContent = `${force.toFixed(2)} N`;
}
}
function resetSimulation() {
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
}
simulationRunning = false;
startBtn.disabled = false;
// Supprimer les éléments de stats
objects.forEach(obj => {
if (obj.statsEl && obj.statsEl.parentNode) {
obj.statsEl.parentNode.removeChild(obj.statsEl);
}
});
objects = [];
// Effacer le canvas
ctx.clearRect(0, 0, simulationCanvas.width, simulationCanvas.height);
// Cacher le panneau de résultats
resultsPanel.classList.add('hidden');
// Réinitialiser les valeurs affichées
fallTime.textContent = '0.00s';
finalSpeed.textContent = '0.00 m/s';
gravForce.textContent = '0.00 N';
// Mettre à jour les poids à 0
selectedPlanets.forEach(planetId => {
const weightEl = document.getElementById(`weight-${planetId}`);
if (weightEl) {
weightEl.textContent = '0 N';
}
});
}
// Initialisation
updatePlanetDataTable();
// Redimensionnement responsive
window.addEventListener('resize', function() {
simulationCanvas.width = simulationContainer.clientWidth;
simulationCanvas.height = simulationContainer.clientHeight;
renderPlanetSelection();
});
});
</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=SaidAmchghal/simulation-de-gravit" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>