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

9.1 KiB
Raw Permalink Blame History

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 StringNames 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.gdgame_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.