@the-inclusionist/engine 6.36.1

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 (267) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +107 -0
  3. package/app/css/style.css +622 -0
  4. package/app/public/vendor/fonts/andika-400-ext.woff2 +0 -0
  5. package/app/public/vendor/fonts/andika-400.woff2 +0 -0
  6. package/app/public/vendor/fonts/andika-700-ext.woff2 +0 -0
  7. package/app/public/vendor/fonts/andika-700.woff2 +0 -0
  8. package/app/public/vendor/fonts/atkinson-400-ext.woff2 +0 -0
  9. package/app/public/vendor/fonts/atkinson-400.woff2 +0 -0
  10. package/app/public/vendor/fonts/atkinson-700-ext.woff2 +0 -0
  11. package/app/public/vendor/fonts/atkinson-700.woff2 +0 -0
  12. package/app/public/vendor/fonts/atkinson-mono-400-ext.woff2 +0 -0
  13. package/app/public/vendor/fonts/atkinson-mono-400.woff2 +0 -0
  14. package/app/public/vendor/fonts/comicneue-400.woff2 +0 -0
  15. package/app/public/vendor/fonts/comicneue-700.woff2 +0 -0
  16. package/app/public/vendor/fonts/greatvibes-400.woff2 +0 -0
  17. package/app/public/vendor/fonts/inter-400.woff2 +0 -0
  18. package/app/public/vendor/fonts/inter-700.woff2 +0 -0
  19. package/app/public/vendor/fonts/lato-400.woff2 +0 -0
  20. package/app/public/vendor/fonts/lato-700.woff2 +0 -0
  21. package/app/public/vendor/fonts/lexend-400-ext.woff2 +0 -0
  22. package/app/public/vendor/fonts/lexend-400.woff2 +0 -0
  23. package/app/public/vendor/fonts/lexend-700-ext.woff2 +0 -0
  24. package/app/public/vendor/fonts/lexend-700.woff2 +0 -0
  25. package/app/public/vendor/fonts/literata-400.woff2 +0 -0
  26. package/app/public/vendor/fonts/literata-700.woff2 +0 -0
  27. package/app/public/vendor/fonts/newsreader-400.woff2 +0 -0
  28. package/app/public/vendor/fonts/newsreader-700.woff2 +0 -0
  29. package/app/public/vendor/fonts/opensans-400.woff2 +0 -0
  30. package/app/public/vendor/fonts/opensans-700.woff2 +0 -0
  31. package/app/public/vendor/fonts/pinyon-400.woff2 +0 -0
  32. package/app/public/vendor/fonts/quattro-400.woff2 +0 -0
  33. package/app/public/vendor/fonts/quattro-700.woff2 +0 -0
  34. package/app/public/vendor/fonts/sourcesans-400.woff2 +0 -0
  35. package/app/public/vendor/fonts/sourcesans-700.woff2 +0 -0
  36. package/app/public/vendor/fonts/sourceserif-400.woff2 +0 -0
  37. package/app/public/vendor/fonts/sourceserif-700.woff2 +0 -0
  38. package/app/public/vendor/fonts/ufcook-700.woff2 +0 -0
  39. package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
  40. package/app/public/vendor/fonts.css +88 -0
  41. package/dist-pkg/boot/create-game.d.ts +159 -0
  42. package/dist-pkg/boot/create-game.js +266 -0
  43. package/dist-pkg/core/a11y-sr.d.ts +4 -0
  44. package/dist-pkg/core/a11y-sr.js +20 -0
  45. package/dist-pkg/core/actions.d.ts +100 -0
  46. package/dist-pkg/core/actions.js +149 -0
  47. package/dist-pkg/core/anel.d.ts +17 -0
  48. package/dist-pkg/core/anel.js +31 -0
  49. package/dist-pkg/core/collision.d.ts +21 -0
  50. package/dist-pkg/core/collision.js +62 -0
  51. package/dist-pkg/core/constants.d.ts +84 -0
  52. package/dist-pkg/core/constants.js +64 -0
  53. package/dist-pkg/core/contract.d.ts +227 -0
  54. package/dist-pkg/core/contract.js +184 -0
  55. package/dist-pkg/core/dom-query.d.ts +9 -0
  56. package/dist-pkg/core/dom-query.js +25 -0
  57. package/dist-pkg/core/entity.d.ts +150 -0
  58. package/dist-pkg/core/entity.js +38 -0
  59. package/dist-pkg/core/escape-html.d.ts +16 -0
  60. package/dist-pkg/core/escape-html.js +32 -0
  61. package/dist-pkg/core/i18n.d.ts +71 -0
  62. package/dist-pkg/core/i18n.js +236 -0
  63. package/dist-pkg/core/layers.d.ts +39 -0
  64. package/dist-pkg/core/layers.js +71 -0
  65. package/dist-pkg/core/letter-grid.d.ts +55 -0
  66. package/dist-pkg/core/letter-grid.js +116 -0
  67. package/dist-pkg/core/loop.d.ts +17 -0
  68. package/dist-pkg/core/loop.js +36 -0
  69. package/dist-pkg/core/password.d.ts +34 -0
  70. package/dist-pkg/core/password.js +205 -0
  71. package/dist-pkg/core/rng.d.ts +20 -0
  72. package/dist-pkg/core/rng.js +61 -0
  73. package/dist-pkg/core/rotulo-acessivel.d.ts +17 -0
  74. package/dist-pkg/core/rotulo-acessivel.js +41 -0
  75. package/dist-pkg/core/run-state.d.ts +115 -0
  76. package/dist-pkg/core/run-state.js +40 -0
  77. package/dist-pkg/core/scenes.d.ts +68 -0
  78. package/dist-pkg/core/scenes.js +64 -0
  79. package/dist-pkg/core/screens.d.ts +16 -0
  80. package/dist-pkg/core/screens.js +28 -0
  81. package/dist-pkg/core/state.d.ts +138 -0
  82. package/dist-pkg/core/state.js +290 -0
  83. package/dist-pkg/core/tiles.d.ts +12 -0
  84. package/dist-pkg/core/tiles.js +61 -0
  85. package/dist-pkg/core/world.d.ts +4 -0
  86. package/dist-pkg/core/world.js +58 -0
  87. package/dist-pkg/educational/activities-registry.d.ts +47 -0
  88. package/dist-pkg/educational/activities-registry.js +95 -0
  89. package/dist-pkg/i18n/en.d.ts +3 -0
  90. package/dist-pkg/i18n/en.js +560 -0
  91. package/dist-pkg/i18n/es.d.ts +3 -0
  92. package/dist-pkg/i18n/es.js +558 -0
  93. package/dist-pkg/i18n/pt.d.ts +3 -0
  94. package/dist-pkg/i18n/pt.js +618 -0
  95. package/dist-pkg/input/default-bindings.d.ts +24 -0
  96. package/dist-pkg/input/default-bindings.js +122 -0
  97. package/dist-pkg/input/devices.d.ts +15 -0
  98. package/dist-pkg/input/devices.js +31 -0
  99. package/dist-pkg/input/edges.d.ts +58 -0
  100. package/dist-pkg/input/edges.js +49 -0
  101. package/dist-pkg/input/gamepad.d.ts +203 -0
  102. package/dist-pkg/input/gamepad.js +531 -0
  103. package/dist-pkg/input/keyboard-runtime.d.ts +61 -0
  104. package/dist-pkg/input/keyboard-runtime.js +83 -0
  105. package/dist-pkg/input/keyboard.d.ts +45 -0
  106. package/dist-pkg/input/keyboard.js +80 -0
  107. package/dist-pkg/input/keydown.d.ts +270 -0
  108. package/dist-pkg/input/keydown.js +381 -0
  109. package/dist-pkg/input/latch.d.ts +30 -0
  110. package/dist-pkg/input/latch.js +63 -0
  111. package/dist-pkg/input/pointer-space.d.ts +40 -0
  112. package/dist-pkg/input/pointer-space.js +51 -0
  113. package/dist-pkg/input/state.d.ts +11 -0
  114. package/dist-pkg/input/state.js +12 -0
  115. package/dist-pkg/input/touch-bindings.d.ts +210 -0
  116. package/dist-pkg/input/touch-bindings.js +365 -0
  117. package/dist-pkg/input/touch.d.ts +173 -0
  118. package/dist-pkg/input/touch.js +314 -0
  119. package/dist-pkg/input/transports.d.ts +90 -0
  120. package/dist-pkg/input/transports.js +96 -0
  121. package/dist-pkg/input/vocabulary-migration.d.ts +58 -0
  122. package/dist-pkg/input/vocabulary-migration.js +125 -0
  123. package/dist-pkg/platform/audio-ambient.d.ts +27 -0
  124. package/dist-pkg/platform/audio-ambient.js +89 -0
  125. package/dist-pkg/platform/audio-earcons.d.ts +24 -0
  126. package/dist-pkg/platform/audio-earcons.js +62 -0
  127. package/dist-pkg/platform/audio-jingles.d.ts +17 -0
  128. package/dist-pkg/platform/audio-jingles.js +54 -0
  129. package/dist-pkg/platform/audio-mixer.d.ts +14 -0
  130. package/dist-pkg/platform/audio-mixer.js +62 -0
  131. package/dist-pkg/platform/audio-nav.d.ts +51 -0
  132. package/dist-pkg/platform/audio-nav.js +99 -0
  133. package/dist-pkg/platform/audio-sonar.d.ts +71 -0
  134. package/dist-pkg/platform/audio-sonar.js +173 -0
  135. package/dist-pkg/platform/audio.d.ts +32 -0
  136. package/dist-pkg/platform/audio.js +191 -0
  137. package/dist-pkg/platform/interruptible-speech.d.ts +19 -0
  138. package/dist-pkg/platform/interruptible-speech.js +79 -0
  139. package/dist-pkg/platform/speech.d.ts +2 -0
  140. package/dist-pkg/platform/speech.js +35 -0
  141. package/dist-pkg/platform/storage.d.ts +87 -0
  142. package/dist-pkg/platform/storage.js +148 -0
  143. package/dist-pkg/platform/tts.d.ts +33 -0
  144. package/dist-pkg/platform/tts.js +142 -0
  145. package/dist-pkg/render/camera.d.ts +58 -0
  146. package/dist-pkg/render/camera.js +105 -0
  147. package/dist-pkg/render/canvas.d.ts +13 -0
  148. package/dist-pkg/render/canvas.js +26 -0
  149. package/dist-pkg/render/cenario-data.d.ts +127 -0
  150. package/dist-pkg/render/cenario-data.js +145 -0
  151. package/dist-pkg/render/city-tex.d.ts +63 -0
  152. package/dist-pkg/render/city-tex.js +228 -0
  153. package/dist-pkg/render/city-tiles.d.ts +11 -0
  154. package/dist-pkg/render/city-tiles.js +140 -0
  155. package/dist-pkg/render/crt.d.ts +19 -0
  156. package/dist-pkg/render/crt.js +92 -0
  157. package/dist-pkg/render/cvd-matrices.d.ts +35 -0
  158. package/dist-pkg/render/cvd-matrices.js +91 -0
  159. package/dist-pkg/render/draw.d.ts +149 -0
  160. package/dist-pkg/render/draw.js +221 -0
  161. package/dist-pkg/render/fx.d.ts +63 -0
  162. package/dist-pkg/render/fx.js +123 -0
  163. package/dist-pkg/render/hc-role-data.d.ts +14 -0
  164. package/dist-pkg/render/hc-role-data.js +24 -0
  165. package/dist-pkg/render/high-contrast.d.ts +109 -0
  166. package/dist-pkg/render/high-contrast.js +254 -0
  167. package/dist-pkg/render/lq-filter.d.ts +37 -0
  168. package/dist-pkg/render/lq-filter.js +94 -0
  169. package/dist-pkg/render/minimap.d.ts +11 -0
  170. package/dist-pkg/render/minimap.js +94 -0
  171. package/dist-pkg/render/parallax.d.ts +85 -0
  172. package/dist-pkg/render/parallax.js +158 -0
  173. package/dist-pkg/render/player-anim.d.ts +71 -0
  174. package/dist-pkg/render/player-anim.js +152 -0
  175. package/dist-pkg/render/port.d.ts +143 -0
  176. package/dist-pkg/render/port.js +29 -0
  177. package/dist-pkg/render/recycling-tex.d.ts +48 -0
  178. package/dist-pkg/render/recycling-tex.js +164 -0
  179. package/dist-pkg/render/scene-city.d.ts +77 -0
  180. package/dist-pkg/render/scene-city.js +181 -0
  181. package/dist-pkg/render/scene-parallax.d.ts +72 -0
  182. package/dist-pkg/render/scene-parallax.js +426 -0
  183. package/dist-pkg/render/scene-sky.d.ts +126 -0
  184. package/dist-pkg/render/scene-sky.js +295 -0
  185. package/dist-pkg/render/screen-pipeline.d.ts +134 -0
  186. package/dist-pkg/render/screen-pipeline.js +177 -0
  187. package/dist-pkg/render/set-cenario.d.ts +54 -0
  188. package/dist-pkg/render/set-cenario.js +103 -0
  189. package/dist-pkg/render/sprite-fx.d.ts +3 -0
  190. package/dist-pkg/render/sprite-fx.js +40 -0
  191. package/dist-pkg/render/textures.d.ts +75 -0
  192. package/dist-pkg/render/textures.js +240 -0
  193. package/dist-pkg/render/title-scene.d.ts +46 -0
  194. package/dist-pkg/render/title-scene.js +75 -0
  195. package/dist-pkg/render/viewports.d.ts +52 -0
  196. package/dist-pkg/render/viewports.js +185 -0
  197. package/dist-pkg/render/viz-axes.d.ts +98 -0
  198. package/dist-pkg/render/viz-axes.js +184 -0
  199. package/dist-pkg/render/viz-modes.d.ts +61 -0
  200. package/dist-pkg/render/viz-modes.js +74 -0
  201. package/dist-pkg/render/viz-setters.d.ts +130 -0
  202. package/dist-pkg/render/viz-setters.js +244 -0
  203. package/dist-pkg/render/weather.d.ts +96 -0
  204. package/dist-pkg/render/weather.js +171 -0
  205. package/dist-pkg/render/wheelchair-sprites.d.ts +24 -0
  206. package/dist-pkg/render/wheelchair-sprites.js +63 -0
  207. package/dist-pkg/render/world-tex.d.ts +18 -0
  208. package/dist-pkg/render/world-tex.js +87 -0
  209. package/dist-pkg/ui/activities-menu.d.ts +258 -0
  210. package/dist-pkg/ui/activities-menu.js +648 -0
  211. package/dist-pkg/ui/caa-sets.d.ts +39 -0
  212. package/dist-pkg/ui/caa-sets.js +65 -0
  213. package/dist-pkg/ui/changed-mark.d.ts +15 -0
  214. package/dist-pkg/ui/changed-mark.js +96 -0
  215. package/dist-pkg/ui/debug-panel.d.ts +67 -0
  216. package/dist-pkg/ui/debug-panel.js +173 -0
  217. package/dist-pkg/ui/dom.d.ts +24 -0
  218. package/dist-pkg/ui/dom.js +47 -0
  219. package/dist-pkg/ui/focus-trap.d.ts +71 -0
  220. package/dist-pkg/ui/focus-trap.js +99 -0
  221. package/dist-pkg/ui/fonts.d.ts +41 -0
  222. package/dist-pkg/ui/fonts.js +59 -0
  223. package/dist-pkg/ui/hud.d.ts +137 -0
  224. package/dist-pkg/ui/hud.js +229 -0
  225. package/dist-pkg/ui/item-announcement.d.ts +19 -0
  226. package/dist-pkg/ui/item-announcement.js +36 -0
  227. package/dist-pkg/ui/layout.d.ts +6 -0
  228. package/dist-pkg/ui/layout.js +54 -0
  229. package/dist-pkg/ui/loop-crash.d.ts +19 -0
  230. package/dist-pkg/ui/loop-crash.js +60 -0
  231. package/dist-pkg/ui/map-hub.d.ts +56 -0
  232. package/dist-pkg/ui/map-hub.js +138 -0
  233. package/dist-pkg/ui/menu-nav.d.ts +141 -0
  234. package/dist-pkg/ui/menu-nav.js +390 -0
  235. package/dist-pkg/ui/pause-icons.d.ts +327 -0
  236. package/dist-pkg/ui/pause-icons.js +620 -0
  237. package/dist-pkg/ui/reach-notice.d.ts +34 -0
  238. package/dist-pkg/ui/reach-notice.js +71 -0
  239. package/dist-pkg/ui/settings-audio.d.ts +139 -0
  240. package/dist-pkg/ui/settings-audio.js +544 -0
  241. package/dist-pkg/ui/settings-caa.d.ts +45 -0
  242. package/dist-pkg/ui/settings-caa.js +137 -0
  243. package/dist-pkg/ui/settings-controls.d.ts +100 -0
  244. package/dist-pkg/ui/settings-controls.js +152 -0
  245. package/dist-pkg/ui/settings-empathy.d.ts +62 -0
  246. package/dist-pkg/ui/settings-empathy.js +137 -0
  247. package/dist-pkg/ui/settings-motion.d.ts +95 -0
  248. package/dist-pkg/ui/settings-motion.js +282 -0
  249. package/dist-pkg/ui/settings-motor.d.ts +77 -0
  250. package/dist-pkg/ui/settings-motor.js +215 -0
  251. package/dist-pkg/ui/settings-panel.d.ts +76 -0
  252. package/dist-pkg/ui/settings-panel.js +187 -0
  253. package/dist-pkg/ui/settings-typo.d.ts +74 -0
  254. package/dist-pkg/ui/settings-typo.js +150 -0
  255. package/dist-pkg/ui/settings-visual.d.ts +111 -0
  256. package/dist-pkg/ui/settings-visual.js +252 -0
  257. package/dist-pkg/ui/shell.d.ts +239 -0
  258. package/dist-pkg/ui/shell.js +430 -0
  259. package/dist-pkg/ui/title.d.ts +36 -0
  260. package/dist-pkg/ui/title.js +60 -0
  261. package/dist-pkg/ui/vlibras.d.ts +11 -0
  262. package/dist-pkg/ui/vlibras.js +87 -0
  263. package/dist-pkg/ui/webcam.d.ts +7 -0
  264. package/dist-pkg/ui/webcam.js +88 -0
  265. package/docs/CREDITS.md +64 -0
  266. package/docs/LICENSES.md +132 -0
  267. package/package.json +115 -0
