feat: Demoted nav brand to span, promoted page headings to h1, added sk…
- "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" GSD-Task: S04/T01
This commit is contained in:
parent
089435a990
commit
5e5961fa92
18 changed files with 646 additions and 18 deletions
|
|
@ -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. |
|
| 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. |
|
| 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. |
|
| S04 | Accessibility & SEO Fixes | low | — | ⬜ | Every page has single H1, skip-to-content link, AA contrast text, and descriptive browser tab title. |
|
||||||
|
|
|
||||||
85
.gsd/milestones/M011/slices/S03/S03-SUMMARY.md
Normal file
85
.gsd/milestones/M011/slices/S03/S03-SUMMARY.md
Normal file
|
|
@ -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"
|
||||||
167
.gsd/milestones/M011/slices/S03/S03-UAT.md
Normal file
167
.gsd/milestones/M011/slices/S03/S03-UAT.md
Normal file
|
|
@ -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
|
||||||
16
.gsd/milestones/M011/slices/S03/tasks/T02-VERIFY.json
Normal file
16
.gsd/milestones/M011/slices/S03/tasks/T02-VERIFY.json
Normal file
|
|
@ -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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,36 @@
|
||||||
# S04: Accessibility & SEO Fixes
|
# 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.
|
**Demo:** After this: Every page has single H1, skip-to-content link, AA contrast text, and descriptive browser tab title.
|
||||||
|
|
||||||
## Tasks
|
## 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 `<h1>Chrysopedia</h1>` in App.tsx nav to `<span>`. Update `.app-header h1` CSS rule to `.app-header__brand span`. Promote `<h2>` to `<h1>` on pages that lack one: TopicsBrowse, CreatorsBrowse, SubTopicPage, AdminReports, AdminPipeline. Add `<h1>` 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 `<main>` tag in App.tsx. Add `<a href="#main-content" className="skip-link">Skip to content</a>` 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
|
||||||
|
|
|
||||||
103
.gsd/milestones/M011/slices/S04/S04-RESEARCH.md
Normal file
103
.gsd/milestones/M011/slices/S04/S04-RESEARCH.md
Normal file
|
|
@ -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 `<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.
|
||||||
43
.gsd/milestones/M011/slices/S04/tasks/T01-PLAN.md
Normal file
43
.gsd/milestones/M011/slices/S04/tasks/T01-PLAN.md
Normal file
|
|
@ -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 `<h1>Chrysopedia</h1>` in App.tsx nav to `<span>`. Update `.app-header h1` CSS rule to `.app-header__brand span`. Promote `<h2>` to `<h1>` on pages that lack one: TopicsBrowse, CreatorsBrowse, SubTopicPage, AdminReports, AdminPipeline. Add `<h1>` 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 `<main>` tag in App.tsx. Add `<a href="#main-content" className="skip-link">Skip to content</a>` 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 `<span>` instead of `<h1>`, 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
|
||||||
92
.gsd/milestones/M011/slices/S04/tasks/T01-SUMMARY.md
Normal file
92
.gsd/milestones/M011/slices/S04/tasks/T01-SUMMARY.md
Normal file
|
|
@ -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.
|
||||||
56
.gsd/milestones/M011/slices/S04/tasks/T02-PLAN.md
Normal file
56
.gsd/milestones/M011/slices/S04/tasks/T02-PLAN.md
Normal file
|
|
@ -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
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
/* Text */
|
/* Text */
|
||||||
--color-text-primary: #e2e2ea;
|
--color-text-primary: #e2e2ea;
|
||||||
--color-text-secondary: #8b8b9a;
|
--color-text-secondary: #8b8b9a;
|
||||||
--color-text-muted: #6b6b7a;
|
--color-text-muted: #828291;
|
||||||
--color-text-active: #e2e2ea;
|
--color-text-active: #e2e2ea;
|
||||||
--color-text-on-header: rgba(255, 255, 255, 0.8);
|
--color-text-on-header: rgba(255, 255, 255, 0.8);
|
||||||
--color-text-on-header-hover: #fff;
|
--color-text-on-header-hover: #fff;
|
||||||
|
|
@ -155,6 +155,20 @@ body {
|
||||||
background: var(--color-bg-page);
|
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 shell ────────────────────────────────────────────────────────────── */
|
||||||
|
|
||||||
.app {
|
.app {
|
||||||
|
|
@ -163,6 +177,26 @@ body {
|
||||||
min-height: 100vh;
|
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 {
|
.app-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -172,7 +206,7 @@ body {
|
||||||
color: var(--color-text-on-header-hover);
|
color: var(--color-text-on-header-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
.app-header h1 {
|
.app-header__brand span {
|
||||||
font-size: 1.125rem;
|
font-size: 1.125rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: -0.01em;
|
letter-spacing: -0.01em;
|
||||||
|
|
|
||||||
|
|
@ -58,9 +58,10 @@ export default function App() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
|
<a href="#main-content" className="skip-link">Skip to content</a>
|
||||||
<header className="app-header" ref={headerRef}>
|
<header className="app-header" ref={headerRef}>
|
||||||
<Link to="/" className="app-header__brand">
|
<Link to="/" className="app-header__brand">
|
||||||
<h1>Chrysopedia</h1>
|
<span>Chrysopedia</span>
|
||||||
</Link>
|
</Link>
|
||||||
{showNavSearch && (
|
{showNavSearch && (
|
||||||
<SearchAutocomplete
|
<SearchAutocomplete
|
||||||
|
|
@ -111,7 +112,7 @@ export default function App() {
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main className="app-main">
|
<main className="app-main" id="main-content">
|
||||||
<Routes>
|
<Routes>
|
||||||
{/* Public routes */}
|
{/* Public routes */}
|
||||||
<Route path="/" element={<Home />} />
|
<Route path="/" element={<Home />} />
|
||||||
|
|
|
||||||
|
|
@ -574,7 +574,7 @@ export default function AdminPipeline() {
|
||||||
<div className="admin-pipeline">
|
<div className="admin-pipeline">
|
||||||
<div className="admin-pipeline__header">
|
<div className="admin-pipeline__header">
|
||||||
<div>
|
<div>
|
||||||
<h2 className="admin-pipeline__title">Pipeline Management</h2>
|
<h1 className="admin-pipeline__title">Pipeline Management</h1>
|
||||||
<p className="admin-pipeline__subtitle">
|
<p className="admin-pipeline__subtitle">
|
||||||
{videos.length} video{videos.length !== 1 ? "s" : ""}
|
{videos.length} video{videos.length !== 1 ? "s" : ""}
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ export default function AdminReports() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="admin-reports">
|
<div className="admin-reports">
|
||||||
<h2 className="admin-reports__title">Content Reports</h2>
|
<h1 className="admin-reports__title">Content Reports</h1>
|
||||||
<p className="admin-reports__subtitle">
|
<p className="admin-reports__subtitle">
|
||||||
{total} report{total !== 1 ? "s" : ""} total
|
{total} report{total !== 1 ? "s" : ""} total
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -86,7 +86,7 @@ export default function CreatorsBrowse() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="creators-browse">
|
<div className="creators-browse">
|
||||||
<h2 className="creators-browse__title">Creators</h2>
|
<h1 className="creators-browse__title">Creators</h1>
|
||||||
<p className="creators-browse__subtitle">
|
<p className="creators-browse__subtitle">
|
||||||
Discover creators and their technique libraries
|
Discover creators and their technique libraries
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -117,21 +117,21 @@ export default function Home() {
|
||||||
<div className="home-how-it-works">
|
<div className="home-how-it-works">
|
||||||
<div className="home-how-it-works__step">
|
<div className="home-how-it-works__step">
|
||||||
<span className="home-how-it-works__number">1</span>
|
<span className="home-how-it-works__number">1</span>
|
||||||
<h3 className="home-how-it-works__title">Creators Share Techniques</h3>
|
<h2 className="home-how-it-works__title">Creators Share Techniques</h2>
|
||||||
<p className="home-how-it-works__desc">
|
<p className="home-how-it-works__desc">
|
||||||
Real producers and sound designers publish in-depth tutorials
|
Real producers and sound designers publish in-depth tutorials
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="home-how-it-works__step">
|
<div className="home-how-it-works__step">
|
||||||
<span className="home-how-it-works__number">2</span>
|
<span className="home-how-it-works__number">2</span>
|
||||||
<h3 className="home-how-it-works__title">AI Extracts Key Moments</h3>
|
<h2 className="home-how-it-works__title">AI Extracts Key Moments</h2>
|
||||||
<p className="home-how-it-works__desc">
|
<p className="home-how-it-works__desc">
|
||||||
We distill hours of video into structured, searchable knowledge
|
We distill hours of video into structured, searchable knowledge
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="home-how-it-works__step">
|
<div className="home-how-it-works__step">
|
||||||
<span className="home-how-it-works__number">3</span>
|
<span className="home-how-it-works__number">3</span>
|
||||||
<h3 className="home-how-it-works__title">You Find Answers Fast</h3>
|
<h2 className="home-how-it-works__title">You Find Answers Fast</h2>
|
||||||
<p className="home-how-it-works__desc">
|
<p className="home-how-it-works__desc">
|
||||||
Search by topic, technique, or creator — get straight to the insight
|
Search by topic, technique, or creator — get straight to the insight
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -161,13 +161,13 @@ export default function Home() {
|
||||||
{/* Navigation cards */}
|
{/* Navigation cards */}
|
||||||
<section className="nav-cards">
|
<section className="nav-cards">
|
||||||
<Link to="/topics" className="nav-card card-stagger" style={{ '--stagger-index': 0 } as React.CSSProperties}>
|
<Link to="/topics" className="nav-card card-stagger" style={{ '--stagger-index': 0 } as React.CSSProperties}>
|
||||||
<h3 className="nav-card__title"><IconTopics /> Topics</h3>
|
<h2 className="nav-card__title"><IconTopics /> Topics</h2>
|
||||||
<p className="nav-card__desc">
|
<p className="nav-card__desc">
|
||||||
Browse techniques organized by category and sub-topic
|
Browse techniques organized by category and sub-topic
|
||||||
</p>
|
</p>
|
||||||
</Link>
|
</Link>
|
||||||
<Link to="/creators" className="nav-card card-stagger" style={{ '--stagger-index': 1 } as React.CSSProperties}>
|
<Link to="/creators" className="nav-card card-stagger" style={{ '--stagger-index': 1 } as React.CSSProperties}>
|
||||||
<h3 className="nav-card__title"><IconCreators /> Creators</h3>
|
<h2 className="nav-card__title"><IconCreators /> Creators</h2>
|
||||||
<p className="nav-card__desc">
|
<p className="nav-card__desc">
|
||||||
Discover creators and their technique libraries
|
Discover creators and their technique libraries
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -188,7 +188,7 @@ export default function Home() {
|
||||||
{/* Featured Technique Spotlight */}
|
{/* Featured Technique Spotlight */}
|
||||||
{featured && (
|
{featured && (
|
||||||
<section className="home-featured">
|
<section className="home-featured">
|
||||||
<h3 className="home-featured__label">Featured Technique</h3>
|
<h2 className="home-featured__label">Featured Technique</h2>
|
||||||
<Link to={`/techniques/${featured.slug}`} className="home-featured__title">
|
<Link to={`/techniques/${featured.slug}`} className="home-featured__title">
|
||||||
{featured.title}
|
{featured.title}
|
||||||
</Link>
|
</Link>
|
||||||
|
|
@ -218,7 +218,7 @@ export default function Home() {
|
||||||
|
|
||||||
{/* Recently Added */}
|
{/* Recently Added */}
|
||||||
<section className="recent-section">
|
<section className="recent-section">
|
||||||
<h3 className="recent-section__title">Recently Added</h3>
|
<h2 className="recent-section__title">Recently Added</h2>
|
||||||
{recentLoading ? (
|
{recentLoading ? (
|
||||||
<div className="loading">Loading…</div>
|
<div className="loading">Loading…</div>
|
||||||
) : recent.length === 0 ? (
|
) : recent.length === 0 ? (
|
||||||
|
|
|
||||||
|
|
@ -52,6 +52,7 @@ export default function SearchResults() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="search-results-page">
|
<div className="search-results-page">
|
||||||
|
<h1 className="sr-only">Search Results</h1>
|
||||||
{/* Inline search bar */}
|
{/* Inline search bar */}
|
||||||
<SearchAutocomplete
|
<SearchAutocomplete
|
||||||
variant="inline"
|
variant="inline"
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ export default function SubTopicPage() {
|
||||||
<span className="breadcrumbs__current" aria-current="page">{subtopicDisplay}</span>
|
<span className="breadcrumbs__current" aria-current="page">{subtopicDisplay}</span>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<h2 className="subtopic-page__title">{subtopicDisplay}</h2>
|
<h1 className="subtopic-page__title">{subtopicDisplay}</h1>
|
||||||
<p className="subtopic-page__subtitle">
|
<p className="subtopic-page__subtitle">
|
||||||
<span className={`badge badge--cat-${slug}`}>{categoryDisplay}</span>
|
<span className={`badge badge--cat-${slug}`}>{categoryDisplay}</span>
|
||||||
<span className="subtopic-page__subtitle-sep">·</span>
|
<span className="subtopic-page__subtitle-sep">·</span>
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ export default function TopicsBrowse() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="topics-browse">
|
<div className="topics-browse">
|
||||||
<h2 className="topics-browse__title">Topics</h2>
|
<h1 className="topics-browse__title">Topics</h1>
|
||||||
<p className="topics-browse__subtitle">
|
<p className="topics-browse__subtitle">
|
||||||
Browse techniques organized by category and sub-topic
|
Browse techniques organized by category and sub-topic
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue