Haunted Room
Pull the chain in a pitch-black room: a bare bulb swings on and a message scrawls itself across the wall, then bleeds.
- Zero dependencies
- React + Tailwind
- Accessible
- Reduced-motion aware
Preview
Animated · Full motion: idle animation, physics and effects. animated (default)
The light pool follows the swinging bulb across stained plaster. Writing is drawn stroke by stroke with a rough displacement filter, then fills and drips; tally scratches appear beside it. While on, the bulb flickers by itself and after every blackout the wall says something new.
Installation
$ npx shadcn@latest add https://guetix.com/r/haunted-room.json
Uses the shadcn CLI. It creates components/guetix/haunted-room.tsx and installs nothing else.
Usage
example.tsx
"use client";
import HauntedRoom from "@/components/guetix/haunted-room";
export default function HauntedRoomDemo({ variant = "animated" }: { variant?: string }) {
return (
<HauntedRoom animated={variant !== "regular"} className="h-full min-h-[460px]">
<p className="font-mono text-[10px] uppercase tracking-[0.4em] text-white/50">Room 237 · open if you dare</p>
</HauntedRoom>
);
}
Halloween campaignHorror game / film landing page404 pageEaster egg
Under the hood
The details that make it feel right, and that a one-line prompt usually misses:
- 01Pendulum + chain spring run in one rAF loop that writes CSS variables, so swinging never re-renders React.
- 02Hand-written look = SVG text stroke-dash drawing + feTurbulence/feDisplacementMap roughness, then fill and drips on a delay.
- 03Darkness is a radial mask centred on the bulb's live position; flicker drives one opacity value.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
| messages | string[] | ["GET OUT", …] | Wall messages, one per blackout. |
| children | ReactNode | — | Content lit under the writing. |
| ink | string | "#7a0606" | Writing color. |
| defaultOn | boolean | false | Start lit. |
| onChange | (on: boolean) => void | — | Called when switched. |
| animated | boolean | true | false = no swing, flicker or blackouts. |
Accessibility
- The chain handle is a `role="switch"` button (drag, click, Enter or Space).
- The current wall message is announced in a polite live region.
- Reduced motion: no swing or flicker; writing appears without drawing.