@the-inclusionist/engine 10.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 (528) hide show
  1. package/README.md +32 -1
  2. package/app/css/style.css +216 -42
  3. package/app/public/vendor/fonts-licences/NOTICE.txt +166 -0
  4. package/app/public/vendor/fonts-licences/OFL-1.1.txt +86 -0
  5. package/app/public/vendor/fonts.css +11 -1175
  6. package/dist-pkg/boot/create-game.d.ts +84 -33
  7. package/dist-pkg/boot/create-game.js +708 -307
  8. package/dist-pkg/core/a11y-sr.d.ts +1 -1
  9. package/dist-pkg/core/a11y-sr.js +3 -5
  10. package/dist-pkg/core/accommodations.d.ts +26 -8
  11. package/dist-pkg/core/accommodations.js +27 -10
  12. package/dist-pkg/core/actions.d.ts +39 -10
  13. package/dist-pkg/core/actions.js +28 -6
  14. package/dist-pkg/core/contract.d.ts +0 -2
  15. package/dist-pkg/core/contract.js +0 -2
  16. package/dist-pkg/core/i18n.d.ts +36 -71
  17. package/dist-pkg/core/i18n.js +131 -198
  18. package/dist-pkg/core/session-clock.d.ts +23 -0
  19. package/dist-pkg/core/session-clock.js +37 -0
  20. package/dist-pkg/i18n/en.js +44 -49
  21. package/dist-pkg/i18n/es.js +44 -49
  22. package/dist-pkg/i18n/pt.js +49 -54
  23. package/dist-pkg/input/gamepad.d.ts +42 -11
  24. package/dist-pkg/input/gamepad.js +50 -15
  25. package/dist-pkg/input/key-default.d.ts +30 -0
  26. package/dist-pkg/input/key-default.js +60 -0
  27. package/dist-pkg/input/keyboard-runtime.d.ts +0 -4
  28. package/dist-pkg/input/keyboard.d.ts +0 -1
  29. package/dist-pkg/input/keydown.d.ts +5 -11
  30. package/dist-pkg/input/keydown.js +3 -3
  31. package/dist-pkg/input/latch-edge.d.ts +10 -2
  32. package/dist-pkg/input/latch-edge.js +4 -3
  33. package/dist-pkg/input/latch-scope.d.ts +27 -22
  34. package/dist-pkg/input/latch-scope.js +25 -37
  35. package/dist-pkg/input/latch-store.d.ts +9 -12
  36. package/dist-pkg/input/latch-store.js +13 -18
  37. package/dist-pkg/input/latch-sync.d.ts +5 -4
  38. package/dist-pkg/input/latch-sync.js +5 -5
  39. package/dist-pkg/input/pad-reading.js +7 -2
  40. package/dist-pkg/input/pad-wizard.d.ts +4 -1
  41. package/dist-pkg/input/pad-wizard.js +60 -9
  42. package/dist-pkg/input/switch-scan.d.ts +29 -12
  43. package/dist-pkg/input/switch-scan.js +15 -2
  44. package/dist-pkg/input/touch-bindings.d.ts +3 -6
  45. package/dist-pkg/input/touch.d.ts +17 -2
  46. package/dist-pkg/input/touch.js +55 -17
  47. package/dist-pkg/input/transport-in-use.d.ts +5 -5
  48. package/dist-pkg/input/transport-in-use.js +5 -5
  49. package/dist-pkg/input/transports.d.ts +0 -9
  50. package/dist-pkg/input/transports.js +1 -1
  51. package/dist-pkg/input/virtual-controller.d.ts +39 -3
  52. package/dist-pkg/input/virtual-controller.js +32 -4
  53. package/dist-pkg/input/voice-map.d.ts +27 -11
  54. package/dist-pkg/input/voice-map.js +41 -43
  55. package/dist-pkg/platform/audio-ambient.d.ts +0 -1
  56. package/dist-pkg/platform/audio-ambient.js +3 -1
  57. package/dist-pkg/platform/audio-mixer.js +4 -9
  58. package/dist-pkg/platform/audio-sonar.d.ts +8 -94
  59. package/dist-pkg/platform/audio-sonar.js +22 -207
  60. package/dist-pkg/platform/audio.d.ts +10 -1
  61. package/dist-pkg/platform/audio.js +35 -2
  62. package/dist-pkg/platform/choose-by-voice.d.ts +70 -0
  63. package/dist-pkg/platform/choose-by-voice.js +157 -0
  64. package/dist-pkg/platform/font-library.d.ts +77 -0
  65. package/dist-pkg/platform/font-library.js +98 -0
  66. package/dist-pkg/platform/font-library.json +721 -0
  67. package/dist-pkg/platform/heavy-catalogue.d.ts +46 -0
  68. package/dist-pkg/platform/heavy-catalogue.js +47 -14
  69. package/dist-pkg/platform/heavy-mirror.d.ts +3 -9
  70. package/dist-pkg/platform/heavy-mirror.js +14 -17
  71. package/dist-pkg/platform/heavy.d.ts +40 -8
  72. package/dist-pkg/platform/heavy.js +177 -22
  73. package/dist-pkg/platform/interruptible-speech.d.ts +15 -8
  74. package/dist-pkg/platform/interruptible-speech.js +37 -13
  75. package/dist-pkg/platform/listener-scope.js +22 -13
  76. package/dist-pkg/platform/locale-host.d.ts +7 -3
  77. package/dist-pkg/platform/locale-host.js +13 -2
  78. package/dist-pkg/platform/reading-in-worker.d.ts +24 -0
  79. package/dist-pkg/platform/reading-in-worker.js +38 -0
  80. package/dist-pkg/platform/reading-model.d.ts +21 -0
  81. package/dist-pkg/platform/reading-model.js +25 -0
  82. package/dist-pkg/platform/reading-runtime.js +13 -8
  83. package/dist-pkg/platform/reading.d.ts +12 -1
  84. package/dist-pkg/platform/reading.js +9 -1
  85. package/dist-pkg/platform/speech-recognition.d.ts +10 -1
  86. package/dist-pkg/platform/speech-recognition.js +29 -9
  87. package/dist-pkg/platform/speech.d.ts +10 -1
  88. package/dist-pkg/platform/speech.js +24 -13
  89. package/dist-pkg/platform/tts.d.ts +37 -4
  90. package/dist-pkg/platform/tts.js +279 -57
  91. package/dist-pkg/platform/voice-listener.d.ts +6 -2
  92. package/dist-pkg/platform/voice-listener.js +1 -1
  93. package/dist-pkg/platform/voice-plan.d.ts +13 -0
  94. package/dist-pkg/platform/voice-plan.js +57 -2
  95. package/dist-pkg/platform/vosk-runtime.d.ts +25 -0
  96. package/dist-pkg/platform/vosk-runtime.js +59 -1
  97. package/dist-pkg/platform/vosk-vocabulary.d.ts +6 -0
  98. package/dist-pkg/platform/vosk-vocabulary.js +178 -0
  99. package/dist-pkg/render/crt.d.ts +11 -9
  100. package/dist-pkg/render/crt.js +6 -10
  101. package/dist-pkg/render/cvd-matrices.d.ts +5 -1
  102. package/dist-pkg/render/cvd-matrices.js +5 -1
  103. package/dist-pkg/render/high-contrast.d.ts +1 -2
  104. package/dist-pkg/render/high-contrast.js +5 -1
  105. package/dist-pkg/render/viz-axes.d.ts +9 -0
  106. package/dist-pkg/render/viz-axes.js +15 -2
  107. package/dist-pkg/ui/aac-sets.d.ts +29 -24
  108. package/dist-pkg/ui/aac-sets.js +22 -30
  109. package/dist-pkg/ui/audio-choices.d.ts +10 -5
  110. package/dist-pkg/ui/audio-choices.js +14 -10
  111. package/dist-pkg/ui/camera-control.d.ts +47 -0
  112. package/dist-pkg/ui/camera-control.js +35 -1
  113. package/dist-pkg/ui/declared-words.d.ts +21 -0
  114. package/dist-pkg/ui/declared-words.js +66 -0
  115. package/dist-pkg/ui/drawing-problems.js +4 -3
  116. package/dist-pkg/ui/eye-control.js +17 -17
  117. package/dist-pkg/ui/face-control.js +15 -15
  118. package/dist-pkg/ui/fonts.d.ts +42 -8
  119. package/dist-pkg/ui/fonts.js +58 -24
  120. package/dist-pkg/ui/footer-scroll-driver.d.ts +14 -0
  121. package/dist-pkg/ui/footer-scroll-driver.js +201 -0
  122. package/dist-pkg/ui/footer-scroll.d.ts +38 -0
  123. package/dist-pkg/ui/footer-scroll.js +55 -0
  124. package/dist-pkg/ui/game-options.d.ts +14 -9
  125. package/dist-pkg/ui/game-options.js +41 -21
  126. package/dist-pkg/ui/hand-control.js +15 -15
  127. package/dist-pkg/ui/help-panel.d.ts +19 -8
  128. package/dist-pkg/ui/help-panel.js +15 -5
  129. package/dist-pkg/ui/hud-bands.d.ts +29 -12
  130. package/dist-pkg/ui/hud-bands.js +63 -25
  131. package/dist-pkg/ui/hud-row.d.ts +34 -0
  132. package/dist-pkg/ui/hud-row.js +80 -0
  133. package/dist-pkg/ui/hud.d.ts +0 -3
  134. package/dist-pkg/ui/layout.js +5 -2
  135. package/dist-pkg/ui/libras-avatar-clip.d.ts +30 -0
  136. package/dist-pkg/ui/libras-avatar-clip.js +86 -0
  137. package/dist-pkg/ui/libras-avatar-load.d.ts +52 -0
  138. package/dist-pkg/ui/libras-avatar-load.js +134 -0
  139. package/dist-pkg/ui/libras-avatar-plan.d.ts +102 -0
  140. package/dist-pkg/ui/libras-avatar-plan.js +211 -0
  141. package/dist-pkg/ui/libras-avatar-player.d.ts +28 -0
  142. package/dist-pkg/ui/libras-avatar-player.js +226 -0
  143. package/dist-pkg/ui/libras-avatar-stage.d.ts +37 -0
  144. package/dist-pkg/ui/libras-avatar-stage.js +208 -0
  145. package/dist-pkg/ui/libras-glosses.d.ts +28 -0
  146. package/dist-pkg/ui/libras-glosses.js +161 -0
  147. package/dist-pkg/ui/menu-intent.d.ts +29 -0
  148. package/dist-pkg/ui/menu-intent.js +35 -1
  149. package/dist-pkg/ui/menu-items.d.ts +10 -0
  150. package/dist-pkg/ui/menu-items.js +29 -0
  151. package/dist-pkg/ui/menu-nav.d.ts +40 -4
  152. package/dist-pkg/ui/menu-nav.js +132 -47
  153. package/dist-pkg/ui/panel-widgets.d.ts +34 -0
  154. package/dist-pkg/ui/panel-widgets.js +86 -0
  155. package/dist-pkg/ui/pause-icons.d.ts +30 -14
  156. package/dist-pkg/ui/pause-icons.js +106 -45
  157. package/dist-pkg/ui/scan-overlay.d.ts +3 -3
  158. package/dist-pkg/ui/scan-overlay.js +11 -3
  159. package/dist-pkg/ui/screen-text.d.ts +25 -0
  160. package/dist-pkg/ui/screen-text.js +152 -0
  161. package/dist-pkg/ui/session-clock.d.ts +32 -0
  162. package/dist-pkg/ui/session-clock.js +67 -0
  163. package/dist-pkg/ui/settings-aac.d.ts +3 -1
  164. package/dist-pkg/ui/settings-aac.js +32 -13
  165. package/dist-pkg/ui/settings-audio.d.ts +10 -5
  166. package/dist-pkg/ui/settings-audio.js +101 -35
  167. package/dist-pkg/ui/settings-controls.d.ts +0 -4
  168. package/dist-pkg/ui/settings-mobility.d.ts +3 -6
  169. package/dist-pkg/ui/settings-mobility.js +4 -46
  170. package/dist-pkg/ui/settings-motion.js +2 -2
  171. package/dist-pkg/ui/settings-panel.d.ts +10 -0
  172. package/dist-pkg/ui/settings-panel.js +25 -1
  173. package/dist-pkg/ui/settings-typo.d.ts +5 -6
  174. package/dist-pkg/ui/settings-typo.js +20 -11
  175. package/dist-pkg/ui/settings-visual.js +2 -2
  176. package/dist-pkg/ui/shell.d.ts +1 -3
  177. package/dist-pkg/ui/shell.js +8 -4
  178. package/dist-pkg/ui/title.d.ts +2 -11
  179. package/dist-pkg/ui/title.js +39 -6
  180. package/dist-pkg/ui/top-band.d.ts +5 -3
  181. package/dist-pkg/ui/top-band.js +21 -25
  182. package/dist-pkg/ui/typo-choices.d.ts +3 -3
  183. package/dist-pkg/ui/typo-choices.js +9 -8
  184. package/dist-pkg/ui/vlibras.d.ts +71 -27
  185. package/dist-pkg/ui/vlibras.js +116 -44
  186. package/dist-pkg/ui/voice-control.d.ts +23 -1
  187. package/dist-pkg/ui/voice-control.js +149 -16
  188. package/dist-pkg/ui/voice-settings.d.ts +2 -3
  189. package/dist-pkg/ui/voice-settings.js +53 -3
  190. package/dist-pkg/ui/where-the-child-is.js +2 -2
  191. package/docs/CREDITS.md +122 -10
  192. package/docs/LICENSES.md +36 -7
  193. package/package.json +34 -9
  194. package/scripts/check-cartridge.mjs +78 -0
  195. package/scripts/game-build.d.mts +25 -0
  196. package/scripts/game-build.mjs +144 -0
  197. package/scripts/heavy-into-the-delivery.mjs +284 -40
  198. package/scripts/libras-avatar.json +680 -0
  199. package/scripts/libras-avatar.mjs +246 -0
  200. package/scripts/libras-glosses/gloss.py +95 -0
  201. package/scripts/libras-glosses/pyproject.toml +32 -0
  202. package/scripts/libras-glosses/uv.lock +781 -0
  203. package/scripts/libras-glosses.mjs +351 -0
  204. package/scripts/licences/OFL-1.1.txt +86 -0
  205. package/scripts/licences/UFL-1.0.txt +96 -0
  206. package/scripts/licences/fonts.mjs +389 -0
  207. package/scripts/licences/third-party.mjs +35 -21
  208. package/app/public/vendor/fonts/bitter-var-ext.woff2 +0 -0
  209. package/app/public/vendor/fonts/bitter-var.woff2 +0 -0
  210. package/app/public/vendor/fonts/bodonimoda-var-ext.woff2 +0 -0
  211. package/app/public/vendor/fonts/bodonimoda-var.woff2 +0 -0
  212. package/app/public/vendor/fonts/cat-abril_fatface-400-ext.woff2 +0 -0
  213. package/app/public/vendor/fonts/cat-abril_fatface-400.woff2 +0 -0
  214. package/app/public/vendor/fonts/cat-adlam_display-400-ext.woff2 +0 -0
  215. package/app/public/vendor/fonts/cat-adlam_display-400.woff2 +0 -0
  216. package/app/public/vendor/fonts/cat-agu_display-400-ext.woff2 +0 -0
  217. package/app/public/vendor/fonts/cat-agu_display-400.woff2 +0 -0
  218. package/app/public/vendor/fonts/cat-alfa_slab_one-400-ext.woff2 +0 -0
  219. package/app/public/vendor/fonts/cat-alfa_slab_one-400.woff2 +0 -0
  220. package/app/public/vendor/fonts/cat-are_you_serious-400-ext.woff2 +0 -0
  221. package/app/public/vendor/fonts/cat-are_you_serious-400.woff2 +0 -0
  222. package/app/public/vendor/fonts/cat-asimovian-400-ext.woff2 +0 -0
  223. package/app/public/vendor/fonts/cat-asimovian-400.woff2 +0 -0
  224. package/app/public/vendor/fonts/cat-atkinson_hyperlegible_next-400-ext.woff2 +0 -0
  225. package/app/public/vendor/fonts/cat-atkinson_hyperlegible_next-400.woff2 +0 -0
  226. package/app/public/vendor/fonts/cat-bakbak_one-400-ext.woff2 +0 -0
  227. package/app/public/vendor/fonts/cat-bakbak_one-400.woff2 +0 -0
  228. package/app/public/vendor/fonts/cat-bangers-400-ext.woff2 +0 -0
  229. package/app/public/vendor/fonts/cat-bangers-400.woff2 +0 -0
  230. package/app/public/vendor/fonts/cat-barriecito-400-ext.woff2 +0 -0
  231. package/app/public/vendor/fonts/cat-barriecito-400.woff2 +0 -0
  232. package/app/public/vendor/fonts/cat-berkshire_swash-400-ext.woff2 +0 -0
  233. package/app/public/vendor/fonts/cat-berkshire_swash-400.woff2 +0 -0
  234. package/app/public/vendor/fonts/cat-bevan-400-ext.woff2 +0 -0
  235. package/app/public/vendor/fonts/cat-bevan-400.woff2 +0 -0
  236. package/app/public/vendor/fonts/cat-bitcount-400-ext.woff2 +0 -0
  237. package/app/public/vendor/fonts/cat-bitcount-400.woff2 +0 -0
  238. package/app/public/vendor/fonts/cat-bowlby_one_sc-400-ext.woff2 +0 -0
  239. package/app/public/vendor/fonts/cat-bowlby_one_sc-400.woff2 +0 -0
  240. package/app/public/vendor/fonts/cat-bungee_shade-400-ext.woff2 +0 -0
  241. package/app/public/vendor/fonts/cat-bungee_shade-400.woff2 +0 -0
  242. package/app/public/vendor/fonts/cat-charm-400-ext.woff2 +0 -0
  243. package/app/public/vendor/fonts/cat-charm-400.woff2 +0 -0
  244. package/app/public/vendor/fonts/cat-charm-700-ext.woff2 +0 -0
  245. package/app/public/vendor/fonts/cat-charm-700.woff2 +0 -0
  246. package/app/public/vendor/fonts/cat-cherry_bomb_one-400-ext.woff2 +0 -0
  247. package/app/public/vendor/fonts/cat-cherry_bomb_one-400.woff2 +0 -0
  248. package/app/public/vendor/fonts/cat-chokokutai-400-ext.woff2 +0 -0
  249. package/app/public/vendor/fonts/cat-chokokutai-400.woff2 +0 -0
  250. package/app/public/vendor/fonts/cat-chonburi-400-ext.woff2 +0 -0
  251. package/app/public/vendor/fonts/cat-chonburi-400.woff2 +0 -0
  252. package/app/public/vendor/fonts/cat-climate_crisis-400-ext.woff2 +0 -0
  253. package/app/public/vendor/fonts/cat-climate_crisis-400.woff2 +0 -0
  254. package/app/public/vendor/fonts/cat-cookie-400.woff2 +0 -0
  255. package/app/public/vendor/fonts/cat-dela_gothic_one-400-ext.woff2 +0 -0
  256. package/app/public/vendor/fonts/cat-dela_gothic_one-400.woff2 +0 -0
  257. package/app/public/vendor/fonts/cat-fascinate_inline-400-ext.woff2 +0 -0
  258. package/app/public/vendor/fonts/cat-fascinate_inline-400.woff2 +0 -0
  259. package/app/public/vendor/fonts/cat-ga_maamli-400-ext.woff2 +0 -0
  260. package/app/public/vendor/fonts/cat-ga_maamli-400.woff2 +0 -0
  261. package/app/public/vendor/fonts/cat-itim-400-ext.woff2 +0 -0
  262. package/app/public/vendor/fonts/cat-itim-400.woff2 +0 -0
  263. package/app/public/vendor/fonts/cat-jacquard_24-400-ext.woff2 +0 -0
  264. package/app/public/vendor/fonts/cat-jacquard_24-400.woff2 +0 -0
  265. package/app/public/vendor/fonts/cat-kablammo-400-ext.woff2 +0 -0
  266. package/app/public/vendor/fonts/cat-kablammo-400.woff2 +0 -0
  267. package/app/public/vendor/fonts/cat-kalam-400-ext.woff2 +0 -0
  268. package/app/public/vendor/fonts/cat-kalam-400.woff2 +0 -0
  269. package/app/public/vendor/fonts/cat-kalam-700-ext.woff2 +0 -0
  270. package/app/public/vendor/fonts/cat-kalam-700.woff2 +0 -0
  271. package/app/public/vendor/fonts/cat-katibeh-400-ext.woff2 +0 -0
  272. package/app/public/vendor/fonts/cat-katibeh-400.woff2 +0 -0
  273. package/app/public/vendor/fonts/cat-lemonada-400-ext.woff2 +0 -0
  274. package/app/public/vendor/fonts/cat-lemonada-400.woff2 +0 -0
  275. package/app/public/vendor/fonts/cat-lilita_one-400-ext.woff2 +0 -0
  276. package/app/public/vendor/fonts/cat-lilita_one-400.woff2 +0 -0
  277. package/app/public/vendor/fonts/cat-lobster-400-ext.woff2 +0 -0
  278. package/app/public/vendor/fonts/cat-lobster-400.woff2 +0 -0
  279. package/app/public/vendor/fonts/cat-luckiest_guy-400-ext.woff2 +0 -0
  280. package/app/public/vendor/fonts/cat-luckiest_guy-400.woff2 +0 -0
  281. package/app/public/vendor/fonts/cat-marhey-400-ext.woff2 +0 -0
  282. package/app/public/vendor/fonts/cat-marhey-400.woff2 +0 -0
  283. package/app/public/vendor/fonts/cat-modak-400-ext.woff2 +0 -0
  284. package/app/public/vendor/fonts/cat-modak-400.woff2 +0 -0
  285. package/app/public/vendor/fonts/cat-momo_signature-400-ext.woff2 +0 -0
  286. package/app/public/vendor/fonts/cat-momo_signature-400.woff2 +0 -0
  287. package/app/public/vendor/fonts/cat-monofett-400-ext.woff2 +0 -0
  288. package/app/public/vendor/fonts/cat-monofett-400.woff2 +0 -0
  289. package/app/public/vendor/fonts/cat-monomakh-400-ext.woff2 +0 -0
  290. package/app/public/vendor/fonts/cat-monomakh-400.woff2 +0 -0
  291. package/app/public/vendor/fonts/cat-monoton-400-ext.woff2 +0 -0
  292. package/app/public/vendor/fonts/cat-monoton-400.woff2 +0 -0
  293. package/app/public/vendor/fonts/cat-nerko_one-400-ext.woff2 +0 -0
  294. package/app/public/vendor/fonts/cat-nerko_one-400.woff2 +0 -0
  295. package/app/public/vendor/fonts/cat-noto_sans_adlam-400-ext.woff2 +0 -0
  296. package/app/public/vendor/fonts/cat-noto_sans_adlam-400.woff2 +0 -0
  297. package/app/public/vendor/fonts/cat-noto_sans_arabic-400-ext.woff2 +0 -0
  298. package/app/public/vendor/fonts/cat-noto_sans_arabic-400.woff2 +0 -0
  299. package/app/public/vendor/fonts/cat-noto_sans_devanagari-400-ext.woff2 +0 -0
  300. package/app/public/vendor/fonts/cat-noto_sans_devanagari-400.woff2 +0 -0
  301. package/app/public/vendor/fonts/cat-noto_sans_hebrew-400-ext.woff2 +0 -0
  302. package/app/public/vendor/fonts/cat-noto_sans_hebrew-400.woff2 +0 -0
  303. package/app/public/vendor/fonts/cat-noto_sans_jp-400-ext.woff2 +0 -0
  304. package/app/public/vendor/fonts/cat-noto_sans_jp-400.woff2 +0 -0
  305. package/app/public/vendor/fonts/cat-noto_sans_khmer-400-ext.woff2 +0 -0
  306. package/app/public/vendor/fonts/cat-noto_sans_khmer-400.woff2 +0 -0
  307. package/app/public/vendor/fonts/cat-noto_sans_kr-400-ext.woff2 +0 -0
  308. package/app/public/vendor/fonts/cat-noto_sans_kr-400.woff2 +0 -0
  309. package/app/public/vendor/fonts/cat-noto_sans_sc-400-ext.woff2 +0 -0
  310. package/app/public/vendor/fonts/cat-noto_sans_sc-400.woff2 +0 -0
  311. package/app/public/vendor/fonts/cat-noto_sans_thai-400-ext.woff2 +0 -0
  312. package/app/public/vendor/fonts/cat-noto_sans_thai-400.woff2 +0 -0
  313. package/app/public/vendor/fonts/cat-ole-400-ext.woff2 +0 -0
  314. package/app/public/vendor/fonts/cat-ole-400.woff2 +0 -0
  315. package/app/public/vendor/fonts/cat-pacifico-400-ext.woff2 +0 -0
  316. package/app/public/vendor/fonts/cat-pacifico-400.woff2 +0 -0
  317. package/app/public/vendor/fonts/cat-patrick_hand-400-ext.woff2 +0 -0
  318. package/app/public/vendor/fonts/cat-patrick_hand-400.woff2 +0 -0
  319. package/app/public/vendor/fonts/cat-petit_formal_script-400-ext.woff2 +0 -0
  320. package/app/public/vendor/fonts/cat-petit_formal_script-400.woff2 +0 -0
  321. package/app/public/vendor/fonts/cat-pixelify_sans-400-ext.woff2 +0 -0
  322. package/app/public/vendor/fonts/cat-pixelify_sans-400.woff2 +0 -0
  323. package/app/public/vendor/fonts/cat-plaster-400-ext.woff2 +0 -0
  324. package/app/public/vendor/fonts/cat-plaster-400.woff2 +0 -0
  325. package/app/public/vendor/fonts/cat-playwrite_ar_guides-400-0.woff2 +0 -0
  326. package/app/public/vendor/fonts/cat-playwrite_at-400-0.woff2 +0 -0
  327. package/app/public/vendor/fonts/cat-playwrite_at_guides-400-0.woff2 +0 -0
  328. package/app/public/vendor/fonts/cat-playwrite_au_nsw-400-0.woff2 +0 -0
  329. package/app/public/vendor/fonts/cat-playwrite_au_nsw_guides-400-0.woff2 +0 -0
  330. package/app/public/vendor/fonts/cat-playwrite_au_qld-400-0.woff2 +0 -0
  331. package/app/public/vendor/fonts/cat-playwrite_au_qld_guides-400-0.woff2 +0 -0
  332. package/app/public/vendor/fonts/cat-playwrite_au_sa-400-0.woff2 +0 -0
  333. package/app/public/vendor/fonts/cat-playwrite_au_sa_guides-400-0.woff2 +0 -0
  334. package/app/public/vendor/fonts/cat-playwrite_au_tas-400-0.woff2 +0 -0
  335. package/app/public/vendor/fonts/cat-playwrite_au_tas_guides-400-0.woff2 +0 -0
  336. package/app/public/vendor/fonts/cat-playwrite_au_vic-400-0.woff2 +0 -0
  337. package/app/public/vendor/fonts/cat-playwrite_au_vic_guides-400-0.woff2 +0 -0
  338. package/app/public/vendor/fonts/cat-playwrite_be_vlg-400-0.woff2 +0 -0
  339. package/app/public/vendor/fonts/cat-playwrite_be_vlg_guides-400-0.woff2 +0 -0
  340. package/app/public/vendor/fonts/cat-playwrite_be_wal-400-0.woff2 +0 -0
  341. package/app/public/vendor/fonts/cat-playwrite_be_wal_guides-400-0.woff2 +0 -0
  342. package/app/public/vendor/fonts/cat-playwrite_br_guides-400-0.woff2 +0 -0
  343. package/app/public/vendor/fonts/cat-playwrite_ca_guides-400-0.woff2 +0 -0
  344. package/app/public/vendor/fonts/cat-playwrite_cl_guides-400-0.woff2 +0 -0
  345. package/app/public/vendor/fonts/cat-playwrite_co_guides-400-0.woff2 +0 -0
  346. package/app/public/vendor/fonts/cat-playwrite_cu_guides-400-0.woff2 +0 -0
  347. package/app/public/vendor/fonts/cat-playwrite_cz-400-0.woff2 +0 -0
  348. package/app/public/vendor/fonts/cat-playwrite_cz_guides-400-0.woff2 +0 -0
  349. package/app/public/vendor/fonts/cat-playwrite_de_grund-400-0.woff2 +0 -0
  350. package/app/public/vendor/fonts/cat-playwrite_de_grund_guides-400-0.woff2 +0 -0
  351. package/app/public/vendor/fonts/cat-playwrite_de_la-400-0.woff2 +0 -0
  352. package/app/public/vendor/fonts/cat-playwrite_de_la_guides-400-0.woff2 +0 -0
  353. package/app/public/vendor/fonts/cat-playwrite_de_sas-400-0.woff2 +0 -0
  354. package/app/public/vendor/fonts/cat-playwrite_de_sas_guides-400-0.woff2 +0 -0
  355. package/app/public/vendor/fonts/cat-playwrite_de_va-400-0.woff2 +0 -0
  356. package/app/public/vendor/fonts/cat-playwrite_de_va_guides-400-0.woff2 +0 -0
  357. package/app/public/vendor/fonts/cat-playwrite_dk_loopet-400-0.woff2 +0 -0
  358. package/app/public/vendor/fonts/cat-playwrite_dk_loopet_guides-400-0.woff2 +0 -0
  359. package/app/public/vendor/fonts/cat-playwrite_dk_uloopet-400-0.woff2 +0 -0
  360. package/app/public/vendor/fonts/cat-playwrite_dk_uloopet_guides-400-0.woff2 +0 -0
  361. package/app/public/vendor/fonts/cat-playwrite_es_deco_guides-400-0.woff2 +0 -0
  362. package/app/public/vendor/fonts/cat-playwrite_es_guides-400-0.woff2 +0 -0
  363. package/app/public/vendor/fonts/cat-playwrite_fr_moderne-400-0.woff2 +0 -0
  364. package/app/public/vendor/fonts/cat-playwrite_fr_moderne_guides-400-0.woff2 +0 -0
  365. package/app/public/vendor/fonts/cat-playwrite_fr_trad-400-0.woff2 +0 -0
  366. package/app/public/vendor/fonts/cat-playwrite_fr_trad_guides-400-0.woff2 +0 -0
  367. package/app/public/vendor/fonts/cat-playwrite_gb_j_guides-400-0.woff2 +0 -0
  368. package/app/public/vendor/fonts/cat-playwrite_gb_s_guides-400-0.woff2 +0 -0
  369. package/app/public/vendor/fonts/cat-playwrite_hr-400-0.woff2 +0 -0
  370. package/app/public/vendor/fonts/cat-playwrite_hr_guides-400-0.woff2 +0 -0
  371. package/app/public/vendor/fonts/cat-playwrite_hr_lijeva-400-0.woff2 +0 -0
  372. package/app/public/vendor/fonts/cat-playwrite_hr_lijeva_guides-400-0.woff2 +0 -0
  373. package/app/public/vendor/fonts/cat-playwrite_hu-400-0.woff2 +0 -0
  374. package/app/public/vendor/fonts/cat-playwrite_hu_guides-400-0.woff2 +0 -0
  375. package/app/public/vendor/fonts/cat-playwrite_id-400-0.woff2 +0 -0
  376. package/app/public/vendor/fonts/cat-playwrite_id_guides-400-0.woff2 +0 -0
  377. package/app/public/vendor/fonts/cat-playwrite_ie-400-0.woff2 +0 -0
  378. package/app/public/vendor/fonts/cat-playwrite_ie_guides-400-0.woff2 +0 -0
  379. package/app/public/vendor/fonts/cat-playwrite_in-400-0.woff2 +0 -0
  380. package/app/public/vendor/fonts/cat-playwrite_in_guides-400-0.woff2 +0 -0
  381. package/app/public/vendor/fonts/cat-playwrite_is-400-0.woff2 +0 -0
  382. package/app/public/vendor/fonts/cat-playwrite_is_guides-400-0.woff2 +0 -0
  383. package/app/public/vendor/fonts/cat-playwrite_it_moderna-400-0.woff2 +0 -0
  384. package/app/public/vendor/fonts/cat-playwrite_it_moderna_guides-400-0.woff2 +0 -0
  385. package/app/public/vendor/fonts/cat-playwrite_it_trad-400-0.woff2 +0 -0
  386. package/app/public/vendor/fonts/cat-playwrite_it_trad_guides-400-0.woff2 +0 -0
  387. package/app/public/vendor/fonts/cat-playwrite_mx_guides-400-0.woff2 +0 -0
  388. package/app/public/vendor/fonts/cat-playwrite_ng_modern-400-0.woff2 +0 -0
  389. package/app/public/vendor/fonts/cat-playwrite_ng_modern_guides-400-0.woff2 +0 -0
  390. package/app/public/vendor/fonts/cat-playwrite_nl-400-0.woff2 +0 -0
  391. package/app/public/vendor/fonts/cat-playwrite_nl_guides-400-0.woff2 +0 -0
  392. package/app/public/vendor/fonts/cat-playwrite_no-400-0.woff2 +0 -0
  393. package/app/public/vendor/fonts/cat-playwrite_no_guides-400-0.woff2 +0 -0
  394. package/app/public/vendor/fonts/cat-playwrite_nz-400-0.woff2 +0 -0
  395. package/app/public/vendor/fonts/cat-playwrite_nz_basic-400-0.woff2 +0 -0
  396. package/app/public/vendor/fonts/cat-playwrite_nz_basic_guides-400-0.woff2 +0 -0
  397. package/app/public/vendor/fonts/cat-playwrite_nz_guides-400-0.woff2 +0 -0
  398. package/app/public/vendor/fonts/cat-playwrite_pe_guides-400-0.woff2 +0 -0
  399. package/app/public/vendor/fonts/cat-playwrite_pl-400-0.woff2 +0 -0
  400. package/app/public/vendor/fonts/cat-playwrite_pl_guides-400-0.woff2 +0 -0
  401. package/app/public/vendor/fonts/cat-playwrite_pt_guides-400-0.woff2 +0 -0
  402. package/app/public/vendor/fonts/cat-playwrite_ro-400-0.woff2 +0 -0
  403. package/app/public/vendor/fonts/cat-playwrite_ro_guides-400-0.woff2 +0 -0
  404. package/app/public/vendor/fonts/cat-playwrite_sk-400-0.woff2 +0 -0
  405. package/app/public/vendor/fonts/cat-playwrite_sk_guides-400-0.woff2 +0 -0
  406. package/app/public/vendor/fonts/cat-playwrite_tz-400-0.woff2 +0 -0
  407. package/app/public/vendor/fonts/cat-playwrite_tz_guides-400-0.woff2 +0 -0
  408. package/app/public/vendor/fonts/cat-playwrite_us_modern_guides-400-0.woff2 +0 -0
  409. package/app/public/vendor/fonts/cat-playwrite_us_trad_guides-400-0.woff2 +0 -0
  410. package/app/public/vendor/fonts/cat-playwrite_vn-400-0.woff2 +0 -0
  411. package/app/public/vendor/fonts/cat-playwrite_vn_guides-400-0.woff2 +0 -0
  412. package/app/public/vendor/fonts/cat-playwrite_za-400-0.woff2 +0 -0
  413. package/app/public/vendor/fonts/cat-playwrite_za_guides-400-0.woff2 +0 -0
  414. package/app/public/vendor/fonts/cat-potta_one-400-ext.woff2 +0 -0
  415. package/app/public/vendor/fonts/cat-potta_one-400.woff2 +0 -0
  416. package/app/public/vendor/fonts/cat-ranchers-400-ext.woff2 +0 -0
  417. package/app/public/vendor/fonts/cat-ranchers-400.woff2 +0 -0
  418. package/app/public/vendor/fonts/cat-ranga-400-ext.woff2 +0 -0
  419. package/app/public/vendor/fonts/cat-ranga-400.woff2 +0 -0
  420. package/app/public/vendor/fonts/cat-ranga-700-ext.woff2 +0 -0
  421. package/app/public/vendor/fonts/cat-ranga-700.woff2 +0 -0
  422. package/app/public/vendor/fonts/cat-risque-400-ext.woff2 +0 -0
  423. package/app/public/vendor/fonts/cat-risque-400.woff2 +0 -0
  424. package/app/public/vendor/fonts/cat-rowdies-400-ext.woff2 +0 -0
  425. package/app/public/vendor/fonts/cat-rowdies-400.woff2 +0 -0
  426. package/app/public/vendor/fonts/cat-rowdies-700-ext.woff2 +0 -0
  427. package/app/public/vendor/fonts/cat-rowdies-700.woff2 +0 -0
  428. package/app/public/vendor/fonts/cat-ruge_boogie-400-ext.woff2 +0 -0
  429. package/app/public/vendor/fonts/cat-ruge_boogie-400.woff2 +0 -0
  430. package/app/public/vendor/fonts/cat-ruslan_display-400-ext.woff2 +0 -0
  431. package/app/public/vendor/fonts/cat-ruslan_display-400.woff2 +0 -0
  432. package/app/public/vendor/fonts/cat-sacramento-400-ext.woff2 +0 -0
  433. package/app/public/vendor/fonts/cat-sacramento-400.woff2 +0 -0
  434. package/app/public/vendor/fonts/cat-sancreek-400-ext.woff2 +0 -0
  435. package/app/public/vendor/fonts/cat-sancreek-400.woff2 +0 -0
  436. package/app/public/vendor/fonts/cat-sankofa_display-400-ext.woff2 +0 -0
  437. package/app/public/vendor/fonts/cat-sankofa_display-400.woff2 +0 -0
  438. package/app/public/vendor/fonts/cat-sedgwick_ave_display-400-ext.woff2 +0 -0
  439. package/app/public/vendor/fonts/cat-sedgwick_ave_display-400.woff2 +0 -0
  440. package/app/public/vendor/fonts/cat-shantell_sans-400-ext.woff2 +0 -0
  441. package/app/public/vendor/fonts/cat-shantell_sans-400.woff2 +0 -0
  442. package/app/public/vendor/fonts/cat-smokum-400-ext.woff2 +0 -0
  443. package/app/public/vendor/fonts/cat-smokum-400.woff2 +0 -0
  444. package/app/public/vendor/fonts/cat-solitreo-400-ext.woff2 +0 -0
  445. package/app/public/vendor/fonts/cat-solitreo-400.woff2 +0 -0
  446. package/app/public/vendor/fonts/cat-spicy_rice-400-ext.woff2 +0 -0
  447. package/app/public/vendor/fonts/cat-spicy_rice-400.woff2 +0 -0
  448. package/app/public/vendor/fonts/cat-sriracha-400-ext.woff2 +0 -0
  449. package/app/public/vendor/fonts/cat-sriracha-400.woff2 +0 -0
  450. package/app/public/vendor/fonts/cat-srisakdi-400-ext.woff2 +0 -0
  451. package/app/public/vendor/fonts/cat-srisakdi-400.woff2 +0 -0
  452. package/app/public/vendor/fonts/cat-srisakdi-700-ext.woff2 +0 -0
  453. package/app/public/vendor/fonts/cat-srisakdi-700.woff2 +0 -0
  454. package/app/public/vendor/fonts/cat-stix_two_math-400-0.woff2 +0 -0
  455. package/app/public/vendor/fonts/cat-tillana-400-ext.woff2 +0 -0
  456. package/app/public/vendor/fonts/cat-tillana-400.woff2 +0 -0
  457. package/app/public/vendor/fonts/cat-tillana-700-ext.woff2 +0 -0
  458. package/app/public/vendor/fonts/cat-tillana-700.woff2 +0 -0
  459. package/app/public/vendor/fonts/cat-titan_one-400-ext.woff2 +0 -0
  460. package/app/public/vendor/fonts/cat-titan_one-400.woff2 +0 -0
  461. package/app/public/vendor/fonts/cat-yatra_one-400-ext.woff2 +0 -0
  462. package/app/public/vendor/fonts/cat-yatra_one-400.woff2 +0 -0
  463. package/app/public/vendor/fonts/cat-yomogi-400-ext.woff2 +0 -0
  464. package/app/public/vendor/fonts/cat-yomogi-400.woff2 +0 -0
  465. package/app/public/vendor/fonts/dmserifdisplay-400-ext.woff2 +0 -0
  466. package/app/public/vendor/fonts/dmserifdisplay-400.woff2 +0 -0
  467. package/app/public/vendor/fonts/domine-var-ext.woff2 +0 -0
  468. package/app/public/vendor/fonts/domine-var.woff2 +0 -0
  469. package/app/public/vendor/fonts/fondamento-400-ext.woff2 +0 -0
  470. package/app/public/vendor/fonts/fondamento-400.woff2 +0 -0
  471. package/app/public/vendor/fonts/fraunces-var-ext.woff2 +0 -0
  472. package/app/public/vendor/fonts/fraunces-var.woff2 +0 -0
  473. package/app/public/vendor/fonts/fredoka-var-ext.woff2 +0 -0
  474. package/app/public/vendor/fonts/fredoka-var.woff2 +0 -0
  475. package/app/public/vendor/fonts/inter-400.woff2 +0 -0
  476. package/app/public/vendor/fonts/inter-700.woff2 +0 -0
  477. package/app/public/vendor/fonts/jakarta-var-ext.woff2 +0 -0
  478. package/app/public/vendor/fonts/jakarta-var.woff2 +0 -0
  479. package/app/public/vendor/fonts/lato-400.woff2 +0 -0
  480. package/app/public/vendor/fonts/lato-700.woff2 +0 -0
  481. package/app/public/vendor/fonts/literata-400.woff2 +0 -0
  482. package/app/public/vendor/fonts/literata-700.woff2 +0 -0
  483. package/app/public/vendor/fonts/lora-var-ext.woff2 +0 -0
  484. package/app/public/vendor/fonts/lora-var.woff2 +0 -0
  485. package/app/public/vendor/fonts/merriweather-var-ext.woff2 +0 -0
  486. package/app/public/vendor/fonts/merriweather-var.woff2 +0 -0
  487. package/app/public/vendor/fonts/newsreader-400.woff2 +0 -0
  488. package/app/public/vendor/fonts/newsreader-700.woff2 +0 -0
  489. package/app/public/vendor/fonts/notosans-var-ext.woff2 +0 -0
  490. package/app/public/vendor/fonts/notosans-var.woff2 +0 -0
  491. package/app/public/vendor/fonts/nunito-var-ext.woff2 +0 -0
  492. package/app/public/vendor/fonts/nunito-var.woff2 +0 -0
  493. package/app/public/vendor/fonts/opendyslexic-400.woff2 +0 -0
  494. package/app/public/vendor/fonts/opensans-400.woff2 +0 -0
  495. package/app/public/vendor/fonts/opensans-700.woff2 +0 -0
  496. package/app/public/vendor/fonts/pinyon-400.woff2 +0 -0
  497. package/app/public/vendor/fonts/playfair-var-ext.woff2 +0 -0
  498. package/app/public/vendor/fonts/playfair-var.woff2 +0 -0
  499. package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
  500. package/app/public/vendor/fonts/quattro-400.woff2 +0 -0
  501. package/app/public/vendor/fonts/quattro-700.woff2 +0 -0
  502. package/app/public/vendor/fonts/quicksand-var-ext.woff2 +0 -0
  503. package/app/public/vendor/fonts/quicksand-var.woff2 +0 -0
  504. package/app/public/vendor/fonts/robotoflex-var-ext.woff2 +0 -0
  505. package/app/public/vendor/fonts/robotoflex-var.woff2 +0 -0
  506. package/app/public/vendor/fonts/sora-var-ext.woff2 +0 -0
  507. package/app/public/vendor/fonts/sora-var.woff2 +0 -0
  508. package/app/public/vendor/fonts/sourcesans-400.woff2 +0 -0
  509. package/app/public/vendor/fonts/sourcesans-700.woff2 +0 -0
  510. package/app/public/vendor/fonts/sourceserif-400.woff2 +0 -0
  511. package/app/public/vendor/fonts/sourceserif-700.woff2 +0 -0
  512. package/app/public/vendor/fonts/spacegrotesk-var-ext.woff2 +0 -0
  513. package/app/public/vendor/fonts/spacegrotesk-var.woff2 +0 -0
  514. package/app/public/vendor/fonts/spectral-400-ext.woff2 +0 -0
  515. package/app/public/vendor/fonts/spectral-400.woff2 +0 -0
  516. package/app/public/vendor/fonts/spectral-700-ext.woff2 +0 -0
  517. package/app/public/vendor/fonts/spectral-700.woff2 +0 -0
  518. package/app/public/vendor/fonts/teachers-var-ext.woff2 +0 -0
  519. package/app/public/vendor/fonts/teachers-var.woff2 +0 -0
  520. package/app/public/vendor/fonts/ubuntu-400-ext.woff2 +0 -0
  521. package/app/public/vendor/fonts/ubuntu-400.woff2 +0 -0
  522. package/app/public/vendor/fonts/ubuntu-700-ext.woff2 +0 -0
  523. package/app/public/vendor/fonts/ubuntu-700.woff2 +0 -0
  524. package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
  525. package/dist-pkg/platform/guide-intensity.d.ts +0 -39
  526. package/dist-pkg/platform/guide-intensity.js +0 -71
  527. package/dist-pkg/ui/latch-refusal.d.ts +0 -33
  528. package/dist-pkg/ui/latch-refusal.js +0 -60
