Spooky Developer’s animations run on two rails. The first is CSS keyframes — small, repeating atmospheric effects like candle flickers and ghost-eye pulses that tick along at all times. The second is Framer Motion — declarative React animations that handle entrance transitions, scroll reveals, hover interactions, and infinite loops. Together they give the site its sense of restless, haunted life without requiring every component to manage its own animation logic.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.
CSS Keyframe Animations
Three animation classes are available as Tailwind utilities. Two are defined inmain.css; one is Tailwind’s built-in.
animate-flicker — Candle Flame
The custom flicker keyframe simulates the organic irregularity of a real candle flame by cycling opacity across four non-linear stops:
main.css
animate-flicker. The second candle adds a style={{ animationDelay: '0.5s' }} so the flames flicker out of phase with each other, as real candles would:
animate-pulse — Ghost Eyes
Tailwind’s built-in animate-pulse drives the ghost eye blink in the IdleGhost component. The ghost appears at the right edge of the screen after 45 seconds of user inactivity, and its two haunt-bg-colored eye dots pulse to suggest blinking:
main.css
IdleGhost.js
animate-bounce — Standard Tailwind
Tailwind’s built-in animate-bounce is available throughout the project for any element that needs a vertical bounce loop. It uses the standard Tailwind keyframe definition:
main.css
Custom 3D Transform Utilities
Three non-standard CSS utilities are defined at the bottom ofmain.css to support the Projects page door-flip effect:
main.css
perspective-1000is applied to the outer grid container to establish a 3D perspective context for all child elements.preserve-3dis applied to each card’s wrapperdivso that the door’s rotation is calculated in the same 3D space.rotate-y-105is toggled onto the door element when the card is clicked (controlled by React state), swinging it open at −105° like a real door:
Projects page
The
rotate-y-105 transform uses -105deg rather than -90deg so the door swings slightly past perpendicular, which reads more naturally as a door being pushed fully open.Framer Motion Patterns
Framer Motion handles all of the app’s interactive and entrance animations. Four patterns appear repeatedly across the codebase.Pattern 1 — Entrance Fade-In
Used on most pages to bring content in smoothly on mount. Elements start invisible and 20px below their final position, then animate to full opacity at rest:Pattern 2 — Scroll-Triggered Reveal
Used on the About (timeline) and Work (experience) pages to reveal each item as it enters the viewport. Items slide in from the left and combine a staggered delay based on their index:viewport={{ once: true }} flag means each item animates in exactly once, not every time the user scrolls past it. The margin: '-100px' triggers the animation slightly before the element fully enters the viewport, so users never see a blank flash.
The Work page uses a vertical variant (y: 50 instead of x: -50) to slide cards upward:
Pattern 3 — Hover Lift
Used on the Skills page candy bowl. Each skill “candy” lifts upward, scales up, and jumps to a high z-index when hovered, so it floats above its neighbours:whileHover state reverts automatically when the cursor leaves — no explicit animate value needed.
Pattern 4 — Infinite Loop (Oscillation)
Used for the rotating site title on the Home page and the ghost drift animation. Arotate array drives the element to oscillate between −2° and +2°, creating a gentle swaying effect:
x loop to drift ghost figures across the full viewport width: