@the-inclusionist/engine 9.0.0 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (532) hide show
  1. package/README.md +120 -78
  2. package/app/css/style.css +761 -377
  3. package/app/public/vendor/fonts/playwrite-cu.woff2 +0 -0
  4. package/app/public/vendor/fonts/playwrite-es-deco.woff2 +0 -0
  5. package/app/public/vendor/fonts/playwrite-es.woff2 +0 -0
  6. package/app/public/vendor/fonts/playwrite-gb-j.woff2 +0 -0
  7. package/app/public/vendor/fonts/playwrite-gb-s.woff2 +0 -0
  8. package/app/public/vendor/fonts/playwrite-pe.woff2 +0 -0
  9. package/app/public/vendor/fonts/playwrite-pt.woff2 +0 -0
  10. package/app/public/vendor/fonts-licences/NOTICE.txt +166 -0
  11. package/app/public/vendor/fonts-licences/OFL-1.1.txt +86 -0
  12. package/app/public/vendor/fonts.css +36 -84
  13. package/dist-pkg/boot/create-game.d.ts +499 -219
  14. package/dist-pkg/boot/create-game.js +3944 -562
  15. package/dist-pkg/core/a11y-sr.d.ts +25 -3
  16. package/dist-pkg/core/a11y-sr.js +38 -19
  17. package/dist-pkg/core/accessible-label.d.ts +17 -0
  18. package/dist-pkg/core/accessible-label.js +37 -0
  19. package/dist-pkg/core/accommodation-subjects.d.ts +26 -0
  20. package/dist-pkg/core/accommodation-subjects.js +32 -0
  21. package/dist-pkg/core/accommodations.d.ts +125 -0
  22. package/dist-pkg/core/accommodations.js +195 -0
  23. package/dist-pkg/core/actions.d.ts +108 -55
  24. package/dist-pkg/core/actions.js +122 -71
  25. package/dist-pkg/core/calm-mode.d.ts +31 -0
  26. package/dist-pkg/core/calm-mode.js +47 -0
  27. package/dist-pkg/core/camera-cycle.d.ts +15 -0
  28. package/dist-pkg/core/camera-cycle.js +7 -0
  29. package/dist-pkg/core/caption-duration.d.ts +15 -0
  30. package/dist-pkg/core/caption-duration.js +19 -0
  31. package/dist-pkg/core/cartridge-problems.d.ts +34 -0
  32. package/dist-pkg/core/cartridge-problems.js +53 -0
  33. package/dist-pkg/core/constants.d.ts +0 -54
  34. package/dist-pkg/core/constants.js +9 -76
  35. package/dist-pkg/core/contract.d.ts +142 -164
  36. package/dist-pkg/core/contract.js +222 -208
  37. package/dist-pkg/core/dom-query.d.ts +4 -4
  38. package/dist-pkg/core/dom-query.js +12 -20
  39. package/dist-pkg/core/entity.d.ts +62 -77
  40. package/dist-pkg/core/entity.js +18 -32
  41. package/dist-pkg/core/escape-html.d.ts +9 -10
  42. package/dist-pkg/core/escape-html.js +16 -20
  43. package/dist-pkg/core/flash-threshold.d.ts +17 -0
  44. package/dist-pkg/core/flash-threshold.js +113 -0
  45. package/dist-pkg/core/game-speed.d.ts +11 -0
  46. package/dist-pkg/core/game-speed.js +16 -0
  47. package/dist-pkg/core/genres.d.ts +20 -0
  48. package/dist-pkg/core/genres.js +44 -0
  49. package/dist-pkg/core/i18n.d.ts +71 -60
  50. package/dist-pkg/core/i18n.js +186 -200
  51. package/dist-pkg/core/loop.d.ts +17 -6
  52. package/dist-pkg/core/loop.js +26 -26
  53. package/dist-pkg/core/pause-icon-catalogue.d.ts +15 -0
  54. package/dist-pkg/core/pause-icon-catalogue.js +54 -0
  55. package/dist-pkg/core/ring.d.ts +17 -0
  56. package/dist-pkg/core/ring.js +29 -0
  57. package/dist-pkg/core/rng.d.ts +7 -12
  58. package/dist-pkg/core/rng.js +23 -53
  59. package/dist-pkg/core/route.d.ts +26 -26
  60. package/dist-pkg/core/route.js +126 -121
  61. package/dist-pkg/core/scenes.d.ts +37 -40
  62. package/dist-pkg/core/scenes.js +41 -43
  63. package/dist-pkg/core/screens.d.ts +4 -4
  64. package/dist-pkg/core/screens.js +11 -16
  65. package/dist-pkg/core/session-clock.d.ts +23 -0
  66. package/dist-pkg/core/session-clock.js +37 -0
  67. package/dist-pkg/core/setting-defaults.d.ts +61 -0
  68. package/dist-pkg/core/setting-defaults.js +66 -0
  69. package/dist-pkg/core/speech-rate.d.ts +29 -0
  70. package/dist-pkg/core/speech-rate.js +47 -0
  71. package/dist-pkg/core/state.d.ts +118 -118
  72. package/dist-pkg/core/state.js +322 -297
  73. package/dist-pkg/core/visual-cycles.d.ts +30 -0
  74. package/dist-pkg/core/visual-cycles.js +52 -0
  75. package/dist-pkg/core/visual-state.d.ts +13 -0
  76. package/dist-pkg/core/visual-state.js +6 -0
  77. package/dist-pkg/educational/activities-registry.d.ts +1 -1
  78. package/dist-pkg/educational/activities-registry.js +9 -8
  79. package/dist-pkg/educational/adaptive-engine.d.ts +11 -11
  80. package/dist-pkg/educational/adaptive-engine.js +17 -17
  81. package/dist-pkg/educational/segment-bar.d.ts +12 -12
  82. package/dist-pkg/educational/segment-bar.js +9 -9
  83. package/dist-pkg/i18n/en.js +324 -223
  84. package/dist-pkg/i18n/es.js +323 -223
  85. package/dist-pkg/i18n/pt.js +348 -226
  86. package/dist-pkg/input/default-bindings.d.ts +37 -38
  87. package/dist-pkg/input/default-bindings.js +127 -144
  88. package/dist-pkg/input/devices.d.ts +7 -7
  89. package/dist-pkg/input/devices.js +17 -16
  90. package/dist-pkg/input/edges.d.ts +23 -32
  91. package/dist-pkg/input/edges.js +23 -28
  92. package/dist-pkg/input/empathy-filter.d.ts +22 -0
  93. package/dist-pkg/input/empathy-filter.js +34 -0
  94. package/dist-pkg/input/face-map.d.ts +96 -0
  95. package/dist-pkg/input/face-map.js +255 -0
  96. package/dist-pkg/input/face-signals.d.ts +40 -0
  97. package/dist-pkg/input/face-signals.js +48 -0
  98. package/dist-pkg/input/gamepad.d.ts +195 -145
  99. package/dist-pkg/input/gamepad.js +383 -556
  100. package/dist-pkg/input/gaze-cycle.d.ts +64 -0
  101. package/dist-pkg/input/gaze-cycle.js +133 -0
  102. package/dist-pkg/input/gaze-relative.d.ts +68 -0
  103. package/dist-pkg/input/gaze-relative.js +142 -0
  104. package/dist-pkg/input/hand-map.d.ts +28 -0
  105. package/dist-pkg/input/hand-map.js +142 -0
  106. package/dist-pkg/input/input-cooldown.d.ts +17 -0
  107. package/dist-pkg/input/input-cooldown.js +39 -0
  108. package/dist-pkg/input/key-default.d.ts +30 -0
  109. package/dist-pkg/input/key-default.js +60 -0
  110. package/dist-pkg/input/keyboard-runtime.d.ts +15 -21
  111. package/dist-pkg/input/keyboard-runtime.js +23 -27
  112. package/dist-pkg/input/keyboard.d.ts +68 -63
  113. package/dist-pkg/input/keyboard.js +83 -105
  114. package/dist-pkg/input/keydown.d.ts +123 -137
  115. package/dist-pkg/input/keydown.js +284 -289
  116. package/dist-pkg/input/latch-edge.d.ts +33 -21
  117. package/dist-pkg/input/latch-edge.js +34 -35
  118. package/dist-pkg/input/latch-scope.d.ts +51 -50
  119. package/dist-pkg/input/latch-scope.js +61 -82
  120. package/dist-pkg/input/latch-store.d.ts +26 -30
  121. package/dist-pkg/input/latch-store.js +42 -50
  122. package/dist-pkg/input/latch-sync.d.ts +29 -29
  123. package/dist-pkg/input/latch-sync.js +25 -25
  124. package/dist-pkg/input/latch.d.ts +14 -17
  125. package/dist-pkg/input/latch.js +29 -37
  126. package/dist-pkg/input/pad-defaults.d.ts +13 -16
  127. package/dist-pkg/input/pad-defaults.js +21 -28
  128. package/dist-pkg/input/pad-reading.d.ts +76 -0
  129. package/dist-pkg/input/pad-reading.js +169 -0
  130. package/dist-pkg/input/pad-wizard.d.ts +99 -0
  131. package/dist-pkg/input/pad-wizard.js +256 -0
  132. package/dist-pkg/input/pointer-space.d.ts +19 -20
  133. package/dist-pkg/input/pointer-space.js +27 -32
  134. package/dist-pkg/input/pointer.d.ts +39 -40
  135. package/dist-pkg/input/pointer.js +42 -45
  136. package/dist-pkg/input/state.d.ts +101 -89
  137. package/dist-pkg/input/state.js +54 -134
  138. package/dist-pkg/input/switch-scan.d.ts +59 -0
  139. package/dist-pkg/input/switch-scan.js +67 -0
  140. package/dist-pkg/input/synthetic-source.d.ts +42 -0
  141. package/dist-pkg/input/synthetic-source.js +57 -0
  142. package/dist-pkg/input/touch-bindings.d.ts +127 -116
  143. package/dist-pkg/input/touch-bindings.js +158 -173
  144. package/dist-pkg/input/touch.d.ts +147 -72
  145. package/dist-pkg/input/touch.js +362 -126
  146. package/dist-pkg/input/transport-in-use.d.ts +107 -0
  147. package/dist-pkg/input/transport-in-use.js +130 -0
  148. package/dist-pkg/input/transports.d.ts +90 -117
  149. package/dist-pkg/input/transports.js +104 -106
  150. package/dist-pkg/input/virtual-controller.d.ts +82 -0
  151. package/dist-pkg/input/virtual-controller.js +69 -0
  152. package/dist-pkg/input/vocabulary-migration.d.ts +50 -49
  153. package/dist-pkg/input/vocabulary-migration.js +80 -79
  154. package/dist-pkg/input/voice-map.d.ts +59 -0
  155. package/dist-pkg/input/voice-map.js +123 -0
  156. package/dist-pkg/platform/audio-ambient.d.ts +13 -2
  157. package/dist-pkg/platform/audio-ambient.js +14 -14
  158. package/dist-pkg/platform/audio-earcons.d.ts +20 -19
  159. package/dist-pkg/platform/audio-earcons.js +51 -42
  160. package/dist-pkg/platform/audio-jingles.js +9 -10
  161. package/dist-pkg/platform/audio-mixer.d.ts +14 -2
  162. package/dist-pkg/platform/audio-mixer.js +31 -39
  163. package/dist-pkg/platform/audio-sonar.d.ts +49 -152
  164. package/dist-pkg/platform/audio-sonar.js +124 -302
  165. package/dist-pkg/platform/audio.d.ts +62 -19
  166. package/dist-pkg/platform/audio.js +193 -168
  167. package/dist-pkg/platform/choose-by-voice.d.ts +70 -0
  168. package/dist-pkg/platform/choose-by-voice.js +157 -0
  169. package/dist-pkg/platform/flash-sampler.d.ts +19 -0
  170. package/dist-pkg/platform/flash-sampler.js +79 -0
  171. package/dist-pkg/platform/font-library.d.ts +77 -0
  172. package/dist-pkg/platform/font-library.js +98 -0
  173. package/dist-pkg/platform/font-library.json +721 -0
  174. package/dist-pkg/platform/heavy-catalogue.d.ts +77 -0
  175. package/dist-pkg/platform/heavy-catalogue.js +256 -0
  176. package/dist-pkg/platform/heavy-mirror.d.ts +20 -0
  177. package/dist-pkg/platform/heavy-mirror.js +73 -0
  178. package/dist-pkg/platform/heavy.d.ts +136 -0
  179. package/dist-pkg/platform/heavy.js +324 -0
  180. package/dist-pkg/platform/interruptible-speech.d.ts +23 -16
  181. package/dist-pkg/platform/interruptible-speech.js +77 -54
  182. package/dist-pkg/platform/kokoro-port.d.ts +27 -0
  183. package/dist-pkg/platform/kokoro-port.js +50 -0
  184. package/dist-pkg/platform/kokoro-runtime.d.ts +20 -0
  185. package/dist-pkg/platform/kokoro-runtime.js +52 -0
  186. package/dist-pkg/platform/kokoro.d.ts +70 -0
  187. package/dist-pkg/platform/kokoro.js +133 -0
  188. package/dist-pkg/platform/listener-scope.d.ts +12 -0
  189. package/dist-pkg/platform/listener-scope.js +90 -0
  190. package/dist-pkg/platform/locale-host.d.ts +14 -0
  191. package/dist-pkg/platform/locale-host.js +45 -0
  192. package/dist-pkg/platform/microphone.d.ts +51 -0
  193. package/dist-pkg/platform/microphone.js +118 -0
  194. package/dist-pkg/platform/onnx-runtime.d.ts +34 -0
  195. package/dist-pkg/platform/onnx-runtime.js +42 -0
  196. package/dist-pkg/platform/reading-in-worker.d.ts +64 -0
  197. package/dist-pkg/platform/reading-in-worker.js +125 -0
  198. package/dist-pkg/platform/reading-model.d.ts +94 -0
  199. package/dist-pkg/platform/reading-model.js +272 -0
  200. package/dist-pkg/platform/reading-runtime.d.ts +24 -0
  201. package/dist-pkg/platform/reading-runtime.js +164 -0
  202. package/dist-pkg/platform/reading-worker.d.ts +41 -0
  203. package/dist-pkg/platform/reading-worker.js +58 -0
  204. package/dist-pkg/platform/reading.d.ts +80 -0
  205. package/dist-pkg/platform/reading.js +136 -0
  206. package/dist-pkg/platform/speech-recognition.d.ts +60 -0
  207. package/dist-pkg/platform/speech-recognition.js +104 -0
  208. package/dist-pkg/platform/speech.d.ts +23 -1
  209. package/dist-pkg/platform/speech.js +29 -18
  210. package/dist-pkg/platform/storage-keys.d.ts +96 -0
  211. package/dist-pkg/platform/storage-keys.js +106 -0
  212. package/dist-pkg/platform/storage.d.ts +46 -111
  213. package/dist-pkg/platform/storage.js +104 -169
  214. package/dist-pkg/platform/tts.d.ts +85 -39
  215. package/dist-pkg/platform/tts.js +447 -127
  216. package/dist-pkg/platform/vision-loop.d.ts +23 -0
  217. package/dist-pkg/platform/vision-loop.js +56 -0
  218. package/dist-pkg/platform/vision.d.ts +132 -0
  219. package/dist-pkg/platform/vision.js +117 -0
  220. package/dist-pkg/platform/voice-listener.d.ts +40 -0
  221. package/dist-pkg/platform/voice-listener.js +88 -0
  222. package/dist-pkg/platform/voice-plan.d.ts +15 -98
  223. package/dist-pkg/platform/voice-plan.js +56 -143
  224. package/dist-pkg/platform/vosk-runtime.d.ts +96 -0
  225. package/dist-pkg/platform/vosk-runtime.js +151 -0
  226. package/dist-pkg/platform/vosk-vocabulary.d.ts +6 -0
  227. package/dist-pkg/platform/vosk-vocabulary.js +178 -0
  228. package/dist-pkg/render/canvas.d.ts +13 -7
  229. package/dist-pkg/render/canvas.js +13 -13
  230. package/dist-pkg/render/crt.d.ts +48 -12
  231. package/dist-pkg/render/crt.js +78 -80
  232. package/dist-pkg/render/cvd-matrices.d.ts +22 -18
  233. package/dist-pkg/render/cvd-matrices.js +37 -38
  234. package/dist-pkg/render/hc-role-data.d.ts +7 -7
  235. package/dist-pkg/render/hc-role-data.js +14 -14
  236. package/dist-pkg/render/high-contrast.d.ts +67 -56
  237. package/dist-pkg/render/high-contrast.js +226 -217
  238. package/dist-pkg/render/low-vision-drawing.d.ts +5 -0
  239. package/dist-pkg/render/low-vision-drawing.js +46 -0
  240. package/dist-pkg/render/lq-filter.d.ts +32 -20
  241. package/dist-pkg/render/lq-filter.js +59 -65
  242. package/dist-pkg/render/port.d.ts +86 -88
  243. package/dist-pkg/render/port.js +17 -23
  244. package/dist-pkg/render/screen-pipeline.d.ts +42 -42
  245. package/dist-pkg/render/screen-pipeline.js +80 -87
  246. package/dist-pkg/render/sprite-fx.d.ts +2 -1
  247. package/dist-pkg/render/sprite-fx.js +16 -14
  248. package/dist-pkg/render/viewports.d.ts +20 -15
  249. package/dist-pkg/render/viewports.js +83 -114
  250. package/dist-pkg/render/viz-axes-labels.d.ts +47 -0
  251. package/dist-pkg/render/viz-axes-labels.js +88 -0
  252. package/dist-pkg/render/viz-axes.d.ts +89 -86
  253. package/dist-pkg/render/viz-axes.js +128 -116
  254. package/dist-pkg/render/viz-modes.d.ts +28 -29
  255. package/dist-pkg/render/viz-modes.js +40 -45
  256. package/dist-pkg/render/viz-refusal.d.ts +32 -0
  257. package/dist-pkg/render/viz-refusal.js +57 -0
  258. package/dist-pkg/render/viz-setters.d.ts +104 -66
  259. package/dist-pkg/render/viz-setters.js +182 -191
  260. package/dist-pkg/ui/aac-sets.d.ts +45 -0
  261. package/dist-pkg/ui/aac-sets.js +41 -0
  262. package/dist-pkg/ui/audio-choices.d.ts +68 -0
  263. package/dist-pkg/ui/audio-choices.js +98 -0
  264. package/dist-pkg/ui/audio-rows-that-apply.d.ts +12 -0
  265. package/dist-pkg/ui/audio-rows-that-apply.js +43 -0
  266. package/dist-pkg/ui/camera-control.d.ts +53 -0
  267. package/dist-pkg/ui/camera-control.js +48 -0
  268. package/dist-pkg/ui/changed-mark.d.ts +9 -8
  269. package/dist-pkg/ui/changed-mark.js +22 -53
  270. package/dist-pkg/ui/control-choices.d.ts +37 -0
  271. package/dist-pkg/ui/control-choices.js +84 -0
  272. package/dist-pkg/ui/debug-panel.d.ts +59 -43
  273. package/dist-pkg/ui/debug-panel.js +68 -69
  274. package/dist-pkg/ui/declared-words.d.ts +21 -0
  275. package/dist-pkg/ui/declared-words.js +66 -0
  276. package/dist-pkg/ui/dom.d.ts +10 -14
  277. package/dist-pkg/ui/dom.js +10 -47
  278. package/dist-pkg/ui/drawing-problems.d.ts +25 -0
  279. package/dist-pkg/ui/drawing-problems.js +94 -0
  280. package/dist-pkg/ui/eye-control.d.ts +36 -0
  281. package/dist-pkg/ui/eye-control.js +178 -0
  282. package/dist-pkg/ui/face-control.d.ts +25 -0
  283. package/dist-pkg/ui/face-control.js +183 -0
  284. package/dist-pkg/ui/focus-trap.d.ts +39 -49
  285. package/dist-pkg/ui/focus-trap.js +56 -65
  286. package/dist-pkg/ui/fonts.d.ts +157 -68
  287. package/dist-pkg/ui/fonts.js +305 -115
  288. package/dist-pkg/ui/footer-scroll-driver.d.ts +14 -0
  289. package/dist-pkg/ui/footer-scroll-driver.js +201 -0
  290. package/dist-pkg/ui/footer-scroll.d.ts +38 -0
  291. package/dist-pkg/ui/footer-scroll.js +55 -0
  292. package/dist-pkg/ui/game-options.d.ts +47 -0
  293. package/dist-pkg/ui/game-options.js +158 -0
  294. package/dist-pkg/ui/gaze-overlay.d.ts +64 -0
  295. package/dist-pkg/ui/gaze-overlay.js +165 -0
  296. package/dist-pkg/ui/hand-control.d.ts +25 -0
  297. package/dist-pkg/ui/hand-control.js +143 -0
  298. package/dist-pkg/ui/help-panel.d.ts +87 -0
  299. package/dist-pkg/ui/help-panel.js +242 -0
  300. package/dist-pkg/ui/hud-bands.d.ts +62 -0
  301. package/dist-pkg/ui/hud-bands.js +146 -0
  302. package/dist-pkg/ui/hud-row.d.ts +34 -0
  303. package/dist-pkg/ui/hud-row.js +80 -0
  304. package/dist-pkg/ui/hud.d.ts +73 -73
  305. package/dist-pkg/ui/hud.js +94 -98
  306. package/dist-pkg/ui/item-announcement.d.ts +13 -12
  307. package/dist-pkg/ui/item-announcement.js +25 -26
  308. package/dist-pkg/ui/layout.d.ts +103 -28
  309. package/dist-pkg/ui/layout.js +117 -99
  310. package/dist-pkg/ui/libras-avatar-clip.d.ts +30 -0
  311. package/dist-pkg/ui/libras-avatar-clip.js +86 -0
  312. package/dist-pkg/ui/libras-avatar-load.d.ts +52 -0
  313. package/dist-pkg/ui/libras-avatar-load.js +134 -0
  314. package/dist-pkg/ui/libras-avatar-plan.d.ts +102 -0
  315. package/dist-pkg/ui/libras-avatar-plan.js +211 -0
  316. package/dist-pkg/ui/libras-avatar-player.d.ts +28 -0
  317. package/dist-pkg/ui/libras-avatar-player.js +226 -0
  318. package/dist-pkg/ui/libras-avatar-stage.d.ts +37 -0
  319. package/dist-pkg/ui/libras-avatar-stage.js +208 -0
  320. package/dist-pkg/ui/libras-glosses.d.ts +28 -0
  321. package/dist-pkg/ui/libras-glosses.js +161 -0
  322. package/dist-pkg/ui/locale-flags.d.ts +16 -0
  323. package/dist-pkg/ui/locale-flags.js +30 -0
  324. package/dist-pkg/ui/loop-crash.d.ts +17 -17
  325. package/dist-pkg/ui/loop-crash.js +33 -62
  326. package/dist-pkg/ui/menu-intent.d.ts +61 -0
  327. package/dist-pkg/ui/menu-intent.js +88 -0
  328. package/dist-pkg/ui/menu-items.d.ts +20 -0
  329. package/dist-pkg/ui/menu-items.js +51 -0
  330. package/dist-pkg/ui/menu-nav.d.ts +98 -92
  331. package/dist-pkg/ui/menu-nav.js +398 -286
  332. package/dist-pkg/ui/mobility-choices.d.ts +26 -0
  333. package/dist-pkg/ui/mobility-choices.js +41 -0
  334. package/dist-pkg/ui/motion-choices.d.ts +31 -0
  335. package/dist-pkg/ui/motion-choices.js +68 -0
  336. package/dist-pkg/ui/motion-scene.d.ts +24 -19
  337. package/dist-pkg/ui/motion-scene.js +29 -61
  338. package/dist-pkg/ui/mount-panel.d.ts +81 -0
  339. package/dist-pkg/ui/mount-panel.js +61 -0
  340. package/dist-pkg/ui/panel-shell.d.ts +65 -31
  341. package/dist-pkg/ui/panel-shell.js +92 -66
  342. package/dist-pkg/ui/panel-widgets.d.ts +150 -0
  343. package/dist-pkg/ui/panel-widgets.js +294 -0
  344. package/dist-pkg/ui/pause-buttons.d.ts +35 -0
  345. package/dist-pkg/ui/pause-buttons.js +66 -0
  346. package/dist-pkg/ui/pause-icons.d.ts +261 -347
  347. package/dist-pkg/ui/pause-icons.js +671 -601
  348. package/dist-pkg/ui/pause-markup.d.ts +86 -0
  349. package/dist-pkg/ui/pause-markup.js +125 -0
  350. package/dist-pkg/ui/reach-notice.d.ts +22 -23
  351. package/dist-pkg/ui/reach-notice.js +55 -58
  352. package/dist-pkg/ui/scan-overlay.d.ts +23 -0
  353. package/dist-pkg/ui/scan-overlay.js +50 -0
  354. package/dist-pkg/ui/screen-text.d.ts +25 -0
  355. package/dist-pkg/ui/screen-text.js +152 -0
  356. package/dist-pkg/ui/session-clock.d.ts +32 -0
  357. package/dist-pkg/ui/session-clock.js +67 -0
  358. package/dist-pkg/ui/settings-aac.d.ts +70 -0
  359. package/dist-pkg/ui/settings-aac.js +208 -0
  360. package/dist-pkg/ui/settings-audio.d.ts +97 -114
  361. package/dist-pkg/ui/settings-audio.js +493 -425
  362. package/dist-pkg/ui/settings-controls.d.ts +53 -96
  363. package/dist-pkg/ui/settings-controls.js +143 -215
  364. package/dist-pkg/ui/settings-empathy.d.ts +39 -30
  365. package/dist-pkg/ui/settings-empathy.js +53 -60
  366. package/dist-pkg/ui/settings-mobility.d.ts +192 -0
  367. package/dist-pkg/ui/settings-mobility.js +332 -0
  368. package/dist-pkg/ui/settings-motion.d.ts +91 -70
  369. package/dist-pkg/ui/settings-motion.js +287 -198
  370. package/dist-pkg/ui/settings-panel.d.ts +58 -45
  371. package/dist-pkg/ui/settings-panel.js +189 -94
  372. package/dist-pkg/ui/settings-typo.d.ts +29 -78
  373. package/dist-pkg/ui/settings-typo.js +158 -129
  374. package/dist-pkg/ui/settings-visual.d.ts +29 -87
  375. package/dist-pkg/ui/settings-visual.js +240 -193
  376. package/dist-pkg/ui/shell.d.ts +112 -135
  377. package/dist-pkg/ui/shell.js +148 -217
  378. package/dist-pkg/ui/simulation-list.d.ts +27 -0
  379. package/dist-pkg/ui/simulation-list.js +76 -0
  380. package/dist-pkg/ui/simulation-over-the-world.d.ts +23 -0
  381. package/dist-pkg/ui/simulation-over-the-world.js +131 -0
  382. package/dist-pkg/ui/simulation-refusal.d.ts +5 -31
  383. package/dist-pkg/ui/simulation-refusal.js +8 -56
  384. package/dist-pkg/ui/switchable-control.d.ts +11 -0
  385. package/dist-pkg/ui/switchable-control.js +19 -0
  386. package/dist-pkg/ui/title.d.ts +3 -12
  387. package/dist-pkg/ui/title.js +49 -18
  388. package/dist-pkg/ui/top-band.d.ts +26 -0
  389. package/dist-pkg/ui/top-band.js +96 -0
  390. package/dist-pkg/ui/typo-choices.d.ts +71 -0
  391. package/dist-pkg/ui/typo-choices.js +85 -0
  392. package/dist-pkg/ui/visual-axes-panel.d.ts +11 -47
  393. package/dist-pkg/ui/visual-axes-panel.js +12 -94
  394. package/dist-pkg/ui/visual-choices.d.ts +77 -0
  395. package/dist-pkg/ui/visual-choices.js +107 -0
  396. package/dist-pkg/ui/vlibras.d.ts +77 -10
  397. package/dist-pkg/ui/vlibras.js +130 -81
  398. package/dist-pkg/ui/voice-control.d.ts +68 -0
  399. package/dist-pkg/ui/voice-control.js +276 -0
  400. package/dist-pkg/ui/voice-settings.d.ts +95 -0
  401. package/dist-pkg/ui/voice-settings.js +352 -0
  402. package/dist-pkg/ui/where-the-child-is.d.ts +31 -0
  403. package/dist-pkg/ui/where-the-child-is.js +70 -0
  404. package/docs/CREDITS.md +300 -44
  405. package/docs/LICENSES.md +189 -145
  406. package/package.json +51 -27
  407. package/scripts/check-cartridge.mjs +78 -0
  408. package/scripts/game-build.d.mts +25 -0
  409. package/scripts/game-build.mjs +144 -0
  410. package/scripts/heavy-into-the-delivery.mjs +388 -0
  411. package/scripts/libras-avatar.json +680 -0
  412. package/scripts/libras-avatar.mjs +246 -0
  413. package/scripts/libras-glosses/gloss.py +95 -0
  414. package/scripts/libras-glosses/pyproject.toml +32 -0
  415. package/scripts/libras-glosses/uv.lock +781 -0
  416. package/scripts/libras-glosses.mjs +351 -0
  417. package/scripts/licences/Apache-2.0.txt +176 -0
  418. package/scripts/licences/GPL-3.0.txt +674 -0
  419. package/scripts/licences/MIT.txt +17 -0
  420. package/scripts/licences/OFL-1.1.txt +86 -0
  421. package/scripts/licences/UFL-1.0.txt +96 -0
  422. package/scripts/licences/fonts.mjs +389 -0
  423. package/scripts/licences/third-party.mjs +301 -0
  424. package/scripts/licences/vosk-browser.NOTICE.txt +268 -0
  425. package/app/public/vendor/fonts/comicneue-400.woff2 +0 -0
  426. package/app/public/vendor/fonts/comicneue-700.woff2 +0 -0
  427. package/app/public/vendor/fonts/fondamento-400-ext.woff2 +0 -0
  428. package/app/public/vendor/fonts/fondamento-400.woff2 +0 -0
  429. package/app/public/vendor/fonts/inter-400.woff2 +0 -0
  430. package/app/public/vendor/fonts/inter-700.woff2 +0 -0
  431. package/app/public/vendor/fonts/lato-400.woff2 +0 -0
  432. package/app/public/vendor/fonts/lato-700.woff2 +0 -0
  433. package/app/public/vendor/fonts/literata-400.woff2 +0 -0
  434. package/app/public/vendor/fonts/literata-700.woff2 +0 -0
  435. package/app/public/vendor/fonts/newsreader-400.woff2 +0 -0
  436. package/app/public/vendor/fonts/newsreader-700.woff2 +0 -0
  437. package/app/public/vendor/fonts/opendyslexic-400.woff2 +0 -0
  438. package/app/public/vendor/fonts/opensans-400.woff2 +0 -0
  439. package/app/public/vendor/fonts/opensans-700.woff2 +0 -0
  440. package/app/public/vendor/fonts/pinyon-400.woff2 +0 -0
  441. package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
  442. package/app/public/vendor/fonts/quattro-400.woff2 +0 -0
  443. package/app/public/vendor/fonts/quattro-700.woff2 +0 -0
  444. package/app/public/vendor/fonts/sourcesans-400.woff2 +0 -0
  445. package/app/public/vendor/fonts/sourcesans-700.woff2 +0 -0
  446. package/app/public/vendor/fonts/sourceserif-400.woff2 +0 -0
  447. package/app/public/vendor/fonts/sourceserif-700.woff2 +0 -0
  448. package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
  449. package/dist-pkg/core/anel.d.ts +0 -17
  450. package/dist-pkg/core/anel.js +0 -31
  451. package/dist-pkg/core/collision.d.ts +0 -21
  452. package/dist-pkg/core/collision.js +0 -62
  453. package/dist-pkg/core/layers.d.ts +0 -63
  454. package/dist-pkg/core/layers.js +0 -95
  455. package/dist-pkg/core/letter-grid.d.ts +0 -55
  456. package/dist-pkg/core/letter-grid.js +0 -116
  457. package/dist-pkg/core/password.d.ts +0 -34
  458. package/dist-pkg/core/password.js +0 -205
  459. package/dist-pkg/core/rotulo-acessivel.d.ts +0 -17
  460. package/dist-pkg/core/rotulo-acessivel.js +0 -41
  461. package/dist-pkg/core/run-state.d.ts +0 -115
  462. package/dist-pkg/core/run-state.js +0 -40
  463. package/dist-pkg/core/tiles.d.ts +0 -12
  464. package/dist-pkg/core/tiles.js +0 -61
  465. package/dist-pkg/core/world.d.ts +0 -4
  466. package/dist-pkg/core/world.js +0 -58
  467. package/dist-pkg/input/origem-sintetica.d.ts +0 -44
  468. package/dist-pkg/input/origem-sintetica.js +0 -60
  469. package/dist-pkg/input/transporte-em-uso.d.ts +0 -101
  470. package/dist-pkg/input/transporte-em-uso.js +0 -130
  471. package/dist-pkg/platform/audio-nav.d.ts +0 -51
  472. package/dist-pkg/platform/audio-nav.js +0 -99
  473. package/dist-pkg/platform/guide-intensity.d.ts +0 -40
  474. package/dist-pkg/platform/guide-intensity.js +0 -73
  475. package/dist-pkg/platform/pesados-catalogo.d.ts +0 -14
  476. package/dist-pkg/platform/pesados-catalogo.js +0 -177
  477. package/dist-pkg/platform/pesados.d.ts +0 -31
  478. package/dist-pkg/platform/pesados.js +0 -75
  479. package/dist-pkg/platform/vozes-prontas.d.ts +0 -28
  480. package/dist-pkg/platform/vozes-prontas.js +0 -61
  481. package/dist-pkg/render/camera.d.ts +0 -58
  482. package/dist-pkg/render/camera.js +0 -105
  483. package/dist-pkg/render/cenario-data.d.ts +0 -127
  484. package/dist-pkg/render/cenario-data.js +0 -145
  485. package/dist-pkg/render/city-tex.d.ts +0 -63
  486. package/dist-pkg/render/city-tex.js +0 -228
  487. package/dist-pkg/render/city-tiles.d.ts +0 -11
  488. package/dist-pkg/render/city-tiles.js +0 -140
  489. package/dist-pkg/render/draw.d.ts +0 -149
  490. package/dist-pkg/render/draw.js +0 -231
  491. package/dist-pkg/render/fx.d.ts +0 -63
  492. package/dist-pkg/render/fx.js +0 -123
  493. package/dist-pkg/render/minimap.d.ts +0 -11
  494. package/dist-pkg/render/minimap.js +0 -94
  495. package/dist-pkg/render/parallax.d.ts +0 -85
  496. package/dist-pkg/render/parallax.js +0 -158
  497. package/dist-pkg/render/player-anim.d.ts +0 -71
  498. package/dist-pkg/render/player-anim.js +0 -152
  499. package/dist-pkg/render/recycling-tex.d.ts +0 -48
  500. package/dist-pkg/render/recycling-tex.js +0 -164
  501. package/dist-pkg/render/scene-city.d.ts +0 -77
  502. package/dist-pkg/render/scene-city.js +0 -181
  503. package/dist-pkg/render/scene-parallax.d.ts +0 -72
  504. package/dist-pkg/render/scene-parallax.js +0 -426
  505. package/dist-pkg/render/scene-sky.d.ts +0 -126
  506. package/dist-pkg/render/scene-sky.js +0 -295
  507. package/dist-pkg/render/set-cenario.d.ts +0 -54
  508. package/dist-pkg/render/set-cenario.js +0 -103
  509. package/dist-pkg/render/textures.d.ts +0 -75
  510. package/dist-pkg/render/textures.js +0 -240
  511. package/dist-pkg/render/title-scene.d.ts +0 -46
  512. package/dist-pkg/render/title-scene.js +0 -75
  513. package/dist-pkg/render/weather.d.ts +0 -96
  514. package/dist-pkg/render/weather.js +0 -171
  515. package/dist-pkg/render/wheelchair-sprites.d.ts +0 -32
  516. package/dist-pkg/render/wheelchair-sprites.js +0 -69
  517. package/dist-pkg/render/world-tex.d.ts +0 -18
  518. package/dist-pkg/render/world-tex.js +0 -87
  519. package/dist-pkg/ui/activities-menu.d.ts +0 -258
  520. package/dist-pkg/ui/activities-menu.js +0 -648
  521. package/dist-pkg/ui/caa-sets.d.ts +0 -39
  522. package/dist-pkg/ui/caa-sets.js +0 -65
  523. package/dist-pkg/ui/latch-refusal.d.ts +0 -32
  524. package/dist-pkg/ui/latch-refusal.js +0 -60
  525. package/dist-pkg/ui/map-hub.d.ts +0 -56
  526. package/dist-pkg/ui/map-hub.js +0 -138
  527. package/dist-pkg/ui/settings-caa.d.ts +0 -45
  528. package/dist-pkg/ui/settings-caa.js +0 -137
  529. package/dist-pkg/ui/settings-motor.d.ts +0 -175
  530. package/dist-pkg/ui/settings-motor.js +0 -342
  531. package/dist-pkg/ui/webcam.d.ts +0 -7
  532. package/dist-pkg/ui/webcam.js +0 -93
