Solstice Vigil: a solo RPG narrated by Gemma 4 in your browser

At the June solstice, the sun stopped setting. You are the wanderer trying to keep day and night from tipping over completely.
That is the premise of SOLSTICE VIGIL, a solo narrative RPG I built for the DEV June Solstice Game Jam. Each choice moves a balance meter between the Long Day and the Hush of Night. Push too far and the vigil ends. There is no boss fight — just a count of how many days you held the wheel, what you became along the way, and which strange things you stumbled into.
I wanted it to feel like an old manuscript you could actually play: mythic, a little lonely, not very chatty.
Play it → · Source on GitHub →
What you can do
- Balance day and night. The meter drives phase, mood, and how close you are to tipping over.
- Get scenes narrated on your own device. Gemma 4 (E2B) runs in Chrome through Google AI Edge LiteRT-LM and WebGPU. No server, no API key, nothing leaving the machine.
- Earn identities instead of picking a class. Titles like Ember Saint or Moon Herald show up after your choices pile up.
- Find rare encounters. Fifteen of them, with a codex, eligibility rules, and cards worth sharing.
- Roll a d20 on bold choices. Sometimes the solstice pushes back.
- Turn on speech narration if you want the scene read aloud. Music ducks while it plays.
- Try demo mode if you do not want to download the ~2 GB model. The full loop works with hand-written scenes.
Demo mode is on the title screen, via ?demo=1, or from the link on the loading screen.
The split that makes it work
The diagram is the important part. JavaScript owns the game: balance, endings, identities, encounters, dice. Gemma gets structured context and returns JSON. It narrates; it does not decide outcomes.

That split is why on-device generation feels playable past the first few scenes instead of falling apart. If the model were also deciding whether you tipped into eternal day or earned a rare encounter, coherence would fall apart fast. Plain JavaScript handles the rules; the model handles the voice.
Places to start reading in the repo:
src/components/game/SolsticeVigil.tsx— game loop, on-device LLM, UI statessrc/lib/prompt.ts— narrator prompt and turn contextsrc/lib/identity.ts/src/data/identities.ts— inferred wanderer titlessrc/lib/encounters.ts/src/data/encounters.ts— rare wonderssrc/lib/dice.ts— d20 resolutiontests/— Playwright unit + E2E (demo mode for CI)
How I built it
On-device Gemma 4
I built this because I wanted to try Gemma 4, and Chrome’s on-device LLM path made that possible without standing up a backend.
The game loads @litert-lm/core from a CDN, pulls the Gemma 4 E2B .litertlm file from Hugging Face, caches it with the Cache API, and streams scene JSON over WebGPU. Save state lives in localStorage. The model narrates; JavaScript decides.
The shell and the manuscript UI
Astro + React was the shell. Static delivery, React island for the game, room to grow if the vigil ever becomes more than one page.
View Transitions handle screen and scene changes. Phase flips and identity reveals feel less like hard cuts that way.
I also went looking for newer CSS worth using. border-shape gives the notched manuscript frames; clip-path covers browsers that do not have it yet. The design spec in the repo (docs/design.md) describes the mood: dark mythic fantasy, gold daylight against cold blue shadow, ruined stone and ritual stillness.
On top of that: Web Speech for optional narration, Gemini for the two soundtrack pieces (The Wheel of Sediment, Vigil of the Still Valley) — same toolchain as The SDLC Song Cycle — and Playwright + TDD because agent-written code looks fine until you actually click through it.
Building mostly from my phone
Most of this was built from my phone. I am a full-time dad, so “can I keep working while away from the desk” was not a bonus constraint. It was the whole point.
I started with a version of my grill-me skill in ChatGPT. What is the loop? Why would anyone share a run? Why solstice, specifically? That argument became the PRD.
Then I moved to Zo Computer and got the first playable prototype working away from my desk: balance meter, phase flip, on-device Gemma, local save. The production app came after that proof.
For visuals I used Google Stitch and ChatGPT to try directions fast. Dense or spacious? Dashboard or manuscript? Gold day or blue night? The spec in docs/design.md is what survived that round.
Desktop Cursor was the one part I could not do on my phone. Once the direction was clear, I ran several implementation plans in parallel. Same shape as the workflow I wrote up in My Current AI Workflow for Building Apps: argue the idea first, prototype early, design before you let agents run loose, ship in small slices, test the behavior for real.
Demo walkthrough
The desktop demo covers the premise, on-device Gemma loading, an identity reveal, a rare encounter, a d20 roll, and demo mode.
Catch up with me on X (twitter):@juan_allo
Share
---
Similar Articles
The SDLC Song Cycle: AI music about shipping software
Learnings from Using Spec Kit for Vibe Coding
7 days of JS by ChatGPT