Files
Space-Game/index.html
2026-05-25 13:00:20 -04:00

970 lines
52 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>GDD::DOCS — EVE-Inspired Multiplayer Prototype</title>
<!-- Styles -->
<link rel="stylesheet" href="css/tokens.css" />
<link rel="stylesheet" href="css/base.css" />
<link rel="stylesheet" href="css/layout.css" />
<link rel="stylesheet" href="css/components.css" />
<!-- Three.js (r160 is last version with UMD global build) -->
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
<!-- Three.js helpers (plain JS — no Babel needed, must run before Babel demos) -->
<script src="js/lib/three-helpers.js"></script>
<!-- React + Babel -->
<script src="https://unpkg.com/react@18.3.1/umd/react.development.js"
integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L"
crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js"
integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm"
crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js"
integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y"
crossorigin="anonymous"></script>
<!-- On-demand page loader (plain JS, before Babel scripts) -->
<script src="js/loader.js"></script>
</head>
<body>
<div id="root"></div>
<!--
All core scripts are inlined so the app works when opened as file://.
Babel Standalone cannot fetch external text/babel scripts on file://
(XHR/fetch is blocked), but it CAN transform inline text/babel blocks.
-->
<!-- ── State store ─────────────────────────────────────────────── -->
<script type="text/babel">
window.GDD = window.GDD || {};
/* Simple pub/sub state store — Zustand-like for prototype */
window.GDD.createStore = function createStore(initialState) {
let state = { ...initialState };
const listeners = new Set();
return {
getState: () => ({ ...state }),
setState: (partial) => {
const next = typeof partial === 'function' ? partial(state) : partial;
state = { ...state, ...next };
listeners.forEach(fn => fn(state));
},
subscribe: (fn) => {
listeners.add(fn);
return () => listeners.delete(fn);
}
};
};
/* App-level store */
window.GDD.appStore = window.GDD.createStore({
currentPage: 'overview',
sidebarCollapsed: false,
sidebarOpen: false, // mobile
connected: true,
playerName: 'Captain Riker',
playerCredits: 125000,
});
</script>
<!-- ── Fake backend ────────────────────────────────────────────── -->
<script type="text/babel">
window.GDD = window.GDD || {};
/* ---- Fake Data ---- */
const SYSTEMS = [
{ id: 'sol', name: 'Sol', security: 1.0, x: 400, y: 300, type: 'G2V Star', planets: 8, stations: ['Jita IV - Moon 4', 'Amarr VIII'], color: '#fbbf24' },
{ id: 'amarr', name: 'Amarr', security: 0.9, x: 550, y: 220, type: 'K4V Star', planets: 6, stations: ['Amarr Prime'], color: '#f59e0b' },
{ id: 'heinoo', name: 'Hek', security: 0.7, x: 320, y: 180, type: 'M3V Star', planets: 4, stations: ['Hek VII'], color: '#22c55e' },
{ id: 'rens', name: 'Rens', security: 0.6, x: 250, y: 350, type: 'G9V Star', planets: 7, stations: ['Rens VI'], color: '#22d3ee' },
{ id: 'dodixie', name: 'Dodixie', security: 0.8, x: 480, y: 400, type: 'F7V Star', planets: 5, stations: ['Dodixie IX'], color: '#a78bfa' },
{ id: 'u-irtyr', name: 'U-IRTYR', security: 0.3, x: 150, y: 250, type: 'M7V Star', planets: 3, stations: [], color: '#ef4444' },
{ id: 'pf-346', name: 'PF-346', security: 0.2, x: 600, y: 350, type: 'K7V Star', planets: 2, stations: ['PF-346 II'], color: '#ef4444' },
{ id: 'huzzah', name: 'YZ-LQL', security: 0.1, x: 100, y: 400, type: 'M2V Star', planets: 1, stations: [], color: '#dc2626' },
{ id: 'pirates', name: 'O-WAMW', security: 0.0, x: 650, y: 150, type: 'M5V Red Giant', planets: 9, stations: [], color: '#991b1b' },
];
const CONNECTIONS = [
['sol', 'amarr'], ['sol', 'heinoo'], ['sol', 'rens'],
['amarr', 'dodixie'], ['amarr', 'pf-346'], ['amarr', 'pirates'],
['heinoo', 'u-irtyr'], ['heinoo', 'rens'],
['rens', 'u-irtyr'], ['rens', 'huzzah'],
['dodixie', 'pf-346'], ['dodixie', 'sol'],
['u-irtyr', 'huzzah'], ['pf-346', 'pirates'],
];
const ASTEROID_TYPES = ['Veldspar', 'Scordite', 'Pyroxeres', 'Kernite', 'Omber', 'Jaspet', 'Hemorphite', 'Arkonor'];
const ORE_PRICES = { Veldspar: 12, Scordite: 28, Pyroxeres: 45, Kernite: 85, Omber: 120, Jaspet: 190, Hemorphite: 340, Arkonor: 620 };
const MODULES = [
{ id: 'laser1', name: 'Mining Laser I', type: 'mining', slot: 'high', power: 40, cpu: 30, cycle: 10, active: false },
{ id: 'laser2', name: 'Mining Laser II', type: 'mining', slot: 'high', power: 50, cpu: 40, cycle: 8, active: false },
{ id: 'shield1', name: 'Shield Booster I', type: 'shield', slot: 'med', power: 30, cpu: 30, cycle: 5, active: false },
{ id: 'turret1', name: '150mm Railgun', type: 'weapon', slot: 'high', power: 50, cpu: 35, damage: 25, cycle: 3, active: false },
{ id: 'turret2', name: '200mm Autocannon', type: 'weapon', slot: 'high', power: 45, cpu: 30, damage: 35, cycle: 2.5, active: false },
{ id: 'warp1', name: '1MN Afterburner', type: 'propulsion', slot: 'med', power: 20, cpu: 25, speed: 1.5, cycle: 0, active: false },
{ id: 'scram1', name: 'Warp Scrambler I', type: 'ewar', slot: 'med', power: 25, cpu: 25, range: 20, cycle: 0, active: false },
{ id: 'armor1', name: 'Armor Plate I', type: 'armor', slot: 'low', power: 20, cpu: 10, cycle: 0, active: false },
{ id: 'magstab1', name: 'Magnetic Field Stabilizer', type: 'damage_mod', slot: 'low', power: 5, cpu: 15, cycle: 0, active: false },
{ id: 'cargo1', name: 'Cargo Expander I', type: 'cargo', slot: 'low', power: 0, cpu: 15, cycle: 0, active: false },
];
const MARKET_ORDERS = [
{ id: 1, station: 'Jita IV - Moon 4', type: 'sell', item: 'Veldspar', price: 14, quantity: 45000, seller: 'MinerKing42' },
{ id: 2, station: 'Jita IV - Moon 4', type: 'sell', item: 'Scordite', price: 32, quantity: 12000, seller: 'RockHound' },
{ id: 3, station: 'Jita IV - Moon 4', type: 'buy', item: 'Arkonor', price: 580, quantity: 500, seller: 'IndustrialMega' },
{ id: 4, station: 'Jita IV - Moon 4', type: 'sell', item: 'Kernite', price: 90, quantity: 8000, seller: 'DeepMiner' },
{ id: 5, station: 'Jita IV - Moon 4', type: 'buy', item: 'Pyroxeres', price: 42, quantity: 20000, seller: 'RefineryCorp' },
{ id: 6, station: 'Amarr Prime', type: 'sell', item: 'Omber', price: 135, quantity: 6000, seller: 'AmarrTrader' },
{ id: 7, station: 'Amarr Prime', type: 'buy', item: 'Jaspet', price: 180, quantity: 3000, seller: 'HighSecOps' },
{ id: 8, station: 'Rens VI', type: 'sell', item: 'Hemorphite', price: 360, quantity: 1200, seller: 'NullRunner' },
{ id: 9, station: 'Rens VI', type: 'sell', item: 'Veldspar', price: 11, quantity: 90000, seller: 'BulkMiner' },
{ id: 10, station: 'Dodixie IX', type: 'buy', item: 'Scordite', price: 30, quantity: 15000, seller: 'GallenteForge' },
];
const PLAYER_INVENTORY = [
{ item: 'Veldspar', quantity: 8500, unitPrice: 12 },
{ item: 'Scordite', quantity: 2300, unitPrice: 28 },
{ item: 'Kernite', quantity: 400, unitPrice: 85 },
{ item: 'Pyroxeres', quantity: 1200, unitPrice: 45 },
];
const PLAYER_SHIPS = [
{ id: 'ship1', name: 'Merlin', class: 'Frigate', system: 'Sol', status: 'active', highSlots: 3, medSlots: 3, lowSlots: 2, cpu: 120, powerGrid: 40, fitted: ['laser1', 'turret1', 'warp1'] },
{ id: 'ship2', name: 'Thrasher', class: 'Destroyer', system: 'Amarr', status: 'docked', highSlots: 7, medSlots: 3, lowSlots: 3, cpu: 180, powerGrid: 65, fitted: ['turret1', 'turret2', 'scram1'] },
];
const PLAYER_BOUNTIES = [
{ target: 'PirateKing99', pool: 125000, tier: 'Dangerous', lastHostile: '2h ago' },
{ target: 'NullSecWarlord', pool: 520000, tier: 'Most Wanted', lastHostile: '30m ago' },
];
const PLAYER_SKILLS = [
{ name: 'Gunnery', level: 2, xp: 380, nextLevel: 500, category: 'Combat' },
{ name: 'Mining', level: 3, xp: 1850, nextLevel: 2000, category: 'Industry' },
{ name: 'Refining', level: 2, xp: 420, nextLevel: 500, category: 'Industry' },
{ name: 'Navigation', level: 1, xp: 80, nextLevel: 100, category: 'Navigation' },
{ name: 'Broker Relations', level: 1, xp: 45, nextLevel: 100, category: 'Trade' },
];
const KILL_FEED = [
{ victim: 'MinerBob', killer: 'PirateKing99', ship: 'Rifter', system: 'U-IRTYR', bounty: 5000, time: '5m ago' },
{ victim: 'TraderAlice', killer: 'CMDR Worf', ship: 'Hauler', system: 'Hek', bounty: 0, time: '12m ago' },
{ victim: 'PirateScout', killer: 'CMDR Picard', ship: 'Merlin', system: 'Sol', bounty: 2000, time: '25m ago' },
];
/* ---- Simulated API ---- */
const delay = (ms) => new Promise(r => setTimeout(r, ms + Math.random() * 50));
window.GDD.api = {
getSystems: async () => { await delay(80); return SYSTEMS.map(s => ({ ...s })); },
getConnections: async () => { await delay(60); return CONNECTIONS.map(c => [...c]); },
getSystemDetail: async (id) => { await delay(100); return SYSTEMS.find(s => s.id === id) || null; },
setDestination: async (systemId) => { await delay(150); return { success: true, destination: systemId, eta: '3m 42s' }; },
getShipStatus: async () => {
await delay(100);
return {
name: 'Merlin', class: 'Frigate', system: 'Sol',
x: 400, y: 300, status: 'idle', speed: 0, maxSpeed: 250,
shields: 100, armor: 100, hull: 100, capacitor: 85,
cargo: { used: 12400, total: 25000 },
target: null,
};
},
getModules: async () => { await delay(80); return MODULES.map(m => ({ ...m, active: false })); },
toggleModule: async (moduleId) => { await delay(100); return { success: true, moduleId, active: true }; },
getNearbyEntities: async () => {
await delay(120);
return [
{ id: 'npc1', name: 'Guristas Pirate', type: 'hostile', x: 430, y: 280, shields: 100, distance: 45 },
{ id: 'asteroid1', name: 'Veldspar Asteroid', type: 'asteroid', x: 370, y: 320, resource: 'Veldspar', quantity: 8500, distance: 12 },
{ id: 'station1', name: 'Jita IV - Moon 4', type: 'station', x: 410, y: 310, distance: 8 },
{ id: 'player2', name: 'CMDR LaForge', type: 'player', x: 390, y: 260, distance: 55 },
];
},
getMarketOrders: async (stationId) => { await delay(150); return MARKET_ORDERS.filter(o => !stationId || o.station === stationId); },
getPlayerInventory: async () => { await delay(80); return PLAYER_INVENTORY.map(i => ({ ...i })); },
placeOrder: async (type, item, price, quantity) => { await delay(200); return { success: true, orderId: Date.now() }; },
sellItem: async (item, quantity, stationId) => { await delay(250); return { success: true, isk: quantity * (ORE_PRICES[item] || 10) }; },
getChatMessages: async () => {
await delay(60);
return [
{ id: 1, sender: 'CMDR Picard', body: 'Heading to Jita with a cargo of Kernite.', time: '14:22' },
{ id: 2, sender: 'CMDR Worf', body: 'Pirates spotted near U-IRTYR gate. Stay alert.', time: '14:25' },
{ id: 3, sender: 'CMDR Data', body: 'Scordite prices up 12% in Amarr this hour.', time: '14:28' },
{ id: 4, sender: 'CMDR Troi', body: 'Anyone want to form a mining fleet in Sol?', time: '14:31' },
];
},
sendMessage: async (body) => { await delay(50); return { success: true, id: Date.now() }; },
getOrePrices: async () => { await delay(80); return { ...ORE_PRICES }; },
getShipFittings: async (shipId) => { await delay(100); const ship = PLAYER_SHIPS.find(s => s.id === shipId); return ship ? ship.fitted.map(id => MODULES.find(m => m.id === id)).filter(Boolean) : []; },
getPlayerShips: async () => { await delay(100); return PLAYER_SHIPS.map(s => ({ ...s })); },
getAvailableModules: async () => { await delay(80); return MODULES.map(m => ({ ...m })); },
fitModule: async (shipId, moduleId) => { await delay(150); return { success: true, shipId, moduleId }; },
unfitModule: async (shipId, slotIndex) => { await delay(100); return { success: true, shipId, slotIndex }; },
refineOre: async (oreType, quantity) => {
await delay(200);
const prices = ORE_PRICES[oreType] || 10;
return { success: true, ore: oreType, quantity, iskEarned: Math.floor(quantity * prices * 0.7), efficiency: 0.7 };
},
manufactureItem: async (blueprintId, stationId) => { await delay(300); return { success: true, jobId: Date.now(), eta: '5m 00s' }; },
getBounties: async () => { await delay(100); return PLAYER_BOUNTIES.map(b => ({ ...b })); },
placeBounty: async (targetPlayer, amount) => { await delay(150); return { success: true, target: targetPlayer, amount }; },
getKillFeed: async () => { await delay(80); return KILL_FEED.map(k => ({ ...k })); },
getPlayerSkills: async () => { await delay(100); return PLAYER_SKILLS.map(s => ({ ...s })); },
sendPrivateMessage: async (recipient, body) => {
const dist = Math.floor(Math.random() * 20);
const lightDelay = Math.floor(Math.sqrt(dist) * 2);
await delay(50);
return { success: true, id: Date.now(), recipient, lightDelay };
},
getBookmarks: async () => {
await delay(80);
return [
{ id: 1, name: 'Safe spot Alpha', system: 'Sol', x: 380, y: 290, type: 'safe' },
{ id: 2, name: 'Good Veldspar belt', system: 'Amarr', x: 560, y: 210, type: 'mining' },
{ id: 3, name: 'Ambush point', system: 'U-IRTYR', x: 160, y: 260, type: 'tactical' },
];
},
};
const CELESTIAL_BODIES = {
'sol': {
description: 'The cradle of humanity. A stable G2V main-sequence star hosting the busiest trade hub in known space.',
faction: 'CONCORD',
population: '12.4 billion',
resources: ['Veldspar', 'Scordite', 'Pyroxeres', 'Kernite'],
bodies: [
{ name: 'Mercury', type: 'rocky', orbit: 6, period: 4, size: 0.3, color: '#a0a0a0', ecc: 0.2, inc: 0.12, moons: [] },
{ name: 'Venus', type: 'rocky', orbit: 9, period: 6.5, size: 0.5, color: '#e8c56d', ecc: 0.01, inc: 0.06, moons: [], atmosphere: '#e8c56d' },
{ name: 'Earth', type: 'terrestrial', orbit: 13, period: 10, size: 0.55, color: '#4a90d9', ecc: 0.017, inc: 0, atmosphere: '#6ac0ff', moons: [{ name: 'Luna', orbit: 2, period: 2, size: 0.15, color: '#c0c0c0' }] },
{ name: 'Mars', type: 'rocky', orbit: 17, period: 14, size: 0.4, color: '#c1440e', ecc: 0.09, inc: 0.03, moons: [{ name: 'Phobos', orbit: 1.2, period: 0.8, size: 0.08, color: '#8a7a6a' }, { name: 'Deimos', orbit: 1.8, period: 1.5, size: 0.06, color: '#7a6a5a' }] },
{ name: 'Asteroid Belt', type: 'belt', innerOrbit: 22, outerOrbit: 26, count: 80 },
{ name: 'Jupiter', type: 'gas', orbit: 32, period: 35, size: 1.2, color: '#c88b3a', ecc: 0.048, inc: 0.02 },
{ name: 'Saturn', type: 'gas', orbit: 42, period: 50, size: 1.0, color: '#d4a560', ecc: 0.054, inc: 0.04, hasRings: true },
],
},
'amarr': {
description: 'Seat of the Amarr Empire. A warm K4V star surrounded by heavily industrialized worlds.',
faction: 'Amarr Empire',
population: '9.1 billion',
resources: ['Kernite', 'Omber', 'Pyroxeres'],
bodies: [
{ name: 'Amarr I', type: 'rocky', orbit: 5, period: 3, size: 0.35, color: '#d4a040', ecc: 0.02, inc: 0.01, moons: [] },
{ name: 'Amarr II', type: 'terrestrial', orbit: 10, period: 8, size: 0.65, color: '#c06030', ecc: 0.01, inc: 0.05, atmosphere: '#c08050', moons: [] },
{ name: 'Amarr III', type: 'gas', orbit: 18, period: 22, size: 0.9, color: '#8b6914', ecc: 0.03, inc: 0.02, moons: [{ name: 'Amarr III-a', orbit: 2, period: 1.5, size: 0.12, color: '#a08040' }] },
{ name: 'Asteroid Belt', type: 'belt', innerOrbit: 24, outerOrbit: 27, count: 60 },
],
},
'heinoo': {
description: 'A frontier system popular with independent miners. Rich in common ores and frequently trafficked by haulers.',
faction: 'Minmatar Republic',
population: '340 million',
resources: ['Veldspar', 'Scordite', 'Plagioclase'],
bodies: [
{ name: 'Hek I', type: 'rocky', orbit: 5, period: 3.5, size: 0.3, color: '#7a8a6a', ecc: 0.05, inc: 0.02, moons: [] },
{ name: 'Hek II', type: 'terrestrial', orbit: 11, period: 9, size: 0.55, color: '#4a8a5a', ecc: 0.03, inc: 0.01, atmosphere: '#6aaa7a', moons: [{ name: 'Hek II-a', orbit: 2, period: 1.8, size: 0.1, color: '#8a8a7a' }] },
{ name: 'Hek III', type: 'gas', orbit: 20, period: 25, size: 0.8, color: '#5a7a9a', ecc: 0.02, inc: 0.03 },
],
},
'rens': {
description: 'Major trade hub in the Minmatar Republic. Bustling commerce and a strong naval presence keep pirates at bay.',
faction: 'Minmatar Republic',
population: '2.1 billion',
resources: ['Scordite', 'Pyroxeres', 'Kernite'],
bodies: [
{ name: 'Rens I', type: 'rocky', orbit: 6, period: 4, size: 0.35, color: '#9a7a5a', ecc: 0.03, inc: 0.01, moons: [] },
{ name: 'Rens II', type: 'terrestrial', orbit: 12, period: 10, size: 0.6, color: '#5a7aaa', ecc: 0.02, inc: 0.04, atmosphere: '#7a9acc', moons: [{ name: 'Rens II-a', orbit: 1.8, period: 1.4, size: 0.12, color: '#aaaaaa' }] },
{ name: 'Asteroid Belt', type: 'belt', innerOrbit: 18, outerOrbit: 22, count: 70 },
{ name: 'Rens III', type: 'gas', orbit: 28, period: 35, size: 1.1, color: '#aa7a3a', ecc: 0.04, inc: 0.02 },
],
},
'dodixie': {
description: 'Federation commerce hub. High-tech industry and cutting-edge research facilities orbit its F7V star.',
faction: 'Gallente Federation',
population: '3.8 billion',
resources: ['Omber', 'Kernite', 'Jaspet'],
bodies: [
{ name: 'Dodixie I', type: 'rocky', orbit: 4, period: 2.5, size: 0.25, color: '#6a5a7a', ecc: 0.01, inc: 0, moons: [] },
{ name: 'Dodixie II', type: 'terrestrial', orbit: 9, period: 7, size: 0.5, color: '#5a8aaa', ecc: 0.015, inc: 0.02, atmosphere: '#7aaacc', moons: [] },
{ name: 'Dodixie III', type: 'gas', orbit: 16, period: 18, size: 0.85, color: '#7a6aaa', ecc: 0.02, inc: 0.01, hasRings: true, moons: [{ name: 'Dodixie III-a', orbit: 2.5, period: 2, size: 0.1, color: '#9a8aba' }] },
],
},
'u-irtyr': {
description: 'Dangerous low-security system. Pirate activity is rampant and uncharted asteroid fields hide valuable ores.',
faction: 'Unclaimed',
population: '~2,000',
resources: ['Hemorphite', 'Jaspet', 'Arkonor'],
bodies: [
{ name: 'U-IRTYR I', type: 'rocky', orbit: 5, period: 3, size: 0.35, color: '#6a4a3a', ecc: 0.15, inc: 0.08, moons: [] },
{ name: 'U-IRTYR II', type: 'rocky', orbit: 10, period: 8, size: 0.3, color: '#5a3a2a', ecc: 0.12, inc: 0.1, moons: [] },
{ name: 'Asteroid Belt', type: 'belt', innerOrbit: 15, outerOrbit: 22, count: 100 },
],
},
'pf-346': {
description: 'Low-sec border system. Contested territory with valuable resources and frequent skirmishes.',
faction: 'Contested',
population: '~15,000',
resources: ['Jaspet', 'Hemorphite', 'Kernite'],
bodies: [
{ name: 'PF-346 I', type: 'rocky', orbit: 6, period: 4, size: 0.3, color: '#7a5a4a', ecc: 0.08, inc: 0.04, moons: [] },
{ name: 'PF-346 II', type: 'terrestrial', orbit: 12, period: 10, size: 0.5, color: '#4a6a5a', ecc: 0.05, inc: 0.03, moons: [{ name: 'PF-346 II-a', orbit: 1.5, period: 1.2, size: 0.08, color: '#8a8a7a' }] },
],
},
'huzzah': {
description: 'Null-sec wasteland. No law, no stations, no mercy. Rare ores attract the desperate and the bold.',
faction: 'Unclaimed',
population: '< 100',
resources: ['Arkonor', 'Bistot', 'Crokmite'],
bodies: [
{ name: 'YZ-LQL I', type: 'rocky', orbit: 5, period: 3.5, size: 0.25, color: '#4a3a2a', ecc: 0.2, inc: 0.15, moons: [] },
{ name: 'Asteroid Belt', type: 'belt', innerOrbit: 8, outerOrbit: 15, count: 120 },
],
},
'pirates': {
description: 'Deep null-sec. Pirate stronghold with hidden bases and rich, unexploited asteroid belts.',
faction: 'Guristas Pirates',
population: 'Unknown',
resources: ['Arkonor', 'Mercoxit', 'Dark Ochre'],
bodies: [
{ name: 'O-WAMW I', type: 'gas', orbit: 8, period: 6, size: 0.7, color: '#8a3a3a', ecc: 0.06, inc: 0.03, moons: [{ name: 'O-WAMW I-a', orbit: 2, period: 1.5, size: 0.12, color: '#6a4a4a' }] },
{ name: 'O-WAMW II', type: 'gas', orbit: 18, period: 20, size: 1.0, color: '#5a2a5a', ecc: 0.04, inc: 0.05, hasRings: true },
{ name: 'Asteroid Belt', type: 'belt', innerOrbit: 25, outerOrbit: 32, count: 90 },
{ name: 'O-WAMW III', type: 'rocky', orbit: 38, period: 45, size: 0.4, color: '#3a2a3a', ecc: 0.1, inc: 0.08, moons: [] },
],
},
};
window.GDD.CONSTANTS = { SYSTEMS, CONNECTIONS, ASTEROID_TYPES, ORE_PRICES, MODULES, MARKET_ORDERS, CELESTIAL_BODIES };
</script>
<!-- ── Router ───────────────────────────────────────────────────── -->
<script type="text/babel">
window.GDD = window.GDD || {};
const { useState, useEffect, useCallback } = React;
/* Hash-based router */
window.GDD.useRouter = function() {
const [page, setPage] = useState(getPage());
function getPage() {
const hash = window.location.hash.slice(1) || 'overview';
return hash;
}
useEffect(() => {
const handler = () => setPage(getPage());
window.addEventListener('hashchange', handler);
return () => window.removeEventListener('hashchange', handler);
}, []);
const navigate = useCallback((path) => {
window.location.hash = path;
}, []);
return { page, navigate };
};
</script>
<!-- ── Sidebar ──────────────────────────────────────────────────── -->
<script type="text/babel">
window.GDD = window.GDD || {};
const GDD = window.GDD;
const { useState, useEffect } = React;
const NAV_SECTIONS = [
{
title: 'Documentation',
items: [
{ id: 'overview', icon: '◈', label: 'Overview' },
{ id: 'architecture', icon: '⬡', label: 'Architecture' },
{ id: 'techstack', icon: '⟐', label: 'Tech Stack' },
{ id: 'backend', icon: '⊞', label: 'Backend Model' },
{ id: 'agents', icon: '⏣', label: 'Agent Lifecycle' },
{ id: 'gameplay', icon: '◉', label: 'Gameplay Loop' },
{ id: 'ships', icon: '◇', label: 'Ships & Fitting' },
{ id: 'economy', icon: '⇄', label: 'Economy & Industry' },
{ id: 'social', icon: '✧', label: 'Progression & Social' },
{ id: 'ship-ai', icon: '◈', label: 'Ship AI — Zora' },
{ id: 'roadmap', icon: '⊞', label: 'Roadmap' },
{ id: 'risks', icon: '◬', label: 'Risks & Questions' },
{ id: 'demo-gallery', icon: '◈', label: 'Demo Gallery' },
]
},
{
title: 'Interactive Demos',
items: [
{ id: 'demo-starmap', icon: '✦', label: 'Star Map' },
{ id: 'demo-movement', icon: '→', label: 'Ship Movement' },
{ id: 'demo-combat', icon: '✸', label: 'Combat System' },
{ id: 'demo-market', icon: '⇄', label: 'Market' },
{ id: 'demo-fitting', icon: '⊞', label: 'Ship Fitting' },
{ id: 'demo-refining', icon: '⚗', label: 'Refining & MFG' },
{ id: 'demo-progression', icon: '▲', label: 'Skill Progression' },
{ id: 'demo-bounty', icon: '✸', label: 'Bounty & Kill Feed' },
{ id: 'demo-gamehud', icon: '◉', label: 'Game HUD' },
{ id: 'demo-chat', icon: '💬', label: 'Chat & Comms' },
{ id: 'demo-zora', icon: '🤖', label: 'Zora Tier 0' },
{ id: 'demo-galaxy', icon: '🌌', label: 'Galaxy Gen' },
]
}
];
function Sidebar({ collapsed, currentPage, onNavigate, onToggle }) {
const [hoveredItem, setHoveredItem] = useState(null);
return (
<aside className={`sidebar${collapsed ? ' collapsed' : ''}`}>
<div className="sidebar-header">
<div className="sidebar-logo">
GDD<span className="logo-dot">::</span>DOCS
</div>
</div>
<nav className="sidebar-nav">
{NAV_SECTIONS.map((section, si) => (
<div key={si}>
<div className="nav-section-title">{section.title}</div>
{section.items.map((item) => (
<a
key={item.id}
className={`nav-item${currentPage === item.id ? ' active' : ''}`}
href={`#${item.id}`}
onClick={(e) => { e.preventDefault(); onNavigate(item.id); }}
onMouseEnter={() => setHoveredItem(item.id)}
onMouseLeave={() => setHoveredItem(null)}
>
<span className="nav-icon">{item.icon}</span>
<span className="nav-label">{item.label}</span>
</a>
))}
</div>
))}
</nav>
</aside>
);
}
GDD.Sidebar = Sidebar;
</script>
<!-- ── TopBar ───────────────────────────────────────────────────── -->
<script type="text/babel">
window.GDD = window.GDD || {};
const GDD = window.GDD;
const PAGE_TITLES = {
'overview': 'Overview',
'architecture': 'Architecture',
'techstack': 'Tech Stack',
'backend': 'Backend Model',
'agents': 'Agent Lifecycle & Scheduling',
'gameplay': 'Gameplay Loop',
'roadmap': 'Roadmap',
'risks': 'Risks & Questions',
'demo-starmap': 'Star Map',
'demo-movement': 'Ship Movement',
'demo-combat': 'Combat System',
'demo-market': 'Market Interface',
};
function TopBar({ collapsed, currentPage, onToggle }) {
const isDemo = currentPage.startsWith('demo-');
const section = isDemo ? 'Demos' : 'Docs';
const title = PAGE_TITLES[currentPage] || currentPage;
return (
<div className="topbar">
<button className="topbar-toggle" onClick={onToggle}>
{collapsed ? '→' : '←'}
</button>
<div className="topbar-breadcrumb">
<span className="bc-root">GDD</span>
<span className="bc-sep">/</span>
<span>{section}</span>
<span className="bc-sep">/</span>
<span className="bc-current">{title}</span>
</div>
<div className="topbar-status">
<span><span className="status-dot online"></span> Connected</span>
<span style={{ color: 'var(--accent)' }}></span>
<span>Prototype v0.1.0</span>
</div>
</div>
);
}
GDD.TopBar = TopBar;
</script>
<!-- ── Overview page ────────────────────────────────────────────── -->
<script type="text/babel">
window.GDD = window.GDD || {};
function OverviewPage() {
return (
<div className="content-inner">
<h1 style={{ marginBottom: '8px' }}>EVE-Inspired Multiplayer Prototype</h1>
<p style={{ color: 'var(--fg-dim)', fontSize: '1.1rem', maxWidth: '680px', marginBottom: 'var(--sp-6)' }}>
A browser-based <strong style={{ color: 'var(--fg-bright)' }}>spreadsheet simulator</strong> in the EVE Online tradition,
set inside a <strong style={{ color: 'var(--fg-bright)' }}>single persistent galaxy</strong> that the server simulates as a living world.
The game is played through UI panels, market tables, inventory grids, and chat channels not by flying a ship.
Movement and combat are deliberately rudimentary; the depth lives in the economy, information diffusion,
strategic decisions, and a galaxy that evolves around you through <strong style={{ color: 'var(--fg-bright)' }}>dynamic PvE events and emergent world story</strong>.
</p>
<div className="stat-grid">
<div className="stat-card">
<div className="stat-value" style={{ color: 'var(--accent)' }}>UI-first</div>
<div className="stat-label">Design Pillar</div>
</div>
<div className="stat-card">
<div className="stat-value" style={{ color: 'var(--cyan)' }}>SpacetimeDB</div>
<div className="stat-label">Backend</div>
</div>
<div className="stat-card">
<div className="stat-value" style={{ color: 'var(--green)' }}>~3 hrs</div>
<div className="stat-label">Session Target</div>
</div>
<div className="stat-card">
<div className="stat-value" style={{ color: 'var(--purple)' }}>Player-led</div>
<div className="stat-label">Economy Model</div>
</div>
<div className="stat-card">
<div className="stat-value" style={{ color: 'var(--red)' }}>Living Galaxy</div>
<div className="stat-label">World Model</div>
</div>
</div>
<div className="section-header">
<span className="section-num">OV-01</span>
<h2 style={{ margin: 0 }}>Product Vision</h2>
</div>
<div className="callout callout-info">
<strong>Primary recommendation:</strong> Build the MVP as a UI-heavy browser game a <em>spreadsheet simulator</em>.
The 3D scene is a strategic map layer for context, not the game itself. Players spend 90% of their time
in tables, charts, and panels: market depth, order books, cargo manifests, fitting spreadsheets,
route planners, and chat. The 3D viewport exists to give spatial awareness, not twitch gameplay.
</div>
<h3>Core Pillars</h3>
<table className="data-table">
<thead>
<tr>
<th>Pillar</th>
<th>Prototype Interpretation</th>
<th>MVP Scope</th>
</tr>
</thead>
<tbody>
<tr>
<td><span className="pill pill-amber">Economy &amp; markets</span></td>
<td style={{ color: 'var(--fg-dim)' }}>Player-led economy with NPC support. Mining refining manufacturing trade. Geographic price differences, contract markets, order books, and <strong style={{ color: 'var(--accent)' }}>information asymmetry between systems</strong> create emergent trade routes and speculative opportunities. <strong style={{ color: 'var(--accent)' }}>This IS the game.</strong></td>
<td><span className="pill pill-green">Era 1</span> <span style={{ fontSize: '0.75rem', color: 'var(--fg-dim)' }}>NPC economy, single-player mining/refining/manufacturing</span><br/><span className="pill pill-cyan" style={{ fontSize: '0.65rem' }}>Era 2</span> <span style={{ fontSize: '0.75rem', color: 'var(--fg-dim)' }}>Player-to-player market, info diffusion</span></td>
</tr>
<tr>
<td><span className="pill pill-cyan">Social & multiplayer</span></td>
<td style={{ color: 'var(--fg-dim)' }}>Local chat, delayed PMs, bounty system, emergent player-driven justice. Communication is range-based. <strong style={{ color: 'var(--accent)' }}>Priority pillar.</strong></td>
<td><span className="pill pill-cyan" style={{ fontSize: '0.65rem' }}>Era 2</span> <span style={{ fontSize: '0.75rem', color: 'var(--fg-dim)' }}>All social features require multiplayer</span></td>
</tr>
<tr>
<td><span className="pill pill-green">Command-based (not action)</span></td>
<td style={{ color: 'var(--fg-dim)' }}>Players issue high-level intentions click a point of interest and the ship autopilots there. Click a hostile and the ship auto-engages. During combat the player manages <strong style={{ color: 'var(--fg)' }}>reactor power allocation</strong> (FTL-style) between systems. No manual flight, no aiming, no skill shots. The skill is in <strong style={{ color: 'var(--fg)' }}>what</strong> you power, not <strong style={{ color: 'var(--fg)' }}>how fast</strong> you click.</td>
<td><span className="pill pill-green">Era 1</span> <span style={{ fontSize: '0.75rem', color: 'var(--fg-dim)' }}>Core combat & movement loop</span></td>
</tr>
<tr>
<td><span className="pill pill-purple">Ship fitting</span></td>
<td style={{ color: 'var(--fg-dim)' }}>CPU/Power Grid slot system. High/Med/Low slots with meaningful fitting tradeoffs. Multiple ships, AI crew (post-MVP).</td>
<td><span className="pill pill-green">Era 1</span> <span style={{ fontSize: '0.75rem', color: 'var(--fg-dim)' }}>Basic fitting, single ship class</span></td>
</tr>
<tr>
<td><span className="pill pill-amber">Emergent lore</span></td>
<td style={{ color: 'var(--fg-dim)' }}>No server has the same lore as another. The galaxy is a <strong style={{ color: 'var(--fg)' }}>single persistent world</strong> with systems, planets, anomalies, and orbiting objects. A world simulation layer spawns PvE events dynamically faction wars, space anomalies, migrations, raids that create a <strong style={{ color: 'var(--fg)' }}>living story unique to every server</strong>. Lore evolves through both player actions and server-driven world events.</td>
<td><span className="pill pill-cyan" style={{ fontSize: '0.65rem' }}>Era 2</span> <span style={{ fontSize: '0.75rem', color: 'var(--fg-dim)' }}>Living galaxy requires world agents</span></td>
</tr>
</tbody>
</table>
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
<span className="section-num">OV-02</span>
<h2 style={{ margin: 0 }}>Design Principles</h2>
</div>
<div className="grid-2">
<div className="card" style={{ borderLeft: '3px solid var(--accent)' }}>
<h4 style={{ color: 'var(--accent)' }}>Spreadsheet simulator, not flight sim</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
90% of gameplay happens in tables, charts, and panels: market order books, cargo manifests, fitting spreadsheets,
route planners, ship AI logs, and chat channels. The 3D viewport gives spatial awareness, not twitch gameplay.
Movement is click-to-autopilot. Combat is click-to-engage with FTL-style power management. <strong style={{ color: 'var(--fg)' }}>The depth is in the economy and information.</strong>
</p>
</div>
<div className="card">
<h4 style={{ color: 'var(--cyan)' }}>Authoritative backend</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
SpacetimeDB owns authoritative game state. The browser renders state and sends player
intentions. The renderer should never become the source of truth.
</p>
</div>
<div className="card">
<h4 style={{ color: 'var(--green)' }}>Information is the real currency</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
Market data propagates at the speed of player travel, not the speed of light. Knowing a price discrepancy exists
before other traders that's the skill. The ship AI (Zora) is a living market intelligence tool. See the
<em> Economy → 📡 Info Diffusion</em> tab for the full model.
</p>
</div>
<div className="card">
<h4 style={{ color: 'var(--purple)' }}>Movement & combat are not action</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: 0 }}>
The player never pilots the ship directly. Click a destination → ship autopilots. Click a hostile → ship auto-engages.
During combat, the player manages <strong style={{ color: 'var(--fg)' }}>reactor power allocation</strong> (FTL-style: weapons/shields/engines/aux)
and <strong style={{ color: 'var(--fg)' }}>subsystem targeting</strong>. That's it. Ship destruction
is an economic event (ISK sink, insurance payout, loot drop), not a competitive action moment. ISK (symbol ) is the canonical in-game currency.
</p>
</div>
</div>
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
<span className="section-num">OV-03</span>
<h2 style={{ margin: 0 }}>Core MVP Loop</h2>
</div>
<div className="card card-accent" style={{ padding: 'var(--sp-6) var(--sp-8)' }}>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.85rem', color: 'var(--fg-dim)', lineHeight: 2 }}>
<span style={{ color: 'var(--cyan)' }}>Connect</span> <span style={{ color: 'var(--accent)' }}>Spawn Ship</span> {' '}
<span style={{ color: 'var(--green)' }}>Navigate</span> {' '}
<span style={{ color: 'var(--purple)' }}>Mine</span> {' '}
<span style={{ color: 'var(--fg-bright)' }}>Inventory</span> {' '}
<span style={{ color: 'var(--cyan)' }}>Station</span> {' '}
<span style={{ color: 'var(--accent)' }}>Sell Ore</span> {' '}
<span style={{ color: 'var(--green)' }}>Chat</span>
</div>
</div>
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
<strong>The real loop is economic.</strong> Connect gather information identify opportunity act on it profit.
The "mine → sell" cycle is the entry point. The endgame is inter-regional arbitrage, supply chain management,
manufacturing empires, and market manipulation all driven by <strong>information diffusion between systems</strong>.
</div>
<h3 style={{ marginTop: 'var(--sp-6)' }}>Minimum Viable Screens</h3>
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-4)' }}>
<strong>Era 1 screens</strong> ship in the single-player proof of concept (Roadmap phases 07). These are the minimum screens needed to validate the core loop is fun.
</div>
<table className="data-table">
<thead>
<tr><th>Screen / Panel</th><th>Minimum Functionality</th></tr>
</thead>
<tbody>
<tr><td>Login / Connect</td><td>Display current identity and connection status.</td></tr>
<tr><td>3D Star-System Map</td><td>Strategic overview ships, asteroids, station, click-to-set-waypoint. <strong>Not a flight sim.</strong></td></tr>
<tr><td>Ship Status Panel</td><td>Name, owner, status, cargo, current action, location.</td></tr>
<tr><td>Inventory Panel</td><td>Item type + quantity grid. Sell button when docked. Cargo capacity bar.</td></tr>
<tr><td>Station Panel</td><td>Dock/undock state, sell ore, view market, refit ship.</td></tr>
<tr><td>Market Panel</td><td>Order book, price per unit, place sell order from inventory. NPC-only economy in Era 1.</td></tr>
<tr><td>Combat HUD</td><td>Target selection, module activation, reactor power allocation bars (FTL-style).</td></tr>
<tr><td>Debug Panel</td><td>Reducer call log, error display, connection metrics, entity count.</td></tr>
</tbody>
</table>
<div className="callout callout-info" style={{ marginTop: 'var(--sp-6)', marginBottom: 'var(--sp-4)' }}>
<strong>Era 2 screens</strong> require SpacetimeDB multiplayer infrastructure (Roadmap phases 815).
</div>
<table className="data-table">
<thead>
<tr><th>Screen / Panel</th><th>Minimum Functionality</th></tr>
</thead>
<tbody>
<tr><td style={{ fontWeight: 600, color: 'var(--accent)' }}>Market Panel </td><td><strong>Primary game surface.</strong> Order book with depth, price history charts, contract specifications, bid/ask spread, long/short positions, margin account, place orders (market/limit/stop). See the Interactive Demos Market demo.</td></tr>
<tr><td style={{ fontWeight: 600, color: 'var(--cyan)' }}>Commodity Ticker</td><td>Scrolling price ticker across all contracts. Real-time price updates. Category filters. Sparkline charts.</td></tr>
<tr><td>Chat Panel</td><td>Send and receive local/system messages. Range-based propagation.</td></tr>
<tr><td>Bounty Board</td><td>Active bounties by tier, place bounty on player, kill feed.</td></tr>
<tr><td>Galaxy Map</td><td>Region/constellation/system hierarchy, faction territory overlay, active world events.</td></tr>
<tr><td>World Event Panel</td><td>Active events in current region, countdown timers, story log access.</td></tr>
</tbody>
</table>
<div className="section-header" style={{ marginTop: 'var(--sp-8)' }}>
<span className="section-num">OV-04</span>
<h2 style={{ margin: 0 }}>HUD & View Mode Architecture</h2>
</div>
<div className="callout callout-info" style={{ marginBottom: 'var(--sp-5)' }}>
<strong>Decision: Hybrid diegetic + panel approach.</strong>
The game uses <strong>two distinct view modes</strong> depending on the player's state. This resolves the ambiguity
between the gamehud demo (which renders diegetic overlays on a 3D viewport) and the spec's description of traditional panels.
Both are correct — they apply to different contexts.
</div>
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
<div className="card" style={{ borderLeft: '3px solid var(--cyan)' }}>
<h4 style={{ color: 'var(--cyan)' }}>🚀 Flight Mode (Undocked)</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
When the player's ship is in space (undocked), the primary view is a <strong style={{ color: 'var(--fg)' }}>3D viewport
with diegetic HUD overlays</strong>. This is what the Game HUD demo validates. The 3D scene shows the ship's
surroundings (asteroids, stations, other ships, celestials) while the HUD overlays provide:
</p>
<ul style={{ color: 'var(--fg-dim)', fontSize: '0.82rem', margin: 0, paddingLeft: 'var(--sp-5)' }}>
<li><strong style={{ color: 'var(--fg)' }}>Shield/armor/hull bars</strong> curved around the viewport center, not flat rectangles</li>
<li><strong style={{ color: 'var(--fg)' }}>Module activation buttons</strong> arranged in a bottom rack, grouped by slot type</li>
<li><strong style={{ color: 'var(--fg)' }}>Overview panel</strong> collapsible sidebar listing all on-grid entities with type, distance, and hostile/friendly status</li>
<li><strong style={{ color: 'var(--fg)' }}>Target lock indicator</strong> centered targeting reticle with lock timer</li>
<li><strong style={{ color: 'var(--fg)' }}>Capacitor gauge</strong> circular arc display</li>
<li><strong style={{ color: 'var(--fg)' }}>Speed/distance HUD</strong> current speed, target distance, ETA</li>
<li><strong style={{ color: 'var(--fg)' }}>Chat stub</strong> minimized chat bubble, expands to full chat on click</li>
</ul>
<div style={{ marginTop: 'var(--sp-3)', fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
Validated by: Game HUD demo · Covers: movement, combat, mining interactions
</div>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--accent)' }}>
<h4 style={{ color: 'var(--accent)' }}>🏢 Station Mode (Docked)</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
When the player is docked at a station, the 3D viewport is replaced by a <strong style={{ color: 'var(--fg)' }}>traditional
panel-based UI</strong> the "spreadsheet simulator" surface. This is the Overview spec's "tables, charts, and panels"
description. Station mode panels include:
</p>
<ul style={{ color: 'var(--fg-dim)', fontSize: '0.82rem', margin: 0, paddingLeft: 'var(--sp-5)' }}>
<li><strong style={{ color: 'var(--fg)' }}>Market Panel</strong> order book, price history charts, contract specifications</li>
<li><strong style={{ color: 'var(--fg)' }}>Inventory Panel</strong> item grid with quantity, type, value estimation</li>
<li><strong style={{ color: 'var(--fg)' }}>Fitting Screen</strong> ship slot layout with drag-and-drop module fitting</li>
<li><strong style={{ color: 'var(--fg)' }}>Refining Interface</strong> batch processing with yield preview</li>
<li><strong style={{ color: 'var(--fg)' }}>Manufacturing Tab</strong> blueprint selection, job queue, material requirements</li>
<li><strong style={{ color: 'var(--fg)' }}>Insurance Panel</strong> coverage tiers, premium calculator, active policies</li>
<li><strong style={{ color: 'var(--fg)' }}>Agent/Mission Panel</strong> NPC agent list, available missions, standings</li>
</ul>
<div style={{ marginTop: 'var(--sp-3)', fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
Validated by: Market, Fitting, Refining demos · Covers: all station-based gameplay
</div>
</div>
</div>
<div className="grid-2" style={{ marginBottom: 'var(--sp-6)' }}>
<div className="card" style={{ borderLeft: '3px solid var(--green)' }}>
<h4 style={{ color: 'var(--green)' }}>🗺 Map Mode (Both)</h4>
<p style={{ color: 'var(--fg-dim)', fontSize: '0.85rem', margin: '0 0 var(--sp-3) 0' }}>
Accessible from either view mode, the map replaces the main viewport with a full-screen 3D strategic map.
<strong style={{ color: 'var(--fg)' }}>Era 1</strong> shows the current star system (single system with celestials, belts, stations).
<strong style={{ color: 'var(--fg)' }}>Era 2</strong> adds the Galaxy Map (multi-system with region/constellation hierarchy, faction overlay, world events).
</p>
<div style={{ fontFamily: 'var(--font-mono)', fontSize: '0.72rem', color: 'var(--muted)' }}>
Era 1: System Map needed · Era 2: Validated by Star Map demo
</div>
</div>
<div className="card" style={{ borderLeft: '3px solid var(--purple)' }}>
<h4 style={{ color: 'var(--purple)' }}>Transition Rules</h4>
<ul style={{ color: 'var(--fg-dim)', fontSize: '0.82rem', margin: 0, paddingLeft: 'var(--sp-5)' }}>
<li><strong style={{ color: 'var(--fg)' }}>Undock:</strong> Flight Mode fades in with 3D viewport. HUD elements animate in sequence (shields modules overview).</li>
<li><strong style={{ color: 'var(--fg)' }}>Dock:</strong> 3D viewport zooms toward station. Fades to Station Mode panel layout.</li>
<li><strong style={{ color: 'var(--fg)' }}>Open Map:</strong> Current viewport shrinks into a corner (minimap). Full map overlay fades in.</li>
<li><strong style={{ color: 'var(--fg)' }}>Close Map:</strong> Map fades out. Previous viewport mode restores.</li>
<li><strong style={{ color: 'var(--fg)' }}>Combat ambush:</strong> If attacked while docked, player auto-undocks into Flight Mode (no safe space abuse).</li>
</ul>
</div>
</div>
<div className="callout callout-warn">
<strong>Why not all-diegetic or all-panel?</strong>
A fully diegetic HUD (Dead Space style) works for immersion but is terrible for spreadsheet gameplay you can't read
an order book through a holographic visor. A fully panel UI (traditional MMO) loses the spatial awareness that makes
space feel like space. The hybrid approach keeps the best of both: diegetic immersion during the action, panel efficiency
during the economy game. The key insight is that <strong>the game alternates between two distinct cognitive modes</strong>
reactive (in-space, monitoring health/modules/overview) and analytical (docked, reading tables/planning routes).
Each view mode is optimized for its cognitive mode.
</div>
</div>
);
}
window.GDD.OverviewPage = OverviewPage;
</script>
<!-- ── App bootstrap (must be last) ─────────────────────────────── -->
<script type="text/babel">
window.GDD = window.GDD || {};
const GDD = window.GDD;
const FULLSCREEN_PAGES = new Set([
'demo-starmap',
'demo-movement',
'demo-combat',
'demo-market',
'demo-fitting',
'demo-refining',
'demo-progression',
'demo-bounty',
'demo-gamehud',
'demo-chat',
'demo-zora',
'demo-galaxy',
]);
// Map pageId -> component name on GDD
const PAGE_COMPONENTS = {
'overview': 'OverviewPage',
'architecture': 'ArchitecturePage',
'techstack': 'TechStackPage',
'backend': 'BackendPage',
'agents': 'AgentsPage',
'gameplay': 'GameplayPage',
'ships': 'ShipsPage',
'economy': 'EconomyPage',
'social': 'SocialPage',
'ship-ai': 'ShipAIPage',
'roadmap': 'RoadmapPage',
'risks': 'RisksPage',
'demo-gallery': 'DemoGalleryPage',
'demo-starmap': 'StarMapDemo',
'demo-movement': 'ShipMovementDemo',
'demo-combat': 'CombatDemo',
'demo-market': 'MarketDemo',
'demo-fitting': 'FittingDemo',
'demo-refining': 'RefiningDemo',
'demo-progression':'ProgressionDemo',
'demo-bounty': 'BountyDemo',
'demo-gamehud': 'GameHudDemo',
'demo-chat': 'ChatDemo',
'demo-zora': 'ZoraDemo',
'demo-galaxy': 'GalaxyDemo',
};
function App() {
const { page, navigate } = GDD.useRouter();
const [collapsed, setCollapsed] = React.useState(false);
const [loading, setLoading] = React.useState(false);
const [loadError, setLoadError] = React.useState(null);
// Load the page component on demand when navigation changes
React.useEffect(() => {
// Check if component already available (inline loaded pages like overview)
const compName = PAGE_COMPONENTS[page];
if (compName && GDD[compName]) {
GDD._loadedPages = GDD._loadedPages || {};
GDD._loadedPages[page] = true;
return;
}
setLoading(true);
setLoadError(null);
GDD.loadPage(page)
.then(() => setLoading(false))
.catch((err) => {
setLoadError(err.message || 'Failed to load page');
setLoading(false);
});
}, [page]);
// Preload adjacent pages in the background after current page loads
React.useEffect(() => {
if (!loading && !loadError) {
// Preload the next/prev pages after a short delay
const allPageIds = Object.keys(PAGE_COMPONENTS);
const idx = allPageIds.indexOf(page);
if (idx >= 0 && idx < allPageIds.length - 1) {
GDD.preloadPage(allPageIds[idx + 1]);
}
if (idx > 0) {
GDD.preloadPage(allPageIds[idx - 1]);
}
}
}, [page, loading, loadError]);
const renderPage = () => {
if (loading) {
return (
<div style={{
display: 'flex', alignItems: 'center', justifyContent: 'center',
height: '60vh', flexDirection: 'column', gap: 'var(--sp-4)',
color: 'var(--muted)', fontFamily: 'var(--font-mono)', fontSize: '0.85rem',
}}>
<div style={{ animation: 'pulse 1.5s ease-in-out infinite' }}>Loading module</div>
</div>
);
}
if (loadError) {
return (
<div style={{
display: 'flex', alignItems: 'center', justifyContent: 'center',
height: '60vh', flexDirection: 'column', gap: 'var(--sp-4)',
color: 'var(--red)', fontFamily: 'var(--font-mono)', fontSize: '0.85rem',
}}>
<div>Failed to load page: {loadError}</div>
<button className="btn btn-sm" onClick={() => window.location.reload()}>Reload</button>
</div>
);
}
const compName = PAGE_COMPONENTS[page];
const Comp = compName && GDD[compName];
if (!Comp) return <GDD.OverviewPage />;
return <Comp />;
};
const isFullscreen = FULLSCREEN_PAGES.has(page);
if (isFullscreen) {
return (
<div className="app-shell fullscreen-demo">
<div className="main-area">
<div className="content content--flush">
{renderPage()}
</div>
</div>
</div>
);
}
return (
<div className="app-shell">
<GDD.Sidebar
collapsed={collapsed}
currentPage={page}
onNavigate={navigate}
onToggle={() => setCollapsed(c => !c)}
/>
<div className="main-area">
<GDD.TopBar
collapsed={collapsed}
currentPage={page}
onToggle={() => setCollapsed(c => !c)}
/>
<div className="content">
{renderPage()}
</div>
</div>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
<!-- All other pages & demos are loaded on-demand by js/loader.js -->
</body>
</html>