Interactive 3D Space Explorer
by LunarGalaxy87660 lines36.8 KB
<!DOCTYPE html>
<html lang="en" style="width:100%; height:100%; margin:0; padding:0; overflow:hidden; background-color:#050714;">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Interactive 3D Space Explorer</title>
</head>
<body style="width:100vw; height:100vh; margin:0; padding:0; overflow:hidden; background-color:#050714; color:#f1f5f9; font-family:system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; user-select:none; -webkit-user-select:none;">
<!-- 3D / CANVAS VIEWPORT CONTAINER -->
<div id="canvas-container" style="position:fixed; top:0; left:0; width:100vw; height:100vh; z-index:1; cursor:grab; background:#050714;">
<canvas id="space-canvas" style="display:block; width:100%; height:100%;"></canvas>
</div>
<!-- TOP HUD HEADER -->
<header id="hud-header" style="position:fixed; top:0; left:0; right:0; z-index:50; display:flex; align-items:center; justify-content:space-between; padding:10px 16px; background:rgba(10,15,38,0.85); -webkit-backdrop-filter:blur(12px); backdrop-filter:blur(12px); border-bottom:1px solid rgba(56,189,248,0.25); box-shadow:0 4px 20px rgba(0,0,0,0.5);">
<div style="display:flex; align-items:center; gap:10px;">
<div style="width:34px; height:34px; border-radius:8px; background:rgba(56,189,248,0.15); border:1px solid rgba(56,189,248,0.35); display:flex; align-items:center; justify-content:center; color:#38bdf8;">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M3 12a9 9 0 1 0 18 0 9 9 0 0 0-18 0"/><path d="M12 3a9 9 0 1 0 0 18 9 9 0 0 0 0-18"/></svg>
</div>
<div>
<div style="font-size:15px; font-weight:800; letter-spacing:0.8px; color:#38bdf8; background:linear-gradient(135deg, #38bdf8, #e0f2fe, #a855f7); -webkit-background-clip:text; -webkit-text-fill-color:transparent;">COSMOS EXPLORER 3D</div>
<div style="font-size:9px; font-family:monospace; color:#94a3b8; letter-spacing:0.5px;">SOLAR SYSTEM & DEEP SPACE TELEMETRY</div>
</div>
</div>
<div style="display:flex; align-items:center; gap:8px;">
<button onclick="setExplorerView('system')" style="display:inline-flex; align-items:center; gap:5px; padding:6px 12px; border-radius:8px; font-size:11px; font-weight:600; cursor:pointer; background:rgba(56,189,248,0.18); color:#38bdf8; border:1px solid rgba(56,189,248,0.4); transition:all 0.2s;">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="4"/><path d="M12 2v2"/><path d="M12 20v2"/><path d="M2 12h2"/><path d="M20 12h2"/></svg>
Solar System
</button>
<button onclick="focusBlackHole()" style="display:inline-flex; align-items:center; gap:5px; padding:6px 12px; border-radius:8px; font-size:11px; font-weight:600; cursor:pointer; background:rgba(30,41,59,0.8); color:#f1f5f9; border:1px solid rgba(255,255,255,0.15); transition:all 0.2s;">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#a855f7" stroke-width="2"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3"/></svg>
Black Hole
</button>
<button id="audio-btn" onclick="toggleAudio()" title="Toggle Soundscape" style="width:32px; height:32px; border-radius:8px; background:rgba(30,41,59,0.8); color:#94a3b8; border:1px solid rgba(255,255,255,0.15); display:inline-flex; align-items:center; justify-content:center; cursor:pointer;">
<svg id="audio-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 5L6 9H2v6h4l5 4V5z"/><line x1="23" y1="9" x2="17" y2="15"/><line x1="17" y1="9" x2="23" y2="15"/></svg>
</button>
<button onclick="toggleAiDrawer()" style="display:inline-flex; align-items:center; gap:5px; padding:6px 12px; border-radius:8px; font-size:11px; font-weight:600; cursor:pointer; background:linear-gradient(135deg, rgba(168,85,247,0.25), rgba(99,102,241,0.25)); color:#e9d5ff; border:1px solid rgba(168,85,247,0.5); transition:all 0.2s;">
<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3L12 3z"/></svg>
Astro-AI
</button>
</div>
</header>
<!-- FLOATING TIME CONTROLS -->
<div id="time-hud" style="position:fixed; bottom:20px; left:20px; z-index:50; width:240px; background:rgba(10,15,38,0.88); -webkit-backdrop-filter:blur(12px); backdrop-filter:blur(12px); border:1px solid rgba(56,189,248,0.25); border-radius:14px; padding:12px; display:flex; flex-direction:column; gap:8px; box-shadow:0 8px 32px rgba(0,0,0,0.5);">
<div style="display:flex; justify-space-between; align-items:center; font-size:10px; font-family:monospace; color:#38bdf8; border-bottom:1px solid rgba(255,255,255,0.08); padding-bottom:5px;">
<span>TIME DILATION</span>
<span id="speed-display" style="font-weight:bold;">1.0x</span>
</div>
<div style="display:flex; align-items:center; gap:8px;">
<button id="pause-btn" onclick="togglePause()" style="width:28px; height:28px; border-radius:6px; background:rgba(30,41,59,0.9); color:#f1f5f9; border:1px solid rgba(255,255,255,0.15); display:inline-flex; align-items:center; justify-content:center; cursor:pointer;">
<svg id="pause-icon" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/></svg>
</button>
<input type="range" id="speed-slider" min="0" max="5" step="0.1" value="1" oninput="updateSpeed(this.value)" style="width:100%; accent-color:#38bdf8; cursor:pointer;" />
</div>
<div style="display:flex; justify-content:space-between; font-size:10px; color:#94a3b8; border-top:1px solid rgba(255,255,255,0.08); padding-top:6px;">
<label style="display:flex; align-items:center; gap:5px; cursor:pointer;">
<input type="checkbox" id="orbit-lines-toggle" checked onchange="toggleOrbitLines(this.checked)" style="accent-color:#38bdf8;">
<span>Show Orbits</span>
</label>
<label style="display:flex; align-items:center; gap:5px; cursor:pointer;">
<input type="checkbox" id="scale-toggle" onchange="toggleRealScale(this.checked)" style="accent-color:#38bdf8;">
<span>Scale</span>
</label>
</div>
</div>
<!-- QUICK PLANET SELECTOR BAR -->
<div id="planet-bar" style="position:fixed; bottom:20px; left:50%; transform:translateX(-50%); z-index:50; background:rgba(10,15,38,0.88); -webkit-backdrop-filter:blur(12px); backdrop-filter:blur(12px); border:1px solid rgba(56,189,248,0.25); border-radius:30px; padding:5px 8px; display:flex; align-items:center; gap:4px; max-width:90vw; overflow-x:auto; box-shadow:0 8px 32px rgba(0,0,0,0.5);">
<button onclick="focusSun()" style="padding:4px 10px; border-radius:16px; border:none; background:transparent; color:#fbbf24; font-size:11px; font-weight:600; cursor:pointer; white-space:nowrap;">Sun</button>
<button onclick="focusPlanet('mercury')" style="padding:4px 10px; border-radius:16px; border:none; background:transparent; color:#cbd5e1; font-size:11px; font-weight:600; cursor:pointer; white-space:nowrap;">Mercury</button>
<button onclick="focusPlanet('venus')" style="padding:4px 10px; border-radius:16px; border:none; background:transparent; color:#f97316; font-size:11px; font-weight:600; cursor:pointer; white-space:nowrap;">Venus</button>
<button onclick="focusPlanet('earth')" style="padding:4px 10px; border-radius:16px; border:none; background:transparent; color:#38bdf8; font-size:11px; font-weight:600; cursor:pointer; white-space:nowrap;">Earth</button>
<button onclick="focusPlanet('mars')" style="padding:4px 10px; border-radius:16px; border:none; background:transparent; color:#ef4444; font-size:11px; font-weight:600; cursor:pointer; white-space:nowrap;">Mars</button>
<button onclick="focusPlanet('jupiter')" style="padding:4px 10px; border-radius:16px; border:none; background:transparent; color:#f59e0b; font-size:11px; font-weight:600; cursor:pointer; white-space:nowrap;">Jupiter</button>
<button onclick="focusPlanet('saturn')" style="padding:4px 10px; border-radius:16px; border:none; background:transparent; color:#eab308; font-size:11px; font-weight:600; cursor:pointer; white-space:nowrap;">Saturn</button>
<button onclick="focusPlanet('uranus')" style="padding:4px 10px; border-radius:16px; border:none; background:transparent; color:#2dd4bf; font-size:11px; font-weight:600; cursor:pointer; white-space:nowrap;">Uranus</button>
<button onclick="focusPlanet('neptune')" style="padding:4px 10px; border-radius:16px; border:none; background:transparent; color:#3b82f6; font-size:11px; font-weight:600; cursor:pointer; white-space:nowrap;">Neptune</button>
</div>
<!-- INSPECTOR DRAWER -->
<div id="inspector-panel" style="position:fixed; top:65px; right:20px; z-index:60; width:340px; max-width:calc(100vw - 40px); max-height:calc(100vh - 85px); background:rgba(10,15,38,0.92); -webkit-backdrop-filter:blur(16px); backdrop-filter:blur(16px); border:1px solid rgba(56,189,248,0.3); border-radius:16px; padding:16px; display:flex; flex-direction:column; gap:12px; box-shadow:0 12px 40px rgba(0,0,0,0.6); transition:transform 0.35s ease, opacity 0.35s ease; transform:translateX(450px); opacity:0; pointer-events:none; overflow-y:auto;">
<div style="display:flex; justify-content:space-between; align-items:flex-start; border-bottom:1px solid rgba(255,255,255,0.08); padding-bottom:8px;">
<div>
<span id="target-type" style="font-size:9px; font-family:monospace; padding:2px 6px; border-radius:4px; background:rgba(56,189,248,0.15); color:#38bdf8; border:1px solid rgba(56,189,248,0.3); letter-spacing:1px;">PLANET TELEMETRY</span>
<div id="target-name" style="font-size:20px; font-weight:900; margin-top:2px;">EARTH</div>
</div>
<button onclick="closeInspector()" style="width:26px; height:26px; border-radius:6px; background:rgba(30,41,59,0.8); color:#94a3b8; border:1px solid rgba(255,255,255,0.15); display:inline-flex; align-items:center; justify-content:center; cursor:pointer;">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</div>
<div id="target-description" style="font-size:11px; color:#cbd5e1; line-height:1.5;">
Earth is the third planet from the Sun and the only astronomical object known to harbor life.
</div>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:8px;">
<div style="background:rgba(15,23,42,0.6); border:1px solid rgba(255,255,255,0.06); border-radius:8px; padding:8px;">
<div style="font-size:8px; font-family:monospace; color:#94a3b8;">DISTANCE FROM SUN</div>
<div id="stat-distance" style="font-size:11px; font-weight:700; color:#38bdf8; margin-top:2px;">149.6 M km</div>
</div>
<div style="background:rgba(15,23,42,0.6); border:1px solid rgba(255,255,255,0.06); border-radius:8px; padding:8px;">
<div style="font-size:8px; font-family:monospace; color:#94a3b8;">ORBITAL PERIOD</div>
<div id="stat-period" style="font-size:11px; font-weight:700; color:#a855f7; margin-top:2px;">365.25 Days</div>
</div>
<div style="background:rgba(15,23,42,0.6); border:1px solid rgba(255,255,255,0.06); border-radius:8px; padding:8px;">
<div style="font-size:8px; font-family:monospace; color:#94a3b8;">DIAMETER</div>
<div id="stat-diameter" style="font-size:11px; font-weight:700; color:#fbbf24; margin-top:2px;">12,742 km</div>
</div>
<div style="background:rgba(15,23,42,0.6); border:1px solid rgba(255,255,255,0.06); border-radius:8px; padding:8px;">
<div style="font-size:8px; font-family:monospace; color:#94a3b8;">KNOWN MOONS</div>
<div id="stat-moons" style="font-size:11px; font-weight:700; color:#34d399; margin-top:2px;">1</div>
</div>
</div>
<div>
<div style="font-size:8px; font-family:monospace; color:#94a3b8; margin-bottom:4px;">ATMOSPHERIC COMPOSITION</div>
<div id="atm-tags" style="display:flex; flex-wrap:wrap; gap:4px;">
<span style="font-size:9px; padding:2px 6px; border-radius:4px; background:rgba(30,41,59,0.8); color:#94a3b8; border:1px solid rgba(255,255,255,0.08);">78% Nitrogen</span>
<span style="font-size:9px; padding:2px 6px; border-radius:4px; background:rgba(30,41,59,0.8); color:#94a3b8; border:1px solid rgba(255,255,255,0.08);">21% Oxygen</span>
</div>
</div>
<button onclick="askAiAboutCurrentPlanet()" style="width:100%; padding:8px; border-radius:8px; font-size:11px; font-weight:600; cursor:pointer; background:rgba(56,189,248,0.2); color:#38bdf8; border:1px solid rgba(56,189,248,0.4); display:inline-flex; align-items:center; justify-content:center; gap:6px;">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m12 3-1.9 5.8a2 2 0 0 1-1.3 1.3L3 12l5.8 1.9a2 2 0 0 1 1.3 1.3L12 21l1.9-5.8a2 2 0 0 1 1.3-1.3L21 12l-5.8-1.9a2 2 0 0 1-1.3-1.3L12 3z"/></svg>
Query Astro-AI Copilot
</button>
</div>
<!-- AI DRAWER -->
<div id="ai-drawer" style="position:fixed; top:65px; right:20px; z-index:70; width:340px; max-width:calc(100vw - 40px); max-height:calc(100vh - 85px); background:rgba(10,15,38,0.92); -webkit-backdrop-filter:blur(16px); backdrop-filter:blur(16px); border:1px solid rgba(168,85,247,0.4); border-radius:16px; padding:16px; display:flex; flex-direction:column; gap:10px; box-shadow:0 12px 40px rgba(0,0,0,0.6); transition:transform 0.35s ease, opacity 0.35s ease; transform:translateX(450px); opacity:0; pointer-events:none; overflow-y:auto;">
<div style="display:flex; justify-content:space-between; align-items:center; border-bottom:1px solid rgba(255,255,255,0.08); padding-bottom:8px;">
<div style="display:flex; align-items:center; gap:8px;">
<div style="padding:5px; background:rgba(168,85,247,0.2); border-radius:6px; color:#c084fc;">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="10" rx="2"/><circle cx="12" cy="5" r="2"/><path d="M12 7v4"/></svg>
</div>
<div>
<div style="font-size:13px; font-weight:700;">Astro-AI Assistant</div>
<div style="font-size:9px; color:#c084fc; font-family:monospace;">Gemini 2.5 Flash Online</div>
</div>
</div>
<button onclick="toggleAiDrawer()" style="width:26px; height:26px; border-radius:6px; background:rgba(30,41,59,0.8); color:#94a3b8; border:1px solid rgba(255,255,255,0.15); display:inline-flex; align-items:center; justify-content:center; cursor:pointer;">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
</div>
<div style="display:flex; gap:6px; overflow-x:auto; padding-bottom:4px; scrollbar-width:none;">
<button onclick="sendQuickPrompt('Explain how Saturn\'s rings formed in simple terms.')" style="padding:4px 8px; border-radius:12px; background:rgba(30,41,59,0.8); border:1px solid rgba(255,255,255,0.1); color:#94a3b8; font-size:10px; white-space:nowrap; cursor:pointer;">🪐 Saturn's Rings</button>
<button onclick="sendQuickPrompt('What would happen if a spacecraft entered a supermassive black hole?')" style="padding:4px 8px; border-radius:12px; background:rgba(30,41,59,0.8); border:1px solid rgba(255,255,255,0.1); color:#94a3b8; font-size:10px; white-space:nowrap; cursor:pointer;">🕳️ Black Hole</button>
<button onclick="sendQuickPrompt('Generate a captain log entry landing on Europa searching for ocean life.')" style="padding:4px 8px; border-radius:12px; background:rgba(30,41,59,0.8); border:1px solid rgba(255,255,255,0.1); color:#94a3b8; font-size:10px; white-space:nowrap; cursor:pointer;">🚀 Mission Log</button>
</div>
<div id="chat-messages" style="flex-grow:1; min-height:160px; max-height:260px; overflow-y:auto; display:flex; flex-direction:column; gap:8px;">
<div style="padding:8px 10px; border-radius:10px; font-size:11px; line-height:1.4; background:rgba(88,28,135,0.35); border:1px solid rgba(168,85,247,0.3); color:#f3e8ff;">
Greetings Spacefarer! I am your Astro-AI Copilot. Ask me any question regarding planetary science, deep space astrophysics, or mission planning.
</div>
</div>
<form onsubmit="handleChatSubmit(event)" style="display:flex; gap:6px; border-top:1px solid rgba(255,255,255,0.08); padding-top:8px;">
<input type="text" id="chat-input" placeholder="Ask Astro-AI about space..." style="flex-grow:1; padding:6px 10px; border-radius:8px; background:rgba(15,23,42,0.8); border:1px solid rgba(255,255,255,0.15); color:#f1f5f9; font-size:11px; outline:none;" />
<button type="submit" id="chat-send-btn" style="width:30px; height:30px; border-radius:8px; background:linear-gradient(135deg, rgba(168,85,247,0.4), rgba(99,102,241,0.4)); color:#e9d5ff; border:1px solid rgba(168,85,247,0.5); display:inline-flex; align-items:center; justify-content:center; cursor:pointer; flex-shrink:0;">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
</button>
</form>
</div>
<script>
// GEMINI API CONFIGURATION
const apiKey = "";
const GEMINI_MODEL = "gemini-2.5-flash-preview-09-2025";
// AUDIO SYNTHESIZER
let audioCtx = null;
let ambientGain = null;
let isAudioPlaying = false;
function initAudio() {
if (audioCtx) return;
try {
const AudioContext = window.AudioContext || window.webkitAudioContext;
audioCtx = new AudioContext();
const osc1 = audioCtx.createOscillator();
const osc2 = audioCtx.createOscillator();
const filter = audioCtx.createBiquadFilter();
ambientGain = audioCtx.createGain();
osc1.type = 'sine';
osc1.frequency.setValueAtTime(55, audioCtx.currentTime);
osc2.type = 'sine';
osc2.frequency.setValueAtTime(110, audioCtx.currentTime);
filter.type = 'lowpass';
filter.frequency.setValueAtTime(180, audioCtx.currentTime);
ambientGain.gain.setValueAtTime(0.04, audioCtx.currentTime);
osc1.connect(filter);
osc2.connect(filter);
filter.connect(ambientGain);
ambientGain.connect(audioCtx.destination);
osc1.start();
osc2.start();
} catch(e) {}
}
function toggleAudio() {
if (!audioCtx) initAudio();
if (!audioCtx) return;
if (audioCtx.state === 'suspended') audioCtx.resume();
const icon = document.getElementById('audio-icon');
if (isAudioPlaying) {
ambientGain.gain.linearRampToValueAtTime(0.0001, audioCtx.currentTime + 0.3);
isAudioPlaying = false;
icon.innerHTML = `<path d="M11 5L6 9H2v6h4l5 4V5z"/><line x1="23" y1="9" x2="17" y2="15"/><line x1="17" y1="9" x2="23" y2="15"/>`;
} else {
ambientGain.gain.linearRampToValueAtTime(0.05, audioCtx.currentTime + 0.3);
isAudioPlaying = true;
icon.innerHTML = `<path d="M11 5L6 9H2v6h4l5 4V5z"/><path d="M19.07 4.93a10 10 0 0 1 0 14.14"/><path d="M15.54 8.46a5 5 0 0 1 0 7.07"/>`;
}
playBeep(440);
}
function playBeep(freq = 600) {
if (!audioCtx || !isAudioPlaying) return;
try {
const osc = audioCtx.createOscillator();
const g = audioCtx.createGain();
osc.frequency.setValueAtTime(freq, audioCtx.currentTime);
g.gain.setValueAtTime(0.02, audioCtx.currentTime);
g.gain.exponentialRampToValueAtTime(0.0001, audioCtx.currentTime + 0.08);
osc.connect(g);
g.connect(audioCtx.destination);
osc.start();
osc.stop(audioCtx.currentTime + 0.08);
} catch(e){}
}
// CELESTIAL DATA
const PLANET_DATA = {
sun: { name: "SUN", type: "YELLOW DWARF STAR", description: "The Sun is the star at the center of the Solar System, generating heat and light via nuclear fusion.", distance: "0 km", period: "230 Million Yrs", diameter: "1,392,700 km", moons: "8 Planets", atmosphere: ["73% Hydrogen", "25% Helium"] },
mercury: { name: "MERCURY", type: "TERRESTRIAL PLANET", description: "Mercury is the smallest planet in the Solar System and closest to the Sun.", distance: "57.9 M km", period: "88 Days", diameter: "4,879 km", moons: "0", atmosphere: ["Sodium", "Oxygen", "Helium"] },
venus: { name: "VENUS", type: "TERRESTRIAL PLANET", description: "Venus has a thick greenhouse atmosphere making it the hottest planet in our solar system.", distance: "108.2 M km", period: "225 Days", diameter: "12,104 km", moons: "0", atmosphere: ["96.5% CO2", "3.5% Nitrogen"] },
earth: { name: "EARTH", type: "TERRESTRIAL PLANET", description: "Earth is the third planet from the Sun and the only known world harboring life.", distance: "149.6 M km", period: "365.25 Days", diameter: "12,742 km", moons: "1 (Luna)", atmosphere: ["78% Nitrogen", "21% Oxygen"] },
mars: { name: "MARS", type: "TERRESTRIAL PLANET", description: "Mars is a dusty, cold desert world with a thin carbon dioxide atmosphere.", distance: "227.9 M km", period: "687 Days", diameter: "6,779 km", moons: "2 (Phobos, Deimos)", atmosphere: ["95% CO2", "2.8% Nitrogen"] },
jupiter: { name: "JUPITER", type: "GAS GIANT", description: "Jupiter is more than twice as massive as all other planets combined, with iconic cloud bands.", distance: "778.5 M km", period: "11.86 Yrs", diameter: "139,820 km", moons: "95 (Europa, Io...)", atmosphere: ["90% Hydrogen", "10% Helium"] },
saturn: { name: "SATURN", type: "GAS GIANT", description: "Adorned with vast ice ringlets, Saturn is a gas giant floating gracefully in deep space.", distance: "1.4 B km", period: "29.45 Yrs", diameter: "116,460 km", moons: "146 (Titan, Enceladus)", atmosphere: ["96% Hydrogen", "3% Helium"] },
uranus: { name: "URANUS", type: "ICE GIANT", description: "Uranus is an ice giant with an extreme axial tilt of nearly 98 degrees.", distance: "2.87 B km", period: "84 Yrs", diameter: "50,724 km", moons: "28", atmosphere: ["83% Hydrogen", "15% Helium", "2% Methane"] },
neptune: { name: "NEPTUNE", type: "ICE GIANT", description: "Neptune is dark, cold, and whipped by supersonic winds at the edge of the major system.", distance: "4.5 B km", period: "164.8 Yrs", diameter: "49,244 km", moons: "16 (Triton)", atmosphere: ["80% Hydrogen", "19% Helium", "1% Methane"] },
blackhole: { name: "SAGITTARIUS A*", type: "SUPERMASSIVE BLACK HOLE", description: "A gravitational singularity at the Milky Way core with an intense light-bending accretion disk.", distance: "26,000 Light Yrs", period: "N/A", diameter: "44 M km", moons: "Captured Stars", atmosphere: ["Accretion Disk", "Gravitational Lensing"] }
};
// EMBEDDED CANVAS ENGINE ENGINE (Independent of external CDNs)
const canvas = document.getElementById('space-canvas');
const ctx = canvas.getContext('2d');
let width = canvas.width = window.innerWidth;
let height = canvas.height = window.innerHeight;
let cameraPos = { x: 0, y: 0, zoom: 1.0 };
let targetCameraPos = { x: 0, y: 0, zoom: 1.0 };
let simulationSpeed = 1.0;
let isPaused = false;
let showOrbitLines = true;
let useRealScale = false;
let selectedId = null;
// Generate Stars
const stars = [];
for (let i = 0; i < 400; i++) {
stars.push({
x: (Math.random() - 0.5) * 3000,
y: (Math.random() - 0.5) * 3000,
size: Math.random() * 1.5 + 0.5,
alpha: Math.random() * 0.8 + 0.2
});
}
// Solar System Bodies
const celestialBodies = [
{ id: 'sun', name: 'Sun', radius: 26, dist: 0, speed: 0, color: '#fbbf24', subColor: '#f59e0b' },
{ id: 'mercury', name: 'Mercury', radius: 4, dist: 55, speed: 0.03, color: '#94a3b8', subColor: '#475569' },
{ id: 'venus', name: 'Venus', radius: 7, dist: 85, speed: 0.02, color: '#f97316', subColor: '#c2410c' },
{ id: 'earth', name: 'Earth', radius: 8, dist: 125, speed: 0.015, color: '#38bdf8', subColor: '#15803d' },
{ id: 'mars', name: 'Mars', radius: 6, dist: 165, speed: 0.012, color: '#ef4444', subColor: '#b91c1c' },
{ id: 'jupiter', name: 'Jupiter', radius: 18, dist: 230, speed: 0.007, color: '#f59e0b', subColor: '#b45309' },
{ id: 'saturn', name: 'Saturn', radius: 14, dist: 300, speed: 0.005, color: '#eab308', subColor: '#713f12', hasRings: true },
{ id: 'uranus', name: 'Uranus', radius: 11, dist: 370, speed: 0.003, color: '#2dd4bf', subColor: '#0f766e' },
{ id: 'neptune', name: 'Neptune', radius: 10, dist: 430, speed: 0.002, color: '#3b82f6', subColor: '#1d4ed8' },
{ id: 'blackhole', name: 'Sagittarius A*', radius: 30, dist: 1200, speed: 0.0005, color: '#a855f7', subColor: '#000000', isBlackHole: true }
];
// State angles
celestialBodies.forEach(b => b.angle = Math.random() * Math.PI * 2);
// Render loop
function render() {
ctx.fillStyle = '#050714';
ctx.fillRect(0, 0, width, height);
// Lerp camera
cameraPos.x += (targetCameraPos.x - cameraPos.x) * 0.08;
cameraPos.y += (targetCameraPos.y - cameraPos.y) * 0.08;
cameraPos.zoom += (targetCameraPos.zoom - cameraPos.zoom) * 0.08;
ctx.save();
ctx.translate(width / 2, height / 2);
ctx.scale(cameraPos.zoom, cameraPos.zoom);
ctx.translate(-cameraPos.x, -cameraPos.y);
// Render Starfield
stars.forEach(s => {
ctx.fillStyle = `rgba(255, 255, 255, ${s.alpha})`;
ctx.beginPath();
ctx.arc(s.x, s.y, s.size, 0, Math.PI * 2);
ctx.fill();
});
// Update positions & draw orbits
celestialBodies.forEach(b => {
if (!isPaused && b.dist > 0) {
b.angle += b.speed * simulationSpeed;
}
b.x = Math.cos(b.angle) * b.dist;
b.y = Math.sin(b.angle) * b.dist;
// Orbit lines
if (showOrbitLines && b.dist > 0 && !b.isBlackHole) {
ctx.strokeStyle = 'rgba(56, 189, 248, 0.12)';
ctx.lineWidth = 1 / cameraPos.zoom;
ctx.beginPath();
ctx.arc(0, 0, b.dist, 0, Math.PI * 2);
ctx.stroke();
}
});
// Draw Bodies
celestialBodies.forEach(b => {
ctx.save();
ctx.translate(b.x, b.y);
const r = useRealScale ? (b.dist === 0 ? 20 : b.radius * 0.5) : b.radius;
if (b.isBlackHole) {
// Accretion Disk Glow
const grad = ctx.createRadialGradient(0, 0, r * 0.8, 0, 0, r * 2.5);
grad.addColorStop(0, '#000000');
grad.addColorStop(0.4, '#a855f7');
grad.addColorStop(1, 'transparent');
ctx.fillStyle = grad;
ctx.beginPath();
ctx.arc(0, 0, r * 2.5, 0, Math.PI * 2);
ctx.fill();
// Black Hole Event Horizon
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(0, 0, r, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#c084fc';
ctx.lineWidth = 2;
ctx.stroke();
} else {
// Glow for Sun
if (b.dist === 0) {
const sunGlow = ctx.createRadialGradient(0, 0, r, 0, 0, r * 2);
sunGlow.addColorStop(0, 'rgba(251, 191, 36, 0.8)');
sunGlow.addColorStop(1, 'transparent');
ctx.fillStyle = sunGlow;
ctx.beginPath();
ctx.arc(0, 0, r * 2, 0, Math.PI * 2);
ctx.fill();
}
// Planet Body Gradient Shading
const bodyGrad = ctx.createRadialGradient(-r * 0.3, -r * 0.3, r * 0.1, 0, 0, r);
bodyGrad.addColorStop(0, b.color);
bodyGrad.addColorStop(1, b.subColor);
ctx.fillStyle = bodyGrad;
ctx.beginPath();
ctx.arc(0, 0, r, 0, Math.PI * 2);
ctx.fill();
// Saturn Rings
if (b.hasRings) {
ctx.strokeStyle = 'rgba(234, 179, 8, 0.6)';
ctx.lineWidth = r * 0.6;
ctx.beginPath();
ctx.ellipse(0, 0, r * 2.2, r * 0.7, Math.PI / 6, 0, Math.PI * 2);
ctx.stroke();
}
}
// Selected Object Highlight Ring
if (selectedId === b.id) {
ctx.strokeStyle = '#38bdf8';
ctx.lineWidth = 2 / cameraPos.zoom;
ctx.beginPath();
ctx.arc(0, 0, r + 6, 0, Math.PI * 2);
ctx.stroke();
}
ctx.restore();
});
// Track camera to selected object
if (selectedId) {
const target = celestialBodies.find(b => b.id === selectedId);
if (target) {
targetCameraPos.x = target.x;
targetCameraPos.y = target.y;
}
}
ctx.restore();
requestAnimationFrame(render);
}
// Interaction Handling (Clicking and Mouse Pan/Zoom)
let isDragging = false;
let dragStart = { x: 0, y: 0 };
canvas.addEventListener('mousedown', (e) => {
isDragging = true;
dragStart = { x: e.clientX, y: e.clientY };
});
canvas.addEventListener('mousemove', (e) => {
if (isDragging && !selectedId) {
const dx = (e.clientX - dragStart.x) / cameraPos.zoom;
const dy = (e.clientY - dragStart.y) / cameraPos.zoom;
targetCameraPos.x -= dx;
targetCameraPos.y -= dy;
dragStart = { x: e.clientX, y: e.clientY };
}
});
window.addEventListener('mouseup', () => isDragging = false);
canvas.addEventListener('wheel', (e) => {
e.preventDefault();
const zoomFactor = e.deltaY < 0 ? 1.15 : 0.85;
targetCameraPos.zoom = Math.max(0.3, Math.min(5.0, targetCameraPos.zoom * zoomFactor));
}, { passive: false });
canvas.addEventListener('click', (e) => {
const rect = canvas.getBoundingClientRect();
const clickX = (e.clientX - rect.left - width / 2) / cameraPos.zoom + cameraPos.x;
const clickY = (e.clientY - rect.top - height / 2) / cameraPos.zoom + cameraPos.y;
let clicked = null;
celestialBodies.forEach(b => {
const dist = Math.hypot(clickX - b.x, clickY - b.y);
if (dist < b.radius + 10) clicked = b;
});
if (clicked) {
if (clicked.id === 'sun') focusSun();
else if (clicked.id === 'blackhole') focusBlackHole();
else focusPlanet(clicked.id);
}
});
// NAVIGATION CONTROLS
function focusPlanet(id) {
selectedId = id;
targetCameraPos.zoom = 2.2;
showInspector(id);
playBeep(650);
}
function focusSun() {
selectedId = 'sun';
targetCameraPos.zoom = 1.4;
showInspector('sun');
playBeep(520);
}
function focusBlackHole() {
selectedId = 'blackhole';
targetCameraPos.zoom = 1.0;
showInspector('blackhole');
playBeep(320);
}
function setExplorerView() {
selectedId = null;
targetCameraPos = { x: 0, y: 0, zoom: 1.0 };
closeInspector();
}
function showInspector(id) {
const data = PLANET_DATA[id];
if (!data) return;
document.getElementById('target-type').innerText = data.type;
document.getElementById('target-name').innerText = data.name;
document.getElementById('target-description').innerText = data.description;
document.getElementById('stat-distance').innerText = data.distance;
document.getElementById('stat-period').innerText = data.period;
document.getElementById('stat-diameter').innerText = data.diameter;
document.getElementById('stat-moons').innerText = data.moons;
const tagContainer = document.getElementById('atm-tags');
tagContainer.innerHTML = '';
data.atmosphere.forEach(item => {
const span = document.createElement('span');
span.style.cssText = "font-size:9px; padding:2px 6px; border-radius:4px; background:rgba(30,41,59,0.8); color:#cbd5e1; border:1px solid rgba(255,255,255,0.08);";
span.innerText = item;
tagContainer.appendChild(span);
});
const panel = document.getElementById('inspector-panel');
panel.style.transform = 'translateX(0)';
panel.style.opacity = '1';
panel.style.pointerEvents = 'auto';
}
function closeInspector() {
const panel = document.getElementById('inspector-panel');
panel.style.transform = 'translateX(450px)';
panel.style.opacity = '0';
panel.style.pointerEvents = 'none';
selectedId = null;
}
function updateSpeed(val) {
simulationSpeed = parseFloat(val);
document.getElementById('speed-display').innerText = simulationSpeed.toFixed(1) + 'x';
}
function togglePause() {
isPaused = !isPaused;
const btn = document.getElementById('pause-icon');
btn.innerHTML = isPaused ?
`<polygon points="5 3 19 12 5 21 5 3"/>` :
`<rect x="6" y="4" width="4" height="16"/><rect x="14" y="4" width="4" height="16"/>`;
}
function toggleOrbitLines(checked) {
showOrbitLines = checked;
}
function toggleRealScale(checked) {
useRealScale = checked;
}
// GEMINI AI COPILOT LOGIC
function toggleAiDrawer() {
const drawer = document.getElementById('ai-drawer');
const isHidden = drawer.style.opacity === '0' || drawer.style.opacity === '';
if (isHidden) {
drawer.style.transform = 'translateX(0)';
drawer.style.opacity = '1';
drawer.style.pointerEvents = 'auto';
playBeep(750);
} else {
drawer.style.transform = 'translateX(450px)';
drawer.style.opacity = '0';
drawer.style.pointerEvents = 'none';
}
}
function askAiAboutCurrentPlanet() {
const name = document.getElementById('target-name').innerText;
toggleAiDrawer();
sendQuickPrompt(`Tell me key scientific facts about ${name} and future exploration plans.`);
}
function sendQuickPrompt(text) {
document.getElementById('chat-input').value = text;
handleChatSubmit(new Event('submit'));
}
async function handleChatSubmit(e) {
e.preventDefault();
const input = document.getElementById('chat-input');
const query = input.value.trim();
if (!query) return;
input.value = '';
appendChatMessage('user', query);
const msgId = appendChatMessage('ai', 'Scanning telemetry database...');
try {
const resText = await callGeminiWithBackoff(query);
updateChatMessage(msgId, resText);
} catch(err) {
updateChatMessage(msgId, "Astro-AI link offline. Please try again.");
}
}
async function callGeminiWithBackoff(userQuery) {
const systemPrompt = "You are Astro-AI, an expert astrophysicist copilot. Provide concise, fascinating, scientifically accurate responses (2 short paragraphs max).";
const payload = {
contents: [{ parts: [{ text: userQuery }] }],
systemInstruction: { parts: [{ text: systemPrompt }] }
};
const delays = [1000, 2000, 4000];
for (let i = 0; i <= delays.length; i++) {
try {
const res = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${apiKey}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (res.ok) {
const data = await res.json();
return data.candidates?.[0]?.content?.parts?.[0]?.text || "No response.";
}
} catch(e){}
if (i < delays.length) await new Promise(r => setTimeout(r, delays[i]));
}
throw new Error("API call failed");
}
let chatCounter = 0;
function appendChatMessage(sender, text) {
chatCounter++;
const id = `msg-${chatCounter}`;
const container = document.getElementById('chat-messages');
const div = document.createElement('div');
div.id = id;
div.style.cssText = sender === 'user' ?
"padding:8px 10px; border-radius:10px; font-size:11px; line-height:1.4; background:rgba(14,116,144,0.4); border:1px solid rgba(56,189,248,0.3); color:#e0f2fe; align-self:flex-end;" :
"padding:8px 10px; border-radius:10px; font-size:11px; line-height:1.4; background:rgba(88,28,135,0.35); border:1px solid rgba(168,85,247,0.3); color:#f3e8ff; align-self:flex-start;";
div.innerText = text;
container.appendChild(div);
container.scrollTop = container.scrollHeight;
return id;
}
function updateChatMessage(id, text) {
const el = document.getElementById(id);
if (el) el.innerText = text;
}
window.onresize = function() {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
};
// START SIMULATION
render();
</script>
</body>
</html>
Game Source: Interactive 3D Space Explorer
Creator: LunarGalaxy87
Libraries: none
Complexity: complex (660 lines, 36.8 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: interactive-3d-space-explorer-lunargalaxy87" to link back to the original. Then publish at arcadelab.ai/publish.