@indiegems/gem-web-sdk 16.3.1 → 18.0.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.
@@ -18,8 +18,8 @@
18
18
  },
19
19
  {
20
20
  "file": "arcade-and-store.md",
21
- "sha256": "e055b2b1ec540da9fae8f41c025de99b0faec29cef8fca35285ee34e23120941",
22
- "updated": "2026-10-29T23:35:13Z"
21
+ "sha256": "7320826529435d290159c8b0707f69819124607d1b38160dbc85f63b385fdea9",
22
+ "updated": "2026-10-30T13:36:37Z"
23
23
  },
24
24
  {
25
25
  "file": "arcade-avatars.md",
@@ -28,8 +28,8 @@
28
28
  },
29
29
  {
30
30
  "file": "arcade-economy.md",
31
- "sha256": "f0bdc98d765fa4794090fc035d2c32d9c125cbdae58ba89fb5e76fcf1fb9da94",
32
- "updated": "2026-10-29T23:35:13Z"
31
+ "sha256": "b72474b4694842a1fda030ab9df79987d2906f7a8c922e8630622d747a4b2c3c",
32
+ "updated": "2026-10-30T13:36:37Z"
33
33
  },
34
34
  {
35
35
  "file": "async-opponents.md",
@@ -48,8 +48,8 @@
48
48
  },
49
49
  {
50
50
  "file": "changelog.md",
51
- "sha256": "e66efedcc9662bea610d3d4edc4e0125f3078e2b49059770f4553d57f37368b3",
52
- "updated": "2026-10-29T23:50:25Z"
51
+ "sha256": "bd5cfd2b2d1d1989a8407e147ce29165caada49f78bcd0d708ded1babe98fc09",
52
+ "updated": "2026-10-30T15:46:14Z"
53
53
  },
54
54
  {
55
55
  "file": "daily-challenges.md",
@@ -78,13 +78,13 @@
78
78
  },
79
79
  {
80
80
  "file": "getting-started.md",
81
- "sha256": "5f6b7c1e3763cb647379ccc96fc8eb4e5f22551f60cfe56bc6a957312f431fe3",
82
- "updated": "2026-10-29T23:50:25Z"
81
+ "sha256": "67a444eb6e1cbf0bbf9af6309db48ae44e9c03a692df60a35c12e604cb646065",
82
+ "updated": "2026-10-30T15:46:14Z"
83
83
  },
84
84
  {
85
85
  "file": "inventory-and-catalogs.md",
86
- "sha256": "032b911f515a7c81b0bcee6065300049651fef8b2108e7930e1d1354eada7a87",
87
- "updated": "2026-10-29T23:35:13Z"
86
+ "sha256": "9c898355af92ab7e17035bad964dce57b26e64624b18e83149b88ae10b96fd9c",
87
+ "updated": "2026-10-30T13:36:37Z"
88
88
  },
89
89
  {
90
90
  "file": "leaderboards.md",
@@ -138,8 +138,8 @@
138
138
  },
139
139
  {
140
140
  "file": "server-bundles.md",
141
- "sha256": "387d213d3770f21dccb32c94aac57b5af32bcac8bbe5ca9a6c3c2d8d51e5a2a2",
142
- "updated": "2026-10-29T23:50:25Z"
141
+ "sha256": "8c2ff15c97a0eb09c9a50ae78b5e728635a478ee879a8aa788b82ca7de387eee",
142
+ "updated": "2026-10-30T15:46:14Z"
143
143
  },
144
144
  {
145
145
  "file": "serverless-functions-js.md",
@@ -163,8 +163,8 @@
163
163
  },
164
164
  {
165
165
  "file": "what-a-game-must-do.md",
166
- "sha256": "9d6f2121306bfee8b886d9555a644faa843d16f0c5200e257698336d747136b1",
167
- "updated": "2026-10-29T21:40:17Z"
166
+ "sha256": "8faa9ef2cef2a5eb73cf95d7c52af914325c2bc26834625c9cb4b5748eecc616",
167
+ "updated": "2026-10-30T14:09:09Z"
168
168
  }
169
169
  ]
170
170
  }
@@ -80,8 +80,8 @@ To check the download yourself: the package npm serves is, byte for byte, the fi
80
80
  also publishes on its download host, with the hash beside it:
81
81
 
