@rydr/game-sdk 6.0.0 → 7.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.
Files changed (44) hide show
  1. package/README.md +9 -0
  2. package/dist/client/PlatformClient.d.ts +25 -2
  3. package/dist/client/PlatformClient.d.ts.map +1 -1
  4. package/dist/client/PlatformClient.js +70 -21
  5. package/dist/client/PlatformClient.js.map +1 -1
  6. package/dist/host/PlatformHost.d.ts +6 -4
  7. package/dist/host/PlatformHost.d.ts.map +1 -1
  8. package/dist/host/PlatformHost.js +3 -3
  9. package/dist/host/PlatformHost.js.map +1 -1
  10. package/dist/protocol/boards.d.ts +13 -5
  11. package/dist/protocol/boards.d.ts.map +1 -1
  12. package/dist/protocol/boards.js.map +1 -1
  13. package/dist/protocol/buttons.d.ts +10 -6
  14. package/dist/protocol/buttons.d.ts.map +1 -1
  15. package/dist/protocol/buttons.js +10 -6
  16. package/dist/protocol/buttons.js.map +1 -1
  17. package/dist/protocol/glyphs.d.ts.map +1 -1
  18. package/dist/protocol/glyphs.js +22 -1
  19. package/dist/protocol/glyphs.js.map +1 -1
  20. package/dist/protocol/messages.d.ts +17 -15
  21. package/dist/protocol/messages.d.ts.map +1 -1
  22. package/dist/protocol/version.d.ts +2 -2
  23. package/dist/protocol/version.d.ts.map +1 -1
  24. package/dist/protocol/version.js +28 -2
  25. package/dist/protocol/version.js.map +1 -1
  26. package/dist/ui/README.md +45 -1
  27. package/dist/ui/index.d.ts +1 -0
  28. package/dist/ui/index.d.ts.map +1 -1
  29. package/dist/ui/index.js +1 -0
  30. package/dist/ui/index.js.map +1 -1
  31. package/dist/ui/keycap.d.ts +23 -0
  32. package/dist/ui/keycap.d.ts.map +1 -1
  33. package/dist/ui/keycap.js +42 -0
  34. package/dist/ui/keycap.js.map +1 -1
  35. package/dist/ui/pause-menu.d.ts +144 -0
  36. package/dist/ui/pause-menu.d.ts.map +1 -0
  37. package/dist/ui/pause-menu.js +500 -0
  38. package/dist/ui/pause-menu.js.map +1 -0
  39. package/dist/ui/showcase/index.d.ts.map +1 -1
  40. package/dist/ui/showcase/index.js +35 -1
  41. package/dist/ui/showcase/index.js.map +1 -1
  42. package/dist/ui/styles.js +21 -0
  43. package/dist/ui/styles.js.map +1 -1
  44. package/package.json +1 -1
