# runicgateway.com The public marketing and documentation site for [Runic Gateway][org] — the platform that puts a private game server's live state on a public website without ever exposing the game to the internet. Two audiences: **server administrators** who want to understand and install the platform, and **developers** who want to build modules and integrations for it. A third arrives with the Android closed beta: **players**, who want the app. **The design of record is [`PLAN.md`](PLAN.md).** It is not a sketch — it carries the verified platform state, the org lead's decisions, the information architecture, and the build phases. Read it before changing anything here. **Status: phase 5 of 12 — the app and the beta.** The foundation, the branding pipeline, the homepage and the five marketing pages are built, and `/app/` and `/beta/` now join them: a signed APK beside the closed-test signup, backed by a SQLite store on a bind mount and an export CLI. Next are the legal pages (phase 6) and then the documentation — the installation path, which is the priority of the whole project — in phases 7 and 8. --- ## Running it ```bash npm install npm run dev # http://localhost:4321 ``` ```bash npm run build # → dist/ (prerendered pages + the Node server entry) npm start # serve the built site ``` Node 22 LTS or newer. ## The checks, and why they are not optional Eleven of them, from `PLAN.md` §12. None is a linter; each one enforces a promise the site makes that would otherwise decay quietly. ```bash npm run check:sidebar # the rendered docs tree still matches the planned one npm run check:screens # every screenshot has an entry, at the size declared npm run check:tokens # no colour literal outside the token file npm run check:brand # the branding pipeline's two quiet failures npm run check:datasafety # the Play declaration still matches /privacy npm run check # astro check npm test # the beta signup's decision path, and the policy data npm run build # everything below reads the build npm run check:links # every internal link resolves GITEA_TOKEN= npm run check:facts # every version agrees with its authority GITEA_TOKEN= npm run check:quickstart # the install page still matches website's own files GITEA_TOKEN= npm run check:reference # every name the Reference lists still exists npm run check:a11y # seven structural accessibility rules, every page npm run check:csp # every inline script and style is hashed in its policy npm run verify # all of the above, in that order ``` **`checkFacts.mjs`** re-reads every version, protocol number and bundle tag in `src/data/platform.json` from its source of truth over the Gitea API — the sidecar's `PROTOCOL_VERSION`, the overlay's `overlay.toml`, the website's `MODULE_API_VERSION`, the installer's published bundle manifest, and each repository's latest release — and fails on any disagreement. When the platform moves, this repository goes red so that someone updates the site. **That failure is the feature**, the same mechanism and the same intent as the Integration Kit's `checkCoreApi.js`. §1 of the plan records what it is guarding against: an earlier draft confidently stated the platform was on protocol 3, because every checkout in the workspace sat on a feature branch whose local `main` had never been fetched. It needs a token — anonymous raw fetches fail on this Gitea instance, and a check that silently skips itself is worse than no check at all. It must be able to read the other repositories in the organisation, not just this one. CI already has this: the workflow maps the org-level `REGISTRY_TOKEN` secret into `GITEA_TOKEN` for that step. **`checkTokens.mjs`** fails the build if a colour literal appears anywhere in `src/` outside `src/styles/tokens.css`. §7 promises that recolouring the site is a file copy and a container restart; a mounted `theme.css` can only redefine custom properties, so a literal in a component is a piece of the site an operator can never reach. Without the check, "one CSS file changes the appearance" becomes "one CSS file changes most of the appearance". **`checkBrand.mjs`** guards the two things about the branding pipeline that fail quietly. It puts every literal `/brand/...` URL in the source through the route's own classifier, so a template asking for a size that is not on the allowlist fails the build rather than 404ing in a browser; and it refuses a brand string short enough that replacing it blindly at boot could corrupt a page. **`checkLinks.mjs`** reads `dist/client` rather than `src/`, because half the links these pages carry are assembled from data files and template literals and a source scan sees an expression. It also refuses a commit permalink into any org repository — those stop tracking the document they name without ever 404ing, which is the failure a link checker would otherwise call healthy. **`checkA11y.mjs`** applies seven structural rules to every built page — one `

` and no skipped heading level, an `alt` on every image, a label on every form control, an accessible name on every link and button, ``, one `
` with a skip link that reaches it, and no positive `tabindex`. Structural on purpose: a static check cannot measure contrast on a rendered page or find a focus trap, and one that pretended to would be trusted for things it cannot see. It covers Starlight's forty pages as well as our ten, so a dependency upgrade that loses a label turns the build red rather than becoming a discovery. **`checkCsp.mjs`** verifies that every route has a policy and that **every inline script and style is covered by a hash in its own page's policy**. That second rule is the one that earns its keep: Astro does not hash `