Skip to main content

Documentation Index

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

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

PlanetaryProfile renders the developer bio as a space-themed card that physically tilts toward the cursor in 3D space. It combines Framer Motion’s useMotionValue, useSpring, and useTransform hooks to convert raw mouse coordinates into a smooth, spring-dampened rotation — no third-party tilt library required. On the left sits a circular avatar placeholder ringed by two independently spinning orbital bands; on the right, name, designation, bio, and a stats grid.

3D Tilt Mechanism

The tilt effect is built from four Framer Motion primitives chained together:
1

Track raw mouse position with useMotionValue

Two useMotionValue(0) instances hold the normalised cursor X and Y positions, both initialised to 0 (card centre).
const mouseX = useMotionValue(0); // range: -0.5 to 0.5
const mouseY = useMotionValue(0); // range: -0.5 to 0.5
2

Smooth the values with useSpring

Each raw motion value is piped through useSpring, which adds spring-physics easing so the card “catches up” to the cursor with natural inertia rather than snapping rigidly.
const springX = useSpring(mouseX);
const springY = useSpring(mouseY);
3

Map spring values to rotation degrees with useTransform

useTransform maps the -0.5 → 0.5 normalised range to rotation angles in degrees. Note the axis swap: horizontal mouse movement drives rotateY; vertical drives rotateX.
// mouseX (left↔right) → rotateY
const rotateY = useTransform(springX, [-0.5, 0.5], ["-15deg", "15deg"]);

// mouseY (up↕down) → rotateX (inverted so moving down tilts the top away)
const rotateX = useTransform(springY, [-0.5, 0.5], ["15deg", "-15deg"]);
4

Apply rotation to the card's motion.div

The card motion.div receives both transform values via the style prop, and transformStyle: "preserve-3d" propagates 3D depth to all children.
<motion.div
  ref={cardRef}
  onMouseMove={handleMouseMove}
  onMouseLeave={handleMouseLeave}
  style={{ rotateX, rotateY, transformStyle: "preserve-3d" }}
  className="relative w-full max-w-3xl aspect-[4/3] rounded-3xl glass-panel p-8 md:p-12
             flex flex-col md:flex-row items-center gap-12"
>

Mouse event handlers

// Normalise cursor position relative to card bounds → -0.5 to 0.5
const handleMouseMove = (e) => {
  if (!cardRef.current) return;
  const { width, height, left, top } = cardRef.current.getBoundingClientRect();
  const normX = (e.clientX - left) / width  - 0.5;
  const normY = (e.clientY - top)  / height - 0.5;
  mouseX.set(normX);
  mouseY.set(normY);
};

// Reset to centre on leave — spring easing makes this feel like the card "settling"
const handleMouseLeave = () => {
  mouseX.set(0);
  mouseY.set(0);
};
The outer <section> carries perspective-[2000px] (a Tailwind arbitrary value), which controls how dramatic the 3D foreshortening looks. A smaller value (e.g. perspective-[800px]) produces a more extreme tilt; a larger value flattens it.
To tune the feel of the spring, pass a config object to useSpring: e.g. useSpring(mouseX, { stiffness: 150, damping: 20 }). Lower damping = more bounce; higher stiffness = snappier tracking.

Avatar Placeholder & Orbital Rings

The left column is a circular container with translateZ(50px) — pushing it 50 px toward the viewer in 3D space, creating parallax depth relative to the flatter right column.
<div
  className="relative w-48 h-48 md:w-64 md:h-64 shrink-0 rounded-full
             border border-aurora-teal/30 shadow-[inset_0_0_50px_rgba(13,148,136,0.2)]
             flex items-center justify-center"
  style={{ transform: "translateZ(50px)" }}
>
Inside sit two motion.div orbital rings and the avatar placeholder itself:

Inner ring — 20 s clockwise

<motion.div
  className="absolute inset-[-20%] border border-aurora-pink/20 rounded-full"
  animate={{ rotate: 360 }}
  transition={{ duration: 20, repeat: Infinity, ease: "linear" }}
  style={{ transform: "rotateX(70deg) rotateY(20deg)" }}
/>

Outer ring — 30 s counter-clockwise

<motion.div
  className="absolute inset-[-40%] border border-aurora-mint/20 rounded-full"
  animate={{ rotate: -360 }}
  transition={{ duration: 30, repeat: Infinity, ease: "linear" }}
  style={{ transform: "rotateX(60deg) rotateY(-10deg)" }}
/>
Both rings use a static transform (set via inline style, not Framer Motion) to tilt them at a planetary-orbit angle before the rotation animation begins. The ease: "linear" keeps the spin perfectly constant — unlike easeInOut, which would cause the rings to oscillate in speed.
The inset-[-20%] and inset-[-40%] classes make the rings overflow their container by 20 % and 40 % respectively, so they visually encircle the avatar without needing a larger wrapper.

Avatar placeholder

