This commit is contained in:
root
2026-09-20 14:16:41 +02:00
parent 0798933b05
commit b6d1909d7f
56 changed files with 3752 additions and 1989 deletions
+12 -16
View File
@@ -1,8 +1,8 @@
// Halcyon Flight Control: application shell and routing.
import { api, keyStore, onUnauthorized } from './api.js';
import { api, keyStore, migrateStorage, onUnauthorized } from './api.js';
import { h, clear, toast } from './dom.js';
import { startStarfield } from './starfield.js';
import { startBackdrop } from './backdrop.js';
import { statusOf } from './status.js';
import { authView } from './view-auth.js';
import { fleetView } from './view-fleet.js';
@@ -18,9 +18,8 @@ const TABS = [
['downlink', 'DOWNLINK'],
];
const VIEWS = { fleet: fleetView, code: codeView, uplink: uplinkView, downlink: downlinkView };
const ORE_TAGS = { iron: 'FE', nickel: 'NI', ice: 'H2O', platinum: 'PT' };
const state = { info: null, me: null, market: null, ships: [], selected: null, tab: 'fleet' };
const state = { info: null, me: null, ships: [], selected: null, tab: 'fleet' };
let shell = null; // { top, nav, select, main }
const tabFromHash = () => {
@@ -36,7 +35,7 @@ const ctx = {
},
selectShip(id, tab) {
state.selected = id;
try { localStorage.setItem('wh.ship', String(id)); } catch { /* ignore */ }
try { localStorage.setItem('halcyon.ship', String(id)); } catch { /* ignore */ }
renderNav();
if (tab) ctx.go(tab); else renderMain();
},
@@ -49,9 +48,9 @@ const ctx = {
};
async function loadAccount() {
const [me, ships, market, info] = await Promise.all([api.me(), api.ships(), api.market().catch(() => null), api.info().catch(() => state.info)]);
Object.assign(state, { me, ships, market, info });
const remembered = Number(safeGet('wh.ship'));
const [me, ships, info] = await Promise.all([api.me(), api.ships(), api.info().catch(() => state.info)]);
Object.assign(state, { me, ships, info });
const remembered = Number(safeGet('halcyon.ship'));
if (!ships.some((s) => s.id === state.selected)) {
state.selected = ships.some((s) => s.id === remembered) ? remembered : (ships[0]?.id ?? null);
}
@@ -98,22 +97,18 @@ function buildShell() {
shell.top,
shell.nav,
shell.main,
h('footer', { class: 'foot' }, 'HALCYON INSTRUMENT & CONTROL // WORMHOLE LINK 1 KB/DAY // THE BELT IS SIMULATED ONCE A DAY'),
h('footer', { class: 'foot' }, 'HALCYON INSTRUMENT & CONTROL'),
);
}
function renderTop() {
if (!shell) return;
const { me, info, market } = state;
const prices = market && info?.ores
? info.ores.map((o, i) => h('span', { class: 'chip ore', title: `${o} price per kg` }, h('b', null, ORE_TAGS[o] ?? o.toUpperCase()), ` ${market[i]}`))
: [h('span', { class: 'chip dim', title: 'Prices appear after the first daily run' }, 'MARKET: NO DATA YET')];
const { me, info } = state;
clear(shell.top).append(
h('div', { class: 'top-brand' }, h('span', { class: 'logo sm' }, 'HALCYON'), h('span', { class: 'dim' }, 'FLIGHT CONTROL')),
h('div', { class: 'top-stats' },
h('span', { class: 'chip' }, 'DAY ', h('b', null, String(info?.day ?? me?.day ?? 0))),
h('span', { class: 'chip' }, 'CREDITS ', h('b', null, (me?.credits ?? 0).toLocaleString('en-US'))),
...prices,
),
h('div', { class: 'top-user' },
h('span', { class: 'callsign' }, me?.name?.toUpperCase() ?? ''),
@@ -134,7 +129,7 @@ function renderNav() {
clear(shell.nav).append(
h('div', { class: 'tabs' },
TABS.map(([id, label]) => h('a', { class: `tab ${state.tab === id ? 'on' : ''}`, href: `#/${id}`, 'aria-current': state.tab === id ? 'page' : null }, label)),
h('a', { class: 'tab', href: '/manual.txt', target: '_blank', rel: 'noopener' }, 'MANUAL'),
h('a', { class: 'tab', href: '/manual.pdf', target: '_blank', rel: 'noopener' }, 'MANUAL'),
),
h('label', { class: 'shipsel' }, 'ACTIVE SHIP', select),
);
@@ -160,7 +155,8 @@ document.addEventListener('visibilitychange', () => {
// ---- boot -------------------------------------------------------------------
startStarfield(document.getElementById('stars'));
migrateStorage(); // before anything reads the key
startBackdrop(document.getElementById('backdrop'));
onUnauthorized(() => logout('SESSION REJECTED: ACCESS KEY NOT RECOGNISED'));
api.info().then((i) => { state.info = i; }).catch(() => {});
if (keyStore.get()) enter(); else showAuth();