// Solar System Simulation class SolarSystem { constructor() { this.canvas = document.getElementById('solarCanvas'); this.ctx = this.canvas.getContext('2d'); this.centerX = 0; this.centerY = 0; this.zoom = 1; this.speed = 1; this.isPaused = false; this.time = 0; this.fps = 60; this.lastFrameTime = performance.now(); this.startDate = new Date(); this.currentDate = new Date(); this.timeLapseSpeed = 1; // days per frame this.planets = [ { name: 'Mercury', type: 'Terrestrial', radius: 4, distance: 60, orbitalPeriod: 87.97, // days color: '#9CA3AF', info: { diameter: '4,879 km', moons: 0, orbitalPeriod: '87.97 days' }, eccentricity: 0.2056, inclination: 7.0 }, { name: 'Venus', type: 'Terrestrial', radius: 7, distance: 85, orbitalPeriod: 224.70, color: '#FEF3C7', info: { diameter: '12,104 km', moons: 0, orbitalPeriod: '224.70 days' }, eccentricity: 0.0067, inclination: 3.39 }, { name: 'Earth', type: 'Terrestrial', radius: 8, distance: 110, orbitalPeriod: 365.26, color: '#3B82F6', info: { diameter: '12,742 km', moons: 1, orbitalPeriod: '365.26 days' }, moons: [{ radius: 2, distance: 15, orbitalPeriod: 27.32, // days color: '#E5E7EB' }], eccentricity: 0.0167, inclination: 0.0 }, { name: 'Mars', type: 'Terrestrial', radius: 5, distance: 140, orbitalPeriod: 686.98, color: '#EF4444', info: { diameter: '6,779 km', moons: 2, orbitalPeriod: '686.98 days' }, moons: [ { radius: 1, distance: 10, orbitalPeriod: 0.32, // days color: '#F3F4F6' }, { radius: 1, distance: 14, orbitalPeriod: 1.26, // days color: '#D1D5DB' } ], eccentricity: 0.0935, inclination: 1.85 }, { name: 'Jupiter', type: 'Gas Giant', radius: 20, distance: 200, orbitalPeriod: 4332.59, color: '#FB923C', info: { diameter: '139,820 km', moons: 79, orbitalPeriod: '11.86 years' }, moons: [ { radius: 2, distance: 28, orbitalPeriod: 1.77, // days color: '#FCD34D' }, { radius: 2, distance: 35, orbitalPeriod: 3.55, // days color: '#FDE68A' } ], eccentricity: 0.0489, inclination: 1.31 }, { name: 'Saturn', type: 'Gas Giant', radius: 17, distance: 260, orbitalPeriod: 10759.22, color: '#F59E0B', hasRings: true, info: { diameter: '116,460 km', moons: 82, orbitalPeriod: '29.46 years' }, moons: [ { radius: 3, distance: 30, orbitalPeriod: 2.74, // days color: '#FBBF24' } ], eccentricity: 0.0565, inclination: 2.49 }, { name: 'Uranus', type: 'Ice Giant', radius: 12, distance: 320, orbitalPeriod: 30688.5, color: '#06B6D4', info: { diameter: '50,724 km', moons: 27, orbitalPeriod: '84.01 years' }, eccentricity: 0.0457, inclination: 0.77 }, { name: 'Neptune', type: 'Ice Giant', radius: 11, distance: 370, orbitalPeriod: 60182, color: '#1E40AF', info: { diameter: '49,244 km', moons: 14, orbitalPeriod: '164.79 years' }, eccentricity: 0.0113, inclination: 1.77 } ]; this.init(); } init() { this.resize(); window.addEventListener('resize', () => this.resize()); this.setupEventListeners(); this.createStars(); this.animate(); this.updateDate(); this.updateLocation(); this.updateTimeDisplay(); setInterval(() => this.updateDate(), 1000); setInterval(() => this.updateLocation(), 30000); } resize() { this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight; this.centerX = this.canvas.width / 2; this.centerY = this.canvas.height / 2; } createStars() { const starsContainer = document.getElementById('starsContainer'); for (let i = 0; i < 200; i++) { const star = document.createElement('div'); star.className = 'star'; star.style.width = Math.random() * 3 + 'px'; star.style.height = star.style.width; star.style.left = Math.random() * 100 + '%'; star.style.top = Math.random() * 100 + '%'; star.style.animationDelay = Math.random() * 3 + 's'; starsContainer.appendChild(star); } } setupEventListeners() { // Speed control const speedSlider = document.getElementById('speedSlider'); speedSlider.addEventListener('input', (e) => { this.speed = parseFloat(e.target.value); this.timeLapseSpeed = this.speed * 0.1; // Convert to days per frame document.getElementById('speedValue').textContent = this.speed.toFixed(1) + 'x'; }); // Zoom control const zoomSlider = document.getElementById('zoomSlider'); zoomSlider.addEventListener('input', (e) => { this.zoom = parseFloat(e.target.value); document.getElementById('zoomValue').textContent = this.zoom.toFixed(1) + 'x'; }); // Pause button const pauseBtn = document.getElementById('pauseBtn'); pauseBtn.addEventListener('click', () => { this.isPaused = !this.isPaused; pauseBtn.innerHTML = this.isPaused ? 'Play' : 'Pause'; feather.replace(); }); // Reset button document.getElementById('resetBtn').addEventListener('click', () => { this.time = 0; this.currentDate = new Date(); this.zoom = 1; this.speed = 1; this.timeLapseSpeed = 0.1; document.getElementById('zoomSlider').value = 1; document.getElementById('speedSlider').value = 1; document.getElementById('zoomValue').textContent = '1.0x'; document.getElementById('speedValue').textContent = '1.0x'; this.updateTimeDisplay(); }); // Fullscreen button document.getElementById('fullscreenBtn').addEventListener('click', () => { if (!document.fullscreenElement) { document.documentElement.requestFullscreen(); } else { document.exitFullscreen(); } }); // Info button document.getElementById('infoBtn').addEventListener('click', () => { const planetInfo = document.getElementById('planetInfo'); planetInfo.classList.toggle('hidden'); }); // Location refresh on click document.getElementById('locationDisplay').addEventListener('click', () => { document.getElementById('locationDisplay').textContent = 'Updating...'; this.updateLocation(); }); // Close info panel document.getElementById('closeInfo').addEventListener('click', () => { document.getElementById('planetInfo').classList.add('hidden'); }); // Time jump controls document.querySelectorAll('.time-unit').forEach(unit => { unit.addEventListener('click', (e) => { const jumpDays = parseInt(e.target.dataset.time); this.time += jumpDays / this.timeLapseSpeed; this.updateTimeDisplay(); this.updateDate(); }); }); // Canvas click for planet info this.canvas.addEventListener('click', (e) => { const rect = this.canvas.getBoundingClientRect(); const x = e.clientX - rect.left - this.centerX; const y = e.clientY - rect.top - this.centerY; for (const planet of this.planets) { const angle = this.time * planet.speed * this.speed * 0.01; const px = Math.cos(angle) * planet.distance * this.zoom; const py = Math.sin(angle) * planet.distance * this.zoom; const distance = Math.sqrt((x - px) ** 2 + (y - py) ** 2); if (distance < planet.radius * this.zoom + 5) { this.showPlanetInfo(planet); break; } } }); } showPlanetInfo(planet) { const info = document.getElementById('planetInfo'); info.classList.remove('hidden'); document.getElementById('planetName').textContent = planet.name; document.getElementById('planetType').textContent = planet.type; document.getElementById('planetDistance').textContent = planet.distance + ' AU'; document.getElementById('planetPeriod').textContent = planet.info.orbitalPeriod; document.getElementById('planetDiameter').textContent = planet.info.diameter; document.getElementById('planetMoons').textContent = planet.info.moons || 'None'; } updateTimeDisplay() { const timeDisplay = document.getElementById('timeDisplay'); if (timeDisplay) { const days = Math.floor(this.timeLapseSpeed * this.time); const years = Math.floor(days / 365.26); const remainingDays = days % 365.26; const months = Math.floor(remainingDays / 30.44); const remainingDays2 = Math.floor(remainingDays % 30.44); let timeString = ''; if (years > 0) timeString += `${years}Y `; if (months > 0) timeString += `${months}M `; if (remainingDays2 > 0 || timeString === '') timeString += `${remainingDays2}D`; timeDisplay.textContent = `Time Lapse: ${timeString}`; } } calculatePlanetPosition(planet, elapsedTime) { const meanAnomaly = (2 * Math.PI * elapsedTime) / planet.orbitalPeriod; const eccentricity = planet.eccentricity || 0; // Solve Kepler's equation (simplified) let E = meanAnomaly; for (let i = 0; i < 5; i++) { E = meanAnomaly + eccentricity * Math.sin(E); } // True anomaly const trueAnomaly = 2 * Math.atan2( Math.sqrt(1 + eccentricity) * Math.sin(E / 2), Math.sqrt(1 - eccentricity) * Math.cos(E / 2) ); // Distance from sun const r = planet.distance * (1 - eccentricity * Math.cos(E)); // Position const x = r * Math.cos(trueAnomaly); const y = r * Math.sin(trueAnomaly); // Apply inclination const inclination = (planet.inclination || 0) * Math.PI / 180; const yInclined = y * Math.cos(inclination); return { x, y: yInclined, r }; } updateDate() { const now = new Date(); document.getElementById('dateDisplay').textContent = now.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: '2-digit', minute: '2-digit', second: '2-digit' }); // Update current date display const currentDateDisplay = document.getElementById('currentDateDisplay'); if (currentDateDisplay) { currentDateDisplay.textContent = this.currentDate.toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }); } } async updateLocation() { const locationDisplay = document.getElementById('locationDisplay'); if (!navigator.geolocation) { locationDisplay.textContent = 'Location not supported'; return; } navigator.geolocation.getCurrentPosition( async (position) => { const { latitude, longitude } = position.coords; try { // Using Nominatim reverse geocoding API const response = await fetch( `https://nominatim.openstreetmap.org/reverse?format=json&lat=${latitude}&lon=${longitude}&zoom=10`, { headers: { 'User-Agent': 'CosmicExplorer/1.0' } } ); if (!response.ok) throw new Error('Location fetch failed'); const data = await response.json(); const city = data.address?.city || data.address?.town || data.address?.village || 'Unknown'; const country = data.address?.country || ''; locationDisplay.textContent = country ? `${city}, ${country}` : city; } catch (error) { // Fallback to coordinates locationDisplay.textContent = `${latitude.toFixed(2)}°, ${longitude.toFixed(2)}°`; } }, (error) => { locationDisplay.textContent = 'Location denied'; } ); } drawSun() { // Simplified sun for performance this.ctx.fillStyle = '#FBBF24'; this.ctx.beginPath(); this.ctx.arc(this.centerX, this.centerY, 25 * this.zoom, 0, Math.PI * 2); this.ctx.fill(); // Simple glow effect this.ctx.fillStyle = 'rgba(251, 191, 36, 0.3)'; this.ctx.beginPath(); this.ctx.arc(this.centerX, this.centerY, 35 * this.zoom, 0, Math.PI * 2); this.ctx.fill(); } drawPlanet(planet) { const elapsedTime = this.timeLapseSpeed * this.time; const position = this.calculatePlanetPosition(planet, elapsedTime); const x = this.centerX + position.x * this.zoom; const y = this.centerY + position.y * this.zoom; // Draw orbit this.ctx.strokeStyle = 'rgba(147, 51, 234, 0.2)'; this.ctx.lineWidth = 1; this.ctx.beginPath(); this.ctx.ellipse(this.centerX, this.centerY, planet.distance * this.zoom, planet.distance * this.zoom, 0, 0, Math.PI * 2); this.ctx.stroke(); // Draw rings for Saturn if (planet.hasRings) { this.ctx.strokeStyle = 'rgba(245, 158, 11, 0.5)'; this.ctx.lineWidth = 3 * this.zoom; this.ctx.beginPath(); this.ctx.ellipse(x, y, planet.radius * this.zoom * 2, planet.radius * this.zoom * 0.7, 0, 0, Math.PI * 2); this.ctx.stroke(); } // Draw planet this.ctx.fillStyle = planet.color; this.ctx.beginPath(); this.ctx.arc(x, y, planet.radius * this.zoom, 0, Math.PI * 2); this.ctx.fill(); // Draw moons if (planet.moons) { planet.moons.forEach(moon => { const moonElapsedTime = this.timeLapseSpeed * this.time; const moonAngle = (2 * Math.PI * moonElapsedTime) / moon.orbitalPeriod; const moonX = x + Math.cos(moonAngle) * moon.distance * this.zoom; const moonY = y + Math.sin(moonAngle) * moon.distance * this.zoom; this.ctx.fillStyle = moon.color; this.ctx.beginPath(); this.ctx.arc(moonX, moonY, moon.radius * this.zoom, 0, Math.PI * 2); this.ctx.fill(); }); } // Draw planet name if (this.zoom > 0.8) { this.ctx.fillStyle = 'rgba(255, 255, 255, 0.7)'; this.ctx.font = `${Math.max(10, 10 * this.zoom)}px Arial`; this.ctx.textAlign = 'center'; this.ctx.fillText(planet.name, x, y - planet.radius * this.zoom - 5); } return { x, y, position }; } adjustBrightness(color, amount) { const num = parseInt(color.replace('#', ''), 16); const r = Math.max(0, Math.min(255, (num >> 16) + amount)); const g = Math.max(0, Math.min(255, ((num >> 8) & 0x00FF) + amount)); const b = Math.max(0, Math.min(255, (num & 0x0000FF) + amount)); return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`; } animate() { // Clear canvas completely for better performance this.ctx.fillStyle = '#000'; this.ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); // Draw sun this.drawSun(); // Draw planets this.planets.forEach(planet => this.drawPlanet(planet)); // Update time if (!this.isPaused) { this.time++; this.currentDate = new Date(this.startDate.getTime() + this.timeLapseSpeed * this.time * 24 * 60 * 60 * 1000); this.updateTimeDisplay(); } // Update stats less frequently if (this.time % 10 === 0) { this.updateStats(); } // Calculate FPS const now = performance.now(); const delta = now - this.lastFrameTime; this.fps = Math.round(1000 / delta); this.lastFrameTime = now; requestAnimationFrame(() => this.animate()); } updateStats() { document.getElementById('fpsDisplay').textContent = this.fps; const totalDays = Math.floor(this.timeLapseSpeed * this.time); document.getElementById('daysDisplay').textContent = totalDays; document.getElementById('yearsDisplay').textContent = (totalDays / 365.26).toFixed(2); } } // Initialize the solar system document.addEventListener('DOMContentLoaded', () => { new SolarSystem(); });