@the-inclusionist/engine 10.0.0 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (528) hide show
  1. package/README.md +32 -1
  2. package/app/css/style.css +216 -42
  3. package/app/public/vendor/fonts-licences/NOTICE.txt +166 -0
  4. package/app/public/vendor/fonts-licences/OFL-1.1.txt +86 -0
  5. package/app/public/vendor/fonts.css +11 -1175
  6. package/dist-pkg/boot/create-game.d.ts +84 -33
  7. package/dist-pkg/boot/create-game.js +708 -307
  8. package/dist-pkg/core/a11y-sr.d.ts +1 -1
  9. package/dist-pkg/core/a11y-sr.js +3 -5
  10. package/dist-pkg/core/accommodations.d.ts +26 -8
  11. package/dist-pkg/core/accommodations.js +27 -10
  12. package/dist-pkg/core/actions.d.ts +39 -10
  13. package/dist-pkg/core/actions.js +28 -6
  14. package/dist-pkg/core/contract.d.ts +0 -2
  15. package/dist-pkg/core/contract.js +0 -2
  16. package/dist-pkg/core/i18n.d.ts +36 -71
  17. package/dist-pkg/core/i18n.js +131 -198
  18. package/dist-pkg/core/session-clock.d.ts +23 -0
  19. package/dist-pkg/core/session-clock.js +37 -0
  20. package/dist-pkg/i18n/en.js +44 -49
  21. package/dist-pkg/i18n/es.js +44 -49
  22. package/dist-pkg/i18n/pt.js +49 -54
  23. package/dist-pkg/input/gamepad.d.ts +42 -11
  24. package/dist-pkg/input/gamepad.js +50 -15
  25. package/dist-pkg/input/key-default.d.ts +30 -0
  26. package/dist-pkg/input/key-default.js +60 -0
  27. package/dist-pkg/input/keyboard-runtime.d.ts +0 -4
  28. package/dist-pkg/input/keyboard.d.ts +0 -1
  29. package/dist-pkg/input/keydown.d.ts +5 -11
  30. package/dist-pkg/input/keydown.js +3 -3
  31. package/dist-pkg/input/latch-edge.d.ts +10 -2
  32. package/dist-pkg/input/latch-edge.js +4 -3
  33. package/dist-pkg/input/latch-scope.d.ts +27 -22
  34. package/dist-pkg/input/latch-scope.js +25 -37
  35. package/dist-pkg/input/latch-store.d.ts +9 -12
  36. package/dist-pkg/input/latch-store.js +13 -18
  37. package/dist-pkg/input/latch-sync.d.ts +5 -4
  38. package/dist-pkg/input/latch-sync.js +5 -5
  39. package/dist-pkg/input/pad-reading.js +7 -2
  40. package/dist-pkg/input/pad-wizard.d.ts +4 -1
  41. package/dist-pkg/input/pad-wizard.js +60 -9
  42. package/dist-pkg/input/switch-scan.d.ts +29 -12
  43. package/dist-pkg/input/switch-scan.js +15 -2
  44. package/dist-pkg/input/touch-bindings.d.ts +3 -6
  45. package/dist-pkg/input/touch.d.ts +17 -2
  46. package/dist-pkg/input/touch.js +55 -17
  47. package/dist-pkg/input/transport-in-use.d.ts +5 -5
  48. package/dist-pkg/input/transport-in-use.js +5 -5
  49. package/dist-pkg/input/transports.d.ts +0 -9
  50. package/dist-pkg/input/transports.js +1 -1
  51. package/dist-pkg/input/virtual-controller.d.ts +39 -3
  52. package/dist-pkg/input/virtual-controller.js +32 -4
  53. package/dist-pkg/input/voice-map.d.ts +27 -11
  54. package/dist-pkg/input/voice-map.js +41 -43
  55. package/dist-pkg/platform/audio-ambient.d.ts +0 -1
  56. package/dist-pkg/platform/audio-ambient.js +3 -1
  57. package/dist-pkg/platform/audio-mixer.js +4 -9
  58. package/dist-pkg/platform/audio-sonar.d.ts +8 -94
  59. package/dist-pkg/platform/audio-sonar.js +22 -207
  60. package/dist-pkg/platform/audio.d.ts +10 -1
  61. package/dist-pkg/platform/audio.js +35 -2
  62. package/dist-pkg/platform/choose-by-voice.d.ts +70 -0
  63. package/dist-pkg/platform/choose-by-voice.js +157 -0
  64. package/dist-pkg/platform/font-library.d.ts +77 -0
  65. package/dist-pkg/platform/font-library.js +98 -0
  66. package/dist-pkg/platform/font-library.json +721 -0
  67. package/dist-pkg/platform/heavy-catalogue.d.ts +46 -0
  68. package/dist-pkg/platform/heavy-catalogue.js +47 -14
  69. package/dist-pkg/platform/heavy-mirror.d.ts +3 -9
  70. package/dist-pkg/platform/heavy-mirror.js +14 -17
  71. package/dist-pkg/platform/heavy.d.ts +40 -8
  72. package/dist-pkg/platform/heavy.js +177 -22
  73. package/dist-pkg/platform/interruptible-speech.d.ts +15 -8
  74. package/dist-pkg/platform/interruptible-speech.js +37 -13
  75. package/dist-pkg/platform/listener-scope.js +22 -13
  76. package/dist-pkg/platform/locale-host.d.ts +7 -3
  77. package/dist-pkg/platform/locale-host.js +13 -2
  78. package/dist-pkg/platform/reading-in-worker.d.ts +24 -0
  79. package/dist-pkg/platform/reading-in-worker.js +38 -0
  80. package/dist-pkg/platform/reading-model.d.ts +21 -0
  81. package/dist-pkg/platform/reading-model.js +25 -0
  82. package/dist-pkg/platform/reading-runtime.js +13 -8
  83. package/dist-pkg/platform/reading.d.ts +12 -1
  84. package/dist-pkg/platform/reading.js +9 -1
  85. package/dist-pkg/platform/speech-recognition.d.ts +10 -1
  86. package/dist-pkg/platform/speech-recognition.js +29 -9
  87. package/dist-pkg/platform/speech.d.ts +10 -1
  88. package/dist-pkg/platform/speech.js +24 -13
  89. package/dist-pkg/platform/tts.d.ts +37 -4
  90. package/dist-pkg/platform/tts.js +279 -57
  91. package/dist-pkg/platform/voice-listener.d.ts +6 -2
  92. package/dist-pkg/platform/voice-listener.js +1 -1
  93. package/dist-pkg/platform/voice-plan.d.ts +13 -0
  94. package/dist-pkg/platform/voice-plan.js +57 -2
  95. package/dist-pkg/platform/vosk-runtime.d.ts +25 -0
  96. package/dist-pkg/platform/vosk-runtime.js +59 -1
  97. package/dist-pkg/platform/vosk-vocabulary.d.ts +6 -0
  98. package/dist-pkg/platform/vosk-vocabulary.js +178 -0
  99. package/dist-pkg/render/crt.d.ts +11 -9
  100. package/dist-pkg/render/crt.js +6 -10
  101. package/dist-pkg/render/cvd-matrices.d.ts +5 -1
  102. package/dist-pkg/render/cvd-matrices.js +5 -1
  103. package/dist-pkg/render/high-contrast.d.ts +1 -2
  104. package/dist-pkg/render/high-contrast.js +5 -1
  105. package/dist-pkg/render/viz-axes.d.ts +9 -0
  106. package/dist-pkg/render/viz-axes.js +15 -2
  107. package/dist-pkg/ui/aac-sets.d.ts +29 -24
  108. package/dist-pkg/ui/aac-sets.js +22 -30
  109. package/dist-pkg/ui/audio-choices.d.ts +10 -5
  110. package/dist-pkg/ui/audio-choices.js +14 -10
  111. package/dist-pkg/ui/camera-control.d.ts +47 -0
  112. package/dist-pkg/ui/camera-control.js +35 -1
  113. package/dist-pkg/ui/declared-words.d.ts +21 -0
  114. package/dist-pkg/ui/declared-words.js +66 -0
  115. package/dist-pkg/ui/drawing-problems.js +4 -3
  116. package/dist-pkg/ui/eye-control.js +17 -17
  117. package/dist-pkg/ui/face-control.js +15 -15
  118. package/dist-pkg/ui/fonts.d.ts +42 -8
  119. package/dist-pkg/ui/fonts.js +58 -24
  120. package/dist-pkg/ui/footer-scroll-driver.d.ts +14 -0
  121. package/dist-pkg/ui/footer-scroll-driver.js +201 -0
  122. package/dist-pkg/ui/footer-scroll.d.ts +38 -0
  123. package/dist-pkg/ui/footer-scroll.js +55 -0
  124. package/dist-pkg/ui/game-options.d.ts +14 -9
  125. package/dist-pkg/ui/game-options.js +41 -21
  126. package/dist-pkg/ui/hand-control.js +15 -15
  127. package/dist-pkg/ui/help-panel.d.ts +19 -8
  128. package/dist-pkg/ui/help-panel.js +15 -5
  129. package/dist-pkg/ui/hud-bands.d.ts +29 -12
  130. package/dist-pkg/ui/hud-bands.js +63 -25
  131. package/dist-pkg/ui/hud-row.d.ts +34 -0
  132. package/dist-pkg/ui/hud-row.js +80 -0
  133. package/dist-pkg/ui/hud.d.ts +0 -3
  134. package/dist-pkg/ui/layout.js +5 -2
  135. package/dist-pkg/ui/libras-avatar-clip.d.ts +30 -0
  136. package/dist-pkg/ui/libras-avatar-clip.js +86 -0
  137. package/dist-pkg/ui/libras-avatar-load.d.ts +52 -0
  138. package/dist-pkg/ui/libras-avatar-load.js +134 -0
  139. package/dist-pkg/ui/libras-avatar-plan.d.ts +102 -0
  140. package/dist-pkg/ui/libras-avatar-plan.js +211 -0
  141. package/dist-pkg/ui/libras-avatar-player.d.ts +28 -0
  142. package/dist-pkg/ui/libras-avatar-player.js +226 -0
  143. package/dist-pkg/ui/libras-avatar-stage.d.ts +37 -0
  144. package/dist-pkg/ui/libras-avatar-stage.js +208 -0
  145. package/dist-pkg/ui/libras-glosses.d.ts +28 -0
  146. package/dist-pkg/ui/libras-glosses.js +161 -0
  147. package/dist-pkg/ui/menu-intent.d.ts +29 -0
  148. package/dist-pkg/ui/menu-intent.js +35 -1
  149. package/dist-pkg/ui/menu-items.d.ts +10 -0
  150. package/dist-pkg/ui/menu-items.js +29 -0
  151. package/dist-pkg/ui/menu-nav.d.ts +40 -4
  152. package/dist-pkg/ui/menu-nav.js +132 -47
  153. package/dist-pkg/ui/panel-widgets.d.ts +34 -0
  154. package/dist-pkg/ui/panel-widgets.js +86 -0
  155. package/dist-pkg/ui/pause-icons.d.ts +30 -14
  156. package/dist-pkg/ui/pause-icons.js +106 -45
  157. package/dist-pkg/ui/scan-overlay.d.ts +3 -3
  158. package/dist-pkg/ui/scan-overlay.js +11 -3
  159. package/dist-pkg/ui/screen-text.d.ts +25 -0
  160. package/dist-pkg/ui/screen-text.js +152 -0
  161. package/dist-pkg/ui/session-clock.d.ts +32 -0
  162. package/dist-pkg/ui/session-clock.js +67 -0
  163. package/dist-pkg/ui/settings-aac.d.ts +3 -1
  164. package/dist-pkg/ui/settings-aac.js +32 -13
  165. package/dist-pkg/ui/settings-audio.d.ts +10 -5
  166. package/dist-pkg/ui/settings-audio.js +101 -35
  167. package/dist-pkg/ui/settings-controls.d.ts +0 -4
  168. package/dist-pkg/ui/settings-mobility.d.ts +3 -6
  169. package/dist-pkg/ui/settings-mobility.js +4 -46
  170. package/dist-pkg/ui/settings-motion.js +2 -2
  171. package/dist-pkg/ui/settings-panel.d.ts +10 -0
  172. package/dist-pkg/ui/settings-panel.js +25 -1
  173. package/dist-pkg/ui/settings-typo.d.ts +5 -6
  174. package/dist-pkg/ui/settings-typo.js +20 -11
  175. package/dist-pkg/ui/settings-visual.js +2 -2
  176. package/dist-pkg/ui/shell.d.ts +1 -3
  177. package/dist-pkg/ui/shell.js +8 -4
  178. package/dist-pkg/ui/title.d.ts +2 -11
  179. package/dist-pkg/ui/title.js +39 -6
  180. package/dist-pkg/ui/top-band.d.ts +5 -3
  181. package/dist-pkg/ui/top-band.js +21 -25
  182. package/dist-pkg/ui/typo-choices.d.ts +3 -3
  183. package/dist-pkg/ui/typo-choices.js +9 -8
  184. package/dist-pkg/ui/vlibras.d.ts +71 -27
  185. package/dist-pkg/ui/vlibras.js +116 -44
  186. package/dist-pkg/ui/voice-control.d.ts +23 -1
  187. package/dist-pkg/ui/voice-control.js +149 -16
  188. package/dist-pkg/ui/voice-settings.d.ts +2 -3
  189. package/dist-pkg/ui/voice-settings.js +53 -3
  190. package/dist-pkg/ui/where-the-child-is.js +2 -2
  191. package/docs/CREDITS.md +122 -10
  192. package/docs/LICENSES.md +36 -7
  193. package/package.json +34 -9
  194. package/scripts/check-cartridge.mjs +78 -0
  195. package/scripts/game-build.d.mts +25 -0
  196. package/scripts/game-build.mjs +144 -0
  197. package/scripts/heavy-into-the-delivery.mjs +284 -40
  198. package/scripts/libras-avatar.json +680 -0
  199. package/scripts/libras-avatar.mjs +246 -0
  200. package/scripts/libras-glosses/gloss.py +95 -0
  201. package/scripts/libras-glosses/pyproject.toml +32 -0
  202. package/scripts/libras-glosses/uv.lock +781 -0
  203. package/scripts/libras-glosses.mjs +351 -0
  204. package/scripts/licences/OFL-1.1.txt +86 -0
  205. package/scripts/licences/UFL-1.0.txt +96 -0
  206. package/scripts/licences/fonts.mjs +389 -0
  207. package/scripts/licences/third-party.mjs +35 -21
  208. package/app/public/vendor/fonts/bitter-var-ext.woff2 +0 -0
  209. package/app/public/vendor/fonts/bitter-var.woff2 +0 -0
  210. package/app/public/vendor/fonts/bodonimoda-var-ext.woff2 +0 -0
  211. package/app/public/vendor/fonts/bodonimoda-var.woff2 +0 -0
  212. package/app/public/vendor/fonts/cat-abril_fatface-400-ext.woff2 +0 -0
  213. package/app/public/vendor/fonts/cat-abril_fatface-400.woff2 +0 -0
  214. package/app/public/vendor/fonts/cat-adlam_display-400-ext.woff2 +0 -0
  215. package/app/public/vendor/fonts/cat-adlam_display-400.woff2 +0 -0
  216. package/app/public/vendor/fonts/cat-agu_display-400-ext.woff2 +0 -0
  217. package/app/public/vendor/fonts/cat-agu_display-400.woff2 +0 -0
  218. package/app/public/vendor/fonts/cat-alfa_slab_one-400-ext.woff2 +0 -0
  219. package/app/public/vendor/fonts/cat-alfa_slab_one-400.woff2 +0 -0
  220. package/app/public/vendor/fonts/cat-are_you_serious-400-ext.woff2 +0 -0
  221. package/app/public/vendor/fonts/cat-are_you_serious-400.woff2 +0 -0
  222. package/app/public/vendor/fonts/cat-asimovian-400-ext.woff2 +0 -0
  223. package/app/public/vendor/fonts/cat-asimovian-400.woff2 +0 -0
  224. package/app/public/vendor/fonts/cat-atkinson_hyperlegible_next-400-ext.woff2 +0 -0
  225. package/app/public/vendor/fonts/cat-atkinson_hyperlegible_next-400.woff2 +0 -0
  226. package/app/public/vendor/fonts/cat-bakbak_one-400-ext.woff2 +0 -0
  227. package/app/public/vendor/fonts/cat-bakbak_one-400.woff2 +0 -0
  228. package/app/public/vendor/fonts/cat-bangers-400-ext.woff2 +0 -0
  229. package/app/public/vendor/fonts/cat-bangers-400.woff2 +0 -0
  230. package/app/public/vendor/fonts/cat-barriecito-400-ext.woff2 +0 -0
  231. package/app/public/vendor/fonts/cat-barriecito-400.woff2 +0 -0
  232. package/app/public/vendor/fonts/cat-berkshire_swash-400-ext.woff2 +0 -0
  233. package/app/public/vendor/fonts/cat-berkshire_swash-400.woff2 +0 -0
  234. package/app/public/vendor/fonts/cat-bevan-400-ext.woff2 +0 -0
  235. package/app/public/vendor/fonts/cat-bevan-400.woff2 +0 -0
  236. package/app/public/vendor/fonts/cat-bitcount-400-ext.woff2 +0 -0
  237. package/app/public/vendor/fonts/cat-bitcount-400.woff2 +0 -0
  238. package/app/public/vendor/fonts/cat-bowlby_one_sc-400-ext.woff2 +0 -0
  239. package/app/public/vendor/fonts/cat-bowlby_one_sc-400.woff2 +0 -0
  240. package/app/public/vendor/fonts/cat-bungee_shade-400-ext.woff2 +0 -0
  241. package/app/public/vendor/fonts/cat-bungee_shade-400.woff2 +0 -0
  242. package/app/public/vendor/fonts/cat-charm-400-ext.woff2 +0 -0
  243. package/app/public/vendor/fonts/cat-charm-400.woff2 +0 -0
  244. package/app/public/vendor/fonts/cat-charm-700-ext.woff2 +0 -0
  245. package/app/public/vendor/fonts/cat-charm-700.woff2 +0 -0
  246. package/app/public/vendor/fonts/cat-cherry_bomb_one-400-ext.woff2 +0 -0
  247. package/app/public/vendor/fonts/cat-cherry_bomb_one-400.woff2 +0 -0
  248. package/app/public/vendor/fonts/cat-chokokutai-400-ext.woff2 +0 -0
  249. package/app/public/vendor/fonts/cat-chokokutai-400.woff2 +0 -0
  250. package/app/public/vendor/fonts/cat-chonburi-400-ext.woff2 +0 -0
  251. package/app/public/vendor/fonts/cat-chonburi-400.woff2 +0 -0
  252. package/app/public/vendor/fonts/cat-climate_crisis-400-ext.woff2 +0 -0
  253. package/app/public/vendor/fonts/cat-climate_crisis-400.woff2 +0 -0
  254. package/app/public/vendor/fonts/cat-cookie-400.woff2 +0 -0
  255. package/app/public/vendor/fonts/cat-dela_gothic_one-400-ext.woff2 +0 -0
  256. package/app/public/vendor/fonts/cat-dela_gothic_one-400.woff2 +0 -0
  257. package/app/public/vendor/fonts/cat-fascinate_inline-400-ext.woff2 +0 -0
  258. package/app/public/vendor/fonts/cat-fascinate_inline-400.woff2 +0 -0
  259. package/app/public/vendor/fonts/cat-ga_maamli-400-ext.woff2 +0 -0
  260. package/app/public/vendor/fonts/cat-ga_maamli-400.woff2 +0 -0
  261. package/app/public/vendor/fonts/cat-itim-400-ext.woff2 +0 -0
  262. package/app/public/vendor/fonts/cat-itim-400.woff2 +0 -0
  263. package/app/public/vendor/fonts/cat-jacquard_24-400-ext.woff2 +0 -0
  264. package/app/public/vendor/fonts/cat-jacquard_24-400.woff2 +0 -0
  265. package/app/public/vendor/fonts/cat-kablammo-400-ext.woff2 +0 -0
  266. package/app/public/vendor/fonts/cat-kablammo-400.woff2 +0 -0
  267. package/app/public/vendor/fonts/cat-kalam-400-ext.woff2 +0 -0
  268. package/app/public/vendor/fonts/cat-kalam-400.woff2 +0 -0
  269. package/app/public/vendor/fonts/cat-kalam-700-ext.woff2 +0 -0
  270. package/app/public/vendor/fonts/cat-kalam-700.woff2 +0 -0
  271. package/app/public/vendor/fonts/cat-katibeh-400-ext.woff2 +0 -0
  272. package/app/public/vendor/fonts/cat-katibeh-400.woff2 +0 -0
  273. package/app/public/vendor/fonts/cat-lemonada-400-ext.woff2 +0 -0
  274. package/app/public/vendor/fonts/cat-lemonada-400.woff2 +0 -0
  275. package/app/public/vendor/fonts/cat-lilita_one-400-ext.woff2 +0 -0
  276. package/app/public/vendor/fonts/cat-lilita_one-400.woff2 +0 -0
  277. package/app/public/vendor/fonts/cat-lobster-400-ext.woff2 +0 -0
  278. package/app/public/vendor/fonts/cat-lobster-400.woff2 +0 -0
  279. package/app/public/vendor/fonts/cat-luckiest_guy-400-ext.woff2 +0 -0
  280. package/app/public/vendor/fonts/cat-luckiest_guy-400.woff2 +0 -0
  281. package/app/public/vendor/fonts/cat-marhey-400-ext.woff2 +0 -0
  282. package/app/public/vendor/fonts/cat-marhey-400.woff2 +0 -0
  283. package/app/public/vendor/fonts/cat-modak-400-ext.woff2 +0 -0
  284. package/app/public/vendor/fonts/cat-modak-400.woff2 +0 -0
  285. package/app/public/vendor/fonts/cat-momo_signature-400-ext.woff2 +0 -0
  286. package/app/public/vendor/fonts/cat-momo_signature-400.woff2 +0 -0
  287. package/app/public/vendor/fonts/cat-monofett-400-ext.woff2 +0 -0
  288. package/app/public/vendor/fonts/cat-monofett-400.woff2 +0 -0
  289. package/app/public/vendor/fonts/cat-monomakh-400-ext.woff2 +0 -0
  290. package/app/public/vendor/fonts/cat-monomakh-400.woff2 +0 -0
  291. package/app/public/vendor/fonts/cat-monoton-400-ext.woff2 +0 -0
  292. package/app/public/vendor/fonts/cat-monoton-400.woff2 +0 -0
  293. package/app/public/vendor/fonts/cat-nerko_one-400-ext.woff2 +0 -0
  294. package/app/public/vendor/fonts/cat-nerko_one-400.woff2 +0 -0
  295. package/app/public/vendor/fonts/cat-noto_sans_adlam-400-ext.woff2 +0 -0
  296. package/app/public/vendor/fonts/cat-noto_sans_adlam-400.woff2 +0 -0
  297. package/app/public/vendor/fonts/cat-noto_sans_arabic-400-ext.woff2 +0 -0
  298. package/app/public/vendor/fonts/cat-noto_sans_arabic-400.woff2 +0 -0
  299. package/app/public/vendor/fonts/cat-noto_sans_devanagari-400-ext.woff2 +0 -0
  300. package/app/public/vendor/fonts/cat-noto_sans_devanagari-400.woff2 +0 -0
  301. package/app/public/vendor/fonts/cat-noto_sans_hebrew-400-ext.woff2 +0 -0
  302. package/app/public/vendor/fonts/cat-noto_sans_hebrew-400.woff2 +0 -0
  303. package/app/public/vendor/fonts/cat-noto_sans_jp-400-ext.woff2 +0 -0
  304. package/app/public/vendor/fonts/cat-noto_sans_jp-400.woff2 +0 -0
  305. package/app/public/vendor/fonts/cat-noto_sans_khmer-400-ext.woff2 +0 -0
  306. package/app/public/vendor/fonts/cat-noto_sans_khmer-400.woff2 +0 -0
  307. package/app/public/vendor/fonts/cat-noto_sans_kr-400-ext.woff2 +0 -0
  308. package/app/public/vendor/fonts/cat-noto_sans_kr-400.woff2 +0 -0
  309. package/app/public/vendor/fonts/cat-noto_sans_sc-400-ext.woff2 +0 -0
  310. package/app/public/vendor/fonts/cat-noto_sans_sc-400.woff2 +0 -0
  311. package/app/public/vendor/fonts/cat-noto_sans_thai-400-ext.woff2 +0 -0
  312. package/app/public/vendor/fonts/cat-noto_sans_thai-400.woff2 +0 -0
  313. package/app/public/vendor/fonts/cat-ole-400-ext.woff2 +0 -0
  314. package/app/public/vendor/fonts/cat-ole-400.woff2 +0 -0
  315. package/app/public/vendor/fonts/cat-pacifico-400-ext.woff2 +0 -0
  316. package/app/public/vendor/fonts/cat-pacifico-400.woff2 +0 -0
  317. package/app/public/vendor/fonts/cat-patrick_hand-400-ext.woff2 +0 -0
  318. package/app/public/vendor/fonts/cat-patrick_hand-400.woff2 +0 -0
  319. package/app/public/vendor/fonts/cat-petit_formal_script-400-ext.woff2 +0 -0
  320. package/app/public/vendor/fonts/cat-petit_formal_script-400.woff2 +0 -0
  321. package/app/public/vendor/fonts/cat-pixelify_sans-400-ext.woff2 +0 -0
  322. package/app/public/vendor/fonts/cat-pixelify_sans-400.woff2 +0 -0
  323. package/app/public/vendor/fonts/cat-plaster-400-ext.woff2 +0 -0
  324. package/app/public/vendor/fonts/cat-plaster-400.woff2 +0 -0
  325. package/app/public/vendor/fonts/cat-playwrite_ar_guides-400-0.woff2 +0 -0
  326. package/app/public/vendor/fonts/cat-playwrite_at-400-0.woff2 +0 -0
  327. package/app/public/vendor/fonts/cat-playwrite_at_guides-400-0.woff2 +0 -0
  328. package/app/public/vendor/fonts/cat-playwrite_au_nsw-400-0.woff2 +0 -0
  329. package/app/public/vendor/fonts/cat-playwrite_au_nsw_guides-400-0.woff2 +0 -0
  330. package/app/public/vendor/fonts/cat-playwrite_au_qld-400-0.woff2 +0 -0
  331. package/app/public/vendor/fonts/cat-playwrite_au_qld_guides-400-0.woff2 +0 -0
  332. package/app/public/vendor/fonts/cat-playwrite_au_sa-400-0.woff2 +0 -0
  333. package/app/public/vendor/fonts/cat-playwrite_au_sa_guides-400-0.woff2 +0 -0
  334. package/app/public/vendor/fonts/cat-playwrite_au_tas-400-0.woff2 +0 -0
  335. package/app/public/vendor/fonts/cat-playwrite_au_tas_guides-400-0.woff2 +0 -0
  336. package/app/public/vendor/fonts/cat-playwrite_au_vic-400-0.woff2 +0 -0
  337. package/app/public/vendor/fonts/cat-playwrite_au_vic_guides-400-0.woff2 +0 -0
  338. package/app/public/vendor/fonts/cat-playwrite_be_vlg-400-0.woff2 +0 -0
  339. package/app/public/vendor/fonts/cat-playwrite_be_vlg_guides-400-0.woff2 +0 -0
  340. package/app/public/vendor/fonts/cat-playwrite_be_wal-400-0.woff2 +0 -0
  341. package/app/public/vendor/fonts/cat-playwrite_be_wal_guides-400-0.woff2 +0 -0
  342. package/app/public/vendor/fonts/cat-playwrite_br_guides-400-0.woff2 +0 -0
  343. package/app/public/vendor/fonts/cat-playwrite_ca_guides-400-0.woff2 +0 -0
  344. package/app/public/vendor/fonts/cat-playwrite_cl_guides-400-0.woff2 +0 -0
  345. package/app/public/vendor/fonts/cat-playwrite_co_guides-400-0.woff2 +0 -0
  346. package/app/public/vendor/fonts/cat-playwrite_cu_guides-400-0.woff2 +0 -0
  347. package/app/public/vendor/fonts/cat-playwrite_cz-400-0.woff2 +0 -0
  348. package/app/public/vendor/fonts/cat-playwrite_cz_guides-400-0.woff2 +0 -0
  349. package/app/public/vendor/fonts/cat-playwrite_de_grund-400-0.woff2 +0 -0
  350. package/app/public/vendor/fonts/cat-playwrite_de_grund_guides-400-0.woff2 +0 -0
  351. package/app/public/vendor/fonts/cat-playwrite_de_la-400-0.woff2 +0 -0
  352. package/app/public/vendor/fonts/cat-playwrite_de_la_guides-400-0.woff2 +0 -0
  353. package/app/public/vendor/fonts/cat-playwrite_de_sas-400-0.woff2 +0 -0
  354. package/app/public/vendor/fonts/cat-playwrite_de_sas_guides-400-0.woff2 +0 -0
  355. package/app/public/vendor/fonts/cat-playwrite_de_va-400-0.woff2 +0 -0
  356. package/app/public/vendor/fonts/cat-playwrite_de_va_guides-400-0.woff2 +0 -0
  357. package/app/public/vendor/fonts/cat-playwrite_dk_loopet-400-0.woff2 +0 -0
  358. package/app/public/vendor/fonts/cat-playwrite_dk_loopet_guides-400-0.woff2 +0 -0
  359. package/app/public/vendor/fonts/cat-playwrite_dk_uloopet-400-0.woff2 +0 -0
  360. package/app/public/vendor/fonts/cat-playwrite_dk_uloopet_guides-400-0.woff2 +0 -0
  361. package/app/public/vendor/fonts/cat-playwrite_es_deco_guides-400-0.woff2 +0 -0
  362. package/app/public/vendor/fonts/cat-playwrite_es_guides-400-0.woff2 +0 -0
  363. package/app/public/vendor/fonts/cat-playwrite_fr_moderne-400-0.woff2 +0 -0
  364. package/app/public/vendor/fonts/cat-playwrite_fr_moderne_guides-400-0.woff2 +0 -0
  365. package/app/public/vendor/fonts/cat-playwrite_fr_trad-400-0.woff2 +0 -0
  366. package/app/public/vendor/fonts/cat-playwrite_fr_trad_guides-400-0.woff2 +0 -0
  367. package/app/public/vendor/fonts/cat-playwrite_gb_j_guides-400-0.woff2 +0 -0
  368. package/app/public/vendor/fonts/cat-playwrite_gb_s_guides-400-0.woff2 +0 -0
  369. package/app/public/vendor/fonts/cat-playwrite_hr-400-0.woff2 +0 -0
  370. package/app/public/vendor/fonts/cat-playwrite_hr_guides-400-0.woff2 +0 -0
  371. package/app/public/vendor/fonts/cat-playwrite_hr_lijeva-400-0.woff2 +0 -0
  372. package/app/public/vendor/fonts/cat-playwrite_hr_lijeva_guides-400-0.woff2 +0 -0
  373. package/app/public/vendor/fonts/cat-playwrite_hu-400-0.woff2 +0 -0
  374. package/app/public/vendor/fonts/cat-playwrite_hu_guides-400-0.woff2 +0 -0
  375. package/app/public/vendor/fonts/cat-playwrite_id-400-0.woff2 +0 -0
  376. package/app/public/vendor/fonts/cat-playwrite_id_guides-400-0.woff2 +0 -0
  377. package/app/public/vendor/fonts/cat-playwrite_ie-400-0.woff2 +0 -0
  378. package/app/public/vendor/fonts/cat-playwrite_ie_guides-400-0.woff2 +0 -0
  379. package/app/public/vendor/fonts/cat-playwrite_in-400-0.woff2 +0 -0
  380. package/app/public/vendor/fonts/cat-playwrite_in_guides-400-0.woff2 +0 -0
  381. package/app/public/vendor/fonts/cat-playwrite_is-400-0.woff2 +0 -0
  382. package/app/public/vendor/fonts/cat-playwrite_is_guides-400-0.woff2 +0 -0
  383. package/app/public/vendor/fonts/cat-playwrite_it_moderna-400-0.woff2 +0 -0
  384. package/app/public/vendor/fonts/cat-playwrite_it_moderna_guides-400-0.woff2 +0 -0
  385. package/app/public/vendor/fonts/cat-playwrite_it_trad-400-0.woff2 +0 -0
  386. package/app/public/vendor/fonts/cat-playwrite_it_trad_guides-400-0.woff2 +0 -0
  387. package/app/public/vendor/fonts/cat-playwrite_mx_guides-400-0.woff2 +0 -0
  388. package/app/public/vendor/fonts/cat-playwrite_ng_modern-400-0.woff2 +0 -0
  389. package/app/public/vendor/fonts/cat-playwrite_ng_modern_guides-400-0.woff2 +0 -0
  390. package/app/public/vendor/fonts/cat-playwrite_nl-400-0.woff2 +0 -0
  391. package/app/public/vendor/fonts/cat-playwrite_nl_guides-400-0.woff2 +0 -0
  392. package/app/public/vendor/fonts/cat-playwrite_no-400-0.woff2 +0 -0
  393. package/app/public/vendor/fonts/cat-playwrite_no_guides-400-0.woff2 +0 -0
  394. package/app/public/vendor/fonts/cat-playwrite_nz-400-0.woff2 +0 -0
  395. package/app/public/vendor/fonts/cat-playwrite_nz_basic-400-0.woff2 +0 -0
  396. package/app/public/vendor/fonts/cat-playwrite_nz_basic_guides-400-0.woff2 +0 -0
  397. package/app/public/vendor/fonts/cat-playwrite_nz_guides-400-0.woff2 +0 -0
  398. package/app/public/vendor/fonts/cat-playwrite_pe_guides-400-0.woff2 +0 -0
  399. package/app/public/vendor/fonts/cat-playwrite_pl-400-0.woff2 +0 -0
  400. package/app/public/vendor/fonts/cat-playwrite_pl_guides-400-0.woff2 +0 -0
  401. package/app/public/vendor/fonts/cat-playwrite_pt_guides-400-0.woff2 +0 -0
  402. package/app/public/vendor/fonts/cat-playwrite_ro-400-0.woff2 +0 -0
  403. package/app/public/vendor/fonts/cat-playwrite_ro_guides-400-0.woff2 +0 -0
  404. package/app/public/vendor/fonts/cat-playwrite_sk-400-0.woff2 +0 -0
  405. package/app/public/vendor/fonts/cat-playwrite_sk_guides-400-0.woff2 +0 -0
  406. package/app/public/vendor/fonts/cat-playwrite_tz-400-0.woff2 +0 -0
  407. package/app/public/vendor/fonts/cat-playwrite_tz_guides-400-0.woff2 +0 -0
  408. package/app/public/vendor/fonts/cat-playwrite_us_modern_guides-400-0.woff2 +0 -0
  409. package/app/public/vendor/fonts/cat-playwrite_us_trad_guides-400-0.woff2 +0 -0
  410. package/app/public/vendor/fonts/cat-playwrite_vn-400-0.woff2 +0 -0
  411. package/app/public/vendor/fonts/cat-playwrite_vn_guides-400-0.woff2 +0 -0
  412. package/app/public/vendor/fonts/cat-playwrite_za-400-0.woff2 +0 -0
  413. package/app/public/vendor/fonts/cat-playwrite_za_guides-400-0.woff2 +0 -0
  414. package/app/public/vendor/fonts/cat-potta_one-400-ext.woff2 +0 -0
  415. package/app/public/vendor/fonts/cat-potta_one-400.woff2 +0 -0
  416. package/app/public/vendor/fonts/cat-ranchers-400-ext.woff2 +0 -0
  417. package/app/public/vendor/fonts/cat-ranchers-400.woff2 +0 -0
  418. package/app/public/vendor/fonts/cat-ranga-400-ext.woff2 +0 -0
  419. package/app/public/vendor/fonts/cat-ranga-400.woff2 +0 -0
  420. package/app/public/vendor/fonts/cat-ranga-700-ext.woff2 +0 -0
  421. package/app/public/vendor/fonts/cat-ranga-700.woff2 +0 -0
  422. package/app/public/vendor/fonts/cat-risque-400-ext.woff2 +0 -0
  423. package/app/public/vendor/fonts/cat-risque-400.woff2 +0 -0
  424. package/app/public/vendor/fonts/cat-rowdies-400-ext.woff2 +0 -0
  425. package/app/public/vendor/fonts/cat-rowdies-400.woff2 +0 -0
  426. package/app/public/vendor/fonts/cat-rowdies-700-ext.woff2 +0 -0
  427. package/app/public/vendor/fonts/cat-rowdies-700.woff2 +0 -0
  428. package/app/public/vendor/fonts/cat-ruge_boogie-400-ext.woff2 +0 -0
  429. package/app/public/vendor/fonts/cat-ruge_boogie-400.woff2 +0 -0
  430. package/app/public/vendor/fonts/cat-ruslan_display-400-ext.woff2 +0 -0
  431. package/app/public/vendor/fonts/cat-ruslan_display-400.woff2 +0 -0
  432. package/app/public/vendor/fonts/cat-sacramento-400-ext.woff2 +0 -0
  433. package/app/public/vendor/fonts/cat-sacramento-400.woff2 +0 -0
  434. package/app/public/vendor/fonts/cat-sancreek-400-ext.woff2 +0 -0
  435. package/app/public/vendor/fonts/cat-sancreek-400.woff2 +0 -0
  436. package/app/public/vendor/fonts/cat-sankofa_display-400-ext.woff2 +0 -0
  437. package/app/public/vendor/fonts/cat-sankofa_display-400.woff2 +0 -0
  438. package/app/public/vendor/fonts/cat-sedgwick_ave_display-400-ext.woff2 +0 -0
  439. package/app/public/vendor/fonts/cat-sedgwick_ave_display-400.woff2 +0 -0
  440. package/app/public/vendor/fonts/cat-shantell_sans-400-ext.woff2 +0 -0
  441. package/app/public/vendor/fonts/cat-shantell_sans-400.woff2 +0 -0
  442. package/app/public/vendor/fonts/cat-smokum-400-ext.woff2 +0 -0
  443. package/app/public/vendor/fonts/cat-smokum-400.woff2 +0 -0
  444. package/app/public/vendor/fonts/cat-solitreo-400-ext.woff2 +0 -0
  445. package/app/public/vendor/fonts/cat-solitreo-400.woff2 +0 -0
  446. package/app/public/vendor/fonts/cat-spicy_rice-400-ext.woff2 +0 -0
  447. package/app/public/vendor/fonts/cat-spicy_rice-400.woff2 +0 -0
  448. package/app/public/vendor/fonts/cat-sriracha-400-ext.woff2 +0 -0
  449. package/app/public/vendor/fonts/cat-sriracha-400.woff2 +0 -0
  450. package/app/public/vendor/fonts/cat-srisakdi-400-ext.woff2 +0 -0
  451. package/app/public/vendor/fonts/cat-srisakdi-400.woff2 +0 -0
  452. package/app/public/vendor/fonts/cat-srisakdi-700-ext.woff2 +0 -0
  453. package/app/public/vendor/fonts/cat-srisakdi-700.woff2 +0 -0
  454. package/app/public/vendor/fonts/cat-stix_two_math-400-0.woff2 +0 -0
  455. package/app/public/vendor/fonts/cat-tillana-400-ext.woff2 +0 -0
  456. package/app/public/vendor/fonts/cat-tillana-400.woff2 +0 -0
  457. package/app/public/vendor/fonts/cat-tillana-700-ext.woff2 +0 -0
  458. package/app/public/vendor/fonts/cat-tillana-700.woff2 +0 -0
  459. package/app/public/vendor/fonts/cat-titan_one-400-ext.woff2 +0 -0
  460. package/app/public/vendor/fonts/cat-titan_one-400.woff2 +0 -0
  461. package/app/public/vendor/fonts/cat-yatra_one-400-ext.woff2 +0 -0
  462. package/app/public/vendor/fonts/cat-yatra_one-400.woff2 +0 -0
  463. package/app/public/vendor/fonts/cat-yomogi-400-ext.woff2 +0 -0
  464. package/app/public/vendor/fonts/cat-yomogi-400.woff2 +0 -0
  465. package/app/public/vendor/fonts/dmserifdisplay-400-ext.woff2 +0 -0
  466. package/app/public/vendor/fonts/dmserifdisplay-400.woff2 +0 -0
  467. package/app/public/vendor/fonts/domine-var-ext.woff2 +0 -0
  468. package/app/public/vendor/fonts/domine-var.woff2 +0 -0
  469. package/app/public/vendor/fonts/fondamento-400-ext.woff2 +0 -0
  470. package/app/public/vendor/fonts/fondamento-400.woff2 +0 -0
  471. package/app/public/vendor/fonts/fraunces-var-ext.woff2 +0 -0
  472. package/app/public/vendor/fonts/fraunces-var.woff2 +0 -0
  473. package/app/public/vendor/fonts/fredoka-var-ext.woff2 +0 -0
  474. package/app/public/vendor/fonts/fredoka-var.woff2 +0 -0
  475. package/app/public/vendor/fonts/inter-400.woff2 +0 -0
  476. package/app/public/vendor/fonts/inter-700.woff2 +0 -0
  477. package/app/public/vendor/fonts/jakarta-var-ext.woff2 +0 -0
  478. package/app/public/vendor/fonts/jakarta-var.woff2 +0 -0
  479. package/app/public/vendor/fonts/lato-400.woff2 +0 -0
  480. package/app/public/vendor/fonts/lato-700.woff2 +0 -0
  481. package/app/public/vendor/fonts/literata-400.woff2 +0 -0
  482. package/app/public/vendor/fonts/literata-700.woff2 +0 -0
  483. package/app/public/vendor/fonts/lora-var-ext.woff2 +0 -0
  484. package/app/public/vendor/fonts/lora-var.woff2 +0 -0
  485. package/app/public/vendor/fonts/merriweather-var-ext.woff2 +0 -0
  486. package/app/public/vendor/fonts/merriweather-var.woff2 +0 -0
  487. package/app/public/vendor/fonts/newsreader-400.woff2 +0 -0
  488. package/app/public/vendor/fonts/newsreader-700.woff2 +0 -0
  489. package/app/public/vendor/fonts/notosans-var-ext.woff2 +0 -0
  490. package/app/public/vendor/fonts/notosans-var.woff2 +0 -0
  491. package/app/public/vendor/fonts/nunito-var-ext.woff2 +0 -0
  492. package/app/public/vendor/fonts/nunito-var.woff2 +0 -0
  493. package/app/public/vendor/fonts/opendyslexic-400.woff2 +0 -0
  494. package/app/public/vendor/fonts/opensans-400.woff2 +0 -0
  495. package/app/public/vendor/fonts/opensans-700.woff2 +0 -0
  496. package/app/public/vendor/fonts/pinyon-400.woff2 +0 -0
  497. package/app/public/vendor/fonts/playfair-var-ext.woff2 +0 -0
  498. package/app/public/vendor/fonts/playfair-var.woff2 +0 -0
  499. package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
  500. package/app/public/vendor/fonts/quattro-400.woff2 +0 -0
  501. package/app/public/vendor/fonts/quattro-700.woff2 +0 -0
  502. package/app/public/vendor/fonts/quicksand-var-ext.woff2 +0 -0
  503. package/app/public/vendor/fonts/quicksand-var.woff2 +0 -0
  504. package/app/public/vendor/fonts/robotoflex-var-ext.woff2 +0 -0
  505. package/app/public/vendor/fonts/robotoflex-var.woff2 +0 -0
  506. package/app/public/vendor/fonts/sora-var-ext.woff2 +0 -0
  507. package/app/public/vendor/fonts/sora-var.woff2 +0 -0
  508. package/app/public/vendor/fonts/sourcesans-400.woff2 +0 -0
  509. package/app/public/vendor/fonts/sourcesans-700.woff2 +0 -0
  510. package/app/public/vendor/fonts/sourceserif-400.woff2 +0 -0
  511. package/app/public/vendor/fonts/sourceserif-700.woff2 +0 -0
  512. package/app/public/vendor/fonts/spacegrotesk-var-ext.woff2 +0 -0
  513. package/app/public/vendor/fonts/spacegrotesk-var.woff2 +0 -0
  514. package/app/public/vendor/fonts/spectral-400-ext.woff2 +0 -0
  515. package/app/public/vendor/fonts/spectral-400.woff2 +0 -0
  516. package/app/public/vendor/fonts/spectral-700-ext.woff2 +0 -0
  517. package/app/public/vendor/fonts/spectral-700.woff2 +0 -0
  518. package/app/public/vendor/fonts/teachers-var-ext.woff2 +0 -0
  519. package/app/public/vendor/fonts/teachers-var.woff2 +0 -0
  520. package/app/public/vendor/fonts/ubuntu-400-ext.woff2 +0 -0
  521. package/app/public/vendor/fonts/ubuntu-400.woff2 +0 -0
  522. package/app/public/vendor/fonts/ubuntu-700-ext.woff2 +0 -0
  523. package/app/public/vendor/fonts/ubuntu-700.woff2 +0 -0
  524. package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
  525. package/dist-pkg/platform/guide-intensity.d.ts +0 -39
  526. package/dist-pkg/platform/guide-intensity.js +0 -71
  527. package/dist-pkg/ui/latch-refusal.d.ts +0 -33
  528. package/dist-pkg/ui/latch-refusal.js +0 -60
