Skip to main content

Documentation 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.

DevHaunt follows a single-page application (SPA) pattern: there is one React root mounted into <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:
FilePurpose
assets/main.jsPrimary bundle — all page components, data arrays, and app bootstrap
assets/main.cssCompiled Tailwind stylesheet plus custom CSS animations
assets/jsx-runtime.jsReact JSX runtime
assets/proxy.jsFramer Motion motion proxy
assets/index.jsFramer Motion AnimatePresence
assets/createLucideIcon.jsLucide React icon factory
components/Navigation.jsNavigation component and router setup
components/Layout.jsLayout wrapper (ambient effects + AnimatePresence)
components/*.jsOne file per themed component

Static page routing pattern

Each route has a matching HTML file in pages/ (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:
<script>
  window.__STATIC_PAGE_ROUTE__ = "/about";
  if (!window.location.hash || window.location.hash === "#/" || window.location.hash === "#") {
    window.location.hash = "/about";
  }
</script>
When a visitor opens 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 from HashRouter down to individual page components. The table below shows the nesting order:
LevelComponentResponsibility
1HashRouter (via react-router-dom)Provides routing context for the whole app
2LayoutRenders ambient effects + wraps content
3CursorTrailReplaces system cursor with glowing trail
3BatSwarmBackground bat animation layer
3FogLayerSVG fog fixed to the bottom of the viewport
3NavigationFixed top nav bar with active-route highlighting
3AnimatePresenceFramer Motion presence context for route transitions
4motion.mainAnimated wrapper for the current page — blur + opacity
5Page componentThe active route component (e.g., HomePage, ProjectsPage)
The full app bootstrap in assets/main.js looks like this:
function App() {
  return (
    <HashRouter>
      <Layout>
        <Routes>
          <Route path="/"            element={<HomePage />} />
          <Route path="/about"       element={<AboutPage />} />
          <Route path="/projects"    element={<ProjectsPage />} />
          <Route path="/skills"      element={<SkillsPage />} />
          <Route path="/work"        element={<WorkPage />} />
          <Route path="/case-studies" element={<CaseStudiesPage />} />
          <Route path="/blog"        element={<BlogPage />} />
          <Route path="/contact"     element={<ContactPage />} />
          <Route path="/testimonials" element={<TestimonialsPage />} />
        </Routes>
      </Layout>
    </HashRouter>
  );
}

ReactDOM.render(<App />, document.getElementById("root"));

Data model

All portfolio content lives as plain JavaScript arrays declared directly in assets/main.js. There is no external API, database, or CMS. The five content arrays are:
VariableTypePowers
Projects arrayArray<{ title, epitaph, tech, isAlive, github, link }>Graveyard page — each entry becomes a Tombstone card
Skills arrayArray<{ name, level, delay }>Pumpkin Patch page — each skill renders as a JackOLantern
Work history arrayArray<{ company, role, period, description }>Hall of Doors — each entry becomes a DoorPanel
Blog articles arrayArray<{ id, title, date, excerpt, color }>Tales page — each article becomes a Postcard on the shelf
Testimonials arrayArray<{ name, role, quote, delay }>Spirits Speak — each testimonial renders as a FloatingGhost
Editing any of these arrays is the primary way to personalize the template.
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:
initial:  { opacity: 0, filter: "blur(10px)" }
animate:  { opacity: 1, filter: "blur(0px)"  }
exit:     { opacity: 0, filter: "blur(10px)" }
transition: { duration: 0.8, ease: "easeInOut" }
The 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.
To speed up route transitions, reduce the duration value in the motion.main transition object inside components/Layout.js. A value of 0.4 still feels atmospheric while being noticeably snappier.

Build docs developers (and LLMs) love