# jb-website The first-draft live site for **joshbairstow.com** — Astro 5 + React islands, styled to a quiet-luxury brand system in warm browns and ink. Serves the apex and three personal subdomains (`blog.`, `art.`, `code.`) from a single build via host-aware reverse-proxy routing. The planning doc — including tech rationale, architecture, decisions log, and open questions — lives at [`docs/planning.md`](docs/planning.md). Read that first if you want context. The canonical brand spec lives in [`docs/design-system/`](docs/design-system/) (imported from a Claude Design handoff bundle); the "one base + one accent" rule and the visit-one / visit-three litmus test are load-bearing for design decisions. ## What's here ``` src/ ├── pages/ │ ├── index.astro apex home (stack layout) │ ├── 404.astro on-brand missing-page │ ├── blog/ │ │ ├── index.astro writing archive │ │ └── posts/[...slug].astro post detail │ ├── art/index.astro generative-art focus view │ └── code/index.astro workshop placeholder ├── layouts/ │ ├── BaseLayout.astro topbar + grain + sprinkled marks + footer │ └── PostLayout.astro editorial reading template ├── components/ │ ├── atoms/ Wordmark, Eyebrow, Mark, InkArrow │ ├── molecules/ IndexRow, LatestLine, PostRow, YearHead │ ├── organisms/ TopBar, SiteFoot │ └── islands/ HeroZone, TideStudyCanvas (React) ├── content/posts/ markdown blog posts (Astro content collection) ├── styles/ colors_and_type.css + site.css └── lib/urls.ts cross-subdomain URL helpers ``` ## Dev ```sh npm install npm run dev # http://localhost:4321 ``` The Astro dev server collapses subdomain links to relative paths so a single localhost preview shows the cross-subdomain navigation working. In a production build the same helpers emit absolute `https://blog.joshbairstow.com/...` URLs. ## Build ```sh npm run build # outputs static site to dist/ npm run preview # serve the build locally ``` ## Deploy (containerised, target = Vultr Sydney VPS) The repo ships a Caddy-fronted container that serves the static build directly with automatic HTTPS. ```sh docker compose build docker compose up -d ``` Caddy's site config (`Caddyfile`) maps host headers → path prefixes: - `joshbairstow.com` → `/srv/` - `blog.joshbairstow.com` → `/srv/blog/` - `art.joshbairstow.com` → `/srv/art/` - `code.joshbairstow.com` → `/srv/code/` Asset paths (`/_astro/*` and `/assets/*`) are re-rooted back to `/srv` from the subdomain blocks so they resolve regardless of which subdomain served the HTML. To add another in-repo subdomain later, add a directory under `src/pages/` and a matching block in the Caddyfile — that's it. ## Writing a blog post Add a `.md` file under `src/content/posts/`. Frontmatter schema (validated by Zod in `src/content.config.ts`): ```yaml --- title: A short title date: 2026-03-12 tag: Field notes # optional series: No. 19 # optional standfirst: One sentence in italic display type, under the title. description: SEO/OG fallback when standfirst is absent. readingMinutes: 6 # optional draft: false # excludes from list when true --- ``` Lead with `{.lead}` after a paragraph to apply the small-caps lead-in styling. The Astro markdown renderer handles the rest against `PostLayout.astro`. ## What's deferred - Blog search (Pagefind wiring) — see planning doc step 8. - Auth middleware — needed when access-gated pages land. - RSS, OG image generation, additional art pieces. - Content for `code.` (currently a workshop placeholder). - Side-hustle subdomains (signage, keycaps) — out of scope; live in their own containers behind the same proxy. ## License All rights reserved unless stated otherwise. Brand marks, type pairings, and prose are personal; please don't redistribute.