juicyjuicy 0.15.0 → 0.16.0

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/dist/index.js CHANGED
@@ -19807,7 +19807,7 @@ function date4(params) {
19807
19807
  }
19808
19808
 
19809
19809
  // ../shared/src/cli.ts
19810
- var CLI_RELEASE = "0.15.0";
19810
+ var CLI_RELEASE = "0.16.0";
19811
19811
  function cliCommand(args) {
19812
19812
  return `npx -y juicyjuicy@${CLI_RELEASE} ${args}`;
19813
19813
  }
@@ -19924,7 +19924,7 @@ var LIMITS = {
19924
19924
  var PUBLISH_LIMIT_WINDOW_HOURS = 24;
19925
19925
  var RATE_LIMITS = {
19926
19926
  /** New accounts, site-wide. */
19927
- signUp: { limit: 30, windowSeconds: 60 * 60 },
19927
+ signUp: { limit: 60, windowSeconds: 60 * 60 },
19928
19928
  /** New accounts per client IP (hashed). */
19929
19929
  signUpNetwork: { limit: 3, windowSeconds: 24 * 60 * 60 },
19930
19930
  /** Verification and password-reset mails per address (D88), keyed by a hash of it. */
@@ -20012,6 +20012,16 @@ var UPLOAD_BUDGET = {
20012
20012
  /** Files one member may upload in the last 24 hours: 5 publishes at `LIMITS.maxFiles`. */
20013
20013
  memberFilesPerDay: 15e3
20014
20014
  };
20015
+ var PROVIDER_ALLOWANCES = {
20016
+ /** Neon Free: past it the compute is suspended until the next billing period. */
20017
+ neonComputeHoursPerMonth: 100,
20018
+ /** Neon Free: past it writes fail. */
20019
+ neonStorageBytes: 1024 * MB,
20020
+ /** Neon Free: past it the compute is suspended, as for CU-hours. */
20021
+ neonEgressBytesPerMonth: 5 * 1024 * MB,
20022
+ /** Cloudflare Email Service on Workers Paid; past it $0.35 per 1,000. */
20023
+ mailIncludedPerMonth: 3e3
20024
+ };
20015
20025
  var THUMBNAIL_EXTENSIONS = ["png", "jpg", "jpeg", "webp"];
20016
20026
 
20017
20027
  // ../shared/src/genres.ts
@@ -20774,14 +20784,14 @@ var GAME_FEATURES = [
20774
20784
  },
20775
20785
  {
20776
20786
  id: "touch",
20777
- name: "Touch controls",
20778
- summary: "The game plays on phones and tablets, with on-screen controls or taps and swipes.",
20779
- adds: "On-screen controls, or taps and swipes, so the game plays on phones and tablets.",
20780
- why: "Many players open links on their phone; without touch they can only watch.",
20781
- detection: { kind: "declared", input: "touch" },
20787
+ name: "Mobile optimization",
20788
+ summary: "The game plays well on phones and tablets: touch controls, screen fit, sound, speed.",
20789
+ adds: "Touch controls that fit the game, a layout that fits a phone screen in either orientation and survives rotating, menus that work by tap, and sound and speed that work on phones.",
20790
+ why: "Many players open links on their phone; without this they may only be able to watch, or get a game that's cut off, tiny or silent.",
20791
+ detection: { kind: "declared", input: "touch", addUntilDeclared: true },
20782
20792
  status: "available",
20783
20793
  recipe: "reference/touch.md",
20784
- task: "add touch controls so the game plays on phones and tablets",
20794
+ task: "optimize the game for mobile so it plays well on phones and tablets: touch controls, screen fit in both orientations, sound and speed",
20785
20795
  docs: "/docs/publish#touch"
20786
20796
  },
20787
20797
  {
@@ -20796,18 +20806,6 @@ var GAME_FEATURES = [
20796
20806
  task: "add gamepad support so the game plays with a controller",
20797
20807
  docs: "/docs/publish#gamepad"
20798
20808
  },
20799
- {
20800
- id: "mouse-lock",
20801
- name: "Mouse lock",
20802
- summary: "Aiming or steering with the mouse keeps the cursor in the game.",
20803
- adds: "Games that aim or steer with the mouse lock the cursor (Pointer Lock), so it can't leave the game.",
20804
- why: "Without it, turning fast drags the cursor out of the frame or onto another screen, and the game stops responding.",
20805
- detection: { kind: "declared" },
20806
- status: "available",
20807
- recipe: "reference/mouse-lock.md",
20808
- task: "add mouse lock so aiming or steering with the mouse keeps the cursor in the game",
20809
- docs: "/docs/publish#mouse-lock"
20810
- },
20811
20809
  {
20812
20810
  id: "auto-pause",
20813
20811
  name: "Auto-pause",
@@ -20821,6 +20819,7 @@ var GAME_FEATURES = [
20821
20819
  docs: "/docs/publish#auto-pause"
20822
20820
  }
20823
20821
  ];
20822
+ var RETIRED_FEATURES = ["mouse-lock"];
20824
20823
  function gameFeature(id) {
20825
20824
  return GAME_FEATURES.find((feature) => feature.id === id);
20826
20825
  }
@@ -20850,9 +20849,9 @@ function featureState(feature, input2) {
20850
20849
  };
20851
20850
  }
20852
20851
  case "declared": {
20853
- const { input: inputKind } = feature.detection;
20852
+ const { input: inputKind, addUntilDeclared } = feature.detection;
20854
20853
  if (inputKind && input2.input?.includes(inputKind)) {
20855
- return { feature, status: "on", canAdd: false };
20854
+ return { feature, status: "on", canAdd: Boolean(addUntilDeclared) && !declared };
20856
20855
  }
20857
20856
  if (input2.declared === null) return { feature, status: "unknown", canAdd: false };
20858
20857
  return { feature, status: declared ? "on" : "off", canAdd: !declared };
@@ -20867,6 +20866,7 @@ function featureState(feature, input2) {
20867
20866
  function featureFindings(input2) {
20868
20867
  const findings = [];
20869
20868
  for (const id of input2.declared) {
20869
+ if (RETIRED_FEATURES.includes(id)) continue;
20870
20870
  const feature = gameFeature(id);
20871
20871
  if (!feature) {
20872
20872
  findings.push({
@@ -27792,7 +27792,7 @@ var program = new Command();
27792
27792
  // package.json
27793
27793
  var package_default = {
27794
27794
  name: "juicyjuicy",
27795
- version: "0.15.0",
27795
+ version: "0.16.0",
27796
27796
  description: "Publish AI-made browser games to juicyjuicy.games",
27797
27797
  type: "module",
27798
27798
  license: "MIT",
@@ -27882,6 +27882,14 @@ The sign-in page says why and how to contest it.`;
27882
27882
  It won't be shown. Upload a different image to replace it.`;
27883
27883
  case "profile_moderated":
27884
27884
  return `JuicyJuicy's moderators removed ${note.subject ?? "something from your profile"}:${quoted(note.comment)}`;
27885
+ // Only the site's banner shows these (D108); a publish never sends them.
27886
+ case "version_held":
27887
+ case "report_held":
27888
+ case "image_held":
27889
+ case "score_removed":
27890
+ case "account_changed":
27891
+ case "game_edited":
27892
+ return `A notice about ${game} is waiting on the JuicyJuicy site:${quoted(note.comment ?? "see the banner there")}`;
27885
27893
  }
27886
27894
  }
27887
27895
  function noteMessages(moderation, warnings, slug) {
@@ -9,22 +9,22 @@ JuicyJuicy hosts **static browser games** (HTML/JS/CSS/assets, WebAssembly) and
9
9
 
10
10
  Run every CLI command with `--json`. Each output line is a JSON object; the **last** line is the result, either `{"ok": true, …}` or `{"ok": false, "error": {"code", "message", "details"?, "hint"?}}`. The exit code is `0` (ok), `1` (fix the game or `juicy.json`), `2` (log in) or `3` (network or server problem, so retry).
11
11
 
12
- Run the CLI exactly as written here, `npx -y juicyjuicy@0.15.0 …`: this skill is made for that release. If JuicyJuicy answers that the CLI is out of date, run the command its message gives. It updates the CLI and this skill together; read this skill again afterwards and use the release it names from then on.
12
+ Run the CLI exactly as written here, `npx -y juicyjuicy@0.16.0 …`: this skill is made for that release. If JuicyJuicy answers that the CLI is out of date, run the command its message gives. It updates the CLI and this skill together; read this skill again afterwards and use the release it names from then on.
13
13
 
14
14
  ## Procedure
15
15
 
16
16
  ### 1. Make sure the CLI is logged in
17
17
 
18
18
  ```bash
19
- npx -y juicyjuicy@0.15.0 whoami --json
19
+ npx -y juicyjuicy@0.16.0 whoami --json
20
20
  ```
21
21
 
22
22
  If it exits with `2`, log in. `login` opens the user's browser and **waits up to 10 minutes** for them to approve:
23
23
 
24
- - Run `npx -y juicyjuicy@0.15.0 login --json` in the background if your shell can, and read its first output line (`{"event": "device_code", "userCode", "verificationUrl"}`) right away, and show the user the code and link without waiting for them to ask: the page shows the same code, and that's how they know the login is theirs.
24
+ - Run `npx -y juicyjuicy@0.16.0 login --json` in the background if your shell can, and read its first output line (`{"event": "device_code", "userCode", "verificationUrl"}`) right away, and show the user the code and link without waiting for them to ask: the page shows the same code, and that's how they know the login is theirs.
25
25
  - Then tell the user, with the real code and link: *"A JuicyJuicy page opened in your browser. Check it shows the code **WXTZ-BCDF** and click **Approve**. If no page opened, go to <verificationUrl>."*
26
26
  - If your shell can't run commands in the background, say that first (without a code: it doesn't exist yet), then run it in the foreground with a timeout of at least 10 minutes. The code is also printed as `Your code: …`.
27
- - The user needs a JuicyJuicy account (it's invite-only for now). If the page says they can't sign in, they need an invite link from a friend who's a member.
27
+ - The user needs a JuicyJuicy account. If they don't have one yet, they can make one on the sign-in page that opens.
28
28
 
29
29
  In CI there's no browser: the `JUICY_TOKEN` environment variable holds a token instead.
30
30
 
@@ -45,7 +45,7 @@ Files and folders starting with `.` (`.env`, `.git/`, …) and `node_modules/` a
45
45
  `juicy.json` sits in the project folder and describes the game. Create or update it with `init`, filling in what you know from the game you built:
46
46
 
47
47
  ```bash
48
- npx -y juicyjuicy@0.15.0 init --yes --json \
48
+ npx -y juicyjuicy@0.16.0 init --yes --json \
49
49
  --title "Snake Deluxe" \
50
50
  --description "Eat the apples, don't bite your tail. Arrow keys or swipe." \
51
51
  --made-with "<your model name>, <the tool you run in>" \
@@ -127,7 +127,7 @@ To replace it, the user saves a screenshot as `thumbnail.png` in the project fol
127
127
 
128
128
  Do this **every time**, also for updates. It protects the user and their players; JuicyJuicy's server runs its own checks as well, so this is help, not a formality to get past.
129
129
 
130
- 1. **Check.** Run `npx -y juicyjuicy@0.15.0 check --json`. Errors (a failed result) must be fixed: explain each one (`details` has a `rule` and a `path`), and offer to fix it. If the user says no, **stop and don't publish**: the server would refuse it anyway. Warnings in the result (`findings`) go into step 2.
130
+ 1. **Check.** Run `npx -y juicyjuicy@0.16.0 check --json`. Errors (a failed result) must be fixed: explain each one (`details` has a `rule` and a `path`), and offer to fix it. If the user says no, **stop and don't publish**: the server would refuse it anyway. Warnings in the result (`findings`) go into step 2.
131
131
  Every file in the result's `files` will be public. If `unusualFiles` isn't empty (names that look like keys or private data, project files like `package.json`, notes and other files a web game doesn't load), **show the user that list and ask whether those files should be public** before publishing; offer to move them out of the build folder or set `buildDir` to a folder with just the game.
132
132
  2. **Security review.** Follow [reference/review.md](reference/review.md): keys and tokens, `innerHTML`/`eval` on network or multiplayer data, `http://` loads, trackers, login or payment forms, anything that leaves the page. Same rule: for what it marks as must-fix, fix it or don't publish.
133
133
  3. **Standards.** Check the game against these and tell the user, in a sentence or two, what falls short; offer to fix it. A "no" still publishes: not every game can have touch controls.
@@ -142,7 +142,7 @@ Do this **every time**, also for updates. It protects the user and their players
142
142
  - Saves progress with JuicyJuicy's window.juicy snippet, if it saves at all, so it follows signed-in players to any browser (not cookies: the play domain clears them).
143
143
  - A Quit or Exit button, if it has one, sends JuicyJuicy's quit message instead of calling window.close().
144
144
 
145
- The game runs in a frame on JuicyJuicy, inside a page. The frame can start at 0×0 or be hidden: when `innerWidth` or `innerHeight` is 0, pause and wait for a `resize` instead of restarting, and give every loop that searches (a free cell for food, a spawn point) a way out. A game stuck in a loop stops every other game in the player's tab from loading. Mouse events stop when the cursor leaves the frame: call `canvas.setPointerCapture(event.pointerId)` on `pointerdown` so a drag keeps getting `pointermove` and `pointerup` outside it. A game that aims or steers with the mouse (a shooter, a camera, a racer) calls `canvas.requestPointerLock()` on a click and reads `event.movementX`/`movementY`: the cursor hides and can't leave the game, not even onto a second screen in fullscreen, and Esc gives it back. Ask for the lock again on the next click.
145
+ The game runs in a frame on JuicyJuicy, inside a page. The frame can start at 0×0 or be hidden: when `innerWidth` or `innerHeight` is 0, pause and wait for a `resize` instead of restarting, and give every loop that searches (a free cell for food, a spawn point) a way out. A game stuck in a loop stops every other game in the player's tab from loading. Mouse events stop when the cursor leaves the frame: call `canvas.setPointerCapture(event.pointerId)` on `pointerdown` so a drag keeps getting `pointermove` and `pointerup` outside it. A game that aims or steers with the mouse (a shooter, a camera, a racer) calls `canvas.requestPointerLock()` on a click and reads `event.movementX`/`movementY`: the cursor hides and can't leave the game, not even onto a second screen in fullscreen, and Esc gives it back. Ask for the lock again on the next click. [reference/mouse-lock.md](reference/mouse-lock.md) has the whole recipe (pausing when the lock ends, menus, engines).
146
146
 
147
147
  Esc belongs to the browser: it leaves fullscreen and Pointer Lock before the game sees it. `navigator.keyboard.lock()`, which hands Esc to a page in fullscreen, fails inside a frame, so don't rely on Esc alone: give pause or the menu another key too (like P).
148
148
 
@@ -156,7 +156,7 @@ Keep it short when there's nothing to say: "Checked: no problems found" is enoug
156
156
  ### 6. Publish
157
157
 
158
158
  ```bash
159
- npx -y juicyjuicy@0.15.0 publish --json --message "<what changed, in a few words>"
159
+ npx -y juicyjuicy@0.16.0 publish --json --message "<what changed, in a few words>"
160
160
  ```
161
161
 
162
162
  This makes the new version live: when the user says "publish", that's what they want. Don't add `--draft` (see "Other commands") unless they asked in words for it not to go live yet. Not for a first publish, a big game or one you're unsure of either: the user would have to find the dashboard and make it live themselves.
@@ -186,14 +186,13 @@ The game's page under My games on JuicyJuicy has an **Upgrades** panel; its Add
186
186
  | Cloud saves | [reference/cloud-saves.md](reference/cloud-saves.md) | `"features": ["cloud-saves"]` |
187
187
  | Gamepad support | [reference/gamepad.md](reference/gamepad.md) | `"input": [… "gamepad"]` and `"features": ["gamepad"]` |
188
188
  | Global highscores | [reference/highscores.md](reference/highscores.md) | `"features": ["highscores"]` |
189
- | Mouse lock | [reference/mouse-lock.md](reference/mouse-lock.md) | `"features": ["mouse-lock"]` |
189
+ | Mobile optimization | [reference/touch.md](reference/touch.md) | `"input": [… "touch"]` and `"features": ["touch"]` |
190
190
  | Multi-language | [reference/languages.md](reference/languages.md) | `"languages": ["en", …]` |
191
191
  | Multiplayer | [reference/multiplayer.md](reference/multiplayer.md) | `"features": ["multiplayer"]` |
192
- | Touch controls | [reference/touch.md](reference/touch.md) | `"input": [… "touch"]` and `"features": ["touch"]` |
193
192
 
194
193
  An upgrade that doesn't fit the game (mouse lock for a game played by clicking, auto-pause for a turn-based one) is the recipe's to say: tell the user why, and change nothing unless they still want it.
195
194
 
196
- 1. **Find the project.** Log in first (step 1). The project is the folder whose `juicy.json` has the game's `slug`: the current folder, or one the user points you to. **If it isn't on this computer** (or you're in a chat app or sandbox), download the live version: `npx -y juicyjuicy@0.15.0 pull <slug> --json` writes it into a new folder named after the slug (`dir` in the result) with its `juicy.json`, and you work there. Read the result's `next`: the download is the built game, so for a bundled one (minified code), ask whether the user has the source somewhere, or use `sourceUrl`, before changing the bundle itself. `pull` only works for the user's own games (`NOT_FOUND` otherwise).
195
+ 1. **Find the project.** Log in first (step 1). The project is the folder whose `juicy.json` has the game's `slug`: the current folder, or one the user points you to. **If it isn't on this computer** (or you're in a chat app or sandbox), download the live version: `npx -y juicyjuicy@0.16.0 pull <slug> --json` writes it into a new folder named after the slug (`dir` in the result) with its `juicy.json`, and you work there. Read the result's `next`: the download is the built game, so for a bundled one (minified code), ask whether the user has the source somewhere, or use `sourceUrl`, before changing the bundle itself. `pull` only works for the user's own games (`NOT_FOUND` otherwise).
197
196
  2. **Read the recipe** for the upgrade and make the change. Keep everything else about the game as it is.
198
197
  3. **Build and try it** (step 2), then **declare it** in `juicy.json` as the table says, adding to what's already listed.
199
198
  4. **Review and publish** as steps 5–7 say, with `--message "Add <upgrade>"`. In step 5's `check` result, `features.detected` lists what JuicyJuicy found in the files; a `feature-missing` warning means a declared upgrade isn't there yet: fix it before publishing.
@@ -201,14 +200,14 @@ An upgrade that doesn't fit the game (mouse lock for a game played by clicking,
201
200
 
202
201
  ## Bring all my games up to date
203
202
 
204
- When the user asks to bring their JuicyJuicy games up to date or fix a **Needs update** badge, when `check` or `publish` warns `requirement-missing`, or after updating the CLI: follow [reference/update-all-games.md](reference/update-all-games.md). It starts with `npx -y juicyjuicy@0.15.0 check-games --json`, which checks every game of the user's at once and settles what a script can; the rest are code changes, one game at a time.
203
+ When the user asks to bring their JuicyJuicy games up to date or fix a **Needs update** badge, when `check` or `publish` warns `requirement-missing`, or after updating the CLI: follow [reference/update-all-games.md](reference/update-all-games.md). It starts with `npx -y juicyjuicy@0.16.0 check-games --json`, which checks every game of the user's at once and settles what a script can; the rest are code changes, one game at a time.
205
204
 
206
205
  ## Remix a game
207
206
 
208
207
  A game marked remixable on JuicyJuicy can be downloaded, changed and published as a new game that links back to it. Log in first (step 1), then:
209
208
 
210
209
  ```bash
211
- npx -y juicyjuicy@0.15.0 remix <slug> --json
210
+ npx -y juicyjuicy@0.16.0 remix <slug> --json
212
211
  ```
213
212
 
214
213
  - The first line is `{"event": "license", "name", "text"}`: the remix license. Tell the user in a sentence what it says. In short: the remix may be published **on JuicyJuicy only**, never anywhere else, and the files aren't to be passed on. The full text is:
@@ -244,10 +243,10 @@ Everything else is in [reference/troubleshooting.md](reference/troubleshooting.m
244
243
 
245
244
  ## Other commands
246
245
 
247
- - `npx -y juicyjuicy@0.15.0 list --json`: the user's games; `list <slug>` shows one game's versions. `--all` lists every game on JuicyJuicy instead (60 a page, `--page 2` and on; `nextPage` in the result), `--owner <username>` another creator's. Narrow and order any list with `--search <title text>`, `--genre` (one of the genre ids above, or several: `board,cards-casino`), `--tag` (a theme), `--engine`, `--remixes`, `--multiplayer` (games with rooms), `--highscores` (games with a leaderboard), `--sort newest|upvoted|downvoted|played` and `--period 24h|week|month|all`, e.g. to find a game to remix.
248
- - `npx -y juicyjuicy@0.15.0 publish --draft --json`: uploads a new version without making it live (players keep the current one). Only when the user asks for that in words (e.g. "upload it but don't put it live yet"), never on your own judgment. The result has `draft: true`; when they say so, make it live with `go-live`, or they can on the dashboard. Only the newest draft can go live.
249
- - `npx -y juicyjuicy@0.15.0 go-live --slug <slug> --json`: makes the game's newest draft live, only when the user says so. Pass the draft's number (`go-live <version>`) when they named one, so a newer draft never goes live by surprise. A draft waiting for an admin's review can't go live yet (`FORBIDDEN`), and nor can one a moderator rejected (`VERSION_REJECTED`) or asked changes to (`CHANGES_REQUESTED`). There's no command to go back to an earlier version: when the user asks for one, explain that JuicyJuicy doesn't keep version history, and offer to fix the game or publish their own earlier files as a new version.
250
- - `npx -y juicyjuicy@0.15.0 recheck <slug> --reply "<what changed>" --json`: asks JuicyJuicy's moderators for another look at a game they blocked, once the user fixed it (see [reference/moderation.md](reference/moderation.md)). Only with the user's OK.
251
- - `npx -y juicyjuicy@0.15.0 pull <slug> --json`: downloads the live version of one of the user's own games into a new folder with its `juicy.json`, to change it when the project isn't on this computer (see "Add a JuicyJuicy upgrade").
252
- - `npx -y juicyjuicy@0.15.0 check-games --json`: checks all the user's games against JuicyJuicy's features and required updates, declares the features it finds where a game never declared them, and lists what still needs a change (see "Bring all my games up to date").
253
- - `npx -y juicyjuicy@0.15.0 open <slug>`: opens the game page in the browser.
246
+ - `npx -y juicyjuicy@0.16.0 list --json`: the user's games; `list <slug>` shows one game's versions. `--all` lists every game on JuicyJuicy instead (60 a page, `--page 2` and on; `nextPage` in the result), `--owner <username>` another creator's. Narrow and order any list with `--search <title text>`, `--genre` (one of the genre ids above, or several: `board,cards-casino`), `--tag` (a theme), `--engine`, `--remixes`, `--multiplayer` (games with rooms), `--highscores` (games with a leaderboard), `--sort newest|upvoted|downvoted|played` and `--period 24h|week|month|all`, e.g. to find a game to remix.
247
+ - `npx -y juicyjuicy@0.16.0 publish --draft --json`: uploads a new version without making it live (players keep the current one). Only when the user asks for that in words (e.g. "upload it but don't put it live yet"), never on your own judgment. The result has `draft: true`; when they say so, make it live with `go-live`, or they can on the dashboard. Only the newest draft can go live.
248
+ - `npx -y juicyjuicy@0.16.0 go-live --slug <slug> --json`: makes the game's newest draft live, only when the user says so. Pass the draft's number (`go-live <version>`) when they named one, so a newer draft never goes live by surprise. A draft waiting for an admin's review can't go live yet (`FORBIDDEN`), and nor can one a moderator rejected (`VERSION_REJECTED`) or asked changes to (`CHANGES_REQUESTED`). There's no command to go back to an earlier version: when the user asks for one, explain that JuicyJuicy doesn't keep version history, and offer to fix the game or publish their own earlier files as a new version.
249
+ - `npx -y juicyjuicy@0.16.0 recheck <slug> --reply "<what changed>" --json`: asks JuicyJuicy's moderators for another look at a game they blocked, once the user fixed it (see [reference/moderation.md](reference/moderation.md)). Only with the user's OK.
250
+ - `npx -y juicyjuicy@0.16.0 pull <slug> --json`: downloads the live version of one of the user's own games into a new folder with its `juicy.json`, to change it when the project isn't on this computer (see "Add a JuicyJuicy upgrade").
251
+ - `npx -y juicyjuicy@0.16.0 check-games --json`: checks all the user's games against JuicyJuicy's features and required updates, declares the features it finds where a game never declared them, and lists what still needs a change (see "Bring all my games up to date").
252
+ - `npx -y juicyjuicy@0.16.0 open <slug>`: opens the game page in the browser.
@@ -16,4 +16,4 @@ JuicyJuicy offers English, French, Italian, German, Spanish and Polish (`en`, `f
16
16
  Run the game, switch to each language and go through the menus and the first minute of play: no missing keys (an untranslated key shows up as the key's name), no clipped text, and the choice is still there after a reload.
17
17
 
18
18
  ## Declare it
19
- Set `languages` in `juicy.json` to every language the game now has, English first, e.g. `"languages": ["en", "fr", "pl"]` (or `npx -y juicyjuicy@0.15.0 init --yes --json --languages en,fr,pl`). Don't add it to `features`: JuicyJuicy reads `languages`, and shows the game in each language's filter.
19
+ Set `languages` in `juicy.json` to every language the game now has, English first, e.g. `"languages": ["en", "fr", "pl"]` (or `npx -y juicyjuicy@0.16.0 init --yes --json --languages en,fr,pl`). Don't add it to `features`: JuicyJuicy reads `languages`, and shows the game in each language's filter.
@@ -40,7 +40,7 @@ The manifest in the project folder. `juicyjuicy init` writes it; edit it by hand
40
40
  | `tags` | | Themes: up to 3 lowercase words or `word-word` for the setting or subject, e.g. `space`, `pirates`, `blackjack`. Not genres or features (touch, remixable: JuicyJuicy shows those by itself), except `multiplayer`, not counted among the 3, for a game players play together without JuicyJuicy's rooms (its own netcode, or one screen). JuicyJuicy cleans them up when saving (spellings fixed, `co-op`, `pvp` and the like saved as `multiplayer`, genre and other feature words moved out, at most 3 themes kept; `check` warns with what it will save). | `--tags a,b` |
41
41
  | `ageRating` | | Who the game is for: `everyone`, `12+` or `16+`, from its content (SKILL.md step 3 says how to pick). Rate every game you publish. Left out, a publish keeps the game's rating; a rating set by JuicyJuicy's moderators wins over this one, and `publish` writes it here. | `--age-rating` |
42
42
  | `languages` | | Up to 10 language codes a player can switch between, English first, e.g. `["en", "pl"]`. Default `["en"]`. Shown as a language filter on JuicyJuicy. | `--languages a,b` |
43
- | `features` | yes | The JuicyJuicy upgrades the game has, by id: `auto-pause`, `browser-menu`, `cloud-saves`, `gamepad`, `highscores`, `mouse-lock`, `multiplayer`, `touch`; `[]` for none. Required: `check` and `publish` stop without it, and `init` adds `[]`. Add an id only once the game has the feature (see SKILL.md, "Add a JuicyJuicy upgrade"); `check` warns when one is listed but missing. Multi-language is `languages`, not a feature id; `touch` and `gamepad` go in `input` too. | |
43
+ | `features` | yes | The JuicyJuicy upgrades the game has, by id: `auto-pause`, `browser-menu`, `cloud-saves`, `gamepad`, `highscores`, `multiplayer`, `touch`; `[]` for none. Required: `check` and `publish` stop without it, and `init` adds `[]`. Add an id only once the game has the feature (see SKILL.md, "Add a JuicyJuicy upgrade"); `check` warns when one is listed but missing. Multi-language is `languages`, not a feature id; `touch` and `gamepad` go in `input` too. | |
44
44
  | `input` | | Any of `keyboard`, `mouse`, `touch`, `gamepad`. | `--input a,b` |
45
45
  | `orientation` | | `landscape`, `portrait` or `any` (default). | `--orientation` |
46
46
  | `aspectRatio` | | `"W:H"` like `"16:9"` or `"4:3"`, or `"fill"` to fill the player. Default `16:9`. | `--aspect-ratio` |
@@ -25,7 +25,7 @@ JuicyJuicy's moderators (its admins) can leave the user a written comment about
25
25
  - after a change request or a rejection: run the review steps and publish a new version (`publish --json`, with a `--message` that says what changed). Tell the user it goes through the safety check again and may wait for a moderator.
26
26
  - after a block: publish if the game's files changed (a new title or description in `juicy.json` also goes out with a publish), then ask for another look:
27
27
  ```bash
28
- npx -y juicyjuicy@0.15.0 recheck <slug> --reply "<what the user changed, in their words>" --json
28
+ npx -y juicyjuicy@0.16.0 recheck <slug> --reply "<what the user changed, in their words>" --json
29
29
  ```
30
30
  The game stays unlisted until an admin lists it again; tell the user that. Write the reply with the user, and keep it to what was changed.
31
31
  4. **If the user doesn't want to change it**, stop. A version with changes requested or rejected never goes live, and a blocked game stays unlisted. Don't try to get around the note: don't republish the same thing to clear it, rename the game, or publish it under a new slug.
@@ -1,6 +1,6 @@
1
1
  # Upgrade: mouse lock
2
2
 
3
- Aiming or steering with the mouse keeps the cursor in the game. Read this when the user asks for mouse lock (the JuicyJuicy Upgrades panel's prompt names this file), then follow "Add a JuicyJuicy upgrade" in SKILL.md to publish it.
3
+ Aiming or steering with the mouse keeps the cursor in the game. Read this when the publish standards in SKILL.md find a game that aims or steers with the mouse without locking it, or when the user asks for mouse lock. JuicyJuicy doesn't lock the mouse for a game: the frame only allows it, and the game has to ask. If the user's game needs it, follow "Add a JuicyJuicy upgrade" in SKILL.md to publish the change.
4
4
 
5
5
  ## Why
6
6
  In a game that turns a camera or aims with the mouse, turning fast drags the cursor out of the game's frame, onto the page or another screen, and the game stops getting the movement. Pointer Lock hides the cursor and keeps sending movement however far the mouse goes. JuicyJuicy's frame allows it.
@@ -19,4 +19,4 @@ In a game that turns a camera or aims with the mouse, turning fast drags the cur
19
19
  Run the game: a click locks the cursor and turning works far past the frame's edges; Esc gives the mouse back and the game pauses with "Click to play"; a click resumes; menus can be clicked.
20
20
 
21
21
  ## Declare it
22
- Add `"mouse-lock"` to `features` in `juicy.json` (create the list if it isn't there, adding to what's listed). JuicyJuicy can't detect this one from the files, so the declaration is what turns it On in the panel; only add it once the lock works this way.
22
+ Nothing to declare: mouse lock isn't a row in the Upgrades panel, so leave `features` as it is. A `juicy.json` that already lists `mouse-lock` is fine as it is too.
@@ -1,23 +1,44 @@
1
- # Upgrade: touch controls
1
+ # Upgrade: mobile optimization
2
2
 
3
- The game plays on phones and tablets. Read this when the user asks for touch controls (the JuicyJuicy Upgrades panel's prompt names this file), then follow "Add a JuicyJuicy upgrade" in SKILL.md to publish it.
3
+ The game plays well on phones and tablets: controls, screen, sound and speed. Read this when the user asks for mobile optimization or touch controls (the JuicyJuicy Upgrades panel's prompt names this file), then follow "Add a JuicyJuicy upgrade" in SKILL.md to publish it.
4
4
 
5
5
  ## Why
6
- Many players open a shared link on their phone. JuicyJuicy shows the game there full width, and players without a keyboard or mouse can only watch unless the game takes touches. Games with touch also get the **Touch controls** chip and filter on JuicyJuicy.
6
+ Many players open a shared link on their phone. JuicyJuicy shows the game there in a frame that fills the screen below a slim bar. Players without a keyboard or mouse can only watch unless the game takes touches, and a game laid out for a desktop window ends up cut off or tiny. Games with touch also get the **Touch controls** chip and filter on JuicyJuicy.
7
7
 
8
8
  ## What to change
9
+ Go through every part, also when the game already takes touches: fix what falls short and leave what's already right. Keyboard and mouse players keep the game as it was.
10
+
11
+ ### Controls
9
12
  1. **Pick the controls that fit the game**, and say which to the user before building, in a sentence:
10
13
  - **Taps and drags** for games already played with the mouse (clicks, aiming, dragging pieces): make the pointer code work for touch. Use Pointer Events (`pointerdown`, `pointermove`, `pointerup`) instead of mouse events, so one handler takes both; for multi-touch, track each `pointerId`.
11
14
  - **Swipes** for games with four directions (snake, 2048, tile puzzles): a swipe longer than about 30 CSS pixels in its main direction is the arrow key.
12
- - **On-screen buttons** for games with keys held down (platformers, shooters, racing): a d-pad or a virtual stick on the left, action buttons on the right. Each button is at least 44×44 CSS pixels with space between, semi-transparent over the game, and holding it acts like holding the key (press on `pointerdown`, release on `pointerup`, `pointercancel` and `pointerleave`).
15
+ - **On-screen buttons** for games with keys held down (platformers, shooters, racing): a d-pad or a virtual stick on the left, action buttons on the right. Each button is at least 44×44 CSS pixels with space between, semi-transparent over the game, and holding it acts like holding the key (press on `pointerdown`, release on `pointerup`, `pointercancel` and `pointerleave`). Moving and jumping at once must work: track each `pointerId`.
13
16
  Engines: Phaser has `this.input.on("pointerdown", …)` and `this.input.addPointer()` for more fingers; Godot has `InputEventScreenTouch`/`ScreenDrag` and a `TouchScreenButton` node that triggers an input action; Unity uses its Input System's on-screen controls.
14
- 2. **Show on-screen buttons only on touch screens**: when `matchMedia("(pointer: coarse)").matches`, or from the first `touchstart`/`pointerdown` with `pointerType === "touch"`. Keyboard players keep the screen as it was.
15
- 3. **Stop the browser taking the gestures.** On the game's canvas or container, set the CSS `touch-action: none` and `user-select: none; -webkit-user-select: none; -webkit-touch-callout: none`, so drags don't scroll or zoom the page and a long press doesn't select or open a menu. Keep `<meta name="viewport" content="width=device-width, initial-scale=1">`.
16
- 4. **Fit a phone screen.** The game scales to its frame (no fixed 1280×720 page with scrollbars); text and buttons stay readable in a narrow window. If the game only works one way round, set `orientation` in `juicy.json` (`landscape` or `portrait`).
17
- 5. **Menus and dialogs work by tap**: every key-only action ("press Enter to start", "Space to restart") gets a tap target too, and the help text says "tap" on touch screens.
17
+ 2. **Show on-screen buttons only on touch screens**: when `matchMedia("(pointer: coarse)").matches`, or from the first `touchstart`/`pointerdown` with `pointerType === "touch"`.
18
+ 3. **Stop the browser taking the gestures.** On the game's canvas or container, set the CSS `touch-action: none` and `user-select: none; -webkit-user-select: none; -webkit-touch-callout: none`, so drags don't scroll or zoom the page and a long press doesn't select or open a menu. On `html, body` set `overscroll-behavior: none; overflow: hidden`, unless the game is a page meant to scroll. Keep `<meta name="viewport" content="width=device-width, initial-scale=1">`.
19
+ 4. **Menus and dialogs work by tap**: every key-only action ("press Enter to start", "Space to restart") gets a tap target too, and the help text says "tap" on touch screens. Anything shown only on hover also needs a tap.
20
+
21
+ ### Screen
22
+ 5. **Fit the frame.** Size the game from the frame (`innerWidth` and `innerHeight`, or CSS `100%`): no fixed 1280×720 page with scrollbars, and nothing important cut off at a narrow width.
23
+ 6. **Survive every resize.** Rotating the phone, the browser's address bar sliding away and the on-screen keyboard all resize the frame. Listen for `resize`, lay the game out again and keep playing: never restart the level or lose the score because of it.
24
+ 7. **Stay readable.** Size text and buttons in CSS pixels from the frame, not from the game's internal resolution, so a 1920-wide canvas shrunk to a phone doesn't shrink its text with it: body text at least 14 CSS pixels, tap targets at least 44.
25
+ 8. **Work both ways round if you can**, with a layout for tall and for wide frames, as the shapes are very different. If the game only works one way, set `orientation` in `juicy.json` (`landscape` or `portrait`) and, when the frame is the other way round, pause and show "Rotate your phone" over the game until a `resize` puts it right. Nothing rotates the game for you: `screen.orientation.lock()` doesn't work on iPhones.
26
+ 9. **Keep controls clear.** On-screen controls sit at least 16 CSS pixels from the frame's edges, under the thumbs, and don't cover the player or the action. In a tall frame, put them below the play area when there's room.
27
+ 10. **Never need fullscreen.** iPhone Safari can't put a game in fullscreen: hide the game's own fullscreen button when `document.fullscreenEnabled` is false.
28
+
29
+ ### Phone browsers
30
+ 11. **Sound starts on a tap.** Phones keep audio silent until the player touches the game: create or `resume()` the `AudioContext` in a `pointerup` or `click` handler (iPhones don't accept `pointerdown`), and `resume()` again when the page becomes visible, since a call or switching apps suspends it.
31
+ 12. **Nothing depends on the mouse or keyboard being there.** Phones have no Pointer Lock: when `requestPointerLock` is missing or fails, aim by dragging or with a stick. Only a real `<input>` brings up the on-screen keyboard: focus it inside the tap's handler and give it `font-size: 16px`, or iPhones zoom the page.
32
+ 13. **Run smoothly.** Cap the drawing resolution on touch screens at `Math.min(devicePixelRatio, 2)`, and use fewer particles or effects there if a round drops frames. Desktop keeps its full quality.
33
+ 14. **Stop when hidden.** Phones switch apps often: on `visibilitychange` to hidden, pause a real-time game and stop its sound.
18
34
 
19
35
  ## Check it
20
- Run the game in the browser's device mode (a phone size, touch on) and play a full round with touch only: start, play, pause, game over, restart. Then check the keyboard and mouse still work as before.
36
+ Run the game in the browser's device mode (a phone size, touch on), upright and on its side (about 390×844 and 844×390), and play a full round with touch only: start, play, pause, game over, restart. Rotate in the middle of a round and check it carries on. Check the page never scrolls or zooms and nothing is cut off. Then check the keyboard and mouse still work as before.
37
+
38
+ Device mode can't show everything, sound on iPhones above all: tell the user, in a sentence, to try the game on a real phone after publishing.
39
+
40
+ ## When it doesn't fit
41
+ Some games can't work on a phone, such as one that needs a full keyboard all the time. Tell the user why, do what still helps (screen fit, readable text) if they want it, and don't declare it.
21
42
 
22
43
  ## Declare it
23
- Add `"touch"` to `input` in `juicy.json` (`"input": ["keyboard", "touch"]`, keeping what's there), and `"touch"` to `features` (`"features": ["touch"]`, adding to the list). JuicyJuicy can't detect this one from the files: `input` is what the site's chip and the Upgrades panel read, so only add it once a full round plays by touch.
44
+ Add `"touch"` to `input` in `juicy.json` (`"input": ["keyboard", "touch"]`, keeping what's there), and `"touch"` to `features` (`"features": ["touch"]`, adding to the list). JuicyJuicy can't detect this one from the files: `input` is what the site's chip reads and `features` is what ends the Upgrades panel's offer, so only add them once a full round plays by touch.
@@ -6,7 +6,7 @@ With `--json`, a failed command's last line is `{"ok": false, "error": {"code",
6
6
 
7
7
  | Code | Meaning | Fix |
8
8
  |---|---|---|
9
- | `UNAUTHORIZED` | No saved login, or the token was revoked. | `npx -y juicyjuicy@0.15.0 login --json` (the user approves in the browser). In CI, set `JUICY_TOKEN`. |
9
+ | `UNAUTHORIZED` | No saved login, or the token was revoked. | `npx -y juicyjuicy@0.16.0 login --json` (the user approves in the browser). In CI, set `JUICY_TOKEN`. |
10
10
  | `EXPIRED_TOKEN` | The login code expired (10 minutes) or was used. | Run `login` again. |
11
11
  | `ACCESS_DENIED` | The user clicked Deny. | Ask them, then run `login` again. |
12
12
 
@@ -2,12 +2,12 @@
2
2
 
3
3
  For "bring my JuicyJuicy games up to date", "fix the required updates", a **Needs update** badge on My games, or a `check` or `publish` that warns `requirement-missing`. Sometimes JuicyJuicy makes a rule every game must follow (say, `juicy.json` listing its `features`). A game that doesn't follow it, or that JuicyJuicy hasn't checked yet, leaves the public lists after the deadline; its link keeps working.
4
4
 
5
- To only say which games need an update, without checking or changing anything: `npx -y juicyjuicy@0.15.0 list --json` gives each of the user's games `needsUpdate`, one entry per required update (`title`, `deadline`, `phase` `enforced` once it's past the deadline, `notChecked` when JuicyJuicy hasn't checked the game yet, which step 1 settles); `[]` means none.
5
+ To only say which games need an update, without checking or changing anything: `npx -y juicyjuicy@0.16.0 list --json` gives each of the user's games `needsUpdate`, one entry per required update (`title`, `deadline`, `phase` `enforced` once it's past the deadline, `notChecked` when JuicyJuicy hasn't checked the game yet, which step 1 settles); `[]` means none.
6
6
 
7
7
  1. **Check every game at once.** Log in first (SKILL.md step 1), then run:
8
8
 
9
9
  ```bash
10
- npx -y juicyjuicy@0.15.0 check-games --json
10
+ npx -y juicyjuicy@0.16.0 check-games --json
11
11
  ```
12
12
 
13
13
  JuicyJuicy checks the live version of each of the user's games on its side. It records which features each one has and, for a game whose `juicy.json` never listed its `features`, declares what it finds in the files (like cloud saves). Nothing is uploaded and no new version is made. The result has one entry per game in `games` (`features` with `on`/`off`/`unknown`, `requirements` with `passed`), `toFix` (the games that still need a code change, each with its `prompts`), `notReached` and `next`.
@@ -15,7 +15,7 @@ To only say which games need an update, without checking or changing anything: `
15
15
  2. **If `notReached` isn't empty**, run `check-games` again: it had no time for those games.
16
16
 
17
17
  3. **For each game in `toFix`**, one at a time:
18
- - Find its project, or download the live version with `npx -y juicyjuicy@0.15.0 pull <slug> --json` (SKILL.md, "Add a JuicyJuicy upgrade", step 1).
18
+ - Find its project, or download the live version with `npx -y juicyjuicy@0.16.0 pull <slug> --json` (SKILL.md, "Add a JuicyJuicy upgrade", step 1).
19
19
  - Do what each prompt says, with the recipe it names when it names one.
20
20
  - Make sure `juicy.json` has `features` (`[]` if the game has none of the upgrades).
21
21
  - Check and publish an update as SKILL.md steps 5–7 say, with `--message "Required update: <what>"`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "juicyjuicy",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "description": "Publish AI-made browser games to juicyjuicy.games",
5
5
  "type": "module",
6
6
  "license": "MIT",