Skip to content

Blaze

Blaze is a site for a long-distance trail: trail clubs, hiking guides and trekking outfits, hut-to-hut routes, pilgrim ways, ultra races, anyone who explains one route stage by stage. It is built like a topographic map: real contour lines behind every page, a display serif, and the trail mark (a red stripe between two white ones) as the eyebrow, the bullet and the underline of marked words. On the home page the whole trail is drawn from OpenStreetMap over real terrain relief, with its elevation profile underneath; every stage has a zoomed-in map, its profile and numbers measured from the map. The demo is a fictional field guide, Trailmark, for a real trail: the Heroes’ Trail of the SNP across Slovakia (Cesta hrdinov SNP), from Devín Castle to the Dukla Pass, in English and Slovak.

Blaze 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)
Stages, guides, journal, 404 Pages → the page’s own entry
Guided trips (the trips, how a week goes, booking, passport and deposits, questions) Pages → Trips page
Plan (the months, trail marks, details, packing list, contact form, questions) Pages → Plan page
Stages (from, to, kilometres, range, difficulty, on the way, water, where to sleep, description) Content → Stages
Guides (role, home ground, qualifications, languages, biography, portrait) Content → Guides
Journal posts Content → Journal
Name, the trail’s name, contact, navigation, header button, profiles Pages → Site settings
Buttons, labels, map texts, chat, emails, game texts Content → Interface texts
Privacy, terms, safety Pages → Legal

In headlines, words wrapped in *stars* are underlined with the trail mark, as in “From Devín to *Dukla*”. A line break in the home page’s headline starts a new line.

The map is drawn once, on your computer, by pnpm map, from what map.config.json names:

  • trail.relation: the trail’s OpenStreetMap route relation. Find the trail on openstreetmap.org, open the route and copy its relation number.
  • trail.start: a point near the start, so the line runs the right way.
  • area.relation: a border to draw round it (a country or a region, also a relation), or area.bbox (west, east, south, north) for a plain rectangle.
  • terrain: the zoom of the relief, how much it exaggerates the hills, how soft it is.

pnpm map -- fetch downloads the trail, the border and open terrain tiles (SRTM, EU-DEM and others) into .map-cache/, chains the trail’s pieces into one line, measures it, and writes src/data/trail.json (the trail, the outline, a point per kilometre with its height, the elevation profile) and the relief in public/map/: two masks the site paints in your colors (so the night and day maps and every accent use the same files), coarse tiles for the whole area, sharp tiles along the trail for the stage maps, and the contour lines behind the pages. -- terrain downloads only the terrain again, -- build and -- render redo the steps without downloading. The maps credit OpenStreetMap and the terrain data; keep the credits.

On the home page the trail draws itself, the relief fades in after the page has loaded and the map tilts a little with the mouse. Point at the trail or the profile and a hiker goes to that kilometre, with its height and stage; a click or tap pins it with a link to the stage, and the arrow keys walk it (Shift for 10 km). On phones the map is wider than the screen and scrolls sideways. The names of ranges and places on the map are content (Home → The map → Names on the map), placed by coordinates.

A stage has a number, where it starts and ends (names, and kilometres from the start of the trail: point at the trail on the home page to read them), its range, a difficulty, a summary, what is on the way, water and where to sleep, and a description. Distance, climbing, descent, the highest point and the walking time are measured from the map between the two kilometres, so they always match it; the walking time uses the standard hiking formula (4 km an hour, 300 m up and 500 m down an hour). Stages are listed by range on the stages page, each card with its profile; a stage’s page shows it zoomed in on the relief, the rest of the trail faint, with its profile and the previous and next stages.

The trips page holds up to four trips, each with its stretch of the trail (fromKm, toKm) drawn on its card (mark one as the favourite), how a guided week goes, booking a planning call with Cal.com and what you sell with Stripe (the demo: a trail passport, a map set and a deposit), each with 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.

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

Feature Where Service
Book a planning call Trips page, loads on click; a preview of the calendar until connected Cal.com
Passport, maps, deposits Trips page (one-time Price ids) Stripe
Newsletter Home and journal pages, with a welcome email Supabase
Contact form Plan page; messages go to the email in Site settings Resend
AI chat On every page, answers about the stages (with their numbers), planning, trips and guides; never promises that a section is safe or open Claude
Search ⌘K or the search button: stages, guides, journal, trips, plan, in the page’s language 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 map (the opening), The story (why the trail has its name: a few paragraphs beside a timeline), The climb (the whole profile, walked by a hiker as you scroll, with marked places), Stages (cards with their profiles), Trail marks (big words between trail marks, sliding as you scroll), Three steps (along a dashed trail), The guides, Numbers, Journal, Newsletter and Closing call (a wide photo whose top edge is cut along the trail’s real profile). With reduced motion the trail stands drawn, the profile complete and nothing moves.

src/content/singletons/brand.json (Pages → Brand in Keystatic) holds three colors: the accent (the trail mark: buttons, the trail on every map, marks), the second (the forest: the night map, the land and the panels take its hue) and the third (dawn: the hiker, summits, the current tab). Buttons are darkened until their white text is readable and every pair is checked; a color that can’t get there stops the build with a message. Brand also sets the fonts (headings and text, all with Central European letters) and the corners. The night map is the default; the paper map comes on when the visitor’s system prefers light, or with the toggle, and the browser remembers the choice.

Blaze ships in English and Slovak, with a switcher in the header and the menu; Slovak pages live under /sk/. Add or remove languages as described in Languages.

A small game, Follow the blaze: at every fork in a foggy forest a trail mark shows on one of three trees for a moment; take that path (left, straight on or right: the buttons, the arrow keys or a swipe) before the fog closes in. Later forks add green and yellow marks of other trails to throw you off: only the red one is yours. Every fork adds to the kilometres walked; the best walk 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, stages (with their numbers), guides and journal, in every language.
  • Pages carry JSON-LD: an Organization, stages and trips as TouristTrips, the guides as Persons, 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 trail map from OpenStreetMap and terrain data
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).