/** * LANE-UI โ€” top strip: bandwidth meter, shipped ticker, run state. * * The brownout has to be legible from this panel alone โ€” it is the one failure the * player must read across the room, so it gets the border, the color, and the flash. */ import type { GameData, ItemDef, SimSnapshot } from '../contracts'; import { createChipRow, type ChipRow } from './chips'; import { cls, el, panel, style, text } from './dom'; import { formatReserve, powerState } from './power'; import { COPY } from './voice'; export interface TopStrip { el: HTMLElement; update(snap: SimSnapshot): void; } function stat(label: string, valueCls?: string): { row: HTMLElement; v: HTMLElement } { const v = el('span', valueCls ? `fk-stat-v ${valueCls}` : 'fk-stat-v'); const row = el('div', 'fk-stat', [el('span', undefined, [label]), v]); return { row, v }; } export function createTopStrip(data: GameData): TopStrip { const root = panel(); root.id = 'fk-top'; const head = el('div', 'fk-h', [COPY.bandwidth]); const drawEl = el('span', 'fk-bw-draw'); const genEl = el('span'); const nums = el('div', 'fk-bw-nums', [drawEl, genEl]); const fill = el('div', 'fk-bar-f'); const bar = el('div', 'fk-bar', [fill]); const brownout = el('div'); brownout.id = 'fk-brownout'; brownout.textContent = COPY.brownout; // Amber, not red: the factory is surviving on stored charge, not failing. const reserve = el('div'); reserve.id = 'fk-reserve'; const buffer = stat('BUFFER', 'fk-buf'); const shipped = stat(COPY.shipped); const run = stat(COPY.tick); run.v.id = 'fk-run'; const items = new Map(data.items.map((i) => [i.id, i])); const chips: ChipRow = createChipRow(items); root.append(head, nums, bar, brownout, reserve, el('div', 'fk-rule'), buffer.row, shipped.row, run.row, chips.el); return { el: root, update(snap) { const { gen, draw, stored, capacity } = snap.bandwidth; text(drawEl, `${draw.toFixed(0)} DRAW`); text(genEl, `${gen.toFixed(0)} GEN`); // BROWNOUT is the sim's flag and nothing else. A covered deficit is the tanks // doing their job, and it gets its own amber word โ€” see power.ts. const p = powerState(snap.bandwidth); style(fill, 'width', `${Math.min(1, p.ratio) * 100}%`); cls(root, 'is-tight', p.state === 'tight'); cls(root, 'is-reserve', p.state === 'reserve'); cls(root, 'is-brownout', p.state === 'brownout'); if (p.state === 'reserve') { text(reserve, `${COPY.onReserve} ยท ${formatReserve(p.reserveSeconds!)}`); } // Capacity (v4) turns the buffer readout into a fuel gauge rather than a number. text(buffer.v, capacity ? `${stored.toFixed(0)}/${capacity.toFixed(0)}` : stored.toFixed(0)); let total = 0; for (const n of Object.values(snap.shippedTotal)) total += n; text(shipped.v, String(total)); text(run.v, snap.paused ? COPY.paused : `${snap.tick}`); cls(run.v, 'is-paused', snap.paused); chips.update( Object.entries(snap.shippedTotal) .filter(([, n]) => n > 0) .map(([item, n]) => ({ item, count: String(n), state: 'met' as const })), ); }, }; }