diff --git a/.planning/phases/13-design-system-foundation/13-01-PLAN.md b/.planning/milestones/v3.0-phases/13-design-system-foundation/13-01-PLAN.md
similarity index 100%
rename from .planning/phases/13-design-system-foundation/13-01-PLAN.md
rename to .planning/milestones/v3.0-phases/13-design-system-foundation/13-01-PLAN.md
diff --git a/.planning/phases/13-design-system-foundation/13-01-SUMMARY.md b/.planning/milestones/v3.0-phases/13-design-system-foundation/13-01-SUMMARY.md
similarity index 100%
rename from .planning/phases/13-design-system-foundation/13-01-SUMMARY.md
rename to .planning/milestones/v3.0-phases/13-design-system-foundation/13-01-SUMMARY.md
diff --git a/.planning/phases/13-design-system-foundation/13-02-PLAN.md b/.planning/milestones/v3.0-phases/13-design-system-foundation/13-02-PLAN.md
similarity index 100%
rename from .planning/phases/13-design-system-foundation/13-02-PLAN.md
rename to .planning/milestones/v3.0-phases/13-design-system-foundation/13-02-PLAN.md
diff --git a/.planning/phases/13-design-system-foundation/13-02-SUMMARY.md b/.planning/milestones/v3.0-phases/13-design-system-foundation/13-02-SUMMARY.md
similarity index 100%
rename from .planning/phases/13-design-system-foundation/13-02-SUMMARY.md
rename to .planning/milestones/v3.0-phases/13-design-system-foundation/13-02-SUMMARY.md
diff --git a/.planning/phases/13-design-system-foundation/13-03-PLAN.md b/.planning/milestones/v3.0-phases/13-design-system-foundation/13-03-PLAN.md
similarity index 100%
rename from .planning/phases/13-design-system-foundation/13-03-PLAN.md
rename to .planning/milestones/v3.0-phases/13-design-system-foundation/13-03-PLAN.md
diff --git a/.planning/phases/13-design-system-foundation/13-03-SUMMARY.md b/.planning/milestones/v3.0-phases/13-design-system-foundation/13-03-SUMMARY.md
similarity index 100%
rename from .planning/phases/13-design-system-foundation/13-03-SUMMARY.md
rename to .planning/milestones/v3.0-phases/13-design-system-foundation/13-03-SUMMARY.md
diff --git a/.planning/phases/13-design-system-foundation/13-04-PLAN.md b/.planning/milestones/v3.0-phases/13-design-system-foundation/13-04-PLAN.md
similarity index 100%
rename from .planning/phases/13-design-system-foundation/13-04-PLAN.md
rename to .planning/milestones/v3.0-phases/13-design-system-foundation/13-04-PLAN.md
diff --git a/.planning/phases/13-design-system-foundation/13-04-SUMMARY.md b/.planning/milestones/v3.0-phases/13-design-system-foundation/13-04-SUMMARY.md
similarity index 100%
rename from .planning/phases/13-design-system-foundation/13-04-SUMMARY.md
rename to .planning/milestones/v3.0-phases/13-design-system-foundation/13-04-SUMMARY.md
diff --git a/.planning/phases/13-design-system-foundation/13-05-PLAN.md b/.planning/milestones/v3.0-phases/13-design-system-foundation/13-05-PLAN.md
similarity index 100%
rename from .planning/phases/13-design-system-foundation/13-05-PLAN.md
rename to .planning/milestones/v3.0-phases/13-design-system-foundation/13-05-PLAN.md
diff --git a/.planning/phases/13-design-system-foundation/13-05-SUMMARY.md b/.planning/milestones/v3.0-phases/13-design-system-foundation/13-05-SUMMARY.md
similarity index 100%
rename from .planning/phases/13-design-system-foundation/13-05-SUMMARY.md
rename to .planning/milestones/v3.0-phases/13-design-system-foundation/13-05-SUMMARY.md
diff --git a/.planning/phases/13-design-system-foundation/13-CONTEXT.md b/.planning/milestones/v3.0-phases/13-design-system-foundation/13-CONTEXT.md
similarity index 100%
rename from .planning/phases/13-design-system-foundation/13-CONTEXT.md
rename to .planning/milestones/v3.0-phases/13-design-system-foundation/13-CONTEXT.md
diff --git a/.planning/phases/13-design-system-foundation/13-DISCUSSION-LOG.md b/.planning/milestones/v3.0-phases/13-design-system-foundation/13-DISCUSSION-LOG.md
similarity index 100%
rename from .planning/phases/13-design-system-foundation/13-DISCUSSION-LOG.md
rename to .planning/milestones/v3.0-phases/13-design-system-foundation/13-DISCUSSION-LOG.md
diff --git a/.planning/milestones/v3.0-phases/13-design-system-foundation/13-PATTERNS.md b/.planning/milestones/v3.0-phases/13-design-system-foundation/13-PATTERNS.md
new file mode 100644
index 0000000..a2754b9
--- /dev/null
+++ b/.planning/milestones/v3.0-phases/13-design-system-foundation/13-PATTERNS.md
@@ -0,0 +1,1084 @@
+# Phase 13: Design System Foundation - Pattern Map
+
+**Mapped:** 2026-05-16
+**Files analyzed:** 22 (new/modified files across all waves)
+**Analogs found:** 22 / 22 (all from go-backend reference implementation)
+
+---
+
+## File Classification
+
+| New/Modified File | Role | Data Flow | Closest Analog | Match Quality |
+|-------------------|------|-----------|----------------|---------------|
+| `backend/internal/web/ui/base.css` | config | static | `go-backend/internal/web/ui/base.css` | exact (verbatim port) |
+| `backend/internal/web/ui/button.css` | config | static | `go-backend/internal/web/ui/button.css` | exact (replace) |
+| `backend/internal/web/ui/badge.css` | config | static | `go-backend/internal/web/ui/badge.css` | exact (replace) |
+| `backend/internal/web/ui/card.css` | config | static | `go-backend/internal/web/ui/card.css` | exact (replace) |
+| `backend/internal/web/ui/input.css` | config | static | `go-backend/internal/web/ui/input.css` | exact (new file) |
+| `backend/internal/web/ui/textarea.css` | config | static | `go-backend/internal/web/ui/textarea.css` | exact (new file) |
+| `backend/internal/web/ui/select.css` | config | static | `go-backend/internal/web/ui/select.css` | exact (new file) |
+| `backend/internal/web/ui/modal.css` | config | static | `go-backend/internal/web/ui/modal.css` | exact (new file) |
+| `backend/internal/web/ui/empty-state.css` | config | static | `go-backend/internal/web/ui/empty-state.css` | exact (new file) |
+| `backend/internal/web/ui/table.css` | config | static | `go-backend/internal/web/ui/table.css` | exact (new file) |
+| `backend/internal/web/ui/icon-button.css` | config | static | `go-backend/internal/web/ui/icon-button.css` | exact (new file) |
+| `backend/internal/web/ui/form-field.css` | config | static | `go-backend/internal/web/ui/form-field.css` | exact (new file) |
+| `backend/internal/web/ui/spacing.css` | config | static | `go-backend/internal/web/ui/spacing.css` | exact (new file) |
+| `backend/tailwind.input.css` | config | static | current file + RESEARCH.md manifest | exact |
+| `backend/internal/web/ui/variants.go` | utility | transform | `go-backend/internal/web/ui/variants.go` | exact (extend) |
+| `backend/internal/web/ui/helpers.go` | utility | transform | `go-backend/internal/web/ui/helpers.go` | exact (extend) |
+| `backend/internal/web/ui/button.templ` | component | request-response | `go-backend/internal/web/ui/button.templ` | exact |
+| `backend/internal/web/ui/card.templ` | component | request-response | `go-backend/internal/web/ui/card.templ` | exact (breaking API change) |
+| `backend/internal/web/ui/input.templ` | component | request-response | `go-backend/internal/web/ui/input.templ` | exact |
+| `backend/internal/web/ui/textarea.templ` | component | request-response | `go-backend/internal/web/ui/textarea.templ` | exact (new file) |
+| `backend/internal/web/ui/select.templ` | component | event-driven | `go-backend/internal/web/ui/select.templ` | exact (new file) |
+| `backend/internal/web/ui/select_helpers.go` | utility | transform | `go-backend/internal/web/ui/select_helpers.go` | exact (new file) |
+| `backend/internal/web/ui/modal.templ` | component | request-response | `go-backend/internal/web/ui/modal.templ` | exact (new file) |
+| `backend/internal/web/ui/empty_state.templ` | component | request-response | `go-backend/internal/web/ui/empty_state.templ` | exact (new file) |
+| `backend/internal/web/ui/table.templ` | component | request-response | `go-backend/internal/web/ui/table.templ` | exact (new file) |
+| `backend/internal/web/ui/icon_button.templ` | component | request-response | `go-backend/internal/web/ui/icon_button.templ` | exact (new file) |
+| `backend/internal/web/ui/form_field.templ` | component | request-response | `go-backend/internal/web/ui/form_field.templ` | exact (new file) |
+| `backend/internal/web/ui/space.templ` | component | request-response | `go-backend/internal/web/ui/space.templ` | exact (new file) |
+| `backend/internal/web/ui/ui_test.go` | test | transform | `go-backend/internal/web/ui/ui_test.go` | exact (extend) |
+| `backend/internal/web/ui/catalog/catalog.templ` | component | request-response | `go-backend/internal/web/ui/catalog/catalog.templ` | role-match (adapted) |
+| `backend/internal/web/ui/catalog/examples.go` | utility | transform | `go-backend/internal/web/ui/catalog/examples.go` | role-match (adapted) |
+| `backend/internal/web/catalog_route_catalog.go` | middleware | request-response | `backend/internal/web/router.go` | role-match |
+| `backend/internal/web/catalog_route_stub.go` | middleware | request-response | build-tag stub pattern (RESEARCH.md) | role-match |
+| `backend/internal/web/ui/auth.css` | config | static | `backend/internal/web/ui/button.css` lines 121–180 | exact (extract) |
+
+---
+
+## Pattern Assignments
+
+### Wave 1: Token and Enum Foundation
+
+---
+
+### `backend/internal/web/ui/base.css` (config, static)
+
+**Analog:** `go-backend/internal/web/ui/base.css`
+**Action:** Replace the 28-line stub entirely. Port the 223-line `:root` block verbatim.
+
+**Current stub pattern** (`backend/internal/web/ui/base.css` lines 1–28):
+```css
+/* 28-line stub — no tokens, only box-sizing reset and body defaults */
+*, *::before, *::after { box-sizing: border-box; }
+body { margin: 0; font-family: system-ui; color: #0f172a; background-color: #ffffff; }
+:focus-visible { outline: 2px solid #2563eb; outline-offset: 2px; }
+```
+
+**Target pattern** (`go-backend/internal/web/ui/base.css` lines 1–60, first section only):
+```css
+:root {
+ /* Text */
+ --color-text-primary: hsl(0 0% 9%);
+ --color-text-secondary: #475467;
+ --color-text-muted: hsl(0 0% 43.5%);
+ --color-text-faint: #9ca3af;
+ --color-text-inverse: #ffffff;
+ --color-text-brand: #804eec;
+ /* ... all 223 lines verbatim ... */
+
+ /* Surfaces */
+ --color-surface-page: hsl(0 0% 100%);
+ --color-surface-default: #ffffff;
+
+ /* Borders */
+ --color-border-default: hsl(0 0% 90.9%);
+
+ /* Brand and focus */
+ --color-brand-primary: #804eec;
+ --color-focus-ring: rgba(124, 58, 237, 0.2);
+}
+```
+
+**Critical rule:** D-T02 — port ALL 223 lines, not just a subset. D-T03 — replace entirely, do not merge.
+
+---
+
+### `backend/internal/web/ui/variants.go` (utility, transform)
+
+**Analog:** `go-backend/internal/web/ui/variants.go`
+
+**Current pattern** (`backend/internal/web/ui/variants.go` lines 1–106):
+- Exported `ButtonClass()`, `BadgeClass()`, `NormalizedButtonVariant()` etc.
+- `ButtonClass` produces compound class: `"ui-button ui-button-solid-default-md"` (line 96)
+- Missing: `IconButtonVariant`, `IconButtonTone`, `SpacingStep` enums
+- Missing: `ButtonVariantGhost`, `BadgeVariantPrimary` constants
+
+**Target multi-class pattern** (`go-backend/internal/web/ui/variants.go` lines 63–64):
+```go
+func buttonClass(variant ButtonVariant, tone ButtonTone, size Size) string {
+ return "ui-button ui-button-" + string(normalizedButtonTone(tone)) +
+ " ui-button-" + string(normalizedButtonVariant(variant)) +
+ " ui-button-" + string(normalizedSize(size))
+}
+// Output: "ui-button ui-button-solid ui-button-default ui-button-md"
+```
+
+**New enums to add** (`go-backend/internal/web/ui/variants.go` lines 28–51):
+```go
+type IconButtonVariant string
+const (
+ IconButtonVariantNeutral IconButtonVariant = "neutral"
+ IconButtonVariantWarning IconButtonVariant = "warning"
+ IconButtonVariantSuccess IconButtonVariant = "success"
+ IconButtonVariantDanger IconButtonVariant = "danger"
+)
+type IconButtonTone string
+const (
+ IconButtonToneSolid IconButtonTone = "solid"
+ IconButtonToneGhost IconButtonTone = "ghost"
+)
+type SpacingStep string
+const (
+ SpacingStepXS SpacingStep = "xs"
+ SpacingStepSM SpacingStep = "sm"
+ SpacingStepMD SpacingStep = "md"
+ SpacingStepLG SpacingStep = "lg"
+ SpacingStepXL SpacingStep = "xl"
+)
+```
+
+**New variant constants to add:**
+```go
+// Add to ButtonVariant block:
+ButtonVariantGhost ButtonVariant = "ghost"
+
+// Add to BadgeVariant block:
+BadgeVariantPrimary BadgeVariant = "primary"
+```
+
+**New class functions to add** (`go-backend/internal/web/ui/variants.go` lines 67–88):
+```go
+func IconButtonClass(variant IconButtonVariant, tone IconButtonTone) string {
+ normalizedVariant := NormalizedIconButtonVariant(variant)
+ switch NormalizedIconButtonTone(tone) {
+ case IconButtonToneGhost:
+ return "borderless-icon-button ui-icon-button-ghost ui-icon-button-" + string(normalizedVariant)
+ default:
+ return "ui-icon-button ui-icon-button-solid ui-icon-button-" + string(normalizedVariant)
+ }
+}
+func SpaceXClass(step SpacingStep) string {
+ return "ui-space-x ui-space-x-" + string(NormalizedSpacingStep(step))
+}
+func SpaceYClass(step SpacingStep) string {
+ return "ui-space-y ui-space-y-" + string(NormalizedSpacingStep(step))
+}
+```
+
+**Updated normalizer for Ghost variant:**
+```go
+func NormalizedButtonVariant(variant ButtonVariant) ButtonVariant {
+ switch variant {
+ case ButtonVariantNeutral, ButtonVariantWarning, ButtonVariantSuccess, ButtonVariantDanger, ButtonVariantGhost:
+ return variant
+ default:
+ return ButtonVariantDefault
+ }
+}
+```
+
+**Naming convention:** Backend uses exported names (`ButtonClass`, `NormalizedButtonVariant`) while go-backend uses unexported (`buttonClass`, `normalizedButtonVariant`). Keep the exported naming for backend consistency.
+
+---
+
+### `backend/internal/web/ui/helpers.go` (utility, transform)
+
+**Analog:** `go-backend/internal/web/ui/helpers.go`
+
+**Current pattern** (`backend/internal/web/ui/helpers.go` lines 1–16):
+- `mergeAttrs(base, override templ.Attributes) templ.Attributes` — keep this
+
+**New functions to add** (`go-backend/internal/web/ui/helpers.go` lines 1–31):
+```go
+func buttonType(value string) string {
+ if value == "" {
+ return "button"
+ }
+ return value
+}
+func inputType(value string) string {
+ if value == "" {
+ return "text"
+ }
+ return value
+}
+func inputID(id string, name string) string {
+ if id != "" {
+ return id
+ }
+ return name
+}
+func textareaRows(rows int) string {
+ if rows <= 0 {
+ rows = 4
+ }
+ return strconv.Itoa(rows)
+}
+```
+
+**Import to add:** `"strconv"` (for `textareaRows`)
+
+---
+
+### Wave 2: Migrate Existing Components
+
+---
+
+### `backend/internal/web/ui/auth.css` (config, static — NEW prerequisite file)
+
+**Analog:** `backend/internal/web/ui/button.css` lines 121–180 (auth-provider block to extract)
+
+**Reason:** Before replacing `button.css` entirely with go-backend's version, the auth-provider selectors (`.auth-provider-button`, `.auth-provider-separator`, etc.) must be moved to a standalone `auth.css` file. Go-backend has no equivalent — this is backend-only CSS from Phase 8.
+
+**Extract pattern** (lines 121–180 of current `backend/internal/web/ui/button.css`):
+```css
+/* auth.css — sign-in provider controls, extracted from button.css in Phase 13 */
+.auth-provider-stack { ... }
+.auth-provider-button { ... }
+.auth-provider-button:hover { ... }
+.auth-provider-button:focus-visible { ... }
+.auth-provider-button-disabled { ... }
+.auth-provider-separator { ... }
+.auth-provider-separator span { ... }
+.auth-provider-separator em { ... }
+```
+
+Then add `@import "./internal/web/ui/auth.css";` to `tailwind.input.css` to preserve login page styling.
+
+---
+
+### `backend/internal/web/ui/button.css` (config, static — REPLACE)
+
+**Analog:** `go-backend/internal/web/ui/button.css`
+
+**Multi-class selector pattern** (`go-backend/internal/web/ui/button.css` lines 1–163):
+```css
+/* Base button */
+.ui-button {
+ align-items: center;
+ border: 0;
+ border-radius: 0.35rem;
+ cursor: pointer;
+ display: inline-flex;
+ font-weight: 600;
+ gap: 0.5rem;
+ justify-content: center;
+ line-height: 1;
+ min-height: 44px;
+ text-decoration: none;
+ transition: background-color 0.2s ease, color 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease;
+}
+.ui-button-icon, .ui-button-icon svg { height: 1rem; width: 1rem; }
+.ui-button:focus-visible { box-shadow: 0 0 0 3px var(--color-focus-ring); outline: none; }
+
+/* Sizes — standalone, apply to all variants */
+.ui-button-sm { font-size: 0.875rem; min-height: 40px; padding: 0.625rem 0.9rem; }
+.ui-button-md { font-size: 0.95rem; padding: 0.7rem 1rem; }
+.ui-button-lg { font-size: 1rem; padding: 0.82rem 1.15rem; }
+
+/* Solid+variant compound selectors */
+.ui-button-solid.ui-button-default { background: var(--color-brand-primary); color: var(--color-text-inverse); }
+.ui-button-solid.ui-button-default:hover { background: var(--color-brand-primary-hover); }
+/* ... all tone+variant combinations from go-backend ... */
+.ui-button-soft.ui-button-danger { background: var(--color-status-danger-soft-bg-alt); color: var(--color-status-danger-soft-foreground-strong); }
+```
+
+**Ghost variant to ADD** (not in go-backend — authored new per RESEARCH.md):
+```css
+/* Ghost variant — new in Phase 13, D-CA01 */
+.ui-button-ghost {
+ background: transparent;
+ color: var(--color-brand-primary);
+}
+.ui-button-ghost:hover { background: var(--color-surface-brand-soft); }
+.ui-button-ghost:focus-visible { box-shadow: 0 0 0 3px var(--color-focus-ring); outline: none; }
+```
+
+**CRITICAL: No nesting rule.** Both the current and go-backend files use flat top-level selectors. `.ui-button-solid.ui-button-default` is a compound selector (two classes on the same element) — not nesting.
+
+---
+
+### `backend/internal/web/ui/button.templ` (component, request-response — UPDATE)
+
+**Analog:** `go-backend/internal/web/ui/button.templ`
+
+**Current pattern** (`backend/internal/web/ui/button.templ` lines 1–23):
+```go
+type ButtonProps struct {
+ Label string
+ Variant ButtonVariant
+ Tone ButtonTone
+ Size Size
+ Type string
+ Attrs templ.Attributes
+}
+templ Button(props ButtonProps) {
+ {{ btnType := props.Type }}
+ if btnType == "" { {{ btnType = "button" }} }
+ {{ class := ButtonClass(props.Variant, props.Tone, props.Size) }}
+
+}
+```
+
+**Target pattern** (`go-backend/internal/web/ui/button.templ` lines 1–22):
+```go
+type ButtonProps struct {
+ Label string
+ Variant ButtonVariant
+ Tone ButtonTone
+ Size Size
+ Type string
+ Icon string // NEW — added from go-backend
+ Attrs templ.Attributes
+}
+templ Button(props ButtonProps) {
+
+}
+```
+
+**Notes:**
+- Switch from inline `{{ btnType := ... }}` to `buttonType()` helper from `helpers.go`
+- `ButtonClass()` output changes from compound to multi-class (via variants.go update)
+- Do NOT add `Disabled bool` — use `Attrs: templ.Attributes{"disabled": true}` per RESEARCH.md
+
+---
+
+### Template hardcode migration (4 files)
+
+**Files with old compound class strings:**
+- `backend/templates/planning.templ` — lines 16, 20, 22, 25
+- `backend/templates/tasks.templ` — lines 158, 375
+- `backend/templates/events.templ` — lines 23, 32
+- `backend/templates/etapes.templ` — lines 57, 65, 77, 86, 93
+
+**Old → new class string mapping:**
+```
+"ui-button ui-button-solid-default-md" → "ui-button ui-button-solid ui-button-default ui-button-md"
+"ui-button ui-button-soft-neutral-md" → "ui-button ui-button-soft ui-button-neutral ui-button-md"
+"ui-button ui-button-soft-danger-md" → "ui-button ui-button-soft ui-button-danger ui-button-md"
+```
+
+**Pattern for additional Tailwind utility classes** (tasks.templ, etapes.templ have extras like `flex-shrink-0`, `text-xs`):
+Keep the extra classes; replace only the `ui-button-*` part:
+```
+class="ui-button ui-button-soft-danger-md flex-shrink-0 text-xs"
+→
+class="ui-button ui-button-soft ui-button-danger ui-button-md flex-shrink-0 text-xs"
+```
+
+---
+
+### `backend/internal/web/ui/badge.css` (config, static — REPLACE)
+
+**Analog:** `go-backend/internal/web/ui/badge.css`
+
+**Target pattern** (`go-backend/internal/web/ui/badge.css` lines 1–33):
+```css
+.ui-badge {
+ border: 1px solid transparent;
+ border-radius: 999px; /* pill shape — different from current */
+ display: inline-flex;
+ font-size: 0.75rem;
+ font-weight: 600;
+ line-height: 1.2;
+ padding: 0.3rem 0.75rem;
+}
+.ui-badge-info { background: var(--color-status-info-soft-bg); border-color: var(--color-status-info-soft-border); color: var(--color-status-info-foreground); }
+.ui-badge-warning { background: var(--color-status-warning-soft-bg); border-color: var(--color-status-warning-soft-border); color: var(--color-status-warning-foreground); }
+.ui-badge-success { background: var(--color-status-success-soft-bg); border-color: var(--color-status-success-soft-border); color: var(--color-status-success-foreground); }
+.ui-badge-danger { background: var(--color-status-danger-soft-bg); border-color: var(--color-status-danger-soft-border); color: var(--color-status-danger-foreground); }
+```
+
+**Primary variant to ADD** (not in go-backend — authored new per D-CA02):
+```css
+.ui-badge-primary {
+ background: var(--color-surface-brand-soft);
+ border-color: rgba(128, 78, 236, 0.3);
+ color: var(--color-text-brand);
+}
+```
+
+---
+
+### `backend/internal/web/ui/card.css` (config, static — REPLACE)
+
+**Analog:** `go-backend/internal/web/ui/card.css`
+
+**Target pattern** (`go-backend/internal/web/ui/card.css` lines 1–27):
+```css
+.ui-card { background: var(--color-surface-default); border: 1px solid var(--color-border-default); border-radius: 1rem; box-shadow: var(--shadow-surface-md); }
+.ui-card-header, .ui-card-body, .ui-card-footer { padding: 1.25rem 1.5rem; }
+.ui-card-header, .ui-card-footer { border-color: var(--color-border-default); }
+.ui-card-header { border-bottom-style: solid; border-bottom-width: 1px; }
+.ui-card-footer { border-top-style: solid; border-top-width: 1px; }
+```
+
+---
+
+### `backend/internal/web/ui/card.templ` (component, request-response — BREAKING UPDATE)
+
+**Analog:** `go-backend/internal/web/ui/card.templ`
+
+**Current pattern** (`backend/internal/web/ui/card.templ` lines 1–11) — children-based:
+```go
+templ Card(attrs templ.Attributes) {
+
+}
+```
+
+**Target pattern** (`go-backend/internal/web/ui/card.templ` lines 1–27) — typed fields:
+```go
+type CardProps struct {
+ Header templ.Component
+ Body templ.Component
+ Footer templ.Component
+}
+templ Card(props CardProps) {
+
+ if props.Header != nil {
+
+ }
+ if props.Body != nil {
+ @props.Body
+ }
+ if props.Footer != nil {
+
+ }
+
+}
+```
+
+**Call site impact:** No production templates call `Card` with children — the only breakage is `TestCard_RendersChildren` in `ui_test.go` (see test section below).
+
+---
+
+### Wave 3: New CSS + templ Files
+
+---
+
+### `backend/internal/web/ui/input.css` (config, static — NEW)
+
+**Analog:** `go-backend/internal/web/ui/input.css` (port verbatim, strip any page-level selectors)
+
+```css
+.ui-input {
+ appearance: none;
+ background: var(--color-surface-default);
+ border: 1px solid var(--color-border-default);
+ border-radius: 0.75rem;
+ color: var(--color-text-primary);
+ font: inherit;
+ line-height: 1.4;
+ min-height: 44px;
+ padding: 0.75rem 0.95rem;
+ width: 100%;
+}
+.ui-input::placeholder { color: var(--color-text-faint); }
+.ui-input:focus { border-color: var(--color-brand-focus); box-shadow: 0 0 0 3px var(--color-focus-ring-strong); outline: none; }
+```
+
+---
+
+### `backend/internal/web/ui/input.templ` (component, request-response — UPDATE existing stub)
+
+**Analog:** `go-backend/internal/web/ui/input.templ`
+
+**Target pattern** (`go-backend/internal/web/ui/input.templ` lines 1–22):
+```go
+type InputProps struct {
+ ID string
+ Name string
+ Value string
+ Placeholder string
+ Type string
+ Attrs templ.Attributes
+}
+templ Input(props InputProps) {
+
+}
+```
+
+**Note on `Disabled`/`Required`:** Go-backend does NOT have these as explicit fields — pass via `Attrs: templ.Attributes{"disabled": true}`. The UI-SPEC allows adding them as explicit fields (D-CA03 is Claude's discretion). Planner may choose to add them with `if props.Disabled { disabled }` conditionals per RESEARCH.md Props detail section.
+
+---
+
+### `backend/internal/web/ui/textarea.templ` (component, request-response — NEW FILE)
+
+**Analog:** `go-backend/internal/web/ui/textarea.templ`
+
+**Pattern** (`go-backend/internal/web/ui/textarea.templ` lines 1–21):
+```go
+type TextareaProps struct {
+ ID string
+ Name string
+ Value string
+ Placeholder string
+ Rows int
+ Attrs templ.Attributes
+}
+templ Textarea(props TextareaProps) {
+
+}
+```
+
+**Helpers used:** `inputID()` and `textareaRows()` from `helpers.go` (add these in Wave 1).
+
+---
+
+### `backend/internal/web/ui/textarea.css` (config, static — NEW FILE)
+
+Port verbatim from go-backend. File mirrors input.css styling with `min-height: 7rem` and `resize: vertical`.
+
+---
+
+### `backend/internal/web/ui/select.templ` + `select_helpers.go` (component, event-driven — NEW FILES)
+
+**Analog:** `go-backend/internal/web/ui/select.templ` + `go-backend/internal/web/ui/select_helpers.go`
+
+**Props pattern** (`go-backend/internal/web/ui/select.templ` lines 1–18):
+```go
+type SelectOption struct {
+ Value string
+ Label string
+ Disabled bool
+}
+type SelectProps struct {
+ ID string
+ Name string
+ Placeholder string
+ Value string
+ Values []string
+ Multiple bool
+ Options []SelectOption
+ Attrs templ.Attributes
+}
+```
+
+**Inline script pattern** (`go-backend/internal/web/ui/select.templ` lines 114–249):
+The `