Inspiration
I've always been fascinated by the dark corners of history—the haunted places, the unsolved mysteries, the chilling "what ifs" that send shivers down your spine. I wanted to create an experience where anyone could explore a location's darkest secrets through AI-generated horror stories. The idea was to combine storytelling with real geographic data, so the terror feels personal and grounded in actual places. When Kiro IDE launched, I saw an opportunity to build something ambitious using its spec-driven development approach.
What it does
Hauntify is an AI-powered horror storytelling app that transforms simple prompts into immersive experiences:
- Generates cinematic horror stories using Groq's LLaMA 3.3 70B model, streamed token-by-token in real-time
- Narrates stories with professional AI voice synthesis via ElevenLabs (with Web Speech API fallback)
- Visualizes locations on an interactive Leaflet map with animated orange-glow markers as each place is mentioned
- Extracts historical timeline events that appear as vertical cards alongside the story
- Synchronizes everything in real-time—streaming text, voice generation, and map updates happen simultaneously
The interface features a split-screen layout (chat on left, map on right) with a dark Halloween-themed aesthetic using black, orange, and purple colors.
How we built it
Tech Stack:
- Frontend: Next.js 16 with React 19 and Tailwind CSS v4
- AI Generation: Groq API (LLaMA 3.3 70B Versatile) for streaming story generation via Server-Sent Events
- Voice Synthesis: ElevenLabs TTS API with Brian voice for dramatic narration
- Maps: Leaflet with custom animated markers and polyline paths connecting events
- Geocoding: Nominatim API (OpenStreetMap) with $1 \text{ req/sec}$ rate limiting and 30-day caching
- State Management: Zustand with localStorage persistence for session recovery
- Validation: Zod schemas for runtime type safety
Key Components:
useChatStreamhook orchestrates the streaming pipeline from prompt to responseAudioQueueManagerclass handles sequential audio playback with proper blob URL lifecycle management- Server-side parser extracts
##TIMELINE##markers with JSON data (year, title, description, place) - Custom hooks (
useAudioPlayer,useMapSync) coordinate voice playback and map updates
Challenges we ran into
Audio Queue Management: Building a custom AudioQueueManager that handles blob URLs, sequential playback, auto-advance, seek controls, and proper cleanup without memory leaks was complex. I implemented a listener pattern for state updates and careful URL.revokeObjectURL lifecycle management.
Streaming Pipeline Coordination: Orchestrating three simultaneous streams (text tokens, timeline extraction, voice generation) without blocking or race conditions required careful use of NDJSON event types and proper AbortControllers.
Timeline Extraction Accuracy: Parsing ##TIMELINE## markers from streaming text while handling malformed JSON, duplicate events, and invalid coordinates needed a robust parser with Zod validation and deduplication.
Rate Limiting for Geocoding: Nominatim API requires $1$ request per second. I implemented throttling with a 30-day localStorage cache to stay within limits while keeping the experience responsive.
Mobile Responsiveness: Creating a seamless experience across desktop ($60/40$ split), tablet ($50/50$), and mobile (tabbed interface) while keeping both components mounted to maintain state.
Accomplishments that we're proud of
- A zero-latency streaming experience where text, voice, and map updates flow simultaneously as the story generates
- A cinematic audio player with full controls (play/pause, seek, volume) and automatic queue management
- A dark horror aesthetic that feels immersive with animated gradients, glowing markers, and Halloween-themed UI
- Session persistence that restores full conversation history, timeline, audio queue, and map state on page reload
- A robust fallback system—Web Speech API works when ElevenLabs isn't available, coordinate validation prevents map errors
What we learned
- Streaming UX beats batch responses: Users perceive speed when something happens immediately (first token, voice starting, map marker appearing)
- Audio blob lifecycle matters: Proper cleanup with
URL.revokeObjectURLprevents memory leaks in long sessions - Rate limiting is essential: Respecting API limits with caching ensures reliability without getting blocked
- Spec-driven development accelerates building: Writing requirements and design docs upfront with Kiro eliminated decision paralysis and prevented costly refactoring
- Type safety catches bugs early: Zod schemas and TypeScript strict mode prevented countless runtime errors
What's next for Hauntify
- Save & Share Stories with unique URLs and social media previews
- Multi-Voice Cast with different voices for narrator vs. character dialogue
- Enhanced Map Layers with historical overlays and custom horror-themed marker icons
- Branching Narratives where users can influence the story direction
- Ambient Audio Effects like reverb, echo, and background soundscapes for deeper immersion
- Export Functionality to download stories as audio files or PDFs
Built With
- elevenlabs-api
- groq
- groq-api-(llama-3.3-70b)
- kiro
- leaflet.js
- motion
- next.js-16
- openstreetmap-nominatim
- radix-ui
- react-19
- tailwind-css-4
- typescript
- vercel
- zod
- zustand
Log in or sign up for Devpost to join the conversation.