@kawaijs/renderer-dom 0.1.11 → 0.1.12

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 (107) hide show
  1. package/README.md +34 -12
  2. package/dist/asset-fallbacks.d.ts +13 -0
  3. package/dist/asset-fallbacks.d.ts.map +1 -0
  4. package/dist/asset-fallbacks.js +41 -0
  5. package/dist/asset-fallbacks.js.map +1 -0
  6. package/dist/components/choice-menu.d.ts +12 -0
  7. package/dist/components/choice-menu.d.ts.map +1 -0
  8. package/dist/components/choice-menu.js +44 -0
  9. package/dist/components/choice-menu.js.map +1 -0
  10. package/dist/components/dialogue-box.d.ts +29 -0
  11. package/dist/components/dialogue-box.d.ts.map +1 -0
  12. package/dist/components/dialogue-box.js +155 -0
  13. package/dist/components/dialogue-box.js.map +1 -0
  14. package/dist/components/hotspot-layer.d.ts +13 -0
  15. package/dist/components/hotspot-layer.d.ts.map +1 -0
  16. package/dist/components/hotspot-layer.js +47 -0
  17. package/dist/components/hotspot-layer.js.map +1 -0
  18. package/dist/components/main-menu.d.ts +36 -0
  19. package/dist/components/main-menu.d.ts.map +1 -0
  20. package/dist/components/main-menu.js +257 -0
  21. package/dist/components/main-menu.js.map +1 -0
  22. package/dist/components/quick-menu.d.ts +22 -0
  23. package/dist/components/quick-menu.d.ts.map +1 -0
  24. package/dist/components/quick-menu.js +59 -0
  25. package/dist/components/quick-menu.js.map +1 -0
  26. package/dist/components/stage-layer.d.ts +39 -0
  27. package/dist/components/stage-layer.d.ts.map +1 -0
  28. package/dist/components/stage-layer.js +253 -0
  29. package/dist/components/stage-layer.js.map +1 -0
  30. package/dist/components/vfx-layer.d.ts +28 -0
  31. package/dist/components/vfx-layer.d.ts.map +1 -0
  32. package/dist/components/vfx-layer.js +310 -0
  33. package/dist/components/vfx-layer.js.map +1 -0
  34. package/dist/embed.d.ts +5 -0
  35. package/dist/embed.d.ts.map +1 -0
  36. package/dist/embed.js +17 -0
  37. package/dist/embed.js.map +1 -0
  38. package/dist/icons.d.ts +31 -0
  39. package/dist/icons.d.ts.map +1 -0
  40. package/dist/icons.js +31 -0
  41. package/dist/icons.js.map +1 -0
  42. package/dist/index.d.ts +31 -1
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +69 -3
  45. package/dist/index.js.map +1 -1
  46. package/dist/layout/viewport-adapter.d.ts +52 -0
  47. package/dist/layout/viewport-adapter.d.ts.map +1 -0
  48. package/dist/layout/viewport-adapter.js +123 -0
  49. package/dist/layout/viewport-adapter.js.map +1 -0
  50. package/dist/modals/about-modal.d.ts +3 -0
  51. package/dist/modals/about-modal.d.ts.map +1 -0
  52. package/dist/modals/about-modal.js +59 -0
  53. package/dist/modals/about-modal.js.map +1 -0
  54. package/dist/modals/achievements-modal.d.ts +4 -0
  55. package/dist/modals/achievements-modal.d.ts.map +1 -0
  56. package/dist/modals/achievements-modal.js +103 -0
  57. package/dist/modals/achievements-modal.js.map +1 -0
  58. package/dist/modals/confirm-modal.d.ts +9 -0
  59. package/dist/modals/confirm-modal.d.ts.map +1 -0
  60. package/dist/modals/confirm-modal.js +80 -0
  61. package/dist/modals/confirm-modal.js.map +1 -0
  62. package/dist/modals/gallery-modal.d.ts +4 -0
  63. package/dist/modals/gallery-modal.d.ts.map +1 -0
  64. package/dist/modals/gallery-modal.js +128 -0
  65. package/dist/modals/gallery-modal.js.map +1 -0
  66. package/dist/modals/history-modal.d.ts +3 -0
  67. package/dist/modals/history-modal.d.ts.map +1 -0
  68. package/dist/modals/history-modal.js +66 -0
  69. package/dist/modals/history-modal.js.map +1 -0
  70. package/dist/modals/input-modal.d.ts +10 -0
  71. package/dist/modals/input-modal.d.ts.map +1 -0
  72. package/dist/modals/input-modal.js +62 -0
  73. package/dist/modals/input-modal.js.map +1 -0
  74. package/dist/modals/save-load-modal.d.ts +3 -0
  75. package/dist/modals/save-load-modal.d.ts.map +1 -0
  76. package/dist/modals/save-load-modal.js +262 -0
  77. package/dist/modals/save-load-modal.js.map +1 -0
  78. package/dist/modals/settings-modal.d.ts +14 -0
  79. package/dist/modals/settings-modal.d.ts.map +1 -0
  80. package/dist/modals/settings-modal.js +128 -0
  81. package/dist/modals/settings-modal.js.map +1 -0
  82. package/dist/renderer.d.ts +63 -56
  83. package/dist/renderer.d.ts.map +1 -1
  84. package/dist/renderer.js +643 -734
  85. package/dist/renderer.js.map +1 -1
  86. package/dist/share-meta.d.ts +27 -0
  87. package/dist/share-meta.d.ts.map +1 -0
  88. package/dist/share-meta.js +113 -0
  89. package/dist/share-meta.js.map +1 -0
  90. package/dist/touch.d.ts +16 -0
  91. package/dist/touch.d.ts.map +1 -0
  92. package/dist/touch.js +60 -0
  93. package/dist/touch.js.map +1 -0
  94. package/dist/types.d.ts +134 -0
  95. package/dist/types.d.ts.map +1 -0
  96. package/dist/types.js +60 -0
  97. package/dist/types.js.map +1 -0
  98. package/dist/utils/focus-trap.d.ts +6 -0
  99. package/dist/utils/focus-trap.d.ts.map +1 -0
  100. package/dist/utils/focus-trap.js +52 -0
  101. package/dist/utils/focus-trap.js.map +1 -0
  102. package/dist/utils/rich-text.d.ts +7 -0
  103. package/dist/utils/rich-text.d.ts.map +1 -0
  104. package/dist/utils/rich-text.js +31 -0
  105. package/dist/utils/rich-text.js.map +1 -0
  106. package/package.json +17 -3
  107. package/src/theme.css +1995 -685
