@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,13 +1,13 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
- import { type FontItem } from './fonts.js';
3
- /** Minimal DOM-selector shape (matches ui/dom.ts's `$`). */
4
- export type { DomQuery } from '../core/dom-query.js';
2
+ import type { Translate } from '../core/i18n.js';
5
3
  /** Minimal platform/storage.ts shape this module needs (get/set only — no direct localStorage access). */
6
4
  export interface TypoStore {
7
5
  get(key: string, fallback?: string | null): string | null;
8
6
  set(key: string, value: string | number | boolean): boolean;
9
7
  }
10
8
  export interface SettingsTypoCtx {
9
+ /** Translates in the page's language — the root's translator (ADR-0232 D3). REQUIRED: text built from nowhere is a raw key. */
10
+ t: Translate;
11
11
  /** DOM selector (querySelector), injected — never reaches `document` globally. */
12
12
  $: DomQuery;
13
13
  /** Screen-reader announcement (core/a11y-sr's srSay), injected. */
@@ -17,32 +17,29 @@ export interface SettingsTypoCtx {
17
17
  /** Element the chosen font is applied to (dataset.fonte + --font-custom): document.documentElement in prod. */
18
18
  root: HTMLElement;
19
19
  /**
20
- * Move a prosa das linhas para o rodapé (`ui/settings-panel` → `fillExplain`). Chamado a CADA `render()`, e
21
- * não só ao abrir — que é o defeito que este parâmetro existe para fechar.
20
+ * Moves the rows' prose to the footer (`ui/settings-panel` → `fillExplain`). Called on EVERY `render()`, not only on
21
+ * open.
22
22
  *
23
- * `fillExplain` roda uma vez quando o overlay é frontalizado e reescreve cada `<span>` para conter só o
24
- * rótulo curto. Mas `render()` reconstrói o `#typo-list` inteiro a cada clique numa fonte, e as linhas novas
25
- * voltam com o `.opt-hint` dentro. Sem esta chamada a descrição aparece DUAS vezes — no rodapé, vinda da
26
- * primeira passada, e sob o nome da fonte, vinda do redesenho. O CLAUDE.md §4 avisa exatamente isso.
23
+ * Relabelling a row puts its `.opt-hint` back inside it. Without this call the description appears TWICE — in the
24
+ * footer, from the first pass, and under the font's name, from the redraw. CLAUDE.md §4 warns about exactly that.
27
25
  *
28
- * OPCIONAL de propósito: um consumidor que monte este painel sem a casca (o segundo consumidor, um teste)
29
- * continua desenhando. O que ele não pode é desenhar prosa duplicada, e sem casca não há rodapé para duplicar.
26
+ * OPTIONAL on purpose: a consumer that mounts this panel without the shell (a test) keeps drawing. What it cannot do
27
+ * is draw duplicated prose, and without the shell there is no footer to duplicate.
30
28
  */
31
29
  fillExplain?: (card: HTMLElement | null) => void;
32
30
  /**
33
- * ESTA FAMÍLIA ESTÁ INSTALADA NO APARELHO? — em produção, `(f) => doc.fonts.check(\`16px "${f}"\`)`.
31
+ * IS THIS FAMILY INSTALLED ON THE DEVICE? — in production, `(f) => doc.fonts.check(\`16px "${f}"\`)`.
34
32
  *
35
- * É o que transforma o «enquanto» do ADR-0012 em código: a opção da Ronde fica desabilitada ENQUANTO
36
- * nenhuma das três faces estiver presente, e volta a ficar disponível quando o adulto instalar uma.
33
+ * It turns ADR-0012's «enquanto» into code: an `off` face stays disabled WHILE none of its families is present, and
34
+ * becomes available again when the adult installs one. (The ronde no longer depends on it: its stack ends in the
35
+ * packaged Cookie, ADR-0154.)
37
36
  *
38
- * ⚠️ INJECTADO E NUNCA `document.fonts` LIDO AQUI, pela regra que este ficheiro já segue para o `$`: um
39
- * global do navegador num módulo que corre em node é o ACHADO 15, e este projecto já pagou por ele com um
40
- * boot rebentado.
41
- * 📌 OPCIONAL, e aqui o padrão é mesmo seguro — ao contrário do `seguraTeclas` do `PauseIconsCtx`, onde os
42
- * dois lados erravam. Sem detector a linha fica desabilitada COM a mensagem, e a mensagem diz ao adulto as
43
- * três fontes que resolvem. O estado por omissão é o de hoje, e é accionável.
37
+ * ⚠️ INJECTED, and `document.fonts` is NEVER read here, by the rule this file already follows for `$`: a browser
38
+ * global in a module that runs in node is a boot waiting to crash.
39
+ * 📌 OPTIONAL, and here the default really is safe: with no detector an `off` row stays disabled WITH its message, and
40
+ * the message says what to install. The default state is actionable.
44
41
  */
45
- fonteInstalada?: (familia: string) => boolean;
42
+ fontInstalled?: (family: string) => boolean;
46
43
  }
47
44
  export interface SettingsTypoApi {
48
45
  /** Re-renders #typo-list from current selection and (re)wires its buttons. Idempotent. */
@@ -52,67 +49,21 @@ export interface SettingsTypoApi {
52
49
  /** Currently selected font key. */
53
50
  getFontKey: () => string;
54
51
  }
55
- /**
56
- * A key is selectable when it exists in the catalog, is not marked `.off` (licence pending, etc.) — and is
57
- * `geral`.
58
- *
59
- * ⚠️ O TERCEIRO TERMO ENTROU EM 2026-09-07 (issue #87), e sem ele o resto da mudança seria decoração: o menu
60
- * deixaria de OFERECER as caligráficas e elas continuariam SELECIONÁVEIS por qualquer outro caminho — o
61
- * `resolveFontKey` de uma chave guardada, um `data-font` num markup de consumidor. «Não está na lista» e «não
62
- * pode ser escolhida» têm de ser a mesma afirmação, ou a lista é só uma sugestão.
63
- */
64
- export declare function isSelectableFont(k: string, instalada?: (familia: string) => boolean): boolean;
65
52
  export { resolveFontKey, persistFontKey } from './fonts.js';
66
53
  import type { DomQuery } from '../core/dom-query.js';
67
- export interface FontCssTarget {
68
- /** Value written to root.dataset.fonte. */
69
- fonte: 'padrao' | 'alfabetizacao' | 'dislexia' | 'custom';
70
- /** Value for the --font-custom CSS property, or null to remove the property. */
71
- customFamily: string | null;
72
- }
73
- /**
74
- * Maps a selectable font key to the CSS it drives. The three canonical EdSP fonts (atkinson/andika/lexend) use
75
- * dedicated data-fonte values (Lexend's keeps the BDA letter/word spacing tied to data-fonte="dislexia"); every
76
- * other catalog font goes through --font-custom with a generic fallback by family (serif/cursive/none).
77
- * Verbatim port of the branching inside the old setGameFont().
78
- */
79
- export declare function fontCssTarget(k: string, it: FontItem): FontCssTarget;
80
- export interface TypoRow {
81
- key: string;
82
- fam: string;
83
- selected: boolean;
84
- disabled: boolean;
85
- /** Description (+ "— <off reason>" when disabled), or '' when there is none. */
86
- note: string;
87
- }
88
- export interface TypoGroupView {
89
- g: string;
90
- rows: TypoRow[];
91
- }
92
- /**
93
- * UMA linha da lista, a partir de uma face do catálogo. Extraída em 2026-09-07 (issue #87) porque o gate do
94
- * mecanismo `.off` precisava de a exercitar com uma face de MENTIRA: as duas únicas entradas desligadas
95
- * saíram do roster, e um caso que dependa da composição do catálogo reprova sempre que o roster muda.
96
- *
97
- * O mecanismo fica, e é preciso: o item 4 da #87 usa-o para a **Ronde**, que só pode ser oferecida se uma de
98
- * três faces estiver instalada, porque duas delas são gratuitas apenas para uso pessoal e não podem ser
99
- * empacotadas.
100
- */
101
- export declare function linhaDaFonte(it: FontItem, fontKey: string, instalada?: (familia: string) => boolean): TypoRow;
102
- /** Pure view-model for the typography list: which row is selected/disabled and its note, per catalog group. */
103
- export declare function typoGroups(fontKey: string, instalada?: (familia: string) => boolean): TypoGroupView[];
54
+ import type { PanelShellCtx } from './panel-shell.js';
104
55
  /**
105
- * Full innerHTML for #typo-list, given the currently selected key. Pure string building — no DOM.
56
+ * Mounts the list ONCE, inside a single radio group. Called again, it RELABELS instead of rebuilding.
106
57
  *
107
- * ⚠️ É UM GRUPO DE RÁDIO SÓ, ATRAVESSANDO AS TRÊS SECÇÕES, e isso é a decisão e não um detalhe de
108
- * marcação: a exclusividade é do MENU inteiro — uma fonte activa no total —, não de cada família. Três
109
- * grupos diriam a quem escuta que dá para ter uma sans E uma serif ao mesmo tempo.
58
+ * ⚠️ ONE RADIO GROUP ACROSS ALL SECTIONS, and that is the decision and not a markup detail: exclusivity belongs to the
59
+ * WHOLE menu — one active font in total —, not to each family. Separate groups would tell a listener they can have a
60
+ * sans AND a serif at once. That is why the `radiogroup` belongs to the PANEL and not the kit: only the panel knows
61
+ * where exclusivity ends.
110
62
  *
111
- * ⚠️ E ISTO DEIXOU DE SER UM INTERRUPTOR EM 2026-09-07. Era `class="mode-btn switch"` + `aria-pressed` +
112
- * `toggleLabel()`, ou seja dezassete interruptores independentes anunciando «Ligado»/«Desligado» para
113
- * escolher UMA fonte. A emenda do ADR-0012 diz o contrário em tantas palavras: «THE MENU IS A CHOICE, NOT
114
- * A TOGGLE […] One font is active; the others are alternatives, not switches.» O `switch` do
115
- * `style.css:427` desenha uma chave de 52×28 px com bolinha — era o desenho de um estado que não existe.
63
+ * 🔴 AND EACH ROW'S LABEL IS DRAWN IN ITS OWN FACE, which is this menu's central behaviour: a list of NAMES lets no one
64
+ * choose a typeface, and whoever most needs to choose is whoever reads the face they are looking at poorly. The note
65
+ * beside it stays in the READING face on purpose — it explains the choice, it is not the choice. (With `fillExplain`,
66
+ * it does not even stay on the row: it goes to the footer.)
116
67
  */
117
- export declare function typoListHTML(fontKey: string, instalada?: (familia: string) => boolean): string;
68
+ export declare function mountTypoInside(t: Translate, ctx: PanelShellCtx, list: HTMLElement, fontKey: string, isInstalled?: (family: string) => boolean): void;
118
69
  export declare function initSettingsTypo(ctx: SettingsTypoCtx): SettingsTypoApi;
@@ -1,194 +1,216 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
- // ui/settings-typo — Typography panel (Estágio 4): extracted from game.js's renderTypo()/setGameFont(). Pure
3
- // logic (catalog view-model, key→CSS-target mapping, persisted-value validation) is separated from the thin
4
- // DOM-touching render()/setFont(). DI via initSettingsTypo(ctx): `$` (DOM selector), `srSay`, `store`
5
- // (platform/storage shape) and `root` (the element the chosen font is applied to — document.documentElement in
6
- // production). Overlay open/close plumbing (frontOverlay, focus management, the #typo hidden toggle, Escape
7
- // handling) is the SHARED helper used by every settings panel and stays in game.js. The font catalog itself
8
- // (FONT_GROUPS/FONT_BY_KEY) stays in ./fonts.js (Phase 2 extraction) — imported here, never duplicated.
9
- // (`toggleLabel` saiu daqui em 2026-09-07: ele devolve «Ligado»/«Desligado», e este menu é uma ESCOLHA.)
10
- import { t } from '../core/i18n.js';
11
- import { FONT_GROUPS, FONT_BY_KEY, DEFAULT_FONT_KEY, papelDaFonte, faceDisponivel } from './fonts.js';
2
+ // ui/settings-typo — Typography panel. The thin DOM-touching render()/setFont(); what a choice IS lives in
3
+ // ./typo-choices.js. DI via initSettingsTypo(ctx): `$` (DOM selector), `srSay`, `store` (platform/storage shape) and
4
+ // `root` (the element the chosen font is applied to — document.documentElement in production). Overlay open/close
5
+ // plumbing (frontOverlay, focus management, Escape handling) is the SHARED helper every settings panel uses. The font
6
+ // catalog itself (FONT_GROUPS/FONT_BY_KEY) lives in ./fonts.js — imported here, never duplicated.
7
+ import { FONT_BY_KEY, DEFAULT_FONT_KEY, faceAvailable, faceScale, familyStack } from './fonts.js';
12
8
  import { markChanged, markMenuChanged, CHANGED_CLASS } from './changed-mark.js';
13
9
  // ---------------------------------------------------------------------------------------------
14
10
  // Pure logic (no `document`, testable in node)
15
11
  // ---------------------------------------------------------------------------------------------
16
- /**
17
- * A key is selectable when it exists in the catalog, is not marked `.off` (licence pending, etc.) — and is
18
- * `geral`.
19
- *
20
- * ⚠️ O TERCEIRO TERMO ENTROU EM 2026-09-07 (issue #87), e sem ele o resto da mudança seria decoração: o menu
21
- * deixaria de OFERECER as caligráficas e elas continuariam SELECIONÁVEIS por qualquer outro caminho — o
22
- * `resolveFontKey` de uma chave guardada, um `data-font` num markup de consumidor. «Não está na lista» e «não
23
- * pode ser escolhida» têm de ser a mesma afirmação, ou a lista é só uma sugestão.
24
- */
25
- export function isSelectableFont(k, instalada) {
26
- const it = FONT_BY_KEY[k];
27
- return !!it && faceDisponivel(it, instalada) && papelDaFonte(it) === 'geral';
28
- }
29
- // A semantica da chave (validacao + migracao da chave antiga) mora em ui/fonts.ts, que e o dono do
30
- // catalogo; re-exportada aqui para quem ja consome este modulo. Uma implementacao, nao duas.
12
+ // The key's semantics (validation + migration of the old key) live in ui/fonts.ts, which owns the catalogue;
13
+ // re-exported here for whoever already consumes this module. One implementation, not two.
31
14
  export { resolveFontKey, persistFontKey } from './fonts.js';
32
15
  import { resolveFontKey, persistFontKey } from './fonts.js';
33
- /**
34
- * Maps a selectable font key to the CSS it drives. The three canonical EdSP fonts (atkinson/andika/lexend) use
35
- * dedicated data-fonte values (Lexend's keeps the BDA letter/word spacing tied to data-fonte="dislexia"); every
36
- * other catalog font goes through --font-custom with a generic fallback by family (serif/cursive/none).
37
- * Verbatim port of the branching inside the old setGameFont().
16
+ import { controlRow, labelRow, sectionHeader } from './panel-widgets.js';
17
+ /*
18
+ * 🎯 WHAT A TYPOGRAPHY CHOICE IS lives in `./typo-choices.js`, and this file keeps the work its name always described:
19
+ * finding the nodes it reaches and never created, wiring them, and reflecting the choice. No alias left behind — a
20
+ * re-export would keep alive a published path nothing here uses, surface with no consumer (#204).
38
21
  */
39
- export function fontCssTarget(k, it) {
40
- if (k === 'atkinson')
41
- return { fonte: 'padrao', customFamily: null };
42
- if (k === 'andika')
43
- return { fonte: 'alfabetizacao', customFamily: null };
44
- if (k === 'lexend')
45
- return { fonte: 'dislexia', customFamily: null };
46
- const suffix = it.fb === 'serif' ? ',Georgia,serif' : it.fb === 'cursive' ? ',cursive' : '';
47
- return { fonte: 'custom', customFamily: `'${it.fam}'${suffix}` };
48
- }
22
+ import { typoGroups, typoRowSpec, fontCssTarget } from './typo-choices.js';
49
23
  /**
50
- * UMA linha da lista, a partir de uma face do catálogo. Extraída em 2026-09-07 (issue #87) porque o gate do
51
- * mecanismo `.off` precisava de a exercitar com uma face de MENTIRA: as duas únicas entradas desligadas
52
- * saíram do roster, e um caso que dependa da composição do catálogo reprova sempre que o roster muda.
24
+ * ⚠️ THE SELECTION MARK, and it exists because COLOUR IS NOT STATE.
53
25
  *
54
- * O mecanismo fica, e é preciso: o item 4 da #87 usa-o para a **Ronde**, que só pode ser oferecida se uma de
55
- * três faces estiver instalada, porque duas delas são gratuitas apenas para uso pessoal e não podem ser
56
- * empacotadas.
26
+ * `.mode-btn.is-on` paints the button with `var(--accent)` — the yellow background the ADR-0012 amendment asks for in
27
+ * so many words. But whoever does not tell the colour apart sees no state at all: the state has to come in TWO forms,
28
+ * and neither of them a colour.
57
29
  */
58
- export function linhaDaFonte(it, fontKey, instalada) {
59
- // ⚠️ A MESMA pergunta que o `isSelectableFont` faz, pela MESMA função. Duas respostas dariam uma linha
60
- // clicável que o clique recusa — ou, pior, uma linha cinzenta que o `resolveFontKey` aceita por outro
61
- // caminho. «Não está disponível» e «não pode ser escolhida» têm de ser a mesma afirmação.
62
- const disabled = !faceDisponivel(it, instalada);
63
- // `d` e `off` também guardam CHAVE. O travessão que junta os dois é pontuação, não frase — as duas
64
- // metades são independentes e cada uma traduz por si.
65
- const desc = it.d ? t(it.d) : '', motivo = it.off ? t(it.off) : '';
66
- const note = desc ? desc + (disabled ? ' — ' + motivo : '') : disabled ? motivo : '';
67
- return { key: it.k, fam: it.fam, selected: fontKey === it.k, disabled, note };
68
- }
69
- /** Pure view-model for the typography list: which row is selected/disabled and its note, per catalog group. */
70
- export function typoGroups(fontKey, instalada) {
71
- // ⚠️ SÓ AS GERAIS ENTRAM NA LISTA (emenda do ADR-0012, issue #87). As caligráficas existem para a criança
72
- // APRENDER a ler letra cursiva — isso é matéria, e vive DENTRO das atividades, em botões próprios. Oferecê-
73
- // las aqui é dar-lhe a matéria como obstáculo em todo lugar onde ela só quer navegar o menu.
74
- //
75
- // Um grupo que fique sem nenhuma face geral desaparece da lista, em vez de aparecer como título vazio.
76
- return FONT_GROUPS.map((g) => ({
77
- g: t(g.g), // `g` guarda CHAVE i18n desde o item 14 (ver ui/fonts)
78
- rows: g.items.filter((it) => papelDaFonte(it) === 'geral').map((it) => linhaDaFonte(it, fontKey, instalada)),
79
- })).filter((grupo) => grupo.rows.length > 0);
80
- }
30
+ const CHOSEN_MARK = '●';
31
+ const OFFERED_MARK = '○';
81
32
  /**
82
- * ⚠️ A MARCA DE SELEÇÃO, e ela existe porque COR NÃO É ESTADO.
33
+ * Mounts the list ONCE, inside a single radio group. Called again, it RELABELS instead of rebuilding.
34
+ *
35
+ * ⚠️ ONE RADIO GROUP ACROSS ALL SECTIONS, and that is the decision and not a markup detail: exclusivity belongs to the
36
+ * WHOLE menu — one active font in total —, not to each family. Separate groups would tell a listener they can have a
37
+ * sans AND a serif at once. That is why the `radiogroup` belongs to the PANEL and not the kit: only the panel knows
38
+ * where exclusivity ends.
83
39
  *
84
- * O `.mode-btn.is-on` pinta o botão com `var(--accent)` — o fundo amarelo que a emenda do ADR-0012 pede
85
- * por extenso. Mas quem não distingue a cor não vê estado nenhum, e é a mesma razão pela qual
86
- * `ui/activities-menu.ts:656` já emite ☑/☐ ao lado do `aria-checked`: «o estado em DUAS formas, e nenhuma
87
- * delas é cor».
40
+ * 🔴 AND EACH ROW'S LABEL IS DRAWN IN ITS OWN FACE, which is this menu's central behaviour: a list of NAMES lets no one
41
+ * choose a typeface, and whoever most needs to choose is whoever reads the face they are looking at poorly. The note
42
+ * beside it stays in the READING face on purpose — it explains the choice, it is not the choice. (With `fillExplain`,
43
+ * it does not even stay on the row: it goes to the footer.)
88
44
  */
89
- const MARCA_ESCOLHIDA = '●';
90
- const MARCA_ALTERNATIVA = '○';
91
- function rowHTML(row) {
92
- const noteHTML = row.note
93
- ? `<br><span class="opt-hint" style="margin:0;font-family:var(--font)">${row.note}</span>`
94
- : '';
95
- const ariaLabel = row.fam + (row.note ? ' — ' + row.note : '');
96
- return (`<div class="ctrl-row"><span style="font-family:'${row.fam}'"><strong>${row.fam}</strong>${noteHTML}</span>` +
97
- `<button class="mode-btn${row.selected ? ' is-on' : ''}" data-font="${row.key}" type="button" role="radio"` +
98
- `${row.disabled ? ' disabled' : ''} aria-checked="${row.selected}" aria-label="${ariaLabel}">` +
99
- `${row.selected ? MARCA_ESCOLHIDA : MARCA_ALTERNATIVA}</button></div>`);
45
+ export function mountTypoInside(t, ctx, list, fontKey, isInstalled) {
46
+ let radios = list.querySelector('[role="radiogroup"]');
47
+ if (!radios) {
48
+ radios = ctx.create('div');
49
+ radios.setAttribute('role', 'radiogroup');
50
+ list.appendChild(radios);
51
+ }
52
+ radios.setAttribute('aria-label', t('font.grupo.rotulo'));
53
+ for (const group of typoGroups(t, fontKey, isInstalled)) {
54
+ const head = radios.querySelector(`[data-typo-group="${group.g}"]`)
55
+ ? null
56
+ : sectionHeader(ctx, group.g, '', group.rows.length);
57
+ if (head) {
58
+ head.setAttribute('data-typo-group', group.g);
59
+ radios.appendChild(head);
60
+ }
61
+ for (const row of group.rows) {
62
+ const spec = typoRowSpec(row);
63
+ const old = ctx.find('#' + spec.id)?.closest('.ctrl-row');
64
+ if (old) {
65
+ labelRow(old, spec);
66
+ dressRow(old, row);
67
+ continue;
68
+ }
69
+ const { row: rowNode, control: faceButton } = controlRow(ctx, spec);
70
+ faceButton.dataset.font = row.key;
71
+ dressRow(rowNode, row);
72
+ radios.appendChild(rowNode);
73
+ }
74
+ }
75
+ }
76
+ /** What the kit does not know about a face: the face itself on the label, and the note in the reading face. */
77
+ function dressRow(where, row) {
78
+ const label = where.querySelector(':scope > span');
79
+ // the whole stack, each family quoted: the ronde's label must draw in Cookie on a device without the three (ADR-0154)
80
+ if (label)
81
+ label.style.fontFamily = familyStack(row.fam);
82
+ const note = where.querySelector('.opt-hint');
83
+ if (note) {
84
+ note.style.fontFamily = 'var(--font)';
85
+ note.style.margin = '0';
86
+ }
100
87
  }
101
88
  /**
102
- * Full innerHTML for #typo-list, given the currently selected key. Pure string building — no DOM.
89
+ * Reflects the choice onto the rows that already exist: the mark, the spoken state and the lock.
103
90
  *
104
- * ⚠️ É UM GRUPO DE RÁDIO SÓ, ATRAVESSANDO AS TRÊS SECÇÕES, e isso é a decisão e não um detalhe de
105
- * marcação: a exclusividade é do MENU inteiro — uma fonte activa no total —, não de cada família. Três
106
- * grupos diriam a quem escuta que dá para ter uma sans E uma serif ao mesmo tempo.
91
+ * 🔴 THE ● / ○ MARK EXISTS BECAUSE COLOUR IS NOT STATE (see `CHOSEN_MARK`).
107
92
  *
108
- * ⚠️ E ISTO DEIXOU DE SER UM INTERRUPTOR EM 2026-09-07. Era `class="mode-btn switch"` + `aria-pressed` +
109
- * `toggleLabel()`, ou seja dezassete interruptores independentes anunciando «Ligado»/«Desligado» para
110
- * escolher UMA fonte. A emenda do ADR-0012 diz o contrário em tantas palavras: «THE MENU IS A CHOICE, NOT
111
- * A TOGGLE […] One font is active; the others are alternatives, not switches.» O `switch` do
112
- * `style.css:427` desenha uma chave de 52×28 px com bolinha — era o desenho de um estado que não existe.
93
+ * 📌 THE LOCK is reflected and not built, because it can CHANGE: an `off` face becomes available the instant the adult
94
+ * installs one of the faces its message names (ADR-0012, the word «enquanto»).
113
95
  */
114
- export function typoListHTML(fontKey, instalada) {
115
- const grupos = typoGroups(fontKey, instalada)
116
- .map((group) => `<h3 class="panel-sub">${group.g}</h3>` + group.rows.map(rowHTML).join(''))
117
- .join('');
118
- return `<div role="radiogroup" aria-label="${t('font.grupo.rotulo')}">${grupos}</div>`;
96
+ function reflectTypo(list, fontKey, isInstalled) {
97
+ // Walks what EXISTS in the list, not the catalogue: reflecting is about the nodes already there, and asking the
98
+ // catalogue again would build the list a second time just to read it.
99
+ for (const b of list.querySelectorAll('button[data-font]')) {
100
+ const it = FONT_BY_KEY[b.dataset.font ?? ''];
101
+ const isChosen = b.dataset.font === fontKey;
102
+ b.classList.toggle('is-on', isChosen);
103
+ b.setAttribute('aria-checked', String(isChosen));
104
+ b.textContent = isChosen ? CHOSEN_MARK : OFFERED_MARK;
105
+ b.disabled = !it || !faceAvailable(it, isInstalled);
106
+ }
119
107
  }
120
108
  // ---------------------------------------------------------------------------------------------
121
109
  // DOM-facing (thin) — requires `document`/injected ctx
122
110
  // ---------------------------------------------------------------------------------------------
123
111
  export function initSettingsTypo(ctx) {
112
+ const { t } = ctx;
124
113
  let fontKey = resolveFontKey(ctx.store);
125
- function setFont(k, announce = false) {
114
+ /*
115
+ * 📌 THE KIT'S CTX COMES FROM THE LIST NODE ITSELF: `ownerDocument` is the document it LIVES in, which is where the
116
+ * rows must be born. So this module's global reach stays zero (ADR-0221) and `SettingsTypoCtx`, which is published
117
+ * surface, gains no required member (ADR-0172).
118
+ */
119
+ const panelCtx = (list) => ({
120
+ find: (sel) => ctx.$(sel),
121
+ create: (tag) => list.ownerDocument.createElement(tag),
122
+ });
123
+ function setFont(k, announce = false) { choose(k, announce, true); }
124
+ function choose(k, announce, persist) {
126
125
  const it = FONT_BY_KEY[k];
127
- // A MESMA função das outras duas leituras: uma face que a lista mostra clicável tem de ser aceite aqui,
128
- // e uma que ela mostra cinzenta tem de ser recusada. Três respostas à mesma pergunta divergem.
129
- if (!it || !faceDisponivel(it, ctx.fonteInstalada))
126
+ // The SAME function as the other two readings: a face the list shows clickable has to be accepted here, and one it
127
+ // shows grey has to be refused. Three answers to the same question drift.
128
+ if (!it || !faceAvailable(it, ctx.fontInstalled))
130
129
  return;
131
130
  fontKey = k;
132
- persistFontKey(ctx.store, k);
131
+ if (persist)
132
+ persistFontKey(ctx.store, k);
133
133
  const target = fontCssTarget(k, it);
134
- ctx.root.dataset.fonte = target.fonte;
134
+ ctx.root.dataset.fonte = target.font;
135
+ /*
136
+ * 🔴 THE JOINED-FACE MARK (ADR-0149 §3), which removes the BDA spacing. `:root[data-cursiva]` returns
137
+ * `letter-spacing`/`word-spacing` to `normal`, because spacing a cursive face breaks it at the joins that make it
138
+ * cursive — «para manter os conectores».
139
+ *
140
+ * ⚠️ IT ERASES WHEN IT IS NOT, not only writes when it is: without the `delete`, a child who chose a cursive face
141
+ * and went back to Atkinson would have the reading face WITHOUT the spacing — the defect in the costliest
142
+ * direction, because whoever goes back to the reading face is whoever needs it.
143
+ */
144
+ if (target.cursive)
145
+ ctx.root.dataset.cursiva = '1';
146
+ else
147
+ delete ctx.root.dataset.cursiva;
135
148
  if (target.customFamily)
136
149
  ctx.root.style.setProperty('--font-custom', target.customFamily);
137
150
  else
138
151
  ctx.root.style.removeProperty('--font-custom');
152
+ // drawn at its floor, never under it (ADR-0176 §4): a face asking 20 px makes the text 25% larger; the others give it back
153
+ ctx.root.style.setProperty('--fonte-escala', String(faceScale(it)));
139
154
  const pv = ctx.$('#typo-preview');
140
155
  if (pv)
141
- pv.style.fontFamily = `'${it.fam}'`;
156
+ pv.style.fontFamily = familyStack(it.fam);
142
157
  if (announce)
143
158
  ctx.srSay(t('sr.typo.font', { fam: it.fam }));
144
159
  }
160
+ /*
161
+ * The listeners are wired ONCE, on the list, by delegation — which mounting once allows. ⚠️ A `disabled` button emits
162
+ * no click, so the lock is still what protects the face the device does not have — not a guard here, which would be
163
+ * a second answer to the same question.
164
+ */
165
+ let listening = false;
145
166
  function render() {
146
167
  const el = ctx.$('#typo-list');
147
168
  if (!el)
148
169
  return;
149
- el.innerHTML = typoListHTML(fontKey, ctx.fonteInstalada);
150
- el.querySelectorAll('button[data-font]').forEach((b) => {
151
- b.addEventListener('click', () => {
152
- const k = b.dataset.font;
170
+ mountTypoInside(t, panelCtx(el), el, fontKey, ctx.fontInstalled);
171
+ reflectTypo(el, fontKey, ctx.fontInstalled);
172
+ if (!listening) {
173
+ listening = true;
174
+ el.addEventListener('click', (ev) => {
175
+ const b = ev.target?.closest('button[data-font]');
176
+ const k = b?.dataset.font;
153
177
  if (!k)
154
178
  return;
155
179
  setFont(k, true);
156
180
  render();
157
181
  });
158
- });
182
+ }
159
183
  const cur = FONT_BY_KEY[fontKey];
160
184
  const pv = ctx.$('#typo-preview');
161
185
  if (pv && cur)
162
- pv.style.fontFamily = `'${cur.fam}'`;
186
+ pv.style.fontFamily = familyStack(cur.fam);
163
187
  refreshMarks();
164
- // A ÚLTIMA COISA DO RENDER, e tem de ser: as linhas acabaram de ser recriadas com a prosa dentro delas.
188
+ // THE LAST THING IN RENDER, and it has to be: the rows were just relabelled with the prose inside them.
165
189
  ctx.fillExplain?.(ctx.$('#typo .overlay__card'));
166
190
  }
167
191
  /**
168
- * A marca de "saiu do padrão" (ADR-0029). Mora DENTRO do render porque é derivada, nunca guardada: ela é
169
- * recalculada de valor-atual-contra-padrão a cada desenho, então não tem como envelhecer no armazenamento.
170
- * Envelhecer na TELA ela tem — se algum dia alguém mudar a fonte sem redesenhar —, e é por isso que a
171
- * atualização anda junto com quem já redesenha, e não numa função própria que se possa esquecer de chamar.
192
+ * The left-the-default mark (ADR-0029). It lives INSIDE render because it is derived, never stored: it is recomputed
193
+ * from current-value-against-default on every draw, so it cannot go stale in storage. It could go stale on SCREEN —
194
+ * if someone changed the font without redrawing —, which is why the update travels with what already redraws.
172
195
  *
173
- * Aqui a linha marcada é a da fonte ESCOLHIDA, e só quando ela não é a padrão: as outras quinze não saíram
174
- * do padrão, foram apenas oferecidas.
196
+ * Here the marked row is the CHOSEN font's, and only when it is not the default: the others did not leave the default,
197
+ * they were only offered.
175
198
  */
176
199
  function refreshMarks() {
177
200
  const changed = fontKey !== DEFAULT_FONT_KEY;
178
201
  const list = ctx.$('#typo-list');
179
- list?.querySelectorAll('.' + CHANGED_CLASS).forEach((el) => markChanged(el, false));
202
+ list?.querySelectorAll('.' + CHANGED_CLASS).forEach((el) => markChanged(t, el, false));
180
203
  const sel = list?.querySelector(`button[data-font="${fontKey}"]`);
181
- markChanged(sel?.closest('.ctrl-row') ?? null, changed);
182
- markMenuChanged(ctx.$('[data-act="tipo"]'), [changed]);
204
+ markChanged(t, sel?.closest('.ctrl-row') ?? null, changed);
205
+ markMenuChanged(t, ctx.$('[data-act="tipo"]'), [changed]);
183
206
  }
184
- // ---- restaurar os padrões DESTE menu (ADR-0028) ----
207
+ // ---- reset THIS menu's defaults (ADR-0028) ----
185
208
  //
186
- // O menu mais simples dos oito: a tipografia guarda uma escolha só, então o reset é uma linha. Ainda assim
187
- // vale dizer para onde ele volta — a Atkinson Hyperlegible não é o padrão por ser bonita, é o padrão por ter
188
- // sido desenhada para quem tem baixa visão. Uma criança que experimentou seis fontes e não consegue mais ler
189
- // a tela precisa de um caminho de volta que termine na MAIS legível, não numa qualquer.
209
+ // Typography stores a single choice, so the reset is one line. Still, where it returns to is worth saying — Atkinson
210
+ // Hyperlegible is not the default for being pretty, it is the default for having been designed for low vision. A
211
+ // child who tried six fonts and can no longer read the screen needs a way back that ends at the MOST legible one.
190
212
  //
191
- // O anúncio nomeia a fonte porque a mudança é visível para quem enxerga e invisível para quem não enxerga.
213
+ // The announcement names the font because the change is visible to whoever sees and invisible to whoever does not.
192
214
  const resetBtn = ctx.$('#typo-reset');
193
215
  if (resetBtn)
194
216
  resetBtn.addEventListener('click', () => {
@@ -196,6 +218,13 @@ export function initSettingsTypo(ctx) {
196
218
  render();
197
219
  ctx.srSay(t('sr.typo.reset', { fam: FONT_BY_KEY[DEFAULT_FONT_KEY].fam }));
198
220
  });
199
- setFont(fontKey, false); // aplica a fonte persistida ao boot (== antigo `setGameFont(fontKey,false)`)
221
+ /*
222
+ * Applies the persisted font at boot. 📌 A stored face THIS page does not have — a library family its game did not declare
223
+ * (ADR-0255) — is drawn as the default and NOT written over: the choice stays stored for a game that declares the family, and
224
+ * `getFontKey` answers the face really drawn instead of one the page cannot draw.
225
+ */
226
+ const stored = FONT_BY_KEY[fontKey];
227
+ const drawable = !!stored && faceAvailable(stored, ctx.fontInstalled);
228
+ choose(drawable ? fontKey : DEFAULT_FONT_KEY, false, drawable);
200
229
  return { render, setFont, getFontKey: () => fontKey };
201
230
  }