Skip to main content

Documentation Index

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

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

PageTransition is a lightweight Framer Motion wrapper component used by every page in Dev Nexus to produce consistent enter and exit animations during client-side navigation. It accepts a single children prop and renders a motion.div that blurs and scales into view when a page mounts and blurs and scales out of view when it unmounts, driven by React Router v6 route changes and Framer Motion’s AnimatePresence.

Props

children
ReactNode
required
The full JSX content of the page being wrapped. Every page component in Dev Nexus passes its top-level JSX as children to PageTransition.

Animation Values

The motion.div is configured with the following fixed animation variants:
PropValueEffect
initial{ opacity: 0, scale: 0.95, filter: 'blur(10px)' }Page starts invisible, slightly shrunk, and blurred
animate{ opacity: 1, scale: 1, filter: 'blur(0px)' }Page fades in, expands to full size, and sharpens into focus
exit{ opacity: 0, scale: 1.05, filter: 'blur(10px)' }Page fades out while expanding slightly and blurring
transition.duration0.5All phases complete in 500 ms
transition.ease'easeInOut'Smooth acceleration and deceleration on both ends
The wrapper also applies layout classes to every page: min-h-screen pt-24 pb-12 px-4 sm:px-6 lg:px-8 relative z-10.

Usage

Wrapping a page component

Every page component in Dev Nexus follows this pattern:
import PageTransition from '../components/PageTransition';

export default function SpellsPage() {
  return (
    <PageTransition>
      <section className="px-8 py-16">
        <h1 className="text-neon-lime font-mono text-4xl tracking-widest">
          [ SPELLS ]
        </h1>
        {/* page content */}
      </section>
    </PageTransition>
  );
}

Enabling exit animations with AnimatePresence

PageTransition only produces exit animations when it is a direct child of Framer Motion’s AnimatePresence. Wrap your route outlet with AnimatePresence in your application shell:
import { AnimatePresence } from 'framer-motion';
import { Routes, Route, useLocation } from 'react-router-dom';

import PortalPage      from './pages/PortalPage';
import OriginPage      from './pages/OriginPage';
import SpellsPage      from './pages/SpellsPage';
// ... other page imports

export default function AppShell() {
  const location = useLocation();

  return (
    <AnimatePresence mode="wait">
      <Routes location={location} key={location.pathname}>
        <Route path="/"             element={<PortalPage />} />
        <Route path="/about"        element={<OriginPage />} />
        <Route path="/projects"     element={<SpellsPage />} />
        {/* ... */}
      </Routes>
    </AnimatePresence>
  );
}
The key={location.pathname} on <Routes> is essential — it forces React to treat each route as a distinct component instance, which triggers the exit animation on the outgoing page before the incoming page mounts. Without it, exit animations will not fire.

How It Works

PageTransition is intentionally minimal:
import { motion } from 'framer-motion';

export default function PageTransition({ children }) {
  return (
    <motion.div
      initial={{ opacity: 0, scale: 0.95, filter: 'blur(10px)' }}
      animate={{ opacity: 1, scale: 1,    filter: 'blur(0px)'  }}
      exit={{    opacity: 0, scale: 1.05, filter: 'blur(10px)' }}
      transition={{ duration: 0.5, ease: 'easeInOut' }}
      className="min-h-screen pt-24 pb-12 px-4 sm:px-6 lg:px-8 relative z-10"
    >
      {children}
    </motion.div>
  );
}
The component itself holds no state and performs no side effects — it is purely a declarative animation boundary. The built-in className also provides each page with standard vertical padding (pt-24 pb-12), responsive horizontal padding, a minimum full-screen height, and a z-10 stacking context above the ParticleField canvas.

Customization Tips

Change animation duration: Adjust transition.duration from 0.5 to a lower value (e.g., 0.3) for snappier transitions or higher (e.g., 0.8) for a more cinematic feel. Use a different easing curve: Replace the ease property in the transition object:
transition={{ duration: 0.4, ease: [0.4, 0, 0.2, 1] }}
Change to a slide transition: Swap the scale + filter approach for a vertical slide:
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0  }}
exit={{    opacity: 0, y: -20 }}
transition={{ duration: 0.4, ease: 'easeInOut' }}
Add a scale effect with blur: Combine both for a more dramatic entrance:
initial={{ opacity: 0, scale: 0.9, filter: 'blur(20px)' }}
animate={{ opacity: 1, scale: 1,   filter: 'blur(0px)'  }}
exit={{    opacity: 0, scale: 1.1, filter: 'blur(20px)' }}
Respect reduced-motion preferences: Use Framer Motion’s useReducedMotion hook to skip animations for users who prefer reduced motion:
import { motion, useReducedMotion } from 'framer-motion';

export default function PageTransition({ children }) {
  const reduce = useReducedMotion();

  return (
    <motion.div
      initial={{ opacity: reduce ? 1 : 0, scale: reduce ? 1 : 0.95 }}
      animate={{ opacity: 1, scale: 1 }}
      exit={{    opacity: reduce ? 1 : 0, scale: reduce ? 1 : 1.05 }}
      transition={{ duration: reduce ? 0 : 0.5, ease: 'easeInOut' }}
      className="min-h-screen pt-24 pb-12 px-4 sm:px-6 lg:px-8 relative z-10"
    >
      {children}
    </motion.div>
  );
}
Set mode="wait" on AnimatePresence (as shown in the usage example above) so the outgoing page fully completes its exit animation before the incoming page begins its enter animation. Without mode="wait", both animations run simultaneously, which can cause layout jumps if pages have different heights.

Build docs developers (and LLMs) love