Skip to content

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.

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 #rrggbb strings.
  • A fonts pair: display and body family stacks.

The descriptor also stores a defaultThemeId which is the theme the game boots into.

The five palette slots cover the typical UI shapes:

SlotDefaultTypical use
bgPrimary#0f0f12Page background
bgSecondary#191920Cards, panels, raised surfaces
textPrimary#e9e9eeBody text
textSecondary#a4a4adMuted text, captions
accent#d77757Buttons, 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.

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.

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.

[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.