# Focus
Menu focus navigation for the cards of GREASEPAINT. A controller has no
pointer, so every menu carries an ordered ring of focusable ids and exactly one
of them is lit; this module walks that ring.
- `Focus.open(list, prefer)` — open a menu: the ids in stick order, and which starts lit.
- `Focus.axis(v, dt)` — feed the `navigate` axis; one flick is one step, a held stick repeats.
- `Focus.point(id)` — a mouse crossing a row or a finger tapping it moves the focus there.
- `Focus.id()` / `Focus.isFocused(id)` — read it.
- `Focus.close()` — nothing is focused until the next `open`.
- `Focus.release()` — let go of a held direction, so a stick held through a transition does not keep walking.
The ring is whatever the card offers, so an interval with three faces and a
fourth row is a ring of four and needs nothing said about it here: `open` takes
the list `screens.itemIds` returns for the phase.
A held stick repeats on a delay measured in seconds AND in frames. On a machine
running long frames a single flick can be one frame carrying half a second of
`dt`, and a delay measured only in time turns that flick into two steps.
Fed by `game/input/console.inputMap`; consumed by `game.ui.Screens`, which asks
it which row to draw lit.
# cards
Every screen First Light: Betocsin puts BETWEEN the player and the tower.
```lua
local Cards = require("game.screens.cards")
ui.registerScreen("flb_card", Cards.title(T.metrics(), view), 800)
```
| Builder | The card |
|---|---|
| `Cards.title(m, view)` | The device, the name, and the two ways in — Take the watch and How to play. |
| `Cards.howto(m, view)` | The warden's handbill: three drawn rules — the crane swings, only the overlap stays, the sky is the clock — and the way back. |
| `Cards.pause(m, view)` | The crane holds. Back to the crane, or start the night again. |
| `Cards.won(m, view)` | The tocsin is rung, with courses, true seats, and how much of the town woke. |
| `Cards.lost(m, view)` | Which way the night took it: It fell, or Too late. |
Every builder is pure. It takes the metrics table from
`game.screens.theme` and a view model, and returns a tree. It registers
nothing, reads no game state and writes none — `Screens.component` builds the
view model and owns the glass.
## The view model
```lua
focus = "start" -- the id of the lit row from game.screens.Focus
fresh = true -- true only while the card's entrance is still playing
dawn = 0.0 courses = 12 perfects = 4 woke = 0.8 reason = "sunrise"
```
`fresh` is what stops the entrance cascade replaying: while it is true the rows
carry their staggered entrance animation, and once the player has moved the
focus the tree is rebuilt without it.
# theme
The look of every screen in First Light: Betocsin, in one place: the palette,
the type scale, the metrics, the drawn devices, and the two pieces every card is
assembled from.
```lua
local T = require("game.screens.theme")
local m = T.metrics()
local tree = T.card(m, { id = "flb_title", children = { ... } })
```
## What it holds
| | |
|---|---|
| `T.color` | The tinctures, by their heraldic names — `azure`, `argent`, `or_`, `gules` — plus the night grounds and the dawn ramp's `indigo` / `rose` / `ember`. |
| `T.font` | Three families: `display` (EB Garamond), `body` (PT Serif), `numeral` (Cousine). |
| `T.metrics()` | Everything a layout needs, off the LIVE `ui.screenSize()`: the type scale, the card column width, the row width, the padding rhythm. |
| `T.device(lit)` | The town's device — the bell over a lozengy tower — as inline SVG. `lit` is how much dawn is on the bronze. |
| `T.lozenge(fill)` | The small diamond used as a bullet, a tick and a focus pip. |
| `T.trimDiagram()` / `T.swingDiagram()` / `T.dawnDiagram()` | The three drawn rules of the how-to card. |
| `T.band(m, id)` | The lozengy rule that divides a card. |
| `T.card(m, opts)` | A card shell: the scrim, the centred column, the frame, the entrance move. |
| `T.row(m, opts)` | One selectable line of a card, with its lit state and its pointer target. |
| `T.para(m, id, text, style)` | A wrapping paragraph. |
| `T.legend(m, text, id)` | The small line under a card's rows naming what each device family presses. |
## Two rules it exists to keep
Nothing is a pixel chosen for one screen. The type scale reads off
`min(width, height 0.75)`, so a tall frame scales type by its width and a wide
one by its height; the card is a single centred column at
`min(0.86 · width, 0.6 · height)`. The same card reads on a 3:4 phone held
upright and on a 16:9 television.
A wrapping label carries its width on the box around it. `T.para` is that
box. A label given both `wrap` and a width collapses to one glyph per line.
# console
The controls a CARD answers to: the title, the how-to-play handbill, the
interval between two acts, the pause card and the two end cards.
It is live exactly while a card is up — `game.ui.Screens` activates it the
instant one goes up and releases it the instant the last one comes down. Its
group is `ui`, and it stands down `player` and `table`, so nothing behind a card
answers while the card has the player.
| Control | Keyboard | Pad | Touch |
|---|---|---|---|
| `navigate` | Arrows or WASD | Left stick | — the rows |
| `confirm` | Space or Enter | South (A / Cross) | — the rows |
| `back` | Backspace | East (B / Circle) | — the row that says so |
| `resume` | Escape | Start | — the Resume row |
`navigate` binds each direction to two keys at once — the WASD key and the arrow
key beside it — because a player reaching for a menu uses whichever is already
under their hand.
## Why no touch class
Every row of every card is a button under a finger: tapping one moves the focus
there and acts on it in the same gesture. A finger does not walk a menu to reach
a row and then confirm it — it puts itself on the row. So each of these four
declares `touch = false` rather than adding an on-screen stick and three
unlabelled circles that would each be a second, worse way to do what the card
already is. Backing out is the how-to's Back row and the pause card's Resume row;
leaving the table is the pause glyph the Table draws.
# core
One-paragraph description of what this module provides and who consumes
it.
## Exports
- `M.exampleFn(arg) -> result` — what it does and when to call it.
- `M.exampleValue: type` — what value it carries.
## Usage
Replace `<identity>` with the string `asset.create` returned — this
module's source path with `/` written as `.` and each segment's type
suffix dropped, the way a module at `/source/lib/thing.module` is
`lib.thing` and one at `/source/kit.package/thing.module` is
`kit.thing`.
```luau
local M = require("<identity>")
M.exampleFn("foo")
```
## Notes
- Anything callers need to know about THIS module: required runtime
ordering, state ownership, side effects.
# hud
The instruments the player reads while the tower goes up, and the caption strip
the Warden's spoken lines print on.
```lua
local Hud = require("game.screens.hud")
ui.registerScreen("flb_hud", Hud.tree(T.metrics(), view), 20)
ui.registerScreen("flb_hud_touch", Hud.controls(T.metrics()), 300)
ui.registerScreen("flb_caption", Hud.caption(T.metrics(), text, alpha), 40)
```
| Builder | What it draws |
|---|---|
| `Hud.tree(m, view)` | Two gradient scrims, the dawn meter down the left edge with the four watches marked, the course readout and true-seat streak in the top-left, the crane's prompt near the bottom, and the two beats that flash — a true seat and a watch. |
| `Hud.controls(m)` | The HUD's own tap targets. Its own screen, because they have to ride above the engine's on-screen touch-control overlay. |
| `Hud.caption(m, text, alpha)` | The written form of whatever the Warden just said. |
| `Hud.skyAt(t)` | The colour the sky wears at a reading of `dawn`, so the meter's head and the sky agree. |
## Portrait first, and still right when wide
Nothing here spans the frame. The meter runs DOWN the left edge, the readout
stacks in a corner, the prompt is one centred line. Every position is a fraction
of the live `ui.screenSize()`, so on a wide screen the meter is simply shorter
and the column narrower — no number in this file was chosen for one shape.
# back
The 'Back' control.
# confirm
The 'Confirm' control.
# navigate
Walking the rows of a card: one flick is one step, a held stick repeats.
Each keyboard direction answers to TWO keys — the WASD key and the arrow key
beside it — because a player reaching for a menu uses whichever is already under
their hand. An axis takes one binding per device class, so each direction is
itself a vector of the two keys that mean it.
On a pad it is the left stick; under a thumb it is the on-screen stick in the
left zone.
# resume
The 'Resume' control.