82
82
  ```bash
83
- curl -fsSL -O https://downloads.gemarcade.com/sdk/v16.3.1/gem-web-sdk.tgz
84
- curl -fsSL https://downloads.gemarcade.com/sdk/v16.3.1/gem-web-sdk.tgz.sri
83
+ curl -fsSL -O https://downloads.gemarcade.com/sdk/v18.0.0/gem-web-sdk.tgz
84
+ curl -fsSL https://downloads.gemarcade.com/sdk/v18.0.0/gem-web-sdk.tgz.sri
85
85
  openssl dgst -sha384 -binary gem-web-sdk.tgz | openssl base64 -A
86
86
  ```
87
87
 
@@ -971,6 +971,35 @@ read the rectangles, so the arcade sends it none and instead raises `insets.top`
971
971
  `--gem-safe-top` — past the bottom of the row. That game keeps working, at the cost of a full-width
972
972
  band; upgrading gives the band back.
973
973
 
974
+ ### Matching the Arcade to your game: `gem.setMenuStyle`
975
+
976
+ The burger button and everything the Arcade draws over your game wear the player's Arcade style
977
+ until you say otherwise. To make them match your game, give the Arcade two colours:
978
+
979
+ ```js
980
+ gem.setMenuStyle({ ground: '#1b2a4a', accent: '#ffb300' }); // at boot, with your first screen
981
+ gem.setMenuStyle(null); // back to the player's own style
982
+ ```
983
+
984
+ - **Colours only.** `ground` is what the Arcade's surfaces are built on and `accent` is its one
985
+ highlight. The Arcade works out the text, rims, button shapes and fonts, so its screens stay
986
+ readable on any ground.
987
+ - **What the Arcade refuses.** Each colour must be exactly `#rrggbb`, and `accent` must reach 3:1
988
+ contrast against `ground`. Anything else is ignored, with a warning on the console giving the
989
+ measured ratio: the last colours stand, nothing is sent, and the call returns `false`.
990
+ - **What wears your colours.** The burger button, the Gem menu and its pages, and every Arcade
991
+ pop-up over your running game, purchase sheets included. They do not reach the Arcade's other
992
+ screens.
993
+ - **Your colours win while your game runs.** They replace the style the player chose, and that
994
+ style comes back when your game ends.
995
+ - **It is screen state, not a toggle**: each call is the whole answer, held before the Arcade
996
+ connects and sent again after any reconnect, so you may change it between levels. A change the
997
+ Arcade receives while its menu or a pop-up is open waits until that closes.
998
+
999
+ `gem.setMenuStyle` returns `true` when the Arcade was told, and `false` before it connects, under an
1000
+ embedder that cannot wear your colours, and for colours it would refuse. On `false` from an embedder
1001
+ there is nothing else to do: never draw a look-alike of its menu.
1002
+
974
1003
  ## Which store-page experiment brought this player in: cohorts
975
1004
 
976
1005
  **You need this only if you run an A/B experiment on your store page and want the game to match
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indiegems/gem-web-sdk",
3
- "version": "16.3.1",
3
+ "version": "18.0.0",
4
4
  "description": "postMessage bridge contract between gem-web and browser-playable games",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -3145,6 +3145,64 @@
3145
3145
  "rateLimit": { "perMinute": 12 }
3146
3146
  },
3147
3147
 
