Files
Christopher Wiebe b451f83174 Initial commit: Theriapolis baseline at port/godot branch point
Captures the pre-Godot-port state of the codebase. This is the rollback
anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md).
All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-30 20:40:51 -07:00
..

Theriapolis — Character Creation Interface

A 7-step character creation wizard prototype, designed in the style of an illuminated codex. Built as a static React-via-Babel prototype so it can be opened directly in a browser with no build step. This README is written for Claude Code (or any developer) to integrate the interface into a real game codebase.

What this is

A clickable, fully-interactive character creator covering:

  1. Clade — broad mammalian family (Canidae, Felidae, etc.)
  2. Species — specific species within the clade
  3. Calling — character class
  4. History — background
  5. Abilities — ability scores via standard array or 4d6-drop-lowest, drag-and-drop assignment with swap
  6. Skills — class skill choices on top of background-locked skills
  7. Sign — name + final review

Plus a floating sidebar that summarizes the character as it's built, and a Tweaks panel for runtime theme/density/font experimentation.

Stack

  • React 18.3 loaded from a CDN as <script src> UMD bundles.
  • Babel Standalone 7.29 transpiles JSX in the browser.
  • No bundler, no package manager, no build step. Open index.html and it runs.
  • Component files are .jsx loaded via <script type="text/babel" src="...">. Each file ends with window.ComponentName = ComponentName; to share globals across script tags (a constraint of in-browser Babel — see "Integration notes" below).
  • Styling: a single <style> block in index.html. CSS variables drive theming, density, and font pairing.
  • Fonts: Cormorant Garamond, Crimson Pro, Spectral, EB Garamond, Cinzel, Uncial Antiqua, JetBrains Mono — all loaded from Google Fonts in index.html.

File map

