glytch/fktry/src/ui/buildbar.ts
type-two c18309f464 [ui] build bar, top strip, inspector, commission fax, toasts
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>
2026-07-17 16:15:50 +10:00

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