# Windows and responsive layouts

window(player, spec) opens or replaces one window for that player.

`window(player, spec)` opens or replaces one window for that player.
`close(player)` removes it. Welcome windows, HUDs and scoreboards are retained
and replayed after scene entry or reconnect; scripts do not need an arrival-delay
timer to display them.

Spec fields: `id`, `title`, `text`, `skin`, `layout`, `items`.
`id` identifies the window in `window_closed`; it is optional, then `nil`.
Window text preserves line breaks. A long window scrolls on small screens.

| Item kind | Fields and interaction |
| --- | --- |
| `button` | `id`, `label`; sends `ui(player, id, nil)` |
| `card` | `id`, `label`, `image` (room asset name/id), `text`, `badge`, `accent` (`#rrggbb`), `disabled`; illustrated action tile |
| `input` | `id`, `label`, `placeholder`; submits trimmed text, up to 40 characters |
| `text` | `text`; preserves paragraphs and newlines |
| `friends` | `id`, optional `label`; selects an online friend, sends their id as value |
| `list` | `rows = {{"name", "value"}, ...}`; display only |

Action ids must be present and unique in a window. Player actions are input to
your rules: validate them against the current game state, including UI events
that arrive after a different screen has opened.

Default windows fit phone and desktop screens. `layout` supports:

- `width`: integer from 240 to 960, in layout pixels.
- `dock`: `center` (default) or `right`. Right-docked flow windows have a draggable desktop header and a scrolling two-column phone panel above combat controls.
- `columns`: 1 or 2 for buttons and cards. Text, lists and fields span all columns.
- `skin`: `auto`, `fit`, or `flow`.
- `phone` and `desktop`: tables overriding these same settings.

Phone layout is selected below 700 CSS pixels wide or 500 high. A small desktop
window can use it too. Players interact with the creator's controls; they do not
edit the creator's UI. Docked desktop windows can be moved by dragging their header.

`skin` at the spec's top level names a thing from the room, including stock.
Its flat voxel view supplies the window artwork. Items can use `x`, `y`, `w`, `h`
in that skin's voxel pixels, with item-level `phone` and `desktop` overrides.
Use `layout.phone.skin = "flow"` to replace precise skin positioning with a
scrolling phone layout. Start with flow layouts for dependable touch controls.

```lua
on("enter", function(p)
  window(p, {
    id = "signals", title = "SIGNALS",
    layout = {width = 600, columns = 2,
      phone = {width = 320, columns = 1, skin = "flow"}},
    items = {
      {kind = "text", text = "Watch the light.\nChoose its colour."},
      {kind = "button", id = "red", label = "RED"},
      {kind = "button", id = "blue", label = "BLUE"}
    }
  })
  every(1, "signals:" .. p.id)
end)

on("window_closed", function(p, id)
  if id == "signals" then stop_timer("signals:" .. p.id) end
end)

on("leave", function(p)
  stop_timer("signals:" .. p.id)
end)
```

The X reports `window_closed` once for the window the player actually dismissed.
Calling `close(player)` from Lua does not emit it. Closing does not automatically
cancel all of a game's timers: stop the relevant timer or mark the screen closed
in your handler, so a subsequent timer does not reopen it.

Window validation refuses invalid fields with an actionable error, for example
`window.items[1].text allows at most 4096 characters`. It does not silently shorten
the window. Limits per spec:

| Field | Maximum |
| --- | --- |
| `title`, item `label`, item `placeholder`, list cell | 80 characters |
| Top-level `text`, each text item's `text` | 4096 characters |
| Window `id`, item `id` | 64 characters |
| Items | 12 |
| Rows per list, cells per row | 12 rows, 3 cells |
| Skin coordinates | Whole numbers from -4096 to 4096; width/height positive |
