diff --git a/.planning/phases/11-polish-responsiveness/11-RESEARCH.md b/.planning/phases/11-polish-responsiveness/11-RESEARCH.md
new file mode 100644
index 0000000..b773d10
--- /dev/null
+++ b/.planning/phases/11-polish-responsiveness/11-RESEARCH.md
@@ -0,0 +1,565 @@
+# Phase 11: Polish & Responsiveness - Research
+
+**Researched:** 2026-04-01
+**Domain:** Tailwind v4 responsive utilities, CSS animations, focus-visible, scroll-into-view
+**Confidence:** HIGH
+
+## Summary
+
+Phase 11 is a pure CSS/layout polish phase — four independent requirements, zero new runtime dependencies. Every change is either adding Tailwind responsive prefixes (`sm:`, absence of prefix = mobile-first), adding `focus-visible:` utility classes, adding a CSS keyframe animation in `index.css`, or calling `element.scrollIntoView()` from a `react-hook-form` submit handler.
+
+The technical risk is very low. Tailwind v4 retains the same responsive prefix syntax from v3 (`sm:`, `md:`, `lg:`). The `focus-visible:` variant is natively supported in Tailwind v4. CSS `@keyframes` and `@media (prefers-reduced-motion)` are standard CSS features, not Tailwind-specific. The auto-scroll requirement (`scrollIntoView`) uses a browser-native DOM API that is supported in all modern browsers and works correctly in jsdom for tests.
+
+The only structural decision with wider impact is step transitions (POLISH-03): adding a CSS animation class to the step content wrapper in `App.tsx` (`WizardShell`). Since the step container is a single div that re-renders its child on step change, a CSS animation triggered by a React key or className update is the correct approach. The `prefers-reduced-motion` guard belongs in `index.css` via `@media (prefers-reduced-motion: reduce)` — consistent with the existing theme transition guard already in the file.
+
+**Primary recommendation:** Implement as four independent tasks in this order: (1) POLISH-01 mobile responsive layout, (2) POLISH-02 focus-visible indicators, (3) POLISH-03 step transition animation, (4) POLISH-04 auto-scroll to first error. Each task touches different files with zero cross-task dependencies.
+
+---
+
+
+## Phase Requirements
+
+| ID | Description | Research Support |
+|----|-------------|-----------------|
+| POLISH-01 | Wizard layout adapts to mobile screens — backend cards stack, form fields go full-width, step indicator collapses, buttons stretch | Tailwind v4 mobile-first responsive prefixes; `BackendCard` grid needs `flex-col` default with `sm:grid-cols-2` or keep `flex-col` stacked; `StepIndicator` label text visibility toggle; buttons need `w-full sm:w-auto` |
+| POLISH-02 | All interactive elements have visible MD3 focus indicators (3px outline) using `focus-visible` for keyboard navigation | `focus-visible:outline` + `focus-visible:outline-3` or `focus-visible:ring-3`; Tailwind v4 supports arbitrary ring widths; apply to buttons, BackendCard, select, tooltip buttons, ThemeToggle, StepIndicator back-nav buttons |
+| POLISH-03 | Step transitions use subtle fade/slide animation that respects `prefers-reduced-motion` | CSS `@keyframes step-in` in `index.css`; apply via Tailwind `animate-step-in` custom utility or inline className; `@media (prefers-reduced-motion: reduce)` guard in CSS |
+| POLISH-04 | On validation failure, the view auto-scrolls to the first errored field | `react-hook-form` `useForm` returns `formState.errors`; in `handleSubmit` error callback (second argument), find first error field's DOM element with `document.getElementById(fieldKey)` and call `.scrollIntoView({ behavior: 'smooth', block: 'center' })` |
+
+
+---
+
+## Standard Stack
+
+### Core
+| Library | Version | Purpose | Why Standard |
+|---------|---------|---------|--------------|
+| Tailwind v4 | 4.2.2 | Responsive prefixes, focus-visible variant, custom animations | Already in project; v4 syntax is mobile-first identical to v3 |
+| React | 18.3.1 | Component key trick for animation reset | Already in project |
+| Browser DOM API | native | `scrollIntoView()` for POLISH-04 | No dependency needed |
+
+### Supporting
+| Library | Version | Purpose | When to Use |
+|---------|---------|---------|-------------|
+| react-hook-form | 7.72.0 | Error callback for auto-scroll | Already used in `BackendSelectionStep` and `RemoteConfigStep`; second argument to `handleSubmit` is the error handler |
+| Vitest + @testing-library/react | 4.1.1 / 16.3.2 | Unit tests for all behavioral changes | Existing test infrastructure |
+
+**No new dependencies.** Zero new runtime packages for this phase.
+
+**Installation:** None required.
+
+---
+
+## Architecture Patterns
+
+### Recommended Project Structure
+```
+src/
+├── index.css # Add @keyframes step-in + @media prefers-reduced-motion guard
+├── App.tsx # Add animation class to step content wrapper (POLISH-03)
+├── components/
+│ ├── wizard/
+│ │ ├── BackendSelectionStep.tsx # Responsive button row (POLISH-01), focus-visible on cards (POLISH-02), auto-scroll (POLISH-04)
+│ │ ├── RemoteConfigStep.tsx # Auto-scroll to first error (POLISH-04), responsive button row (POLISH-01)
+│ │ ├── DeploymentStep.tsx # Responsive button row (POLISH-01)
+│ │ ├── ReviewStep.tsx # Responsive button row (POLISH-01)
+│ │ └── StepIndicator.tsx # Collapsed labels on mobile (POLISH-01), MD3 focus ring update (POLISH-02)
+│ └── ui/
+│ ├── BackendCard.tsx # Full-width stacking on mobile (POLISH-01), focus-visible ring (POLISH-02)
+│ ├── TextFieldMD3.tsx # Already full-width (w-full); verify — no change needed
+│ └── ThemeToggle.tsx # focus-visible ring (POLISH-02)
+└── styles/
+ └── md3-buttons.ts # Update focus-visible from ring-2 to ring-3 / outline-3 (POLISH-02)
+```
+
+### Pattern 1: Tailwind v4 Mobile-First Responsive Layout (POLISH-01)
+
+**What:** Tailwind v4 uses mobile-first breakpoints. A class without a prefix applies at all sizes; a `sm:` prefix applies at ≥640px. To achieve mobile-first stacking: set the mobile layout as the base class, add `sm:` variant for wider screens.
+
+**Backend cards — current layout:** `BackendCard` components are rendered in a `
` with no grid/flex wrapper. Each `BackendCard` itself is `flex flex-col items-start`. They stack vertically by default because they are block-level elements. To make them fill the card container width on mobile and appear in a grid on larger screens:
+
+```tsx
+// BackendSelectionStep.tsx — wrap backend cards in a responsive grid
+
+ {/* BackendCard buttons already have w-full implied by grid cell */}
+
+```
+
+**BackendCard — ensure full width in grid cell:**
+```tsx
+// BackendCard.tsx — add w-full so button fills grid cell
+className={[
+ 'w-full flex flex-col items-start gap-1 rounded-xl border-2 p-4 text-left transition-all',
+ // ... rest of classes
+].join(' ')}
+```
+
+**Button rows — current layout:** All step components have `
`. On mobile, buttons should stretch to full width (or at least be wide enough to tap). On wider screens, shrink back to auto width:
+
+```tsx
+// All step button rows
+
+
+
+
+```
+
+**StepIndicator — mobile collapse:** On small screens, the 4-step indicator with labels is cramped. The label text below each circle can be hidden on mobile with `hidden sm:block`:
+
+```tsx
+// StepIndicator.tsx — hide label text on mobile
+{label}
+// (for all three states: completed, active, future)
+```
+
+The connector lines and circles remain visible on mobile — only text labels collapse.
+
+**App.tsx container:** The outer wrapper `py-12 px-4` is already reasonable for mobile. The `max-w-2xl` container is appropriate. No change needed at the shell level.
+
+**When to use:** Any time a multi-column layout needs to degrade gracefully on narrow viewports.
+
+### Pattern 2: MD3 Focus-Visible Indicators (POLISH-02)
+
+**What:** MD3 specifies a 3dp focus ring. The current `md3-buttons.ts` already uses `focus-visible:ring-2` (2px). This needs to change to a 3px indicator. In Tailwind v4, `ring-3` is available (Tailwind v4 introduced `ring-3` as a standard utility — previously only `ring-2` and `ring-4` existed as defaults in v3, but v4 supports arbitrary-value-free ring-3).
+
+**Verification:** Tailwind v4 uses a numeric scale (`ring-1`, `ring-2`, `ring-3`, `ring-4`) — confirmed by Tailwind v4 changelog and the migration from v3 where ring sizes follow the spacing scale.
+
+**Current state of buttons (md3-buttons.ts):**
+```ts
+// Current: ring-2 with primary/50 opacity
+'focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50'
+```
+
+**Update to:**
+```ts
+// POLISH-02: 3px MD3 focus ring
+'focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-primary'
+```
+
+**BackendCard — currently missing focus-visible:**
+```tsx
+// BackendCard.tsx — add focus-visible ring
+'focus-visible:outline-none focus-visible:ring-3 focus-visible:ring-primary focus-visible:ring-offset-2'
+```
+
+**Select element in FieldRenderer:**
+```tsx
+// FieldRenderer.tsx select branch — update focus ring
+'border-outline focus:ring-2' // → change to focus-visible:ring-3
+```
+
+**Tooltip buttons in FieldRenderer — currently only hover/click, no focus indicator:**
+```tsx
+// FieldRenderer.tsx tooltip buttons — add focus-visible
+className="... focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary rounded"
+```
+
+**StepIndicator completed-step buttons — current:**
+```tsx
+// group-focus-visible:ring-2 group-focus-visible:ring-primary/50
+// Update to group-focus-visible:ring-3 group-focus-visible:ring-primary
+```
+
+**ThemeToggle:** Read the file to confirm current focus state — likely needs focus-visible ring added.
+
+**Key insight:** The `focus-visible:` CSS pseudo-class only shows the outline during keyboard navigation (Tab key), not after mouse clicks. This is the correct MD3 and WCAG behavior. Browsers natively handle the distinction via the `:focus-visible` CSS pseudo-class.
+
+### Pattern 3: Step Transition Animation (POLISH-03)
+
+**What:** A subtle fade+slide-up animation when the step content changes. Applied to the content wrapper in `App.tsx` (`WizardShell`). The animation triggers when the step content re-renders.
+
+**Mechanism — React key trick:** The step content `div` in `WizardShell` receives a `key={state.currentStep}`. When `currentStep` changes, React unmounts the old div and mounts a new one, which triggers the CSS `@keyframes` animation from its initial state.
+
+**CSS in index.css:**
+```css
+@keyframes step-in {
+ from {
+ opacity: 0;
+ transform: translateY(8px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* Reduced-motion guard */
+@media (prefers-reduced-motion: reduce) {
+ .animate-step-in {
+ animation: none;
+ }
+}
+```
+
+**Tailwind v4 custom animation registration (in index.css @theme block):**
+```css
+@theme {
+ /* ... existing tokens ... */
+ --animate-step-in: step-in 200ms ease-out both;
+}
+```
+
+This registers `animate-step-in` as a Tailwind utility class (Tailwind v4 `@theme` block wires `--animate-*` to `animate-*` utilities).
+
+**App.tsx usage:**
+```tsx
+// WizardShell — add key + animation class to step content wrapper
+
+ {CurrentStep}
+
+```
+
+**Alternative without @theme:** Apply animation directly via `style` prop or CSS class defined in `@layer utilities`. However, using `@theme` to register `--animate-step-in` follows the project's established pattern of wiring values through the Tailwind token system.
+
+**prefers-reduced-motion handling:** Two approaches:
+1. CSS `@media (prefers-reduced-motion: reduce) { .animate-step-in { animation: none; } }` — declarative, no JS
+2. Tailwind `motion-reduce:animate-none` utility class on the same element
+
+Both work. The CSS approach in `index.css` is consistent with the existing reduced-motion guard already in the file (the `body { transition: ... }` block at line 44). **Use approach 1 (CSS media query in index.css).**
+
+**Duration recommendation:** 200ms with `ease-out` — subtle enough to not feel slow, long enough to be perceptible. The 8px translateY offset matches MD3 "standard" motion easing.
+
+### Pattern 4: Auto-Scroll to First Error (POLISH-04)
+
+**What:** When form validation fails (clicking Next with invalid fields), automatically scroll the viewport to the first field that has an error.
+
+**Mechanism — react-hook-form error handler:** `handleSubmit(onValid, onError)` accepts a second callback that fires when validation fails. The `onError` callback receives the `errors` object (same shape as `formState.errors`).
+
+```tsx
+// BackendSelectionStep.tsx — add error handler
+function onInvalidSubmit(errors: FieldErrors) {
+ const firstErrorKey = Object.keys(errors)[0];
+ if (firstErrorKey) {
+ const el = document.getElementById(firstErrorKey);
+ el?.scrollIntoView({ behavior: 'smooth', block: 'center' });
+ }
+}
+
+// In JSX:
+