DevHaunt follows a single-page application (SPA) pattern: there is one React root mounted intoDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/apursley2012/dev-haunt/llms.txt
Use this file to discover all available pages before exploring further.
<div id="root"> in index.html, and all navigation between pages happens client-side through React Router v6 without a full browser reload. To support direct URL access (bookmarking /projects, sharing a link to /blog, and so on) without a server-side router, each route also has a corresponding static HTML file in the pages/ directory that bootstraps the hash and hands off to the SPA.
Vite build output
Vite compiles the entire React application into a small set of ESM bundles that are loaded by every HTML entry point:| File | Purpose |
|---|---|
assets/main.js | Primary bundle — all page components, data arrays, and app bootstrap |
assets/main.css | Compiled Tailwind stylesheet plus custom CSS animations |
assets/jsx-runtime.js | React JSX runtime |
assets/proxy.js | Framer Motion motion proxy |
assets/index.js | Framer Motion AnimatePresence |
assets/createLucideIcon.js | Lucide React icon factory |
components/Navigation.js | Navigation component and router setup |
components/Layout.js | Layout wrapper (ambient effects + AnimatePresence) |
components/*.js | One file per themed component |
Static page routing pattern
Each route has a matching HTML file inpages/ (for example, pages/About.html). These files serve a single purpose: tell the React app which hash route to activate when the page loads. They do this with a small inline script:
pages/About.html directly, the script sets the URL hash to #/about before React mounts. React Router reads that hash and renders the correct page component. The SPA then takes over for all subsequent navigation.
The
window.__STATIC_PAGE_ROUTE__ variable is set but not actively consumed by the React application in the current build — it is a marker for tooling and future server-side logic. The hash redirect is what actually drives the initial route.Component hierarchy
The application mounts as a single tree fromHashRouter down to individual page components. The table below shows the nesting order:
| Level | Component | Responsibility |
|---|---|---|
| 1 | HashRouter (via react-router-dom) | Provides routing context for the whole app |
| 2 | Layout | Renders ambient effects + wraps content |
| 3 | CursorTrail | Replaces system cursor with glowing trail |
| 3 | BatSwarm | Background bat animation layer |
| 3 | FogLayer | SVG fog fixed to the bottom of the viewport |
| 3 | Navigation | Fixed top nav bar with active-route highlighting |
| 3 | AnimatePresence | Framer Motion presence context for route transitions |
| 4 | motion.main | Animated wrapper for the current page — blur + opacity |
| 5 | Page component | The active route component (e.g., HomePage, ProjectsPage) |
assets/main.js looks like this:
Data model
All portfolio content lives as plain JavaScript arrays declared directly inassets/main.js. There is no external API, database, or CMS. The five content arrays are:
| Variable | Type | Powers |
|---|---|---|
| Projects array | Array<{ title, epitaph, tech, isAlive, github, link }> | Graveyard page — each entry becomes a Tombstone card |
| Skills array | Array<{ name, level, delay }> | Pumpkin Patch page — each skill renders as a JackOLantern |
| Work history array | Array<{ company, role, period, description }> | Hall of Doors — each entry becomes a DoorPanel |
| Blog articles array | Array<{ id, title, date, excerpt, color }> | Tales page — each article becomes a Postcard on the shelf |
| Testimonials array | Array<{ name, role, quote, delay }> | Spirits Speak — each testimonial renders as a FloatingGhost |
Because
assets/main.js is a minified bundle, the array variables appear as single-letter names (M, R, C, F, _) rather than the descriptive names shown above. Use your editor’s search to locate each array by a known string value from the placeholder data (for example, search for "Specter UI" to find the projects array).Route transitions
AnimatePresence from Framer Motion wraps the active route inside the Layout component. The motion.main wrapper applies a blur-and-opacity animation on every route change:
mode="wait" prop on AnimatePresence ensures the exiting page finishes its blur-out before the entering page begins blurring in, keeping transitions smooth even on fast navigation.