🎮ArcadeLab

Interactive 3D Space Explorer

by LunarGalaxy87
660 lines36.8 KB
▶ Play
<!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.