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
+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 },
}