Appearance
Zones & Layout
How content is routed to screen regions, and how layouts define those regions.
The Zone System
Zones are named content areas in the layout. When a passage writes to a zone, that zone's content is replaced in the UI. Zones that aren't written by the current passage stay as they were.
A zone switch happens with @zone(name). Everything after the directive (prose, macros, links) targets that zone until the next @zone() or the end of the passage.
ana
:: Bar_BartenderGreet
// No @zone here — content before the first @zone targets the default zone.
@zone(text)
The bartender watches you without expression.
// ^ This prose goes into the "text" zone.
@zone(options)
(link: "Order a drink")[(action: Bar_OrderBeer)]
(link: "Leave")[(goto: MainStreet)]
// ^ These links go into the "options" zone.Zones run until the next zone directive or the end of the passage; there are no closing brackets. Think of @zone(name) as "switch the output target to this zone."
Only zones written by the current passage update. If a passage writes to text and options but not portrait, the portrait stays from the previous passage. This is what makes (update:) work: it replaces just what you write and nothing else.
Adding to a zone instead of replacing it
Writing to a zone (via @zone() or an (action:)) replaces its content. When you instead want to add to a zone without rebuilding it (a running combat log, an incremental reveal, a growing list), use (append: "zone")[...], (prepend: "zone")[...], or (replace: "zone")[...]. These mutate the zone's live DOM in place. The added content is transient: it is cleared on the next (goto:) or whenever that zone is legitimately rebuilt.
ana
:: Combat_Swing [action]
(append: "log")[You swing — a clean hit for 6 damage.]Layout Templates
A layout template defines what zones exist on screen and how they're arranged. The template is set in GameInit and can be changed at runtime with (layout: TemplateName).
ana
// In GameInit
(layout: Layout_Standard)
// Switching mid-game (e.g., entering a phone UI)
(layout: Layout_Phone)When you switch layouts, zones that exist in both keep their current content. Zones only in the old layout disappear. New zones start empty.
Standard zones (in Layout_Standard):
| Zone | Purpose |
|---|---|
image | Background image or video |
text | Main narrative prose |
options | Clickable choices |
header | Persistent top bar |
sidebar_character | Character/player portrait and stats |
sidebar_npc | NPC portrait, name, relationship indicator |
notify | Notification popups |
feedback | Stat/relationship feedback overlay |
Game authors can define custom layouts with custom zone names. The engine doesn't enforce a fixed zone set.
The header bar lays out in three slots: the shortcut buttons you render into the header zone sit on the left, the current location ($world.location, set by [location] tags) is centered automatically, and the clock/date sits on the right. You only author the buttons; location and clock are engine-managed overlays.
The notify and feedback overlays default to the top-right and top-left corners. Reposition them in GameInit with (notify-position:) and (feedback-position:) to any of top-left, top-right, bottom-left, bottom-right, top-center, or bottom-center.
Authoring a .layout file
Layout files live in layouts/ and use a simple key: value format. Lines starting with // are comments.
// layouts/Layout_Phone.layout
name: Layout_Phone
zones: text, options, notify
css: layouts.cssRequired fields:
| Field | Description |
|---|---|
name | Layout ID used in (layout: Name) calls |
zones | Comma-separated list of zone names this layout provides |
css | CSS file to load alongside this layout |
Zone names are free-form identifiers. Use @zone(zoneName) inside passages to direct output. The engine only knows about zones declared in the active layout's zones: list; output to any other zone name is discarded (with a warning in dev builds).
Switching layouts at runtime:
ana
@zone(options)
(link: "Open phone")[(action: OpenPhone)]
:: OpenPhone
(layout: Layout_Phone)When (layout:) switches templates, zones shared between old and new keep their content; zones only in the old template disappear; new zones start empty.