Spell Index’s animation layer is built entirely on Framer Motion for React-driven transitions and a single custom CSS keyframe for the decorative reverse-spin element. Every animated element usesDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/apursley2012/spell-index/llms.txt
Use this file to discover all available pages before exploring further.
motion.div (or motion.p, motion.h1, etc.) from Framer Motion — the library is aliased as a in the compiled bundle, so you’ll see a.div, a.p, and so on throughout the source. Animations are intentionally staggered and eased to feel deliberate rather than snappy, matching the slow-burn atmosphere of the portfolio theme.
Core Patterns
1. Fade-in from below (entrance animation)
The most frequently used pattern — elements start invisible and slightly below their final position, then rise into view. Used on page headings, hero sections, and the contact form wrapper:y: -20 variant (dropping down instead of rising) is used for page titles:
2. Scroll-triggered with whileInView
The Case Studies page uses whileInView to animate timeline entries only when they scroll into the viewport. This is the canonical scroll-triggered pattern across the app:
margin: '-100px' offset triggers the animation slightly before the element fully enters the viewport, so content begins animating as the user approaches it rather than when it’s fully visible.
3. Staggered children with delay
The TarotCard and project grid components stagger each child by passing an incremental delay prop, creating a cascading reveal effect:
delay prop is wired to the transition:
delay: index * 0.3 for a slightly slower stagger:
4. Scale entrance
The Skills page wraps the SpellBook in a scale entrance — the book grows from 90% to full size as it fades in, giving it a conjuring-from-thin-air feel:5. Spring-based 3D page flip (SpellBook)
The SpellBook component uses adirection-aware variant map to produce a 3D page-turn effect. The direction value is either 1 (next page) or -1 (previous page), and it controls which way the Y-axis rotation travels:
motion.div receives the current direction value via the custom prop, and the parent AnimatePresence also receives custom:
type: 'spring' transition with bounce: 0.2 gives the page a subtle elastic settle at the end of each flip, as if the paper is genuinely resisting being turned.
When using
AnimatePresence with variants, the key prop on the motion.div child is what tells Framer Motion that the element has changed and an exit/enter cycle should run. Without a changing key, AnimatePresence will never trigger the exit animation.6. Looping candle flame
The CandleTimeline renders amotion.div that loops forever, cycling through scale, rotation, and opacity values to simulate a flickering flame:
duration and then loops. The second flame element — a blurred glow halo — pulses more slowly:
PageTransition Component
Every route in the app is wrapped in a<PageTransition> component. It applies a blur-fade that makes pages dissolve in and out rather than cutting hard:
key={location.pathname} is what triggers a fresh animation cycle on every route change — Framer Motion treats a new key as a newly mounted component.
Wrapping a route
Every route element inmain.js is wrapped with <PageTransition>:
AnimatePresence mode="wait"
The router-level AnimatePresence wraps the Routes component with mode="wait", which ensures the exiting page fully completes its blur-out before the entering page starts blurring in:
AnimatePresence requires its direct children to have a key prop that changes when the content changes. The key={location.pathname} on Routes (or on the motion.div inside PageTransition) fulfills this requirement. Omitting it causes exit animations to never fire.[0.22, 1, 0.36, 1] is a cubic-bezier approximation of an “ease-out expo” — fast initial movement that decelerates smoothly to a rest. This matches the feeling of a heavy tome falling open.
CSS Animation: animate-spin-reverse-slow
A single custom CSS keyframe is defined outside Tailwind’s animation system. It powers the decorative circular ornament (seen in the hero section’s MoonPhases component):
360deg to 0deg, producing a counter-clockwise rotation. The 20-second duration keeps it barely perceptible — a slow orbital drift rather than an obvious spin.
Animation Quick Reference
| Pattern | Props Used | Typical Duration | Component |
|---|---|---|---|
| Fade-up entrance | initial/animate | 1s | All page headings |
| Drop-down entrance | initial/animate | default | Section headers |
| Scroll-triggered | whileInView, viewport | 0.6s | Case studies timeline |
| Staggered children | delay={index * 0.2} | 0.8s | TarotCard, project grid |
| Scale entrance | initial/animate w/ scale | 0.8s | SpellBook, contact form |
| 3D page flip | custom, variants | 0.6s spring | SpellBook |
| Candle flame loop | animate keyframe array | 0.5s ∞ | CandleTimeline |
| Page blur-fade | PageTransition wrapper | 0.8s | Every route |
| Reverse orbital | .animate-spin-reverse-slow | 20s ∞ | Hero ornament |