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>
76 lines
2.1 KiB
TypeScript
76 lines
2.1 KiB
TypeScript
/**
|
|
* LANE-UI — build bar. One button per machine in GameData.machines, hotkeys 1-9.
|
|
* Click or hotkey selects for placement; main.ts does the ghost and the actual place.
|
|
*/
|
|
import type { GameData } from '../contracts';
|
|
import { attrs, cls, el, panel, text } from './dom';
|
|
import { machineColor } from './palette';
|
|
import type { BuildSelection } from './selection';
|
|
import { COPY, DIR_NAME } from './voice';
|
|
|
|
export interface BuildBar {
|
|
el: HTMLElement;
|
|
/** Select the machine in slot `n` (1-based, as printed on the button). */
|
|
selectSlot(n: number): void;
|
|
sync(): void;
|
|
}
|
|
|
|
export function createBuildBar(data: GameData, sel: BuildSelection): BuildBar {
|
|
const root = panel();
|
|
root.id = 'fk-build';
|
|
|
|
const row = el('div', 'fk-build-row');
|
|
const hint = el('div');
|
|
hint.id = 'fk-build-hint';
|
|
|
|
const buttons: Array<{ def: string; btn: HTMLButtonElement }> = [];
|
|
|
|
data.machines.forEach((m, i) => {
|
|
const btn = el('button', 'fk-build-btn');
|
|
attrs(btn, { type: 'button', title: `${m.name} — ${m.kind}` });
|
|
|
|
const ico = el('div', 'fk-build-ico');
|
|
ico.style.background = machineColor(m);
|
|
btn.append(ico);
|
|
|
|
// Only the first nine get a printed hotkey; the rest are click-only until the
|
|
// bar grows tabs/pages (a later round's problem).
|
|
if (i < 9) {
|
|
const key = el('span', 'fk-build-key');
|
|
key.textContent = String(i + 1);
|
|
btn.append(key);
|
|
}
|
|
|
|
btn.append(el('span', 'fk-build-name', [m.name]));
|
|
btn.addEventListener('click', () => sel.select(m.id));
|
|
row.append(btn);
|
|
buttons.push({ def: m.id, btn });
|
|
});
|
|
|
|
root.append(row, hint);
|
|
|
|
function sync() {
|
|
const cur = sel.get();
|
|
for (const b of buttons) cls(b.btn, 'is-sel', cur?.def === b.def);
|
|
if (cur) {
|
|
const def = data.machines.find((m) => m.id === cur.def);
|
|
text(hint, `${def?.name ?? cur.def} · FACING ${DIR_NAME[cur.dir]} · ${COPY.placingHint}`);
|
|
} else {
|
|
text(hint, COPY.idleHint);
|
|
}
|
|
cls(hint, 'is-placing', !!cur);
|
|
}
|
|
|
|
sel.onChange(sync);
|
|
sync();
|
|
|
|
return {
|
|
el: root,
|
|
selectSlot(n) {
|
|
const b = buttons[n - 1];
|
|
if (b) sel.select(b.def);
|
|
},
|
|
sync,
|
|
};
|
|
}
|