This commit is contained in:
Audiobuzz
2026-06-18 12:03:45 +00:00
parent ada4aa4b18
commit 3b776dd970
47 changed files with 3416 additions and 110 deletions
+44
View File
@@ -0,0 +1,44 @@
<script>
import Navbar from './lib/Navbar.svelte'
import StatusBar from './lib/StatusBar.svelte'
import { nav } from './lib/router.svelte.js'
import { clock, hhmmss } from './lib/clock.svelte.js'
import Dashboard from './pages/Dashboard.svelte'
import Power from './pages/Power.svelte'
import Navigation from './pages/Navigation.svelte'
import LifeSupport from './pages/LifeSupport.svelte'
import Comms from './pages/Comms.svelte'
const pages = {
dashboard: Dashboard,
power: Power,
nav: Navigation,
life: LifeSupport,
comms: Comms,
}
let Current = $derived(pages[nav.current] ?? Dashboard)
const ticker =
'SYS // all primary buses nominal ◦ FTL capacitor charging — 61% ◦ ' +
'sensor sweep grid 7-K complete ◦ comms relay 2 degraded — rerouting ◦ ' +
'reactor output 84.2% rated ◦ next waypoint: Tarsis Station, ETA 04:12:38 ◦ '
</script>
<div class="relative z-10 flex h-full flex-col">
<Navbar />
<StatusBar />
<main class="flex-1 overflow-y-auto p-4 md:p-6">
<Current />
</main>
<footer class="flex items-center gap-3 overflow-hidden border-t border-haze/20 bg-void/70 px-4 py-1.5">
<span class="shrink-0 text-[0.6rem] uppercase tracking-[0.2em] text-flare glow-sm">● LIVE</span>
<span class="shrink-0 text-[0.6rem] tabular-nums text-haze">{hhmmss(clock.now)}</span>
<div class="relative flex-1 overflow-hidden">
<span class="ticker text-[0.65rem] uppercase tracking-[0.18em] text-haze">{ticker}{ticker}</span>
</div>
</footer>
</div>
+95
View File
@@ -0,0 +1,95 @@
@import "tailwindcss";
/* Cyber sci-fi palette. Defining these as @theme tokens makes them available
as Tailwind utilities (bg-hull, text-term, border-haze, ...). Font is the
stock DejaVu Sans Mono shipped in the kiosk image — no web fonts (offline). */
@theme {
--color-void: #040a0d;
--color-hull: #081419;
--color-hull2: #0c1f27;
--color-term: #00ffd0;
--color-haze: #4a8c84;
--color-flare: #ff2e88;
--color-warn: #ffb000;
--color-ok: #2dff8a;
--color-alert: #ff3b3b;
--font-mono: "DejaVu Sans Mono", ui-monospace, "SFMono-Regular", "Courier New", monospace;
}
:root { color-scheme: dark; }
html, body, #app { height: 100%; margin: 0; padding: 0; }
body {
background-color: var(--color-void);
color: var(--color-term);
font-family: var(--font-mono);
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
}
/* Faint engineering-grid backdrop + center glow + vignette. */
#app::before {
content: "";
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none;
background-image:
linear-gradient(rgba(0, 255, 208, 0.035) 1px, transparent 1px),
linear-gradient(90deg, rgba(0, 255, 208, 0.035) 1px, transparent 1px),
radial-gradient(circle at 50% 22%, rgba(0, 90, 100, 0.35), transparent 65%);
background-size: 42px 42px, 42px 42px, 100% 100%;
}
/* CRT scanline overlay sitting above content. */
#app::after {
content: "";
position: fixed;
inset: 0;
z-index: 60;
pointer-events: none;
background: repeating-linear-gradient(
to bottom,
rgba(0, 0, 0, 0) 0px,
rgba(0, 0, 0, 0) 2px,
rgba(0, 0, 0, 0.16) 3px
);
mix-blend-mode: multiply;
}
.glow { text-shadow: 0 0 10px currentColor; }
.glow-sm { text-shadow: 0 0 6px currentColor; }
/* Cut-corner panel edge — the recurring "console module" silhouette. */
.cut {
clip-path: polygon(
0 0,
calc(100% - 14px) 0,
100% 14px,
100% 100%,
14px 100%,
0 calc(100% - 14px)
);
}
@keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.2; } }
.blink { animation: blink 1.4s steps(2, jump-none) infinite; }
@keyframes ticker { from { transform: translateX(100%); } to { transform: translateX(-100%); } }
.ticker { display: inline-block; white-space: nowrap; animation: ticker 32s linear infinite; }
/* Radar sweep wedge. */
@keyframes radar { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
.radar-sweep {
background: conic-gradient(from 0deg, rgba(0, 255, 208, 0.28), rgba(0, 255, 208, 0) 70deg);
border-radius: 9999px;
animation: radar 4s linear infinite;
}
/* Thin neon scrollbars to match the theme. */
* { scrollbar-width: thin; scrollbar-color: rgba(0, 255, 208, 0.3) transparent; }
*::-webkit-scrollbar { width: 8px; height: 8px; }
*::-webkit-scrollbar-thumb { background: rgba(0, 255, 208, 0.3); }
*::-webkit-scrollbar-track { background: transparent; }
+15
View File
@@ -0,0 +1,15 @@
<script>
// Horizontal gauge. `tone` is a full bg-* class (statically scannable).
let { label, pct = 0, tone = 'bg-term', value = null, unit = '%' } = $props()
let clamped = $derived(Math.max(0, Math.min(100, pct)))
</script>
<div class="space-y-1">
<div class="flex items-baseline justify-between text-[0.65rem] uppercase tracking-wider">
<span class="text-haze">{label}</span>
<span class="text-term tabular-nums glow-sm">{value ?? clamped}{unit}</span>
</div>
<div class="h-2 w-full overflow-hidden border border-haze/30 bg-void">
<div class="h-full {tone} transition-[width] duration-700 ease-out" style="width:{clamped}%"></div>
</div>
</div>
+31
View File
@@ -0,0 +1,31 @@
<script>
import { navItems, ship } from './data.js'
import { nav } from './router.svelte.js'
</script>
<nav class="flex items-stretch border-b border-haze/30 bg-hull/80">
<div class="flex items-center gap-3 border-r border-haze/30 px-4 py-3">
<span class="text-flare glow text-2xl leading-none">◭</span>
<div class="leading-tight">
<div class="text-term glow-sm text-sm tracking-[0.25em]">{ship.name}</div>
<div class="text-haze text-[0.6rem] tracking-[0.2em]">{ship.registry}</div>
</div>
</div>
<div class="flex flex-1 items-stretch overflow-x-auto">
{#each navItems as item}
<a
href="#/{item.id}"
aria-current={nav.current === item.id ? 'page' : undefined}
class="group relative flex items-center gap-2 px-4 py-3 text-xs uppercase tracking-[0.2em] transition-colors
{nav.current === item.id ? 'text-term glow-sm bg-term/5' : 'text-haze hover:text-term hover:bg-term/5'}"
>
<span class="text-base leading-none">{item.glyph}</span>
<span class="hidden md:inline">{item.label}</span>
{#if nav.current === item.id}
<span class="absolute inset-x-0 bottom-0 h-[2px] bg-term glow"></span>
{/if}
</a>
{/each}
</div>
</nav>
+17
View File
@@ -0,0 +1,17 @@
<script>
// Reusable console "module": cut-corner border, header strip, body slot.
// `accent` is a full Tailwind class string so the class is statically visible
// to Tailwind's scanner (e.g. "text-flare").
let { title = '', accent = 'text-term', children } = $props()
</script>
<section class="cut relative border border-haze/30 bg-hull/70">
<div class="absolute left-0 top-0 h-[2px] w-12 bg-term/70"></div>
<header class="flex items-center gap-2 border-b border-haze/25 bg-hull2/50 px-3 py-2">
<span class="text-term/60 text-xs">▌</span>
<h2 class="text-[0.7rem] uppercase tracking-[0.3em] {accent} glow-sm">{title}</h2>
</header>
<div class="p-3 md:p-4">
{@render children?.()}
</div>
</section>
+25
View File
@@ -0,0 +1,25 @@
<script>
// Sweeping radar with positioned blips. `blips`: [{x,y,tone,label}] in %.
let { blips = [] } = $props()
</script>
<div class="relative mx-auto aspect-square w-full max-w-[300px] overflow-hidden rounded-full border border-haze/40 bg-void/60">
<div class="absolute inset-[14%] rounded-full border border-haze/20"></div>
<div class="absolute inset-[32%] rounded-full border border-haze/20"></div>
<div class="absolute inset-[48%] rounded-full border border-haze/20"></div>
<div class="absolute left-1/2 top-0 h-full w-px -translate-x-1/2 bg-haze/15"></div>
<div class="absolute top-1/2 left-0 h-px w-full -translate-y-1/2 bg-haze/15"></div>
<div class="radar-sweep absolute inset-0"></div>
<!-- own ship -->
<div class="absolute left-1/2 top-1/2 h-2.5 w-2.5 -translate-x-1/2 -translate-y-1/2 rotate-45 bg-flare glow"></div>
{#each blips as b}
<div
class="absolute h-2 w-2 -translate-x-1/2 -translate-y-1/2 rounded-full {b.tone ?? 'bg-term'} glow blink"
style="left:{b.x}%; top:{b.y}%"
title={b.label}
></div>
{/each}
</div>
+11
View File
@@ -0,0 +1,11 @@
<script>
let { label, value, unit = '', tone = 'text-term', sub = '' } = $props()
</script>
<div class="flex flex-col gap-1">
<span class="text-[0.6rem] uppercase tracking-[0.2em] text-haze">{label}</span>
<span class="text-2xl md:text-3xl font-bold tabular-nums {tone} glow-sm leading-none">
{value}{#if unit}<span class="ml-1 text-sm font-normal text-haze">{unit}</span>{/if}
</span>
{#if sub}<span class="text-[0.65rem] text-haze">{sub}</span>{/if}
</div>
@@ -0,0 +1,19 @@
<script>
import { ship } from './data.js'
import { clock, stardate, hhmmss } from './clock.svelte.js'
</script>
<div class="flex flex-wrap items-center justify-between gap-x-6 gap-y-1 border-b border-haze/20 bg-void/60 px-4 py-1.5 text-[0.65rem] uppercase tracking-[0.18em] text-haze">
<div class="flex flex-wrap items-center gap-x-6 gap-y-1">
<span><span class="text-haze/70">CLASS</span> <span class="text-term">{ship.class}</span></span>
<span><span class="text-haze/70">CMD</span> <span class="text-term">{ship.captain}</span></span>
</div>
<div class="flex flex-wrap items-center gap-x-6 gap-y-1">
<span class="flex items-center gap-2">
<span class="h-2 w-2 rounded-full bg-ok glow blink"></span>
<span class="text-ok glow-sm">CONDITION GREEN</span>
</span>
<span><span class="text-haze/70">STARDATE</span> <span class="text-term tabular-nums">{stardate(clock.now)}</span></span>
<span><span class="text-haze/70">MISSION TIME</span> <span class="text-term tabular-nums glow-sm">{hhmmss(clock.now)}</span></span>
</div>
</div>
+16
View File
@@ -0,0 +1,16 @@
<script>
// Status pill. Literal class map so every variant survives Tailwind purging.
let { tone = 'term', children } = $props()
const map = {
term: 'border-term/60 text-term',
ok: 'border-ok/60 text-ok',
warn: 'border-warn/60 text-warn',
alert: 'border-alert/60 text-alert',
flare: 'border-flare/60 text-flare',
haze: 'border-haze/60 text-haze',
}
</script>
<span class="inline-flex items-center gap-1 border px-2 py-0.5 text-[0.6rem] uppercase tracking-[0.18em] glow-sm {map[tone] ?? map.term}">
{@render children?.()}
</span>
+24
View File
@@ -0,0 +1,24 @@
// Shared 1 Hz clock. Module-level $state + a single interval, so every consumer
// (status bar, footer) shares one timer. Read `clock.now` reactively.
let _now = $state(new Date())
if (typeof window !== 'undefined') {
setInterval(() => { _now = new Date() }, 1000)
}
export const clock = {
get now() { return _now },
}
// Playful "stardate" derived from the wall clock, purely cosmetic.
export function stardate(d) {
const epoch = Date.UTC(2350, 0, 1)
const days = (d.getTime() - epoch) / 86_400_000
return (47000 + days / 3.65).toFixed(1)
}
export function hhmmss(d) {
const p = (n) => String(n).padStart(2, '0')
return `${p(d.getHours())}:${p(d.getMinutes())}:${p(d.getSeconds())}`
}
+137
View File
@@ -0,0 +1,137 @@
// Dummy data for the spaceship terminal. All static/fictional — no real
// telemetry source yet. A future runtime agent could replace these with a
// fetch() to a fleet API; the components only read from here.
export const navItems = [
{ id: 'dashboard', label: 'BRIDGE', glyph: '◈' },
{ id: 'power', label: 'REACTOR', glyph: '⬡' },
{ id: 'nav', label: 'NAVIGATION', glyph: '✧' },
{ id: 'life', label: 'LIFE SUPPORT', glyph: '❂' },
{ id: 'comms', label: 'COMMS', glyph: '☰' },
]
export const ship = {
name: 'ISV AURELIAN',
registry: 'FN-774-Δ',
class: 'Lancer-class Deep Survey Cruiser',
captain: 'Cmdr. V. Okonkwo',
}
// --- BRIDGE / dashboard ------------------------------------------------------
export const systemStatus = [
{ name: 'Reactor Core', state: 'NOMINAL', tone: 'ok' },
{ name: 'Shield Matrix', state: 'NOMINAL', tone: 'ok' },
{ name: 'FTL Drive', state: 'CHARGING', tone: 'warn' },
{ name: 'Life Support', state: 'NOMINAL', tone: 'ok' },
{ name: 'Hull Integrity', state: '98.4%', tone: 'ok' },
{ name: 'Weapons', state: 'STANDBY', tone: 'term' },
{ name: 'Sensor Array', state: 'NOMINAL', tone: 'ok' },
{ name: 'Comms Relay', state: 'DEGRADED', tone: 'alert' },
]
export const subsystemLoad = [
{ label: 'Propulsion', pct: 72, tone: 'bg-term' },
{ label: 'Shields', pct: 64, tone: 'bg-term' },
{ label: 'Life Support', pct: 38, tone: 'bg-ok' },
{ label: 'Sensors', pct: 51, tone: 'bg-term' },
{ label: 'FTL Charge', pct: 61, tone: 'bg-warn' },
{ label: 'Weapons', pct: 9, tone: 'bg-haze' },
]
export const logEntries = [
{ t: '07:42:11', tone: 'warn', msg: 'Jump capacitor at 61% — full charge ETA 00:18:24' },
{ t: '07:39:02', tone: 'alert', msg: 'Comms relay 2 packet loss 14% — rerouting via relay 1' },
{ t: '07:31:55', tone: 'term', msg: 'Sensor sweep complete — 3 contacts logged in grid 7-K' },
{ t: '07:28:40', tone: 'ok', msg: 'Hull micro-fracture sealed on deck 4, section C' },
{ t: '07:14:09', tone: 'ok', msg: 'Reactor output stabilised at 84.2% rated' },
{ t: '06:58:21', tone: 'term', msg: 'Course correction +0.4° applied to heading 287' },
]
// --- REACTOR / power ---------------------------------------------------------
export const reactor = {
output: 84.2, // % rated
coreTemp: 4120, // K
containment: 99.1, // % field strength
rpm: 11840, // turbine
}
export const powerDistribution = [
{ label: 'Propulsion Bus', pct: 72, tone: 'bg-term' },
{ label: 'Shield Emitters', pct: 64, tone: 'bg-term' },
{ label: 'FTL Capacitor', pct: 61, tone: 'bg-warn' },
{ label: 'Sensors / EW', pct: 51, tone: 'bg-term' },
{ label: 'Life Support', pct: 38, tone: 'bg-ok' },
{ label: 'Habitation', pct: 27, tone: 'bg-ok' },
{ label: 'Weapons Standby', pct: 9, tone: 'bg-haze' },
]
export const fuelCells = [
{ id: 'CELL-A', charge: 96, tone: 'bg-ok' },
{ id: 'CELL-B', charge: 91, tone: 'bg-ok' },
{ id: 'CELL-C', charge: 78, tone: 'bg-term' },
{ id: 'CELL-D', charge: 44, tone: 'bg-warn' },
]
// --- NAVIGATION --------------------------------------------------------------
export const navState = {
heading: 287.4, // degrees
velocity: 0.21, // c
system: 'Cygnus Reach',
sector: 'Grid 7-K',
destination: 'Tarsis Station',
eta: '04:12:38',
ftlCharge: 61,
coords: { x: '+1184.22', y: '-0473.90', z: '+0061.07' },
}
export const waypoints = [
{ name: 'Tarsis Station', dist: '2.4 AU', status: 'LOCKED', tone: 'ok' },
{ name: 'Helex Belt', dist: '0.8 AU', status: 'PASSING', tone: 'warn' },
{ name: 'Relay Buoy 19', dist: '11.6 AU', status: 'QUEUED', tone: 'term' },
{ name: 'Cygnus Gate', dist: '34.0 AU', status: 'QUEUED', tone: 'term' },
]
// blips positioned in % within the radar circle (50/50 = center = own ship)
export const contacts = [
{ x: 64, y: 38, tone: 'bg-term', label: 'Freighter "Coriol"' },
{ x: 41, y: 27, tone: 'bg-warn', label: 'Unknown — passive' },
{ x: 72, y: 66, tone: 'bg-ok', label: 'Tarsis Station' },
{ x: 30, y: 58, tone: 'bg-flare', label: 'Debris field' },
]
// --- LIFE SUPPORT ------------------------------------------------------------
export const atmosphere = [
{ label: 'Oxygen', value: 20.9, unit: '%', pct: 87, tone: 'bg-ok' },
{ label: 'CO₂', value: 0.04, unit: '%', pct: 12, tone: 'bg-ok' },
{ label: 'Cabin Press.', value: 101.1, unit: 'kPa', pct: 92, tone: 'bg-ok' },
{ label: 'Temperature', value: 21.4, unit: '°C', pct: 71, tone: 'bg-term' },
{ label: 'Humidity', value: 44, unit: '%', pct: 44, tone: 'bg-term' },
{ label: 'Water Reserve', value: 68, unit: '%', pct: 68, tone: 'bg-term' },
]
export const decks = [
{ deck: 'Deck 1 — Bridge', o2: 'OK', temp: '21.0°C', tone: 'ok' },
{ deck: 'Deck 2 — Habitation', o2: 'OK', temp: '22.3°C', tone: 'ok' },
{ deck: 'Deck 3 — Med / Sci', o2: 'OK', temp: '20.6°C', tone: 'ok' },
{ deck: 'Deck 4 — Engineering', o2: 'LOW', temp: '26.8°C', tone: 'warn' },
{ deck: 'Deck 5 — Cargo', o2: 'SEALED', temp: '8.1°C', tone: 'term' },
]
export const crew = { total: 84, onDuty: 31, medical: 2, cryo: 12 }
// --- COMMS -------------------------------------------------------------------
export const channels = [
{ name: 'Fleet Command', band: 'Q-band', status: 'ENCRYPTED', tone: 'ok' },
{ name: 'Tarsis Control', band: 'S-band', status: 'OPEN', tone: 'term' },
{ name: 'Relay Buoy 19', band: 'X-band', status: 'WEAK', tone: 'warn' },
{ name: 'Distress Guard', band: '121.5', status: 'MONITOR', tone: 'term' },
{ name: 'Internal Net', band: 'Hardline', status: 'NOMINAL', tone: 'ok' },
]
export const messages = [
{ t: '07:41', from: 'FLEET CMD', tone: 'ok', text: 'Patrol pattern delta authorised. Maintain EMCON.' },
{ t: '07:33', from: 'TARSIS CTL', tone: 'term', text: 'Docking bay 4 reserved. Transmit manifest on approach.' },
{ t: '07:19', from: 'RELAY-19', tone: 'warn', text: 'Signal degradation detected. Recommend X-band switch.' },
{ t: '06:52', from: 'CORIOL', tone: 'term', text: 'Thanks for the escort, Aurelian. Safe travels.' },
{ t: '06:40', from: 'FLEET CMD', tone: 'flare', text: 'Priority weather: ion storm forming in grid 9-B.' },
]
+17
View File
@@ -0,0 +1,17 @@
// Reactive "telemetry" helper: returns an object whose .value drifts around a
// base value on an interval, so static dummy data feels like a live readout.
// Call inside a component's script (it registers an $effect for cleanup).
export function useJitter(base, { amp = 1, decimals = 0, every = 2000 } = {}) {
let value = $state(base)
$effect(() => {
const id = setInterval(() => {
const delta = (Math.random() - 0.5) * 2 * amp
value = Number((base + delta).toFixed(decimals))
}, every)
return () => clearInterval(id)
})
return { get value() { return value } }
}
@@ -0,0 +1,20 @@
// Tiny hash-based router using a module-level rune. Hash routing keeps deep
// links client-side (they never reach nginx), so the reverse proxy needs no
// SPA-fallback rewrite. `nav.current` is reactive: read it in markup/$derived.
function parse() {
if (typeof window === 'undefined') return 'dashboard'
const raw = window.location.hash.replace(/^#\/?/, '')
return raw.split(/[/?]/)[0] || 'dashboard'
}
let _route = $state(parse())
if (typeof window !== 'undefined') {
window.addEventListener('hashchange', () => { _route = parse() })
if (!window.location.hash) window.location.hash = '#/dashboard'
}
export const nav = {
get current() { return _route },
}
+7
View File
@@ -0,0 +1,7 @@
import { mount } from 'svelte'
import './app.css'
import App from './App.svelte'
const app = mount(App, { target: document.getElementById('app') })
export default app
+62
View File
@@ -0,0 +1,62 @@
<script>
import Panel from '../lib/Panel.svelte'
import Stat from '../lib/Stat.svelte'
import Bar from '../lib/Bar.svelte'
import Tag from '../lib/Tag.svelte'
import { channels, messages } from '../lib/data.js'
const fromTone = {
ok: 'text-ok', warn: 'text-warn', alert: 'text-alert',
flare: 'text-flare', term: 'text-term',
}
</script>
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4 lg:grid-cols-4">
<Panel title="Uplink"><Stat label="fleet command" value="ENCRYPTED" tone="text-ok" sub="Q-band · 0% loss" /></Panel>
<Panel title="Active Channels"><Stat label="monitored" value={channels.length} sub="1 degraded" /></Panel>
<Panel title="Signal Latency"><Stat label="to relay" value={418} unit="ms" tone="text-warn" sub="lightspeed lag" /></Panel>
<Panel title="EMCON"><Stat label="emissions" value="DELTA" tone="text-flare" sub="restricted transmit" /></Panel>
</div>
<div class="grid gap-4 lg:grid-cols-2">
<Panel title="Channels" accent="text-term">
<table class="w-full text-xs">
<thead>
<tr class="text-left text-haze/70 uppercase tracking-wider">
<th class="pb-2 font-normal">Channel</th>
<th class="pb-2 font-normal">Band</th>
<th class="pb-2 text-right font-normal">Status</th>
</tr>
</thead>
<tbody class="divide-y divide-haze/15">
{#each channels as c}
<tr>
<td class="py-2 text-term">{c.name}</td>
<td class="py-2 tabular-nums text-haze">{c.band}</td>
<td class="py-2 text-right"><Tag tone={c.tone}>{c.status}</Tag></td>
</tr>
{/each}
</tbody>
</table>
<div class="mt-4 space-y-3">
<Bar label="Antenna alignment" pct={97} tone="bg-ok" />
<Bar label="Relay-19 link" pct={43} tone="bg-warn" />
</div>
</Panel>
<Panel title="Message Traffic" accent="text-flare">
<ul class="space-y-2">
{#each messages as m}
<li class="border border-haze/20 bg-void/40 px-3 py-2">
<div class="flex items-center justify-between text-[0.6rem] uppercase tracking-wider">
<span class="{fromTone[m.tone] ?? 'text-term'} glow-sm">{m.from}</span>
<span class="tabular-nums text-haze">{m.t}</span>
</div>
<p class="mt-1 text-xs leading-snug text-haze/90">{m.text}</p>
</li>
{/each}
</ul>
</Panel>
</div>
</div>
@@ -0,0 +1,61 @@
<script>
import Panel from '../lib/Panel.svelte'
import Stat from '../lib/Stat.svelte'
import Bar from '../lib/Bar.svelte'
import Tag from '../lib/Tag.svelte'
import { useJitter } from '../lib/live.svelte.js'
import { systemStatus, subsystemLoad, logEntries } from '../lib/data.js'
const output = useJitter(84.2, { amp: 0.6, decimals: 1 })
const velocity = useJitter(0.21, { amp: 0.004, decimals: 3, every: 1500 })
const hull = useJitter(98.4, { amp: 0.05, decimals: 2, every: 3000 })
const shields = useJitter(91.7, { amp: 0.8, decimals: 1 })
// Literal classes so Tailwind keeps them (no dynamic interpolation).
const dot = {
term: 'text-term', ok: 'text-ok', warn: 'text-warn',
alert: 'text-alert', flare: 'text-flare',
}
</script>
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4 lg:grid-cols-4">
<Panel title="Velocity"><Stat label="relative" value={velocity.value} unit="c" sub="sublight cruise" /></Panel>
<Panel title="Hull Integrity"><Stat label="structural" value={hull.value} unit="%" tone="text-ok" sub="no active breaches" /></Panel>
<Panel title="Shield Matrix"><Stat label="forward arc" value={shields.value} unit="%" sub="harmonics stable" /></Panel>
<Panel title="Reactor Output"><Stat label="rated" value={output.value} unit="%" tone="text-warn" sub="core nominal" /></Panel>
</div>
<div class="grid gap-4 lg:grid-cols-3">
<Panel title="Systems Status" accent="text-term">
<ul class="divide-y divide-haze/15">
{#each systemStatus as s}
<li class="flex items-center justify-between py-2 text-sm">
<span class="text-haze">{s.name}</span>
<Tag tone={s.tone}>{s.state}</Tag>
</li>
{/each}
</ul>
</Panel>
<Panel title="Subsystem Load" accent="text-term">
<div class="space-y-3">
{#each subsystemLoad as b}
<Bar label={b.label} pct={b.pct} tone={b.tone} />
{/each}
</div>
</Panel>
<Panel title="Event Log" accent="text-flare">
<ul class="space-y-2 text-xs">
{#each logEntries as e}
<li class="flex gap-2">
<span class="shrink-0 tabular-nums text-haze">{e.t}</span>
<span class="shrink-0 {dot[e.tone] ?? 'text-term'}">▍</span>
<span class="text-haze/90 leading-snug">{e.msg}</span>
</li>
{/each}
</ul>
</Panel>
</div>
</div>
@@ -0,0 +1,60 @@
<script>
import Panel from '../lib/Panel.svelte'
import Stat from '../lib/Stat.svelte'
import Bar from '../lib/Bar.svelte'
import Tag from '../lib/Tag.svelte'
import { atmosphere, decks, crew } from '../lib/data.js'
</script>
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4 lg:grid-cols-4">
<Panel title="Crew Aboard"><Stat label="souls" value={crew.total} sub={crew.onDuty + ' on active duty'} /></Panel>
<Panel title="In Cryo"><Stat label="long-haul" value={crew.cryo} tone="text-term" sub="pods nominal" /></Panel>
<Panel title="Medical"><Stat label="infirmary" value={crew.medical} tone="text-warn" sub="under observation" /></Panel>
<Panel title="Atmosphere"><Stat label="overall" value="STABLE" tone="text-ok" sub="recyclers nominal" /></Panel>
</div>
<div class="grid gap-4 lg:grid-cols-3">
<Panel title="Atmospheric Mix" accent="text-term">
<div class="space-y-3 lg:col-span-2">
{#each atmosphere as a}
<Bar label={a.label} pct={a.pct} tone={a.tone} value={a.value} unit={' ' + a.unit} />
{/each}
</div>
</Panel>
<Panel title="Deck Environment" accent="text-term">
<table class="w-full text-xs">
<thead>
<tr class="text-left text-haze/70 uppercase tracking-wider">
<th class="pb-2 font-normal">Deck</th>
<th class="pb-2 font-normal">O₂</th>
<th class="pb-2 text-right font-normal">Temp</th>
</tr>
</thead>
<tbody class="divide-y divide-haze/15">
{#each decks as d}
<tr>
<td class="py-2 text-term">{d.deck}</td>
<td class="py-2"><Tag tone={d.tone}>{d.o2}</Tag></td>
<td class="py-2 text-right tabular-nums text-haze">{d.temp}</td>
</tr>
{/each}
</tbody>
</table>
</Panel>
<Panel title="Recyclers & Reserves" accent="text-flare">
<div class="space-y-3">
<Bar label="CO₂ scrubbers" pct={88} tone="bg-ok" />
<Bar label="O₂ generation" pct={82} tone="bg-ok" />
<Bar label="Water recovery" pct={68} tone="bg-term" />
<Bar label="Waste reclaim" pct={59} tone="bg-term" />
<div class="mt-3 flex items-center justify-between border-t border-haze/20 pt-3 text-xs">
<span class="text-haze uppercase tracking-wider">Emergency O₂</span>
<Tag tone="ok">72 hrs</Tag>
</div>
</div>
</Panel>
</div>
</div>
@@ -0,0 +1,80 @@
<script>
import Panel from '../lib/Panel.svelte'
import Stat from '../lib/Stat.svelte'
import Bar from '../lib/Bar.svelte'
import Tag from '../lib/Tag.svelte'
import Radar from '../lib/Radar.svelte'
import { useJitter } from '../lib/live.svelte.js'
import { navState, waypoints, contacts } from '../lib/data.js'
const heading = useJitter(navState.heading, { amp: 0.3, decimals: 1, every: 1500 })
const velocity = useJitter(navState.velocity, { amp: 0.004, decimals: 3, every: 1500 })
</script>
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4 lg:grid-cols-4">
<Panel title="Heading"><Stat label="bearing" value={heading.value} unit="°" sub="auto-helm engaged" /></Panel>
<Panel title="Velocity"><Stat label="relative" value={velocity.value} unit="c" sub="sublight" /></Panel>
<Panel title="Current System"><Stat label="position" value={navState.system} tone="text-term" sub={navState.sector} /></Panel>
<Panel title="Destination"><Stat label="next waypoint" value={navState.destination} tone="text-flare" sub={'ETA ' + navState.eta} /></Panel>
</div>
<div class="grid gap-4 lg:grid-cols-3">
<Panel title="Tactical Plot" accent="text-term">
<Radar blips={contacts} />
<div class="mt-3 grid grid-cols-2 gap-x-3 gap-y-1 text-[0.65rem] text-haze">
{#each contacts as c}
<div class="flex items-center gap-2">
<span class="h-2 w-2 rounded-full {c.tone} glow"></span>
<span class="truncate">{c.label}</span>
</div>
{/each}
</div>
</Panel>
<Panel title="Flight Plan" accent="text-term">
<table class="w-full text-xs">
<thead>
<tr class="text-left text-haze/70 uppercase tracking-wider">
<th class="pb-2 font-normal">Waypoint</th>
<th class="pb-2 font-normal">Range</th>
<th class="pb-2 text-right font-normal">Status</th>
</tr>
</thead>
<tbody class="divide-y divide-haze/15">
{#each waypoints as w}
<tr>
<td class="py-2 text-term">{w.name}</td>
<td class="py-2 tabular-nums text-haze">{w.dist}</td>
<td class="py-2 text-right"><Tag tone={w.tone}>{w.status}</Tag></td>
</tr>
{/each}
</tbody>
</table>
</Panel>
<Panel title="FTL Drive" accent="text-flare">
<div class="space-y-4">
<Bar label="Jump capacitor" pct={navState.ftlCharge} tone="bg-warn" />
<div class="grid grid-cols-3 gap-2 text-center text-xs">
<div class="border border-haze/20 bg-void/40 py-2">
<div class="text-[0.55rem] uppercase text-haze">X</div>
<div class="tabular-nums text-term glow-sm">{navState.coords.x}</div>
</div>
<div class="border border-haze/20 bg-void/40 py-2">
<div class="text-[0.55rem] uppercase text-haze">Y</div>
<div class="tabular-nums text-term glow-sm">{navState.coords.y}</div>
</div>
<div class="border border-haze/20 bg-void/40 py-2">
<div class="text-[0.55rem] uppercase text-haze">Z</div>
<div class="tabular-nums text-term glow-sm">{navState.coords.z}</div>
</div>
</div>
<div class="flex items-center justify-between border-t border-haze/20 pt-3 text-xs">
<span class="text-haze uppercase tracking-wider">Jump readiness</span>
<Tag tone="warn">CHARGING</Tag>
</div>
</div>
</Panel>
</div>
</div>
+62
View File
@@ -0,0 +1,62 @@
<script>
import Panel from '../lib/Panel.svelte'
import Stat from '../lib/Stat.svelte'
import Bar from '../lib/Bar.svelte'
import Tag from '../lib/Tag.svelte'
import { useJitter } from '../lib/live.svelte.js'
import { reactor, powerDistribution, fuelCells } from '../lib/data.js'
const output = useJitter(reactor.output, { amp: 0.7, decimals: 1 })
const temp = useJitter(reactor.coreTemp, { amp: 18, decimals: 0, every: 1500 })
const rpm = useJitter(reactor.rpm, { amp: 40, decimals: 0, every: 1200 })
const cont = useJitter(reactor.containment, { amp: 0.15, decimals: 1, every: 2500 })
</script>
<div class="space-y-4">
<div class="grid grid-cols-2 gap-4 lg:grid-cols-4">
<Panel title="Core Output"><Stat label="rated power" value={output.value} unit="%" tone="text-warn" sub="84% of 2.4 TW" /></Panel>
<Panel title="Core Temp"><Stat label="plasma" value={temp.value} unit="K" tone="text-flare" sub="within tolerance" /></Panel>
<Panel title="Containment"><Stat label="field strength" value={cont.value} unit="%" tone="text-ok" sub="magnetic bottle stable" /></Panel>
<Panel title="Turbine"><Stat label="primary spindle" value={rpm.value} unit="rpm" sub="no vibration faults" /></Panel>
</div>
<div class="grid gap-4 lg:grid-cols-3">
<Panel title="Power Distribution" accent="text-term">
<div class="space-y-3 lg:col-span-2">
{#each powerDistribution as p}
<Bar label={p.label} pct={p.pct} tone={p.tone} />
{/each}
</div>
</Panel>
<Panel title="Fuel Cells" accent="text-term">
<div class="space-y-3">
{#each fuelCells as c}
<Bar label={c.id} pct={c.charge} tone={c.tone} />
{/each}
<div class="mt-3 flex items-center justify-between border-t border-haze/20 pt-3 text-xs">
<span class="text-haze">Deuterium reserve</span>
<Tag tone="ok">77% / 14 days</Tag>
</div>
</div>
</Panel>
<Panel title="Reactor Controls" accent="text-flare">
<div class="space-y-2">
{#each [
{ l: 'Auto-throttle', v: 'ENGAGED', t: 'ok' },
{ l: 'SCRAM arming', v: 'SAFE', t: 'term' },
{ l: 'Coolant loop A', v: 'FLOW 100%', t: 'ok' },
{ l: 'Coolant loop B', v: 'STANDBY', t: 'term' },
{ l: 'Plasma injectors', v: '6 / 6 ONLINE', t: 'ok' },
{ l: 'Overload damper', v: 'ARMED', t: 'warn' },
] as r}
<div class="flex items-center justify-between border border-haze/20 bg-void/40 px-3 py-2 text-xs">
<span class="text-haze uppercase tracking-wider">{r.l}</span>
<Tag tone={r.t}>{r.v}</Tag>
</div>
{/each}
</div>
</Panel>
</div>
</div>