Skip to main content

Documentation Index

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

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

Every page in Oracle is encased in a layer of shared UI chrome built from three components: Navigation, Layout, and CustomCursor. None of them render content — they provide the persistent sidebar, the page-transition choreography, and the branded cursor that make Oracle feel like a cohesive mystical interface rather than a collection of disconnected pages. Layout is the outermost shell that composes the other two, so every route automatically inherits all three. The Navigation component is a fixed right-side sidebar, 256px wide (w-64), that persists across every route change without unmounting or re-rendering. Header area At the top of the sidebar sits an animated SVG logo: a turquoise outer circle, a dashed inner circle, and a diamond polygon. The entire mark rotates continuously on a 60-second infinite loop, giving it the feel of a slowly turning celestial orrery. Below the logo, “Alex Weaver” is set in font-serif and “Grimoire” appears in font-mono uppercase as a subtitle. Navigation items The sidebar renders the zp route array as a vertical list of NavLink elements. Each item displays a Unicode glyph on the left and the route label on the right:
const zp = [
  { path: "/",            label: "Home",         glyph: "☉" },
  { path: "/about",       label: "About",        glyph: "☽" },
  { path: "/projects",    label: "Projects",     glyph: "✦" },
  { path: "/skills",      label: "Skills",       glyph: "⚝" },
  { path: "/work",        label: "Work",         glyph: "⚒" },
  { path: "/case-studies",label: "Case Studies", glyph: "☿" },
  { path: "/articles",    label: "Articles",     glyph: "✎" },
  { path: "/testimonials",label: "Testimonials", glyph: "♆" },
  { path: "/contact",     label: "Contact",      glyph: "✉" },
];
Active indicator rail The currently active route is highlighted by a 2px-wide turquoise bar positioned on the left edge of the active item. This bar is a Framer Motion motion.span with layoutId="nav-active-rail". Because layoutId is shared across all nav items, Framer Motion automatically animates the bar sliding from the previously active item to the newly active one whenever the route changes — no manual position tracking required:
// Spring config on the layout animation
transition: {
  type: "spring",
  stiffness: 400,
  damping: 30,
}
The bar itself has a turquoise box-shadow glow so it reads as a glowing energy pulse tracking down the sidebar. Footer The sidebar closes with the text “Est. MMXXIV” set in font-mono, anchoring the mystical branding with a founding date in Roman numerals.

Layout

Layout is the single wrapper component rendered at the router level, meaning it mounts once and never unmounts. It is responsible for the ambient background, assembling the three chrome components, and orchestrating page transitions. Background layer Two fixed, blurred circular gradients are painted behind all content: a turquoise/5 gradient in the top-left corner and a midnight/40 gradient in the bottom-right. These never move or animate — they establish the dark, faintly luminous atmosphere of the page. Content area The main content area uses pr-72 pl-8 py-12 to push content away from the 64px-wide right sidebar, giving pages a comfortable reading column without overlap. Page transitions Layout wraps page content in <AnimatePresence mode="wait">. Each page component is wrapped in a motion.div with the following animation config:
const pageTransition = {
  initial:    { opacity: 0, filter: "blur(10px)", y: 20  },
  animate:    { opacity: 1, filter: "blur(0px)",  y: 0   },
  exit:       { opacity: 0, filter: "blur(10px)", y: -20 },
  transition: { duration: 0.8, ease: [0.22, 1, 0.36, 1]  },
};
Pages emerge from a blurred, slightly lower position and exit upward into blur — evoking pages being turned in a grimoire. The custom cubic-bezier easing ([0.22, 1, 0.36, 1]) produces a fast initial movement that decelerates smoothly into place. Smoke overlay A second AnimatePresence instance manages a radial-gradient overlay that fades in and out between route changes, adding a brief “smoke” transition layer on top of the page blur effect for extra visual depth.
The mode="wait" prop on AnimatePresence is what makes the blur transitions feel intentional rather than chaotic. It ensures the exit animation of the leaving page completes fully before the entering page’s animation begins — so both blur states are never on screen simultaneously, and each transition is a clean, sequential reveal.

CustomCursor

CustomCursor replaces the system pointer site-wide, reinforcing the bespoke aesthetic at the most micro-interaction level. It hides the native cursor with cursor: none applied globally and renders two layered elements — a ring and a dot — that follow the pointer. Spring-driven tracking Mouse position is captured on mousemove and fed into two Framer Motion useSpring values:
const springConfig = { damping: 25, stiffness: 700 };
const x = useSpring(mouseX, springConfig);
const y = useSpring(mouseY, springConfig);
The high stiffness keeps the cursor snappy and close to the real pointer, while the damping prevents overshoot. The two layers — a 32px outer ring and an 8px inner dot — both track the same spring values, so they move in unison. Visual layers
  • Outer ring: 32px circle, border-turquoise, rendered with mix-blend-mode: screen so it composites additively over the dark background and glows without obscuring content.
  • Inner dot: 8px solid turquoise circle with a turquoise box-shadow glow, providing a precise hot-spot anchor.
Hover states CustomCursor attaches mouseenter/mouseleave listeners to all interactive elements matching a, button, input, textarea, [role="button"]. When the pointer enters one of these targets the outer ring scales to 1.5× and gains a semi-transparent turquoise fill plus a stronger glow, signaling interactivity without changing the native pointer icon. Particle trail On every mousemove event there is a 50% chance that a particle is emitted at the current cursor position. Each particle is pushed into a state array (capped at 15 concurrent particles) and rendered as a motion.div that plays a self-contained animation:
// Per-particle animation
initial:    { opacity: 0.8, scale: 1 }
animate:    { opacity: 0, scale: 0, y: particleY + 20 }
transition: { duration: 0.8, ease: "easeOut" }
Particles are rendered as 1px turquoise circles (w-1 h-1) at the captured cursor position. Each one fades from 0.8 opacity to transparent, shrinks to zero scale, and drifts 20px downward over 800ms before being removed from the state array.

Build docs developers (and LLMs) love