FolioTier 1
theme-tokens
Every value the renderer reads when laying out a project — dialogue box, namebox, choice buttons, colors, type, playback, textbox, reference resolution.
Reference page — theme tokens are a typed snapshot the renderer reads, not a verb the parser emits. The tokens are real today; the Phase 2 JSON5 authoring surface is in flight.
What a theme is
A project carries one presentation snapshot — a typed value with two halves: tokens (numeric / color / font configuration) and assets (references to images and fonts in the project's asset library). The renderer reads the snapshot on every frame; the authoring surface in Phase 2 is a JSON5 editor over the same shape.
The canonical shape is defined in
canonical-project.ts
(PresentationTokens + PresentationAssets); the two seed values
live in
presentation-presets.ts
(renpyDefaultPreset(), modernPreset()). Importing a Ren'Py
project produces a renpy-default snapshot derived from the project's
gui.rpy; new Vizno projects start on modern.
Coordinate system
All x/y/width/height values are in reference-resolution pixels. Reference resolution defaults to 1280 × 720 (matching Ren'Py's default base); the renderer scales the whole stage to fit the player surface while preserving aspect ratio.
xalign / yalign are 0-to-1 anchor scalars (0 = left/top, 0.5 =
center, 1 = right/bottom). xpos / ypos are absolute pixel
offsets at reference resolution. Mixed positioning (anchor + offset)
matches Ren'Py's xalign 0 xpos 268 semantics — anchor first, then
offset from that anchor.
dialogue
Layout of the dialogue text inside the textbox.
| Token | Type | Default (Ren'Py) | Default (modern) | Means |
|---|---|---|---|---|
| textXalign | number | 0 | 0 | Horizontal anchor of the text inside the textbox. 0 = left, 0.5 = center. |
| width | number | 744 | 1024 | Wrap width in pixels. Long lines break at this width. |
| xpos | number | 268 | 128 | Left margin from the stage's left edge. |
| ypos | number | 50 | 28 | Top margin from the textbox's top edge (not the stage's). |
namebox
The character-name plate that sits at the top-left of the textbox.
| Token | Type | Default (Ren'Py) | Default (modern) | Means |
|---|---|---|---|---|
| borders | {top,right,bottom,left} | 5/5/5/5 | 8/16/16/8 | Inner padding inside the namebox background. |
| height | number | null | null | null | Fixed height in pixels; null lets the text drive it. |
| tile | boolean | false | false | When true, tile the namebox background image instead of stretching. |
| width | number | null | null | null | Fixed width in pixels; null lets the text drive it. |
| xalign | number | 0 | 0 | Horizontal anchor of the namebox inside its parent. |
| xpos | number | 240 | 0 | Horizontal offset from the anchor. |
| ypos | number | 0 | -36 | Vertical offset from the textbox's top edge. Negative values float the namebox above the textbox. |
choice
The button column for choice blocks.
| Token | Type | Default (Ren'Py) | Default (modern) | Means |
|---|---|---|---|---|
| button.borders | {top,right,bottom,left} | 5/100/100/5 | 12/24/24/12 | Inner padding inside each button. Ren'Py's wide left/right reflects sidebar art frames. |
| button.height | number | null | null | null | Fixed button height; null lets the label drive it. |
| button.hoverColor | hex string | #ffffff | #ffffff | Label color on hover. |
| button.idleColor | hex string | #cccccc | #ffffff | Label color at rest. |
| button.textXalign | number | 0.5 | 0.5 | Horizontal alignment of the label inside the button. |
| button.tile | boolean | false | false | Tile the button background image instead of stretching. |
| button.width | number | 790 | 720 | Button width in pixels. |
| spacing | number | 22 | 14 | Vertical gap between consecutive buttons. |
| xalign | number | 0.5 | 0.5 | Horizontal anchor of the column. |
| yanchor | number | 0.5 | 0.5 | Vertical anchor of the column. |
| ypos | number | 270 | 520 | Vertical position of the column at the anchor. |
colors
Project-wide color palette. Theme tokens that take a color (e.g.
button.idleColor) resolve hex literals directly; in Phase 2 they
will also accept token names from this group (colors.accent,
colors.text).
| Token | Default (Ren'Py) | Default (modern) | Means |
|---|---|---|---|
| accent | #cc6600 | #b5542f | Brand accent — selected choice, active link, system highlights. |
| hover | #e0a366 | #e0a366 | Generic hover state where no specific color is set. |
| hoverMuted | #7a3d00 | #7a3d00 | Hover state for muted / secondary elements. |
| idle | #555555 | #9ca3af | Default text color for non-dialogue UI surfaces (menus, history). |
| idleSmall | #aaaaaa | #cbd5f5 | Smaller / secondary text color. |
| insensitive | #5555557f | #94a3b87f | Disabled state color. Includes alpha. |
| interfaceText | #ffffff | #ffffff | Default text color inside system UI (preferences, save / load). |
| muted | #512800 | #3a1e10 | Quiet background fill behind muted text. |
| selected | #ffffff | #ffffff | Selected-state text color (e.g. current language). |
| text | #ffffff | #ffffff | Default dialogue text color. |
type
Font family + size for the three text surfaces.
| Token | Type | Default (Ren'Py) | Default (modern) | Means |
|---|---|---|---|---|
| textFont | string | DejaVuSans.ttf | Inter, system-ui, sans-serif | Dialogue font. Filename when bundled (e.g. DejaVuSans.ttf) or CSS family stack when web-only. |
| textSize | number | 22 | 22 | Dialogue font size in points at reference resolution. |
| nameTextFont | string | DejaVuSans.ttf | Inter, system-ui, sans-serif | Namebox font. |
| nameTextSize | number | 30 | 16 | Namebox font size. |
| interfaceTextFont | string | DejaVuSans.ttf | Inter, system-ui, sans-serif | System-UI font (preferences, save / load). |
| interfaceTextSize | number | 24 | 18 | System-UI font size. |
playback
Runtime behaviors that affect how scenes play, not how they look.
| Token | Type | Default | Means |
|---|---|---|---|
| clickAnywhereToAdvance | boolean | true | When true, a click anywhere in the player advances the current line. When false, only the advance button does. |
| textCps | number | 30 | Characters per second for the dialogue typing effect. 0 disables the effect (text appears whole). The {slow} text tag halves this; the {cps=…} tag is deferred (see text-tags). |
| windowShowTime | number | 0.2 | Seconds the textbox takes to fade in when dialogue resumes from a window hide state. |
| windowHideTime | number | 0.2 | Seconds the textbox takes to fade out when entering a window hide state. |
transition dissolve and transition fade now lower through the
effect catalog (@dissolve / @fade); their default duration is
0.5s, fixed in transition-lowering.ts.
Use @dissolve duration=… or @fade duration=… to override
per-call.
textbox
The dialogue textbox container itself (background + position).
| Token | Type | Default (Ren'Py) | Default (modern) | Means |
|---|---|---|---|---|
| height | number | 185 | 232 | Textbox height in pixels at reference resolution. |
| yalign | number | 1 | 1 | Vertical anchor of the textbox in the stage. 1 = pinned to the bottom edge. |
The textbox's background image is an asset reference (see § assets).
reference
Reference resolution for the entire stage. All positional tokens are measured against this; the renderer scales the stage to fit the player surface while preserving aspect ratio.
| Token | Type | Default | Means |
|---|---|---|---|
| width | number | 1280 | Reference stage width in pixels. |
| height | number | 720 | Reference stage height in pixels. |
Vizno does not currently support per-project reference resolution
overrides at runtime — the snapshot value is informational and used
by the importer to translate Ren'Py's gui.init width / height.
assets
The presentation.assets map references asset IDs in the
project's asset library (the same library show / scene /
audio verbs reference). Each field is optional; missing fields fall
back to the runtime's default rendering.
| Field | Means |
|---|---|
| textboxBackground | PNG used as the dialogue textbox background. Imported from gui/textbox.png. |
| nameboxBackground | PNG used behind the namebox. Imported from gui/namebox.png. |
| choiceButtonIdleBackground | PNG behind each choice button at rest. Imported from gui/button/choice_idle_background.png. |
| choiceButtonHoverBackground | PNG behind each choice button on hover. Imported from gui/button/choice_hover_background.png. |
| textFont | Font asset (TTF / OTF / WOFF / WOFF2) for dialogue. |
| nameFont | Font asset for the namebox. |
| interfaceFont | Font asset for system UI. |
Importer linking: when a project ships gui/textbox.png, the
importer's deterministic pass writes the asset ID to
presentation.assets.textboxBackground. Same for the other
well-known Ren'Py paths.
Notes
The snapshot model means there is no layoutPreset runtime branch
— old code that did if (layoutPreset === "modern") {...} was
removed in Phase 2 Step 1,
and the vestigial layoutPreset field was stripped from snapshots
entirely in Step 6 (one-shot SQL migration plus type / validator
edits). The two preset functions in presentation-presets.ts remain
as seed-value helpers — createBlankCanonicalProject and the
importer hydrate new projects from them, and the studio Theme tab's
"apply preset" buttons drop a fresh preset into the editor buffer —
but the renderer reads tokens, not preset names.
Per docs/folio-roadmap.md § Out of scope for v1:
the visual theme editor (knobs and sliders for these tokens) is
not v1 — JSON5 editing is the only authoring surface in v1.x.