@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,648 +0,0 @@
1
- // SPDX-License-Identifier: AGPL-3.0-or-later
2
- // ui/activities-menu.ts — the TITLE-SCREEN menus and the "pick an activity" flow (Estágio 4). Owns everything
3
- // between "the player is looking at the splash" and "the round actually starts": the activity submenus' markup
4
- // (buildTitleMenus), arrow-key traversal (titleButtons/navTitle), the whole `#title-overlay` click dispatcher
5
- // (`data-tm`/`data-tm-back`/`data-act-id`/`data-tab-n`/`data-fnot`/`data-cen`/`#np-btn`/`#tab-play`), the
6
- // two-step start (startActivity → scenario picker → reallyStart), the current activity's category (actCat) and
7
- // the persisted menu selections (tabuada numbers, fraction notations).
8
- //
9
- // DI via initActivitiesMenu(ctx) — no I/O at import time. Imported DIRECTLY (leaf modules, never game.js):
10
- // core/state.ts → `activity`/`setActivityValue` (live binding + persistence + event), `players`,
11
- // `numPlayers`. The VALIDATION of the id lives here, on purpose: core/state.ts stores
12
- // whatever it is given, so somebody outside has to know the catalog.
13
- // educational/activities-registry.ts → the catalog (getActivity/hasActivity/isValidActivityId/
14
- // DEFAULT_ACTIVITY_ID). Never re-declared here; this module only *reads* it.
15
- // É CURRÍCULO, não jogo (ADR-0032) — e é por isso que a aresta antiga `ui/ → game/`
16
- // morreu por mudança de ENDEREÇO. O que sobra é `ui/ → educational/`, que é uma
17
- // aresta menor e ainda assim uma aresta: o menu conhece UM catálogo. O conserto
18
- // verdadeiro é a EdSP entregar o catálogo por injeção — e até lá o gate conta
19
- // esta linha, em vez de deixá-la passar por ter mudado de pasta.
20
- // platform/storage.ts → every read/write goes through `store.KEYS` (`tabsel`, `fracnot`); no raw localStorage.
21
- // Injected instead of imported, and why:
22
- // `$` / `getActiveElement` / `enterFullscreen` — the DOM handles, so the node project can drive the whole
23
- // dispatcher with plain objects (same contract as input/gamepad.ts and ui/settings-*.ts).
24
- // `srSay`/`srAlert` — core/a11y-sr.ts touches `document` at CALL time; injecting keeps node tests honest.
25
- // `titleShow` — ui/title.ts's `show()`, already extracted; game.js builds it with its own ctx, so we take the
26
- // instance rather than a second one.
27
- // everything else (`cenarios`, `setCenario`, `setModeValue`, `setQuizLevel`, `isMobile`, `fitsN`,
28
- // `setNumPlayers`, `restartGame`, `setPhase`, `hideTips`) is still game.js's — see ActivitiesMenuCtx.
29
- //
30
- // NOT here (deliberately): ui/title.ts's submenu show/hide, render/title-scene.ts's PIXI backdrop,
31
- // updateTitleLegend (device legend — gamepad slice), the `#title-icons` a11y shortcut row (pause-icons slice;
32
- // it only shares the `#title-overlay` element, never a listener), and setMode()/applyLetra() (HUD toggles).
33
- // `activity`/`setActivityValue` NÃO são mais importados: eles vão para `game/state` (Fase B do plano), e
34
- // este módulo é ENGINE — o gate de fronteira proíbe engine importar de `game/`, e a lista dele esvaziou em
35
- // 2026-08-25. Chegam por injeção, como todo o resto do que é do jogo.
36
- import { escaparHtml } from '../core/escape-html.js'; // #106: o id do cenario vem do JOGO
37
- import { getActivity, hasActivity, isValidActivityId, DEFAULT_ACTIVITY_ID, activityCategory } from '../educational/activities-registry.js';
38
- import * as store from '../platform/storage.js';
39
- import { t } from '../core/i18n.js';
40
- import { TITLE_MENU_IDS_ORDERED as TITLE_MENU_ORDER } from './title.js';
41
- export { activityCategory, modeForCategory } from '../educational/activities-registry.js';
42
- import { passoNoAnel } from '../core/anel.js';
43
- import { anunciarItem } from './item-announcement.js';
44
- import { rotuloAcessivel } from '../core/rotulo-acessivel.js';
45
- // LIGAÇÃO VIVA (ESM), e não cópia: `menuIndexOn` muda quando a criança desliga o índice no menu, e o valor
46
- // aqui acompanha sem precisar de assinatura nem de um campo a mais no `ctx`.
47
- import { menuIndexOn } from '../core/state.js';
48
- // -------------------------------------------------------------------------------------------------------
49
- // Menu DATA (verbatim from game.js) — plain tables, no behaviour.
50
- // -------------------------------------------------------------------------------------------------------
51
- /** The five literacy levels of the Ferreiro psychogenesis, by number. Domain content (pt-BR) — read by educators. */
52
- export const QL_NAME = {
53
- 1: 'pré-silábico', 2: 'silábico', 3: 'silábico-alfabético', 4: 'escritor', 5: 'escritor cego',
54
- };
55
- /** Which literacy activity trains which of the 5 levels — setActivity reuses the psychogenesis ladder. */
56
- export const ALF_LEVEL = { alf1: 1, alf2: 2, alf3: 3, alf4: 4, alf5: 5 };
57
- /** The pause menu's items — MENU DATA, so it lives with the other menu tables. The pause slice (ui/pause-icons)
58
- * receives it through its own ctx instead of re-declaring it; nobody owns two copies of a list of buttons. */
59
- export const PM_BTNS = [
60
- // ===================== A ORDEM É A DECISÃO (ADR-0044 §2) =====================
61
- // Eram DOZE itens, e `resume` — a SAÍDA — era a 11ª parada do cartão, porque os dez ícones de a11y vinham
62
- // antes na ordem de leitura. Quem pausa e não enxerga varria dez alternadores e um cabeçalho antes de achar
63
- // "Continuar". Menu de onde não se sai é armadilha, e a armadilha custa mais caro para quem não a enxerga.
64
- //
65
- // `resume` PRIMEIRO porque é para isso que serve uma pausa. `quit` ÚLTIMO porque é o desfecho menos
66
- // desejado dela — e, como a lista é ANEL (item 1), uma tecla para CIMA a partir de `resume` chega nele:
67
- // longe na leitura, perto no dedo.
68
- //
69
- // Os sete painéis de ajuste desceram para `PM_OPTIONS_BTNS`.
70
- { act: 'resume' },
71
- // `acessibilidade` leva o direcional à BARRA RÁPIDA de acessibilidade em vez de ao jogador. Enquanto a
72
- // barra mora dentro do cartão de pausa, é para lá que ele aponta; quando o item 7 a levar para o HUD, o
73
- // item a segue. O que ele significa não muda: "dirigir a barra rápida".
74
- { act: 'acessibilidade' },
75
- { act: 'addplayer' },
76
- { act: 'options' },
77
- { act: 'ajuda' },
78
- { act: 'print' },
79
- { act: 'quit' },
80
- ];
81
- /**
82
- * O SUBMENU DE OPÇÕES — os sete painéis que saíram da lista raiz, na ordem que o Dev ditou.
83
- *
84
- * A saída vem PRIMEIRO aqui também, pela mesma razão que `resume` vem primeiro lá: a regra do ADR-0044 é
85
- * sobre menus, não sobre um menu. Um submenu de onde não se sai é a mesma armadilha, um nível abaixo.
86
- *
87
- * 'letra' virou 'caa' (ADR-0028): era um CICLO de duas posições cujo rótulo mudava junto (`letra: true`), e
88
- * virou a porta de um menu. Sem ciclo não há rótulo dinâmico, então ele volta a ser traduzível como os
89
- * irmãos — o `letra: true` existia só para o i18n não sobrescrever o ABC/abc que o ciclo escrevia.
90
- */
91
- export const PM_OPTIONS_BTNS = [
92
- { act: 'pmback' },
93
- { act: 'caa' },
94
- { act: 'empatia' },
95
- { act: 'audio' },
96
- { act: 'motora' },
97
- { act: 'tipo' },
98
- { act: 'visual' },
99
- { act: 'anim' },
100
- ];
101
- /** i18n KEY of the spoken/aria name of each fraction notation. Keys, not text: a module-level const is
102
- * evaluated once at import and would freeze the language at boot (see the note in input/devices). */
103
- export const FNOT_LBL = {
104
- v: 'fnot.v', d: 'fnot.d', dec: 'fnot.dec', pct: 'fnot.pct', mix: 'fnot.mix',
105
- };
106
- /** Visible label = the notation ITSELF (x/y), so the five toggles fit side by side; aria-label keeps the word. */
107
- export const FNOT_SYM = {
108
- v: `<span class="fv"><b>x</b><b>y</b></span>`, d: 'x/y', dec: 'x,y', pct: 'x%',
109
- mix: `x<span class="fv"><b>y</b><b>z</b></span>`,
110
- };
111
- /** i18n KEY of the footer text (same style as the pause menu) shown on focus/hover of a notation toggle. */
112
- export const FNOT_DESC = {
113
- v: 'fnot.desc.v', d: 'fnot.desc.d', dec: 'fnot.desc.dec', pct: 'fnot.desc.pct', mix: 'fnot.desc.mix',
114
- };
115
- /** The notation keys in menu order — also the defaults' key order (`v` on, rest off). */
116
- export const FNOT_KEYS = ['v', 'd', 'dec', 'pct', 'mix'];
117
- /** Ids of the literacy submenu, in menu order. */
118
- export const ALF_MENU_IDS = ['alf1', 'alf2', 'alf3', 'alf4', 'alf5'];
119
- /** Ids of the math submenu (the "Fração" button is not an activity — it opens another submenu). */
120
- export const MAT_MENU_IDS = ['mat1', 'mat2', 'mat3', 'mat4', 'mat5', 'mat6'];
121
- /** Ids of the fractions submenu, in menu order. */
122
- export const FR_MENU_IDS = ['fr2', 'fr3', 'fr42', 'fr5', 'fr632', 'fr2a6'];
123
- /** The 6 title submenus, in the order game.js has always scanned them for the visible one. */
124
- // A lista ordenada vem de ui/title, que e a dona dos submenus; aqui so ganha o nome que este modulo e
125
- // seus testes ja usavam. Duas copias mantidas em sincronia por disciplina eram uma a mais.
126
- export { TITLE_MENU_IDS_ORDERED as TITLE_MENU_ORDER } from './title.js';
127
- /** The two rows of the tabuada number picker (0–5 then 6–10). */
128
- export const TAB_ROWS = [[0, 1, 2, 3, 4, 5], [6, 7, 8, 9, 10]];
129
- /** Default tabuada selection when storage is empty (or holds something unusable). */
130
- export const TAB_SEL_DEFAULT = [2, 3, 4, 5];
131
- // -------------------------------------------------------------------------------------------------------
132
- // PURE logic — no document, no storage; this is what the node project exercises.
133
- // -------------------------------------------------------------------------------------------------------
134
- /** Unknown/absent id → the default activity. game.js's `if(!hasActivity(id))id=DEFAULT_ACTIVITY_ID`. */
135
- export function normalizeActivityId(id) {
136
- return hasActivity(id) ? id : DEFAULT_ACTIVITY_ID;
137
- }
138
- /**
139
- * Where "Voltar" on the scenario submenu must return to — a pure mapping from the activity that opened it:
140
- * number-picking activities (Tabuada/Divisão) came through `tm-tab`, fraction activities through `tm-fr`,
141
- * and otherwise the category's own submenu; free play came straight off `tm-main`.
142
- * VERBATIM caveat: game.js dereferences `getActivity(id)` without a guard, so an unknown id throws here too.
143
- */
144
- export function cenBackMenuFor(id) {
145
- const a = getActivity(id);
146
- return a.pick ? 'tm-tab' : a.dens ? 'tm-fr' : a.cat === 'alf' ? 'tm-alf' : a.cat === 'mat' ? 'tm-mat' : 'tm-main';
147
- }
148
- /**
149
- * Reads the persisted tabuada selection. VERBATIM port, quirks included: the filter is `n>=0 && n<=10`, which
150
- * JavaScript evaluates after coercion — so `"3"`, `true` and `null` all survive it while `'x'` and `{}` do not.
151
- * Anything that is not a non-empty array falls back to [2,3,4,5]; an array that filters down to EMPTY stays
152
- * empty (the "Jogar" button then refuses with an srAlert, which is how game.js has always behaved).
153
- */
154
- export function sanitizeTabSel(raw) {
155
- if (Array.isArray(raw) && raw.length)
156
- return raw.filter((n) => n >= 0 && n <= 10);
157
- return [...TAB_SEL_DEFAULT];
158
- }
159
- /**
160
- * Reads the persisted fraction notations. Defaults `{v:1,d:0,dec:0,pct:0,mix:0}`; only the five known keys are
161
- * copied over (any extra key in storage is ignored) and each is coerced to 0/1; if the result would leave every
162
- * notation off, `v` is forced back on — the menu's invariant "at least one notation is always lit".
163
- */
164
- export function sanitizeFracNot(raw) {
165
- const d = { v: 1, d: 0, dec: 0, pct: 0, mix: 0 };
166
- if (raw && typeof raw === 'object') {
167
- const s = raw;
168
- for (const k of FNOT_KEYS)
169
- if (k in s)
170
- d[k] = s[k] ? 1 : 0;
171
- }
172
- if (!Object.values(d).some((x) => x))
173
- d.v = 1;
174
- return d;
175
- }
176
- /** How many notations are currently on — the guard behind "Deixe ao menos uma notação ligada." */
177
- export function fracNotOnCount(f) {
178
- return Object.values(f).filter((x) => x).length;
179
- }
180
- /** True when turning `k` OFF is allowed (it is not the last one lit). */
181
- export function canToggleFracNot(f, k) {
182
- return !(f[k] && fracNotOnCount(f) <= 1);
183
- }
184
- /** Player count staged on the splash: 1..4, never outside. */
185
- export function clampPendingPlayers(n) {
186
- return Math.max(1, Math.min(4, n));
187
- }
188
- /**
189
- * Next focused index for arrow traversal. VERBATIM quirk: when nothing in the submenu has focus (`cur < 0`),
190
- * BOTH directions land on index 0 — pressing "up" from nowhere does not wrap to the last button.
191
- *
192
- * A CONTA DO ANEL saiu daqui e passou a ser `passoNoAnel` (ui/menu-nav), que é a mesma do menu de pausa. Eram
193
- * duas escritas da mesma volta — `(cur + d + len) % len` aqui, a de lá — e o ADR-0044 §5 pede UMA resposta
194
- * para "o que acontece no fim da lista" no jogo inteiro. Duas cópias respondem igual até o dia em que uma
195
- * delas é corrigida sozinha.
196
- */
197
- export function nextTitleIndex(cur, len, k) {
198
- const d = (k.down || k.right) ? 1 : -1;
199
- return cur < 0 ? 0 : passoNoAnel(len, cur, d);
200
- }
201
- /**
202
- * O NOME de uma atividade, no idioma vigente.
203
- *
204
- * A chave, quando existe, vence o texto cru — e a ausência dela é significado, não esquecimento: as cinco
205
- * atividades de alfabetização não declaram chave porque a palavra e a sílaba SÃO a matéria da atividade
206
- * (pilar 3 do ADR-0010). Todo o resto declara, matemática inclusive: `2 + 3` independe de língua.
207
- *
208
- * Resolve AQUI, no ponto de exibição, e não no catálogo — `educational/activities-registry` é DADO e não
209
- * importa nada; e uma `const` de módulo com `t()` já resolvido congelaria o idioma no boot.
210
- */
211
- export function nomeDaAtividade(a) {
212
- if (!a)
213
- return '';
214
- return a.nomeKey ? t(a.nomeKey) : a.nome;
215
- }
216
- /** A descrição de rodapé de uma atividade, pela mesma regra do nome. */
217
- export function descricaoDaAtividade(a) {
218
- if (!a)
219
- return '';
220
- return a.dKey ? t(a.dKey) : (a.d || '');
221
- }
222
- /** Footer description for a focused/hovered title button: notation text wins, else the activity's `d`.
223
- * The notation half resolves its key here; the activity's `d` comes from educational/activities-registry
224
- * and is still pt-BR, for the same curriculum-boundary reason as the activity `nome` (ADR-0032). */
225
- export function titleDescFor(actId, fnotKey) {
226
- if (fnotKey) {
227
- const k = FNOT_DESC[fnotKey];
228
- return k ? t(k) : '';
229
- }
230
- return descricaoDaAtividade(getActivity(actId ?? ''));
231
- }
232
- // --- HTML builders (pure strings; the DOM layer only assigns them) ---------------------------------------
233
- /** One activity button, with the optional example sub-label (literacy). */
234
- export function activityBtnHtml(id) {
235
- const a = getActivity(id);
236
- return `<button class="title-btn" data-act-id="${id}" type="button">${nomeDaAtividade(a)}${a.sub ? `<span class="act-sub">${a.sub}</span>` : ''}</button>`;
237
- }
238
- /** The "Voltar" ghost button of a submenu. */
239
- export function backBtnHtml(to) { return `<button class="title-btn ghost" data-tm-back="${to}" type="button">${t('menu.back')}</button>`; }
240
- /** The live-region footer that shows the focused minigame's description. */
241
- export const TM_DESC_HTML = `<div class="tm-desc" aria-live="polite"></div>`;
242
- /** A submenu heading. O parâmetro chamava-se `t` e sombreava a função de tradução — quarta colisão desse
243
- * nome no projeto; recebe o texto JÁ resolvido, e agora se chama `titulo` para dizê-lo. */
244
- export function tmTitleHtml(titulo) { return `<h3 class="tm-title">${titulo}</h3>`; }
245
- export function alfMenuHtml() {
246
- return tmTitleHtml(t('menu.alf')) + ALF_MENU_IDS.map(activityBtnHtml).join('') + backBtnHtml('tm-main') + TM_DESC_HTML;
247
- }
248
- export function matMenuHtml() {
249
- return tmTitleHtml(t('menu.mat')) + MAT_MENU_IDS.map(activityBtnHtml).join('')
250
- + `<button class="title-btn" data-tm-fr="1" type="button">${t('menu.frac')}</button>` + backBtnHtml('tm-main') + TM_DESC_HTML;
251
- }
252
- /**
253
- * AS CINCO NOTAÇÕES — ajustes, e não itens de menu (pedido do Dev).
254
- *
255
- * Elas eram `.title-btn`, a MESMA classe dos itens que ABREM uma atividade, empilhadas logo acima deles: para
256
- * quem enxerga, cinco coisas com cara de "entrar" que na verdade LIGAM e DESLIGAM. Para quem navega por
257
- * escuta é pior — o cursor atravessa dez itens em que os cinco primeiros respondem "ligado/desligado" e os
258
- * cinco últimos começam uma partida, sem nada entre eles dizendo que a regra mudou. É a mesma doença que o
259
- * item 5 do ADR-0044 curou no cartão de pausa: dois modelos de interação numa tela, sem fronteira.
260
- *
261
- * Três coisas mudam: MOLDURA com nome (`fieldset`/`legend`, a fronteira que o leitor anuncia ao entrar),
262
- * MARCA visível na frente (`☑`/`☐`, para o estado não depender de cor — WCAG 1.4.1) e PAPEL de caixa de
263
- * seleção (`role="checkbox"` + `aria-checked`), porque a pergunta aqui é "está marcada?" e não "confirmar?".
264
- *
265
- * ⚠️ CONTINUAM SENDO `<button>`, e é deliberado: `titleButtons()` varre `button` para montar o anel de
266
- * navegação. Um `<input type="checkbox">` seria mais correto no papel e tiraria as cinco do alcance do
267
- * direcional — quem mais precisa delas é justamente quem só tem ele.
268
- */
269
- export function fracNotsHtml(f) {
270
- return `<fieldset class="frac-nots"><legend>${t('menu.notationGroupAria')}</legend>`
271
- + FNOT_KEYS.map((k) => `<button class="fnot-opt" data-fnot="${k}" type="button" role="checkbox" aria-checked="${!!f[k]}" aria-label="${t(FNOT_LBL[k])}"><span class="fnot-marca" aria-hidden="true">${f[k] ? '☑' : '☐'}</span><span class="fnot-sym">${FNOT_SYM[k]}</span></button>`).join('')
272
- + `</fieldset>`;
273
- }
274
- export function frMenuHtml(f) {
275
- return tmTitleHtml(t('menu.fracTitle')) + fracNotsHtml(f)
276
- + FR_MENU_IDS.map(activityBtnHtml).join('') + backBtnHtml('tm-mat') + TM_DESC_HTML;
277
- }
278
- export function tabRowHtml(row, sel) {
279
- return row.map((n) => `<button class="title-btn tab-num${sel.includes(n) ? ' tab-on' : ''}" data-tab-n="${n}" type="button" aria-pressed="${sel.includes(n)}">${n}</button>`).join('');
280
- }
281
- export function tabMenuHtml(sel) {
282
- return tmTitleHtml(t('menu.tab')) + `<div class="game-subtitle">${t('menu.tabHint')}</div>`
283
- + TAB_ROWS.map((r) => `<div class="tab-row">${tabRowHtml(r, sel)}</div>`).join('')
284
- + `<button class="title-btn" id="tab-play" type="button">${t('menu.play')}</button>` + backBtnHtml('tm-mat');
285
- }
286
- export function cenMenuHtml(cenarios) {
287
- // `c.nome` é a CHAVE do cenário (render/cenario-data); resolve aqui, no ponto de exibição.
288
- //
289
- // ⚠️ E O `c.id` VAI ESCAPADO (issue #106). A lista de cenários é entregue pelo JOGO, e um jogo vive noutro
290
- // repositório (ADR-0083) — um id com uma aspa fecharia o `data-cen="…"` e o que viesse a seguir viraria
291
- // atributo. Escapar e não construir nós porque aqui é UM valor dentro de um construtor de string, e o par
292
- // que o guarda existe: `activities-menu` tem caso com id hostil.
293
- return tmTitleHtml(t('menu.cen')) + cenarios.map((c) => `<button class="title-btn" data-cen="${escaparHtml(c.id)}" type="button">${t(c.nome)}</button>`).join('')
294
- + `<button class="title-btn ghost" data-cen-back="1" type="button">${t('menu.back')}</button>`;
295
- }
296
- // -------------------------------------------------------------------------------------------------------
297
- // Abbreviated labels ("A12e" → "Acessibilidade") — hover/focus scrolls the hidden letters open.
298
- // Generic sweep over the option bar (.mode-btn) AND the pause menu (.pm-btn).
299
- // -------------------------------------------------------------------------------------------------------
300
- /** token found in a label → the letters it hides. */
301
- export const ABBR_MID = { 'A12e': 'cessibilidad', 'S11e': 'ensibilidad' };
302
- /** Splits a label around its abbreviation token. `pre` keeps the first letter, `suf` starts at the last one. */
303
- export function abbrParts(txt) {
304
- let tok = null;
305
- for (const t in ABBR_MID) {
306
- if (txt.includes(t)) {
307
- tok = t;
308
- break;
309
- }
310
- }
311
- if (!tok)
312
- return null;
313
- const i = txt.indexOf(tok);
314
- return { pre: txt.slice(0, i + 1), suf: txt.slice(i + tok.length - 1), hid: ABBR_MID[tok] };
315
- }
316
- /** Label with `k` of the hidden letters revealed; the remaining count stands in for the rest ("A12e" at k=0). */
317
- export function abbrText(p, k) {
318
- const N = p.hid.length;
319
- return p.pre + p.hid.slice(0, k) + ((N - k > 0) ? String(N - k) : '') + p.suf;
320
- }
321
- /**
322
- * Wires one button's expand/collapse animation. Idempotent (`data-abbrDone`) and a no-op for labels without a
323
- * token — which, today, is EVERY button in the app: no label in index.html or PM_BTNS contains "A12e"/"S11e"
324
- * any more (they were spelled out). Ported verbatim rather than deleted; see the extraction report.
325
- */
326
- export function attachAbbr(b) {
327
- if (!b || b.dataset.abbrDone)
328
- return;
329
- const parts = abbrParts(b.textContent || '');
330
- if (!parts)
331
- return;
332
- const N = parts.hid.length;
333
- b.dataset.abbrDone = '1';
334
- let k = 0, tgt = 0;
335
- let timer = null;
336
- const render = () => { b.textContent = abbrText(parts, k); };
337
- const tick = () => { if (k === tgt) {
338
- if (timer)
339
- clearInterval(timer);
340
- timer = null;
341
- return;
342
- } k += k < tgt ? 1 : -1; render(); };
343
- const go = (t) => { tgt = t; if (!timer)
344
- timer = setInterval(tick, 16); };
345
- render();
346
- b.addEventListener('mouseenter', () => go(N));
347
- b.addEventListener('mouseleave', () => go(0));
348
- b.addEventListener('focus', () => go(N));
349
- b.addEventListener('blur', () => go(0));
350
- }
351
- export function initActivitiesMenu(ctx) {
352
- // ACTIVITY arrives from core/state.ts already read from storage; the CATALOG check is ours (state.ts has no
353
- // opinion about which ids exist), so a value left over from an activity that no longer ships falls back here.
354
- if (!isValidActivityId(ctx.getActivityId() ?? ''))
355
- ctx.setActivityId(DEFAULT_ACTIVITY_ID);
356
- // Escopo DO JOGO agora (ver os dois escopos em platform/storage); a leitura herda do nome antigo.
357
- const tabSel = sanitizeTabSel(store.getJSONComLegado(store.KEYS.tabsel(ctx.gameId), store.KEYS.tabselLegado, null));
358
- const fracNot = sanitizeFracNot(store.getJSONComLegado(store.KEYS.fracnot(ctx.gameId), store.KEYS.fracnotLegado, null));
359
- /**
360
- * Escreve o rótulo do #np-btn — o VISÍVEL e o do leitor de tela, juntos.
361
- *
362
- * Uma função só, e chamada também no INIT, porque era aí que o defeito morava: o texto vinha do
363
- * index.html em português e a `aria-label` também, e ambos só se corrigiam no PRIMEIRO clique. Numa build
364
- * em inglês o botão nascia dizendo "Nº de jogadores" e assim ficava para quem não o clicasse.
365
- *
366
- * O texto visível deixou de ter um `<span id="np-n">` filho: aquele span existia para o JavaScript trocar
367
- * só o número, e era ele que impedia o `data-i18n` de funcionar aqui (o `applyDom` escreve `textContent` e
368
- * o destruiria). Com o rótulo inteiro vindo de `t()`, o span não tem mais função.
369
- */
370
- function escreverNp(n) {
371
- const b = ctx.$('#np-btn');
372
- if (!b)
373
- return;
374
- b.textContent = t('menu.playerCount', { n });
375
- b.setAttribute('aria-label', t('menu.playerCountAria', { n }));
376
- }
377
- let pendingAct = 'ludico';
378
- let pendingPlayers = 1;
379
- let cenBack = 'tm-main';
380
- escreverNp(pendingPlayers); // o botão nasce no idioma certo, não no do markup
381
- let tabFor = 'mat5'; // which "pick numbers" activity the tabuada submenu is currently serving
382
- function actCat() { return activityCategory(ctx.getActivityId()); }
383
- function setActivity(id) {
384
- id = normalizeActivityId(id);
385
- ctx.setActivityId(id); // game/state: valor + persistência + evento. A validação continua sendo daqui.
386
- const cat = getActivity(id).cat;
387
- if (cat === 'alf')
388
- ctx.setQuizLevel(ALF_LEVEL[id], false); // reuses the 5 psychogenesis levels
389
- // O MODE NÃO É ESCRITO AQUI, e é este apagamento que fecha a issue #54: ele DERIVA de `activity`, que a
390
- // linha acima acabou de gravar. Enquanto havia dois caminhos de escrita, um deles podia mentir.
391
- }
392
- // R-splash 2: after the challenge is chosen, PLAYER 1 picks the SCENARIO (the others get "aguarde").
393
- function startActivity(id) {
394
- pendingAct = id;
395
- cenBack = cenBackMenuFor(id);
396
- ctx.titleShow('tm-cen');
397
- ctx.srSay(t('sr.menu.pickScenario'));
398
- }
399
- function reallyStart() {
400
- const id = pendingAct;
401
- setActivity(id);
402
- if (ctx.isMobile()) {
403
- if (pendingPlayers > 1)
404
- pendingPlayers = 1;
405
- ctx.enterFullscreen();
406
- }
407
- ctx.getPlayers().forEach((p) => { p.alfWins = 0; });
408
- if (pendingPlayers !== ctx.getNumPlayers())
409
- ctx.setNumPlayers(pendingPlayers);
410
- else
411
- ctx.restartGame();
412
- // O NOME da atividade atravessa como parâmetro, ainda em pt-BR: os nomes vivem em
413
- // educational/activities-registry e são CURRÍCULO — que o pilar 3 manda REESCREVER por idioma, não
414
- // traduzir (ADR-0032). A moldura — ". Jogo iniciado." — é o que traduz aqui.
415
- ctx.setPhase('playing');
416
- ctx.hideTips();
417
- ctx.srSay(t('sr.menu.gameStarted', { atividade: nomeDaAtividade(getActivity(id)) }));
418
- }
419
- function buildTitleMenus() {
420
- const alf = ctx.$('#tm-alf');
421
- if (alf)
422
- alf.innerHTML = alfMenuHtml();
423
- const mat = ctx.$('#tm-mat');
424
- if (mat)
425
- mat.innerHTML = matMenuHtml();
426
- const fr = ctx.$('#tm-fr');
427
- if (fr)
428
- fr.innerHTML = frMenuHtml(fracNot);
429
- const tab = ctx.$('#tm-tab');
430
- if (tab)
431
- tab.innerHTML = tabMenuHtml(tabSel);
432
- const cen = ctx.$('#tm-cen');
433
- if (cen)
434
- cen.innerHTML = cenMenuHtml(ctx.cenarios);
435
- }
436
- /** Buttons of the ONE submenu that is currently visible (the others carry `hidden`). */
437
- function titleButtons() {
438
- const m = TITLE_MENU_ORDER.map((id) => ctx.$('#' + id)).find((el) => el && !el.hidden);
439
- return m ? [...m.querySelectorAll('button')] : [];
440
- }
441
- /**
442
- * O botão "Voltar" desta tela, seja qual for o atributo que o marca.
443
- *
444
- * São DOIS atributos porque os alvos são de naturezas diferentes: cinco telas voltam para um destino FIXO
445
- * (`data-tm-back="tm-main"`), e a de cenário volta para a tela de onde se veio, calculada em `cenBackMenuFor`
446
- * — daí `data-cen-back`, sem valor. A distinção é legítima; o defeito era `navTitle` conhecer só a primeira.
447
- *
448
- * O EFEITO ERA ESTE: na tela de cenário — a última antes do jogo começar, por onde passa TODA partida — o
449
- * botão Voltar do controle não fazia nada. Quem usa mouse ou toque vê e clica o "Voltar" que está ali; quem
450
- * depende do controle só conseguia ir para a frente, para dentro de um jogo. É a GAG A1 (menu navegável por
451
- * controle) quebrada na única tela que ninguém consegue evitar.
452
- */
453
- const isBackButton = (b) => b.dataset.tmBack != null || b.dataset.cenBack != null;
454
- /**
455
- * As duas partes de um botão do menu inicial, SEPARADAS.
456
- *
457
- * `textContent` cola o sub-rótulo no rótulo — o menu de alfabetização anunciava "Descobrindo palavrasBABA",
458
- * porque a palavra de exemplo vive num `<span class="act-sub">` sem espaço em volta. Separar não é cosmética:
459
- * o exemplo é o VALOR do item (a palavra que aquele minijogo treina), e o `anunciarItem` já sabe onde pôr um
460
- * valor. Clona antes de arrancar o `<span>` para não mexer no botão que está na tela.
461
- */
462
- function partesDoBotao(b) {
463
- // RÓTULO DECLARADO VENCE, e é o que o leitor de tela já vai anunciar. MEDIDO no botão de número de
464
- // jogadores: o jogo narrava "◀ Number of players: 1 ▶" enquanto o leitor dizia "Number of players: 1.
465
- // Click on the left for fewer…" — duas frases para o mesmo item, e a do jogo lendo os glifos das setas.
466
- const declarado = rotuloAcessivel(b);
467
- if (b.getAttribute('aria-label'))
468
- return { rotulo: declarado, estado: '' };
469
- const sub = b.querySelector('.act-sub');
470
- if (!sub)
471
- return { rotulo: b.textContent || '', estado: '' };
472
- const copia = b.cloneNode(true);
473
- copia.querySelectorAll('.act-sub').forEach((e) => e.remove());
474
- return { rotulo: copia.textContent || '', estado: sub.textContent || '' };
475
- }
476
- function navTitle(k) {
477
- const bs = titleButtons();
478
- if (!bs.length)
479
- return;
480
- const i = bs.indexOf(ctx.getActiveElement());
481
- if (k.up || k.down || k.left || k.right) {
482
- const n = nextTitleIndex(i, bs.length, k);
483
- bs[n].focus();
484
- // O índice "N de M" entra AQUI e não no `srSay`: é o menu que sabe quantos itens tem (ADR-0044, item 3).
485
- ctx.srSay(anunciarItem({ ...partesDoBotao(bs[n]), posicao: n + 1, total: bs.length }, menuIndexOn));
486
- }
487
- else if (k.yes) {
488
- (i < 0 ? bs[0] : bs[i]).click();
489
- }
490
- else if (k.no) {
491
- const back = bs.find(isBackButton);
492
- if (back)
493
- back.click();
494
- }
495
- }
496
- // --- wiring -------------------------------------------------------------------------------------------
497
- const ov = ctx.$('#title-overlay');
498
- if (!ov) {
499
- // No splash in this document: game.js's titleSetup() bailed out the same way, WITHOUT building the menus.
500
- return { actCat, setActivity, startActivity, reallyStart, buildTitleMenus, titleButtons, navTitle, tabSel, fracNot, getCenBack: () => cenBack, getPendingPlayers: () => pendingPlayers };
501
- }
502
- buildTitleMenus();
503
- // Player-count selector by KEYBOARD: ←/→ on the single button means −1/+1 (a click uses the half you hit).
504
- ov.addEventListener('keydown', (e) => {
505
- const b = e.target?.closest('#np-btn');
506
- if (!b)
507
- return;
508
- const ke = e;
509
- if (ke.key === 'ArrowLeft' || ke.key === 'ArrowRight') {
510
- ke.preventDefault();
511
- b.dataset.np = ke.key === 'ArrowLeft' ? '-1' : '1';
512
- b.click();
513
- }
514
- });
515
- // Footer (same style as the pause menu): the focused/hovered minigame's or notation's description.
516
- const showDesc = (e) => {
517
- const b = e.target?.closest('button[data-act-id],button[data-fnot]');
518
- if (!b)
519
- return;
520
- const d = titleDescFor(b.dataset.actId, b.dataset.fnot);
521
- const box = b.closest('.title-menu');
522
- const el = box && box.querySelector('.tm-desc');
523
- if (el)
524
- el.textContent = d;
525
- };
526
- ov.addEventListener('focusin', showDesc);
527
- ov.addEventListener('mouseover', showDesc);
528
- ov.addEventListener('click', (e) => {
529
- const b = e.target?.closest('button');
530
- if (!b)
531
- return;
532
- if (b.classList.contains('title-btn')) {
533
- b.classList.remove('act-fx');
534
- void b.offsetWidth;
535
- b.classList.add('act-fx');
536
- } // ACTIVATION effect
537
- const go = (fn) => { if (ov._busy)
538
- return; ov._busy = true; setTimeout(() => { ov._busy = false; fn(); }, 230); }; // the animation plays BEFORE the screen changes
539
- if (b.id === 'np-btn') { // ONE button "◀ Nº de jogadores: X ▶": the side you clicked (or ←/→) decides +1 / −1
540
- let d = +(b.dataset.np || 0);
541
- b.dataset.np = ''; // ←/→ set data-np on keydown; a click uses the position
542
- if (!d) {
543
- const r = b.getBoundingClientRect();
544
- d = (e.clientX - r.left) < r.width / 2 ? -1 : 1;
545
- }
546
- const n = clampPendingPlayers(pendingPlayers + d);
547
- // MESMO evento que game/session anuncia, e a frase divergia: esta omitia o mínimo de 640×360. Passa a
548
- // usar a chave já unificada, em vez de virar uma terceira redação da mesma recusa.
549
- if (n > 1 && !ctx.fitsN(n)) {
550
- ctx.srAlert(t('sr.screens.wontFitN', { n }));
551
- return;
552
- }
553
- pendingPlayers = n;
554
- escreverNp(n);
555
- ctx.srSay(t(n > 1 ? 'sr.menu.playersN' : 'sr.menu.player1', { n }));
556
- return;
557
- }
558
- if (b.dataset.tmFr) {
559
- go(() => { ctx.titleShow('tm-fr'); ctx.srSay(t('sr.menu.fractionsIntro')); });
560
- return;
561
- }
562
- if (b.dataset.fnot) {
563
- const k = b.dataset.fnot; // NOTATION toggle (immediate; at least 1 ALWAYS on)
564
- if (!canToggleFracNot(fracNot, k)) {
565
- ctx.srAlert(t('sr.menu.keepOneNotation'));
566
- return;
567
- }
568
- fracNot[k] = fracNot[k] ? 0 : 1;
569
- store.setJSON(store.KEYS.fracnot(ctx.gameId), fracNot);
570
- // O ESTADO EM DUAS FORMAS, e nenhuma delas é cor: `aria-checked` para quem escuta e a MARCA ☑/☐ para
571
- // quem vê. Era `tab-on` + `aria-pressed` — realce de fundo e papel de botão de comando —, e o realce
572
- // sozinho reprova a WCAG 1.4.1: quem tem baixa visão ou daltonismo ficava sem resposta para "quais
573
- // estão ligadas?".
574
- b.setAttribute('aria-checked', String(!!fracNot[k]));
575
- const marca = b.querySelector('.fnot-marca');
576
- if (marca)
577
- marca.textContent = fracNot[k] ? '☑' : '☐';
578
- ctx.srSay(t(fracNot[k] ? 'sr.menu.notationOn' : 'sr.menu.notationOff', { notacao: t(FNOT_LBL[k]) }));
579
- return;
580
- }
581
- if (b.dataset.cen) {
582
- const c = b.dataset.cen;
583
- go(() => { ctx.setCenario(c); reallyStart(); });
584
- return;
585
- }
586
- if (b.dataset.cenBack) {
587
- go(() => ctx.titleShow(cenBack));
588
- return;
589
- }
590
- if (b.dataset.tm === 'ludico') {
591
- go(() => startActivity('ludico'));
592
- return;
593
- }
594
- if (b.dataset.tm === 'alf') {
595
- go(() => ctx.titleShow('tm-alf'));
596
- return;
597
- }
598
- if (b.dataset.tm === 'mat') {
599
- go(() => ctx.titleShow('tm-mat'));
600
- return;
601
- }
602
- if (b.dataset.tmBack) {
603
- const to = b.dataset.tmBack;
604
- go(() => ctx.titleShow(to));
605
- return;
606
- }
607
- if (b.id === 'tab-play') {
608
- if (!tabSel.length) {
609
- ctx.srAlert(t('sr.menu.pickOneNumber'));
610
- return;
611
- }
612
- go(() => startActivity(tabFor));
613
- return;
614
- }
615
- if (b.dataset.tabN != null) {
616
- const n = +b.dataset.tabN, i = tabSel.indexOf(n); // toggle: no screen change → immediate
617
- if (i >= 0)
618
- tabSel.splice(i, 1);
619
- else
620
- tabSel.push(n);
621
- store.setJSON(store.KEYS.tabsel(ctx.gameId), tabSel);
622
- b.classList.toggle('tab-on', i < 0);
623
- b.setAttribute('aria-pressed', String(i < 0));
624
- ctx.srSay(t(i < 0 ? 'sr.menu.numberOn' : 'sr.menu.numberOff', { n }));
625
- return;
626
- }
627
- if (b.dataset.actId) {
628
- const id = b.dataset.actId;
629
- if (getActivity(id).pick) {
630
- go(() => {
631
- tabFor = id;
632
- buildTitleMenus();
633
- const titulo = ctx.$('#tm-tab .tm-title');
634
- if (titulo)
635
- titulo.textContent = nomeDaAtividade(getActivity(id)); // Tabuada/Divisão heading
636
- ctx.titleShow('tm-tab');
637
- ctx.srSay(t('sr.menu.pickNumbers', { atividade: nomeDaAtividade(getActivity(id)) }));
638
- });
639
- }
640
- else
641
- go(() => startActivity(id));
642
- }
643
- });
644
- return {
645
- actCat, setActivity, startActivity, reallyStart, buildTitleMenus, titleButtons, navTitle,
646
- tabSel, fracNot, getCenBack: () => cenBack, getPendingPlayers: () => pendingPlayers,
647
- };
648
- }