@@ -0,0 +1,500 @@
1
+ /**
2
+ * PAUSE MENU — the in-game menu every RYDR game shares, opened by the game's own `OPTIONS` button.
3
+ *
4
+ * This is the GAME's menu, deliberately distinct from the PLATFORM's overlay (which the shell opens on
5
+ * `MENU` / `M` / phone MENU and which never reaches a game — see `ButtonName`). The platform overlay
6
+ * carries the rider's concerns: activity, hardware, volume, exit. This one carries the *game's*:
7
+ * resume, restart, back to your own menu. It deliberately quotes the platform overlay's visual language
8
+ * — dim + blurred backdrop, frosted rows, the staggered "warp" entrance — so pause reads as a RYDR
9
+ * surface one level shallower than the platform's, rather than as a second, unrelated menu.
10
+ *
11
+ * const pause = mountPauseMenu(document.body, {
12
+ * session,
13
+ * title: "Split Racing",
14
+ * canOpen: () => phase === "playing", // the game decides WHEN it may open
15
+ * onOpen: () => { paused = true }, // the game freezes its own loop
16
+ * onClose: () => { paused = false },
17
+ * items: [
18
+ * { label: "Restart", shortcut: "DIAMOND_UP", onSelect: () => restart() },
19
+ * { label: "Back to menu", onSelect: () => go("/") },
20
+ * ],
21
+ * });
22
+ *
23
+ * `Resume` is prepended for free, so the shortest useful menu is no items at all.
24
+ *
25
+ * **Everything the rider reads is in the GAME's font.** The title and every row inherit the host's
26
+ * font-family (override with the `font` option or the `--rydr-pause-font` custom property), because
27
+ * this menu belongs to the game, not to the shell — the one exception being the keycaps, which keep
28
+ * their standard RYDR type because they are pictures of the rider's physical controller.
29
+ *
30
+ * **What it does NOT do: stop your game.** No library can — your loop, timers and physics are yours.
31
+ * The menu tells you when it opens and closes; freeze there. What it *does* guarantee is that a game
32
+ * which keeps running cannot be *driven* while the rider is in the menu: it takes the controller over
33
+ * for the duration (`session.grabInput`), so your `onButton` handlers go quiet, `isDown`/`stick` read
34
+ * resting, and anything held is released first. It also declares `"menu"` to the shell while it's up,
35
+ * so trainer resistance eases like on any other non-racing screen, and restores `"playing"` on resume.
36
+ *
37
+ * A row may advertise a `shortcut` — the button the rider can press *in-game* to do the same thing
38
+ * (`Restart · Shortcut [Y]`). Binding that shortcut during play stays the game's own job; the menu
39
+ * only teaches it, and honours it while the menu itself is open (where it owns input anyway).
40
+ */
41
+ import { DEFAULT_GLYPH_SET } from "../protocol/glyphs.js";
42
+ import { createSpatialNav } from "../nav/spatial-nav.js";
43
+ import { createKeycap, createLabelKeycap } from "./keycap.js";
44
+ import { injectCss } from "./styles.js";
45
+ /**
46
+ * The rider's controller lettering: an explicit `glyphSet` wins, else the one a real `PlatformSession`
47
+ * carries, else Xbox. Probed structurally rather than declared on {@link PauseMenuSession} — same
48
+ * reasoning as `createKeycap`'s own probe: that type is the *minimal* slice the menu needs, and
49
+ * demanding a hardware snapshot would break every hand-rolled stub for a value that's optional anyway.
50
+ */
51
+ function resolveGlyphSet(opts) {
52
+ if (opts.glyphSet)
53
+ return opts.glyphSet;
54
+ const probe = opts.session;
55
+ return probe?.hardware?.current?.glyphSet ?? DEFAULT_GLYPH_SET;
56
+ }
57
+ /** Entrance/exit duration + per-row stagger, mirrored in the CSS below. */
58
+ const ANIM_MS = 500;
59
+ const STAGGER_MS = 26;
60
+ /** Rows warp outward this fraction of their distance from the list's centre. */
61
+ const WARP_PUSH = 0.9;
62
+ let pauseCssInjected = false;
63
+ function injectPauseCss() {
64
+ if (pauseCssInjected || document.getElementById("rydr-pause-css")) {
65
+ pauseCssInjected = true;
66
+ return;
67
+ }
68
+ const style = document.createElement("style");
69
+ style.id = "rydr-pause-css";
70
+ style.textContent = CSS;
71
+ document.head.appendChild(style);
72
+ pauseCssInjected = true;
73
+ }
74
+ /**
75
+ * Mount the pause menu (hidden until opened). Appended to `host` — normally `document.body`, so the
76
+ * overlay covers the game whatever the DOM under it looks like.
77
+ */
78
+ export function mountPauseMenu(host, opts) {
79
+ injectCss(); // the keycaps' own .rydr-ui-* styles
80
+ injectPauseCss();
81
+ const { session, openButton = "OPTIONS", resumeLabel = "Resume", activity = true, captureInput = true, cue, } = opts;
82
+ const glyphSet = resolveGlyphSet(opts);
83
+ let items = opts.items ? [...opts.items] : [];
84
+ let open = false;
85
+ let closing = false;
86
+ let closeTimer = null;
87
+ let releaseInput = null;
88
+ let nav = null;
89
+ /** The rows currently in the DOM, index-aligned with {@link rowItems}. */
90
+ let rows = [];
91
+ /** The item each row stands for — `Resume` first, then `items`. */
92
+ let rowItems = [];
93
+ /** Keycaps built for the current rows (disposed on every rebuild). */
94
+ let rowCaps = [];
95
+ // ── DOM ────────────────────────────────────────────────────────────────────────────────────────
96
+ const root = document.createElement("div");
97
+ root.className = "rydr-pause";
98
+ root.tabIndex = -1;
99
+ if (opts.font)
100
+ root.style.setProperty("--rydr-pause-font", opts.font);
101
+ const inner = document.createElement("div");
102
+ inner.className = "rydr-pause-inner";
103
+ const titleEl = document.createElement("div");
104
+ titleEl.className = "rydr-pause-title";
105
+ titleEl.textContent = opts.title;
106
+ const listEl = document.createElement("div");
107
+ listEl.className = "rydr-pause-list";
108
+ inner.append(titleEl, listEl);
109
+ root.appendChild(inner);
110
+ host.appendChild(root);
111
+ // Backdrop click resumes — the mouse equivalent of pressing the open button again.
112
+ root.addEventListener("click", (e) => {
113
+ if (e.target === root)
114
+ close();
115
+ });
116
+ // ── Keycap press feedback ──────────────────────────────────────────────────────────────────────
117
+ // The keycaps can't listen to `session.onButton`: while the menu holds an input grab, the session
118
+ // delivers nothing to ordinary listeners (that's the point of the grab). So they subscribe to this
119
+ // local source, which the menu feeds from whichever path is actually live.
120
+ const capListeners = new Set();
121
+ const capSource = {
122
+ onButton: (cb) => {
123
+ capListeners.add(cb);
124
+ return () => capListeners.delete(cb);
125
+ },
126
+ };
127
+ /**
128
+ * The confirm keycap, which RIDES the focused row: every row carries a fixed-width slot at its end
129
+ * and the cap is moved into the focused one. Deliberately not the platform overlay's fixed-position
130
+ * roving cap — these rows are full-width, so a cap anchored to a row's right edge would land on top
131
+ * of that row's shortcut keycap. Riding a real slot also needs no measuring, so it can't drift
132
+ * mid-animation or on scroll.
133
+ */
134
+ const confirmCap = createKeycap("DIAMOND_DOWN", {
135
+ variant: "solo",
136
+ colored: false,
137
+ animate: true,
138
+ press: capSource,
139
+ glyphSet,
140
+ });
141
+ confirmCap.el.classList.add("rydr-pause-cap");
142
+ // ── Rows ───────────────────────────────────────────────────────────────────────────────────────
143
+ function buildRows() {
144
+ for (const cap of rowCaps)
145
+ cap.dispose();
146
+ rowCaps = [];
147
+ listEl.textContent = "";
148
+ rowItems = [{ label: resumeLabel, onSelect: () => { }, shortcut: openButton }, ...items];
149
+ rows = rowItems.map((item, i) => {
150
+ const row = document.createElement("button");
151
+ row.type = "button";
152
+ row.className = "rydr-pause-row" + (item.danger ? " danger" : "");
153
+ if (item.disabled)
154
+ row.disabled = true;
155
+ row.style.setProperty("--i", String(i));
156
+ row.style.setProperty("--ri", String(rowItems.length - 1 - i));
157
+ const text = document.createElement("span");
158
+ text.className = "rydr-pause-text";
159
+ const label = document.createElement("span");
160
+ label.className = "rydr-pause-label";
161
+ label.textContent = item.label;
162
+ text.appendChild(label);
163
+ if (item.hint) {
164
+ const hint = document.createElement("span");
165
+ hint.className = "rydr-pause-hint";
166
+ hint.textContent = item.hint;
167
+ text.appendChild(hint);
168
+ }
169
+ row.appendChild(text);
170
+ const right = document.createElement("span");
171
+ right.className = "rydr-pause-right";
172
+ if (item.shortcut) {
173
+ const shortcut = document.createElement("span");
174
+ shortcut.className = "rydr-pause-shortcut";
175
+ const tag = document.createElement("span");
176
+ tag.className = "rydr-pause-shortcut-tag";
177
+ tag.textContent = "Shortcut";
178
+ const cap = createLabelKeycap(item.shortcut, { press: capSource, glyphSet });
179
+ rowCaps.push(cap);
180
+ if (item.disabled)
181
+ cap.setDisabled(true);
182
+ shortcut.append(tag, cap.el);
183
+ right.appendChild(shortcut);
184
+ }
185
+ // The slot the confirm keycap moves into when this row takes focus. Always present (and always
186
+ // the same width) so focus moving between rows never reflows the list.
187
+ const slot = document.createElement("span");
188
+ slot.className = "rydr-pause-confirm";
189
+ right.appendChild(slot);
190
+ row.appendChild(right);
191
+ row.addEventListener("click", () => select(i));
192
+ listEl.appendChild(row);
193
+ return row;
194
+ });
195
+ }
196
+ /**
197
+ * Set each row's warp offset from its position in the list, so the entrance flies rows out from (and
198
+ * back to) their own side of the centre. Measured with the transform-invariant layout box, never
199
+ * `getBoundingClientRect()`, which returns the live *transformed* rect mid-animation and would make
200
+ * every replay drift.
201
+ */
202
+ function computeOffsets() {
203
+ const cy = listEl.offsetHeight / 2;
204
+ for (const row of rows) {
205
+ const rc = row.offsetTop + row.offsetHeight / 2;
206
+ row.style.setProperty("--dy", ((rc - cy) * WARP_PUSH).toFixed(0) + "px");
207
+ }
208
+ }
209
+ /** Move the confirm keycap into the focused row's slot (out of the DOM when nothing is focused). */
210
+ function placeCap() {
211
+ const row = open ? nav?.current() ?? null : null;
212
+ const slot = row?.querySelector(".rydr-pause-confirm");
213
+ if (!slot) {
214
+ confirmCap.el.remove();
215
+ return;
216
+ }
217
+ if (confirmCap.el.parentNode !== slot)
218
+ slot.appendChild(confirmCap.el);
219
+ }
220
+ // ── Selection ──────────────────────────────────────────────────────────────────────────────────
221
+ /**
222
+ * Run row `i`. Closes FIRST by default — so the game is already unfrozen and holding its own input
223
+ * again by the time the action runs (a restart shouldn't begin behind a menu that's still up).
224
+ */
225
+ function select(i) {
226
+ const item = rowItems[i];
227
+ if (!item || item.disabled)
228
+ return;
229
+ confirmCap.setPressed(true);
230
+ setTimeout(() => confirmCap.setPressed(false), 160);
231
+ cue?.("nav.confirm");
232
+ if (item.keepOpen) {
233
+ item.onSelect();
234
+ return;
235
+ }
236
+ close();
237
+ item.onSelect();
238
+ }
239
+ // ── Input ──────────────────────────────────────────────────────────────────────────────────────
240
+ /** Route one `down` edge. Returns whether the menu consumed it. */
241
+ function handleButton(name) {
242
+ if (!open) {
243
+ if (name !== openButton)
244
+ return false;
245
+ return openMenu();
246
+ }
247
+ if (name === openButton) {
248
+ close();
249
+ return true;
250
+ }
251
+ // A row's advertised shortcut fires that row directly — the menu owns input here, so honouring it
252
+ // costs nothing and matches what the row promises.
253
+ const shortcutIndex = rowItems.findIndex((it) => it.shortcut === name && !it.disabled);
254
+ if (shortcutIndex > 0) {
255
+ select(shortcutIndex);
256
+ return true;
257
+ }
258
+ nav?.handle(name); // directions, A (→ onActivate), B (→ onBack → close)
259
+ return true;
260
+ }
261
+ /** Feed an edge to the in-menu keycaps, then route `down` edges. Shared by both input paths. */
262
+ function onEdge(e) {
263
+ for (const cb of capListeners)
264
+ cb(e);
265
+ if (e.edge === "down")
266
+ handleButton(e.name);
267
+ }
268
+ // The always-on subscription: it's what hears the open button while the menu is closed. While a grab
269
+ // is active this receives nothing (the grab is the only recipient) — hence the `releaseInput` guard,
270
+ // which keeps a session WITHOUT `grabInput` (a stub) working off this one path.
271
+ const offSession = session?.onButton((e) => {
272
+ if (releaseInput)
273
+ return; // the grab handler already has it
274
+ onEdge(e);
275
+ });
276
+ // ── Open / close ───────────────────────────────────────────────────────────────────────────────
277
+ function openMenu() {
278
+ if (open)
279
+ return true;
280
+ if (opts.canOpen && !opts.canOpen())
281
+ return false;
282
+ if (closeTimer !== null) {
283
+ clearTimeout(closeTimer);
284
+ closeTimer = null;
285
+ }
286
+ open = true;
287
+ closing = false;
288
+ root.classList.remove("closing");
289
+ root.classList.add("open");
290
+ buildRows();
291
+ computeOffsets();
292
+ if (captureInput && session?.grabInput)
293
+ releaseInput = session.grabInput(onEdge);
294
+ if (activity)
295
+ session?.setActivity?.("menu");
296
+ // Pull keyboard focus onto the overlay so a game that also handles DOM focus can't keep it.
297
+ root.focus?.();
298
+ nav = createSpatialNav({
299
+ root: listEl,
300
+ selector: ".rydr-pause-row:not([disabled])",
301
+ ring: false, // the ring is styled below
302
+ wrap: true, // a single-axis list: DOWN past the last returns to the first
303
+ pointer: true, // hover and controller share one focus state
304
+ cue,
305
+ onActivate: (el) => select(rows.indexOf(el)),
306
+ onBack: close,
307
+ onFocusChange: () => placeCap(),
308
+ });
309
+ nav.focusFirst(); // land on Resume — the safe default
310
+ placeCap();
311
+ opts.onOpen?.();
312
+ return true;
313
+ }
314
+ function close() {
315
+ if (!open && !closing)
316
+ return;
317
+ if (closeTimer !== null)
318
+ return; // already closing
319
+ open = false;
320
+ closing = true;
321
+ // Keep the overlay mounted and lit for the exit animation; `.closing` only fades the backdrop.
322
+ root.classList.remove("open");
323
+ root.classList.add("closing");
324
+ releaseInput?.();
325
+ releaseInput = null;
326
+ if (activity)
327
+ session?.setActivity?.("playing");
328
+ nav?.dispose();
329
+ nav = null;
330
+ // The cap leaves with the focus ring, but the rows stay mounted for the warp-out.
331
+ confirmCap.el.remove();
332
+ cue?.("nav.back");
333
+ opts.onClose?.();
334
+ closeTimer = setTimeout(() => {
335
+ closeTimer = null;
336
+ closing = false;
337
+ root.classList.remove("closing");
338
+ listEl.textContent = "";
339
+ for (const cap of rowCaps)
340
+ cap.dispose();
341
+ rowCaps = [];
342
+ rows = [];
343
+ }, ANIM_MS + rows.length * STAGGER_MS + 140);
344
+ }
345
+ return {
346
+ root,
347
+ open: openMenu,
348
+ close,
349
+ toggle: () => {
350
+ if (open) {
351
+ close();
352
+ return false;
353
+ }
354
+ return openMenu();
355
+ },
356
+ isOpen: () => open,
357
+ setItems: (next) => {
358
+ items = [...next];
359
+ if (open) {
360
+ buildRows();
361
+ computeOffsets();
362
+ nav?.revalidate();
363
+ nav?.focusFirst();
364
+ placeCap();
365
+ }
366
+ },
367
+ setTitle: (title) => {
368
+ titleEl.textContent = title;
369
+ },
370
+ handleButton,
371
+ dispose: () => {
372
+ offSession?.();
373
+ releaseInput?.();
374
+ releaseInput = null;
375
+ // Torn down mid-open (a game unmounting its screen): hand the trainer back to gameplay, or the
376
+ // rider would keep riding the menu's eased resistance with no menu on screen.
377
+ if (open && activity)
378
+ session?.setActivity?.("playing");
379
+ open = false;
380
+ if (closeTimer !== null)
381
+ clearTimeout(closeTimer);
382
+ nav?.dispose();
383
+ nav = null;
384
+ for (const cap of rowCaps)
385
+ cap.dispose();
386
+ rowCaps = [];
387
+ confirmCap.dispose();
388
+ capListeners.clear();
389
+ root.remove();
390
+ },
391
+ };
392
+ }
393
+ /**
394
+ * The overlay's own `.rydr-pause-*` CSS — self-contained, injected once, no global styles or CSS
395
+ * variables required. Deliberately quotes the platform menu's numbers (the same dim, blur, radii and
396
+ * warp curve) so the two surfaces read as one family.
397
+ *
398
+ * `font-family: var(--rydr-pause-font, inherit)` on every text rule is load-bearing: the menu must
399
+ * render in the GAME's font, which it gets by inheriting from the host. The keycaps are the exception —
400
+ * they carry their own type from `styles.ts`, because they depict the rider's physical controller.
401
+ */
402
+ const CSS = `
403
+ .rydr-pause { position: fixed; inset: 0; z-index: 1400; display: flex; justify-content: center;
404
+ overflow-x: hidden; overflow-y: auto; padding: clamp(40px, 7vh, 88px) 24px 96px;
405
+ font-family: var(--rydr-pause-font, inherit);
406
+ background: rgba(2, 6, 23, calc(0.82 * var(--bd, 0)));
407
+ -webkit-backdrop-filter: blur(calc(14px * var(--bd, 0)));
408
+ backdrop-filter: blur(calc(14px * var(--bd, 0)));
409
+ opacity: 0; pointer-events: none; visibility: hidden;
410
+ transition: background 0.45s ease, backdrop-filter 0.45s ease, opacity 0.35s ease; }
411
+ .rydr-pause:focus { outline: none; }
412
+ .rydr-pause.open { opacity: 1; pointer-events: auto; visibility: visible; --bd: 1; }
413
+ /* closing: stay visible so the rows can warp back out — only the backdrop fades */
414
+ .rydr-pause.closing { opacity: 1; visibility: visible; pointer-events: none; --bd: 0;
415
+ transition: background 0.45s ease, backdrop-filter 0.45s ease; }
416
+
417
+ .rydr-pause-inner { width: 100%; max-width: 620px; margin: auto 0; display: flex; flex-direction: column; gap: 26px; }
418
+ /* the game's name, in the game's font — the biggest thing on the screen */
419
+ .rydr-pause-title { font-family: var(--rydr-pause-font, inherit); text-align: center;
420
+ font-size: clamp(2.4rem, 8vw, 3.4rem); font-weight: 800; letter-spacing: -0.03em; line-height: 1;
421
+ color: #f2f5fb; text-shadow: 0 6px 30px rgba(0, 0, 0, 0.6); }
422
+ .rydr-pause-list { position: relative; display: flex; flex-direction: column; gap: 12px; }
423
+
424
+ /* a row: frosted glass, label left, shortcut right */
425
+ .rydr-pause-row { appearance: none; cursor: pointer; width: 100%; box-sizing: border-box;
426
+ display: flex; align-items: center; justify-content: space-between; gap: 18px;
427
+ min-height: 74px; padding: 0.9rem 1.1rem 0.9rem 1.4rem; border-radius: 18px;
428
+ font-family: var(--rydr-pause-font, inherit); color: #f2f5fb; text-align: left;
429
+ background: rgba(255, 255, 255, 0.06); border: 1px solid rgba(255, 255, 255, 0.14);
430
+ -webkit-backdrop-filter: blur(20px) saturate(120%);
431
+ backdrop-filter: blur(20px) saturate(120%);
432
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.08);
433
+ transition: transform 0.14s ease, border-color 0.14s ease, box-shadow 0.14s ease, background 0.14s ease;
434
+ will-change: transform, opacity, filter; }
435
+ .rydr-pause-row:hover:not([disabled]) { background: rgba(255, 255, 255, 0.1); border-color: rgba(255, 255, 255, 0.3); }
436
+ .rydr-pause-row:focus-visible { outline: none; }
437
+ .rydr-pause-row[disabled] { cursor: default; opacity: 0.45; }
438
+ .rydr-pause-row.danger .rydr-pause-label { color: #ff8b8b; }
439
+ .rydr-pause-text { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
440
+ .rydr-pause-label { font-family: var(--rydr-pause-font, inherit); font-weight: 700; font-size: 1.6rem;
441
+ letter-spacing: 0.01em; line-height: 1.1; overflow-wrap: anywhere; }
442
+ .rydr-pause-hint { font-family: var(--rydr-pause-font, inherit); font-size: 0.95rem; font-weight: 500;
443
+ color: rgba(238, 244, 255, 0.62); line-height: 1.25; }
444
+ /* right side: the "Shortcut" tag + its keycap, then the slot the confirm cap rides into */
445
+ .rydr-pause-right { display: flex; align-items: center; gap: 18px; flex: 0 0 auto; }
446
+ .rydr-pause-shortcut { display: flex; align-items: center; gap: 9px; flex: 0 0 auto; }
447
+ .rydr-pause-shortcut-tag { font-family: var(--rydr-pause-font, inherit); font-size: 0.7rem; font-weight: 700;
448
+ letter-spacing: 0.14em; text-transform: uppercase; color: rgba(238, 244, 255, 0.5); }
449
+ /* the slot is always there, at a FIXED width (one solo keycap), so focus moving row to row can never
450
+ reflow the list — only the cap inside it appears and disappears */
451
+ .rydr-pause-confirm { flex: 0 0 38px; height: 38px; display: flex; align-items: center; justify-content: center; }
452
+
453
+ /* roving controller focus: white ring + halo, as on the platform overlay */
454
+ .rydr-pause-row[nav-focused] { outline: none; transform: scale(1.02);
455
+ border-color: rgba(255, 255, 255, 0.9);
456
+ box-shadow: 0 0 0 3px #ffffff, 0 0 22px 0 rgba(255, 255, 255, 0.16), 0 22px 50px rgba(0, 0, 0, 0.55);
457
+ z-index: 2; }
458
+
459
+ /* the confirm keycap, sitting in the focused row's slot */
460
+ .rydr-pause-cap { pointer-events: none;
461
+ filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.7)) drop-shadow(0 8px 16px rgba(0, 0, 0, 0.5)); }
462
+
463
+ /* ── entrance / exit: rows warp in from their own side of the list, staggered ──
464
+ fill: backwards (not both) so the resting frame hands control back to plain CSS and the
465
+ [nav-focused] scale can take over without a flash. */
466
+ .rydr-pause.open .rydr-pause-row { animation: rydr-pause-warp-in ${ANIM_MS}ms cubic-bezier(0.1, 0.8, 0.4, 1) backwards;
467
+ animation-delay: calc(var(--ri, 0) * ${STAGGER_MS}ms); }
468
+ .rydr-pause.open .rydr-pause-title { animation: rydr-pause-warp-in-top ${ANIM_MS}ms cubic-bezier(0.1, 0.8, 0.4, 1) backwards; }
469
+ .rydr-pause.closing .rydr-pause-row { animation: rydr-pause-warp-out ${ANIM_MS}ms cubic-bezier(0.6, 0, 0.9, 0.2) both;
470
+ animation-delay: calc(var(--i, 0) * ${STAGGER_MS}ms); }
471
+ .rydr-pause.closing .rydr-pause-title { animation: rydr-pause-warp-out-top ${ANIM_MS}ms cubic-bezier(0.6, 0, 0.9, 0.2) both; }
472
+ @keyframes rydr-pause-warp-in {
473
+ 0% { opacity: 0; transform: translateY(calc(var(--dy, 0px) * 1.5)) scale(2.4); filter: blur(6px); }
474
+ 55% { opacity: 1; filter: blur(0); }
475
+ 100% { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
476
+ }
477
+ @keyframes rydr-pause-warp-out {
478
+ 0% { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
479
+ 45% { opacity: 1; filter: blur(0); }
480
+ 100% { opacity: 0; transform: translateY(calc(var(--dy, 0px) * 1.5)) scale(2.4); filter: blur(6px); }
481
+ }
482
+ @keyframes rydr-pause-warp-in-top {
483
+ 0% { opacity: 0; transform: translateY(-70px) scale(1.9); filter: blur(6px); }
484
+ 55% { opacity: 1; filter: blur(0); }
485
+ 100% { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
486
+ }
487
+ @keyframes rydr-pause-warp-out-top {
488
+ 0% { opacity: 1; transform: translateY(0) scale(1); filter: blur(0); }
489
+ 45% { opacity: 1; filter: blur(0); }
490
+ 100% { opacity: 0; transform: translateY(-70px) scale(1.9); filter: blur(6px); }
491
+ }
492
+ @media (prefers-reduced-motion: reduce) {
493
+ .rydr-pause.open .rydr-pause-row,
494
+ .rydr-pause.open .rydr-pause-title { animation: rydr-pause-fade 0.3s ease both !important; }
495
+ .rydr-pause.closing .rydr-pause-row,
496
+ .rydr-pause.closing .rydr-pause-title { animation: rydr-pause-fade 0.2s ease reverse both !important; }
497
+ @keyframes rydr-pause-fade { from { opacity: 0; } to { opacity: 1; } }
498
+ }
499
+ `;
500
+ //# sourceMappingURL=pause-menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pause-menu.js","sourceRoot":"","sources":["../../src/ui/pause-menu.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/showcase/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA0BH,OAAO,EAA2B,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAE7E,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAoBpD,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,mGAAmG;AACnG,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,GAAE,iBAAsB,GAAG,UAAU,CA6W3F"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/showcase/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AA6BH,OAAO,EAA2B,KAAK,eAAe,EAAE,MAAM,cAAc,CAAC;AAE7E,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAoBpD,MAAM,WAAW,iBAAiB;IAChC;;;OAGG;IACH,OAAO,CAAC,EAAE,eAAe,CAAC;IAC1B;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,UAAU;IACzB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,mGAAmG;AACnG,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,GAAE,iBAAsB,GAAG,UAAU,CA2Z3F"}
@@ -12,7 +12,7 @@
12
12
  * components never pulls the catalog into its bundle. Pass a real `session` for live watts + real
