@rydr/game-sdk 3.0.0 → 3.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 (57) hide show
  1. package/README.md +72 -0
  2. package/dist/client/PlatformClient.d.ts +10 -1
  3. package/dist/client/PlatformClient.d.ts.map +1 -1
  4. package/dist/client/PlatformClient.js +76 -5
  5. package/dist/client/PlatformClient.js.map +1 -1
  6. package/dist/protocol/buttons.d.ts +30 -0
  7. package/dist/protocol/buttons.d.ts.map +1 -1
  8. package/dist/protocol/buttons.js +33 -1
  9. package/dist/protocol/buttons.js.map +1 -1
  10. package/dist/protocol/messages.d.ts.map +1 -1
  11. package/dist/protocol/version.d.ts +1 -1
  12. package/dist/protocol/version.js +1 -1
  13. package/dist/ui/action-card.d.ts +39 -0
  14. package/dist/ui/action-card.d.ts.map +1 -0
  15. package/dist/ui/action-card.js +38 -0
  16. package/dist/ui/action-card.js.map +1 -0
  17. package/dist/ui/action-diamond.d.ts +41 -0
  18. package/dist/ui/action-diamond.d.ts.map +1 -0
  19. package/dist/ui/action-diamond.js +35 -0
  20. package/dist/ui/action-diamond.js.map +1 -0
  21. package/dist/ui/card.d.ts +45 -0
  22. package/dist/ui/card.d.ts.map +1 -0
  23. package/dist/ui/card.js +73 -0
  24. package/dist/ui/card.js.map +1 -0
  25. package/dist/ui/choice-card.d.ts +73 -0
  26. package/dist/ui/choice-card.d.ts.map +1 -0
  27. package/dist/ui/choice-card.js +141 -0
  28. package/dist/ui/choice-card.js.map +1 -0
  29. package/dist/ui/dialogue-card.d.ts +54 -0
  30. package/dist/ui/dialogue-card.d.ts.map +1 -0
  31. package/dist/ui/dialogue-card.js +107 -0
  32. package/dist/ui/dialogue-card.js.map +1 -0
  33. package/dist/ui/index.d.ts +31 -0
  34. package/dist/ui/index.d.ts.map +1 -0
  35. package/dist/ui/index.js +31 -0
  36. package/dist/ui/index.js.map +1 -0
  37. package/dist/ui/keycap.d.ts +77 -0
  38. package/dist/ui/keycap.d.ts.map +1 -0
  39. package/dist/ui/keycap.js +160 -0
  40. package/dist/ui/keycap.js.map +1 -0
  41. package/dist/ui/labeled-diamond.d.ts +32 -0
  42. package/dist/ui/labeled-diamond.d.ts.map +1 -0
  43. package/dist/ui/labeled-diamond.js +86 -0
  44. package/dist/ui/labeled-diamond.js.map +1 -0
  45. package/dist/ui/showcase/gallery.d.ts +56 -0
  46. package/dist/ui/showcase/gallery.d.ts.map +1 -0
  47. package/dist/ui/showcase/gallery.js +85 -0
  48. package/dist/ui/showcase/gallery.js.map +1 -0
  49. package/dist/ui/showcase/index.d.ts +29 -0
  50. package/dist/ui/showcase/index.d.ts.map +1 -0
  51. package/dist/ui/showcase/index.js +255 -0
  52. package/dist/ui/showcase/index.js.map +1 -0
  53. package/dist/ui/styles.d.ts +12 -0
  54. package/dist/ui/styles.d.ts.map +1 -0
  55. package/dist/ui/styles.js +183 -0
  56. package/dist/ui/styles.js.map +1 -0
  57. package/package.json +13 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-card.js","sourceRoot":"","sources":["../../src/ui/action-card.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAA+B,MAAM,WAAW,CAAC;AACnE,OAAO,EAAE,YAAY,EAA0E,MAAM,aAAa,CAAC;AAgBnH;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,IAAiB,EAAE,MAAoB,EAAE,OAA0B,EAAE;IACnG,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC1C,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,gDAAgD;IAClF,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAE5B,OAAO;QACL,GAAG,IAAI;QACP,MAAM;QACN,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,WAAW,EAAE,MAAM,CAAC,WAAW;QAC/B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,0FAA0F;QAC1F,WAAW,EAAE,CAAC,EAAW,EAAE,EAAE,GAAG,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,WAAW,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;KAChF,CAAC;AACJ,CAAC;AAYD,4FAA4F;AAC5F,MAAM,UAAU,mBAAmB,CAAC,MAAoB,EAAE,UAAyB,MAAM;IACvF,OAAO,YAAY,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC,EAAE,CAAC;AAC9C,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,iBAAiB,CAAC,IAAiB,EAAE,MAAoB,EAAE,KAAa;IACtF,OAAO,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;AAClD,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * ACTION DIAMOND — the full A/B/Y/Z move diamond: four action cards in a losange (Y top, A right, B
3
+ * bottom, Z left). Each card is exactly the {@link mountActionCard} card (same `.rydr-ui-card` style +
4
+ * keycap), with a freeform content SLOT the consumer fills. The diamond is a centred grid that sizes
5
+ * to its content; `compact` / `rowGap` / `columnGap` tune the spread.
6
+ */
7
+ import { type ButtonLetter, type ButtonSource } from "./keycap.js";
8
+ import { type ActionCard } from "./action-card.js";
9
+ /** One diamond item: a full action card (same `.rydr-ui-card` style) with a `setActive` highlight added. */
10
+ export interface DiamondCard extends ActionCard {
11
+ /** Highlight this card (e.g. the currently-selected move). */
12
+ setActive(on: boolean): void;
13
+ }
14
+ export interface ActionDiamond {
15
+ root: HTMLElement;
16
+ /** The four cards by face button (Y top · A right · B bottom · Z left). */
17
+ cards: Record<ButtonLetter, DiamondCard>;
18
+ dispose(): void;
19
+ }
20
+ export interface ActionDiamondOptions {
21
+ colored?: boolean;
22
+ /** Initial content per button — a string label (e.g. the attack name) or custom DOM in `content`. */
23
+ cards?: Partial<Record<ButtonLetter, {
24
+ content?: string | HTMLElement;
25
+ label?: string;
26
+ }>>;
27
+ /** Vertical gap (px) between the stacked Y/B centre cards. */
28
+ rowGap?: number;
29
+ /** Horizontal gap (px) between the centre column and the Z/A side cards. */
30
+ columnGap?: number;
31
+ /** Wire every card's keycap to real controller input (pressed while its button is held). */
32
+ press?: ButtonSource;
33
+ }
34
+ /**
35
+ * Mount the A/B/Y/Z move diamond: Z left · Y+B stacked in the centre column · A right. Each card is
36
+ * the {@link mountActionCard} card with a freeform content SLOT — an attack-name string, or any custom
37
+ * DOM. The cluster sizes to its content and stays centred; per-card `setActive`/`setCooldown` drive
38
+ * the move state.
39
+ */
40
+ export declare function mountActionDiamond(host: HTMLElement, opts?: ActionDiamondOptions): ActionDiamond;
41
+ //# sourceMappingURL=action-diamond.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-diamond.d.ts","sourceRoot":"","sources":["../../src/ui/action-diamond.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAmB,KAAK,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAIpE,4GAA4G;AAC5G,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C,8DAA8D;IAC9D,SAAS,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,WAAW,CAAC;IAClB,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,WAAW,CAAC,CAAC;IACzC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,MAAM,WAAW,oBAAoB;IACnC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,qGAAqG;IACrG,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,YAAY,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;IAC1F,8DAA8D;IAC9D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4FAA4F;IAC5F,KAAK,CAAC,EAAE,YAAY,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,GAAE,oBAAyB,GAAG,aAAa,CAkBpG"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * ACTION DIAMOND — the full A/B/Y/Z move diamond: four action cards in a losange (Y top, A right, B
3
+ * bottom, Z left). Each card is exactly the {@link mountActionCard} card (same `.rydr-ui-card` style +
4
+ * keycap), with a freeform content SLOT the consumer fills. The diamond is a centred grid that sizes
5
+ * to its content; `compact` / `rowGap` / `columnGap` tune the spread.
6
+ */
7
+ import {} from "./keycap.js";
8
+ import { mountActionCard } from "./action-card.js";
9
+ const POS_CLASS = { Y: "pos-top", A: "pos-rgt", B: "pos-bot", Z: "pos-lft" };
10
+ /**
11
+ * Mount the A/B/Y/Z move diamond: Z left · Y+B stacked in the centre column · A right. Each card is
12
+ * the {@link mountActionCard} card with a freeform content SLOT — an attack-name string, or any custom
13
+ * DOM. The cluster sizes to its content and stays centred; per-card `setActive`/`setCooldown` drive
14
+ * the move state.
15
+ */
16
+ export function mountActionDiamond(host, opts = {}) {
17
+ const colored = opts.colored ?? true;
18
+ const root = document.createElement("div");
19
+ root.className = "rydr-ui-dia";
20
+ if (opts.rowGap != null)
21
+ root.style.rowGap = `${opts.rowGap}px`;
22
+ if (opts.columnGap != null)
23
+ root.style.columnGap = `${opts.columnGap}px`;
24
+ const cards = {};
25
+ for (const button of ["Y", "A", "B", "Z"]) {
26
+ const init = opts.cards?.[button];
27
+ const card = mountActionCard(root, button, { colored, animate: false, inline: true, press: opts.press, content: init?.content, label: init?.label });
28
+ card.root.classList.add("rydr-ui-dia-card", POS_CLASS[button]);
29
+ card.setActive = (on) => card.root.classList.toggle("active", on);
30
+ cards[button] = card;
31
+ }
32
+ host.appendChild(root);
33
+ return { root, cards, dispose: () => root.remove() };
34
+ }
35
+ //# sourceMappingURL=action-diamond.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-diamond.js","sourceRoot":"","sources":["../../src/ui/action-diamond.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAwC,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,eAAe,EAAmB,MAAM,kBAAkB,CAAC;AAEpE,MAAM,SAAS,GAAiC,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC;AA2B3G;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAiB,EAAE,OAA6B,EAAE;IACnF,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,IAAI,CAAC;IACrC,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;IAC3C,IAAI,CAAC,SAAS,GAAG,aAAa,CAAC;IAC/B,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI;QAAE,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC;IAChE,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI;QAAE,IAAI,CAAC,KAAK,CAAC,SAAS,GAAG,GAAG,IAAI,CAAC,SAAS,IAAI,CAAC;IAEzE,MAAM,KAAK,GAAG,EAAuC,CAAC;IACtD,KAAK,MAAM,MAAM,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAmB,EAAE,CAAC;QAC5D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC;QAClC,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,CAAgB,CAAC;QACpK,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,kBAAkB,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;QAC/D,IAAI,CAAC,SAAS,GAAG,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QAClE,KAAK,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC;IACvB,CAAC;IAED,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;IACvB,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AACvD,CAAC"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * CARD — the dark glass card (the "ATTAQUER" look): a styled frame with a CONTENT SLOT (`card.body`)
3
+ * the game fills with whatever it wants (a label, a creature plate, a dialogue line, …). No keycap —
4
+ * that's {@link mountActionCard}, which composes this card with a controller button on top.
5
+ *
6
+ * A standalone card WRAPS its content; `inline:false` (default) places it fixed bottom-centre (use
7
+ * `setScreenPos` to track a projected point), `inline:true` lays it in normal flow.
8
+ */
9
+ export interface CardOptions {
10
+ /** Initial slot content — a string (set as text) or an element appended into `body`. */
11
+ content?: string | HTMLElement;
12
+ /** Convenience: a single line of label text (rendered as the card's body). */
13
+ label?: string;
14
+ /** Lay the card inline (in normal flow) instead of fixed bottom-centre. */
15
+ inline?: boolean;
16
+ /** Fixed card width (number → px, or any CSS length). Omit → the card hugs its content. */
17
+ width?: number | string;
18
+ /** Fixed card height (number → px, or any CSS length). Omit → the card hugs its content. */
19
+ height?: number | string;
20
+ }
21
+ /** A mounted card: a styled frame + a content slot the game owns. */
22
+ export interface Card {
23
+ root: HTMLElement;
24
+ /** The content SLOT — append your own DOM here, or use setContent/setLabel. */
25
+ body: HTMLElement;
26
+ setLabel(text: string): void;
27
+ setContent(content: string | HTMLElement): void;
28
+ /** Disabled = the action is unavailable: the card (and any floating keycap) fades to 50% + inert. */
29
+ setDisabled(on: boolean): void;
30
+ setVisible(v: boolean): void;
31
+ /** Centre the card at a viewport pixel (e.g. a creature projected to screen). */
32
+ setScreenPos(x: number, y: number): void;
33
+ dispose(): void;
34
+ }
35
+ /**
36
+ * Build the card DOM + handle WITHOUT attaching it to a host. Also returns the inner `.rydr-ui-card`
37
+ * element so {@link mountActionCard} can hang a keycap on it. Internal building block — most callers
38
+ * want `mountCard` or `mountActionCard`.
39
+ */
40
+ export declare function buildCard(opts?: CardOptions): Card & {
41
+ card: HTMLElement;
42
+ };
43
+ /** Mount a standalone dark card (no keycap) with a content slot. */
44
+ export declare function mountCard(host: HTMLElement, opts?: CardOptions): Card;
45
+ //# sourceMappingURL=card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card.d.ts","sourceRoot":"","sources":["../../src/ui/card.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,WAAW,WAAW;IAC1B,wFAAwF;IACxF,OAAO,CAAC,EAAE,MAAM,GAAG,WAAW,CAAC;IAC/B,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2FAA2F;IAC3F,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACxB,4FAA4F;IAC5F,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CAC1B;AAED,qEAAqE;AACrE,MAAM,WAAW,IAAI;IACnB,IAAI,EAAE,WAAW,CAAC;IAClB,+EAA+E;IAC/E,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAAC;IAChD,qGAAqG;IACrG,WAAW,CAAC,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,iFAAiF;IACjF,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,IAAI,GAAE,WAAgB,GAAG,IAAI,GAAG;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,CAgD9E;AAED,oEAAoE;AACpE,wBAAgB,SAAS,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,GAAE,WAAgB,GAAG,IAAI,CAIzE"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * CARD — the dark glass card (the "ATTAQUER" look): a styled frame with a CONTENT SLOT (`card.body`)
3
+ * the game fills with whatever it wants (a label, a creature plate, a dialogue line, …). No keycap —
4
+ * that's {@link mountActionCard}, which composes this card with a controller button on top.
5
+ *
6
+ * A standalone card WRAPS its content; `inline:false` (default) places it fixed bottom-centre (use
7
+ * `setScreenPos` to track a projected point), `inline:true` lays it in normal flow.
8
+ */
9
+ import { injectCss } from "./styles.js";
10
+ /**
11
+ * Build the card DOM + handle WITHOUT attaching it to a host. Also returns the inner `.rydr-ui-card`
12
+ * element so {@link mountActionCard} can hang a keycap on it. Internal building block — most callers
13
+ * want `mountCard` or `mountActionCard`.
14
+ */
15
+ export function buildCard(opts = {}) {
16
+ injectCss();
17
+ const root = document.createElement("div");
18
+ root.className = "rydr-ui-root" + (opts.inline ? " inline" : "");
19
+ const card = document.createElement("div");
20
+ card.className = "rydr-ui-card";
21
+ // size is the consumer's choice: hug content (default), or a fixed frame when width/height are given.
22
+ if (opts.width != null || opts.height != null) {
23
+ card.classList.add("fixed");
24
+ const len = (v) => (typeof v === "number" ? `${v}px` : v);
25
+ if (opts.width != null)
26
+ card.style.width = len(opts.width);
27
+ if (opts.height != null)
28
+ card.style.height = len(opts.height);
29
+ }
30
+ const body = document.createElement("div");
31
+ body.className = "rydr-ui-card-body";
32
+ card.appendChild(body);
33
+ root.appendChild(card);
34
+ const setContent = (content) => {
35
+ body.textContent = "";
36
+ if (typeof content === "string") {
37
+ const span = document.createElement("span");
38
+ span.className = "rydr-ui-card-lbl";
39
+ span.textContent = content;
40
+ body.appendChild(span);
41
+ }
42
+ else {
43
+ body.appendChild(content);
44
+ }
45
+ };
46
+ if (opts.content != null)
47
+ setContent(opts.content);
48
+ else if (opts.label != null)
49
+ setContent(opts.label);
50
+ return {
51
+ root,
52
+ card,
53
+ body,
54
+ setLabel: (t) => setContent(t),
55
+ setContent,
56
+ setDisabled: (on) => card.classList.toggle("disabled", on),
57
+ setVisible: (v) => { root.style.opacity = v ? "1" : "0"; },
58
+ setScreenPos(x, y) {
59
+ root.style.left = `${x}px`;
60
+ root.style.top = `${y}px`;
61
+ root.style.bottom = "auto";
62
+ root.style.transform = "translate(-50%, -50%)";
63
+ },
64
+ dispose: () => root.remove(),
65
+ };
66
+ }
67
+ /** Mount a standalone dark card (no keycap) with a content slot. */
68
+ export function mountCard(host, opts = {}) {
69
+ const card = buildCard(opts);
70
+ host.appendChild(card.root);
71
+ return card;
72
+ }
73
+ //# sourceMappingURL=card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"card.js","sourceRoot":"","sources":["../../src/ui/card.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * CHOICE CARD — a PLAYER-facing menu built from the kit: a {@link Card} (dark container) holding an
3
+ * optional SPEAKER name, an optional PROMPT line, and a vertical list of selectable OPTIONS. One option
4
+ * is highlighted at a time; ▲▼ move the highlight (clamped at the ends) and the confirm KEYCAP (default
5
+ * A) rides the right of the highlighted option. Where the dialogue card is the NPC talking, this is how
6
+ * the PLAYER answers — e.g. "Voici le Vivax que j'aimerais aller voir…" + a list of Vivax.
7
+ *
8
+ * `open({...})` shows the menu; `moveBy(±1)` / `setIndex(i)` move the highlight; `confirm()` returns the
9
+ * chosen `{index,label}` (and fires `onConfirm`). The consumer wires these to controller input
10
+ * (UP/DOWN → moveBy, A → confirm), mirroring how the dialogue card's `advance()` is driven.
11
+ *
12
+ * Each option + the prompt share the dialogue card's soft `maxChars` budget (a long string logs a dev
13
+ * warning), and the rows clamp + wrap so an over-long label can't break the layout. Fixed bottom-centre
14
+ * by default (a classic RPG bar), or `inline:true` to lay it in normal flow.
15
+ */
16
+ import { type Card } from "./card.js";
17
+ import { type ButtonLetter, type ButtonSource, type Keycap } from "./keycap.js";
18
+ export interface ChoiceCardOptions {
19
+ /** The confirm button parked on the highlighted option (default "A"). */
20
+ button?: ButtonLetter;
21
+ /** Lay the card inline (normal flow) instead of fixed bottom-centre. */
22
+ inline?: boolean;
23
+ /** Wire the confirm keycap to real controller input (pressed while its button is held). */
24
+ press?: ButtonSource;
25
+ /** Soft per-string character budget for the prompt and each option (default 140). */
26
+ maxChars?: number;
27
+ /**
28
+ * Show a directional-keycap guide on the left of the highlighted option (default true): both UP and
29
+ * DOWN always appear, with the unavailable one disabled (UP at the top, DOWN at the bottom).
30
+ */
31
+ arrows?: boolean;
32
+ }
33
+ export interface ChoiceConfig {
34
+ /** Speaker name above the prompt (e.g. the player's name); omit/empty to hide. */
35
+ name?: string;
36
+ /** The question shown above the options (e.g. "Voici le Vivax que j'aimerais aller voir…"). Optional
37
+ * but strongly recommended — it frames the choice; omit/empty to hide. */
38
+ prompt?: string;
39
+ /** The selectable options, top to bottom. */
40
+ options: string[];
41
+ /** Initially highlighted index (default 0). */
42
+ selected?: number;
43
+ /** Called when `confirm()` runs, with the chosen option. */
44
+ onConfirm?: (index: number, label: string) => void;
45
+ }
46
+ export interface ChoiceCard {
47
+ root: HTMLElement;
48
+ /** The underlying card (container) — for styling / positioning. */
49
+ card: Card;
50
+ /** The confirm keycap (drive its state if you want). */
51
+ keycap: Keycap;
52
+ /** Show the menu: speaker + prompt + options, highlighting `selected` (default 0). */
53
+ open(cfg: ChoiceConfig): void;
54
+ /** Move the highlight by `delta` (clamped at the ends, matching the arrow guide); returns the new index. */
55
+ moveBy(delta: number): number;
56
+ /** Set the highlight to `i` (clamped into range); returns the new index. */
57
+ setIndex(i: number): number;
58
+ /** The currently highlighted index. */
59
+ index(): number;
60
+ /** Confirm the current option: fires `onConfirm` and returns `{index,label}` (null if no options). */
61
+ confirm(): {
62
+ index: number;
63
+ label: string;
64
+ } | null;
65
+ /** Hide the card and reset. */
66
+ close(): void;
67
+ setVisible(v: boolean): void;
68
+ setScreenPos(x: number, y: number): void;
69
+ dispose(): void;
70
+ }
71
+ /** Mount a choice card: name + prompt + a highlighted option list + a confirm cue & keycap. */
72
+ export declare function mountChoiceCard(host: HTMLElement, opts?: ChoiceCardOptions): ChoiceCard;
73
+ //# sourceMappingURL=choice-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"choice-card.d.ts","sourceRoot":"","sources":["../../src/ui/choice-card.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAa,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAkC,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAEhH,MAAM,WAAW,iBAAiB;IAChC,yEAAyE;IACzE,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2FAA2F;IAC3F,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,qFAAqF;IACrF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;+EAC2E;IAC3E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,+CAA+C;IAC/C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4DAA4D;IAC5D,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACpD;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,WAAW,CAAC;IAClB,mEAAmE;IACnE,IAAI,EAAE,IAAI,CAAC;IACX,wDAAwD;IACxD,MAAM,EAAE,MAAM,CAAC;IACf,sFAAsF;IACtF,IAAI,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,CAAC;IAC9B,4GAA4G;IAC5G,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC9B,4EAA4E;IAC5E,QAAQ,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,uCAAuC;IACvC,KAAK,IAAI,MAAM,CAAC;IAChB,sGAAsG;IACtG,OAAO,IAAI;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACnD,+BAA+B;IAC/B,KAAK,IAAI,IAAI,CAAC;IACd,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,+FAA+F;AAC/F,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,GAAE,iBAAsB,GAAG,UAAU,CAyH3F"}
@@ -0,0 +1,141 @@
1
+ /**
2
+ * CHOICE CARD — a PLAYER-facing menu built from the kit: a {@link Card} (dark container) holding an
3
+ * optional SPEAKER name, an optional PROMPT line, and a vertical list of selectable OPTIONS. One option
4
+ * is highlighted at a time; ▲▼ move the highlight (clamped at the ends) and the confirm KEYCAP (default
5
+ * A) rides the right of the highlighted option. Where the dialogue card is the NPC talking, this is how
6
+ * the PLAYER answers — e.g. "Voici le Vivax que j'aimerais aller voir…" + a list of Vivax.
7
+ *
8
+ * `open({...})` shows the menu; `moveBy(±1)` / `setIndex(i)` move the highlight; `confirm()` returns the
9
+ * chosen `{index,label}` (and fires `onConfirm`). The consumer wires these to controller input
10
+ * (UP/DOWN → moveBy, A → confirm), mirroring how the dialogue card's `advance()` is driven.
11
+ *
12
+ * Each option + the prompt share the dialogue card's soft `maxChars` budget (a long string logs a dev
13
+ * warning), and the rows clamp + wrap so an over-long label can't break the layout. Fixed bottom-centre
14
+ * by default (a classic RPG bar), or `inline:true` to lay it in normal flow.
15
+ */
16
+ import { mountCard } from "./card.js";
17
+ import { createKeycap, createDpadKeycap } from "./keycap.js";
18
+ /** Mount a choice card: name + prompt + a highlighted option list + a confirm cue & keycap. */
19
+ export function mountChoiceCard(host, opts = {}) {
20
+ const maxChars = Math.max(1, opts.maxChars ?? 140);
21
+ const showArrows = opts.arrows ?? true;
22
+ const wrap = document.createElement("div");
23
+ wrap.className = "rydr-ui-choice";
24
+ const nameEl = document.createElement("div");
25
+ nameEl.className = "rydr-ui-choice-name";
26
+ const promptEl = document.createElement("div");
27
+ promptEl.className = "rydr-ui-choice-prompt";
28
+ const listEl = document.createElement("div");
29
+ listEl.className = "rydr-ui-choice-list";
30
+ const keycap = createKeycap(opts.button ?? "A", { variant: "solo", animate: true, press: opts.press });
31
+ // the keycap is parked on the highlighted row by render(); it has no fixed home in the card.
32
+ wrap.append(nameEl, promptEl, listEl);
33
+ const card = mountCard(host, { content: wrap, inline: opts.inline });
34
+ card.setVisible(false);
35
+ let options = [];
36
+ let rows = [];
37
+ let gutters = [];
38
+ let arrowKeycaps = [];
39
+ let idx = 0;
40
+ let onConfirm;
41
+ const clearArrows = () => {
42
+ arrowKeycaps.forEach((k) => k.dispose());
43
+ arrowKeycaps = [];
44
+ gutters.forEach((gu) => { gu.textContent = ""; });
45
+ };
46
+ // Char count is a rough proxy for "will this fit" — enough to flag a likely overflow to the author.
47
+ const checkLen = (s, what) => {
48
+ if (s.length > maxChars)
49
+ console.warn(`[choice-card] ${what} is ${s.length} chars (budget ${maxChars}); it may be clipped: "${s.slice(0, 48)}…"`);
50
+ };
51
+ const render = () => {
52
+ rows.forEach((r, i) => r.classList.toggle("selected", i === idx));
53
+ // park the (single) confirm keycap on the right of the highlighted row so it rides selection.
54
+ const selectedRow = rows[idx];
55
+ if (selectedRow)
56
+ selectedRow.appendChild(keycap.el);
57
+ if (!showArrows)
58
+ return;
59
+ // arrows live ONLY on the highlighted row: BOTH UP and DOWN always show, but the one with nowhere
60
+ // to go is disabled (UP at the top, DOWN at the bottom) — a moving guide of where ▲▼ can take you.
61
+ clearArrows();
62
+ const guide = gutters[idx];
63
+ if (!guide)
64
+ return;
65
+ const up = createDpadKeycap("UP", { animate: false });
66
+ const dn = createDpadKeycap("DOWN", { animate: false });
67
+ up.setDisabled(idx <= 0);
68
+ dn.setDisabled(idx >= options.length - 1);
69
+ arrowKeycaps.push(up, dn);
70
+ guide.append(up.el, dn.el);
71
+ };
72
+ return {
73
+ root: card.root,
74
+ card,
75
+ keycap,
76
+ open(cfg) {
77
+ const name = cfg.name ?? "";
78
+ const prompt = cfg.prompt ?? "";
79
+ nameEl.textContent = name;
80
+ nameEl.style.display = name ? "" : "none";
81
+ promptEl.textContent = prompt;
82
+ promptEl.style.display = prompt ? "" : "none";
83
+ if (prompt)
84
+ checkLen(prompt, "prompt");
85
+ options = cfg.options.slice();
86
+ onConfirm = cfg.onConfirm;
87
+ clearArrows();
88
+ listEl.textContent = "";
89
+ gutters = [];
90
+ rows = options.map((label, i) => {
91
+ checkLen(label, `option ${i}`);
92
+ const row = document.createElement("div");
93
+ row.className = "rydr-ui-choice-opt" + (showArrows ? " has-arrows" : "");
94
+ if (showArrows) {
95
+ // an empty, absolutely-positioned left gutter; render() fills only the selected row's gutter
96
+ // with the right arrows (UP unless first, DOWN unless last) — so the guide rides the highlight.
97
+ const guide = document.createElement("div");
98
+ guide.className = "rydr-ui-choice-arrows";
99
+ row.appendChild(guide);
100
+ gutters[i] = guide;
101
+ }
102
+ const lbl = document.createElement("span");
103
+ lbl.className = "rydr-ui-choice-label";
104
+ lbl.textContent = label;
105
+ row.appendChild(lbl);
106
+ listEl.appendChild(row);
107
+ return row;
108
+ });
109
+ idx = Math.min(Math.max(0, cfg.selected ?? 0), Math.max(0, options.length - 1));
110
+ render();
111
+ card.setVisible(true);
112
+ },
113
+ moveBy(delta) {
114
+ if (options.length === 0)
115
+ return idx;
116
+ idx = Math.min(Math.max(0, idx + delta), options.length - 1); // clamp at the ends
117
+ render();
118
+ return idx;
119
+ },
120
+ setIndex(i) {
121
+ if (options.length === 0)
122
+ return idx;
123
+ idx = Math.min(Math.max(0, i), options.length - 1);
124
+ render();
125
+ return idx;
126
+ },
127
+ index: () => idx,
128
+ confirm() {
129
+ const label = options[idx];
130
+ if (label == null)
131
+ return null;
132
+ onConfirm?.(idx, label);
133
+ return { index: idx, label };
134
+ },
135
+ close: () => { card.setVisible(false); clearArrows(); options = []; rows = []; idx = 0; },
136
+ setVisible: card.setVisible,
137
+ setScreenPos: card.setScreenPos,
138
+ dispose: () => { clearArrows(); card.dispose(); },
139
+ };
140
+ }
141
+ //# sourceMappingURL=choice-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"choice-card.js","sourceRoot":"","sources":["../../src/ui/choice-card.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * DIALOGUE CARD — an NPC discussion box built from the kit: a {@link Card} (dark container) holding
3
+ * the speaker's NAME, the LINE below (revealed with a small typewriter), and a controller KEYCAP
4
+ * (default A, from the barrel) in the corner as the "continue" affordance — preceded by a cue that
5
+ * reads ▸ while there's more to say and flips to ✓ on the last screen (the next A press closes it).
6
+ *
7
+ * The consumer authors **lines** — each one is shown DIRECTLY as a single screen (one A press per
8
+ * line), never re-wrapped or split by the component. To keep a long line from breaking the layout,
9
+ * the box is a fixed N-line frame with `overflow:hidden` (it can't grow), and each line has a soft
10
+ * character budget (`maxChars`): exceed it and the card logs a dev warning so the author knows the
11
+ * line may overflow. Char count is a rough proxy for "will this fit" — enough to give an idea.
12
+ *
13
+ * `open(name, lines)` starts a conversation; `advance()` finishes the current typewriter, steps to the
14
+ * next line, or — past the last line — closes the card and returns `false`. Fixed bottom-centre by
15
+ * default (a classic RPG bar), or `inline:true` to lay it in normal flow.
16
+ */
17
+ import { type Card } from "./card.js";
18
+ import { type ButtonLetter, type ButtonSource, type Keycap } from "./keycap.js";
19
+ export interface DialogueCardOptions {
20
+ /** The "continue" button shown in the corner (default "A"). */
21
+ button?: ButtonLetter;
22
+ /** Lay the card inline (normal flow) instead of fixed bottom-centre. */
23
+ inline?: boolean;
24
+ /** Typewriter speed, ms per character (default 18; 0 = reveal instantly). */
25
+ typeMs?: number;
26
+ /** Wire the continue keycap to real controller input (pressed while its button is held). */
27
+ press?: ButtonSource;
28
+ /** Box height, in text lines: the line is clipped to this fixed frame so it can't grow (default 3). */
29
+ maxLines?: number;
30
+ /** Soft per-line character budget: a longer line logs a dev warning (default 140). */
31
+ maxChars?: number;
32
+ /** Fires with the line index whenever a NEW line becomes visible (at `open` and on each `advance` step,
33
+ * not when a press merely finishes the typewriter). Used e.g. to drive a speaker's talking animation. */
34
+ onLine?: (idx: number) => void;
35
+ }
36
+ export interface DialogueCard {
37
+ root: HTMLElement;
38
+ /** The underlying card (container) — for styling / positioning. */
39
+ card: Card;
40
+ /** The continue keycap (drive its state if you want). */
41
+ keycap: Keycap;
42
+ /** Start a conversation: speaker name + lines. Each line is shown directly as one screen. */
43
+ open(name: string, lines: string[]): void;
44
+ /** Finish the typewriter, step to the next line, or close past the last line; `false` once closed. */
45
+ advance(): boolean;
46
+ /** Hide the card and reset. */
47
+ close(): void;
48
+ setVisible(v: boolean): void;
49
+ setScreenPos(x: number, y: number): void;
50
+ dispose(): void;
51
+ }
52
+ /** Mount a discussion card: name + typed line + a "continue" cue & keycap, inside the shared dark card. */
53
+ export declare function mountDialogueCard(host: HTMLElement, opts?: DialogueCardOptions): DialogueCard;
54
+ //# sourceMappingURL=dialogue-card.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialogue-card.d.ts","sourceRoot":"","sources":["../../src/ui/dialogue-card.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAa,KAAK,IAAI,EAAE,MAAM,WAAW,CAAC;AACjD,OAAO,EAAgB,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,KAAK,MAAM,EAAE,MAAM,aAAa,CAAC;AAK9F,MAAM,WAAW,mBAAmB;IAClC,+DAA+D;IAC/D,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,6EAA6E;IAC7E,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,4FAA4F;IAC5F,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB,uGAAuG;IACvG,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;8GAC0G;IAC1G,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,mEAAmE;IACnE,IAAI,EAAE,IAAI,CAAC;IACX,yDAAyD;IACzD,MAAM,EAAE,MAAM,CAAC;IACf,6FAA6F;IAC7F,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC;IAC1C,sGAAsG;IACtG,OAAO,IAAI,OAAO,CAAC;IACnB,+BAA+B;IAC/B,KAAK,IAAI,IAAI,CAAC;IACd,UAAU,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IAC7B,YAAY,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,OAAO,IAAI,IAAI,CAAC;CACjB;AAED,2GAA2G;AAC3G,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,WAAW,EAAE,IAAI,GAAE,mBAAwB,GAAG,YAAY,CA6EjG"}
@@ -0,0 +1,107 @@
1
+ /**
2
+ * DIALOGUE CARD — an NPC discussion box built from the kit: a {@link Card} (dark container) holding
3
+ * the speaker's NAME, the LINE below (revealed with a small typewriter), and a controller KEYCAP
4
+ * (default A, from the barrel) in the corner as the "continue" affordance — preceded by a cue that
5
+ * reads ▸ while there's more to say and flips to ✓ on the last screen (the next A press closes it).
6
+ *
7
+ * The consumer authors **lines** — each one is shown DIRECTLY as a single screen (one A press per
8
+ * line), never re-wrapped or split by the component. To keep a long line from breaking the layout,
9
+ * the box is a fixed N-line frame with `overflow:hidden` (it can't grow), and each line has a soft
10
+ * character budget (`maxChars`): exceed it and the card logs a dev warning so the author knows the
11
+ * line may overflow. Char count is a rough proxy for "will this fit" — enough to give an idea.
12
+ *
13
+ * `open(name, lines)` starts a conversation; `advance()` finishes the current typewriter, steps to the
14
+ * next line, or — past the last line — closes the card and returns `false`. Fixed bottom-centre by
15
+ * default (a classic RPG bar), or `inline:true` to lay it in normal flow.
16
+ */
17
+ import { mountCard } from "./card.js";
18
+ import { createKeycap } from "./keycap.js";
19
+ const CUE_MORE = "▸"; // there's another page after this one
20
+ const CUE_END = "✓"; // last page — the next press closes the conversation
21
+ /** Mount a discussion card: name + typed line + a "continue" cue & keycap, inside the shared dark card. */
22
+ export function mountDialogueCard(host, opts = {}) {
23
+ const ms = opts.typeMs ?? 18;
24
+ const maxLines = Math.max(1, opts.maxLines ?? 3);
25
+ const maxChars = Math.max(1, opts.maxChars ?? 140);
26
+ const wrap = document.createElement("div");
27
+ wrap.className = "rydr-ui-dlg";
28
+ const nameEl = document.createElement("div");
29
+ nameEl.className = "rydr-ui-dlg-name";
30
+ const lineEl = document.createElement("div");
31
+ lineEl.className = "rydr-ui-dlg-line";
32
+ const go = document.createElement("div");
33
+ go.className = "rydr-ui-dlg-go";
34
+ const cueEl = document.createElement("span");
35
+ cueEl.className = "rydr-ui-dlg-cue";
36
+ const keycap = createKeycap(opts.button ?? "A", { variant: "solo", animate: true, press: opts.press });
37
+ go.append(cueEl, keycap.el);
38
+ wrap.append(nameEl, lineEl, go);
39
+ const card = mountCard(host, { content: wrap, inline: opts.inline });
40
+ card.setVisible(false);
41
+ // Lock the visible line to a fixed N-line frame (line-height is 1.4) and clip overflow: a line that
42
+ // runs past the budget is trimmed, not allowed to grow the box and break the surrounding layout.
43
+ lineEl.style.height = `${(maxLines * 1.4).toFixed(2)}em`;
44
+ lineEl.style.overflow = "hidden";
45
+ let lines = [];
46
+ let idx = 0;
47
+ let timer = null;
48
+ const stop = () => { if (timer !== null) {
49
+ clearInterval(timer);
50
+ timer = null;
51
+ } };
52
+ const showLine = () => {
53
+ stop();
54
+ opts.onLine?.(idx); // a new line is now on screen → let the caller react (e.g. alternate a talking clip)
55
+ const full = lines[idx] ?? "";
56
+ cueEl.textContent = idx >= lines.length - 1 ? CUE_END : CUE_MORE; // ✓ on the last line, else ▸
57
+ if (ms <= 0) {
58
+ lineEl.textContent = full;
59
+ return;
60
+ }
61
+ let shown = 0;
62
+ lineEl.textContent = "";
63
+ timer = setInterval(() => {
64
+ shown++;
65
+ lineEl.textContent = full.slice(0, shown);
66
+ if (shown >= full.length)
67
+ stop();
68
+ }, ms);
69
+ };
70
+ const close = () => { stop(); card.setVisible(false); lines = []; idx = 0; };
71
+ return {
72
+ root: card.root,
73
+ card,
74
+ keycap,
75
+ open(name, ls) {
76
+ // Char count is a rough proxy for "will this fit the box" — enough to flag a likely overflow.
77
+ ls.forEach((l, i) => {
78
+ if (l.length > maxChars)
79
+ console.warn(`[dialogue-card] line ${i} is ${l.length} chars (budget ${maxChars}); it may be clipped: "${l.slice(0, 48)}…"`);
80
+ });
81
+ nameEl.textContent = name;
82
+ lines = ls;
83
+ idx = 0;
84
+ card.setVisible(true);
85
+ showLine();
86
+ },
87
+ advance() {
88
+ if (timer !== null) {
89
+ stop();
90
+ lineEl.textContent = lines[idx] ?? "";
91
+ return true;
92
+ } // finish the line
93
+ idx++;
94
+ if (idx >= lines.length) {
95
+ close();
96
+ return false;
97
+ } // past the last line → close
98
+ showLine();
99
+ return true;
100
+ },
101
+ close,
102
+ setVisible: card.setVisible,
103
+ setScreenPos: card.setScreenPos,
104
+ dispose: () => { stop(); card.dispose(); },
105
+ };
106
+ }
107
+ //# sourceMappingURL=dialogue-card.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialogue-card.js","sourceRoot":"","sources":["../../src/ui/dialogue-card.ts"],"names":[],"mappings":"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"}