Initial commit: Sirius en Guadeloupe - One page Svelte app

This commit is contained in:
sucupira
2025-07-09 14:42:31 -04:00
commit 1cbabf7ce6
24 changed files with 4841 additions and 0 deletions
+263
View File
@@ -0,0 +1,263 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Infographie 3D : La Vague Planétaire de Sirius</title>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&family=Playfair+Display:wght@700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #0a192f;
color: #ccd6f6;
overflow-x: hidden;
}
h1, h2, h3 {
font-family: 'Playfair Display', serif;
color: #ffffff;
}
#globe-container {
cursor: grab;
width: 100%;
max-width: 800px;
margin: auto;
aspect-ratio: 1 / 1;
}
#tooltip {
position: absolute;
display: none;
background-color: rgba(10, 25, 47, 0.85);
color: #ccd6f6;
padding: 8px 12px;
border-radius: 6px;
font-size: 0.9rem;
pointer-events: none;
border: 1px solid #303C55;
white-space: nowrap;
}
</style>
</head>
<body class="antialiased">
<div class="max-w-5xl mx-auto p-4 md:p-8">
<header class="text-center my-12">
<h1 class="text-3xl md:text-5xl font-bold tracking-tight">La Vague Planétaire de Sirius</h1>
<p class="text-lg md:text-xl mt-4 text-gray-400">Une animation 3D du lever héliaque autour du globe</p>
</header>
<section class="my-8">
<p class="text-center max-w-3xl mx-auto mb-8">
Interagissez avec le globe pour explorer la progression du lever héliaque de Sirius. L'onde lumineuse animée représente la "vague de visibilité" qui se déplace de l'hémisphère Sud (fin juin) vers le Nord (fin juillet). Survolez les points pour voir les détails de chaque lieu. Vous pouvez faire tourner le globe en cliquant et en déplaçant la souris.
</p>
<div id="globe-container" class="shadow-2xl rounded-full"></div>
<div id="tooltip"></div>
</section>
<section class="my-16">
<h2 class="text-2xl md:text-3xl font-bold text-center mb-6">Le Moteur du Phénomène : Latitude et Saisons</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8 max-w-4xl mx-auto">
<div class="bg-[#112240] p-6 rounded-lg border border-gray-700">
<h3 class="font-bold text-xl mb-2">Départ au Sud</h3>
<p class="text-gray-400">La vague commence dans l'hémisphère Sud car c'est l'hiver austral. Les nuits étant plus longues, le ciel reste sombre plus longtemps à l'aube, permettant à Sirius de se montrer plus tôt dans l'année (fin juin).</p>
</div>
<div class="bg-[#112240] p-6 rounded-lg border border-gray-700">
<h3 class="font-bold text-xl mb-2">Arrivée au Nord</h3>
<p class="text-gray-400">La vague atteint l'hémisphère Nord en plein été boréal. Les nuits sont plus courtes, il faut donc attendre plus longtemps (fin juillet) pour que le Soleil soit suffisamment loin sous l'horizon pour laisser Sirius apparaître.</p>
</div>
</div>
</section>
<footer class="text-center text-gray-500 text-sm mt-16 py-8 border-t border-gray-700">
<p>Animation 3D réalisée avec Three.js. Données basées sur le rapport des éphémérides.</p>
</footer>
</div>
<script>
let scene, camera, renderer, globe, raycaster, mouse;
let INTERSECTED;
const cities = [
{ name: 'La Réunion', lat: -20.88, lon: 55.45, date: '27 Juin' },
{ name: 'Kinshasa', lat: -4.44, lon: 15.26, date: '16 Juillet' },
{ name: 'Guadeloupe', lat: 16.26, lon: -61.55, date: '22 Juillet' },
{ name: 'Martinique', lat: 14.64, lon: -61.02, date: '23 Juillet' },
{ name: 'Dakar', lat: 14.71, lon: -17.46, date: '23 Juillet' },
{ name: 'Addis-Abeba', lat: 9.03, lon: 38.74, date: '26 Juillet' },
{ name: 'Abidjan', lat: 5.36, lon: -4.00, date: '28 Juillet' },
{ name: 'Lomé', lat: 6.13, lon: 1.21, date: '28 Juillet' },
{ name: 'Accra', lat: 5.60, lon: -0.18, date: '28 Juillet' },
{ name: 'Benin City', lat: 6.33, lon: 5.62, date: '28 Juillet' },
{ name: 'Juba', lat: 4.85, lon: 31.57, date: '28 Juillet' }
];
const cityMarkers = new THREE.Group();
const tooltip = document.getElementById('tooltip');
let wave;
function init() {
const container = document.getElementById('globe-container');
// Scene
scene = new THREE.Scene();
// Camera
camera = new THREE.PerspectiveCamera(45, container.clientWidth / container.clientHeight, 0.1, 1000);
camera.position.z = 3.5;
// Renderer
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(container.clientWidth, container.clientHeight);
renderer.setPixelRatio(window.devicePixelRatio);
container.appendChild(renderer.domElement);
// Lights
scene.add(new THREE.AmbientLight(0xcccccc, 0.5));
const dirLight = new THREE.DirectionalLight(0xffffff, 0.8);
dirLight.position.set(5, 3, 5);
scene.add(dirLight);
// Globe
const loader = new THREE.TextureLoader();
const texture = loader.load('https://raw.githubusercontent.com/jeromeetienne/threex.planets/master/images/earthmap1k.jpg', () => {
const globeGeometry = new THREE.SphereGeometry(1, 64, 64);
const globeMaterial = new THREE.MeshPhongMaterial({ map: texture, color: 0x99aaff });
globe = new THREE.Mesh(globeGeometry, globeMaterial);
scene.add(globe);
globe.add(cityMarkers);
plotCities();
});
// Animated Wave
const waveGeometry = new THREE.TorusGeometry(1.01, 0.05, 16, 100);
waveGeometry.rotateX(Math.PI / 2);
const waveMaterial = new THREE.MeshBasicMaterial({
color: 0x64ffda,
transparent: true,
opacity: 0.4,
side: THREE.DoubleSide
});
wave = new THREE.Mesh(waveGeometry, waveMaterial);
scene.add(wave);
// Raycaster
raycaster = new THREE.Raycaster();
mouse = new THREE.Vector2();
// Event Listeners
container.addEventListener('mousemove', onMouseMove, false);
container.addEventListener('mousedown', onMouseDown, false);
window.addEventListener('resize', onWindowResize, false);
}
function plotCities() {
cities.forEach(city => {
const latRad = city.lat * (Math.PI / 180);
const lonRad = -city.lon * (Math.PI / 180);
const x = Math.cos(latRad) * Math.cos(lonRad);
const y = Math.sin(latRad);
const z = Math.cos(latRad) * Math.sin(lonRad);
const markerGeometry = new THREE.SphereGeometry(0.015, 16, 16);
const markerMaterial = new THREE.MeshBasicMaterial({ color: 0x64ffda });
const marker = new THREE.Mesh(markerGeometry, markerMaterial);
marker.position.set(x, y, z);
marker.userData = { name: city.name, date: city.date };
cityMarkers.add(marker);
});
}
let isDragging = false;
let previousMousePosition = { x: 0, y: 0 };
function onMouseDown(event) {
isDragging = true;
}
function onMouseMove(event) {
const container = document.getElementById('globe-container');
const rect = container.getBoundingClientRect();
// For dragging
if (isDragging) {
const deltaMove = {
x: event.clientX - previousMousePosition.x,
y: event.clientY - previousMousePosition.y
};
const rotateAngleX = (deltaMove.y * 0.5) * (Math.PI / 180);
const rotateAngleY = (deltaMove.x * 0.5) * (Math.PI / 180);
globe.rotation.x += rotateAngleX;
globe.rotation.y += rotateAngleY;
}
previousMousePosition = { x: event.clientX, y: event.clientY };
// For raycasting
mouse.x = ((event.clientX - rect.left) / container.clientWidth) * 2 - 1;
mouse.y = -((event.clientY - rect.top) / container.clientHeight) * 2 + 1;
// For tooltip
tooltip.style.left = `${event.clientX + 15}px`;
tooltip.style.top = `${event.clientY + 15}px`;
}
document.addEventListener('mouseup', () => { isDragging = false; });
function onWindowResize() {
const container = document.getElementById('globe-container');
camera.aspect = container.clientWidth / container.clientHeight;
camera.updateProjectionMatrix();
renderer.setSize(container.clientWidth, container.clientHeight);
}
function animate() {
requestAnimationFrame(animate);
if (!isDragging) {
globe.rotation.y += 0.001;
}
// Animate the wave
const time = Date.now() * 0.0002;
// A sine wave moving from south pole (-PI/2) to north pole (PI/2)
const latAngle = Math.sin(time) * (Math.PI / 2);
wave.position.y = Math.sin(latAngle);
const scale = Math.cos(latAngle);
wave.scale.set(scale, 1, scale);
// Raycasting logic
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(cityMarkers.children);
if (intersects.length > 0) {
if (INTERSECTED != intersects[0].object) {
if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
INTERSECTED = intersects[0].object;
INTERSECTED.currentHex = INTERSECTED.material.emissive.getHex();
INTERSECTED.material.emissive.setHex(0xff0000);
tooltip.style.display = 'block';
tooltip.innerHTML = `<strong>${INTERSECTED.userData.name}</strong><br>${INTERSECTED.userData.date}`;
}
} else {
if (INTERSECTED) INTERSECTED.material.emissive.setHex(INTERSECTED.currentHex);
INTERSECTED = null;
tooltip.style.display = 'none';
}
renderer.render(scene, camera);
}
init();
animate();
</script>
</body>
</html>