Skip to main content

Documentation Index

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

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

Every transition in Sorcerer is deliberate: animations are not decorative flourishes but functional cues that guide the visitor’s attention from section to section, reveal content at the moment it enters the viewport, and mirror the slow, theatrical pacing of a conjurer at work. All motion is implemented with Framer Motion — its motion components, AnimatePresence, useScroll, useTransform, and useInView utilities cover the full range of effects in the portfolio, from letter-by-letter hero reveals to scroll-driven parallax and SVG constellation drawing.

Animation Patterns

1. Fade In from Below

Used on section heading blocks across the projects, skills, work history, blog, and contact pages. Each heading <motion.div> starts invisible and 20 px below its final position, then resolves to full opacity at rest.
<motion.div
  initial={{ opacity: 0, y: 20 }}
  animate={{ opacity: 1, y: 0 }}
  transition={{ duration: 1 }}
>

2. Blur Reveal with Letter Stagger

Used on the hero name (“ALEX WEAVER”) and on testimonial quote text. The name string is split into individual characters; each letter is wrapped in its own <motion.span> with a staggered delay of index * 0.15 seconds. On load, each character transitions from blurred teal to the normal moonlight-silver text color.
<motion.span
  initial={{ opacity: 0, filter: 'blur(10px)', color: '#2dd4bf' }}
  animate={{ opacity: 1, filter: 'blur(0px)', color: '#e2e8f0' }}
  transition={{ duration: 1, delay: index * 0.15, ease: 'easeOut' }}
>
The space character between first and last name is rendered as a spacer <span> with a fixed width class (w-4 md:w-8) so word spacing is preserved without disrupting the per-character animation loop.

3. Scroll Parallax

Used on the moon orb decorative element on the About page. useScroll tracks the window’s vertical scroll position, and useTransform maps the scroll range [0, 1000] to a vertical translation of [0, 300] pixels, causing the orb to drift downward as the visitor scrolls — creating a depth illusion between the background decoration and the foreground text.
const { scrollY } = useScroll();
const y = useTransform(scrollY, [0, 1000], [0, 300]);

<motion.div style={{ y }}>

4. Viewport Trigger (whileInView)

Used on project cards on the Projects page and on each case study block on the Case Studies page. Rather than animating on mount, these elements remain invisible until they scroll into the viewport, then slide up from 20 px below with a duration of 0.8 s. The margin: '-100px' offset fires the animation slightly before the element reaches the visible edge of the screen, so it feels ready when the visitor’s eye arrives.
<motion.div
  initial={{ opacity: 0, y: 20 }}
  whileInView={{ opacity: 1, y: 0 }}
  viewport={{ once: true, margin: '-100px' }}
  transition={{ duration: 0.8 }}
>

5. SVG Path Draw

Used on the constellation edge lines connecting skill nodes on the Skills page. Each <motion.line> starts with pathLength: 0 (invisible) and animates to pathLength: 1 (fully drawn) when isInView becomes true. A staggered delay of index * 0.2 seconds causes the constellation to draw itself one edge at a time, appearing to trace connections between skills.
<motion.line
  initial={{ pathLength: 0 }}
  animate={isInView ? { pathLength: 1 } : { pathLength: 0 }}
  transition={{ duration: 1.5, delay: index * 0.2, ease: 'easeInOut' }}
/>
Skill nodes (the dot-and-label pairs) use a companion spring animation — transition={{ delay: index * 0.1 + 0.5, type: 'spring' }} — that pops each node into place with a natural bounce after the lines begin drawing.

6. AnimatePresence Exit Transitions

Used on the contact form (which transitions to a success state) and the testimonials carousel (which cycles between three quotes). AnimatePresence with mode="wait" ensures the exiting element fully completes its exit animation before the entering element begins mounting.
<AnimatePresence mode="wait">
  <motion.div
    key={activeIndex}
    initial={{ opacity: 0, filter: 'blur(10px)', scale: 0.9 }}
    animate={{ opacity: 1, filter: 'blur(0px)', scale: 1 }}
    exit={{ opacity: 0, filter: 'blur(10px)', scale: 1.1 }}
    transition={{ duration: 0.8 }}
  />
</AnimatePresence>
The key prop is essential: changing it tells AnimatePresence that the previous child has unmounted and a new one has mounted, triggering both the exit and enter sequences. On the testimonials page, key={t} is set to the active testimonial index, which increments automatically every 6 seconds via a setInterval.

useInView Hook

The Skills page uses Framer Motion’s useInView hook directly to drive both the SVG path draw and the skill node spring animations. A ref is attached to the constellation container <div>, and isInView flips to true when the container scrolls within −100 px of the viewport edge.
const ref = useRef(null);
const isInView = useInView(ref, { once: true, margin: '-100px' });
isInView is then passed as a condition to each animate prop — animate={isInView ? { pathLength: 1 } : { pathLength: 0 }} — so none of the constellation animations begin until the visitor has scrolled to the Skills section.

Scroll Timeline (Work History)

The Work History page animates a vertical timeline line that grows from top to bottom as the visitor scrolls through the job entries. This uses useScroll with a target ref and a scroll offset array to measure progress relative to the container element rather than the whole window.
const ref = useRef(null);
const { scrollYProgress } = useScroll({
  target: ref,
  offset: ['start end', 'end start'],
});
const height = useTransform(scrollYProgress, [0, 0.8], ['0%', '100%']);

// Applied to the animated timeline line:
<motion.div style={{ height }} />
The offset ['start end', 'end start'] means progress begins when the container’s top edge reaches the bottom of the viewport and ends when the container’s bottom edge leaves the top — spanning the full scroll distance through the section. The height is mapped only up to 0.8 of that progress so the line appears fully drawn before the visitor reaches the section footer.
All viewport-triggered animations use once: true, meaning they fire exactly once — on the first time the element enters the viewport. Scrolling back up and then down again will not replay them. Remove once: true from any viewport or useInView call to enable re-triggering on each viewport entry.
Framer Motion is bundled into assets/proxy.js alongside React and ReactDOM. It is not loaded from a CDN at runtime — all motion primitives (motion, AnimatePresence, useScroll, useTransform, useInView) are resolved from this local bundle. If you are working in the source repository, Framer Motion is listed as a dependency in package.json and is tree-shaken during the Vite build.

Build docs developers (and LLMs) love