@@ -1,201 +1,132 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
- // ui/menu-nav.ts — NAVEGAÇÃO UNIVERSAL de menus e diálogos: andar, escolher e voltar sem mouse.
2
+ // ui/menu-nav.ts — UNIVERSAL menu and dialog navigation: move, choose and go back without a mouse.
3
3
  //
4
- // Este módulo é acessibilidade pura, e é o coração do pilar de a11y no que diz respeito a operação por
5
- // teclado (WCAG 2.1.1) e a ordem de foco (2.4.3). A regra que ele encarna é a do projeto, escrita no game.js
6
- // antes de qualquer código: QUALQUER menu aberto — o de pausa OU um submenu de configuração — é navegável
7
- // pelos MESMOS seis gestos, e esses seis gestos valem igualmente para teclado, controle, olhos e fala:
8
- // · cima/baixo/esquerda/direita — andam entre itens; nos `select`/`slider`, esquerda/direita AJUSTAM o valor
9
- // · sim — confirma, alterna, entra
10
- // · não — volta ao menu anterior; na raiz, volta ao jogo (= Continuar)
11
- // Por isso o módulo não fala em "tecla": ele fala em INTENÇÃO (`NavKeys`). O teclado é só um dos tradutores —
12
- // input/gamepad.ts monta exatamente o mesmo `{yes,no,up,down,left,right}` e chama `navDialog`/`navPause` daqui.
13
- // É essa forma comum que faz "o jogo inteiro é operável de seis maneiras" ser verdade por construção e não por
14
- // disciplina.
4
+ // Operation by keyboard (WCAG 2.1.1) and focus order (2.4.3). Any open menu — the pause card or a settings panel —
5
+ // answers the SAME six intents, whether they come from the keyboard, a controller, the eyes or speech:
6
+ // · up/down/left/right — move between items; on a list, a slider or a steps control, left/right ADJUST the value
7
+ // · yes — confirm, toggle, enter
8
+ // · no — back one level; at the pause card's root, back to the game
9
+ // So the module speaks of INTENT (`NavKeys`), never of keys. The keyboard is one translator (`menuNavKey`, below);
10
+ // `input/gamepad` builds the same `NavKeys` and calls `navDialog`/`navPause` from here. That shared shape is what makes
11
+ // «every menu works six ways» true by construction rather than by discipline.
15
12
  //
