Status Draft Story **As a** storyteller, **I want** a sticky split component, **so that** narrative and media synchronize during scroll. Acceptance Criteria 1. components implemented with docs. 2. Reduced‑motion mode disables heavy animations gracefully. 3. Mobile layout stacks content; performance budget respected. Tasks / Subtasks - [ ] Scaffold pattern components (AC: 1) - [ ] Create folder `src/components/patterns/sticky-split/` - [ ] Add files: `Section.tsx`, `Sticky.tsx`, `Track.tsx`, `Panel.tsx`, `index.ts` - [ ] Export namespaced API: `StickySplit = { Section, Sticky, Track, Panel }` - [ ] Define props: - Section: `{ id: string; height?: string | number; side?: 'left'|'right'; className?: string }` - Sticky: `{ offset?: string; className?: string }` - Track: `{ className?: string }` - Panel: `{ index: number; inVariants?: Variants; outVariants?: Variants; className?: string }` - [ ] Provide sensible defaults: `height='200vh'`, `side='left'`, sticky `offset='var(--sticky-top, 8vh)'` - [ ] Progress computation (AC: 1) - [ ] Implement `useSectionProgress(id)` in `src/components/patterns/sticky-split/useSectionProgress.ts` - [ ] Use `IntersectionObserver` on Section root to compute 0..1 progress across its scroll range - [ ] Ensure passive listeners only; no wheel/touch blocking; avoid scroll‑jacking - [ ] Animation integration (AC: 1) - [ ] Use `framer-motion` `motion.div` inside `Panel` and map `progress` to opacity/transform via `useTransform` - [ ] Add optional `inVariants/outVariants` props; default to subtle fade/scale - [ ] Reduced motion (AC: 2) - [ ] Use `useReducedMotion()` from framer‑motion; if true, disable transforms and show panels as static stack - [ ] Mobile layout + fallbacks (AC: 3) - [ ] Apply responsive CSS: below `md`, stack vertically; disable sticky; panels visible with light reveal‑on‑view (IO add/remove `is-visible` class) - [ ] Above `md`, two‑column grid with left sticky narrative and right scroll track; allow `side='right'` to invert - [ ] Tokens and base styles (AC: 1,3) - [ ] Define CSS vars in `src/app/globals.css`: `--sticky-top`, `--panel-gap`, `--reveal-duration`, `--reveal-ease` - [ ] Provide utility classes for the pattern container grid and gaps using Tailwind + CSS vars - [ ] Documentation & example (AC: 1) - [ ] Add `src/components/patterns/sticky-split/README.md` with usage, props, and examples - [ ] Include a small demo component `Demo.tsx` showcasing 3 panels; do not add a new route in this story - [ ] Quality gates & perf - [ ] Lint passes: `npm run lint` - [ ] Build passes: `npm run build` - [ ] Verify no main thread jank on mid‑range device; keep frame budget ≈16ms for panel transitions - [ ] Confirm passive listeners; no prevention of native scroll Dev Notes - Context - Derived from PRD Story 1.3. Target: reusable pattern inspired by “Basement Foundry” style; no scroll‑jacking. - Typography and tokens are handled in Story 1.1; this story may add a few CSS vars specific to the pattern. - Relevant Source Tree - Component home: `src/components/patterns/sticky-split/*` - Tailwind/CSS tokens: `src/app/globals.css`, `tailwind.config.ts` - Motion libs: `framer-motion`/`motion` available in dependencies. - Implementation Guidance - Section renders a responsive two‑column grid (`md:grid md:grid-cols-2`) with a fixed sticky column and a scrolling track column; swap order with `side`. - Sticky uses `position: sticky; top: var(--sticky-top)` and inherits tokenized spacing/gap via CSS vars. - Track holds `Panel` children; each Panel uses the section progress (or per‑panel thresholds like `index / (N-1)`) to compute in/out. - Reduced motion: early return static markup; prefer opacity reveals without transforms. - Accessibility: preserve DOM order for reading; keep focusable content reachable; avoid trapping focus. - Performance: avoid large fixed layers; prefer `will-change: transform` only while animating; lazy‑load media inside panels. Testing - Manual - With default motion: panels fade/transform smoothly as the section scrolls; no scroll blocking. - With `prefers-reduced-motion`: panels render as static stack; no transforms/animations. - Mobile: stacked layout; sticky disabled; content readable and navigable. - Perf - Inspect Chrome Performance or DevTools frame rate timeline; verify transitions stay near 60fps on typical hardware. - A11y - Keyboard navigation reaches all interactive elements inside Sticky and Track; focus order logical. Change Log | Date | Version | Description | Author | |------|---------|-------------|--------| | 2025-09-24 | v1 | Initial draft from PRD Story 1.3 | Scrum Master | Dev Agent Record Agent Model Used {{agent_model_name_version}} Debug Log References Completion Notes List File List QA Results