Skip to content

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):

ZonePurpose
imageBackground image or video
textMain narrative prose
optionsClickable choices
headerPersistent top bar
sidebar_characterCharacter/player portrait and stats
sidebar_npcNPC portrait, name, relationship indicator
notifyNotification popups
feedbackStat/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.css

Required fields:

FieldDescription
nameLayout ID used in (layout: Name) calls
zonesComma-separated list of zone names this layout provides
cssCSS 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.