` and `items-center` on the flex wrapper so the whole hero block centers inside the container. ListGrid below stays as a grid. In `RollSection`, the `` button row should switch to `justify-center`.
**Tests**: snapshot or className assertion in a new `home-page.test.tsx`.
### U9 — Carousel always visible during / after spin
**Files**: `src/components/landing/carousel-animation.tsx`.
**Change**: Root cause — during the `"spinning"` phase (lines 96–120), `scrollOffsetRef.current += FAST_SPEED` accumulates up to ~3780px over 3500ms **without** the auto-scroll wrap (`scrollOffsetRef.current -= SET_WIDTH` at line 92–94). Once past `SET_WIDTH` it's still within the tripled strip visually, but there's no guard against running off the third copy. And after settle, offset is never normalized back into the first-to-second copy range, so subsequent spins extend further.
**Fix**: Apply the modulo wrap inside the spinning branch too: after each `scrollOffsetRef.current += delta`, do `if (scrollOffsetRef.current >= SET_WIDTH) scrollOffsetRef.current -= SET_WIDTH`. On settle, normalize: `scrollOffsetRef.current = scrollOffsetRef.current % SET_WIDTH` (or snap-to-poster math that already targets the middle copy). This keeps the visible window always inside the middle copy of the tripled set without requiring more DOM — memory footprint unchanged.
**Do NOT** drop back to fewer posters or re-introduce virtualization. The user explicitly asked to avoid that.
**Tests**: unit test for a helper function extracted from the animation loop: `wrapOffset(offset, setWidth) → number in [0, setWidth)`. Keep the rAF loop untested (jsdom can't do it meaningfully).
## E2E Verification Recipe
Per user decision: **unit tests + manual QA**. Each worker must:
1. Run `npm test` — must stay green.
2. Run `npx tsc --noEmit 2>&1 | grep -v "__tests__" | grep -v "\.test\." | grep -v ".next/types"` — no NEW errors in `src/` (pre-existing `.next/types/validator.ts` error is known and unrelated).
3. **Do not** attempt to boot a dev server in-worktree — the parent coordinator already has `npm run dev` on localhost:3000 for manual QA post-merge.
4. Before committing, invoke `Skill` tool with `skill: "simplify"` to review the diff.
5. If tests or typecheck fail, fix and re-run before creating the PR.
Manual QA after merge will be performed by the coordinator against localhost:3000.
## Worker Instruction Template (shared by all units)
Each worker gets the shared conventions above + its unit spec + the boilerplate from `/batch` phase 2:
```
After you finish implementing the change:
1. Simplify — Invoke the Skill tool with skill: "simplify".
2. Run unit tests — `npm test`. Fix any failures.
3. Typecheck — `npx tsc --noEmit`. Ignore pre-existing errors in .next/types and __tests__ dirs; fix anything new in src/.
4. Do NOT boot a dev server — coordinator does manual QA post-merge.
5. Commit and push — clear message, then `gh pr create`.
6. Report — end with `PR: ` or `PR: none — `.
```
## Files to modify — reference map
| Unit | Primary files |
|------|---------------|
| U1 | `src/components/movies/genre-tag.tsx`, `expanded-panel.tsx`, `poster-grid.tsx`, `movie-list-client.tsx` |
| U2 | new `src/components/movies/genre-filter-dropdown.tsx`; `movie-list-client.tsx`, `poster-grid.tsx` |
| U3 | `src/components/movies/movie-list-client.tsx` |
| U4 | `src/components/movies/watched-button.tsx` |
| U5 | new `src/components/movies/more-info-modal.tsx`, new `src/hooks/use-tmdb-movie-details.ts`, `expanded-panel.tsx` |
| U6 | `src/components/movies/movie-list-client.tsx`, `src/components/dice/roll-bar.tsx` |
| U7 | new `src/components/ui/back-button.tsx`, `src/app/(app)/layout.tsx`, `src/app/(app)/list/[id]/page.tsx`, `(auth)/layout.tsx`, `(public)/layout.tsx`, `admin/layout.tsx` |
| U8 | `src/app/(app)/home/page.tsx`, `src/components/home/roll-section.tsx` |
| U9 | `src/components/landing/carousel-animation.tsx` |
## Merge conflict hotspots
`movie-list-client.tsx` is touched by **U1, U2, U3, U6**. These conflicts are predictable and resolvable by hand at merge time — each unit's edits are in different regions of the file (U1 removes genre-select plumbing; U2 adds dropdown below SearchBar; U3 adds `searchDismissed` state in handleAdd; U6 reorders JSX so RollBar is first). Expect to merge U6 first (pure reorder), then U3 (tiny state add), then U2 (new component + prop plumbing), then U1 (removal of what U2 replaces).
## Out of scope
- Axe browser scan, VO/Orca testing, and the Phase 4 E2E recipe — tracked in `research/PHASE4_TEST_MATRIX.md` and owned by the human QA pass.
- Real-time `supabase-realtime` container restart loop (pre-existing).