Skip to content

Haven

Haven is a site for a boutique hotel: small hotels, guesthouses, B&Bs, country inns and small resorts. It is set by the sea at night: deep ink blue with the faint depth contours of an old sea chart, sea glass, candle gold and rose clay, an elegant serif, brass key fobs and a glass pill nav. The home page opens on one room, photographed from the same spot through a whole day as you scroll, with the rating, the headline, one line, Check availability and Call over it from the first frame. The demo is a fictional hotel, Marram House.

Haven 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.

Page Edit it in
Home Pages → Home page (a list of sections)
Rooms (every room as a card, what comes with every stay, questions) Pages → Rooms page
A room (a page each: size, beds, sleeps, view, features, what’s in the room, from price a night, two photos, availability, questions) Content → Rooms
Book (the booking request, availability, the deposit, policies, questions) Pages → Book page
Dining (the story, menus with prices, the bar, a table, allergies) Pages → Dining page
Spa and experiences (each with how long, price and when) Pages → Spa and experiences page
Offers and packages Pages → Offers page
Gift vouchers Pages → Gift vouchers page
Weddings and events (spaces, what’s included, the enquiry form) Pages → Weddings and events page
Journal Content → Journal (one page can be for returning guests only)
Visit (the map, getting here, hours, arriving, message form) Pages → Visit page
Questions (in groups) Pages → FAQ page
Name, phone, address, check-in, rating, hours, navigation, more pages, profiles Pages → Site settings
Buttons, labels, chat, emails, game texts Content → Interface texts
Privacy, terms, booking and cancellation Pages → Legal

In headlines, words wrapped in *stars* are set in italic over a tide line, a thin wave in your accent, as in “Wake up to the *sea*”.

The hero is the site’s signature. Four photos of the same room from exactly the same spot (Pages → Home page → Hero → Photos) are revealed one after another as you scroll while the screen stays put:

  1. Dawn: the linen curtains drawn, glowing pink before the sun is up. It is there from the first frame and is the page’s largest image, so it loads first.
  2. Morning: the curtains part from the middle behind two seams of sunlight; breakfast on the bed, dust drifting in the beams.
  3. Golden hour: the evening spreads out from where the sun sits over the sea, the water glittering.
  4. Night: the sky darkens from the top down, then the lamps come on one by one and the stars come out.

A clock in the caption card counts the hours and minutes from one moment to the next, with the temperature and the state of the sea (“06:12 · 11 °C · Sea calm” to “22:15 · 14 °C · Stars out”); write your own in the hero’s four moments. The later photos load after the page has loaded, so they never slow it down. With reduced motion, or in a browser without scroll-driven animations, the hero is one still: the room at night, with the four moments listed under it.

To use your own room, take four photos from a tripod (the window in the middle), or generate one and edit it into the other three so nothing moves. The positions of the lamps that bloom at night are in the lamps list at the top of src/components/sections/Hero.astro, in percent of the photo’s width and height, and the sun’s position in the evening mask (73% 30%).

Each room has a page with its key fob (the room number), size, beds, sleeps, view, short features such as “Step-free” or “Dogs welcome”, what’s in the room, the from price a night, two photos and questions; search engines get it as a schema.org HotelRoom with an offer. Request this room opens the booking request with the room filled in; Check availability shows the calendar.

Booking works by request, as most small hotels prefer: the guest sends their dates, nights, guests, a room and an offer or voucher; the request is emailed to you and you reply with the price. Availability comes from Cal.com (it loads on click; until it’s connected the page shows a preview of the calendar). The deposit that holds a room is paid with Stripe (a one-time Price id) or Lemon Squeezy (a variant id) in Pages → Book page → Deposit. The site never confirms a booking by itself.

Dining, spa, offers, vouchers and weddings

Section titled “Dining, spa, offers, vouchers and weddings”

Menus are lists of courses with dishes and prices, set like a printed menu with dotted leaders. Each offer has a price on a key fob, for whom, when and what’s included; its button opens the booking request with the offer named. Gift vouchers are one-time payments (a Stripe Price id or Lemon Squeezy variant id on each voucher). The weddings page has the spaces with their capacities and an enquiry form that is emailed to you.

The visit page and the home section “Getting here” show a map of the streets round the hotel, drawn from OpenStreetMap in your colors, with the sea (from the coastline), a pin, your address and directions links. It is part of the page (no embed, no cookies) and follows the theme and your accent. To draw your own, put your coordinates in map.config.json and run pnpm map -- fetch.

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

Feature Where Service
Availability Book page and every room, loads on click; a preview of the calendar until connected Cal.com
Deposit and gift vouchers Book page (deposit) and vouchers page, one-time Stripe or Lemon Squeezy
Letters by email Home and journal pages, with a welcome email Supabase or Mailchimp
Booking requests, event enquiries, messages Book, weddings and visit pages; to the email in Site settings Resend
AI chat Every page; answers about rooms, prices from, dining, the spa, offers and getting here; never says a room is free Claude or OpenAI
Returning guests’ page Journal Clerk
Search ⌘K or the search button: rooms, pages, journal built in, or Algolia
Analytics Every page; Google Analytics only after consent Plausible, Umami, Google Analytics, PostHog
Photos Production builds Cloudinary (optional)

The home page is built from these sections, in any order: Hero, Rooms (cards whose photos open like doors), Numbers (beside a tide gauge, the sea rising as it scrolls in), A photo and a story (used for dining and the spa: the photo drifts like a camera on a slider and warms to lamplight), Offers, Journal, Letters (a rose clay band), Getting here (the map) and Closing call (the lit front door beside the headline, dark in both themes).

src/content/singletons/brand.json (Pages → Brand in Keystatic) holds three colors: the accent (sea glass: buttons, marks, tide lines), the second (candle gold: stars, small capitals, the clock and the glow of the lamps) and the third (rose clay: the favourite offer, the returning guests badge, the letters band). Buttons and rose clay are darkened until their white text is readable, candle gold is adjusted for each theme; a color that can’t get there stops the build with a message. Brand also sets the fonts and the corners. The site opens in its dark theme even when the visitor’s system is light; the toggle switches to the day (linen and ink) and the browser remembers it.

Haven is ready for more languages: see Languages.

A small game, Full house: tonight every room in the house is taken and every party at the desk wants something: a sea view, no stairs, a dog with them, beds for four, a quiet room, to be next to friends or well away from someone. Tap a guest, then a room (or Tab and Enter), then hand out the keys: unhappy guests show what went wrong, and every check that fails costs 20 points. Every night can be done; three nights, 4, 6 and 8 rooms; 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, rooms and the journal for AI assistants, in every language.
  • Pages carry JSON-LD: the hotel as a Hotel (address, position, check-in and check-out, price range, hours, rating), rooms as HotelRooms with offers, the restaurant with its menus, offers, vouchers and the deposit as offers, questions as a FAQPage, and each journal post as a BlogPosting.
Terminal window
pnpm dev # local site, editor at /keystatic
pnpm build # production build
pnpm map -- fetch # draw the map round map.config.json from OpenStreetMap
pnpm cms sanity schema|seed # move content to Sanity (see Content → Sanity)