Skip to main content

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.

Haunt mode is the master switch that controls every scary visual in Spooky Developer. A single boolean — 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 behind isHaunted. When the flag is false, each component returns early or skips its animation entirely.
EffectComponentTriggerExtra condition
Ghost cursor trailCursorTrailmousemoveNon-touch device only
Spider dropSpiderScareFirst clickhasClicked is false
Idle ghost slide-inIdleGhost45 s of inactivityResets on any user event
Contact success animationContactPageForm submissionPumpkin icon rises from bottom
404 floating ghostNotFoundPagePage loadLoops indefinitely
Homepage pumpkin pupilsHomePagemousemovePupils 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:
// Initial state is always true on first render
const [isHaunted, setIsHaunted] = useState(true);

useEffect(() => {
  // Override with stored preference if one exists
  const stored = localStorage.getItem('haunt-enabled');
  if (stored !== null) {
    setIsHaunted(stored === 'true');
  }
  // If stored is null (first-ever visit), isHaunted stays true
}, []);
This means there is a brief first render where 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:
sessionStorage.getItem('haunt-clicked') === 'true' && setHasClicked(true);
KeyStorageValuesResets
'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:
const toggleHaunt = () => {
  const next = !isHaunted;
  setIsHaunted(next);
  localStorage.setItem('haunt-enabled', String(next));
};
Calling this from any component inside 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:
function HauntToggle() {
  const { isHaunted, toggleHaunt } = useHaunt();
  return (
    <button
      onClick={toggleHaunt}
      className="px-4 py-2 bg-haunt-moon/20 text-haunt-moon
                 border border-haunt-moon/50 rounded-full
                 hover:bg-haunt-moon/40 transition-colors"
    >
      {isHaunted ? '🔕 Disable haunting' : '👻 Enable haunting'}
    </button>
  );
}
The button label reflects the current state and requires no extra prop threading — 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:
// In HauntContext.js — change the initial value to false:
const [isHaunted, setIsHaunted] = useState(false); // was: true

useEffect(() => {
  const stored = localStorage.getItem('haunt-enabled');
  if (stored !== null) {
    setIsHaunted(stored === 'true');
  }
  // If stored is null (first-ever visit), isHaunted stays false — opt-in mode
}, []);
Returning visitors whose preference is already stored in 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 in Layout.js. To remove a specific scare without touching its source file, simply delete its import and JSX from the layout:
// In Layout.js — remove SpiderScare:
import { CursorTrail } from '../scares/CursorTrail';
// import { SpiderScare } from '../scares/SpiderScare'; // removed
import { IdleGhost } from '../scares/IdleGhost';

// And remove from the JSX return:
// <SpiderScare />  // removed
The remaining effects (CursorTrail and IdleGhost) continue to respect isHaunted as normal — nothing else needs updating.
To reset haunt preferences during development, open DevTools → Application → Local Storage, find your localhost origin, and delete the haunt-enabled key. Refresh the page and the context will re-evaluate as a first-time visit, defaulting back to true. To re-trigger the spider drop, also clear haunt-clicked from Session Storage in the same panel.

Build docs developers (and LLMs) love