Haunt mode is the master switch that controls every scary visual in Spooky Developer. A single boolean —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.
isHaunted — flows through the app via React Context, enabling or disabling cursor trails, spider drops, idle ghosts, and page-level animations without touching individual component logic. Because the preference is persisted in localStorage, visitors who opt out stay opted out until they change their mind.
What Haunt Mode Controls
The following effects are gated behindisHaunted. When the flag is false, each component returns early or skips its animation entirely.
| Effect | Component | Trigger | Extra condition |
|---|---|---|---|
| Ghost cursor trail | CursorTrail | mousemove | Non-touch device only |
| Spider drop | SpiderScare | First click | hasClicked is false |
| Idle ghost slide-in | IdleGhost | 45 s of inactivity | Resets on any user event |
| Contact success animation | ContactPage | Form submission | Pumpkin icon rises from bottom |
| 404 floating ghost | NotFoundPage | Page load | Loops indefinitely |
| Homepage pumpkin pupils | HomePage | mousemove | Pupils track regardless — only visual intensity changes |
The pumpkin pupil tracking on the home page reads
mousePos from HauntContext regardless of isHaunted. The pupils always follow the cursor; haunt mode controls the surrounding spooky effects, not the tracking math itself.CursorTrail
CursorTrail consumes isHaunted and mousePos from useHaunt(). It also detects touch devices with 'ontouchstart' in window || navigator.maxTouchPoints > 0 and short-circuits entirely on mobile, so tablet and phone visitors never see ghost icons cluttering their screen.
When active, up to five trailing GhostIcon elements are rendered via AnimatePresence. Each trail particle fades and scales down over 500 ms before being removed from state.
SpiderScare
SpiderScare reads isHaunted and hasClicked from context. It listens for a global click event; the very first click while haunt mode is on triggers a SpiderIcon that drops from y: "-100vh" to y: "20vh", sways slightly, then retracts — the entire animation lasts 4 seconds. After that, registerClick() writes 'haunt-clicked': 'true' to sessionStorage, ensuring the spider only ever drops once per browser session.
IdleGhost
IdleGhost starts a 45-second setTimeout on mount (and resets it on mousemove, keydown, click, and scroll). When the timer fires, a GhostIcon slides in from the right edge of the viewport at top: 50%. Any user interaction hides it immediately and restarts the clock. Setting isHaunted to false calls setVisible(false) synchronously and tears down all listeners.
Persistence Behavior
HauntContext initialises isHaunted to true immediately, then reconciles with localStorage inside a useEffect after first render:
isHaunted is true before the useEffect fires. For returning visitors who have opted out, effects are enabled for one paint cycle and then disabled — in practice this is imperceptible, but it is the actual runtime sequence. On a first visit with no stored preference, isHaunted remains true for the entire session.
On mount, sessionStorage is also checked for the spider’s one-shot flag:
| Key | Storage | Values | Resets |
|---|---|---|---|
'haunt-enabled' | localStorage | 'true' / 'false' | Never (until cleared manually) |
'haunt-clicked' | sessionStorage | 'true' | Every new browser session / tab close |
The Toggle
useHaunt() exports a toggleHaunt() function that flips the flag and immediately persists it:
HauntProvider re-renders every consumer in one React cycle — the cursor trail vanishes, the idle timer clears, and layout-level scares stop instantly.
Toggle Button Example
Drop this anywhere inside the component tree to give visitors a one-click way to opt out:useHaunt() reads directly from context.
Disabling Haunt Mode by Default
Out of the box,isHaunted defaults to true for first-time visitors. To flip this to an opt-in model (effects off until the visitor enables them), change the useState initial value in HauntContext.js and add a guard in the useEffect:
localStorage are unaffected — only truly first-time visitors (no 'haunt-enabled' key) will see the new default of false.
Selectively Disabling Effects
All three scare components are mounted unconditionally inLayout.js. To remove a specific scare without touching its source file, simply delete its import and JSX from the layout:
CursorTrail and IdleGhost) continue to respect isHaunted as normal — nothing else needs updating.