Caldera
Caldera is a site for a thermal bath or spa: thermal baths, bathhouses, saunas, wellness centres. It is made like an Art Nouveau bathhouse. The home page opens on the baths’ floor plan in 3D on a turquoise mosaic: cream tiled floors, white walls raised. The pools start empty and the saunas cold. As you scroll, the camera goes to the way in, then to each bath in turn, and the bath comes alive: its pools fill with turquoise water, or the room glows warm, with a card beside it (its name, temperature and hours). At the end every bath is full. Turquoise, gold and terracotta on cream tiles, photos under arches like bathhouse windows, headings in a Secession display face. The demo is a fictional bath under a glass dome in Budapest, Opal Baths, with six baths.
Caldera 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.
Your floor plan
Section titled “Your floor plan”The plan is drawn from data, so it can be any bath. Edit it in Pages → The floor plan:
- Set the
widthandheightin metres. - Add your areas as rectangles (
x,y,w,hin metres, from the top-left corner). Link an area to a bath by the bath’s file name (e.g.hot-spring): it takes the bath’s colour, name and number. Leave it empty for the entrance, changing rooms, the café. Mark one area as the entrance. Keep areas edge to edge, so their walls line up. - Add the doors: the middle of each door on a wall, its width, and whether that wall runs up and down.
- Add the pools and furniture as rectangles and circles. Mark pools as
pool: they show empty until their bath fills, then fill with water.
A bath that doesn’t hold water (a sauna, a steam room: turn off water on the bath) glows warm
instead. Baths fill in the order of the areas. The same plan shows flat on the baths page and on each
bath’s page, with that bath full. With reduced motion every bath stands full and the cards are a list
under the plan.
Pages and their content
Section titled “Pages and their content”| Page | Edit it in |
|---|---|
| Home | Pages → Home page (a list of sections; the floor plan’s texts are in its first section) |
| Baths (a filter by kind, the baths, the plan, the bathing rules) | Pages → Baths page and Content → Baths |
| A bath (picture, kind, temperature, hours, good to know, story, close-up, the plan) | Content → Baths |
| Passes (day passes and gift cards with checkout, how they work) | Pages → Passes page |
| Treatments (treatments and prices, booking a time) | Pages → Treatments 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* are set in turquoise over a gold ripple.
Passes and treatments
Section titled “Passes and treatments”Each pass has a name, a price, a few words, a colour and a checkout. Turn on “Book online” and add a Stripe Price id (or a Lemon Squeezy variant id), and the button takes visitors to checkout; after paying they get an email to show at the turnstile and come back to the passes page with a message. Treatments list their prices and book a time with Cal.com.
Integrations
Section titled “Integrations”Everything works without keys; each feature switches on with its variables in .env.example.
| Feature | Where | Service |
|---|---|---|
| Passes and gift cards | Passes page | Stripe or Lemon Squeezy |
| Treatments | Treatments 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 baths, passes, treatments, visit details and journal | Claude or OpenAI |
| Search | ⌘K or the search button: baths, passes, treatments, journal, visit | built in, or Algolia |
| Analytics | Every page; Google Analytics only after consent | Plausible, Umami, Google Analytics, PostHog |
| Members-only posts | Journal | Clerk |
Sections
Section titled “Sections”The home page is built from these sections, in any order: The floor plan (the signature), Baths, How a visit works, Feature (e.g. treatments), Numbers, Journal, Newsletter and a closing call over a photo from edge to edge.
Colors and brand
Section titled “Colors and brand”src/content/singletons/brand.json (Pages → Brand in Keystatic) holds three colors: the accent
(turquoise: buttons, water, links, the night’s hue), the second (gold: the rims, the ripple under
marked words, warm rooms) and the third (terracotta: saunas and fills). Every bath 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, headings,
labels) and the corners (soft or arched). 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.
Languages
Section titled “Languages”Caldera is ready for more languages: see Languages.
The 404 page
Section titled “The 404 page”A small game, Just right: the attendants’ old puzzle. With two or three jugs that hold only so many litres, measure exactly the litres the pool needs: fill a jug from the spring, empty it, or pour one into another until it is full. Three pools, each harder. The fewest moves is worked out for every pool, and each scores 100 × fewest ÷ yours. The best score is kept in the visitor’s browser.
AI assistants and search engines
Section titled “AI assistants and search engines”AGENTS.mddescribes the project for coding assistants (Claude Code reads it throughCLAUDE.md, Cursor through.cursor/rules/)./llms.txtlists your pages and baths for AI assistants, in every language.- Pages carry JSON-LD: the baths as a
PublicBath, each bath as aTouristAttraction, passes as anOfferCatalog, treatments as aService, questions as aFAQPage, posts asBlogPostings.
Commands
Section titled “Commands”pnpm dev # local site, editor at /keystaticpnpm map # redraw the visit page's map (npm run map -- fetch)pnpm build # production buildpnpm 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).
