diff --git a/.planning/phases/01-level-up-pf2e-regelkonform/01-UI-SPEC.md b/.planning/phases/01-level-up-pf2e-regelkonform/01-UI-SPEC.md new file mode 100644 index 0000000..d88a24e --- /dev/null +++ b/.planning/phases/01-level-up-pf2e-regelkonform/01-UI-SPEC.md @@ -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 `
{characterName}
`. +- Right: close 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 `