FolioTier 2
text-tags
Inline formatting + animation grammar that lives inside `say` and `narrate` string content.
Reference page. Text tags are inline grammar inside say / narrate
string content, not a standalone verb. The parser shipped in Phase 3
Step 4 — see
apps/web/src/lib/runtime/effects/text/parser.ts.
The per-glyph renderer ({vibrate} / {wave}) shipped in Step 5; the
static-tag styling ({b} / {i} / {color} / {size}) shipped in
Step 6; and the special timing tags ({glitch} / {slow}) shipped in
Step 7 — they drive a per-character reveal schedule at
effects/text/reveal-schedule.ts.
This page documents the closed grammar so the parser, the importer's
deterministic pass, and the AI importer pass all share one target.
How tags work
A tag is delimited by { and }. Static tags wrap a span of text
with a matching {/tag}; animated and special tags have the same
shape. The grammar is closed — only the tags listed here are
recognized. Unknown tags are a parse error at import time and an
inline editor warning in the studio, both with the documented
"unknown text tag" issue code; the migration report surfaces them
under the same code.
say "Emma": "I {b}can't{/b} believe you {i}did{/i} that."
Nesting is allowed; mismatched closers reject at parse time. Tags
that take a value use = ({color=#ff0066}…{/color}); tags without
a value use the bare form ({b}…{/b}).
Static formatting
These render synchronously — the text appears with the formatting
applied, no per-glyph animation. All four are present in the corpus
(the-question + Acting Lessons combined: {i} × 317, {b} × 102,
{color=} × 59, {size=} × 12).
| Tag | Value | Effect |
|---|---|---|
| {b}…{/b} | — | Bold weight. Maps to CSS font-weight: 700. |
| {i}…{/i} | — | Italic style. Maps to CSS font-style: italic. |
| {color=…}…{/color} | #rrggbb / #rgb hex or theme token (e.g. dialogue.accent) | Override the text color for the wrapped span. |
| {size=…}…{/size} | Integer point size (8–96) or relative (+2, -4) | Override the font size. Relative values stack with the surrounding context size. |
Out-of-range values reject at parse time per G3.1 (same shared validator as the effects catalog).
I {b}can't{/b} believe you {i}did{/i} that.{b} and {i} compose freely with surrounding plain text — same line, two static spans.
The {color=#ff5566}red light{/color} blinks once, then the {size=+6}whole{/size} room goes still.{color} accepts hex literals or theme-token paths; {size} accepts absolute (8–96) or relative (±24) values.
Animated formatting
These render per-glyph — each character in the wrapped span is its
own animated <span>. Per Phase 3 Step 5: soft cap ~80 glyphs per
animated tag. Longer spans fall back to whole-block animation to
avoid the per-glyph cost.
| Tag | Value | Effect |
|---|---|---|
| {vibrate}…{/vibrate} | — | Each glyph jitters in place at low amplitude. Use for nervousness, fear, instability. |
| {wave}…{/wave} | — | Each glyph sine-waves on the y-axis with a per-glyph phase offset. Use for sing-song, dreaminess, levity. |
Both are Folio-native — not present in the Ren'Py corpus. They land with the per-glyph renderer in Phase 3 Step 5.
She watched {vibrate}everything{/vibrate} unravel.{vibrate} jitters each glyph independently — a low-amplitude tremor that reads as nervousness.
They could hear the {wave}broadcast{/wave} from a mile out.{wave} sine-waves each glyph on the y-axis with a left-to-right phase offset; the traveling motion is what separates it from {vibrate}.
Special timing
These affect how the line types out, not how individual glyphs look. Same closed-grammar shape; both Folio-native.
| Tag | Value | Effect |
|---|---|---|
| {glitch}…{/glitch} | — | Wrapped span types with intermittent garbled-character flashes that resolve to the real text. Use for corruption, malfunction, possession. |
| {slow}…{/slow} | — | Wrapped span types at half the surrounding speed (or the project's text.slow-cps token, when set). Use for gravity, weariness, deliberation. |
{slow}Rain crawls{/slow} down the station glass.{slow} halves the typewriter speed inside the wrapped span. Click replay to watch the opening drag before the rest of the line snaps back to cadence.
The red {glitch}ON AIR{/glitch} light warms the room.{glitch} garbles each non-whitespace char for ~150ms before settling on the real character. Whitespace is exempt so word-wrap boundaries stay clean.
What's deliberately out of v1
The corpus surfaces additional Ren'Py text tags that v1 does not adopt. Each is a deliberate scope call, not an oversight:
| Tag | Corpus presence | Why out of v1 |
|---|---|---|
| {w=…} / {w} | Heavy in Tier 3+ (297 + 57 in Eternum + What a Legend) | Pacing knob authors typically over-use. Phase 3's {slow} covers the narrative-pacing case; explicit per-line waits flow through the manual lane until a v1.x decision lands. |
| {nw} | Heavy in Tier 3+ (252 hits) | "No wait — auto-advance" is a runtime mode, not a per-line tag in Folio. Authors set auto-advance on the player; the importer notes {nw} lines for re-checking after import. |
| {cps=…} | Heavy in Tier 3+ (249 hits) | Project-wide typing speed is a theme token (text.cps), not an inline tag. The importer lifts {cps=N} blocks to per-line {slow} when the value is half the project default; otherwise the manual lane. |
| {font=…} | 145 hits in What a Legend | Font swaps are a theme concern — namebox / dialogue / choice already get distinct font tokens. Inline font swap is a power tool that opens the typography surface wider than v1 wants. |
| {image=…} | 321 hits in What a Legend | Inline emoji / icon insertion is a sandbox-VN affordance. Phase 6 Track A's Locations subsystem owns the hotspot/affordance equivalent. |
| {a=…} / {outlinecolor=…} / {u} | Sparse | Ren'Py UI screen affordances or styling minutiae that don't belong in narrative content. Migration report surfaces them; manual lane absorbs. |
The closed-grammar discipline is the point. Adding a tag means deciding what authoring problem it solves and what the closed-form spelling is — not "well, Ren'Py has it."
Notes
A future tag lands via the same catalog-growth process documented in
docs/folio-effects-catalog.md:
matrix evidence → proposal → wiki page → Phase 3 module → importer
pattern recognizer.
The importer's deterministic pass lowers the supported tags above verbatim. Anything else — including the Tier 3+ tags listed in the "deliberately out" table — flows through the migration report's manual lane with a stable per-tag issue code.