Skip to content

Tideline

Tideline is a site for a boat company in a real city of canals or rivers: canal cruises, river trips, boat tours, a water taxi, boat hire. The home page opens on a long panorama of house fronts mirrored in the canal, which sails past sideways as you scroll (at dusk in the dark theme, the windows lit). A split-flap departures board lists the boats of the day, and the cruises stand in a row as canal houses, their pictures cut to step, neck and bell gables. Every route is computed along the real canals on a map of your city drawn from OpenStreetMap in a vivid 3D style. Coral, turquoise and sunshine yellow, headings in a round, heavy grotesque. The demo is a fictional small-boat company in Amsterdam, Slow Water Boats.

Tideline 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 on the water:

  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. A city on the sea or an estuary is mapped only by its coastline; the map command closes it into water.
  3. Give your landmarks their latitude and longitude, your cruises their route (the landmarks in order, plus waypoints to steer it) and boarding pier. With "routeOn": "water" in map.config.json (the default here) routes follow the centre lines of the canals and rivers, and every distance is measured along them. A landmark on a quay is fine: the route passes the nearest point on the water. Set "routeOn": "streets" for tours on foot or by bike.

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.

Page Edit it in
Home Pages → Home page (a list of sections)
Cruises (every route on the city, the cruises, private hire) Pages → Cruises page and Content → Cruises
A cruise (the route on the plan, sight by sight with the kilometre, the facts, tickets) Content → Cruises
Along the way (every landmark on the map, a filter by kind) Pages → Along the way page and Content → Landmarks
A landmark (what it is, what you see from the boat, nearby on the map) Content → Landmarks
Skippers Pages → Captains page and Content → Captains
Journal Pages → Journal page and Content → Journal
Boarding (the piers on the plan, practical details, the contact form, questions) Pages → Boarding page
Name, city, contact, navigation Pages → Site settings
District and canal names on the map Pages → The map

In headlines, words wrapped in *stars* get a wavy sunshine underline.

Each cruise has a summary, its route, a duration, when it sails, its departures and departure times (for the board), the boat and seats, the price and a note, the boarding pier, highlights, a picture and a colour. Its page shows the route on the plan with each landmark at its kilometre. Turn on “Sell seats online” and add a Stripe Price id (or a Lemon Squeezy variant id) and the cruise’s button takes guests to checkout; after paying they get an email with the boarding pier. Groups book a private boat with Cal.com.

Landmarks have a kind (the along the way page filters by it without JavaScript), a fact and what you see from the boat. Captains have the boat they skipper.

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

Feature Where Service
Tickets Each cruise’s page Stripe or Lemon Squeezy
Private hire Cruises page, loads on click Cal.com
Newsletter Home and journal pages, with a welcome email Supabase or Mailchimp
Contact form Boarding page; messages go to the email in Site settings Resend
AI chat Every page; answers from your cruises (with their routes), landmarks, captains, boarding and journal Claude or OpenAI
Search ⌘K or the search button: cruises, landmarks, captains, journal, boarding built in, or Algolia
Analytics Every page; Google Analytics only after consent Plausible, Umami, Google Analytics, PostHog
Members-only posts Journal Clerk

The home page is built from these sections, in any order: The canal front (the signature: the panorama that sails past as you scroll; its day and dusk pictures are in the section), Departures (the split-flap board, from each cruise’s departure times), Cruises (as gabled houses), Along the way (the sights as a strip that passes the boat), Captains, Numbers, Journal, Newsletter and a closing call over a photo from edge to edge.

src/content/singletons/brand.json (Pages → Brand in Keystatic) holds three colors: the accent (coral: buttons, pins and the route), the second (turquoise: fills, panel shadows and the evening) and the third (sunshine: the wave under marked words, the next departure, highlights). 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 dusk, unless the visitor picks one with the toggle; the maps switch with it.

Tideline is ready for more languages: see Languages.

A small game, Harbour jam: a 6 × 6 harbour packed with moored boats, each sliding only along its own length. Tap a boat, then the water it should slide to (or arrows, Tab to change boat); one slide is one move however far it goes. Clear a way and get the coral tour boat out through the gap in the harbour wall. The fewest moves for every harbour is found by a search over every position, and you score 100 × the fewest moves ÷ yours. Three harbours, each more crowded. 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, cruises and landmarks for AI assistants, in every language.
  • Pages carry JSON-LD: the business as a TravelAgency, cruises as TouristTrips with their itinerary and price, landmarks as TouristAttractions, questions as a FAQPage, posts as BlogPostings.
Terminal window
pnpm dev # local site, editor at /keystatic
pnpm map # draw your city (npm run map -- fetch | build | render)
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).