Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/neon-retro-sys-admin/llms.txt

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

CursorTrail is a purely decorative ambient effect that draws a short trail of neon particles behind the user’s mouse cursor. It listens to window.mousemove events, maintains a rolling history of up to 15 cursor positions, and renders each position as a small circular motion.div that fades and shrinks out over half a second. The three neon colors — magenta, cyan, and lime — cycle in sequence across the trail, creating a multi-colored comet effect. The component is mounted globally in the Layout so it’s active on every page without any per-page configuration.

Behavior

Particle Rendering

Each particle is an absolute-positioned motion.div with a 12px × 12px (w-3 h-3) circular shape. Its position is set from the recorded clientX/clientY coordinates of the corresponding mousemove event (offset by 6px on each axis to center the dot on the cursor). The trail rotates through three colors based on the particle’s array index modulo 3:
index % 3ColorHex valueBox shadow
0Magenta#ff00ff0 0 10px #ff00ff
1Cyan#00ffff0 0 10px #00ffff
2Lime#39ff140 0 10px #39ff14
Both background and box-shadow are applied via inline styles so the colors can be computed dynamically from the index.

Animation Lifecycle

Framer Motion’s AnimatePresence wraps the particle list, enabling exit animations when particles are removed. Each dot animates through:
initial={{ opacity: 0.8, scale: 1 }}
animate={{ opacity: 0, scale: 0.2 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5, ease: 'easeOut' }}
The result is a trail that fades and collapses toward a point over 0.5s, giving it a soft comet-tail quality rather than an abrupt disappearance.

Cursor History Management

The component uses useState to store an array of position objects ({ x, y, id }). On each mousemove event, a new entry is appended and the oldest is dropped if the array would exceed 15 entries:
setDots(prev => {
  const next = [...prev, { x: e.clientX, y: e.clientY, id: counter++ }];
  if (next.length > 15) next.shift();
  return next;
});
A 100ms setTimeout is reset on every mouse move. If the mouse stops moving for 100ms, the array is cleared, causing all remaining particles to exit via AnimatePresence.

Stacking and Interaction

The outer container is fixed inset-0 z-[9999] overflow-hidden with pointer-events: none. Because pointer events are disabled on the entire overlay, the trail never interferes with clicks, hovers, or any other interactions on the page beneath it.

Accessibility: Reduced Motion

On mount, CursorTrail checks window.matchMedia('(prefers-reduced-motion: reduce)') and stores the result in a reducedMotion state variable. It also attaches a change listener to that media query so the component responds dynamically if the system preference changes while the page is open. When reducedMotion is true, the component returns null immediately — no DOM nodes are created, no event listeners are attached for mouse tracking, and no animations run.
The prefers-reduced-motion check covers both users who have enabled “Reduce Motion” in their operating system accessibility settings and browsers that expose this preference. Because the component returns null entirely (rather than just disabling the animation), there is zero performance overhead for users who prefer reduced motion.

No Props

CursorTrail manages all of its state internally via useState and useEffect. It does not accept any props. Color cycling, trail length, animation timing, and reduced-motion behavior are all hardcoded to ensure a consistent Y2K aesthetic across the entire site.

Usage in Layout

CursorTrail is mounted once inside the Layout component, making it active on every page:
import { CursorTrail } from '../components/layout/CursorTrail';

export function Layout() {
  return (
    <>
      <RetroNav />
      <Marquee text="*** WELCOME TO MY CORNER OF THE INTERNET ***" />
      <main className="max-w-6xl mx-auto px-4 py-8">
        <Outlet />
      </main>
      <CursorTrail />
    </>
  );
}
Because the overlay is fixed and pointer-events: none, placement in the JSX tree doesn’t affect layering or interaction — it will always float above all other content.

Build docs developers (and LLMs) love