# @ferret/ui: API

HUD and menus as a responsive HTML/CSS overlay that works with cursor/A/B, mouse and touch.

20 exports. Generated from the source (`packages/ui/src`), so it matches the code.

## Classes

### LevelMenu

*class* · `packages/ui/src/level-menu.ts:124`

```ts
class LevelMenu implements Service
```

Opens on the menu action, pauses the world while it is up and reads the cursor keys, Enter (A) and Escape (B) itself (the fixed step does not run
while paused), plus a gamepad's d-pad, A, B and Start.

- `readonly name: "level-menu"`
- `readonly model: LevelMenuModel`
- `constructor(handlers: LevelMenuHandlers, readonly opts: LevelMenuOptions = {})`
- `init(world: World)`
- `press(button: 'up' | 'down' | 'accept' | 'back')` — Feed the menu from any source (keys, gamepad, a test).
- `fixedUpdate()` — Called before the components' fixed update, at the fixed rate.
- `update()` — Called once per rendered frame.
- `refreshScale()`
- `dispose()`

### LevelMenuModel

*class* · `packages/ui/src/level-menu.ts:30`

```ts
class LevelMenuModel
```

- `page: LevelMenuPage`
- `index: number`
- `isOpen: boolean`
- `onChange?: () => void`
- `onOpen?: () => void`
- `onClose?: () => void`
- `constructor(readonly handlers: LevelMenuHandlers, readonly labels: LevelMenuLabels = DEFAULT_LABELS)`
- `rows(): LevelMenuRow[]` — the rows of the page being shown
- `get current(): LevelMenuRow | null`
- `open()`
- `close()`
- `toggle()` — the menu action (Escape, Start, the Menu button) while the game runs opens it; while it is open it closes it, as IA_Escape and the widget's key handler do
- `move(delta: number)`
- `hover(i: number)`
- `accept()`
- `back()` — B and Escape: the whole menu closes (the widget's key handler closes the widget, not one page)

### MenuModel

*class* · `packages/ui/src/model.ts:20`

```ts
class MenuModel
```

A menu you steer with the cursor, A and B only (up/down/accept/back), or the pointer. Submenus stack; back pops them and,
from the first menu, closes it. Disabled items are skipped by the cursor.

- `onChange?: () => void`
- `onClose?: () => void`
- `get isOpen(): boolean`
- `get menu(): MenuDef | null`
- `get index(): number`
- `get current(): MenuItem | null`
- `open(def: MenuDef): void`
- `close(): void`
- `move(delta: number): void`
- `hover(index: number): void` — Point at an item (mouse hover). Disabled items cannot be pointed at.
- `accept(): void`
- `back(): void`

### ToastQueue

*class* · `packages/ui/src/model.ts:82`

```ts
class ToastQueue
```

Short messages that show for a while and go ("Picked up: brass key"). At most `max` show at once, the rest wait.

- `active: Toast[]`
- `onChange?: () => void`
- `constructor(readonly max = 3, readonly defaultMs = 3)`
- `push(text: string, o: { seconds?: number; kind?: string } = {}): number`
- `update(dt: number): void`
- `clear(): void`

### UIService

*class* · `packages/ui/src/service.ts:43`

```ts
class UIService implements Service
```

The HTML overlay: HUD lines, the use-prompt, toasts, the loading screen and the menu. Everything shown is data first
(`hud`, `promptText`, `loading`, `toasts`, `menu`), the DOM only draws it, so games and tests can read what the player sees.
Cursor, A and B drive the menu (arrows, Enter, Escape; gamepad d-pad, A, B); mouse and touch work on the items directly.

- `readonly name: "ui"`
- `readonly hud: Map<string, string>`
- `readonly toasts: ToastQueue`
- `readonly menu: MenuModel`
- `promptText: string`
- `loading: Loading`
- `scale: number`
- `constructor(readonly opts: UIOptions = {})`
- `init(world: World)`
- `showFailure(e: FailedEvent)` — The engine's failure screen: what went wrong in a line, then Retry and Back. It is a menu, so the cursor, A and B, the mouse and touch all work, at any screen size. Retry travels again with the same options.
- `setPrompt(text: string)` — Show the use-prompt for an interactable: "[E] Open door". An empty text hides it.
- `setHud(key: string, text: string | null)`
- `toast(text: string, o?: { seconds?: number; kind?: string })`
- `openMenu(def: MenuDef)` — Open a menu and pause the game while it is up.
- `refreshScale()`
- `press(button: 'up' | 'down' | 'accept' | 'back')` — Feed the menu from any source (keys, gamepad, a test).
- `update(dt: number)` — Called once per rendered frame.
- `fixedUpdate()` — Called before the components' fixed update, at the fixed rate.
- `dispose()`

## Functions

### failureText

*function* · `packages/ui/src/service.ts:23`

```ts
function failureText(e: FailedEvent): string
```

A short sentence for the player, with the reason as a code for support.

## Interfaces

### FailedEvent

*interface* · `packages/ui/src/service.ts:20`

```ts
interface FailedEvent
```

What `level:failed` carries (see LevelService.travel).

- `id: string`
- `reason?: string`
- `handled?: boolean`
- `opts?: Record<string, unknown>`
- `error?: { message?: string; status?: number }`

### LevelMenuHandlers

*interface* · `packages/ui/src/level-menu.ts:12`

```ts
interface LevelMenuHandlers
```

- `places: LevelMenuPlace[]` — every place, in the order the list shows them; `name` is what the row says
- `onPlace(id: string): void`
- `onHall(): void`
- `onStart(): void`
- `onQuit(): void`

### LevelMenuLabels

*interface* · `packages/ui/src/level-menu.ts:21`

```ts
interface LevelMenuLabels
```

- `title: string`
- `locations: string`
- `hall: string`
- `start: string`
- `quit: string`
- `back: string`
- `close: string`

### LevelMenuOptions

*interface* · `packages/ui/src/level-menu.ts:98`

```ts
interface LevelMenuOptions
```

- `document?: Document`
- `parent?: HTMLElement`
- `menuAction?: string` — the button action that opens and closes the menu (default "menu")
- `scale?: () => number` — the UI scale, set as `--ui` on the menu's root
- `blocked?: () => boolean` — return true while something else owns the screen (a failure screen, the sign-in card): the menu does not open then
- `labels?: Partial<LevelMenuLabels>`

### LevelMenuPlace

*interface* · `packages/ui/src/level-menu.ts:10`

```ts
interface LevelMenuPlace
```

The level-select menu of a game with many places: a main page (Locations, two relocations, Quit) and a scrolling Locations page (Back, then one row
per place). Steered by the cursor (up, down), A (accept) and B (close) only, or the pointer. The model is plain data and runs headless; `LevelMenu`
is the service that opens it on the menu action, pauses the world and draws it. The game decides what the rows do and how they look (class names
`ferret-lm*`, see `LEVEL_MENU_CSS` for the structure only).

- `id: string`
- `name: string`

### LevelMenuRow

*interface* · `packages/ui/src/level-menu.ts:28`

```ts
interface LevelMenuRow
```

- `id: string`
- `label: string`
- `kind: 'locations' | 'hall' | 'start' | 'quit' | 'back' | 'place'`
- `danger?: boolean`

### MenuDef

*interface* · `packages/ui/src/model.ts:14`

```ts
interface MenuDef
```

- `title: string`
- `items: MenuItem[]`
- `message?: string`

### MenuItem

*interface* · `packages/ui/src/model.ts:3`

```ts
interface MenuItem
```

The parts of the UI that are plain data, so they run (and are tested) without a DOM.

- `id: string`
- `label: string`
- `disabled?: boolean`
- `action?: () => void` — called on accept; a submenu opens instead if `submenu` is set
- `submenu?: MenuDef`
- `value?: () => string` — a value shown at the right (a setting)

### Toast

*interface* · `packages/ui/src/model.ts:79`

```ts
interface Toast
```

- `id: number`
- `text: string`
- `kind: string`
- `left: number`

### UIOptions

*interface* · `packages/ui/src/service.ts:5`

```ts
interface UIOptions
```

- `document?: Document` — where the overlay goes; without a document the service keeps its state only (headless tests, servers)
- `parent?: HTMLElement`
- `scale?: () => number` — returns the UI scale (use `() => uiScaleFor(platform.screen())`); called again on `refreshScale()`
- `useAction?: string` — the button action that uses an interactable; its label is shown in the prompt (default "use")
- `menuAction?: string` — the action that opens the pause menu, when `pauseMenu` is set (default "menu")
- `pauseMenu?: () => MenuDef` — opened by the menu action; the game pauses while it is open

## Types

### LevelMenuPage

*type* · `packages/ui/src/level-menu.ts:27`

```ts
type LevelMenuPage = 'main' | 'locations'
```

## Constants

### DEFAULT_LABELS

*const* · `packages/ui/src/level-menu.ts:23`

```ts
const DEFAULT_LABELS: LevelMenuLabels
```

### LEVEL_MENU_CSS

*const* · `packages/ui/src/level-menu.ts:110`

```ts
const LEVEL_MENU_CSS: "\n:where(.ferret-lm){position:fixed;inset:0;display:none;align-items:center;justify-content:center;z-index:20;pointer-events:auto;font:calc(var(--ui,1)*16px)/1.3 system-ui,sans-serif;color:#fff;user-select:none;-webkit-user-select:none}\n:where(.ferret-lm.open){display:flex}\n:where(.ferret-lm__panel){position:relative;box-sizing:border-box;display:flex;flex-direction:column;overflow:hidden;backg …
```

### UI_CSS

*const* · `packages/ui/src/style.ts:2`

```ts
const UI_CSS: "\n.ferret-ui{position:fixed;inset:0;pointer-events:none;font:calc(var(--ui,1)*16px)/1.3 system-ui,sans-serif;color:#fff;\n  padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);user-select:none;-webkit-user-select:none;z-index:10}\n.ferret-ui *{box-sizing:border-box}\n.ferret-ui .hud{position:absolute;top:calc(var(--ui,1)*12px);left:cal …
```

The overlay's stylesheet. Sizes are in `calc(var(--ui) * N px)` so the whole UI follows `uiScaleFor` (console, phone, desktop).