@@ -2,24 +2,17 @@
2
2
  // core/i18n — internationalisation.
3
3
  // The default language (pt) is a STATIC import, so its dictionary is ready before anything renders (a synchronous boot).
4
4
  // The others load on demand when the language changes, as their own chunks (see the loaders below).
5
+ //
6
+ // 🔴 THIS MODULE HOLDS NO STATE (ADR-0232 D3, erratum of 2026-09-25). Everything that changes — the language in use, the
7
+ // dictionaries loaded, the game's own dictionary, who listens to a change, the boot's pending load, the port the choice is
8
+ // kept through — lives in the TRANSLATOR a composition root builds (`createTranslator`). What stays at module level is
9
+ // constant: the base dictionary, the list of languages, their loaders and their regions. Two roots on one page, or two
10
+ // test files, share nothing through this module.
5
11
  import pt from '../i18n/pt.js';
6
12
  const AVAILABLE = ['pt', 'en', 'es'];
7
13
  const base = pt; // the base dictionary (fallback), typed
8
- const DICTS = { pt: base }; // dictionaries already loaded (pt built in)
9
- let localePort = null;
10
- /** Gives this module the port for the chosen language; the composition root calls it before `initI18n`. */
11
- export function loadLocale(p) { localePort = p; }
12
- /* ===================== THE DICTIONARY OF WHOEVER CONSUMES THE ENGINE =====================
13
- *
14
- * ⚠️ FROM OUTSIDE, THE ENGINE'S DICTIONARIES ARE A WALL. The locales are resolved AT THIS ENGINE'S BUILD, against THIS
15
- * folder. A game that installs `@the-inclusionist/engine` cannot put a file in there, and `DICTS` is private. Without
16
- * this layer it would have NO way to its own keys — and pillar 3 makes no exception for a consumer.
17
- *
18
- * A SEPARATE LAYER, and not `DICTS[code] = {...DICTS[code], ...extra}`: `DICTS.pt` IS the object imported from
19
- * `../i18n/pt.ts`. Merging there would mutate the engine's own dictionary, and two games on the same page would inherit
20
- * each other's strings. `tests/i18n-consumer-dict.node.test.js` pins exactly that.
21
- */
22
- const EXTRA = {};
14
+ /** The languages a page can switch to — a constant, so it stays a function of the module (ADR-0232 D3). */
15
+ export function availableLocales() { return AVAILABLE.slice(); }
23
16
  /** Puts `entries` into `layer` for `code`, refusing what carries markup (see `hasMarkup`); returns the refused keys. */
