Skip to content

Prose & Text

Prose styling, interpolation and sanitization rules, and click-to-continue/text transitions.

Prose Styling

Ana provides several macros for styling prose content beyond simple $variable interpolation.

Paragraphs and line flow

Ana follows a what-you-type-is-what-you-get model for line breaks:

  • Each source line renders on its own line. A single newline between two prose lines becomes a visible line break. Write two lines one under the other and that is exactly how they appear.
  • A blank line starts a new paragraph. A blank line closes the current paragraph and opens a fresh one, with paragraph spacing between them. Consecutive blank lines collapse to a single break, so you never get empty paragraphs.
  • Block-level content always stands alone. Headings on their own line, (table:), (panel:), images, --- rules, (div:)/(text-align:) containers, and form/dialog widgets flush whatever paragraph is open and render as their own block, then prose resumes afterward.
  • (each:), (if:), (elseif:), (else:), and @hook injections are transparent to line flow. They never add line breaks of their own. The structural newlines around a macro (the line it sits on, the newlines inside its [...] block) produce nothing in the output; only the prose a macro actually emits participates in line flow, exactly as if you had written that content inline at the call site. This is why you can spread an (if:)/(elseif:)/(else:) chain, or a loop body, across as many lines as you like for readability without it ever inserting stray blank lines.

Because only emitted prose drives line breaks, a one-line loop body like

ana
(each: _t in (theme-list:))[- _t]

runs each iteration with no break between them (there is no newline inside the body to produce one). Put a newline inside the body to place each iteration on its own line:

ana
(each: _t in (theme-list:))[- _t
]

Joining wrapped lines with @collapse

Occasionally you want the opposite: a single paragraph that you have hard-wrapped across several source lines for editor comfort, rendered as one continuous block of prose. Add the @collapse modifier to the passage header. That passage joins consecutive lines with a single space, the way Markdown does, while blank lines still separate paragraphs and block content still stands alone.

ana
:: Lore @collapse

This sentence is wrapped across
several source lines, but it renders
as one flowing paragraph.

Everywhere else, the line-break default applies. In practice you rarely need @collapse: writing one sentence per line reads cleanly in the source and matches the output.

Inline vs block: what stands on its own

Under the line-break model, every prose line and every standalone macro line already lands on its own line. The inline-versus-block distinction now controls something narrower: whether a macro's output sits inside the current paragraph (inline) or breaks out into its own block box with paragraph spacing around it (block).

Renders asMacrosFlow behavior
inline <span>(text-style:) (text-color:) (text-opacity:) (text-size:) (font:) (text-rotate:) (text-rotate-x:) (text-rotate-y:) (span:), and (h1:)(h6:) used mid-lineStays in the current paragraph. On its own line it takes a line break like any prose line; written next to other text on one line it flows inline with that text.
block element (<div>, <h1><h6>, <table>, <hr>, …)(text-align:) (div:) (h1:)(h6:) on their own line, (table:) (panel:), images, ---, form/dialog widgetsFlushes the open paragraph and renders as its own block, with paragraph spacing.

Headings appear in both rows on purpose. A heading on its own line is a real block <h1><h6>. The same heading written inside a prose line renders as an inline sized span, so Sizes run (h1: "h1") down to (h6: "h6"). flows on one line at varied sizes instead of breaking apart.

A line that begins with an inline macro and continues with text, like the reactive-value pattern below, stays on one line too. The macro output and the text that follows it are one line, not two:

ana
In your pack:
(watch-var: $inv.beer) beer
(watch-var: $inv.bread) bread

renders 1 beer and 1 bread each on their own line, with In your pack: above them.

To put paragraph spacing between a series of inline-styled demos, separate them with blank lines:

ana
(text-style: "bold")[Roses are red,]

(text-style: "italic")[Violets are blue.]

And (br:) still forces a manual line break within a line when you need one mid-sentence:

ana
Line one(br:)Line two

Markdown shortcuts (inline in prose)

These work on any prose line in a passage body:

This is **bold text** in a sentence.
This is *italic text* in a sentence.
This word is ~~crossed out~~.
This is ==highlighted==.
This is `inline code`.

---

The last example (--- on its own line) renders an <hr> separator.

Limitation: Markdown spans cannot contain inline macros. For formatted text with macros inside, use (text-style:) instead.

Inline code

`inline code` renders as <code> and is shown verbatim: $variables and _temp names inside backticks are not interpolated, so you can write example Ana syntax like `(set: $player.gold to 10)` and have it display exactly as typed.

Fenced code blocks

Wrap a run of lines in triple backticks to render a <pre><code> block, useful for showing multi-line Ana syntax or other code samples in documentation-style passages:

