Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/apursley2012/mystery-haunting/llms.txt

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

The Eye-Witness Accounts page (route: /testimonials) replaces the conventional testimonial carousel with something far more unsettling: a set of disembodied SVG eyeball widgets scattered across the viewport, each one silently tracking the visitor’s mouse cursor in real time. The page title reads “EYE-WITNESS ACCOUNTS” with the subtitle “They’re always watching.” Click any eye and it blinks open a floating quote card, revealing the client testimonial within. The eyes are positioned absolutely at defined percentage coordinates so they appear organically spread across the screen rather than in a predictable grid.

Testimonials

QuoteAuthorPosition (x%, y%)
He fixed bugs I didn’t even know existed. Terrifyingly efficient.CTO, Tech Corp20%, 30%
The code is clean, but I swear I hear whispers when I read it.Lead Dev, Startup.io70%, 20%
Delivered the project dead on time.Product Manager80%, 70%
A monster in the terminal. 10/10 would hire again.Client30%, 80%

Eye Tracking Mechanic

1

Capture global mouse position

A mousemove event listener is registered on window when the component mounts. On every mouse movement it stores the cursor’s clientX and clientY coordinates in React state, making the position available to every eye widget simultaneously.
useEffect(() => {
  const handleMouseMove = (e) => setMousePos({ x: e.clientX, y: e.clientY });
  window.addEventListener('mousemove', handleMouseMove);
  return () => window.removeEventListener('mousemove', handleMouseMove);
}, []);
2

Calculate pupil angle per eye

Each eye widget reads its own DOM position using a ref and getBoundingClientRect(). It then calculates the angle from the eye’s centre to the cursor using Math.atan2, producing a direction vector that always points toward the cursor regardless of where the eye sits on the page.
const angle = Math.atan2(
  mousePos.y - eyeCenter.y,
  mousePos.x - eyeCenter.x
);
3

Cap pupil offset at 20px

The raw distance between the cursor and the eye centre is computed with Math.hypot. The pupil offset is then capped at a maximum of 20px using Math.min, preventing the pupil from travelling outside the visible iris area no matter how far away the cursor is. Because the pupil is rendered inside an SVG using percentage coordinates, the final position is expressed as a percentage offset from centre (50, 50).
const offset = Math.min(20, Math.hypot(
  mousePos.x - eyeCenter.x,
  mousePos.y - eyeCenter.y
));
const pupilX = 50 + Math.cos(angle) * offset;
const pupilY = 50 + Math.sin(angle) * offset;
4

Toggle quote card on click

Each eye maintains a local isOpen boolean in state. Clicking the eye flips this value. When isOpen is true, Framer Motion animates the testimonial quote card from opacity: 0, scale: 0.8, y: 10 to fully visible and in position — and reverses the animation when the eye is clicked again to close it.
<motion.div
  animate={isOpen ? { opacity: 1, scale: 1, y: 0 } : { opacity: 0, scale: 0.8, y: 10 }}
>
  {testimonial.text}
</motion.div>

Absolute Positioning

Each eye component is rendered with position: absolute and placed at its designated x and y percentage coordinates using inline left and top styles, e.g. left: '20%', top: '30%'. The parent container is position: relative and fills the full viewport height, giving the eyes room to roam the entire page canvas.

Customization

Add a new object to the testimonials array in the page source. Each entry requires a unique id, the quote text, the author label, and x/y percentage coordinates for placement.
const testimonials = [
  {
    id: 1,
    text: 'He fixed bugs I didn\'t even know existed. Terrifyingly efficient.',
    author: 'CTO, Tech Corp',
    x: 20,  // % from left
    y: 30   // % from top
  },
  {
    id: 2,
    text: 'The code is clean, but I swear I hear whispers when I read it.',
    author: 'Lead Dev, Startup.io',
    x: 70,
    y: 20
  },
  {
    id: 3,
    text: 'Delivered the project dead on time.',
    author: 'Product Manager',
    x: 80,
    y: 70
  },
  {
    id: 4,
    text: 'A monster in the terminal. 10/10 would hire again.',
    author: 'Client',
    x: 30,
    y: 80
  },
  // Add more entries here
];
Each entry automatically receives its own SVG eye widget, mouse-tracking logic, and quote card toggle — no additional wiring is needed.
Use x/y values between 10–85% to keep eyes within the visible viewport area. Values too close to 0 or 100 risk clipping the eye or its quote card against the edge of the screen.

Build docs developers (and LLMs) love