diff --git a/.gsd/milestones/M011/M011-ROADMAP.md b/.gsd/milestones/M011/M011-ROADMAP.md
index 582c017..526f558 100644
--- a/.gsd/milestones/M011/M011-ROADMAP.md
+++ b/.gsd/milestones/M011/M011-ROADMAP.md
@@ -8,5 +8,5 @@ Transform Chrysopedia from functionally adequate to engaging and accessible. Add
|----|-------|------|---------|------|------------|
| S01 | Interaction Delight & Discovery | medium | — | ✅ | Cards animate on hover with scale+shadow. Card grids stagger entrance. Featured technique has glow treatment. Random Technique button navigates to a random page. |
| S02 | Topics, Creator Stats & Card Polish | medium | — | ✅ | Topics page loads collapsed, expands with animation. Creator stats show colored topic pills. Cards limit tags to 4 with +N more. Empty subtopics show Coming soon. |
-| S03 | Global Search & Mobile Navigation | medium | — | ⬜ | Compact search bar in nav on all pages. Cmd+K focuses it. Mobile viewport shows hamburger menu. |
+| S03 | Global Search & Mobile Navigation | medium | — | ✅ | Compact search bar in nav on all pages. Cmd+K focuses it. Mobile viewport shows hamburger menu. |
| S04 | Accessibility & SEO Fixes | low | — | ⬜ | Every page has single H1, skip-to-content link, AA contrast text, and descriptive browser tab title. |
diff --git a/.gsd/milestones/M011/slices/S03/S03-SUMMARY.md b/.gsd/milestones/M011/slices/S03/S03-SUMMARY.md
new file mode 100644
index 0000000..97a6f39
--- /dev/null
+++ b/.gsd/milestones/M011/slices/S03/S03-SUMMARY.md
@@ -0,0 +1,85 @@
+---
+id: S03
+parent: M011
+milestone: M011
+provides:
+ - Compact nav search bar on all non-home pages with Cmd+K focus
+ - Mobile hamburger menu with stacked nav links at <768px
+requires:
+ []
+affects:
+ - S04
+key_files:
+ - frontend/src/components/SearchAutocomplete.tsx
+ - frontend/src/App.tsx
+ - frontend/src/App.css
+ - frontend/src/pages/Home.tsx
+ - frontend/src/pages/SearchResults.tsx
+key_decisions:
+ - Refactored SearchAutocomplete from heroSize boolean to variant string prop for cleaner multi-context usage
+ - Mobile nav search uses second component instance (no globalShortcut) rather than CSS repositioning to avoid double keyboard handler registration
+patterns_established:
+ - Variant prop pattern for component display modes (hero/inline/nav) — reusable for other components that need context-dependent sizing
+observability_surfaces:
+ - none
+drill_down_paths:
+ - .gsd/milestones/M011/slices/S03/tasks/T01-SUMMARY.md
+ - .gsd/milestones/M011/slices/S03/tasks/T02-SUMMARY.md
+duration: ""
+verification_result: passed
+completed_at: 2026-03-31T08:46:50.106Z
+blocker_discovered: false
+---
+
+# S03: Global Search & Mobile Navigation
+
+**Added compact nav search bar with Cmd+K shortcut on all non-home pages and mobile hamburger menu with 44px touch targets and three auto-close mechanisms.**
+
+## What Happened
+
+Two tasks delivered the slice goal cleanly. T01 refactored SearchAutocomplete from a boolean heroSize prop to a variant string prop ('hero' | 'inline' | 'nav'), added a globalShortcut prop for Cmd+K/Ctrl+K keyboard focus, created nav-variant CSS with compact sizing and high z-index dropdown, and wired the nav search into App.tsx conditionally on non-home routes. Home.tsx and SearchResults.tsx callers were updated to use the new variant prop. The deprecated heroSize prop remains as a backwards-compat fallback.
+
+T02 added a hamburger menu button visible below 768px that toggles a mobile nav panel. Three auto-close mechanisms were implemented: route change (useEffect on location.pathname), Escape key (keydown listener), and outside click (ref-based click detection). The hamburger icon toggles between three-line and X via conditional SVG. All mobile nav links get min-height: 44px touch targets. A second SearchAutocomplete instance (without globalShortcut to avoid double registration) renders inside the mobile panel. AdminDropdown was restyled to full-width static submenu in the mobile panel.
+
+Build passes cleanly (51 modules, 813ms). Both tasks verified via build and browser testing.
+
+## Verification
+
+Frontend build succeeds: `cd frontend && npm run build` — 51 modules transformed, built in 813ms, zero errors. Code inspection confirms: variant prop with three modes, globalShortcut Cmd+K handler, hamburger button with aria-expanded, menuOpen state with three close mechanisms, 44px min-height on mobile nav links, nav search conditional on non-home routes.
+
+## Requirements Advanced
+
+- R020 — Nav search bar rendered on all non-home pages with Cmd+K/Ctrl+K keyboard shortcut
+- R021 — Hamburger menu visible below 768px with 44px touch targets, three auto-close mechanisms
+
+## Requirements Validated
+
+None.
+
+## New Requirements Surfaced
+
+None.
+
+## Requirements Invalidated or Re-scoped
+
+None.
+
+## Deviations
+
+SearchAutocomplete kept deprecated heroSize prop as fallback (variant takes precedence). Mobile nav search rendered as second component instance rather than CSS-repositioned — avoids double Cmd+K registration. AdminDropdown restyled to full-width in mobile panel.
+
+## Known Limitations
+
+None.
+
+## Follow-ups
+
+None.
+
+## Files Created/Modified
+
+- `frontend/src/components/SearchAutocomplete.tsx` — Replaced heroSize boolean with variant prop, added globalShortcut prop for Cmd+K, nav-variant rendering
+- `frontend/src/App.tsx` — Added hamburger menu state, three auto-close mechanisms, conditional nav search, mobile menu panel
+- `frontend/src/App.css` — Nav search compact styles, hamburger button, mobile breakpoint panel, 44px touch targets
+- `frontend/src/pages/Home.tsx` — Updated caller to variant="hero"
+- `frontend/src/pages/SearchResults.tsx` — Updated caller to variant="inline"
diff --git a/.gsd/milestones/M011/slices/S03/S03-UAT.md b/.gsd/milestones/M011/slices/S03/S03-UAT.md
new file mode 100644
index 0000000..189099b
--- /dev/null
+++ b/.gsd/milestones/M011/slices/S03/S03-UAT.md
@@ -0,0 +1,167 @@
+# S03: Global Search & Mobile Navigation — UAT
+
+**Milestone:** M011
+**Written:** 2026-03-31T08:46:50.106Z
+
+# S03 UAT: Global Search & Mobile Navigation
+
+## Preconditions
+- Chrysopedia frontend running (dev server or production build)
+- Browser with DevTools available for viewport resizing
+
+---
+
+## Test 1: Nav Search Bar Visibility
+
+**Steps:**
+1. Navigate to homepage (`/`)
+2. Observe the navigation header
+
+**Expected:** No search bar in the nav header (homepage has its own hero search)
+
+3. Navigate to `/topics`
+4. Observe the navigation header
+
+**Expected:** Compact search input visible in the nav bar between brand and right section
+
+5. Navigate to `/creators`, then any `/technique/:slug` page
+
+**Expected:** Search bar present in nav on both pages
+
+---
+
+## Test 2: Nav Search Functionality
+
+**Steps:**
+1. On `/topics`, click the nav search input
+2. Type "reverb"
+3. Observe typeahead dropdown
+
+**Expected:** Typeahead dropdown appears with results, positioned with high z-index over page content
+
+4. Press Enter
+
+**Expected:** Navigates to search results page for "reverb"
+
+---
+
+## Test 3: Cmd+K Keyboard Shortcut
+
+**Steps:**
+1. Navigate to `/creators`
+2. Click somewhere on the page body (not in search)
+3. Press Cmd+K (Mac) or Ctrl+K (Windows/Linux)
+
+**Expected:** Nav search input receives focus. Browser default Cmd+K behavior is prevented.
+
+4. Type a query and press Enter
+
+**Expected:** Search executes normally
+
+---
+
+## Test 4: Cmd+K Not Active on Homepage
+
+**Steps:**
+1. Navigate to homepage (`/`)
+2. Press Cmd+K
+
+**Expected:** No nav search to focus (homepage uses hero search). Browser default may trigger.
+
+---
+
+## Test 5: Hamburger Menu Visibility
+
+**Steps:**
+1. Open DevTools, set viewport to 390×844 (iPhone 14)
+2. Navigate to any page
+
+**Expected:** Hamburger button (☰) visible in header. Desktop nav links hidden.
+
+3. Set viewport to 1024×768 (desktop)
+
+**Expected:** Hamburger button hidden. Desktop nav links visible inline.
+
+---
+
+## Test 6: Hamburger Menu Toggle
+
+**Steps:**
+1. At mobile viewport (390px wide), tap hamburger button
+
+**Expected:** Nav panel slides open below header. Hamburger icon changes to X. Links stacked vertically.
+
+2. Tap X button
+
+**Expected:** Nav panel closes with transition. Icon reverts to ☰.
+
+---
+
+## Test 7: Mobile Touch Targets
+
+**Steps:**
+1. At mobile viewport, open hamburger menu
+2. Inspect nav links in DevTools
+
+**Expected:** Each nav link has min-height of 44px. Padding provides comfortable touch area.
+
+---
+
+## Test 8: Auto-Close on Route Change
+
+**Steps:**
+1. At mobile viewport, open hamburger menu
+2. Tap a nav link (e.g., "Topics")
+
+**Expected:** Page navigates to Topics. Menu closes automatically.
+
+---
+
+## Test 9: Auto-Close on Escape
+
+**Steps:**
+1. At mobile viewport, open hamburger menu
+2. Press Escape key
+
+**Expected:** Menu closes. Focus returns to page.
+
+---
+
+## Test 10: Auto-Close on Outside Click
+
+**Steps:**
+1. At mobile viewport, open hamburger menu
+2. Tap/click on the page content area below the menu
+
+**Expected:** Menu closes.
+
+---
+
+## Test 11: Mobile Search in Menu
+
+**Steps:**
+1. At mobile viewport, open hamburger menu
+2. Locate search input inside the menu panel
+
+**Expected:** Search input is present and full-width inside mobile menu.
+
+3. Type a query and submit
+
+**Expected:** Search executes, menu closes on navigation.
+
+---
+
+## Test 12: Desktop Layout Unchanged
+
+**Steps:**
+1. At desktop viewport (1280px wide), navigate through all pages
+
+**Expected:** No hamburger button. Nav links displayed inline. Search bar compact in header on non-home pages. No layout regressions from S01/S02 work.
+
+---
+
+## Edge Cases
+
+- **Rapid toggle:** Quickly tap hamburger open/close 5 times — no stuck state
+- **Resize while open:** Open menu at mobile width, drag viewport to desktop width — menu should close or hide gracefully
+- **Multiple Cmd+K presses:** Press Cmd+K repeatedly — input stays focused, no errors in console
diff --git a/.gsd/milestones/M011/slices/S03/tasks/T02-VERIFY.json b/.gsd/milestones/M011/slices/S03/tasks/T02-VERIFY.json
new file mode 100644
index 0000000..7e3f529
--- /dev/null
+++ b/.gsd/milestones/M011/slices/S03/tasks/T02-VERIFY.json
@@ -0,0 +1,16 @@
+{
+ "schemaVersion": 1,
+ "taskId": "T02",
+ "unitId": "M011/S03/T02",
+ "timestamp": 1774946733530,
+ "passed": true,
+ "discoverySource": "task-plan",
+ "checks": [
+ {
+ "command": "cd frontend",
+ "exitCode": 0,
+ "durationMs": 7,
+ "verdict": "pass"
+ }
+ ]
+}
diff --git a/.gsd/milestones/M011/slices/S04/S04-PLAN.md b/.gsd/milestones/M011/slices/S04/S04-PLAN.md
index 1718769..185ae58 100644
--- a/.gsd/milestones/M011/slices/S04/S04-PLAN.md
+++ b/.gsd/milestones/M011/slices/S04/S04-PLAN.md
@@ -1,6 +1,36 @@
# S04: Accessibility & SEO Fixes
-**Goal:** Bring the site to WCAG 2.1 Level AA on core metrics and add SEO page titles
+**Goal:** Every page has a single H1, skip-to-content link, AA-compliant text contrast, and a descriptive browser tab title.
**Demo:** After this: Every page has single H1, skip-to-content link, AA contrast text, and descriptive browser tab title.
## Tasks
+- [x] **T01: Demoted nav brand to span, promoted page headings to h1, added skip-to-content link, and raised muted text contrast to AA compliance** — Three accessibility fixes that all touch App.tsx/App.css and page heading elements:
+
+1. **Heading hierarchy (R022):** Demote `
Chrysopedia
` in App.tsx nav to ``. Update `.app-header h1` CSS rule to `.app-header__brand span`. Promote `
` to `
` on pages that lack one: TopicsBrowse, CreatorsBrowse, SubTopicPage, AdminReports, AdminPipeline. Add `
` to SearchResults (currently has no heading). Fix heading level skips in Home.tsx: the 'How It Works' h3s (lines 120/127/134) become h2s; 'Featured Technique' h3 (line 191) becomes h2; 'Recently Added' h3 (line 221) becomes h2.
+
+2. **Skip-to-content link (R023):** Add `id="main-content"` to the `` tag in App.tsx. Add `Skip to content` as the first child of `.app`. Add `.skip-link` CSS: visually hidden by default, visible on `:focus`, positioned at top of viewport.
+
+3. **Contrast fix (R024):** Change `--color-text-muted` from `#6b6b7a` to `#828291` in App.css `:root` block. This achieves 5.05:1 on page bg and 4.56:1 on surface bg — both above AA 4.5:1 threshold.
+ - Estimate: 30m
+ - Files: frontend/src/App.tsx, frontend/src/App.css, frontend/src/pages/Home.tsx, frontend/src/pages/TopicsBrowse.tsx, frontend/src/pages/CreatorsBrowse.tsx, frontend/src/pages/SubTopicPage.tsx, frontend/src/pages/SearchResults.tsx, frontend/src/pages/AdminReports.tsx, frontend/src/pages/AdminPipeline.tsx
+ - Verify: cd frontend && npx tsc --noEmit && npm run build
+- [ ] **T02: Add useDocumentTitle hook and wire descriptive titles into all pages** — Create a `useDocumentTitle` custom hook and call it from every page component so browser tabs show descriptive, route-specific titles.
+
+1. Create `frontend/src/hooks/useDocumentTitle.ts` with a hook that sets `document.title` on mount and when the title string changes. Reset to 'Chrysopedia' on unmount (cleanup).
+
+2. Wire the hook into all 10 page components with these title patterns:
+ - Home: `Chrysopedia — Production Knowledge, Distilled`
+ - TopicsBrowse: `Topics — Chrysopedia`
+ - SubTopicPage: `{subtopic} — {category} — Chrysopedia` (dynamic, updates when data loads)
+ - CreatorsBrowse: `Creators — Chrysopedia`
+ - CreatorDetail: `{name} — Chrysopedia` (dynamic, updates when creator loads)
+ - TechniquePage: `{title} — Chrysopedia` (dynamic, updates when technique loads)
+ - SearchResults: `Search: {query} — Chrysopedia` (dynamic, updates with query param)
+ - About: `About — Chrysopedia`
+ - AdminReports: `Content Reports — Chrysopedia`
+ - AdminPipeline: `Pipeline Management — Chrysopedia`
+
+3. For dynamic pages (SubTopicPage, CreatorDetail, TechniquePage, SearchResults), call the hook with the resolved value so the title updates when async data loads. Use a fallback like `Chrysopedia` while loading.
+ - Estimate: 25m
+ - Files: frontend/src/hooks/useDocumentTitle.ts, frontend/src/pages/Home.tsx, frontend/src/pages/TopicsBrowse.tsx, frontend/src/pages/SubTopicPage.tsx, frontend/src/pages/CreatorsBrowse.tsx, frontend/src/pages/CreatorDetail.tsx, frontend/src/pages/TechniquePage.tsx, frontend/src/pages/SearchResults.tsx, frontend/src/pages/About.tsx, frontend/src/pages/AdminReports.tsx, frontend/src/pages/AdminPipeline.tsx
+ - Verify: cd frontend && npx tsc --noEmit && npm run build
diff --git a/.gsd/milestones/M011/slices/S04/S04-RESEARCH.md b/.gsd/milestones/M011/slices/S04/S04-RESEARCH.md
new file mode 100644
index 0000000..8f4a176
--- /dev/null
+++ b/.gsd/milestones/M011/slices/S04/S04-RESEARCH.md
@@ -0,0 +1,103 @@
+# 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 `
Chrysopedia
` 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 `
`).
+
+**Pages with their own h1 (need nav h1 removed):**
+- `Home.tsx:101` — `
Production Knowledge, Distilled
`
+- `About.tsx:7` — `
About Chrysopedia
`
+- `CreatorDetail.tsx:98` — `
{creator.name}
`
+- `TechniquePage.tsx:243` — `
{displayTitle}
`
+
+**Pages WITHOUT h1 (need their h2 promoted to h1):**
+- `TopicsBrowse.tsx:96` — `
Topics
` → promote to h1
+- `CreatorsBrowse.tsx:89` — `
Creators
` → promote to h1
+- `SubTopicPage.tsx:112` — `
{subtopicDisplay}
` → promote to h1
+- `SearchResults.tsx` — no heading at all, needs an h1 added
+- `AdminReports.tsx:116` — `
Content Reports
` → promote to h1
+- `AdminPipeline.tsx:577` — `
Pipeline Management
` → promote to h1
+
+**Fix:** Change `App.tsx:63` from `
` to `` (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. `` at `App.tsx:114` is the target.
+
+**Fix:** Add `id="main-content"` to the `` tag. Add a visually-hidden skip link as the first child of `.app`: `Skip to content`. 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.
diff --git a/.gsd/milestones/M011/slices/S04/tasks/T01-PLAN.md b/.gsd/milestones/M011/slices/S04/tasks/T01-PLAN.md
new file mode 100644
index 0000000..4e0c401
--- /dev/null
+++ b/.gsd/milestones/M011/slices/S04/tasks/T01-PLAN.md
@@ -0,0 +1,43 @@
+---
+estimated_steps: 4
+estimated_files: 9
+skills_used: []
+---
+
+# T01: Fix heading hierarchy, add skip-to-content link, and fix muted text contrast
+
+Three accessibility fixes that all touch App.tsx/App.css and page heading elements:
+
+1. **Heading hierarchy (R022):** Demote `
Chrysopedia
` in App.tsx nav to ``. Update `.app-header h1` CSS rule to `.app-header__brand span`. Promote `
` to `
` on pages that lack one: TopicsBrowse, CreatorsBrowse, SubTopicPage, AdminReports, AdminPipeline. Add `
` to SearchResults (currently has no heading). Fix heading level skips in Home.tsx: the 'How It Works' h3s (lines 120/127/134) become h2s; 'Featured Technique' h3 (line 191) becomes h2; 'Recently Added' h3 (line 221) becomes h2.
+
+2. **Skip-to-content link (R023):** Add `id="main-content"` to the `` tag in App.tsx. Add `Skip to content` as the first child of `.app`. Add `.skip-link` CSS: visually hidden by default, visible on `:focus`, positioned at top of viewport.
+
+3. **Contrast fix (R024):** Change `--color-text-muted` from `#6b6b7a` to `#828291` in App.css `:root` block. This achieves 5.05:1 on page bg and 4.56:1 on surface bg — both above AA 4.5:1 threshold.
+
+## Inputs
+
+- ``frontend/src/App.tsx` — nav h1 to demote, main tag needs id, skip link insertion point`
+- ``frontend/src/App.css` — `.app-header h1` rule to update, `:root` muted color to fix, skip-link styles to add`
+- ``frontend/src/pages/Home.tsx` — h3 level skips to fix (lines 120/127/134/164/170/191/221)`
+- ``frontend/src/pages/TopicsBrowse.tsx` — h2 to promote to h1 (line 96)`
+- ``frontend/src/pages/CreatorsBrowse.tsx` — h2 to promote to h1 (line 89)`
+- ``frontend/src/pages/SubTopicPage.tsx` — h2 to promote to h1 (line 112)`
+- ``frontend/src/pages/SearchResults.tsx` — needs h1 added (has no heading currently)`
+- ``frontend/src/pages/AdminReports.tsx` — h2 to promote to h1 (line 116)`
+- ``frontend/src/pages/AdminPipeline.tsx` — h2 to promote to h1 (line 577)`
+
+## Expected Output
+
+- ``frontend/src/App.tsx` — nav brand uses `` instead of `
`, skip link added, main has id`
+- ``frontend/src/App.css` — `.app-header h1` changed to `.app-header__brand span`, skip-link styles added, `--color-text-muted` updated to `#828291``
+- ``frontend/src/pages/Home.tsx` — heading levels fixed (h3→h2 for How It Works, Featured, Recently Added, nav cards)`
+- ``frontend/src/pages/TopicsBrowse.tsx` — h2→h1 promoted`
+- ``frontend/src/pages/CreatorsBrowse.tsx` — h2→h1 promoted`
+- ``frontend/src/pages/SubTopicPage.tsx` — h2→h1 promoted`
+- ``frontend/src/pages/SearchResults.tsx` — h1 heading added`
+- ``frontend/src/pages/AdminReports.tsx` — h2→h1 promoted`
+- ``frontend/src/pages/AdminPipeline.tsx` — h2→h1 promoted`
+
+## Verification
+
+cd frontend && npx tsc --noEmit && npm run build
diff --git a/.gsd/milestones/M011/slices/S04/tasks/T01-SUMMARY.md b/.gsd/milestones/M011/slices/S04/tasks/T01-SUMMARY.md
new file mode 100644
index 0000000..8b6b0e7
--- /dev/null
+++ b/.gsd/milestones/M011/slices/S04/tasks/T01-SUMMARY.md
@@ -0,0 +1,92 @@
+---
+id: T01
+parent: S04
+milestone: M011
+provides: []
+requires: []
+affects: []
+key_files: ["frontend/src/App.tsx", "frontend/src/App.css", "frontend/src/pages/Home.tsx", "frontend/src/pages/SearchResults.tsx", "frontend/src/pages/TopicsBrowse.tsx", "frontend/src/pages/CreatorsBrowse.tsx", "frontend/src/pages/SubTopicPage.tsx", "frontend/src/pages/AdminReports.tsx", "frontend/src/pages/AdminPipeline.tsx"]
+key_decisions: ["Used visually-hidden sr-only h1 for SearchResults since the page has no visible heading", "Changed nav brand from h1 to span preserving existing CSS styling approach"]
+patterns_established: []
+drill_down_paths: []
+observability_surfaces: []
+duration: ""
+verification_result: "TypeScript compilation (tsc --noEmit) and production build (npm run build) both pass with exit code 0. Verified each page has exactly one h1, skip-link and main-content id are present in App.tsx, and muted color token is updated."
+completed_at: 2026-03-31T08:52:36.572Z
+blocker_discovered: false
+---
+
+# T01: Demoted nav brand to span, promoted page headings to h1, added skip-to-content link, and raised muted text contrast to AA compliance
+
+> Demoted nav brand to span, promoted page headings to h1, added skip-to-content link, and raised muted text contrast to AA compliance
+
+## What Happened
+---
+id: T01
+parent: S04
+milestone: M011
+key_files:
+ - frontend/src/App.tsx
+ - frontend/src/App.css
+ - frontend/src/pages/Home.tsx
+ - frontend/src/pages/SearchResults.tsx
+ - frontend/src/pages/TopicsBrowse.tsx
+ - frontend/src/pages/CreatorsBrowse.tsx
+ - frontend/src/pages/SubTopicPage.tsx
+ - frontend/src/pages/AdminReports.tsx
+ - frontend/src/pages/AdminPipeline.tsx
+key_decisions:
+ - Used visually-hidden sr-only h1 for SearchResults since the page has no visible heading
+ - Changed nav brand from h1 to span preserving existing CSS styling approach
+duration: ""
+verification_result: passed
+completed_at: 2026-03-31T08:52:36.572Z
+blocker_discovered: false
+---
+
+# T01: Demoted nav brand to span, promoted page headings to h1, added skip-to-content link, and raised muted text contrast to AA compliance
+
+**Demoted nav brand to span, promoted page headings to h1, added skip-to-content link, and raised muted text contrast to AA compliance**
+
+## What Happened
+
+Applied three accessibility fixes across 9 files: (1) Fixed heading hierarchy by demoting nav h1 to span and promoting h2→h1 on all page components, added sr-only h1 to SearchResults, fixed h3→h2 level skips in Home.tsx. (2) Added skip-to-content link with keyboard-focusable skip-link pattern. (3) Changed --color-text-muted from #6b6b7a to #828291 for AA contrast compliance.
+
+## Verification
+
+TypeScript compilation (tsc --noEmit) and production build (npm run build) both pass with exit code 0. Verified each page has exactly one h1, skip-link and main-content id are present in App.tsx, and muted color token is updated.
+
+## Verification Evidence
+
+| # | Command | Exit Code | Verdict | Duration |
+|---|---------|-----------|---------|----------|
+| 1 | `cd frontend && npx tsc --noEmit` | 0 | ✅ pass | 2600ms |
+| 2 | `cd frontend && npm run build` | 0 | ✅ pass | 3100ms |
+
+
+## Deviations
+
+None.
+
+## Known Issues
+
+None.
+
+## Files Created/Modified
+
+- `frontend/src/App.tsx`
+- `frontend/src/App.css`
+- `frontend/src/pages/Home.tsx`
+- `frontend/src/pages/SearchResults.tsx`
+- `frontend/src/pages/TopicsBrowse.tsx`
+- `frontend/src/pages/CreatorsBrowse.tsx`
+- `frontend/src/pages/SubTopicPage.tsx`
+- `frontend/src/pages/AdminReports.tsx`
+- `frontend/src/pages/AdminPipeline.tsx`
+
+
+## Deviations
+None.
+
+## Known Issues
+None.
diff --git a/.gsd/milestones/M011/slices/S04/tasks/T02-PLAN.md b/.gsd/milestones/M011/slices/S04/tasks/T02-PLAN.md
new file mode 100644
index 0000000..3547f25
--- /dev/null
+++ b/.gsd/milestones/M011/slices/S04/tasks/T02-PLAN.md
@@ -0,0 +1,56 @@
+---
+estimated_steps: 14
+estimated_files: 11
+skills_used: []
+---
+
+# T02: Add useDocumentTitle hook and wire descriptive titles into all pages
+
+Create a `useDocumentTitle` custom hook and call it from every page component so browser tabs show descriptive, route-specific titles.
+
+1. Create `frontend/src/hooks/useDocumentTitle.ts` with a hook that sets `document.title` on mount and when the title string changes. Reset to 'Chrysopedia' on unmount (cleanup).
+
+2. Wire the hook into all 10 page components with these title patterns:
+ - Home: `Chrysopedia — Production Knowledge, Distilled`
+ - TopicsBrowse: `Topics — Chrysopedia`
+ - SubTopicPage: `{subtopic} — {category} — Chrysopedia` (dynamic, updates when data loads)
+ - CreatorsBrowse: `Creators — Chrysopedia`
+ - CreatorDetail: `{name} — Chrysopedia` (dynamic, updates when creator loads)
+ - TechniquePage: `{title} — Chrysopedia` (dynamic, updates when technique loads)
+ - SearchResults: `Search: {query} — Chrysopedia` (dynamic, updates with query param)
+ - About: `About — Chrysopedia`
+ - AdminReports: `Content Reports — Chrysopedia`
+ - AdminPipeline: `Pipeline Management — Chrysopedia`
+
+3. For dynamic pages (SubTopicPage, CreatorDetail, TechniquePage, SearchResults), call the hook with the resolved value so the title updates when async data loads. Use a fallback like `Chrysopedia` while loading.
+
+## Inputs
+
+- ``frontend/src/pages/Home.tsx` — needs useDocumentTitle call`
+- ``frontend/src/pages/TopicsBrowse.tsx` — needs useDocumentTitle call`
+- ``frontend/src/pages/SubTopicPage.tsx` — needs dynamic useDocumentTitle call with subtopic/category`
+- ``frontend/src/pages/CreatorsBrowse.tsx` — needs useDocumentTitle call`
+- ``frontend/src/pages/CreatorDetail.tsx` — needs dynamic useDocumentTitle call with creator name`
+- ``frontend/src/pages/TechniquePage.tsx` — needs dynamic useDocumentTitle call with technique title`
+- ``frontend/src/pages/SearchResults.tsx` — needs dynamic useDocumentTitle call with search query`
+- ``frontend/src/pages/About.tsx` — needs useDocumentTitle call`
+- ``frontend/src/pages/AdminReports.tsx` — needs useDocumentTitle call`
+- ``frontend/src/pages/AdminPipeline.tsx` — needs useDocumentTitle call`
+
+## Expected Output
+
+- ``frontend/src/hooks/useDocumentTitle.ts` — new custom hook file`
+- ``frontend/src/pages/Home.tsx` — useDocumentTitle wired`
+- ``frontend/src/pages/TopicsBrowse.tsx` — useDocumentTitle wired`
+- ``frontend/src/pages/SubTopicPage.tsx` — useDocumentTitle wired with dynamic subtopic/category`
+- ``frontend/src/pages/CreatorsBrowse.tsx` — useDocumentTitle wired`
+- ``frontend/src/pages/CreatorDetail.tsx` — useDocumentTitle wired with dynamic creator name`
+- ``frontend/src/pages/TechniquePage.tsx` — useDocumentTitle wired with dynamic technique title`
+- ``frontend/src/pages/SearchResults.tsx` — useDocumentTitle wired with dynamic query`
+- ``frontend/src/pages/About.tsx` — useDocumentTitle wired`
+- ``frontend/src/pages/AdminReports.tsx` — useDocumentTitle wired`
+- ``frontend/src/pages/AdminPipeline.tsx` — useDocumentTitle wired`
+
+## Verification
+
+cd frontend && npx tsc --noEmit && npm run build
diff --git a/frontend/src/App.css b/frontend/src/App.css
index 4f16779..5376611 100644
--- a/frontend/src/App.css
+++ b/frontend/src/App.css
@@ -13,7 +13,7 @@
/* Text */
--color-text-primary: #e2e2ea;
--color-text-secondary: #8b8b9a;
- --color-text-muted: #6b6b7a;
+ --color-text-muted: #828291;
--color-text-active: #e2e2ea;
--color-text-on-header: rgba(255, 255, 255, 0.8);
--color-text-on-header-hover: #fff;
@@ -155,6 +155,20 @@ body {
background: var(--color-bg-page);
}
+/* ── Utility ──────────────────────────────────────────────────────────────── */
+
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
+
/* ── App shell ────────────────────────────────────────────────────────────── */
.app {
@@ -163,6 +177,26 @@ body {
min-height: 100vh;
}
+/* ── Skip-to-content link ─────────────────────────────────────────────────── */
+
+.skip-link {
+ position: absolute;
+ left: -9999px;
+ top: 0;
+ z-index: 999;
+ padding: 0.5rem 1rem;
+ background: var(--color-accent);
+ color: var(--color-bg-page);
+ font-size: 0.875rem;
+ font-weight: 600;
+ text-decoration: none;
+ border-radius: 0 0 0.375rem 0;
+}
+
+.skip-link:focus {
+ left: 0;
+}
+
.app-header {
display: flex;
align-items: center;
@@ -172,7 +206,7 @@ body {
color: var(--color-text-on-header-hover);
}
-.app-header h1 {
+.app-header__brand span {
font-size: 1.125rem;
font-weight: 600;
letter-spacing: -0.01em;
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 98955df..6d36267 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -58,9 +58,10 @@ export default function App() {
return (