175 lines
11 KiB
Markdown
175 lines
11 KiB
Markdown
|
|
# 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.
|