24
17
  function addEntries(layer, code, entries) {
25
18
  const refused = [];
@@ -41,41 +34,28 @@ function addEntries(layer, code, entries) {
41
34
  layer[code] = { ...layer[code], ...accepted };
42
35
  return refused;
43
36
  }
44
- /**
45
- * Registers THIS game's keys for a language. Callable before the language exists — whoever registers `en` before any
46
- * `setLocale('en')` is served when the switch happens.
47
- *
48
- * ⚠️ IT DOES NOT RE-APPLY THE DOM, on purpose. `applyDom` needs a ROOT, and reaching the global `document` under the
49
- * caller's feet is a defect that already cost one fix. A consumer registering after the static markup was translated
50
- * calls `applyDom(root)` itself — and the normal case is to register at boot, before any text is on screen.
51
- */
52
- export function registerDict(code, entries) {
53
- return addEntries(EXTRA, code, entries);
54
- }
55
37
  /**
56
38
  * THE KEYS A CARTRIDGE REGISTERED IN ONE OF THE THREE LANGUAGES AND NOT IN ANOTHER (study item E4; ADR-0010 pillar 3),
57
39
  * one line per missing language, for `problems`. 📏 Measured: the games that register a dictionary do it in pt, en and es,
58
40
  * and nothing checked it — a key forgotten in one language shows Portuguese there, and nobody is told.
59
41
  * A cartridge that registered nothing is not accused: strings it never gave the engine, the engine cannot see.
60
- * Over `layers` taken together — a translator asks it for its root's dictionary (ADR-0232 D3).
61
42
  */
62
- function gapsIn(layers) {
43
+ function gapsIn(layer) {
63
44
  const registered = new Set();
64
- for (const layer of layers)
65
- for (const code of AVAILABLE)
66
- for (const key in layer[code] ?? {})
67
- registered.add(key);
45
+ for (const code of AVAILABLE)
46
+ for (const key in layer[code] ?? {})
47
+ registered.add(key);
68
48
  if (!registered.size)
69
49
  return [];
70
50
  const SHOW = 5;
71
51
  const gapLines = [];
72
52
  for (const code of AVAILABLE) {
73
- const missing = [...registered].filter((key) => !layers.some((layer) => key in (layer[code] ?? {})));
53
+ const missing = [...registered].filter((key) => !(key in (layer[code] ?? {})));
74
54
  if (!missing.length)
75
55
  continue;
76
56
  const rest = missing.length > SHOW ? ` (and ${missing.length - SHOW} more)` : '';
77
57
  gapLines.push(`the cartridge's dictionary lacks ${code} for ${missing.slice(0, SHOW).join(', ')}${rest}: `
78
- + `a child playing in ${code} reads the fallback there (registerDict)`);
58
+ + `a child playing in ${code} reads the fallback there (CreateGameOptions.dictionaries)`);
79
59
  }
80
60
  return gapLines;
81
61
  }
@@ -83,72 +63,36 @@ function gapsIn(layers) {
83
63
  * Does a string carry markup?
84
64
  *
85
65
  * ⚠️ WHY THIS LIVES HERE, AND NOT IN THE ~15 SINKS THAT CONSUME i18n. The gate `tests/i18n-without-markup.node.test.js`
86
- * sweeps THIS tree's dictionaries and proves no entry has a tag. It does not reach — and cannot reach — `EXTRA`: those
87
- * are strings a GAME registers at run time, from another repository (ADR-0083), and a test in this tree never sees them.
66
+ * sweeps THIS tree's dictionaries and proves no entry has a tag. It does not reach — and cannot reach — a game's own
67
+ * dictionary: those are strings a GAME registers at run time, from another repository (ADR-0083), and a test in this tree
68
+ * never sees them.
88
69
  *
89
- * ⚠️ AND THEY WIN OVER THE ENGINE'S DICTIONARY. The resolver looks at `EXTRA` first, so a game can override ANY key —
90
- * including the ones the engine pastes into markup. Without this check "i18n" would have stopped meaning "text someone
91
- * in this tree reviewed", and nothing would record the change.
70
+ * ⚠️ AND THEY WIN OVER THE ENGINE'S DICTIONARY. The resolver looks at the game's dictionary first, so a game can override
71
+ * ANY key — including the ones the engine pastes into markup. Without this check "i18n" would have stopped meaning "text
72
+ * someone in this tree reviewed", and nothing would record the change.
92
73
  *
93
- * The check sits at the BOUNDARY and not in the sinks because the boundary is ONE: every string from a game passes
94
- * here. Fifteen sinks would be fifteen places to forget, and forgetting leaves no trace.
74
+ * The check sits at the BOUNDARY and not in the sinks because the boundary is ONE: every string from a game passes here.
95
75
  *
96
- * The sieve is deliberately coarse — `<` followed by a letter or a slash, and `&` of an entity. Does it refuse `a < b`
97
- * written with a space? No: `< ` does not match. "5<10"? No, the digit does not match. What it refuses is what looks
98
- * like a tag, and an interface sentence that needs one needs another sentence.
76
+ * The sieve is deliberately coarse — `<` followed by a letter or a slash, and `&` of an entity. `a < b` with a space and
77
+ * "5<10" pass; what it refuses is what looks like a tag, and an interface sentence that needs one needs another sentence.
99
78
  */
100
79
  function hasMarkup(value) {
101
80
  return typeof value === 'string' && (/<[a-zA-Z/!?]/.test(value) || /&[a-zA-Z#][a-zA-Z0-9]*;/.test(value));
102
81
  }
103
- /**
104
- * The resolution chain, in five steps. It MIRRORS the one that already existed (`locale → pt → the key itself`), with
105
- * the consumer glued to each step instead of stacked on top:
106
- *
107
- * 1. consumer in the current language · 2. engine in the current language ·
108
- * 3. consumer in pt · 4. engine in pt · 5. the key itself
109
- *
110
- * Step 3 is what keeps a game that only wrote pt READABLE when the child switches to English: they read Portuguese,
111
- * exactly as they already do when the engine lacks a key. Degrading beats going quiet, the same choice the silent
112
- * `.catch` in `initI18n` makes for a chunk that does not load.
113
- *
114
- * Step 2 coming BEFORE step 3 is the only defensible order: the engine's right language is worth more than the
115
- * consumer's wrong one. Inverting it would show Portuguese in a Spanish interface that had the translation to hand.
116
- */
117
- function resolveKey(key, layers = [EXTRA]) {
118
- for (const layer of layers) {
119
- const fromGame = layer[locale];
120
- if (fromGame && key in fromGame)
121
- return fromGame[key];
122
- }
123
- if (key in dict)
124
- return dict[key];
125
- for (const layer of layers) {
126
- const fromGameInPt = layer.pt;
127
- if (fromGameInPt && key in fromGameInPt)
128
- return fromGameInPt[key];
129
- }
130
- return key in base ? base[key] : key;
131
- }
132
82
  /* ===================== THE LOADERS, AND WHY THEY ARE NOT A GLOB =====================
133
83
  *
134
84
  * This used to be `import.meta.glob('../i18n/*.ts')`. ⚠️ In the PACKAGE build (`tsc -p tsconfig.pkg.json`) that line
135
- * survives the emit intact, and there it is false twice over:
136
- * · `tsc` is not Vite: it copies `import.meta.glob(...)` as an ordinary call. In a consumer that does not transform
137
- * the module, `import.meta.glob` is `undefined` and the call blows up on load.
138
- * · And even transformed, the pattern says `*.ts` — next to the EMITTED file there are only `.js` files. The glob
139
- * would match nothing, `ensure()` would fall to `return base`, and every language but pt would become Portuguese
140
- * WITHOUT ANY ERROR — the silent failure mode this project already paid for once.
85
+ * survives the emit intact, and there it is false twice over: `tsc` copies `import.meta.glob(...)` as an ordinary call
86
+ * (undefined in a consumer that does not transform the module), and the pattern says `*.ts` next to emitted `.js` files —
87
+ * every language but pt would silently become Portuguese.
141
88
  *
142
- * ENUMERATED, THEN — and it costs little: the glob swept three files that `AVAILABLE` above ALREADY lists. Code
143
- * splitting is not lost either, because what Vite splits is the dynamic `import()`, which stays: `en` and `es` still
144
- * ship as their own chunks, and a child who stays in pt never evaluates them. The one cost is three lines to keep by
145
- * hand the day a fourth language arrives — the same day and the same file as `AVAILABLE`. */
89
+ * ENUMERATED, THEN — and code splitting is not lost, because what Vite splits is the dynamic `import()`: `en` and `es`
90
+ * still ship as their own chunks, and a child who stays in pt never evaluates them. The one cost is three lines to keep
91
+ * by hand the day a fourth language arrives — the same day and the same file as `AVAILABLE`. */
146
92
  const loaders = {
147
93
  en: () => import('../i18n/en.js'),
148
94
  es: () => import('../i18n/es.js'),
149
95
  };
150
- let locale = 'pt';
151
- let dict = base;
152
96
  /** Interpolates `{param}` into a resolved string. */
153
97
  function interpolate(s, params) {
154
98
  if (params)
@@ -156,134 +100,123 @@ function interpolate(s, params) {
156
100
  s = s.replaceAll('{' + k + '}', String(params[k]));
157
101
  return s;
158
102
  }
159
- // Translates a key; the fallback chain is in `resolveKey()` above. Interpolates {param}.
160
- export function t(key, params) {
161
- return interpolate(resolveKey(key), params);
162
- }
163
- export function getLocale() { return locale; }
164
103
  /**
165
- * THE BCP-47 TAG OF THE CURRENT LANGUAGE — what `<html lang>` says and what the browser's speech and recognition are given.
104
+ * THE BCP-47 TAG OF A LANGUAGE — what `<html lang>` says and what the browser's speech and recognition are given.
166
105
  *
167
106
  * Each language carries the REGION its flag stands for (the Dev, 2026-09-16: «Brasil, Estados Unidos e México. Cada bandeira indica a
168
- * localização para qual o app está configurado»): pt-BR, en-US, es-MX. It was pt-BR with `en` and `es` bare, so the browser picked the
169
- * variant; the language button now states the place. Voices are still matched on the language alone (`voicesForLocale`), so a region
170
- * narrows nothing a child can hear.
107
+ * localização para qual o app está configurado»): pt-BR, en-US, es-MX. Voices are still matched on the language alone
108
+ * (`voicesForLocale`), so a region narrows nothing a child can hear. A translator answers it for the page's language.
171
109
  */
172
110
  const REGION_OF = { pt: 'pt-BR', en: 'en-US', es: 'es-MX' };
173
- export function bcp47(code = locale) { return REGION_OF[code] ?? code; }
174
- function availableLocales() { return AVAILABLE.slice(); }
175
- // Applies the declarative translations of the markup: [data-i18n] → textContent; [data-i18n-aria] → aria-label.
176
- function applyDom(root) {
177
- root.querySelectorAll('[data-i18n]').forEach((el) => { const k = el.getAttribute('data-i18n'); if (k)
178
- el.textContent = t(k); });
179
- root.querySelectorAll('[data-i18n-aria]').forEach((el) => { const k = el.getAttribute('data-i18n-aria'); if (k)
180
- el.setAttribute('aria-label', t(k)); });
181
- }
182
- async function ensure(code) {
183
- if (DICTS[code])
184
- return DICTS[code];
185
- const load = loaders[code];
186
- if (!load)
187
- return base; // a language with no file → falls back to pt
188
- const mod = await load();
189
- DICTS[code] = mod.default;
190
- return DICTS[code];
191
- }
192
- // Switches the language (loading on demand), keeps it, and hands the page effects to the host.
193
- export async function setLocale(code) {
194
- // first, before anything changes: a language switched and then refused would leave the page half moved (ADR-0178)
195
- const port = localePort;
196
- if (!port)
197
- throw new Error('core/i18n: setLocale kept a language before loadLocale — the composition root loads the stored settings first (ADR-0178)');
198
- if (!AVAILABLE.includes(code))
199
- code = 'pt';
200
- dict = await ensure(code);
201
- locale = code;
202
- port.set(port.KEYS.lang, code);
203
- // 📌 The three page effects — `<html lang>`, re-translating the markup, telling the page — in ONE call to the host, which
204
- // is what keeps this module free of `document` and `window` (ADR-0173, ADR-0221 step 7g).
205
- port.applied?.(locale, bcp47(code));
206
- // and then every translator's listeners, each root's through its own disposing door (ADR-0232 D3, ADR-0220)
207
- for (const listener of [...changeListeners]) {
208
- try {
209
- listener(locale);
210
- }
211
- catch { /* a listener failed; the language changed */ }
212
- }
213
- }
214
- /** Who hears a language change — the PAGE's, as the language is (ADR-0232 D3 erratum): each translator subscribes here. */
215
- const changeListeners = new Set();
216
- function pickDefault() {
217
- const saved = localePort ? localePort.get(localePort.KEYS.lang, null) : null;
218
- if (saved && AVAILABLE.includes(saved))
219
- return saved;
220
- const nav = ((localePort?.preferred?.() || 'pt').slice(0, 2)).toLowerCase();
221
- return AVAILABLE.includes(nav) ? nav : 'pt';
222
- }
223
- /** The promise of the load asked for at boot. Resolves at once when the language is pt (static dictionary). */
224
- let pending = Promise.resolve();
225
- /**
226
- * Boot: applies pt (synchronously, so the page is never blank) and, if the preferred language is another, ASKS for the
227
- * switch — which is asynchronous, because the other locales are on-demand chunks.
228
- *
229
- * It still returns the locale synchronously and does NOT block on its own: whoever needs to wait calls `localeReady()`.
230
- *
231
- * `root` COMES IN instead of being read from the global: the composition root receives the host's document by
232
- * injection and had no way to pass it on while this line reached the global `document` under it. In a browser it is
233
- * the same; in the `node` project it is the difference between booting against a fake DOM and not booting.
234
- */
235
- export function initI18n(root) {
236
- applyDom(root);
237
- const def = pickDefault();
238
- // A silent `.catch` on purpose: a locale chunk that fails to load degrades to pt, and degrading is FAR better than
239
- // freezing the boot. Without it, an `await localeReady()` outside would bring the whole game down over the language.
240
- if (def !== 'pt')
241
- pending = setLocale(def).catch(() => { });
242
- return locale;
243
- }
244
- /**
245
- * Resolves when the language chosen at boot has finished loading (at once, if it is pt).
246
- *
247
- * ========================= WHY THIS HAD TO EXIST =========================
248
- * When the language loaded LAST, after the HUD and the menus were built, pt cost nothing — and en/es cost half the
249
- * interface: `applyDom` fixes the STATIC markup (`data-i18n`), but what JavaScript builds had captured the pt text and
250
- * nobody rebuilt it. `t()` answered "City" and the button on screen said "Cidade".
251
- *
252
- * This answers the narrow question, which has one answer: the interface is not built before the language is known.
253
- * Switching language AT RUN TIME is a different question, answered by the redraw on `i18n:change` (ADR-0225).
254
- */
255
- export function localeReady() { return pending; }
111
+ export function bcp47(code) { return REGION_OF[code] ?? code; }
256
112
  /**
257
- * Builds a translator, with a game dictionary of its OWN (ADR-0232 D3 erratum): what one root registers, another root on the
258
- * same page does not read. The language stays the page's.
259
- *
260
- * ⚠️ TRANSITIONAL: after its own dictionary it still reads the page-wide one that the module-level `registerDict` writes,
261
- * so a game that registers there before `createGame` keeps its words — until the Dev decides how a game translates what it
262
- * declares, and the module-level `t` and `registerDict` leave (issue #207).
113
+ * Builds a translator. `port` keeps the chosen language and tells the page (ADR-0178); without one there is no page to tell
114
+ * and nothing to keep, and `setLocale` refuses — a test that only translates needs none.
263
115
  */
264
- export function createTranslator() {
116
+ export function createTranslator(port) {
117
+ let locale = 'pt';
118
+ let dict = base;
119
+ /** Dictionaries already loaded (pt built in). */
120
+ const loaded = { pt: base };
121
+ /** THIS game's dictionary. A separate layer and not a merge into `loaded`: `loaded.pt` IS the imported engine object. */
265
122
  const own = {};
266
- const layers = [own, EXTRA];
267
- const tr = (key, params) => interpolate(resolveKey(key, layers), params);
268
- const applyTo = (root) => {
123
+ const listeners = new Set();
124
+ let pending = Promise.resolve();
125
+ /*
126
+ * The resolution chain, in five steps: 1. game in the current language · 2. engine in the current language · 3. game in pt ·
127
+ * 4. engine in pt · 5. the key itself. Step 3 keeps a game that only wrote pt READABLE in English (degrading beats going
128
+ * quiet); step 2 before step 3 because the engine's right language is worth more than the game's wrong one.
129
+ */
130
+ const resolveKey = (key) => {
131
+ const fromGame = own[locale];
132
+ if (fromGame && key in fromGame)
133
+ return fromGame[key];
134
+ if (key in dict)
135
+ return dict[key];
136
+ const fromGameInPt = own.pt;
137
+ if (fromGameInPt && key in fromGameInPt)
138
+ return fromGameInPt[key];
139
+ return key in base ? base[key] : key;
140
+ };
141
+ const t = (key, params) => interpolate(resolveKey(key), params);
142
+ const applyDom = (root) => {
269
143
  root.querySelectorAll('[data-i18n]').forEach((el) => { const k = el.getAttribute('data-i18n'); if (k)
270
- el.textContent = tr(k); });
144
+ el.textContent = t(k); });
271
145
  root.querySelectorAll('[data-i18n-aria]').forEach((el) => { const k = el.getAttribute('data-i18n-aria'); if (k)
272
- el.setAttribute('aria-label', tr(k)); });
146
+ el.setAttribute('aria-label', t(k)); });
147
+ };
148
+ const ensure = async (code) => {
149
+ if (loaded[code])
150
+ return loaded[code];
151
+ const load = loaders[code];
152
+ if (!load)
153
+ return base; // a language with no file → falls back to pt
154
+ const mod = await load();
155
+ loaded[code] = mod.default;
156
+ return loaded[code];
157
+ };
158
+ const tellListeners = () => {
159
+ for (const listener of [...listeners]) {
160
+ try {
161
+ listener(locale);
162
+ }
163
+ catch { /* a listener failed; the language changed */ }
164
+ }
165
+ };
166
+ const setLocale = async (requested) => {
167
+ // first, before anything changes: a language switched and then refused would leave the page half moved (ADR-0178)
168
+ if (!port)
169
+ throw new Error('core/i18n: setLocale on a translator without a port — the composition root builds it with the stored settings (ADR-0178)');
170
+ const code = AVAILABLE.includes(requested) ? requested : 'pt';
171
+ dict = await ensure(code);
172
+ locale = code;
173
+ port.set(port.KEYS.lang, code);
174
+ // 📌 The three page effects — `<html lang>`, re-translating the markup, telling the page — in ONE call to the host, which
175
+ // is what keeps this module free of `document` and `window` (ADR-0173, ADR-0221 step 7g).
176
+ port.applied?.(locale, bcp47(code));
177
+ tellListeners();
178
+ };
179
+ // 📌 ANOTHER ROOT SWITCHED THE PAGE: this one follows without keeping or telling again — the root that switched did both.
180
+ port?.follow?.((code) => {
181
+ if (code === locale || !AVAILABLE.includes(code))
182
+ return;
183
+ void ensure(code).then((d) => { dict = d; locale = code; tellListeners(); });
184
+ });
185
+ const pickDefault = () => {
186
+ const saved = port ? port.get(port.KEYS.lang, null) : null;
187
+ if (saved && AVAILABLE.includes(saved))
188
+ return saved;
189
+ const nav = ((port?.preferred?.() || 'pt').slice(0, 2)).toLowerCase();
190
+ return AVAILABLE.includes(nav) ? nav : 'pt';
191
+ };
192
+ const init = (root) => {
193
+ applyDom(root);
194
+ const def = pickDefault();
195
+ // A silent `.catch` on purpose: a locale chunk that fails to load degrades to pt, and degrading is FAR better than
196
+ // freezing the boot. Without it, an `await ready()` outside would bring the whole game down over the language.
197
+ if (def !== 'pt')
198
+ pending = setLocale(def).catch(() => { });
199
+ return locale;
200
+ };
201
+ const word = (key) => {
202
+ const inLocale = own[locale];
203
+ if (inLocale && key in inLocale)
204
+ return inLocale[key];
205
+ const inPt = own.pt;
206
+ return inPt && key in inPt ? inPt[key] : null;
273
207
  };
274
208
  return {
275
- t: tr,
209
+ t,
276
210
  locale: () => locale,
277
- bcp47,
211
+ bcp47: (code = locale) => bcp47(code),
278
212
  setLocale,
279
- onChange: (react) => { changeListeners.add(react); return () => { changeListeners.delete(react); }; },
280
- applyDom: applyTo,
213
+ onChange: (react) => { listeners.add(react); return () => { listeners.delete(react); }; },
214
+ applyDom,
215
+ init,
281
216
  ready: () => pending,
282
217
  registerDict: (code, entries) => addEntries(own, code, entries),
283
- dictionaryGaps: () => gapsIn(layers),
218
+ dictionaryGaps: () => gapsIn(own),
219
+ word,
220
+ declares: (key) => Object.values(own).some((entries) => key in entries),
284
221
  };
285
222
  }
286
- const i18n = { t, getLocale, availableLocales, applyDom, setLocale, initI18n, localeReady, registerDict };
287
- export default i18n;
288
- // 🔴 Exposing this on `window.__i18n` is not done here (ADR-0221 step 7g): hanging off a window is the job of whoever
289
- // HAS one. `platform/locale-host.exposeI18n` does it, called by the root — and the object exposed is exactly this default.
@@ -0,0 +1,23 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /**
3
+ * Where the session stands: the share of the disc left (1 → 0), the milliseconds and whole minutes left (rounded UP), and
4
+ * whether it is over.
5
+ */
6
+ export interface SessionReading {
7
+ readonly left: number;
8
+ readonly leftMs: number;
9
+ readonly minutesLeft: number;
10
+ readonly over: boolean;
11
+ }
12
+ /**
13
+ * The time left as digits, whole seconds rounded UP — the last second reads «00:01», never «00:00» early. Hours show only while
14
+ * there are hours (`1:00:00`, then `59:59`): the Dev, «Se não houver horas, não coloque 0: no começo» (ADR-0239 errata).
15
+ */
16
+ export declare function clockDigits(leftMs: number): string;
17
+ /** Reads the session `elapsedMs` after it started. The disc never goes below empty nor above full. */
18
+ export declare function readSession(elapsedMs: number): SessionReading;
19
+ /** The dictionary key and the minutes for the clock's accessible name — words a screen reader says, never a `H:MM`. */
20
+ export declare function clockWords(reading: SessionReading): {
21
+ readonly key: string;
22
+ readonly minutes: number;
23
+ };
@@ -0,0 +1,37 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // core/session-clock.ts — what the SESSION CLOCK reads, with no browser (ADR-0236, ADR-0240, ADR-0050 §3; issue #94).
3
+ //
4
+ // The clock is a Time Timer: a disc that empties continuously over ONE HOUR, beside the time left in digits (ADR-0239) — never
5
+ // numbers only, and the digits never change unit as a warning. It measures the SESSION, never an activity (ADR-0050 §3,
6
+ // ADR-0048 §7b): nothing here knows a question, a round or a game.
7
+ //
8
+ // 📌 THE HOUR IS FIXED, AND NOTHING ABOUT IT IS A SETTING (ADR-0240): the clock always shows, always measures one hour, and at the
9
+ // end it turns red. What limits the child's session is the adult's to set, on the adult's surface — Bússola Escolar's menu,
10
+ // which the student cannot reach — so the engine keeps no length and no ending in the child's settings or storage.
11
+ //
12
+ // 📌 A STATELESS MODULE (ADR-0232): the hour and the arithmetic. The root measures the time, `ui/session-clock` draws.
13
+ const MINUTE_MS = 60_000;
14
+ /** The session: one hour, the Time Timer's whole disc (ADR-0240). */
15
+ const SESSION_MS = 60 * MINUTE_MS;
16
+ /**
17
+ * The time left as digits, whole seconds rounded UP — the last second reads «00:01», never «00:00» early. Hours show only while
18
+ * there are hours (`1:00:00`, then `59:59`): the Dev, «Se não houver horas, não coloque 0: no começo» (ADR-0239 errata).
19
+ */
20
+ export function clockDigits(leftMs) {
21
+ const total = Math.ceil(Math.max(0, leftMs) / 1000);
22
+ const two = (n) => String(n).padStart(2, '0');
23
+ const hours = Math.floor(total / 3600);
24
+ const minutesAndSeconds = `${two(Math.floor(total / 60) % 60)}:${two(total % 60)}`;
25
+ return hours > 0 ? `${hours}:${minutesAndSeconds}` : minutesAndSeconds;
26
+ }
27
+ /** Reads the session `elapsedMs` after it started. The disc never goes below empty nor above full. */
28
+ export function readSession(elapsedMs) {
29
+ const leftMs = Math.min(SESSION_MS, Math.max(0, SESSION_MS - elapsedMs));
30
+ return { left: leftMs / SESSION_MS, leftMs, minutesLeft: Math.ceil(leftMs / MINUTE_MS), over: leftMs === 0 };
31
+ }
32
+ /** The dictionary key and the minutes for the clock's accessible name — words a screen reader says, never a `H:MM`. */
33
+ export function clockWords(reading) {
34
+ if (reading.over)
35
+ return { key: 'clock.over', minutes: 0 };
36
+ return { key: reading.minutesLeft === 1 ? 'clock.left.one' : 'clock.left', minutes: reading.minutesLeft };
37
+ }