16
- // O QUE VEIO, E DE ONDE (bloco "NAVEGAÇÃO UNIVERSAL de menus" do game.js)
17
- // · `sharedDialogOpen` · `menuItems` · `menuFocus` · `dialogBack` — a leitura do "onde estou" e o voltar.
18
- // · `navDialog` — andar dentro de um diálogo de configuração (o único lugar que ajusta select/slider).
19
- // · `pauseSetSel` · `navPause` — andar dentro de um menu de pausa, que NÃO usa foco do navegador e sim uma
20
- // classe de seleção própria (`.pm-sel`/`.pi-sel`), porque ele é desenhado dentro da tela do jogador.
21
- // · `menuNavKey` — o tradutor de teclado, em fase de CAPTURA.
13
+ // TWO KINDS OF MENU. A settings panel selects by browser FOCUS (`navDialog`). The pause card is drawn inside the player's
14
+ // screen and selects by a CLASS (`.pm-sel`, `navPause`) — and a class fires no announcement, which is why the pause card
15
+ // speaks its items through `srSay` and a panel does not need to for its focus.
22
16
  //
23
- // A DUPLICAÇÃO QUE ESTA EXTRAÇÃO CUROU
24
- // `sharedDialogOpen` do game.js e `topVisibleOverlay` de ui/settings-panel.ts eram A MESMA FUNÇÃO escrita
25
- // duas vezes: mesmo seletor de escopo (`#game-region .overlay`), mesmo filtro (`!hidden`), mesma ordenação
26
- // estável por z-index efetivo, mesmo "vence o último". Conferi as duas linha a linha antes de unificar. Aqui
27
- // `sharedDialogOpen` é um ALIAS de `ctx.topVisibleOverlay` — o nome antigo sobrevive porque input/gamepad.ts o
28
- // consome por esse nome, mas o CÓDIGO agora existe uma vez só, em ui/settings-panel.ts. O ganho não é linha de
29
- // menos: enquanto eram duas, uma correção de escopo (é justamente o que o defeito 1 abaixo pede) podia ser
30
- // aplicada num lado e não no outro, e o sintoma seria "às vezes o foco volta".
17
+ // `sharedDialogOpen` is an ALIAS of `ui/settings-panel.topVisibleOverlay`, not a copy: the two were once the same function
18
+ // written twice, and a scope fix applied to one would not have reached the other.
31
19
  //
