A love letter to Ultima IV: Quest of the Avatar's character creation — the wagon, the Gypsy, the cards of virtue, the abacus of stones.
Live: https://ultimaiv.pages.dev/
A single-page recreation of the 7-question virtue bracket, in original art and voice. Eight tarot cards laid on a velvet table; an otherworldly voice weighs your soul against itself; one stone stays lit while the others go dark; the chosen virtue is named, and the wagon fades. Then the deck opens so you can read about every virtue you didn't become.
Not the game. Just the ceremony.
- https://ultimaiv.pages.dev/ — full ceremony. Enter the wagon, hear the Gypsy's greeting, weigh seven dilemmas, watch your stones go dark one by one, see the chosen card revealed and named.
- https://ultimaiv.pages.dev/cards/ — skip the ceremony, just flip through the deck. The intro screen also has a small "skip ahead" link.
The end of the ceremony lands you in the same deck browser, with your chosen virtue selected, a permalink to your reading, and a way back to the wagon door if you want to begin again.
.
├── index.html single-file prototype (open via local server)
├── _redirects Cloudflare Pages routing for /cards
├── seed/
│ ├── virtues.json canonical bundle for each of the 8 virtues
│ ├── script.json the Gypsy's fixed lines (locked)
│ ├── parables.json 28 short medieval-parable dilemmas, one per pairing
│ ├── cards.tsv art-prompt seeds for the 8 tarot cards
│ └── gypsy_script.md authoring notes for the script
├── cards/
│ ├── v1/ first-pass deck (archive)
│ ├── v2/ current deck — virtue + mantra + stone + town
│ └── v2_fix/ targeted re-rolls of Honor + Compassion
├── scenes/
│ ├── wagon_interior.jpeg
│ ├── card_back.jpeg
│ └── gypsy.jpeg
├── audio/
│ ├── gypsy/ 16 fixed Gypsy lines (entry, transitions, 8 closings, whisper)
│ └── parables/ 28 parable scenes, one per virtue pairing
└── scripts/
├── gen_cards.sh parallel xAI Grok render of the deck from seed/cards.tsv
├── gen_scene.sh one-off scene image
├── gen_voice.sh ElevenLabs render of every line in seed/script.json
├── gen_parables.sh ElevenLabs render of every scene in seed/parables.json
└── deploy.sh Cloudflare Pages deploy (stages dist/, then wrangler)
- Front-end: vanilla HTML / CSS / JS, single self-contained file
- Hosting: Cloudflare Pages (free tier)
- Art: xAI Grok image generation (~$0.10/card, $0.80 for a full deck)
- Voice: ElevenLabs — voice ID set via
ELEVENLABS_VOICE_GYPSYin.env - Future: D1 for permalink/session storage and live LLM parables
cp .env.example .env # fill in your own keys
python3 -m http.server 8765
open http://localhost:8765file:// doesn't work for the audio (browser security); use any local
server. The /cards/ subpath is only set up by the deploy step — locally,
hit / and use the on-screen skip link or invoke openCardBrowser(0) from
the console to test the deck view.
./scripts/gen_cards.sh v3 # full deck → cards/v3/
./scripts/gen_cards.sh v3 honor valor # just those two
./scripts/gen_scene.sh scenes/foo.jpeg "prompt..."If you re-roll into a new versioned folder (v3, v4…), update the card:
paths in DATA.virtues inside index.html so the browser picks up the new
art. The deploy script copies whichever directories live under cards/.
./scripts/gen_voice.sh # 16 Gypsy lines
./scripts/gen_parables.sh # 28 parable scenes
./scripts/gen_voice.sh <ELEVENLABS_VOICE_ID> # swap voice for the Gypsy
./scripts/gen_parables.sh <ELEVENLABS_VOICE_ID> # match voice for parablesBoth scripts refuse to clobber existing files; delete or move the target mp3s before re-rolling. Settings are tuned for ceremonial pacing (low stability, high similarity, mid style).
./scripts/deploy.shRequires CLOUDFLARE_API_TOKEN, CLOUDFLARE_ACCOUNT_ID, and
PAGES_PROJECT in .env. The token needs Cloudflare Pages → Edit
permission for the account. The script stages dist/, copies
index.html to dist/cards/index.html (so /cards/ works alongside the
existing cards/ image directory), then runs wrangler pages deploy.
| Virtue | Principles | Class | Mantra | Stone | Town | Companion |
|---|---|---|---|---|---|---|
| Honesty | Truth | Mage | AHM | sapphire | Moonglow | Mariah |
| Compassion | Love | Bard | MU | yellow | Britain | Iolo |
| Valor | Courage | Fighter | RA | red | Jhelom | Geoffrey |
| Justice | Truth + Love | Druid | BEH | emerald | Yew | Jaana |
| Sacrifice | Love + Courage | Tinker | CAH | amber | Minoc | Julia |
| Honor | Courage + Truth | Paladin | SUMM | amethyst | Trinsic | Dupre |
| Spirituality | all three | Ranger | OM | white diamond | Skara Brae | Shamino |
| Humility | Void (absence of Pride) | Shepherd | LUM | black | New Magincia | Katrina |
Each card in the deck browser also carries an original prose description and four short suggestions for cultivating that virtue, in the same archaic register as the Gypsy's voice.
Original art, voice, prose, and code in this repository are mine. The Ultima setting, virtues, mantras, town names, and Britannian iconography belong to their respective rights holders; this work is a fan tribute, not affiliated with or endorsed by anyone associated with the Ultima series.