Skip to content

Lodge

Lodge is a site for a boutique hotel: a small hotel, a guesthouse, a B&B, a few apartments with a front desk. The home page opens on the reception, a picture that fills the window and that the camera walks up to as you scroll (in the evening, with the lamp lit, in the dark theme). The rooms hang as keys on the board: point at one, focus it or tap it, and its enamel tag turns over to show the room, its price and the button to book. The concierge’s picks around the hotel come as postcards, each with a stamp showing the minutes on foot from the door and a tip written by hand. Cobalt, saffron and pink on a faint pattern of old floor tiles, headings in a warm, round serif. The demo is a fictional hotel in El Born, Barcelona, Casa Safrà.

Lodge 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 template draws the map itself, so it can be any city:

  1. Set the area in map.config.json: area with its south, west, north and east edges in degrees (right-click on openstreetmap.org → “Show address” gives you a point’s latitude and longitude). About 2 km across works best, with the hotel near the middle.
  2. Run npm run map -- fetch. It downloads the area from OpenStreetMap (one request, up to a minute), builds the map’s geometry and the walkable streets in src/data/map/ and draws four base maps into public/map/: the city in 3D and as a plan, each by day and at night. A city on the sea is mapped only by its coastline; the map command closes it into water.
  3. Put the hotel in Pages → The map (building: its name, latitude and longitude). Give your places their latitude and longitude and your walks their route. The minutes on foot are measured along the real streets when the site builds, at 80 metres a minute, so they are never typed by hand.

Map data is © OpenStreetMap contributors, under the Open Database License. Every map shows the attribution; keep it.

Page Edit it in
Home Pages → Home page (a list of sections; the reception’s two pictures are in its first section)
Rooms (a filter by guests, the rooms with their price and features, booking, groups and events) Pages → Rooms page and Content → Rooms
Nearby (every place on the map, a filter by kind, the minutes from the hotel) Pages → Neighbourhood page and Content → Places
A place (what it is, the concierge’s tip, nearby on the map) Content → Places
Walks (every walk on the city) and a walk (on the plan, place by place with its minute) Pages → Walks page and Content → Walks
Journal Pages → Journal page and Content → Journal
Visit (the hotel and the nearest places on the plan, directions, check-in, the contact form, questions) Pages → Visit page
Name, city, contact, navigation Pages → Site settings
The hotel and district names on the map Pages → The map

In headlines, words wrapped in *stars* sit on a band of saffron, like a stroke of paint.

Each room has how many it sleeps, its size, bed, view, price and a note (e.g. “a night, breakfast included”), features, a few words and a picture. On the home page the rooms hang as keys, numbered in order; the rooms page lists them with a filter by guests. Turn on “Book online” and add a Stripe Price id (or a Lemon Squeezy variant id) for the deposit, and the room’s button takes guests to checkout; after paying they get an email and come back to the rooms page with a message. Groups and events book a call with Cal.com.

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

Feature Where Service
Booking (deposit) Rooms page and the keys on the home page Stripe or Lemon Squeezy
Groups and events Rooms and walks pages, 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, places (with their minutes), walks, visit details and journal Claude or OpenAI
Search ⌘K or the search button: rooms, places, walks, 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 reception (the signature: the camera walks up to the key wall as you scroll), Rooms (the keys on the board), Numbers, Nearby (the concierge’s postcards), Walks, 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 (cobalt: buttons, the key tags, pins and walks), the second (saffron: the rims of the tags, the band under marked words, the line under buttons, the stamps) and the third (pink: soft fills and tags). 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; the hand on the postcards is Caveat. The theme follows the system, day or night, unless the visitor picks one with the toggle; the pictures and maps switch with it.

Lodge is ready for more languages: see Languages.

A small game, Finish the tile: a Barcelona floor tile is half or a quarter painted, and you paint the rest so the pattern is whole. On the first tile the right half mirrors the left, on the second every quarter mirrors its neighbour, on the third every quarter is the painted one turned a quarter round. Tap a square to change its colour (or arrows, 1–3 for a colour and Enter to fire the tile); each tile scores 100 × the squares painted right ÷ the squares to paint. 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, walks and places for AI assistants, in every language.
  • Pages carry JSON-LD: the hotel as a Hotel with its HotelRooms and their prices, walks as TouristTrips, places as TouristAttractions, questions as a FAQPage, posts as BlogPostings.
Terminal window
pnpm dev # local site, editor at /keystatic
pnpm map # draw your city (npm run map -- fetch | build | render)
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).