window.GDD = window.GDD || {}; const { useState, useEffect, useCallback, useMemo } = React; function FittingDemo() { const [ship, setShip] = useState(null); const [ships, setShips] = useState([]); const [availableModules, setAvailableModules] = useState([]); const [fittedModules, setFittedModules] = useState({ high: [], med: [], low: [] }); const [selectedModule, setSelectedModule] = useState(null); const [filterSlot, setFilterSlot] = useState('all'); const [notifications, setNotifications] = useState([]); useEffect(() => { window.GDD.api.getPlayerShips().then(s => { setShips(s); if (s.length > 0) setShip(s[0]); }); window.GDD.api.getAvailableModules().then(m => setAvailableModules(m)); }, []); useEffect(() => { if (!ship) return; window.GDD.api.getShipFittings(ship.id).then(fitted => { const slots = { high: [], med: [], low: [] }; fitted.forEach(m => { if (m.slot === 'high') slots.high.push(m); else if (m.slot === 'med') slots.med.push(m); else if (m.slot === 'low') slots.low.push(m); }); setFittedModules(slots); }); }, [ship]); const addNotif = useCallback((msg, color) => { const id = Date.now(); setNotifications(prev => [...prev, { id, msg, color }]); setTimeout(() => setNotifications(prev => prev.filter(n => n.id !== id)), 3000); }, []); const cpuUsage = useMemo(() => { let total = 0; Object.values(fittedModules).flat().forEach(m => total += m.cpu); return total; }, [fittedModules]); const gridUsage = useMemo(() => { let total = 0; Object.values(fittedModules).flat().forEach(m => total += m.power); return total; }, [fittedModules]); const cpuMax = ship ? ship.cpu : 0; const gridMax = ship ? ship.powerGrid : 0; const cpuOver = cpuUsage > cpuMax; const gridOver = gridUsage > gridMax; const handleFit = useCallback((mod) => { if (!ship) return; const slot = mod.slot; const maxSlots = slot === 'high' ? ship.highSlots : slot === 'med' ? ship.medSlots : ship.lowSlots; const currentCount = fittedModules[slot].length; if (currentCount >= maxSlots) { addNotif(`No empty ${slot} slots available.`, 'var(--red)'); return; } const newCpu = cpuUsage + mod.cpu; const newGrid = gridUsage + mod.power; if (newCpu > cpuMax) { addNotif(`CPU exceeded: ${newCpu}/${cpuMax}. Remove a module first.`, 'var(--red)'); return; } if (newGrid > gridMax) { addNotif(`Power Grid exceeded: ${newGrid}/${gridMax}. Remove a module first.`, 'var(--red)'); return; } setFittedModules(prev => ({ ...prev, [slot]: [...prev[slot], { ...mod, uid: Date.now() + Math.random() }], })); addNotif(`${mod.name} fitted to ${slot} slot.`, 'var(--green)'); }, [ship, fittedModules, cpuUsage, gridUsage, cpuMax, gridMax, addNotif]); const handleUnfit = useCallback((slot, index) => { const mod = fittedModules[slot][index]; setFittedModules(prev => ({ ...prev, [slot]: prev[slot].filter((_, i) => i !== index), })); addNotif(`${mod.name} removed from ${slot} slot.`, 'var(--muted)'); }, [fittedModules, addNotif]); const filteredModules = filterSlot === 'all' ? availableModules : availableModules.filter(m => m.slot === filterSlot); const slotConfig = [ { key: 'high', label: 'High Slots', color: 'var(--red)', icon: '◆', max: ship?.highSlots || 0 }, { key: 'med', label: 'Medium Slots', color: 'var(--cyan)', icon: '◇', max: ship?.medSlots || 0 }, { key: 'low', label: 'Low Slots', color: 'var(--green)', icon: '○', max: ship?.lowSlots || 0 }, ]; const moduleTypeIcon = (type) => { switch(type) { case 'weapon': return '⊕'; case 'shield': return '◎'; case 'mining': return '⛏'; case 'propulsion': return '»'; case 'ewar': return '◎'; case 'armor': return '◼'; case 'damage_mod': return '↯'; case 'cargo': return '□'; default: return '•'; } }; if (!ship) return

Loading ship data...

; return (
e.currentTarget.style.color='var(--fg-bright)'} onMouseLeave={e => e.currentTarget.style.color='var(--muted)'}>← Back to Docs

Ship Fitting Demo

Drag modules into slot bays. CPU and Power Grid are hard limits — overfitting is blocked. Select a ship and build your loadout.

{/* Notifications */}
{notifications.map(n => (
{n.msg}
))}
{/* HUD-style fitting strip */}
{ship?.name || 'Loading...'} {ship && {ship.class}} {ship &&
} {ship && ( <>
CPU
0.8 ? '#f0a030' : '#22d3ee', borderRadius: 'var(--radius-pill)' }} />
{cpuUsage}/{cpuMax}
PWR
0.8 ? '#f0a030' : '#22c55e', borderRadius: 'var(--radius-pill)' }} />
{gridUsage}/{gridMax}
)} {ship?.system || ''} · {ship?.status === 'docked' ? '● DOCKED' : '○ IN SPACE'}
{/* Ship selector */}
{ships.map(s => ( ))}
{/* Ship stats + resource bars */}
Fitting Console | {ship.name} · {ship.class}-class {ship.system} · {ship.status === 'docked' ? '● DOCKED' : '○ IN SPACE'}
{/* Module browser */}
Module Browser
{['all', 'high', 'med', 'low'].map(f => ( ))}
{filteredModules.map(mod => (
setSelectedModule(mod)} onDoubleClick={() => handleFit(mod)} >
{moduleTypeIcon(mod.type)}
{mod.name}
{mod.cpu} CPU · {mod.power} PG · {mod.slot}
))}
{/* Fitting area */}
{/* CPU / Grid bars */}
CPU {cpuUsage} / {cpuMax} tf{cpuOver ? ' ⚠ OVER' : ''}
0.8 ? 'var(--accent)' : 'var(--cyan)', }} />
POWER GRID {gridUsage} / {gridMax} MW{gridOver ? ' ⚠ OVER' : ''}
0.8 ? 'var(--accent)' : 'var(--green)', }} />
{/* Slot bays */} {slotConfig.map(slot => (
{slot.icon} {slot.label} {fittedModules[slot.key].length} / {slot.max}
{Array.from({ length: slot.max }).map((_, i) => { const mod = fittedModules[slot.key][i]; return (
{ if (mod) handleUnfit(slot.key, i); else if (selectedModule?.slot === slot.key) handleFit(selectedModule); }} > {mod ? ( <> {moduleTypeIcon(mod.type)} {mod.name.replace(' I', '').replace(' II', '')} {mod.cpu}/{mod.power} ) : ( Empty )}
); })}
))}
{/* Selected module detail */} {selectedModule && (
{moduleTypeIcon(selectedModule.type)}

{selectedModule.name}

Slot: {selectedModule.slot} Type: {selectedModule.type} CPU: {selectedModule.cpu} tf Grid: {selectedModule.power} MW {selectedModule.damage && Damage: {selectedModule.damage}} {selectedModule.cycle > 0 && Cycle: {selectedModule.cycle}s}
)} {/* Controls hint */}
How to use: Select a module from the browser (left panel), then click an empty slot bay to fit it. Double-click a module in the browser to quick-fit. Click a fitted module to remove it. CPU and Power Grid are enforced — overspending is blocked with a warning.
); } window.GDD.FittingDemo = FittingDemo;