Skip to content

Stroll

Stroll is a site for guided walks in a real city: walking-tour companies, independent guides, tourist information centres and cities. Its maps are the real city, drawn from OpenStreetMap in an illustrated 3D style by a command in the template, and every walk’s route follows the real streets. On the home page the walk draws itself as you scroll, the camera flying from sight to sight. The demo walks through Prague. It runs from edge to edge of the window.

Stroll 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-3 km across works best.
  2. Run npm run map -- fetch. It downloads the area from OpenStreetMap (one request, up to a minute), builds the map’s geometry 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 dusk, saved as AVIF in every width the site uses (so building the site never re-encodes them).
  3. Give your sights their latitude and longitude, your walks their route and meeting point. The routes are found along the real streets when the site builds.

The map’s colours are in map.config.json → colors; after changing them, npm run map -- render redraws the maps without downloading again. camera sets how steeply the 3D view looks down, how tall buildings are drawn, and how many floors a building has when OpenStreetMap doesn’t know.

Map data is © OpenStreetMap contributors, under the Open Database License. Every map shows the attribution; keep it. See the template’s LICENSES.md.

Page Edit it in
Home Pages → Home page (a list of sections)
Walks, sights, guides, journal, visit, 404 Pages → the page’s own entry
Map labels (districts, the river) Pages → The map
Walks (route, meeting point, tickets) Content → Walks
Sights (with their latitude and longitude) Content → Sights
Guides Content → Guides
Journal posts Content → Journal
Name, description, city, 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* are underlined with a dotted gold route, as in “The whole of old Prague, *one street at a time*”.

  • A walk has a route: the sights in the order you visit them (by their file name), plus waypoints with a latitude and longitude where you want it to take a particular street. The site finds the way between them along the walkable streets, measures the distance and draws it on the maps. It also has a meeting point, duration, times, languages, group size, price, highlights, a picture and a colour.
  • Tickets: turn on “Sell tickets with Stripe Checkout” and put the one-time Price id of one ticket there; the walk’s page gets a working button. Walkers come back to the walks page, and the Stripe webhook (/api/stripe/webhook) emails them the meeting point.
  • Private walks are booked with Cal.com on the walks pages.
  • A sight has its kind (the sights page filters by it), a summary, latitude and longitude, when it was built, hours, entry and a picture. Sights are numbered on the maps by their order.

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

Feature Where Service
Tickets Each walk’s page Stripe
Private walks Walks pages, loads on click Cal.com
Newsletter Home and journal 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 walks (with their routes), sights, guides, visit page and journal Claude
Search ⌘K or the search button: walks, sights, guides, journal, 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 walk (the route draws itself as you scroll; pick which walk), Walks, Sights, Guides, Numbers, From the journal, 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 (buttons and pins), the second (the river: cool fills) and the third (gold: highlights and the walk’s line on the maps). 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. Brand also sets the fonts (text, headings, and the mono font for labels and distances) and the corners.

Stroll is ready for more languages: see Languages.

A small game, Postcard: a postcard of your city, cut from the 3D map around one of your sights, is cut into tiles and shuffled. Slide the tiles back into place (tap a tile next to the gap, or use the arrow keys). Fewer moves, more points; three postcards, the last one cut finer, 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 your city.
  • /llms.txt lists your pages, walks, sights and journal for AI assistants, in every language.
  • Pages carry JSON-LD: the company as a TravelAgency, walks as TouristTrips with their stops, sights as TouristAttractions with their coordinates, the questions on the visit page, and each post.
Terminal window
pnpm dev # local site, editor at /keystatic
pnpm map # draw your city (map.config.json)
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).