13
13
  * button reflection; omit it and the catalog renders static.
14
14
  */
15
- import { createKeycap, createDpadKeycap, mountCard, mountActionCard, mountActionDiamond, mountDialogueCard, mountChoiceCard, mountLabeledDiamond, mountSoloLabeledDiamond, mountRarityCard, createPowerRace, RARITY, } from "../index.js";
15
+ import { createKeycap, createDpadKeycap, mountCard, mountActionCard, mountActionDiamond, mountDialogueCard, mountChoiceCard, mountLabeledDiamond, mountSoloLabeledDiamond, mountRarityCard, mountPauseMenu, createPowerRace, RARITY, } from "../index.js";
16
16
  import { Gallery, el, makeCanvas } from "./gallery.js";
17
17
  const FACES = ["DIAMOND_DOWN", "DIAMOND_LEFT", "DIAMOND_UP", "DIAMOND_RIGHT"];
18
18
  const DIRS = ["UP", "RIGHT", "DOWN", "LEFT"];
@@ -50,6 +50,7 @@ export function mountUiShowcase(host, opts = {}) {
50
50
  const ftp = session?.identity.ftp ?? 250;
51
51
  let race;
52
52
  let raceHideT = 0;
53
+ let pause;
53
54
  const launchRace = (o) => {
54
55
  race?.dispose();
55
56
  raceHideT = 0;
@@ -351,6 +352,38 @@ export function mountUiShowcase(host, opts = {}) {
351
352
  text: { heading: "RÉSONANCE INSTABLE", prompt: "PÉDALE POUR CHARGER", deadlineLabel: "INSTABILITÉ", chargeLabel: "CHARGE", win: "CHARGÉE", lose: "TROP TARD" },
352
353
  });
353
354
  }
