53 KiB
phase, slug, status, shadcn_initialized, preset, created, revised, reviewed_at
| phase | slug | status | shadcn_initialized | preset | created | revised | reviewed_at |
|---|---|---|---|---|---|---|---|
| 1 | level-up-pf2e-regelkonform | approved | false | none | 2026-04-27 | 2026-04-27 | 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 inclient/src/features/characters/components/. Where CONTEXT.md locks a behavior, the relevantD-XXdecision 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, Minus, Plus, Trash2, ArrowLeft) — 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 minimum —
h-11(Buttondefault),h-11 w-11(Buttonicon). - Choice-cards: minimum 64px tap height (so an entire card is a single touch target including its title row + meta row — exceeds 44px).
- Boost-step
+/-controls:h-11 w-11(44×44px) — see Boost-Step contract. - Stepper-dot buttons: visible dot is
h-2 w-2/h-2.5 w-2.5but the wrapping<button>ish-11 w-11with-m-1to keep the visual gap at 8–12px while the hit-zone is a genuine 44×44 — see Stepper subsection for the honest math.
Exceptions (micro-utilities for inline chip / badge / icon-stack patterns only — NOT promoted to the layout grid):
- Chip / badge padding:
px-1.5 py-0.5(6×2px) — used inside source-tag badges, source-cap chips, level chips, delta chips. These are inline glyph-sized elements, never standalone surfaces. - Inline icon offset:
mt-0.5(2px) — used to optically align an icon's baseline with its sibling text on chip/banner heads (e.g.RotateCcwin DRAFT-resume banner,AlertTrianglein violations banner). - Footer button gap:
gap-3(12px) — used in the dialog footer row only when two buttons sit at the right edge with a clear visual separation; layout-grid gaps remain on the 4/8/16/24 ladder.
These micro-values stay as Tailwind utilities and never appear on layout-level surfaces (modals, cards, sections, body padding).
Typography
The codebase has no custom font-size tokens — sizes come from Tailwind defaults. Phase 1 declares 4 sizes (12 / 14 / 18 / 24) and 2 weights to satisfy the 3-4-size, 2-weight contract.
| Role | Size | Tailwind class | Weight | Line Height | Usage |
|---|---|---|---|---|---|
| Auxiliary / Chrome | 12px | text-xs |
400 (font-normal); 600 (font-semibold) inside chips |
1.4 | Stepper labels (current/completed), meta-row chips on choice-cards (level, source, rarity, cap-erreicht, action-cost), banner sub-lines (Zuletzt bearbeitet …), card sub-lines (English-name under German-name), positive/negative delta chips in Review, footer help text in Boost-step. Two weights coexist within this size: chip labels (e.g. badge text) are 600; banner sub-lines and English-name fallbacks are 400. |
| Body | 14px | text-sm |
400 (font-normal) |
1.5 (leading-normal) |
Choice-card descriptions, prereq text, banner body, dialog body, tooltip body, Spellcaster info-strip body, Skill-row text. |
| Label | 14px | text-sm |
600 (font-semibold) |
1.4 | Choice-card titles, step-section labels, button labels, banner headings, dialog headings (when ≤ text-base). |
| 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) in font-mono. |
Total: 4 sizes (12 / 14 / 18 / 24) — within the 3-4-size limit. 2 weights: 400 (normal) and 600 (semibold). 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, text-xs for chips).
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 generic cancel surfaces. |
Accent reserved-for list (the only places primary-500 may be applied in this phase):
- Primary CTA buttons in the wizard footer:
Weiter,Bestätigen,Zurück zur Übersicht(afterÄndernin Review) —Button variant="default"(which isbg-primary-500). - Active stepper dot: the dot for the current step uses
bg-primary-500; completed dots usebg-primary-500/40; future dots usebg-bg-tertiary. - Selected choice-card border + checkmark: the chosen card (boost target, talent, skill, doctrine, …) gets
border-primary-500 ring-1 ring-primary-500/40, and aCheckicon intext-primary-500appears in the top-right corner. - DRAFT-resume banner accent stripe and the "Fortsetzen" button: the resume banner has a left-border
border-l-4 border-primary-500, body texttext-text-primary, and the "Fortsetzen" CTA isButton variant="default". - "Stufe steigen"-Button on the character-sheet header (when enabled — i.e. character is below cap and no foreign DRAFT exists):
Button variant="default"withSparklesicon. Ändernlink in Review-step Section A:text-xs text-primary-500 hover:underline— the only inline-link use of accent in the wizard.
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. (d) "+1 (Cap bei 18)" chip in Boost-step. |
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 destructive-confirm dialog. (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)"). |
Inherited Exceptions — Source-tag chroma palette
The 6 raw-hue source badges below are an inherited convention from feat-detail-modal.tsx:22-29 (existing featSourceColors map) and are intentionally exempt from the single-accent rule. They are used only for talent-source identification badges in the meta row of choice-cards — never for layout, surfaces, banner backgrounds, CTAs, or interactive accents. The selection accent (primary-500) remains the only interactive accent across the wizard.
| 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 — return-to-Review after Ändern revision |
Zurück zur Übersicht (icon: ArrowLeft on left, Button variant="default", replaces Weiter while user is revising a step entered via Ändern from Review — see Review-step contract for re-validation behavior) |
| Choice-card click result label | Wählen (only used inside the prereq-confirm dialog as "Trotzdem wählen") |
| Stepper progress label (always visible — mobile and desktop) | Schritt {n} von {m} — {currentStepLabel} (e.g. Schritt 3 von 7 — Klassentalent) |
| Confirm-dialog secondary action (cancel within a destructive-confirm dialog only) | Abbrechen (Button variant="ghost") — used only inside the destructive/prereq confirm dialogs. The wizard footer itself has no Abbrechen button; mid-flow exit happens via the header X close button or backdrop click. |
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) |
Review revalidation after Ändern cleared a later choice |
Inline note in the affected Review row: Diese Wahl wurde durch eine frühere Änderung zurückgesetzt — bitte erneut treffen. (text-xs text-warning-500) — the row's Ändern link is replaced by Wählen (text-xs text-primary-500) which jumps directly to that step. |
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 X (close) in header 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", iconRotateCcw) +Verwerfen(Button variant="ghost",text-error-500, iconTrash2).
"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-width2xl(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" aria-label="Schließen"><X className="h-5 w-5" /></Button>triggering the same backdrop-confirm logic as the backdrop click. This is the only cancel surface in the wizard chrome (the footer carries no Abbrechen button).
Stepper (always visible, between Header and Body)
Mobile-first compact dot-stepper with always-visible progress label:
- Outer container:
<nav role="navigation" aria-label="Wizard-Schritte" className="px-4 py-3 border-b border-border">. - Progress label (always visible, mobile and desktop): rendered as the first child of the stepper, above the dot row:
This guarantees that on mobile (where dot-row labels are hidden) the user always knows which step they are on. With up to 11 conditional steps this label is essential, not optional.
<div className="text-xs text-text-secondary pb-2"> Schritt {currentIdx+1} von {totalSteps} — {currentStepLabel} </div> - Dot row:
<ol className="flex items-center gap-2 overflow-x-auto">. - Each dot is wrapped in a real button so the touch target is genuine 44×44:
<button type="button" onClick={() => goToStep(idx)} disabled={idx > currentIdx} aria-label={`${stepLabel}${idx === currentIdx ? ', aktuell' : idx < currentIdx ? ', abgeschlossen' : ', noch nicht verfügbar'}`} aria-current={idx === currentIdx ? 'step' : undefined} className="h-11 w-11 -m-1 flex items-center justify-center disabled:cursor-not-allowed" > {/* visible dot */} <span className={cn('rounded-full transition-colors duration-150', dotState)} /> </button>- The
-m-1negative margin on the wrapper compensates for the 44px hit-zone so the perceived gap between dots stays at 8–12px while the tappable area is 44×44 — honest math, not a 6px-padding-claims-44px trick.
- The
- Visible-dot states (rendered inside the wrapping button):
- Future:
h-2 w-2 rounded-full bg-bg-tertiary. - Current:
h-2.5 w-2.5 rounded-full bg-primary-500. - Completed:
h-2 w-2 rounded-full bg-primary-500/40.
- Future:
- Step labels next to dots (visible
sm:and up only — desktop bonus): each completed/current dot shows its step labeltext-xs font-semibold text-text-primaryto its right; completed dots additionally render aCheckicon ath-3 w-3 text-success-500overlaid on the dot. Mobile users rely on the always-visible progress label above the row instead. - Step-label vocabulary (one of, depending on which steps apply this level):
Merkmale,Wahl,Boost,Skill,Klasse,Fertigkeit,Allgemein,Abstammung,Archetyp,Zauber,Übersicht. Conditional steps not shown. - Connector between dots:
h-px w-4 bg-bg-tertiary(bg-primary-500/40between completed pairs). The connector sits inside its own non-interactive<span>between the dot buttons. - Future steps: dot button is
disabled— no jumping forward to incomplete future steps. Completed and current steps are interactive (back-navigation only).
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-3 p-4 border-t border-border bg-bg-secondary. - Left side (contextual hint, optional): a
<span className="text-xs text-text-secondary">may renderSchritt {n}/{m}as a redundant footer hint, or be empty. The footer carries no Abbrechen button — mid-flow exit happens exclusively via the headerXor backdrop click. - 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 in the normal forward flow:
<Button variant="default" onClick={next} disabled={!stepValid}>Weiter <ChevronRight className="h-4 w-4" /></Button> - On the Review step:
<Button variant="default" onClick={commit} isLoading={isCommitting}><Check className="h-4 w-4" /> Bestätigen</Button> - When the user is on a step they re-entered via Review's
Ändernlink, the primary right button changes to<Button variant="default" onClick={returnToReview}><ArrowLeft className="h-4 w-4" /> Zurück zur Übersicht</Button>(replacesWeiteruntil the user returns to Review). See Review-step contract for the re-validation contract.
- 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):
-
Top row (
flex items-start justify-between gap-2):- Left:
<h4 className="text-sm font-semibold text-text-primary">{germanName ?? englishName}</h4>+ sub-linetext-xs text-text-mutedshowing the English name if a German name exists. - Right (icons stack): in this order, only if applicable:
Lockicon (h-4 w-4 text-text-muted) if locked (already chosen / cap reached / not eligible at this slot).AlertTriangleicon (h-4 w-4 text-warning-500) if prereq is non-evaluable (D-03) — wrapped in a tooltip showing the raw prereq string.Checkicon (h-4 w-4 text-primary-500) if currently selected.
- Left:
-
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-secondaryStufe {n}+. - Action-cost badge if applicable (uses existing
ActionIconcomponent fromclient/src/shared/components/ui/action-icon.tsx). - Rarity chip if not
Common(existing convention fromadd-feat-modal.tsx:294-302). - Cap-erreicht chip on Skill-Increase cards:
bg-warning-500/10 text-warning-500 px-1.5 py-0.5 roundedtextCap erreicht.
-
Description (truncated):
mt-2 text-sm text-text-secondary line-clamp-2. AMehrlink beneath the truncation opens the existingFeatDetailModal(or a similar detail modal for non-talent options) as a layered modal atz-60.
Locked / unavailable states
- Already chosen this Level-Up (e.g. cannot pick the same skill twice):
disabled+Lockicon + 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 abg-error-500/10 text-error-500 text-xschipVoraussetzung 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
AlertTriangleicon. Click triggers the prereq-confirm dialog with the rawprerequisitesstring. - Cap reached (skill-increase):
disabled+ warning chipCap erreicht+ tooltipDiese Fertigkeit ist auf deiner Stufe nicht weiter erhöhbar.
Selected state
- Border
border-primary-500+ haloring-1 ring-primary-500/40. - Top-right
Checkicontext-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 rows (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.
The row container is a <div>, not a <button>. Tapping the row body itself has no behavior. Only the inner +/- controls are interactive — they are real <button> elements with proper aria-labels. This avoids the invalid HTML pattern of nesting <button> inside <button> and keeps a11y/event propagation clean. The row's hover state (hover:border-border-hover) is purely a decorative styling cue, not a clickable affordance.
Per attribute row:
<li 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 className="flex-1 min-w-0">
<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>
{currentScore >= 18 && count >= 1 && (
<span className="inline-block mt-1 text-xs text-warning-500
bg-warning-500/10 px-1.5 py-0.5 rounded">
+1 (Cap bei 18)
</span>
)}
</div>
<div className="flex items-center gap-2 flex-shrink-0">
<button
type="button"
onClick={dec}
disabled={count === 0}
aria-label={`${abbreviation} Boost verringern`}
className="h-11 w-11 rounded-lg bg-bg-elevated flex items-center justify-center
disabled:opacity-40 disabled:cursor-not-allowed
hover:bg-bg-elevated/80 transition-colors"
>
<Minus className="h-5 w-5" />
</button>
<span className="text-lg font-semibold text-text-primary tabular-nums w-8 text-center">
{count}
</span>
<button
type="button"
onClick={inc}
disabled={!canIncrement}
aria-label={`${abbreviation} Boost erhöhen`}
className="h-11 w-11 rounded-lg bg-bg-elevated flex items-center justify-center
disabled:opacity-40 disabled:cursor-not-allowed
hover:bg-bg-elevated/80 transition-colors"
>
<Plus className="h-5 w-5" />
</button>
</div>
</li>
- The
+/-controls areh-11 w-11(44×44px) — meeting the 44px touch-target floor declared in the Spacing Scale. LucideMinus/Plusicons render ath-5 w-5to keep visual weight balanced inside the larger button. - Responsive note for narrow viewports (
<360px): the row layout falls back fromflex-rowtoflex-colso the+/-cluster sits below the attribute label rather than to its right. Implementation: wrap the row in a container withflex-col @[360px]:flex-row @[360px]:items-center @[360px]:justify-between(Tailwind container queries) — or, if container queries are unavailable, accept the cramped layout and ensure the row hasflex-wrap gap-3so the cluster wraps under the label rather than overlapping it. - "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 >= 18ANDcount >= 1, the inline chip+1 (Cap bei 18)renders below the score line intext-warning-500. Otherwise the row implies +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>— whenboostsSpent === 4, theWeiterbutton enables. - A boost cannot be doubled-up: max
countper 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_COLORSincharacter-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 chipCap 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-2with<Sparkles className="h-4 w-4" />icon and copyFree Archetype: aktiv+ sub-linetext-xs text-text-secondaryDu 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 includesDedication. - Nach erster Dedication (D-07 — Pathbuilder behavior): all
featType === 'Archetype'cards (any archetype).
- Vor erster Dedication: cards filtered to
- A small text below the filter-toggle row reads either
Wähle eine Dedication.orTalente 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 pressWeiterimmediately. - 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.Weiterenables whenspellsPicked === 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-1.5 py-0.5 rounded text-xs), spell-level chipGrad {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
choiceOptionsReffromClassProgressionresolves to a list of options. Each option's description is rendered in the truncated description area;Mehropens a layered detail-modal atz-60. - After picking, the meta row shows the badge
Klassenmerkmal-Wahl(bg-secondary-500/20 text-secondary-300 text-xs px-1.5 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, { revisionMode: true })}
className="text-xs text-primary-500 hover:underline"
>
Ändern
</button>
</div>
Ändern revision contract
When the user clicks Ändern on a Review row:
- Wizard navigates to that step (back-navigation).
- The wizard footer's primary right button changes from
Weiterto<Button variant="default"><ArrowLeft className="h-4 w-4" /> Zurück zur Übersicht</Button>(iconArrowLeft).Zurück(left button) still works as normal back-nav, as does direct stepper-dot tapping. - The user revises their choice on that step.
- When the user clicks
Zurück zur Übersicht, the wizard runs chain re-validation:- Walk forward through every step that comes after the revised one.
- For each later step, check whether the previously-saved choice is still valid given the new earlier choice (e.g. a Skill-Increase that depended on a now-removed boost; a Talent whose evaluable prereq now fails because of a swapped earlier feat).
- Any step whose saved choice is now invalid has its choice cleared (set back to "not yet picked").
- Wizard returns to the Review step.
- Cleared choices appear in Review Section A with the inline note
Diese Wahl wurde durch eine frühere Änderung zurückgesetzt — bitte erneut treffen.(text-xs text-warning-500), and theÄndernlink on that row is replaced by aWählenlink (text-xs text-primary-500) that jumps to the cleared step. - The
Bestätigenbutton is disabled until every Review row has a valid choice.
This contract avoids the "click Weiter through 6 steps again" trap: users always get a single explicit return path (Zurück zur Übersicht) and only re-enter steps that the chain actually invalidated.
Direct stepper-dot taps continue to work as conventional back-nav (no re-validation triggered) so users can browse without committing to a revision.
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", aSparklesicontext-primary-500is 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-3 mt-4 justify-end">withAbbrechen(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. Note: the Abbrechen ghost button only appears inside these confirm dialogs. The wizard chrome itself never shows a top-level Abbrechen button.
Component Contract — DRAFT-Resume Banner (D-14)
Two surfaces share this banner:
- 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.
- Wizard entry — when the user clicks
Stufe steigenand 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 bodyEntwurf 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:
-
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 noxtranslation and a 0.1s opacity-only fade. (Implemented viauseReducedMotion()hook from framer-motion.)
- Wrapper:
-
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 (decorative only — row is a <div>), +/- 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), revision-mode (Zurück zur Übersicht replaces Weiter), committing (isLoading on Bestätigen) |
| Stepper dot button | future (disabled), current (aria-current="step"), completed (interactive, back-nav), all wrapped at 44×44 hit-zone |
| 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 |
| Review row | normal (with Ändern link), revalidation-cleared (with warning note + Wählen link), all-valid (Bestätigen enabled) |
Accessibility
- All interactive elements have a 44×44px tap-target floor (CLAUDE.md mobile-first). The boost
+/-controls and the stepper-dot wrappers each meet this floor explicitly (h-11 w-11). - All buttons have
aria-labelfor icon-only variants:- Header close button:
aria-label="Schließen". - Boost
+:aria-label="{abbreviation} Boost erhöhen"(e.g.STR Boost erhöhen). - Boost
-:aria-label="{abbreviation} Boost verringern". - Stepper dot buttons:
aria-label="{stepLabel}, {state}"where state ∈aktuell/abgeschlossen/noch nicht verfügbar.
- Header close button:
- Modals use
role="dialog"+aria-modal="true"+aria-labelledbypointing at the header H2. - The stepper uses
role="navigation"+ each completed/current dot hasaria-current="step"for current. - Choice-cards are native
<button>— keyboard focus + Enter/Space activatesonSelect. - The Boost-step row is a
<li>within a<ul>, never a<button>— the only interactive children are the+/-buttons. No nested-button HTML. - 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#f5f5f7on#1a1a1fis 14:1;text-text-secondary#a1a1a6is 7.4:1;text-warning-500onbg-warning-500/10is 4.5:1). prefers-reduced-motionhonored (see Motion section).
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, Ändern link, Zurück zur Übersicht) |
--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, "+1 (Cap bei 18)" chip, revalidation-cleared note |
--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 orchestrationlevel-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 2level-up-step-feat-class.tsx— step 3level-up-step-feat-skill.tsx— step 4level-up-step-feat-general.tsx— step 5level-up-step-feat-ancestry.tsx— step 6level-up-step-feat-archetype.tsx— step 7 (Free Archetype slot)level-up-step-spellcaster.tsx— step 8level-up-step-review.tsx— step 9level-up-choice-card.tsx— shared choice-card primitivelevel-up-prereq-confirm-dialog.tsx— secondary z-60 dialog for D-03 prereq + abort + discardlevel-up-resume-banner.tsx— D-14 banner on character-sheetlevel-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