32
- // ======================= DOIS DEFEITOS CONHECIDOS — PRESERVADOS VERBATIM, NÃO CONSERTE =======================
20
+ // ======================= TWO KNOWN DEFECTS, PINNED BY TESTS — DO NOT FIX ONE WITHOUT THE OTHER =======================
33
21
  //
34
- // DEFEITO 1 — o escopo de `sharedDialogOpen` não alcança os menus de pausa (WCAG 2.4.3 · foco perdido).
35
- // O escopo é `#game-region .overlay`. Os nove diálogos de configuração estão lá (o `inCanvasMenus()` do game.js
36
- // reparenta os onze elementos para dentro do `#game-region`), MAS os menus de pausa por tela são
37
- // `.screen-pause` — classe diferente, construída por ui/pause-icons.ts, e nunca `.overlay`. Consequência:
38
- // `menuFocus(sharedDialogOpen())` devolve `null` quando o último diálogo se fecha, `menuFocus` sai pelo guarda
39
- // de nulo, e o foco fica no botão que acabou de ficar `hidden` — o navegador o joga no `<body>`. Quem sofre são
40
- // `#typo` (closeTypo) e `#help` (closeHelp), os dois únicos que devolvem o foco por esse caminho.
22
+ // DEFECT 1 — `sharedDialogOpen`'s scope does not see the pause card (WCAG 2.4.3, focus lost). The scope is
23
+ // `#game-region .overlay`; the pause card is `.screen-pause`, never `.overlay`. When the last dialog closes over an open
24
+ // pause card, `menuFocus(sharedDialogOpen())` receives `null`, leaves at its guard, and the focus stays on a button that
25
+ // has just become hidden — the browser drops it on `<body>`.
41
26
  //
