Appearance
Modals, Themes & Audio
Modal overlay screens, the theme system, and audio playback.
Modal Overlays
Modals render a named passage into a full-viewport overlay, blocking game input while open.
(modal-open: "PassageName")
Opens the modal overlay and renders the named passage into the modal zone. It stacks, so you can open a modal from within a modal.
ana
(link: "Open inventory")[(modal-open: "InventoryScreen")](modal-close:)
Closes the top modal on the stack. When the stack is empty, the overlay hides.
ana
(link: "Close")[(modal-close:)]Stack example:
ana
:: InventoryScreen
@zone(modal)
Choose an item.
(link: "Inspect item")[(modal-open: "ItemDetail")]
(link: "Close")[(modal-close:)]
:: ItemDetail
@zone(modal)
Detailed item view.
(link: "Back")[(modal-close:)]Theme System
Player-selectable themes backed by CSS html[data-theme="name"] overrides.
(theme-define: "id", "Label")
Registers a theme. Call in GameInit. The engine expects the CSS file at styles/themes/{id}.css, which must be linked in index.html. Authors can define as many themes as they like.
ana
:: GameInit @system(init)
(theme-define: "dark", "Dark")
(theme-define: "light", "Light")
(theme-define: "default", "Default")
(theme-define: "dracula", "Dracula") // custom theme(theme: "id")
(theme: "id", persist, false)
Switches the active theme by setting the data-theme attribute on <html>. By default, the choice is saved to localStorage and restored on the next session.
Pass persist, false for narrative effects that should not override the player's preferred theme:
ana
// From a settings screen — permanent player choice
(theme: $world.selectedTheme)
// Narrative effect — temporary, reverts on next theme call
(theme: "dracula", persist, false)
// ... a few passages later, restore player's theme:
(theme: (theme-current:)) // won't help — see note belowNote on transient narrative effects: since (theme: "x", persist, false) doesn't save, you need to track the previous theme yourself:
ana
(set: $world.prevTheme to (theme-current:))
(theme: "dracula", persist, false)
// ... dramatic passages ...
(theme: $world.prevTheme) // restore(theme-list:)
Expression macro. Returns an array of registered theme IDs in registration order. Use with (dropdown:) or (link-cycle:) in a settings screen.
ana
// In a settings passage:
(dropdown: $world.theme, "Theme", (theme-list:))(theme-current:)
Expression macro. Returns the currently active theme ID string (reads data-theme from <html>). Returns an empty string "" when no theme has been set, not null or undefined.
ana
Current theme: (theme-current:)
(if: (theme-current:) is "light")[
(link: "Switch to Dark")[(action: _Theme_Dark)]
]
// Check if any theme is active:
(if: (theme-current:) is "")[
No theme set yet.
]Theme CSS format
Create styles/themes/my-theme.css:
css
html[data-theme="my-theme"] {
--ana-bg: #1c1510;
--ana-text: #e8ddd0;
--ana-text-muted: #8a7d68;
--ana-border: rgba(255, 255, 255, 0.12);
--ana-border-subtle: rgba(255, 255, 255, 0.07);
/* ... override any --ana-* vars you want to change ... */
}Only override the tokens you need; all others inherit the :root dark defaults from engine.css.
Link the file in index.html after the other theme files, before game.css:
html
<link rel="stylesheet" href="/styles/themes/dark.css">
<link rel="stylesheet" href="/styles/themes/light.css">
<link rel="stylesheet" href="/styles/themes/my-theme.css">
<link rel="stylesheet" href="/styles/game.css">Audio
(audio: track, channel, behavior?, durationMs?)
Play an audio track on a named channel. The channel arg replaces the old channel: kwarg form.
- track: filename (
.mp3appended if no extension) - channel:
bgm,sfx,ambiance,voice, or a custom channel - behavior:
play,loop,fade-in,fade-out,stop(defaultplay; inherits from(default-transition: audio, ...)) - durationMs: optional fade duration in ms (default 1000)
ana
(audio: pub_music, bgm, loop)
(audio: door_open, sfx, play)
(audio: combat_theme, bgm, fade-in, 2000)
(audio: old_track, bgm, fade-out)(audio-crossfade: newTrack, channel, durationMs?)
Fades out the current track on channel, then fades in newTrack. The total transition takes durationMs ms (default 1000; each half takes durationMs / 2).
ana
(audio-crossfade: combat_theme.mp3, bgm, 1500)(audio-channel: name)
Define a custom audio channel beyond the four built-ins (bgm, sfx, ambiance, voice). Call in GameInit.
ana
(audio-channel: "narrator")