790 lines
31 KiB
TypeScript
790 lines
31 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 { NodeGraph, type NodeGraphEdge, type NodeGraphNode } from '@/components/ui/node-graph'
|
|
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'
|
|
|
|
// 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), with
|
|
// placeholder SpaceWanderer content — no game logic yet. 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 shipSystems: SystemEntry[] = [
|
|
{ name: 'REACTOR', status: 'ACTIVE', detail: '94.2%' },
|
|
{ name: 'LIFE SUPPORT', status: 'ACTIVE' },
|
|
{ name: 'NAV COMPUTER', status: 'SCANNING' },
|
|
{ name: 'WEAPONS', status: 'OFFLINE' },
|
|
{ name: 'COMMS', status: 'ACTIVE' },
|
|
]
|
|
|
|
// WORLD MAP — every node is a solar system, edges are jump routes.
|
|
const worldMapNodes: NodeGraphNode[] = [
|
|
{ id: 'kepler', label: 'Kepler Drift', x: 50, y: 48, status: 'ACTIVE', sublabel: 'CURRENT' },
|
|
{ id: 'vesper', label: 'Vesper Reach', x: 22, y: 26, status: 'NEUTRAL', sublabel: '3 WORLDS' },
|
|
{ id: 'hollow', label: 'Hollow Gate', x: 74, y: 20, status: 'NEUTRAL', sublabel: '5 WORLDS' },
|
|
{ id: 'cinder', label: 'Cinder Field', x: 86, y: 56, status: 'WARNING', sublabel: 'RADIATION' },
|
|
{ id: 'lantern', label: 'Lantern Void', x: 30, y: 74, status: 'NEUTRAL', sublabel: '2 WORLDS' },
|
|
{ id: 'sable', label: 'Sable Reach', x: 8, y: 46, status: 'OFFLINE', sublabel: 'UNKNOWN' },
|
|
{ id: 'orpheus', label: 'Orpheus Deep', x: 52, y: 10, status: 'NEUTRAL', sublabel: '7 WORLDS' },
|
|
{ id: 'mire', label: 'Mire Station', x: 68, y: 84, status: 'ACTIVE', sublabel: 'OUTPOST' },
|
|
{ id: 'thalassa', label: 'Thalassa Bend', x: 12, y: 86, status: 'NEUTRAL', sublabel: '4 WORLDS' },
|
|
{ id: 'kuiper', label: 'Kuiper Shard', x: 90, y: 30, status: 'CRITICAL', sublabel: 'WARZONE' },
|
|
]
|
|
|
|
const worldMapEdges: NodeGraphEdge[] = [
|
|
{ from: 'kepler', to: 'vesper', animated: true },
|
|
{ from: 'kepler', to: 'hollow', label: 'JUMP-2' },
|
|
{ from: 'kepler', to: 'lantern', animated: true },
|
|
{ from: 'kepler', to: 'mire' },
|
|
{ from: 'vesper', to: 'orpheus' },
|
|
{ from: 'hollow', to: 'kuiper', label: 'JUMP-1' },
|
|
{ from: 'cinder', to: 'kuiper' },
|
|
{ from: 'lantern', to: 'thalassa' },
|
|
{ from: 'sable', to: 'vesper' },
|
|
{ from: 'mire', to: 'cinder' },
|
|
]
|
|
|
|
// LOCAL SYSTEM — explorable worlds of the current system (regenerated per save after each jump, later).
|
|
const explorablePlanets = [
|
|
{ name: 'AURELLE', type: 'TERRESTRIAL // OCEAN CONTINENTS', state: 'UNEXPLORED' as const },
|
|
{ name: 'BRIMSTONE', type: 'GAS GIANT // CLOUD DECKS', state: 'SCANNED' as const },
|
|
{ name: 'ASHFALL (MOON)', type: 'VOLCANIC MOON // SULFUR VEINS', state: 'UNEXPLORED' as const },
|
|
{ name: 'CINDERROCK', type: 'ROCKY // IRON RICH', state: 'UNEXPLORED' as const },
|
|
{ name: 'DRIFTBAND', type: 'ASTEROID FIELD // HAZARD', state: 'HAZARD' as const },
|
|
]
|
|
|
|
const crew = [
|
|
{ name: 'CMDR. E. VANCE', role: 'COMMANDER', good: 'STEADY UNDER PRESSURE', bad: 'RIGID — SLOW TO ADAPT' },
|
|
{ name: 'LT. M. OKONKWO', role: 'PILOT', good: 'RECKLESS COURAGE', bad: 'SHORT FUSE' },
|
|
{ name: 'DR. I. SORENSEN', role: 'MEDIC', good: 'CALM IN A CRISIS', bad: 'OVERCAUTIOUS' },
|
|
{ name: 'R. "SPARK" DELACRUZ', role: 'ENGINEER', good: 'FIXES ANYTHING', bad: 'CARELESS WITH FUSES' },
|
|
]
|
|
|
|
const shipSystemCards = [
|
|
{ name: 'REACTOR', level: 'LV 2', status: 'ACTIVE' as const, actions: ['EXPAND', 'UPGRADE'] },
|
|
{ name: 'LIFE SUPPORT', level: 'LV 1', status: 'ACTIVE' as const, actions: ['EXPAND', 'UPGRADE'] },
|
|
{ name: 'NAV COMPUTER', level: 'LV 3', status: 'SCANNING' as const, actions: ['EXPAND', 'UPGRADE'] },
|
|
{ name: 'WEAPONS', level: 'OFFLINE', status: 'OFFLINE' as const, actions: ['BUILD NEW'] },
|
|
{ name: 'COMMS ARRAY', level: 'LV 1', status: 'ACTIVE' as const, actions: ['EXPAND', 'UPGRADE'] },
|
|
]
|
|
|
|
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)
|
|
|
|
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="ACTIVE">4 ONLINE</Badge>
|
|
</div>
|
|
</PanelHeader>
|
|
|
|
<StatusGrid systems={shipSystems} />
|
|
</Panel>
|
|
|
|
<Panel notch="sm">
|
|
<PanelHeader>
|
|
<PanelTitle>VESSEL METRICS</PanelTitle>
|
|
</PanelHeader>
|
|
<PanelContent>
|
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '0.875rem' }}>
|
|
<Progress label="HULL INTEGRITY" value={87} />
|
|
<Progress label="REACTOR OUTPUT" value={94} />
|
|
<Progress label="CREW ROSTER / 6" value={50} />
|
|
</div>
|
|
</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 */}
|
|
<TabsContent value="world-map" style={{ margin: 0, padding: '1rem' }}>
|
|
<NodeGraph
|
|
nodes={worldMapNodes}
|
|
edges={worldMapEdges}
|
|
variant="ACTIVE"
|
|
title="GALACTIC CHART // JUMP ROUTES (PLACEHOLDER)"
|
|
height={380}
|
|
style={{ width: '100%' }}
|
|
/>
|
|
</TabsContent>
|
|
|
|
{/* LOCAL SYSTEM TAB */}
|
|
<TabsContent value="local-system" style={{ margin: 0, padding: '1rem' }}>
|
|
<div
|
|
style={{
|
|
border: '1px solid var(--border)',
|
|
background: 'var(--surface)',
|
|
overflow: 'hidden',
|
|
}}
|
|
>
|
|
<div
|
|
style={{
|
|
padding: '0.4rem 0.75rem',
|
|
borderBottom: '1px solid var(--border)',
|
|
background: 'var(--surface-raised)',
|
|
fontSize: '0.6rem',
|
|
color: 'var(--text-muted)',
|
|
letterSpacing: '0.12em',
|
|
textTransform: 'uppercase',
|
|
}}
|
|
>
|
|
EXPLORABLE WORLDS // FIND THINGS HERE (PLACEHOLDER)
|
|
</div>
|
|
{explorablePlanets.map((planet, i) => (
|
|
<div
|
|
key={planet.name}
|
|
style={{
|
|
display: 'flex',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
gap: '0.5rem',
|
|
padding: '0.6rem 0.75rem',
|
|
borderBottom:
|
|
i < explorablePlanets.length - 1
|
|
? '1px solid var(--border)'
|
|
: 'none',
|
|
}}
|
|
>
|
|
<div style={{ overflow: 'hidden' }}>
|
|
<div
|
|
style={{
|
|
fontSize: '0.68rem',
|
|
color: 'var(--text-secondary)',
|
|
letterSpacing: '0.04em',
|
|
textTransform: 'uppercase',
|
|
}}
|
|
>
|
|
{planet.name}
|
|
</div>
|
|
<div
|
|
style={{
|
|
fontSize: '0.57rem',
|
|
color: 'var(--text-muted)',
|
|
letterSpacing: '0.06em',
|
|
marginTop: '2px',
|
|
}}
|
|
>
|
|
{planet.type}
|
|
</div>
|
|
</div>
|
|
<Badge
|
|
variant={
|
|
planet.state === 'UNEXPLORED'
|
|
? 'SCANNING'
|
|
: planet.state === 'HAZARD'
|
|
? 'WARNING'
|
|
: 'ACTIVE'
|
|
}
|
|
>
|
|
{planet.state}
|
|
</Badge>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
{/* CREW TAB */}
|
|
<TabsContent value="crew" style={{ margin: 0, padding: '1rem' }}>
|
|
<div
|
|
style={{
|
|
display: 'grid',
|
|
gridTemplateColumns: narrow ? '1fr' : 'repeat(2, 1fr)',
|
|
gap: '0.75rem',
|
|
}}
|
|
>
|
|
{crew.map((member) => (
|
|
<Card key={member.name}>
|
|
<CardHeader>
|
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
<CardTitle>{member.name}</CardTitle>
|
|
<Badge variant="ACTIVE" className="ml-auto">
|
|
ACTIVE
|
|
</Badge>
|
|
</div>
|
|
<p
|
|
style={{
|
|
fontSize: '0.6rem',
|
|
color: 'var(--text-muted)',
|
|
letterSpacing: '0.1em',
|
|
textTransform: 'uppercase',
|
|
}}
|
|
>
|
|
{member.role}
|
|
</p>
|
|
</CardHeader>
|
|
<CardContent style={{ display: 'flex', flexDirection: 'column', gap: '0.4rem' }}>
|
|
<div style={{ fontSize: '0.7rem', letterSpacing: '0.05em' }}>
|
|
<span style={{ color: 'var(--color-green)' }}>+ </span>
|
|
<span style={{ color: 'var(--text-secondary)' }}>{member.good}</span>
|
|
</div>
|
|
<div style={{ fontSize: '0.7rem', letterSpacing: '0.05em' }}>
|
|
<span style={{ color: 'var(--color-red)' }}>- </span>
|
|
<span style={{ color: 'var(--text-secondary)' }}>{member.bad}</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</TabsContent>
|
|
|
|
{/* SHIP SYSTEMS TAB */}
|
|
<TabsContent value="ship-systems" style={{ margin: 0, padding: '1rem' }}>
|
|
<div
|
|
style={{
|
|
display: 'grid',
|
|
gridTemplateColumns: narrow ? '1fr' : 'repeat(2, 1fr)',
|
|
gap: '0.75rem',
|
|
}}
|
|
>
|
|
{shipSystemCards.map((system) => (
|
|
<Card key={system.name}>
|
|
<CardHeader>
|
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
|
<CardTitle>{system.name}</CardTitle>
|
|
<Badge variant={system.status} className="ml-auto">
|
|
{system.level}
|
|
</Badge>
|
|
</div>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<p style={{ fontSize: '0.65rem', color: 'var(--text-muted)', letterSpacing: '0.08em' }}>
|
|
MODULE BAY // PLACEHOLDER READOUT
|
|
</p>
|
|
</CardContent>
|
|
<CardFooter className="gap-2">
|
|
{system.actions.map((action) => (
|
|
<Button key={action} variant="OUTLINE" size="SM">
|
|
{action}
|
|
</Button>
|
|
))}
|
|
</CardFooter>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
</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>
|
|
)
|
|
}
|