Entry + shell

  • index.html — host page. Contains the full stylesheet, font imports, script tags, and the React mount point (#app). The design system (colors, typography, stepper, cards, chips, popovers, drag affordances) lives entirely in this file's <style> block.
  • src/main.jsx — React mount; wires useTweaks, loads JSON data, renders <App /> + <TweaksWiring />.
  • src/app.jsx — wizard shell. Owns the master state object, the step list, per-step validation, forward-navigation gating, and the right-hand <Aside /> summary.

Step components + helpers

  • src/steps.jsx — all 7 step components: StepClade, StepSpecies, StepClass, StepBackground, StepStats, StepSkills, StepReview. Read and write through the shared state object passed via props.
  • src/data.jsx — runtime constants and helpers: ABILITIES, ABILITY_LABELS, SKILL_LABEL, SKILL_DESC, SKILL_ABILITY, SIZE_LABEL, LANGUAGES, ITEM_NAME, CLASS_CLADE_REC, STANDARD_ARRAY, abilityMod(), signed(), loadData().
  • src/trait-hint.jsx — hover-popover primitives:
    • TraitName — the base hover chip used everywhere a trait/feature name is shown.
    • LanguageChip — a TraitName keyed off the LANGUAGES table.
    • SkillChip — a TraitName keyed off SKILL_LABEL + SKILL_DESC, with the governing ability shown as a tag.
    • BonusPill — small +N/N badge whose hover popover lists the bonus's sources (clade, species).
  • src/portrait.jsx — placeholder portrait component (silhouette / heraldry / placeholder modes).
  • src/trait-readings.jsx — optional flavor text. Provides the "plain-language reading" that appears at the bottom of trait hover popovers when the plainReadings tweak is on.

Tweaks panel (optional)

  • tweaks-panel.jsx — starter component providing <TweaksPanel>, useTweaks(), and the <TweakRadio> / <TweakSelect> / <TweakToggle> controls.
  • src/tweaks.jsx — content of the panel: maps tweak values onto CSS custom properties on <html> (theme, density, font pair).

These two files are not load-bearing for the wizard itself — strip them if you don't want a runtime theme/density toggle.

Content data

  • data/clades.json
  • data/species.json
  • data/classes.json
  • data/backgrounds.json

These are the source of truth for everything the UI displays. The fields the UI reads from each:

  • clade: id, name, flavor, ability_mods{STR,DEX,...}, traits[{id,name,description}], detriments[{id,name,description}], languages[langId], aura{...}
  • species: id, clade_id, name, size, base_speed_ft, ability_mods, traits, detriments
  • class: id, name, hit_die, primary_ability[], saves[], armor_proficiencies, weapon_proficiencies, tool_proficiencies, skills_choose, skill_options[], starting_kit, level_table[{level,prof,features[]}], feature_definitions{key:{name,kind,description,uses_per_long_rest?}}
    • Only level-1 features are shown on Calling cards (filtered via level_table[level=1].features).
  • background: id, name, flavor, skill_proficiencies[], tool_proficiencies[], feature_name, feature_description

Running it as-is

# Any static server. Examples:
python3 -m http.server 8000
# or
npx serve .

Open http://localhost:8000/.

You can't open index.html via file:// because it fetch()es JSON files — browsers block that on the file protocol.

Integration notes for Claude Code

When porting this into a real codebase (Next.js, Vite, CRA, or any module-bundler setup), do the following:

1. Convert .jsx files to ES modules

Each component file currently ends with bindings like:

window.App = App;
window.TraitName = TraitName;

This is a workaround for in-browser Babel: each <script type="text/babel"> runs in its own scope, so globals are how files share components. In a real build:

  • Replace window.X = X with export { X }; (or export default X).
  • Replace cross-file global references (e.g. App, TraitName, SKILL_LABEL) with import { ... } from "./...".
  • Move React/ReactDOM/Babel <script> tags out of index.html and into your dependency graph (npm install react react-dom).

2. Move the stylesheet out of index.html

The <style> block is monolithic but logically grouped. Extract to one or more .css files (or CSS modules / Tailwind / styled-components, depending on your project). The CSS-variable theme system (--bg, --ink, --gild, --seal, --rule, --serif-display, --serif-body, etc., plus [data-theme] and [data-density] overrides) should be preserved as-is — the Tweaks panel toggles them via inline style.

3. Replace loadData() with your data layer

loadData() in src/data.jsx does:

const [clades, species, classes, backgrounds] = await Promise.all([
  fetch("data/clades.json").then(r => r.json()),
  fetch("data/species.json").then(r => r.json()),
  // ...
]);

Replace with however your game serves character-creation content (REST, GraphQL, static import, redux store, etc.). The shape the wizard expects is documented above; if your game's canonical schema differs, write an adapter that maps your fields → the prototype's expected fields, or refactor the components to read your fields directly.

4. Wire the "Confirm & Begin" handoff

The final step (StepReview in src/steps.jsx) currently does:

onClick={() => alert(`${state.name} steps into Theriapolis. (Confirmed.)`)}

Replace this with whatever your game does to receive a finished character. The full character object lives in state (defined in src/app.jsx):

{
  cladeId, speciesId, classId, backgroundId,
  statMethod,            // "array" | "roll"
  statPool,              // [{value: 15}, ...] — unassigned values
  statAssign,            // { STR: 15, DEX: 14, ... } — assigned values
  statHistory,           // [{vals: [15,14,...], ts}, ...] — roll history
  chosenSkills,          // ["athletics", "stealth"]
  name,
  portraitStyle,         // ui-only: "silhouette" | "heraldry" | "placeholder"
}

Compute final ability scores by summing statAssign[ab] + clade.ability_mods[ab] + species.ability_mods[ab] (see Aside in src/app.jsx for the canonical computation). Skill proficiencies are [...background.skill_proficiencies, ...chosenSkills].

5. Decide what to do with the Tweaks panel

The Tweaks panel is a development-time affordance — it lets you swap themes, densities, and font pairings live and persist them. For a shipped game you almost certainly want to:

  • Pick one theme/density/font combination and bake those values into your stylesheet.
  • Delete tweaks-panel.jsx and src/tweaks.jsx.
  • Remove the <TweaksWiring /> line from src/main.jsx.

If you want to keep theme switching as a player-facing feature, lift the values into your settings system instead of using the host-rewrite mechanism the panel currently uses (which is editor-specific).

6. Drag-and-drop on the Abilities step

StepStats uses native HTML5 drag-and-drop with dataTransfer. The payload is a JSON string of one of:

  • {from: "pool", value: 15, idx: 2} — dragging an unassigned value from the pool
  • {from: "slot", value: 15, ability: "STR"} — dragging an already-assigned value out of an ability slot

Drop targets handle pool→slot, slot→slot (swap), and slot→pool. If your target framework wraps drag-and-drop (react-dnd, dnd-kit), refactor accordingly — the logic in handleDrop / dropToPool is the part to preserve.

7. Hover popovers and viewport clamping

TraitName in src/trait-hint.jsx measures its trigger with getBoundingClientRect() and clamps the popover to documentElement.clientWidth/Height (not window.innerWidth/Height — the former excludes scrollbar gutters). It also flips above/below based on available space. This is a deliberate detail; if you replace it with a generic tooltip library, verify the behavior near viewport edges.

8. Step navigation gating

In src/app.jsx, the stepper locks any step whose path is blocked by an unmet earlier step. The user can always click backward to revisit a completed folio. The "Next" button uses the same validate(step) predicate. If your game wants to allow free navigation (e.g. a "skip ahead and fill in later" flow), relax the locked computation in the STEPS.map(...) block.

Known caveats

  • No save/load: the wizard is in-memory only. Refresh = start over. Add persistence at whatever layer your game uses.
  • No subclass selection: the data files include subclass_select / subclass_feature placeholder features, but the UI doesn't surface a subclass picker. The placeholders are filtered out of all chip displays. Add a subclass step (or an inline subclass picker on the Calling card) when ready.
  • Stats are level 1 only: the calling cards intentionally show only level-1 features; ASI, level scaling, and HP-on-level-up are out of scope.
  • Tool/weapon proficiencies are not surfaced: they're in the data but the UI only displays skills. Add them if your game needs them at character creation.
  • Equipment is not surfaced: starting_kit arrays exist in classes.json but aren't shown. Add an equipment step or hand off the kit at creation.

License / attribution

Whatever your project's license is. The font choices and the codex aesthetic are art direction, not licensed assets.