Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18ca5eae6c | ||
|
|
b05ac32da6 | ||
|
|
85627201f8 | ||
|
|
fb11a8ef81 |
@@ -1,5 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## v0.9.1 — 2026-09-29 06:47
|
||||
- UI layout pass (visual only — no game logic or API changes): CARGO HOLD // INVENTORY moved out of the LOCAL SYSTEM tab into a persistent panel in the right column; the UPCOMING ENCOUNTERS placeholder was removed entirely; the SHIP DATA placeholder was merged into VESSEL METRICS.
|
||||
- Grid: `260px / 1fr / 240px` → `320px / 1fr / 360px` (left pane ~+23%, right pane ~50% wider).
|
||||
- Cargo state wiring follows the existing lifted-state pattern: MissionControl fetches `/api/system` on mount and owns the SystemState; LocalSystemPanel receives it as a prop, keeps its planet list + scan result + explore action, and reports each scan's response up via `onChanged` (same as ShipSystemsPanel) — so CARGO HOLD mirrors scans live with no polling.
|
||||
- VESSEL METRICS now carries the two SHIP DATA rows that had real values: SECTOR (current system name from the lifted state) and TURN (`ship.turn`). The rest was dropped — HULL INTEGRITY / REACTOR OUTPUT / CREW ACTIVE duplicate the existing progress bars, TOTAL MODULES duplicates the left column's "N ONLINE" badge, DRIFT VELOCITY has no game equivalent.
|
||||
- e2e: no assertion changes needed — the LOCAL SYSTEM tests only assert panel internals (header, EXPLORE buttons, SCAN RESULT) and API state; CARGO HOLD / UPCOMING ENCOUNTERS / SHIP DATA were never referenced by any spec. All 15 specs compile unchanged.
|
||||
|
||||
## v0.9.0 — 2026-09-29 02:47
|
||||
- EVENT LOG tab — the last of the seven tabs to go live (issue #11): the terminal now renders the save's real game events instead of a placeholder feed. Every action already appends an event row (new game, jump, scan, research, ship upgrade/expand/build, mission accept + completion); this issue adds the read side and the rendering.
|
||||
- New endpoint: `GET /api/events` → latest N events, newest first (`{id, turn, type, message, createdAt}`), auto-initializes like all other reads. Optional `?limit=` (default 50, cap 200; non-integers or out-of-range values → 400). OpenAPI spec in code updated (14 → 15 paths) with the Event schema enumerating the eight event types.
|
||||
|
||||
+37
-86
@@ -1,69 +1,50 @@
|
||||
'use client'
|
||||
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import { useCallback, useState } from 'react'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Button } from '@/components/ui/button'
|
||||
import type { ExploreResult, SystemState } from '@/lib/game/save'
|
||||
import type { FoundItem } from '@/lib/game/world'
|
||||
|
||||
// LOCAL SYSTEM tab — the bodies of the current system (planets + staggered moons) with an
|
||||
// EXPLORE button per unexplored row. Fetches /api/system on mount; explore responses are
|
||||
// applied directly (no polling). Turn-based: one scan costs a turn, each body once.
|
||||
// EXPLORE button per unexplored row. The parent (MissionControl) owns the /api/system state so
|
||||
// the right column's CARGO HOLD mirrors scans immediately; this panel reports each scan's
|
||||
// response up via onChanged and keeps only its own UI bits locally (no polling). Turn-based:
|
||||
// one scan costs a turn, each body once.
|
||||
|
||||
export function LocalSystemPanel() {
|
||||
const [state, setState] = useState<SystemState | null>(null)
|
||||
interface LocalSystemPanelProps {
|
||||
system: SystemState | null
|
||||
onChanged: (next: SystemState) => void
|
||||
}
|
||||
|
||||
export function LocalSystemPanel({ system, onChanged }: LocalSystemPanelProps) {
|
||||
const [lastOutcome, setLastOutcome] = useState<FoundItem[] | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [exploringId, setExploringId] = useState<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false
|
||||
fetch('/api/system')
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
return (await res.json()) as SystemState
|
||||
})
|
||||
.then((s) => {
|
||||
if (!cancelled) setState(s)
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (!cancelled) setError(err instanceof Error ? err.message : String(err))
|
||||
})
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [])
|
||||
const explore = useCallback(
|
||||
async (planetId: number) => {
|
||||
setExploringId(planetId)
|
||||
try {
|
||||
const res = await fetch('/api/explore', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ planetId }),
|
||||
})
|
||||
const body = (await res.json()) as ExploreResult & { error?: string }
|
||||
if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`)
|
||||
onChanged(body) // fresh state straight from the response — parent mirrors it to CARGO HOLD
|
||||
setLastOutcome(body.outcome)
|
||||
} catch (err: unknown) {
|
||||
setError(err instanceof Error ? err.message : String(err))
|
||||
} finally {
|
||||
setExploringId(null)
|
||||
}
|
||||
},
|
||||
[onChanged]
|
||||
)
|
||||
|
||||
const explore = useCallback(async (planetId: number) => {
|
||||
setExploringId(planetId)
|
||||
try {
|
||||
const res = await fetch('/api/explore', {
|
||||
method: 'POST',
|
||||
headers: { 'content-type': 'application/json' },
|
||||
body: JSON.stringify({ planetId }),
|
||||
})
|
||||
const body = (await res.json()) as ExploreResult & { error?: string }
|
||||
if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`)
|
||||
setState(body) // fresh state straight from the response — no refetch needed
|
||||
setLastOutcome(body.outcome)
|
||||
} catch (err: unknown) {
|
||||
setError(err instanceof Error ? err.message : String(err))
|
||||
} finally {
|
||||
setExploringId(null)
|
||||
}
|
||||
}, [])
|
||||
|
||||
if (error && !state) {
|
||||
return (
|
||||
<div style={{ border: '1px solid var(--border)', background: 'var(--surface)', padding: '0.75rem' }}>
|
||||
<span style={{ fontSize: '0.62rem', color: 'var(--color-red)', letterSpacing: '0.08em' }}>
|
||||
SYSTEM SCAN OFFLINE // {error.toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!state) {
|
||||
if (!system) {
|
||||
return (
|
||||
<div style={{ border: '1px solid var(--border)', background: 'var(--surface)', padding: '0.75rem' }}>
|
||||
<span style={{ fontSize: '0.62rem', color: 'var(--text-muted)', letterSpacing: '0.12em' }}>SCANNING SYSTEM…</span>
|
||||
@@ -88,9 +69,9 @@ export function LocalSystemPanel() {
|
||||
textTransform: 'uppercase',
|
||||
}}
|
||||
>
|
||||
EXPLORABLE WORLDS // {state.systemName.toUpperCase()} — TURN {state.turn}
|
||||
EXPLORABLE WORLDS // {system.systemName.toUpperCase()} — TURN {system.turn}
|
||||
</div>
|
||||
{state.planets.map((row, i) => {
|
||||
{system.planets.map((row, i) => {
|
||||
const depth = row.parent !== null ? 1 : 0
|
||||
const badge = badgeFor(row.explored, row.hazard)
|
||||
return (
|
||||
@@ -102,7 +83,7 @@ export function LocalSystemPanel() {
|
||||
alignItems: 'center',
|
||||
gap: '0.5rem',
|
||||
padding: depth ? '0.6rem 0.75rem 0.6rem 1.75rem' : '0.6rem 0.75rem',
|
||||
borderBottom: i < state.planets.length - 1 ? '1px solid var(--border)' : 'none',
|
||||
borderBottom: i < system.planets.length - 1 ? '1px solid var(--border)' : 'none',
|
||||
}}
|
||||
>
|
||||
<div style={{ overflow: 'hidden' }}>
|
||||
@@ -117,7 +98,7 @@ export function LocalSystemPanel() {
|
||||
{row.name}
|
||||
</div>
|
||||
<div style={{ fontSize: '0.57rem', color: 'var(--text-muted)', letterSpacing: '0.06em', marginTop: '2px' }}>
|
||||
{depth ? `MOON OF ${state.planets.find((p) => p.id === row.parent)?.name ?? '?'} // ${row.type}` : row.type}
|
||||
{depth ? `MOON OF ${system.planets.find((p) => p.id === row.parent)?.name ?? '?'} // ${row.type}` : row.type}
|
||||
</div>
|
||||
</div>
|
||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
||||
@@ -147,36 +128,6 @@ export function LocalSystemPanel() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<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',
|
||||
}}
|
||||
>
|
||||
CARGO HOLD // INVENTORY
|
||||
</div>
|
||||
{state.inventory.length === 0 ? (
|
||||
<div style={{ padding: '0.6rem 0.75rem' }}>
|
||||
<span style={{ fontSize: '0.62rem', color: 'var(--text-muted)', letterSpacing: '0.08em' }}>EMPTY — EXPLORE WORLDS TO FIND THINGS</span>
|
||||
</div>
|
||||
) : (
|
||||
state.inventory.map((item) => (
|
||||
<div key={`${item.kind}-${item.name}`} style={{ display: 'flex', justifyContent: 'space-between', padding: '0.5rem 0.75rem' }}>
|
||||
<span style={{ fontSize: '0.62rem', color: 'var(--text-secondary)', letterSpacing: '0.04em', textTransform: 'uppercase' }}>
|
||||
{item.name} <span style={{ color: 'var(--text-muted)' }}>// {item.kind}</span>
|
||||
</span>
|
||||
<Badge variant="ACTIVE">×{item.qty}</Badge>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
|
||||
{error && (
|
||||
<div style={{ border: '1px solid var(--border)', background: 'var(--surface)', padding: '0.5rem 0.75rem' }}>
|
||||
<span style={{ fontSize: '0.62rem', color: 'var(--color-red)', letterSpacing: '0.08em' }}>SCAN FAILED // {error.toUpperCase()}</span>
|
||||
|
||||
+71
-130
@@ -15,14 +15,15 @@ import { ShipSystemsPanel } from '@/components/ship-systems'
|
||||
import { SkillTreePanel } from '@/components/skill-tree'
|
||||
import { WorldMapPanel } from '@/components/world-map'
|
||||
import { CREW_CAPACITY } from '@/lib/game/ship-costs'
|
||||
import type { CrewDto } from '@/lib/game/save'
|
||||
import type { CrewDto, SystemState } from '@/lib/game/save'
|
||||
import type { ShipState } from '@/lib/game/ship'
|
||||
import type { TechDto } from '@/lib/game/tech'
|
||||
|
||||
// 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
|
||||
// All seven tabs render live data (/api/world, /api/system, /api/crew, /api/ship, /api/tech,
|
||||
// /api/missions, /api/events). Single player, no auth, by design.
|
||||
// showcase (src/app/_pages/showcase/mission-control-content.tsx in baxy5/scificn-ui). All seven
|
||||
// tabs render live data (/api/world, /api/system, /api/crew, /api/ship, /api/tech, /api/missions,
|
||||
// /api/events); the right column's CARGO HOLD renders from the lifted /api/system state. Single
|
||||
// player, no auth, by design.
|
||||
|
||||
function useNarrow(threshold = 900): boolean {
|
||||
const [narrow, setNarrow] = useState(
|
||||
@@ -36,31 +37,15 @@ function useNarrow(threshold = 900): boolean {
|
||||
return narrow
|
||||
}
|
||||
|
||||
// ── Placeholder content (no game logic — real values land with the turn engine) ──
|
||||
|
||||
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' },
|
||||
]
|
||||
|
||||
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.
|
||||
// Ship + system state is owned here (not in the tab panels): the SHIP SYSTEMS tab mirrors ship
|
||||
// actions via setShip, and the right column's CARGO HOLD renders from the lifted inventory —
|
||||
// the LOCAL SYSTEM tab reports each scan's response up via setSystem. Crew count feeds the
|
||||
// roster bar — refetched on page load, like the other panels.
|
||||
const [ship, setShip] = useState<ShipState | null>(null)
|
||||
const [system, setSystem] = useState<SystemState | null>(null)
|
||||
const [techs, setTechs] = useState<TechDto[] | null>(null)
|
||||
const [crewCount, setCrewCount] = useState<number | null>(null)
|
||||
|
||||
@@ -75,6 +60,15 @@ export function MissionControl() {
|
||||
if (!cancelled) setShip(s)
|
||||
})
|
||||
.catch(() => {}) // left column + tab keep their loading state on failure
|
||||
fetch('/api/system')
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
return (await res.json()) as SystemState
|
||||
})
|
||||
.then((s) => {
|
||||
if (!cancelled) setSystem(s)
|
||||
})
|
||||
.catch(() => {})
|
||||
fetch('/api/tech')
|
||||
.then(async (res) => {
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
@@ -249,7 +243,7 @@ export function MissionControl() {
|
||||
style={{
|
||||
flex: 1,
|
||||
display: 'grid',
|
||||
gridTemplateColumns: narrow ? '1fr' : '260px 1fr 240px',
|
||||
gridTemplateColumns: narrow ? '1fr' : '320px 1fr 360px',
|
||||
gap: '1px',
|
||||
background: 'var(--border)',
|
||||
}}
|
||||
@@ -297,11 +291,34 @@ export function MissionControl() {
|
||||
<PanelContent>
|
||||
{ship ? (
|
||||
// Bars driven by real values: hull/reactor from system levels (level × 20, capped),
|
||||
// crew roster vs the display cap.
|
||||
// crew roster vs the display cap; SECTOR/TURN rows folded in from the old SHIP DATA
|
||||
// placeholder (the rest of it duplicated these bars or had no game equivalent).
|
||||
<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 style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
{[
|
||||
{ label: 'SECTOR', value: (system?.systemName ?? '—').toUpperCase() },
|
||||
{ label: 'TURN', value: String(ship.turn) },
|
||||
].map((item, i) => (
|
||||
<div
|
||||
key={item.label}
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
padding: '0.4rem 0',
|
||||
borderBottom: i < 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>
|
||||
</div>
|
||||
) : (
|
||||
<span style={{ fontSize: '0.62rem', color: 'var(--text-muted)', letterSpacing: '0.12em' }}>LOADING…</span>
|
||||
@@ -352,9 +369,9 @@ export function MissionControl() {
|
||||
<WorldMapPanel height={380} />
|
||||
</TabsContent>
|
||||
|
||||
{/* LOCAL SYSTEM TAB — live data from /api/system */}
|
||||
{/* LOCAL SYSTEM TAB — live data (state owned by MissionControl; scans report up) */}
|
||||
<TabsContent value="local-system" style={{ margin: 0, padding: '1rem' }}>
|
||||
<LocalSystemPanel />
|
||||
<LocalSystemPanel system={system} onChanged={setSystem} />
|
||||
</TabsContent>
|
||||
|
||||
{/* CREW TAB — live data from /api/crew */}
|
||||
@@ -386,7 +403,7 @@ export function MissionControl() {
|
||||
</Panel>
|
||||
</div>
|
||||
|
||||
{/* ── RIGHT: UPCOMING ENCOUNTERS + SHIP DATA ── */}
|
||||
{/* ── RIGHT: CARGO HOLD (persistent — inventory lifted from /api/system) ── */}
|
||||
<div
|
||||
style={{
|
||||
background: 'var(--background)',
|
||||
@@ -399,110 +416,34 @@ export function MissionControl() {
|
||||
>
|
||||
<Panel notch="sm">
|
||||
<PanelHeader>
|
||||
<PanelTitle>UPCOMING ENCOUNTERS</PanelTitle>
|
||||
<div style={{ marginLeft: 'auto' }}>
|
||||
<Badge variant="SCANNING">{encounters.length} QUEUED</Badge>
|
||||
</div>
|
||||
<PanelTitle>CARGO HOLD // INVENTORY</PanelTitle>
|
||||
{system && (
|
||||
<div style={{ marginLeft: 'auto' }}>
|
||||
<Badge variant={system.inventory.length > 0 ? 'ACTIVE' : 'OFFLINE'}>
|
||||
{system.inventory.reduce((n, i) => n + i.qty, 0)} UNITS
|
||||
</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}
|
||||
{!system ? (
|
||||
<div style={{ padding: '0.6rem 0.75rem' }}>
|
||||
<span style={{ fontSize: '0.62rem', color: 'var(--text-muted)', letterSpacing: '0.12em' }}>LOADING…</span>
|
||||
</div>
|
||||
) : system.inventory.length === 0 ? (
|
||||
<div style={{ padding: '0.6rem 0.75rem' }}>
|
||||
<span style={{ fontSize: '0.62rem', color: 'var(--text-muted)', letterSpacing: '0.08em' }}>EMPTY — EXPLORE WORLDS TO FIND THINGS</span>
|
||||
</div>
|
||||
) : (
|
||||
system.inventory.map((item) => (
|
||||
<div key={`${item.kind}-${item.name}`} style={{ display: 'flex', justifyContent: 'space-between', padding: '0.5rem 0.75rem' }}>
|
||||
<span style={{ fontSize: '0.62rem', color: 'var(--text-secondary)', letterSpacing: '0.04em', textTransform: 'uppercase' }}>
|
||||
{item.name} <span style={{ color: 'var(--text-muted)' }}>// {item.kind}</span>
|
||||
</span>
|
||||
<Badge variant="ACTIVE">×{item.qty}</Badge>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</PanelContent>
|
||||
</Panel>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "space-wanderer",
|
||||
"version": "0.9.0",
|
||||
"version": "0.9.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev -p 3000",
|
||||
|
||||
Reference in New Issue
Block a user