Files
code_of_conquest_dnd/docs/superpowers/specs/2026-07-10-shared-theme-design.md
Phillip Tarrant 2d7ed469bf docs(specs): add M3-a shared Theme design
First of three M3 cycles (Theme -> shell 2a -> Title). Palette.gd consts
as the single source of truth; game_theme.tres for flat-bordered UI;
shaders for the three signature surfaces. EB Garamond replaces
proprietary Georgia. Proof is a human-eyeballed showcase harness.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 20:13:25 -05:00

197 lines
9.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Shared Theme — design
**Milestone:** M3 (Visual foundation & shell), item **M3-a** — the first of three
cycles (Theme → Main Window shell 2a → Title). See `docs/roadmap.md` and
`CLAUDE.md` §16.
**§2 side:** n/a — pure presentation. Owns no game state, makes no AI/network
call. It is the visual system every later screen inherits.
**Depends on:** nothing (foundation). **Unblocks:** every M3+ screen.
---
## 1. Goal
Deliver the one shared visual system the mockups describe (`/mockups/README.md`,
"Global Design System") as reusable Godot resources, so every screen built after
this pulls the same palette, fonts, styleboxes, and signature surfaces — "the
look is one system." Nothing renders a real screen yet; this is the toolbox the
screens are built from.
Because a Theme has no behavior, its **proof is a showcase harness** a human
eyeballs in the editor — the same "live-proven / human-confirmed" bar M0M2 held.
## 2. The core architectural split
A Godot `StyleBox` **cannot run a shader** — the engine draws styleboxes itself.
So the deliverable splits in two, each half using the right tool:
| Concern | Mechanism | Covers |
|---|---|---|
| **Crisp bordered UI** | `game_theme.tres` (StyleBoxFlat + fonts) | buttons/CTA, tabs active/inactive, chips/tags, item tiles, cards, insets, dark panels; all three font families at their sizes |
| **The 3 signature surfaces** | `ShaderMaterial` on `Panel`/`ColorRect`, wrapped as drop-in mini-scenes | parchment sheet, dark diagonal-stripe bay, radial vignette overlay |
| **Code-driven color** | `palette.gd` consts | rarity bars, HP/MP/stamina gradients, disposition/turn-order colour coding |
The "procedural shaders" decision applies to the **three big backdrops only**. A
chip or button is flat-bordered and fits `StyleBoxFlat` natively; a shader on
every chip would be wasted work.
`palette.gd` is the **single source of truth** for every hex value. The
`game_theme.tres` styleboxes are authored to those same values; the shaders take
them as uniforms. **One palette, three consumers.**
## 3. Fonts (redistribution constraint)
Godot exports must **bundle** their fonts. The mock's three families become:
| Mock role | Bundled face | Licence | Notes |
|---|---|---|---|
| Georgia (prose / headings / logo) | **EB Garamond** (Regular, Italic, Bold) | OFL | Redistributable. Georgia is Microsoft-proprietary and **cannot** ship. EB Garamond is a warm old-style serif — the "aged tome" DM-prose voice. Its strong italic carries the narrative-body *italic* the mock uses. |
| Architects Daughter (hand-inked accent) | **Architects Daughter** (Regular) | OFL | Ships as-is; the mock already used the Google face. Nameplates, big stat numbers, DM-header labels. |
| Monospace (UI chrome) | **JetBrains Mono** (Regular) | OFL | Default pick (clean, wide, reads well UPPERCASE with letter-spacing); swappable for another OFL mono without design change. Labels, keybinds, numeric readouts, tags. |
## 4. File layout
```
client/
scripts/theme/
palette.gd # const Color tokens — the source of truth
theme_keys.gd # const StringNames for type-variations, so scripts never
# hardcode Theme string keys
assets/theme/
fonts/
EBGaramond-Regular.ttf EBGaramond-Italic.ttf EBGaramond-Bold.ttf
ArchitectsDaughter-Regular.ttf
JetBrainsMono-Regular.ttf
shaders/
parchment.gdshader # vertical gradient #ece2ca -> #e4d8bd
dark_bay.gdshader # 135deg repeating stripe #26221c/#221e18 + radial vignette
vignette.gdshader # standalone dim/blur-edge overlay (pause, dialogue)
game_theme.tres # the Theme resource — fonts, sizes, StyleBoxFlat variations
scenes/theme/
surfaces/
ParchmentPanel.tscn # Panel + parchment material (drop-in backdrop)
DarkBay.tscn # Panel + dark_bay material
VignetteOverlay.tscn # ColorRect + vignette material
theme_showcase.tscn # the proof: every token, stylebox, font, surface, one screen
theme_showcase.gd
tests/unit/
test_palette.gd
test_theme_resource.gd
```
Follows existing conventions: `scripts/<domain>/`, harness scenes under
`scenes/`, `tests/unit/test_*.gd`.
## 5. Contents
### 5.1 `palette.gd`
Every hex from the README's "Color tokens" section as `const Color`, grouped by
role:
- **Dark backgrounds** — near-black stage, bay stripe pair, translucent panels,
the dark-border ladder (`#6b6152``#1a1512`).
- **Parchment** — sheet gradient stops, card fills + insets, parchment borders.
- **Ink** — heading / body / muted / label / gold-accent inks.
- **Text-on-dark** — bone/cream primaries, secondaries, muted monos.
- **Accents** — blood-red set, gold/brass set, steel blue-teal set, green set.
- **Stat bars** — HP / MP / stamina gradient endpoints + track/border.
- **Rarity** — the five rarity colours as an ordered array.
Plus the small helpers the code-driven cases need:
- `rarity_color(rarity) -> Color`
- stat-bar gradient stops for HP / MP / stamina (returned as a pair, or two
named consts each).
This is the file the whole game reads from for any colour a Theme can't express.
### 5.2 `theme_keys.gd`
`const StringName`s for every type-variation name (`PrimaryCTA`, `Tab`,
`TabActive`, `Chip`, `ItemTile`, `ParchmentCard`, `DarkPanel`, …), so scripts
that set `theme_type_variation` reference a constant, never a raw string that can
typo silently.
### 5.3 `game_theme.tres`
The six reusable pieces from the README, each authored as a Godot **type
variation** (applied via `theme_type_variation` on any Control):
- **PrimaryCTA** — blood-red CTA button (gradient-look `StyleBoxFlat`, `#8f3a34`
border, cream text, brightened hover).
- **Tab** / **TabActive** — inactive (parchment `#efe6cf` + `#cbb684` border,
muted text) vs active (solid `#8f3a34`, cream text).
- **Chip** — mono-uppercase tag base; the *semantic* colour (red/gold/grey) is
set by the consuming script from `palette.gd`, since it varies per use.
- **ItemTile** — rounded square, 2px border (rarity colour applied by script),
plus a recessed dashed **empty** variant.
- **ParchmentCard** — `#f1e8d2` fill, `#bfa878` border, and a darker inset
variant.
- **DarkPanel** — translucent `rgba(18,15,11,.7)`.
Plus the three font families wired into the Theme's default / heading / mono
font roles at the README's sizes and letter-spacing.
### 5.4 The three surface scenes
Each wraps one shader as a **one-line drop-in**: later screens instance
`DarkBay.tscn` / `ParchmentPanel.tscn` / `VignetteOverlay.tscn` instead of
rebuilding a backdrop. Shader uniforms default to the `palette.gd` values (baked
into the `.tscn`/material), so the surfaces match the tokens without a runtime
dependency on the script.
- `parchment.gdshader` — vertical gradient `#ece2ca → #e4d8bd`.
- `dark_bay.gdshader` — 135° repeating stripe `#26221c` / `#221e18` (16px bands)
plus the radial vignette overlay.
- `vignette.gdshader` — standalone edge-dim overlay for the pause/dialogue dim.
## 6. The proof — `theme_showcase.tscn`
A single scrollable screen rendering, for a human to eyeball in the editor:
- the full palette as labelled swatches,
- every stylebox type-variation (CTA, both tabs, chips in each semantic colour,
filled + empty item tiles, parchment card + inset, dark panel),
- all three fonts at their real sizes (heading, prose body italic, hand-inked
accent, mono chrome),
- the rarity ladder,
- a sample HP / MP / stamina bar,
- the three shader surfaces side by side.
Pure presentation — no AI, no network, no game state. This is what confirms "the
look is one system" before any real screen is built.
## 7. Testing
- **`test_palette.gd`** — asserts the documented tokens exist and are `Color`;
`rarity_color()` maps each rarity to the right colour and handles out-of-range
safely; the stat-bar stop helpers return the documented values.
- **`test_theme_resource.gd`** — `game_theme.tres` loads without error, and every
named type-variation in `theme_keys.gd` resolves on the Theme (no dangling
key).
**Visual correctness is not GUT-testable** — it is an explicit **human-confirm**
step against `theme_showcase.tscn`, exactly as M2's live model proofs were
human-confirmed. The spec is not "done" until a human has opened the showcase and
signed off that it reads as the mockups' system.
## 8. Explicitly out of scope (this cycle)
- Any real screen (the Main Window shell 2a and Title are the next two M3
cycles).
- Real art of any kind — portraits, key art, iso/battlefield renders, item/ability
icons remain the mocks' dashed **art slots**, filled later.
- Responsive/stretch tuning beyond the project's existing 1920×1080 canvas
assumption (`canvas_items` stretch is a shell-cycle concern, not a Theme one).
- Baked-PNG texture variants — the three surfaces are shader-only this cycle.
- Animation (ember particles, pulse/`cturn` keyframes) — those belong to the
screens that use them, not the Theme.
## 9. Reconciliations touched
None. The two flagged reconciliations (damage determinism → M5, class names →
M4) do not touch presentation. This cycle introduces no charter conflict.