Skip to main content

Documentation Index

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

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

The Layout component is the persistent root shell of v-doom. It mounts once and stays alive for the entire session, composing every global visual element — the custom cursor, particle canvas, fixed navigation bar, and page-transition wrapper — while delegating the active route’s content to React Router’s <Outlet />. Every page in the portfolio renders inside Layout; nothing bypasses it.

Overview

Layout assembles the following elements on every route:
  • <Cursor /> — the custom animated cursor overlay, rendered at the very top of the tree so it sits above all other content
  • <ParticleBackground /> — a full-viewport canvas of drifting particles rendered behind all other content
  • <Navigation /> — the fixed top navigation bar with logo and route links
  • <AnimatePresence mode="wait"> — Framer Motion’s presence wrapper that orchestrates page-enter and page-exit animations without overlap
  • <Outlet /> — the React Router outlet that renders the matched child route’s page component
The outermost container sets the site-wide color foundation:
<div className="min-h-screen bg-midnight text-bone relative selection:bg-violet/40 selection:text-amber">
Text selections across the entire site inherit the violet/amber highlight palette from this single class.
AnimatePresence is re-exported from Cursor.js and imported from there in Layout, not directly from framer-motion. Both Cursor and AnimatePresence come from the same import.

Page Transitions

Every route change triggers a blur-fade transition powered by Framer Motion’s <AnimatePresence>. The key prop on <m.main> is set to the current pathname (via React Router’s useLocation()), so Framer Motion treats each navigation as a distinct component mount/unmount cycle.
<AnimatePresence mode="wait">
  <m.main
    key={pathname}
    initial={{ opacity: 0, filter: 'blur(10px)' }}
    animate={{ opacity: 1, filter: 'blur(0px)' }}
    exit={{ opacity: 0, filter: 'blur(10px)' }}
    transition={{ duration: 0.6, ease: 'easeInOut' }}
    className="relative z-10 min-h-screen pt-24 pb-12 px-6 md:px-12 max-w-7xl mx-auto"
  >
    <Outlet />
  </m.main>
</AnimatePresence>
The mode="wait" option ensures the exiting page fully completes its exit animation before the incoming page begins its entrance — preventing two pages from being visible simultaneously.
To adjust the transition speed, change the duration value on the transition prop. 0.6 (600 ms) is the default. For a snappier feel try 0.35; for a more ceremonial reveal try 0.9.

Composition Order

The stacking context is intentional. Each layer is assigned a z-index that keeps global chrome above the content canvas:
LayerComponentz-index
Particle background<ParticleBackground />z-0 (base)
Main content area<m.main> / <Outlet />z-10
Navigation bar<Navigation />z-50
Custom cursor<Cursor />z-[100]
The particle background intentionally sits beneath everything. The navigation bar sits above page content so scroll does not obscure it. The cursor sits above all other elements so the amber dot is always visible, even over modals or overlays.

Content Container

The <m.main> element doubles as both the animation target and the layout constraint for all page content:
className="relative z-10 min-h-screen pt-24 pb-12 px-6 md:px-12 max-w-7xl mx-auto"
ClassPurpose
pt-24Clears the fixed navigation bar (≈ 96 px) so page content is never obscured
pb-12Breathing room at the bottom of every page
px-6 md:px-12Responsive horizontal padding — tighter on mobile, wider on desktop
max-w-7xl mx-autoCenters content within an 80 rem max-width column
If you need a full-bleed hero section on a specific page, you can break out of this constraint by using negative margins or a separate fixed/absolute element scoped to that page component — the container only applies within <m.main>.

Router Integration

Layout is registered as the React Router element for the root / route. All child routes are nested beneath it:
<Route path="/" element={<Layout />}>
  <Route index element={<Home />} />
  <Route path="about" element={<About />} />
  <Route path="projects" element={<Projects />} />
  <Route path="skills" element={<Skills />} />
  <Route path="experience" element={<Experience />} />
  <Route path="contact" element={<Contact />} />
</Route>
The <Outlet /> inside Layout renders whichever element matches the current URL. Adding a new page only requires adding a new <Route> here — Layout handles all surrounding chrome automatically.

Build docs developers (and LLMs) love