42
- // O QUE EU VERIFIQUEI ANTES DE ESCREVER ISTO (a nota do coordenador estava certa, e a conclusão dela também):
43
- // li os NOVE `close*` e o `app/index.html` inteiro, um por um. De todos os nove, só UM devolve o foco de fato:
44
- // · touchcfg → `#opt-touchcfg` — EXISTE (index.html, dentro do painel #movement). Funciona.
45
- // · options → `#opt-controls` — NÃO existe no index.html. No-op silencioso.
46
- // · movement → `#opt-movement` — NÃO existe. No-op.
47
- // · visual → `#opt-visual` — NÃO existe. No-op.
48
- // · audio → `#opt-sound` — NÃO existe. No-op.
49
- // · animation → `#opt-animation` — NÃO existe (o próprio game.js já anota isso na linha do `#animation-close`).
50
- // · empathy → `#opt-empathy` — NÃO existe. No-op.
51
- // · typo/help → `menuFocus(sharedDialogOpen())` — o caminho quebrado descrito acima.
52
- // Os ids que EXISTEM no index.html com prefixo `opt-` são: opt-title, opt-telas, opt-letra, opt-facil,
53
- // opt-altmove, opt-touchcfg, opt-eyes, opt-modocego, opt-tts, opt-onebtn, opt-wheelchair, opt-hearing,
54
- // opt-captions. Nenhum `#opt-visual`/`#opt-sound`/`#opt-movement`/`#opt-controls`/`#opt-empathy`/`#opt-animation`
55
- // — são ganchos para uma barra de botões que ainda não existe. Ou seja: NÃO é "sete certos e dois errados". É
56
- // "um certo e oito quebrados, por duas causas diferentes" — e a causa dos oito (botão inexistente) não é a
57
- // mesma dos dois (escopo do seletor). Quem consertar precisa saber disso, porque arrumar só o escopo deixa oito
58
- // painéis ainda largando o foco.
27
+ // DEFECT 2 — Escape is resolved in CAPTURE, by z-index, and consumed with `stopPropagation()`. `menuNavKey` runs before
28
+ // any bubble listener on the window, so the topmost visible dialog is the one that closes, and the registration-order
29
+ // chain (`overlays.escapeTarget()`, read by `input/keydown`) never decides while a dialog is open. Escape is also the same
30
+ // intent as action3 (`no`): at the pause card's root it means «back to the game», and nothing tells «close a dialog» from
31
+ // «leave the pause» apart. The capture and the `stopPropagation()` are also what keep a later window listener — a game's
32
+ // own keydown — from acting on the same Escape: removing them without a replacement lets one key do two things.
59
33
  //
60
- // DEFEITO 2 — `menuNavKey` trata Escape como "voltar", em CAPTURA, com `stopPropagation()`, e resolve por
61
- // z-index em vez de pela cadeia registrada. `addEventListener('keydown', menuNavKey, true)` roda ANTES do
62
- // ouvinte principal do game.js (que é de BOLHA, na mesma janela); o `stopPropagation()` na fase de captura
63
- // impede o evento de descer e, portanto, de voltar — o ouvinte de bolha nunca vê a tecla. Efeitos verificados
64
- // lendo os dois ouvintes lado a lado:
65
- // (a) a cadeia `overlays.escapeTarget()` (ORDEM DE REGISTRO: options→movement→animation→visual→empathy→
66
- // audio→typo→touchcfg→help) é código morto com o jogo pausado. Quem decide é o topo da pilha de z-index.
67
- // O próprio game.js já anota isso ("MEDIDO no navegador") no ouvinte de bolha.
68
- // (b) `#help` e `#touchcfg` estão registrados com `inEscapeChain:false` e por isso ficam fora da cadeia de Escape.
69
- // Enquanto a captura os cobrir por z-index, Escape os fecha; se alguém tirar o `stopPropagation()` ou a
70
- // fase de captura sem antes dar flag aos dois, a tecla cai no ouvinte de bolha e o
71
- // `if(Escape||Enter) togglePause()` de lá DESPAUSA O JOGO com o diálogo de Ajuda ainda aberto.
72
- // Ou seja: o comportamento correto de hoje depende de um `stopPropagation()`, não da cadeia — é uma rede
73
- // de segurança acidental, e é exatamente o que o conserto vai mexer.
74
- // (c) Escape é a MESMA intenção que "especial" do gamepad (`no`). Não há como distinguir "fechar diálogo" de
75
- // "voltar ao jogo" — na raiz do menu de pausa, `no` faz `setPhase('playing')`.
76
- // `menuNavKey` é exportado por si só (e não só instalado) justamente para o teste poder dispará-lo sem depender
77
- // de fase de propagação, e `attach()` existe para o game.js instalar exatamente como antes.
34
+ // `tests/menu-nav.browser.test.js` and `tests/menu-nav.node.test.js` pin both as they are today, and fail on purpose if
35
+ // the region is «improved» without the whole fix. `menuNavKey` is exported on its own so a test can fire it without
36
+ // depending on the propagation phase; `attach()` installs it.
37
+ // ======================================================================================================================
78
38
  //
79
- // Os testes de `menu-nav` PINAM os dois comportamentos ACIMA como estão hoje. Eles falham de propósito se
80
- // alguém "melhorar" a região sem tratar o conserto inteiro — que é o ponto: o conserto futuro precisa da rede.
81
- // ============================================================================================================
39
+ // WHAT IS NOT HERE: the overlay stack, the registry and closing by id belong to `ui/settings-panel` — this module
40
+ // consumes `topVisibleOverlay` and `closeById` and reimplements nothing. The quick bar belongs to `ui/pause-icons`; this
41
+ // module only asks whether a screen is on it (`onBar`) and hands it a step (`navBar`).
82
42
  //
83
- // O QUE FICOU DE FORA, E POR QUÊ
84
- // · `updateTitleLegend` foi para ui/shell.ts, não para cá. Ela não navega nada — não lê foco, não trata tecla,
85
- // não anda entre itens; pinta o rodapé de UMA tela (o título) e quem a chama é `setPhase('title')`. Trazê-la
86
- // obrigaria a casca a importar o módulo de navegação só para desenhar uma legenda.
87
- // · `padKind()` não veio para lugar nenhum: JÁ ESTÁ em `input/touch.ts` (Onda A), com corpo idêntico, e a
88
- // cópia do game.js não tem chamador nenhum. É código morto duplicado — apagar, não mover.
89
- // · `navTitle`/`titleButtons` são de ui/activities-menu.ts (menu inicial), já extraídos.
90
- // · A pilha de z-index, o registro de overlays e o fechar por id são de ui/settings-panel.ts. Este módulo
91
- // CONSOME (`topVisibleOverlay`, `closeById`) e não reimplementa.
43
+ // INJECTION: nothing is reached. `isNavigable`, `withIndex` and `onBar` are QUESTIONS asked of the ctx rather than state
44
+ // read from `core/state`, so a game answers in its own model — a quiz whose settings are always open answers
45
+ // `isNavigable(): true` without pretending to be paused. `getPauseMenu`, `closePadWiz`, `isCapturing` and `setPhase` are
46
+ // resolved at call time, because what they reach is built in the root after this module.
92
47
  //
93
- // INJEÇÃO E ORDEM DE BOOT
94
- // · `phase` entra como binding vivo de core/state.ts (deixou de ser `let` do game.js na Fase 2) — mesmo
95
- // precedente de game/session.ts e input/touch.ts. O que AINDA é `let` do game.js entra por getter:
96
- // `vpPause` (`getPauseMenu(i)`), reatribuído por `buildGameHud`.
97
- // · `closePadWiz` é LAZY (`(save) => gamepadApi.closePadWiz(save)`): input/gamepad.ts é `const` do game.js e o
98
- // ctx DELE consome `sharedDialogOpen`/`navDialog`/`navPause` daqui. A dependência é mútua, e a única forma
99
- // de quebrar o ciclo sem reordenar o boot é ela ser resolvida na CHAMADA. Mesmo motivo para `isCapturing`
100
- // (ui/settings-controls.ts) e para `setPhase` (o envelope içado do game.js sobre ui/shell.ts).
101
- // · `actionOf`/`whichPlayer` entram já ligados a input/keyboard-runtime.ts, que é `const` declarado bem no
102
- // alto do game.js — mas entram como callback mesmo assim, porque o roteamento por jogador é dele e não daqui.
103
- //
104
- // SEM I/O NO IMPORT: o corpo do módulo só declara dados e funções puras. Todo efeito passa por `initMenuNav`.
105
- //
106
- // Ver docs/5-Refactoring/plano-modularizacao-mapa.md (C3).
48
+ // NO I/O ON IMPORT: the module body only declares data and pure functions. Every effect goes through `initMenuNav`.
107
49
  // ---------------------------------------------------------------------------------------------------------
108
- // PURO — a decisão de navegação, sem DOM. Testável no project `node`.
50
+ // PURE — the navigation decision, without a DOM. Testable in the `node` project.
109
51
  // ---------------------------------------------------------------------------------------------------------
110
- /* As tabelas de tecla GENÉRICA (as que valem para qualquer jogador, mesmo sem remap). Viraram `Set` porque é
111
- o que elas sempre foram semanticamente — pertinência, não ordem — e porque um `Set` nomeado deixa a tabela
112
- auditável de fora (o teste importa a constante em vez de repetir os literais). Conteúdo VERBATIM. */
113
- /** Confirma/entra. `NumpadEnter` conta aqui (mas NÃO pausa: ver o ouvinte de bolha do game.js). */
114
- export const KEY_YES = new Set(['Space', 'KeyJ', 'Enter', 'NumpadEnter']);
115
- /** Volta. ⚠️ DEFEITO 2: `Escape` é a MESMA intenção que a ação "especial" do gamepad. Verbatim. */
116
- export const KEY_NO = new Set(['Escape']);
117
- export const KEY_UP = new Set(['ArrowUp', 'KeyW']);
118
- export const KEY_DOWN = new Set(['ArrowDown', 'KeyS']);
119
- export const KEY_LEFT = new Set(['ArrowLeft', 'KeyA']);
120
- export const KEY_RIGHT = new Set(['ArrowRight', 'KeyD']);
121
- /**
122
- * Traduz (tecla física, ação remapeada do dono da tecla) → intenção. `act` é `null` quando a tecla não é de
123
- * nenhum jogador (tecla genérica). Verbatim das seis linhas de `menuNavKey`.
124
- */
125
- export function menuKeyIntent(code, act) {
126
- return {
127
- yes: KEY_YES.has(code) || act === 'action2',
128
- no: KEY_NO.has(code) || act === 'action3',
129
- up: KEY_UP.has(code) || act === 'up',
130
- down: KEY_DOWN.has(code) || act === 'down',
131
- left: KEY_LEFT.has(code) || act === 'left',
132
- right: KEY_RIGHT.has(code) || act === 'right',
133
- };
134
- }
135
- /** Alguma intenção foi expressa? Definição única em input/edges; aqui só o nome que este módulo sempre teve. */
52
+ /** Was any intent expressed? Defined once in input/edges; here under the name this module has always used. */
136
53
  import { hasNavIntent as hasIntent } from '../input/edges.js';