@@ -0,0 +1,390 @@
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.
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.
15
+ //
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.
22
+ //
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".
31
+ //
32
+ // ======================= DOIS DEFEITOS CONHECIDOS — PRESERVADOS VERBATIM, NÃO CONSERTE =======================
33
+ //
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.
41
+ //
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.
59
+ //
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.
78
+ //
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
+ // ============================================================================================================
82
+ //
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.
92
+ //
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).
107
+ // ---------------------------------------------------------------------------------------------------------
108
+ // PURO — a decisão de navegação, sem DOM. Testável no project `node`.
109
+ // ---------------------------------------------------------------------------------------------------------
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. */
136
+ 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';
141
+ 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
+ }
160
+ /**
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.
168
+ *
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.
176
+ */
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);
180
+ }
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`). */
184
+ const CARD_SELECTOR = '.overlay__card, .pause-card';
185
+ 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".
188
+ const sharedDialogOpen = () => ctx.topVisibleOverlay();
189
+ function menuItems(menu) {
190
+ 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);
195
+ }
196
+ function menuFocus(menu) {
197
+ if (!menu)
198
+ return; // ⚠️ DEFEITO 1: com os menus de pausa fora do escopo, este guarda é o fim da linha
199
+ const it = menuItems(menu);
200
+ if (!it.length)
201
+ return;
202
+ const cur = it.indexOf(ctx.getActiveElement());
203
+ (cur >= 0 ? it[cur] : it[0]).focus();
204
+ }
205
+ function dialogBack(menu) {
206
+ if (!ctx.closeById(menu.id))
207
+ menu.hidden = true; // sem entrada no registro: some na marra
208
+ menuFocus(sharedDialogOpen());
209
+ }
210
+ /** Ajusta um `select` (esquerda/direita = passo preso; sim = passo com volta) e dispara `change`. */
211
+ function tweakSelect(el, delta) {
212
+ el.selectedIndex = delta === 'wrap' ? selectWrap(el.selectedIndex, el.options.length) : selectStep(el.selectedIndex, el.options.length, delta);
213
+ el.dispatchEvent(new Event('change', { bubbles: true }));
214
+ }
215
+ /** Ajusta um `input[type=range]` em um `step` e dispara `input` (é o evento que os painéis escutam). */
216
+ function tweakRange(el, delta) {
217
+ el.value = String(rangeStep(+el.value, +el.min, +el.max, +el.step, delta));
218
+ el.dispatchEvent(new Event('input', { bubbles: true }));
219
+ }
220
+ function navDialog(menu, k) {
221
+ const items = menuItems(menu);
222
+ if (!items.length)
223
+ 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];
230
+ if (k.no) {
231
+ dialogBack(menu);
232
+ return;
233
+ }
234
+ 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();
245
+ return;
246
+ }
247
+ if (k.up || k.down) {
248
+ items[passoNoAnel(items.length, idx, k.down ? 1 : -1)].focus();
249
+ return;
250
+ }
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
+ }
260
+ }
261
+ /* ===================== menu de pausa (seleção por classe, não por foco) ===================== */
262
+ /**
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.
269
+ */
270
+ function pauseSetSel(menu, el) {
271
+ 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');
275
+ }
276
+ function navPause(menu, playerIndex, k) {
277
+ 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');
284
+ return;
285
+ }
286
+ ctx.setPhase('playing');
287
+ return; // "não" na raiz → volta ao jogo (retoma todos)
288
+ }
289
+ const items = [...menu.querySelectorAll(PM_ITENS_VISIVEIS)];
290
+ 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.
292
+ if (k.yes) {
293
+ ctx.setPauseActor(playerIndex);
294
+ if (cur)
295
+ cur.click();
296
+ return;
297
+ }
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.
300
+ if (!cur)
301
+ 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);
305
+ 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()));
314
+ }
315
+ /* ===================== teclado ===================== */
316
+ /** O assistente de gamepad fica POR CIMA de tudo: com ele aberto, só Escape passa (e cancela). */
317
+ function padWizKey(e) {
318
+ const pw = ctx.$('#padwiz');
319
+ if (!pw || pw.hidden)
320
+ return false;
321
+ if (e.code === 'Escape') {
322
+ ctx.closePadWiz(false);
323
+ e.preventDefault();
324
+ e.stopPropagation();
325
+ }
326
+ return true; // aberto = consome (mesmo sem ser Escape: o resto do menu não navega por baixo dele)
327
+ }
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(); };
330
+ function menuNavKey(e) {
331
+ if (ctx.isCapturing())
332
+ return; // remap em andamento: a tecla é dele
333
+ if (padWizKey(e))
334
+ 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.
339
+ //
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())
354
+ 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))
359
+ 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.
373
+ const dlg = sharedDialogOpen();
374
+ if (dlg) {
375
+ consumir(e);
376
+ navDialog(dlg, k);
377
+ 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);
383
+ }
384
+ // Sem diálogo e sem menu: a tecla NÃO é nossa. Segue o caminho dela até quem for o dono.
385
+ }
386
+ function attach() {
387
+ ctx.win.addEventListener('keydown', menuNavKey, true);
388
+ }
389
+ return { sharedDialogOpen, menuItems, menuFocus, dialogBack, navDialog, pauseSetSel, navPause, menuNavKey, attach };
390
+ }