Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/groovy/llms.txt

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

Layout.js is the single shell component rendered at the top-level / React Router route. It is responsible for three things that must be present on every page: the sticky Navbar, the RainbowCursor global effect, and the animated page transition that wraps each child route rendered by <Outlet />. Because Layout owns the AnimatePresence boundary, no individual page component needs to handle its own entrance or exit — every route automatically inherits the blur-and-scale transition.

Page Transition

Route changes are animated with a combination of opacity, scale, and a CSS blur filter. The page fades and scales down slightly as it exits, and the incoming page fades and scales up from a subtly blurred state. The full transition config is:
// Entrance (animate)
{ opacity: 1, scale: 1, filter: 'blur(0px)' }

// Exit (exit)
{ opacity: 0, scale: 1.05, filter: 'blur(10px)' }

// Initial (before mount)
{ opacity: 0, scale: 0.95, filter: 'blur(10px)' }

// Transition timing
{ duration: 0.5, ease: 'easeInOut' }
The motion.main element wraps the <Outlet /> so the entire page content — not just a subsection — participates in the transition.

AnimatePresence Key

useLocation from React Router is called inside Layout, and its pathname is passed as the key prop on the animated motion.main element. This ensures Framer Motion treats each navigation as a genuine unmount/remount cycle, triggering both the exit animation of the departing page and the entrance animation of the arriving page.
If you add a new route and want it to skip the shared transition, wrap its page component’s top-level element in a motion.div with layout="preserve-aspect" and provide explicit initial/animate/exit props that override the inherited values. The AnimatePresence boundary in Layout will still manage the lifecycle — your overrides simply replace the default blur-scale keyframes for that page.

Usage

Layout accepts no props and should not be instantiated directly. It is wired into the router definition:
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import Layout from './components/Layout';
import Home from './pages/Home';
import Work from './pages/Work';
import Contact from './pages/Contact';

const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    children: [
      { index: true, element: <Home /> },
      { path: 'work',    element: <Work /> },
      { path: 'contact', element: <Contact /> },
    ],
  },
]);

export default function App() {
  return <RouterProvider router={router} />;
}
The RainbowCursor rendered inside Layout appends a <canvas> element to document.body. If you add a page that uses a custom cursor via CSS (cursor: none), set pointer-events: none on the canvas to prevent it from intercepting clicks in that section.

Build docs developers (and LLMs) love