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’s visual identity is carried by three self-contained components that render on every page without any props. They sit at the top of the component tree — above <Outlet> and independent of page content — so they never unmount during navigation and their animations run continuously throughout the session.

AuroraBackground

Location: components/AuroraBackground.js AuroraBackground paints the full-screen space environment that lives behind all page content. It is position: fixed, covers the entire viewport, and sits at z-index: -1 so it never intercepts pointer events or obscures any UI element.

SVG displacement filter

An inline <svg> (hidden from layout) defines an aurora-filter SVG filter that chains feTurbulence (fractal noise at baseFrequency: 0.01 0.02, 3 octaves) into feDisplacementMap (scale 50). Although the filter is defined here, the blob divs below use Tailwind blur instead of the SVG filter for their primary visual effect.

Animated blobs

Three motion.div elements — teal, violet, and cyan — form the aurora curtain. They share opacity-30 mix-blend-screen filter blur-[100px] and are positioned to overlap across the screen. Each blob animates its x, y, and scale properties on an independent infinite loop:
// Teal blob — top-left anchor
animate={{ x: [0, 100, 0], y: [0, 50, 0], scale: [1, 1.2, 1] }}
transition={{ duration: 20, repeat: Infinity, ease: "easeInOut" }}

// Violet blob — top-right anchor, delayed 2 s
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 — bottom anchor, delayed 5 s
animate={{ x: [0, 50, 0], y: [0, -50, 0], scale: [1, 1.1, 1] }}
transition={{ duration: 18, repeat: Infinity, ease: "easeInOut", delay: 5 }}
The staggered durations (20 s, 25 s, 18 s) and delays ensure the blobs never move in sync, producing an organic aurora shimmer.

Starfield overlays

Two div elements carry base64-embedded SVG patterns as background-image. The first scrolls forward with animate-[aurora-flow_100s_linear_infinite]; the second scrolls in reverse at 70s. The aurora-flow keyframes are defined in assets/main.css (compiled Tailwind). Together they create parallax depth between the two starfield layers.

Vignette

A final div renders bg-[radial-gradient(ellipse_at_center,transparent_0%,#04060f_100%)] at opacity-80, darkening the corners so page text always has contrast against the light aurora blobs. Props: none — AuroraBackground is fully self-contained.

CustomCursor

Location: components/CustomCursor.js CustomCursor replaces the browser’s default pointer with a two-layer spring-physics cursor. It mounts its own mousemove and mouseover event listeners and tears them down on unmount — no external state or context is required.

State

const [{ x, y }, setPos] = useState({ x: 0, y: 0 });
const [isHovering, setIsHovering]  = useState(false);
x and y update on every mousemove via e.clientX / e.clientY. isHovering is set to true by the mouseover handler when the event target satisfies any of these conditions:
  • tagName === 'a'
  • tagName === 'button'
  • .closest('a') returns a match
  • .closest('button') returns a match
  • .classList.contains('interactive')

Layers

The cursor renders as two concentric motion.div elements, both position: fixed at top: 0, left: 0, offset to their visual center via the animated x / y values.
LayerClassesSpring configHover scale
Inner dotw-3 h-3 bg-aurora-cyan rounded-full + mix-blend-screen shadow-[0_0_10px_#22d3ee,0_0_20px_#14b8a6]stiffness 500, damping 28, mass 0.51.5×
Outer ringw-8 h-8 rounded-full border border-aurora-teal/30 bg-aurora-teal/10 + mix-blend-screenstiffness 250, damping 35, mass 0.81.8×
The outer ring has a lower stiffness and higher mass than the inner dot, so it lags behind slightly and creates the impression of magnetic attraction.
// Inner dot (z-[9999])
<motion.div
  animate={{ x: pos.x - 6,  y: pos.y - 6,  scale: isHovering ? 1.5 : 1 }}
  transition={{ type: "spring", stiffness: 500, damping: 28, mass: 0.5 }}
/>

// Outer ring (z-[9998])
<motion.div
  animate={{ x: pos.x - 16, y: pos.y - 16, scale: isHovering ? 1.8 : 1 }}
  transition={{ type: "spring", stiffness: 250, damping: 35, mass: 0.8 }}
/>

Making a custom element interactive

Add the interactive CSS class to any element that is not already an <a> or <button>. The mouseover handler checks for this class via classList.contains("interactive") and will trigger the hover-scale effect:
<div className="interactive">Hover me for cursor effect</div>
Props: none — CustomCursor manages its own event listeners.
Location: components/Navigation.js Navigation is the fixed top header rendered on every page. It is position: fixed, spans the full viewport width, and sits at z-index: 50 — above page content but below the cursor layers.

Structure

The header is a three-column flex row (justify-between items-center):
  • Left — logo area: A pulsing Radio icon (Lucide, w-4 h-4 text-aurora-cyan) inside a small rounded badge with an animated motion.div that scales and fades on an infinite 2-second loop. Beside it, two stacked <span> elements display the wordmark.
  • Center — nav links: Hidden on mobile (hidden md:flex). A pill-shaped container (bg-midnight-navy/50 backdrop-blur-md border border-white/10 rounded-full p-1) holds the seven NavLink entries from the Vp array.
  • Right — status badge: A pulsing Activity icon (Lucide, w-3 h-3) beside the text “RECEIVING SIGNAL” in aurora-violet font-mono text-[10px].
PathLabelID
/HOMEhome
/aboutABOUTabout
/projectsPROJECTSprojects
/skillsSKILLSskills
/writingWRITINGwriting
/case-studiesCASE STUDIEScase-studies
/contactCONTACTcontact

Active pill animation

When a NavLink is active, a motion.div with layoutId="nav-pill" renders behind its label. All seven nav items share the same layoutId, so Framer Motion’s shared layout system slides the pill smoothly from one link to another on navigation rather than abruptly swapping it.
{isActive && (
  <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 }}
  />
)}

Mobile behaviour

Nav links are hidden below the md breakpoint (hidden md:flex). There is no hamburger menu in the current implementation — only the logo and signal status badge are visible on narrow viewports.
To change the wordmark text, update the two <span> elements inside the logo div in components/Navigation.js. The top line — "Telemetry Online" — uses font-mono text-[10px] text-aurora-teal tracking-widest uppercase. The bottom line — "OBSERVATORY" — uses font-sans text-sm font-semibold text-white tracking-wider.

Build docs developers (and LLMs) love