feat(site): phase 1 — the foundation
Some checks failed
PR checks / checks (pull_request) Failing after 4m19s

Astro 7 with the Node adapter, Starlight mounted at /docs, the token file, both
self-hosted typefaces, the layout shell, and the two build-time checks from §12.

The palette's gold and cyan are sampled from runic-emblem.png rather than
guessed, per §11: 494,059 opaque pixels binned by hue, each value annotated with
its measured contrast against the ground, and restricted rather than brightened
where a ratio fails.

- checkTokens.mjs fails the build on any colour literal outside tokens.css,
  which is what keeps §7's "recolouring is a file copy" promise true.
- checkFacts.mjs re-reads all 14 externally-sourced facts from their authorities
  over the Gitea API and fails on disagreement. It also enforces D13: no email
  address in the source outside brand-default/brand.json.
- Both were negative-tested; neither has ever been allowed to pass by default.

§6 asks for output:'server' with per-page prerender=true. Astro 7 expresses the
same runtime shape as output:'static' with an adapter, opting individual routes
out — so the default is static rather than accidentally server-rendered.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-19 19:08:52 -05:00
parent 650ea21ad4
commit 66187dde5d
25 changed files with 10462 additions and 0 deletions

46
src/lib/tokens.mjs Normal file
View File

@@ -0,0 +1,46 @@
// Vite inlines the file's text at build time. This is deliberately NOT a `readFileSync`
// against `import.meta.url`: that works in dev and then throws ENOENT during prerender,
// because the bundled chunk sits in `dist/server/.prerender/` and the CSS does not follow
// it there. `?raw` puts the bytes in the bundle, where they are needed.
import tokensCss from '../styles/tokens.css?raw';
/**
* Reads `tokens.css` at build time and exposes its custom properties to JavaScript.
*
* This exists because a few values have to leave CSS: `<meta name="theme-color">`, the OG
* card's background, an SVG diagram's stroke. Copying them into a template would be
* exactly the drift §7 warns about — "one CSS file changes most of the appearance, and
* then there is a hardcoded #0e1318 in the footer" — and `checkTokens.mjs` would fail the
* build for it, correctly.
*
* So the token file stays the single source and this reads it, rather than the other way
* round. Deliberately a plain regex over `--name: value;` and not a CSS parser: the file
* it reads is one we own and keep flat, and a dependency here would be a dependency in the
* build of every page.
*
* Note that this resolves the STOCK values. A bind-mounted `theme.css` overrides tokens in
* the browser, at runtime, which is the whole point — anything derived through this module
* is therefore build-time and will not follow a mounted theme. Keep that list short.
*/
function readTokens() {
const withoutComments = tokensCss.replace(/\/\*[\s\S]*?\*\//g, '');
const out = {};
for (const match of withoutComments.matchAll(/(--[a-z0-9-]+)\s*:\s*([^;]+);/gi)) {
out[match[1]] = match[2].trim();
}
return Object.freeze(out);
}
export const tokens = readTokens();
/** Throws rather than emitting `undefined` into a template. */
export function token(name) {
const value = tokens[name];
if (!value) {
throw new Error(
`Unknown design token "${name}". Every token is defined in src/styles/tokens.css; ` +
`add it there rather than inlining a value at the call site.`
);
}
return value;
}