Phase 6: character creation & level-up
- Ability generation: standard array, point buy (27-pt, validated), 4d6kh3 roll; AbilityGenModal with assign-from-pool + steppers (pure abilityGen lib + tests) - Level-up flow: increment level, HP gain (average or roll hit die + CON) - Print / save-to-PDF: print button + @media print hides app chrome and roll tray - Plan: added Phase 11 (data-driven Assistant) to the roadmap Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,6 +17,8 @@ import { AttacksSection } from './sheet/AttacksSection';
|
||||
import { ResourcesSection } from './sheet/ResourcesSection';
|
||||
import { SpellcastingSection } from './sheet/SpellcastingSection';
|
||||
import { DefensesSection } from './sheet/DefensesSection';
|
||||
import { AbilityGenModal } from './sheet/AbilityGenModal';
|
||||
import { LevelUpModal } from './sheet/LevelUpModal';
|
||||
|
||||
const ABILITIES: AbilityKey[] = ['str', 'dex', 'con', 'int', 'wis', 'cha'];
|
||||
|
||||
@@ -33,6 +35,8 @@ const RANK_LABEL: Record<ProficiencyRank, string> = {
|
||||
export function CharacterSheet({ character }: { character: Character }) {
|
||||
// Local editable copy; write-through to the DB on change (debounced + flush on unmount).
|
||||
const [c, setC] = useState<Character>(character);
|
||||
const [levelUp, setLevelUp] = useState(false);
|
||||
const [genScores, setGenScores] = useState(false);
|
||||
const save = useDebouncedCallback((next: Character) => {
|
||||
// Persist everything except identity/timestamps (update() stamps updatedAt).
|
||||
const { id, campaignId: _campaignId, createdAt: _createdAt, updatedAt: _updatedAt, ...rest } = next;
|
||||
@@ -84,7 +88,11 @@ export function CharacterSheet({ character }: { character: Character }) {
|
||||
<span className="rounded-full bg-elevated px-2 py-0.5 text-xs uppercase tracking-wide text-muted">
|
||||
{c.kind === 'pc' ? 'PC' : 'NPC'} · {sys.label}
|
||||
</span>
|
||||
<div className="ml-auto text-sm text-muted">{profLabel}</div>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<span className="text-sm text-muted">{profLabel}</span>
|
||||
<Button size="sm" variant="secondary" onClick={() => setLevelUp(true)}>Level up</Button>
|
||||
<Button size="sm" variant="ghost" onClick={() => window.print()} title="Print / save as PDF">Print</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="mb-6 grid gap-3 sm:grid-cols-2 lg:grid-cols-4">
|
||||
@@ -118,7 +126,10 @@ export function CharacterSheet({ character }: { character: Character }) {
|
||||
|
||||
{/* Abilities */}
|
||||
<section className="mb-6">
|
||||
<SectionTitle>Ability Scores</SectionTitle>
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<SectionTitle>Ability Scores</SectionTitle>
|
||||
<Button size="sm" variant="ghost" onClick={() => setGenScores(true)}>Generate</Button>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-3 sm:grid-cols-6">
|
||||
{ABILITIES.map((a) => {
|
||||
const mod = sys.abilityModifier(c.abilities[a]);
|
||||
@@ -212,6 +223,13 @@ export function CharacterSheet({ character }: { character: Character }) {
|
||||
className="min-h-32 w-full resize-y rounded-md border border-line bg-surface px-3 py-2 text-sm text-ink focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent/60"
|
||||
/>
|
||||
</section>
|
||||
|
||||
{genScores && (
|
||||
<AbilityGenModal onClose={() => setGenScores(false)} onApply={(abilities) => update({ abilities })} />
|
||||
)}
|
||||
{levelUp && (
|
||||
<LevelUpModal character={c} onClose={() => setLevelUp(false)} onApply={(patch) => update(patch)} />
|
||||
)}
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user