@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
@@ -1,5 +1,11 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
2
  import type { NavKeys } from '../input/edges.js';
3
+ import type { Action } from '../core/actions.js';
4
+ /**
5
+ * The position a menu reads as «yes»: confirming the item under the cursor. Named once because a second reader presses it —
6
+ * the voice, which puts the cursor on an item said by name and confirms it the way every transport does (ADR-0194 §2).
7
+ */
8
+ export declare const MENU_CONFIRM: Action;
3
9
  /** Confirm/enter. `NumpadEnter` counts here (but does NOT pause — see `input/keydown`). */
4
10
  export declare const KEY_YES: ReadonlySet<string>;
5
11
  /** Back. ⚠️ DEFECT 2: `Escape` is the SAME intent as the gamepad's "special" action. */
@@ -13,6 +19,29 @@ export declare const KEY_RIGHT: ReadonlySet<string>;
13
19
  * player (a generic key).
14
20
  */
15
21
  export declare function menuKeyIntent(code: string, act: string | null): NavKeys;
22
+ /** A step of a menu one-button scanning offers — what a menu does, not a key (ADR-0218 erratum of 2026-09-26). */
23
+ export type MenuStep = 'next' | 'previous' | 'confirm' | 'back' | 'increase' | 'decrease';
24
+ /**
25
+ * WHAT ONE-BUTTON SCANNING OFFERS INSIDE A MENU, in order after «cancel» (interface log 2026-09-26). By how often a child needs
26
+ * each: every menu here is a ring, so «next» alone reaches every item, and it comes first; «confirm» ends every choice; «back»
27
+ * leaves — the quick pause, a sub-list, a panel, the card —; «previous» is a shortcut the ring already covers, so it waits last.
28
+ */
29
+ export declare const MENU_SCAN: readonly MenuStep[];
30
+ /**
31
+ * WHAT THE CONTROL UNDER A MENU'S CURSOR LETS A SIDEWAYS STEP DO — the question `ui/menu-nav` answers with the rule its left
32
+ * and right keys follow: `item` has no value (a button, a switch, the quick bar, the pause card); `list` is a `<select>`, whose
33
+ * value left and right step and whose «confirm» goes round; `value` is a slider or a ⯇ ⯈ steps control, which left and right
34
+ * adjust and «confirm» leaves untouched.
35
+ */
36
+ export type MenuCursor = 'item' | 'list' | 'value';
37
+ /**
38
+ * THE PASS IN A MENU, for the control under its cursor (ADR-0218 erratum; interface log 2026-09-26, the sideways step). Only a
39
+ * control with a value offers «increase» and «decrease» — they come right after «next», which keeps its place as the first
40
+ * step of every menu — and a slider or a steps control does not offer «confirm», which does nothing there: a cycle never
41
+ * stops on a position that does nothing (ADR-0155).
42
+ */
43
+ export declare function menuScanFor(cursor: MenuCursor): readonly MenuStep[];
44
+ export declare function menuStepKeys(step: MenuStep): NavKeys;
16
45
  /** `select` with left/right: one step, WITHOUT wrapping — adjusting a VALUE is not navigating a list. */
17
46
  export declare function selectStep(selectedIndex: number, optionsLen: number, delta: number): number;
18
47
  /** `select` with "yes": one step, WITH wrapping. That is the deliberate difference between confirming and adjusting. */
@@ -1,5 +1,10 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
2
  import { stepInRing } from '../core/ring.js';
3
+ /**
4
+ * The position a menu reads as «yes»: confirming the item under the cursor. Named once because a second reader presses it —
5
+ * the voice, which puts the cursor on an item said by name and confirms it the way every transport does (ADR-0194 §2).
6
+ */
7
+ export const MENU_CONFIRM = 'action2';
3
8
  /* The GENERIC key tables (the ones that hold for any player, even with no remap). They are `Set`s because that is
4
9
  what they are — membership, not order — and because a named `Set` keeps the table auditable from outside (the
5
10
  test imports the constant instead of repeating the literals). */
@@ -17,7 +22,7 @@ export const KEY_RIGHT = new Set(['ArrowRight', 'KeyD']);
17
22
  */