@@ -0,0 +1,14 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ export interface FooterScrollHost {
3
+ readonly doc: Pick<Document, 'createRange' | 'createTreeWalker' | 'createElement'>;
4
+ readonly getComputedStyle: (el: Element) => CSSStyleDeclaration;
5
+ readonly MutationObserver: typeof MutationObserver;
6
+ readonly setTimeout: (fn: () => void, ms: number) => number;
7
+ readonly clearTimeout: (id: number) => void;
8
+ /** The child's caption rate (ADR-0183 §4), read at every start. */
9
+ readonly ppm: () => number;
10
+ /** Reduced motion — the system's or the child's — read at every start. */
11
+ readonly reduced: () => boolean;
12
+ }
13
+ /** Starts watching `root`. Returns the way to stop: every animation cancelled and timer cleared, the observer disconnected. */
14
+ export declare function watchFooterScroll(root: HTMLElement, host: FooterScrollHost): () => void;
@@ -0,0 +1,201 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // ui/footer-scroll-driver — A FOOTER EXPLANATION LONGER THAN ITS LINES MOVES INSIDE THEM (ADR-0245): the DOM half of
3
+ // `ui/footer-scroll`, which holds the pace.
4
+ //
5
+ // Watches the region for the two explanation footers — the screen's band (`.barra-explicacao`) and a panel's
6
+ // (`.opt-explain`). A NEW text, or a footer that comes into view, starts from the top; a text that fits is measured once and
7
+ // left alone, its DOM untouched. The box does not change: `[data-scroll]` swaps the two-line clamp for a block of the same
8
+ // lines (style.css).
9
+ // · GLIDE: the words rise continuously (ADR-0245 erratum, 2026-09-26 — a line at a time was what the Dev rejected). They are
10
+ // moved into one `.footer-scroll-text` block, and a Web Animation translates it: sub-pixel and on the compositor, where
11
+ // `scrollTop` stands on whole pixels. The text node the game wrote is MOVED, not copied, in the same task as the write and
12
+ // before a frame is drawn, and nothing is written into the live region after — narration and the screen reader still get
13
+ // the whole text once (§4).
14
+ // · PAGES (reduced motion): no slide, so `scrollTop` and a timer per page, as the plan says.
15
+ // Pointer over it or focus in it holds the motion where it is (§3, WCAG 2.2.2): the animation paused, or the page timer
16
+ // cleared. A footer that leaves the screen keeps no animation and no timer.
17
+ //
18
+ // 📌 Everything it reaches arrives through the host (ADR-0232): the document, the timers, the styles and the observer.
19
+ import { footerScrollPlan, readLines } from './footer-scroll.js';
20
+ const FOOTERS = '.barra-explicacao, .opt-explain';
21
+ /** The block the words glide in (style.css makes it a block while the footer scrolls). */
22
+ const TEXT = 'footer-scroll-text';
23
+ /** Starts watching `root`. Returns the way to stop: every animation cancelled and timer cleared, the observer disconnected. */
24
+ export function watchFooterScroll(root, host) {
25
+ const seen = new Map();
26
+ const held = new WeakSet();
27
+ const wired = new WeakSet();
28
+ const visible = (el) => el.isConnected && el.getClientRects().length > 0;
29
+ const linesShown = (el) => parseInt(host.getComputedStyle(el).getPropertyValue('--footer-lines'), 10) || 2;
30
+ function wordTops(el) {
31
+ const tops = [];
32
+ const walker = host.doc.createTreeWalker(el, 4); // NodeFilter.SHOW_TEXT
33
+ const range = host.doc.createRange();
34
+ for (let n = walker.nextNode(); n; n = walker.nextNode()) {
35
+ for (const m of (n.textContent ?? '').matchAll(/\S+/g)) {
36
+ range.setStart(n, m.index);
37
+ range.setEnd(n, m.index + m[0].length);
38
+ const box = range.getClientRects()[0];
39
+ if (box)
40
+ tops.push(box.top);
41
+ }
42
+ }
43
+ return tops;
44
+ }
45
+ function forget(el) {
46
+ const s = seen.get(el);
47
+ if (s?.timer != null)
48
+ host.clearTimeout(s.timer);
49
+ if (s?.glide) {
50
+ s.glide.anim.onfinish = null;
51
+ s.glide.anim.cancel();
52
+ }
53
+ seen.delete(el);
54
+ delete el.dataset.scroll;
55
+ delete el.dataset.topLine;
56
+ el.scrollTop = 0;
57
+ }
58
+ /**
59
+ * The words inside the one block that glides, MOVED there (never copied: the game's text node stays the one it wrote). The
60
+ * block a footer already has is kept — a restart, or the same text written again, goes on in it.
61
+ */
62
+ function gather(el, text) {
63
+ let block = text;
64
+ if (!block) {
65
+ const only = el.childNodes.length === 1 ? el.firstElementChild : null;
66
+ if (only instanceof HTMLElement && only.classList.contains(TEXT))
67
+ return only;
68
+ block = host.doc.createElement('span');
69
+ block.className = TEXT;
70
+ }
71
+ const loose = [...el.childNodes].filter((n) => n !== block);
72
+ if (loose.length)
73
+ block.replaceChildren(...loose);
74
+ if (block.parentNode !== el)
75
+ el.append(block);
76
+ return block;
77
+ }
78
+ /** Paces a footer as it is now: held, it stays where it is; free, it goes on from there. */
79
+ function pace(el, s) {
80
+ if (s.glide) {
81
+ if (held.has(el))
82
+ s.glide.anim.pause();
83
+ else
84
+ s.glide.anim.play();
85
+ return;
86
+ }
87
+ arm(el, s);
88
+ }
89
+ function glide(el, s, plan) {
90
+ const text = gather(el);
91
+ const rise = `translateY(${-s.lines.starts[plan.toLine]}px)`;
92
+ const total = plan.waitMs + plan.travelMs + plan.holdMs;
93
+ // still while the first view is read, one speed to the last view, still while it is read
94
+ const anim = text.animate([
95
+ { transform: 'translateY(0)', offset: 0 },
96
+ { transform: 'translateY(0)', offset: plan.waitMs / total },
97
+ { transform: rise, offset: (plan.waitMs + plan.travelMs) / total },
98
+ { transform: rise, offset: 1 },
99
+ ], { duration: total, easing: 'linear' });
100
+ anim.onfinish = () => start(el, true); // the end: again from the top, measured again
101
+ s.glide = { text, anim };
102
+ pace(el, s);
103
+ }
104
+ const pagesOf = (s) => (s.plan?.mode === 'pages' ? s.plan : null);
105
+ function arm(el, s) {
106
+ if (s.timer != null)
107
+ host.clearTimeout(s.timer);
108
+ s.timer = null;
109
+ const pages = pagesOf(s);
110
+ if (!pages || held.has(el))
111
+ return;
112
+ s.timer = host.setTimeout(() => {
113
+ s.timer = null;
114
+ if (s.step + 1 < pages.steps.length)
115
+ show(el, s, pages, s.step + 1);
116
+ else
117
+ start(el, true); // the end: again from the top, measured again
118
+ }, pages.steps[s.step].holdMs);
119
+ }
120
+ function show(el, s, pages, step) {
121
+ s.step = step;
122
+ const line = pages.steps[step].line;
123
+ el.dataset.topLine = String(line);
124
+ el.scrollTop = s.lines.starts[line];
125
+ arm(el, s);
126
+ }
127
+ function wire(el) {
128
+ if (wired.has(el))
129
+ return;
130
+ wired.add(el);
131
+ const hold = () => { held.add(el); const s = seen.get(el); if (s)
132
+ pace(el, s); };
133
+ const release = () => { held.delete(el); const s = seen.get(el); if (s)
134
+ pace(el, s); };
135
+ el.addEventListener('pointerenter', hold);
136
+ el.addEventListener('pointerleave', release);
137
+ el.addEventListener('focusin', hold);
138
+ el.addEventListener('focusout', (e) => { if (!el.contains(e.relatedTarget))
139
+ release(); });
140
+ }
141
+ /** A footer whose text or lines changed, or that came into view: measured, and moved only if it does not fit. */
142
+ function start(el, again = false) {
143
+ const key = `${linesShown(el)}|${el.textContent ?? ''}`;
144
+ const had = seen.get(el);
145
+ if (!again && had?.key === key) {
146
+ // the same text written again is not a new text: it goes on gliding, in the block it was in
147
+ if (had.glide)
148
+ gather(el, had.glide.text);
149
+ return;
150
+ }
151
+ forget(el);
152
+ if (!visible(el))
153
+ return;
154
+ el.dataset.scroll = ''; // measured as it will scroll: a block, no clamp
155
+ const lines = readLines(wordTops(el), parseFloat(host.getComputedStyle(el).lineHeight));
156
+ // a page without Web Animations turns pages: the text still passes whole, at the same pace
157
+ const reduced = host.reduced() || typeof el.animate !== 'function';
158
+ const plan = footerScrollPlan(lines.words, linesShown(el), host.ppm(), reduced);
159
+ const s = { key, plan, lines, step: 0, timer: null, glide: null };
160
+ seen.set(el, s);
161
+ if (!plan) {
162
+ delete el.dataset.scroll;
163
+ return;
164
+ }
165
+ el.dataset.scroll = plan.mode;
166
+ wire(el);
167
+ if (plan.mode === 'glide')
168
+ glide(el, s, plan);
169
+ else
170
+ show(el, s, plan, 0);
171
+ }
172
+ function footersIn(node, into) {
173
+ const el = node.nodeType === 1 ? node : node.parentElement;
174
+ const own = el?.closest(FOOTERS);
175
+ if (own)
176
+ into.add(own);
177
+ if (node.nodeType === 1)
178
+ el.querySelectorAll(FOOTERS).forEach((f) => into.add(f));
179
+ }
180
+ const observer = new host.MutationObserver((records) => {
181
+ const touched = new Set();
182
+ for (const r of records) {
183
+ footersIn(r.target, touched);
184
+ r.addedNodes.forEach((n) => footersIn(n, touched));
185
+ }
186
+ // a `hidden` changed somewhere: every footer may have come into view, or changed its number of lines
187
+ if (records.some((r) => r.type === 'attributes'))
188
+ footersIn(root, touched);
189
+ for (const el of [...seen.keys()])
190
+ if (!visible(el))
191
+ forget(el);
192
+ touched.forEach((el) => start(el));
193
+ });
194
+ observer.observe(root, { childList: true, characterData: true, subtree: true, attributes: true, attributeFilter: ['hidden'] });
195
+ root.querySelectorAll(FOOTERS).forEach((el) => start(el));
196
+ return () => {
197
+ observer.disconnect();
198
+ for (const el of [...seen.keys()])
199
+ forget(el);
200
+ };
201
+ }
@@ -0,0 +1,38 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /** One page: the first line on show, and how long it stays before the next. */
3
+ export interface FooterScrollStep {
4
+ readonly line: number;
5
+ readonly holdMs: number;
6
+ }
7
+ /**
8
+ * Rises continuously: still for `waitMs`, then up at one speed for `travelMs` until `toLine` tops the view (the last view),
9
+ * held there `holdMs`.
10
+ */
11
+ export interface FooterGlidePlan {
12
+ readonly mode: 'glide';
13
+ readonly waitMs: number;
14
+ readonly travelMs: number;
15
+ readonly holdMs: number;
16
+ readonly toLine: number;
17
+ }
18
+ /** Turns a whole view at a time, without sliding (reduced motion). */
19
+ export interface FooterPagesPlan {
20
+ readonly mode: 'pages';
21
+ readonly steps: readonly FooterScrollStep[];
22
+ }
23
+ export type FooterScrollPlan = FooterGlidePlan | FooterPagesPlan;
24
+ /** The rendered lines: how many words each holds, and where each starts, from the top of the first. */
25
+ export interface FooterLines {
26
+ readonly words: readonly number[];
27
+ readonly starts: readonly number[];
28
+ }
29
+ /**
30
+ * The lines, from the TOP of each word as laid out. A word lower than the line's top by more than half a line opens a new one;
31
+ * a word broken across two lines counts where it starts.
32
+ */
33
+ export declare function readLines(wordTops: readonly number[], lineHeight: number): FooterLines;
34
+ /**
35
+ * The plan for a text of `wordsPerLine.length` lines shown `shown` at a time, or `null` when it fits — a text that fits never
36
+ * moves. The rate is one of the three the child can choose; any other number reads as the slowest (`isCaptionRate`).
37
+ */
38
+ export declare function footerScrollPlan(wordsPerLine: readonly number[], shown: number, ppm: number, reduced: boolean): FooterScrollPlan | null;
@@ -0,0 +1,55 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // ui/footer-scroll — WHEN A LONG FOOTER EXPLANATION MOVES, AND FOR HOW LONG (ADR-0245).
3
+ //
4
+ // The footer shows its lines (two; one while the button legend shares the column, ADR-0164). A text that needs more moves
5
+ // INSIDE them, at the child's caption rate — the arithmetic of `core/caption-duration`, 60000 / ppm ms a word:
6
+ // · GLIDE (ADR-0245 erratum, 2026-09-26: «suave e contínua, lentamente»): it waits for the first view to be read, then rises
7
+ // at ONE constant speed until its last line is in view — the distance passes in the time the words below the first view
8
+ // take, so the last line arrives when every word has been read —, holds the last view for its words, and the caller starts
9
+ // again from the top;
10
+ // · PAGES, with reduced motion: no slide — a whole view at a time, each page held for the words it brings.
11
+ //
12
+ // 📌 PURE: word positions and numbers in, a plan out. The DOM half (`ui/footer-scroll-driver`) measures and moves.
13
+ import { isCaptionRate } from '../core/caption-duration.js';
14
+ /**
15
+ * The lines, from the TOP of each word as laid out. A word lower than the line's top by more than half a line opens a new one;
16
+ * a word broken across two lines counts where it starts.
17
+ */
18
+ export function readLines(wordTops, lineHeight) {
19
+ const words = [];
20
+ const starts = [];
21
+ let top = -Infinity;
22
+ for (const t of wordTops) {
23
+ if (t > top + lineHeight / 2) {
24
+ top = t;
25
+ words.push(0);
26
+ starts.push(t - (wordTops[0] ?? 0));
27
+ }
28
+ words[words.length - 1] += 1;
29
+ }
30
+ return { words, starts };
31
+ }
32
+ /**
33
+ * The plan for a text of `wordsPerLine.length` lines shown `shown` at a time, or `null` when it fits — a text that fits never
34
+ * moves. The rate is one of the three the child can choose; any other number reads as the slowest (`isCaptionRate`).
35
+ */
36
+ export function footerScrollPlan(wordsPerLine, shown, ppm, reduced) {
37
+ const last = wordsPerLine.length - shown; // the first line of the last view
38
+ if (shown < 1 || last <= 0)
39
+ return null;
40
+ const msPerWord = 60_000 / isCaptionRate(ppm);
41
+ const read = (from, to) => Math.round(wordsPerLine.slice(from, to).reduce((a, b) => a + b, 0) * msPerWord);
42
+ // the last view is held as long as its lines take to read
43
+ const endHold = read(last, last + shown);
44
+ if (!reduced) {
45
+ return { mode: 'glide', waitMs: read(0, shown), travelMs: read(shown, wordsPerLine.length), holdMs: endHold, toLine: last };
46
+ }
47
+ const steps = [{ line: 0, holdMs: read(0, shown) }];
48
+ for (let line = shown; steps[steps.length - 1].line < last; line += shown) {
49
+ const top = Math.min(line, last);
50
+ const before = steps[steps.length - 1].line;
51
+ // every other page, as long as the lines it brought
52
+ steps.push({ line: top, holdMs: top === last ? endHold : read(before + shown, top + shown) });
53
+ }
54
+ return { mode: 'pages', steps };
55
+ }
@@ -1,21 +1,24 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
2
  import type { Translate } from '../core/i18n.js';
