Skip to content

Tiny Town

Tiny Town is a site for a place with many parts on one map: a new neighbourhood or regeneration project, a coworking campus, a market hall or shopping street, a town or village, a university or holiday park. It’s made like a building-block diorama: an isometric town, drawn from your own layout in your brand’s colours, builds itself as you scroll and flies to each district; places are pinned on the map; homes, workspace passes, what’s on, news and a visit page complete it. It runs from edge to edge of the window.

Tiny Town 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)
Places, homes, workspace, what’s on, news, visit, 404 Pages → the page’s own entry
The map’s districts Pages → The town
Places (with their pins) Content → Places
Homes Content → Homes
Events Content → What’s on
News posts Content → News
Name, description, email, address, navigation, header button, profiles Pages → Site settings
Buttons, labels, chat, search, emails, game texts Content → Interface texts
Privacy, terms Pages → Legal

In headlines, words wrapped in *stars* get a park-green highlight, as in “A whole *quarter*”.

The town is drawn by the template from src/data/town.ts: the baseboard’s size, water, green and paving areas, and every building as a block (position and size in tiles, height, a paint: brick, paper, canal, park or ink, rows of windows, a glass roof), with bridges, trees and boats. Change it to draw your own place; it’s coloured from your brand, and windows light up in the dark theme.

Pages → The town lists the districts, each with a key, a name and its position on the map in percent. The home page’s town section has chapters that each fly to a district; places and homes name their district too, and each place has a pin (percent of the map) for the places page and its own page. townPoint(x, y, z) in town.ts gives you the percentages of a tile.

On the home page the town builds itself block by block as you scroll, then the camera flies to each chapter’s district while its card scrolls past. Visitors who prefer reduced motion see the finished town.

  • Places have a kind (the places page filters by it), a summary, address, hours, district, pin, a square picture and a colour; featured ones come first on the home page.
  • Homes have a kind, size, price, status (available, reserved or sold), features and a picture; each page offers a viewing with Cal.com. The homes page also has register interest for new homes (a sign-up list in Supabase with a welcome email).
  • Passes (Pages → Workspace page): a pass with Sell with Stripe Checkout turned on and a one-time Price id gets a working button; customers come back to the workspace page and get an email from the Stripe webhook (/api/stripe/webhook), where you can add your own steps.

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

Feature Where Service
Passes Workspace page Stripe
Viewings Homes and home pages, loads on click Cal.com
Register interest Homes page, with a welcome email Supabase
Newsletter Home and news pages, with a welcome email Supabase
Contact form Visit page; messages go to the email in Site settings Resend
AI chat On every page, answers from your places, homes, workspace, events, visit page and news Claude
Search ⌘K or the search button: places, homes, events, news, workspace, visit built in, or Algolia
Analytics Every page; Google Analytics only after consent Plausible, Umami, Google Analytics

The home page is built from these sections, in any order: The town (builds itself, then flies to each chapter’s district), Places, Homes, What’s on, Numbers, Latest news, Newsletter sign-up and Closing call (a picture from edge to edge).

src/content/singletons/brand.json (Pages → Brand in Keystatic) holds three colors: the accent is brick (buttons, roofs, pins), the second the canal (water, cool fills), the third the park (grass, highlights, selected states). Buttons are darkened until their white text is readable, the second and third are lightened until dark text on them is; a color that can’t get there stops the build with a message. The paper, ink and the dusk theme are derived from them. Brand also sets the fonts (text, headings, and the mono font for labels and addresses) and the corners (round like toy blocks, or softer).

Tiny Town is ready for more languages: see Languages.

A small game, Deliver it: three addresses light up on a grid of streets; drive the delivery van corner by corner (arrow keys, the on-screen arrows, or tap the next corner) past all of them and back to the depot. The fewer streets you drive, the more points; five rounds, and 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/), including how to draw the map.
  • /llms.txt lists your pages, places, homes and news for AI assistants, in every language.
  • Pages carry JSON-LD: the quarter as a Place, places as LocalBusinesses, events, the questions on the visit page, and each post.
Terminal window
pnpm dev # local site, editor at /keystatic
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).