Skip to main content

Documentation Index

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

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

The Testimonials page presents client feedback as voices channeled from the beyond. Rather than a static list of quotes, a single testimonial occupies the full vertical center of the viewport at any one time. It appears from a blur — as if materializing from mist — holds for 8 seconds, then fades back out as the next voice takes its place. A large decorative quotation mark floats above the text, and the author credit fades in with a 1.5 s delay after the quote itself, giving it the air of a séance revelation.

Overview

The Testimonials page lives at route /testimonials and is titled “Channeled Reviews”. Its opening quote — “Listen closely. The voices of past collaborators echo in the void.” — positions client feedback as supernatural endorsements rather than conventional reviews. An index state variable tracks the current testimonial, incremented every 8 seconds by a setInterval inside useEffect. The index wraps back to 0 using modulo arithmetic. AnimatePresence with mode="wait" ensures the outgoing testimonial fully completes its exit before the incoming one begins.
The useEffect interval is cleaned up by returning clearInterval(intervalId) from the effect. This prevents memory leaks when the component unmounts (e.g., navigating away mid-cycle) and avoids the stale-closure pitfall by using the functional form of setState: setIndex(prev => (prev + 1) % testimonials.length).

Layout & Components

AnimatePresence (mode=wait)

The testimonial block is wrapped in AnimatePresence mode="wait". Each motion.div uses key={index} so React treats each new index as a distinct element, triggering the full enter/exit cycle. Without mode="wait", the incoming and outgoing testimonials would overlap during the transition.

Blur-in transition

The motion.div enters with initial: { opacity: 0, filter: "blur(10px)", scale: 0.9 } and animates to { opacity: 1, filter: "blur(0px)", scale: 1 }. The exit mirrors the entrance but expands slightly (scale: 1.1) to suggest the voice dissipating outward.

Decorative quotation mark

A large " character rendered in text-6xl text-gold/20 sits absolutely positioned above the testimonial text. It does not animate — it’s a purely decorative typographic element.

Author credit fade-in

The author line is a nested motion.div with initial: { opacity: 0 } and animate: { opacity: 1 } using transition: { delay: 1.5, duration: 1 }. The 1.5 s delay means the author identity is revealed only after the quote has fully appeared, mimicking the suspense of identifying a channeled spirit.

Data Structure

Testimonials are defined as an array of objects. Add as many as needed — the interval cycle will include all of them:
const testimonials = [
  {
    text: "They didn't just build the app, they breathed life into it. The code is so clean it's almost supernatural.",
    author: "Client from the E-Commerce Realm",
  },
  {
    text: "Bugs seem to vanish before they even appear. It's like they have a sixth sense for edge cases.",
    author: "Lead Architect, Mystic Systems",
  },
  {
    text: "The most reliable spellcaster I've ever hired. Delivered the project before the blood moon.",
    author: "Product Manager, Tech Coven",
  },
];
The current index cycles via:
useEffect(() => {
  const interval = setInterval(() => {
    setIndex((prev) => (prev + 1) % testimonials.length);
  }, 8000); // 8 seconds per testimonial

  return () => clearInterval(interval);
}, []);

Animations

Each testimonial motion.div uses a transition of duration: 2, ease: "easeInOut" for both entrance and exit. The 2 s duration is intentionally long — the slow materialisation is central to the channeling aesthetic. The blur travels from 10px to 0px on entry and 0px to 10px on exit.
Alongside the blur, the scale animates from 0.9 (slightly shrunken) to 1.0 on entry, and from 1.0 to 1.1 (slightly expanded) on exit. This gives the impression of the quote “arriving” from a distance and “departing” by expanding past the viewer.
The testimonial motion.div also carries the animate-waver CSS class, which is defined as a custom Tailwind animation in the project’s config. It applies a subtle vertical float loop, making the visible quote appear to hover in the void rather than sitting rigidly on the page.
The h1 (“Channeled Reviews”) and quote paragraph use the standard initial: { opacity: 0, y: 20 }animate: { opacity: 1, y: 0 } entrance shared across all Fortune Seeker pages. This is positioned absolutely at top: 32px so it stays visible regardless of the testimonial cycling below.

Customization

1

Add more testimonials

Push new objects into the testimonials array. Each must have a text (the quote) and an author string. The cycle duration of 8 seconds per testimonial is fixed in the setInterval call — adjust the 8000 value to increase or decrease display time per quote.
2

Shorten transition time

The blur transition uses duration: 2. For a snappier feel reduce to 1 or 1.2. Keep in mind the author’s delay: 1.5 — if you shorten the transition below 1.5, the author will not have time to fade in before the next cycle begins. Set delay to transitionDuration - 0.5 as a safe rule.
3

Add manual navigation

Implement previous/next buttons by wiring two onClick handlers that call setIndex(prev => (prev - 1 + testimonials.length) % testimonials.length) and setIndex(prev => (prev + 1) % testimonials.length). Render them as subtle gold arrow buttons flanking the testimonial container, and consider pausing the setInterval while the user is interacting manually.
4

Style the author attribution

The author renders as — Author Name in text-gold/60 font-serif italic tracking-wider. To add a title or company on a second line, split author into name and role fields and render them as separate elements with distinct font sizes.
If you render this page inside a component that unmounts and remounts frequently (e.g., a tab switcher or a modal), always verify that the useEffect cleanup function correctly cancels the setInterval. A leaked interval will continue firing state updates on an unmounted component, causing React warnings and potential memory issues.

Build docs developers (and LLMs) love