Page Choreography
This document explains how each major route is assembled, what motion sequencing it uses, and where page-level orchestration versus shared component concerns live.
For the motion primitive catalog, see Motion architecture. For the component layer, see Component architecture.
Route assembly overview
Home (/)
Home presents identity and CV/projects links immediately, alongside a faux-VS Code IDE. The optional, nonmodal preferences panel opens from “Customize appearance & audio”, which acts as a disclosure toggle (aria-expanded/aria-controls). It dismisses after five idle seconds; hover or keyboard focus pauses the timer. Keyboard or assistive-technology activation moves focus into the panel; pointer activation leaves focus on the opener. Escape, click-away, or either dismiss button closes it; keyboard dismissal restores focus to its Customize opener, and header settings remain available. Audio starts only after explicit play. The IDE remains windowed until explicitly expanded; close, minimize, restore, drag, and resize remain available. Off/reduced motion settles the hero immediately and shows a stable terminal transcript.
Assembly
Entrance sequence
Page-owned state
| State | Purpose |
|---|---|
ideWindowState | normal / minimized / expanded |
ideWindowSize | width × height for resize |
| Drag constraints | Bound to hero container via heroBoundsRef |
| Typewriter playing | Triggered by HeroMotionPath completion |
| Scroll transforms | heroScale and heroOpacity from scroll progress |
| Optional customization | useHomeWelcomeSequence(); opened by the user; five-second idle timer paused during interaction |
IDE window interactions
CV (/cv)
The CV page has two modes: a default grid layout and an immersive story viewer. Default mode reveals deferred sections on scroll, a known section hash, or the “Explore full CV” button. An explicit reveal focuses Experience; a deep link focuses its target. Resume and contact actions remain visible in About.
Default mode — assembly
Section layout metadata
src/pages/cvPageLayout.ts defines per-section placement and motion timing:
| Section | Desktop region | Desktop delay | Desktop triggerOnView | Mobile order |
|---|---|---|---|---|
| about | top | 0ms | false (immediate) | 0 |
| experience | main | 120ms | true | 1 |
| education | main | 240ms | true | 2 |
| volunteering | main | 360ms | true | 3 |
| github | sidebar | 120ms | true | 4 |
| certificates | sidebar | 240ms | true | 5 |
| coding | main | 480ms | true | 6 |
On mobile, all sections stack vertically with delayMs: 0 and most use triggerOnView: true (viewport-triggered reveal).
Within each section
Each CVSection* component wraps a CVSectionCard which contains:
SectionHeading(overline + title)- Feature content (list, profile card, GitHub panel, etc.)
- Internal
AnimatedContentListfor repeated entries (with per-item stagger) - Optional
TabPanel/AnimatedSlideListfor expandable detail
Story mode (?mode=story)
Activated via query parameter. Replaces the grid layout with CVStoryViewer, a fullscreen scroll container that renders the ordered story chapters from buildCVStoryItems().
Climbing (/climbing)
Assembly
useFuzzySearch()provides route/location filtering across both route tables- initial load shows the Climbing h1, introduction, and “View climbing log” button; analytics and tables reveal on either the existing scroll threshold or the button. The button moves focus to the log, and both searches have persistent labels.
- once unlocked, the main climbing card enters with the same spring-style card motion used by the deferred CV sections
- Analytics panels use
SectionPanelfor dense data display
Photography (/photography)
Assembly
AlbumCardwraps inMotionTiltCardon desktop for 3D tilt parallaxStaggerChildrenorchestrates sequential reveal across album cards- Image load state tracked to prevent layout shift during loading
Category detail (/photography/:slug)
- Album metadata is in normal document flow below the cover; location chips use a native details disclosure.
- Quilted layout uses MUI
ImageListwith one column below 600 px, two below 900 px, and four above; desktop spans are clamped to the available columns. - Display media uses generated width-descriptor WebP sources; download links retain originals.
ImmersiveLightboxis a full-screen overlay with keyboard navigation, download, and close- Legacy slug redirects handled in the component for backward compatibility
Blog (/blog)
Public route rendered as part of the standard route set.
Assembly
BlogHerousesContentCardwith display-scale typography (editorial exception)BlogPostListrendersBlogPostCarditems in a responsive grid- Tag filtering is page-local state driven by
useBlogData().tags
Post detail (/blog/:slug)
BlogArticleBodyrenders typedBlogContentBlock[]: text, code, image, callout, blockquoteBlogCodeBlockprovides syntax highlighting with copy-to-clipboard- Not-found slugs render
RouteRecoveryPanelwith contextual suggestions
NotFound (/*)
Assembly
- No
PageFrame— usesBackgroundPaperdirectly for full-bleed treatment StaggerChildren+MotionItemanimate the text revealRouteRecoveryPanelprovides contextual navigation suggestions based on the attempted path
Page-level vs component-level concerns
| Concern | Belongs at page level | Belongs in component |
|---|---|---|
| Section ordering and delay timing | ✓ | |
delayMs for each section | ✓ (via layout metadata) | |
triggerOnView per section | ✓ (via layout metadata) | |
| Tab/accordion open state | ✓ (feature component) | |
| Search/filter state | ✓ (page-local state) | |
| Stagger timing within a section | ✓ (AnimatedContentList) | |
| Hover/tilt interactions | ✓ (motion primitives) | |
| Window drag/resize (Home) | ✓ | |
| Story mode scroll progress + active chapter tracking | ✓ (CVStoryViewer) | |
| Background image selection | ✓ (passed to PageFrame) | |
| SEO metadata | ✓ (via useDocumentMetadata()) |
Choreography rules for new pages
- Use
PageFrameas the scaffold unless the page needs a full-bleed background (then useBackgroundPaper) - Wrap sections in
MotionSectionorSectionCardfor viewport-triggered reveals - Define section delays in a layout metadata object (not inline) if the page has multiple sections with staggered timing
- Let shared components handle their internal animation — don't override stagger timing from the page
- Use
SectionHeadingfor section intros — don't build custom title stacks - Keep page-level state minimal — filters, search, expanded mode — and pass data down as props
- Follow the data flow: hook → page → section component → list → card
Further reading
- Motion architecture — token system, variants, intensity scaling
- Component architecture — shared primitives and feature components
- App architecture — route definitions and page composition model
- Design system reference — surface and layout primitive catalog