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.

Layout is DevHaunt’s root page wrapper. Every route is rendered inside it, giving the entire site a consistent spooky atmosphere: the cursor trail glows pumpkin orange, bats drift across the background, fog creeps along the bottom of the viewport, and the top navigation stays pinned. On top of that, Layout manages Framer Motion’s AnimatePresence so each page dissolves smoothly in and out with a blur transition whenever the route changes.

Component signature

Layout accepts a single prop — children — and has no configuration surface of its own. All ambient effects are mounted unconditionally; their individual components control their own behaviour.
function Layout({ children }) { ... }

JSX structure

function Layout({ children }) {
  const location = useLocation();
  return (
    <div className="min-h-screen bg-night text-ghost relative selection:bg-pumpkin selection:text-night">
      <CursorTrail />
      <BatSwarm />
      <FogLayer />
      <Navigation />
      <AnimatePresence mode="wait">
        <motion.main
          key={location.pathname}
          initial={{ opacity: 0, filter: 'blur(10px)' }}
          animate={{ opacity: 1, filter: 'blur(0px)' }}
          exit={{ opacity: 0, filter: 'blur(10px)' }}
          transition={{ duration: 0.8, ease: 'easeInOut' }}
          className="pt-24 pb-32 px-4 md:px-8 max-w-7xl mx-auto min-h-screen flex flex-col items-center justify-center relative z-10"
        >
          {children}
        </motion.main>
      </AnimatePresence>
    </div>
  );
}

Page transitions

Each route change triggers a full blur-dissolve cycle:
PhaseopacityfilterDuration
Enter (initial)0blur(10px)
Visible (animate)1blur(0px)0.8 s
Exit (exit)0blur(10px)0.8 s
The easeInOut timing curve makes the blur feel like the page is materialising out of the fog, and the matching exit keeps the transition symmetrical.

Why key={location.pathname}?

AnimatePresence detects when a child is replaced by watching its React key. By passing location.pathname as the key on motion.main, a new <motion.main> is mounted for every route change — which tells AnimatePresence to play the exit animation on the outgoing page before mounting the incoming one. Without this key the blur transition would not fire on navigation.
mode="wait" on AnimatePresence ensures the exiting page fully completes its animation before the entering page begins. Remove it only if you want both pages to animate simultaneously.

Usage

Wrap your route tree in Layout inside your application’s root so every page inherits the ambient effects and page transitions automatically.
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Layout from './components/Layout';
import Home from './pages/Home';
import About from './pages/About';
import Projects from './pages/Projects';

export default function App() {
  return (
    <BrowserRouter>
      <Layout>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/projects" element={<Projects />} />
        </Routes>
      </Layout>
    </BrowserRouter>
  );
}

Z-index layering

The ambient effects use carefully staged z-index values so they never compete with interactive page content:
LayerComponentz-index
Page content (motion.main)(your routes)10
Bat swarmBatSwarm30
Fog overlayFogLayer40
Navigation barNavigation50
Cursor trailCursorTrail50
All ambient layers use pointer-events: none, so they never intercept clicks intended for page content.

Build docs developers (and LLMs) love