3148
+ {
3149
+ "type": "gem.menuStyle",
3150
+ "direction": "frameToParent",
3151
+ "transport": "port",
3152
+ "$comment": [
3153
+ "The colours the game would like the arcade's own screens over it to wear,",
3154
+ "so they match the game: in the Gem Arcade, the burger button, the Gem menu",
3155
+ "and its pages, and every Arcade pop-up over the running game. With no",
3156
+ "message at all those screens wear the player's own Arcade style.",
3157
+ "",
3158
+ "TWO COLOURS, BOTH OR NEITHER. `ground` is the colour the arcade's surfaces",
3159
+ "are built on and `accent` its one highlight. A message with neither hands",
3160
+ "the arcade back to the player's own style; a message with only one is",
3161
+ "refused by the parent. The parent works out everything else, text, rims,",
3162
+ "button shapes and fonts, so a game can never make its screens unreadable.",
3163
+ "",
3164
+ "SCREEN STATE, NOT A TOGGLE, like `gem.gemCorner`. Each message is the whole",
3165
+ "answer and the latest one wins, so a game may change it between levels.",
3166
+ "The SDK holds it from before the bridge is up and re-sends it after every",
3167
+ "handshake, so a parent that reconnects wears what the game had asked.",
3168
+ "",
3169
+ "THE PARENT DECIDES, AND IT CHECKS AGAIN. The frame is untrusted, so a",
3170
+ "parent checks both values itself before a byte of them reaches a style:",
3171
+ "exactly `#` and six hexadecimal digits, and at least 3:1 WCAG contrast",
3172
+ "between `accent` and `ground`. On failure it keeps the screens as they",
3173
+ "are and tells the game nothing; the SDK refuses the same values on the",
3174
+ "game's side, where a developer sees why. A format of its own is not used",
3175
+ "for the hex, because a parent built on an older contract refuses a format",
3176
+ "it does not know.",
3177
+ "",
3178
+ "ON THE PARENT'S TERMS. In the Gem Arcade the game's colours win over the",
3179
+ "player's chosen style for as long as that game runs, a change that arrives",
3180
+ "while one of the arcade's surfaces is open waits for it to close, and the",
3181
+ "player's own style comes back when the game ends.",
3182
+ "",
3183
+ "A PRESENTATION REQUEST ONLY, and not an authorization control. Cosmetic:",
3184
+ "nothing may key a trust decision on it.",
3185
+ "",
3186
+ "Gated by `menuStyle`: sent only to a parent that advertised it. To any",
3187
+ "other, `gem.setMenuStyle` returns false."
3188
+ ],
3189
+ "fields": {
3190
+ "ground": {
3191
+ "kind": "string",
3192
+ "maxLength": 7,
3193
+ "optional": true,
3194
+ "$comment": "`#rrggbb`, the colour the arcade's surfaces are built on. Absent with `accent` to clear."
3195
+ },
3196
+ "accent": {
3197
+ "kind": "string",
3198
+ "maxLength": 7,
3199
+ "optional": true,
3200
+ "$comment": "`#rrggbb`, the one highlight, at least 3:1 against `ground`. Absent with `ground` to clear."
3201
+ }
3202
+ },
3203
+ "rateLimit": { "perMinute": 12 }
3204
+ },
3205
+
3148
3206
  {
3149
3207
  "type": "gem.settingsDeclare",
3150
3208
  "direction": "frameToParent",
@@ -5581,6 +5639,114 @@
5581
5639
  }
5582
5640
  },
5583
5641
  "rateLimit": { "perMinute": 30 }
