Skip to main content

Documentation Index

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

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

PageTransition is a layout wrapper that animates page entry and exit using Framer Motion’s AnimatePresence. Every route in sys-core is wrapped in PageTransition so that navigating between sections produces a polished interstitial effect: the incoming page fades in from a slightly scaled-down, blurred state while a burst of glowing cyan horizontal streaks fires across the screen, evoking a hyperspace jump or signal handshake.

How It Works

PageTransition receives the current route’s children and uses the current location.pathname (read via React Router’s useLocation) as the key prop on the inner motion.div. Changing the key causes React to unmount the old element and mount a new one, which is exactly what AnimatePresence needs to orchestrate coordinated enter and exit animations. The component renders two layers:
  1. Content layer — a motion.div keyed to the pathname that wraps the actual page content. It animates opacity, scale, and a CSS blur filter simultaneously.
  2. Streak overlay — a second motion.div, also keyed to the pathname, that renders 20 randomly-positioned horizontal cyan-signal bars. Each bar scales from zero width and shoots across the screen in ~0.3 s, then the entire overlay fades to opacity 0 in 0.5 s. The result is a brief “scanning” flash on every navigation.
// Simplified shape of PageTransition
import { motion } from 'framer-motion';
import { useLocation } from 'react-router-dom';

const PageTransition = ({ children }) => {
  const location = useLocation();

  return (
    <div className="relative min-h-screen w-full overflow-hidden">
      {/* Page content — animates in/out with fade + scale + blur */}
      <motion.div
        key={location.pathname}
        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.4, ease: 'easeOut' }}
        className="min-h-screen w-full"
      >
        {children}
      </motion.div>

      {/* Cyan streak burst — fires once on every route change */}
      <motion.div
        key={`streak-${location.pathname}`}
        initial={{ opacity: 1 }}
        animate={{ opacity: 0 }}
        transition={{ duration: 0.5, ease: 'easeOut' }}
        className="fixed inset-0 pointer-events-none z-40 flex items-center justify-center"
      >
        {Array.from({ length: 20 }).map((_, i) => (
          <motion.div
            key={i}
            className="absolute h-[2px] bg-cyan-signal shadow-[0_0_10px_#06b6d4]"
            style={{
              top:       `${randomPercent}%`,
              left:      `${randomPercent}%`,
              width:     `${randomWidth}px`,   // 50–250 px
              transform: `rotate(${randomDeg}deg)`,
            }}
            initial={{ scaleX: 0, opacity: 0 }}
            animate={{ scaleX: 1, opacity: 1, x: [0, randomShoot] }}
            transition={{ duration: 0.3, ease: 'easeIn' }}
          />
        ))}
      </motion.div>
    </div>
  );
};

Animation Values

All values below are taken directly from the compiled source of components/layout/PageTransition.js. The streak positions and travel distances are randomised at render time so each navigation looks slightly different.

Content layer

Propertyinitialanimateexit
opacity010
scale0.9511.05
filter (blur)blur(10px)blur(0px)blur(10px)
duration0.4 s
easeeaseOut
The page enters by expanding slightly and sharpening into focus. On exit it over-scales to 1.05 and re-blurs, suggesting the scene is receding before the new one materialises.

Streak overlay

PropertyValue
Count20 bars
Height2 px
Width50–250 px (random per bar)
Colour#06b6d4 (cyan-signal) with a 10 px outer glow
Rotation0–360 ° (random per bar)
scaleX animation0 → 1 over 0.3 s (easeIn)
Horizontal travel (x)0 → ±0–500 px (random direction per bar)
Overlay fade-outopacity: 1 → 0 over 0.5 s (easeOut)

Usage

Wrap each route’s element with PageTransition inside the route definition. AnimatePresence must be placed at a level above the routes so it can observe component unmounts — in sys-core this is handled by passing mode="wait" to AnimatePresence around the Routes component:
import { AnimatePresence } from 'framer-motion';
import { Routes, Route, useLocation } from 'react-router-dom';
import PageTransition from './components/layout/PageTransition';

import Home        from './pages/Home';
import About       from './pages/About';
import Projects    from './pages/Projects';

function AnimatedRoutes() {
  const location = useLocation();

  return (
    <AnimatePresence mode="wait">
      <Routes location={location} key={location.pathname}>
        <Route path="/"        element={<PageTransition><Home /></PageTransition>} />
        <Route path="/about"   element={<PageTransition><About /></PageTransition>} />
        <Route path="/projects"element={<PageTransition><Projects /></PageTransition>} />
        {/* … remaining routes */}
      </Routes>
    </AnimatePresence>
  );
}
Every new route component is individually wrapped so the streak burst and content fade fire per-route rather than per-subtree.

Extending the Animation

To adjust timing or motion feel, edit the transition objects in PageTransition.js:
// Slower, more cinematic entrance
transition={{ duration: 0.7, ease: [0.16, 1, 0.3, 1] }}

// Springier exit scale
exit={{ opacity: 0, scale: 1.08, filter: 'blur(8px)' }}

// Reduce streaks for a subtler effect
Array.from({ length: 8 }).map(...)

Dependencies

PageTransition relies on Framer Motion’s PresenceContext internally. AnimatePresence must wrap the router outlet at the app level — if it is missing, exit animations will be skipped and the streak overlay will not fire on navigation away from a page. In sys-core this is already wired up in main.js where AnimatePresence mode="wait" wraps the Routes tree.
PackagePurpose
framer-motionmotion.div animations and PresenceContext for mount/unmount detection
react-router-domuseLocation to derive the per-route key that triggers AnimatePresence
Both packages are included in the sys-core project and require no additional installation.

Build docs developers (and LLMs) love