@the-inclusionist/engine 9.0.0 → 11.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 (532) hide show
  1. package/README.md +120 -78
  2. package/app/css/style.css +761 -377
  3. package/app/public/vendor/fonts/playwrite-cu.woff2 +0 -0
  4. package/app/public/vendor/fonts/playwrite-es-deco.woff2 +0 -0
  5. package/app/public/vendor/fonts/playwrite-es.woff2 +0 -0
  6. package/app/public/vendor/fonts/playwrite-gb-j.woff2 +0 -0
  7. package/app/public/vendor/fonts/playwrite-gb-s.woff2 +0 -0
  8. package/app/public/vendor/fonts/playwrite-pe.woff2 +0 -0
  9. package/app/public/vendor/fonts/playwrite-pt.woff2 +0 -0
  10. package/app/public/vendor/fonts-licences/NOTICE.txt +166 -0
  11. package/app/public/vendor/fonts-licences/OFL-1.1.txt +86 -0
  12. package/app/public/vendor/fonts.css +36 -84
  13. package/dist-pkg/boot/create-game.d.ts +499 -219
  14. package/dist-pkg/boot/create-game.js +3944 -562
  15. package/dist-pkg/core/a11y-sr.d.ts +25 -3
  16. package/dist-pkg/core/a11y-sr.js +38 -19
  17. package/dist-pkg/core/accessible-label.d.ts +17 -0
  18. package/dist-pkg/core/accessible-label.js +37 -0
  19. package/dist-pkg/core/accommodation-subjects.d.ts +26 -0
  20. package/dist-pkg/core/accommodation-subjects.js +32 -0
  21. package/dist-pkg/core/accommodations.d.ts +125 -0
  22. package/dist-pkg/core/accommodations.js +195 -0
  23. package/dist-pkg/core/actions.d.ts +108 -55
  24. package/dist-pkg/core/actions.js +122 -71
  25. package/dist-pkg/core/calm-mode.d.ts +31 -0
  26. package/dist-pkg/core/calm-mode.js +47 -0
  27. package/dist-pkg/core/camera-cycle.d.ts +15 -0
  28. package/dist-pkg/core/camera-cycle.js +7 -0
  29. package/dist-pkg/core/caption-duration.d.ts +15 -0
  30. package/dist-pkg/core/caption-duration.js +19 -0
  31. package/dist-pkg/core/cartridge-problems.d.ts +34 -0
  32. package/dist-pkg/core/cartridge-problems.js +53 -0
  33. package/dist-pkg/core/constants.d.ts +0 -54
  34. package/dist-pkg/core/constants.js +9 -76
  35. package/dist-pkg/core/contract.d.ts +142 -164
  36. package/dist-pkg/core/contract.js +222 -208
  37. package/dist-pkg/core/dom-query.d.ts +4 -4
  38. package/dist-pkg/core/dom-query.js +12 -20
  39. package/dist-pkg/core/entity.d.ts +62 -77
  40. package/dist-pkg/core/entity.js +18 -32
  41. package/dist-pkg/core/escape-html.d.ts +9 -10
  42. package/dist-pkg/core/escape-html.js +16 -20
  43. package/dist-pkg/core/flash-threshold.d.ts +17 -0
  44. package/dist-pkg/core/flash-threshold.js +113 -0
  45. package/dist-pkg/core/game-speed.d.ts +11 -0
  46. package/dist-pkg/core/game-speed.js +16 -0
  47. package/dist-pkg/core/genres.d.ts +20 -0
  48. package/dist-pkg/core/genres.js +44 -0
  49. package/dist-pkg/core/i18n.d.ts +71 -60
  50. package/dist-pkg/core/i18n.js +186 -200
  51. package/dist-pkg/core/loop.d.ts +17 -6
  52. package/dist-pkg/core/loop.js +26 -26
  53. package/dist-pkg/core/pause-icon-catalogue.d.ts +15 -0
  54. package/dist-pkg/core/pause-icon-catalogue.js +54 -0
  55. package/dist-pkg/core/ring.d.ts +17 -0
  56. package/dist-pkg/core/ring.js +29 -0
  57. package/dist-pkg/core/rng.d.ts +7 -12
  58. package/dist-pkg/core/rng.js +23 -53
  59. package/dist-pkg/core/route.d.ts +26 -26
  60. package/dist-pkg/core/route.js +126 -121
  61. package/dist-pkg/core/scenes.d.ts +37 -40
  62. package/dist-pkg/core/scenes.js +41 -43
  63. package/dist-pkg/core/screens.d.ts +4 -4
  64. package/dist-pkg/core/screens.js +11 -16
  65. package/dist-pkg/core/session-clock.d.ts +23 -0
  66. package/dist-pkg/core/session-clock.js +37 -0
  67. package/dist-pkg/core/setting-defaults.d.ts +61 -0
  68. package/dist-pkg/core/setting-defaults.js +66 -0
  69. package/dist-pkg/core/speech-rate.d.ts +29 -0
  70. package/dist-pkg/core/speech-rate.js +47 -0
  71. package/dist-pkg/core/state.d.ts +118 -118
  72. package/dist-pkg/core/state.js +322 -297
  73. package/dist-pkg/core/visual-cycles.d.ts +30 -0
  74. package/dist-pkg/core/visual-cycles.js +52 -0
  75. package/dist-pkg/core/visual-state.d.ts +13 -0
  76. package/dist-pkg/core/visual-state.js +6 -0
  77. package/dist-pkg/educational/activities-registry.d.ts +1 -1
  78. package/dist-pkg/educational/activities-registry.js +9 -8
  79. package/dist-pkg/educational/adaptive-engine.d.ts +11 -11
  80. package/dist-pkg/educational/adaptive-engine.js +17 -17
  81. package/dist-pkg/educational/segment-bar.d.ts +12 -12
  82. package/dist-pkg/educational/segment-bar.js +9 -9
  83. package/dist-pkg/i18n/en.js +324 -223
  84. package/dist-pkg/i18n/es.js +323 -223
  85. package/dist-pkg/i18n/pt.js +348 -226
  86. package/dist-pkg/input/default-bindings.d.ts +37 -38
  87. package/dist-pkg/input/default-bindings.js +127 -144
  88. package/dist-pkg/input/devices.d.ts +7 -7
  89. package/dist-pkg/input/devices.js +17 -16
  90. package/dist-pkg/input/edges.d.ts +23 -32
  91. package/dist-pkg/input/edges.js +23 -28
  92. package/dist-pkg/input/empathy-filter.d.ts +22 -0
  93. package/dist-pkg/input/empathy-filter.js +34 -0
  94. package/dist-pkg/input/face-map.d.ts +96 -0
  95. package/dist-pkg/input/face-map.js +255 -0
  96. package/dist-pkg/input/face-signals.d.ts +40 -0
  97. package/dist-pkg/input/face-signals.js +48 -0
  98. package/dist-pkg/input/gamepad.d.ts +195 -145
  99. package/dist-pkg/input/gamepad.js +383 -556
  100. package/dist-pkg/input/gaze-cycle.d.ts +64 -0
  101. package/dist-pkg/input/gaze-cycle.js +133 -0
  102. package/dist-pkg/input/gaze-relative.d.ts +68 -0
  103. package/dist-pkg/input/gaze-relative.js +142 -0
  104. package/dist-pkg/input/hand-map.d.ts +28 -0
  105. package/dist-pkg/input/hand-map.js +142 -0
  106. package/dist-pkg/input/input-cooldown.d.ts +17 -0
  107. package/dist-pkg/input/input-cooldown.js +39 -0
  108. package/dist-pkg/input/key-default.d.ts +30 -0
  109. package/dist-pkg/input/key-default.js +60 -0
  110. package/dist-pkg/input/keyboard-runtime.d.ts +15 -21
  111. package/dist-pkg/input/keyboard-runtime.js +23 -27
  112. package/dist-pkg/input/keyboard.d.ts +68 -63
  113. package/dist-pkg/input/keyboard.js +83 -105
  114. package/dist-pkg/input/keydown.d.ts +123 -137
  115. package/dist-pkg/input/keydown.js +284 -289
  116. package/dist-pkg/input/latch-edge.d.ts +33 -21
  117. package/dist-pkg/input/latch-edge.js +34 -35
  118. package/dist-pkg/input/latch-scope.d.ts +51 -50
  119. package/dist-pkg/input/latch-scope.js +61 -82
  120. package/dist-pkg/input/latch-store.d.ts +26 -30
  121. package/dist-pkg/input/latch-store.js +42 -50
  122. package/dist-pkg/input/latch-sync.d.ts +29 -29
  123. package/dist-pkg/input/latch-sync.js +25 -25
  124. package/dist-pkg/input/latch.d.ts +14 -17
  125. package/dist-pkg/input/latch.js +29 -37
  126. package/dist-pkg/input/pad-defaults.d.ts +13 -16
  127. package/dist-pkg/input/pad-defaults.js +21 -28
  128. package/dist-pkg/input/pad-reading.d.ts +76 -0
  129. package/dist-pkg/input/pad-reading.js +169 -0
  130. package/dist-pkg/input/pad-wizard.d.ts +99 -0
  131. package/dist-pkg/input/pad-wizard.js +256 -0
  132. package/dist-pkg/input/pointer-space.d.ts +19 -20
  133. package/dist-pkg/input/pointer-space.js +27 -32
  134. package/dist-pkg/input/pointer.d.ts +39 -40
  135. package/dist-pkg/input/pointer.js +42 -45
  136. package/dist-pkg/input/state.d.ts +101 -89
  137. package/dist-pkg/input/state.js +54 -134
  138. package/dist-pkg/input/switch-scan.d.ts +59 -0
  139. package/dist-pkg/input/switch-scan.js +67 -0
  140. package/dist-pkg/input/synthetic-source.d.ts +42 -0
  141. package/dist-pkg/input/synthetic-source.js +57 -0
  142. package/dist-pkg/input/touch-bindings.d.ts +127 -116
  143. package/dist-pkg/input/touch-bindings.js +158 -173
  144. package/dist-pkg/input/touch.d.ts +147 -72
  145. package/dist-pkg/input/touch.js +362 -126
  146. package/dist-pkg/input/transport-in-use.d.ts +107 -0
  147. package/dist-pkg/input/transport-in-use.js +130 -0
  148. package/dist-pkg/input/transports.d.ts +90 -117
  149. package/dist-pkg/input/transports.js +104 -106
  150. package/dist-pkg/input/virtual-controller.d.ts +82 -0
  151. package/dist-pkg/input/virtual-controller.js +69 -0
  152. package/dist-pkg/input/vocabulary-migration.d.ts +50 -49
  153. package/dist-pkg/input/vocabulary-migration.js +80 -79
  154. package/dist-pkg/input/voice-map.d.ts +59 -0
  155. package/dist-pkg/input/voice-map.js +123 -0
  156. package/dist-pkg/platform/audio-ambient.d.ts +13 -2
  157. package/dist-pkg/platform/audio-ambient.js +14 -14
  158. package/dist-pkg/platform/audio-earcons.d.ts +20 -19
  159. package/dist-pkg/platform/audio-earcons.js +51 -42
  160. package/dist-pkg/platform/audio-jingles.js +9 -10
  161. package/dist-pkg/platform/audio-mixer.d.ts +14 -2
  162. package/dist-pkg/platform/audio-mixer.js +31 -39
  163. package/dist-pkg/platform/audio-sonar.d.ts +49 -152
  164. package/dist-pkg/platform/audio-sonar.js +124 -302
  165. package/dist-pkg/platform/audio.d.ts +62 -19
  166. package/dist-pkg/platform/audio.js +193 -168
  167. package/dist-pkg/platform/choose-by-voice.d.ts +70 -0
  168. package/dist-pkg/platform/choose-by-voice.js +157 -0
  169. package/dist-pkg/platform/flash-sampler.d.ts +19 -0
  170. package/dist-pkg/platform/flash-sampler.js +79 -0
  171. package/dist-pkg/platform/font-library.d.ts +77 -0
  172. package/dist-pkg/platform/font-library.js +98 -0
  173. package/dist-pkg/platform/font-library.json +721 -0
  174. package/dist-pkg/platform/heavy-catalogue.d.ts +77 -0
  175. package/dist-pkg/platform/heavy-catalogue.js +256 -0
  176. package/dist-pkg/platform/heavy-mirror.d.ts +20 -0
  177. package/dist-pkg/platform/heavy-mirror.js +73 -0
  178. package/dist-pkg/platform/heavy.d.ts +136 -0
  179. package/dist-pkg/platform/heavy.js +324 -0
  180. package/dist-pkg/platform/interruptible-speech.d.ts +23 -16
  181. package/dist-pkg/platform/interruptible-speech.js +77 -54
  182. package/dist-pkg/platform/kokoro-port.d.ts +27 -0
  183. package/dist-pkg/platform/kokoro-port.js +50 -0
  184. package/dist-pkg/platform/kokoro-runtime.d.ts +20 -0
  185. package/dist-pkg/platform/kokoro-runtime.js +52 -0
  186. package/dist-pkg/platform/kokoro.d.ts +70 -0
  187. package/dist-pkg/platform/kokoro.js +133 -0
  188. package/dist-pkg/platform/listener-scope.d.ts +12 -0
  189. package/dist-pkg/platform/listener-scope.js +90 -0
  190. package/dist-pkg/platform/locale-host.d.ts +14 -0
  191. package/dist-pkg/platform/locale-host.js +45 -0
  192. package/dist-pkg/platform/microphone.d.ts +51 -0
  193. package/dist-pkg/platform/microphone.js +118 -0
  194. package/dist-pkg/platform/onnx-runtime.d.ts +34 -0
  195. package/dist-pkg/platform/onnx-runtime.js +42 -0
  196. package/dist-pkg/platform/reading-in-worker.d.ts +64 -0
  197. package/dist-pkg/platform/reading-in-worker.js +125 -0
  198. package/dist-pkg/platform/reading-model.d.ts +94 -0
  199. package/dist-pkg/platform/reading-model.js +272 -0
  200. package/dist-pkg/platform/reading-runtime.d.ts +24 -0
  201. package/dist-pkg/platform/reading-runtime.js +164 -0
  202. package/dist-pkg/platform/reading-worker.d.ts +41 -0
  203. package/dist-pkg/platform/reading-worker.js +58 -0
  204. package/dist-pkg/platform/reading.d.ts +80 -0
  205. package/dist-pkg/platform/reading.js +136 -0
  206. package/dist-pkg/platform/speech-recognition.d.ts +60 -0
  207. package/dist-pkg/platform/speech-recognition.js +104 -0
  208. package/dist-pkg/platform/speech.d.ts +23 -1
  209. package/dist-pkg/platform/speech.js +29 -18
  210. package/dist-pkg/platform/storage-keys.d.ts +96 -0
  211. package/dist-pkg/platform/storage-keys.js +106 -0
  212. package/dist-pkg/platform/storage.d.ts +46 -111
  213. package/dist-pkg/platform/storage.js +104 -169
  214. package/dist-pkg/platform/tts.d.ts +85 -39
  215. package/dist-pkg/platform/tts.js +447 -127
  216. package/dist-pkg/platform/vision-loop.d.ts +23 -0
  217. package/dist-pkg/platform/vision-loop.js +56 -0
  218. package/dist-pkg/platform/vision.d.ts +132 -0
  219. package/dist-pkg/platform/vision.js +117 -0
  220. package/dist-pkg/platform/voice-listener.d.ts +40 -0
  221. package/dist-pkg/platform/voice-listener.js +88 -0
  222. package/dist-pkg/platform/voice-plan.d.ts +15 -98
  223. package/dist-pkg/platform/voice-plan.js +56 -143
  224. package/dist-pkg/platform/vosk-runtime.d.ts +96 -0
  225. package/dist-pkg/platform/vosk-runtime.js +151 -0
  226. package/dist-pkg/platform/vosk-vocabulary.d.ts +6 -0
  227. package/dist-pkg/platform/vosk-vocabulary.js +178 -0
  228. package/dist-pkg/render/canvas.d.ts +13 -7
  229. package/dist-pkg/render/canvas.js +13 -13
  230. package/dist-pkg/render/crt.d.ts +48 -12
  231. package/dist-pkg/render/crt.js +78 -80
  232. package/dist-pkg/render/cvd-matrices.d.ts +22 -18
  233. package/dist-pkg/render/cvd-matrices.js +37 -38
  234. package/dist-pkg/render/hc-role-data.d.ts +7 -7
  235. package/dist-pkg/render/hc-role-data.js +14 -14
  236. package/dist-pkg/render/high-contrast.d.ts +67 -56
  237. package/dist-pkg/render/high-contrast.js +226 -217
  238. package/dist-pkg/render/low-vision-drawing.d.ts +5 -0
  239. package/dist-pkg/render/low-vision-drawing.js +46 -0
  240. package/dist-pkg/render/lq-filter.d.ts +32 -20
  241. package/dist-pkg/render/lq-filter.js +59 -65
  242. package/dist-pkg/render/port.d.ts +86 -88
  243. package/dist-pkg/render/port.js +17 -23
  244. package/dist-pkg/render/screen-pipeline.d.ts +42 -42
  245. package/dist-pkg/render/screen-pipeline.js +80 -87
  246. package/dist-pkg/render/sprite-fx.d.ts +2 -1
  247. package/dist-pkg/render/sprite-fx.js +16 -14
  248. package/dist-pkg/render/viewports.d.ts +20 -15
  249. package/dist-pkg/render/viewports.js +83 -114
  250. package/dist-pkg/render/viz-axes-labels.d.ts +47 -0
  251. package/dist-pkg/render/viz-axes-labels.js +88 -0
  252. package/dist-pkg/render/viz-axes.d.ts +89 -86
  253. package/dist-pkg/render/viz-axes.js +128 -116
  254. package/dist-pkg/render/viz-modes.d.ts +28 -29
  255. package/dist-pkg/render/viz-modes.js +40 -45
  256. package/dist-pkg/render/viz-refusal.d.ts +32 -0
  257. package/dist-pkg/render/viz-refusal.js +57 -0
  258. package/dist-pkg/render/viz-setters.d.ts +104 -66
  259. package/dist-pkg/render/viz-setters.js +182 -191
  260. package/dist-pkg/ui/aac-sets.d.ts +45 -0
  261. package/dist-pkg/ui/aac-sets.js +41 -0
  262. package/dist-pkg/ui/audio-choices.d.ts +68 -0
  263. package/dist-pkg/ui/audio-choices.js +98 -0
  264. package/dist-pkg/ui/audio-rows-that-apply.d.ts +12 -0
  265. package/dist-pkg/ui/audio-rows-that-apply.js +43 -0
  266. package/dist-pkg/ui/camera-control.d.ts +53 -0
  267. package/dist-pkg/ui/camera-control.js +48 -0
  268. package/dist-pkg/ui/changed-mark.d.ts +9 -8
  269. package/dist-pkg/ui/changed-mark.js +22 -53
  270. package/dist-pkg/ui/control-choices.d.ts +37 -0
  271. package/dist-pkg/ui/control-choices.js +84 -0
  272. package/dist-pkg/ui/debug-panel.d.ts +59 -43
  273. package/dist-pkg/ui/debug-panel.js +68 -69
  274. package/dist-pkg/ui/declared-words.d.ts +21 -0
  275. package/dist-pkg/ui/declared-words.js +66 -0
  276. package/dist-pkg/ui/dom.d.ts +10 -14
  277. package/dist-pkg/ui/dom.js +10 -47
  278. package/dist-pkg/ui/drawing-problems.d.ts +25 -0
  279. package/dist-pkg/ui/drawing-problems.js +94 -0
  280. package/dist-pkg/ui/eye-control.d.ts +36 -0
  281. package/dist-pkg/ui/eye-control.js +178 -0
  282. package/dist-pkg/ui/face-control.d.ts +25 -0
  283. package/dist-pkg/ui/face-control.js +183 -0
  284. package/dist-pkg/ui/focus-trap.d.ts +39 -49
  285. package/dist-pkg/ui/focus-trap.js +56 -65
  286. package/dist-pkg/ui/fonts.d.ts +157 -68
  287. package/dist-pkg/ui/fonts.js +305 -115
  288. package/dist-pkg/ui/footer-scroll-driver.d.ts +14 -0
  289. package/dist-pkg/ui/footer-scroll-driver.js +201 -0
  290. package/dist-pkg/ui/footer-scroll.d.ts +38 -0
  291. package/dist-pkg/ui/footer-scroll.js +55 -0
  292. package/dist-pkg/ui/game-options.d.ts +47 -0
  293. package/dist-pkg/ui/game-options.js +158 -0
  294. package/dist-pkg/ui/gaze-overlay.d.ts +64 -0
  295. package/dist-pkg/ui/gaze-overlay.js +165 -0
  296. package/dist-pkg/ui/hand-control.d.ts +25 -0
  297. package/dist-pkg/ui/hand-control.js +143 -0
  298. package/dist-pkg/ui/help-panel.d.ts +87 -0
  299. package/dist-pkg/ui/help-panel.js +242 -0
  300. package/dist-pkg/ui/hud-bands.d.ts +62 -0
  301. package/dist-pkg/ui/hud-bands.js +146 -0
  302. package/dist-pkg/ui/hud-row.d.ts +34 -0
  303. package/dist-pkg/ui/hud-row.js +80 -0
  304. package/dist-pkg/ui/hud.d.ts +73 -73
  305. package/dist-pkg/ui/hud.js +94 -98
  306. package/dist-pkg/ui/item-announcement.d.ts +13 -12
  307. package/dist-pkg/ui/item-announcement.js +25 -26
  308. package/dist-pkg/ui/layout.d.ts +103 -28
  309. package/dist-pkg/ui/layout.js +117 -99
  310. package/dist-pkg/ui/libras-avatar-clip.d.ts +30 -0
  311. package/dist-pkg/ui/libras-avatar-clip.js +86 -0
  312. package/dist-pkg/ui/libras-avatar-load.d.ts +52 -0
  313. package/dist-pkg/ui/libras-avatar-load.js +134 -0
  314. package/dist-pkg/ui/libras-avatar-plan.d.ts +102 -0
  315. package/dist-pkg/ui/libras-avatar-plan.js +211 -0
  316. package/dist-pkg/ui/libras-avatar-player.d.ts +28 -0
  317. package/dist-pkg/ui/libras-avatar-player.js +226 -0
  318. package/dist-pkg/ui/libras-avatar-stage.d.ts +37 -0
  319. package/dist-pkg/ui/libras-avatar-stage.js +208 -0
  320. package/dist-pkg/ui/libras-glosses.d.ts +28 -0
  321. package/dist-pkg/ui/libras-glosses.js +161 -0
  322. package/dist-pkg/ui/locale-flags.d.ts +16 -0
  323. package/dist-pkg/ui/locale-flags.js +30 -0
  324. package/dist-pkg/ui/loop-crash.d.ts +17 -17
  325. package/dist-pkg/ui/loop-crash.js +33 -62
  326. package/dist-pkg/ui/menu-intent.d.ts +61 -0
  327. package/dist-pkg/ui/menu-intent.js +88 -0
  328. package/dist-pkg/ui/menu-items.d.ts +20 -0
  329. package/dist-pkg/ui/menu-items.js +51 -0
  330. package/dist-pkg/ui/menu-nav.d.ts +98 -92
  331. package/dist-pkg/ui/menu-nav.js +398 -286
  332. package/dist-pkg/ui/mobility-choices.d.ts +26 -0
  333. package/dist-pkg/ui/mobility-choices.js +41 -0
  334. package/dist-pkg/ui/motion-choices.d.ts +31 -0
  335. package/dist-pkg/ui/motion-choices.js +68 -0
  336. package/dist-pkg/ui/motion-scene.d.ts +24 -19
  337. package/dist-pkg/ui/motion-scene.js +29 -61
  338. package/dist-pkg/ui/mount-panel.d.ts +81 -0
  339. package/dist-pkg/ui/mount-panel.js +61 -0
  340. package/dist-pkg/ui/panel-shell.d.ts +65 -31
  341. package/dist-pkg/ui/panel-shell.js +92 -66
  342. package/dist-pkg/ui/panel-widgets.d.ts +150 -0
  343. package/dist-pkg/ui/panel-widgets.js +294 -0
  344. package/dist-pkg/ui/pause-buttons.d.ts +35 -0
  345. package/dist-pkg/ui/pause-buttons.js +66 -0
  346. package/dist-pkg/ui/pause-icons.d.ts +261 -347
  347. package/dist-pkg/ui/pause-icons.js +671 -601
  348. package/dist-pkg/ui/pause-markup.d.ts +86 -0
  349. package/dist-pkg/ui/pause-markup.js +125 -0
  350. package/dist-pkg/ui/reach-notice.d.ts +22 -23
  351. package/dist-pkg/ui/reach-notice.js +55 -58
  352. package/dist-pkg/ui/scan-overlay.d.ts +23 -0
  353. package/dist-pkg/ui/scan-overlay.js +50 -0
  354. package/dist-pkg/ui/screen-text.d.ts +25 -0
  355. package/dist-pkg/ui/screen-text.js +152 -0
  356. package/dist-pkg/ui/session-clock.d.ts +32 -0
  357. package/dist-pkg/ui/session-clock.js +67 -0
  358. package/dist-pkg/ui/settings-aac.d.ts +70 -0
  359. package/dist-pkg/ui/settings-aac.js +208 -0
  360. package/dist-pkg/ui/settings-audio.d.ts +97 -114
  361. package/dist-pkg/ui/settings-audio.js +493 -425
  362. package/dist-pkg/ui/settings-controls.d.ts +53 -96
  363. package/dist-pkg/ui/settings-controls.js +143 -215
  364. package/dist-pkg/ui/settings-empathy.d.ts +39 -30
  365. package/dist-pkg/ui/settings-empathy.js +53 -60
  366. package/dist-pkg/ui/settings-mobility.d.ts +192 -0
  367. package/dist-pkg/ui/settings-mobility.js +332 -0
  368. package/dist-pkg/ui/settings-motion.d.ts +91 -70
  369. package/dist-pkg/ui/settings-motion.js +287 -198
  370. package/dist-pkg/ui/settings-panel.d.ts +58 -45
  371. package/dist-pkg/ui/settings-panel.js +189 -94
  372. package/dist-pkg/ui/settings-typo.d.ts +29 -78
  373. package/dist-pkg/ui/settings-typo.js +158 -129
  374. package/dist-pkg/ui/settings-visual.d.ts +29 -87
  375. package/dist-pkg/ui/settings-visual.js +240 -193
  376. package/dist-pkg/ui/shell.d.ts +112 -135
  377. package/dist-pkg/ui/shell.js +148 -217
  378. package/dist-pkg/ui/simulation-list.d.ts +27 -0
  379. package/dist-pkg/ui/simulation-list.js +76 -0
  380. package/dist-pkg/ui/simulation-over-the-world.d.ts +23 -0
  381. package/dist-pkg/ui/simulation-over-the-world.js +131 -0
  382. package/dist-pkg/ui/simulation-refusal.d.ts +5 -31
  383. package/dist-pkg/ui/simulation-refusal.js +8 -56
  384. package/dist-pkg/ui/switchable-control.d.ts +11 -0
  385. package/dist-pkg/ui/switchable-control.js +19 -0
  386. package/dist-pkg/ui/title.d.ts +3 -12
  387. package/dist-pkg/ui/title.js +49 -18
  388. package/dist-pkg/ui/top-band.d.ts +26 -0
  389. package/dist-pkg/ui/top-band.js +96 -0
  390. package/dist-pkg/ui/typo-choices.d.ts +71 -0
  391. package/dist-pkg/ui/typo-choices.js +85 -0
  392. package/dist-pkg/ui/visual-axes-panel.d.ts +11 -47
  393. package/dist-pkg/ui/visual-axes-panel.js +12 -94
  394. package/dist-pkg/ui/visual-choices.d.ts +77 -0
  395. package/dist-pkg/ui/visual-choices.js +107 -0
  396. package/dist-pkg/ui/vlibras.d.ts +77 -10
  397. package/dist-pkg/ui/vlibras.js +130 -81
  398. package/dist-pkg/ui/voice-control.d.ts +68 -0
  399. package/dist-pkg/ui/voice-control.js +276 -0
  400. package/dist-pkg/ui/voice-settings.d.ts +95 -0
  401. package/dist-pkg/ui/voice-settings.js +352 -0
  402. package/dist-pkg/ui/where-the-child-is.d.ts +31 -0
  403. package/dist-pkg/ui/where-the-child-is.js +70 -0
  404. package/docs/CREDITS.md +300 -44
  405. package/docs/LICENSES.md +189 -145
  406. package/package.json +51 -27
  407. package/scripts/check-cartridge.mjs +78 -0
  408. package/scripts/game-build.d.mts +25 -0
  409. package/scripts/game-build.mjs +144 -0
  410. package/scripts/heavy-into-the-delivery.mjs +388 -0
  411. package/scripts/libras-avatar.json +680 -0
  412. package/scripts/libras-avatar.mjs +246 -0
  413. package/scripts/libras-glosses/gloss.py +95 -0
  414. package/scripts/libras-glosses/pyproject.toml +32 -0
  415. package/scripts/libras-glosses/uv.lock +781 -0
  416. package/scripts/libras-glosses.mjs +351 -0
  417. package/scripts/licences/Apache-2.0.txt +176 -0
  418. package/scripts/licences/GPL-3.0.txt +674 -0
  419. package/scripts/licences/MIT.txt +17 -0
  420. package/scripts/licences/OFL-1.1.txt +86 -0
  421. package/scripts/licences/UFL-1.0.txt +96 -0
  422. package/scripts/licences/fonts.mjs +389 -0
  423. package/scripts/licences/third-party.mjs +301 -0
  424. package/scripts/licences/vosk-browser.NOTICE.txt +268 -0
  425. package/app/public/vendor/fonts/comicneue-400.woff2 +0 -0
  426. package/app/public/vendor/fonts/comicneue-700.woff2 +0 -0
  427. package/app/public/vendor/fonts/fondamento-400-ext.woff2 +0 -0
  428. package/app/public/vendor/fonts/fondamento-400.woff2 +0 -0
  429. package/app/public/vendor/fonts/inter-400.woff2 +0 -0
  430. package/app/public/vendor/fonts/inter-700.woff2 +0 -0
  431. package/app/public/vendor/fonts/lato-400.woff2 +0 -0
  432. package/app/public/vendor/fonts/lato-700.woff2 +0 -0
  433. package/app/public/vendor/fonts/literata-400.woff2 +0 -0
  434. package/app/public/vendor/fonts/literata-700.woff2 +0 -0
  435. package/app/public/vendor/fonts/newsreader-400.woff2 +0 -0
  436. package/app/public/vendor/fonts/newsreader-700.woff2 +0 -0
  437. package/app/public/vendor/fonts/opendyslexic-400.woff2 +0 -0
  438. package/app/public/vendor/fonts/opensans-400.woff2 +0 -0
  439. package/app/public/vendor/fonts/opensans-700.woff2 +0 -0
  440. package/app/public/vendor/fonts/pinyon-400.woff2 +0 -0
  441. package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
  442. package/app/public/vendor/fonts/quattro-400.woff2 +0 -0
  443. package/app/public/vendor/fonts/quattro-700.woff2 +0 -0
  444. package/app/public/vendor/fonts/sourcesans-400.woff2 +0 -0
  445. package/app/public/vendor/fonts/sourcesans-700.woff2 +0 -0
  446. package/app/public/vendor/fonts/sourceserif-400.woff2 +0 -0
  447. package/app/public/vendor/fonts/sourceserif-700.woff2 +0 -0
  448. package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
  449. package/dist-pkg/core/anel.d.ts +0 -17
  450. package/dist-pkg/core/anel.js +0 -31
  451. package/dist-pkg/core/collision.d.ts +0 -21
  452. package/dist-pkg/core/collision.js +0 -62
  453. package/dist-pkg/core/layers.d.ts +0 -63
  454. package/dist-pkg/core/layers.js +0 -95
  455. package/dist-pkg/core/letter-grid.d.ts +0 -55
  456. package/dist-pkg/core/letter-grid.js +0 -116
  457. package/dist-pkg/core/password.d.ts +0 -34
  458. package/dist-pkg/core/password.js +0 -205
  459. package/dist-pkg/core/rotulo-acessivel.d.ts +0 -17
  460. package/dist-pkg/core/rotulo-acessivel.js +0 -41
  461. package/dist-pkg/core/run-state.d.ts +0 -115
  462. package/dist-pkg/core/run-state.js +0 -40
  463. package/dist-pkg/core/tiles.d.ts +0 -12
  464. package/dist-pkg/core/tiles.js +0 -61
  465. package/dist-pkg/core/world.d.ts +0 -4
  466. package/dist-pkg/core/world.js +0 -58
  467. package/dist-pkg/input/origem-sintetica.d.ts +0 -44
  468. package/dist-pkg/input/origem-sintetica.js +0 -60
  469. package/dist-pkg/input/transporte-em-uso.d.ts +0 -101
  470. package/dist-pkg/input/transporte-em-uso.js +0 -130
  471. package/dist-pkg/platform/audio-nav.d.ts +0 -51
  472. package/dist-pkg/platform/audio-nav.js +0 -99
  473. package/dist-pkg/platform/guide-intensity.d.ts +0 -40
  474. package/dist-pkg/platform/guide-intensity.js +0 -73
  475. package/dist-pkg/platform/pesados-catalogo.d.ts +0 -14
  476. package/dist-pkg/platform/pesados-catalogo.js +0 -177
  477. package/dist-pkg/platform/pesados.d.ts +0 -31
  478. package/dist-pkg/platform/pesados.js +0 -75
  479. package/dist-pkg/platform/vozes-prontas.d.ts +0 -28
  480. package/dist-pkg/platform/vozes-prontas.js +0 -61
  481. package/dist-pkg/render/camera.d.ts +0 -58
  482. package/dist-pkg/render/camera.js +0 -105
  483. package/dist-pkg/render/cenario-data.d.ts +0 -127
  484. package/dist-pkg/render/cenario-data.js +0 -145
  485. package/dist-pkg/render/city-tex.d.ts +0 -63
  486. package/dist-pkg/render/city-tex.js +0 -228
  487. package/dist-pkg/render/city-tiles.d.ts +0 -11
  488. package/dist-pkg/render/city-tiles.js +0 -140
  489. package/dist-pkg/render/draw.d.ts +0 -149
  490. package/dist-pkg/render/draw.js +0 -231
  491. package/dist-pkg/render/fx.d.ts +0 -63
  492. package/dist-pkg/render/fx.js +0 -123
  493. package/dist-pkg/render/minimap.d.ts +0 -11
  494. package/dist-pkg/render/minimap.js +0 -94
  495. package/dist-pkg/render/parallax.d.ts +0 -85
  496. package/dist-pkg/render/parallax.js +0 -158
  497. package/dist-pkg/render/player-anim.d.ts +0 -71
  498. package/dist-pkg/render/player-anim.js +0 -152
  499. package/dist-pkg/render/recycling-tex.d.ts +0 -48
  500. package/dist-pkg/render/recycling-tex.js +0 -164
  501. package/dist-pkg/render/scene-city.d.ts +0 -77
  502. package/dist-pkg/render/scene-city.js +0 -181
  503. package/dist-pkg/render/scene-parallax.d.ts +0 -72
  504. package/dist-pkg/render/scene-parallax.js +0 -426
  505. package/dist-pkg/render/scene-sky.d.ts +0 -126
  506. package/dist-pkg/render/scene-sky.js +0 -295
  507. package/dist-pkg/render/set-cenario.d.ts +0 -54
  508. package/dist-pkg/render/set-cenario.js +0 -103
  509. package/dist-pkg/render/textures.d.ts +0 -75
  510. package/dist-pkg/render/textures.js +0 -240
  511. package/dist-pkg/render/title-scene.d.ts +0 -46
  512. package/dist-pkg/render/title-scene.js +0 -75
  513. package/dist-pkg/render/weather.d.ts +0 -96
  514. package/dist-pkg/render/weather.js +0 -171
  515. package/dist-pkg/render/wheelchair-sprites.d.ts +0 -32
  516. package/dist-pkg/render/wheelchair-sprites.js +0 -69
  517. package/dist-pkg/render/world-tex.d.ts +0 -18
  518. package/dist-pkg/render/world-tex.js +0 -87
  519. package/dist-pkg/ui/activities-menu.d.ts +0 -258
  520. package/dist-pkg/ui/activities-menu.js +0 -648
  521. package/dist-pkg/ui/caa-sets.d.ts +0 -39
  522. package/dist-pkg/ui/caa-sets.js +0 -65
  523. package/dist-pkg/ui/latch-refusal.d.ts +0 -32
  524. package/dist-pkg/ui/latch-refusal.js +0 -60
  525. package/dist-pkg/ui/map-hub.d.ts +0 -56
  526. package/dist-pkg/ui/map-hub.js +0 -138
  527. package/dist-pkg/ui/settings-caa.d.ts +0 -45
  528. package/dist-pkg/ui/settings-caa.js +0 -137
  529. package/dist-pkg/ui/settings-motor.d.ts +0 -175
  530. package/dist-pkg/ui/settings-motor.js +0 -342
  531. package/dist-pkg/ui/webcam.d.ts +0 -7
  532. package/dist-pkg/ui/webcam.js +0 -93
