@looop-games/cli 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/agent-surface.mjs +14 -32
- package/lib/create.mjs +132 -86
- package/lib/feedback.mjs +110 -6
- package/package.json +1 -2
- package/template/.claude/skills/build/SKILL.md +0 -266
- package/template/.claude/skills/engine/SKILL.md +0 -100
- package/template/.claude/skills/feedback/SKILL.md +0 -71
- package/template/.claude/skills/handbook/SKILL.md +0 -142
- package/template/.claude/skills/qa/SKILL.md +0 -53
- package/template/.claude/skills/todo/SKILL.md +0 -54
- package/template/.claude/skills/update-looop/SKILL.md +0 -74
- package/template/AGENTS.md +0 -93
- package/template/CLAUDE.md +0 -4
- package/template/GEMINI.md +0 -4
- package/template/boot.smoke.mjs +0 -21
- package/template/game.js +0 -53
- package/template/gitignore +0 -2
- package/template/handbook/design.md +0 -8
- package/template/handbook/feel.md +0 -8
- package/template/handbook/qa.md +0 -9
- package/template/index.html +0 -26
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: todo
|
|
3
|
-
description: Capture a bug, idea, or improvement for later without acting on it now. Use when the creator (or you, mid-build) spots something worth remembering — "we should fix that jump later", "idea — a boss level", "note that down" — and when they want to see what's captured or start work on one.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# /todo — capture now, decide later
|
|
7
|
-
|
|
8
|
-
A **todo** is the one lightweight capture type in this repo: bug, idea,
|
|
9
|
-
improvement — all the same thing until someone decides to act. Capturing must
|
|
10
|
-
be nearly free; never derail the current work to file one.
|
|
11
|
-
|
|
12
|
-
## Capture
|
|
13
|
-
|
|
14
|
-
Write one small file per todo at `notes/todos/<YYYY-MM-DD-HHMM>-<short-slug>.md`
|
|
15
|
-
(creation datetime first, always — the whole `notes/` tree names files this way
|
|
16
|
-
so they list in order):
|
|
17
|
-
|
|
18
|
-
```markdown
|
|
19
|
-
---
|
|
20
|
-
status: open # open | done | promoted | abandoned
|
|
21
|
-
created: YYYY-MM-DD
|
|
22
|
-
---
|
|
23
|
-
# <One-line summary>
|
|
24
|
-
|
|
25
|
-
What was seen / imagined, in a couple of sentences. Enough that a future
|
|
26
|
-
session understands it cold. Reference files by path if relevant.
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Then get back to what you were doing. If the creator says something mid-build
|
|
30
|
-
that is clearly a "later" item, offer to capture it in one line — don't stop
|
|
31
|
-
the milestone.
|
|
32
|
-
|
|
33
|
-
## Browse
|
|
34
|
-
|
|
35
|
-
"What's on the list?" → read `notes/todos/`, summarize the open ones in a few
|
|
36
|
-
lines each. Group bugs vs ideas by content, not by folder — there is
|
|
37
|
-
deliberately only one folder.
|
|
38
|
-
|
|
39
|
-
## Promote
|
|
40
|
-
|
|
41
|
-
When the creator decides to actually DO a todo, it becomes a **plan**: start
|
|
42
|
-
`/build`, which creates the plan in `notes/plans/` through its discussion
|
|
43
|
-
phase, seeded from the todo's content. Mark the todo `status: promoted` with a
|
|
44
|
-
line pointing at the plan file. Small fixes don't need promotion — just do
|
|
45
|
-
them and mark the todo `done`.
|
|
46
|
-
|
|
47
|
-
## Rules
|
|
48
|
-
|
|
49
|
-
- Never delete a todo — mark it `done` (with a line saying what happened),
|
|
50
|
-
`promoted`, or — when the creator decides NOT to do it — `abandoned` **with
|
|
51
|
-
a one-line why**. The why is the value: it saves re-deciding the same thing
|
|
52
|
-
next month. "Won't do" and "did it" must never collapse into one status.
|
|
53
|
-
- A todo about **Looop itself** (the engine, the CLI, the platform — not this
|
|
54
|
-
game) is `/feedback`'s job, not a todo.
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: update-looop
|
|
3
|
-
description: Update Looop itself — the engine, and the skills and instructions this repo runs on. Use when the creator asks for the latest Looop ("is there an update?", "update looop", "get the newest version"), when a Looop feature they've heard about is missing, or when something looks like a Looop bug that may already be fixed. Not for updating their GAME — that's just building.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# /update-looop — get the latest Looop, and explain what changed
|
|
7
|
-
|
|
8
|
-
`npx looop update` moves this game to the newest Looop release. It is the **only**
|
|
9
|
-
command that writes to Looop's files inside this repo — `dev` and `publish` never
|
|
10
|
-
do. That is deliberate: an update is something the creator *asks* for.
|
|
11
|
-
|
|
12
|
-
But it does write into **their** repo. After it runs, `git status` shows changes
|
|
13
|
-
they did not make. **Your job is to make sure that is never a surprise.**
|
|
14
|
-
|
|
15
|
-
## Before
|
|
16
|
-
|
|
17
|
-
Tell them what an update touches, in one or two sentences:
|
|
18
|
-
|
|
19
|
-
- **The engine** — the game code they import from (`/shared/...`). Their game
|
|
20
|
-
keeps running on the old engine until they publish again, so an update is safe
|
|
21
|
-
to take at any time.
|
|
22
|
-
- **The skills and instructions** — the files in `.claude/skills/` and the Looop
|
|
23
|
-
section of `AGENTS.md`. These are Looop's, living in their repo.
|
|
24
|
-
- **Never their game.** Not `index.html`, not `game.js`, not `handbook/`, not
|
|
25
|
-
`notes/`. And never a skill they wrote, or an edit they made to one of ours —
|
|
26
|
-
where they've changed something, their version wins and the update says so.
|
|
27
|
-
|
|
28
|
-
If they have uncommitted work, say so first — the update's changes will land
|
|
29
|
-
alongside it, and a mixed diff is harder to read. Offer to save theirs first.
|
|
30
|
-
|
|
31
|
-
## Run it
|
|
32
|
-
|
|
33
|
-
```bash
|
|
34
|
-
npx looop update
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
It prints exactly what it did: every file updated, every file removed, and every
|
|
38
|
-
file it **kept because the creator had changed it**. Read that output — it is the
|
|
39
|
-
source of truth for the next step, not your assumptions about what a release
|
|
40
|
-
contains.
|
|
41
|
-
|
|
42
|
-
## After — the part that matters
|
|
43
|
-
|
|
44
|
-
**Report the change in their terms, not ours.** Don't paste the file list back at
|
|
45
|
-
them; tell them what is now different about what you and they can do:
|
|
46
|
-
|
|
47
|
-
> *"Looop updated to engine 0.1.8. The `/update-handbook` skill was replaced by a
|
|
48
|
-
> broader `/handbook` — same job, plus it now keeps your game's vision and design
|
|
49
|
-
> pillars. Your own `/my-thing` skill was left alone."*
|
|
50
|
-
|
|
51
|
-
Then **offer the commit** — never run it unasked:
|
|
52
|
-
|
|
53
|
-
> *"That changed 3 of Looop's files in your repo. Want me to save them?
|
|
54
|
-
> (`git add -A && git commit -m "update Looop to engine 0.1.8"`)"*
|
|
55
|
-
|
|
56
|
-
Two things to volunteer without being asked:
|
|
57
|
-
|
|
58
|
-
- **If they'd edited a Looop file**, the update kept *their* version and did not
|
|
59
|
-
apply ours. Say which file, and that they may want to re-apply their change on
|
|
60
|
-
top of the new one — otherwise they'll silently miss the update to that file
|
|
61
|
-
forever.
|
|
62
|
-
- **If the update mentions a backup folder** (`.looop/backup-<version>/`), that
|
|
63
|
-
is the pre-update copy of files whose history we couldn't be sure of. Tell them
|
|
64
|
-
it exists. They can delete it once they're happy.
|
|
65
|
-
|
|
66
|
-
## Rules
|
|
67
|
-
|
|
68
|
-
- **Never run `looop update` as a side effect of something else.** It is its own
|
|
69
|
-
act, with its own consent. If a bug looks like it might already be fixed
|
|
70
|
-
upstream, *propose* the update — don't just do it mid-build.
|
|
71
|
-
- **Never commit on their behalf** without asking, even when the only changes are
|
|
72
|
-
Looop's own files. It is their repo and their history.
|
|
73
|
-
- The live game at `play.looop.games` does **not** change until they run
|
|
74
|
-
`npx looop publish`. Say so — creators reasonably assume an update is live.
|
package/template/AGENTS.md
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# {{name}} — a Looop game
|
|
2
|
-
|
|
3
|
-
<!-- looop:managed:start -->
|
|
4
|
-
<!-- Everything between the looop:managed markers belongs to the Looop
|
|
5
|
-
platform and is REWRITTEN when the engine updates. Add your own
|
|
6
|
-
instructions BELOW the end marker — updates never touch them. -->
|
|
7
|
-
|
|
8
|
-
This folder is a standalone Looop game. You (the agent) build it for a person
|
|
9
|
-
who is probably **not a developer** — the "creator". They describe the game in
|
|
10
|
-
plain language and make the judgment calls; you own everything else: process,
|
|
11
|
-
code, and verification. They should never need to learn git, npm, or servers.
|
|
12
|
-
|
|
13
|
-
## Where things go
|
|
14
|
-
|
|
15
|
-
The workflows live as **skills** in `.claude/skills/` (aliased at
|
|
16
|
-
`.agents/skills/` — same files); each skill's `description` says when it
|
|
17
|
-
applies. This table routes only what has no skill:
|
|
18
|
-
|
|
19
|
-
| The creator wants… | Do this |
|
|
20
|
-
|---|---|
|
|
21
|
-
| To "save my work" | `git add -A && git commit` (and push, if the repo has a remote). Saving is local; it is **not** publishing. |
|
|
22
|
-
| To update Looop / the engine | `/update-looop`. It is the ONLY thing that writes Looop's files into this repo (`.claude/skills/`, the managed block of this file) — `dev` and `publish` never do, so an update is never a surprise in their git. The live game changes only on the next publish. |
|
|
23
|
-
| Better game feel (tuning speeds, jumps, timings) | The engine's **tweaks** library (`node_modules/@looop-games/engine/shared/ui/tweaks/`) + `shared/practices/feel.md`. |
|
|
24
|
-
| To change how an engine file behaves | Copy it to `overrides/shared/<same path>` and edit the copy — dev and publish serve your override instead of the official module. Imports stay `/shared/...`; never rewrite them. An override is a fork: that file stops receiving engine updates until you re-port it. |
|
|
25
|
-
|
|
26
|
-
## Commands (run from this folder)
|
|
27
|
-
|
|
28
|
-
- `npx looop dev` — the full local stack: the game at
|
|
29
|
-
http://localhost:8000/games/{{name}}/index.html, multiplayer on :1999,
|
|
30
|
-
platform services (LLM etc.) on :8788. Leave it running; edits hot-reload.
|
|
31
|
-
**On a new machine the first `dev` signs the creator in: a browser window
|
|
32
|
-
opens, they log in and click Approve (~30 seconds, once per machine). Tell
|
|
33
|
-
them it's coming BEFORE you run it** — an unexplained browser popup mid-build
|
|
34
|
-
reads as something going wrong.
|
|
35
|
-
- `npx looop test` — the per-change gate: runs every `*.test.mjs` (unit, under
|
|
36
|
-
node) and every `*.smoke.mjs` (against a real dev stack it boots itself on
|
|
37
|
-
free ports) in this folder.
|
|
38
|
-
- `npx looop login` — connect this machine to the creator's Looop account
|
|
39
|
-
(once per machine; the first `looop dev` runs it automatically).
|
|
40
|
-
- `npx looop publish` — ship to https://play.looop.games/g/{{name}}.
|
|
41
|
-
**The first publish permanently claims the name `{{name}}` for the creator's
|
|
42
|
-
account** — from then on only they can update it. `--slug <alt>` publishes an
|
|
43
|
-
A/B copy without touching the live game.
|
|
44
|
-
|
|
45
|
-
## The rules that are physics (not preference)
|
|
46
|
-
|
|
47
|
-
1. **Multiplayer-first.** Any state other players can see (positions, physics,
|
|
48
|
-
scores, world objects) rides the room's authority — `room.update(...)`,
|
|
49
|
-
`room.send('input', ...)` — never local mutation only one client sees.
|
|
50
|
-
Verify multiplayer behaviour with TWO browser contexts, not one.
|
|
51
|
-
2. **The engine is read-only — and it is not the ceiling.** `/shared/...`
|
|
52
|
-
imports come from the installed engine at `node_modules/@looop-games/engine`
|
|
53
|
-
(not an npm dependency — `looop dev` downloads the version pinned in
|
|
54
|
-
`package.json`'s `looop.engine` and reinstalls it if an `npm install` prunes
|
|
55
|
-
it). Never edit files in `node_modules` — use `overrides/shared/` (see the
|
|
56
|
-
table above). But read-only means *don't edit it*, **not** *don't exceed
|
|
57
|
-
it*: a Looop game is a web page, so **anything the browser can do, this game
|
|
58
|
-
can do.** The library is a bag of conveniences you draw from, not a fence
|
|
59
|
-
around what the game may be — what it doesn't have, you build, here in the
|
|
60
|
-
game folder. **Never tell the creator that Looop "can't" do something**
|
|
61
|
-
(see `/engine` — it is almost always false, and it retires their idea
|
|
62
|
-
without their consent).
|
|
63
|
-
3. **Never hand-roll a server.** Only `looop dev` (or `looop test`) serves
|
|
64
|
-
this game: they alias `/shared/...` and inject the platform layer. A plain
|
|
65
|
-
static server 404s every engine import and the game silently never boots.
|
|
66
|
-
4. **Never handle tokens or keys.** Login is a browser device flow; platform
|
|
67
|
-
services (LLM etc.) are already wired through the local shim. If something
|
|
68
|
-
seems to need an API key, you're on the wrong path.
|
|
69
|
-
5. **Read what's already known before writing game code.** First this game's
|
|
70
|
-
own **`handbook/`** (its locked truths — qa.md, feel.md, design.md), then
|
|
71
|
-
the engine craft docs at
|
|
72
|
-
`node_modules/@looop-games/engine/shared/practices/` — `feel.md`,
|
|
73
|
-
`architecture.md`, `multiplayer.md`, `qa.md` (and `eval.md` when anything
|
|
74
|
-
is LLM-generated), which apply to every game.
|
|
75
|
-
The handbook augments the generic docs; where they touch the same topic,
|
|
76
|
-
the handbook is this game's answer.
|
|
77
|
-
6. **Anything with an assertion is a regression test.** Playwright checks live
|
|
78
|
-
in `<aspect>.smoke.mjs` files (excluded from publish, discovered by
|
|
79
|
-
`looop test`). Keep them; delete only assertion-free scratch.
|
|
80
|
-
|
|
81
|
-
## This repo's own knowledge
|
|
82
|
-
|
|
83
|
-
- **`handbook/`** — durable truth about THIS game: `qa.md` (its checks),
|
|
84
|
-
`feel.md` (locked feel values), `design.md` (its pillars). Consult it before
|
|
85
|
-
working; grow it with `/handbook`.
|
|
86
|
-
- **`notes/`** — work tracking: `notes/plans/` (what's being built — `/build`
|
|
87
|
-
runs from these), `notes/todos/` (captured bugs/ideas), and
|
|
88
|
-
`notes/feedback/` (reports for the Looop team, written by `/feedback`).
|
|
89
|
-
Nothing under `notes/` is ever published.
|
|
90
|
-
|
|
91
|
-
<!-- looop:managed:end -->
|
|
92
|
-
|
|
93
|
-
<!-- Your own instructions go below this line. -->
|
package/template/CLAUDE.md
DELETED
package/template/GEMINI.md
DELETED
package/template/boot.smoke.mjs
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// Boot smoke — the game's first regression test, shipped with the scaffold.
|
|
2
|
-
// Proves the dev stack serves the game page AND the engine alias (a plain
|
|
3
|
-
// static server would 404 every /shared/... import and the game would
|
|
4
|
-
// silently never boot). Runs under `npx looop test`, which boots a real dev
|
|
5
|
-
// stack and exports its URL. Grow your own checks as `<aspect>.smoke.mjs`
|
|
6
|
-
// files next to this one.
|
|
7
|
-
const url = process.env.LOOOP_TEST_GAME_URL || process.env.URL;
|
|
8
|
-
if (!url) {
|
|
9
|
-
console.error('boot smoke: no game URL — run this via `npx looop test`.');
|
|
10
|
-
process.exit(1);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const page = await fetch(url);
|
|
14
|
-
if (!page.ok) throw new Error(`game page: ${page.status} for ${url}`);
|
|
15
|
-
if (!(await page.text()).includes('<canvas')) throw new Error('game page served, but no canvas in it');
|
|
16
|
-
|
|
17
|
-
const engineModule = new URL('/shared/ui/room/client.js', url);
|
|
18
|
-
const engine = await fetch(engineModule);
|
|
19
|
-
if (!engine.ok) throw new Error(`engine alias broken: ${engineModule.pathname} → ${engine.status}`);
|
|
20
|
-
|
|
21
|
-
console.log('boot smoke: game page + engine alias OK');
|
package/template/game.js
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
// A tiny multiplayer plaza — walk around, see everyone else.
|
|
2
|
-
// This is a REAL Looop game: it already syncs across browsers and works
|
|
3
|
-
// published. Reshape it into your game; the room + identity plumbing stays.
|
|
4
|
-
import { createRoom, getIdentity } from '/shared/ui/room/client.js';
|
|
5
|
-
|
|
6
|
-
const me = getIdentity(); // injected by the platform (fails closed if absent)
|
|
7
|
-
const canvas = document.getElementById('game');
|
|
8
|
-
const ctx = canvas.getContext('2d');
|
|
9
|
-
const size = () => { canvas.width = innerWidth; canvas.height = innerHeight; };
|
|
10
|
-
addEventListener('resize', size); size();
|
|
11
|
-
|
|
12
|
-
const self = { name: me.name, color: me.color, x: Math.random() * 400 - 200, y: Math.random() * 400 - 200 };
|
|
13
|
-
// One room per game: the slug (injected by the serve path) is the room id.
|
|
14
|
-
const room = createRoom({ room: window.GAME_SLUG ?? 'dev', self });
|
|
15
|
-
|
|
16
|
-
let others = new Map();
|
|
17
|
-
room.onPlayers = (players) => { others = new Map(players); };
|
|
18
|
-
// Handy for smokes: how many players does this client see (self included)?
|
|
19
|
-
window.__looopPlayers = () => others.size + 1;
|
|
20
|
-
|
|
21
|
-
const keys = new Set();
|
|
22
|
-
addEventListener('keydown', (e) => keys.add(e.key.toLowerCase()));
|
|
23
|
-
addEventListener('keyup', (e) => keys.delete(e.key.toLowerCase()));
|
|
24
|
-
|
|
25
|
-
const SPEED = 220; // px/s — tune the feel!
|
|
26
|
-
let last = performance.now();
|
|
27
|
-
function tick(now) {
|
|
28
|
-
const dt = Math.min((now - last) / 1000, 0.05); last = now;
|
|
29
|
-
const dx = (keys.has('d') || keys.has('arrowright')) - (keys.has('a') || keys.has('arrowleft'));
|
|
30
|
-
const dy = (keys.has('s') || keys.has('arrowdown')) - (keys.has('w') || keys.has('arrowup'));
|
|
31
|
-
if (dx || dy) {
|
|
32
|
-
const n = Math.hypot(dx, dy);
|
|
33
|
-
self.x += (dx / n) * SPEED * dt;
|
|
34
|
-
self.y += (dy / n) * SPEED * dt;
|
|
35
|
-
room.update({ x: self.x, y: self.y });
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
ctx.fillStyle = '#0f1220';
|
|
39
|
-
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
40
|
-
const cx = canvas.width / 2, cy = canvas.height / 2;
|
|
41
|
-
const drawAvatar = (p) => {
|
|
42
|
-
ctx.fillStyle = p.color ?? '#888';
|
|
43
|
-
ctx.fillRect(cx + (p.x ?? 0) - self.x - 14, cy + (p.y ?? 0) - self.y - 14, 28, 28);
|
|
44
|
-
ctx.fillStyle = '#e7e9f4';
|
|
45
|
-
ctx.font = '12px system-ui';
|
|
46
|
-
ctx.textAlign = 'center';
|
|
47
|
-
ctx.fillText(p.name ?? '?', cx + (p.x ?? 0) - self.x, cy + (p.y ?? 0) - self.y - 22);
|
|
48
|
-
};
|
|
49
|
-
for (const p of others.values()) drawAvatar(p);
|
|
50
|
-
drawAvatar(self);
|
|
51
|
-
requestAnimationFrame(tick);
|
|
52
|
-
}
|
|
53
|
-
requestAnimationFrame(tick);
|
package/template/gitignore
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# Design — the pillars
|
|
2
|
-
|
|
3
|
-
The rules that follow from the vision: the principles new ideas get checked
|
|
4
|
-
against. Written as
|
|
5
|
-
they emerge from real decisions (via `/handbook`), not invented up
|
|
6
|
-
front — an empty file is honest; a speculative pillar is a trap.
|
|
7
|
-
|
|
8
|
-
_No pillars yet._
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
# Feel — locked values
|
|
2
|
-
|
|
3
|
-
Feel calls the creator has blessed: the value, where it lives, and why it's
|
|
4
|
-
right — so no future session "improves" them away. General feel craft lives in
|
|
5
|
-
the engine's `shared/practices/feel.md`; this file is only what THIS game has
|
|
6
|
-
locked. `/handbook` adds entries.
|
|
7
|
-
|
|
8
|
-
_Nothing locked yet._
|
package/template/handbook/qa.md
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# QA — this game's own checks
|
|
2
|
-
|
|
3
|
-
Checks THIS game has earned, on top of the engine's master list
|
|
4
|
-
(`node_modules/@looop-games/engine/shared/practices/qa.md`) and the executable
|
|
5
|
-
tests `npx looop test` discovers. `/qa` runs all three sources; `/handbook`
|
|
6
|
-
adds entries here when a playtest catches something no automated check saw —
|
|
7
|
-
but prefer writing a `*.smoke.mjs` when the check can be executed.
|
|
8
|
-
|
|
9
|
-
_No game-specific checks yet. The first playtest catch starts this list._
|
package/template/index.html
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
<!doctype html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="utf-8">
|
|
5
|
-
<!-- Mobile baseline (engine practices, architecture.md): locked viewport,
|
|
6
|
-
no double-tap/pinch zoom, no text selection on game UI. Required on
|
|
7
|
-
every Looop game — keep all four pieces when reshaping this file. -->
|
|
8
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
|
9
|
-
<title>{{name}}</title>
|
|
10
|
-
<style>
|
|
11
|
-
html, body { margin: 0; height: 100%; background: #0f1220; overflow: hidden; touch-action: manipulation; }
|
|
12
|
-
* { -webkit-user-select: none; user-select: none; -webkit-touch-callout: none; -webkit-tap-highlight-color: transparent; }
|
|
13
|
-
input, textarea, [contenteditable] { -webkit-user-select: text; user-select: text; }
|
|
14
|
-
canvas { display: block; width: 100vw; height: 100vh; }
|
|
15
|
-
</style>
|
|
16
|
-
<script>
|
|
17
|
-
// Safari ignores user-scalable=no — block iOS gesture-zoom too.
|
|
18
|
-
for (const ev of ['gesturestart', 'gesturechange', 'gestureend'])
|
|
19
|
-
document.addEventListener(ev, (e) => e.preventDefault());
|
|
20
|
-
</script>
|
|
21
|
-
</head>
|
|
22
|
-
<body>
|
|
23
|
-
<canvas id="game"></canvas>
|
|
24
|
-
<script type="module" src="game.js"></script>
|
|
25
|
-
</body>
|
|
26
|
-
</html>
|