package/README.md CHANGED
@@ -7,33 +7,55 @@ Part of the [Kawaijs](https://github.com/biagio-scaglia/KawaiJS) visual novel en
7
7
  ## Installation
8
8
 
9
9
  ```bash
10
- npm install @kawaijs/renderer-dom
10
+ npm install @kawaijs/renderer-dom @kawaijs/runtime @kawaijs/parser
11
11
  ```
12
12
 
13
13
  ## Quick Example
14
14
 
15
15
  ```typescript
16
16
  import { compileScript } from '@kawaijs/parser';
17
- import { mountKawaApp } from '@kawaijs/renderer-dom';
18
- import '@kawaijs/renderer-dom/src/theme.css';
17
+ import { StoryVM } from '@kawaijs/runtime';
18
+ import { DOMRenderer } from '@kawaijs/renderer-dom';
19
+ import '@kawaijs/renderer-dom/dist/theme.css';
19
20
 
20
21
  const story = compileScript(`
21
- character yumia "Yumia"
22
+ character yumia "Yumia" #f43f5e
22
23
 
23
24
  label start:
24
- scene bg classroom
25
- show yumia happy
25
+ scene bg classroom with fade
26
+ vfx sakura
27
+ show yumia happy at center with bounce
26
28
  yumia "Hello from Kawaijs DOM renderer!"
27
29
  `);
28
30
 
29
- mountKawaApp(story, document.getElementById('app')!);
31
+ const vm = new StoryVM(story);
32
+
33
+ const renderer = new DOMRenderer(vm, {
34
+ container: document.getElementById('app')!,
35
+ mainMenu: {
36
+ enabled: true,
37
+ title: "My Visual Novel",
38
+ subtitle: "A Web Tale"
39
+ }
40
+ });
30
41
  ```
31
42
 
32
43
  ## Features
33
- - **Semantic DOM Elements**: `.kawa-root`, `.kawa-stage`, `.kawa-dialogue-box`, `.kawa-choices`.
34
- - **CSS Custom Properties**: Fully customizable theme tokens (`--kawa-primary-accent`, `--kawa-dialogue-bg`, etc.).
35
- - **Typewriter Effect**: Smooth animated text with skip on click.
36
- - **Built-in UI**: Quick menu (Back, History, Save, Load).
44
+
45
+ - **Ren'Py-Style Start / Main Menu**: Start Game, Continue, Load, CG Gallery, Preferences, About, and Quit. Fully customizable through `MainMenuOptions` or standard CSS.
46
+ - **Atmospheric VFX Layer**: Canvas particles for `sakura` (cherry blossoms), `rain`, `snow`, plus `fog` / `tint` overlays (`tint` layers over weather without stopping petals).
47
+ - **Scene & Sprite Transitions**: Background `with fade`; sprite `with bounce` / `dissolve` / `nod` / `shake` / `slideleft` / `slideright`.
48
+ - **Camera Directives & Shakes**: `shake`, `vpunch`, `hpunch`, and screen `flash`.
49
+ - **Unlockable CG Gallery & Lightbox**: Fullscreen illustration modal with progress tracking and lightbox viewer.
50
+ - **Save & Load Modals**: Multi-slot save system with live state previews, slot deletion, and timestamps.
51
+ - **Preferences Modal**: Interactive sliders for Typewriter speed, Auto-forward delay, Music and SFX volume.
52
+ - **Dialogue History / Backlog**: Formatted log of all past speaker interactions (synced on rollback/load).
53
+ - **Responsive 16:9 Letterboxing**: Automatically scales to any screen aspect ratio.
54
+ - **Rich Text & Interpolation**: Built-in support for dynamic variables `[var]`, `{b}`, `{i}`, `{color=...}`, `{size=...}` tags with XSS sanitization.
55
+ - **Keyboard Navigation**: Space/Enter advance, Backspace rollback, A auto, Ctrl skip, S/L save/load, H history, P preferences, Esc title.
56
+ - **Theme / Style hooks**: `theme "noir|sakura|ocean|dusk"` and `style dialogue|choices|stage …` map to CSS tokens / classes.
57
+ - **Share & embed**: dynamic Open Graph meta per scene; `?embed=1` for iframe-friendly chrome; `?at=` deep-links.
37
58
 
38
59
  ## License
39
- MIT © Biagio Scaglia
60
+
61
+ MIT License © 2026 [Biagio Scaglia](https://github.com/biagio-scaglia)
@@ -0,0 +1,13 @@
1
+ import type { AssetType } from './types.js';
2
+ /** Raster + vector extensions tried when a character path resolves to `.svg` by default. */
3
+ export declare const IMAGE_EXTENSIONS: readonly [".png", ".jpg", ".jpeg", ".webp", ".svg"];
4
+ /**
5
+ * Build candidate URLs for a character asset key (`yumia` or `yumia/happy`).
6
+ * Prefer raster (png/jpg/webp) before the default `.svg` so real art wins.
7
+ */
8
+ export declare function characterAssetCandidates(pathKey: string, resolve: (path: string, type: AssetType) => string): string[];
9
+ /**
10
+ * Attach sequential src fallbacks on an image (png/jpg/webp before giving up).
11
+ */
12
+ export declare function bindImageSrcFallbacks(img: HTMLImageElement, candidates: string[], onAllFailed?: () => void): void;
13
+ //# sourceMappingURL=asset-fallbacks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-fallbacks.d.ts","sourceRoot":"","sources":["../src/asset-fallbacks.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAE5C,4FAA4F;AAC5F,eAAO,MAAM,gBAAgB,qDAAsD,CAAC;AAEpF;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,KAAK,MAAM,GACjD,MAAM,EAAE,CAoBV;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,gBAAgB,EACrB,UAAU,EAAE,MAAM,EAAE,EACpB,WAAW,CAAC,EAAE,MAAM,IAAI,GACvB,IAAI,CAeN"}
@@ -0,0 +1,41 @@
1
+ /** Raster + vector extensions tried when a character path resolves to `.svg` by default. */
2
+ export const IMAGE_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.webp', '.svg'];
3
+ /**
4
+ * Build candidate URLs for a character asset key (`yumia` or `yumia/happy`).
5
+ * Prefer raster (png/jpg/webp) before the default `.svg` so real art wins.
6
+ */
7
+ export function characterAssetCandidates(pathKey, resolve) {
8
+ const primary = resolve(pathKey, 'character');
9
+ const extMatch = primary.match(/^(.*)\.(svg|png|jpe?g|webp|gif)$/i);
10
+ if (!extMatch) {
11
+ return IMAGE_EXTENSIONS.map((ext) => `${primary}${ext}`);
12
+ }
13
+ const base = extMatch[1];
14
+ const primaryExt = `.${extMatch[2].toLowerCase()}`;
15
+ const normalizedPrimary = primaryExt === '.jpeg' ? '.jpg' : primaryExt === '.jpg' ? '.jpg' : primaryExt;
16
+ // Explicit non-svg path → that file first, then siblings
17
+ if (normalizedPrimary !== '.svg') {
18
+ const rest = IMAGE_EXTENSIONS.filter((ext) => ext !== normalizedPrimary && !(normalizedPrimary === '.jpg' && ext === '.jpeg'));
19
+ return [primary, ...rest.map((ext) => `${base}${ext}`)];
20
+ }
21
+ // Default resolver appended .svg → try real bitmaps first
22
+ return IMAGE_EXTENSIONS.map((ext) => `${base}${ext}`);
23
+ }
24
+ /**
25
+ * Attach sequential src fallbacks on an image (png/jpg/webp before giving up).
26
+ */
27
+ export function bindImageSrcFallbacks(img, candidates, onAllFailed) {
28
+ let index = 0;
29
+ const tryNext = () => {
30
+ if (index >= candidates.length) {
31
+ onAllFailed?.();
32
+ return;
33
+ }
34
+ img.src = candidates[index++];
35
+ };
36
+ img.onerror = () => {
37
+ tryNext();
38
+ };
39
+ tryNext();
40
+ }
41
+ //# sourceMappingURL=asset-fallbacks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asset-fallbacks.js","sourceRoot":"","sources":["../src/asset-fallbacks.ts"],"names":[],"mappings":"AAEA,4FAA4F;AAC5F,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAU,CAAC;AAEpF;;;GAGG;AACH,MAAM,UAAU,wBAAwB,CACtC,OAAe,EACf,OAAkD;IAElD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC9C,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACpE,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,gBAAgB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,OAAO,GAAG,GAAG,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAC;IAC1B,MAAM,UAAU,GAAG,IAAI,QAAQ,CAAC,CAAC,CAAE,CAAC,WAAW,EAAE,EAAE,CAAC;IACpD,MAAM,iBAAiB,GACrB,UAAU,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,UAAU,CAAC;IAEhF,yDAAyD;IACzD,IAAI,iBAAiB,KAAK,MAAM,EAAE,CAAC;QACjC,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,KAAK,iBAAiB,IAAI,CAAC,CAAC,iBAAiB,KAAK,MAAM,IAAI,GAAG,KAAK,OAAO,CAAC,CAAC,CAAC;QAC/H,OAAO,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,GAAG,EAAE,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,0DAA0D;IAC1D,OAAO,gBAAgB,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,GAAG,EAAE,CAAC,CAAC;AACxD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,qBAAqB,CACnC,GAAqB,EACrB,UAAoB,EACpB,WAAwB;IAExB,IAAI,KAAK,GAAG,CAAC,CAAC;IACd,MAAM,OAAO,GAAG,GAAS,EAAE;QACzB,IAAI,KAAK,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;YAC/B,WAAW,EAAE,EAAE,CAAC;YAChB,OAAO;QACT,CAAC;QACD,GAAG,CAAC,GAAG,GAAG,UAAU,CAAC,KAAK,EAAE,CAAE,CAAC;IACjC,CAAC,CAAC;IAEF,GAAG,CAAC,OAAO,GAAG,GAAG,EAAE;QACjB,OAAO,EAAE,CAAC;IACZ,CAAC,CAAC;IAEF,OAAO,EAAE,CAAC;AACZ,CAAC"}
@@ -0,0 +1,12 @@
1
+ import type { ChoiceOption } from '@kawaijs/ast';
2
+ export interface ChoiceMenuCallbacks {
3
+ onSelect: (index: number) => void;
4
+ }
5
+ export declare class ChoiceMenuComponent {
6
+ readonly el: HTMLDivElement;
7
+ private readonly callbacks;
8
+ constructor(callbacks: ChoiceMenuCallbacks);
9
+ render(choices: readonly ChoiceOption[] | null | undefined): void;
10
+ hide(): void;
11
+ }
12
+ //# sourceMappingURL=choice-menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"choice-menu.d.ts","sourceRoot":"","sources":["../../src/components/choice-menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAEjD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,qBAAa,mBAAmB;IAC9B,SAAgB,EAAE,EAAE,cAAc,CAAC;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;gBAEpC,SAAS,EAAE,mBAAmB;IASnC,MAAM,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IAkCjE,IAAI,IAAI,IAAI;CAIpB"}
@@ -0,0 +1,44 @@
1
+ export class ChoiceMenuComponent {
2
+ el;
3
+ callbacks;
4
+ constructor(callbacks) {
5
+ this.callbacks = callbacks;
6
+ this.el = document.createElement('div');
7
+ this.el.className = 'kawa-choice-container kawa-choices';
8
+ this.el.setAttribute('role', 'group');
9
+ this.el.setAttribute('aria-label', 'Choices');
10
+ this.el.style.display = 'none';
11
+ }
12
+ render(choices) {
13
+ this.el.innerHTML = '';
14
+ if (!choices || choices.length === 0) {
15
+ this.el.style.display = 'none';
16
+ return;
17
+ }
18
+ this.el.style.display = 'flex';
19
+ choices.forEach((choice, index) => {
20
+ const btn = document.createElement('button');
21
+ btn.type = 'button';
22
+ btn.className = 'kawa-choice-btn';
23
+ btn.textContent = choice.text;
24
+ btn.setAttribute('tabindex', '0');
25
+ btn.setAttribute('aria-label', `Option ${index + 1}: ${choice.text}`);
26
+ btn.addEventListener('click', (e) => {
27
+ e.preventDefault();
28
+ e.stopPropagation();
29
+ this.callbacks.onSelect(index);
30
+ });
31
+ this.el.appendChild(btn);
32
+ });
33
+ // Auto-focus first choice for keyboard navigation
34
+ const firstBtn = this.el.querySelector('button');
35
+ if (firstBtn) {
36
+ firstBtn.focus();
37
+ }
38
+ }
39
+ hide() {
40
+ this.el.style.display = 'none';
41
+ this.el.innerHTML = '';
42
+ }
43
+ }
44
+ //# sourceMappingURL=choice-menu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"choice-menu.js","sourceRoot":"","sources":["../../src/components/choice-menu.ts"],"names":[],"mappings":"AAMA,MAAM,OAAO,mBAAmB;IACd,EAAE,CAAiB;IAClB,SAAS,CAAsB;IAEhD,YAAY,SAA8B;QACxC,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,EAAE,CAAC,SAAS,GAAG,oCAAoC,CAAC;QACzD,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QACtC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,SAAS,CAAC,CAAC;QAC9C,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;IACjC,CAAC;IAEM,MAAM,CAAC,OAAmD;QAC/D,IAAI,CAAC,EAAE,CAAC,SAAS,GAAG,EAAE,CAAC;QAEvB,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;YAC/B,OAAO;QACT,CAAC;QAED,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;QAE/B,OAAO,CAAC,OAAO,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE;YAChC,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC7C,GAAG,CAAC,IAAI,GAAG,QAAQ,CAAC;YACpB,GAAG,CAAC,SAAS,GAAG,iBAAiB,CAAC;YAClC,GAAG,CAAC,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC;YAC9B,GAAG,CAAC,YAAY,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;YAClC,GAAG,CAAC,YAAY,CAAC,YAAY,EAAE,UAAU,KAAK,GAAG,CAAC,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YAEtE,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;gBAClC,CAAC,CAAC,cAAc,EAAE,CAAC;gBACnB,CAAC,CAAC,eAAe,EAAE,CAAC;gBACpB,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YACjC,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;QAEH,kDAAkD;QAClD,MAAM,QAAQ,GAAG,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QACjD,IAAI,QAAQ,EAAE,CAAC;YACb,QAAQ,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;IACH,CAAC;IAEM,IAAI;QACT,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;QAC/B,IAAI,CAAC,EAAE,CAAC,SAAS,GAAG,EAAE,CAAC;IACzB,CAAC;CACF"}
@@ -0,0 +1,29 @@
1
+ export interface DialogueBoxCallbacks {
2
+ onAdvance: () => void;
3
+ }
4
+ export declare class DialogueBoxComponent {
5
+ readonly el: HTMLDivElement;
6
+ private readonly speakerTagEl;
7
+ private readonly dialogueTextEl;
8
+ private readonly announceEl;
9
+ private readonly indicatorEl;
10
+ private typewriterSpeed;
11
+ private typewriterInterval;
12
+ private isTypewriting;
13
+ private fullCurrentText;
14
+ private onTypewriterComplete?;
15
+ constructor(typewriterSpeed?: number);
16
+ setTypewriterSpeed(speed: number): void;
17
+ getIsTypewriting(): boolean;
18
+ render(dialogue: {
19
+ speaker?: string;
20
+ speakerDisplayName?: string;
21
+ speakerColor?: string;
22
+ text: string;
23
+ } | null | undefined, onComplete?: () => void): void;
24
+ finishTypewriter(): void;
25
+ private announceFinishedLine;
26
+ private startTypewriter;
27
+ destroy(): void;
28
+ }
29
+ //# sourceMappingURL=dialogue-box.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialogue-box.d.ts","sourceRoot":"","sources":["../../src/components/dialogue-box.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,qBAAa,oBAAoB;IAC/B,SAAgB,EAAE,EAAE,cAAc,CAAC;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAiB;IAC9C,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAiB;IAChD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAiB;IAC5C,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAiB;IAE7C,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,kBAAkB,CAAuB;IACjD,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,eAAe,CAAM;IAC7B,OAAO,CAAC,oBAAoB,CAAC,CAAa;gBAE9B,eAAe,SAAK;IA2CzB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;IAIvC,gBAAgB,IAAI,OAAO;IAI3B,MAAM,CACX,QAAQ,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,EACnH,UAAU,CAAC,EAAE,MAAM,IAAI,GACtB,IAAI;IA8CA,gBAAgB,IAAI,IAAI;IAiB/B,OAAO,CAAC,oBAAoB;IAe5B,OAAO,CAAC,eAAe;IA8BhB,OAAO,IAAI,IAAI;CAMvB"}
@@ -0,0 +1,155 @@
1
+ import { SVG_ICONS } from '../icons.js';
2
+ import { formatRichText } from '../utils/rich-text.js';
3
+ export class DialogueBoxComponent {
4
+ el;
5
+ speakerTagEl;
6
+ dialogueTextEl;
7
+ announceEl;
8
+ indicatorEl;
9
+ typewriterSpeed;
10
+ typewriterInterval = null;
11
+ isTypewriting = false;
12
+ fullCurrentText = '';
13
+ onTypewriterComplete;
14
+ constructor(typewriterSpeed = 20) {
15
+ this.typewriterSpeed = typewriterSpeed;
16
+ this.el = document.createElement('div');
17
+ this.el.className = 'kawa-dialogue-box kawa-dialogue';
18
+ this.el.setAttribute('role', 'region');
19
+ this.el.setAttribute('aria-label', 'Dialogue');
20
+ this.el.setAttribute('tabindex', '0');
21
+ this.speakerTagEl = document.createElement('div');
22
+ this.speakerTagEl.className = 'kawa-speaker-tag kawa-speaker';
23
+ this.dialogueTextEl = document.createElement('div');
24
+ this.dialogueTextEl.className = 'kawa-dialogue-text kawa-text';
25
+ // Visible typewriter must NOT be live — per-character updates spam screen readers.
26
+ this.dialogueTextEl.setAttribute('aria-hidden', 'true');
27
+ this.announceEl = document.createElement('div');
28
+ this.announceEl.className = 'kawa-dialogue-announce';
29
+ this.announceEl.setAttribute('aria-live', 'polite');
30
+ this.announceEl.setAttribute('aria-atomic', 'true');
31
+ // Visually hidden but available to AT
32
+ this.announceEl.style.position = 'absolute';
33
+ this.announceEl.style.width = '1px';
34
+ this.announceEl.style.height = '1px';
35
+ this.announceEl.style.padding = '0';
36
+ this.announceEl.style.margin = '-1px';
37
+ this.announceEl.style.overflow = 'hidden';
38
+ this.announceEl.style.clip = 'rect(0, 0, 0, 0)';
39
+ this.announceEl.style.whiteSpace = 'nowrap';
40
+ this.announceEl.style.border = '0';
41
+ this.indicatorEl = document.createElement('div');
42
+ this.indicatorEl.className = 'kawa-continue-indicator';
43
+ this.indicatorEl.setAttribute('aria-hidden', 'true');
44
+ this.indicatorEl.innerHTML = SVG_ICONS.arrowDown;
45
+ this.el.appendChild(this.speakerTagEl);
46
+ this.el.appendChild(this.dialogueTextEl);
47
+ this.el.appendChild(this.announceEl);
48
+ this.el.appendChild(this.indicatorEl);
49
+ }
50
+ setTypewriterSpeed(speed) {
51
+ this.typewriterSpeed = speed;
52
+ }
53
+ getIsTypewriting() {
54
+ return this.isTypewriting;
55
+ }
56
+ render(dialogue, onComplete) {
57
+ if (!dialogue) {
58
+ this.el.style.display = 'none';
59
+ this.announceEl.textContent = '';
60
+ return;
61
+ }
62
+ this.el.style.display = 'block';
63
+ // Speaker Tag
64
+ if (dialogue.speakerDisplayName || dialogue.speaker) {
65
+ this.speakerTagEl.textContent = dialogue.speakerDisplayName ?? dialogue.speaker ?? '';
66
+ this.speakerTagEl.style.display = 'inline-block';
67
+ if (dialogue.speakerColor) {
68
+ this.speakerTagEl.style.backgroundColor = dialogue.speakerColor;
69
+ }
70
+ else {
71
+ this.speakerTagEl.style.backgroundColor = '';
72
+ }
73
+ }
74
+ else {
75
+ this.speakerTagEl.style.display = 'none';
76
+ }
77
+ // Dialogue Text
78
+ this.onTypewriterComplete = onComplete;
79
+ this.fullCurrentText = dialogue.text;
80
+ if (this.typewriterInterval) {
81
+ clearInterval(this.typewriterInterval);
82
+ this.typewriterInterval = null;
83
+ }
84
+ // Clear previous announcement while typing so AT only hears the finished line.
85
+ this.announceEl.textContent = '';
86
+ this.el.setAttribute('aria-busy', 'true');
87
+ if (this.typewriterSpeed <= 0) {
88
+ this.dialogueTextEl.innerHTML = formatRichText(this.fullCurrentText);
89
+ this.isTypewriting = false;
90
+ this.indicatorEl.style.opacity = '1';
91
+ this.announceFinishedLine(dialogue);
92
+ this.onTypewriterComplete?.();
93
+ }
94
+ else {
95
+ this.startTypewriter(this.fullCurrentText, dialogue);
96
+ }
97
+ }
98
+ finishTypewriter() {
99
+ if (!this.isTypewriting)
100
+ return;
101
+ if (this.typewriterInterval) {
102
+ clearInterval(this.typewriterInterval);
103
+ this.typewriterInterval = null;
104
+ }
105
+ this.isTypewriting = false;
106
+ this.dialogueTextEl.innerHTML = formatRichText(this.fullCurrentText);
107
+ this.indicatorEl.style.opacity = '1';
108
+ const speaker = this.speakerTagEl.style.display === 'none' ? undefined : this.speakerTagEl.textContent || undefined;
109
+ this.announceFinishedLine({
110
+ speakerDisplayName: speaker,
111
+ text: this.fullCurrentText
112
+ });
113
+ this.onTypewriterComplete?.();
114
+ }
115
+ announceFinishedLine(dialogue) {
116
+ this.el.setAttribute('aria-busy', 'false');
117
+ const who = dialogue.speakerDisplayName || dialogue.speaker;
118
+ const plain = (() => {
119
+ const temp = document.createElement('div');
120
+ temp.innerHTML = formatRichText(dialogue.text);
121
+ return temp.textContent || dialogue.text;
122
+ })();
123
+ this.announceEl.textContent = who ? `${who}: ${plain}` : plain;
124
+ }
125
+ startTypewriter(text, dialogue) {
126
+ this.isTypewriting = true;
127
+ this.indicatorEl.style.opacity = '0';
128
+ this.dialogueTextEl.innerHTML = '';
129
+ const formattedFull = formatRichText(text);
130
+ // Extract text chunks or plain characters
131
+ const temp = document.createElement('div');
132
+ temp.innerHTML = formattedFull;
133
+ const plainText = temp.textContent || '';
134
+ const graphemes = Array.from(plainText);
135
+ let charIdx = 0;
136
+ this.typewriterInterval = window.setInterval(() => {
137
+ charIdx++;
138
+ if (charIdx >= graphemes.length) {
139
+ this.finishTypewriter();
140
+ }
141
+ else {
142
+ this.dialogueTextEl.textContent = graphemes.slice(0, charIdx).join('');
143
+ }
144
+ }, this.typewriterSpeed);
145
+ // Keep dialogue ref for finish path via fullCurrentText / speakerTag
146
+ void dialogue;
147
+ }
148
+ destroy() {
149
+ if (this.typewriterInterval) {
150
+ clearInterval(this.typewriterInterval);
151
+ this.typewriterInterval = null;
152
+ }
153
+ }
154
+ }
155
+ //# sourceMappingURL=dialogue-box.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dialogue-box.js","sourceRoot":"","sources":["../../src/components/dialogue-box.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAMvD,MAAM,OAAO,oBAAoB;IACf,EAAE,CAAiB;IAClB,YAAY,CAAiB;IAC7B,cAAc,CAAiB;IAC/B,UAAU,CAAiB;IAC3B,WAAW,CAAiB;IAErC,eAAe,CAAS;IACxB,kBAAkB,GAAkB,IAAI,CAAC;IACzC,aAAa,GAAG,KAAK,CAAC;IACtB,eAAe,GAAG,EAAE,CAAC;IACrB,oBAAoB,CAAc;IAE1C,YAAY,eAAe,GAAG,EAAE;QAC9B,IAAI,CAAC,eAAe,GAAG,eAAe,CAAC;QAEvC,IAAI,CAAC,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,EAAE,CAAC,SAAS,GAAG,iCAAiC,CAAC;QACtD,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACvC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,UAAU,CAAC,CAAC;QAC/C,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QAEtC,IAAI,CAAC,YAAY,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAClD,IAAI,CAAC,YAAY,CAAC,SAAS,GAAG,+BAA+B,CAAC;QAE9D,IAAI,CAAC,cAAc,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QACpD,IAAI,CAAC,cAAc,CAAC,SAAS,GAAG,8BAA8B,CAAC;QAC/D,mFAAmF;QACnF,IAAI,CAAC,cAAc,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QAExD,IAAI,CAAC,UAAU,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAChD,IAAI,CAAC,UAAU,CAAC,SAAS,GAAG,wBAAwB,CAAC;QACrD,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC;QACpD,IAAI,CAAC,UAAU,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QACpD,sCAAsC;QACtC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,GAAG,UAAU,CAAC;QAC5C,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC;QACpC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,KAAK,CAAC;QACrC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC;QACpC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;QACtC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,QAAQ,GAAG,QAAQ,CAAC;QAC1C,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,GAAG,kBAAkB,CAAC;QAChD,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,UAAU,GAAG,QAAQ,CAAC;QAC5C,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,CAAC;QAEnC,IAAI,CAAC,WAAW,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QACjD,IAAI,CAAC,WAAW,CAAC,SAAS,GAAG,yBAAyB,CAAC;QACvD,IAAI,CAAC,WAAW,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;QACrD,IAAI,CAAC,WAAW,CAAC,SAAS,GAAG,SAAS,CAAC,SAAS,CAAC;QAEjD,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACvC,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;QACzC,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QACrC,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IACxC,CAAC;IAEM,kBAAkB,CAAC,KAAa;QACrC,IAAI,CAAC,eAAe,GAAG,KAAK,CAAC;IAC/B,CAAC;IAEM,gBAAgB;QACrB,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAEM,MAAM,CACX,QAAmH,EACnH,UAAuB;QAEvB,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;YAC/B,IAAI,CAAC,UAAU,CAAC,WAAW,GAAG,EAAE,CAAC;YACjC,OAAO;QACT,CAAC;QAED,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC;QAEhC,cAAc;QACd,IAAI,QAAQ,CAAC,kBAAkB,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;YACpD,IAAI,CAAC,YAAY,CAAC,WAAW,GAAG,QAAQ,CAAC,kBAAkB,IAAI,QAAQ,CAAC,OAAO,IAAI,EAAE,CAAC;YACtF,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,GAAG,cAAc,CAAC;YACjD,IAAI,QAAQ,CAAC,YAAY,EAAE,CAAC;gBAC1B,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,eAAe,GAAG,QAAQ,CAAC,YAAY,CAAC;YAClE,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,eAAe,GAAG,EAAE,CAAC;YAC/C,CAAC;QACH,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;QAC3C,CAAC;QAED,gBAAgB;QAChB,IAAI,CAAC,oBAAoB,GAAG,UAAU,CAAC;QACvC,IAAI,CAAC,eAAe,GAAG,QAAQ,CAAC,IAAI,CAAC;QAErC,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC5B,aAAa,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;YACvC,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QACjC,CAAC;QAED,+EAA+E;QAC/E,IAAI,CAAC,UAAU,CAAC,WAAW,GAAG,EAAE,CAAC;QACjC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;QAE1C,IAAI,IAAI,CAAC,eAAe,IAAI,CAAC,EAAE,CAAC;YAC9B,IAAI,CAAC,cAAc,CAAC,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;YACrE,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;YAC3B,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC;YACrC,IAAI,CAAC,oBAAoB,CAAC,QAAQ,CAAC,CAAC;YACpC,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAC;QAChC,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAEM,gBAAgB;QACrB,IAAI,CAAC,IAAI,CAAC,aAAa;YAAE,OAAO;QAChC,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC5B,aAAa,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;YACvC,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QACjC,CAAC;QACD,IAAI,CAAC,aAAa,GAAG,KAAK,CAAC;QAC3B,IAAI,CAAC,cAAc,CAAC,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QACrE,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC;QACrC,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,WAAW,IAAI,SAAS,CAAC;QACpH,IAAI,CAAC,oBAAoB,CAAC;YACxB,kBAAkB,EAAE,OAAO;YAC3B,IAAI,EAAE,IAAI,CAAC,eAAe;SAC3B,CAAC,CAAC;QACH,IAAI,CAAC,oBAAoB,EAAE,EAAE,CAAC;IAChC,CAAC;IAEO,oBAAoB,CAAC,QAI5B;QACC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QAC3C,MAAM,GAAG,GAAG,QAAQ,CAAC,kBAAkB,IAAI,QAAQ,CAAC,OAAO,CAAC;QAC5D,MAAM,KAAK,GAAG,CAAC,GAAG,EAAE;YAClB,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;YAC3C,IAAI,CAAC,SAAS,GAAG,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YAC/C,OAAO,IAAI,CAAC,WAAW,IAAI,QAAQ,CAAC,IAAI,CAAC;QAC3C,CAAC,CAAC,EAAE,CAAC;QACL,IAAI,CAAC,UAAU,CAAC,WAAW,GAAG,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,KAAK,KAAK,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IACjE,CAAC;IAEO,eAAe,CACrB,IAAY,EACZ,QAAyE;QAEzE,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC;QAC1B,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,OAAO,GAAG,GAAG,CAAC;QACrC,IAAI,CAAC,cAAc,CAAC,SAAS,GAAG,EAAE,CAAC;QAEnC,MAAM,aAAa,GAAG,cAAc,CAAC,IAAI,CAAC,CAAC;QAE3C,0CAA0C;QAC1C,MAAM,IAAI,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QAC3C,IAAI,CAAC,SAAS,GAAG,aAAa,CAAC;QAC/B,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC;QACzC,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAExC,IAAI,OAAO,GAAG,CAAC,CAAC;QAChB,IAAI,CAAC,kBAAkB,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE;YAChD,OAAO,EAAE,CAAC;YACV,IAAI,OAAO,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;gBAChC,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC1B,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,cAAc,CAAC,WAAW,GAAG,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACzE,CAAC;QACH,CAAC,EAAE,IAAI,CAAC,eAAe,CAAC,CAAC;QAEzB,qEAAqE;QACrE,KAAK,QAAQ,CAAC;IAChB,CAAC;IAEM,OAAO;QACZ,IAAI,IAAI,CAAC,kBAAkB,EAAE,CAAC;YAC5B,aAAa,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;YACvC,IAAI,CAAC,kBAAkB,GAAG,IAAI,CAAC;QACjC,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,13 @@
1
+ import type { HotspotOption } from '@kawaijs/ast';
2
+ /**
3
+ * Absolute-positioned clickable regions over the stage / CG.
4
+ * Coordinates are normalized 0–1 relative to the stage.
5
+ */
6
+ export declare class HotspotLayerComponent {
7
+ private readonly onSelect;
8
+ readonly el: HTMLDivElement;
9
+ constructor(onSelect: (id: string) => void);
10
+ render(hotspots: readonly HotspotOption[] | null | undefined): void;
11
+ destroy(): void;
12
+ }
13
+ //# sourceMappingURL=hotspot-layer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hotspot-layer.d.ts","sourceRoot":"","sources":["../../src/components/hotspot-layer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD;;;GAGG;AACH,qBAAa,qBAAqB;IAGpB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAFrC,SAAgB,EAAE,EAAE,cAAc,CAAC;gBAEN,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI;IAOpD,MAAM,CAAC,QAAQ,EAAE,SAAS,aAAa,EAAE,GAAG,IAAI,GAAG,SAAS,GAAG,IAAI;IA+BnE,OAAO,IAAI,IAAI;CAIvB"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Absolute-positioned clickable regions over the stage / CG.
3
+ * Coordinates are normalized 0–1 relative to the stage.
4
+ */
5
+ export class HotspotLayerComponent {
6
+ onSelect;
7
+ el;
8
+ constructor(onSelect) {
9
+ this.onSelect = onSelect;
10
+ this.el = document.createElement('div');
11
+ this.el.className = 'kawa-hotspot-layer';
12
+ this.el.setAttribute('aria-label', 'Interactive regions');
13
+ this.el.style.display = 'none';
14
+ }
15
+ render(hotspots) {
16
+ this.el.replaceChildren();
17
+ if (!hotspots || hotspots.length === 0) {
18
+ this.el.style.display = 'none';
19
+ this.el.setAttribute('aria-hidden', 'true');
20
+ return;
21
+ }
22
+ this.el.style.display = 'block';
23
+ this.el.setAttribute('aria-hidden', 'false');
24
+ for (const spot of hotspots) {
25
+ const btn = document.createElement('button');
26
+ btn.type = 'button';
27
+ btn.className = 'kawa-hotspot';
28
+ btn.dataset.hotspotId = spot.id;
29
+ btn.setAttribute('aria-label', spot.id);
30
+ btn.title = spot.id;
31
+ btn.style.left = `${spot.x * 100}%`;
32
+ btn.style.top = `${spot.y * 100}%`;
33
+ btn.style.width = `${spot.w * 100}%`;
34
+ btn.style.height = `${spot.h * 100}%`;
35
+ btn.addEventListener('click', (e) => {
36
+ e.stopPropagation();
37
+ this.onSelect(spot.id);
38
+ });
39
+ this.el.appendChild(btn);
40
+ }
41
+ }
42
+ destroy() {
43
+ this.el.replaceChildren();
44
+ this.el.remove();
45
+ }
46
+ }
47
+ //# sourceMappingURL=hotspot-layer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hotspot-layer.js","sourceRoot":"","sources":["../../src/components/hotspot-layer.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,OAAO,qBAAqB;IAGH;IAFb,EAAE,CAAiB;IAEnC,YAA6B,QAA8B;QAA9B,aAAQ,GAAR,QAAQ,CAAsB;QACzD,IAAI,CAAC,EAAE,GAAG,QAAQ,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC;QACxC,IAAI,CAAC,EAAE,CAAC,SAAS,GAAG,oBAAoB,CAAC;QACzC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,YAAY,EAAE,qBAAqB,CAAC,CAAC;QAC1D,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;IACjC,CAAC;IAEM,MAAM,CAAC,QAAqD;QACjE,IAAI,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC;QAE1B,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvC,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,MAAM,CAAC;YAC/B,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,MAAM,CAAC,CAAC;YAC5C,OAAO;QACT,CAAC;QAED,IAAI,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,GAAG,OAAO,CAAC;QAChC,IAAI,CAAC,EAAE,CAAC,YAAY,CAAC,aAAa,EAAE,OAAO,CAAC,CAAC;QAE7C,KAAK,MAAM,IAAI,IAAI,QAAQ,EAAE,CAAC;YAC5B,MAAM,GAAG,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;YAC7C,GAAG,CAAC,IAAI,GAAG,QAAQ,CAAC;YACpB,GAAG,CAAC,SAAS,GAAG,cAAc,CAAC;YAC/B,GAAG,CAAC,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,EAAE,CAAC;YAChC,GAAG,CAAC,YAAY,CAAC,YAAY,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC;YACxC,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,CAAC;YACpB,GAAG,CAAC,KAAK,CAAC,IAAI,GAAG,GAAG,IAAI,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;YACpC,GAAG,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,IAAI,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;YACnC,GAAG,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,IAAI,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;YACrC,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;YACtC,GAAG,CAAC,gBAAgB,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;gBAClC,CAAC,CAAC,eAAe,EAAE,CAAC;gBACpB,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACzB,CAAC,CAAC,CAAC;YACH,IAAI,CAAC,EAAE,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;IACH,CAAC;IAEM,OAAO;QACZ,IAAI,CAAC,EAAE,CAAC,eAAe,EAAE,CAAC;QAC1B,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC;IACnB,CAAC;CACF"}
@@ -0,0 +1,36 @@
1
+ import type { StoryVM } from '@kawaijs/runtime';
2
+ import type { MainMenuItem, MainMenuOptions, AssetType } from '../types.js';
3
+ export interface MainMenuComponentCallbacks {
4
+ onStartNewGame: () => void;
5
+ onContinueGame: () => Promise<boolean>;
6
+ assetResolver: (path: string, type: AssetType) => string;
7
+ getSettingsConfig: () => {
8
+ typewriterSpeed: number;
9
+ autoDelayMs: number;
10
+ musicVolume?: number;
11
+ soundVolume?: number;
12
+ onSettingsChange: (s: {
13
+ typewriterSpeed: number;
14
+ autoDelayMs: number;
15
+ musicVolume?: number;
16
+ soundVolume?: number;
17
+ }) => void;
18
+ };
19
+ }
20
+ export declare class MainMenuComponent {
21
+ private readonly vm;
22
+ private readonly rootEl;
23
+ private readonly options?;
24
+ private readonly callbacks;
25
+ private menuEl?;
26
+ private isVisible;
27
+ constructor(rootEl: HTMLElement, vm: StoryVM, options: MainMenuOptions | undefined, callbacks: MainMenuComponentCallbacks);
28
+ mount(container: HTMLElement): void;
29
+ show(): void;
30
+ hide(): void;
31
+ isActive(): boolean;
32
+ refresh(): void;
33
+ getDefaultMenuItems(): MainMenuItem[];
34
+ private buildDOM;
35
+ }
36
+ //# sourceMappingURL=main-menu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main-menu.d.ts","sourceRoot":"","sources":["../../src/components/main-menu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAEhD,OAAO,KAAK,EAAE,YAAY,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAS5E,MAAM,WAAW,0BAA0B;IACzC,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,cAAc,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACvC,aAAa,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,KAAK,MAAM,CAAC;IACzD,iBAAiB,EAAE,MAAM;QACvB,eAAe,EAAE,MAAM,CAAC;QACxB,WAAW,EAAE,MAAM,CAAC;QACpB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,gBAAgB,EAAE,CAAC,CAAC,EAAE;YACpB,eAAe,EAAE,MAAM,CAAC;YACxB,WAAW,EAAE,MAAM,CAAC;YACpB,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,WAAW,CAAC,EAAE,MAAM,CAAC;SACtB,KAAK,IAAI,CAAC;KACZ,CAAC;CACH;AAED,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAU;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA6B;IAEvD,OAAO,CAAC,MAAM,CAAC,CAAiB;IAChC,OAAO,CAAC,SAAS,CAAS;gBAEd,MAAM,EAAE,WAAW,EAAE,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,eAAe,GAAG,SAAS,EAAE,SAAS,EAAE,0BAA0B;IAOlH,KAAK,CAAC,SAAS,EAAE,WAAW,GAAG,IAAI;IASnC,IAAI,IAAI,IAAI;IAcZ,IAAI,IAAI,IAAI;IAQZ,QAAQ,IAAI,OAAO;IAInB,OAAO,IAAI,IAAI;IAoBf,mBAAmB,IAAI,YAAY,EAAE;IA8D5C,OAAO,CAAC,QAAQ;CAyIjB"}