Skip to main content

Documentation Index

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

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

Telemetry is animated end-to-end with Framer Motion. Every moving element — page transitions, the aurora background, the custom cursor, the nav indicator, orbital rings, and scroll reveals — has explicit, tunable configuration values you can modify directly in the source files. This page documents each animation system and explains how to adjust it.

Page Transitions

All route changes are managed by Framer Motion’s AnimatePresence component with mode="wait". This setting ensures the outgoing page completes its exit animation before the incoming page starts entering, preventing both pages from being visible simultaneously. The transition wrapper is applied at the router level in main.js:
// Page transition wrapper — applied to every route change
initial: { opacity: 0, y: 20, filter: 'blur(10px)' }
animate: { opacity: 1, y: 0,  filter: 'blur(0px)' }
exit:    { opacity: 0, y: -20, filter: 'blur(10px)' }
transition: { duration: 0.5, ease: 'easeOut' }
What each property does:
  • opacity — Fades the page in from transparent on enter, out to transparent on exit.
  • y — Slides the page up 20px on enter (starts below, moves to position) and up 20px further on exit (continues moving up and away).
  • filter: 'blur(10px)' — Adds a soft defocus on enter and exit, reinforcing the “tuning in” space-signal metaphor.
  • duration: 0.5 — The transition takes 500ms.
  • ease: 'easeOut' — The motion decelerates at the end, giving a settling feel.
To make transitions faster, lower the duration. To use a slide-only transition without the blur effect, remove the filter property entirely:
// Slide-only, no blur
initial:    { opacity: 0, y: 20 }
animate:    { opacity: 1, y: 0  }
exit:       { opacity: 0, y: -20 }
transition: { duration: 0.35, ease: 'easeOut' }
Framer Motion’s AnimatePresence requires a unique key prop on the animated element to detect when a route change has occurred. Telemetry uses location.pathname (obtained from React Router’s useLocation()) as the key — when the pathname changes, React unmounts the old page element and mounts a new one, triggering the exit/enter sequence.

Aurora Background Animations

The three aurora blobs in AuroraBackground.js each have an independent Framer Motion keyframe animation. They move in continuous loops on different timings to create an organic, non-repeating flow. All three share a parent <div> with opacity-30 mix-blend-screen filter blur-[100px] — it is these CSS properties, not the Framer Motion values, that produce the luminous glow effect. Teal blob — positioned top-left (top-[-20%] left-[-10%] w-[70%] h-[60%]):
animate={{ x: [0, 100, 0], y: [0, 50, 0], scale: [1, 1.2, 1] }}
transition={{ duration: 20, repeat: Infinity, ease: 'easeInOut' }}
Violet blob — positioned top-right (top-[20%] right-[-20%] w-[60%] h-[80%]):
animate={{ x: [0, -150, 0], y: [0, -100, 0], scale: [1, 1.5, 1] }}
transition={{ duration: 25, repeat: Infinity, ease: 'easeInOut', delay: 2 }}
Cyan blob — positioned bottom-center (bottom-[-20%] left-[20%] w-[80%] h-[50%]):
animate={{ x: [0, 50, 0], y: [0, -50, 0], scale: [1, 1.1, 1] }}
transition={{ duration: 18, repeat: Infinity, ease: 'easeInOut', delay: 5 }}
The delay values stagger the blobs so they never reach their peak positions at the same time. The keyframe arrays ([start, peak, end]) define the motion path — the animation interpolates from the first value to the second, then back to the third (which matches the first, creating a seamless loop). To speed up or slow down: Change the duration values. Lower numbers make blobs move faster. To make blobs travel further: Increase the displacement numbers in the animate arrays — for example, changing x: [0, 100, 0] to x: [0, 200, 0] doubles the horizontal travel distance. To add a fourth blob: Copy an existing blob element, give it a new position class and color (bg-aurora-magenta, bg-sunset-orange, etc.), and set a unique duration and delay. The two starfield SVG layers also animate using a CSS keyframe applied as a Tailwind arbitrary value:
  • Layer 1: animate-[aurora-flow_100s_linear_infinite] — slow clockwise scroll
  • Layer 2: animate-[aurora-flow_70s_linear_infinite_reverse] — slightly faster, reversed scroll
These timings are set as Tailwind class names, so to change them you need to update the class string directly in AuroraBackground.js.

Custom Cursor Animations

