# S05 Research: Sticky Reading Header

## Summary

Straightforward UI feature. A thin fixed bar slides in when the article title (`h1.technique-header__title`) scrolls out of view, showing the technique title and current section name. Slides out when scrolling back to the top. All required patterns (IntersectionObserver, activeId) already exist in `TableOfContents.tsx`.

## Recommendation

Single new component `ReadingHeader.tsx` that:
1. Uses IntersectionObserver on the H1 element to detect title visibility
2. Receives `activeId` and `sections` props from the parent (same data already flowing to `TableOfContents`)
3. CSS transition for slide-in/out (`transform: translateY(-100%)` → `translateY(0)`)

Requires lifting `activeId` state up from `TableOfContents.tsx` into `TechniquePage.tsx` so both `TableOfContents` and `ReadingHeader` can consume it. The observer logic currently inside `TableOfContents` moves to the parent or a shared hook.

## Implementation Landscape

### Existing Code

| File | Role | Lines |
|------|------|-------|
| `frontend/src/pages/TechniquePage.tsx` | Article page; renders title H1, two-column grid, passes sections to ToC | ~370 |
| `frontend/src/components/TableOfContents.tsx` | Sidebar ToC with IntersectionObserver scroll-spy tracking `activeId` | ~100 |
| `frontend/src/App.css` | All styles; technique section starts line 1842 | 5723 |

### Key Structural Facts

- **Article title**: `<h1 className="technique-header__title">{displayTitle}</h1>` inside `.technique-columns__main`
- **activeId state + IntersectionObserver**: Currently private inside `TableOfContents.tsx`. Must be lifted to `TechniquePage.tsx` so `ReadingHeader` can also read it.
- **Section slugs**: `slugify()` exported from `TableOfContents.tsx`, already shared
- **Sidebar**: `.technique-columns__sidebar` has `position: sticky; top: 1.5rem`
- **App header**: `.app-header` is NOT sticky/fixed — scrolls with page. No z-index set on it.
- **z-index landscape**: skip-to-content at 999, mobile menu at 200, modals at 1000. Reading header should use ~100.
- **Mobile breakpoint**: 768px — sidebar collapses to single column. Reading header should still work on mobile.
- **v2 gate**: ToC only renders for v2 format pages. Reading header should follow the same gate.

### Architecture

**State lifting approach:**
- Extract IntersectionObserver logic from `TableOfContents` into a `useActiveSection(sections)` custom hook (or just move the useState + useEffect into `TechniquePage.tsx`)
- `TableOfContents` receives `activeId` as a prop instead of managing it
- `ReadingHeader` receives `activeId`, `sections`, `title`, and `visible` (boolean from H1 observer)

**H1 visibility detection:**
- Second IntersectionObserver in `TechniquePage.tsx` watching the H1 element
- When H1 is not intersecting → show reading header; when intersecting → hide
- No rootMargin needed — simple "is the title visible at all" check

**Slide animation:**
- `position: fixed; top: 0; left: 0; right: 0; z-index: 100`
- `transform: translateY(-100%)` when hidden, `translateY(0)` when visible
- `transition: transform 0.3s ease`
- This is CSS-only, no animation library needed

**Current section display:**
- Look up `activeId` in `sections` array to find the matching heading text
- Display as "Title — Current Section" or "Title · Current Section"

### Natural Task Seams

1. **T01: Lift activeId + create ReadingHeader component** — Extract observer from ToC into parent, create ReadingHeader component with fixed positioning and slide animation, wire into TechniquePage, add CSS
2. **T02: Verify build + visual behavior** — Production build, check that ToC still highlights correctly, reading header appears/disappears, mobile breakpoint hides appropriately

Actually, this is small enough for a single task. The state lift is mechanical, the component is ~40 lines, the CSS is ~30 lines.

### Constraints

- Must not break existing ToC scroll-spy behavior
- Must only appear on v2 technique pages (same gate as ToC)
- Must not overlap with modal (z-index 1000) or mobile menu (z-index 200)
- CSS transition only — no spring/animation libraries
- Reading header should show a truncated title on narrow viewports

### Verification

- `npm run build` passes with zero errors/warnings
- ReadingHeader component exists and is rendered conditionally
- CSS contains `.reading-header` with `position: fixed` and `transform` transition
- ToC still shows active section highlighting (observer logic preserved)
