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>
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:
- Clade — broad mammalian family (Canidae, Felidae, etc.)
- Species — specific species within the clade
- Calling — character class
- History — background
- Abilities — ability scores via standard array or 4d6-drop-lowest, drag-and-drop assignment with swap
- Skills — class skill choices on top of background-locked skills
- 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.htmland it runs. - Component files are
.jsxloaded via<script type="text/babel" src="...">. Each file ends withwindow.ComponentName = ComponentName;to share globals across script tags (a constraint of in-browser Babel — see "Integration notes" below). - Styling: a single
<style>block inindex.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; wiresuseTweaks, loads JSON data, renders<App />+<TweaksWiring />.src/app.jsx— wizard shell. Owns the masterstateobject, 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 sharedstateobject 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— aTraitNamekeyed off theLANGUAGEStable.SkillChip— aTraitNamekeyed offSKILL_LABEL+SKILL_DESC, with the governing ability shown as a tag.BonusPill— small+N/−Nbadge 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 theplainReadingstweak 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.jsondata/species.jsondata/classes.jsondata/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).
- Only level-1 features are shown on Calling cards (filtered via
- 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 = Xwithexport { X };(orexport default X). - Replace cross-file global references (e.g.
App,TraitName,SKILL_LABEL) withimport { ... } from "./...". - Move React/ReactDOM/Babel
<script>tags out ofindex.htmland 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.jsxandsrc/tweaks.jsx. - Remove the
<TweaksWiring />line fromsrc/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_featureplaceholder 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_kitarrays exist inclasses.jsonbut 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.