DevHaunt brings its haunted atmosphere to life through two complementary animation layers. CSS keyframe animations handle the ambient, looping effects that run continuously in the background — the drifting fog, flickering windows, swaying sign, and floating ghost bob. Framer Motion takes over for anything that responds to user interaction or navigation: page transitions, entrance reveals, scroll-driven transforms, and staggered list appearances. Understanding which layer does what makes it straightforward to tune, extend, or disable any effect independently.Documentation Index
Fetch the complete documentation index at: https://mintlify.com/apursley2012/dev-haunt/llms.txt
Use this file to discover all available pages before exploring further.
Animation Stack
| Layer | Technology | Used for |
|---|---|---|
| Ambient loops | CSS @keyframes | Fog drift, window flicker, sign sway, ghost float |
| Entrance & transitions | Framer Motion motion.* | Page blur-in/out, fade-slide reveals, staggered cards |
| Scroll-reactive | React useState + useEffect | Door opening perspective transform in HauntedHouse.js |
| Continuous Framer loops | animate + repeat: Infinity | Moon bob, cloud drift in HauntedHouse.js |
Page Transitions
All route-level transitions are handled byAnimatePresence in components/Layout.js. The mode="wait" option ensures the exiting page fully completes its blur-out before the entering page starts its blur-in, preventing two pages from rendering simultaneously.
components/Layout.js
CSS Keyframe Animations
All four CSS animations are defined inassets/main.css and applied via Tailwind utility classes or direct class names on the relevant elements.
animate-flicker — window candlelight
animate-flicker — window candlelight
Simulates an irregularly flickering light source inside the house windows in Three window groups in
HauntedHouse.js. The opacity drops to 0.4 at specific keyframe offsets that mimic a real candle’s unpredictable stutter.assets/main.css
HauntedHouse.js use this class with staggered animationDelay values (0s, 1s, 2s) so the windows flicker independently rather than in sync.To slow the flicker: change 3s to 5s. To make it more aggressive: add more opacity-0 keyframe stops.animate-sway — hanging sign
animate-sway — hanging sign
Applies a gentle pendulum rotation to the The To increase the arc: widen the rotation range from
DevHaunt wooden sign that hangs below the roofline in the SVG illustration. The rotation pivots around 200px 150px, the top-center of the sign’s rope attachment point.assets/main.css
<g> element in HauntedHouse.js uses both the CSS class and a Framer Motion wrapper:components/HauntedHouse.js
±5deg to ±10deg. To speed up the pendulum: reduce 4s to 2s.fog-drift — ambient fog layer
fog-drift — ambient fog layer
Powers the To slow the fog: increase
FogLayer component that sits fixed at the bottom of the viewport. Two overlapping fog layers animate in opposite directions at different speeds, creating a natural layered fog effect. A mask-image gradient fades the fog out toward the top so it blends seamlessly into the page content.assets/main.css
The fog SVG path is inlined as a
data: URI so no external image request is made. The fill color is %23f4f1ea (URL-encoded #f4f1ea, the ghost token). Update this hex inside the data URI string to change the fog color.40s and 30s. To thicken the fog: increase the SVG path’s opacity attribute from 0.15 / 0.1 to a higher value.group-hover:animate-float — ghost bob
group-hover:animate-float — ghost bob
Triggers a vertical bob on elements inside a Tailwind The moon/ghost element in
group container when hovered. Used for the floating ghost character in HauntedHouse.js via animate-float-style behavior.assets/main.css
HauntedHouse.js uses a Framer Motion continuous loop for the same visual effect (see Continuous Float Loop below), while the CSS version activates only on hover within a group.Framer Motion Patterns
DevHaunt uses Framer Motion consistently across all page components. The following patterns cover every variant present in the codebase.Entrance Animation — Fade and Slide Up
The most common pattern in the codebase. Used on every page heading, hero section, and intro block. The element starts invisible and 20px below its natural position, then animates to full opacity at its correct location.Staggered List Entrance
Used on the Projects (Graveyard) and Testimonials pages. Each item in the list multipliesindex * 0.1 for its delay, so the cards cascade in one after another rather than all appearing at once.
Scale Entrance
Used on the About page’sTrickOrTreatBag illustration. The element begins at 80% scale and fades in, creating a “materializing” effect.
Continuous Float Loop
The moon inHauntedHouse.js bobs continuously using a Framer Motion animate array — no CSS keyframe required. The repeat: Infinity option loops it forever.
components/HauntedHouse.js
components/HauntedHouse.js
Scroll-Reactive Door Transform
HauntedHouse.js uses a plain React scroll listener (not Framer’s useScroll) to drive a CSS perspective rotation on the front door. As the user scrolls down, the door swings open up to a maximum of 45 degrees.
components/HauntedHouse.js
0.5 multiplier. To allow a wider swing, raise the 45 cap. To reverse the direction (door opens left), negate the rotation value.
Disabling Animations
Respecting prefers-reduced-motion
Wrapping the app in Framer Motion’s MotionConfig component is the cleanest way to automatically disable all Framer animations for users who have enabled the reduced-motion OS setting. The entry point would look like:
"user" value reads the prefers-reduced-motion media query. Animations are only suppressed when the user has explicitly requested it; everyone else sees the full experience.
MotionConfig reducedMotion="user" disables Framer Motion animations only. The CSS keyframe animations (fog-drift, flicker, sway, float) are not affected. To also suppress those, add a CSS media query at the end of assets/main.css:assets/main.css
Disabling All Animations Globally
To turn off every Framer Motion animation unconditionally (useful for testing or screenshot tools), usereducedMotion="always" in the same MotionConfig wrapper described above:
As with the
reducedMotion="user" approach, this change requires rebuilding from source. For the static dist, the practical alternative is to add the CSS @media (prefers-reduced-motion: reduce) block shown above, which suppresses the CSS keyframe loops without any JavaScript changes.