Skip to main content

Documentation Index

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

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

The Testimonials page — titled “Praise from the Living” — turns social proof into a haunted spectacle. Instead of a static card grid, each testimonial is embodied by a translucent ghost that endlessly drifts across the viewport. Visitors who catch a ghost by hovering over it pause its flight and see a tooltip card appear above it with the full quote. It is playful, memorable, and rewards curiosity.

Route

/testimonials

Testimonials Data

All testimonials are defined in a top-level constant:
const TESTIMONIALS = [
  { name: 'CTO, SpookyCorp',  text: 'They revived our dead project in record time.' },
  { name: 'Lead Designer',    text: 'Pixel perfect implementation. Scary good.' },
  { name: 'Product Manager',  text: 'Never missed a deadline. Might be a vampire.' },
];
#NameQuote
1CTO, SpookyCorp”They revived our dead project in record time.”
2Lead Designer”Pixel perfect implementation. Scary good.”
3Product Manager”Never missed a deadline. Might be a vampire.”

Ghost Element

Each testimonial is rendered using the shared GhostIcon SVG component:
<GhostIcon className="w-16 h-16 text-haunt-moon/50 group-hover:text-haunt-moon transition-colors" />
At rest the icon is rendered at 50 % opacity (text-haunt-moon/50). On hover it brightens to full text-haunt-moon, drawing the eye to whichever ghost the visitor is pursuing.

Infinite Scroll Animation

Each ghost uses Framer Motion to animate from off-screen left (x: -200) to beyond the right edge of the viewport (x: '100vw'), repeating forever:
<motion.div
  initial={{ x: -200, y: index * 100 + 50 }}
  animate={{ x: '100vw' }}
  transition={{
    duration: 20 + index * 5,  // 20s, 25s, 30s
    repeat: Infinity,
    ease: 'linear',
    delay: index * 3,          // staggered start: 0s, 3s, 6s
  }}
>
GhostDurationStart delayVertical offset
Ghost 1 (CTO)20 s0 sy: 50
Ghost 2 (Designer)25 s3 sy: 150
Ghost 3 (PM)30 s6 sy: 250
The staggered delays and different vertical positions (index * 100 + 50) ensure ghosts never fully overlap.
To make a ghost drift faster, reduce its duration value. To slow it down, increase it. A value around 15 feels frenetic; 40 creates a serene, slow haunting. You can also adjust delay to change how long after page load each ghost first appears.

Hover-to-Pause Behaviour

Framer Motion’s whileHover prop is applied to each ghost container:
whileHover={{ animationPlayState: 'paused', scale: 1.1, zIndex: 50 }}
While the pointer is over the ghost:
  • The animation is paused in place
  • The ghost scales up by 10 %, making it feel “caught”
  • Its z-index is raised to 50 so the tooltip card is never obscured by another ghost

Tooltip Card

The testimonial quote is revealed as an absolutely-positioned tooltip rendered above the ghost:
<div className="absolute bottom-full mb-4 opacity-0 group-hover:opacity-100 transition-opacity
                w-64 bg-haunt-tombstoneDark p-4 rounded-lg border border-haunt-moon/30
                text-center shadow-xl pointer-events-none">
  <p className="text-haunt-bone text-sm italic mb-2">"{testimonial.text}"</p>
  <p className="text-haunt-pumpkin text-xs font-bold">- {testimonial.name}</p>
  {/* downward-pointing diamond arrow */}
  <div className="absolute -bottom-2 left-1/2 -translate-x-1/2 w-4 h-4
                  bg-haunt-tombstoneDark border-b border-r border-haunt-moon/30 rotate-45" />
</div>
The tooltip is pointer-events-none so it cannot interfere with the ghost’s own hover target. The quote is in italics; the attribution is in text-haunt-pumpkin to provide visual hierarchy.

Page Title Z-Index

The <h1> title is given relative z-10 to keep it readable above the ghost layer, which sits in a relative h-96 container directly below:
<h1 className="text-5xl text-haunt-moon mb-16 text-center relative z-10">
  Praise from the Living
</h1>

Adding a Testimonial

Append an entry to the TESTIMONIALS array:
const TESTIMONIALS = [
  { name: 'CTO, SpookyCorp',  text: 'They revived our dead project in record time.' },
  { name: 'Lead Designer',    text: 'Pixel perfect implementation. Scary good.' },
  { name: 'Product Manager',  text: 'Never missed a deadline. Might be a vampire.' },
  // Add your entry here:
  { name: 'VP Engineering',   text: 'Delivered a year\'s roadmap in six months. Witchcraft.' },
];
Each new entry automatically receives the next index-derived duration, delay, and y offset, so ghosts continue to spread evenly across the container height.
If you add many testimonials the vertical container (h-96) may become too crowded. Consider increasing its height or capping ghost y offsets with a modulo so they wrap around vertically.

Build docs developers (and LLMs) love