The Magical Portfolio solves a fundamental tension of static hosting: React Router expects to own the URL path (Documentation Index
Fetch the complete documentation index at: https://mintlify.com/apursley2012/magical/llms.txt
Use this file to discover all available pages before exploring further.
/about, /projects, …), but static file servers can only serve files that exist on disk. Rather than requiring 404-rewrite rules — which are host-specific and sometimes unavailable — the portfolio uses hash-based routing. Every page is addressable as pages/About.html#/about. The fragment (#/about) is consumed entirely client-side by React Router; the file server only ever sees the path portion, which always resolves to a real .html file.
Hash Routing Mechanism
Each HTML shim contains a small inline script that runs before the React bundle loads. Its job is to ensure the URL always carries a hash fragment, so React Router’sHashRouter has something to parse:
-
window.__STATIC_PAGE_ROUTE__is set to the canonical route path for this page (e.g."/","/about","/case-studies"). The React app reads this value on boot to initialise the router with the correct starting location even before any user navigation. -
The IIFE checks whether
window.location.hashis absent or is the bare"#"string. If so, it immediately replaces the URL with the same path + a#/(or#/about, depending on the page) fragment. This handles the case where a user lands onpages/About.htmldirectly without a fragment — the redirect fires instantly, before React mounts, so there is no flash.
Route Definitions
The portfolio ships with seven routes. Each has its own HTML shim, a descriptive page title, and an arcane name used throughout the UI:| URL Path | HTML File | Page Title | Arcane Name |
|---|---|---|---|
/ | index.html | Home | Portal |
/about | pages/About.html | About | Chronicle |
/projects | pages/Projects.html | Projects | Grimoire |
/skills | pages/Skills.html | Skills | Affinities |
/writing | pages/Writing.html | Writing | Tomes |
/case-studies | pages/CaseStudies.html | Case Studies | Rituals |
/contact | pages/Contact.html | Contact | Summoning |
index.html sets window.__STATIC_PAGE_ROUTE__ = "/" and redirects to #/. The pages/ shims set their respective route strings and redirect to the appropriate fragment (#/about, #/case-studies, etc.).
Navigation Array
TheNavigation component is driven by a static navItems array defined at the top of components/Navigation.js. Each item maps a React Router path to a human label and a Lucide React icon:
navItems.slice(1) (skipping the Portal/home entry) as <NavLink> components. When a link is active, Framer Motion animates a shared layout indicator (layoutId="nav-indicator") across the active item using spring physics (stiffness: 300, damping: 30), creating the sliding highlight effect.
The Portal link (path: "/") appears as the logo/wordmark on the left side of the nav bar rather than in the link list, keeping the navigation compact.
Adding a New Route
Create the page component
Add a new React component file to your source tree, for example
src/pages/Compendium.jsx. Give it a default export rendering your page content. Follow the existing pattern of wrapping the page in a min-h-screen pt-12 pb-24 container so it clears the fixed navigation bar.Register the route in the router
Open the App shell (the file that defines the The
<Routes> tree) and import your new component. Add a <Route> entry:AnimatePresence wrapper keyed on useLocation().pathname will automatically apply the standard blur/fade page transition to the new route.Add the nav item
Append an entry to the Import
navItems array in components/Navigation.js. Choose an appropriate Lucide icon and an arcane label that fits the mystical theme:Library (or your chosen icon) from lucide-react at the top of the file.Create the HTML shim file
Copy an existing shim — for example
pages/About.html — to pages/Compendium.html. Update the three fields that are page-specific:<title>tag: changeAbout | magical→Compendium | magicalwindow.__STATIC_PAGE_ROUTE__: change"/about"→"/compendium"- The hash-redirect string: change
"#/about"→"#/compendium"
../assets/main.js and ../assets/main.css are correct for a file sitting inside pages/.pages/Compendium.html#/compendium (direct link) and via the navigation bar inside the running SPA.