137
- import { mostrarSubmenuDaPausa, PM_ITENS_VISIVEIS } from './pause-icons.js';
138
- import { anunciarItem } from './item-announcement.js';
139
- import { rotuloAcessivel } from '../core/rotulo-acessivel.js';
140
- import { passoNoAnel } from '../core/anel.js';
54
+ import { backToRoot, markPauseItem, PM_VISIBLE_ITEMS } from './pause-icons.js';
55
+ import { announceItem } from './item-announcement.js';
56
+ import { accessibleLabel } from '../core/accessible-label.js';
57
+ import { stepInRing } from '../core/ring.js';
58
+ import { navigableItems } from './menu-items.js';
59
+ import { menuKeyIntent, selectStep, selectWrap, rangeStep, stepInPause } from './menu-intent.js';
141
60
  export { hasNavIntent as hasIntent } from '../input/edges.js';
142
- // A CONTA DO ANEL mudou de casa para `core/anel` no item 7 do ADR-0044: `ui/pause-icons` passou a precisar
143
- // dela, e como ESTE módulo já importa aquele, a volta fecharia um ciclo de importação — que em ESM não
144
- // estoura na hora, estoura no boot em TDZ. O nome público fica: é daqui que os menus e os testes já a
145
- // importavam, e mudar isso seria pedir uma edição em cada um deles para não ganhar nada.
146
- export { passoNoAnel } from '../core/anel.js';
147
- /** `select` com esquerda/direita: um passo, SEM dar a volta — ajustar VALOR não é navegar lista (ver acima). */
148
- export function selectStep(selectedIndex, optionsLen, delta) {
149
- return Math.max(0, Math.min(optionsLen - 1, selectedIndex + delta));
150
- }
151
- /** `select` com "sim": um passo, COM volta. É a diferença deliberada entre confirmar e ajustar. */
152
- export function selectWrap(selectedIndex, optionsLen) {
153
- return (selectedIndex + 1) % optionsLen;
154
- }
155
- /** `input[type=range]` com esquerda/direita: um `step` (default 1), preso entre `min` e `max`. */
156
- export function rangeStep(value, min, max, step, delta) {
157
- const st = step || 1; // `+cur.step||1`: step ausente/0/NaN vira 1, verbatim
158
- return Math.max(min, Math.min(max, value + delta * st));
159
- }
61
+ // The ring's arithmetic lives in `core/ring`: `ui/pause-icons` needs it too, and this module imports that one, so keeping
62
+ // it here would close an import cycle — which in ESM does not fail on the spot, it fails at boot in the TDZ. The public
63
+ // name stays here because the menus and the tests import it from here.
64
+ export { stepInRing } from '../core/ring.js';
65
+ // The items' selector lives in `ui/menu-items` (ADR-0158): what counts as an item is defined once, and the spoken
66
+ // «N of M» and the panel shell read the same list.
160
67
  /**
161
- * O PASSO DO CURSOR NO MENU DE PAUSA — um ANEL, agora que a pausa é uma lista (ADR-0044, itens 1 e 7).
162
- *
163
- * ISTO SUBSTITUI `pauseGridMove`, e a substituição é o desfecho do ADR-0044, não uma limpeza. O que havia
164
- * era uma GRADE de duas zonas — a barra de dez ícones em cima, a lista de itens em duas colunas embaixo — e
165
- * uma fronteira entre elas com quatro regras próprias ("de cima, 'baixo' cai sempre no primeiro item", "da
166
- * primeira linha, 'cima' sobe para o ícone de mesmo índice", …). Cada regra dessas era uma coisa a mais para
167
- * a criança descobrir sem ver, e nenhuma delas era descobrível: só se aprendia esbarrando.
68
+ * The parts a panel control SAYS (ADR-0159 rule 1, XAG 106: «Gamma, slider, 38%, 6 of 9»): the label with the ROLE, and
69
+ * the VALUE. The index is added by `announceItem`.
168
70
  *
169
- * A XAG 106 permite laço para menu LINEAR e o PROÍBE para grade de duas dimensões — numa grade, dar a volta
170
- * teleporta o cursor para o outro canto e a pessoa perde a noção de onde está. Era por isso que o anel do
171
- * item 1 valia para todo menu do jogo MENOS este. Com a barra no HUD (item 7), o cartão passou a ter uma
172
- * lista só, e o laço deixou de ser proibido para virar o recomendado.
173
- *
174
- * O que se ganha em troca das quatro regras: `quit` fica a UMA tecla para CIMA de `resume`. Último na
175
- * leitura, vizinho no dedo.
71
+ * The label is the row's `<strong>` when the control lives in one (it is what is SEEN, and a switch's text is its state,
72
+ * not its name); outside a row, or on a slider with a name of its own («Volume de Música»), it is the accessible name.
176
73
  */
