Locale
Locale is a site for a new residential building: a developer’s project, an estate agent’s new release, a housing co-operative. The home page opens on the building’s front, a photograph with every flat in the release lit in its own windows by its status: free flats glow warm, as if someone were home, reserved ones in lilac, sold ones stay dark. Point at a flat and its card appears; click it and you are at its card on the apartments page, with a floor plan drawn from its rooms and area. Around the building, your neighbourhood is drawn from OpenStreetMap in a vivid 3D style, and every school, park, market and stop shows its minutes on foot from the front door, measured along the streets. Emerald, tangerine and lilac on a cream site plan, headings in a high-contrast serif. The demo is a fictional building of 48 flats in Vienna’s Leopoldstadt, Kastanienhof.
Locale 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 neighbourhood
Section titled “Your neighbourhood”The template draws the map itself, so it can be any city:
- Set the area in
map.config.json:areawith 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 your building near the middle. - 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 insrc/data/map/and draws four base maps intopublic/map/: the city in 3D and as a plan, each by day and at dusk, saved as AVIF in every width the site uses. - Put your building in Pages → The map (
building: its name, latitude and longitude). Give your places their latitude and longitude and your walks their route (the places in order, plus waypoints to steer it). 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.
The map’s colours are in map.config.json → colors; after changing them, npm run map -- render
redraws the maps without downloading again. Map data is © OpenStreetMap contributors, under the Open
Database License. Every map shows the attribution; keep it.
Pages and their content
Section titled “Pages and their content”| Page | Edit it in |
|---|---|
| Home | Pages → Home page (a list of sections) |
| Apartments (the lit front, a filter by rooms, the flats with their status, price and floor plan, reservations, viewings) | Pages → Apartments page and Content → Apartments |
| Neighbourhood (every place on the map, a filter by kind, the minutes from home) | Pages → Neighbourhood page and Content → Places |
| A place (what it is, what it means for the people who live here, 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 building and the nearest stops on the plan, directions, details, the contact form, questions) | Pages → Visit page |
| Name, city, contact, navigation | Pages → Site settings |
| Your building, district names on the map | Pages → The map |
In headlines, words wrapped in *stars* are set in emerald on a tangerine underline.
The building front
Section titled “The building front”The front pictures (by day, and at dusk for the dark theme, same size and framing) are in Pages → The
map → Your building. src/data/front.json says where their windows are, floor by floor, in percent of
the picture; each apartment names its floor and its first and last window (Content → Apartments → On the
building front). With your own pictures, measure their windows once and the flats light up in the right
places.
Apartments and reservations
Section titled “Apartments and reservations”Each apartment has its rooms, area, outdoor space, floor, where it faces, price, status (available, reserved or sold), a few words and a picture. The apartments page filters them by rooms without JavaScript. Turn on “Reserve online” on an available apartment and add a Stripe Price id (or a Lemon Squeezy variant id) for the reservation fee, and its button takes buyers to checkout; after paying they get an email and come back to the apartments page with a message. Viewings of the show flat are booked with Cal.com.
Integrations
Section titled “Integrations”Everything works without keys; each feature switches on with its variables in .env.example.
| Feature | Where | Service |
|---|---|---|
| Reservations | Apartments page | Stripe or Lemon Squeezy |
| Viewings | Apartments and walks pages, loads on click | Cal.com |
| Newsletter (new homes and building news) | 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 apartments, places (with their minutes), walks, visit details and journal | Claude or OpenAI |
| Search | ⌘K or the search button: apartments, 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 |
Sections
Section titled “Sections”The home page is built from these sections, in any order: The building (the signature: the front with every flat lit by its status), Apartments (the free ones first), Numbers, Around the corner (places as cards with their minutes), Walks, 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
(emerald: buttons, pins and the walks), the second (tangerine: marked words, the band on panels and
your building on the map, free flats’ windows) and the third (lilac: soft fills, reserved flats). 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 theme follows the system, day or evening, unless the visitor picks one with the toggle;
the maps switch with it.
Languages
Section titled “Languages”Locale is ready for more languages: see Languages.
The 404 page
Section titled “The 404 page”A small game, Best address: a 7 × 7 plot with blocks of houses, empty lots and a few places (a school, the tram, a market, a park, a café), each visited so many times a week. Build your home on the lot where the walking adds up to the fewest minutes a week (tap a lot, or arrows and Enter); streets go round the blocks. The best lot is found by measuring every lot from every place, and you score 100 × the best ÷ yours. Three plots, each with more places. 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, walks and places for AI assistants, in every language.- Pages carry JSON-LD: the building as an
ApartmentComplexwith itsApartments and their prices and availability, the sales office as aRealEstateAgent, walks asTouristTrips, places asTouristAttractions, questions as aFAQPage, posts asBlogPostings.
Commands
Section titled “Commands”pnpm dev # local site, editor at /keystaticpnpm map # draw your neighbourhood (npm run map -- fetch | build | render)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).