```
(set: $player.hp to 10)
(add: $player.hp, 5)
```

An optional language tag right after the opening backticks is added as a language-<tag> class on the <code> element (for use with syntax-highlighting CSS/JS you supply):

```ana
(goto: MainStreet)
```

Like inline code spans, fenced code blocks are rendered completely verbatim: no $variable/_temp interpolation, no inline formatting, no inline macros. The block always stands on its own (it flushes any open paragraph), the same way a heading or (table:) does.

Bullet and numbered lists

Start a line with -, *, or 1. (any number followed by a period) to build a list. A run of consecutive list-marker lines becomes one <ul> or <ol>:

- Beer
- Iron Knife
- Torch

1. Pick a tab
2. Click an item
3. Read the detail pane

Whether the list renders as bullets (<ul>) or numbers (<ol>) is decided by the first item's marker: -/* produce an unordered list, N. produces an ordered list.

List items support the same inline content as a regular prose line: bold/italic/strike/highlight/code spans, $variable interpolation, _temp names, and inline macros all work:

ana
- Current gold: $player.gold
- **Important:** talk to the bartender first
- Roll result: (dice: 1, 6)

Indent a line further than the item above it to nest a sublist inside that item:

- Town
    - Tavern
    - Market
        - Butcher
        - Baker
- Countryside

Like fenced code blocks, lists are always block-level: they flush any open paragraph and stand on their own.

Styling macros (beyond markdown)

When the markdown shortcuts above aren't enough, when you need to combine effects, animate, align, or run macros inside styled content, reach for the block-styling macros. Their key advantage over the **bold** shortcut is that the block body supports full macro execution ((if:), (each:), interpolation):

ana
(text-style: "bold")[You have (count: $inv, "beer") beers.]
(text-style: "bold", "shudder")[DANGER!]          // styles combine

What's available, at a glance:

  • (text-style:): 31 named styles (bold/italic/underline families, strike variants, super/subscript, transforms like mirror/upside-down, textured outline/shadow/emboss, motion effects like shudder/sway, blur), combinable in one call.
  • (text-align:), (h1:)(h6:), (font:): alignment, semantic headings, and font family.
  • (text-color:), (text-opacity:), (text-size:), (text-rotate:): inline CSS-style spans.
  • (transition:): a one-shot entry animation on one block of content.

The block transition is not the same as the whole-zone click-to-continue transition (set with (default-transition: text, ...), covered below). Use (transition:) when a single piece of content within a passage should draw attention: a name change, a revealed clue, a consequence appearing after a delay.

Every style name, exact signatures, and CSS hooks: see Text, Prose Styling & Transitions in the reference.

Tooltips

To attach a hover/focus tooltip, add a tip, "text" pair to a (link:) or (img:) for a plain text tip, or wrap content in (tooltip:) with a nested (tip:) block for a rich tooltip that can include images. Both forms also show on keyboard focus and are themeable via --ana-tooltip-*.

ana
(link: "the locket", tip, "A tarnished silver locket.")[(goto: LocketScene)]

(tooltip:)[
    the strange potion
    (tip:)[(img: potion_red.png) Smells of cloves and something metallic.]
]

Text & Interpolation Rules

$variable interpolation happens when prose is rendered, not when macro arguments are evaluated. Concretely:

  • In prose (and inside block bodies [...]), write $player.name directly and it prints the value. (Never wrap it in curly braces; {$player.name} prints the literal braces.)
  • Inside a quoted string argument, $var is interpolated only if that macro renders its string as prose/notification text. For example, (notify: "You are in $world.location.") interpolates because the notification is displayed. A string you store in a variable stays literal: (set: _s to "Hello $world.location") keeps the $... text. To build a string from values, concatenate with +: (set: _s to "Hello " + $world.location).

Player-typed input is inert text. Values from (input:), (prompt:), and the other form macros are rendered with the browser's text APIs, so a player typing <script> or (goto: X) into a name field shows those characters literally; they are never executed as HTML or Ana. The one place raw HTML runs is (html: "..."), which is an explicit author opt-in: never feed player input into (html:).

Click-to-Continue & Text Transitions

Set a default text transition in GameInit and the text zone animates in on every navigation:

ana
(default-transition: text, typewriter)

Three modes:

ModeEffect
typewriterReveals text character by character (~30 ms/char).
dissolveAll paragraphs fade in together.
lineOne paragraph at a time, ~400 ms apart.

While text is animating the options zone is hidden; it appears when the animation finishes. Clicking anywhere in the text zone skips the remaining animation and reveals everything immediately; that's the "click to continue" behavior. No per-passage setup is needed beyond the one GameInit line.

This is distinct from (transition:), which animates one specific wrapped block at any time (see Prose Styling).