ThemeManager
The ThemeManager owns the visual theme of your UI: background colors, text colors, accent color, display and body fonts. Themes are exposed as CSS custom properties on <body>, so a theme swap repaints the entire UI without re-rendering DOM.
Open it from Inspector → Systems → ThemeManager → Open.
Schema
Section titled “Schema”Each theme has:
- An id (
kebab-case):default,light,high-contrast,arcade-neon. - A name (human-readable, shown in any settings UI): “Default”, “Light”, “High Contrast”.
- A palette of 5 colors as
#rrggbbstrings. - A fonts pair: display and body family stacks.
The descriptor also stores a defaultThemeId which is the theme the game boots into.
Palette
Section titled “Palette”The five palette slots cover the typical UI shapes:
| Slot | Default | Typical use |
|---|---|---|
bgPrimary | #0f0f12 | Page background |
bgSecondary | #191920 | Cards, panels, raised surfaces |
textPrimary | #e9e9ee | Body text |
textSecondary | #a4a4ad | Muted text, captions |
accent | #d77757 | Buttons, links, highlights, focus rings |
The bundles map these to CSS variables (--color-bg-primary, etc.) on <body>. Your CSS reads them by variable name, so any element styled via the system repaints when the theme swaps.
Two font stack strings:
display: used for headings, titles, menu items. Typically a serif or display face.body: used for body text and UI labels. Typically a system sans-serif.
The defaults bundle Georgia for display and the platform’s system sans for body. Both are zero-cost (already on every machine) and look fine.
For custom fonts, link them in your index.html (<link rel="preload" as="font"> then @font-face in CSS) and reference the family name in the font stack. Themes don’t ship the font files themselves; they just point at them.
Runtime theme swap
Section titled “Runtime theme swap”In code:
themeManager.setTheme('arcade-neon');The manager rewrites the CSS variables and emits a theme_changed event on the EventBus. Anything that wants to know about the swap (a debug overlay, a screenshot manager) subscribes.
In devtools, the bundles expose window.__forge.theme.setTheme('your-id') for live testing. The Web minimal bundle wires this explicitly because flipping themes is the fastest visual smoke test.
Common patterns
Section titled “Common patterns”High-contrast mode: ship a high-contrast theme with white text on near-black and a saturated accent. Hook it from the SettingsManager’s accessibility category.
Per-area mood: swap themes when the player enters a different zone. A forest area might shift accent to green; a final boss arena might shift bg to a desaturated red.
Light mode for daytime tab: detect prefers-color-scheme on boot and pick light or default accordingly.
Editing the config by hand
Section titled “Editing the config by hand”[systems.ThemeManager.config]defaultThemeId = "default"
[[systems.ThemeManager.config.themes]] id = "default" name = "Default" fonts = { display = "Georgia, ui-serif, serif", body = "-apple-system, ui-sans-serif, system-ui, sans-serif" } palette = { bgPrimary = "#0f0f12", bgSecondary = "#191920", textPrimary = "#e9e9ee", textSecondary = "#a4a4ad", accent = "#d77757" }
[[systems.ThemeManager.config.themes]] id = "high-contrast" name = "High Contrast" fonts = { display = "Georgia, ui-serif, serif", body = "-apple-system, ui-sans-serif, system-ui, sans-serif" } palette = { bgPrimary = "#000000", bgSecondary = "#1a1a1a", textPrimary = "#ffffff", textSecondary = "#cccccc", accent = "#ffe600" }Validation: theme ids are unique and match ^[a-z][a-z0-9-]*$. Palette values must be #rrggbb. The defaultThemeId must be a registered theme.