@playus.club/games-sdk 0.1.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.
Files changed (56) hide show
  1. package/LICENSE.md +35 -0
  2. package/README.md +129 -0
  3. package/dist/sdk/babylon/canvas.d.ts +30 -0
  4. package/dist/sdk/babylon/canvas.d.ts.map +1 -0
  5. package/dist/sdk/babylon/index.d.ts +3 -0
  6. package/dist/sdk/babylon/index.d.ts.map +1 -0
  7. package/dist/sdk/babylon.js +52 -0
  8. package/dist/sdk/bridge.d.ts +2 -0
  9. package/dist/sdk/bridge.d.ts.map +1 -0
  10. package/dist/sdk/chunks/background-DLy8kjVf.js +47 -0
  11. package/dist/sdk/chunks/debug-BKXPXMKn.js +92 -0
  12. package/dist/sdk/chunks/touch-hint-BZBB3COY.js +148 -0
  13. package/dist/sdk/helpers/timeFormat.d.ts +3 -0
  14. package/dist/sdk/helpers/timeFormat.d.ts.map +1 -0
  15. package/dist/sdk/i18n.d.ts +55 -0
  16. package/dist/sdk/i18n.d.ts.map +1 -0
  17. package/dist/sdk/index.d.ts +19 -0
  18. package/dist/sdk/index.d.ts.map +1 -0
  19. package/dist/sdk/index.js +366 -0
  20. package/dist/sdk/mobile-interaction.d.ts +4 -0
  21. package/dist/sdk/mobile-interaction.d.ts.map +1 -0
  22. package/dist/sdk/native-bridge.d.ts +45 -0
  23. package/dist/sdk/native-bridge.d.ts.map +1 -0
  24. package/dist/sdk/overlay/debug.d.ts +31 -0
  25. package/dist/sdk/overlay/debug.d.ts.map +1 -0
  26. package/dist/sdk/overlay/index.d.ts +5 -0
  27. package/dist/sdk/overlay/index.d.ts.map +1 -0
  28. package/dist/sdk/overlay/touch-hint.d.ts +12 -0
  29. package/dist/sdk/overlay/touch-hint.d.ts.map +1 -0
  30. package/dist/sdk/overlay.js +3 -0
  31. package/dist/sdk/phaser/container.d.ts +25 -0
  32. package/dist/sdk/phaser/container.d.ts.map +1 -0
  33. package/dist/sdk/phaser/index.d.ts +3 -0
  34. package/dist/sdk/phaser/index.d.ts.map +1 -0
  35. package/dist/sdk/phaser.js +72 -0
  36. package/dist/sdk/random.d.ts +5 -0
  37. package/dist/sdk/random.d.ts.map +1 -0
  38. package/dist/sdk/sound.d.ts +36 -0
  39. package/dist/sdk/sound.d.ts.map +1 -0
  40. package/dist/sdk/tap-to-start.d.ts +19 -0
  41. package/dist/sdk/tap-to-start.d.ts.map +1 -0
  42. package/dist/sdk/three/canvas.d.ts +13 -0
  43. package/dist/sdk/three/canvas.d.ts.map +1 -0
  44. package/dist/sdk/three/index.d.ts +3 -0
  45. package/dist/sdk/three/index.d.ts.map +1 -0
  46. package/dist/sdk/three.js +36 -0
  47. package/dist/sdk/timing.d.ts +4 -0
  48. package/dist/sdk/timing.d.ts.map +1 -0
  49. package/dist/sdk/types/background.d.ts +33 -0
  50. package/dist/sdk/types/background.d.ts.map +1 -0
  51. package/dist/sdk/url-params.d.ts +5 -0
  52. package/dist/sdk/url-params.d.ts.map +1 -0
  53. package/package.json +77 -0
  54. package/src/playus/fonts.css +29 -0
  55. package/src/playus/global.css +14 -0
  56. package/src/playus/styles.css +61 -0
