<!DOCTYPE html>
<html lang="en" class="h-full dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Interactive Earth & Satellite Orbit Visualizer - Three.js WebGL</title>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Outfit:wght@600;700;800&display=swap" rel="stylesheet">
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
body { margin: 0; background: radial-gradient(circle at center, #08142c 0%, #020617 100%); overflow: hidden; font-family: 'Inter', sans-serif; }
#globeCanvas { width: 100%; height: 100%; cursor: grab; touch-action: none; }
#globeCanvas:active { cursor: grabbing; }
</style>
</head>
<body class="h-screen text-white flex flex-col select-none">
<!-- Top Glass Header -->
<header class="h-14 bg-slate-900/80 backdrop-blur-xl border-b border-cyan-500/20 px-4 sm:px-6 flex items-center justify-between z-20">
<div class="flex items-center gap-3">
<span class="w-9 h-9 rounded-xl bg-gradient-to-tr from-cyan-500 to-blue-600 flex items-center justify-center text-lg shadow-lg shadow-cyan-500/25">🌍</span>
<div>
<h1 class="font-bold text-sm sm:text-base tracking-tight text-white">3D Global Satellite & Telemetry Globe</h1>
<p class="text-[10px] text-cyan-400 font-mono">WebGL Three.js • Real-Time Orbital Nodes</p>
</div>
</div>
<div class="flex items-center gap-2">
<button onclick="setTheme('cyber')" class="px-3 py-1.5 rounded-lg bg-cyan-500/20 border border-cyan-500/40 text-cyan-300 text-xs font-bold hover:bg-cyan-500 hover:text-slate-950 transition">Cyber Cyan</button>
<button onclick="setTheme('emerald')" class="px-3 py-1.5 rounded-lg bg-emerald-500/20 border border-emerald-500/40 text-emerald-300 text-xs font-bold hover:bg-emerald-500 hover:text-slate-950 transition">Matrix Green</button>
<button onclick="setTheme('nebula')" class="px-3 py-1.5 rounded-lg bg-purple-500/20 border border-purple-500/40 text-purple-300 text-xs font-bold hover:bg-purple-500 hover:text-slate-950 transition">Nebula Violet</button>
</div>
</header>
<!-- 3D Viewport & Floating Telemetry HUD -->
<div class="flex-1 relative overflow-hidden">
<canvas id="globeCanvas"></canvas>
<!-- Left Floating Telemetry Card -->
<div class="absolute top-4 left-4 w-64 bg-slate-900/75 backdrop-blur-xl border border-slate-800 rounded-2xl p-4 space-y-3 pointer-events-auto">
<div class="flex items-center justify-between">
<span class="text-[10px] font-mono uppercase tracking-wider text-cyan-400 font-bold">● Live Orbital Ring</span>
<span class="text-[10px] font-mono text-emerald-400">60 FPS</span>
</div>
<div class="grid grid-cols-2 gap-2 text-xs">
<div class="p-2.5 rounded-xl bg-slate-950/80 border border-slate-800">
<span class="text-[10px] text-slate-400 block">Satellites</span>
<span class="font-bold text-white font-mono">24 Active</span>
</div>
<div class="p-2.5 rounded-xl bg-slate-950/80 border border-slate-800">
<span class="text-[10px] text-slate-400 block">Data Hubs</span>
<span class="font-bold text-cyan-400 font-mono">128 Nodes</span>
</div>
</div>
<div class="space-y-1">
<label class="text-[11px] text-slate-300 flex justify-between">
<span>Orbit Rotation Speed</span>
<span id="speedVal" class="text-cyan-400 font-mono">1.0x</span>
</label>
<input id="speedSlider" type="range" min="0" max="3" step="0.2" value="1" class="w-full accent-cyan-500">
</div>
</div>
<!-- Bottom Touch/Mouse Instructions -->
<div class="absolute bottom-4 left-1/2 -translate-x-1/2 bg-slate-900/80 backdrop-blur-md border border-slate-800 px-4 py-2 rounded-full text-xs text-slate-300 flex items-center gap-2 pointer-events-none">
<span class="w-2 h-2 rounded-full bg-cyan-400 animate-ping"></span>
<span>Drag with Mouse or Finger to Rotate 360° • Scroll to Zoom</span>
</div>
</div>
<script>
const canvas = document.getElementById('globeCanvas');
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / (window.innerHeight - 56), 0.1, 1000);
camera.position.set(0, 0.8, 5.2);
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight - 56);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
const worldGroup = new THREE.Group();
scene.add(worldGroup);
// 1. Core Wireframe & Shaded Earth Sphere
const sphereGeo = new THREE.SphereGeometry(1.55, 40, 40);
const coreMat = new THREE.MeshPhongMaterial({
color: 0x06182c,
emissive: 0x041224,
specular: 0x06b6d4,
shininess: 35,
transparent: true,
opacity: 0.94
});
const earthMesh = new THREE.Mesh(sphereGeo, coreMat);
worldGroup.add(earthMesh);
// 2. Glowing Cyber Latitude/Longitude Wireframe Overlay
const wireGeo = new THREE.SphereGeometry(1.56, 24, 24);
const wireMat = new THREE.MeshBasicMaterial({
color: 0x06b6d4,
wireframe: true,
transparent: true,
opacity: 0.25
});
const wireMesh = new THREE.Mesh(wireGeo, wireMat);
worldGroup.add(wireMesh);
// 3. Glowing City Data Beacons on Surface
const beaconGroup = new THREE.Group();
const beaconMat = new THREE.MeshBasicMaterial({ color: 0x22d3ee });
for (let i = 0; i < 48; i++) {
const phi = Math.acos(-1 + (2 * i) / 48);
const theta = Math.sqrt(48 * Math.PI) * phi;
const r = 1.58;
const bGeo = new THREE.SphereGeometry(0.03, 8, 8);
const bMesh = new THREE.Mesh(bGeo, beaconMat);
bMesh.position.set(
r * Math.cos(theta) * Math.sin(phi),
r * Math.sin(theta) * Math.sin(phi),
r * Math.cos(phi)
);
beaconGroup.add(bMesh);
}
worldGroup.add(beaconGroup);
// 4. Dual Orbital Satellite Rings
const ringGeo1 = new THREE.TorusGeometry(2.1, 0.012, 16, 120);
const ringMat = new THREE.MeshBasicMaterial({ color: 0x38bdf8, transparent: true, opacity: 0.55 });
const orbitRing1 = new THREE.Mesh(ringGeo1, ringMat);
orbitRing1.rotation.x = Math.PI / 2.4;
orbitRing1.rotation.y = 0.3;
worldGroup.add(orbitRing1);
const orbitRing2 = new THREE.Mesh(new THREE.TorusGeometry(2.35, 0.01, 16, 120), ringMat);
orbitRing2.rotation.x = -Math.PI / 3;
orbitRing2.rotation.z = 0.5;
worldGroup.add(orbitRing2);
// 5. Starfield Background
const starGeo = new THREE.BufferGeometry();
const starCount = 700;
const starPos = new Float32Array(starCount * 3);
for (let i = 0; i < starCount * 3; i++) {
starPos[i] = (Math.random() - 0.5) * 35;
}
starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
const stars = new THREE.Points(starGeo, new THREE.PointsMaterial({ color: 0x94a3b8, size: 0.04 }));
scene.add(stars);
// Lights
scene.add(new THREE.AmbientLight(0xffffff, 0.6));
const dirLight = new THREE.DirectionalLight(0x22d3ee, 1.8);
dirLight.position.set(5, 4, 5);
scene.add(dirLight);
// Pointer Drag Rotation (Mouse + Mobile Touch)
let dragging = false, prevX = 0, prevY = 0, rotSpeed = 1;
canvas.addEventListener('pointerdown', e => { dragging = true; prevX = e.clientX; prevY = e.clientY; });
window.addEventListener('pointermove', e => {
if (!dragging) return;
worldGroup.rotation.y += (e.clientX - prevX) * 0.007;
worldGroup.rotation.x += (e.clientY - prevY) * 0.007;
prevX = e.clientX; prevY = e.clientY;
});
window.addEventListener('pointerup', () => dragging = false);
canvas.addEventListener('wheel', e => {
camera.position.z = Math.max(3.2, Math.min(8, camera.position.z + e.deltaY * 0.003));
});
document.getElementById('speedSlider').addEventListener('input', e => {
rotSpeed = parseFloat(e.target.value);
document.getElementById('speedVal').innerText = rotSpeed.toFixed(1) + 'x';
});
function setTheme(mode) {
const colors = {
cyber: { wire: 0x06b6d4, beacon: 0x22d3ee, ring: 0x38bdf8 },
emerald: { wire: 0x10b981, beacon: 0x34d399, ring: 0x059669 },
nebula: { wire: 0xa855f7, beacon: 0xe879f9, ring: 0xc084fc }
}[mode];
wireMat.color.setHex(colors.wire);
beaconMat.color.setHex(colors.beacon);
ringMat.color.setHex(colors.ring);
dirLight.color.setHex(colors.beacon);
}
function animate() {
requestAnimationFrame(animate);
if (!dragging) {
worldGroup.rotation.y += 0.004 * rotSpeed;
}
orbitRing1.rotation.z += 0.008 * rotSpeed;
orbitRing2.rotation.y -= 0.006 * rotSpeed;
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / (window.innerHeight - 56);
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight - 56);
});
</script>
</body>
</html>
/* Built-in 3D WebGL & Glassmorphic Shaders included in index.html */
// Complete Three.js 3D scene, camera, lighting, and pointer controls included