Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/mystery-haunting/llms.txt

Use this file to discover all available pages before exploring further.

Mystery Haunting uses React Router v6 (react-router-dom@6) with a BrowserRouter to manage client-side navigation across nine portfolio pages. The BrowserRouter uses the browser’s History API so that every route maps to a clean pathname (e.g., /about) rather than a hash fragment. To support static hosting on GitHub Pages — where there is no server capable of rewriting arbitrary paths to index.html — each route also has a corresponding file in pages/ that seeds window.location.hash before the app boots (see Static Page Exports and Deep Linking below). The router is initialized inside the root App component, which wraps a Routes tree containing one Route per portfolio section.

Route Reference

PathComponentSpooky LabelDescription
/LandingThe FoyerAnimated welcome page with a spooky haunted house silhouette, floating ghost icons, and a “ENTER IF YOU DARE” CTA button
/aboutAboutThe AtticMonster dossier page with an interactive card that flips open on click to reveal subject details
/projectsProjectsThe GraveyardProject showcase rendered as tombstone cards with hover pop-up ghost cards
/skillsSkillsThe LabAnimated potion bottle skill bars that fill with colored liquid on scroll into view
/workWorkPrevious HauntingsCareer timeline with alternating entries and animated house icons that light up on hover
/case-studiesCaseStudiesAutopsy ReportsPost-mortem bug reports styled as aged paper case files with a “RESOLVED” stamp
/blogBlogForbidden LibraryInteractive bookshelf with hoverable book spines that tilt and glow on hover
/testimonialsTestimonialsEye-WitnessesMouse-tracking SVG eye widgets that follow cursor movement; click to reveal testimonial quotes
/contactContactSéance CircleOuija board contact form with a planchette that animates to each typed letter’s position on the board

Page Transition Animation

Every route change triggers a 3D book-flip transition powered by Framer Motion’s AnimatePresence and motion.div. The Layout component wraps page content in an AnimatePresence mode="wait" block so the outgoing page fully completes its exit animation before the incoming page begins its entrance. The transition is driven by a shared variants object:
const pageVariants = {
  initial: {
    opacity: 0,
    rotateY: -90,
    transformPerspective: 1200,
    transformOrigin: "left center",
  },
  in: {
    opacity: 1,
    rotateY: 0,
    transition: { duration: 0.8, ease: "easeOut" },
  },
  out: {
    opacity: 0,
    rotateY: 90,
    transformOrigin: "right center",
    transition: { duration: 0.8, ease: "easeIn" },
  },
};
  • initial — the incoming page starts rotated 90° away from the viewer (like a closed book page), invisible, with a 1200 px perspective applied.
  • in — the page rotates to face the viewer (rotateY: 0), fading in over 0.8 s with an ease-out curve.
  • out — the departing page rotates 90° in the opposite direction and fades out over 0.8 s with an ease-in curve, simulating a page being turned away.
The motion.div is keyed by location.pathname, so React treats each route as a distinct element and triggers the full enter/exit lifecycle on every navigation:
<AnimatePresence mode="wait">
  <motion.div
    key={location.pathname}
    initial="initial"
    animate="in"
    exit="out"
    variants={pageVariants}
    className="min-h-full w-full absolute inset-0"
  >
    {children}
  </motion.div>
</AnimatePresence>
The perspective-[1200px] utility is applied to the <main> container in Layout to establish the 3D rendering context required for the rotateY transforms to appear correctly in the browser.

Route Definitions

All nine routes are declared inside a single <Routes> block in the root component. Each route maps a URL path to its corresponding page component:
<Routes>
  <Route path="/" element={<Landing />} />
  <Route path="/about" element={<About />} />
  <Route path="/projects" element={<Projects />} />
  <Route path="/skills" element={<Skills />} />
  <Route path="/work" element={<Work />} />
  <Route path="/case-studies" element={<CaseStudies />} />
  <Route path="/blog" element={<Blog />} />
  <Route path="/testimonials" element={<Testimonials />} />
  <Route path="/contact" element={<Contact />} />
</Routes>
The <Routes> and <Route> components are imported from react-router-dom and compiled into the assets/main.js bundle (aliased as S and d respectively in the minified output).

Static Page Exports and Deep Linking

Because BrowserRouter relies on the server to serve index.html for every path, direct access to a route URL on static GitHub Pages hosting would normally return a 404. To work around this, each pages/ HTML file sets window.__STATIC_PAGE_ROUTE__ and seeds window.location.hash before the app boots, giving the React app a hint about which route to render:
<script>
  window.__STATIC_PAGE_ROUTE__ = "/about";
  if (!window.location.hash || window.location.hash === "#/" || window.location.hash === "#") {
    window.location.hash = "/about";
  }
</script>
Setting window.location.hash changes the URL in the browser (e.g., to pages/About.html#/about) before the React bundle executes. The BrowserRouter then reads window.location and can use the seeded hash value to navigate to the correct route on first render, so the right page component is displayed without a redirect or flash of the landing page.
The sidebar navigation in Layout uses the useLocation hook from React Router to compare location.pathname against each nav item’s path. The active link receives the text-ghost-teal text-glow-teal classes and a filled teal dot indicator; inactive links show muted bone text with a hollow dot that turns pumpkin on hover.

Build docs developers (and LLMs) love