witch-dev uses Framer Motion as its single animation engine for all React-driven motion — page transitions, entrance sequences, hover physics, and infinite ambient loops. One CSS animation (Documentation Index
Fetch the complete documentation index at: https://mintlify.com/apursley2012/witch-dev/llms.txt
Use this file to discover all available pages before exploring further.
fog-drift) lives outside Framer Motion because it runs on a purely decorative element that never needs JS-controlled state. Everything else is declarative motion.* props.
Page Transitions
Every route change plays a blur-and-scale transition viaAnimatePresence wrapping the router outlet. The key is set to location.pathname so React treats each route as a distinct element and triggers enter/exit animations on navigation.
mode="wait" tells AnimatePresence to fully complete the exiting animation before the entering animation begins. Without it, the outgoing page and incoming page would overlap mid-blur — useful for crossfades, but wrong for a sharp scene-change feel.
The exit scale goes slightly above 1 (scale: 1.05) rather than below — the page appears to push toward the viewer as it blurs out, reinforcing a sense of depth and forward motion into the next route.
filter: 'blur()' is GPU-composited in modern browsers but still triggers a repaint on every frame in some environments. If frame drops appear on low-powered devices, replace filter: 'blur(10px)' with opacity: 0 alone — the transition remains readable without the blur cost.Staggered Entrance
The mobile navigation menu staggers its items so they cascade in one by one rather than all appearing at once. Each item reads its own index from the map and adds a proportional delay:0s, 0.1s, 0.2s, 0.3s, 0.4s — a tight, readable cascade. Increasing the multiplier (e.g. index * 0.15) slows the stagger; decreasing it toward 0.05 collapses the items into near-simultaneous entry.
Spring Physics
Interactive elements use Framer Motion’sspring transition type for organic, physical-feeling motion. Two distinct spring configurations appear in the codebase:
- Skill tile hover (snappy spring)
Radar Polygon Entrance
The skills radar chart animates its filled polygon in on mount with a scale-up from zero, giving it the feel of a spell diagram materializing:opacity: 0.2 rather than full opacity — the fill is intentionally translucent so the radar grid lines beneath it remain visible. The easeOut curve decelerates smoothly at full scale, mimicking a shape settling into place.
Infinite Animations
Two ambient background systems run on infinite loops to keep the UI feeling alive at rest.- Floating particles
- Rotating sigil rings
±10vw. The opacity keyframes fade the particle in and out over its travel so it never pops on or off. mix-blend-screen makes the green particles glow against coven-black.Height Animation
The About page timeline uses animated accordion panels. Each entry expands and collapses with a smooth height transition driven byAnimatePresence:
height: 'auto' is intentional — the panel content has variable height depending on description length, so a fixed pixel target is impractical. Framer Motion measures the rendered height internally and interpolates to it.
overflow: 'hidden' is required on the animated wrapper. Without it, content that overflows the container during the height tween (when height is between 0 and auto) will be visible outside the panel boundaries during animation.CSS Animation: Fog Drift
The background fog blobs use a pure CSS animation — no Framer Motion — because they are purely decorative, never need JS-driven state, and can run entirely on the compositor thread without a React render loop:translate(-10%) and translate(10%) while gently scaling it up and varying its opacity — enough movement to feel organic but slow enough to be subliminal at 30 seconds per cycle. linear timing keeps the drift at a constant pace; the opacity oscillation creates a natural slow pulse.
Apply the class in JSX like any Tailwind utility:
Performance Notes
All animations in witch-dev are written to stay off the main thread where possible:GPU-accelerated properties only
Animations target
opacity, transform (translate, scale, rotate), and filter (blur). These properties are composited on the GPU without triggering layout or paint. Avoid animating width, height (except with Framer Motion’s height-auto feature), top, left, or margin — these force layout recalculation on every frame.pointer-events: none on background layers
Particle containers, fog blobs, and rotating rings all carry
pointer-events-none (Tailwind: pointer-events-none). This ensures that the continuously-animating background layers never interfere with click, hover, or focus events on the UI above them.Framer Motion’s
AnimatePresence and motion.* components add roughly ~34 kB gzipped to the bundle. For a personal portfolio this is well within budget, but if bundle size becomes a concern, framer-motion/dist/framer-motion.esm.js supports tree-shaking — import only the hooks and components you use (motion, AnimatePresence, useAnimation) rather than the full package default export.