Dyed in the Wool is deployed as a fully static site on GitHub Pages. Because GitHub Pages serves files from a flat directory without server-side redirect rules, the app uses React Router v6’sDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/apursley2012/dyed-in-the-wool/llms.txt
Use this file to discover all available pages before exploring further.
HashRouter — routing via the URL hash (/#/about) rather than the path (/about). This means every direct URL visit lands on index.html, and the hash fragment tells the router which page to display.
Why HashRouter?
GitHub Pages returns a 404 for any path it doesn’t have a file for. WithBrowserRouter, navigating directly to https://example.github.io/dyed-in-the-wool/about would produce a 404 because there’s no about/index.html. With HashRouter, the URL becomes https://example.github.io/dyed-in-the-wool/#/about — the hash is never sent to the server, so index.html is always served and React Router handles the rest client-side.
Route Definitions
The app defines six routes, all nested under the root"/" path which renders <Layout> as their shared shell via <Outlet />.
| Hash URL | Route path | Component | Page title |
|---|---|---|---|
/#/ | / (index) | Home | ”DYED IN THE WOOL” hero |
/#/about | about | About | ”THE HUMAN BEHIND THE DYE” |
/#/projects | projects | Projects | ”PATTERNS I’VE MADE” |
/#/skills | skills | Skills | ”COLOR PALETTE” |
/#/work | work | Work | ”THE TAPESTRY” |
/#/contact | contact | Contact | ”LEAVE A MARK” |
Navigation Links
The header nav is rendered insideLayout and maps over a static navLinks array to produce NavLink components for each route. Active state is detected via React Router’s isActive prop and visually indicated by a Framer Motion underline with a shared layoutId, so the indicator slides smoothly between nav items when the active route changes.
layoutId="nav-indicator" causes Framer Motion to treat every instance of that motion.div as the same element — when the active route changes, the indicator bar animates its position from the old link to the new one via a spring transition. The initial={false} prop prevents the bar from animating in from scratch on the first render.
The header itself uses mix-blend-difference so the white text and indicator remain legible regardless of whatever colorful background content is behind it.
Static HTML Page Stubs
Each route has a corresponding pre-built HTML file in/pages/ (e.g. pages/About.html). These files allow the deployed site to handle bookmarked or shared links that include the full path — the user’s browser fetches the stub, which immediately sets the correct hash and loads the main bundle.
window.__STATIC_PAGE_ROUTE__ (available for inspection) and redirects the hash to #/about if it isn’t already set, then the main bundle loads and the HashRouter renders the correct page.
Page Transitions
AnimatePresence with mode="wait" wraps a motion.div keyed on location.pathname inside Layout. When a route changes, AnimatePresence first plays the exit animation on the outgoing page, waits for it to complete, then plays the enter animation on the incoming page — preventing both from rendering simultaneously.
The transition uses opacity paired with a filter: blur() effect, giving route changes a soft, out-of-focus dissolve feel rather than a hard cut or slide.
key={location.pathname} is what triggers a remount (and therefore a new animation cycle) whenever the active route changes. Without the key, React would reuse the same DOM node and no transition would play.