diff --git a/.claude/launch.json b/.claude/launch.json
new file mode 100644
index 0000000..c6c4b69
--- /dev/null
+++ b/.claude/launch.json
@@ -0,0 +1,11 @@
+{
+ "version": "0.0.1",
+ "configurations": [
+ {
+ "name": "heredita-site",
+ "runtimeExecutable": "node",
+ "runtimeArgs": ["scripts/dev-server.js", "8765"],
+ "port": 8765
+ }
+ ]
+}
diff --git a/.gitignore b/.gitignore
index 97aad37..ac2ea16 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,6 @@ node_modules/
# Local Netlify folder
.netlify
+
+# Machine-local Claude Code permission state (launch.json is shared, this isn't)
+.claude/settings.local.json
diff --git a/ADMIN-CMS.md b/ADMIN-CMS.md
new file mode 100644
index 0000000..608fb29
--- /dev/null
+++ b/ADMIN-CMS.md
@@ -0,0 +1,94 @@
+# Editing the Updates page — guide for admins
+
+The patch notes at **heredita.net/updates** are not written in code. They live in
+one file that a small web editor writes for you: **Decap CMS**.
+
+You do not need Git, an editor, or any developer tooling. You need a browser and
+an invite.
+
+---
+
+## 1. Getting an account
+
+Registration is **invite only** — there is no sign-up form, by design. The site
+owner sends you an invite from the Netlify dashboard:
+
+> Netlify → the Heredita site → **Identity** → **Invite users** → your email
+
+You get an email titled roughly *"You've been invited to join …"*. Click
+**Accept the invite**, set a password, and you're done. That link works once, so
+if it expires just ask for another.
+
+## 2. Signing in
+
+1. Go to **https://heredita.net/admin/** (bookmark it — nothing on the site links
+ to it).
+2. Enter the email and password from step 1.
+3. You land on **Updates / Patch Notes**.
+
+## 3. Writing a patch note
+
+Open **All patch notes**. You get a list of entries; the **top one is what
+visitors see first**, and it's also the one the home page pulls into its "Latest
+patch" strip.
+
+Click **Add patch note**, then drag it to the top of the list. Fields:
+
+| Field | What it is | Example |
+| --- | --- | --- |
+| **Version tag** | Short label on the left of the card | `v0.5` or `Pre-alpha` |
+| **Date** | Shown next to the version | `2026-08-14` |
+| **Tagline** | Optional short suffix after the date | `Map Pack` |
+| **Headline** | The big handwritten title on the card | `Loading screens and dice tweaks` |
+| **Bullet list** | One bullet per change | see below |
+
+For bullets, wrap words in `**` to bold them — that's the only formatting the
+page renders:
+
+```
+**New:** six hand-drawn loading screens.
+**Fix:** chat no longer crashes with 4+ players.
+```
+
+renders as **New:** six hand-drawn loading screens.
+
+## 4. Publishing
+
+Press **Publish** (top right). That's it — the change is committed to the
+`main` branch on GitHub, which triggers a rebuild, and the new note is live in
+roughly a minute. Hard-refresh if you still see the old one.
+
+**Publishing goes straight live.** There is no draft or review step
+(`publish_mode: simple`), so read it once before you press the button.
+
+## 5. Deleting or reordering
+
+Same screen. Use the trash icon on an entry to remove it, or drag entries to
+reorder. Newest belongs at the top.
+
+---
+
+## Notes for whoever maintains this
+
+- **What the CMS actually writes:** `site/content/updates.json`. Both
+ `updates.html` and the home page's "Latest patch" strip fetch that file at
+ runtime, so a published note updates both. Nothing else needs editing.
+- **Schema:** `site/admin/config.yml`. Add a field there and it appears in the
+ editor.
+- **Auth:** Netlify Identity + Git Gateway. The live site is on **Vercel**;
+ the Netlify project exists only to run Identity and Git Gateway
+ (see `netlify.toml`) and never serves pages.
+- **Two things to confirm in the Netlify dashboard** if login misbehaves:
+ - Identity → **Registration preferences → Invite only** (the admin page also
+ hides the sign-up tab in CSS, but that is cosmetic — the server-side
+ setting is what actually stops a forged sign-up).
+ - Identity → **Services → Git Gateway** enabled, with repo access to
+ `Zeyy21/HERE`.
+- **Split-hosting setup:** the public site is served from Vercel, while Identity
+ and Git Gateway live at `capable-sawine-7fa143.netlify.app`. The Decap
+ backend declares both services explicitly via `backend.identity_url` and
+ `backend.gateway_url` in `site/admin/config.yml`; the admin page deliberately
+ does not load the legacy Identity widget. The public landing page still
+ initializes the widget against the same Identity endpoint so invitation and
+ recovery links continue to work. Keep these values in sync if the Netlify
+ project is ever renamed.
diff --git a/README.md b/README.md
index c157755..59b73b3 100644
--- a/README.md
+++ b/README.md
@@ -11,9 +11,15 @@ Plain HTML + CSS + vanilla JS. No build step.
## Local dev
```bash
-cd site
-python -m http.server 8000
-# open http://localhost:8000/
+cd site && python3 -m http.server 8000
+```
+
+Then open . There's also a dependency-free Node
+equivalent, which serves with caching disabled so edits show up on a plain
+reload:
+
+```bash
+node scripts/dev-server.js 8765
```
## Deploy
@@ -34,9 +40,35 @@ site/
├── home.html dashboard
├── play.html launch page + preview footage carousel
├── about.html mission + features
-├── updates.html patch notes
-├── terms.html placeholder T&C
+├── updates.html patch notes, rendered from content/updates.json
+├── terms.html T&C
├── css/chalkboard.css
-├── js/app.js PLAY_URL points to app.heredita.net
+├── js/app.js session, play CTAs, carousel; PLAY_URL → app.heredita.net
+├── js/nav.js top-nav behaviour (sliding marker, menus, mobile drawer)
└── assets/ logos + 5 screenshots
```
+
+## Design conventions
+
+The chalkboard theme leans on one rule that keeps the whole site legible at a
+glance:
+
+| Border | Means |
+| --- | --- |
+| Solid hairline | A real, finished thing |
+| **Dashed** | A sketch — locked, unbuilt, coming soon |
+| Accent solid | The thing you're on right now |
+
+So dashes are never decoration. If something is dashed, it isn't shipped yet.
+
+Type has three roles: **Caveat** for display headings and the brand, **Special
+Elite** for typewritten metadata (dates, versions, stamps, kickers), and
+**Cabin** for body copy and every control.
+
+The header markup is duplicated verbatim across pages (no build step, by
+design) — `js/nav.js` only enhances it, so the nav still works with JS off. If
+you change a nav item, change it in every page.
+
+## Legal
+
+Heredita is operated by Anywhere Connection Corporation.
diff --git a/scripts/dev-server.js b/scripts/dev-server.js
new file mode 100644
index 0000000..0ffa7e7
--- /dev/null
+++ b/scripts/dev-server.js
@@ -0,0 +1,62 @@
+/* Static file server for local development.
+ *
+ * `python -m http.server` is the simpler option and is what the README
+ * suggests, but Python 3.9 resolves os.getcwd() while parsing its own
+ * arguments, so it refuses to start when the launcher's working directory
+ * isn't readable. This has no such problem and needs no dependencies.
+ *
+ * node scripts/dev-server.js [port]
+ */
+const http = require('http');
+const fs = require('fs');
+const path = require('path');
+
+const ROOT = path.join(__dirname, '..', 'site');
+const PORT = Number(process.argv[2]) || 8765;
+
+const TYPES = {
+ '.html': 'text/html; charset=utf-8',
+ '.css': 'text/css; charset=utf-8',
+ '.js': 'text/javascript; charset=utf-8',
+ '.json': 'application/json; charset=utf-8',
+ '.svg': 'image/svg+xml',
+ '.png': 'image/png',
+ '.webp': 'image/webp',
+ '.ico': 'image/x-icon',
+ '.txt': 'text/plain; charset=utf-8',
+ '.yml': 'text/yaml; charset=utf-8'
+};
+
+http.createServer((req, res) => {
+ let urlPath;
+ try {
+ urlPath = decodeURIComponent(req.url.split('?')[0]);
+ } catch {
+ res.writeHead(400).end('bad request');
+ return;
+ }
+ if (urlPath.endsWith('/')) urlPath += 'index.html';
+
+ const file = path.join(ROOT, path.normalize(urlPath));
+ // path.normalize collapses ".." — re-check so a crafted URL can't escape.
+ if (file !== ROOT && !file.startsWith(ROOT + path.sep)) {
+ res.writeHead(403).end('forbidden');
+ return;
+ }
+
+ fs.readFile(file, (err, buf) => {
+ if (err) {
+ res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
+ res.end('404 ' + urlPath);
+ return;
+ }
+ res.writeHead(200, {
+ 'Content-Type': TYPES[path.extname(file).toLowerCase()] || 'application/octet-stream',
+ // No caching, so edits show up on reload without a hard refresh.
+ 'Cache-Control': 'no-store'
+ });
+ res.end(buf);
+ });
+}).listen(PORT, () => {
+ console.log('Heredita dev server → http://localhost:' + PORT + '/');
+});
diff --git a/site/about.html b/site/about.html
index 5616419..4968e4a 100644
--- a/site/about.html
+++ b/site/about.html
@@ -5,46 +5,63 @@