@rydr/game-sdk 7.0.1 → 8.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 (115) hide show
  1. package/README.md +47 -4
  2. package/dist/client/PlatformClient.d.ts +14 -7
  3. package/dist/client/PlatformClient.d.ts.map +1 -1
  4. package/dist/client/PlatformClient.js.map +1 -1
  5. package/dist/difficulty/index.d.ts +141 -0
  6. package/dist/difficulty/index.d.ts.map +1 -0
  7. package/dist/difficulty/index.js +300 -0
  8. package/dist/difficulty/index.js.map +1 -0
  9. package/dist/nav/README.md +1 -1
  10. package/dist/protocol/glyphs.d.ts +7 -0
  11. package/dist/protocol/glyphs.d.ts.map +1 -1
  12. package/dist/protocol/glyphs.js +26 -0
  13. package/dist/protocol/glyphs.js.map +1 -1
  14. package/dist/protocol/version.d.ts +1 -1
  15. package/dist/protocol/version.js +1 -1
  16. package/dist/ui/README.md +143 -11
  17. package/dist/ui/action-diamond.js +1 -1
  18. package/dist/ui/action-diamond.js.map +1 -1
  19. package/dist/ui/controller/combo-demo.d.ts +74 -0
  20. package/dist/ui/controller/combo-demo.d.ts.map +1 -0
  21. package/dist/ui/controller/combo-demo.js +295 -0
  22. package/dist/ui/controller/combo-demo.js.map +1 -0
  23. package/dist/ui/controller/combo-strip.d.ts +36 -0
  24. package/dist/ui/controller/combo-strip.d.ts.map +1 -0
  25. package/dist/ui/controller/combo-strip.js +66 -0
  26. package/dist/ui/controller/combo-strip.js.map +1 -0
  27. package/dist/ui/controller/control-trainer.d.ts +132 -0
  28. package/dist/ui/controller/control-trainer.d.ts.map +1 -0
  29. package/dist/ui/controller/control-trainer.js +570 -0
  30. package/dist/ui/controller/control-trainer.js.map +1 -0
  31. package/dist/ui/controller/controller-map.d.ts +82 -0
  32. package/dist/ui/controller/controller-map.d.ts.map +1 -0
  33. package/dist/ui/controller/controller-map.js +291 -0
  34. package/dist/ui/controller/controller-map.js.map +1 -0
  35. package/dist/ui/controller/controller-pad.d.ts +122 -0
  36. package/dist/ui/controller/controller-pad.d.ts.map +1 -0
  37. package/dist/ui/controller/controller-pad.js +200 -0
  38. package/dist/ui/controller/controller-pad.js.map +1 -0
  39. package/dist/ui/controller/index.d.ts +26 -0
  40. package/dist/ui/controller/index.d.ts.map +1 -0
  41. package/dist/ui/controller/index.js +26 -0
  42. package/dist/ui/controller/index.js.map +1 -0
  43. package/dist/ui/controller/input-chip.d.ts +101 -0
  44. package/dist/ui/controller/input-chip.d.ts.map +1 -0
  45. package/dist/ui/controller/input-chip.js +323 -0
  46. package/dist/ui/controller/input-chip.js.map +1 -0
  47. package/dist/ui/controller/layouts/dualsense.d.ts +12 -0
  48. package/dist/ui/controller/layouts/dualsense.d.ts.map +1 -0
  49. package/dist/ui/controller/layouts/dualsense.js +64 -0
  50. package/dist/ui/controller/layouts/dualsense.js.map +1 -0
  51. package/dist/ui/controller/layouts/index.d.ts +20 -0
  52. package/dist/ui/controller/layouts/index.d.ts.map +1 -0
  53. package/dist/ui/controller/layouts/index.js +34 -0
  54. package/dist/ui/controller/layouts/index.js.map +1 -0
  55. package/dist/ui/controller/layouts/joycon.d.ts +21 -0
  56. package/dist/ui/controller/layouts/joycon.d.ts.map +1 -0
  57. package/dist/ui/controller/layouts/joycon.js +76 -0
  58. package/dist/ui/controller/layouts/joycon.js.map +1 -0
  59. package/dist/ui/controller/layouts/switch-pro.d.ts +12 -0
  60. package/dist/ui/controller/layouts/switch-pro.d.ts.map +1 -0
  61. package/dist/ui/controller/layouts/switch-pro.js +60 -0
  62. package/dist/ui/controller/layouts/switch-pro.js.map +1 -0
  63. package/dist/ui/controller/layouts/xbox.d.ts +11 -0
  64. package/dist/ui/controller/layouts/xbox.d.ts.map +1 -0
  65. package/dist/ui/controller/layouts/xbox.js +59 -0
  66. package/dist/ui/controller/layouts/xbox.js.map +1 -0
  67. package/dist/ui/controller/layouts/zwift.d.ts +27 -0
  68. package/dist/ui/controller/layouts/zwift.d.ts.map +1 -0
  69. package/dist/ui/controller/layouts/zwift.js +62 -0
  70. package/dist/ui/controller/layouts/zwift.js.map +1 -0
  71. package/dist/ui/controller/pad-svg.d.ts +105 -0
  72. package/dist/ui/controller/pad-svg.d.ts.map +1 -0
  73. package/dist/ui/controller/pad-svg.js +289 -0
  74. package/dist/ui/controller/pad-svg.js.map +1 -0
  75. package/dist/ui/controller/parts-map.d.ts +96 -0
  76. package/dist/ui/controller/parts-map.d.ts.map +1 -0
  77. package/dist/ui/controller/parts-map.js +187 -0
  78. package/dist/ui/controller/parts-map.js.map +1 -0
  79. package/dist/ui/controller/styles.d.ts +19 -0
  80. package/dist/ui/controller/styles.d.ts.map +1 -0
  81. package/dist/ui/controller/styles.js +364 -0
  82. package/dist/ui/controller/styles.js.map +1 -0
  83. package/dist/ui/controller/tokens.d.ts +106 -0
  84. package/dist/ui/controller/tokens.d.ts.map +1 -0
  85. package/dist/ui/controller/tokens.js +50 -0
  86. package/dist/ui/controller/tokens.js.map +1 -0
  87. package/dist/ui/controller/types.d.ts +157 -0
  88. package/dist/ui/controller/types.d.ts.map +1 -0
  89. package/dist/ui/controller/types.js +61 -0
  90. package/dist/ui/controller/types.js.map +1 -0
  91. package/dist/ui/index.d.ts +9 -1
  92. package/dist/ui/index.d.ts.map +1 -1
  93. package/dist/ui/index.js +9 -1
  94. package/dist/ui/index.js.map +1 -1
  95. package/dist/ui/keycap.d.ts +65 -20
  96. package/dist/ui/keycap.d.ts.map +1 -1
  97. package/dist/ui/keycap.js +99 -28
  98. package/dist/ui/keycap.js.map +1 -1
  99. package/dist/ui/{pause-menu.d.ts → option-menu.d.ts} +34 -24
  100. package/dist/ui/option-menu.d.ts.map +1 -0
  101. package/dist/ui/{pause-menu.js → option-menu.js} +134 -90
  102. package/dist/ui/option-menu.js.map +1 -0
  103. package/dist/ui/showcase/gallery.d.ts +13 -1
  104. package/dist/ui/showcase/gallery.d.ts.map +1 -1
  105. package/dist/ui/showcase/gallery.js.map +1 -1
  106. package/dist/ui/showcase/index.d.ts.map +1 -1
  107. package/dist/ui/showcase/index.js +265 -9
  108. package/dist/ui/showcase/index.js.map +1 -1
  109. package/dist/ui/solo-labeled-diamond.js +1 -1
  110. package/dist/ui/solo-labeled-diamond.js.map +1 -1
  111. package/dist/ui/styles.js +14 -9
  112. package/dist/ui/styles.js.map +1 -1
  113. package/package.json +5 -1
  114. package/dist/ui/pause-menu.d.ts.map +0 -1
  115. package/dist/ui/pause-menu.js.map +0 -1
