initial commit

This commit is contained in:
Josh Bairstow 2026-06-01 19:35:56 +10:00
commit 3ba76b4f02
90 changed files with 12507 additions and 0 deletions

216
docs/planning.md Normal file
View file

@ -0,0 +1,216 @@
# jb-website — Live Planning Document
> Living doc. Updated as decisions are made. The goal is to capture intent, constraints, and the reasoning behind tech choices so we don't re-litigate them and don't accidentally box ourselves in.
## 1. Project intent
A personal website that serves as the canonical home for Josh Bairstow's writing, generative/digital art, and a public index pointing out to side-hustle businesses. First draft of what will actually go live — not a throwaway mockup. Must leave room for the work in §3 without major rewrites.
The design system, voice, and four reference pages (Home, Blog index, Post, Art focus view) are captured in the Claude Design handoff bundle. The visual target is locked; the implementation is open.
## 2. Confirmed requirements
### 2.1 Pages (initial scope)
- **Home / landing** — fused composition (intro + hero zone + selected writing + subdomain index). Three layout variants exist in the design (`split`, `stack`, `index`); only one ships as the canonical URL at launch.
- **Blog index** — typographic post archive grouped by year, with featured lead post.
- **Blog post** — reading template with editorial prose rhythm, pull-quote, inline figure, prev/next nav.
- **Art focus view** — single-piece immersive view with caption and spec rail; the work itself is a live generative canvas.
- **404 / missing-asset page** — on-brand "this doesn't exist yet" or "coming soon" page that links back home. Used for any nav target that hasn't been built yet.
### 2.2 Navigation
- All top-nav links must resolve: existing page if built, otherwise the 404/missing page; never a dead link.
- Subdomain index rows on the home page link out to side-hustle subdomains (see §2.5), not to internal routes.
### 2.3 Blog mechanics
- **Content source:** static markdown files committed to the repo (likely `content/posts/*.md` with frontmatter). No CMS.
- **Rendering:** the framework translates markdown → the design-system styling (Bodoni headings, Hanken body, small-caps lead-in, ochre links, etc.). Authors write content; layout is automatic.
- **Browsing:** archive view (already designed) groups by year with hover affordances.
- **Search:** basic — title + body + tag substring match is enough for v1. Not Algolia-grade.
- **Out of scope for v1:** comments, RSS bells & whistles, multi-author, drafts UI. RSS feed itself is cheap and worth shipping.
### 2.4 Art pages
- Heavy client-side JS is expected (canvas / WebGL / WebGPU / shaders).
- Each art piece should be able to ship its own dependencies without bloating other pages.
- The framework must allow per-page islands of JS without forcing the rest of the site to load them.
### 2.5 Subdomain side-hustles
- Distinct businesses (Code experiments, Signage, Keycaps, etc.) live on their own subdomains.
- Served by a **reverse proxy + container per subdomain** at the infrastructure layer.
- **Out of scope for this repo.** This codebase only needs to link out to them; it does not host or build them.
### 2.6 Auth (future, plan-for-not-build)
- Goal: gate certain pages or sections to me + named collaborators.
- Likely shape: low-friction login (passkey, magic link, or GitHub OAuth — TBD), middleware-protected routes, an allowlist of authorized identities.
- Not a v1 feature. Tech choice should not preclude adding this later.
### 2.7 Design system
- Source files from the handoff bundle (`colors_and_type.css`, `site.css`, SVG marks, type tokens) become the foundation.
- Components built in an **atomic design** style: atoms (typography, marks, hairlines) → molecules (post row, index row, eyebrow + title pair) → organisms (topbar, sitefoot, masthead, feature card) → templates (page layouts) → pages.
- **Brand system docs imported** to `docs/design-system/` (2026-06-01). Includes `colors_and_type.css`, `README.md` (governing idea, hard guardrails, voice, marks), `SKILL.md`, `assets/` (SVG marks + grain), `preview/` (specimen cards), and `ui_kits/home/` (reference JSX components — Wordmark, HeroZone, LatestLine, SubdomainIndex). Treat as the canonical spec.
- **One rule to internalise** (from the brand README): *one base aesthetic and one accent, not equal partners.* Base ~90% — quiet luxury, warm browns + ink. Accent — rare, contained, ochre. Litmus test for any decision: *barely noticed on visit one, a small delight on visit three.*
## 3. Future work that must not be hamstrung
These are not v1, but the v1 choices must leave room for them:
1. Heavier JS art pieces — multiple pieces, each potentially with its own libraries.
2. Auth-gated areas.
3. RSS, sitemap, OG cards.
4. Maybe per-post comments or a guestbook (low priority).
5. Possible analytics (privacy-respecting).
6. View transitions / page-to-page animation polish.
## 4. Tech decision
### 4.1 Choice: **Astro 5 + React islands**
Astro is a static-first site builder that:
- Ships zero JS by default — matters for the home/blog/post pages which are content, not apps.
- Allows **islands** of any framework (React, Svelte, vanilla) on a per-component basis, hydrated independently. The art pages can be React (or vanilla canvas) without forcing React onto the blog.
- Has first-class markdown / MDX content collections with typed frontmatter — the blog requirement maps to it directly.
- Has file-based routing, `404.astro`, and middleware for future auth.
- Supports SSR per-route when needed (for auth-gated pages later), while keeping the rest SSG.
- Works with [Pagefind](https://pagefind.app) for build-time blog search at near-zero runtime cost.
### 4.2 Alternatives considered
| Option | Why not |
|---|---|
| Plain HTML/CSS/JS | Forces hand-rolling the markdown pipeline, search index, component reuse, future auth middleware. Looks "simple" but pays for that simplicity later. |
| Next.js | Heavier baseline; React-everywhere assumption taxes every page. Worth it if most pages were app-shaped, but here they're document-shaped. Adds friction for markdown content collections. |
| SvelteKit | Solid technically; user's note about "larger react framework" suggests React is the preferred ecosystem for the heavy bits. Astro lets us still use React where it matters. |
| Remix / React Router 7 | SSR-first, optimized for app-style data flows. Mismatch with the document-heavy nature of this site. |
### 4.3 Implications of the Astro choice
- **Hosting:** flexible — static build works on Cloudflare Pages, Netlify, Vercel, or self-hosted Nginx behind the reverse proxy. SSR (for auth later) wants a Node/edge runtime; Cloudflare Pages + Workers or self-hosted Node both work.
- **Auth path:** Astro middleware + Auth.js or Lucia when the time comes. Adds SSR mode for protected routes; rest of site stays SSG.
- **Search path:** Pagefind runs at build time; outputs a static index the client fetches lazily. No server needed.
- **Subdomain wiring:** unchanged — this repo only needs to render external links with the correct hrefs. Reverse proxy does the rest.
## 5. Proposed repo shape (draft, will evolve)
```
jb-website/
├── docs/
│ ├── planning.md # this file
│ └── design-system/ # imported brand system docs
├── public/
│ ├── assets/ # SVG marks, grain texture, favicons
│ └── og/ # generated OG images
├── src/
│ ├── components/
│ │ ├── atoms/ # Eyebrow, Mark, Hairline, Wordmark, Coord
│ │ ├── molecules/ # PostRow, IndexRow, MetaLine, FeatureCard
│ │ ├── organisms/ # TopBar, SiteFoot, Masthead, HeroZone
│ │ └── islands/ # React/JS-heavy: ArtCanvas, HeroPointer, Search
│ ├── layouts/
│ │ ├── BaseLayout.astro # html, head, grain, marks, topbar, footer
│ │ ├── PostLayout.astro # reading template; used by markdown posts
│ │ └── ArtLayout.astro # immersive focus view
│ ├── content/
│ │ ├── config.ts # collection schemas (zod)
│ │ ├── posts/ # markdown blog posts
│ │ └── art/ # markdown for art pieces; canvas in islands/
│ ├── pages/
│ │ ├── index.astro # home
│ │ ├── blog/
│ │ │ ├── index.astro # archive
│ │ │ └── [slug].astro # post detail (renders content/posts)
│ │ ├── art/
│ │ │ ├── index.astro # listing (future)
│ │ │ └── [slug].astro # focus view
│ │ └── 404.astro # missing / coming-soon page
│ ├── styles/
│ │ ├── colors_and_type.css # from handoff bundle
│ │ └── site.css # shared chrome
│ └── middleware.ts # placeholder for future auth
└── astro.config.mjs
```
## 6. Domain & deployment architecture
### 6.1 Domain layout (confirmed)
- **Apex:** `joshbairstow.com` — the home / landing page (this repo's primary surface).
- **Personal subdomains (this repo serves them — "in-repo paths"):**
- `blog.joshbairstow.com` — writing archive + posts
- `art.joshbairstow.com` — generative & digital art
- `code.joshbairstow.com` — code experiments & small tools (confirmed in-repo)
- The architecture must remain open to adding more in-repo subdomains later without restructuring (e.g. a `notes.`, `talks.`, or similar). The host-aware routing model in §6.2 generalises to any number of additional paths.
- **External-container subdomains (separate repos / containers, out of scope here):**
- At least one side-hustle is confirmed for a separate container; signage and/or keycaps may use brand-name apex domains rather than `*.joshbairstow.com`.
- Each external container plugs into the same reverse proxy.
### 6.2 Multi-subdomain serving — decision needed
Three subdomains (`apex`, `blog.`, `art.`) need to be served by *this* repo. Two viable shapes:
**Option A — One Astro project, host-aware routing (recommended).** One codebase, one container. Routes live under `/`, `/blog`, `/art`. The reverse proxy maps host headers:
```
joshbairstow.com/* → backend /*
blog.joshbairstow.com/* → backend /blog/*
art.joshbairstow.com/* → backend /art/*
```
Pros: shared design system at compile time, single build, single deploy, single search index, no duplication. Internal cross-subdomain links use absolute URLs.
Cons: a little proxy config; you must remember to use absolute URLs when crossing subdomains in author-facing markdown.
**Option B — One Astro project per subdomain.** Three containers, three builds, design system imported as a shared package. More isolation; more deployment overhead. Worth it only if the subdomains diverge structurally (which they shouldn't).
**Default plan:** Option A. The routing rule generalises — any new in-repo subdomain (e.g. `notes.joshbairstow.com`) becomes a new path in the Astro project plus one extra block in the Caddyfile. The `code.` subdomain confirmed in-repo joins `blog.` and `art.` under the same scheme.
### 6.3 Hosting target — confirmed VPS + containers
Recommendation for a project of this size:
**Confirmed:** Vultr (Sydney region) — $6/mo droplet (1 vCPU / 1 GB) sufficient for a static site + a few containerised side-hustles; bumps to $12 / 2 GB if memory pressure arises. Sydney PoP keeps latency tight for AU readers and still serves global traffic acceptably.
**Suggested stack on the VPS:**
- **Caddy** as the reverse proxy — automatic HTTPS via Let's Encrypt, dead-simple Caddyfile, handles the host-header → path-prefix routing cleanly.
- **Docker + docker compose** — the Astro static output baked into a tiny container (Caddy + the `dist/` files, or just nginx-alpine + static files); side-hustle containers added alongside under the same compose file.
- **Deployment** options:
- Build locally → push image to a registry (GHCR or Docker Hub) → SSH to VPS → `compose pull && up -d`. Fully manual, very transparent.
- Or GitHub Actions: build + push image on tag, then a webhook or SSH step triggers the VPS pull. Slightly more setup, far less ceremony per deploy. Recommended once the workflow is steady.
### 6.4 Other open questions
- **Tweaks panel:** drop it (it's a design-time tool), or keep a slimmed runtime theme toggle (Ink / Bone)? Default: drop for v1; theme is dark-ink only.
- **Auth shape:** passkey vs. magic link vs. GitHub OAuth. Deferrable.
- **Analytics:** if any, which (Plausible self-hosted, Umami self-hosted, none).
- **`code.` subdomain content:** ~~is this also served from this repo~~ — confirmed in-repo (2026-06-01).
- **Design system docs source:** ~~URL 404'd~~ — imported successfully (2026-06-01); see `docs/design-system/`.
## 7. Decisions log
| Date | Decision | Rationale |
|---|---|---|
| 2026-06-01 | Adopt Astro 5 with React islands as the framework | Best fit for static-content + heavy-JS-on-some-pages mix; doesn't preclude future React, auth, or interactivity work |
| 2026-06-01 | Blog posts are markdown files in `src/content/posts/` rendered via Astro content collections | Matches stated requirement; gives typed frontmatter and design-system templating for free |
| 2026-06-01 | Side-hustle subdomains are out of scope for this repo; only outbound links are wired here | Confirmed — served by separate containers behind a reverse proxy |
| 2026-06-01 | Home page ships the `stack` (editorial, centered) layout as the canonical landing | User selection — leaning into the editorial feel for v1 |
| 2026-06-01 | Apex `joshbairstow.com` + `blog.` + `art.` (+ likely `code.`) served from this single Astro project; subdomain routing handled at the reverse proxy | Avoids duplicating the design system; one build, one deploy |
| 2026-06-01 | Hosting target: VPS + Docker + Caddy as reverse proxy, containerised deploy workflow | Per user direction; matches the side-hustle container pattern; Caddy gives us automatic HTTPS for free |
| 2026-06-01 | VPS provider: Vultr (Sydney region) | Best AU latency for the smallest tier; modern hardware; easy Docker setup |
| 2026-06-01 | `code.` subdomain is in-repo (third in-repo section alongside `blog.` and `art.`) | User confirmed; routing model already supports it |
| 2026-06-01 | Architecture must remain open to adding further in-repo subdomain paths later | User flagged this as a constraint; host-aware routing in §6.2 scales to N paths without rework |
| 2026-06-01 | Brand-system docs imported to `docs/design-system/` from a fresh handoff bundle | Establishes the canonical brand spec in-repo so future work can cite it |
## 8. Next steps
Approved & queued:
1. Scaffold the Astro project; port `colors_and_type.css` and `site.css` from the handoff into `src/styles/`; copy SVG assets into `public/assets/`.
2. Build `BaseLayout.astro` (topbar + footer + grain + marks). Topbar links resolve to absolute subdomain URLs so they work whether viewed at the apex or a subdomain.
3. Implement Home page in the `stack` layout (canonical landing). Hero zone keeps the pointer-tracked light gradient as the one signature motion moment.
4. Implement on-brand 404 / missing-asset page (any unbuilt internal route falls here).
5. Wire the markdown content collection + Post layout; port the reference post as the first real entry.
6. Implement Blog index against the collection.
7. Implement Art focus view; port the generative tide-study canvas as a React (or vanilla) island.
8. Wire Pagefind for blog search.
9. Containerise (Caddyfile + `dist/` in an alpine image). Write a sample `compose.yml` for the VPS with the host-header routing for `apex`, `blog.`, `art.`.
10. Defer: auth middleware, additional art pieces, RSS, OG images, `code.` subdomain content.
Blocked / waiting on user input:
- *(none — all blockers cleared as of 2026-06-01; ready to scaffold on user's go-ahead.)*