Initial commit: Sirius en Guadeloupe - One page Svelte app
This commit is contained in:
@@ -0,0 +1,635 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="fr">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vague Planétaire de Sirius - Lever Héliaque 2025</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: hidden;
|
||||
background: radial-gradient(circle at center, #0a192f 0%, #020c1b 100%);
|
||||
font-family: 'Arial', sans-serif;
|
||||
color: #ccd6f6;
|
||||
}
|
||||
|
||||
/* Contrôles */
|
||||
.controls {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%);
|
||||
color: #0a192f;
|
||||
border: none;
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 4px 15px rgba(255, 215, 0, 0.3);
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(255, 215, 0, 0.5);
|
||||
}
|
||||
|
||||
.btn.secondary {
|
||||
background: linear-gradient(135deg, #64ffda 0%, #64b5f6 100%);
|
||||
box-shadow: 0 4px 15px rgba(100, 255, 218, 0.3);
|
||||
}
|
||||
|
||||
/* Tooltip amélioré */
|
||||
#tooltip {
|
||||
position: absolute;
|
||||
padding: 12px 16px;
|
||||
background: rgba(10, 25, 47, 0.95);
|
||||
color: #ccd6f6;
|
||||
border-radius: 8px;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
z-index: 100;
|
||||
font-size: 0.9rem;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.5);
|
||||
border: 1px solid rgba(255, 215, 0, 0.3);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
#tooltip strong {
|
||||
color: #ffd700;
|
||||
font-size: 1.1rem;
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
#tooltip .date {
|
||||
color: #64ffda;
|
||||
}
|
||||
|
||||
/* Info Panel */
|
||||
.info-panel {
|
||||
position: fixed;
|
||||
bottom: 1rem;
|
||||
left: 1rem;
|
||||
background: rgba(10, 25, 47, 0.9);
|
||||
padding: 1.5rem;
|
||||
border-radius: 12px;
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 215, 0, 0.2);
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.info-panel h2 {
|
||||
color: #ffd700;
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.info-panel p {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
color: #8892b0;
|
||||
}
|
||||
|
||||
/* Loading */
|
||||
.loading {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
text-align: center;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.loading-spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 3px solid rgba(255, 215, 0, 0.3);
|
||||
border-top-color: #ffd700;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
margin-top: 1rem;
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
/* Légende */
|
||||
.legend {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
left: 1rem;
|
||||
background: rgba(10, 25, 47, 0.9);
|
||||
padding: 1rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(255, 215, 0, 0.2);
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.legend h3 {
|
||||
color: #ffd700;
|
||||
font-size: 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin: 0.3rem 0;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.legend-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
/* Mobile responsive */
|
||||
@media (max-width: 768px) {
|
||||
.info-panel {
|
||||
max-width: 200px;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.legend {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.controls {
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.5rem 1rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Loading Screen -->
|
||||
<div class="loading" id="loading">
|
||||
<div class="loading-spinner"></div>
|
||||
<div class="loading-text">Chargement de la carte stellaire...</div>
|
||||
</div>
|
||||
|
||||
<!-- Controls -->
|
||||
<div class="controls">
|
||||
<button class="btn" id="modeBtn">Mode Manuel</button>
|
||||
<button class="btn secondary" id="waveBtn">Afficher Vague</button>
|
||||
<button class="btn secondary" id="resetBtn">Réinitialiser</button>
|
||||
</div>
|
||||
|
||||
<!-- Tooltip -->
|
||||
<div id="tooltip"></div>
|
||||
|
||||
<!-- Legend -->
|
||||
<div class="legend">
|
||||
<h3>Lever Héliaque de Sirius</h3>
|
||||
<div class="legend-item">
|
||||
<div class="legend-icon" style="background: #ffd700;"></div>
|
||||
<span>Sites d'observation</span>
|
||||
</div>
|
||||
<div class="legend-item">
|
||||
<div class="legend-icon" style="background: #64ffda;"></div>
|
||||
<span>Vague de Sirius</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Info Panel -->
|
||||
<div class="info-panel">
|
||||
<h2>Wep Ronpet 2025</h2>
|
||||
<p>Le lever héliaque de Sirius marque le Nouvel An égyptien. Cette visualisation montre la progression de ce phénomène à travers le monde.</p>
|
||||
</div>
|
||||
|
||||
<!-- Three.js -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
||||
<script>
|
||||
// Configuration
|
||||
const config = {
|
||||
globeRadius: 1,
|
||||
markerSize: 0.08,
|
||||
waveSpeed: 0.001,
|
||||
autoRotateSpeed: 0.0005,
|
||||
cameraDistance: 3.2,
|
||||
colors: {
|
||||
marker: 0xffd700,
|
||||
wave: 0x64ffda,
|
||||
glow: 0xffd700,
|
||||
atmosphere: 0x64b5f6
|
||||
}
|
||||
};
|
||||
|
||||
// Initialize Three.js
|
||||
const renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
alpha: true,
|
||||
powerPreference: "high-performance"
|
||||
});
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
|
||||
renderer.shadowMap.enabled = true;
|
||||
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
||||
document.body.appendChild(renderer.domElement);
|
||||
|
||||
// Scene setup
|
||||
const scene = new THREE.Scene();
|
||||
scene.fog = new THREE.Fog(0x020c1b, 8, 20);
|
||||
|
||||
// Camera
|
||||
const camera = new THREE.PerspectiveCamera(
|
||||
45,
|
||||
window.innerWidth / window.innerHeight,
|
||||
0.1,
|
||||
1000
|
||||
);
|
||||
camera.position.z = config.cameraDistance;
|
||||
|
||||
// Lights
|
||||
const ambientLight = new THREE.AmbientLight(0x404040, 1.5);
|
||||
scene.add(ambientLight);
|
||||
|
||||
const sunLight = new THREE.DirectionalLight(0xffffff, 1);
|
||||
sunLight.position.set(5, 3, 5);
|
||||
sunLight.castShadow = true;
|
||||
scene.add(sunLight);
|
||||
|
||||
const backLight = new THREE.DirectionalLight(0x64b5f6, 0.3);
|
||||
backLight.position.set(-5, -3, -5);
|
||||
scene.add(backLight);
|
||||
|
||||
// Stars background
|
||||
const starsGeometry = new THREE.BufferGeometry();
|
||||
const starsCount = 5000;
|
||||
const starsPositions = new Float32Array(starsCount * 3);
|
||||
|
||||
for(let i = 0; i < starsCount * 3; i += 3) {
|
||||
const radius = 50 + Math.random() * 450;
|
||||
const theta = Math.random() * Math.PI * 2;
|
||||
const phi = Math.acos(Math.random() * 2 - 1);
|
||||
|
||||
starsPositions[i] = radius * Math.sin(phi) * Math.cos(theta);
|
||||
starsPositions[i + 1] = radius * Math.sin(phi) * Math.sin(theta);
|
||||
starsPositions[i + 2] = radius * Math.cos(phi);
|
||||
}
|
||||
|
||||
starsGeometry.setAttribute('position', new THREE.BufferAttribute(starsPositions, 3));
|
||||
|
||||
const starsMaterial = new THREE.PointsMaterial({
|
||||
color: 0xffffff,
|
||||
size: 0.5,
|
||||
transparent: true,
|
||||
opacity: 0.8,
|
||||
blending: THREE.AdditiveBlending
|
||||
});
|
||||
|
||||
const stars = new THREE.Points(starsGeometry, starsMaterial);
|
||||
scene.add(stars);
|
||||
|
||||
// Globe group
|
||||
const globeGroup = new THREE.Group();
|
||||
scene.add(globeGroup);
|
||||
|
||||
// Texture loader with error handling
|
||||
const loader = new THREE.TextureLoader();
|
||||
const textureUrls = {
|
||||
earth: 'https://raw.githubusercontent.com/jeromeetienne/threex.planets/master/images/earthmap1k.jpg',
|
||||
earthBump: 'https://raw.githubusercontent.com/jeromeetienne/threex.planets/master/images/earthbump1k.jpg',
|
||||
earthSpec: 'https://raw.githubusercontent.com/jeromeetienne/threex.planets/master/images/earthspec1k.jpg'
|
||||
};
|
||||
|
||||
// Sites data with accurate coordinates
|
||||
const sites = [
|
||||
{name: 'La Réunion', lat: -21.1151, lon: 55.5364, date: '27 Juin', time: '04:50', order: 1},
|
||||
{name: 'Kinshasa', lat: -4.4419, lon: 15.2663, date: '16 Juillet', time: '05:20', order: 2},
|
||||
{name: 'Guadeloupe', lat: 16.2650, lon: -61.5510, date: '22 Juillet', time: '05:10', order: 3},
|
||||
{name: 'Martinique', lat: 14.6415, lon: -61.0242, date: '23 Juillet', time: '05:08', order: 4},
|
||||
{name: 'Dakar', lat: 14.7167, lon: -17.4677, date: '23 Juillet', time: '05:15', order: 5},
|
||||
{name: 'Abidjan', lat: 5.3600, lon: -4.0083, date: '28 Juillet', time: '05:25', order: 6},
|
||||
{name: 'Lomé', lat: 6.1375, lon: 1.2123, date: '28 Juillet', time: '05:22', order: 7},
|
||||
{name: 'Accra', lat: 5.6037, lon: -0.1870, date: '28 Juillet', time: '05:23', order: 8},
|
||||
{name: 'Cotonou', lat: 6.3703, lon: 2.3912, date: '28 Juillet', time: '05:24', order: 9},
|
||||
{name: 'Lagos', lat: 6.5244, lon: 3.3792, date: '28 Juillet', time: '05:26', order: 10},
|
||||
{name: 'Yaoundé', lat: 3.8480, lon: 11.5021, date: '29 Juillet', time: '05:28', order: 11},
|
||||
{name: 'Libreville', lat: 0.4162, lon: 9.4673, date: '29 Juillet', time: '05:30', order: 12},
|
||||
{name: 'Brazzaville', lat: -4.2634, lon: 15.2429, date: '29 Juillet', time: '05:32', order: 13},
|
||||
{name: 'Kampala', lat: 0.3476, lon: 32.5825, date: '29 Juillet', time: '05:35', order: 14},
|
||||
{name: 'Nairobi', lat: -1.2921, lon: 36.8219, date: '30 Juillet', time: '05:37', order: 15},
|
||||
{name: 'Addis-Abeba', lat: 9.0320, lon: 38.7466, date: '26 Juillet', time: '05:18', order: 16},
|
||||
{name: 'Le Caire', lat: 30.0444, lon: 31.2357, date: '3 Août', time: '05:00', order: 17},
|
||||
{name: 'Paris', lat: 48.8566, lon: 2.3522, date: '12 Août', time: '05:45', order: 18}
|
||||
];
|
||||
|
||||
// Load textures and create globe
|
||||
Promise.all([
|
||||
new Promise((resolve, reject) => {
|
||||
loader.load(textureUrls.earth, resolve, undefined, reject);
|
||||
}),
|
||||
new Promise((resolve, reject) => {
|
||||
loader.load(textureUrls.earthBump, resolve, undefined, reject);
|
||||
}),
|
||||
new Promise((resolve, reject) => {
|
||||
loader.load(textureUrls.earthSpec, resolve, undefined, reject);
|
||||
})
|
||||
]).then(([earthMap, earthBump, earthSpec]) => {
|
||||
// Hide loading
|
||||
document.getElementById('loading').style.display = 'none';
|
||||
|
||||
// Create globe
|
||||
const globeGeometry = new THREE.SphereGeometry(config.globeRadius, 64, 64);
|
||||
const globeMaterial = new THREE.MeshPhongMaterial({
|
||||
map: earthMap,
|
||||
bumpMap: earthBump,
|
||||
bumpScale: 0.05,
|
||||
specularMap: earthSpec,
|
||||
specular: new THREE.Color(0x444444),
|
||||
shininess: 20
|
||||
});
|
||||
|
||||
const globe = new THREE.Mesh(globeGeometry, globeMaterial);
|
||||
globe.castShadow = true;
|
||||
globe.receiveShadow = true;
|
||||
globeGroup.add(globe);
|
||||
|
||||
// Atmosphere
|
||||
const atmosphereGeometry = new THREE.SphereGeometry(config.globeRadius * 1.1, 32, 32);
|
||||
const atmosphereMaterial = new THREE.ShaderMaterial({
|
||||
vertexShader: `
|
||||
varying vec3 vNormal;
|
||||
void main() {
|
||||
vNormal = normalize(normalMatrix * normal);
|
||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
varying vec3 vNormal;
|
||||
void main() {
|
||||
float intensity = pow(0.7 - dot(vNormal, vec3(0.0, 0.0, 1.0)), 2.0);
|
||||
gl_FragColor = vec4(0.3, 0.6, 1.0, 1.0) * intensity;
|
||||
}
|
||||
`,
|
||||
blending: THREE.AdditiveBlending,
|
||||
side: THREE.BackSide
|
||||
});
|
||||
|
||||
const atmosphere = new THREE.Mesh(atmosphereGeometry, atmosphereMaterial);
|
||||
globeGroup.add(atmosphere);
|
||||
|
||||
// Wave system
|
||||
const waveGroup = new THREE.Group();
|
||||
globeGroup.add(waveGroup);
|
||||
|
||||
// Create wave rings
|
||||
const waveRings = [];
|
||||
for(let i = 0; i < 3; i++) {
|
||||
const radius = 1.01 + (i * 0.02); // Légèrement au-dessus du globe
|
||||
const waveGeometry = new THREE.TorusGeometry(radius, 0.05, 16, 100);
|
||||
waveGeometry.rotateX(Math.PI / 2); // Rotation pour que le torus soit horizontal
|
||||
|
||||
const waveMaterial = new THREE.MeshBasicMaterial({
|
||||
color: config.colors.wave,
|
||||
transparent: true,
|
||||
opacity: 0.4 - (i * 0.1),
|
||||
side: THREE.DoubleSide
|
||||
});
|
||||
|
||||
const wave = new THREE.Mesh(waveGeometry, waveMaterial);
|
||||
waveRings.push(wave);
|
||||
waveGroup.add(wave);
|
||||
}
|
||||
|
||||
// Markers
|
||||
const markersGroup = new THREE.Group();
|
||||
globeGroup.add(markersGroup);
|
||||
|
||||
// Create custom marker geometry (simplified ankh shape)
|
||||
const markerShape = new THREE.Shape();
|
||||
markerShape.moveTo(0, -0.5);
|
||||
markerShape.lineTo(0, 0.2);
|
||||
markerShape.moveTo(-0.3, 0);
|
||||
markerShape.lineTo(0.3, 0);
|
||||
markerShape.moveTo(0, 0.2);
|
||||
markerShape.absarc(0, 0.3, 0.2, Math.PI, 0, true);
|
||||
|
||||
const markerGeometry = new THREE.ShapeGeometry(markerShape);
|
||||
|
||||
// Add markers for each site
|
||||
sites.forEach(site => {
|
||||
// Conversion identique à l'exemple
|
||||
const latRad = site.lat * (Math.PI / 180);
|
||||
const lonRad = -site.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);
|
||||
|
||||
// Marker group
|
||||
const markerGroup = new THREE.Group();
|
||||
|
||||
// Simple sphere marker comme dans l'exemple
|
||||
const markerGeometry = new THREE.SphereGeometry(0.015, 16, 16);
|
||||
const markerMaterial = new THREE.MeshBasicMaterial({
|
||||
color: config.colors.marker,
|
||||
emissive: 0x000000
|
||||
});
|
||||
const marker = new THREE.Mesh(markerGeometry, markerMaterial);
|
||||
|
||||
// Glow effect
|
||||
const glowGeometry = new THREE.SphereGeometry(0.025, 16, 16);
|
||||
const glowMaterial = new THREE.MeshBasicMaterial({
|
||||
color: config.colors.glow,
|
||||
transparent: true,
|
||||
opacity: 0.3,
|
||||
blending: THREE.AdditiveBlending
|
||||
});
|
||||
const glow = new THREE.Mesh(glowGeometry, glowMaterial);
|
||||
|
||||
markerGroup.add(marker);
|
||||
markerGroup.add(glow);
|
||||
|
||||
// Position directement sur le globe (rayon = 1)
|
||||
markerGroup.position.set(x, y, z);
|
||||
|
||||
markerGroup.userData = site;
|
||||
markersGroup.add(markerGroup);
|
||||
});
|
||||
|
||||
// Animation variables
|
||||
let autoRotate = true;
|
||||
let showWave = true;
|
||||
let mouseX = 0, mouseY = 0;
|
||||
let targetRotationX = 0, targetRotationY = 0;
|
||||
|
||||
// Controls
|
||||
const modeBtn = document.getElementById('modeBtn');
|
||||
const waveBtn = document.getElementById('waveBtn');
|
||||
const resetBtn = document.getElementById('resetBtn');
|
||||
|
||||
modeBtn.addEventListener('click', () => {
|
||||
autoRotate = !autoRotate;
|
||||
modeBtn.textContent = autoRotate ? 'Mode Manuel' : 'Mode Auto';
|
||||
});
|
||||
|
||||
waveBtn.addEventListener('click', () => {
|
||||
showWave = !showWave;
|
||||
waveGroup.visible = showWave;
|
||||
waveBtn.textContent = showWave ? 'Masquer Vague' : 'Afficher Vague';
|
||||
});
|
||||
|
||||
resetBtn.addEventListener('click', () => {
|
||||
targetRotationX = 0;
|
||||
targetRotationY = 0;
|
||||
camera.position.z = config.cameraDistance;
|
||||
});
|
||||
|
||||
// Mouse interaction
|
||||
let isDragging = false;
|
||||
const raycaster = new THREE.Raycaster();
|
||||
const mouse = new THREE.Vector2();
|
||||
const tooltip = document.getElementById('tooltip');
|
||||
|
||||
renderer.domElement.addEventListener('mousedown', () => {
|
||||
isDragging = true;
|
||||
renderer.domElement.style.cursor = 'grabbing';
|
||||
});
|
||||
|
||||
window.addEventListener('mouseup', () => {
|
||||
isDragging = false;
|
||||
renderer.domElement.style.cursor = 'grab';
|
||||
});
|
||||
|
||||
window.addEventListener('mousemove', (event) => {
|
||||
if (isDragging && !autoRotate) {
|
||||
targetRotationY += event.movementX * 0.005;
|
||||
targetRotationX += event.movementY * 0.005;
|
||||
targetRotationX = Math.max(-Math.PI/2, Math.min(Math.PI/2, targetRotationX));
|
||||
}
|
||||
|
||||
// Update mouse position for raycasting
|
||||
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
||||
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
||||
});
|
||||
|
||||
// Click detection
|
||||
renderer.domElement.addEventListener('click', (event) => {
|
||||
raycaster.setFromCamera(mouse, camera);
|
||||
const intersects = raycaster.intersectObjects(markersGroup.children, true);
|
||||
|
||||
if (intersects.length > 0) {
|
||||
const marker = intersects[0].object.parent;
|
||||
if (marker.userData && marker.userData.name) {
|
||||
const data = marker.userData;
|
||||
tooltip.innerHTML = `
|
||||
<strong>${data.name}</strong>
|
||||
<span class="date">${data.date} à ${data.time}</span>
|
||||
<br><small>Ordre: ${data.order}/${sites.length}</small>
|
||||
`;
|
||||
tooltip.style.left = event.clientX + 10 + 'px';
|
||||
tooltip.style.top = event.clientY - 30 + 'px';
|
||||
tooltip.style.display = 'block';
|
||||
|
||||
setTimeout(() => {
|
||||
tooltip.style.display = 'none';
|
||||
}, 3000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Zoom with mouse wheel
|
||||
renderer.domElement.addEventListener('wheel', (event) => {
|
||||
event.preventDefault();
|
||||
camera.position.z += event.deltaY * 0.002;
|
||||
camera.position.z = Math.max(2, Math.min(5, camera.position.z));
|
||||
});
|
||||
|
||||
// Animation loop
|
||||
function animate() {
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
// Auto-rotate
|
||||
if (autoRotate) {
|
||||
targetRotationY += config.autoRotateSpeed;
|
||||
}
|
||||
|
||||
// Smooth rotation
|
||||
globeGroup.rotation.y += (targetRotationY - globeGroup.rotation.y) * 0.1;
|
||||
globeGroup.rotation.x += (targetRotationX - globeGroup.rotation.x) * 0.1;
|
||||
|
||||
// Animate wave
|
||||
if (showWave) {
|
||||
const time = Date.now() * 0.0002; // Même vitesse que l'exemple
|
||||
|
||||
// Oscillation entre sud et nord comme dans l'exemple
|
||||
const latAngle = Math.sin(time) * (Math.PI / 2);
|
||||
|
||||
waveRings.forEach((ring, i) => {
|
||||
const offset = i * 0.05; // Petit décalage entre les anneaux
|
||||
|
||||
// Position Y basée sur le sinus de l'angle de latitude
|
||||
ring.position.y = Math.sin(latAngle + offset);
|
||||
|
||||
// Scale basé sur le cosinus pour maintenir la forme circulaire
|
||||
const scale = Math.cos(latAngle + offset);
|
||||
ring.scale.set(scale, 1, scale);
|
||||
|
||||
// Opacité proportionnelle à l'échelle
|
||||
ring.material.opacity = (0.4 - (i * 0.1)) * Math.max(0.2, Math.abs(scale));
|
||||
});
|
||||
}
|
||||
|
||||
// Rotate stars slowly
|
||||
stars.rotation.y += 0.0001;
|
||||
|
||||
// Animate markers
|
||||
markersGroup.children.forEach((marker, i) => {
|
||||
const time = Date.now() * 0.001;
|
||||
const offset = i * 0.2;
|
||||
const scale = 1 + Math.sin(time + offset) * 0.1;
|
||||
marker.scale.setScalar(scale);
|
||||
});
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
animate();
|
||||
|
||||
}).catch(error => {
|
||||
console.error('Error loading textures:', error);
|
||||
document.getElementById('loading').innerHTML = `
|
||||
<div style="color: #ff6b6b;">Erreur de chargement</div>
|
||||
<div style="color: #8892b0; margin-top: 10px;">Vérifiez votre connexion internet</div>
|
||||
`;
|
||||
});
|
||||
|
||||
// Handle window resize
|
||||
window.addEventListener('resize', () => {
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
});
|
||||
|
||||
// Initial cursor style
|
||||
renderer.domElement.style.cursor = 'grab';
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user