177
- export function passoNaPausa(len, idx, k) {
178
- const d = (k.down || k.right) ? 1 : -1;
179
- return passoNoAnel(len, idx < 0 ? 0 : idx, d);
74
+ export function controlParts(t, el) {
75
+ const row = el.closest('.ctrl-row')?.querySelector('strong')?.textContent?.trim() || '';
76
+ const [, role, read] = CONTROL_KINDS.find(([recognises]) => recognises(el));
77
+ const { label, value } = read(el, row, accessibleLabel(el), t);
78
+ return { label: `${label}, ${t(role)}`, state: value };
79
+ }
80
+ /** A slider's position as a whole percentage: a missing maximum reads as 100, and a range of zero width as 0. */
81
+ function sliderPercent(r) {
82
+ const min = +r.min || 0, max = +r.max || 100;
83
+ const pct = max > min ? Math.round(((+r.value - min) / (max - min)) * 100) : 0;
84
+ return `${pct}%`;
180
85
  }
181
- /** Os controles que contam como "item navegável" de um menu. Verbatim do seletor do `menuItems`. */
182
- const ITEM_SELECTOR = 'button:not([disabled]), select:not([disabled]), input[type=range]:not([disabled])';
183
- /** Onde os itens moram: o card do diálogo (`.overlay__card`) ou o card da pausa (`.pause-card`). */
86
+ /**
87
+ * HOW EACH KIND OF CONTROL IS RECOGNISED AND READ, and the ORDER is the rule: the first row that recognises the element reads
88
+ * it, and the last — a button — recognises everything. A stepper and a slider prefer their own name (a slider can be «Music
89
+ * volume» on a row called «Sound»); the others prefer the row's label, because the text of a switch is its state, not its name.
90
+ */
91
+ const CONTROL_KINDS = [
92
+ [(el) => el.hasAttribute('data-passos'), 'sr.papel.passos',
93
+ (el, row) => ({ label: el.getAttribute('aria-label') || row, value: el.getAttribute('aria-valuetext') ?? '' })],
94
+ [(el) => el.tagName === 'SELECT', 'sr.papel.lista',
95
+ (el, row, name) => ({ label: row || name, value: el.selectedOptions?.[0]?.textContent?.trim() ?? '' })],
96
+ [(el) => el.tagName === 'INPUT', 'sr.papel.cursor',
97
+ (el, row) => ({ label: el.getAttribute('aria-label') || row, value: sliderPercent(el) })],
98
+ [(el) => el.hasAttribute('aria-pressed'), 'sr.papel.interruptor',
99
+ (el, row, name, t) => ({ label: row || name, value: t(el.getAttribute('aria-pressed') === 'true' ? 'state.on' : 'state.off') })],
100
+ [(el) => el.getAttribute('role') === 'radio', 'sr.papel.opcao',
101
+ (el, row, name, t) => ({ label: row || name, value: el.getAttribute('aria-checked') === 'true' ? t('sr.estado.selecionado') : '' })],
102
+ [() => true, 'sr.papel.botao', (_el, _row, name) => ({ label: name, value: '' })],
103
+ ];
104
+ /** Where the items live: the dialog's card (`.overlay__card`) or the pause card (`.pause-card`). */
184
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
+ }
185
117
  export function initMenuNav(ctx) {
186
- /* ===================== diálogos de configuração ===================== */
187
- // ALIAS, não cópia: o corpo mora em ui/settings-panel.ts. Ver "a duplicação que esta extração curou".
118
+ const { t } = ctx;
119
+ /* ===================== settings dialogs ===================== */
120
+ // An ALIAS, not a copy: the body lives in ui/settings-panel.ts (see the header).
188
121
  const sharedDialogOpen = () => ctx.topVisibleOverlay();
189
122
  function menuItems(menu) {
190
123
  const card = menu.querySelector(CARD_SELECTOR) || menu;
191
- // `offsetParent === null` = fora do fluxo de layout (escondido, `display:none` ou dentro de aba oculta).
192
- // É o único filtro de VISIBILIDADE real que existe aqui: sem ele o cursor pousa em botão invisível e o
193
- // leitor de tela anuncia um controle que ninguém vê. Verbatim.
194
- return [...card.querySelectorAll(ITEM_SELECTOR)].filter((el) => el.offsetParent !== null);
124
+ // The VISIBILITY filter (`offsetParent`) lives with the selector in `ui/menu-items`.
125
+ return navigableItems(card);
195
126
  }
196
127
  function menuFocus(menu) {
197
128
  if (!menu)
198
- return; // ⚠️ DEFEITO 1: com os menus de pausa fora do escopo, este guarda é o fim da linha
129
+ return; // ⚠️ DEFECT 1: with the pause card outside the scope, this guard is the end of the line
199
130
  const it = menuItems(menu);
200
131
  if (!it.length)
201
132
  return;
@@ -204,187 +135,368 @@ export function initMenuNav(ctx) {
204
135
  }
205
136
  function dialogBack(menu) {
206
137
  if (!ctx.closeById(menu.id))
207
- menu.hidden = true; // sem entrada no registro: some na marra
138
+ menu.hidden = true; // no registered entry: hide it outright
208
139
  menuFocus(sharedDialogOpen());
209
140
  }
210
- /** Ajusta um `select` (esquerda/direita = passo preso; sim = passo com volta) e dispara `change`. */
141
+ /** Adjusts a `select` (left/right = a step that stops at the ends; yes = a step that wraps) and fires `change`. */
211
142
  function tweakSelect(el, delta) {
212
143
  el.selectedIndex = delta === 'wrap' ? selectWrap(el.selectedIndex, el.options.length) : selectStep(el.selectedIndex, el.options.length, delta);
213
144
  el.dispatchEvent(new Event('change', { bubbles: true }));
214
145
  }
215
- /** Ajusta um `input[type=range]` em um `step` e dispara `input` (é o evento que os painéis escutam). */
146
+ /** Moves an `input[type=range]` by one `step` and fires `input` (the event the panels listen to). */
216
147
  function tweakRange(el, delta) {
217
148
  el.value = String(rangeStep(+el.value, +el.min, +el.max, +el.step, delta));
218
149
  el.dispatchEvent(new Event('input', { bubbles: true }));
219
150
  }
151
+ /**
152
+ * THE ITEM REACHED SAYS ITSELF (ADR-0159 rule 1): label, role, value and «N de M», in that order.
153
+ *
154
+ * The browser's focus moves by itself, but the engine's narration (the one a child playing in blind mode hears) does
155
+ * not listen to focus; it listens to this.
156
+ */
157
+ function sayItem(items, n) {
158
+ const el = items[n];
159
+ if (!el)
160
+ return;
161
+ const { label: partLabel, state: partState } = controlParts(t, el);
162
+ ctx.srSay(announceItem(t, { label: partLabel, state: partState, position: n + 1, total: items.length }, ctx.withIndex()));
163
+ }
164
+ function focusAndSay(items, n) {
165
+ items[n]?.focus();
166
+ sayItem(items, n);
167
+ }
168
+ /** Where the cursor is. With the focus outside the dialog (or on its card), it enters by the first item — and says it. */
169
+ function cursorIn(items) {
170
+ const idx = items.indexOf(ctx.getActiveElement());
171
+ if (idx >= 0)
172
+ return idx;
173
+ focusAndSay(items, 0);
174
+ return 0;
175
+ }
176
+ /**
177
+ * Left or right: a list, a slider or a steps control is ADJUSTED, and the new value is said — someone adjusting by ear has
178
+ * no other way to know where it stopped; anything else walks the ring, like up and down.
179
+ */
180
+ function sideways(items, idx, d) {
181
+ const cur = items[idx];
182
+ if (cursorKind(cur) === 'item') {
183
+ focusAndSay(items, stepInRing(items.length, idx, d));
184
+ return;
185
+ }
186
+ if (cur.tagName === 'SELECT') {
187
+ const before = cur.value;
188
+ tweakSelect(cur, d);
189
+ // an adjustment its owner refused (and put back) says nothing here: the owner already said why
190
+ if (cur.value !== before)
191
+ sayItem(items, idx);
192
+ return;
193
+ }
194
+ if (cur.tagName === 'INPUT') {
195
+ tweakRange(cur, d);
196
+ sayItem(items, idx);
197
+ return;
198
+ }
199
+ // A steps control ⯇ ⯈ (ADR-0151): left and right are the adjustment itself; its owner listens to `passo` (and announces).
200
+ cur.dispatchEvent(new CustomEvent('passo', { detail: d, bubbles: true }));
201
+ }
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. */
203
+ function confirm(items, idx) {
204
+ const cur = items[idx];
205
+ const kind = cursorKind(cur);
206
+ if (kind === 'list') {
207
+ tweakSelect(cur, 'wrap');
208
+ sayItem(items, idx);
209
+ return;
210
+ }
211
+ if (kind === 'item')
212
+ cur.click();
213
+ }
220
214
  function navDialog(menu, k) {
221
215
  const items = menuItems(menu);
222
216
  if (!items.length)
223
217
  return;
224
- let idx = items.indexOf(ctx.getActiveElement());
225
- if (idx < 0) {
226
- idx = 0;
227
- items[0].focus();
228
- } // foco fora do diálogo (ou no card): entra pelo primeiro
229
- const cur = items[idx];
218
+ const idx = cursorIn(items);
230
219
  if (k.no) {
231
220
  dialogBack(menu);
232
221
  return;
233
222
  }
234
223
  if (k.left || k.right) {
235
- const d = k.right ? 1 : -1;
236
- if (cur.tagName === 'SELECT') {
237
- tweakSelect(cur, d);
238
- return;
239
- }
240
- if (cur.tagName === 'INPUT') {
241
- tweakRange(cur, d);
242
- return;
243
- }
244
- items[passoNoAnel(items.length, idx, d)].focus();
224
+ sideways(items, idx, k.right ? 1 : -1);
245
225
  return;
246
226
  }
247
227
  if (k.up || k.down) {
248
- items[passoNoAnel(items.length, idx, k.down ? 1 : -1)].focus();
228
+ focusAndSay(items, stepInRing(items.length, idx, k.down ? 1 : -1));
249
229
  return;
250
230
  }
251
- if (k.yes) {
252
- if (cur.tagName === 'SELECT') {
253
- tweakSelect(cur, 'wrap');
254
- return;
255
- }
256
- if (cur.tagName === 'INPUT')
257
- return; // slider não tem "confirmar" — só ajuste
258
- cur.click();
259
- }
231
+ if (k.yes)
232
+ confirm(items, idx);
260
233
  }
261
- /* ===================== menu de pausa (seleção por classe, não por foco) ===================== */
234
+ /* ===================== pause card (selected by class, not by focus) ===================== */
262
235
  /**
263
- * Move o cursor do menu de pausa. Só ITENS: desde o item 7 do ADR-0044 a barra de ícones vive no HUD, e o
264
- * cursor dela é dela.
265
- *
266
- * O QUE SAIU DAQUI, e vale registrar: esta função também escrevia a legenda dos ícones, porque o cursor
267
- * atravessava a fronteira entre as duas zonas. Sem a fronteira, escrever legenda de ícone a partir do menu
268
- * de pausa seria um módulo mexendo na tela de outro.
236
+ * Moves the pause card's cursor. Items only: since ADR-0044 item 7 the quick bar lives in the HUD and keeps its own
237
+ * cursor — writing the bar's caption from here would be one module drawing on another's screen.
269
238
  */
270
239
  function pauseSetSel(menu, el) {
271
240
  if (!el)
272
- return; // índice fora da lista (menu vazio): não mexe em nada — verbatim
273
- menu.querySelectorAll('.pm-sel').forEach((b) => b.classList.remove('pm-sel'));
274
- el.classList.add('pm-sel');
241
+ return; // an index outside the list (an empty card): touch nothing
242
+ markPauseItem(menu, el); // the one place the card's cursor moves, and is kept in view (issue #134)
275
243
  }
276
244
  function navPause(menu, playerIndex, k) {
277
245
  if (k.no) {
278
- // "não" DENTRO do submenu de opções volta à raiz, e não ao jogo (ADR-0044, item 5). A regra "voltar sai
279
- // um nível" já valia para os diálogos de configuração (`dialogBack`); o que mudou é que o menu de pausa
280
- // passou a TER um nível a mais. Sem esta linha, quem entra em Opções sem enxergar só sairia despausando.
281
- const aberto = menu.querySelector('.pause-menu:not([hidden])');
282
- if (aberto && aberto.dataset.sub === 'opcoes') {
283
- mostrarSubmenuDaPausa(menu, 'raiz');
246
+ // «No» inside any sub-list goes up ONE level, to the root, and not back to the game (ADR-0044 item 5) — the same
247
+ // «back leaves one level» the settings dialogs have (`dialogBack`). The question is «is this NOT the root?», not
248
+ // «is this the options list?»: with three lists (ADR-0146), asking the second would make «no» from the game's own
249
+ // options resume the game when the child asked to go back.
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.
252
+ if (open && open.dataset.sub && open.dataset.sub !== 'raiz') {
253
+ backToRoot(menu);
284
254
  return;
285
255
  }
286
256
  ctx.setPhase('playing');
287
- return; // "não" na raiz → volta ao jogo (retoma todos)
257
+ return; // «no» at the root → back to the game (resumes everyone)
288
258
  }
289
- const items = [...menu.querySelectorAll(PM_ITENS_VISIVEIS)];
259
+ const items = [...menu.querySelectorAll(PM_VISIBLE_ITEMS)];
290
260
  const cur = menu.querySelector('.pm-sel') || items[0];
291
- // "sim": o jogador que agiu vira o `pauseActor` (o submenu de a11y abre na aba dele) e o item é clicado.
261
+ // «Yes»: the player who acted becomes the `pauseActor` (the accessibility options open on their tab) and the item is clicked.
292
262
  if (k.yes) {
293
263
  ctx.setPauseActor(playerIndex);
294
264
  if (cur)
295
265
  cur.click();
296
266
  return;
297
267
  }
298
- // Guarda NOVA (o original estouraria em `cur.classList` aqui): menu sem `.pm-btn` nenhum. Não é o defeito
299
- // preservado — é um TypeError, e trocar um crash por um no-op não tira rede de conserto nenhum.
268
+ // A card with no item at all: nothing to move (without this guard the next line throws on `cur`).
300
269
  if (!cur)
301
270
  return;
302
- // UMA LISTA, um anel. A barra de ícones saiu do cartão no item 7 do ADR-0044, e com ela saíram as quatro
303
- // regras de fronteira que ninguém conseguia descobrir sem esbarrar.
304
- const n = passoNaPausa(items.length, items.indexOf(cur), k);
271
+ // ONE list, one ring: the quick bar is not in the card (ADR-0044 item 7), so there are no borders between zones to cross.
272
+ const n = stepInPause(items.length, items.indexOf(cur), k);
273
+ selectAndSayInPause(menu, items, n);
274
+ }
275
+ function selectAndSayInPause(menu, items, n) {
305
276
  pauseSetSel(menu, items[n]);
306
- // E O ITEM NOVO É FALADO. Este menu não usa foco do navegador — seleciona por classe, porque é desenhado
307
- // dentro da tela do jogador —, então nada dispara anúncio sozinho: nem foco, nem `aria-activedescendant`,
308
- // nem região viva. MEDIDO no jogo construído: a seta andava e o `#sr-status` ficava vazio. O item 3 do
309
- // ADR-0044 pede posição e total "em todo lugar", e este era o lugar onde ele não tinha chegado — o menu
310
- // que o item 5 reconstruiu.
311
- // `rotuloAcessivel` e não `textContent`: um item com `aria-label` seria narrado de um jeito pelo jogo e de
312
- // outro pelo leitor de tela, e quem ouve os dois não teria como saber qual é a verdadeira.
313
- ctx.srSay(anunciarItem({ rotulo: rotuloAcessivel(items[n]), posicao: n + 1, total: items.length }, ctx.comIndice()));
277
+ // AND THE NEW ITEM IS SPOKEN. This card selects by class, not by browser focus — it is drawn inside the player's
278
+ // screen — so nothing announces it by itself: no focus, no `aria-activedescendant`, no live region. ADR-0044 item 3
279
+ // asks for position and total everywhere.
280
+ // `accessibleLabel` and not `textContent`: an item with an `aria-label` would otherwise be narrated one way by the
281
+ // game and another by the screen reader, and whoever hears both could not tell which is true.
282
+ // 🔴 A LOCKED ITEM SAYS WHY when it is reached (ADR-0161): right after its name, and written in the footer.
283
+ const reason = items[n].getAttribute('aria-disabled') === 'true' ? (items[n].dataset.motivo ?? '') : '';
284
+ const announcement = announceItem(t, { label: accessibleLabel(items[n]), position: n + 1, total: items.length }, ctx.withIndex());
285
+ ctx.srSay(reason ? `${announcement}. ${reason}` : announcement);
286
+ ctx.explainItem?.(reason || null);
314
287
  }
315
- /* ===================== teclado ===================== */
316
- /** O assistente de gamepad fica POR CIMA de tudo: com ele aberto, só Escape passa (e cancela). */
288
+ /* ===================== keyboard ===================== */
289
+ const padWizOpen = () => { const pw = ctx.$('#padwiz'); return !!pw && !pw.hidden; };
290
+ /** The controller-mapping panel sits ON TOP of everything: while it is open, only Escape gets through (and cancels). */
317
291
  function padWizKey(e) {
318
- const pw = ctx.$('#padwiz');
319
- if (!pw || pw.hidden)
292
+ if (!padWizOpen())
320
293
  return false;
321
294
  if (e.code === 'Escape') {
322
295
  ctx.closePadWiz(false);
323
- e.preventDefault();
324
- e.stopPropagation();
296
+ consume(e);
325
297
  }
326
- return true; // aberto = consome (mesmo sem ser Escape: o resto do menu não navega por baixo dele)
298
+ return true; // open = consumed (even when it is not Escape: no menu underneath navigates while it is open)
327
299
  }
328
- /** Consome a tecla: ela era nossa, e ninguém mais deve vê-la. Uma função só para o par nunca se separar. */
329
- const consumir = (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); };
330
304
  function menuNavKey(e) {
331
305
  if (ctx.isCapturing())
332
- return; // remap em andamento: a tecla é dele
306
+ return; // a remap in progress: the key is its
333
307
  if (padWizKey(e))
334
308
  return;
335
- // ===================== O MODO `accessibility` VEM ANTES DO GUARDA DE "NAVEGÁVEL" =====================
336
- // `isNavigable()` é `phase === 'paused'`, e este modo roda com o jogo ANDANDO — é para isso que ele
337
- // existe: ajustar a acessibilidade DURANTE a partida, sem parar. Se ele ficasse depois do guarda, a
338
- // direção cairia no personagem e o modo não faria nada, que é a versão silenciosa da armadilha.
309
+ // ===================== THE QUICK BAR COMES BEFORE THE «NAVIGABLE» GUARD =====================
310
+ // `isNavigable()` can be false while the game runs (a platformer answers `phase === 'paused'`), and the bar works
311
+ // with the game RUNNING — that is what it is for: adjusting accessibility DURING play. After the guard, the
312
+ // direction would fall to the character and the bar would do nothing, silently.
339
313
  //
340
- // No TECLADO a saída é Escape (o `no` do projeto). O START do controle é a segunda saída e entra por
341
- // `input/gamepad`; aqui ele não tem par próprio, porque Enter já é "confirmar" e roubá-lo tiraria da
342
- // criança o único jeito de ATIVAR o ícone sob o cursor.
343
- const donoTecla = ctx.whichPlayer(e.code);
344
- const pTecla = donoTecla < 0 ? 0 : donoTecla;
345
- if (ctx.naBarraDe(pTecla)) {
346
- const kb = menuKeyIntent(e.code, donoTecla >= 0 ? ctx.actionOf(e.code, pTecla) : null);
347
- if (hasIntent(kb)) {
348
- consumir(e);
349
- ctx.navBar(pTecla, kb);
350
- }
351
- return; // na barra, tecla de menu é da barra — com ou sem intenção, não desce para o personagem
352
- }
353
- if (!ctx.isNavigable())
314
+ // On the KEYBOARD the way out is Escape (the project's `no`). A controller's START is the second way out and comes
315
+ // through `input/gamepad`; the keyboard has no pair of its own for it, because Enter is already «confirm» and taking
316
+ // it would leave the child no way to ACTIVATE the icon under the cursor.
317
+ const { player, keys } = intentOf(e.code);
318
+ // on the bar, a key with no intent is not consumed either — it simply moves nothing
319
+ if (!hasIntent(keys) || !menusTake(player))
354
320
  return;
355
- const owner = ctx.whichPlayer(e.code);
356
- const pi = owner < 0 ? 0 : owner; // tecla genérica → Jogador 1
357
- const k = menuKeyIntent(e.code, owner >= 0 ? ctx.actionOf(e.code, pi) : null);
358
- if (!hasIntent(k))
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);
359
341
  return;
360
- // A TECLA SÓ É CONSUMIDA SE HOUVER O QUE NAVEGAR. Antes, `preventDefault()` + `stopPropagation()` vinham
361
- // AQUI, antes de se saber se havia diálogo ou menu aberto — e o `menuNavKey` matava o evento para depois
362
- // descobrir que não tinha nada a fazer com ele. Na plataforma isso era invisível: `isNavigable()` é
363
- // `phase === 'paused'`, e pausar ABRE o menu, então quase nunca havia um caso "navegável e nada aberto".
364
- //
365
- // No segundo consumidor não era invisível — era total. Um quiz cujos ajustes estão SEMPRE disponíveis
366
- // responde `isNavigable(): true`, e com isso TODA tecla com intenção de menu morria aqui: as setas de
367
- // escolher alternativa, e o `S` do sonar. Duas funcionalidades que existiam e não chegavam à criança.
368
- //
369
- // A REDE DE SEGURANÇA DO ESCAPE FICA INTACTA, e era o risco real desta mudança. O bloco (b) acima explica
370
- // que `#help` e `#touchcfg` dependem do `stopPropagation()` para não deixarem a tecla cair no ouvinte de
371
- // bolha, que despausaria o jogo com o diálogo aberto. Esses dois casos entram por `sharedDialogOpen()`
372
- // — há diálogo, logo a tecla É consumida, exatamente como antes.
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;
366
+ }
367
+ /** Whose key this is — a key no player owns is Player 1's — and what it asks of a menu. */
368
+ function intentOf(code) {
369
+ const owner = ctx.whichPlayer(code);
370
+ const player = owner < 0 ? 0 : owner;
371
+ return { player, keys: menuKeyIntent(code, owner >= 0 ? ctx.actionOf(code, player) : null) };
372
+ }
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;
380
+ const dlg = sharedDialogOpen();
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);
423
+ }
424
+ else
425
+ el.focus();
426
+ return true;
427
+ }
428
+ /* ===================== holding on an item ===================== */
429
+ /*
430
+ * A PRESS HELD ON AN ITEM PLACES THE CURSOR THERE AND SAYS IT, WITHOUT ACTIVATING IT (ADR-0159 rule 2, erratum of
431
+ * 2026-09-13). «Deixar o dedo apertado ou o botão do mouse apertado sobre o item deve dar a função de posicionar o
432
+ * cursor sem "apertar".» (Dev) — the pad left the menus (ADR-0166), and a pointer had one meaning only: activation.
433
+ * 📌 A short press is untouched; after a hold, the ONE click the browser fires on release is swallowed, in capture,
434
+ * before the item's own listener. Threshold: the iOS long-press default, 0.5 s.
435
+ */
436
+ const HOLD_MS = 500;
437
+ let holding = null;
438
+ let clickToSwallow = null;
439
+ /** The open menu and the item under `target`, when `target` is inside one: the top dialog first, else a pause card. */
440
+ function itemUnder(target) {
441
+ const no = target;
442
+ if (!no || typeof no.closest !== 'function')
443
+ return null;
373
444
  const dlg = sharedDialogOpen();
374
445
  if (dlg) {
375
- consumir(e);
376
- navDialog(dlg, k);
446
+ const items = menuItems(dlg);
447
+ const n = items.findIndex((el) => el.contains(no));
448
+ return n >= 0 ? { menu: dlg, items, n, inPause: false } : null;
449
+ }
450
+ const menu = no.closest('.screen-pause');
451
+ if (!menu || menu.hidden)
452
+ return null;
453
+ const items = [...menu.querySelectorAll(PM_VISIBLE_ITEMS)];
454
+ const n = items.findIndex((el) => el.contains(no));
455
+ return n >= 0 ? { menu, items, n, inPause: true } : null;
456
+ }
457
+ function release() {
458
+ if (holding)
459
+ clearTimeout(holding.timer);
460
+ holding = null;
461
+ }
462
+ function onPress(e) {
463
+ release();
464
+ clickToSwallow = null;
465
+ const under = itemUnder(e.target);
466
+ if (!under)
377
467
  return;
378
- } // diálogo de a11y aberto: navega ele (compartilhado)
379
- const menu = ctx.getPauseMenu(pi); // senão: menu de pausa do PRÓPRIO jogador
380
- if (menu && !menu.hidden) {
381
- consumir(e);
382
- navPause(menu, pi, k);
468
+ const id = e.pointerId;
469
+ holding = {
470
+ id,
471
+ timer: setTimeout(() => {
472
+ holding = null;
473
+ clickToSwallow = under.items[under.n] ?? null;
474
+ if (under.inPause)
475
+ selectAndSayInPause(under.menu, under.items, under.n);
476
+ else
477
+ focusAndSay(under.items, under.n);
478
+ }, HOLD_MS),
479
+ };
480
+ }
481
+ function onRelease(e) {
482
+ if (holding && holding.id === e.pointerId)
483
+ release();
484
+ }
485
+ function onClick(e) {
486
+ const swallowed = clickToSwallow;
487
+ clickToSwallow = null;
488
+ if (swallowed && e.target instanceof Node && swallowed.contains(e.target)) {
489
+ e.preventDefault();
490
+ e.stopPropagation();
383
491
  }
384
- // Sem diálogo e sem menu: a tecla NÃO é nossa. Segue o caminho dela até quem for o dono.
385
492
  }
386
493
  function attach() {
387
494
  ctx.win.addEventListener('keydown', menuNavKey, true);
495
+ ctx.win.addEventListener('pointerdown', onPress, true);
496
+ ctx.win.addEventListener('pointerup', onRelease, true);
497
+ ctx.win.addEventListener('pointercancel', onRelease, true);
498
+ ctx.win.addEventListener('click', onClick, true);
388
499
  }
389
- 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 };
390
502
  }