package/LICENSE.md ADDED
@@ -0,0 +1,35 @@
1
+ # Playus Games SDK License
2
+
3
+ Copyright (c) 2026 Dapps Pte Ltd. All rights reserved.
4
+
5
+ ## Grant
6
+
7
+ Subject to the terms below, Dapps Pte Ltd grants you a limited, non-exclusive, non-transferable, revocable, royalty-free license to use, reproduce, and modify this SDK solely to develop, test, and build web games intended for review by and distribution within the Playus apps operated by Dapps Pte Ltd ("Playus"), and to distribute the SDK only in compiled form as part of such a game bundle delivered to Playus.
8
+
9
+ ## Restrictions
10
+
11
+ You may not:
12
+
13
+ - use the SDK, or any part of it (including the native bridge protocol implementation), to integrate or run games or other web content in any app, platform, or service other than Playus;
14
+ - redistribute, sublicense, sell, or publish the SDK or modified versions of it outside of a game bundle delivered to Playus;
15
+ - remove or alter copyright or license notices.
16
+
17
+ ## Your games remain yours
18
+
19
+ This license covers the SDK only. You keep all rights to your own game code and assets. Terms for reviewing, publishing, and monetizing games inside Playus are agreed separately with Dapps Pte Ltd and take precedence over this license where they conflict.
20
+
21
+ ## Contributions
22
+
23
+ By submitting a contribution to this repository you grant Dapps Pte Ltd a perpetual, worldwide, royalty-free license to use, modify, and distribute it as part of the SDK.
24
+
25
+ ## Termination
26
+
27
+ This license terminates automatically if you breach it or when Dapps Pte Ltd withdraws it in writing. On termination you must stop using the SDK for new development; game bundles already accepted by Playus remain unaffected.
28
+
29
+ ## No warranty
30
+
31
+ THE SDK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. TO THE MAXIMUM EXTENT PERMITTED BY LAW, DAPPS PTE LTD SHALL NOT BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM THE USE OF THE SDK.
32
+
33
+ ## Contact
34
+
35
+ Dapps Pte Ltd — https://dapps.ltd — team@dapps.ltd
package/README.md ADDED
@@ -0,0 +1,129 @@
1
+ <p align="center">
2
+ <img src="docs/assets/playus-web-preview.jpg" alt="Playus dice logo" width="720" />
3
+ </p>
4
+
5
+ # Playus Games SDK
6
+
7
+ SDK and local host simulator for building Playus-compatible web games.
8
+
9
+ Partners build games in their own repository and deliver a pre-built static bundle to Playus. The bundle must already use the Playus SDK runtime so it works inside the Playus iOS and Android WebViews without a source-code adaptation step.
10
+
11
+ ## Getting The SDK
12
+
13
+ ```sh
14
+ npm install @playus.club/games-sdk
15
+ ```
16
+
17
+ The npm package contains the runtime only. For the local host simulator and the example games, clone this repository (see Quick Start below).
18
+
19
+ The SDK speaks Playus bridge protocol v3. Note the SDK version you built with — it is part of the bundle delivery metadata.
20
+
21
+ ## Quick Start (this repo)
22
+
23
+ ```sh
24
+ npm install
25
+ npm run dev
26
+ ```
27
+
28
+ Open the local host simulator:
29
+
30
+ ```txt
31
+ http://localhost:8091
32
+ ```
33
+
34
+ The simulator loads the included examples and validates the real Playus bridge flow, including `ready`, `hostReady`, `hostReadyAck`, live score updates, finish events, language params, debug mode, and host mute callbacks.
35
+
36
+ ## SDK Usage
37
+
38
+ Import the SDK in your entry module (not behind a dynamic import — the host verifies the bridge exists right after page load):
39
+
40
+ ```ts
41
+ import {
42
+ createTapToStartOverlay,
43
+ nativeBridge,
44
+ sound,
45
+ } from '@playus.club/games-sdk';
46
+ import '@playus.club/games-sdk/styles.css';
47
+
48
+ nativeBridge.configure({ gameId: 'your-game-id' });
49
+
50
+ createTapToStartOverlay({
51
+ text: {
52
+ en: 'Tap to start',
53
+ de: 'Tippen zum Starten',
54
+ fr: 'Touchez pour commencer',
55
+ es: 'Toca para empezar',
56
+ it: 'Tocca per iniziare',
57
+ },
58
+ mode: 'dismiss-only',
59
+ onStart: () => {
60
+ nativeBridge.game.started();
61
+ nativeBridge.game.score(0);
62
+ },
63
+ });
64
+
65
+ await sound.preload(['positive-input']);
66
+ nativeBridge.game.ready({ version: '1.0.0' });
67
+ ```
68
+
69
+ When the run ends:
70
+
71
+ ```ts
72
+ nativeBridge.game.finished(finalScore);
73
+ ```
74
+
75
+ Engine helpers: `@playus.club/games-sdk/phaser`, `@playus.club/games-sdk/babylon`, and `@playus.club/games-sdk/three` provide ready-made containers and configs. See [the game contract](docs/game-contract.md) for the full runtime rules.
76
+
77
+ ## Examples
78
+
79
+ Three small complete games. All follow the full [game contract](docs/game-contract.md) with localized overlays, sounds, and haptics — and each shows a different feature mix:
80
+
81
+ | | `games/starter-game` (plain TS) | `games/phaser-example` | `games/babylon-example` |
82
+ | --- | --- | --- | --- |
83
+ | Game | Hit 5 targets fast | Pop bubbles for 20s | Tap the odd cube, endless levels |
84
+ | Score | Time: negative seconds, `score(0)` at start, whole-second live updates, exact fractional final | Points with live updates | Levels with an endless difficulty ramp |
85
+ | Seeded random | New layout per try | Same pattern every try (`includePlayContext: false`) | `seededShuffle` + float ranges |
86
+ | Start overlay | `dismiss-only`, default tap hint | `dismiss-only`, `tap-rapid` hint | `pass-first-input`, board visible behind hint |
87
+ | Also shows | Real-time score timer (no clamping) | Delta clamping, countdown clock, warning sound | Transparent background, debug overlay, DPR cap, brief end feedback, `error()` |
88
+
89
+ ## Testing Your Own Bundle
90
+
91
+ The simulator's full handshake only works same-origin. To test your built bundle:
92
+
93
+ 1. Copy your build output into `public/<your-game-id>/` in this repo.
94
+ 2. Run `npm run dev` and enter `/<your-game-id>/` as the Game URL in the simulator.
95
+
96
+ Cross-origin URLs (e.g. your own dev server) still show outgoing events, but `hostReady` cannot be delivered — the simulator marks this and skips handshake checks.
97
+
98
+ ## Delivering A Bundle
99
+
100
+ Plain source delivery is preferred when you can share it — see [CONTRIBUTING.md](CONTRIBUTING.md). For bundle delivery, Playus expects a static web bundle:
101
+
102
+ ```txt
103
+ dist/
104
+ index.html
105
+ assets/...
106
+ ```
107
+
108
+ The bundle must:
109
+
110
+ - run from a static host path with no backend required
111
+ - reference all assets **relatively** — with Vite set `base: './'` and check that the built `index.html` uses `./assets/...`, not `/assets/...` (Playus hosts serve bundles from a subpath)
112
+ - include the Playus SDK in the compiled output
113
+ - send its bundle version via `ready({ version })`
114
+ - call `ready()` only after required assets for the first playable frame are loaded
115
+ - send meaningful live `score()` updates
116
+ - call `finished(finalScore)` exactly once
117
+ - support `lang` from the URL hash for in-game text and overlays
118
+
119
+ Playus handles signing, hosting, game metadata, score type assignment, leaderboard UI, and final result UI.
120
+
121
+ ## Docs
122
+
123
+ - [Game contract](docs/game-contract.md) — the runtime rules every bundle must follow
124
+ - [Assets and mobile performance](docs/assets-and-performance.md)
125
+ - [Submission checklist](docs/submission-checklist.md)
126
+
127
+ ## License
128
+
129
+ Source-available under the [Playus Games SDK License](LICENSE.md): free to use for building and delivering games for the Playus apps; not for use in other apps or platforms.
@@ -0,0 +1,30 @@
1
+ import { type BackgroundConfig } from '../types/background';
2
+ import { Color4 } from '@babylonjs/core/Maths/math.color';
3
+ export type CanvasOptions = {
4
+ background?: BackgroundConfig;
5
+ };
6
+ export declare function createCanvas(options?: CanvasOptions): HTMLCanvasElement;
7
+ /**
8
+ * Returns Engine options based on background config with performance optimizations.
9
+ * Use this when creating the Babylon.js Engine.
10
+ *
11
+ * @example
12
+ * const engineOpts = getEngineOptions(background);
13
+ * this.engine = new Engine(this.canvas, true, engineOpts);
14
+ */
15
+ export declare function getEngineOptions(background?: BackgroundConfig): {
16
+ alpha: boolean;
17
+ antialias: boolean;
18
+ preserveDrawingBuffer: boolean;
19
+ stencil: boolean;
20
+ desynchronized: boolean;
21
+ };
22
+ /**
23
+ * Returns a Color4 for scene.clearColor based on background config.
24
+ * Use this when setting up the scene.
25
+ *
26
+ * @example
27
+ * this.scene.clearColor = getClearColor(background);
28
+ */
29
+ export declare function getClearColor(background?: BackgroundConfig): Color4;
30
+ //# sourceMappingURL=canvas.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"canvas.d.ts","sourceRoot":"","sources":["../../../src/playus/babylon/canvas.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,gBAAgB,EAAqC,MAAM,qBAAqB,CAAC;AAC/F,OAAO,EAAE,MAAM,EAAE,MAAM,kCAAkC,CAAC;AAE1D,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B,CAAC;AAEF,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,aAAa,GAAG,iBAAiB,CAuDvE;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,CAAC,EAAE,gBAAgB;;;;;;EAS7D;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,UAAU,CAAC,EAAE,gBAAgB,GAAG,MAAM,CAmBnE"}
@@ -0,0 +1,3 @@
1
+ export { createCanvas, getClearColor, getEngineOptions } from './canvas';
2
+ export type { CanvasOptions } from './canvas';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/playus/babylon/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AACzE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC"}
@@ -0,0 +1,52 @@
1
+ import { i as e, n as t, o as n, r } from "./chunks/background-DLy8kjVf.js";
2
+ import { r as i } from "./chunks/debug-BKXPXMKn.js";
3
+ import { Color4 as a } from "@babylonjs/core/Maths/math.color";
4
+ //#region src/playus/babylon/canvas.ts
5
+ function o(r) {
6
+ let a = t(r?.background), o = document.createElement("div");
7
+ o.id = "game-root", Object.assign(o.style, {
8
+ position: "fixed",
9
+ inset: "0",
10
+ display: "grid",
11
+ placeItems: "center",
12
+ ...a && { background: a }
13
+ }), e(o), document.body.appendChild(o);
14
+ let s = document.createElement("div");
15
+ if (s.id = "game-viewport", Object.assign(s.style, {
16
+ width: "min(100vw, calc(100vh * 0.625))",
17
+ aspectRatio: "0.625",
18
+ position: "relative",
19
+ ...a && { background: a }
20
+ }), e(s), o.appendChild(s), i()) {
21
+ let e = document.createElement("div");
22
+ e.id = "debug-background", Object.assign(e.style, {
23
+ position: "absolute",
24
+ inset: "0",
25
+ background: "rgba(0, 0, 0, 0.1)",
26
+ pointerEvents: "none",
27
+ zIndex: "-1"
28
+ }), s.appendChild(e);
29
+ }
30
+ let c = document.createElement("canvas");
31
+ return c.id = "gameCanvas", Object.assign(c.style, {
32
+ width: "100%",
33
+ height: "100%",
34
+ display: "block"
35
+ }), e(c), n(c), s.appendChild(c), c;
36
+ }
37
+ function s(e) {
38
+ return {
39
+ alpha: r(e),
40
+ antialias: !0,
41
+ preserveDrawingBuffer: !1,
42
+ stencil: !1,
43
+ desynchronized: !1
44
+ };
45
+ }
46
+ function c(e) {
47
+ if (r(e)) return new a(0, 0, 0, 0);
48
+ let n = t(e), i = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(n);
49
+ return i ? new a(parseInt(i[1], 16) / 255, parseInt(i[2], 16) / 255, parseInt(i[3], 16) / 255, 1) : new a(0, 0, 0, 1);
50
+ }
51
+ //#endregion
52
+ export { o as createCanvas, c as getClearColor, s as getEngineOptions };
@@ -0,0 +1,2 @@
1
+ export { ColorConfig, NativeBridge, nativeBridge, roundScoreForBridge, } from './native-bridge';
2
+ //# sourceMappingURL=bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge.d.ts","sourceRoot":"","sources":["../../src/playus/bridge.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,mBAAmB,GACpB,MAAM,iBAAiB,CAAC"}
@@ -0,0 +1,47 @@
1
+ //#region src/playus/mobile-interaction.ts
2
+ var e = new URLSearchParams(window.location.search), t = d("playusNoMobilePolicy"), n = d("playusNoTouchDefaultGuard"), r = e.get("playusTouchAction"), i = [
3
+ "a[href]",
4
+ "button",
5
+ "input",
6
+ "select",
7
+ "textarea",
8
+ "[contenteditable]",
9
+ "[role=\"button\"]",
10
+ "[role=\"slider\"]"
11
+ ].join(","), a = !1, o = /* @__PURE__ */ new WeakSet();
12
+ function s() {
13
+ t || a || (a = !0, u(document.documentElement), document.body ? u(document.body) : document.addEventListener("DOMContentLoaded", () => {
14
+ document.body && u(document.body);
15
+ }, { once: !0 }), document.addEventListener("selectstart", f, !0), document.addEventListener("contextmenu", f, !0), document.addEventListener("dragstart", f, !0));
16
+ }
17
+ function c(e) {
18
+ return t ? e : (u(e), e.style.touchAction = r || "none", e);
19
+ }
20
+ function l(e) {
21
+ return t || n || o.has(e) ? e : (o.add(e), e.addEventListener("touchstart", f, { passive: !1 }), e.addEventListener("touchmove", f, { passive: !1 }), e);
22
+ }
23
+ function u(e) {
24
+ let t = e.style;
25
+ t.userSelect = "none", t.webkitUserSelect = "none", t.webkitTouchCallout = "none", t.webkitTapHighlightColor = "transparent", t.overscrollBehavior = "none";
26
+ }
27
+ function d(t) {
28
+ let n = e.get(t);
29
+ return n === "" || n === "1" || n === "true";
30
+ }
31
+ function f(e) {
32
+ p(e.target) || e.preventDefault();
33
+ }
34
+ function p(e) {
35
+ return e instanceof Element ? e.closest(i) !== null : !1;
36
+ }
37
+ //#endregion
38
+ //#region src/playus/types/background.ts
39
+ var m = { transparent: !0 };
40
+ function h(e) {
41
+ return !e || e.transparent;
42
+ }
43
+ function g(e) {
44
+ if (!(!e || e.transparent === !0)) return e.color;
45
+ }
46
+ //#endregion
47
+ export { s as a, c as i, g as n, l as o, h as r, m as t };
@@ -0,0 +1,92 @@
1
+ //#region src/playus/url-params.ts
2
+ function e(e) {
3
+ try {
4
+ let t = window.location.hash.substring(1);
5
+ return new URLSearchParams(t).get(e) || new URLSearchParams(window.location.search).get(e);
6
+ } catch {
7
+ return null;
8
+ }
9
+ }
10
+ function t(t) {
11
+ let n = e("groupgame");
12
+ if (n) return t?.includePlayContext === !1 ? n : `${n}.${e("playcontext")}`;
13
+ }
14
+ //#endregion
15
+ //#region src/playus/overlay/debug.ts
16
+ function n() {
17
+ try {
18
+ let e = document.createElement("canvas"), t = e.getContext("webgl2") || e.getContext("webgl");
19
+ if (!t) return {
20
+ supported: !1,
21
+ renderer: "Canvas 2D (no WebGL)",
22
+ vendor: "CPU"
23
+ };
24
+ let n = t.getExtension("WEBGL_debug_renderer_info");
25
+ return n ? {
26
+ supported: !0,
27
+ renderer: t.getParameter(n.UNMASKED_RENDERER_WEBGL),
28
+ vendor: t.getParameter(n.UNMASKED_VENDOR_WEBGL)
29
+ } : {
30
+ supported: !0,
31
+ renderer: "WebGL (details hidden)",
32
+ vendor: "Unknown"
33
+ };
34
+ } catch {
35
+ return {
36
+ supported: !1,
37
+ renderer: "Detection failed",
38
+ vendor: "Unknown"
39
+ };
40
+ }
41
+ }
42
+ function r() {
43
+ return e("d") === "1";
44
+ }
45
+ function i(e) {
46
+ let t = document.createElement("div");
47
+ t.id = "debug-overlay", Object.assign(t.style, {
48
+ position: "absolute",
49
+ top: "5px",
50
+ left: "50%",
51
+ transform: "translateX(-50%)",
52
+ padding: "2px 6px",
53
+ background: "rgba(0,0,0,0.35)",
54
+ color: "#fff",
55
+ fontFamily: "system-ui, -apple-system, Segoe UI, Roboto, sans-serif",
56
+ fontSize: "12px",
57
+ lineHeight: "1.2",
58
+ borderRadius: "6px",
59
+ pointerEvents: "none",
60
+ userSelect: "none",
61
+ display: "none",
62
+ zIndex: "9999"
63
+ }), e.appendChild(t);
64
+ let n = document.createElement("div");
65
+ n.textContent = "— fps", t.appendChild(n);
66
+ let r = document.createElement("div");
67
+ r.style.fontSize = "10px", r.style.opacity = "0.8", r.textContent = "", t.appendChild(r);
68
+ function i(e) {
69
+ n.textContent = `${e | 0} fps`;
70
+ }
71
+ function a(e) {
72
+ r.textContent = e;
73
+ }
74
+ function o() {
75
+ t.style.display = "block";
76
+ }
77
+ function s() {
78
+ t.style.display = "none";
79
+ }
80
+ function c() {
81
+ t.parentElement && t.parentElement.removeChild(t);
82
+ }
83
+ return {
84
+ setFps: i,
85
+ setRenderer: a,
86
+ show: o,
87
+ hide: s,
88
+ destroy: c
89
+ };
90
+ }
91
+ //#endregion
92
+ export { e as a, t as i, n, r, i as t };
@@ -0,0 +1,148 @@
1
+ //#region src/playus/overlay/touch-hint.ts
2
+ var e = !1;
3
+ function t() {
4
+ if (e) return;
5
+ e = !0;
6
+ let t = document.createElement("style");
7
+ t.id = "touch-hint-keyframes", t.textContent = "\n @keyframes th-tap {\n 0%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.4; }\n 50% { transform: translate(-50%, -50%) scale(1.35); opacity: 1.0; }\n }\n @keyframes th-tap-rapid {\n 0% { transform: translate(-50%, -50%) scale(1); opacity: 0; }\n 20% { transform: translate(-50%, -50%) scale(1.25); opacity: 1.0; }\n 50% { transform: translate(-50%, -50%) scale(1); opacity: 0; }\n 100% { transform: translate(-50%, -50%) scale(1); opacity: 0; }\n }\n @keyframes th-drag-h {\n 0%, 100% { left: 5%; }\n 50% { left: 95%; }\n }\n @keyframes th-tap-sides-l {\n 0%, 48%, 100% { opacity: 0.2; transform: translate(-50%, -50%) scale(1); }\n 12%, 38% { opacity: 1.0; transform: translate(-50%, -50%) scale(1.25); }\n }\n @keyframes th-tap-sides-r {\n 0%, 52%, 100% { opacity: 0.2; transform: translate(-50%, -50%) scale(1); }\n 62%, 88% { opacity: 1.0; transform: translate(-50%, -50%) scale(1.25); }\n }\n @keyframes th-tap-timed {\n 0%, 60% { transform: translate(-50%, -50%) scale(1); opacity: 0.25; }\n 70% { transform: translate(-50%, -50%) scale(1.4); opacity: 1.0; }\n 85%, 100% { transform: translate(-50%, -50%) scale(1); opacity: 0.25; }\n }\n @keyframes th-drag-free {\n 0% { transform: translate(-50%, -50%) translate(90%, -70%); }\n 12% { transform: translate(-50%, -50%) translate(30%, 90%); }\n 25% { transform: translate(-50%, -50%) translate(-90%, 30%); }\n 37% { transform: translate(-50%, -50%) translate(-30%, -90%); }\n 50% { transform: translate(-50%, -50%) translate(90%, -30%); }\n 62% { transform: translate(-50%, -50%) translate(30%, 90%); }\n 75% { transform: translate(-50%, -50%) translate(-90%, 30%); }\n 87% { transform: translate(-50%, -50%) translate(-30%, -90%); }\n 100% { transform: translate(-50%, -50%) translate(90%, -70%); }\n }\n @keyframes th-swipe-4dir {\n 0% { transform: translate(-50%, -50%) translate(140%, 0); opacity: 0; }\n 5% { transform: translate(-50%, -50%) translate(90%, 0); opacity: 0.9; }\n 8% { transform: translate(-50%, -50%) translate(-90%, 0); opacity: 0.9; }\n 13% { transform: translate(-50%, -50%) translate(-140%, 0); opacity: 0; }\n\n 25% { transform: translate(-50%, -50%) translate(-140%, 0); opacity: 0; }\n 30% { transform: translate(-50%, -50%) translate(-90%, 0); opacity: 0.9; }\n 33% { transform: translate(-50%, -50%) translate(90%, 0); opacity: 0.9; }\n 38% { transform: translate(-50%, -50%) translate(140%, 0); opacity: 0; }\n\n 50% { transform: translate(-50%, -50%) translate(0, 80%); opacity: 0; }\n 55% { transform: translate(-50%, -50%) translate(0, 50%); opacity: 0.9; }\n 58% { transform: translate(-50%, -50%) translate(0, -50%); opacity: 0.9; }\n 63% { transform: translate(-50%, -50%) translate(0, -80%); opacity: 0; }\n\n 75% { transform: translate(-50%, -50%) translate(0, -80%); opacity: 0; }\n 80% { transform: translate(-50%, -50%) translate(0, -50%); opacity: 0.9; }\n 83% { transform: translate(-50%, -50%) translate(0, 50%); opacity: 0.9; }\n 88% { transform: translate(-50%, -50%) translate(0, 80%); opacity: 0; }\n 100% { transform: translate(-50%, -50%) translate(0, 80%); opacity: 0; }\n }\n @keyframes th-swipe-h {\n 0% { transform: translate(-50%, -50%) translate(140%, 0); opacity: 0; }\n 5% { transform: translate(-50%, -50%) translate(90%, 0); opacity: 0.9; }\n 8% { transform: translate(-50%, -50%) translate(-90%, 0); opacity: 0.9; }\n 13% { transform: translate(-50%, -50%) translate(-140%, 0); opacity: 0; }\n\n 50% { transform: translate(-50%, -50%) translate(-140%, 0); opacity: 0; }\n 55% { transform: translate(-50%, -50%) translate(-90%, 0); opacity: 0.9; }\n 58% { transform: translate(-50%, -50%) translate(90%, 0); opacity: 0.9; }\n 63% { transform: translate(-50%, -50%) translate(140%, 0); opacity: 0; }\n 100% { transform: translate(-50%, -50%) translate(140%, 0); opacity: 0; }\n }\n @keyframes th-swipe-down {\n 0% { transform: translate(-50%, -50%) translate(0, -80%); opacity: 0; }\n 8% { transform: translate(-50%, -50%) translate(0, -50%); opacity: 0.9; }\n 14% { transform: translate(-50%, -50%) translate(0, 50%); opacity: 0.9; }\n 22% { transform: translate(-50%, -50%) translate(0, 80%); opacity: 0; }\n 100% { transform: translate(-50%, -50%) translate(0, 80%); opacity: 0; }\n }\n ", document.head.appendChild(t);
8
+ }
9
+ var n = {
10
+ position: "absolute",
11
+ borderRadius: "50%",
12
+ background: "rgba(255, 255, 255, 1.0)",
13
+ pointerEvents: "none"
14
+ };
15
+ function r(e, t) {
16
+ let r = document.createElement("div");
17
+ return Object.assign(r.style, {
18
+ ...n,
19
+ background: t,
20
+ width: e,
21
+ aspectRatio: "1"
22
+ }), r;
23
+ }
24
+ function i(e, t) {
25
+ let n = r("30%", t);
26
+ Object.assign(n.style, {
27
+ top: "50%",
28
+ left: "50%",
29
+ animation: "th-tap 1.2s ease-in-out infinite"
30
+ }), e.appendChild(n);
31
+ }
32
+ function a(e, t) {
33
+ let n = r("28%", t);
34
+ Object.assign(n.style, {
35
+ top: "50%",
36
+ left: "5%",
37
+ transform: "translate(-50%, -50%)",
38
+ opacity: "0.7",
39
+ animation: "th-drag-h 2s ease-in-out infinite"
40
+ }), e.appendChild(n);
41
+ }
42
+ function o(e, t) {
43
+ let n = r("22%", t);
44
+ Object.assign(n.style, {
45
+ top: "50%",
46
+ left: "15%",
47
+ opacity: "0.15",
48
+ animation: "th-tap-sides-l 2s ease-in-out infinite"
49
+ }), e.appendChild(n);
50
+ let i = r("22%", t);
51
+ Object.assign(i.style, {
52
+ top: "50%",
53
+ left: "85%",
54
+ opacity: "0.15",
55
+ animation: "th-tap-sides-r 2s ease-in-out infinite"
56
+ }), e.appendChild(i);
57
+ }
58
+ function s(e, t) {
59
+ let n = r("30%", t);
60
+ Object.assign(n.style, {
61
+ top: "50%",
62
+ left: "50%",
63
+ animation: "th-tap-timed 2s ease-in-out infinite"
64
+ }), e.appendChild(n);
65
+ }
66
+ function c(e, t) {
67
+ let n = r("26%", t);
68
+ Object.assign(n.style, {
69
+ top: "50%",
70
+ left: "50%",
71
+ opacity: "0.7",
72
+ animation: "th-drag-free 3.5s ease-in-out infinite"
73
+ }), e.appendChild(n);
74
+ }
75
+ function l(e, t) {
76
+ let n = r("18%", t);
77
+ Object.assign(n.style, {
78
+ top: "35%",
79
+ left: "50%",
80
+ animation: "th-swipe-4dir 4s linear infinite"
81
+ }), e.appendChild(n);
82
+ }
83
+ function u(e, t) {
84
+ let n = r("30%", t);
85
+ Object.assign(n.style, {
86
+ top: "50%",
87
+ left: "50%",
88
+ animation: "th-tap-rapid 0.9s ease-in-out infinite"
89
+ }), e.appendChild(n);
90
+ }
91
+ function d(e, t) {
92
+ let n = r("18%", t);
93
+ Object.assign(n.style, {
94
+ top: "50%",
95
+ left: "50%",
96
+ animation: "th-swipe-h 3s linear infinite"
97
+ }), e.appendChild(n);
98
+ }
99
+ function f(e, t) {
100
+ let n = r("24%", t);
101
+ Object.assign(n.style, {
102
+ top: "50%",
103
+ left: "50%",
104
+ animation: "th-swipe-down 2s linear infinite"
105
+ }), e.appendChild(n);
106
+ }
107
+ var p = {
108
+ tap: i,
109
+ "drag-horizontal": a,
110
+ "tap-sides": o,
111
+ "tap-timed": s,
112
+ "drag-free": c,
113
+ "swipe-4dir": l,
114
+ "swipe-horizontal": d,
115
+ "swipe-down": f,
116
+ "tap-rapid": u
117
+ }, m = {
118
+ "tap-sides": "75%",
119
+ "swipe-4dir": "85%",
120
+ "swipe-horizontal": "85%"
121
+ };
122
+ function h(e, n, r = "#ffffff", i) {
123
+ t();
124
+ let a = document.createElement("div");
125
+ return Object.assign(a.style, {
126
+ position: "absolute",
127
+ top: i?.top ?? "62%",
128
+ left: "50%",
129
+ transform: "translateX(-50%)",
130
+ width: i?.width ?? m[e] ?? "40%",
131
+ aspectRatio: "1",
132
+ pointerEvents: "none",
133
+ zIndex: "11",
134
+ display: "none"
135
+ }), p[e](a, r), n.appendChild(a), {
136
+ show() {
137
+ a.style.display = "block";
138
+ },
139
+ hide() {
140
+ a.style.display = "none";
141
+ },
142
+ destroy() {
143
+ a.remove();
144
+ }
145
+ };
146
+ }
147
+ //#endregion
148
+ export { h as t };
@@ -0,0 +1,3 @@
1
+ export declare function formatSecondsAsClock(secondsInput: number): string;
2
+ export declare function formatMillisecondsAsClock(millisecondsInput: number): string;
3
+ //# sourceMappingURL=timeFormat.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"timeFormat.d.ts","sourceRoot":"","sources":["../../../src/playus/helpers/timeFormat.ts"],"names":[],"mappings":"AAAA,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAWjE;AAED,wBAAgB,yBAAyB,CAAC,iBAAiB,EAAE,MAAM,GAAG,MAAM,CAO3E"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Supported languages - single source of truth
3
+ */
4
+ export declare const SUPPORTED_LANGUAGES: readonly ["en", "de", "fr", "es", "it"];
5
+ export type Language = typeof SUPPORTED_LANGUAGES[number];
6
+ export declare const DEFAULT_LANGUAGE: Language;
7
+ /**
8
+ * Translation dictionary type - key-first structure
9
+ * All translations for a key are grouped together
10
+ */
11
+ export type TranslationDict<Keys extends string> = {
12
+ [K in Keys]: {
13
+ [L in Language]?: string;
14
+ };
15
+ };
16
+ /**
17
+ * Get current language from URL parameter
18
+ * Reads ?lang=de from URL (follows debug.ts pattern)
19
+ *
20
+ * @returns Language code or default ('en')
21
+ *
22
+ * @example
23
+ * // URL: http://localhost:8080/?lang=de
24
+ * getCurrentLanguage(); // Returns 'de'
25
+ *
26
+ * // URL: http://localhost:8080/
27
+ * getCurrentLanguage(); // Returns 'en'
28
+ */
29
+ export declare function getCurrentLanguage(): Language;
30
+ /**
31
+ * Create a type-safe translation function with template support
32
+ *
33
+ * This is the main API that games use. It:
34
+ * 1. Detects current language from URL
35
+ * 2. Returns translation function with full type safety
36
+ * 3. Automatically falls back to English if translation missing
37
+ * 4. Supports template variables for dynamic strings
38
+ *
39
+ * @param translations - Dictionary of translations (key-first structure)
40
+ * @returns Translation function t(key) or t(key, vars)
41
+ *
42
+ * @example
43
+ * const translations = {
44
+ * hint: { en: "Tap to start", de: "Tippen zum Starten" },
45
+ * level: { en: "Level {n}", de: "Level {n}" }
46
+ * };
47
+ * const t = createTranslator(translations);
48
+ * t('hint'); // Returns "Tap to start" or "Tippen zum Starten"
49
+ * t('level', { n: 5 }); // Returns "Level 5"
50
+ */
51
+ export declare function createTranslator<Keys extends string>(translations: TranslationDict<Keys>): {
52
+ (key: Keys): string;
53
+ (key: Keys, vars: Record<string, string | number>): string;
54
+ };
55
+ //# sourceMappingURL=i18n.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/playus/i18n.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,eAAO,MAAM,mBAAmB,yCAA0C,CAAC;AAC3E,MAAM,MAAM,QAAQ,GAAG,OAAO,mBAAmB,CAAC,MAAM,CAAC,CAAC;AAC1D,eAAO,MAAM,gBAAgB,EAAE,QAAe,CAAC;AAE/C;;;GAGG;AACH,MAAM,MAAM,eAAe,CAAC,IAAI,SAAS,MAAM,IAAI;KAChD,CAAC,IAAI,IAAI,GAAG;SACV,CAAC,IAAI,QAAQ,CAAC,CAAC,EAAE,MAAM;KACzB;CACF,CAAC;AAEF;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,IAAI,QAAQ,CAM7C;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,SAAS,MAAM,EAClD,YAAY,EAAE,eAAe,CAAC,IAAI,CAAC,GAClC;IACD,CAAC,GAAG,EAAE,IAAI,GAAG,MAAM,CAAC;IACpB,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,CAAC;CAC5D,CAoBA"}
@@ -0,0 +1,19 @@
1
+ export { ColorConfig, NativeBridge, nativeBridge, roundScoreForBridge, } from './bridge';
2
+ export { createSeededRandom, seededBetween, seededFloatBetween, seededShuffle } from './random';
3
+ export { getGameSeed, getUrlParam } from './url-params';
4
+ export { clampGameplayDeltaMs, clampGameplayDeltaSeconds } from './timing';
5
+ export { createTranslator, getCurrentLanguage } from './i18n';
6
+ export type { Language, TranslationDict } from './i18n';
7
+ export { createTapToStartOverlay } from './tap-to-start';
8
+ export type { LocalizedText, TapToStartMode, TapToStartOverlay } from './tap-to-start';
9
+ export { sound } from './sound';
10
+ export type { SoundId, SoundPlayOptions } from './sound';
11
+ export { applyMobileSurfaceStyle, installMobileSelectionPolicy, installTouchDefaultGuard, } from './mobile-interaction';
12
+ export { createDebugOverlay, getRendererInfo, isDebugMode } from './overlay/debug';
13
+ export { createTouchHint } from './overlay/touch-hint';
14
+ export type { DebugOverlay } from './overlay/debug';
15
+ export type { TouchHint, TouchHintType } from './overlay/touch-hint';
16
+ export type { BackgroundConfig } from './types/background';
17
+ export { DEFAULT_BACKGROUND, getBackgroundColor, isTransparent } from './types/background';
18
+ export { formatMillisecondsAsClock, formatSecondsAsClock } from './helpers/timeFormat';
19
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/playus/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,mBAAmB,GACpB,MAAM,UAAU,CAAC;AAClB,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAChG,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,yBAAyB,EAAE,MAAM,UAAU,CAAC;AAC3E,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AAC9D,YAAY,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,QAAQ,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AACzD,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACvF,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAC;AAChC,YAAY,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AACzD,OAAO,EACL,uBAAuB,EACvB,4BAA4B,EAC5B,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,YAAY,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrE,YAAY,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC3F,OAAO,EAAE,yBAAyB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}