Skip to content

Sheen

Sheen is a site for a house cleaning business: regular cleaning, deep cleans, move-in and move-out, cleaning after builders, rental turnovers and extras. It is light and calm, built from one picture: low morning sun raking across a polished oak floor in a white limewashed room. The home page opens on that room across the whole screen, a linen curtain breathing in a short film, with the phone number and a price button right on it. Behind every page soft patches of window light drift as you scroll; the colours are olive, butter sunlight and lavender on warm linen. The demo is a fictional business, Wren & Linen Home Cleaning.

Sheen 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)
Services, before & after, guides, 404 Pages → the page’s own entry
Prices (standard visit, packages, how a visit works, booking, deposits and gift cards, questions) Pages → Prices page
Contact (price form, towns, map, hours, details, questions) Pages → Contact page
Services (icon, badge colour, price from, how long, what’s included, who it’s good for, questions, photo) Content → Services
Before & after (where, when, what you found, team, time, cost, the two photos, story) Content → Before & after
Guides (one can be for members only) Content → Guides
Name, phone, address, insurance, navigation, header button, profiles Pages → Site settings
Buttons, labels, chat, emails, game texts Content → Interface texts
Privacy, terms, guarantee Pages → Legal

In headlines, words wrapped in *stars* are set in the accent with a slanted band of sunlight that slides in behind them as they scroll into view, as in “Clean enough to *catch the light*.”

The home page’s first section fills the whole screen on every device. Its texts are yours: the status line (“Now booking Monday to Saturday”), the small line, the headline, the lead, two buttons (make the first one your phone number as a tel: link and it gets a phone icon) and up to three facts under them on wider screens. The picture is one or two files:

  • Photo: 16:9 and at least 1920 pixels wide, light and calm in the middle where the text sits. It shows at once and is what search engines and slow phones see. Set Photo focus to the part that should stay in view on a narrow screen (e.g. 38% 60%).
  • Film (optional): a short silent MP4 loop of the same view in public/media/ (1–2 MB, 1280 wide), e.g. /media/hero.mp4. It starts loading only after the page has, fades in over the photo and pauses when you scroll past. Leave it empty to show the photo only. Visitors who prefer reduced motion or save data see the photo.

The text sits on a soft veil of linen white that keeps it readable even over a black frame, so the opening stays light in both themes. On load, a squeegee stroke wipes a faint haze off the photo once.

Each story has two photos from the same spot, before you started and when you were done. Keep the camera still between them (a tripod or a mark on the floor) so they line up. On the story’s page, and for the newest story on the home page, the two are laid over each other with a squeegee blade between them: drag it, or use the arrow keys, to compare. As the photo scrolls into view the blade sweeps in to the middle. The other stories show as cards with the “before” tucked into a corner.

Each service has a round badge in one of the three colours: olive, butter or lavender. Service photos are wiped clean of a faint haze as they scroll into view and catch a glint of light when you point at them.

The prices page holds your standard visit on a slanted patch of sunlight with its small print (extra rooms, regular visits, what’s included), up to four packages (mark one as the popular one), how a visit works as a checklist whose ticks draw themselves (also on the home page), booking a free walk-through with Cal.com, and deposits and gift cards you take with Stripe or Lemon Squeezy: each one gets a one-time price id. Without keys, the booking shows a preview of the calendar and the payment buttons explain how to switch them on.

The map is drawn from OpenStreetMap in your colours, with a pin on your office and a ring for the area you cover. It is part of the page (no embed, no cookies) and follows the theme and your accent. To draw your own, put your office’s coordinates in map.config.json and run pnpm map -- fetch. detail: "area" (the default in Sheen) draws the main roads, railways, water and woods of a whole district, and Contact page → Map → Radius sets the ring; detail: "streets" draws the streets and buildings round one address instead.

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

Feature Where Service
Book a walk-through Prices page, loads on click; a preview of the calendar until connected Cal.com
Deposits and gift cards Prices page (one-time prices) Stripe or Lemon Squeezy
Cleaning notes by email Home and guides pages, with a welcome email Supabase or Mailchimp
Members-only guides A guide marked members only shows its text after signing in Clerk
Price form Contact page; messages go to the email in Site settings Resend
AI chat On every page, answers about services, prices, before & after and areas; never advises mixing cleaning products Claude or OpenAI
Search ⌘K or the search button: services, before & after, guides, prices, contact built in, or Algolia
Analytics Every page; Google Analytics only after consent Plausible, Umami, PostHog, Google Analytics
Photos Served from your Cloudinary account when it is set Cloudinary

The home page is built from these sections, in any order: Opening (the photo and film), Services (cards with a round badge), Before & after (the newest story to drag and compare, then cards), How a visit works (a checklist that ticks itself), Numbers (set large with a band of sun), The area on a map, Guides, Cleaning notes by email (an olive-black panel) and Closing call (an olive-black band with window light and a photo in a white mount). With reduced motion nothing moves.

src/content/singletons/brand.json (Pages → Brand in Keystatic) holds three colors: the accent (olive: buttons, links, marked words, the ticks and the footer’s band), the second (butter sunlight: the light behind marked words and numbers, the sun patches, fills with dark text) and the third (lavender: chips and small fills with dark text, and the moonlight of the dark theme). The accent is darkened until white text on it is readable, butter and lavender are lightened until dark text on them is; a color that can’t get there stops the build with a message. Brand also sets the fonts and the corners (rounded with pill buttons, or nearly square). The site opens on the linen day theme even when the visitor’s system is dark; the toggle switches to dusk, and the browser remembers the choice. The opening and the olive-black bands look the same in both.

Sheen is ready for more languages: see Languages.

A small game, Wet floor: a room seen from above, furniture on some of the tiles and the door in one wall. Mop every tile, one step at a time (the arrow keys or WASD, a swipe, a tap on the tile next to you, or the on-screen arrows), and leave through the door last. A mopped tile is wet and can’t be stepped on again, so plan the way round: stuck with dry tiles left, you’ve mopped yourself into a corner and the room starts over. Every room is checked to have a way through. Three rooms, bigger each time, 100 points each less 25 per restart; 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, services, before & after stories and guides for AI assistants, in every language.
  • Pages carry JSON-LD: the business as a HouseCleaningService with the areas it serves and its packages, services as Services with their price, before & after stories as CreativeWorks, how a visit works as a HowTo, questions as a FAQPage, and each guide 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)

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