5642
+ },
5643
+ {
5644
+ "type": "gem.requestStoreOfferSheet",
5645
+ "direction": "frameToParent",
5646
+ "transport": "port",
5647
+ "$comment": [
5648
+ "Ask the parent to SHOW one SKU it listed in gem.storeOffersResult, in",
5649
+ "the Arcade's own one-offer sheet over the paused game - real money, not",
5650
+ "Gems. Gated by the `storeOfferSheet` capability. Answered by",
5651
+ "`gem.storeOfferSheetResult`.",
5652
+ "",
5653
+ "THE ARCADE DRAWS THE OFFER; THE GAME ONLY POINTS AT IT. gem.requestStorePurchase",
5654
+ "is for an offer the game drew itself from gem.storeOffersResult, and goes",
5655
+ "straight to checkout. This is for an offer whose pitch the bridge cannot",
5656
+ "carry: its perks, a sale, what a holder already has. All of that stays",
5657
+ "with the parent, so none of it - least of all whether this player holds",
5658
+ "a pass - crosses to the game. The sheet's Buy opens the same checkout",
5659
+ "gem.requestStorePurchase opens.",
5660
+ "",
5661
+ "EVERY RULE OF gem.requestStorePurchase HOLDS, and it shares them rather",
5662
+ "than having its own: an acting message (a press the parent can see, one",
5663
+ "at a time across all of them, both checked before `skuId` is resolved,",
5664
+ "the answer bound to this frame session); only a SKU the parent would",
5665
+ "list now; an offer cooling down opens nothing and answers `declined` /",
5666
+ "`cancelled`; the SAME back-off, so a game cannot escape one by switching",
5667
+ "message; resume, then answer; evidence, not accounting.",
5668
+ "",
5669
+ "THE SHEET IS NOT A SECOND CONFIRMATION, and it does not change",
5670
+ "gem.requestStorePurchase's straight-to-checkout rule. It is the pitch;",
5671
+ "the checkout is still the confirmation, with its own pay control. The",
5672
+ "player leaving the sheet without pressing Buy is a dismissal, exactly",
5673
+ "as closing a checkout is, and starts the back-off.",
5674
+ "",
5675
+ "A PARENT THAT ADVERTISES `storeOfferSheet` ADVERTISES `store`: the",
5676
+ "game's SKU comes from gem.storeOffersResult, and a purchase that answers",
5677
+ "`ok` / `pending` here is followed by gem.storePurchaseSettled.",
5678
+ "",
5679
+ "Every field is untrusted text: match `skuId` against the offers you",
5680
+ "read, never interpolate it into a path, a query or a log line."
5681
+ ],
5682
+ "fields": {
5683
+ "requestId": {
5684
+ "kind": "string",
5685
+ "maxLength": 64,
5686
+ "$comment": "Opaque and frame-chosen. Echo-only, and never a key on the money path: the parent derives its own request identity."
5687
+ },
5688
+ "skuId": {
5689
+ "kind": "string",
5690
+ "maxLength": 64,
5691
+ "$comment": "One `skuId` from gem.storeOffersResult. Frame-chosen: matched by the parent against the listing it would show now."
5692
+ }
5693
+ },
5694
+ "rateLimit": {
5695
+ "perMinute": 12,
5696
+ "$comment": "gem.requestStorePurchase's limit and its argument: it bounds a BUGGY game, not a hostile one. What bounds the player's experience is the activation check, the one-at-a-time rule and the back-off."
5697
+ }
5698
+ },
5699
+ {
5700
+ "type": "gem.storeOfferSheetResult",
5701
+ "direction": "parentToFrame",
5702
+ "transport": "port",
5703
+ "$comment": [
5704
+ "The answer to one gem.requestStoreOfferSheet, carrying exactly what",
5705
+ "gem.storePurchaseResult carries and meaning the same. Gated by the",
5706
+ "`storeOfferSheet` capability.",
5707
+ "",
5708
+ " ok - the player paid. With no reason, fulfilment has",
5709
+ " settled; with `reason: pending`,",
5710
+ " gem.storePurchaseSettled follows when it does.",
5711
+ " declined - nothing was bought: the player left the sheet or closed",
5712
+ " the checkout, or the offer is cooling down (`cancelled`);",
5713
+ " or no press, another acting message open or a back-off",
5714
+ " running (`busy`).",
5715
+ " unavailable - no sale the parent can vouch for. With `store_off`,",
5716
+ " `sign_in_required`, `not_found` or the provider's own",
5717
+ " failure code, nothing was bought. With `error`, the",
5718
+ " OUTCOME IS UNKNOWN and the player may have paid. The SDK",
5719
+ " resolves the same `unavailable` / `error` when the port",
5720
+ " closes under an open sheet.",
5721
+ "",
5722
+ "`cancelled` DOES NOT SAY WHICH. The game is not told whether the player",
5723
+ "left the sheet or the checkout, and nothing here says what the sheet",
5724
+ "showed.",
5725
+ "",
5726
+ "NO TIMEOUT ON THE GAME'S SIDE while the port is alive: the parent always",
5727
+ "answers, and a player may read a sheet for as long as they like.",
5728
+ "NEVER AUTO-RETRIED. Only a fresh press may ask again."
5729
+ ],
5730
+ "fields": {
5731
+ "requestId": {
5732
+ "kind": "string",
5733
+ "maxLength": 64,
5734
+ "$comment": "Echoes the gem.requestStoreOfferSheet that asked. Echo-only."
5735
+ },
5736
+ "outcome": {
5737
+ "kind": "enum",
5738
+ "values": ["ok", "declined", "unavailable"],
5739
+ "$comment": "Closed at three, as gem.purchaseResult's."
5740
+ },
5741
+ "reason": {
5742
+ "kind": "string",
5743
+ "maxLength": 64,
5744
+ "format": "code",
5745
+ "optional": true,
5746
+ "$comment": "An open code, on any outcome: `pending` with `ok`; `cancelled` or `busy` with `declined`; `store_off`, `sign_in_required`, `not_found`, `error` or the payment provider's own failure code with `unavailable`. Normalized to the code grammar before it is sent, as gem.storePurchaseResult's. A receiver must not fail on a code it does not know."
5747
+ }
5748
+ },
5749
+ "rateLimit": { "perMinute": 30 }
5584
5750
  }
5585
5751
  ],
5586
5752
 
@@ -295,6 +295,19 @@
295
295
  "gem.storePurchaseResult",
296
296
  "gem.storePurchaseSettled"
297
297
  ]
298
+ },
299
+ "storeOfferSheet": {
300
+ "implemented": true,
301
+ "gates": [
302
+ "gem.requestStoreOfferSheet",
303
+ "gem.storeOfferSheetResult"
304
+ ]
305
+ },
306
+ "menuStyle": {
307
+ "implemented": true,
308
+ "gates": [
309
+ "gem.menuStyle"
310
+ ]
298
311
  }
299
312
  }
300
313
  }