diff --git a/docs/superpowers/specs/2026-07-10-shared-theme-design.md b/docs/superpowers/specs/2026-07-10-shared-theme-design.md new file mode 100644 index 0000000..cf39a4c --- /dev/null +++ b/docs/superpowers/specs/2026-07-10-shared-theme-design.md @@ -0,0 +1,196 @@ +# 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 M0–M2 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//`, 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.