The five M1 surfaces, replacing the stub: - buildbar.ts: one button per machine, hotkeys 1-9, R rotates, Esc or right-click clears. LANE-DATA shipped 21 machines, so the bar wraps rather than running off both edges of the viewport; slots 10-21 are click-only for now (paging proposed in NOTES). - topstrip.ts: bandwidth meter going amber at 85% and red + flashing BROWNOUT over 100%, buffer, shipped ticker + per-item chips, tick or HALTED. The brownout is legible from this panel alone, which is the one failure the player has to read across the room. - inspector.ts: name, codex flavor, intake/output chips with live buffers, process bar, heat, jam reason. Holds an entity id and re-looks-up each frame, so a removed unit closes itself. - fax.ts: the commission card with wants-as-chips and a FAX SENT stamp on commissionDone. - toasts.ts: max 4, 6s TTL, colored per event kind. Click-to-inspect is not wired: the UI cannot reach renderer.pickTile (UIBus exposes only dispatch/selectedBuild, and ui.init never sees the Renderer). TAB cycles the unit manifest as a stopgap so the inspector is reachable without a console. Contract request filed in NOTES. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
50 lines
1.4 KiB
TypeScript
50 lines
1.4 KiB
TypeScript
/**
|
|
* LANE-UI — event toasts, bottom right. Max 4 on screen, 6s each, oldest evicted.
|
|
*
|
|
* Wall-clock timing is fine here: toasts are chrome, not sim. The determinism rule in
|
|
* MASTERPLAN binds `src/sim/**`, and nothing in this file feeds back into it.
|
|
*/
|
|
import type { SimEvent } from '../contracts';
|
|
import { cls, el } from './dom';
|
|
import { eventToast } from './voice';
|
|
|
|
export interface Toasts {
|
|
el: HTMLElement;
|
|
push(ev: SimEvent, machineName: (entityId: number) => string): void;
|
|
/** Called once per frame to expire old lines. */
|
|
update(nowMs: number): void;
|
|
}
|
|
|
|
const TTL_MS = 6000;
|
|
const FADE_MS = 400; // must match the .fk-toast.is-out transition
|
|
const MAX = 4;
|
|
|
|
export function createToasts(): Toasts {
|
|
const root = el('div');
|
|
root.id = 'fk-toasts';
|
|
|
|
const live: Array<{ node: HTMLElement; dieAt: number }> = [];
|
|
|
|
function retire(entry: { node: HTMLElement; dieAt: number }) {
|
|
cls(entry.node, 'is-out', true);
|
|
setTimeout(() => entry.node.remove(), FADE_MS);
|
|
}
|
|
|
|
return {
|
|
el: root,
|
|
push(ev, machineName) {
|
|
const msg = eventToast(ev, machineName);
|
|
if (!msg) return;
|
|
|
|
const node = el('div', `fk-toast k-${ev.kind}`, [msg]);
|
|
root.append(node);
|
|
live.push({ node, dieAt: performance.now() + TTL_MS });
|
|
|
|
while (live.length > MAX) retire(live.shift()!);
|
|
},
|
|
update(nowMs) {
|
|
while (live.length && live[0].dieAt <= nowMs) retire(live.shift()!);
|
|
},
|
|
};
|
|
}
|