# S04 Research: Accessibility & SEO Fixes

## Summary

Straightforward accessibility fixes across known files using established patterns. Four requirements: heading hierarchy (R022), skip-to-content link (R023), text contrast AA compliance (R024), and page-specific document titles (R025). No new dependencies, no architectural decisions, no unfamiliar technology.

## Recommendation

Four independent tasks, one per requirement. All touch the same small set of files (App.tsx, App.css, and page components). Order doesn't matter — none depend on each other. The heading hierarchy fix touches the most files and has the most nuance (promoting h2→h1 on some pages, demoting nav h1→span, fixing heading level skips).

## Implementation Landscape

### R022 — Heading Hierarchy Fix

**Current state:** `App.tsx:63` has `<h1>Chrysopedia</h1>` in the nav brand link. This means every page has *two* h1s — the nav one plus the page's own (Home, About, CreatorDetail, TechniquePage each have explicit `<h1>`).

**Pages with their own h1 (need nav h1 removed):**
- `Home.tsx:101` — `<h1>Production Knowledge, Distilled</h1>`
- `About.tsx:7` — `<h1>About Chrysopedia</h1>`
- `CreatorDetail.tsx:98` — `<h1>{creator.name}</h1>`
- `TechniquePage.tsx:243` — `<h1>{displayTitle}</h1>`

**Pages WITHOUT h1 (need their h2 promoted to h1):**
- `TopicsBrowse.tsx:96` — `<h2>Topics</h2>` → promote to h1
- `CreatorsBrowse.tsx:89` — `<h2>Creators</h2>` → promote to h1
- `SubTopicPage.tsx:112` — `<h2>{subtopicDisplay}</h2>` → promote to h1
- `SearchResults.tsx` — no heading at all, needs an h1 added
- `AdminReports.tsx:116` — `<h2>Content Reports</h2>` → promote to h1
- `AdminPipeline.tsx:577` — `<h2>Pipeline Management</h2>` → promote to h1

**Fix:** Change `App.tsx:63` from `<h1>` to `<span>` (visually styled the same via CSS class). Promote h2→h1 on pages that lack one. Update CSS selectors if any target `h1` inside the brand link.

**Heading level skips:**
- `Home.tsx` has h1 → h3 (skips h2) in the "How It Works" section. The h3s at lines 120/127/134 should be h2 or the section needs restructuring. Also h3 at line 191 (Featured label) and line 221 (Recently Added) — these should be h2.
- `SearchResults.tsx` uses h3 for group titles — should be h2 under the new h1.

### R023 — Skip-to-Content Link

**Current state:** No skip link exists. `<main className="app-main">` at `App.tsx:114` is the target.

**Fix:** Add `id="main-content"` to the `<main>` tag. Add a visually-hidden skip link as the first child of `.app`: `<a href="#main-content" className="skip-link">Skip to content</a>`. CSS: position offscreen by default, visible on `:focus`.

### R024 — Text Contrast AA Compliance

**Current values (`:root` in App.css lines 14-16):**
- `--color-text-primary: #e2e2ea` — passes easily
- `--color-text-secondary: #8b8b9a` — **5.70:1 on page bg, 5.14:1 on surface** → already passes AA (4.5:1)
- `--color-text-muted: #6b6b7a` — **3.65:1 on page bg, 3.29:1 on surface** → **fails AA**

**Background colors:**
- `--color-bg-page: #0f0f14`
- `--color-bg-surface: #1a1a24`

**Fix:** Bump `--color-text-muted` from `#6b6b7a` to `#828291`. This gives 5.05:1 on page bg and 4.56:1 on surface — comfortably above 4.5:1 AA threshold while maintaining the visual hierarchy (muted < secondary < primary).

Header text colors (`rgba(255,255,255,0.8)` etc.) all pass easily against the dark header background.

### R025 — Page-Specific Document Titles

**Current state:** No `document.title` usage anywhere in the codebase. Browser tab shows the default from `index.html`.

**Fix:** Add a `useDocumentTitle` custom hook (or inline `useEffect` in each page) that sets `document.title` on mount/update. Pattern:

```typescript
// In each page component:
useEffect(() => {
  document.title = "Topics — Chrysopedia";
}, []);

// For dynamic pages:
useEffect(() => {
  if (creator) document.title = `${creator.name} — Chrysopedia`;
}, [creator]);
```

**Pages needing titles (10 total):**
| Page | Title pattern |
|------|---------------|
| Home | `Chrysopedia — Production Knowledge, Distilled` |
| TopicsBrowse | `Topics — Chrysopedia` |
| SubTopicPage | `{subtopic} — {category} — Chrysopedia` |
| CreatorsBrowse | `Creators — Chrysopedia` |
| CreatorDetail | `{name} — Chrysopedia` |
| TechniquePage | `{title} — Chrysopedia` |
| SearchResults | `Search: {query} — Chrysopedia` |
| About | `About — Chrysopedia` |
| AdminReports | `Content Reports — Chrysopedia` |
| AdminPipeline | `Pipeline Management — Chrysopedia` |

A shared `useDocumentTitle(title: string)` hook keeps it DRY.

## Verification

- **Heading hierarchy:** Browser DevTools → `document.querySelectorAll('h1')` returns exactly 1 per page. Check with `$$('h1, h2, h3, h4, h5, h6').map(h => h.tagName)` — levels must be sequential.
- **Skip link:** Tab from fresh page load → first focus shows "Skip to content" link → activating it scrolls to main content.
- **Contrast:** The fix is a single CSS variable change. Verify with browser DevTools color picker contrast checker or computed values.
- **Document titles:** Navigate to each route and check `document.title` in console.
- **Build:** `cd frontend && npx tsc --noEmit && npm run build` — zero errors.

## Pitfalls

- **CSS selectors targeting `h1` in nav brand:** Check if any styles use `.app-header__brand h1` or similar. These need updating to target `span` after the heading demotion.
- **Heading level cascade in Home.tsx:** The "How It Works" section jumps h1→h3. Simply promoting to h2 is correct since h1 is the hero title and these are section-level headings below it.
