# 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

```bash
# 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:
```js
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:
```js
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:
```js
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`):

```js
{
  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.
