Skip to main content

/projects

Stay Guide

Mobile-first welcome guide for short-stay guests, in Portuguese, English and Spanish. Static Astro with no client framework, fifteen sections served by one route file, and a content layer where a fact is written once.

Stack
  • Astro 7
  • TypeScript
  • Tailwind v4
  • Zod
  • YAML
  • GitHub Pages

Case study

Guests ask the same questions in the same order: the wifi password, the check-in time, how the coffee corner works. Those answers live in an Airbnb thread, which is where they get lost. This is the guide that replaces the thread, published as static files behind a QR code. First property: Apartamento Amsterdam in Uberlândia, in three languages.

Sections are data, not code

There is one route file for all fifteen sections. Each section is a markdown file whose frontmatter carries a blocks array, validated by a Zod discriminated union, and one component dispatches on the block type with a switch whose default branch fails to compile when a type has no case. Adding a section means adding a slug and a file; it never means writing a page.

Eleven block types cover everything the guide needs to say: prose, callout, a big check-in time, a tap-to-copy field, three flavours of list, two grids of icons, a media list, an address, an FAQ and a phone list. Constraining the vocabulary is the point. A block I cannot express is a prompt to ask whether the guide should be saying it at all.

One fact, one place

Facts live in the property's YAML, not in the markdown: check-in and check-out times, phone numbers, wifi credentials. A block names the fact it wants with a source key instead of repeating the value, so changing a check-out time touches one line rather than three translations.

The same rule is what makes translation incremental. A section missing from one locale falls back to the default and says so in a callout, so a half-translated guide has no 404s. What the content gate does fail is two locales that both have the file and disagree about which blocks are in it, which is the drift that would otherwise arrive quietly.

- type: bigTime
  label: Check-in a partir de
  source: checkIn
The block names the fact; the value lives once in the property YAML. Three locales read it, one edit changes it.

A second property is a YAML file

Theming is per property and costs one key. Palettes are blocks of tone custom properties, and Tailwind's inline theme resolves every utility against them at runtime, so naming a palette in a property's YAML re-themes the whole guide without touching a component. Four palettes ship, one of them light, and adding the light one is what forced elevation to stop being hardcoded.

A scaffold script writes a new property's YAML plus a placeholder section per locale, all schema-valid. Since the only property published is the first one, the thing most likely to rot is the multi-property path nothing exercises, so one of the gates builds a fixture second property and asserts what only a second property can prove: that the index page appears, that the redirect changes, that no palette leaks across.