@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
@@ -25,7 +25,7 @@ export declare function inputModeOf(s: {
25
25
  readonly toggleMove?: boolean;
26
26
  readonly switchScan?: boolean;
27
27
  }): InputMode;
28
- export declare function nextInputMode(m: InputMode, holdsKeys: boolean, latchRequired?: boolean): InputMode;
28
+ export declare function nextInputMode(m: InputMode, holdsKeys: boolean): InputMode;
29
29
  import type { Store } from '../platform/storage.js';
30
30
  /**
31
31
  * Hover and focus on a bar's icons write its NAME in the bar's `.pause-icons-cap` and ask for its EXPLANATION.
@@ -88,15 +88,6 @@ export interface IconStateSnapshot {
88
88
  locale?: string;
89
89
  /** False disables the blind/TTS icons: those need an audio output nobody else is listening to. */
90
90
  privateOutput: boolean;
91
- /**
92
- * Does the device in use REQUIRE the latch? (ADR-0113 clause 3.) With the eyes, the face, gestures and speech the latch
93
- * is what makes the input work, so there is no choice to offer: the ☝️ cycle then skips `standard` (see
94
- * `inputModeOrder`).
95
- *
96
- * OPTIONAL because adding a required member to a published snapshot is a breaking change (the shape gate,
97
- * `tests/public-surface-shrinks-by-declaration`, fails on it). Absent means «nobody told me», which degrades to «not required».
98
- */
99
- latchRequired?: boolean;
100
91
  /** No voice speaks the current language (ADR-0185): the narration icon is locked. Absent reads as a voice. */
101
92
  noVoice?: boolean;
102
93
  }
