Skip to main content

Documentation Index

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

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

The GhostSprite component renders a single SVG ghost character that endlessly traverses the viewport from left to right, bobbing gently on a sinusoidal Y path. Multiple instances can be scattered across a page background to create a living, arcade-flavored atmosphere without distracting from the main content. The sprite sits at z-0, stays at opacity-30, and never captures pointer events, making it a purely decorative background element.

Props

color
string
default:"#a3ff12"
CSS color value applied to the ghost body’s fill and the drop-shadow glow filter. Accepts any valid CSS color string (hex, rgb(), named color, etc.). Defaults to arcade lime.
delay
number
default:0
Framer Motion animation delay in seconds before the ghost begins its traversal. Use staggered delays across multiple instances to avoid a synchronized “wall” of ghosts.
duration
number
default:20
Total time in seconds for one complete left-to-right pass (from off-screen left to off-screen right). Lower values produce faster ghosts; higher values produce slower, more atmospheric drifts.
startX
string
default:"-10vw"
CSS viewport-relative string for the ghost’s horizontal start position. The animation always ends at 110vw, so the ghost enters and exits off-screen regardless of this value.
startY
string
default:"50vh"
CSS viewport-relative string for the ghost’s vertical anchor position. The bob animation oscillates around this value: up by 100 px, down by 50 px, then back.
scale
number
default:1
Uniform scale factor applied to the 48×48 px SVG ghost. Use values above 1 for larger background ghosts or below 1 for distant, smaller ones.

Usage

import GhostSprite from "./components/GhostSprite";

// Single ghost with defaults
<GhostSprite />

// Customised ghost
<GhostSprite
  color="#ff00aa"
  delay={3}
  duration={15}
  startY="30vh"
  scale={1.5}
/>

// Staggered flock across a page section
{[0, 5, 11, 17].map((delay, i) => (
  <GhostSprite
    key={i}
    color={["#a3ff12", "#00ffff", "#ff00aa", "#ff6600"][i]}
    delay={delay}
    duration={18 + i * 2}
    startY={`${20 + i * 15}vh`}
    scale={0.8 + i * 0.15}
  />
))}
Use the delay prop to stagger multiple instances. Gaps of 4–6 seconds between ghosts prevent them from clustering and maintain a natural, unpredictable feel.

Visual & Behavioral Description

SVG Shape

The ghost is a hand-crafted 48×48 px SVG with three layers:
LayerShapeFill
BodySingle path forming the rounded ghost silhouette with wavy bottom hemcolor prop
Eye socketsTwo elliptical cut-out paths#0a0a0a (near-black)
PupilsTwo smaller elliptical paths inside the sockets#00ffff (cyan)
The fixed cyan pupils give every ghost the same hollow stare regardless of the body color, maintaining visual coherence across color variants.

Glow Effect

The wrapping motion.div carries the Tailwind class drop-shadow-[0_0_8px_currentColor]. Because currentColor inherits from the element’s color CSS property (which is set to the color prop), the glow hue automatically matches the ghost body — a lime ghost glows lime, a magenta ghost glows magenta.

Motion

The animation is driven by a Framer Motion animate object with two simultaneous axes: Horizontal (x) — linear traversal:
animate={{ x: ["−10vw", "110vw"] }}
transition={{
  x: {
    duration,       // full pass time in seconds
    ease: "linear", // constant velocity, no easing
    repeat: Infinity,
    delay,
  },
}}
Vertical (y) — sinusoidal bob:
animate={{ y: [startY, `calc(${startY} - 100px)`, `calc(${startY} + 50px)`, startY] }}
transition={{
  y: {
    duration: duration / 2, // bob cycle is half the full traverse time
    ease: "easeInOut",
    repeat: Infinity,
    delay,
  },
}}
The Y keyframe sequence — anchor → up 100 px → down 50 px → anchor — produces an asymmetric float that feels organic rather than mechanical.

Implementation Notes

import { motion } from "framer-motion";

export default function GhostSprite({
  color = "#a3ff12",
  delay = 0,
  duration = 20,
  startX = "-10vw",
  startY = "50vh",
  scale = 1,
}) {
  return (
    <motion.div
      className="absolute pointer-events-none z-0 opacity-30"
      style={{ color }} // enables currentColor glow
      initial={{ x: startX, y: startY }}
      animate={{
        x: [startX, "110vw"],
        y: [startY, `calc(${startY} - 100px)`, `calc(${startY} + 50px)`, startY],
      }}
      transition={{
        x: { duration, ease: "linear", repeat: Infinity, delay },
        y: { duration: duration / 2, ease: "easeInOut", repeat: Infinity, delay },
      }}
    >
      <svg
        width={48 * scale}
        height={48 * scale}
        viewBox="0 0 48 48"
        className="drop-shadow-[0_0_8px_currentColor]"
      >
        {/* Body */}
        <path d="..." fill={color} />
        {/* Eye sockets */}
        <path d="..." fill="#0a0a0a" />
        <path d="..." fill="#0a0a0a" />
        {/* Pupils */}
        <path d="..." fill="#00ffff" />
        <path d="..." fill="#00ffff" />
      </svg>
    </motion.div>
  );
}
GhostSprite uses position: absolute, so its parent container must have position: relative (or another positioning context) set. If placed inside a PageTransition wrapper, the wrapper’s relative class satisfies this requirement automatically.

Where It’s Used

GhostSprite instances are placed directly inside page-level components (e.g., the title screen, projects page) as background decoration. They are siblings of the main content, not wrappers around it — content sits at higher z-index values while the ghosts remain at z-0.

Build docs developers (and LLMs) love