This is a submission for the Hacktoberfest Open-Source AI Challenge Week 4: Ghost in the Machine
What I Built
Candlekeeper is an offline ofrenda where an AI can only remember what you keep alive.
In Día de los Muertos, the dead live on as long as someone remembers them. I turned that belief into the one rule the AI has to follow:
- Every story you write becomes a candle on an altar.
- A candle's strength fades over time (
exp(-days / 14)) unless you revisit it. - The AI, called the Candlelight, can only speak from candles that are still lit. Once a memory drops below 30%, it is hidden from the model, and the Candlelight has to say "I no longer remember that."
- It never pretends to be the person. The screen always says: "This is not them. It is only what you remember."
I wanted the opposite of the "AI that brings back the dead" idea. This one is honest about forgetting.
That is the "honor the past" half of the theme. For the "spooky" half, I asked a darker question: what if the machine that remembers everything isn't gentle?
Haunted Mode swaps the altar's chat for a green CRT terminal where an entity inside your computer talks back. A haunting slider (0 to 100) controls how unsettling it gets. It quotes what you typed last time, mentions your altar memories, notices the time of day, idle time and battery level, prints lines you never typed, and at high levels the terminal crashes and reboots. It also has a horror soundscape generated entirely in code.
Same model, same local memories, two opposite moods: faithful on the altar, unsettling in the terminal.
Other features: Time Travel (fast-forward up to 60 days and watch candles dim), Export Ofrenda (save the altar as one offline HTML file), and a Gemma live / Demo mode badge so you always know whether the real model is answering.
Demo
The video has no voice, so please turn on captions (CC). Turn your sound on for the Haunted Mode section, since that audio is generated in code.
To run it yourself (Node.js 20.19+ and Ollama):
ollama pull gemma3
OLLAMA_ORIGINS="*" ollama serve
npm install
npm run dev
Then open http://localhost:3000, go to Settings and press Test connection.
Code
🕯️ Candlekeeper
A Día de los Muertos ofrenda where memories fade unless you keep them alive, and a local AI that only knows what you still remember. Flip on Haunted Mode and the same memories are turned against you by something living inside a green CRT terminal.
Built for the DEV Hacktoberfest Open-Source AI Challenge, Week 4: "Ghost in the Machine".
Everything runs on your own machine. The AI is Gemma, served locally by Ollama. No cloud, no API key, no account, no tracking. If Ollama is not running, the app falls back to an offline demo mode so it never breaks.
Screenshots
Screenshots were captured with Ollama offline, so they show the app's fallback behaviour. With Ollama running, the chat header shows a green Gemma live badge.
Table
…MIT licensed. The README has architecture diagrams and a file-by-file explanation. I started and finished this project during the challenge window.
How I Built It
Open pieces: Gemma 3 (4B) running locally through Ollama, with a React 19 + TypeScript + Vite front end, Tailwind CSS 4, Motion, tsParticles and lottie-react. There is no backend, no API key and no database. Memories live in the browser, and everything is bundled locally with no CDN. After the one-time setup (installing dependencies and pulling the model), it needs no internet.
Gemma does four jobs, all through one file (src/services/ai.ts) and Ollama's /api/chat:
- Ask the Candlelight: answers questions using only the lit memories.
- Add a Memory: reads your story and returns JSON with a poetic title, a one-line summary and three tags.
- Photos: the code sends an attached photo to Gemma's vision input for a short description. I did not demo this in the video.
- The haunted entity: gets the haunting level, your earlier messages, your altar memories, the local time and battery, and replies in at most three short sentences.
Grounding by data, not just by prompt. The most important design choice is that faded memories are sent to the model as titles only, with the story text hidden. Gemma can't recall what it was never shown. If no memory is lit at all, the app doesn't call the model and answers "I no longer remember that" directly.
It can't break mid-demo. Every AI function tries Ollama first and falls back to offline scripted replies if Ollama is off. The chat badge switches between Gemma live and Demo mode.
Horror audio with no audio files. A WebAudio engine builds a dissonant drone, a rising whine, wind, a heartbeat that speeds up, whispers, creaks, a detuned music box and a crash stinger, all scaled by the haunting level. My first version was a single 53 Hz tone, which laptop speakers can't even play, so I rebuilt it in layers.
One bug I liked finding. In Haunted Mode the page gets a cold color filter, and a CSS filter quietly makes position: fixed children size to the whole page instead of the window. My "full-screen" terminal could scroll its Exit button out of view. Rendering it through a React portal on <body> fixed it.
AI assistance: I used Google AI Studio for the first UI draft, and Claude for code review, the Ollama integration, debugging, automated browser checks and the README. The idea, direction, testing and decisions were mine.
Why Does Open Innovation Matter?
Candlekeeper holds the most private kind of data there is: stories about people we have lost, and their photos. Open-weight Gemma running locally made three things possible that a closed API would not:
- Privacy by design. The stories never leave the machine. There's no server to leak from and no account to hand them to.
- No shutdown risk. A memorial that depends on someone else's API can disappear when a company changes its pricing or closes the product. This one is a folder of code plus a model file on your own disk, and the exported ofrenda is a single HTML file that works with no server at all.
- Real control. Because I control exactly what the model sees, I can enforce the core idea at the data level. Faded memories are never sent. That is much harder to guarantee through a remote service. I can also swap the model in Settings (for example a smaller Gemma on a modest laptop) and read every prompt in the code.
The honest tradeoff: a 4B model is less polished than the largest hosted models, and grounding reduces but cannot completely eliminate loose paraphrasing. For this project, privacy and control mattered more than the last bit of fluency.
The Spirit of It
Three moments from my demo stayed with me, because they came straight from the idea:
- I asked the altar, "Hi, do you remember me?" It began with "I no longer remember that" and offered only what was in the saved memories. It didn't pretend.
- When I opened Haunted Mode, the terminal greeted me with a line built from my own earlier typing: "You came back. Last time you typed 'I don't want to talk to you' and thought closing the terminal ended the conversation."
- I typed "what she does on sunday?" and Gemma answered, using only the story I had saved: "Braiding. Orange blossom. Always."
The sample grandmother, Abuela Elena Morales, is a fictional placeholder, and the portrait is an AI-generated image of a fictional person. In the app, you replace her with someone you actually remember.
Prize Categories
Best Use of Gemma: Gemma 3 (4B) runs locally through Ollama for grounded question answering, structured JSON extraction, vision, and the haunted-terminal persona.
Credits: React, Vite, Tailwind CSS, Motion, tsParticles, lottie-react, lucide-react, and the Cormorant Garamond and Inter fonts via Fontsource (see the in-app Credits panel and the README for licenses). Gemma by Google, served by Ollama.









Top comments (0)