@@ -136,8 +127,8 @@ export interface ActionableIcons {
136
127
  * DOES THIS GAME HOLD ANY KEY? — `GameDeclaration.holdsKeys`, the field of ADR-0115.
137
128
  *
138
129
  * The latch exists for whoever cannot keep a key pressed; in a game where nothing is held it has nothing to hold, and a
139
- * control that does nothing teaches a child that the setting she depends on is broken. (A different absence from
140
- * ADR-0113 clause 3's, where the device REQUIRES the latch: see `latchRequired`.)
130
+ * control that does nothing teaches a child that the setting she depends on is broken. On eyes, face, gestures and
131
+ * speech it is also the latch's default (ADR-0249) — read by the root at every command, not here.
141
132
  *
142
133
  * A FUNCTION, not a value (as in ADR-0084): an answer read once goes stale silently, and with one `createGame` serving
143
134
  * several cartridges (ADR-0142) the icon would describe the game that booted first.
@@ -188,6 +179,11 @@ export declare function rootThatActs(rootEl: readonly PauseMenuButton[], options
188
179
  * counted in «N de M» (a locked one is not hidden — it is reached, and says why).
189
180
  */
190
181
  export declare const PM_VISIBLE_ITEMS = ".pause-menu:not([hidden]) .pm-btn:not([hidden])";
182
+ /**
183
+ * The other direction of the same table: the item of the root that opens each list. It is where «back» puts the cursor
184
+ * (ADR-0130 rule 1), and the name a list is announced by (`ui/where-the-child-is`). The root has no door.
185
+ */
186
+ export declare const LIST_DOOR: Readonly<Record<PauseSub, string | null>>;
191
187
  /**
192
188
  * Switches the visible list of ONE pause card, and puts the cursor on the FIRST item of the list that came in.
193
189
  *
@@ -195,6 +191,23 @@ export declare const PM_VISIBLE_ITEMS = ".pause-menu:not([hidden]) .pm-btn:not([
195
191
  * access to the button tables. Every list already exists in the markup, so the switch is DOM only.
196
192
  */
197
193
  export declare function showPauseOptions(sp: HTMLElement, sub: PauseSub): HTMLElement | null;
194
+ /**
195
+ * PUTS THE CARD'S CURSOR ON `el` — the one place every move of it goes through, whatever moved it: the arrows and the pad
196
+ * (`ui/menu-nav`), a finger, a held press, a door between lists, «back». The card selects by class, not by browser focus,
197
+ * so nothing scrolls it by itself: an item past the visible part of a long list would be marked where the child cannot see
198
+ * it. So the list is scrolled HERE, inside the card, until the item is wholly in view (issue #134, `keepInView`).
199
+ */
200
+ export declare function markPauseItem(sp: HTMLElement, el: HTMLElement | null | undefined): void;
201
+ /**
202
+ * «BACK» FROM A LIST OF THE CARD: the root comes in with the cursor on THE ITEM THAT OPENED the list being left, not on
203
+ * the root's first item (ADR-0130 rule 1). A child who went into «Opções», looked, and came back is where she was; landing
204
+ * on «Voltar» would send her walking the root again to find her place, and a child who cannot see the mark would not even
205
+ * know she had been moved.
206
+ *
207
+ * Opening the card is another question and keeps `showPauseOptions(sp, 'raiz')`: a card opens on item 1 (ADR-0158).
208
+ * A door that is gone or hidden leaves the cursor on the first item, which is all there is to return to.
209
+ */
210
+ export declare function backToRoot(sp: HTMLElement): HTMLElement | null;
198
211
  /**
199
212
  * WHAT AN INTENT MEANS INSIDE THE `accessibility` MODE (ADR-0044 item 7).
200
213
  *
@@ -230,7 +243,7 @@ export interface PauseIconsCtx {
230
243
  getPlayers: () => readonly unknown[];
231
244
  /** aria-live "polite" — every successful toggle announces its NEW state. */
232
245
  srSay: (text: string) => void;
233
- /** aria-live "assertive" — the refusals (a shared audio output, a device that always latches). */
246
+ /** aria-live "assertive" — the refusals (a shared audio output, no voice for the language). */
234
247
  srAlert: (text: string) => void;
235
248
  /**
236
249
  * Called after EVERY exit from the bar mode, with the screen and whether it was silent. Absent, nothing happens. It lets
@@ -277,7 +290,10 @@ export interface PauseIconsCtx {
277
290
  setCatGain: (k: string) => void;
278
291
  /** Repaints the TTS row of the auditory panel after the icon changes it. */
279
292
  reflectTtsPanel: () => void;
280
- /** Whether the TTS icon also calls `reflectTtsPanel`. `createGame` passes `true`; `false` leaves the panel as it was. */
293
+ /**
294
+ * Whether the TTS icon also calls `reflectTtsPanel`. `createGame` passes `false`: it repaints the panel from the mixer's
295
+ * `onCatChange`, which hears this icon's write like any other (ADR-0247). A host without that listener passes `true`.
296
+ */
281
297
  reflectTtsPanelEnabled: boolean;
282
298
  isLibrasOn: () => boolean;
283
299
  toggleLibras: () => void;
@@ -43,6 +43,7 @@ import { quickBarMarkup, screenPauseMarkup, } from './pause-markup.js';
43
43
  import { PAUSE_ICONS, pauseIcon } from '../core/pause-icon-catalogue.js';
44
44
  import { nextTheme, nextCorrection, hasHighContrast, DEFAULT_VISUAL, } from '../render/viz-axes.js';
45
45
  import { announceItem } from './item-announcement.js';
46
+ import { keepInView } from './menu-items.js';
46
47
  import { accessibleLabel } from '../core/accessible-label.js';
47
48
  import { stepInRing } from '../core/ring.js'; // from the LEAF, not from ui/menu-nav: see the note there
48
49
  // A LIVE BINDING (ESM): the index can be turned off in the menu, and the value here follows without a subscription.
@@ -64,22 +65,18 @@ export function inputModeOf(s) {
64
65
  return s.switchScan ? 'scan' : s.toggleMove ? 'sticky' : 'standard';
65
66
  }
66
67
  /**
67
- * The next position. Two things take one away, and neither is an error to report — a cycle simply never stops where nothing
68
- * would happen (ADR-0155), and a word that promised what this game cannot do would teach a child that her setting is broken
69
- * (ADR-0106 §5):
68
+ * The next position. A GAME THAT HOLDS NO KEY has nothing for the latch to hold, so the icon offers standard and one button:
69
+ * a cycle never stops where nothing would happen (ADR-0155), and a word that promised what this game cannot do would teach a
70
+ * child that her setting is broken (ADR-0106 §5).
70
71
  *
71
- * · A GAME THAT HOLDS NO KEY has nothing for the latch to hold, so the icon offers standard and one button.
72
- * · A DEVICE THAT SENDS ONE COMMAND AT A TIME always latches and cannot be asked not to (ADR-0113 clause 3, ADR-0211), so
73
- * `standard` is unreachable there. 🔴 That used to grey the whole icon out, and with three positions that would have cost a
74
- * child playing with her eyes the one-button scan as well — the lock is the latch's, not the scan's.
72
+ * 📌 THE DEVICE TAKES NOTHING AWAY (ADR-0249): on eyes, face, gestures and speech the latch starts as the game answers
73
+ * `holdsKeys()`, and the child may turn it on or off like on any other device — so all three positions are offered there too.
75
74
  */
76
- function inputModeOrder(holdsKeys, latchRequired = false) {
77
- if (!holdsKeys)
78
- return ['standard', 'scan'];
79
- return latchRequired ? ['sticky', 'scan'] : ['standard', 'sticky', 'scan'];
75
+ function inputModeOrder(holdsKeys) {
76
+ return holdsKeys ? ['standard', 'sticky', 'scan'] : ['standard', 'scan'];
80
77
  }
81
- export function nextInputMode(m, holdsKeys, latchRequired = false) {
82
- const order = inputModeOrder(holdsKeys, latchRequired);
78
+ export function nextInputMode(m, holdsKeys) {
79
+ const order = inputModeOrder(holdsKeys);
83
80
  const i = order.indexOf(m);
84
81
  return order[(i < 0 ? 0 : i + 1) % order.length];
85
82
  }
@@ -88,8 +85,8 @@ export function nextInputMode(m, holdsKeys, latchRequired = false) {
88
85
  import { nextGameSpeed } from '../core/game-speed.js';
89
86
  import { KEYS } from '../platform/storage-keys.js';
90
87
  import { setMoveLatch } from './settings-mobility.js';
91
- import { latchRefusal } from './latch-refusal.js';
92
88
  import { PM_BTNS, PM_OPTIONS_BTNS, PM_GAME_BTNS } from './pause-buttons.js';
89
+ import { aacMenuLocked } from './aac-sets.js';
93
90
  import { SCENE_KEYS, CHARACTER_ANIMATIONS, readStoredScene, storeScene } from './motion-scene.js';
94
91
  /**
95
92
  * Hover and focus on a bar's icons write its NAME in the bar's `.pause-icons-cap` and ask for its EXPLANATION.
@@ -187,9 +184,8 @@ const ICON_VISUAL = Object.freeze({
187
184
  tts: (s) => ({ on: s.ttsOn, dis: !s.privateOutput || !!s.noVoice }),
188
185
  libras: (s) => ({ on: s.librasOn }),
189
186
  tea: (s) => ({ on: s.calmMode === 2, calm: s.calmMode === 1 }),
190
- // ⚠️ AND IT IS NEVER GREYED OUT. `latchRequired` says the DEVICE in use sends one command at a time and the latch cannot be
191
- // turned off (ADR-0113 clause 3) — which the CYCLE tells, since `standard` simply does not appear in it. Greying the icon
192
- // would take the one-button scan away from the child playing with their eyes, who is the likeliest to need it.
187
+ // ⚠️ AND IT IS NEVER GREYED OUT: every device may be in any of its positions (ADR-0249), and what a game cannot hold the
188
+ // CYCLE tells by not offering it — greying the icon would take the one-button scan away with it.
193
189
  altmove: (s) => ({ on: inputModeOf(s) !== 'standard' }),
194
190
  contrast: (s) => ({ on: hasHighContrast(s.visual) }),
195
191
  velocidade: (s) => ({ on: (s.speed ?? 1) < 1 }),
@@ -252,17 +248,25 @@ const ENGINE_ITEMS = new Set(['options', 'opcoesdojogo', 'pmback', 'acessibilida
252
248
  * «Número de jogadores»: the GAME decides how many (ADR-0147) — and one general reason for the rest. Resolved at every
253
249
  * refresh, so it follows the language of the moment the card opens.
254
250
  */
255
- const OWN_REASONS = new Set(['ajuda', 'addplayer', 'opcoesdojogo']);
251
+ const OWN_REASONS = new Set(['ajuda', 'addplayer', 'opcoesdojogo', 'caa']);
256
252
  function itemReason(t, act) {
257
253
  return t(OWN_REASONS.has(act) ? `pause.motivo.${act}` : 'pause.motivo');
258
254
  }
255
+ /**
256
+ * A door the ENGINE keeps locked whatever the game hands over. Today one: `caa`, the AAC menu, disabled until a
257
+ * pictogram set is licensed (ADR-0233 erratum). Its reason is only «menu disabled» — no licence explanation reaches the
258
+ * child — and a game's own `caa` action does not unlock it.
259
+ */
260
+ function lockedByEngine(act) {
261
+ return act === 'caa' && aacMenuLocked();
262
+ }
259
263
  /**
260
264
  * THE MENU ITEMS THIS GAME CAN ACTUALLY ACTIVATE. The others are not hidden: they stay on the card, LOCKED with their
261
265
  * reason said (ADR-0161) — an item that does nothing when pressed, and says nothing, reads to a screen-reader user as an
262
266
  * item that does not exist.
263
267
  */
264
268
  function itemsThatAct(buttons, acts) {
265
- return buttons.filter((b) => ENGINE_ITEMS.has(b.act) || typeof acts[b.act] === 'function');
269
+ return buttons.filter((b) => !lockedByEngine(b.act) && (ENGINE_ITEMS.has(b.act) || typeof acts[b.act] === 'function'));
266
270
  }
267
271
  /**
268
272
  * THE ROOT LIST, with one more rule: `options` is a DOOR, and a door to an empty room acts no more than a dead button.
@@ -299,6 +303,11 @@ export const PM_VISIBLE_ITEMS = '.pause-menu:not([hidden]) .pm-btn:not([hidden])
299
303
  * row, not one more branch.
300
304
  */
301
305
  const DOOR_TO_LIST = Object.freeze({ options: 'opcoes', opcoesdojogo: 'jogo', pmback: 'raiz' });
306
+ /**
307
+ * The other direction of the same table: the item of the root that opens each list. It is where «back» puts the cursor
308
+ * (ADR-0130 rule 1), and the name a list is announced by (`ui/where-the-child-is`). The root has no door.
309
+ */
310
+ export const LIST_DOOR = Object.freeze({ raiz: null, opcoes: 'options', jogo: 'opcoesdojogo' });
302
311
  /**
303
312
  * Switches the visible list of ONE pause card, and puts the cursor on the FIRST item of the list that came in.
304
313
  *
@@ -308,11 +317,42 @@ const DOOR_TO_LIST = Object.freeze({ options: 'opcoes', opcoesdojogo: 'jogo', pm
308
317
  export function showPauseOptions(sp, sub) {
309
318
  sp.querySelectorAll('.pause-menu').forEach((m) => { m.hidden = m.dataset.sub !== sub; });
310
319
  const first = sp.querySelector(PM_VISIBLE_ITEMS);
311
- sp.querySelectorAll('.pm-sel,.pi-sel').forEach((b) => b.classList.remove('pm-sel', 'pi-sel'));
312
- if (first)
313
- first.classList.add('pm-sel');
320
+ sp.querySelectorAll('.pi-sel').forEach((b) => b.classList.remove('pi-sel'));
321
+ markPauseItem(sp, first);
314
322
  return first;
315
323
  }
324
+ /**
325
+ * PUTS THE CARD'S CURSOR ON `el` — the one place every move of it goes through, whatever moved it: the arrows and the pad
326
+ * (`ui/menu-nav`), a finger, a held press, a door between lists, «back». The card selects by class, not by browser focus,
327
+ * so nothing scrolls it by itself: an item past the visible part of a long list would be marked where the child cannot see
328
+ * it. So the list is scrolled HERE, inside the card, until the item is wholly in view (issue #134, `keepInView`).
329
+ */
330
+ export function markPauseItem(sp, el) {
331
+ if (!el)
332
+ return;
333
+ sp.querySelectorAll('.pm-sel').forEach((b) => b.classList.remove('pm-sel'));
334
+ el.classList.add('pm-sel');
335
+ keepInView(el);
336
+ }
337
+ /**
338
+ * «BACK» FROM A LIST OF THE CARD: the root comes in with the cursor on THE ITEM THAT OPENED the list being left, not on
339
+ * the root's first item (ADR-0130 rule 1). A child who went into «Opções», looked, and came back is where she was; landing
340
+ * on «Voltar» would send her walking the root again to find her place, and a child who cannot see the mark would not even
341
+ * know she had been moved.
342
+ *
343
+ * Opening the card is another question and keeps `showPauseOptions(sp, 'raiz')`: a card opens on item 1 (ADR-0158).
344
+ * A door that is gone or hidden leaves the cursor on the first item, which is all there is to return to.
345
+ */
346
+ export function backToRoot(sp) {
347
+ const left = sp.querySelector('.pause-menu:not([hidden])')?.dataset.sub;
348
+ const first = showPauseOptions(sp, 'raiz');
349
+ const door = left ? LIST_DOOR[left] : null;
350
+ const opener = door ? [...sp.querySelectorAll(PM_VISIBLE_ITEMS)].find((b) => b.dataset.act === door) : undefined;
351
+ if (!opener || opener === first)
352
+ return first;
353
+ markPauseItem(sp, opener);
354
+ return opener;
355
+ }
316
356
  export function barAction(k, hasStart) {
317
357
  if (hasStart || k.no)
318
358
  return 'sair';
@@ -395,8 +435,6 @@ export function initPauseIcons(ctx) {
395
435
  const setPauseActor = (i) => { if (ctx.setPauseActor)
396
436
  ctx.setPauseActor(i); };
397
437
  function hasPrivateOutput(i) { return hasPrivateOutputIn(P(), ctx.getNumPlayers(), i); }
398
- /** The latch refusal for this player now, or `null`. Recomputed: the device in use changes. */
399
- function refusalNow(i) { return ctx.transportInUse ? latchRefusal(ctx.transportInUse(i)) : null; }
400
438
  function iconState(i) {
401
439
  const p = P()[i] || {};
402
440
  const cat = ctx.getAudioCat();
@@ -415,7 +453,6 @@ export function initPauseIcons(ctx) {
415
453
  camera: ctx.settings.cameraControl,
416
454
  locale: ctx.translator.locale(),
417
455
  privateOutput: hasPrivateOutput(i),
418
- latchRequired: refusalNow(i) !== null,
419
456
  noVoice: !!ctx.noVoice?.(),
420
457
  };
421
458
  }
@@ -478,21 +515,15 @@ export function initPauseIcons(ctx) {
478
515
  altmove: (i) => {
479
516
  // verbatim: `players[i].toggleMove` with no `||{}` guard (unlike contrast/cvd below).
480
517
  const latched = !!P()[i].toggleMove;
481
- const next = nextInputMode(inputModeOf({ toggleMove: latched, switchScan: ctx.settings.switchScan }), ctx.holdsKeys(), refusalNow(i) !== null);
518
+ const next = nextInputMode(inputModeOf({ toggleMove: latched, switchScan: ctx.settings.switchScan }), ctx.holdsKeys());
482
519
  ctx.settings.setSwitchScanValue(next === 'scan');
483
520
  // 📌 ENTERING THE SCAN LEAVES THE LATCH WHERE SHE PUT IT — the scan wins in `inputModeOf`, so the position shown is never
484
521
  // ambiguous and coming back out returns her to the choice she had made. Leaving it writes the position she walked to.
485
522
  // ⚠️ And the latch writer ANNOUNCES BY ITSELF, so it is called only where it changes something: otherwise the child would
486
523
  // hear «não precisa segurar, desligado» when what ended was the scan.
524
+ // 📌 On every device, eyes and voice included (ADR-0249): the write goes under the transport in use, and that stored
525
+ // choice is what wins over the game's default the next time that transport presses.
487
526
  if (next !== 'scan' && latched !== (next === 'sticky')) {
488
- // ⚠️ THE REFUSAL, over the WRITE, which is the only thing it ever meant. The cycle above already skips `standard` on a
489
- // device that always latches, so this should be unreachable — and it stays because `iconAct` is exported and the two
490
- // rules could drift apart, which is the same reason the guards below give for not being belt and braces.
491
- const refusal = refusalNow(i);
492
- if (refusal) {
493
- ctx.srAlert(t(refusal.key));
494
- return;
495
- }
496
527
  setToggleMove(i, next === 'sticky');
497
528
  return;
498
529
  }
@@ -523,18 +554,26 @@ export function initPauseIcons(ctx) {
523
554
  if (face)
524
555
  ctx.srSay(t('sr.typo.font', { fam: face }));
525
556
  },
526
- // PLAYING THROUGH THE WEBCAM (ADR-0215): off → hands → face → eyes → off; stored in one key, so one mode at a time.
557
+ /*
558
+ * PLAYING THROUGH THE WEBCAM (ADR-0215) and BY SPEAKING (ADR-0189, issue #184): each writes the child's answer, one stored key.
559
+ *
560
+ * 🔴 A POSITION THAT STARTS A CONTROL IS ANNOUNCED BY THE CONTROL, NOT HERE. At this line nothing has started: the control
561
+ * starts after the write, asynchronously, and answers either way — «Pronto: já pode jogar…» when it is really on, or the
562
+ * reason assertively when it cannot start (and the answer goes back to off). Saying the position here told the child
563
+ * «Comando de voz: ligado» over a button that read «desligado», on every press where the microphone or the runtime failed.
564
+ * Off is true the moment it is written, so off is still said here.
565
+ */
527
566
  camera: () => {
528
567
  const v = nextCameraControl(ctx.settings.cameraControl);
529
568
  ctx.settings.setCameraControlValue(v);
530
- ctx.srSay(t('sr.icon.camera', { v: t(CAMERA_MODE_NAME[v]) }));
569
+ if (v === 'off')
570
+ ctx.srSay(t('sr.icon.camera', { v: t('state.off') }));
531
571
  },
532
- // PLAYING BY SPEAKING (ADR-0189, issue #184): on or off, one stored key. What cannot start puts it back to off and says why,
533
- // which is the control's own job (`ui/voice-control`) — this only writes the child's answer.
534
572
  voice: () => {
535
573
  const v = !ctx.settings.voiceControl;
536
574
  ctx.settings.setVoiceControlValue(v);
537
- ctx.srSay(t('sr.icon.voice', { v: t(v ? 'state.on' : 'state.off') }));
575
+ if (!v)
576
+ ctx.srSay(t('sr.icon.voice', { v: t('state.off') }));
538
577
  },
539
578
  // THE LANGUAGE (the Dev, 2026-09-16): the next flag; `setLocale` stores it and every surface redraws on `i18n:change`. Said in the NEW
540
579
  // language, once it has loaded.
@@ -566,9 +605,7 @@ export function initPauseIcons(ctx) {
566
605
  ctx.srAlert(t('sr.icon.needsPrivateOutput'));
567
606
  return;
568
607
  }
569
- // No blanket refusal for `altmove` here (ADR-0218): refusing the whole icon on a device that always latches would also
570
- // lock «one button only» away from the child playing with their eyes. The lock lives in the CYCLE, which skips
571
- // `standard` on that device, and the refusal lives inside the action, over the latch write only.
608
+ // No refusal for `altmove` (ADR-0249): no device locks the latch, and what a game cannot hold the CYCLE leaves out.
572
609
  const act = ICON_ACTS[k];
573
610
  if (act)
574
611
  act(i);
@@ -614,6 +651,25 @@ export function initPauseIcons(ctx) {
614
651
  // corrects it. 📌 Measured in a browser back when a guard skipped some of them: five icons in English and three still in
615
652
  // Portuguese, on the same bar.
616
653
  b.setAttribute('aria-label', computeIconLabel(t, k, st));
654
+ refreshCursorCaption(b);
655
+ }
656
+ /**
657
+ * THE NAME UNDER THE ROW FOLLOWS THE LABEL OF THE ICON UNDER THE CURSOR (ADR-0159 rule 10, ADR-0167). The caption is the
658
+ * visible half of what the cursor says, and the reflection is how a state changed ELSEWHERE reaches the icon — the 🦯, 📷
659
+ * and 👄 through the root's `stateOn`, the 🗣 through the mixer's `onCatChange` (ADR-0247). Written only on a move or a press,
660
+ * the caption kept the state the child had left while the label spoke the new one.
661
+ *
662
+ * Only the CURSOR's icon (`.pi-sel`, which lives only on the bars `getA11yBars` hands over): another icon's name under the
663
+ * row would move the cursor's only visible mark. And only when the text CHANGED: the per-screen bar's caption is a live
664
+ * region, and the same words rewritten are said again.
665
+ */
666
+ function refreshCursorCaption(b) {
667
+ if (!b.classList.contains('pi-sel'))
668
+ return;
669
+ const cap = ctx.getA11yBars().find((bar) => bar.contains(b))?.querySelector('.pause-icons-cap');
670
+ const text = accessibleLabel(b); // name and state only: «N de M» is spoken, never written (ADR-0167)
671
+ if (cap && cap.textContent !== text)
672
+ cap.textContent = text;
617
673
  }
618
674
  function reflectIconsIn(root, i) {
619
675
  if (!root)
@@ -694,11 +750,12 @@ export function initPauseIcons(ctx) {
694
750
  * with it (ADR-0044 item 3) and says how many items the new list has.
695
751
  */
696
752
  function announceList(sp, sub) {
697
- const first = showPauseOptions(sp, sub);
698
- if (!first)
753
+ // «back» lands on the door that opened the list being left (ADR-0130 rule 1); any other door, on the first item
754
+ const cursor = sub === 'raiz' ? backToRoot(sp) : showPauseOptions(sp, sub);
755
+ if (!cursor)
699
756
  return;
700
757
  const items = [...sp.querySelectorAll(PM_VISIBLE_ITEMS)];
701
- ctx.srSay(announceItem(t, { label: first.textContent || '', position: 1, total: items.length }, ctx.settings.menuIndexOn));
758
+ ctx.srSay(announceItem(t, { label: cursor.textContent || '', position: items.indexOf(cursor) + 1, total: items.length }, ctx.settings.menuIndexOn));
702
759
  }
703
760
  /* ===================== THE `accessibility` MODE (ADR-0044 item 7) ===================== */
704
761
  /**
@@ -824,6 +881,10 @@ export function initPauseIcons(ctx) {
824
881
  }
825
882
  /** One item of screen `i`'s pause card pressed — a locked one, a door between its lists, the quick bar, or an action. */
826
883
  function pressPauseItem(i, sp, b) {
884
+ // 🔴 THE CURSOR GOES WHERE THE CHILD PRESSED (ADR-0130 rule 1). A finger or a mouse opens a panel without walking the
885
+ // arrows, and the mark stayed on the item the arrows had last reached — so «back» from the panel returned her to an
886
+ // item she never chose. The keyboard's press is already on its item; this is the pointer's half.
887
+ markPauseItem(sp, b);
827
888
  // LOCKED (ADR-0161): pressing it SAYS the reason and does nothing — neither door nor action.
828
889
  if (b.getAttribute('aria-disabled') === 'true') {
829
890
  const reason = b.dataset.motivo ?? '';
@@ -1,8 +1,8 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
2
  import type { Action } from '../core/actions.js';
3
- import { type ScanItem } from '../input/switch-scan.js';
4
- /** What the chip reads for one item. Pure: the caller brings the game's labeller and the word for «take nothing». */
5
- export declare function scanItemText(item: ScanItem, label: (a: Action) => string | null, cancelWord: string): string;
3
+ import type { Translate } from '../core/i18n.js';
4
+ /** What the chip reads for one item. Pure: the engine's word where the item is the engine's, else the game's own word. */
5
+ export declare function scanItemText(item: string, label: (a: Action) => string | null, t: Translate): string;
6
6
  export interface ScanOverlay {
7
7
  /**
8
8
  * Puts the item on screen, and answers whether the words CHANGED. The same item twice does not rewrite the node — a live
@@ -9,9 +9,17 @@
9
9
  // «Confirmar» or «Dizer a resposta», never `action2` (ADR-0074). A position the game did not name is not offered at all — the
10
10
  // list is built from the named ones — because a scan that stopped on a word nobody wrote would cost the child a full pass.
11
11
  import { SCAN_CANCEL } from '../input/switch-scan.js';
12
- /** What the chip reads for one item. Pure: the caller brings the game's labeller and the word for «take nothing». */
13
- export function scanItemText(item, label, cancelWord) {
14
- return item === SCAN_CANCEL ? cancelWord : (label(item) ?? '');
12
+ /** The ENGINE's words, by dictionary key — the wording is an interface choice (interface log 2026-09-26). */
13
+ const ENGINE_WORDS = new Map([
14
+ [SCAN_CANCEL, 'scan.nothing'],
15
+ ['select', 'scan.door.menus'], ['start', 'scan.door.pause'],
16
+ ['next', 'scan.menu.next'], ['confirm', 'scan.menu.confirm'], ['back', 'scan.menu.back'], ['previous', 'scan.menu.previous'],
17
+ ['increase', 'scan.menu.increase'], ['decrease', 'scan.menu.decrease'],
18
+ ]);
19
+ /** What the chip reads for one item. Pure: the engine's word where the item is the engine's, else the game's own word. */
20
+ export function scanItemText(item, label, t) {
21
+ const key = ENGINE_WORDS.get(item);
22
+ return key ? t(key) : (label(item) ?? '');
15
23
  }
16
24
  /**
17
25
  * Mounts the chip inside the game's region, under the quick bar's band.
@@ -0,0 +1,25 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { Action } from '../core/actions.js';
3
+ import type { WorldScope } from '../core/contract.js';
4
+ /** What the reader is built over. Both are read at every call: a `mount()` swaps the cartridge, a panel opens. */
5
+ export interface ScreenTextCtx {
6
+ /** The root's document. */
7
+ readonly doc: Document;
8
+ /** The world the mounted game declares (`GameDeclaration.world`). */
9
+ readonly world: () => WorldScope;
10
+ }
11
+ /**
12
+ * WHAT IS ON THE SCREEN NOW, as the sonar reads it — `''` when there is nothing the engine can read: no world, an empty one,
13
+ * or a world drawn on a canvas.
14
+ */
15
+ export declare function screenText(ctx: ScreenTextCtx): string;
16
+ /**
17
+ * The `problems` line for a world the sonar cannot read — none, or one (ADR-0169: the subject, the child's cost, the fix).
18
+ * Measured at every read of `problems`: a canvas is often drawn after the boot.
19
+ */
20
+ export declare function unreadableWorldProblems(ctx: ScreenTextCtx): string[];
21
+ /**
22
+ * A press made WITH A MENU OPEN: the menu sonar's position reads what is in front (`read`) and takes the press. Any other
23
+ * position is left to the menus — answers whether this one was taken.
24
+ */
25
+ export declare function menuSonarPress(action: Action, ctx: ScreenTextCtx, read: (text: string) => void): boolean;
@@ -0,0 +1,152 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /**
3
+ * THE SONAR'S POSITION WHILE A MENU IS OPEN. In play the position is the game's (its `preset`); with a menu open the positions
4
+ * are the engine's, and there R1 reads the menu — the Dev's default for the sonar («Tecla padrão para o sonar deve ser R1»).
5
+ */
6
+ const MENU_SONAR = 'rightShoulder';
7
+ /** The engine's chrome the child did not ask about (see the header). */
8
+ const NOT_ASKED_ABOUT = [
9
+ '.pause-icons', '.screen-a11y', '.rodape-da-tela', '.scan-now', '#touch-controls', '.touch', '.sr-only',
10
+ '.gaze-overlay', '.face-overlay', '.hand-overlay', '.hud-row-clock',
11
+ ].join(', ');
12
+ /** What is open in front of the world. */
13
+ const DIALOG = '[role="dialog"], [role="alertdialog"], [aria-modal="true"]';
14
+ /** Elements whose content is not words on the screen (a `<select>` shows its chosen option only — see `spokenAs`). */
15
+ const NOT_WORDS = /^(script|style|template|noscript|svg|canvas|video|audio|iframe|object|option|optgroup)$/i;
16
+ /** A line that already ends a sentence is not given a full stop. */
17
+ const ENDS_A_SENTENCE = /[.!?…:;]$/u;
18
+ /**
19
+ * Is this one element drawn for the eye, and meant for everyone? Its ancestors are asked by whoever walks down to it.
20
+ * 📌 The RENDERING decides, not the `hidden` attribute: `hidden` hides by `display: none`, which is read here, and a stylesheet
21
+ * that shows a `[hidden]` element puts it on the screen.
22
+ */
23
+ function isShown(el) {
24
+ if (el.hasAttribute('inert') || el.getAttribute('aria-hidden') === 'true')
25
+ return false;
26
+ const style = el.ownerDocument?.defaultView?.getComputedStyle(el);
27
+ return !style || (style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0');
28
+ }
29
+ /** Is it drawn, with every ancestor drawn too? For an element found by a query rather than walked down to. */
30
+ function isOnScreen(el) {
31
+ for (let node = el; node; node = node.parentElement)
32
+ if (!isShown(node))
33
+ return false;
34
+ return true;
35
+ }
36
+ /**
37
+ * Can this node be walked and asked? A host double may hand elements without the DOM's questions; then there is nothing the
38
+ * engine can read, and it says nothing rather than throwing into the sonar (a capability of the environment, ADR-0169).
39
+ */
40
+ function canBeRead(el) {
41
+ return !!el && typeof el.matches === 'function' && typeof el.querySelectorAll === 'function' && typeof el.hasAttribute === 'function';
42
+ }
43
+ /** The dialog in front, or `null`: the one holding the focus, else the last one open in the document. */
44
+ function dialogInFront(doc) {
45
+ if (typeof doc.querySelectorAll !== 'function')
46
+ return null;
47
+ const open = [...doc.querySelectorAll(DIALOG)].filter((d) => canBeRead(d) && isOnScreen(d) && !d.matches(NOT_ASKED_ABOUT));
48
+ const focused = doc.activeElement;
49
+ return open.find((d) => !!focused && d.contains(focused)) ?? open[open.length - 1] ?? null;
50
+ }
51
+ /** The world's element, or `null` when the game declared none, its selector matches nothing, or it cannot be read. */
52
+ function worldElement(ctx) {
53
+ const scope = ctx.world();
54
+ const el = scope.kind === 'element' && typeof ctx.doc.querySelector === 'function' ? ctx.doc.querySelector(scope.selector) : null;
55
+ return canBeRead(el) ? el : null;
56
+ }
57
+ /** Does the world draw on a canvas the engine did not put there? Then its words are pixels. */
58
+ function drawsOnCanvas(world) {
59
+ const canvases = world.matches('canvas') ? [world] : [...world.querySelectorAll('canvas')];
60
+ return canvases.some((c) => !c.closest(NOT_ASKED_ABOUT) && isOnScreen(c));
61
+ }
62
+ /** The words an element stands for instead of its children, or `null` to read its children. */
63
+ function spokenAs(el) {
64
+ if (el.getAttribute('role') === 'img')
65
+ return el.getAttribute('aria-label') ?? '';
66
+ if (el.localName === 'img')
67
+ return el.getAttribute('alt') ?? '';
68
+ if (el.localName === 'select')
69
+ return el.selectedOptions[0]?.textContent ?? '';
70
+ return null;
71
+ }
72
+ /** Does the element start a line of its own? Everything that is not inline does — a button, a heading, a row. */
73
+ function breaksTheLine(el) {
74
+ const display = el.ownerDocument?.defaultView?.getComputedStyle(el).display ?? 'block';
75
+ return el.localName === 'br' || (display !== 'inline' && display !== 'contents');
76
+ }
77
+ /**
78
+ * The text under `root`, in DOM order, one line per block. Lines are joined as sentences, so the voice pauses between the
79
+ * statement and each option and the interpreter receives them apart.
80
+ */
81
+ function textOf(root) {
82
+ const lines = [];
83
+ let line = '';
84
+ const endLine = () => {
85
+ const words = line.replace(/\s+/gu, ' ').trim();
86
+ if (words)
87
+ lines.push(words);
88
+ line = '';
89
+ };
90
+ const readChildren = (el) => {
91
+ for (const child of el.childNodes ?? []) {
92
+ if (child.nodeType === 3)
93
+ line += child.textContent ?? '';
94
+ else if (child.nodeType === 1)
95
+ visit(child);
96
+ }
97
+ };
98
+ const visit = (el) => {
99
+ if (!isShown(el) || el.matches(NOT_ASKED_ABOUT) || NOT_WORDS.test(el.localName))
100
+ return;
101
+ const own = breaksTheLine(el);
102
+ if (own)
103
+ endLine();
104
+ const said = spokenAs(el);
105
+ if (said === null)
106
+ readChildren(el);
107
+ else
108
+ line += ` ${said} `;
109
+ if (own)
110
+ endLine();
111
+ };
112
+ visit(root);
113
+ endLine();
114
+ return lines.map((l) => (ENDS_A_SENTENCE.test(l) ? l : `${l}.`)).join(' ');
115
+ }
116
+ /**
117
+ * WHAT IS ON THE SCREEN NOW, as the sonar reads it — `''` when there is nothing the engine can read: no world, an empty one,
118
+ * or a world drawn on a canvas.
119
+ */
120
+ export function screenText(ctx) {
121
+ const front = dialogInFront(ctx.doc);
122
+ if (front)
123
+ return textOf(front);
124
+ const world = worldElement(ctx);
125
+ return world && !drawsOnCanvas(world) ? textOf(world) : '';
126
+ }
127
+ /**
128
+ * The `problems` line for a world the sonar cannot read — none, or one (ADR-0169: the subject, the child's cost, the fix).
129
+ * Measured at every read of `problems`: a canvas is often drawn after the boot.
130
+ */
131
+ export function unreadableWorldProblems(ctx) {
132
+ const world = worldElement(ctx);
133
+ if (!world || !drawsOnCanvas(world))
134
+ return [];
135
+ return ['the sonar cannot read this game\'s screen: its world draws on a <canvas>, whose words are pixels to the engine, so '
136
+ + 'a child who presses the sonar hears only the nearest target\'s name and side instead of what is written on screen — '
137
+ + 'and in deaf mode the interpreter signs that, not the screen (ADR-0234). Write the words the child must read as text '
138
+ + 'in the page, inside the world; a contract field for a game to hand the engine its screen\'s text is a decision the '
139
+ + 'Dev has not taken'];
140
+ }
141
+ /**
142
+ * A press made WITH A MENU OPEN: the menu sonar's position reads what is in front (`read`) and takes the press. Any other
143
+ * position is left to the menus — answers whether this one was taken.
144
+ */
145
+ export function menuSonarPress(action, ctx, read) {
146
+ if (action !== MENU_SONAR)
147
+ return false;
148
+ const text = screenText(ctx);
149
+ if (text)
150
+ read(text);
151
+ return true;
152
+ }
@@ -0,0 +1,32 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { Translate } from '../core/i18n.js';
3
+ /**
4
+ * What the clock needs, all lent by the root. The three host ports are REQUIRED and may carry `undefined` — a host without
5
+ * them is answered here, not with a branch in the root (ADR-0232 erratum of 2026-09-25): with no clock or no tick there is no
6
+ * session to measure, and nothing is mounted, silently, as a capability of the environment (ADR-0169).
7
+ */
8
+ export interface SessionClockCtx {
9
+ readonly doc: Document;
10
+ /** Where the clock goes: the HUD row's centre cell. Absent, or a node that takes no children, and nothing is mounted. */
11
+ readonly slot: HTMLElement | null;
12
+ readonly t: Translate;
13
+ /** The host's `performance`, milliseconds and monotonic. The session starts at the mount. */
14
+ readonly performance: {
15
+ now(): number;
16
+ } | undefined;
17
+ readonly setInterval: ((fn: () => void, ms: number) => number) | undefined;
18
+ readonly clearInterval: ((handle: number) => void) | undefined;
19
+ /** The polite announcer: said once, when the hour runs out. */
20
+ readonly announce: (text: string) => void;
21
+ /** Called after every redraw, so the root can measure the row again when the clock's size changed. */
22
+ readonly drawn: () => void;
23
+ }
24
+ export interface SessionClockMounted {
25
+ readonly el: HTMLElement;
26
+ /** Reads the time again and redraws what changed (the tick calls it; a language change too). */
27
+ refresh(): void;
28
+ /** Stops the tick and takes the clock off the page. */
29
+ remove(): void;
30
+ }
31
+ /** Mounts the clock in its slot, draws it now, and redraws it every second until `remove()`; `null` where it cannot. */
32
+ export declare function mountSessionClock(ctx: SessionClockCtx): SessionClockMounted | null;