Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .claude/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"version": "0.0.1",
"configurations": [
{
"name": "heredita-site",
"runtimeExecutable": "node",
"runtimeArgs": ["scripts/dev-server.js", "8765"],
"port": 8765
}
]
}
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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
94 changes: 94 additions & 0 deletions ADMIN-CMS.md
Original file line number Diff line number Diff line change
@@ -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.
44 changes: 38 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <http://localhost:8000/>. 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
Expand All @@ -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.
62 changes: 62 additions & 0 deletions scripts/dev-server.js
Original file line number Diff line number Diff line change
@@ -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 + '/');
});
Loading