@@ -0,0 +1,165 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // ui/gaze-overlay — HOW THE EYE CONTROL SHOWS ITSELF OVER THE GAME (ADR-0213 §6; issue #194).
3
+ //
4
+ // Five regions: four at the edges the eyes travel to, and the middle, where the child looks to measure the rest. A region says something
5
+ // only while the gaze is in it, so the text is always where the eyes already are:
6
+ // · a look that PREPARES (it did not come from the opposite side) says where to go now — up «go down!», right «go left!», and so on;
7
+ // · a look that came from the opposite side walks its cycle: «Loading...» (settling, and the cancel step) → the direction's Lucide arrow →
8
+ // the face button, as the Xbox letter and the PlayStation shape each inside a circle → the shoulder inside a square → round again;
9
+ // · the middle asks to be looked at, with the seconds left, while the rest is being measured.
10
+ // Text is 16 px on a 720×360 game region and grows with it. The regions are outlined, never hatched (ADR-0215: the 📷's eyes position);
11
+ // the eyes and brows are the landmark lines only — never the camera's picture (ADR-0212) — mirrored, so they move the way the child moves.
12
+ // What a region shows is decided by `whatRegionShows`, plain data; `drawGazeOverlay` only follows it. Places and colours: interface-log.
13
+ import { GAZE_GROUPS, CANCEL } from '../input/gaze-cycle.js';
14
+ /** Centre x, centre y, width, height — fractions of the game region. */
15
+ export const GAZE_REGIONS = {
16
+ up: [0.5, 0.1, 0.26, 0.18], right: [0.87, 0.5, 0.24, 0.24], down: [0.5, 0.9, 0.26, 0.18], left: [0.13, 0.5, 0.24, 0.24],
17
+ middle: [0.5, 0.5, 0.24, 0.26],
18
+ };
19
+ /** Each zone's face button: the Xbox letter and the PlayStation shape of the button on that side (ADR-0213 erratum: north is «Y»). */
20
+ export const FACE_BUTTON = {
21
+ up: ['Y', 'triangle'], right: ['B', 'circle'], down: ['A', 'cross'], left: ['X', 'square'],
22
+ };
23
+ export const SHOULDER = { up: 'R1', right: 'R2', down: 'L2', left: 'L1' };
24
+ /**
25
+ * Lucide `arrow-up`, `arrow-right`, `arrow-down`, `arrow-left`, 24×24 path data. Lucide's LICENSE lists these four as derived from Feather,
26
+ * so the notice they carry is Feather's MIT (Cole Bemis), not Lucide's ISC — credited in docs/CREDITS.md.
27
+ */
28
+ export const LUCIDE_ARROWS = {
29
+ up: ['m5 12 7-7 7 7', 'M12 19V5'], right: ['M5 12h14', 'm12 5 7 7-7 7'], down: ['M12 5v14', 'm19 12-7 7-7-7'], left: ['m12 19-7-7 7-7', 'M19 12H5'],
30
+ };
31
+ const NOTHING = { kind: 'nothing' };
32
+ /**
33
+ * What a preview shows, by its place in the zone's group (`GAZE_GROUPS`): the direction, the face button, the shoulder. The
34
+ * ORDER is the group's, so a place the table has no row for — an item from another zone's group — shows nothing.
35
+ */
36
+ const PREVIEW_SHOWS = [
37
+ () => ({ kind: 'arrow' }),
38
+ () => ({ kind: 'button' }),
39
+ (region) => ({ kind: 'shoulder', label: SHOULDER[region] }),
40
+ ];
41
+ export function whatRegionShows(region, v) {
42
+ if (region === 'middle')
43
+ return v.restReady ? { kind: 'nothing' } : { kind: 'text', key: 'gaze.lookHere' };
44
+ if (region !== v.zone)
45
+ return { kind: 'nothing' };
46
+ if (!v.armed)
47
+ return v.preparing ? { kind: 'text', key: `gaze.prepare.${region}` } : { kind: 'nothing' };
48
+ if (!v.preview || v.preview.item === CANCEL)
49
+ return { kind: 'text', key: 'gaze.loading' };
50
+ const i = GAZE_GROUPS[region].indexOf(v.preview.item);
51
+ return PREVIEW_SHOWS[i]?.(region) ?? NOTHING;
52
+ }
53
+ /** 16 px on a 720×360 game region, in proportion to it, bounded by the tighter side. */
54
+ export const gazeFontPx = (width, height) => 16 * Math.min(width / 720, height / 360);
55
+ const FAINT = 'rgba(234,242,248,0.35)', PREPARE = '#ffd23f', ARMED = '#3ddc84', LINES = '#ffffff';
56
+ function shape(ctx, s, x, y, r) {
57
+ ctx.beginPath();
58
+ if (s === 'triangle') {
59
+ ctx.moveTo(x, y - r);
60
+ ctx.lineTo(x + r * 0.87, y + r * 0.5);
61
+ ctx.lineTo(x - r * 0.87, y + r * 0.5);
62
+ ctx.closePath();
63
+ }
64
+ else if (s === 'circle')
65
+ ctx.arc(x, y, r, 0, 2 * Math.PI);
66
+ else if (s === 'square')
67
+ ctx.rect(x - r * 0.75, y - r * 0.75, r * 1.5, r * 1.5);
68
+ else {
69
+ ctx.moveTo(x - r * 0.7, y - r * 0.7);
70
+ ctx.lineTo(x + r * 0.7, y + r * 0.7);
71
+ ctx.moveTo(x + r * 0.7, y - r * 0.7);
72
+ ctx.lineTo(x - r * 0.7, y + r * 0.7);
73
+ }
74
+ ctx.stroke();
75
+ }
76
+ /** Draws the five regions and the face's lines. is the root's translator (ADR-0232 D3): the regions' words are its keys. */
77
+ export function drawGazeOverlay(t, ctx, width, height, view, { face = null } = {}) {
78
+ ctx.clearRect(0, 0, width, height);
79
+ const f = gazeFontPx(width, height);
80
+ ctx.textAlign = 'center';
81
+ ctx.textBaseline = 'middle';
82
+ ctx.lineCap = 'round';
83
+ ctx.lineJoin = 'round';
84
+ for (const region of Object.keys(GAZE_REGIONS))
85
+ drawRegion(ctx, region, width, height, f, view, t);
86
+ if (face)
87
+ drawFaceLines(ctx, face, width, height, f);
88
+ }
89
+ /** One region: its outline — faint when it shows nothing, green when armed (never the middle), yellow otherwise — and what it shows. */
90
+ function drawRegion(ctx, region, width, height, f, view, say) {
91
+ const [fx, fy, fw, fh] = GAZE_REGIONS[region];
92
+ const w = fw * width, h = fh * height, cx = fx * width, cy = fy * height;
93
+ const shows = whatRegionShows(region, view);
94
+ const active = shows.kind !== 'nothing';
95
+ const colour = !active ? FAINT : view.armed && region !== 'middle' ? ARMED : PREPARE;
96
+ ctx.strokeStyle = colour;
97
+ ctx.lineWidth = active ? 3 : 1;
98
+ ctx.strokeRect(cx - w / 2, cy - h / 2, w, h);
99
+ ctx.fillStyle = colour;
100
+ ctx.lineWidth = Math.max(1.5, f / 8);
101
+ ctx.shadowColor = '#000';
102
+ ctx.shadowBlur = 4;
103
+ DRAW_SHOWS[shows.kind](ctx, { region, cx, cy, f }, shows, view, say);
104
+ ctx.shadowBlur = 0;
105
+ }
106
+ /** How each thing a region can show is drawn — one row per kind, so a new kind is a new row and a missing one does not compile. */
107
+ const DRAW_SHOWS = {
108
+ nothing: () => { },
109
+ // only the middle counts down, and its words step up to make room for the seconds
110
+ text: (ctx, { region, cx, cy, f }, shows, view, say) => {
111
+ const counting = region === 'middle' && view.restLeftMs !== undefined;
112
+ ctx.font = `bold ${f}px system-ui`;
113
+ ctx.fillText(say(shows.key), cx, counting ? cy - f * 0.7 : cy);
114
+ if (counting) {
115
+ ctx.font = `${f * 0.85}px system-ui`;
116
+ ctx.fillText(`${((view.restLeftMs ?? 0) / 1000).toFixed(1)} s`, cx, cy + f * 0.8);
117
+ }
118
+ },
119
+ arrow: (ctx, { region, cx, cy, f }) => {
120
+ const side = f * 2.6;
121
+ ctx.save();
122
+ ctx.translate(cx - side / 2, cy - side / 2);
123
+ ctx.scale(side / 24, side / 24);
124
+ ctx.lineWidth = 2;
125
+ for (const d of LUCIDE_ARROWS[region])
126
+ ctx.stroke(new Path2D(d));
127
+ ctx.restore();
128
+ },
129
+ // the Xbox letter in one circle, the PlayStation shape in the other
130
+ button: (ctx, { region, cx, cy, f }) => {
131
+ const r = f * 1.15, dx = r * 1.35, [letter, s] = FACE_BUTTON[region];
132
+ ctx.beginPath();
133
+ ctx.arc(cx - dx, cy, r, 0, 2 * Math.PI);
134
+ ctx.stroke();
135
+ ctx.font = `bold ${f * 1.1}px system-ui`;
136
+ ctx.fillText(letter, cx - dx, cy + f * 0.05);
137
+ ctx.beginPath();
138
+ ctx.arc(cx + dx, cy, r, 0, 2 * Math.PI);
139
+ ctx.stroke();
140
+ shape(ctx, s, cx + dx, cy, r * 0.55);
141
+ },
142
+ shoulder: (ctx, { cx, cy, f }, shows) => {
143
+ const side = f * 2.4;
144
+ ctx.strokeRect(cx - side / 2, cy - side / 2, side, side);
145
+ ctx.font = `bold ${f * 1.05}px system-ui`;
146
+ ctx.fillText(shows.label, cx, cy + f * 0.05);
147
+ },
148
+ };
149
+ /** The eyes and brows from the landmarks, mirrored so they move the way the child moves; a line naming a missing landmark is skipped. */
150
+ function drawFaceLines(ctx, face, width, height, f) {
151
+ ctx.strokeStyle = LINES;
152
+ ctx.lineWidth = Math.max(2, f / 8);
153
+ ctx.shadowColor = '#000';
154
+ ctx.shadowBlur = 3;
155
+ for (const { start, end } of [...face.lines.eyes, ...face.lines.brows]) {
156
+ const a = face.landmarks[start], b = face.landmarks[end];
157
+ if (!a || !b)
158
+ continue;
159
+ ctx.beginPath();
160
+ ctx.moveTo((1 - a.x) * width, a.y * height);
161
+ ctx.lineTo((1 - b.x) * width, b.y * height);
162
+ ctx.stroke();
163
+ }
164
+ ctx.shadowBlur = 0;
165
+ }
@@ -0,0 +1,25 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { Translate } from '../core/i18n.js';
3
+ import type { VirtualController } from '../input/virtual-controller.js';
4
+ import { type HandTrackerLoad, type VisionDeps } from '../platform/vision.js';
5
+ import { type VisionLoopDeps } from '../platform/vision-loop.js';
6
+ import type { CameraFeed } from './eye-control.js';
7
+ import type { SwitchableControl } from './switchable-control.js';
8
+ export interface HandControlDeps {
9
+ /** Translates in the page's language — the root's translator (ADR-0232 D3). REQUIRED: text built from nowhere is a raw key. */
10
+ t: Translate;
11
+ readonly doc: Document;
12
+ readonly region: HTMLElement;
13
+ readonly base: string;
14
+ /** Whether a file is in the checked cache, handed to the tracker's loader (ADR-0232 D4: the root lends the cache). */
15
+ readonly hasFile: VisionDeps['hasFile'];
16
+ readonly loop: VisionLoopDeps;
17
+ readonly controller: VirtualController;
18
+ readonly say: (text: string) => void;
19
+ readonly alert: (text: string) => void;
20
+ readonly report: (line: string) => void;
21
+ readonly turnOff: () => void;
22
+ readonly openFeed: () => Promise<CameraFeed>;
23
+ readonly loadTracker?: (deps: VisionDeps) => Promise<HandTrackerLoad>;
24
+ }
25
+ export declare function createHandControl(d: HandControlDeps): SwitchableControl;
@@ -0,0 +1,143 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // ui/hand-control — PLAYING WITH HAND GESTURES, PUT TOGETHER (ADR-0210, ADR-0206, ADR-0212 §2; issue #191).
3
+ //
4
+ // The camera and MediaPipe's Gesture Recognizer (platform/vision), the frame loop, the Dev's hands map (input/hand-map), the presses on the
5
+ // virtual controller with the source `gestos` (input/virtual-controller, ADR-0111), and the hands' lines over the game. The 📷 at its
6
+ // hands position drives it (ADR-0215): it reads the hand and draws its bones — never the camera's picture; any other position lets go.
7
+ // · No rest to measure: a gesture is a shape, not a movement from a rest, and presses once seen for 300 ms.
8
+ // · A frame with no hand lets go of every held action.
9
+ // · What cannot start is said, written once in `problems`, and puts the 📷 back to off.
10
+ import { gesturesSeen, createHandMapReader } from '../input/hand-map.js';
11
+ import { loadHandTracker } from '../platform/vision.js';
12
+ import { createVisionLoop } from '../platform/vision-loop.js';
13
+ import { gazeFontPx } from './gaze-overlay.js';
14
+ import { startCameraReader } from './camera-control.js';
15
+ // 📌 THE SHAPE IS THE FAMILY'S (ADR-0221 step 7f): what this module returns is a SwitchableControl, not a fourth copy of it.
16
+ /** The hand's bones, mirrored so they move the way the child moves; a bone to a landmark the hand does not have is skipped. */
17
+ function drawBones(g, w, h, bones, hand) {
18
+ g.strokeStyle = '#ffffff';
19
+ g.lineWidth = Math.max(2, gazeFontPx(w, h) / 6);
20
+ g.shadowColor = '#000';
21
+ g.shadowBlur = 3;
22
+ for (const { start, end } of bones) {
23
+ const a = hand[start], b = hand[end];
24
+ if (!a || !b)
25
+ continue;
26
+ g.beginPath();
27
+ g.moveTo((1 - a.x) * w, a.y * h);
28
+ g.lineTo((1 - b.x) * w, b.y * h);
29
+ g.stroke();
30
+ }
31
+ g.shadowBlur = 0;
32
+ }
33
+ export function createHandControl(d) {
34
+ const { t } = d;
35
+ const loadTracker = d.loadTracker ?? loadHandTracker;
36
+ const said = new Set();
37
+ const once = (kind, line, spoken) => { if (!said.has(kind)) {
38
+ said.add(kind);
39
+ d.report(line);
40
+ } d.alert(spoken); };
41
+ let on = false, running = false, starting = null;
42
+ const current = () => on;
43
+ let tracker = null, feed = null, canvas = null;
44
+ let loop = null;
45
+ let read = createHandMapReader();
46
+ const held = new Set();
47
+ const releaseAll = () => { for (const a of held)
48
+ d.controller.release(a, 'gestos'); held.clear(); };
49
+ const frame = (ms) => {
50
+ if (!tracker || !feed || !canvas || !feed.ready())
51
+ return;
52
+ const det = tracker.detect(feed.frame, ms);
53
+ const hand = det?.landmarks?.[0];
54
+ const w = canvas.width = d.region.clientWidth, h = canvas.height = d.region.clientHeight;
55
+ const g = canvas.getContext('2d'); // setting the width above already cleared the layer
56
+ // a hand that leaves starts the reading over: one that flickers out for a frame must be held again when it comes back
57
+ if (!hand) {
58
+ releaseAll();
59
+ read = createHandMapReader();
60
+ return;
61
+ }
62
+ // only the recognizer's TOP answer is a gesture; its «None» is a name the map does not know, and reads nothing
63
+ const names = (det?.gestures?.[0] ?? []).slice(0, 1).map((c) => c.categoryName);
64
+ holdExactly(new Set(read(ms, gesturesSeen(hand, names)).held));
65
+ drawBones(g, w, h, tracker.handLines, hand);
66
+ };
67
+ /** Hold exactly what the reading holds now: let go of what left it, press what joined it — once. */
68
+ const holdExactly = (now) => {
69
+ for (const a of held)
70
+ if (!now.has(a)) {
71
+ d.controller.release(a, 'gestos');
72
+ held.delete(a);
73
+ }
74
+ for (const a of now)
75
+ if (!held.has(a)) {
76
+ d.controller.press(a, 'gestos');
77
+ held.add(a);
78
+ }
79
+ };
80
+ const health = (hh, fps) => {
81
+ if (hh === 'stalled')
82
+ once('stalled', 'gesture control: a second without a camera frame — meanwhile the child\'s gestures command nothing; keep the tab in front and the camera free', t('sr.hands.stopped'));
83
+ if (hh === 'slow')
84
+ once('slow', `gesture control: ${fps} frames a second, under 8 — gestures are missed and the child may not be answered; close other heavy tabs or use a lighter device`, t('sr.hands.slow'));
85
+ };
86
+ const stop = () => {
87
+ loop?.stop();
88
+ loop = null;
89
+ releaseAll();
90
+ feed?.close();
91
+ feed = null;
92
+ tracker?.close();
93
+ tracker = null;
94
+ canvas?.remove();
95
+ canvas = null;
96
+ running = false;
97
+ };
98
+ /** Nothing started: said, written once, and the 📷 back to off — unless it already moved on, and then nobody is waiting (as the 👄). */
99
+ const failed = (kind, line, spoken) => {
100
+ if (!current())
101
+ return;
102
+ once(kind, line, spoken);
103
+ d.turnOff();
104
+ };
105
+ const start = async () => {
106
+ const opened = await startCameraReader({
107
+ subject: 'gesture control', loses: 'play with gestures',
108
+ spoken: { files: () => t('sr.hands.needsInternet'), runtime: () => t('sr.hands.failed'), camera: () => t('sr.hands.noCamera') },
109
+ }, { load: () => loadTracker({ base: d.base, hasFile: d.hasFile }), openFeed: d.openFeed, failed });
110
+ if (!opened)
111
+ return;
112
+ tracker = opened.tracker;
113
+ feed = opened.feed;
114
+ canvas = d.doc.createElement('canvas');
115
+ canvas.className = 'hand-overlay';
116
+ canvas.setAttribute('aria-hidden', 'true');
117
+ Object.assign(canvas.style, { position: 'absolute', inset: '0', width: '100%', height: '100%', pointerEvents: 'none', zIndex: '5' });
118
+ d.region.appendChild(canvas);
119
+ read = createHandMapReader();
120
+ loop = createVisionLoop(d.loop, { onFrame: frame, onError: (e) => once('frame', `gesture control: a frame failed (${String(e)}) — the loop goes on, but a repeated failure means the child is not being read`, t('sr.hands.stopped')), onHealth: health });
121
+ running = true;
122
+ d.say(t('sr.hands.ready'));
123
+ loop.start();
124
+ };
125
+ return {
126
+ async apply(next) {
127
+ on = next;
128
+ if (starting)
129
+ await starting;
130
+ if (!on) {
131
+ if (running)
132
+ stop();
133
+ return;
134
+ }
135
+ if (running)
136
+ return;
137
+ starting = start().finally(() => { starting = null; });
138
+ await starting;
139
+ if (!current() && running)
140
+ stop();
141
+ },
142
+ };
143
+ }
@@ -0,0 +1,87 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import { type Action, type ActionWords } from '../core/actions.js';
3
+ /** One line of the help table. `key` is `null` when this child's keyboard does not reach the position. */
4
+ export interface HelpRow {
5
+ readonly action: Action;
6
+ /** Already human-readable (`keyName`), or `null` — «this transport does not reach it» is information. */
7
+ readonly key: string | null;
8
+ /** The GAME's word. Never an identifier: a position without one produces no row at all. */
9
+ readonly word: string;
10
+ /** The game's own sentence for this position, when it declared one. */
11
+ readonly hint?: string;
12
+ }
13
+ /**
14
+ * The help table for one seat.
15
+ *
16
+ * ⚠️ WALKS `ACTIONS` AND NOT THE PRESET'S OWN KEYS, for the reason `presetActions` already carries: the
17
+ * canonical order is the one the child meets everywhere else — the remap screen, the pad wizard, the
18
+ * on-screen legend. A help screen ordered by whatever order the game happened to write its object in would
19
+ * teach a different order from every other surface.
20
+ *
21
+ * @param preset this game's positions and their words, RESOLVED now in the page's language (`core/actions.wordsOf`).
22
+ * @param keysOf the codes bound to a position FOR THIS SEAT. `kbFor(seat)[action]`, so a child who remapped
23
+ * sees her own key. `null`/empty means the keyboard does not reach it.
24
+ * @param keyName code → readable label (`ui/settings-controls.keyName`), injected so this half stays pure.
25
+ */
26
+ export declare function helpRows(preset: ActionWords | null | undefined, keysOf: (a: Action) => readonly string[] | null | undefined, keyName: (code: string) => string): HelpRow[];
27
+ /** What the slide says where the keyboard reaches nothing. A KEY, not a sentence — the caller translates it. */
28
+ export declare const NO_KEY = "help.noKey";
29
+ /** What building the slide show needs from the document — injected, as the panel shell takes it. */
30
+ export interface SlideCtx {
31
+ readonly create: (tag: string) => HTMLElement;
32
+ }
33
+ /** The surface a «how to play» figure draws on, and the time in seconds since the slide showed (still under reduced motion). */
34
+ export interface HowToPlaySurface {
35
+ readonly ctx: CanvasRenderingContext2D;
36
+ readonly width: number;
37
+ readonly height: number;
38
+ readonly time: number;
39
+ }
40
+ /**
41
+ * One «how to play» slide, declared by the cartridge (ADR-0195; issue #188): «O "Como jogar" é justamente algo a ser feito pelo
42
+ * cartucho.» Its text is a KEY of the game's dictionary, resolved by the root's translator at every showing, so it follows the
43
+ * language (ADR-0232 D3, erratum of 2026-09-25); the figure is drawn by the game on the engine's surface — art stays data the
44
+ * game draws, never an embedded picture — and may animate by the time it is given.
45
+ */
46
+ export interface HowToPlaySlide {
47
+ readonly textKey: string;
48
+ readonly figure?: (surface: HowToPlaySurface) => void;
49
+ }
50
+ /** A «how to play» slide as it is SHOWN: its text resolved now (`playSlidesOf`), and its figure. */
51
+ export interface PlaySlide {
52
+ readonly text: string;
53
+ readonly figure?: (surface: HowToPlaySurface) => void;
54
+ }
55
+ /**
56
+ * The cartridge's slides, their texts resolved NOW through `word` (`Translator.word`). A slide whose text the game's
57
+ * dictionaries lack is left out — never shown as its key — and `problems` names it (`ui/declared-words`).
58
+ */
59
+ export declare function playSlidesOf(slides: readonly HowToPlaySlide[], word: (key: string) => string | null): PlaySlide[];
60
+ /** Why a `howToPlay` declaration is malformed; empty when well formed. Absent is well formed: the help shows the buttons alone. */
61
+ export declare function howToPlayProblems(slides: unknown): string[];
62
+ /** What the engine's frame loop gives an animated figure; injected, so the loop is the page's. */
63
+ export interface FigureClock {
64
+ readonly requestFrame: (cb: (ms: number) => void) => number;
65
+ readonly cancelFrame: (id: number) => void;
66
+ /** Reduced motion: the figure is drawn once, at time 0, and never again. */
67
+ readonly reduced: boolean;
68
+ }
69
+ /**
70
+ * Draws the shown slide's figure, and keeps drawing it while the slide show is in the document and the same slide is shown.
71
+ * Returns a stop. A figure that throws is a cartridge defect: it stops drawing and the text stays.
72
+ */
73
+ export declare function animateFigure(el: HTMLElement, slide: Pick<PlaySlide, 'figure'>, clock: FigureClock): () => void;
74
+ /** Builds the slide show's frame: the stop, its two arrows, the slide and the dots. `showSlide` fills it. */
75
+ export declare function mountSlides(ctx: SlideCtx): HTMLElement;
76
+ /**
77
+ * Shows slide `i` (held at the ends, like every steps control: the last slide is a wall, not a way back to the first) and returns
78
+ * the index shown, and what a screen reader hears of it. A slide is a button row (`helpRows`) or a cartridge's «how to play» slide
79
+ * (ADR-0195), which the help puts first.
80
+ */
81
+ export declare function showSlide(el: HTMLElement, rows: readonly (HelpRow | PlaySlide)[], i: number, ctx: SlideCtx & {
82
+ readonly t: (k: string, p?: Record<string, string>) => string;
83
+ readonly title: string;
84
+ }): {
85
+ readonly index: number;
86
+ readonly spoken: string;
87
+ };
@@ -0,0 +1,242 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // ui/help-panel — WHICH BUTTON DOES WHAT, in this game, on this child's keyboard: a slide show, one slide per button.
3
+ //
4
+ // ========================= WHY THIS MODULE EXISTS =========================
5
+ // 🔴 MEASURED 2026-09-12: the pause card carries a `ajuda` item and NOTHING in the engine can action it, so
6
+ // `itemsThatAct` hides it in every game. The help screen that used to fill it — `openHelp()` — did not
7
+ // die: it LEFT WITH THE CARTRIDGE (#111) and now lives in `game-platformer/app/js/main.ts`. So each of the
8
+ // three hundred games would have to write its own, which is the arrangement ADR-0139 measured failing in five
9
+ // games of six.
10
+ //
11
+ // 🎯 AND THE ENGINE ALREADY HAS EVERY INGREDIENT, which is what makes this cheap rather than new:
12
+ // · WHICH positions this game uses, and the game's own WORD for each — `ActionPreset` (ADR-0085).
13
+ // · The sentence that explains one — the `hint` of that preset, a field declared, typed, documented and,
14
+ // until this module, never displayed anywhere.
15
+ // · WHICH KEY reaches it FOR THIS CHILD — `input/keyboard-runtime.kbFor(seat)`, i.e. her remap and not the
16
+ // factory default.
17
+ //
18
+ // ⚠️ THE ENGINE NEVER NAMES AN ACTION HERE, and that is the whole boundary (ADR-0074, ADR-0086). It knows a
19
+ // POSITION exists; only the game knows the word. A row whose word is missing is therefore ABSENT — never a
20
+ // row reading «action2», which is the defect `labellerFrom` returns `null` to prevent.
21
+ //
22
+ // 📌 AND THIS IS THE MIGRATION `ui/settings-controls.ACT_LABEL` HAS BEEN WAITING FOR. That table holds ONE
23
+ // game's words — `act.run`, `act.jump` — inside the engine, and its own header says it can only leave once
24
+ // the help screen asks the GAME instead. This module is a help screen that asks the game.
25
+ import { ACTIONS } from '../core/actions.js';
26
+ /**
27
+ * The help table for one seat.
28
+ *
29
+ * ⚠️ WALKS `ACTIONS` AND NOT THE PRESET'S OWN KEYS, for the reason `presetActions` already carries: the
30
+ * canonical order is the one the child meets everywhere else — the remap screen, the pad wizard, the
31
+ * on-screen legend. A help screen ordered by whatever order the game happened to write its object in would
32
+ * teach a different order from every other surface.
33
+ *
34
+ * @param preset this game's positions and their words, RESOLVED now in the page's language (`core/actions.wordsOf`).
35
+ * @param keysOf the codes bound to a position FOR THIS SEAT. `kbFor(seat)[action]`, so a child who remapped
36
+ * sees her own key. `null`/empty means the keyboard does not reach it.
37
+ * @param keyName code → readable label (`ui/settings-controls.keyName`), injected so this half stays pure.
38
+ */
39
+ export function helpRows(preset, keysOf, keyName) {
40
+ if (!preset)
41
+ return [];
42
+ const rows = [];
43
+ for (const action of ACTIONS) {
44
+ const word = preset[action];
45
+ // ⚠️ A BLANK LABEL IS THE SAME AS NO LABEL, and it is the case `presetProblems` already names: a row
46
+ // whose word is whitespace reads, to a screen reader, as a button with no name.
47
+ if (!word || !word.label.trim())
48
+ continue;
49
+ // 📌 THE FIRST CODE, and the rest are not lost — they are the same position, and a help screen that listed
50
+ // every alias would spend the child's attention on the keyboard instead of on the game. The remap screen
51
+ // is where all of them are visible, because THERE they are the subject.
52
+ const codes = keysOf(action);
53
+ const key = codes && codes.length ? keyName(codes[0]) : null;
54
+ rows.push({ action, key, word: word.label, ...(word.hint ? { hint: word.hint } : {}) });
55
+ }
56
+ return rows;
57
+ }
58
+ /** What the slide says where the keyboard reaches nothing. A KEY, not a sentence — the caller translates it. */
59
+ export const NO_KEY = 'help.noKey';
60
+ /**
61
+ * The cartridge's slides, their texts resolved NOW through `word` (`Translator.word`). A slide whose text the game's
62
+ * dictionaries lack is left out — never shown as its key — and `problems` names it (`ui/declared-words`).
63
+ */
64
+ export function playSlidesOf(slides, word) {
65
+ return slides.flatMap((s) => {
66
+ const text = word(s.textKey);
67
+ return text ? [{ text, ...(s.figure ? { figure: s.figure } : {}) }] : [];
68
+ });
69
+ }
70
+ /** Why a `howToPlay` declaration is malformed; empty when well formed. Absent is well formed: the help shows the buttons alone. */
71
+ export function howToPlayProblems(slides) {
72
+ if (slides === undefined)
73
+ return [];
74
+ if (!Array.isArray(slides))
75
+ return ['howToPlay must be a list of slides'];
76
+ const out = [];
77
+ slides.forEach((s, i) => {
78
+ const at = `howToPlay[${i}]`;
79
+ if (!s || typeof s !== 'object') {
80
+ out.push(`${at} must be a slide`);
81
+ return;
82
+ }
83
+ if (typeof s.textKey !== 'string' || !s.textKey.trim())
84
+ out.push(`${at}.textKey must name the slide's text by a key of the game's dictionary`);
85
+ if (s.figure !== undefined && typeof s.figure !== 'function')
86
+ out.push(`${at}.figure must be a function drawing on the surface it is given`);
87
+ });
88
+ return out;
89
+ }
90
+ const isFromGame = (s) => typeof s.text === 'string';
91
+ /**
92
+ * Draws the shown slide's figure, and keeps drawing it while the slide show is in the document and the same slide is shown.
93
+ * Returns a stop. A figure that throws is a cartridge defect: it stops drawing and the text stays.
94
+ */
95
+ export function animateFigure(el, slide, clock) {
96
+ const screen = el.querySelector('.slide-figura');
97
+ const ctx = screen?.getContext('2d');
98
+ if (!screen || !ctx || !slide.figure)
99
+ return () => { };
100
+ const figureFn = slide.figure;
101
+ // the drawing surface is the size it is shown at, in CSS pixels, so a figure's coordinates are the ones the child sees
102
+ const screenBox = screen.getBoundingClientRect();
103
+ if (screenBox.width > 0 && screenBox.height > 0) {
104
+ screen.width = Math.round(screenBox.width);
105
+ screen.height = Math.round(screenBox.height);
106
+ }
107
+ let id = 0, still = false, start = -1;
108
+ const draw = (ms) => {
109
+ if (still || !el.isConnected)
110
+ return;
111
+ if (start < 0)
112
+ start = ms;
113
+ const time = clock.reduced ? 0 : (ms - start) / 1000;
114
+ ctx.clearRect(0, 0, screen.width, screen.height);
115
+ try {
116
+ figureFn({ ctx, width: screen.width, height: screen.height, time });
117
+ }
118
+ catch {
119
+ still = true;
120
+ return;
121
+ }
122
+ if (!clock.reduced)
123
+ id = clock.requestFrame(draw);
124
+ };
125
+ draw(0);
126
+ return () => { still = true; clock.cancelFrame(id); };
127
+ }
128
+ /** Builds the slide show's frame: the stop, its two arrows, the slide and the dots. `showSlide` fills it. */
129
+ export function mountSlides(ctx) {
130
+ const el = ctx.create('div');
131
+ el.className = 'slides';
132
+ el.setAttribute('role', 'spinbutton');
133
+ el.setAttribute('tabindex', '0');
134
+ el.setAttribute('data-passos', '');
135
+ const arrow = (delta, glyph) => {
136
+ const s = ctx.create('span');
137
+ s.className = 'passo-seta';
138
+ s.setAttribute('data-passo', String(delta));
139
+ s.setAttribute('aria-hidden', 'true');
140
+ s.textContent = glyph;
141
+ s.addEventListener('click', () => el.dispatchEvent(new CustomEvent('passo', { detail: delta, bubbles: true })));
142
+ return s;
143
+ };
144
+ const slide = ctx.create('div');
145
+ slide.className = 'slide';
146
+ slide.setAttribute('aria-hidden', 'true'); // heard through the stop's value, once
147
+ const figureFn = ctx.create('canvas');
148
+ figureFn.className = 'slide-figura';
149
+ figureFn.hidden = true;
150
+ const keyGlyphOf = ctx.create('kbd');
151
+ keyGlyphOf.className = 'slide-tecla';
152
+ const word = ctx.create('p');
153
+ word.className = 'slide-palavra';
154
+ const text = ctx.create('p');
155
+ text.className = 'slide-texto';
156
+ const dots = ctx.create('div');
157
+ dots.className = 'slide-pontos';
158
+ for (const child of [figureFn, keyGlyphOf, word, text, dots])
159
+ slide.appendChild(child);
160
+ el.appendChild(arrow(-1, '◀'));
161
+ el.appendChild(slide);
162
+ el.appendChild(arrow(1, '▶'));
163
+ return el;
164
+ }
165
+ /**
166
+ * Shows slide `i` (held at the ends, like every steps control: the last slide is a wall, not a way back to the first) and returns
167
+ * the index shown, and what a screen reader hears of it. A slide is a button row (`helpRows`) or a cartridge's «how to play» slide
168
+ * (ADR-0195), which the help puts first.
169
+ */
170
+ export function showSlide(el, rows, i, ctx) {
171
+ const last = Math.max(0, rows.length - 1);
172
+ const shown = Math.max(0, Math.min(last, i));
173
+ const r = rows[shown];
174
+ const parts = slidePartsOf(el);
175
+ if (!r || !parts)
176
+ return { index: shown, spoken: '' };
177
+ drawDots(parts.dots, rows.length, shown, ctx);
178
+ const spokenText = isFromGame(r) ? showPlaySlide(parts, r) : showButtonSlide(parts, r, ctx.t);
179
+ el.setAttribute('aria-label', ctx.title);
180
+ el.setAttribute('aria-valuemin', '0');
181
+ el.setAttribute('aria-valuemax', String(last));
182
+ el.setAttribute('aria-valuenow', String(shown));
183
+ el.setAttribute('aria-valuetext', spokenText);
184
+ el.querySelector('[data-passo="-1"]')?.classList.toggle('no-limite', shown === 0);
185
+ el.querySelector('[data-passo="1"]')?.classList.toggle('no-limite', shown === last);
186
+ return { index: shown, spoken: spokenText };
187
+ }
188
+ function slidePartsOf(el) {
189
+ const slide = el.querySelector('.slide');
190
+ const figure = el.querySelector('.slide-figura');
191
+ const keyCap = el.querySelector('.slide-tecla');
192
+ const word = el.querySelector('.slide-palavra');
193
+ const text = el.querySelector('.slide-texto');
194
+ const dots = el.querySelector('.slide-pontos');
195
+ if (!slide || !figure || !keyCap || !word || !text || !dots)
196
+ return null;
197
+ return { slide, figure, keyCap, word, text, dots };
198
+ }
199
+ /** One dot per slide, the current one filled — the place without a number (ADR-0167). */
200
+ function drawDots(dots, count, current, ctx) {
201
+ while (dots.firstChild)
202
+ dots.removeChild(dots.firstChild);
203
+ Array.from({ length: count }, (_, n) => {
204
+ const dot = ctx.create('span');
205
+ dot.className = n === current ? 'slide-ponto is-on' : 'slide-ponto';
206
+ return dots.appendChild(dot);
207
+ });
208
+ }
209
+ /** What is heard, the parts joined — a sentence that already ends in a full stop is not given a second one. */
210
+ const joinParts = (parts) => parts.filter(Boolean).map((part) => part.replace(/[.!?…]+\s*$/, '')).join('. ');
211
+ /**
212
+ * A cartridge's «how to play» slide (ADR-0195): its text and, if it has one, its figure — no key cap, no word, and no action,
213
+ * whatever the slide before it was.
214
+ */
215
+ function showPlaySlide(p, r) {
216
+ p.slide.setAttribute('data-kind', 'play');
217
+ p.slide.removeAttribute('data-act');
218
+ p.figure.hidden = !r.figure;
219
+ p.keyCap.hidden = true;
220
+ p.word.hidden = true;
221
+ const phrase = r.text;
222
+ p.text.textContent = phrase;
223
+ p.text.hidden = false;
224
+ return joinParts([phrase]);
225
+ }
226
+ /** A button's slide: the child's key (or that the keyboard does not reach it, marked), the game's word, and its sentence. */
227
+ function showButtonSlide(p, r, t) {
228
+ p.slide.setAttribute('data-kind', 'button');
229
+ p.slide.setAttribute('data-act', r.action);
230
+ p.figure.hidden = true;
231
+ p.keyCap.hidden = false;
232
+ p.word.hidden = false;
233
+ p.keyCap.textContent = r.key ?? t(NO_KEY);
234
+ if (r.key)
235
+ p.keyCap.removeAttribute('data-sem-tecla');
236
+ else
237
+ p.keyCap.setAttribute('data-sem-tecla', '1');
238
+ p.word.textContent = r.word;
239
+ p.text.textContent = r.hint ?? '';
240
+ p.text.hidden = !r.hint;
241
+ return joinParts([r.word, r.hint, r.key ? t('help.slide.tecla', { k: r.key }) : t(NO_KEY)]);
242
+ }