色系: 中文
Public · Technical readers

Anatomy of a Four-Page Site

One config file grows the whole site. Taste is taste; engineering is engineering.

v1.0.02026-08-19 Mila Chen

This document covers how mila-site is built. The design decisions (why watercolor, why an aurora) live in the blog post; this is engineering only.

01Architecture overview

A static Astro site on a free Cloudflare Pages subdomain: hosting cost 0, domain cost 0. The case for Astro is simple: zero JS by default, content collections turn posts into Markdown files in a folder, and the build output is plain static files with no server to babysit. A four-page site needs nothing heavier.

02One config file grows all the styling

Not a single color or font is hard-coded in any component. The single source of truth is one brand.json: palette, fonts, both light/dark palettes, channel links. A pre-build script emits it as CSS variables plus metadata; components only ever reference variables.

✦ The payoff comes at revision time Swapping the palette, the fonts, or the tagline means editing one JSON file and rebuilding.

This skeleton will later generalize into a template: a new brand site equals a fresh brand.json plus a content directory.

03How light/dark mode is engineered

Dual mode is not a filter. Every color token has separate light and dark values, and even the cover images are a matched pair of paintings; the browser only loads the one for the active theme.

There are three states: an explicit user choice (data-theme on the html element, stored in localStorage), following the system (prefers-color-scheme), and an inline script that applies the theme before render to avoid a white flash.

⚠ The easiest thing to miss Every themed CSS rule needs two selectors to cover both paths, "manually chose dark" and "following the system"; write only one and the toggle button fights the OS setting.

04i18n: a four-page site doesn't deserve a framework

Bilingual, / for Traditional Chinese and /en/ for English. No i18n framework: both languages' copy live in one dictionary file, routes are hand-written, and a post ships in a single language by setting lang in its frontmatter. I evaluated frameworks; for four pages, the abstraction is bigger than the problem.

So when does a framework earn its place? My three tripwires — hit any one and it's time to re-evaluate:

  • A third language. Two languages are an if/else; three become a matrix, and the dictionary file buckles first.
  • Too many routes to hand-write safely. With enough pages, hreflang, sitemaps, and language fallbacks are mechanical work a framework should own; humans eventually miss one.
  • Translation stops being a one-person job. Translation workflows, fallback to a default language, tracking what's untranslated: that is framework territory.

Until then, every added layer of abstraction is interest paid on a problem that doesn't exist.

05Motion engineering: keeping the aurora alive across pages

Motion has one goal: the whole site should carry the aurora's flow. Three pieces of engineering:

  • Page changes use View Transitions; the background aurora layer carries transition:persist, so the element survives navigation untouched and its animation never restarts. Without this, the aurora blinks out on every page change.
  • Scroll reveals use IntersectionObserver, with a fuse: if nothing fires within 2.5 seconds, everything is force-shown. A dead animation is acceptable; invisible content is not.
  • Turn on prefers-reduced-motion and everything goes still.

✎ Motion discipline Slow and light, one metaphor at a time (the aurora). The first version layered colored fog over the page: technically animated, visually zero. Tearing it down produced the current light-and-mist approach.

06The image pipeline

The cover auroras come from an image model, with style held by anchors rather than luck: every generation attaches my own watercolor paintings as reference images with a fixed set of style terms, so the output stays in one family. Everything is re-saved through PIL before it ships, stripping all metadata. Generation runs on a subscription quota at zero API cost — a pipeline that deserves its own write-up.

← Back to: Why the site looks like this