docs(01): UI design contract for level-up wizard
This commit is contained in:
677
.planning/phases/01-level-up-pf2e-regelkonform/01-UI-SPEC.md
Normal file
677
.planning/phases/01-level-up-pf2e-regelkonform/01-UI-SPEC.md
Normal file
@@ -0,0 +1,677 @@
|
||||
---
|
||||
phase: 1
|
||||
slug: level-up-pf2e-regelkonform
|
||||
status: draft
|
||||
shadcn_initialized: false
|
||||
preset: none
|
||||
created: 2026-04-27
|
||||
---
|
||||
|
||||
# Phase 1 — UI Design Contract
|
||||
|
||||
> Visual and interaction contract for the Level-Up Wizard. Generated by gsd-ui-researcher,
|
||||
> verified by gsd-ui-checker.
|
||||
>
|
||||
> Most decisions are inherited from CONTEXT.md, the project's existing design tokens
|
||||
> (`client/src/index.css`), and the established modal/component patterns in
|
||||
> `client/src/features/characters/components/`. Where CONTEXT.md locks a behavior,
|
||||
> the relevant `D-XX` decision ID is referenced inline.
|
||||
|
||||
---
|
||||
|
||||
## Design System
|
||||
|
||||
| Property | Value |
|
||||
|----------|-------|
|
||||
| Tool | none (hand-built primitives) |
|
||||
| Preset | not applicable |
|
||||
| Component library | hand-built shadcn-style primitives in `client/src/shared/components/ui/` (`Button`, `Input`, `Card`, `Spinner`, `ActionIcon`); reused for the wizard. |
|
||||
| Icon library | `lucide-react` 0.562.0 (`ChevronLeft`, `ChevronRight`, `X`, `AlertTriangle`, `Sparkles`, `Swords`, `Star`, `BookOpen`, `Shield`, `Heart`, `Eye`, `Footprints`, `Wand2`, `Check`, `Lock`, `Info`, `RotateCcw`) — never emojis (CLAUDE.md). |
|
||||
| Font | Inter (sans), JetBrains Mono (mono) — declared in `--font-sans`, `--font-mono` (`client/src/index.css:66-67`). |
|
||||
| Theme | Tailwind v4 `@theme` block in `client/src/index.css`. All tokens already exist; UI-SPEC adds **no new tokens**. |
|
||||
| Animation lib | Framer Motion 12.26.2 is installed but unused today. Phase 1 introduces it for the wizard step transition (see "Motion" section). |
|
||||
|
||||
---
|
||||
|
||||
## Spacing Scale
|
||||
|
||||
Declared values (all multiples of 4, all already present in Tailwind v4 default scale used by the codebase):
|
||||
|
||||
| Token | Value | Usage in Phase 1 |
|
||||
|-------|-------|-------------------|
|
||||
| xs | 4px (`p-1`, `gap-1`) | Icon gap inside choice-card chips, badge padding |
|
||||
| sm | 8px (`p-2`, `gap-2`) | Stepper-dot gap, footer button gap, badge stacks |
|
||||
| md | 16px (`p-4`, `gap-4`) | Wizard body padding (mobile), step-section padding, choice-card inner padding |
|
||||
| lg | 24px (`p-6`, `gap-6`) | Wizard body padding (`sm:` desktop), Review-step section padding |
|
||||
| xl | 32px (`p-8`) | Reserved for review-step major section breaks on desktop |
|
||||
| 2xl | 48px | Not used in Phase 1 |
|
||||
| 3xl | 64px | Not used in Phase 1 |
|
||||
|
||||
Touch-target minimums (mobile-first, CLAUDE.md):
|
||||
|
||||
- All interactive buttons: 44px height — `h-11` (`Button` `default`), `h-11 w-11` (`Button` `icon`).
|
||||
- Choice-cards: minimum 64px tap height (so an entire card is a single touch target including its title row + meta row — exceeds 44px).
|
||||
- Stepper-dot buttons: 32px tap-target (`h-8 w-8`) but padded to a 44px hit-zone via `p-1.5` wrapper.
|
||||
|
||||
Exceptions: none.
|
||||
|
||||
---
|
||||
|
||||
## Typography
|
||||
|
||||
The codebase has no custom font-size tokens — sizes come from Tailwind defaults. Phase 1 declares **3 sizes + display** and **2 weights** to satisfy the 3-4-size, 2-weight contract.
|
||||
|
||||
| Role | Size | Tailwind class | Weight | Line Height | Usage |
|
||||
|------|------|----------------|--------|-------------|-------|
|
||||
| Body | 14px | `text-sm` | 400 (`font-normal`) | 1.5 (`leading-normal`) | Choice-card descriptions, prereq text, banner body, footer help text, tooltip body |
|
||||
| Label | 14px | `text-sm` | 600 (`font-semibold`) | 1.4 | Choice-card titles, step-section labels, button labels, badge text |
|
||||
| Heading | 18px | `text-lg` | 600 (`font-semibold`) | 1.3 | Wizard header title (`Stufenaufstieg — Stufe X`), step-screen heading (`Boost setzen`, `Klassentalent wählen`, …), modal H2 |
|
||||
| Display | 24px | `text-2xl` | 600 (`font-semibold`) | 1.2 | Review-step "before/after" stat numbers (HP-Max, AC, DC, Wahrnehmung, Saves) |
|
||||
|
||||
Weights: only **400 (normal)** for body and **600 (semibold)** for everything else. No `font-medium` (500), no `font-bold` (700). This matches the existing `add-condition-modal.tsx`, `rest-modal.tsx`, `add-feat-modal.tsx` patterns (`text-lg font-semibold` for headers, `text-sm` for body).
|
||||
|
||||
Mono font (JetBrains Mono) is reserved for stat numbers and dice notation in the Review step (`font-mono`).
|
||||
|
||||
---
|
||||
|
||||
## Color
|
||||
|
||||
The 60/30/10 split maps directly to existing tokens in `client/src/index.css`:
|
||||
|
||||
| Role | Value | Token | Usage |
|
||||
|------|-------|-------|-------|
|
||||
| Dominant (60%) | `#0f0f12` | `bg-bg-primary` | Page background behind the wizard backdrop |
|
||||
| Secondary (30%) | `#1a1a1f` / `#242429` | `bg-bg-secondary` (modal panel), `bg-bg-tertiary` (choice-cards, info-strips, stepper rail) | Wizard surface, choice-card surfaces, step-section containers, stepper inactive state |
|
||||
| Accent (10%) | `#c26dbc` | `primary-500` (`text-primary-500`, `bg-primary-500`, `border-primary-500`) | **EXACTLY** these elements only (see reserved-for list below) |
|
||||
| Destructive | `#ef4444` | `error-500` (`text-error-500`, `bg-error-500`) | "Verwerfen" (DRAFT discard) confirm-dialog button only — **not** Abbrechen (Abbrechen = `outline`, see Copywriting). |
|
||||
|
||||
Accent reserved-for list (the only places `primary-500` may be applied in this phase):
|
||||
|
||||
1. **Primary CTA buttons in the wizard footer**: `Weiter`, `Bestätigen` — `Button variant="default"` (which is `bg-primary-500`).
|
||||
2. **Active stepper dot**: the dot for the current step uses `bg-primary-500`; completed dots use `bg-primary-500/40`; future dots use `bg-bg-tertiary`.
|
||||
3. **Selected choice-card border + checkmark**: the chosen card (boost target, talent, skill, doctrine, …) gets `border-primary-500 ring-1 ring-primary-500/40`, and a `Check` icon in `text-primary-500` appears in the top-right corner.
|
||||
4. **DRAFT-resume banner accent stripe and the "Fortsetzen" button**: the resume banner has a left-border `border-l-4 border-primary-500`, body text `text-text-primary`, and the "Fortsetzen" CTA is `Button variant="default"`.
|
||||
5. **"Stufe steigen"-Button on the character-sheet header** (when enabled — i.e. character is below cap and no foreign DRAFT exists): `Button variant="default"` with `Sparkles` icon.
|
||||
|
||||
`primary-500` is **never** used for: choice-card backgrounds (those stay `bg-bg-tertiary`), step-headings (those use `text-text-primary`), tab/section dividers (`border-border`), tooltip backgrounds, banner backgrounds.
|
||||
|
||||
Semantic color usage (already established by existing modals — `rest-modal.tsx` is the canonical reference):
|
||||
|
||||
| Semantic | Token | Reserved For |
|
||||
|----------|-------|--------------|
|
||||
| Warning (`#f59e0b`, `warning-500` / `yellow-400`) | `text-warning-500` `bg-warning-500/10` `border-warning-500/20` | (a) `AlertTriangle` icon at the top-right of any **choice-card whose prereq is non-evaluable** (D-03). (b) Pathbuilder-import-violation banner background (D-06). (c) "Cap erreicht" indicator on a skill that cannot be increased further. |
|
||||
| Error (`#ef4444`, `error-500` / `red-400`) | `text-error-500` `bg-error-500/10` `border-error-500/20` | (a) "Verwerfen" button in the DRAFT-resume banner and the Abbrechen-with-unsaved-changes confirm. (b) Inline form-validation errors (e.g. boost target invalid). |
|
||||
| Success (`#22c55e`, `success-500` / `green-400`) | `text-success-500` | Checkmarks in completed stepper dots; "+X HP", "+1 Save" deltas in the Review step (positive change). |
|
||||
| Info (`#3b82f6`, `info-500` / `blue-400`) | `text-info-500` `bg-info-500/10` | Read-only info strips in the wizard (e.g. "Free Archetype: aktiv", "Spellcaster-Slot +1 (automatisch)"). |
|
||||
|
||||
Source-tag colors for talent badges (existing convention in `feat-detail-modal.tsx:22-29`, copy verbatim):
|
||||
|
||||
| Source | Token classes |
|
||||
|--------|---------------|
|
||||
| Klassentalent | `bg-red-500/20 text-red-400` |
|
||||
| Abstammungstalent | `bg-blue-500/20 text-blue-400` |
|
||||
| Allgemeines Talent | `bg-yellow-500/20 text-yellow-400` |
|
||||
| Fertigkeitstalent | `bg-green-500/20 text-green-400` |
|
||||
| Archetypentalent | `bg-purple-500/20 text-purple-400` |
|
||||
| Bonustalent | `bg-cyan-500/20 text-cyan-400` |
|
||||
|
||||
---
|
||||
|
||||
## Copywriting Contract
|
||||
|
||||
All copy is **German** (CLAUDE.md / PROJECT.md). German imperative tone, du-Form, no emojis, no exclamation marks except in genuine warnings.
|
||||
|
||||
### Primary CTAs
|
||||
|
||||
| Element | Copy |
|
||||
|---------|------|
|
||||
| Character-sheet button to start the wizard | `Stufe steigen` (icon: `Sparkles`) |
|
||||
| Wizard footer — go to next step | `Weiter` (icon: `ChevronRight` on right) |
|
||||
| Wizard footer — go to previous step | `Zurück` (icon: `ChevronLeft` on left, `Button variant="outline"`) |
|
||||
| Wizard footer — final commit on Review step | `Bestätigen` (replaces `Weiter`; icon: `Check` on right) |
|
||||
| Wizard footer — abort wizard mid-flow | `Abbrechen` (`Button variant="ghost"`, secondary placement) |
|
||||
| Choice-card click result label | `Wählen` (only used inside the prereq-confirm dialog as "Trotzdem wählen") |
|
||||
|
||||
### Wizard-step screen headings (one per step, `text-lg font-semibold`)
|
||||
|
||||
| Step | Heading | Sub-line (`text-sm text-text-secondary`) |
|
||||
|------|---------|--------------------------------------------|
|
||||
| 0 — Klassenmerkmale (auto) | `Klassenmerkmale auf Stufe {N}` | `Diese Merkmale werden automatisch übernommen.` |
|
||||
| 0a — Klassenmerkmal-Wahl (`choiceType`) | `{Merkmalname} wählen` (z.B. `Lehre wählen`, `Schule wählen`, `Waffenmeisterschaft wählen`) | `Wähle eine Option — diese Wahl ist endgültig nach Bestätigung.` |
|
||||
| 1 — Boost-Set | `Attributs-Boosts setzen` | `Wähle vier verschiedene Attribute. Werte über 18 erhalten +1, sonst +2.` |
|
||||
| 2 — Skill-Increase | `Fertigkeits-Erhöhung` | `Wähle eine Fertigkeit, um sie um eine Stufe zu erhöhen.` |
|
||||
| 3 — Klassentalent | `Klassentalent wählen` | `Talente, deren Voraussetzungen du erfüllst.` |
|
||||
| 4 — Fertigkeitstalent | `Fertigkeitstalent wählen` | `Talente, deren Voraussetzungen du erfüllst.` |
|
||||
| 5 — Allgemein-Talent | `Allgemeines Talent wählen` | `Allgemein- oder Fertigkeitstalente sind erlaubt.` |
|
||||
| 6 — Ancestry-Talent | `Abstammungstalent wählen` | `Talente deiner Abstammung und deines Erbes.` |
|
||||
| 7 — Free-Archetype-Slot | `Archetypentalent (Free Archetype)` | (vor Dedication) `Wähle eine Dedication.` / (nach Dedication) `Talente jedes Archetyps sind erlaubt (Pathbuilder-Verhalten).` |
|
||||
| 8 — Spellcaster | `Zauberprogression` | (spontan) `Repertoire um {N} Zauber erweitern.` / (vorbereitet) `Slot-Erhöhung wird automatisch angewendet.` |
|
||||
| 9 — Review | `Übersicht & Bestätigen` | `Vergleiche Vorher/Nachher. Erst nach „Bestätigen" wird der Charakter geändert.` |
|
||||
|
||||
### Empty / Loading / Error states
|
||||
|
||||
| State | Copy |
|
||||
|-------|------|
|
||||
| Loading talent list (Spinner + label) | `Talente werden geladen …` |
|
||||
| Loading any list (boost options, skills, formulas) | `Wird geladen …` |
|
||||
| Empty: no talents meet prerequisites at this slot | Heading: `Keine erfüllbaren Talente gefunden` — Body: `Alle Talente dieser Kategorie haben Voraussetzungen, die du derzeit nicht erfüllst. Du kannst trotzdem ein Talent mit Warnung wählen — aktiviere "Auch nicht erfüllbare anzeigen".` (toggle button text: `Auch nicht erfüllbare anzeigen` / `Nur erfüllbare anzeigen`). |
|
||||
| Empty: no skills can be increased (cap at all) | Heading: `Keine Fertigkeit erhöhbar` — Body: `Alle Fertigkeiten haben das für deine Stufe erlaubte Maximum erreicht. Diesen Schritt überspringen?` (button: `Schritt überspringen`). |
|
||||
| Empty: no spells available for repertoire (impossible at L2+, but defensive) | `Keine Zauber verfügbar.` |
|
||||
| Error: API failure during step (e.g. talent search) | Heading: `Talente konnten nicht geladen werden` — Body: `Versuche es erneut oder wähle ein anderes Filter.` — Action: `Erneut versuchen` (`Button variant="outline"`). |
|
||||
| Error: commit transaction failed (rollback) | Heading: `Stufenaufstieg konnte nicht gespeichert werden` — Body: `Deine Wahlen sind sicher als Entwurf gespeichert. Bitte versuche es erneut. Wenn der Fehler bleibt, lade die Seite neu.` — Action: `Erneut versuchen` (`Button variant="default"`) + `Schließen` (`Button variant="ghost"`). DRAFT bleibt erhalten. |
|
||||
| Error: prereq evaluator returns "non-evaluable" | (Inline am Talent — kein eigener State; siehe Prereq-Confirm-Dialog) |
|
||||
|
||||
### Destructive confirmations
|
||||
|
||||
| Action | Trigger | Confirm-Dialog |
|
||||
|--------|---------|----------------|
|
||||
| **DRAFT verwerfen** | Click `Verwerfen` in DRAFT-resume banner (D-14) | Heading: `Entwurf verwerfen?` — Body: `Deine bisherigen Wahlen für Stufe {N} werden gelöscht. Diese Aktion kann nicht rückgängig gemacht werden.` — Buttons: `Abbrechen` (ghost) + `Verwerfen` (`destructive`, `Trash2` icon). |
|
||||
| **Wizard mid-flow abbrechen** mit ungesicherten Änderungen | Click `Abbrechen` button or backdrop click after at least one choice was made | Heading: `Wizard abbrechen?` — Body: `Deine bisherigen Wahlen werden als Entwurf gespeichert und du kannst später fortsetzen.` — Buttons: `Weiter bearbeiten` (default) + `Als Entwurf speichern und schließen` (outline). **Note:** discard is NOT offered here — only commit-or-save. Discard happens only via the resume-banner "Verwerfen". |
|
||||
| **Talent mit nicht erfüllter Voraussetzung wählen** (D-03) | Click `Wählen` on a choice-card that carries the yellow `AlertTriangle` (non-evaluable prereq) | Heading: `Voraussetzung nicht prüfbar` — Body: `Voraussetzung: „{prereqText}". Dimension47 kann diese Bedingung nicht automatisch prüfen. Erfüllst du sie?` — Buttons: `Abbrechen` (ghost) + `Trotzdem wählen` (`Button variant="default"`, no destructive coloring — this is informed user choice, not a destructive op). |
|
||||
| **Bestätigen (Final Commit)** in Review step | Click `Bestätigen` | No additional dialog — the Review step IS the confirmation surface. The button is `Button variant="default"` with `isLoading` spinner during commit. After success: wizard closes with a 2-second toast `Stufenaufstieg bestätigt — Stufe {N}.` (info-tone, `bg-success-500/10`). |
|
||||
|
||||
### Banner copy (D-06: Pathbuilder-Import-Violations)
|
||||
|
||||
Position: directly below the character-sheet header, above the tab navigation. Background `bg-warning-500/10`, border `border-l-4 border-warning-500`, padding `p-4`.
|
||||
|
||||
- Heading (`text-sm font-semibold text-warning-500`): `{N} Talente mit nicht erfüllter Voraussetzung`
|
||||
- Body (`text-sm text-text-secondary`): `Beim Import wurde festgestellt, dass folgende Talente Voraussetzungen haben, die der Charakter nicht erfüllt. Liste prüfen und ggf. nachträglich anpassen.`
|
||||
- Action: `Liste anzeigen` (`Button variant="ghost"` size sm, expand-collapse — when expanded, shows a `<ul>` of `{TalentName} — Voraussetzung: {prereqText}` items).
|
||||
- Dismiss: not dismissible (the banner stays until the violations are resolved by GM/Player editing — Phase 1 ships read-only listing only; resolution UI is out of scope, deferred to v2 retrain).
|
||||
|
||||
### Banner copy (D-14: DRAFT-Resume-Banner)
|
||||
|
||||
Position: at the top of the character-sheet page (above the avatar header) AND inside the wizard if user clicks "Stufe steigen" while a DRAFT exists. Background `bg-bg-tertiary`, border `border-l-4 border-primary-500`, padding `p-4`.
|
||||
|
||||
- Heading (`text-sm font-semibold text-text-primary`): `Du hast eine offene Stufenaufstiegs-Session — Stufe {N}.`
|
||||
- Body (`text-sm text-text-secondary`): `Zuletzt bearbeitet: {relativeDate}.` (z.B. `vor 3 Tagen`).
|
||||
- Actions: `Fortsetzen` (`Button variant="default"`, icon `RotateCcw`) + `Verwerfen` (`Button variant="ghost"`, `text-error-500`, icon `Trash2`).
|
||||
|
||||
### "Stufe steigen"-Button on character-sheet header
|
||||
|
||||
Position: in the right-side header button cluster of `character-sheet-page.tsx` (lines 1607-1626 — alongside Download, Edit, Delete). Order: **first** in the cluster (left of Download).
|
||||
|
||||
| Character state | Button rendering |
|
||||
|-----------------|------------------|
|
||||
| Eligible, no DRAFT | `Button variant="default"` size `sm` — label `Stufe steigen`, icon `Sparkles`, fully enabled. |
|
||||
| Eligible, DRAFT exists | `Button variant="default"` size `sm` — label `Stufe fortsetzen`, icon `RotateCcw`, fully enabled. (Consistent with D-14 banner; both routes open the same wizard, the banner is just an upper-page reminder.) |
|
||||
| Character at level cap (level 20) | Hidden entirely. (No disabled state to avoid confusion — the button just isn't there.) |
|
||||
| User is neither owner nor GM | Hidden entirely. |
|
||||
|
||||
---
|
||||
|
||||
## Component Contract — Wizard Chrome
|
||||
|
||||
### Container
|
||||
|
||||
The wizard reuses the project's modal pattern (canonical: `add-feat-modal.tsx:246-260`):
|
||||
|
||||
```
|
||||
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center">
|
||||
<div className="absolute inset-0 bg-black/60" onClick={handleBackdropClick} />
|
||||
<div className="relative w-full sm:max-w-2xl max-h-[90vh] bg-bg-secondary
|
||||
rounded-t-2xl sm:rounded-2xl flex flex-col overflow-hidden">
|
||||
{/* Header */}
|
||||
{/* Stepper */}
|
||||
{/* Body (current step) */}
|
||||
{/* Footer */}
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
- Mobile: full-width bottom-sheet (`items-end`, `rounded-t-2xl`).
|
||||
- Desktop (`sm:` ≥ 640px): centered modal, max-width `2xl` (672px), `rounded-2xl`.
|
||||
- Backdrop click: opens the "Wizard mid-flow abbrechen" confirm if any choice has been made; closes silently if no choice yet.
|
||||
|
||||
### Header (always visible)
|
||||
|
||||
- Container: `flex items-center justify-between p-4 border-b border-border`.
|
||||
- Left: `<h2 className="text-lg font-semibold text-text-primary">Stufenaufstieg — Stufe {N}</h2>` + sub-line `<p className="text-xs text-text-secondary">{characterName}</p>`.
|
||||
- Right: close button `<Button variant="ghost" size="icon"><X className="h-5 w-5" /></Button>` triggering the same backdrop logic.
|
||||
|
||||
### Stepper (always visible, between Header and Body)
|
||||
|
||||
Mobile-first compact dot-stepper (no labels on mobile, labels visible on `sm:` and up):
|
||||
|
||||
- Container: `flex items-center gap-2 px-4 py-3 border-b border-border overflow-x-auto`.
|
||||
- Each step is a clickable dot **only for steps already completed or the current step** (no jumping forward to incomplete future steps).
|
||||
- Dot states:
|
||||
- **Future**: `h-2 w-2 rounded-full bg-bg-tertiary` (no label).
|
||||
- **Current**: `h-2.5 w-2.5 rounded-full bg-primary-500` + step label visible on `sm:` (`text-xs font-semibold text-text-primary`).
|
||||
- **Completed**: `h-2 w-2 rounded-full bg-primary-500/40` + on hover/click navigates back; on `sm:` shows `Check` icon at `h-3 w-3 text-success-500` overlay.
|
||||
- Tap-target: each dot is wrapped in `<button className="p-1.5 -m-1.5">` to extend the hit-zone to 44px.
|
||||
- Step labels (visible `sm:` and up): one of `Merkmale`, `Wahl`, `Boost`, `Skill`, `Klasse`, `Fertigkeit`, `Allgemein`, `Abstammung`, `Archetyp`, `Zauber`, `Übersicht` — exactly the steps that apply to this level. Conditional steps not shown.
|
||||
- Connector between dots: `h-px w-4 bg-bg-tertiary` (`bg-primary-500/40` between completed pairs).
|
||||
|
||||
### Body (per-step content area)
|
||||
|
||||
- Container: `flex-1 overflow-y-auto p-4 sm:p-6`.
|
||||
- Top of body shows the step heading + sub-line (see Copywriting table above).
|
||||
- Below: the step's content (Choice-Cards, Boost-Picker, Skill-Picker, Review-Diff, …).
|
||||
- Mobile: one wahlpunkt per scroll-screen — choice-cards stack vertically, full-width.
|
||||
- Desktop: choice-cards may form a 2-column grid (`grid grid-cols-1 sm:grid-cols-2 gap-3`) for talent picks; boost picker stays single-column for clarity.
|
||||
|
||||
### Footer (always visible)
|
||||
|
||||
- Container: `flex items-center justify-between gap-2 p-4 border-t border-border bg-bg-secondary`.
|
||||
- Left: `<Button variant="ghost" onClick={handleAbort}>Abbrechen</Button>` — always visible, always live.
|
||||
- Right cluster (`flex items-center gap-2`):
|
||||
- `<Button variant="outline" onClick={prev} disabled={isFirstStep}><ChevronLeft className="h-4 w-4" /> Zurück</Button>`
|
||||
- On non-final steps: `<Button variant="default" onClick={next} disabled={!stepValid}>Weiter <ChevronRight className="h-4 w-4" /></Button>`
|
||||
- On Review step: `<Button variant="default" onClick={commit} isLoading={isCommitting}><Check className="h-4 w-4" /> Bestätigen</Button>`
|
||||
- Footer is sticky at the bottom of the modal (the body scrolls, the footer does not).
|
||||
|
||||
---
|
||||
|
||||
## Component Contract — Choice-Card
|
||||
|
||||
The choice-card is the primary interactive element across most steps (talent picks, boost-target picks, skill-increase picks, doctrine/school picks). Single canonical layout, used everywhere.
|
||||
|
||||
### Layout (per card)
|
||||
|
||||
Container:
|
||||
```
|
||||
<button
|
||||
onClick={() => onSelect(option)}
|
||||
disabled={isLocked}
|
||||
className={cn(
|
||||
'w-full text-left p-4 rounded-xl border transition-colors',
|
||||
'min-h-[64px]', // touch-target floor
|
||||
!isSelected && !isLocked && 'bg-bg-tertiary border-border hover:border-border-hover',
|
||||
isSelected && 'bg-bg-tertiary border-primary-500 ring-1 ring-primary-500/40',
|
||||
isLocked && 'bg-bg-tertiary/60 border-border opacity-60 cursor-not-allowed',
|
||||
)}
|
||||
>
|
||||
```
|
||||
|
||||
Inner structure (top row → meta row → optional description):
|
||||
|
||||
1. **Top row** (`flex items-start justify-between gap-2`):
|
||||
- Left: `<h4 className="text-sm font-semibold text-text-primary">{germanName ?? englishName}</h4>` + sub-line `text-xs text-text-muted` showing the English name if a German name exists.
|
||||
- Right (icons stack): in this order, only if applicable:
|
||||
- `Lock` icon (`h-4 w-4 text-text-muted`) if locked (already chosen / cap reached / not eligible at this slot).
|
||||
- `AlertTriangle` icon (`h-4 w-4 text-warning-500`) if prereq is **non-evaluable** (D-03) — wrapped in a tooltip showing the raw prereq string.
|
||||
- `Check` icon (`h-4 w-4 text-primary-500`) if currently selected.
|
||||
|
||||
2. **Meta row** (`mt-2 flex flex-wrap gap-2 items-center text-xs`):
|
||||
- Source badge (existing color map — Klassen-/Abstammungs-/etc., see Color section).
|
||||
- Level chip: `text-text-secondary` `Stufe {n}+`.
|
||||
- Action-cost badge if applicable (uses existing `ActionIcon` component from `client/src/shared/components/ui/action-icon.tsx`).
|
||||
- Rarity chip if not `Common` (existing convention from `add-feat-modal.tsx:294-302`).
|
||||
- **Cap-erreicht** chip on Skill-Increase cards: `bg-warning-500/10 text-warning-500 px-2 py-0.5 rounded` text `Cap erreicht`.
|
||||
|
||||
3. **Description (truncated)**: `mt-2 text-sm text-text-secondary line-clamp-2`. A `Mehr` link beneath the truncation opens the existing `FeatDetailModal` (or a similar detail modal for non-talent options) **as a layered modal** at `z-60`.
|
||||
|
||||
### Locked / unavailable states
|
||||
|
||||
- **Already chosen this Level-Up** (e.g. cannot pick the same skill twice): `disabled` + `Lock` icon + opacity 60%. No click effect.
|
||||
- **Prereq fails (evaluable)**: hidden by default. A toggle in the step body (`Auch nicht erfüllbare anzeigen`) shows them grayed (`opacity-60`) with a `bg-error-500/10 text-error-500 text-xs` chip `Voraussetzung nicht erfüllt: {parsed reason}`. Click still enabled but routes through the prereq-confirm dialog.
|
||||
- **Prereq non-evaluable (D-03)**: shown by default (it's only a warning, not a block). Yellow `AlertTriangle` icon. Click triggers the prereq-confirm dialog with the raw `prerequisites` string.
|
||||
- **Cap reached** (skill-increase): `disabled` + warning chip `Cap erreicht` + tooltip `Diese Fertigkeit ist auf deiner Stufe nicht weiter erhöhbar.`
|
||||
|
||||
### Selected state
|
||||
|
||||
- Border `border-primary-500` + halo `ring-1 ring-primary-500/40`.
|
||||
- Top-right `Check` icon `text-primary-500`.
|
||||
- Background remains `bg-bg-tertiary` — the accent is **only** the border + checkmark, not the surface fill (60/30/10 discipline).
|
||||
|
||||
---
|
||||
|
||||
## Component Contract — Boost-Set Step
|
||||
|
||||
Special layout: 6 attribute "buttons" (STR, DEX, CON, INT, WIS, CHA) with a count of how many of the 4 boosts are spent on each. Mobile: vertical stack of 6 attribute rows. Desktop: 2-column grid.
|
||||
|
||||
Per attribute row (button-style, full-width):
|
||||
```
|
||||
<button className="w-full p-4 rounded-xl border border-border bg-bg-tertiary
|
||||
flex items-center justify-between min-h-[64px]
|
||||
hover:border-border-hover">
|
||||
<div>
|
||||
<h4 className="text-sm font-semibold text-text-primary">{abbreviation} — {germanName}</h4>
|
||||
<p className="text-xs text-text-secondary">
|
||||
Aktuell {currentScore}{capReached ? ' (Cap)' : ''} → wird {newScore}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<button onClick={dec} disabled={count === 0} className="h-9 w-9 rounded-lg bg-bg-elevated"><Minus /></button>
|
||||
<span className="text-lg font-semibold text-text-primary tabular-nums w-8 text-center">{count}</span>
|
||||
<button onClick={inc} disabled={!canIncrement} className="h-9 w-9 rounded-lg bg-bg-elevated"><Plus /></button>
|
||||
</div>
|
||||
</button>
|
||||
```
|
||||
|
||||
- "wird {newScore}" reflects the +2 / +1 cap-bei-18 rule live (not the full character recompute — that's review-only per D-12, but this single-attribute preview stays correct because the math is local).
|
||||
- **Cap visualisation**: when `currentScore >= 18` AND `count >= 1`, the row shows a small chip `+1 (Cap bei 18)` next to the new-score line in `text-warning-500`. Otherwise `+2`.
|
||||
- **Footer-helper** below the 6 rows: `<p className="text-xs text-text-secondary mt-4">{boostsSpent} von 4 Boosts gewählt. Du musst genau 4 verschiedene Attribute boosten.</p>` — when `boostsSpent === 4`, the `Weiter` button enables.
|
||||
- A boost cannot be doubled-up: max `count` per attribute is 1 (PF2e rule on level-up boosts). The +/- controls enforce this.
|
||||
|
||||
---
|
||||
|
||||
## Component Contract — Skill-Increase Step
|
||||
|
||||
A scrollable list of 16+ skills (or all the character's trained-or-higher skills, plus untrained ones to allow new training).
|
||||
|
||||
Per skill row (compact):
|
||||
```
|
||||
<button className="w-full p-3 rounded-lg border border-border bg-bg-tertiary
|
||||
flex items-center justify-between min-h-[56px]
|
||||
hover:border-border-hover disabled:opacity-60">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-sm font-semibold text-text-primary">{germanSkillName}</span>
|
||||
<span className="text-xs text-text-secondary">{abilityAbbreviation}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-text-secondary">{germanCurrentProf}</span>
|
||||
<ChevronRight className="h-3 w-3 text-text-muted" />
|
||||
<span className={cn('text-xs font-semibold', proficiencyColors[newProf])}>{germanNewProf}</span>
|
||||
</div>
|
||||
</button>
|
||||
```
|
||||
|
||||
- Reuses the existing proficiency color map (`feat-detail-modal.tsx` → `PROFICIENCY_COLORS` in `character-sheet-page.tsx`): `TRAINED text-blue-500`, `EXPERT text-purple-500`, `MASTER text-orange-500`, `LEGENDARY text-red-500`.
|
||||
- **Cap-erreicht skills** (e.g. trying to push to MASTER before L7) are rendered with `disabled` + warning chip `Cap erreicht`.
|
||||
- **Already trained skills (UNTRAINED case is rare, only for first-time training)** are shown normally.
|
||||
- Mobile: one column. Desktop: still one column for clarity (16 rows × 56px ≈ 900px which fits in the modal scroll body).
|
||||
|
||||
---
|
||||
|
||||
## Component Contract — Free-Archetype-Slot Step (D-08)
|
||||
|
||||
Top of the step body shows the read-only toggle status:
|
||||
|
||||
- If FA is **enabled** for this character: info strip `bg-info-500/10 border border-info-500/20 text-info-500 p-3 rounded-lg flex items-center gap-2` with `<Sparkles className="h-4 w-4" />` icon and copy `Free Archetype: aktiv` + sub-line `text-xs text-text-secondary` `Du erhältst zusätzlich einen Archetypen-Talent-Slot.`
|
||||
- If FA is **disabled**: the entire step is skipped (not rendered in the stepper).
|
||||
- Below the strip: choice-cards filtered by archetype rules:
|
||||
- Vor erster Dedication: cards filtered to `featType === 'Archetype'` AND trait includes `Dedication`.
|
||||
- Nach erster Dedication (D-07 — Pathbuilder behavior): all `featType === 'Archetype'` cards (any archetype).
|
||||
- A small text below the filter-toggle row reads either `Wähle eine Dedication.` or `Talente jedes Archetyps sind erlaubt (Pathbuilder-Verhalten).` (matches sub-line copy table above).
|
||||
|
||||
---
|
||||
|
||||
## Component Contract — Spellcaster Step
|
||||
|
||||
Top of the step body shows a read-only info strip describing what's automatic:
|
||||
|
||||
- Vorbereiteter Caster: `<info strip> Slot-Erhöhung: +{N} Slot auf Grad {M} (automatisch)` — and the body has **no choice-cards** beyond this strip; the user can press `Weiter` immediately.
|
||||
- Spontaner Caster (Bard, Sorcerer, Oracle, Witch with Patron-Variant, Summoner spontaneously cast — D-18): info strip + a **repertoire-pick** sub-section. The repertoire-pick is a list of choice-cards (one per spell), filtered by tradition and by the user's known spell-levels. Cap message: `Wähle {N} Zauber für dein Repertoire.` `{spellsPicked} von {N} gewählt.` `Weiter` enables when `spellsPicked === N`.
|
||||
|
||||
Spell choice-cards reuse the canonical Choice-Card layout, with these adaptations:
|
||||
- Top row title: `{germanSpellName}` + sub-line englishName.
|
||||
- Meta row: tradition chip (`bg-primary-500/10 text-primary-400 px-2 py-0.5 rounded text-xs`), spell-level chip `Grad {n}`, traits.
|
||||
|
||||
---
|
||||
|
||||
## Component Contract — Choice-Klassenmerkmal Sub-Step (D-19)
|
||||
|
||||
Triggered when the `ClassProgression` row for `(class, targetLevel)` carries `choiceType` (e.g. Cleric Doctrine, Wizard School, Fighter Weapon Mastery, Champion Cause, Sorcerer Bloodline if not at L1, …).
|
||||
|
||||
Layout: a vertical stack of mutually-exclusive choice-cards (canonical Choice-Card layout), single-column on mobile and desktop. Selecting one card auto-deselects the previous (radio-group semantics, but using the same visual Choice-Card).
|
||||
|
||||
- Header sub-line is dynamic based on the choice key (see Copywriting table — `Lehre wählen`, `Schule wählen`, …).
|
||||
- The `choiceOptionsRef` from `ClassProgression` resolves to a list of options. Each option's description is rendered in the truncated description area; `Mehr` opens a layered detail-modal at `z-60`.
|
||||
- After picking, the meta row shows the badge `Klassenmerkmal-Wahl` (`bg-secondary-500/20 text-secondary-300 text-xs px-2 py-0.5 rounded`).
|
||||
|
||||
---
|
||||
|
||||
## Component Contract — Review Step (D-12)
|
||||
|
||||
Two-section layout, mobile-first vertically stacked, desktop two-column where helpful.
|
||||
|
||||
### Section A — Wahlen-Zusammenfassung
|
||||
|
||||
A `Card`-wrapped list of every choice the user made, grouped by step. Each row:
|
||||
|
||||
```
|
||||
<div className="flex items-start justify-between gap-3 py-2 border-b border-border last:border-0">
|
||||
<div className="flex items-center gap-2">
|
||||
{stepIcon} {/* e.g. Star for talent, Sparkles for skill, Wand2 for repertoire */}
|
||||
<span className="text-xs text-text-secondary">{stepLabel}</span>
|
||||
<span className="text-sm font-semibold text-text-primary">{choiceLabel}</span>
|
||||
</div>
|
||||
<button onClick={() => goToStep(step)} className="text-xs text-primary-500 hover:underline">Ändern</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
Tap on `Ändern` returns to that step (back-navigation in the stepper); user can revise then re-enter Review.
|
||||
|
||||
### Section B — Stat-Diff (Vorher / Nachher)
|
||||
|
||||
Two-column "Vorher / Nachher" layout. Mobile: stacked (Vorher card on top, Nachher card below). Desktop: side-by-side `grid grid-cols-1 sm:grid-cols-2 gap-4`.
|
||||
|
||||
Each card uses `Card` + `CardHeader` + `CardContent`:
|
||||
|
||||
- Card title (`CardTitle`): `Vorher (Stufe {N-1})` / `Nachher (Stufe {N})` — for "Nachher", a `Sparkles` icon `text-primary-500` is rendered next to the title.
|
||||
- Card content: a 5-row `<dl>`:
|
||||
|
||||
| Stat | Vorher | Nachher |
|
||||
|------|--------|---------|
|
||||
| HP-Max | `{old}` | `{new}` |
|
||||
| RK | `{old}` | `{new}` |
|
||||
| Klassen-DC | `{old}` | `{new}` |
|
||||
| Wahrnehmung | `{old}` | `{new}` |
|
||||
| Rettungswürfe | `Fort {old} / Ref {old} / Wille {old}` | `Fort {new} / Ref {new} / Wille {new}` |
|
||||
|
||||
Numbers in the Nachher card use `font-mono text-2xl font-semibold text-text-primary`. Deltas are rendered inline as small chips:
|
||||
- Positive change: `text-xs px-1.5 py-0.5 rounded bg-success-500/10 text-success-500` (`+{delta}`).
|
||||
- No change: omitted (no chip).
|
||||
- Negative change (rare — only if a class transition causes it; defensive): `bg-error-500/10 text-error-500` (`{delta}`).
|
||||
|
||||
### Section C — Spellcaster-Diff (only if applicable)
|
||||
|
||||
Shown below B when the character is a caster: a small table of spell-slot increments and (for spontaneous) repertoire deltas.
|
||||
|
||||
```
|
||||
<dl className="grid grid-cols-2 gap-2 text-sm">
|
||||
<dt className="text-text-secondary">Slots Grad 3</dt><dd className="text-text-primary">3 → 4 (<span className="text-success-500">+1</span>)</dd>
|
||||
<dt className="text-text-secondary">Repertoire</dt><dd className="text-text-primary">+{N} Zauber gewählt</dd>
|
||||
</dl>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Contract — Prereq-Confirm Dialog (D-03)
|
||||
|
||||
A **secondary modal layered over the wizard** at `z-60` (wizard sits at `z-50`, dialog at `z-60` — both share the same fixed-positioned root, dialog gets its own backdrop `bg-black/40`).
|
||||
|
||||
- Container: `relative w-full sm:max-w-md p-6 bg-bg-secondary rounded-2xl border border-warning-500/30`.
|
||||
- Header: `<div className="flex items-center gap-3 mb-3"> <AlertTriangle className="h-5 w-5 text-warning-500" /> <h3 className="text-base font-semibold text-text-primary">Voraussetzung nicht prüfbar</h3> </div>`
|
||||
- Body: `<p className="text-sm text-text-secondary mb-2">Voraussetzung:</p> <p className="text-sm font-semibold text-text-primary mb-4 p-3 rounded-lg bg-bg-tertiary border-l-4 border-warning-500">„{prereqText}"</p> <p className="text-sm text-text-secondary">Dimension47 kann diese Bedingung nicht automatisch prüfen. Erfüllst du sie?</p>`
|
||||
- Footer: `<div className="flex gap-2 mt-4 justify-end">` with `Abbrechen` (`Button variant="ghost"`) + `Trotzdem wählen` (`Button variant="default"`).
|
||||
|
||||
The same dialog shape is reused for "Wizard mid-flow abbrechen", "DRAFT verwerfen" — each with their own copy + heading + appropriate button variant.
|
||||
|
||||
---
|
||||
|
||||
## Component Contract — DRAFT-Resume Banner (D-14)
|
||||
|
||||
Two surfaces share this banner:
|
||||
|
||||
1. **Character-sheet page** (when a DRAFT exists for this character) — at the very top of the page, above the avatar header. Persistent until DRAFT is committed or discarded.
|
||||
2. **Wizard entry** — when the user clicks `Stufe steigen` and a DRAFT exists, the wizard opens directly to the last step the user was on, with a small inline strip at the top of the body `Entwurf wiederhergestellt — letzte Bearbeitung: {relativeDate}.` (`text-xs text-info-500 bg-info-500/10 p-2 rounded mb-4`).
|
||||
|
||||
Banner layout (surface 1):
|
||||
```
|
||||
<div className="bg-bg-tertiary border-l-4 border-primary-500 p-4 rounded-r-lg flex items-start justify-between gap-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<RotateCcw className="h-5 w-5 text-primary-500 flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<h3 className="text-sm font-semibold text-text-primary">
|
||||
Du hast eine offene Stufenaufstiegs-Session — Stufe {N}.
|
||||
</h3>
|
||||
<p className="text-xs text-text-secondary mt-0.5">
|
||||
Zuletzt bearbeitet: {relativeDate}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="ghost" size="sm" onClick={discard} className="text-error-500">
|
||||
<Trash2 className="h-4 w-4" /> Verwerfen
|
||||
</Button>
|
||||
<Button variant="default" size="sm" onClick={resume}>
|
||||
<RotateCcw className="h-4 w-4" /> Fortsetzen
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Component Contract — Pathbuilder-Import-Violations Banner (D-06)
|
||||
|
||||
Surface: directly below the avatar header, above the tab-navigation row. Persistent — Phase 1 ships listing only; user-driven resolution UI is v2.
|
||||
|
||||
```
|
||||
<div className="bg-warning-500/10 border-l-4 border-warning-500 p-4 rounded-r-lg">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertTriangle className="h-5 w-5 text-warning-500 flex-shrink-0 mt-0.5" />
|
||||
<div className="flex-1">
|
||||
<h3 className="text-sm font-semibold text-warning-500">
|
||||
{N} Talente mit nicht erfüllter Voraussetzung
|
||||
</h3>
|
||||
<p className="text-sm text-text-secondary mt-1">
|
||||
Beim Import wurde festgestellt, dass folgende Talente Voraussetzungen haben,
|
||||
die der Charakter nicht erfüllt. Liste prüfen und ggf. nachträglich anpassen.
|
||||
</p>
|
||||
<Button variant="ghost" size="sm" onClick={toggleList} className="mt-2 -ml-2">
|
||||
{expanded ? <ChevronDown /> : <ChevronRight />} Liste {expanded ? 'verbergen' : 'anzeigen'}
|
||||
</Button>
|
||||
{expanded && (
|
||||
<ul className="mt-2 space-y-1 text-sm text-text-secondary">
|
||||
{violations.map(v => (
|
||||
<li key={v.featId}>
|
||||
<span className="font-semibold text-text-primary">{v.featName}</span>
|
||||
<span className="text-text-muted"> — Voraussetzung: {v.prereqText}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Motion
|
||||
|
||||
Phase 1 is the first feature in the codebase to use `framer-motion`. Two motion contracts:
|
||||
|
||||
1. **Step transitions** (slide horizontal — direction = next/prev):
|
||||
- Wrapper: `<AnimatePresence mode="wait">` around the step body.
|
||||
- Each step `<motion.div key={stepId} initial={{ opacity: 0, x: direction === 'forward' ? 24 : -24 }} animate={{ opacity: 1, x: 0 }} exit={{ opacity: 0, x: direction === 'forward' ? -24 : 24 }} transition={{ duration: 0.2, ease: 'easeOut' }}>`.
|
||||
- Direction is derived from the previous step index vs. current step index.
|
||||
- Reduced-motion: respects `prefers-reduced-motion: reduce` — when set, the wrapper falls back to no `x` translation and a 0.1s opacity-only fade. (Implemented via `useReducedMotion()` hook from framer-motion.)
|
||||
|
||||
2. **Modal entry/exit**: existing CSS animations from `index.css` (`@keyframes slide-up` / `fade-in`) are sufficient for the wizard container itself. No framer-motion on the outer modal.
|
||||
|
||||
**Stepper dot transitions**: pure CSS `transition-colors duration-150 ease-out` on `bg-primary-500` / `bg-bg-tertiary` swaps. No framer-motion needed.
|
||||
|
||||
**Confirm-dialog (prereq, abort, discard)**: existing `slide-up 0.3s ease-out` from `index.css` applied to the dialog panel. No framer-motion.
|
||||
|
||||
---
|
||||
|
||||
## States Inventory
|
||||
|
||||
For each interactive surface, the executor must implement:
|
||||
|
||||
| Surface | States required |
|
||||
|---------|-----------------|
|
||||
| Choice-card | default, hover (`border-border-hover`), focus (`ring-2 ring-primary-500/40`), selected, locked (already chosen / cap), prereq-warning (yellow icon), prereq-fail (hidden by default) |
|
||||
| Boost-attribute row | default, hover, +/- disabled when count=0 / cap reached / 4 boosts spent |
|
||||
| Skill row | default, hover, cap-reached (disabled + warning chip) |
|
||||
| Talent list | loading (`Spinner` + label), empty (heading + body + show-unavailable toggle), error (heading + body + retry button), populated |
|
||||
| Wizard footer | first-step (Zurück disabled), middle-step (both enabled when `stepValid`), final-step (Bestätigen replaces Weiter), committing (`isLoading` on Bestätigen) |
|
||||
| DRAFT-resume banner | character-sheet surface (always visible until resolved), wizard inline (one-shot info strip on entry) |
|
||||
| Pathbuilder-violations banner | collapsed (default), expanded (list visible) |
|
||||
| `Stufe steigen` button on character header | enabled-no-draft (label `Stufe steigen`), enabled-with-draft (label `Stufe fortsetzen`, icon `RotateCcw`), hidden (cap or no permission) |
|
||||
| Prereq-confirm dialog | default, accepting click closes dialog and selects card |
|
||||
|
||||
---
|
||||
|
||||
## Accessibility
|
||||
|
||||
- All interactive elements have a 44×44px tap-target floor (CLAUDE.md mobile-first).
|
||||
- All buttons have `aria-label` for icon-only variants (close X, +/-, stepper dots).
|
||||
- Modals use `role="dialog"` + `aria-modal="true"` + `aria-labelledby` pointing at the header H2.
|
||||
- The stepper uses `role="navigation"` + each completed/current dot has `aria-current="step"` for current and `aria-label="{stepLabel}, abgeschlossen"` for completed.
|
||||
- Choice-cards are native `<button>` — keyboard focus + Enter/Space activates `onSelect`.
|
||||
- Prereq-confirm dialog traps focus (focus-lock) — first focusable element is `Abbrechen`, Esc closes the dialog.
|
||||
- All text passes WCAG AA contrast on `bg-bg-secondary` (`text-text-primary` `#f5f5f7` on `#1a1a1f` is 14:1; `text-text-secondary` `#a1a1a6` is 7.4:1; `text-warning-500` on `bg-warning-500/10` is 4.5:1).
|
||||
- `prefers-reduced-motion` honored (see Motion section).
|
||||
|
||||
---
|
||||
|
||||
## Spacing Scale
|
||||
|
||||
(Re-stated for the checker — see top of file for the canonical table.)
|
||||
|
||||
All spacing is `4 / 8 / 16 / 24` for Phase 1; `32` reserved for Review-step desktop. No values outside this set.
|
||||
|
||||
---
|
||||
|
||||
## Design Tokens — Inventory (for executor reference)
|
||||
|
||||
The wizard introduces **zero new design tokens**. All colors, fonts, radii, shadows are read from the existing `@theme` block in `client/src/index.css`:
|
||||
|
||||
| Token name | Used by Phase 1 |
|
||||
|------------|-----------------|
|
||||
| `--color-primary-500` | wizard accent (CTA, selected card, stepper, banner stripe, button) |
|
||||
| `--color-primary-500/40`, `--color-primary-500/20`, `--color-primary-500/10` | halos, badges, info strips |
|
||||
| `--color-bg-primary` | page background |
|
||||
| `--color-bg-secondary` | wizard modal surface |
|
||||
| `--color-bg-tertiary` | choice-card surface, banner background, info-strip background |
|
||||
| `--color-bg-elevated` | +/- step buttons in boost picker |
|
||||
| `--color-text-primary`, `--color-text-secondary`, `--color-text-muted` | text hierarchy |
|
||||
| `--color-border`, `--color-border-hover`, `--color-border-focus` | card outlines, hover, focus |
|
||||
| `--color-warning-500`, `--color-warning-500/10`, `--color-warning-500/20` | non-evaluable prereq icon, violations banner |
|
||||
| `--color-error-500`, `--color-error-500/10` | DRAFT-discard button, prereq-fail chip |
|
||||
| `--color-success-500`, `--color-success-500/10` | completed-step check, positive Δ chips |
|
||||
| `--color-info-500`, `--color-info-500/10` | "Free Archetype: aktiv" strip, "automatisch"-info strips |
|
||||
| `--radius-lg` (8px), `--radius-xl` (12px), `--radius-2xl` (16px) | rounded corners on cards / modals |
|
||||
|
||||
---
|
||||
|
||||
## Registry Safety
|
||||
|
||||
| Registry | Blocks Used | Safety Gate |
|
||||
|----------|-------------|-------------|
|
||||
| (none — hand-built primitives in `client/src/shared/components/ui/`) | `Button`, `Input`, `Card`, `Spinner`, `ActionIcon` (existing) | not applicable — code is owned in-repo, not pulled from a registry |
|
||||
| (no third-party shadcn registry declared) | — | not applicable |
|
||||
|
||||
No `npx shadcn` add operations occur in Phase 1. All wizard components are written from scratch under `client/src/features/characters/components/level-up/` (planner discretion on exact filenames; UI-SPEC dictates the component contracts above).
|
||||
|
||||
---
|
||||
|
||||
## Component File Plan (advisory — final filenames at planner's discretion)
|
||||
|
||||
The following kebab-case files implement the contracts above. The planner may merge or split, but each contract section above must be implemented somewhere:
|
||||
|
||||
- `level-up-wizard.tsx` — outer container, stepper, header, footer, motion orchestration
|
||||
- `level-up-step-class-features.tsx` — step 0 (auto-class-features summary)
|
||||
- `level-up-step-class-feature-choice.tsx` — step 0a (Cleric Doctrine, Wizard School, …)
|
||||
- `level-up-step-boost.tsx` — step 1 (4 attribute boosts)
|
||||
- `level-up-step-skill-increase.tsx` — step 2
|
||||
- `level-up-step-feat-class.tsx` — step 3
|
||||
- `level-up-step-feat-skill.tsx` — step 4
|
||||
- `level-up-step-feat-general.tsx` — step 5
|
||||
- `level-up-step-feat-ancestry.tsx` — step 6
|
||||
- `level-up-step-feat-archetype.tsx` — step 7 (Free Archetype slot)
|
||||
- `level-up-step-spellcaster.tsx` — step 8
|
||||
- `level-up-step-review.tsx` — step 9
|
||||
- `level-up-choice-card.tsx` — shared choice-card primitive
|
||||
- `level-up-prereq-confirm-dialog.tsx` — secondary z-60 dialog for D-03 prereq + abort + discard
|
||||
- `level-up-resume-banner.tsx` — D-14 banner on character-sheet
|
||||
- `level-up-violations-banner.tsx` — D-06 banner on character-sheet
|
||||
|
||||
---
|
||||
|
||||
## Checker Sign-Off
|
||||
|
||||
- [ ] Dimension 1 Copywriting: PASS
|
||||
- [ ] Dimension 2 Visuals: PASS
|
||||
- [ ] Dimension 3 Color: PASS
|
||||
- [ ] Dimension 4 Typography: PASS
|
||||
- [ ] Dimension 5 Spacing: PASS
|
||||
- [ ] Dimension 6 Registry Safety: PASS
|
||||
|
||||
**Approval:** pending
|
||||
Reference in New Issue
Block a user