@@ -0,0 +1,74 @@
1
+ /**
2
+ * COMBO DEMO — the dynamic view: a looping animation of the combination a rider must perform.
3
+ *
4
+ * ```ts
5
+ * const demo = mountComboDemo(host, {
6
+ * press: session, // for the rider's lettering, NOT for input
7
+ * action: { label: "Dash", combo: ["LT", "LSTICK:left"] },
8
+ * });
9
+ * demo.setAction(next); // swaps cleanly mid-loop
10
+ * ```
11
+ *
12
+ * The timeline is **derived from the combo**, never authored per action, which is what keeps it generic:
13
+ *
14
+ * - *hold* (default) — idle, then each control engages in turn 200 ms apart (a stick eases to full
15
+ * deflection rather than snapping), everything holds together for a beat, releases, and repeats. The
16
+ * stagger is the point: it shows that the controls are held *simultaneously*, which a single
17
+ * all-at-once flash does not.
18
+ * - *sequence* (`action.sequence`) — each control presses, holds and releases in turn, with the strip's
19
+ * `→` separators filling in behind the current step.
20
+ * - a stick token with no direction sweeps once around its travel circle: "any direction".
21
+ *
22
+ * It drives exactly the same `PadDriver.setPart` / `setStick` and `ComboStrip.setPressed` that real input
23
+ * drives in the static view, so the demo and the live pad cannot drift apart — there is one renderer.
24
+ *
25
+ * **Self-driving, unlike `createPowerRace`.** This is an attract animation with no gameplay coupling, so
26
+ * it owns its own `requestAnimationFrame` rather than asking the game for a `tick(dt)`. It pauses while
27
+ * the tab is hidden, and `play()`/`pause()` are there for the caller.
28
+ *
29
+ * **`prefers-reduced-motion` is honoured by not animating at all**: the demo renders the fully-engaged end
30
+ * state statically. A looping motion graphic is precisely what that media query exists to suppress, and a
31
+ * still frame of "all of this, held together" still answers the rider's question.
32
+ */
33
+ import type { GlyphSet } from "../../protocol/glyphs.js";
34
+ import { type ControllerPad, type PadInputSource } from "./controller-pad.js";
35
+ import type { StickPictogramStyle } from "./input-chip.js";
36
+ import { type ControlAction } from "./tokens.js";
37
+ import type { PadBrandKey } from "./types.js";
38
+ export interface ComboDemoOptions {
39
+ /** The single action to demonstrate. */
40
+ action: ControlAction;
41
+ /**
42
+ * A session, used ONLY to learn the rider's lettering and to follow pad swaps — the demo deliberately
43
+ * does not wire it to input. The animation is the sole writer of the drawing's state; letting real
44
+ * presses write it too would make the two fight for the same classes every frame.
45
+ */
46
+ press?: PadInputSource;
47
+ brand?: PadBrandKey;
48
+ glyphSet?: GlyphSet;
49
+ /** Draw the controller. Default true; `false` gives a compact chips-only cue. */
50
+ showPad?: boolean;
51
+ /** Show the action's label above the animation. Default true. */
52
+ showLabel?: boolean;
53
+ size?: "sm" | "md" | "lg";
54
+ labels?: boolean;
55
+ colored?: boolean;
56
+ accent?: string;
57
+ /** Drawing convention for the joystick pictograms in the chips. See {@link StickPictogramStyle}. */
58
+ stickStyle?: StickPictogramStyle;
59
+ /** Playback rate. `1` = normal, `0.5` = half speed for a slow walkthrough. */
60
+ speed?: number;
61
+ /** Called each time the loop completes a full cycle. */
62
+ onCycle?: () => void;
63
+ }
64
+ export interface ComboDemo {
65
+ readonly root: HTMLDivElement;
66
+ readonly pad: ControllerPad | undefined;
67
+ setAction(action: ControlAction): void;
68
+ play(): void;
69
+ pause(): void;
70
+ setSpeed(mult: number): void;
71
+ dispose(): void;
72
+ }
73
+ export declare function mountComboDemo(host: HTMLElement, opts: ComboDemoOptions): ComboDemo;
74
+ //# sourceMappingURL=combo-demo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combo-demo.d.ts","sourceRoot":"","sources":["../../../src/ui/controller/combo-demo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzD,OAAO,EAAsB,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAClG,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAG3D,OAAO,EAAgC,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AAC/E,OAAO,KAAK,EAAE,WAAW,EAAW,MAAM,YAAY,CAAC;AAEvD,MAAM,WAAW,gBAAgB;IAC/B,wCAAwC;IACxC,MAAM,EAAE,aAAa,CAAC;IACtB;;;;OAIG;IACH,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,iFAAiF;IACjF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,iEAAiE;IACjE,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oGAAoG;IACpG,UAAU,CAAC,EAAE,mBAAmB,CAAC;IACjC,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,aAAa,GAAG,SAAS,CAAC;IACxC,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,IAAI,CAAC;IACvC,IAAI,IAAI,IAAI,CAAC;IACb,KAAK,IAAI,IAAI,CAAC;IACd,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,IAAI,IAAI,CAAC;CACjB;AA4CD,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,gBAAgB,GAAG,SAAS,CA4OnF"}
@@ -0,0 +1,295 @@
1
+ /**
2
+ * COMBO DEMO — the dynamic view: a looping animation of the combination a rider must perform.
3
+ *
4
+ * ```ts
5
+ * const demo = mountComboDemo(host, {
6
+ * press: session, // for the rider's lettering, NOT for input
7
+ * action: { label: "Dash", combo: ["LT", "LSTICK:left"] },
8
+ * });
9
+ * demo.setAction(next); // swaps cleanly mid-loop
10
+ * ```
11
+ *
12
+ * The timeline is **derived from the combo**, never authored per action, which is what keeps it generic:
13
+ *
14
+ * - *hold* (default) — idle, then each control engages in turn 200 ms apart (a stick eases to full
15
+ * deflection rather than snapping), everything holds together for a beat, releases, and repeats. The
16
+ * stagger is the point: it shows that the controls are held *simultaneously*, which a single
17
+ * all-at-once flash does not.
18
+ * - *sequence* (`action.sequence`) — each control presses, holds and releases in turn, with the strip's
19
+ * `→` separators filling in behind the current step.
20
+ * - a stick token with no direction sweeps once around its travel circle: "any direction".
21
+ *
22
+ * It drives exactly the same `PadDriver.setPart` / `setStick` and `ComboStrip.setPressed` that real input
23
+ * drives in the static view, so the demo and the live pad cannot drift apart — there is one renderer.
24
+ *
25
+ * **Self-driving, unlike `createPowerRace`.** This is an attract animation with no gameplay coupling, so
26
+ * it owns its own `requestAnimationFrame` rather than asking the game for a `tick(dt)`. It pauses while
27
+ * the tab is hidden, and `play()`/`pause()` are there for the caller.
28
+ *
29
+ * **`prefers-reduced-motion` is honoured by not animating at all**: the demo renders the fully-engaged end
30
+ * state statically. A looping motion graphic is precisely what that media query exists to suppress, and a
31
+ * still frame of "all of this, held together" still answers the rider's question.
32
+ */
33
+ import { createComboStrip } from "./combo-strip.js";
34
+ import { mountControllerPad } from "./controller-pad.js";
35
+ import { litPartOfControl } from "./parts-map.js";
36
+ import { injectPadCss } from "./styles.js";
37
+ import { parseToken, STICK_DIR_VECTOR } from "./tokens.js";
38
+ // Timings in seconds. Tuned to read at a glance from a few feet away, not to be brisk.
39
+ const HOLD_IDLE = 0.4;
40
+ const HOLD_STAGGER = 0.2;
41
+ const HOLD_RAMP = 0.2;
42
+ const HOLD_SUSTAIN = 0.7;
43
+ const HOLD_RELEASE = 0.25;
44
+ const HOLD_TAIL = 0.5;
45
+ const SEQ_IDLE = 0.4;
46
+ const SEQ_PRESS = 0.35;
47
+ const SEQ_GAP = 0.15;
48
+ const SEQ_TAIL = 0.4;
49
+ /** One full revolution of an undirected stick sweep. */
50
+ const SWEEP_PERIOD = 1.2;
51
+ const smoothstep = (t) => {
52
+ const c = Math.max(0, Math.min(1, t));
53
+ return c * c * (3 - 2 * c);
54
+ };
55
+ function channelsOf(action) {
56
+ return action.combo.map((token, index) => {
57
+ const t = parseToken(token);
58
+ if (t.kind === "stick") {
59
+ return {
60
+ index,
61
+ stick: {
62
+ side: t.stick === "LSTICK" ? "l" : "r",
63
+ vec: t.dir ? STICK_DIR_VECTOR[t.dir] : null, // null = undirected → sweep
64
+ },
65
+ };
66
+ }
67
+ return { index, part: litPartOfControl(t.name) };
68
+ });
69
+ }
70
+ export function mountComboDemo(host, opts) {
71
+ injectPadCss();
72
+ const root = document.createElement("div");
73
+ root.className = "rydr-paddemo";
74
+ host.appendChild(root);
75
+ const showPad = opts.showPad ?? true;
76
+ const showLabel = opts.showLabel ?? true;
77
+ let speed = opts.speed ?? 1;
78
+ let labelEl;
79
+ let hintEl;
80
+ if (showLabel) {
81
+ labelEl = document.createElement("div");
82
+ labelEl.className = "rydr-paddemo-label";
83
+ root.appendChild(labelEl);
84
+ hintEl = document.createElement("div");
85
+ hintEl.className = "rydr-paddemo-hint";
86
+ root.appendChild(hintEl);
87
+ }
88
+ const padCell = document.createElement("div");
89
+ const stripCell = document.createElement("div");
90
+ if (showPad)
91
+ root.appendChild(padCell);
92
+ root.appendChild(stripCell);
93
+ // Glyph-only shim: gives the pad the rider's lettering and pad-swap notifications, while its inert
94
+ // `onButton` guarantees real presses never write the drawing the animation owns.
95
+ const glyphOnly = opts.press && {
96
+ onButton: () => () => { },
97
+ hardware: opts.press.hardware,
98
+ };
99
+ const pad = showPad
100
+ ? mountControllerPad(padCell, {
101
+ press: glyphOnly,
102
+ brand: opts.brand,
103
+ glyphSet: opts.glyphSet,
104
+ size: opts.size ?? "md",
105
+ labels: opts.labels,
106
+ accent: opts.accent,
107
+ })
108
+ : undefined;
109
+ let action = opts.action;
110
+ let channels = channelsOf(action);
111
+ let strip;
112
+ function buildStrip() {
113
+ strip = createComboStrip(action, {
114
+ glyphSet: opts.glyphSet ?? pad?.glyphSet() ?? undefined,
115
+ colored: opts.colored,
116
+ stickStyle: opts.stickStyle,
117
+ });
118
+ stripCell.textContent = "";
119
+ stripCell.appendChild(strip.el);
120
+ }
121
+ function syncLabel() {
122
+ if (labelEl)
123
+ labelEl.textContent = action.label;
124
+ if (hintEl) {
125
+ hintEl.textContent = action.hint ?? "";
126
+ hintEl.style.display = action.hint ? "" : "none";
127
+ }
128
+ }
129
+ buildStrip();
130
+ syncLabel();
131
+ /** Total cycle length, in seconds at speed 1. */
132
+ function cycleLength() {
133
+ const n = Math.max(1, action.combo.length);
134
+ if (action.sequence)
135
+ return SEQ_IDLE + n * (SEQ_PRESS + SEQ_GAP) + SEQ_TAIL;
136
+ return HOLD_IDLE + (n - 1) * HOLD_STAGGER + HOLD_RAMP + HOLD_SUSTAIN + HOLD_RELEASE + HOLD_TAIL;
137
+ }
138
+ /**
139
+ * Write the state of the whole combination at cycle time `t` (seconds).
140
+ *
141
+ * One function for both modes and for the reduced-motion still, so there is exactly one place that
142
+ * decides what "engaged" looks like.
143
+ */
144
+ function renderAt(t) {
145
+ const driver = pad?.driver();
146
+ if (action.sequence) {
147
+ let completed = 0;
148
+ channels.forEach((ch) => {
149
+ const start = SEQ_IDLE + ch.index * (SEQ_PRESS + SEQ_GAP);
150
+ const end = start + SEQ_PRESS;
151
+ if (t >= end)
152
+ completed++;
153
+ const on = t >= start && t < end;
154
+ const ramp = on ? smoothstep((t - start) / Math.min(0.12, SEQ_PRESS / 3)) : 0;
155
+ applyChannel(ch, on, ramp, t - start, driver);
156
+ });
157
+ strip.setStep(completed);
158
+ return;
159
+ }
160
+ const n = Math.max(1, action.combo.length);
161
+ const allDown = HOLD_IDLE + (n - 1) * HOLD_STAGGER + HOLD_RAMP;
162
+ const sustainEnd = allDown + HOLD_SUSTAIN;
163
+ const releaseEnd = sustainEnd + HOLD_RELEASE;
164
+ for (const ch of channels) {
165
+ const onset = HOLD_IDLE + ch.index * HOLD_STAGGER;
166
+ const on = t >= onset && t < sustainEnd;
167
+ let ramp = 0;
168
+ if (t >= onset && t < sustainEnd)
169
+ ramp = smoothstep((t - onset) / HOLD_RAMP);
170
+ else if (t >= sustainEnd && t < releaseEnd)
171
+ ramp = 1 - smoothstep((t - sustainEnd) / HOLD_RELEASE);
172
+ applyChannel(ch, on, ramp, t - onset, driver);
173
+ }
174
+ }
175
+ function applyChannel(ch, on, ramp, since, driver) {
176
+ if (ch.stick) {
177
+ let x;
178
+ let y;
179
+ if (ch.stick.vec) {
180
+ x = ch.stick.vec[0] * ramp;
181
+ y = ch.stick.vec[1] * ramp;
182
+ }
183
+ else {
184
+ // Undirected: sweep the cap around its circle so it reads as "any direction".
185
+ const angle = (Math.max(0, since) / SWEEP_PERIOD) * Math.PI * 2;
186
+ x = Math.cos(angle) * ramp;
187
+ y = Math.sin(angle) * ramp;
188
+ }
189
+ driver?.setStick(ch.stick.side, x, y);
190
+ strip.setDeflectionAt(ch.index, x, y);
191
+ // A stick is "pressed" in the strip once it's meaningfully deflected — matches the pad's own glow.
192
+ strip.setPressedAt(ch.index, ramp > 0.35);
193
+ return;
194
+ }
195
+ strip.setPressedAt(ch.index, on);
196
+ if (ch.part && driver) {
197
+ driver.setPart(ch.part, on);
198
+ if (ch.part === "lt")
199
+ driver.setMeter("l", on ? 1 : 0);
200
+ if (ch.part === "rt")
201
+ driver.setMeter("r", on ? 1 : 0);
202
+ }
203
+ }
204
+ /** The still frame for reduced motion: everything engaged, sticks at full deflection. */
205
+ function renderStill() {
206
+ const driver = pad?.driver();
207
+ for (const ch of channels)
208
+ applyChannel(ch, true, 1, 0, driver);
209
+ strip.setStep(action.combo.length);
210
+ }
211
+ // ── the loop ────────────────────────────────────────────────────────────────────────────────────
212
+ const reduceMotion = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
213
+ let raf = 0;
214
+ let elapsed = 0;
215
+ let lastTs = 0;
216
+ let wantPlaying = true;
217
+ function frame(ts) {
218
+ raf = requestAnimationFrame(frame);
219
+ const dt = lastTs ? Math.min(0.05, (ts - lastTs) / 1000) : 0;
220
+ lastTs = ts;
221
+ elapsed += dt * speed;
222
+ const len = cycleLength();
223
+ if (elapsed >= len) {
224
+ elapsed -= len * Math.floor(elapsed / len);
225
+ opts.onCycle?.();
226
+ }
227
+ renderAt(elapsed);
228
+ }
229
+ function start() {
230
+ if (raf || reduceMotion)
231
+ return;
232
+ lastTs = 0;
233
+ raf = requestAnimationFrame(frame);
234
+ }
235
+ function stop() {
236
+ if (raf)
237
+ cancelAnimationFrame(raf);
238
+ raf = 0;
239
+ }
240
+ const onVisibility = () => {
241
+ if (document.visibilityState === "hidden")
242
+ stop();
243
+ else if (wantPlaying)
244
+ start();
245
+ };
246
+ document.addEventListener("visibilitychange", onVisibility);
247
+ if (reduceMotion)
248
+ renderStill();
249
+ else
250
+ start();
251
+ // A pad swap rebuilds the drawing (new nodes, new driver) and re-letters, so the chips must follow.
252
+ const offRebuild = pad?.onRebuild(() => {
253
+ buildStrip();
254
+ if (reduceMotion)
255
+ renderStill();
256
+ });
257
+ function reset(next) {
258
+ action = next;
259
+ channels = channelsOf(action);
260
+ // Release the previous combination before the new one draws, or a part the old action lit and the
261
+ // new one doesn't would stay lit forever.
262
+ pad?.driver().clearAll();
263
+ buildStrip();
264
+ syncLabel();
265
+ elapsed = 0;
266
+ lastTs = 0;
267
+ if (reduceMotion)
268
+ renderStill();
269
+ }
270
+ return {
271
+ root,
272
+ pad,
273
+ setAction: reset,
274
+ play() {
275
+ wantPlaying = true;
276
+ start();
277
+ },
278
+ pause() {
279
+ wantPlaying = false;
280
+ stop();
281
+ },
282
+ setSpeed(mult) {
283
+ speed = Math.max(0.05, mult);
284
+ },
285
+ dispose() {
286
+ stop();
287
+ document.removeEventListener("visibilitychange", onVisibility);
288
+ offRebuild?.();
289
+ strip.dispose();
290
+ pad?.dispose();
291
+ root.remove();
292
+ },
293
+ };
294
+ }
295
+ //# sourceMappingURL=combo-demo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combo-demo.js","sourceRoot":"","sources":["../../../src/ui/controller/combo-demo.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * COMBO STRIP — the row of chips that says what to press for one action.
3
+ *
4
+ * [L2] + [✕] two controls held together (the default)
5
+ * [L2] → [✕] two controls in order (`action.sequence`)
6
+ * [stick◀] one control
7
+ *
8
+ * This is the "combination" half of a callout card, and the demo drives the same object, so the static
9
+ * legend and the animated tutorial can never disagree about what a combination looks like.
10
+ *
11
+ * Addressing is by INDEX rather than by token, because a combination may legitimately repeat a control
12
+ * (a double-tap sequence is `["DIAMOND_DOWN", "DIAMOND_DOWN"]`) and a token lookup would drive both
13
+ * chips at once. `setPressed(token, …)` exists for convenience and hits the first match.
14
+ */
15
+ import { type InputChip, type InputChipOptions } from "./input-chip.js";
16
+ import type { ComboToken, ControlAction } from "./tokens.js";
17
+ export interface ComboStrip {
18
+ readonly el: HTMLElement;
19
+ readonly tokens: readonly ComboToken[];
20
+ readonly chips: readonly InputChip[];
21
+ /** Press/release the chip at `index`. Out-of-range indices are ignored. */
22
+ setPressedAt(index: number, on: boolean): void;
23
+ /** Press/release the FIRST chip carrying `token`. */
24
+ setPressed(token: ComboToken, on: boolean): void;
25
+ setAllPressed(on: boolean): void;
26
+ /** Deflect the stick chip at `index` (no-op on other chip kinds). Convention: right = +1, up = +1. */
27
+ setDeflectionAt(index: number, x: number, y: number): void;
28
+ /**
29
+ * In a sequence, mark separators before `step` as done so the eye can follow the progression.
30
+ * `-1` clears. No effect on a simultaneous combo, which has no ordering to show.
31
+ */
32
+ setStep(step: number): void;
33
+ dispose(): void;
34
+ }
35
+ export declare function createComboStrip(action: ControlAction, opts?: InputChipOptions): ComboStrip;
36
+ //# sourceMappingURL=combo-strip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combo-strip.d.ts","sourceRoot":"","sources":["../../../src/ui/controller/combo-strip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAmB,KAAK,SAAS,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAEzF,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE7D,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;IACrC,2EAA2E;IAC3E,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/C,qDAAqD;IACrD,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IACjD,aAAa,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IACjC,sGAAsG;IACtG,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3D;;;OAGG;IACH,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,aAAa,EAAE,IAAI,GAAE,gBAAqB,GAAG,UAAU,CAiD/F"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * COMBO STRIP — the row of chips that says what to press for one action.
3
+ *
4
+ * [L2] + [✕] two controls held together (the default)
5
+ * [L2] → [✕] two controls in order (`action.sequence`)
6
+ * [stick◀] one control
7
+ *
8
+ * This is the "combination" half of a callout card, and the demo drives the same object, so the static
9
+ * legend and the animated tutorial can never disagree about what a combination looks like.
10
+ *
11
+ * Addressing is by INDEX rather than by token, because a combination may legitimately repeat a control
12
+ * (a double-tap sequence is `["DIAMOND_DOWN", "DIAMOND_DOWN"]`) and a token lookup would drive both
13
+ * chips at once. `setPressed(token, …)` exists for convenience and hits the first match.
14
+ */
15
+ import { createInputChip } from "./input-chip.js";
16
+ import { injectPadCss } from "./styles.js";
17
+ export function createComboStrip(action, opts = {}) {
18
+ injectPadCss();
19
+ const el = document.createElement("span");
20
+ el.className = "rydr-pad-strip";
21
+ const tokens = [...action.combo];
22
+ const chips = [];
23
+ const seps = [];
24
+ tokens.forEach((token, i) => {
25
+ if (i > 0) {
26
+ const sep = document.createElement("span");
27
+ sep.className = "rydr-pad-strip-sep";
28
+ // "+" = at the same time, "→" = then. The glyph is the whole explanation.
29
+ sep.textContent = action.sequence ? "→" : "+";
30
+ el.appendChild(sep);
31
+ seps.push(sep);
32
+ }
33
+ const chip = createInputChip(token, opts);
34
+ chips.push(chip);
35
+ el.appendChild(chip.el);
36
+ });
37
+ const setPressedAt = (index, on) => chips[index]?.setPressed(on);
38
+ return {
39
+ el,
40
+ tokens,
41
+ chips,
42
+ setPressedAt,
43
+ setPressed(token, on) {
44
+ const i = tokens.indexOf(token);
45
+ if (i >= 0)
46
+ setPressedAt(i, on);
47
+ },
48
+ setAllPressed(on) {
49
+ for (const chip of chips)
50
+ chip.setPressed(on);
51
+ },
52
+ setDeflectionAt(index, x, y) {
53
+ chips[index]?.setDeflection(x, y);
54
+ },
55
+ setStep(step) {
56
+ // Separator i sits between chip i and chip i+1, so it is "done" once step has passed chip i+1.
57
+ seps.forEach((sep, i) => sep.classList.toggle("done", action.sequence === true && step > i));
58
+ },
59
+ dispose() {
60
+ for (const chip of chips)
61
+ chip.dispose();
62
+ el.remove();
63
+ },
64
+ };
65
+ }
66
+ //# sourceMappingURL=combo-strip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"combo-strip.js","sourceRoot":"","sources":["../../../src/ui/controller/combo-strip.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,eAAe,EAAyC,MAAM,iBAAiB,CAAC;AACzF,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsB3C,MAAM,UAAU,gBAAgB,CAAC,MAAqB,EAAE,OAAyB,EAAE;IACjF,YAAY,EAAE,CAAC;IACf,MAAM,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAC1C,EAAE,CAAC,SAAS,GAAG,gBAAgB,CAAC;IAEhC,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACjC,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,MAAM,IAAI,GAAkB,EAAE,CAAC;IAE/B,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAC1B,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;YACV,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;YAC3C,GAAG,CAAC,SAAS,GAAG,oBAAoB,CAAC;YACrC,0EAA0E;YAC1E,GAAG,CAAC,WAAW,GAAG,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;YAC9C,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;YACpB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjB,CAAC;QACD,MAAM,IAAI,GAAG,eAAe,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;QAC1C,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,MAAM,YAAY,GAA+B,CAAC,KAAK,EAAE,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,EAAE,CAAC,CAAC;IAE7F,OAAO;QACL,EAAE;QACF,MAAM;QACN,KAAK;QACL,YAAY;QACZ,UAAU,CAAC,KAAK,EAAE,EAAE;YAClB,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAChC,IAAI,CAAC,IAAI,CAAC;gBAAE,YAAY,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAClC,CAAC;QACD,aAAa,CAAC,EAAE;YACd,KAAK,MAAM,IAAI,IAAI,KAAK;gBAAE,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QAChD,CAAC;QACD,eAAe,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC;YACzB,KAAK,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpC,CAAC;QACD,OAAO,CAAC,IAAI;YACV,+FAA+F;YAC/F,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,KAAK,IAAI,IAAI,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;QAC/F,CAAC;QACD,OAAO;YACL,KAAK,MAAM,IAAI,IAAI,KAAK;gBAAE,IAAI,CAAC,OAAO,EAAE,CAAC;YACzC,EAAE,CAAC,MAAM,EAAE,CAAC;QACd,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * CONTROL TRAINER — a guided lesson: "here's what to press; now you do it."
3
+ *
4
+ * Where {@link mountComboDemo} only *shows* a combination on a loop, this one **watches the rider** and
5
+ * gates on them actually performing it. It exists for the paired lessons where two related controls have
6
+ * to be taught together — the case that named it being the trainer resistance, which is one concept
7
+ * (`Changer la résistance`) with two opposite halves (`−` and `+`).
8
+ *
9
+ * ```ts
10
+ * mountControlTrainer(modalBody, {
11
+ * title: "Changer la résistance",
12
+ * press: shellInput,
13
+ * steps: [
14
+ * { label: "Réduire la résistance", combo: ["LSTICK_PRESS", "LB"] },
15
+ * { label: "Augmenter la résistance", combo: ["LSTICK_PRESS", "RB"] },
16
+ * ],
17
+ * onComplete: () => closeModal(),
18
+ * });
19
+ * ```
20
+ *
21
+ * Layout: the title, then one panel per step laid left → right, then **one shared controller drawing
22
+ * spanning the full width** underneath. Sharing the drawing is the point — both halves of the lesson are
23
+ * on the same physical pad, and seeing the left paddle and the right paddle light up on one picture is
24
+ * what teaches the pairing.
25
+ *
26
+ * Each step walks three states, and each one has to *look* like what it means:
27
+ *
28
+ * 1. **hint** — whitish, pulsing, on both the panel's chips and the pad's buttons. This is an
29
+ * instruction, so it must not look like a press; the pulse is what separates the two, since a solid
30
+ * fill is what a real press already looks like. The prompt shows **one step at a time, alternating**,
31
+ * and builds each combination up **cumulatively** — the first control lights, STAYS lit, and the next
32
+ * joins it a beat later. Prompting both halves at once would light four controls on one pad and read
33
+ * as "press all of this"; blinking them in turn would never show that they are held together.
34
+ * 2. **active** — the accent colour, and the pulse STOPS. The rider has started but the combination
35
+ * isn't complete yet: on a two-control step this is the state between the first control and the
36
+ * second.
37
+ * 3. **done** — the step's PANEL turns green and its ✓ pops, the moment the WHOLE combination is held.
38
+ * Holding all of it is doing it; there is nothing further to wait for, and it stays done whether or
39
+ * not the rider keeps the controls down.
40
+ *
41
+ * **The controller drawing is never green.** White and blue have already taught the rider that colour on
42
+ * the pad means *now*, so progress there would be a third colour breaking that rule — a green button
43
+ * beside a white one reads as "and this one too". Progress lives on the panels, whose chips picture the
44
+ * same combination one line up, so the confirmation still lands on the controls they just pressed. See
45
+ * `paint` for the two languages.
46
+ *
47
+ * When every step is done, `onComplete` fires after a short beat so the last ✓ is actually seen. The
48
+ * component does not close anything itself — it has no idea it's in a modal. The host owns that, exactly
49
+ * as with every other piece of this kit.
50
+ *
51
+ * ⚠️ **The resistance case needs a shell-side input source.** `LB`/`RB` are shell-owned: the
52
+ * SDK client turns those keys into `trainer.adjustResistance` intents and they never reach a game's
53
+ * `onButton` (see {@link ShellControl}). So a *game* can build a trainer for its own buttons, but a
54
+ * resistance lesson has to be driven by the shell, which is also the only place that lesson belongs.
55
+ */
56
+ import type { ButtonEdge } from "../../protocol/buttons.js";
57
+ import type { AxisName } from "../../protocol/buttons.js";
58
+ import type { GlyphSet } from "../../protocol/glyphs.js";
59
+ import { type ControllerPad } from "./controller-pad.js";
60
+ import type { StickPictogramStyle } from "./input-chip.js";
61
+ import { type ComboToken, type ControlName } from "./tokens.js";
62
+ import type { PadBrandKey } from "./types.js";
63
+ /**
64
+ * The input the lesson watches.
65
+ *
66
+ * Typed on {@link ControlName}, not `ButtonName`, so a shell-side source can report `LB`/`RB`.
67
+ * A `PlatformSession` satisfies it structurally and can drive any lesson made of game buttons.
68
+ */
69
+ export interface TrainerInputSource {
70
+ onButton(cb: (e: {
71
+ name: ControlName;
72
+ edge: ButtonEdge;
73
+ }) => void): () => void;
74
+ axis?(name: AxisName): number;
75
+ hardware?: {
76
+ current: {
77
+ glyphSet?: GlyphSet;
78
+ };
79
+ subscribe?(cb: (snapshot: {
80
+ glyphSet?: GlyphSet;
81
+ }) => void): () => void;
82
+ };
83
+ }
84
+ export interface TrainerStep {
85
+ /** What the rider has to do — the panel heading. */
86
+ label: string;
87
+ /** The controls that do it. Several tokens = they must all be engaged together. */
88
+ combo: readonly ComboToken[];
89
+ /** The controls must be engaged IN ORDER rather than together. */
90
+ sequence?: boolean;
91
+ /** A quieter line under the label. */
92
+ hint?: string;
93
+ /** Stable key, for `onStepComplete`. Falls back to `label`. */
94
+ id?: string;
95
+ }
96
+ export interface ControlTrainerOptions {
97
+ /** The one thing the lesson is about — e.g. "Changer la résistance". */
98
+ title: string;
99
+ /** One panel per step, laid left → right in the order given. */
100
+ steps: readonly TrainerStep[];
101
+ /** The input to watch. Without it the lesson still renders and prompts, but can never complete. */
102
+ press?: TrainerInputSource;
103
+ brand?: PadBrandKey;
104
+ glyphSet?: GlyphSet;
105
+ labels?: boolean;
106
+ accent?: string;
107
+ colored?: boolean;
108
+ stickStyle?: StickPictogramStyle;
109
+ /** Pad size preset. Default `"lg"` — the drawing is the shared reference here, so make it readable. */
110
+ size?: "sm" | "md" | "lg";
111
+ /**
112
+ * How long to hold on the final ✓ before `onComplete`. Default 900ms.
113
+ *
114
+ * Not decoration: firing the instant the last control is released would yank the lesson away before the
115
+ * rider sees that they got it right, which is the only feedback the whole exercise exists to give.
116
+ */
117
+ completionDelayMs?: number;
118
+ onStepComplete?(step: TrainerStep, index: number): void;
119
+ /** Every step done. The host closes its modal here — the component never closes itself. */
120
+ onComplete?(): void;
121
+ }
122
+ export interface ControlTrainer {
123
+ readonly root: HTMLDivElement;
124
+ readonly pad: ControllerPad;
125
+ /** Which steps are done, by index. */
126
+ completed(): readonly boolean[];
127
+ /** Back to square one — every step un-done, prompts pulsing again. */
128
+ reset(): void;
129
+ dispose(): void;
130
+ }
131
+ export declare function mountControlTrainer(host: HTMLElement, opts: ControlTrainerOptions): ControlTrainer;
132
+ //# sourceMappingURL=control-trainer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"control-trainer.d.ts","sourceRoot":"","sources":["../../../src/ui/controller/control-trainer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsDG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC5D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAEzD,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,KAAK,EAAkB,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAI3E,OAAO,EAAgD,KAAK,UAAU,EAAE,KAAK,WAAW,EAAE,MAAM,aAAa,CAAC;AAC9G,OAAO,KAAK,EAAE,WAAW,EAAW,MAAM,YAAY,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,IAAI,EAAE,UAAU,CAAA;KAAE,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;IAC/E,IAAI,CAAC,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAC;IAC9B,QAAQ,CAAC,EAAE;QACT,OAAO,EAAE;YAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;SAAE,CAAC;QACjC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,EAAE;YAAE,QAAQ,CAAC,EAAE,QAAQ,CAAA;SAAE,KAAK,IAAI,GAAG,MAAM,IAAI,CAAC;KACzE,CAAC;CACH;AAED,MAAM,WAAW,WAAW;IAC1B,oDAAoD;IACpD,KAAK,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sCAAsC;IACtC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,+DAA+D;IAC/D,EAAE,CAAC,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,qBAAqB;IACpC,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,KAAK,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9B,mGAAmG;IACnG,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B,KAAK,CAAC,EAAE,WAAW,CAAC;IACpB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,mBAAmB,CAAC;IACjC,uGAAuG;IACvG,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IAC1B;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,CAAC,IAAI,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACxD,2FAA2F;IAC3F,UAAU,CAAC,IAAI,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,aAAa,CAAC;IAC5B,sCAAsC;IACtC,SAAS,IAAI,SAAS,OAAO,EAAE,CAAC;IAChC,sEAAsE;IACtE,KAAK,IAAI,IAAI,CAAC;IACd,OAAO,IAAI,IAAI,CAAC;CACjB;AA0DD,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,qBAAqB,GAAG,cAAc,CAuelG"}