642 lines
24 KiB
TypeScript
642 lines
24 KiB
TypeScript
'use client'
|
||
|
||
import { useEffect, useState } from 'react'
|
||
import { Badge } from '@/components/ui/badge'
|
||
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
|
||
import { Button } from '@/components/ui/button'
|
||
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card'
|
||
import { Panel, PanelContent, PanelHeader, PanelTitle } from '@/components/ui/panel'
|
||
import { Progress } from '@/components/ui/progress'
|
||
import { StatCard } from '@/components/ui/stat-card'
|
||
import { StatusGrid, type SystemEntry } from '@/components/ui/status-grid'
|
||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
||
import { Terminal, type TerminalLine } from '@/components/ui/terminal'
|
||
import { CrewPanel } from '@/components/crew-panel'
|
||
import { LocalSystemPanel } from '@/components/local-system'
|
||
import { ShipSystemsPanel } from '@/components/ship-systems'
|
||
import { WorldMapPanel } from '@/components/world-map'
|
||
import { CREW_CAPACITY } from '@/lib/game/ship-costs'
|
||
import type { CrewDto } from '@/lib/game/save'
|
||
import type { ShipState } from '@/lib/game/ship'
|
||
|
||
// Interface base: layout + visual treatment rebuilt from the scificn-ui Mission Control
|
||
// showcase (src/app/_pages/showcase/mission-control-content.tsx in baxy5/scificn-ui). The
|
||
// WORLD MAP, LOCAL SYSTEM, CREW and SHIP SYSTEMS tabs render live data (/api/world, /api/system,
|
||
// /api/crew, /api/ship); the other tabs keep placeholder content until their own issues land.
|
||
// Single player, no auth, by design.
|
||
|
||
function useNarrow(threshold = 900): boolean {
|
||
const [narrow, setNarrow] = useState(
|
||
() => typeof window !== 'undefined' && window.innerWidth < threshold
|
||
)
|
||
useEffect(() => {
|
||
const handle = () => setNarrow(window.innerWidth < threshold)
|
||
window.addEventListener('resize', handle, { passive: true })
|
||
return () => window.removeEventListener('resize', handle)
|
||
}, [threshold])
|
||
return narrow
|
||
}
|
||
|
||
function now(): string {
|
||
return new Date().toLocaleTimeString('en-GB', { hour12: false })
|
||
}
|
||
|
||
// ── Placeholder content (no game logic — real values land with the turn engine) ──
|
||
|
||
const skillCards = [
|
||
{ name: 'ADVANCED FUSION CORE', unlocks: 'REACTOR LV 4 + OVERCLOCK', state: 'AVAILABLE' as const },
|
||
{ name: 'HULL PLATING SCHEMATICS', unlocks: 'BUILD ARMOR BAY MODULES', state: 'AVAILABLE' as const },
|
||
{ name: 'CREW TRAINING PROTOCOLS', unlocks: 'RETRAIN CREW TRAITS', state: 'LOCKED' as const },
|
||
{ name: 'JUMP DRIVE TUNING', unlocks: 'SHORTER JUMP COOLDOWN', state: 'LOCKED' as const },
|
||
]
|
||
|
||
const encounters = [
|
||
{ name: 'DERELICT FREIGHTER', when: 'T-07:14', id: '#E-102' },
|
||
{ name: 'PIRATE SKIRMISH', when: 'T-22:40', id: '#E-103' },
|
||
{ name: 'ANOMALY FIELD', when: 'T-48:12', id: '#E-104' },
|
||
]
|
||
|
||
const shipData = [
|
||
{ label: 'TOTAL MODULES', value: '34' },
|
||
{ label: 'HULL INTEGRITY', value: '87%' },
|
||
{ label: 'REACTOR OUTPUT', value: '94.2%' },
|
||
{ label: 'CREW ACTIVE', value: '3 / 6' },
|
||
{ label: 'SECTOR', value: '7G // DRIFT LANE' },
|
||
{ label: 'DRIFT VELOCITY', value: '0.4 KM/S' },
|
||
{ label: 'TURN', value: '12' },
|
||
]
|
||
|
||
const logLines: TerminalLine[] = [
|
||
{ type: 'system', text: 'SPACEWANDERER // INITIALIZING SHIP SYSTEMS...', timestamp: now() },
|
||
{ type: 'output', text: 'HULL SCAN COMPLETE — INTEGRITY 87%', timestamp: now() },
|
||
{ type: 'output', text: 'REACTOR OUTPUT STABLE AT 94.2%', timestamp: now() },
|
||
{ type: 'output', text: 'CREW MANIFEST LOADED — 3 ACTIVE PERSONNEL', timestamp: now() },
|
||
{ type: 'warn', text: 'WEAPONS OFFLINE — MODULE NOT INSTALLED', timestamp: now() },
|
||
{ type: 'system', text: 'ALL SYSTEMS NOMINAL. AWAITING FIRST TURN.', timestamp: now() },
|
||
]
|
||
|
||
export function MissionControl() {
|
||
const narrow = useNarrow()
|
||
const [tick, setTick] = useState(0)
|
||
// Ship state is owned here (not in the tab panel) so the left column mirrors ship actions
|
||
// immediately; the SHIP SYSTEMS tab applies each action's response via setShip. Crew count
|
||
// feeds the roster bar — refetched on page load, like the other panels.
|
||
const [ship, setShip] = useState<ShipState | null>(null)
|
||
const [crewCount, setCrewCount] = useState<number | null>(null)
|
||
|
||
useEffect(() => {
|
||
let cancelled = false
|
||
fetch('/api/ship')
|
||
.then(async (res) => {
|
||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||
return (await res.json()) as ShipState
|
||
})
|
||
.then((s) => {
|
||
if (!cancelled) setShip(s)
|
||
})
|
||
.catch(() => {}) // left column + tab keep their loading state on failure
|
||
fetch('/api/crew')
|
||
.then(async (res) => {
|
||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||
return (await res.json()) as CrewDto[]
|
||
})
|
||
.then((c) => {
|
||
if (!cancelled) setCrewCount(c.length)
|
||
})
|
||
.catch(() => {})
|
||
return () => {
|
||
cancelled = true
|
||
}
|
||
}, [])
|
||
|
||
useEffect(() => {
|
||
const id = setInterval(() => setTick((t) => t + 1), 1000)
|
||
return () => clearInterval(id)
|
||
}, [])
|
||
|
||
const timeStr = new Date().toLocaleTimeString('en-GB', { hour12: false })
|
||
|
||
return (
|
||
<div
|
||
style={{
|
||
minHeight: '100vh',
|
||
background: 'var(--background)',
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
fontFamily: 'var(--font-mono)',
|
||
}}
|
||
>
|
||
{/* ── HEADER ── */}
|
||
<header
|
||
style={{
|
||
height: '44px',
|
||
background: 'var(--surface)',
|
||
borderBottom: '1px solid var(--border)',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
padding: '0 1.25rem',
|
||
gap: '0.75rem',
|
||
position: 'sticky',
|
||
top: 0,
|
||
zIndex: 50,
|
||
flexShrink: 0,
|
||
}}
|
||
>
|
||
<span
|
||
style={{
|
||
color: 'var(--text-muted)',
|
||
fontSize: '0.7rem',
|
||
letterSpacing: '0.08em',
|
||
flexShrink: 0,
|
||
}}
|
||
>
|
||
◄ SHIP
|
||
</span>
|
||
|
||
<span style={{ color: 'var(--border)', flexShrink: 0 }}>│</span>
|
||
|
||
<h1
|
||
style={{
|
||
margin: 0,
|
||
color: 'var(--color-green)',
|
||
fontSize: '0.85rem',
|
||
fontWeight: 700,
|
||
letterSpacing: '0.12em',
|
||
textShadow: 'var(--text-glow-green)',
|
||
flexShrink: 0,
|
||
}}
|
||
>
|
||
SPACEWANDERER
|
||
</h1>
|
||
|
||
{!narrow && (
|
||
<span
|
||
style={{
|
||
color: 'var(--text-muted)',
|
||
fontSize: '0.72rem',
|
||
letterSpacing: '0.08em',
|
||
flexShrink: 0,
|
||
}}
|
||
>
|
||
// SHIP STATUS // CREW MANIFEST
|
||
</span>
|
||
)}
|
||
|
||
<div style={{ flex: 1 }} />
|
||
|
||
{!narrow && (
|
||
<Badge variant="ACTIVE">SHIP NOMINAL</Badge>
|
||
)}
|
||
|
||
<Badge variant="SCANNING">SECTOR SCAN</Badge>
|
||
|
||
<span
|
||
style={{
|
||
color: 'var(--color-green)',
|
||
fontSize: '0.65rem',
|
||
letterSpacing: '0.1em',
|
||
textShadow: 'var(--text-glow-green)',
|
||
flexShrink: 0,
|
||
opacity: tick % 2 === 0 ? 1 : 0.5,
|
||
transition: 'opacity 0.3s',
|
||
}}
|
||
>
|
||
{timeStr}
|
||
</span>
|
||
</header>
|
||
|
||
{/* ── BODY ── */}
|
||
<div
|
||
style={{
|
||
flex: 1,
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
gap: '1px',
|
||
background: 'var(--border)',
|
||
}}
|
||
>
|
||
{/* STAT CARDS — placeholder ship readouts */}
|
||
<div
|
||
style={{
|
||
background: 'var(--background)',
|
||
padding: '0.75rem 1rem',
|
||
display: 'grid',
|
||
gridTemplateColumns: narrow ? '1fr 1fr' : 'repeat(4, 1fr)',
|
||
gap: '0.75rem',
|
||
}}
|
||
>
|
||
<StatCard
|
||
label="HULL INTEGRITY"
|
||
value="87%"
|
||
sublabel="NOMINAL · NO BREACHES"
|
||
variant="ACTIVE"
|
||
/>
|
||
<StatCard
|
||
label="REACTOR OUTPUT"
|
||
value="94.2%"
|
||
sublabel="STABLE · IDLE CYCLE"
|
||
variant="ACTIVE"
|
||
/>
|
||
<StatCard
|
||
label="CREW ON BOARD"
|
||
value="3 / 6"
|
||
sublabel="AWAITING MANIFEST"
|
||
variant="DEFAULT"
|
||
/>
|
||
<StatCard
|
||
label="NEXT ENCOUNTER"
|
||
value="T-07:14"
|
||
sublabel="SECTOR 7G // DRIFT LANE"
|
||
variant="WARNING"
|
||
/>
|
||
</div>
|
||
|
||
{/* 3-COLUMN GRID */}
|
||
<div
|
||
style={{
|
||
flex: 1,
|
||
display: 'grid',
|
||
gridTemplateColumns: narrow ? '1fr' : '260px 1fr 240px',
|
||
gap: '1px',
|
||
background: 'var(--border)',
|
||
}}
|
||
>
|
||
{/* ── LEFT: SHIP SYSTEMS + VESSEL METRICS (mirrors the SHIP SYSTEMS tab) ── */}
|
||
<div
|
||
style={{
|
||
background: 'var(--background)',
|
||
padding: '1rem',
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
gap: '0.875rem',
|
||
overflowY: 'auto',
|
||
}}
|
||
>
|
||
<Panel notch="sm">
|
||
<PanelHeader>
|
||
<PanelTitle>SHIP SYSTEMS</PanelTitle>
|
||
<div style={{ marginLeft: 'auto' }}>
|
||
<Badge variant={ship && ship.systems.some((s) => s.built) ? 'ACTIVE' : 'OFFLINE'}>
|
||
{ship ? `${ship.systems.filter((s) => s.built).length} ONLINE` : '—'}
|
||
</Badge>
|
||
</div>
|
||
</PanelHeader>
|
||
|
||
{ship ? (
|
||
<StatusGrid
|
||
systems={ship.systems.map((s) => ({
|
||
name: s.type,
|
||
status: s.built ? ('ACTIVE' as const) : ('OFFLINE' as const),
|
||
detail: s.built ? `LV ${s.level} · CAP ${s.capacity}` : undefined,
|
||
}))}
|
||
/>
|
||
) : (
|
||
<div style={{ padding: '0.5rem 0.75rem' }}>
|
||
<span style={{ fontSize: '0.62rem', color: 'var(--text-muted)', letterSpacing: '0.12em' }}>LOADING…</span>
|
||
</div>
|
||
)}
|
||
</Panel>
|
||
|
||
<Panel notch="sm">
|
||
<PanelHeader>
|
||
<PanelTitle>VESSEL METRICS</PanelTitle>
|
||
</PanelHeader>
|
||
<PanelContent>
|
||
{ship ? (
|
||
// Bars driven by real values: hull/reactor from system levels (level × 20, capped),
|
||
// crew roster vs the display cap.
|
||
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.875rem' }}>
|
||
<Progress label="HULL INTEGRITY" value={Math.min(100, (ship.systems.find((s) => s.type === 'HULL')?.level ?? 0) * 20)} />
|
||
<Progress label="REACTOR OUTPUT" value={Math.min(100, (ship.systems.find((s) => s.type === 'REACTOR')?.level ?? 0) * 20)} />
|
||
<Progress label={`CREW ROSTER / ${CREW_CAPACITY}`} value={Math.round(((crewCount ?? 0) / CREW_CAPACITY) * 100)} />
|
||
</div>
|
||
) : (
|
||
<span style={{ fontSize: '0.62rem', color: 'var(--text-muted)', letterSpacing: '0.12em' }}>LOADING…</span>
|
||
)}
|
||
</PanelContent>
|
||
</Panel>
|
||
</div>
|
||
|
||
{/* ── CENTER: GAME TEMPLATE TABS (visual only — no handlers, placeholder data) ── */}
|
||
<div
|
||
style={{
|
||
background: 'var(--background)',
|
||
padding: '1rem',
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
gap: '0.875rem',
|
||
overflowY: 'auto',
|
||
}}
|
||
>
|
||
<Panel notch="md" style={{ flex: 1 }}>
|
||
<PanelHeader>
|
||
<PanelTitle>OPERATIONS // SHIP COMMAND</PanelTitle>
|
||
<div style={{ marginLeft: 'auto', display: 'flex', gap: '0.5rem' }}>
|
||
<Badge variant="ACTIVE">SYS NOMINAL</Badge>
|
||
</div>
|
||
</PanelHeader>
|
||
|
||
<PanelContent style={{ padding: 0 }}>
|
||
<Tabs defaultValue="world-map">
|
||
<TabsList
|
||
style={{
|
||
padding: '0 1rem',
|
||
background: 'var(--surface-raised)',
|
||
overflowX: 'auto',
|
||
}}
|
||
>
|
||
<TabsTrigger value="world-map">WORLD MAP</TabsTrigger>
|
||
<TabsTrigger value="local-system">LOCAL SYSTEM</TabsTrigger>
|
||
<TabsTrigger value="crew">CREW</TabsTrigger>
|
||
<TabsTrigger value="ship-systems">SHIP SYSTEMS</TabsTrigger>
|
||
<TabsTrigger value="skill-tree">SKILL TREE</TabsTrigger>
|
||
<TabsTrigger value="missions">MISSIONS</TabsTrigger>
|
||
<TabsTrigger value="event-log">EVENT LOG</TabsTrigger>
|
||
</TabsList>
|
||
|
||
{/* WORLD MAP TAB — live data from /api/world */}
|
||
<TabsContent value="world-map" style={{ margin: 0, padding: '1rem' }}>
|
||
<WorldMapPanel height={380} />
|
||
</TabsContent>
|
||
|
||
{/* LOCAL SYSTEM TAB — live data from /api/system */}
|
||
<TabsContent value="local-system" style={{ margin: 0, padding: '1rem' }}>
|
||
<LocalSystemPanel />
|
||
</TabsContent>
|
||
|
||
{/* CREW TAB — live data from /api/crew */}
|
||
<TabsContent value="crew" style={{ margin: 0, padding: '1rem' }}>
|
||
<CrewPanel narrow={narrow} />
|
||
</TabsContent>
|
||
|
||
{/* SHIP SYSTEMS TAB — live data from /api/ship (state owned by MissionControl) */}
|
||
<TabsContent value="ship-systems" style={{ margin: 0, padding: '1rem' }}>
|
||
<ShipSystemsPanel ship={ship} narrow={narrow} onChanged={setShip} />
|
||
</TabsContent>
|
||
|
||
{/* SKILL TREE TAB */}
|
||
<TabsContent value="skill-tree" style={{ margin: 0, padding: '1rem' }}>
|
||
<div
|
||
style={{
|
||
display: 'grid',
|
||
gridTemplateColumns: narrow ? '1fr' : 'repeat(2, 1fr)',
|
||
gap: '0.75rem',
|
||
}}
|
||
>
|
||
{skillCards.map((skill) => (
|
||
<Card key={skill.name}>
|
||
<CardHeader>
|
||
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||
<CardTitle>{skill.name}</CardTitle>
|
||
<Badge variant={skill.state === 'AVAILABLE' ? 'ACTIVE' : 'OFFLINE'} className="ml-auto">
|
||
{skill.state}
|
||
</Badge>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
<p style={{ fontSize: '0.65rem', color: 'var(--text-muted)', letterSpacing: '0.08em' }}>
|
||
UNLOCKS: {skill.unlocks}
|
||
</p>
|
||
</CardContent>
|
||
<CardFooter>
|
||
<Button variant="OUTLINE" size="SM">
|
||
RESEARCH
|
||
</Button>
|
||
</CardFooter>
|
||
</Card>
|
||
))}
|
||
</div>
|
||
</TabsContent>
|
||
|
||
{/* MISSIONS TAB */}
|
||
<TabsContent value="missions" style={{ margin: 0, padding: '1rem' }}>
|
||
<div
|
||
style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}
|
||
>
|
||
<Alert variant="STATUS">
|
||
<AlertTitle>ENCOUNTER #E-102 — DERELICT FREIGHTER</AlertTitle>
|
||
<AlertDescription>
|
||
Placeholder briefing. Real encounter resolution lands with the turn engine.
|
||
</AlertDescription>
|
||
</Alert>
|
||
|
||
<div
|
||
style={{
|
||
display: 'grid',
|
||
gridTemplateColumns: narrow ? '1fr' : '1fr 1fr',
|
||
gap: '0.625rem',
|
||
}}
|
||
>
|
||
{[
|
||
{ label: 'DESTINATION', value: 'SECTOR 7G // DRIFT LANE' },
|
||
{ label: 'ETA', value: 'T-07:14 (PLACEHOLDER)' },
|
||
{ label: 'THREAT LEVEL', value: 'LOW — UNMANNED' },
|
||
{ label: 'CARGO MANIFEST', value: 'UNKNOWN' },
|
||
].map((item) => (
|
||
<div
|
||
key={item.label}
|
||
style={{
|
||
padding: '0.75rem',
|
||
background: 'var(--surface)',
|
||
border: '1px solid var(--border)',
|
||
}}
|
||
>
|
||
<div
|
||
style={{
|
||
fontSize: '0.55rem',
|
||
color: 'var(--text-muted)',
|
||
letterSpacing: '0.12em',
|
||
marginBottom: '0.35rem',
|
||
}}
|
||
>
|
||
{item.label}
|
||
</div>
|
||
<div
|
||
style={{
|
||
fontSize: '0.8rem',
|
||
color: 'var(--text-secondary)',
|
||
letterSpacing: '0.04em',
|
||
}}
|
||
>
|
||
{item.value}
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</TabsContent>
|
||
|
||
{/* EVENT LOG TAB */}
|
||
<TabsContent value="event-log" style={{ margin: 0, padding: '1rem' }}>
|
||
<Terminal
|
||
lines={logLines}
|
||
title="SHIP LOG // PLACEHOLDER FEED"
|
||
height="22rem"
|
||
style={{ width: '100%' }}
|
||
/>
|
||
</TabsContent>
|
||
</Tabs>
|
||
</PanelContent>
|
||
</Panel>
|
||
</div>
|
||
|
||
{/* ── RIGHT: UPCOMING ENCOUNTERS + SHIP DATA ── */}
|
||
<div
|
||
style={{
|
||
background: 'var(--background)',
|
||
padding: '1rem',
|
||
display: 'flex',
|
||
flexDirection: 'column',
|
||
gap: '0.875rem',
|
||
overflowY: 'auto',
|
||
}}
|
||
>
|
||
<Panel notch="sm">
|
||
<PanelHeader>
|
||
<PanelTitle>UPCOMING ENCOUNTERS</PanelTitle>
|
||
<div style={{ marginLeft: 'auto' }}>
|
||
<Badge variant="SCANNING">{encounters.length} QUEUED</Badge>
|
||
</div>
|
||
</PanelHeader>
|
||
<PanelContent style={{ padding: 0 }}>
|
||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||
{encounters.map((enc, i) => {
|
||
const isNext = i === 0
|
||
const isLast = i === encounters.length - 1
|
||
return (
|
||
<div
|
||
key={enc.id}
|
||
style={{
|
||
padding: '0.6rem 0.75rem',
|
||
borderBottom: isLast ? 'none' : '1px solid var(--border)',
|
||
background: isNext ? 'var(--surface-raised)' : 'transparent',
|
||
}}
|
||
>
|
||
<div
|
||
style={{
|
||
display: 'flex',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'flex-start',
|
||
gap: '0.5rem',
|
||
}}
|
||
>
|
||
<div style={{ overflow: 'hidden', flex: 1 }}>
|
||
<div
|
||
style={{
|
||
fontSize: '0.68rem',
|
||
color: isNext ? 'var(--text-secondary)' : 'var(--text-muted)',
|
||
letterSpacing: '0.04em',
|
||
overflow: 'hidden',
|
||
textOverflow: 'ellipsis',
|
||
whiteSpace: 'nowrap',
|
||
textTransform: 'uppercase',
|
||
}}
|
||
>
|
||
{enc.name}
|
||
</div>
|
||
<div
|
||
style={{
|
||
fontSize: '0.57rem',
|
||
color: 'var(--text-muted)',
|
||
letterSpacing: '0.06em',
|
||
marginTop: '2px',
|
||
}}
|
||
>
|
||
{enc.when} // {enc.id}
|
||
</div>
|
||
</div>
|
||
<Badge variant={isNext ? 'WARNING' : 'SCANNING'}>
|
||
{isNext ? 'NEXT' : 'SCHED'}
|
||
</Badge>
|
||
</div>
|
||
</div>
|
||
)
|
||
})}
|
||
</div>
|
||
</PanelContent>
|
||
</Panel>
|
||
|
||
<Panel notch="sm">
|
||
<PanelHeader>
|
||
<PanelTitle>SHIP DATA</PanelTitle>
|
||
</PanelHeader>
|
||
<PanelContent>
|
||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||
{shipData.map((item, i) => (
|
||
<div
|
||
key={item.label}
|
||
style={{
|
||
display: 'flex',
|
||
justifyContent: 'space-between',
|
||
alignItems: 'center',
|
||
padding: '0.4rem 0',
|
||
borderBottom:
|
||
i < shipData.length - 1 ? '1px solid var(--border)' : 'none',
|
||
}}
|
||
>
|
||
<span
|
||
style={{
|
||
fontSize: '0.6rem',
|
||
color: 'var(--text-muted)',
|
||
letterSpacing: '0.1em',
|
||
}}
|
||
>
|
||
{item.label}
|
||
</span>
|
||
<span
|
||
style={{
|
||
fontSize: '0.78rem',
|
||
color: 'var(--color-green)',
|
||
textShadow: 'var(--text-glow-green)',
|
||
letterSpacing: '0.04em',
|
||
fontWeight: 600,
|
||
}}
|
||
>
|
||
{item.value}
|
||
</span>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</PanelContent>
|
||
</Panel>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* ── FOOTER ── */}
|
||
<footer
|
||
style={{
|
||
minHeight: '44px',
|
||
background: 'var(--surface)',
|
||
borderTop: '1px solid var(--border)',
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
flexWrap: 'wrap',
|
||
padding: '0.5rem 1.25rem',
|
||
gap: '0.5rem',
|
||
position: 'sticky',
|
||
bottom: 0,
|
||
zIndex: 50,
|
||
flexShrink: 0,
|
||
}}
|
||
>
|
||
<span style={{ fontSize: '0.6rem', color: 'var(--text-muted)', letterSpacing: '0.08em' }}>
|
||
INTERFACE BASE: SCIFICN MISSION CONTROL
|
||
</span>
|
||
<span style={{ color: 'var(--border)' }}>│</span>
|
||
<span style={{ fontSize: '0.6rem', color: 'var(--text-muted)', letterSpacing: '0.08em' }}>
|
||
CONTENT: PLACEHOLDER — NO GAME LOGIC YET
|
||
</span>
|
||
<span style={{ color: 'var(--border)' }}>│</span>
|
||
<span style={{ fontSize: '0.6rem', color: 'var(--text-muted)', letterSpacing: '0.08em' }}>
|
||
SINGLE PLAYER // NO AUTH
|
||
</span>
|
||
|
||
<div style={{ flex: 1 }} />
|
||
|
||
<Badge variant="ACTIVE">SYS ONLINE</Badge>
|
||
</footer>
|
||
</div>
|
||
)
|
||
}
|