Skip to content

Cipher

Cipher is a site for an escape room venue: escape rooms, puzzle venues, immersive game venues. It is drawn like a heist blueprint. The home page opens on your venue’s floor plan in 3D, on a deep blue blueprint with its walls raised. As you scroll, the camera goes to the way in, then to each room in turn, and the room’s floor lights up in its own colour (and stays lit), with a card beside it: the room’s name, its line, difficulty, players and price. At the end the whole venue is lit. Ultramarine, flamingo and mint on blueprint paper with a grid, stencil headings, panels like drawing sheets with crop marks, buttons like stamped tags. The demo is a fictional venue in Rotterdam, Lockwork, with five rooms.

Cipher comes in two variants, chosen when you create the site: files/Sanity (static pages, any host) and EmDash (--cms emdash, rendered on request on Cloudflare). The pages are the same in both.

The plan is drawn from data, so it can be any venue. Edit it in Pages → The floor plan:

  1. Set the venue’s width and height in metres.
  2. Add your areas as rectangles (x, y, w, h in metres, from the top-left corner). Link an area to a room by the room’s file name (e.g. the-vault): it takes the room’s colour, name and number. Leave it empty for the lobby, corridors, the lounge. Mark one area as the entrance. Keep areas edge to edge, so their walls line up.
  3. Add the doors: the middle of each door on a wall, its width, and whether that wall runs up and down. The walls are drawn with a gap there and a door swing.
  4. Add a few pieces of furniture (rectangles and circles) if you like: they make the plan read.

Rooms light up in the order of the areas. The same plan shows flat on the rooms page and on each room’s page, with that room lit. With reduced motion the plan stands lit and the cards are a list under it.

Page Edit it in
Home Pages → Home page (a list of sections; the floor plan’s texts are in its first section)
Rooms (a filter by difficulty, the rooms, the plan, the difficulty scale) Pages → Rooms page and Content → Rooms
A room (picture, difficulty, players, time, age, price, booking, story, close-up, the plan) Content → Rooms
Groups (what groups book, how it works, booking a date) Pages → Groups page
Gift vouchers (vouchers with checkout, how they work, the small print) Pages → Gift vouchers page
Journal Pages → Journal page and Content → Journal
Visit (hours, the map, safety, getting here, questions, the contact form) Pages → Visit page
Name, contact, navigation Pages → Site settings
The floor plan Pages → The floor plan

In headlines, words wrapped in *stars* sit on a flamingo highlighter band.

Each room has a tagline, summary, difficulty (1 to 5, shown as padlocks; the five names are in Interface texts → Rooms), players, time, age, the share of teams that get out (optional), a price and a note (e.g. “2 to 6 players, the same price”), a tall picture, a wide close-up, a colour and its number (order). Turn on “Book online” and add a Stripe Price id (or a Lemon Squeezy variant id), and the room’s button takes the team to checkout; after paying they get an email and come back to the rooms page with a message. Gift vouchers work the same way, each with its own price id. Groups book a call with Cal.com.

Everything works without keys; each feature switches on with its variables in .env.example.

Feature Where Service
Booking and gift vouchers Each room’s page, the vouchers page Stripe or Lemon Squeezy
Groups and parties Groups page, loads on click Cal.com
Newsletter Home and journal pages, with a welcome email Supabase or Mailchimp
Contact form Visit page; messages go to the email in Site settings Resend
AI chat Every page; answers from your rooms (never the solutions), groups, vouchers, visit details and journal Claude or OpenAI
Search ⌘K or the search button: rooms, vouchers, groups, journal, visit built in, or Algolia
Analytics Every page; Google Analytics only after consent Plausible, Umami, Google Analytics, PostHog
Members-only posts Journal Clerk

The home page is built from these sections, in any order: The floor plan (the signature), Rooms, How it works, Feature (e.g. groups), Numbers, Journal, Newsletter and a closing call over a photo from edge to edge.

src/content/singletons/brand.json (Pages → Brand in Keystatic) holds three colors: the accent (ultramarine: buttons, the grid, links, the night blueprint’s hue), the second (flamingo: rooms, the highlighter band, button shadows) and the third (mint: rooms and fills). Every room picks one of the three. Buttons are darkened until their white text reads; every color is adjusted for each theme, and one that can’t get there stops the build with a message. Brand also sets the fonts (text, stencilled headings, labels) and the corners. The site opens by day; night comes from the toggle.

The visit page shows a map of the streets around you, drawn from OpenStreetMap: set your entrance’s coordinates in map.config.json and run npm run map -- fetch.

Cipher is ready for more languages: see Languages.

A small game, Crack the code: set the dials of a combination lock and try the code. After each try the lock tells you how many symbols are right and in place, and how many are right but on another dial. Three locks: three dials of five symbols, then four of six, then four of eight. Each lock scores 100 × par ÷ your tries (at most 100), nothing if you run out. Tap a dial to turn it, or use the arrow keys and Enter. The best score is kept in the visitor’s browser.

  • AGENTS.md describes the project for coding assistants (Claude Code reads it through CLAUDE.md, Cursor through .cursor/rules/).
  • /llms.txt lists your pages and rooms for AI assistants, in every language.
  • Pages carry JSON-LD: the venue as an EntertainmentBusiness, rooms as Products with their price, vouchers as an OfferCatalog, groups as a Service, questions as a FAQPage, posts as BlogPostings.
Terminal window
pnpm dev # local site, editor at /keystatic
pnpm map # redraw the visit page's map (npm run map -- fetch)
pnpm build # production build
pnpm cms sanity schema|seed # move content to Sanity (see Content → Sanity)

In the EmDash variant: npm run dev, npm run deploy (Cloudflare) and npm run emdash:schema (after changing the content model).