Skip to content

Commit 7a49174

Browse files
Add game engine landing page (#25)
* Add game engine landing page * Fix review findings: contrast, mobile headline, landmarks, diagram label Two accessibility defects, one contrast failure, one typography regression at 320px, and four smaller items from the spec review. Landmarks: header and footer sat inside <main>, which strips their banner and contentinfo roles -- those apply only when not nested in main. Both moved out. Diagram text alternative: aria-label was on a bare <div>, whose implicit role is generic, so the description was announced by nothing. Added role="group", hid the decorative relationship words from assistive technology, and made the kind branches a real list. Contrast: --landing-border-strong was rgba(255,255,255,0.24), which composites to rgb(68,69,71) over #090a0d for 2.06:1 -- under the 3:1 minimum for the meaning-bearing borders it exists to serve. Raised to 0.35 (3.14:1); computed that 0.34 is the floor. Mobile headline: the clamp floor of 3.6rem rendered the hero as one word per line at 320px -- six lines. It never overflowed, so it passed the letter of the 320px rule while losing the composed line. Floor dropped to 2.1rem and the deliberate <br> is now suppressed below the mobile breakpoint, so the break is an enhancement rather than a fixture. Verified in a browser: three lines at 320px, the composed two at 1280px, zero horizontal overflow at both. Also: refusals and capabilities no longer render as matched ledgers, since two identical blocks read as one and cancel the contrast the pairing exists for; Campaigns links to content-packs rather than vision, which was a semantic mismatch; documentation links navigate in place instead of each opening a tab, with the repository link keeping target=_blank and announcing it; the ledger index uses padStart rather than a hardcoded zero; and one duplicated noun run was trimmed. Route inventory gains content-packs and clients, both valid routes it had simply omitted -- it is the only check on these cross-site links. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Add the motion pass and reconcile AGENTS.md Motion (Phase 5), which had never been implemented -- the page shipped with no animation at all. Everything added here is additive: delete css/motion.css and the page is unchanged in content, only in timing. Reveal safety is the design constraint, not an afterthought. Content is visible by default; the hiding rule is scoped to .js-reveal, which the hook adds only once an IntersectionObserver actually exists. So the page reads when JavaScript never runs, throws early, or the browser lacks the API. Two further guards: elements already in view are revealed on the observer's first callback, and a three-second failsafe reveals everything regardless, so a bailed-out observer costs a delay rather than a blank section. The hero deliberately does not stagger its headline. The page is client-rendered and already starts blank waiting for the bundle; animating the eyebrow, headline and ellipsis on top of that would put over a second of nothing in front of the most important words. Only the signature and scroll invitation stagger, because their timing is the joke. Reduced motion neutralises reveals at the source -- opacity 1, no transform, no transition -- rather than shortening them, so nothing waits on an intersection event. Hover and focus states survive, since they carry state rather than motion. Also restores the signature easter egg, desktop-only and deliberately so: touch has no hover, and making a decorative line focusable would add a tab stop that announces nothing. Both variants share one grid cell, so the swap costs no layout shift, and the alternate is absent from layout entirely on mobile. AGENTS.md was a copy of CLAUDE.md with one assistant's name mechanically replaced by another's, which rewrote nine real references into paths and packages that do not exist -- ~/.Codex/skills/graphify/, npx Codex-mem doctor, /Codex-mem:learn-codebase -- while leaving 14 KB free to drift from its source. Replaced with a pointer to CLAUDE.md and agent.md. A duplicate that can disagree with its original is the failure mode CLAUDE.md itself tracks. Verified in a browser at 320px and 1280px: five reveal targets, hero paints immediately, all five reduced-motion rules resolve, signature spans stack in one cell, zero horizontal overflow. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Decide landing/docs routing, chain anchors through every section, tighten type and spacing Three unrelated fixes, requested together. Routing: the landing page is packaged to be served at "/", with the docs at "/docs" on the same origin. That is a decision about path structure, not a full hosting choice -- narrower than the "hosting: not decided" question this bundle has deliberately left open twice already. So the six /docs/engine/... routes and the docs index move from absolute cross-site URLs to root-relative paths. Which host or platform serves the combined output, and how this project's dist/ and the docs build get assembled into one deployable tree, remain open and untouched -- docs/docusaurus.config.ts and docs/sidebar.ts are unchanged, per instruction, since that build process is expected to be reworked and wiring an assembly step against it now would be effort spent twice. 00-repository-reality.md and the bundle README are updated to state the split plainly: routing decided, platform not. Anchors: the hero's "Scroll. It escalates." was the only anchor on the page, reaching exactly one section further -- origin, and no other section had an id at all. Every section now has one, and a small "Continue" link at the end of each (except the last) chains to the next, extending the hero's own device the whole way down instead of leaving it as a one-off. Two sections use two-column grids (contract, ledgers); the link goes inside the second column/article in both so it doesn't become a stray third grid item. Type and spacing: h1's clamp ceiling was 8.6rem (137.6px), h2's was 4.7rem (75.2px) -- both cut roughly in half (5.5rem, 3rem) to match an editorial page rather than a poster. Section padding-block ceiling dropped from 10rem to 6rem, hero padding from 8rem to 5rem, and every large margin-top/gap in between (ledgers, architecture diagram, cta-actions, footer) scaled down by a similar third to a half. Verified in a browser: h1 88px / h2 46px at 1280px (was 137.6/75.2), zero horizontal overflow at 320px, all ten section ids and eight Continue link targets present and correctly chained (also asserted in a new test). Anchor navigation itself was confirmed with a real dispatched click after synthetic .click() calls misleadingly failed to scroll in this automation harness -- that turned out to be a property of the test tool, reproducing identically on the pre-existing, untouched hero anchor, not a page defect. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Decide hosting: GitHub Pages, same domain as the docs Corrects "Hosting platform: Not decided. Not GitHub Pages" across the bundle. That reflected an instruction given earlier in this working session; a later one in the same session confirmed GitHub Pages, so the documentation was stating something already superseded. This is not a new deployment target -- it is the existing one, now serving both projects. docs-deploy.yml already deploys the docs site to GitHub Pages at game-engine.subzerodev.com (docs/docusaurus.config.ts, .config/ DocumentationRules.psd1). Pages serves exactly one site per repository, so "hosting = GitHub Pages" was never going to mean a second Pages site -- it means this repo's one deployment, at this one domain, covering "/" (landing) and "/docs" (documentation) together. That is exactly the shape the routing decision from the previous commit already assumed, which is why settling the platform changes nothing about the routes themselves. Unblocked: the domain was the one fact standing between "wait on a hosting decision" and a real canonical URL / Open Graph URL. Both are now set in site/index.html (https://game-engine.subzerodev.com/) and enforced by scripts/verify-build.mjs the same way the other social tags already are, so a future edit that drops them fails the build rather than failing silently. Still not built: the mechanism that merges this project's dist/ and the docs site's own build output into the one artifact tree docs-deploy.yml uploads. Neither docs/docusaurus.config.ts, docs/sidebar.ts nor docs-deploy.yml is touched -- that stays deferred, not because the platform is unclear (it no longer is), but because the docs build process is expected to be reworked, and wiring an assembly step against a process about to change would be effort spent twice. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Wire the landing page into the GitHub Pages deploy: build/Merge-LandingPage.ps1 Builds the assembly step that was the one remaining gap between "PR #25 merges" and "the site is actually live" -- landing at "/", docs at "/docs", one GitHub Pages deployment. The mechanism: docs-build.ps1 runs as before, unmodified; the landing page (site/) builds independently; a new script, build/Merge-LandingPage.ps1, overlays it onto the docs output -- overwriting only the docs build's generated-from-README root index.html and adding the landing page's assets. docs/docusaurus.config.ts and docs/sidebar.ts are untouched. The collision question -- do the two builds' assets/ folders step on each other -- was the one real unknown, and it was verified rather than assumed: pulled ghcr.io/the-running-dev/docs-template, ran the actual docs build, and inspected the real output. Docusaurus nests its bundle under assets/css/ and assets/js/; Vite writes flat hashed files directly into assets/. They never write the same path. The script also refuses to proceed if the merge changes anything under the docs build's own docs/ subtree, as a second, mechanical guard on top of the verified fact. Wired into both docs-ci.yml's verify job -- so every PR builds and merges both projects and proves the combined artifact, the same way it already proved the docs-only build -- and docs-deploy.yml itself. Ran the full pipeline locally end to end against the real container before pushing: real docs build, real landing build, real merge, root index.html correctly becomes the landing page, docs/engine/* unchanged, canonical and og:url URLs present in the merged output. Nothing here executes against the live site until this PR is reviewed and merged -- docs-deploy.yml only triggers on push to main. Updated plans/06-landing-page/00-repository-reality.md, its README, and 14-content-inventory.md to record the build-assembly step as built rather than open, and agent.md with the verified collision fact and the pending change to what "/" serves once this merges. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * Address PR review: enable strict TS, explicit ReactNode import, enforce site/ checks in CI Three findings from automated review. Two real, one verified false. 1. site/tsconfig.app.json and tsconfig.node.json did not set compilerOptions.strict. Real gap -- added to both. Ran typecheck fresh with the build-info cache cleared: zero errors surfaced, so this was a missing safeguard, not a mask over existing problems. 2. Claimed App.tsx's `children: React.ReactNode` (no import) should fail `tsc -b` with "Cannot find namespace 'React'", given the app tsconfig restricts `types` to vite/client only. Checked before touching anything: cleared the build-info cache and ran `npm run typecheck` fresh -- it passed, both locally and in every prior CI run including the one that just landed. The claim doesn't hold: any .tsx file using the automatic JSX runtime (jsx: "react-jsx") implicitly resolves react/jsx-runtime's types, which pulls in @types/react's global declarations regardless of the restricted `types` array -- a real but non-obvious TypeScript behavior a static reviewer wouldn't see. Not a bug. Made the import explicit anyway (`import type { ReactNode } from "react"`), since relying on an implicit global without saying so is fair to flag even when it isn't broken -- a future reader shouldn't need to know the JSX-runtime quirk to understand the type. 3. Real gap: docs-ci.yml and docs-deploy.yml both ran `npm --prefix site run build`, which only proves the bundle compiles. Vitest and the Open Graph tag verifier (site/scripts/verify-build.mjs) were defined but not enforced by either required check -- a broken component test or a missing meta tag could merge and deploy as long as `vite build` itself succeeded. Both workflows now run `npm --prefix site run check` instead, the same command run locally before every push in this PR: format, lint, typecheck, vitest, build, verify-build.mjs. Verified end to end against the real container before pushing, same as the merge script: `check` passes clean with strict mode on (format, lint, typecheck, 10 tests, build, OG-tag verification), then the merge onto the docs build still succeeds and leaves docs/ untouched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent a57f5c2 commit 7a49174

28 files changed

Lines changed: 6116 additions & 48 deletions

.github/workflows/docs-ci.yml

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
name: Docs CI
22

33
# Everything documentation needs except deploy: check the authored Markdown,
4-
# then build the site to prove it still builds. No path filters -- both jobs
5-
# are meant to be required status checks, and a required check that never runs
6-
# leaves a pull request permanently blocked.
4+
# then build the combined site (docs + landing page) to prove it still builds.
5+
# No path filters -- both jobs are meant to be required status checks, and a
6+
# required check that never runs leaves a pull request permanently blocked.
77
#
88
# The gate and the build are not the same check. Docusaurus fails on unresolved
99
# routes inside the site; the gate covers what the site build never sees --
1010
# README.md, which is not part of the site at all, plus relative link targets,
1111
# heading anchors, product-name casing, and drift between a generated file and
12-
# its source.
12+
# its source. Neither one checks that the landing page (site/) still merges
13+
# cleanly onto the docs build -- that is what the verify job's extra steps are
14+
# for, so a PR proves the deploy path before it can run for real.
1315

1416
"on":
1517
pull_request:
@@ -64,6 +66,29 @@ jobs:
6466
- name: Build documentation
6567
run: /template/scripts/docs-build.ps1 -SourceDocs ./docs -OutputPath artifacts/docs
6668

69+
# The landing page is a second, independent project (site/) sharing
70+
# this one GitHub Pages deployment: it serves "/", the docs serve
71+
# "/docs". The base image already has Node -- no setup-node needed.
72+
#
73+
# `check` runs format:check, lint, typecheck, vitest and the build
74+
# (site/package.json), not just the build. A bare `build` would let
75+
# a broken component test or a missing Open Graph tag merge and
76+
# deploy as long as the bundle still compiled -- `check` is what a
77+
# contributor runs locally before pushing, made a required step
78+
# here too, not a weaker CI-only substitute for it.
79+
- name: Build and verify landing page
80+
run: |
81+
npm --prefix site ci
82+
npm --prefix site run check
83+
84+
# Overlays the landing page onto the docs build -- overwrites the
85+
# docs build's generated-from-README root index.html, merges in
86+
# the landing page's assets, and refuses to proceed if anything
87+
# under artifacts/docs/docs/ changed. See build/Merge-LandingPage.ps1
88+
# for why the two builds never collide.
89+
- name: Merge landing page into documentation build
90+
run: ./build/Merge-LandingPage.ps1 -LandingDist site/dist -DocsOutput artifacts/docs
91+
6792
# Run the same archiving step docs-deploy.yml uses, so the deploy
6893
# path is covered before merge rather than after it. This action
6994
# tars the site with `tar --hard-dereference`, which fails outright

.github/workflows/docs-deploy.yml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,27 @@ jobs:
5353
- name: Build Documentation
5454
run: /template/scripts/docs-build.ps1 -SourceDocs ./docs -OutputPath artifacts/docs
5555

56+
# The landing page is a second, independent project (site/) sharing
57+
# this one GitHub Pages deployment: it serves "/", the docs serve
58+
# "/docs". The base image already has Node -- no setup-node needed.
59+
#
60+
# `check`, not a bare build -- see docs-ci.yml's verify job, which
61+
# runs the same command. This job is the one that actually runs on
62+
# a push to main (verify skips push events), so it is also the
63+
# last thing standing between a broken landing page and a real
64+
# deploy if a check was ever bypassed getting there.
65+
- name: Build and verify landing page
66+
run: |
67+
npm --prefix site ci
68+
npm --prefix site run check
69+
70+
# Overlays the landing page onto the docs build -- see
71+
# build/Merge-LandingPage.ps1, and the same step in docs-ci.yml's
72+
# verify job, which proves this on every PR before it ever runs
73+
# here on push to main.
74+
- name: Merge landing page into documentation build
75+
run: ./build/Merge-LandingPage.ps1 -LandingDist site/dist -DocsOutput artifacts/docs
76+
5677
- name: Upload Pages Artifact
5778
uses: actions/upload-pages-artifact@v5
5879
with:

AGENTS.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Agent Instructions
2+
3+
This file exists for tools that look for `AGENTS.md` by convention. It is a pointer, not a
4+
copy — the instructions live in two files, and duplicating them here would give the
5+
repository two answers to every question.
6+
7+
| Read | For |
8+
|---|---|
9+
| [`CLAUDE.md`](CLAUDE.md) | **Standing instructions.** What this project is, where the specs and source live, the docs-site tooling, working conventions, and the drift hazards this repo keeps hitting. Start here. |
10+
| [`agent.md`](agent.md) | **Lessons learned the hard way.** Retrospective notes — token economy, what worked, repo-specific drift hazards, open concerns. |
11+
12+
Both apply regardless of which agent or tool is reading them. Nothing in either is specific
13+
to one assistant except the names of the tools it describes, and those are named accurately
14+
because getting them wrong sends the reader to paths that do not exist.
15+
16+
## Why this is a pointer
17+
18+
An earlier version of this file was a full copy of `CLAUDE.md` with every occurrence of one
19+
assistant's name mechanically replaced by another's. That rewrote nine real references into
20+
paths and packages that do not exist — `~/.Codex/skills/graphify/`, `npx Codex-mem doctor`,
21+
`/Codex-mem:learn-codebase` — while leaving 14 KB of otherwise identical prose free to drift
22+
from its source.
23+
24+
A duplicate that can disagree with its original is the failure mode `CLAUDE.md` itself
25+
tracks, applied to the instructions. One file, one answer.

agent.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ only when it would have changed a decision.
3535
A pattern built from the text you changed confirms your edits instead of finding your misses.
3636
**Removals are where this bites**: a bad edit contradicts something visibly, a missed removal
3737
is silent.
38+
- **Pull the real image before reasoning about it.** Merging the landing page (`site/`, a Vite
39+
build) onto the docs build (`docs/`, Docusaurus) meant one real question: do their `assets/`
40+
folders collide? Guessing wrong would have silently overwritten one build's JS with the
41+
other's. `docker pull ghcr.io/the-running-dev/docs-template` and running the actual build
42+
answered it in two commands: Docusaurus nests under `assets/css/` and `assets/js/`; Vite
43+
writes flat hashed files straight into `assets/`. Never touch. `build/Merge-LandingPage.ps1`
44+
ships on that verified fact, not an assumption about how either bundler works.
3845
- **Spec before code.** Building ahead of spec is where drift starts. Asked to "keep going"
3946
into code, we stopped and wrote the core spec (`04-core`) first — which immediately
4047
exposed that `03`'s kind-state duplicated envelope fields. That reconciliation would have
@@ -84,7 +91,11 @@ only when it would have changed a decision.
8491
Docusaurus 3, port 3000, and the local `sidebar.ts`. A production build passed with no
8592
leftover template docs in the sidebar. Re-verify when the base image tag changes. `/docs/`
8693
now serves the generated homepage (`docs/docs/index.md`); `/docs/engine/vision` is the
87-
first spec page beneath it, and the bare domain forwards to `/docs/`.
94+
first spec page beneath it. **Once `feature/landing-page` merges, the bare domain `/` stops
95+
being `docs/src/pages/index.md` (generated from README) and becomes the standalone landing
96+
page (`site/`) instead**`build/Merge-LandingPage.ps1`, wired into both `docs-ci.yml` and
97+
`docs-deploy.yml`, overlays it on every build. `/docs/` and everything beneath it is
98+
unaffected; only the site root changes owner.
8899
- **Two link checks, and between them everything is now gated.** `build/Test-Documentation.ps1`
89100
hard-fails on relative links and heading anchors — that is the one that catches a doc
90101
rename. Both of Docusaurus's own passes are back to `'throw'`, which re-covers the

build/Merge-LandingPage.ps1

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<#
2+
.SYNOPSIS
3+
Overlays the built landing page onto a built documentation site, so the
4+
combined tree serves the landing page at "/" and the docs at "/docs".
5+
6+
.DESCRIPTION
7+
Two independent projects, one GitHub Pages deployment. The docs build
8+
(docs-build.ps1) already puts the actual documentation under docs/docs/...
9+
-- routeBasePath: 'docs' in docs/docusaurus.config.ts sees to that -- but
10+
its own site root is docs/src/pages/index.md, generated from README.md.
11+
That generated homepage is superseded once the landing page exists: this
12+
script overwrites it with the landing page's own index.html and merges in
13+
its assets, leaving everything under docs/ inside the output untouched.
14+
15+
The merge is safe because the two builds never write the same paths.
16+
Docusaurus nests its bundle under assets/css/ and assets/js/; Vite writes
17+
flat hashed files directly into assets/. Verified against a real build of
18+
both projects, not assumed -- see the commit that added this script.
19+
20+
.PARAMETER LandingDist
21+
Path to the built landing page (a Vite `dist/` directory).
22+
23+
.PARAMETER DocsOutput
24+
Path to the built documentation site (docs-build.ps1's -OutputPath).
25+
26+
.EXAMPLE
27+
./build/Merge-LandingPage.ps1 -LandingDist ./site/dist -DocsOutput ./artifacts/docs
28+
#>
29+
[CmdletBinding()]
30+
param(
31+
[Parameter()]
32+
[ValidateNotNullOrEmpty()]
33+
[string]$LandingDist = 'site/dist',
34+
35+
[Parameter()]
36+
[ValidateNotNullOrEmpty()]
37+
[string]$DocsOutput = 'artifacts/docs'
38+
)
39+
40+
Set-StrictMode -Version 3.0
41+
$ErrorActionPreference = 'Stop'
42+
43+
if (-not (Test-Path -LiteralPath $LandingDist -PathType Container)) {
44+
throw "Landing page build not found at '$LandingDist'. Run 'npm --prefix site run build' first."
45+
}
46+
47+
if (-not (Test-Path -LiteralPath $DocsOutput -PathType Container)) {
48+
throw "Documentation build not found at '$DocsOutput'. Run docs-build.ps1 first."
49+
}
50+
51+
$docsSubtree = Join-Path $DocsOutput 'docs'
52+
if (-not (Test-Path -LiteralPath $docsSubtree -PathType Container)) {
53+
throw "'$DocsOutput' does not look like a docs-build.ps1 output -- no 'docs' subdirectory found. Refusing to merge into a directory that isn't a real docs build, to avoid silently producing a broken site."
54+
}
55+
$docsPageCountBefore = (Get-ChildItem -LiteralPath $docsSubtree -Recurse -File).Count
56+
57+
$landingIndex = Join-Path $LandingDist 'index.html'
58+
if (-not (Test-Path -LiteralPath $landingIndex -PathType Leaf)) {
59+
throw "'$LandingDist' has no index.html -- is this really a Vite build output?"
60+
}
61+
62+
# Overwrite the docs build's generated-from-README homepage with the landing
63+
# page. This is the one intentional collision: both projects produce a root
64+
# index.html, and the landing page wins.
65+
Copy-Item -LiteralPath $landingIndex -Destination (Join-Path $DocsOutput 'index.html') -Force
66+
67+
$landingAssets = Join-Path $LandingDist 'assets'
68+
if (Test-Path -LiteralPath $landingAssets -PathType Container) {
69+
$destAssets = Join-Path $DocsOutput 'assets'
70+
New-Item -ItemType Directory -Path $destAssets -Force | Out-Null
71+
Copy-Item -Path (Join-Path $landingAssets '*') -Destination $destAssets -Recurse -Force
72+
}
73+
74+
# Anything else Vite emitted at the dist root (favicons, manifest files, etc.)
75+
# that is not index.html or assets/ -- copy it across too, without touching
76+
# docs/.
77+
Get-ChildItem -LiteralPath $LandingDist -Force |
78+
Where-Object { $_.Name -notin @('index.html', 'assets') } |
79+
ForEach-Object {
80+
Copy-Item -LiteralPath $_.FullName -Destination $DocsOutput -Recurse -Force
81+
}
82+
83+
$docsPageCountAfter = (Get-ChildItem -LiteralPath $docsSubtree -Recurse -File).Count
84+
if ($docsPageCountAfter -ne $docsPageCountBefore) {
85+
throw "The merge changed the file count under '$docsSubtree' ($docsPageCountBefore -> $docsPageCountAfter). The landing page must never write into docs/ -- aborting rather than shipping a possibly-corrupted docs tree."
86+
}
87+
88+
Write-Host "[MERGE] Landing page overlaid onto '$DocsOutput'. docs/ untouched ($docsPageCountAfter files)." -ForegroundColor Green

0 commit comments

Comments
 (0)