{/* Replace this div with an <img> to use a real photo */}
<div className="w-3/4 h-3/4 rounded-full bg-gradient-to-br from-aurora-teal/40 to-space-900 blur-sm mix-blend-screen" />
To swap in a real photo:
<img
  src="/images/your-photo.jpg"
  alt="Alex Vance"
  className="w-3/4 h-3/4 rounded-full object-cover"
/>
Remove the blur-sm and mix-blend-screen classes from the placeholder when adding a real image — they are cosmetic stand-ins designed to make the teal gradient look like an abstract portrait.

Orbital stat labels

Two monospaced labels sit above and below the avatar circle, pinned with absolute positioning:
{/* Top label */}
<div className="absolute -top-4 left-1/2 -translate-x-1/2 font-mono text-[10px] text-aurora-mint tracking-widest">
  MASS: 1.4M LOC       {/* ← total lines of code written */}
</div>

{/* Bottom label */}
<div className="absolute -bottom-4 left-1/2 -translate-x-1/2 font-mono text-[10px] text-aurora-pink tracking-widest">
  ORBIT: 5 YRS EXP     {/* ← years of experience */}
</div>

Right Column — Bio Content

The right column sits at translateZ(30px) — 20 px behind the avatar, adding a subtle parallax layer during tilt.
<div className="flex-1" style={{ transform: "translateZ(30px)" }}>

Name and designation

<h2 className="font-sans text-3xl md:text-4xl font-bold text-slate-100 mb-2">
  Subject: <span className="text-aurora-teal">Alex Vance</span>  {/* ← your name */}
</h2>

<h3 className="font-mono text-sm text-slate-400 tracking-widest uppercase mb-6
               border-b border-slate-800 pb-4">
  Designation: Frontend Architect   {/* ← your title */}
</h3>

Bio paragraphs

<p>
  Originating from the creative sector, I navigate the complex gravitational pulls
  between aesthetic design and rigorous engineering.
</p>
<p>
  My current trajectory involves building highly interactive, accessible, and performant
  web applications that feel less like software and more like{" "}
  <span className="text-aurora-pink italic font-serif">digital experiences</span>.
</p>

Stats grid

<div className="mt-8 grid grid-cols-2 gap-4">

  <div className="glass-panel p-4 rounded-xl">
    <div className="font-mono text-xs text-slate-500 mb-1">COORDINATES</div>
    <div className="font-sans text-sm text-slate-200">San Francisco, CA</div>  {/* ← your location */}
  </div>

  <div className="glass-panel p-4 rounded-xl">
    <div className="font-mono text-xs text-slate-500 mb-1">STATUS</div>
    <div className="font-sans text-sm text-aurora-mint flex items-center gap-2">
      <span className="w-2 h-2 rounded-full bg-aurora-mint animate-pulse" />
      Accepting Transmissions    {/* ← your availability */}
    </div>
  </div>

</div>
The pulsing dot beside the status value is a plain <span> styled with Tailwind’s animate-pulse — change bg-aurora-mint to bg-red-400 and the text to "Off-Grid" to signal unavailability.

Where the Component is Rendered

PlanetaryProfile is used on the /about route, wrapped in a page layout defined in main.js:
function About() {
  return (
    <div className="w-full max-w-7xl mx-auto pt-10">
      <div className="px-6 mb-10 text-center">
        <h1 className="font-sans text-4xl md:text-5xl font-bold text-slate-100 mb-4">
          Stellar Cartography
        </h1>
        <p className="font-serif italic text-xl text-slate-400">
          Mapping the origins and trajectory of the developer.
        </p>
      </div>
      <PlanetaryProfile />
    </div>
  );
}
The page heading "Stellar Cartography" and subheading live in main.js, not inside PlanetaryProfile itself.

Customisation Reference

Name & Title

Edit the <h2> and <h3> strings directly in PlanetaryProfile.js. The name Alex Vance appears once inside an <h2>; the designation Frontend Architect appears once inside an <h3>.

Bio Paragraphs

Two <p> elements live inside a space-y-4 div in the right column. The second paragraph contains a styled <span> for the italic “digital experiences” phrase — preserve or repurpose it.

MASS & ORBIT Labels

The MASS: 1.4M LOC and ORBIT: 5 YRS EXP strings are in two absolutely-positioned <div> elements flanking the avatar circle. Update them to reflect your own stats.

Location & Status

San Francisco, CA and Accepting Transmissions are plain text nodes inside the two glass-panel grid cells. Change status to "Off-Grid" and swap bg-aurora-mintbg-red-400 on the pulse dot when unavailable.

Tilt Intensity

Adjust the [-15deg, 15deg] ranges in the two useTransform calls to reduce or increase maximum tilt angle. Lower values (e.g. [-8deg, 8deg]) produce a subtler effect.

Avatar Photo

Replace the gradient placeholder <div> with <img src="..." className="w-3/4 h-3/4 rounded-full object-cover" />. Remove the blur-sm and mix-blend-screen classes that were on the placeholder.

Build docs developers (and LLMs) love