The custom cursor in CustomCursor.js replaces the native OS cursor with two layered elements driven by Framer Motion spring physics. The two layers have different spring configs intentionally — the inner dot is tight and responsive while the outer ring lags behind, creating a trailing feel. Inner dotw-3 h-3 bg-aurora-cyan rounded-full (tight, snappy):
animate={{ x: mouseX - 6, y: mouseY - 6, scale: isHovering ? 1.5 : 1 }}
transition={{ type: 'spring', stiffness: 500, damping: 28, mass: 0.5 }}
Outer ringw-8 h-8 rounded-full border border-aurora-teal/30 (loose, laggy):
animate={{ x: mouseX - 16, y: mouseY - 16, scale: isHovering ? 1.8 : 1 }}
transition={{ type: 'spring', stiffness: 250, damping: 35, mass: 0.8 }}
Spring parameter guide:
  • stiffness — How quickly the element snaps toward the target. Higher = snappier. Lower = floatier.
  • damping — How much the oscillation is suppressed. Higher = less bounce, faster settle. Lower = more bounce.
  • mass — Simulated weight. Higher = more lag and heavier feel. Lower = quicker response.
Both elements expand on hover (isHovering is true when the cursor is over an <a>, <button>, or any element with the interactive class). To make the cursor feel tighter overall, increase stiffness on both layers and decrease mass. To make it floatier, do the opposite. The sliding active-state indicator behind the current nav item in Navigation.js uses Framer Motion’s shared layout animation. The layoutId="nav-pill" prop means Framer Motion automatically animates the element’s position whenever it moves to a different nav item — you only need to render it under the active item and Framer Motion handles the interpolation.
// Active pill under current nav item
<motion.div
  layoutId="nav-pill"
  className="absolute inset-0 bg-aurora-teal/20 border border-aurora-teal/50 rounded-full -z-10"
  transition={{ type: 'spring', stiffness: 300, damping: 30 }}
/>
To make the pill slide faster: Increase stiffness (e.g., 500). To make it bouncier: Decrease damping (e.g., 15). Be aware that very low damping causes the pill to overshoot and oscillate noticeably before settling. To replace the spring with a tween: Change type to 'tween' and add duration: 0.2.

Hero Orbital Rings

The home page hero section displays two concentric orbital rings animated with continuous rotation. Both rings are driven by a simple Framer Motion animate with repeat: Infinity. The values below are representative examples drawn from the compiled output — consult your source file for the exact numbers: Outer ringborder-aurora-teal/20 border-t-aurora-cyan/60 (clockwise):
animate={{ rotate: 360 }}
transition={{ duration: 20, repeat: Infinity, ease: 'linear' }}
Inner ringborder-aurora-violet/20 border-b-aurora-magenta/60 (counter-clockwise):
animate={{ rotate: -360 }}
transition={{ duration: 15, repeat: Infinity, ease: 'linear' }}
ease: 'linear' is critical here — any other easing function would cause the ring to visibly speed up and slow down during each revolution, breaking the illusion of constant orbital motion. To change orbit speed: Lower the duration value for a faster orbit, raise it for slower. The inner ring is intentionally faster than the outer ring — maintaining this speed difference preserves the visual depth of a real orbital system. Counter-clockwise rotation: The negative rotate: -360 is what drives the inner ring in the opposite direction. To make both rings rotate the same direction, change the inner ring to rotate: 360. The Projects page also uses continuous rotation for individual project orbiters, with duration values driven by project data and orbit radius — each project orbits at a different speed to suggest varying distances from a center point.

Scroll Animations

Sections throughout the site use Framer Motion’s whileInView prop to trigger entry animations when elements scroll into the viewport. The viewport={{ once: true }} option means each element only animates on its first scroll-into-view and stays in its final state permanently:
// Timeline entry on the About page
<motion.div
  initial={{ opacity: 0, x: -50 }}
  whileInView={{ opacity: 1, x: 0 }}
  viewport={{ once: true, margin: '-100px' }}
  transition={{ duration: 0.5, delay: index * 0.1 }}
>
The margin: '-100px' triggers the animation when the element is 100px above the viewport edge, giving a slightly early reveal that feels natural when scrolling at normal speed. To make elements re-animate on every scroll: Remove once: true from the viewport object:
viewport={{ margin: '-100px' }}
To stagger a list of items: Use the array index to offset each item’s delay, as the About page timeline does (delay: index * 0.1). This creates a cascade where each card enters 100ms after the previous one.

Build docs developers (and LLMs) love