355
+ // --- the in-game pause menu -------------------------------------------
356
+ g.section("Pause menu (overlay)", "The GAME's own menu, opened in-game by OPTIONS (keyboard O, phone OPT, the pad's Menu/Options/+ button) — Resume is free, the game adds the rest. ▲▼ move, A picks, OPTIONS or B resumes, and a row can advertise the shortcut that does the same thing during play. Every word is in the GAME's font (inherited from the page — the second button forces one instead). With a real session it takes the controller over while it's up: the button monitor above goes quiet until you resume.");
357
+ {
358
+ const row = el(g.list, "div", "bs-btnrow");
359
+ const status = el(g.list, "div", "bs-cap", " ");
360
+ const launch = (title, items, font) => {
361
+ pause?.dispose();
362
+ pause = mountPauseMenu(document.body, {
363
+ session,
364
+ title,
365
+ items,
366
+ font,
367
+ onOpen: () => { status.textContent = "Paused — the game would freeze its loop here."; },
368
+ onClose: () => { status.textContent = "Resumed."; },
369
+ });
370
+ pause.open();
371
+ };
372
+ const pick = (what) => () => { status.textContent = `Picked: ${what}`; };
373
+ const mk = (label, run) => {
374
+ const b = el(row, "button", "bs-btn", label);
375
+ b.addEventListener("click", run);
376
+ };
377
+ mk("Open pause menu", () => launch("Split Racing", [
378
+ { label: "Restart", shortcut: "DIAMOND_UP", onSelect: pick("Restart") },
379
+ { label: "Back to menu", onSelect: pick("Back to menu") },
380
+ ]));
381
+ mk("Every row state (+ forced font)", () => launch("Tavern Panic", [
382
+ { label: "Restart", shortcut: "DIAMOND_UP", hint: "You'll lose this run's score", onSelect: pick("Restart") },
383
+ { label: "Skip this level", shortcut: "RT", disabled: true, onSelect: pick("Skip") },
384
+ { label: "Back to menu", danger: true, onSelect: pick("Back to menu") },
385
+ ], "Georgia, 'Times New Roman', serif"));
386
+ }
354
387
  g.run({
355
388
  session,
356
389
  perFrame: (dt) => {
@@ -389,6 +422,7 @@ export function mountUiShowcase(host, opts = {}) {
389
422
  choice?.dispose();
390
423
  dialogue?.dispose();
391
424
  race?.dispose();
425
+ pause?.dispose();
392
426
  for (const c of rarityCards)
393
427
  c.dispose();
394
428
  g.dispose();