3
3
  import type { PanelShellCtx } from './panel-shell.js';
4
- /** One position of a steps or list row: the value the cartridge reads and writes, and its label in the interface language. */
4
+ /** One position of a steps or list row: the value the cartridge reads and writes, and the key of its label. */
5
5
  export interface GameOptionValue {
6
6
  readonly value: string;
7
- readonly label: string;
7
+ readonly labelKey: string;
8
8
  }
9
9
  interface GameOptionBase {
10
10
  /** Stable and unique among the game's rows; the control is `#game-option-<id>`. */
11
11
  readonly id: string;
12
- /** The row's short name, in the interface language. */
13
- readonly label: string;
14
- /** The explanation, shown in the footer (CLAUDE.md §4). */
15
- readonly hint?: string;
12
+ /** The key of the row's short name, in the game's dictionary. */
13
+ readonly labelKey: string;
14
+ /** The key of the explanation, shown in the footer (CLAUDE.md §4). */
15
+ readonly hintKey?: string;
16
16
  }
17
17
  export type GameOption = (GameOptionBase & {
18
- /** Steps «◀ Label: value ▶», up to five positions; a list, any number (ADR-0130 erratum). */
18
+ /**
19
+ * An exclusive choice. The engine draws it by its SIZE (ADR-0130 rule 3): five positions or fewer are one row «◀ Label:
20
+ * value ▶», more a dropdown — whichever kind is declared. `steps` still refuses more than five, since the game asked for a cycle.
21
+ */
19
22
  readonly kind: 'steps' | 'list';
20
23
  readonly values: readonly GameOptionValue[];
21
24
  /** The value in use. Read at every opening and after every write: what shows is what the cartridge keeps. */
@@ -29,14 +32,16 @@ export type GameOption = (GameOptionBase & {
29
32
  /** Why a `gameOptions` declaration is malformed; empty when it is well formed. Absent is well formed: no options of its own. */
30
33
  export declare function gameOptionsProblems(options: unknown): string[];
31
34
  export interface GameOptionsDrawCtx extends PanelShellCtx {
32
- /** Translates in the page's language — the root's translator (ADR-0232 D3). REQUIRED: text built from nowhere is a raw key. */
35
+ /** Translates the engine's own words in the page's language — the root's translator (ADR-0232 D3). */
33
36
  t: Translate;
37
+ /** Resolves a key the GAME declared, in the page's language; `null` when its dictionaries lack it (`Translator.word`). */
38
+ readonly word: (key: string) => string | null;
34
39
  /** Where a change is said (the polite live region). */
35
40
  readonly say: (text: string) => void;
36
41
  }
37
42
  /**
38
43
  * Draws the rows into `list`, replacing what was there — the rows are the CURRENT cartridge's, and `mount()` may have
39
- * swapped it (ADR-0142). Every value shown is READ from the cartridge, at drawing and after each write.
44
+ * swapped it (ADR-0142). Every value shown is READ from the cartridge, and every word RESOLVED, at drawing and after each write.
40
45
  */
41
46
  export declare function drawGameOptions(ctx: GameOptionsDrawCtx, list: HTMLElement, options: readonly GameOption[]): void;
42
47
  export {};
@@ -1,9 +1,7 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
- import { controlRow, mountSteps, updateSteps, nextStep } from './panel-widgets.js';
2
+ import { controlRow, mountSteps, updateSteps, nextStep, MAX_CYCLE_POSITIONS } from './panel-widgets.js';
3
3
  import { toggleLabel } from './dom.js';
4
4
  const KINDS = ['steps', 'list', 'switch'];
5
- /** ADR-0130 erratum, rule 3: steps cycle among FEW positions — up to five; past that, a list. */
6
- const MAX_STEPS = 5;
7
5
  /** Why a `gameOptions` declaration is malformed; empty when it is well formed. Absent is well formed: no options of its own. */
8
6
  export function gameOptionsProblems(options) {
9
7
  if (options === undefined)
@@ -31,20 +29,22 @@ function idProblems(o, at, ids) {
31
29
  ids.add(o.id);
32
30
  return [];
33
31
  }
32
+ /** A key: text that is not blank. */
33
+ const isKey = (k) => typeof k === 'string' && !!k.trim();
34
34
  /** Each field a row must have, and what is said when it does not: a row comes from outside the engine, so its TYPE is checked. */
35
35
  const FIELD_CHECKS = [
36
36
  [(o) => KINDS.includes(o.kind), `kind must be one of ${KINDS.join(', ')}`],
37
- [(o) => typeof o.label === 'string' && !!o.label.trim(), "label must say what the row is, in the game's words"],
38
- [(o) => o.hint === undefined || typeof o.hint === 'string', 'hint must be text'],
37
+ [(o) => isKey(o.labelKey), "labelKey must name the row by a key of the game's dictionary"],
38
+ [(o) => o.hintKey === undefined || isKey(o.hintKey), "hintKey must be a key of the game's dictionary"],
39
39
  [(o) => typeof o.read === 'function', 'read must be a function returning the value in use'],
40
40
  [(o) => typeof o.write === 'function', 'write must be a function taking the new value'],
41
41
  ];
42
- /** The positions of a steps or list row: at least two, at most five for steps, each with a value and a label, none repeated. */
42
+ /** The positions of a steps or list row: at least two, at most five for steps, each with a value and a label key, none repeated. */
43
43
  function valuesProblems(o, at) {
44
44
  const values = o.values;
45
45
  if (!Array.isArray(values) || values.length < 2)
46
46
  return [`${at}.values must hold at least two positions`];
47
- const out = o.kind === 'steps' && values.length > MAX_STEPS
47
+ const out = o.kind === 'steps' && values.length > MAX_CYCLE_POSITIONS
48
48
  ? [`${at}.values holds ${values.length} steps: steps hold at most five positions — past that, declare a list (ADR-0130)`] : [];
49
49
  const seen = new Set();
50
50
  values.forEach((v, j) => {
@@ -55,25 +55,44 @@ function valuesProblems(o, at) {
55
55
  return out;
56
56
  }
57
57
  function positionProblem(v, at, seen) {
58
- if (!v || typeof v.value !== 'string' || typeof v.label !== 'string' || !v.label.trim())
59
- return `${at} must have a value and a label`;
58
+ if (!v || typeof v.value !== 'string' || !isKey(v.labelKey))
59
+ return `${at} must have a value and a labelKey`;
60
60
  if (seen.has(v.value))
61
61
  return `${at} «${v.value}» repeats an earlier position`;
62
62
  seen.add(v.value);
63
63
  return null;
64
64
  }
65
+ function rowWords(o, word) {
66
+ const label = word(o.labelKey);
67
+ if (!label)
68
+ return null;
69
+ const hint = o.hintKey ? word(o.hintKey) : null;
70
+ const values = new Map();
71
+ if (o.kind !== 'switch') {
72
+ for (const v of o.values) {
73
+ const positionLabel = word(v.labelKey);
74
+ if (!positionLabel)
75
+ return null;
76
+ values.set(v.value, positionLabel);
77
+ }
78
+ }
79
+ return { label, ...(hint ? { hint } : {}), values };
80
+ }
65
81
  /**
66
82
  * Draws the rows into `list`, replacing what was there — the rows are the CURRENT cartridge's, and `mount()` may have
67
- * swapped it (ADR-0142). Every value shown is READ from the cartridge, at drawing and after each write.
83
+ * swapped it (ADR-0142). Every value shown is READ from the cartridge, and every word RESOLVED, at drawing and after each write.
68
84
  */
69
85
  export function drawGameOptions(ctx, list, options) {
70
86
  const { t } = ctx;
71
87
  while (list.firstChild)
72
88
  list.removeChild(list.firstChild); // node by node: no markup sink, and a host without `replaceChildren` still clears
73
89
  for (const o of options) {
90
+ const words = rowWords(o, ctx.word);
91
+ if (!words)
92
+ continue;
74
93
  const id = `game-option-${o.id}`;
75
94
  if (o.kind === 'switch') {
76
- const { row: rowNode, control: switchButton } = controlRow(ctx, { id, label: o.label, hint: o.hint });
95
+ const { row: rowNode, control: switchButton } = controlRow(ctx, { id, label: words.label, hint: words.hint });
77
96
  const reflect = () => {
78
97
  const on = o.read();
79
98
  switchButton.classList.toggle('is-on', on);
@@ -84,40 +103,41 @@ export function drawGameOptions(ctx, list, options) {
84
103
  reflect();
85
104
  switchButton.addEventListener('click', () => {
86
105
  o.write(!o.read());
87
- ctx.say(`${o.label}: ${toggleLabel(t, reflect())}`);
106
+ ctx.say(`${words.label}: ${toggleLabel(t, reflect())}`);
88
107
  });
89
108
  list.appendChild(rowNode);
90
109
  continue;
91
110
  }
92
- const labelOf = (chosen) => o.values.find((v) => v.value === chosen)?.label ?? chosen;
93
- if (o.kind === 'list') {
94
- const { row: rowNode, control: control } = controlRow(ctx, { id, label: o.label, hint: o.hint, shape: 'escolha' });
111
+ const labelOf = (chosen) => words.values.get(chosen) ?? '';
112
+ // ⚠️ BY SIZE, NOT BY KIND (ADR-0130 rule 3): a `list` of five or fewer is a cycle row like `steps`, below
113
+ if (o.values.length > MAX_CYCLE_POSITIONS) {
114
+ const { row: rowNode, control: control } = controlRow(ctx, { id, label: words.label, hint: words.hint, shape: 'escolha' });
95
115
  const sel = control;
96
116
  for (const v of o.values) {
97
117
  const op = ctx.create('option');
98
118
  op.value = v.value;
99
- op.textContent = v.label;
119
+ op.textContent = labelOf(v.value);
100
120
  sel.appendChild(op);
101
121
  }
102
122
  sel.value = o.read();
103
123
  sel.addEventListener('change', () => {
104
124
  o.write(sel.value);
105
125
  sel.value = o.read();
106
- ctx.say(`${o.label}: ${labelOf(sel.value)}`);
126
+ ctx.say(`${words.label}: ${labelOf(sel.value)}`);
107
127
  });
108
128
  list.appendChild(rowNode);
109
129
  continue;
110
130
  }
111
131
  // steps: the row IS the control, «◀ Label: value ▶» (ADR-0130 erratum); the hint rides in the row for the footer
112
132
  const selectedIndex = () => Math.max(0, o.values.findIndex((v) => v.value === o.read()));
113
- const spec = () => ({ label: o.label, values: o.values.map((v) => v.label), current: selectedIndex() });
133
+ const spec = () => ({ label: words.label, values: o.values.map((v) => labelOf(v.value)), current: selectedIndex() });
114
134
  const rowNode = ctx.create('div');
115
135
  rowNode.className = 'ctrl-row ctrl-row--passos';
116
136
  const envelope = ctx.create('span');
117
- if (o.hint) {
137
+ if (words.hint) {
118
138
  const explanation = ctx.create('span');
119
139
  explanation.className = 'opt-hint';
120
- explanation.textContent = o.hint;
140
+ explanation.textContent = words.hint;
121
141
  envelope.appendChild(explanation);
122
142
  }
123
143
  rowNode.appendChild(envelope);
@@ -131,7 +151,7 @@ export function drawGameOptions(ctx, list, options) {
131
151
  return; // at the wall nothing moved, and nothing is said
132
152
  o.write(o.values[nextIndex].value);
133
153
  updateSteps(stepper, spec());
134
- ctx.say(`${o.label}: ${labelOf(o.read())}`);
154
+ ctx.say(`${words.label}: ${labelOf(o.read())}`);
135
155
  });
136
156
  list.appendChild(rowNode);
137
157
  }
@@ -11,6 +11,7 @@ import { gesturesSeen, createHandMapReader } from '../input/hand-map.js';
11
11
  import { loadHandTracker } from '../platform/vision.js';
12
12
  import { createVisionLoop } from '../platform/vision-loop.js';
13
13
  import { gazeFontPx } from './gaze-overlay.js';
14
+ import { startCameraReader } from './camera-control.js';
14
15
  // 📌 THE SHAPE IS THE FAMILY'S (ADR-0221 step 7f): what this module returns is a SwitchableControl, not a fourth copy of it.
15
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. */
16
17
  function drawBones(g, w, h, bones, hand) {
@@ -94,23 +95,22 @@ export function createHandControl(d) {
94
95
  canvas = null;
95
96
  running = false;
96
97
  };
97
- const start = async () => {
98
- const load = await loadTracker({ base: d.base, hasFile: d.hasFile });
99
- if (!load.ok) {
100
- once('files', `gesture control: ${load.missing.join(', ')} not on this device — the child cannot play with gestures; open the game once online so the install fetches them`, t('sr.hands.needsInternet'));
101
- d.turnOff();
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())
102
101
  return;
103
- }
104
- try {
105
- feed = await d.openFeed();
106
- }
107
- catch {
108
- load.tracker.close();
109
- once('camera', 'gesture control: the camera did not open — the child cannot play with gestures; allow the camera for this page, or plug one in', t('sr.hands.noCamera'));
110
- d.turnOff();
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
111
  return;
112
- }
113
- tracker = load.tracker;
112
+ tracker = opened.tracker;
113
+ feed = opened.feed;
114
114
  canvas = d.doc.createElement('canvas');
115
115
  canvas.className = 'hand-overlay';
116
116
  canvas.setAttribute('aria-hidden', 'true');
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
- import { type Action, type ActionPreset } from '../core/actions.js';
2
+ import { type Action, type ActionWords } from '../core/actions.js';
3
3
  /** One line of the help table. `key` is `null` when this child's keyboard does not reach the position. */
4
4
  export interface HelpRow {
5
5
  readonly action: Action;
@@ -18,12 +18,12 @@ export interface HelpRow {
18
18
  * on-screen legend. A help screen ordered by whatever order the game happened to write its object in would
19
19
  * teach a different order from every other surface.
20
20
  *
21
- * @param preset what this game declares — positions, words, hints.
21
+ * @param preset this game's positions and their words, RESOLVED now in the page's language (`core/actions.wordsOf`).
22
22
  * @param keysOf the codes bound to a position FOR THIS SEAT. `kbFor(seat)[action]`, so a child who remapped
23
23
  * sees her own key. `null`/empty means the keyboard does not reach it.
24
24
  * @param keyName code → readable label (`ui/settings-controls.keyName`), injected so this half stays pure.
25
25
  */
26
- export declare function helpRows(preset: ActionPreset | null | undefined, keysOf: (a: Action) => readonly string[] | null | undefined, keyName: (code: string) => string): HelpRow[];
26
+ export declare function helpRows(preset: ActionWords | null | undefined, keysOf: (a: Action) => readonly string[] | null | undefined, keyName: (code: string) => string): HelpRow[];
27
27
  /** What the slide says where the keyboard reaches nothing. A KEY, not a sentence — the caller translates it. */
28
28
  export declare const NO_KEY = "help.noKey";
29
29
  /** What building the slide show needs from the document — injected, as the panel shell takes it. */
@@ -39,13 +39,24 @@ export interface HowToPlaySurface {
39
39
  }
40
40
  /**
41
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.» The text is resolved at every showing, so it follows the language; the figure is drawn by the game on the engine's
43
- * surface — art stays data the game draws, never an embedded picture — and may animate by the time it is given.
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.
44
45
  */
45
46
  export interface HowToPlaySlide {
46
- readonly text: () => string;
47
+ readonly textKey: string;
47
48
  readonly figure?: (surface: HowToPlaySurface) => void;
48
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[];
49
60
  /** Why a `howToPlay` declaration is malformed; empty when well formed. Absent is well formed: the help shows the buttons alone. */
50
61
  export declare function howToPlayProblems(slides: unknown): string[];
51
62
  /** What the engine's frame loop gives an animated figure; injected, so the loop is the page's. */
@@ -59,7 +70,7 @@ export interface FigureClock {
59
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.
60
71
  * Returns a stop. A figure that throws is a cartridge defect: it stops drawing and the text stays.
61
72
  */
62
- export declare function animateFigure(el: HTMLElement, slide: HowToPlaySlide, clock: FigureClock): () => void;
73
+ export declare function animateFigure(el: HTMLElement, slide: Pick<PlaySlide, 'figure'>, clock: FigureClock): () => void;
63
74
  /** Builds the slide show's frame: the stop, its two arrows, the slide and the dots. `showSlide` fills it. */
64
75
  export declare function mountSlides(ctx: SlideCtx): HTMLElement;
65
76
  /**
@@ -67,7 +78,7 @@ export declare function mountSlides(ctx: SlideCtx): HTMLElement;
67
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
68
79
  * (ADR-0195), which the help puts first.
69
80
  */
70
- export declare function showSlide(el: HTMLElement, rows: readonly (HelpRow | HowToPlaySlide)[], i: number, ctx: SlideCtx & {
81
+ export declare function showSlide(el: HTMLElement, rows: readonly (HelpRow | PlaySlide)[], i: number, ctx: SlideCtx & {
71
82
  readonly t: (k: string, p?: Record<string, string>) => string;
72
83
  readonly title: string;
73
84
  }): {