Tell a visitor whose browser will not run the game what happened, in the page the server already renders.
grep -rn noscript src finds nothing, and the served page has 27 <button>s and two <canvas>es. So a visitor with scripting blocked - a locked-down work laptop, a privacy extension, a bundle that 404s behind a proxy - gets the full menu: Play, Map, Difficulty, Fullscreen, all of it rendered and all of it dead. Every click does nothing and the page never says a word. The leaderboard below it works, which makes it worse: the page looks alive.
The pattern to copy is already in the repository. main.ts:100 handles the other half of this exactly right: no WebGL2 and the menu is replaced with <h2>No WebGL2</h2><p>This browser cannot run the game. Everything else on the page still works.</p>. That is the sentence and the tone; it just cannot run when the script itself never runs.
Done looks like
[ ] A <noscript> block in src/pages/game.ts that a scripting-off visitor sees where the menu is, in the page's own voice: this needs JavaScript and WebGL2, the board below still works. No new page, no redirect.
[ ] The controls that cannot work are not offered as if they could: with scripting off the menu's buttons are hidden or visibly disabled, driven by CSS the <noscript> block turns on - no script required to reach that state, and no flash of dead buttons before it applies.
[ ] No layout shift for the normal visitor: with scripting on, the block renders nothing and occupies nothing. State in the pull how you checked.
[ ] The CSP is unchanged. style-src 'self' 'unsafe-inline' already allows the page's own inline sheet, so this needs no new directive and no new external file.
[ ] Tests in src/pages/game.test.ts: the served HTML contains the <noscript> block and its sentence, and the rule that disables the menu is in the sheet. Keep it to what the string says - this is a server-rendered page and its test is a string test.
[ ] bun run check green.
Files I expect to change
src/pages/game.ts, src/pages/game.test.ts.
Risks
Do not hide the leaderboard: it is the half of the page that works without a script, and saying so is the point.
<noscript> inside <head> may only contain link/style/meta; this belongs in the body, next to the menu it is about.
Adjacent but not this: #206 adds the icon and the manifest. Keep that out of here.
Crew
scout - the server-rendered page and its string tests.
Tell a visitor whose browser will not run the game what happened, in the page the server already renders.
grep -rn noscript srcfinds nothing, and the served page has 27<button>s and two<canvas>es. So a visitor with scripting blocked - a locked-down work laptop, a privacy extension, a bundle that 404s behind a proxy - gets the full menu: Play, Map, Difficulty, Fullscreen, all of it rendered and all of it dead. Every click does nothing and the page never says a word. The leaderboard below it works, which makes it worse: the page looks alive.The pattern to copy is already in the repository.
main.ts:100handles the other half of this exactly right: no WebGL2 and the menu is replaced with<h2>No WebGL2</h2><p>This browser cannot run the game. Everything else on the page still works.</p>. That is the sentence and the tone; it just cannot run when the script itself never runs.Done looks like
<noscript>block insrc/pages/game.tsthat a scripting-off visitor sees where the menu is, in the page's own voice: this needs JavaScript and WebGL2, the board below still works. No new page, no redirect.<noscript>block turns on - no script required to reach that state, and no flash of dead buttons before it applies.style-src 'self' 'unsafe-inline'already allows the page's own inline sheet, so this needs no new directive and no new external file.src/pages/game.test.ts: the served HTML contains the<noscript>block and its sentence, and the rule that disables the menu is in the sheet. Keep it to what the string says - this is a server-rendered page and its test is a string test.bun run checkgreen.Files I expect to change
src/pages/game.ts,src/pages/game.test.ts.Risks
<noscript>inside<head>may only contain link/style/meta; this belongs in the body, next to the menu it is about.Crew
scout - the server-rendered page and its string tests.