18
23
  export function menuKeyIntent(code, act) {
19
24
  return {
20
- yes: KEY_YES.has(code) || act === 'action2',
25
+ yes: KEY_YES.has(code) || act === MENU_CONFIRM,
21
26
  no: KEY_NO.has(code) || act === 'action3',
22
27
  up: KEY_UP.has(code) || act === 'up',
23
28
  down: KEY_DOWN.has(code) || act === 'down',
@@ -25,6 +30,35 @@ export function menuKeyIntent(code, act) {
25
30
  right: KEY_RIGHT.has(code) || act === 'right',
26
31
  };
27
32
  }
33
+ /**
34
+ * WHAT ONE-BUTTON SCANNING OFFERS INSIDE A MENU, in order after «cancel» (interface log 2026-09-26). By how often a child needs
35
+ * each: every menu here is a ring, so «next» alone reaches every item, and it comes first; «confirm» ends every choice; «back»
36
+ * leaves — the quick pause, a sub-list, a panel, the card —; «previous» is a shortcut the ring already covers, so it waits last.
37
+ */
38
+ export const MENU_SCAN = ['next', 'confirm', 'back', 'previous'];
39
+ /**
40
+ * THE PASS IN A MENU, for the control under its cursor (ADR-0218 erratum; interface log 2026-09-26, the sideways step). Only a
41
+ * control with a value offers «increase» and «decrease» — they come right after «next», which keeps its place as the first
42
+ * step of every menu — and a slider or a steps control does not offer «confirm», which does nothing there: a cycle never
43
+ * stops on a position that does nothing (ADR-0155).
44
+ */
45
+ export function menuScanFor(cursor) {
46
+ if (cursor === 'item')
47
+ return MENU_SCAN;
48
+ const sideways = ['next', 'increase', 'decrease'];
49
+ return cursor === 'list' ? [...sideways, 'confirm', 'back', 'previous'] : [...sideways, 'back', 'previous'];
50
+ }
51
+ /**
52
+ * The intent a step is — the one a key with that meaning would carry, so it moves a menu the way the key does. «increase» is the
53
+ * right key and «decrease» the left one: the same step a keyboard adjusts a value with.
54
+ */
55
+ const STEP_INTENT = {
56
+ next: { down: true }, previous: { up: true }, confirm: { yes: true }, back: { no: true },
57
+ increase: { right: true }, decrease: { left: true },
58
+ };
59
+ export function menuStepKeys(step) {
60
+ return { ...STEP_INTENT[step] };
61
+ }
28
62
  /** `select` with left/right: one step, WITHOUT wrapping — adjusting a VALUE is not navigating a list. */
29
63
  export function selectStep(selectedIndex, optionsLen, delta) {
30
64
  return Math.max(0, Math.min(optionsLen - 1, selectedIndex + delta));
@@ -8,3 +8,13 @@ export declare const ITEM_SELECTOR = "button:not([disabled]), select:not([disabl
8
8
  * cursor lands on an invisible control and the screen reader announces something nobody sees.
9
9
  */
10
10
  export declare function navigableItems(card: ParentNode): HTMLElement[];
11
+ /**
12
+ * KEEPS A MENU ITEM WHOLLY IN VIEW inside the box that scrolls it — the card, never the page (the Dev: the page itself
13
+ * does not scroll). A cursor that walks off the visible part of a list is a cursor the child has lost: this is what makes
14
+ * a list longer than the screen usable, whatever moved the cursor (issue #134, the Dev's «os menus não rolam»).
15
+ *
16
+ * The box's `scroll-padding` is honoured, so the band of explanation at the bottom of the screen, which the card runs
17
+ * under, never covers the item. Nothing moves when the item is already in view. A document with no layout (a node
18
+ * double) is left alone.
19
+ */
20
+ export declare function keepInView(item: HTMLElement): void;
@@ -5,6 +5,8 @@
5
5
  // copy of the selector is how «2 de 7» and the item the cursor is on drift apart. (Until ADR-0167 it also drew a
6
6
  // visible number per stop; that left — the place is heard, after the name.)
7
7
  //
8
+ // And how a stop is kept in view inside the card that scrolls it (`keepInView`), which every mover of a cursor shares.
9
+ //
8
10
  // 📌 A LEAF, importing nothing: `ui/mount-panel` needs the stops and must not pull `ui/menu-nav`, which pulls the
9
11
  // whole pause slice.
10
12
  /** The controls that are a stop of the cursor. `[data-passos]` is ONE stop: its arrows are finger targets. */
@@ -20,3 +22,30 @@ export function navigableItems(card) {
20
22
  }
21
23
  // 🔴 `numerarItens` AND `ATRIBUTO_DO_NUMERO` LEFT (ADR-0167): no stop shows a number. The list above still gives the
22
24
  // spoken index its position — «…, 2 de 7» after the name, from `ui/item-announcement.announceItem`.
25
+ /**
26
+ * KEEPS A MENU ITEM WHOLLY IN VIEW inside the box that scrolls it — the card, never the page (the Dev: the page itself
27
+ * does not scroll). A cursor that walks off the visible part of a list is a cursor the child has lost: this is what makes
28
+ * a list longer than the screen usable, whatever moved the cursor (issue #134, the Dev's «os menus não rolam»).
29
+ *
30
+ * The box's `scroll-padding` is honoured, so the band of explanation at the bottom of the screen, which the card runs
31
+ * under, never covers the item. Nothing moves when the item is already in view. A document with no layout (a node
32
+ * double) is left alone.
33
+ */
34
+ export function keepInView(item) {
35
+ const view = item.ownerDocument?.defaultView;
36
+ if (!view || typeof item.getBoundingClientRect !== 'function')
37
+ return;
38
+ let box = item.parentElement;
39
+ while (box && !(/(auto|scroll)/.test(view.getComputedStyle(box).overflowY) && box.scrollHeight > box.clientHeight))
40
+ box = box.parentElement;
41
+ if (!box || box === item.ownerDocument.documentElement || box === item.ownerDocument.body)
42
+ return;
43
+ const style = view.getComputedStyle(box);
44
+ const top = box.getBoundingClientRect().top + box.clientTop + (parseFloat(style.scrollPaddingTop) || 0);
45
+ const bottom = box.getBoundingClientRect().top + box.clientTop + box.clientHeight - (parseFloat(style.scrollPaddingBottom) || 0);
46
+ const r = item.getBoundingClientRect();
47
+ if (r.top < top)
48
+ box.scrollTop -= top - r.top;
49
+ else if (r.bottom > bottom)
50
+ box.scrollTop += Math.min(r.bottom - bottom, r.top - top); // a tall item shows its top
51
+ }
@@ -1,9 +1,6 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
- /** ui/dom.ts `$` — injected; the module never reaches `document`. */
3
- export type { DomQuery } from '../core/dom-query.js';
4
- /** The intent, not the key. Defined once in input/edges; only re-exported here. */
2
+ /** The intent, not the key. Defined once in input/edges. */
5
3
  import type { NavKeys } from '../input/edges.js';
6
- export type { NavKeys } from '../input/edges.js';
7
4
  /** What this module reads from a `KeyboardEvent`. */
8
5
  export interface NavKeyEvent {
9
6
  code: string;
@@ -13,6 +10,7 @@ export interface NavKeyEvent {
13
10
  import type { EventTargetLike } from '../input/touch-bindings.js';
14
11
  import type { DomQuery } from '../core/dom-query.js';
15
12
  import type { Translate } from '../core/i18n.js';
13
+ import { type MenuCursor } from './menu-intent.js';
16
14
  export { hasNavIntent as hasIntent } from '../input/edges.js';
17
15
  export { stepInRing } from '../core/ring.js';
18
16
  export interface MenuNavCtx {
@@ -72,6 +70,12 @@ export interface MenuNavCtx {
72
70
  closePadWiz: (save: boolean) => void;
73
71
  /** input/keyboard-runtime.ts: whose key is this? (-1 = generic). */
74
72
  whichPlayer: (code: string) => number;
73
+ /**
74
+ * Does player `i` steer this screen's menus — the dialog on top and the pause card? On a screen several players share, only
75
+ * the first does (ADR-0144, erratum of 2026-09-26): another player's direction, confirm and back move, press and close nothing.
76
+ * Absent: every player does — each has a screen of her own. The quick bar is asked per player already (`onBar`).
77
+ */
78
+ leadsTheScreen?: (i: number) => boolean;
75
79
  /** input/keyboard-runtime.ts: which action is this key FOR that player (honouring the remap)? */
76
80
  actionOf: (code: string, playerIndex: number) => string | null;
77
81
  /** Where `attach()` installs the menus' listeners, in CAPTURE. See `EventTargetLike` in input/touch-bindings. */
@@ -94,6 +98,38 @@ export interface MenuNavApi {
94
98
  navPause: (menu: HTMLElement, playerIndex: number, k: NavKeys) => void;
95
99
  /** The keyboard translator. Exported on its own so a test can fire it without depending on the propagation phase. */
96
100
  menuNavKey: (e: NavKeyEvent) => void;
101
+ /**
102
+ * Did the menus CONSUME this key event? `stopPropagation()` keeps it from the nodes further on, not from the listeners after
103
+ * `menuNavKey` on the same window capture — and the keyboard conductor is one. It asks here, so a key that moved, confirmed
104
+ * or CLOSED a menu is never also played in the same event (ADR-0111 erratum of 2026-09-26): with the menu already closed,
105
+ * «is a menu open?» answers no, and resume was answering a question too.
106
+ */
107
+ consumed: (e: object) => boolean;
108
+ /**
109
+ * ONE STEP OF A MENU WITH NO KEY: the intents move the menu a key of player `playerIndex` would move now — the quick bar,
110
+ * the dialog on top or that player's open pause card — through the same path and guards as `menuNavKey`. Answers whether a
111
+ * menu took it. One-button scanning is the caller (ADR-0218 erratum of 2026-09-26): its press is not a menu key.
112
+ */
113
+ navIntent: (playerIndex: number, keys: NavKeys) => boolean;
114
+ /**
115
+ * WHAT A SIDEWAYS STEP OF PLAYER `playerIndex` WOULD DO NOW (ADR-0218 erratum; one-button scanning offers «increase» and
116
+ * «decrease» only where they act): the kind of the control under the cursor of the panel on top — the focused item, or the
117
+ * first, where a step enters — under the guards a name said by voice has; `item` wherever a step would adjust nothing: the
118
+ * quick bar, a pause card, no menu, a remap, the controller-mapping panel.
119
+ */
120
+ underCursor: (playerIndex: number) => MenuCursor;
121
+ /**
122
+ * THE NAMES A CHILD CAN SAY (ADR-0194 §1): the accessible names of the items a key of player `playerIndex` would move now —
123
+ * the dialog on top, else that player's open pause card — and none while a key would move no menu. LOCKED items are in it
124
+ * (ADR-0194 §5): saying one confirms it like any transport, and a locked item confirmed says its reason and does nothing.
125
+ */
126
+ itemNames: (playerIndex: number) => string[];
127
+ /**
128
+ * PUTS THE CURSOR on the item with this accessible name, without activating it and without announcing it — the menu the
129
+ * name belongs to is the one `itemNames` reads. The caller then confirms through the virtual controller like any
130
+ * transport (ADR-0194 §2). `false` when no such item is there now: nothing moved.
131
+ */
132
+ pointAt: (name: string, playerIndex: number) => boolean;
97
133
  /** Installs `menuNavKey` on the window, in CAPTURE. */
98
134
  attach: () => void;
99
135
  }
@@ -51,7 +51,7 @@
51
51
  // ---------------------------------------------------------------------------------------------------------
52
52
  /** Was any intent expressed? Defined once in input/edges; here under the name this module has always used. */
53
53
  import { hasNavIntent as hasIntent } from '../input/edges.js';
54
- import { showPauseOptions, PM_VISIBLE_ITEMS } from './pause-icons.js';
54
+ import { backToRoot, markPauseItem, PM_VISIBLE_ITEMS } from './pause-icons.js';
55
55
  import { announceItem } from './item-announcement.js';
56
56
  import { accessibleLabel } from '../core/accessible-label.js';
57
57
  import { stepInRing } from '../core/ring.js';
@@ -103,6 +103,17 @@ const CONTROL_KINDS = [
103
103
  ];
104
104
  /** Where the items live: the dialog's card (`.overlay__card`) or the pause card (`.pause-card`). */
105
105
  const CARD_SELECTOR = '.overlay__card, .pause-card';
106
+ /**
107
+ * WHAT LEFT AND RIGHT DO TO A PANEL'S CONTROL — ONE answer, read by the keys (`sideways`, `confirm`) and by one-button scanning
108
+ * (`underCursor`), so a scan never offers «increase» where the right key would walk the ring instead: a `<select>` is a list, a
109
+ * slider (the only `<input>` an item can be, `ui/menu-items`) or a ⯇ ⯈ steps control (ADR-0151) is a value, anything else
110
+ * is an item.
111
+ */
112
+ function cursorKind(el) {
113
+ if (el.tagName === 'SELECT')
114
+ return 'list';
115
+ return el.tagName === 'INPUT' || el.hasAttribute('data-passos') ? 'value' : 'item';
116
+ }
106
117
  export function initMenuNav(ctx) {
107
118
  const { t } = ctx;
108
119
  /* ===================== settings dialogs ===================== */
@@ -168,6 +179,10 @@ export function initMenuNav(ctx) {
168
179
  */
169
180
  function sideways(items, idx, d) {
170
181
  const cur = items[idx];
182
+ if (cursorKind(cur) === 'item') {
183
+ focusAndSay(items, stepInRing(items.length, idx, d));
184
+ return;
185
+ }
171
186
  if (cur.tagName === 'SELECT') {
172
187
  const before = cur.value;
173
188
  tweakSelect(cur, d);
@@ -182,23 +197,19 @@ export function initMenuNav(ctx) {
182
197
  return;
183
198
  }
184
199
  // A steps control ⯇ ⯈ (ADR-0151): left and right are the adjustment itself; its owner listens to `passo` (and announces).
185
- if (cur.hasAttribute('data-passos')) {
186
- cur.dispatchEvent(new CustomEvent('passo', { detail: d, bubbles: true }));
187
- return;
188
- }
189
- focusAndSay(items, stepInRing(items.length, idx, d));
200
+ cur.dispatchEvent(new CustomEvent('passo', { detail: d, bubbles: true }));
190
201
  }
191
202
  /** «Yes»: a list goes round and says the new option; a slider or a steps control has nothing to confirm; anything else is clicked. */
192
203
  function confirm(items, idx) {
193
204
  const cur = items[idx];
194
- if (cur.tagName === 'SELECT') {
205
+ const kind = cursorKind(cur);
206
+ if (kind === 'list') {
195
207
  tweakSelect(cur, 'wrap');
196
208
  sayItem(items, idx);
197
209
  return;
198
210
  }
199
- if (cur.tagName === 'INPUT' || cur.hasAttribute('data-passos'))
200
- return;
201
- cur.click();
211
+ if (kind === 'item')
212
+ cur.click();
202
213
  }
203
214
  function navDialog(menu, k) {
204
215
  const items = menuItems(menu);
@@ -228,8 +239,7 @@ export function initMenuNav(ctx) {
228
239
  function pauseSetSel(menu, el) {
229
240
  if (!el)
230
241
  return; // an index outside the list (an empty card): touch nothing
231
- menu.querySelectorAll('.pm-sel').forEach((b) => b.classList.remove('pm-sel'));
232
- el.classList.add('pm-sel');
242
+ markPauseItem(menu, el); // the one place the card's cursor moves, and is kept in view (issue #134)
233
243
  }
234
244
  function navPause(menu, playerIndex, k) {
235
245
  if (k.no) {
@@ -238,8 +248,9 @@ export function initMenuNav(ctx) {
238
248
  // «is this the options list?»: with three lists (ADR-0146), asking the second would make «no» from the game's own
239
249
  // options resume the game when the child asked to go back.
240
250
  const open = menu.querySelector('.pause-menu:not([hidden])');
251
+ // ⚠️ AND THE CURSOR LANDS ON THE DOOR THAT OPENED THE LIST (ADR-0130 rule 1), not on the root's first item.
241
252
  if (open && open.dataset.sub && open.dataset.sub !== 'raiz') {
242
- showPauseOptions(menu, 'raiz');
253
+ backToRoot(menu);
243
254
  return;
244
255
  }
245
256
  ctx.setPhase('playing');
@@ -275,20 +286,21 @@ export function initMenuNav(ctx) {
275
286
  ctx.explainItem?.(reason || null);
276
287
  }
277
288
  /* ===================== keyboard ===================== */
289
+ const padWizOpen = () => { const pw = ctx.$('#padwiz'); return !!pw && !pw.hidden; };
278
290
  /** The controller-mapping panel sits ON TOP of everything: while it is open, only Escape gets through (and cancels). */
279
291
  function padWizKey(e) {
280
- const pw = ctx.$('#padwiz');
281
- if (!pw || pw.hidden)
292
+ if (!padWizOpen())
282
293
  return false;
283
294
  if (e.code === 'Escape') {
284
295
  ctx.closePadWiz(false);
285
- e.preventDefault();
286
- e.stopPropagation();
296
+ consume(e);
287
297
  }
288
298
  return true; // open = consumed (even when it is not Escape: no menu underneath navigates while it is open)
289
299
  }
290
- /** Consumes the key: it was ours, and nobody else should see it. One function, so the pair never comes apart. */
291
- const consume = (e) => { e.preventDefault(); e.stopPropagation(); };
300
+ /** The key events the menus consumed, for `consumed` — weak, so an event is forgotten with it. */
301
+ const consumedKeys = new WeakSet();
302
+ /** Consumes the key: it was ours, and nobody else should see it. One function, so the three never come apart. */
303
+ const consume = (e) => { e.preventDefault(); e.stopPropagation(); consumedKeys.add(e); };
292
304
  function menuNavKey(e) {
293
305
  if (ctx.isCapturing())
294
306
  return; // a remap in progress: the key is its
@@ -303,16 +315,54 @@ export function initMenuNav(ctx) {
303
315
  // through `input/gamepad`; the keyboard has no pair of its own for it, because Enter is already «confirm» and taking
304
316
  // it would leave the child no way to ACTIVATE the icon under the cursor.
305
317
  const { player, keys } = intentOf(e.code);
306
- if (ctx.onBar(player)) {
307
- if (hasIntent(keys)) {
308
- consume(e);
309
- ctx.navBar(player, keys);
310
- }
311
- return; // on the bar, a menu key belongs to the bar — with or without an intent, it does not reach the character
312
- }
313
- if (!ctx.isNavigable() || !hasIntent(keys))
318
+ // on the bar, a key with no intent is not consumed either — it simply moves nothing
319
+ if (!hasIntent(keys) || !menusTake(player))
314
320
  return;
315
- navOpenMenu(e, player, keys);
321
+ // THE KEY IS CONSUMED BEFORE THE MENU MOVES: a step that closes the last menu leaves no menu for a later listener to see
322
+ consume(e);
323
+ steerMenus(player, keys);
324
+ }
325
+ /**
326
+ * Would a key of player `pi` with an intent move a menu now? The quick bar first — it works with the game RUNNING — then the
327
+ * dialog on top or that player's open pause card, and only where a menu may be navigated.
328
+ *
329
+ * 📌 THE KEY IS CONSUMED ONLY IF THERE IS SOMETHING TO NAVIGATE. A game whose settings are always open answers
330
+ * `isNavigable(): true`, and consuming before knowing would swallow every key with a menu intent — the arrows that choose
331
+ * an answer in the quiz, for one. DEFECT 2's safety net stays: with a dialog open the key IS consumed, so one Escape closes
332
+ * the dialog and no later listener sees it.
333
+ */
334
+ function menusTake(pi) {
335
+ return ctx.onBar(pi) || (ctx.isNavigable() && !!menuUnderKeys(pi));
336
+ }
337
+ /** One step of the menu a key of `pi` moves: the bar, else the dialog on top, else that player's own pause card. */
338
+ function steerMenus(pi, k) {
339
+ if (ctx.onBar(pi)) {
340
+ ctx.navBar(pi, k);
341
+ return;
342
+ }
343
+ const open = menuUnderKeys(pi);
344
+ if (open?.inPause)
345
+ navPause(open.menu, pi, k);
346
+ else if (open)
347
+ navDialog(open.menu, k);
348
+ }
349
+ /**
350
+ * AN INTENT WITH NO KEY (ADR-0218 erratum of 2026-09-26): one-button scanning offers a menu's own steps and takes one with a
351
+ * press that is not a menu key — so it asks here, and the step goes the way a key's would, under the same guards.
352
+ * The controller-mapping panel on top takes only «back», which cancels it, as Escape does.
353
+ */
354
+ function navIntent(pi, k) {
355
+ if (ctx.isCapturing())
356
+ return false;
357
+ if (padWizOpen()) {
358
+ if (k.no)
359
+ ctx.closePadWiz(false);
360
+ return true;
361
+ }
362
+ if (!hasIntent(k) || !menusTake(pi))
363
+ return false;
364
+ steerMenus(pi, k);
365
+ return true;
316
366
  }
317
367
  /** Whose key this is — a key no player owns is Player 1's — and what it asks of a menu. */
318
368
  function intentOf(code) {
@@ -320,26 +370,60 @@ export function initMenuNav(ctx) {
320
370
  const player = owner < 0 ? 0 : owner;
321
371
  return { player, keys: menuKeyIntent(code, owner >= 0 ? ctx.actionOf(code, player) : null) };
322
372
  }
323
- /** The key moves the dialog on top if there is one, else the player's own pause card if it is open, else nothing. */
324
- function navOpenMenu(e, pi, k) {
325
- // THE KEY IS CONSUMED ONLY IF THERE IS SOMETHING TO NAVIGATE. A game whose settings are always open answers
326
- // `isNavigable(): true`, and consuming before knowing would swallow every key with a menu intent — the arrows that
327
- // choose an answer in the quiz, for one.
328
- //
329
- // DEFECT 2's safety net stays: with a dialog open the key IS consumed, so one Escape closes the dialog and no later
330
- // listener sees it.
373
+ /**
374
+ * The menu a key of player `pi` moves: the dialog on top if there is one, else that player's OWN open pause card — and none for
375
+ * a player who does not lead this screen (ADR-0144 erratum): the dialog on top is the screen's, not every seat's.
376
+ */
377
+ function menuUnderKeys(pi) {
378
+ if (ctx.leadsTheScreen && !ctx.leadsTheScreen(pi))
379
+ return null;
331
380
  const dlg = sharedDialogOpen();
332
- if (dlg) {
333
- consume(e);
334
- navDialog(dlg, k);
335
- return;
336
- } // an accessibility dialog is open: it is the one navigated
337
- const menu = ctx.getPauseMenu(pi); // otherwise: the player's OWN pause card
338
- if (menu && !menu.hidden) {
339
- consume(e);
340
- navPause(menu, pi, k);
381
+ if (dlg)
382
+ return { menu: dlg, inPause: false };
383
+ const menu = ctx.getPauseMenu(pi);
384
+ return menu && !menu.hidden ? { menu, inPause: true } : null;
385
+ }
386
+ /* ===================== an item said by name (ADR-0194) ===================== */
387
+ /**
388
+ * The stops of the menu a key of `pi` would move NOW, under the same guards `menuNavKey` applies before moving one: a name is
389
+ * sayable exactly where confirming would reach it, so the confirm that follows `pointAt` lands on the named item.
390
+ *
391
+ * 🔴 LOCKED STOPS INCLUDED (ADR-0194 §5, ADR-0161): the cursor stops on a locked item, so its name is sayable too — and what
392
+ * saying it does is what confirming it does, because it IS a confirm: the item's own press says its reason and acts on nothing
393
+ * (`ui/pause-icons.pressPauseItem`, a panel's refusal). No second, voice-only «say the reason» exists to drift from that one.
394
+ */
395
+ function sayableItems(pi) {
396
+ if (ctx.isCapturing() || padWizOpen() || ctx.onBar(pi) || !ctx.isNavigable())
397
+ return null;
398
+ const open = menuUnderKeys(pi);
399
+ if (!open)
400
+ return null;
401
+ return { ...open, items: open.inPause ? [...open.menu.querySelectorAll(PM_VISIBLE_ITEMS)] : menuItems(open.menu) };
402
+ }
403
+ function itemNames(pi) {
404
+ return (sayableItems(pi)?.items ?? []).map((el) => accessibleLabel(el)).filter(Boolean);
405
+ }
406
+ /** A panel's control under the cursor — where `navDialog` would put a step: the focused item, else the first (`cursorIn`). */
407
+ function underCursor(pi) {
408
+ const open = sayableItems(pi);
409
+ if (!open || open.inPause)
410
+ return 'item';
411
+ const at = open.items.indexOf(ctx.getActiveElement());
412
+ const el = open.items[at >= 0 ? at : 0];
413
+ return el ? cursorKind(el) : 'item';
414
+ }
415
+ function pointAt(name, pi) {
416
+ const open = sayableItems(pi);
417
+ const el = open?.items.find((it) => accessibleLabel(it) === name);
418
+ if (!open || !el)
419
+ return false;
420
+ if (open.inPause) {
421
+ pauseSetSel(open.menu, el);
422
+ ctx.explainItem?.(null);
341
423
  }
342
- // No dialog and no card: the key is NOT ours. It goes on to whoever owns it.
424
+ else
425
+ el.focus();
426
+ return true;
343
427
  }
344
428
  /* ===================== holding on an item ===================== */
345
429
  /*
@@ -413,5 +497,6 @@ export function initMenuNav(ctx) {
413
497
  ctx.win.addEventListener('pointercancel', onRelease, true);
414
498
  ctx.win.addEventListener('click', onClick, true);
415
499
  }
416
- return { sharedDialogOpen, menuItems, menuFocus, dialogBack, navDialog, pauseSetSel, navPause, menuNavKey, attach };
500
+ const consumed = (e) => consumedKeys.has(e);
501
+ return { sharedDialogOpen, menuItems, menuFocus, dialogBack, navDialog, pauseSetSel, navPause, menuNavKey, consumed, navIntent, underCursor, itemNames, pointAt, attach };
417
502
  }
@@ -112,5 +112,39 @@ export declare function nextStep(from: number, total: number, delta: number): nu
112
112
  * controller's left and right emit it through `ui/menu-nav`. The user listens to a single event.
113
113
  */
114
114
  export declare function mountSteps(ctx: PanelShellCtx, spec: StepsSpec): HTMLElement;
115
+ /**
116
+ * How many positions still CYCLE. The Dev's number (ADR-0130 erratum, 2026-09-12): «Acima de cinco itens deve ser dropdown».
117
+ * Five or fewer are one row «◀ Label: value ▶» — every position is one step away and the row always says where it is;
118
+ * more than five, a dropdown, because a cycle that long is walked more than it is chosen.
119
+ */
120
+ export declare const MAX_CYCLE_POSITIONS = 5;
121
+ /** One position of an exclusive choice: what is stored, and what is shown and heard (already translated). */
122
+ export interface ChoicePosition {
123
+ readonly value: string;
124
+ readonly label: string;
125
+ }
126
+ /** An exclusive choice: its spoken name, its positions in order, and the value in use. */
127
+ export interface ChoiceSpec {
128
+ readonly label: string;
129
+ readonly values: readonly ChoicePosition[];
130
+ readonly current: string;
131
+ }
132
+ /**
133
+ * Builds the control for ONE EXCLUSIVE CHOICE, and its SIZE picks the control, not the panel's author (ADR-0130 rule 3):
134
+ * up to `MAX_CYCLE_POSITIONS` a steps control (`mountSteps`), more a `<select>`. A choice whose size depends on the moment —
135
+ * the game's named positions, the audio outputs plugged in — takes the control its size asks for NOW, drawn again when it
136
+ * changes.
137
+ *
138
+ * Both shapes answer the same way, so whoever builds a row does not branch: `pick(value)` runs when the child chooses, the
139
+ * value in use is in `data-value`, and a bubbling `change` follows the pick — a list that listens for `change` hears either.
140
+ * A value not among the positions shows the first, as a `<select>` does.
141
+ */
142
+ export declare function mountChoice(ctx: PanelShellCtx, spec: ChoiceSpec, pick: (value: string) => void): HTMLElement;
143
+ /**
144
+ * REWRITES a choice built by `mountChoice`: its name, its positions' words and the value in use — the redraw a panel does
145
+ * on every open, after a language change or when the value moved elsewhere. The SHAPE stays: a choice whose size can
146
+ * cross five is built again, not updated.
147
+ */
148
+ export declare function updateChoice(el: HTMLElement, spec: ChoiceSpec): void;
115
149
  /** Reflects the current position: the written value, what is heard, and the ends that no longer move. */
116
150
  export declare function updateSteps(el: HTMLElement, spec: StepsSpec): void;
@@ -187,6 +187,92 @@ export function mountSteps(ctx, spec) {
187
187
  updateSteps(el, spec);
188
188
  return el;
189
189
  }
190
+ /* ===================== ONE EXCLUSIVE CHOICE, DRAWN BY ITS SIZE (ADR-0130 rule 3 and erratum) ===================== */
191
+ /**
192
+ * How many positions still CYCLE. The Dev's number (ADR-0130 erratum, 2026-09-12): «Acima de cinco itens deve ser dropdown».
193
+ * Five or fewer are one row «◀ Label: value ▶» — every position is one step away and the row always says where it is;
194
+ * more than five, a dropdown, because a cycle that long is walked more than it is chosen.
195
+ */
196
+ export const MAX_CYCLE_POSITIONS = 5;
197
+ /**
198
+ * Builds the control for ONE EXCLUSIVE CHOICE, and its SIZE picks the control, not the panel's author (ADR-0130 rule 3):
199
+ * up to `MAX_CYCLE_POSITIONS` a steps control (`mountSteps`), more a `<select>`. A choice whose size depends on the moment —
200
+ * the game's named positions, the audio outputs plugged in — takes the control its size asks for NOW, drawn again when it
201
+ * changes.
202
+ *
203
+ * Both shapes answer the same way, so whoever builds a row does not branch: `pick(value)` runs when the child chooses, the
204
+ * value in use is in `data-value`, and a bubbling `change` follows the pick — a list that listens for `change` hears either.
205
+ * A value not among the positions shows the first, as a `<select>` does.
206
+ */
207
+ export function mountChoice(ctx, spec, pick) {
208
+ if (spec.values.length <= MAX_CYCLE_POSITIONS) {
209
+ const steps = mountSteps(ctx, stepsOf(spec));
210
+ writeCycle(steps, spec);
211
+ steps.addEventListener('passo', (ev) => {
212
+ // read back from the element, never from this closure: an `updateChoice` since may have brought other words
213
+ const live = readCycle(steps);
214
+ const from = indexOfValue(live);
215
+ const to = nextStep(from, live.values.length, ev.detail);
216
+ if (to === from)
217
+ return; // at the wall nothing moved, and nothing is picked
218
+ const value = live.values[to].value;
219
+ writeCycle(steps, { ...live, current: value });
220
+ pick(value);
221
+ steps.dispatchEvent(new Event('change', { bubbles: true }));
222
+ });
223
+ return steps;
224
+ }
225
+ const list = ctx.create('select');
226
+ list.className = 'vol';
227
+ fillList(list, spec);
228
+ list.addEventListener('change', () => { list.dataset.value = list.value; pick(list.value); });
229
+ return list;
230
+ }
231
+ /** Where the current value sits among the positions; one not among them shows the first, as a `<select>` does. */
232
+ const indexOfValue = (s) => Math.max(0, s.values.findIndex((v) => v.value === s.current));
233
+ const stepsOf = (s) => ({ label: s.label, values: s.values.map((v) => v.label), current: indexOfValue(s) });
234
+ /**
235
+ * A cycle row keeps its whole choice ON THE ELEMENT — the positions in `data-positions`, the value in `data-value`, the
236
+ * name in `aria-label` — so the control carries no state of its own that a redraw could leave behind.
237
+ */
238
+ function writeCycle(el, s) {
239
+ el.dataset.positions = JSON.stringify(s.values);
240
+ el.dataset.value = s.current;
241
+ updateSteps(el, stepsOf(s));
242
+ }
243
+ function readCycle(el) {
244
+ return {
245
+ label: el.getAttribute('aria-label') ?? '',
246
+ values: JSON.parse(el.dataset.positions ?? '[]'),
247
+ current: el.dataset.value ?? '',
248
+ };
249
+ }
250
+ function fillList(list, s) {
251
+ // a `<select>` whose label is a sibling is announced as «combo box» and nothing else without its own name
252
+ list.setAttribute('aria-label', s.label);
253
+ while (list.firstChild)
254
+ list.removeChild(list.firstChild);
255
+ for (const v of s.values) {
256
+ const option = list.ownerDocument.createElement('option');
257
+ option.value = v.value;
258
+ option.textContent = v.label; // text, never markup: a game's word goes through here
259
+ list.appendChild(option);
260
+ }
261
+ list.value = s.current;
262
+ list.dataset.value = list.value;
263
+ }
264
+ /**
265
+ * REWRITES a choice built by `mountChoice`: its name, its positions' words and the value in use — the redraw a panel does
266
+ * on every open, after a language change or when the value moved elsewhere. The SHAPE stays: a choice whose size can
267
+ * cross five is built again, not updated.
268
+ */
269
+ export function updateChoice(el, spec) {
270
+ if (el.tagName === 'SELECT')
271
+ fillList(el, spec);
272
+ // read by the attribute `writeCycle` always writes, and not by `hasAttribute`: a host's minimal document may lack it
273
+ else if (el.dataset?.positions !== undefined)
274
+ writeCycle(el, spec);
275
+ }
190
276
  /** Reflects the current position: the written value, what is heard, and the ends that no longer move. */
191
277
  export function updateSteps(el, spec) {
192
278
  const lastIndex = Math.max(0, spec.values.length - 1);