@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
@@ -13,11 +13,14 @@
13
13
  // 📌 WHY IT IS `ui/` AND NOT `core/`: half of these resolve i18n KEYS (`t`) into the words a child reads, and a label is
14
14
  // interface. What is genuinely free of language — the category lists and the volume arithmetic — stays here beside the rest
15
15
  // rather than being split again for the sake of a layer.
16
- /** The SOUND-NAVIGATION categories (cane/sonar/guard/guide) — their master volume is separate from the game's sound. */
17
- export const NAV_CATS = ['sonar', 'guard', 'guide'];
16
+ /**
17
+ * The SOUND-NAVIGATION categories — their master volume is separate from the game's sound. Only the sonar since `guard` and
18
+ * `guide` left (ADR-0258): the engine plays nothing in them since the guide moved to the platformer.
19
+ */
20
+ export const NAV_CATS = ['sonar'];
18
21
  /** The game's GENERAL categories (TTS lives in the Voice section, outside this list). */
19
22
  // ⚠️ THEY BELONG TO THE «AUDIO» PANEL since ADR-0151, not to the hearing one: what is taste (music, ambience) does not live
20
- // beside what is accessibility (sonar, guard, guide). And `other` left — it controlled no sound at all.
23
+ // beside what is accessibility (the sonar). And `other` left — it controlled no sound at all.
21
24
  export const GEN_CATS = ['music', 'ambient', 'interact', 'earcons'];
22
25
  /** 0..1 -> 0..100 rounded (slider display value). */
23
26
  export function volPercent(v) {
@@ -28,8 +31,8 @@ export function volPercent(v) {
28
31
  * `ui/settings-audio.buildCatRow`, where the row is also RECONCILED instead of reborn. A markup builder with no consumer is
29
32
  * published debt — it is deleted, not moved.
30
33
  *
31
- * 📌 And no alias is left: a re-export would keep alive a path nothing imports and make the surface snapshot lie, because
32
- * it does not see re-exports (issue #204).
34
+ * 📌 And no alias is left: a re-export would keep alive a published path nothing imports — surface with no consumer
35
+ * (issue #204).
33
36
  */
34
37
  /** #navsound-master's value: the loudest of the nav categories, as a 0..100 slider value. */
35
38
  export function navMasterVolume(state, navCats = NAV_CATS) {
@@ -61,19 +64,20 @@ export function voiceEngineOptions(neuralAvailable) {
61
64
  return neuralAvailable ? TTS_ENGINE_OPTIONS : TTS_ENGINE_OPTIONS.filter(([v]) => v !== 'kokoro');
62
65
  }
63
66
  /**
64
- * The system voices in the language asked for; with none of them, the whole list.
67
+ * The system voices in the language asked for, and ONLY those (ADR-0185): with none of them, none — the list then says there is
68
+ * no voice, and the speech rows lock.
65
69
  *
66
70
  * It was called `pickPtVoices` and filtered a hard-coded `/^pt/i`, so a game in English offered a child a list of PORTUGUESE
67
71
  * voices to read English text. The name told the truth about what it did and lied about what it should do.
68
72
  *
69
73
  * The comparison is by language PREFIX and not by the whole tag: a child playing in pt-BR should also be able to choose a
70
- * pt-PT voice if that is the only one installed, and a school's browser rarely has the exact variant. The fallback to the
71
- * whole list stays: an empty list would be worse than a list in the wrong language, which at least she can hear and reject.
74
+ * pt-PT voice if that is the only one installed, and a school's browser rarely has the exact variant. 🔴 There is no fallback
75
+ * to the whole list: a voice of another language reads the text with the wrong phonetics, and ADR-0243 §3 decided that a wrong
76
+ * voice is worse than none — silence is noticed, a wrong accent is learned.
72
77
  */
73
78
  export function pickVoicesFor(voices, lang) {
74
79
  const pref = lang.slice(0, 2).toLowerCase();
75
- const sameTag = voices.filter((v) => v.lang.slice(0, 2).toLowerCase() === pref);
76
- return sameTag.length ? sameTag : voices;
80
+ return voices.filter((v) => v.lang.slice(0, 2).toLowerCase() === pref);
77
81
  }
78
82
  /** "<name> (<lang>)" option label. */
79
83
  export function voiceLabel(v) {
@@ -4,3 +4,50 @@ import type { SwitchableControl } from './switchable-control.js';
4
4
  export declare function followCameraMode(mode: CameraControl, controls: {
5
5
  readonly [M in Exclude<CameraControl, 'off'>]: SwitchableControl;
6
6
  }): void;
7
+ /** Why a camera mode did not start: its files are not on the device, its runtime did not load, or the camera did not open. */
8
+ export type CameraStartFailure = 'files' | 'runtime' | 'camera';
9
+ /** How one camera mode names itself when it cannot start. */
10
+ export interface CameraWords {
11
+ /** Its name in `problems`: «gesture control». */
12
+ readonly subject: string;
13
+ /** What the child loses, after «the child cannot»: «play with gestures». */
14
+ readonly loses: string;
15
+ /**
16
+ * What the child hears for each failure, in the page's language at the moment it happens. Each is the mode's own `t('…')`
17
+ * with a literal key, so `tests/no-key-reaches-the-child-unresolved` still reads it — a key built here would not be checked.
18
+ */
19
+ readonly spoken: {
20
+ readonly [K in CameraStartFailure]: () => string;
21
+ };
22
+ }
23
+ export interface CameraStartDeps<T extends {
24
+ close(): void;
25
+ }, F> {
26
+ /** The reader's loader (`platform/vision`), already given the page and the checked cache. */
27
+ readonly load: () => Promise<{
28
+ readonly ok: true;
29
+ readonly tracker: T;
30
+ } | {
31
+ readonly ok: false;
32
+ readonly missing: readonly string[];
33
+ }>;
34
+ readonly openFeed: () => Promise<F>;
35
+ /** Nothing started: the control says it, writes it once, and puts the 📷 back to off — if it is still wanted. */
36
+ readonly failed: (kind: CameraStartFailure, line: string, spoken: string) => void;
37
+ }
38
+ /**
39
+ * THE READER, THEN THE CAMERA — or the reason neither is running, said through `failed`; `null` when nothing started.
40
+ *
41
+ * 🔴 A LOADER THAT REJECTS IS A FAILURE LIKE ANY OTHER (ADR-0169). `platform/vision` answers the files it cannot find, but a
42
+ * runtime that cannot be IMPORTED (a server that sends `.mjs` as `text/plain`, an address that answers 404) or a vision task
43
+ * that cannot be built on the GPU nor on the CPU rejects instead, and the three controls awaited it with nothing around it: the
44
+ * rejection went up through `apply` into `followCameraMode`'s `void` and died unhandled, with the 📷 lit, nothing said and
45
+ * nothing in `problems` — measured in a real page on 2026-09-25. The 👄 had met the same defect on 2026-09-21
46
+ * (`ui/voice-control`); this is the one place the three camera modes now start through, so a fourth cannot miss it.
47
+ */
48
+ export declare function startCameraReader<T extends {
49
+ close(): void;
50
+ }, F>(words: CameraWords, d: CameraStartDeps<T, F>): Promise<{
51
+ tracker: T;
52
+ feed: F;
53
+ } | null>;
@@ -1,5 +1,5 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
- // ui/camera-control — THE 📷 POSITION TO THE THREE CAMERA CONTROLS (ADR-0215; issue #199).
2
+ // ui/camera-control — THE 📷 POSITION TO THE THREE CAMERA CONTROLS (ADR-0215; issue #199), AND HOW EACH ONE STARTS.
3
3
  //
4
4
  // One stored position (`core/state.cameraControl`): off · hands · face · eyes. Each control starts only at its own position, so one camera
5
5
  // mode at a time holds by construction (ADR-0197). The ones turning off are told first, so a camera is let go before the next mode asks.
@@ -12,3 +12,37 @@ export function followCameraMode(mode, controls) {
12
12
  if (m === mode)
13
13
  void c.apply(true);
14
14
  }
15
+ /**
16
+ * THE READER, THEN THE CAMERA — or the reason neither is running, said through `failed`; `null` when nothing started.
17
+ *
18
+ * 🔴 A LOADER THAT REJECTS IS A FAILURE LIKE ANY OTHER (ADR-0169). `platform/vision` answers the files it cannot find, but a
19
+ * runtime that cannot be IMPORTED (a server that sends `.mjs` as `text/plain`, an address that answers 404) or a vision task
20
+ * that cannot be built on the GPU nor on the CPU rejects instead, and the three controls awaited it with nothing around it: the
21
+ * rejection went up through `apply` into `followCameraMode`'s `void` and died unhandled, with the 📷 lit, nothing said and
22
+ * nothing in `problems` — measured in a real page on 2026-09-25. The 👄 had met the same defect on 2026-09-21
23
+ * (`ui/voice-control`); this is the one place the three camera modes now start through, so a fourth cannot miss it.
24
+ */
25
+ export async function startCameraReader(words, d) {
26
+ const { subject, loses, spoken } = words;
27
+ let load;
28
+ try {
29
+ load = await d.load();
30
+ }
31
+ catch (e) {
32
+ const why = e instanceof Error ? e.message : String(e);
33
+ d.failed('runtime', `${subject}: the vision runtime did not load (${why}) — the child cannot ${loses}; check that the delivery carries the vision files and that the server serves .mjs files with the MIME type text/javascript`, spoken.runtime());
34
+ return null;
35
+ }
36
+ if (!load.ok) {
37
+ d.failed('files', `${subject}: ${load.missing.join(', ')} not on this device — the child cannot ${loses}; open the game once online so the install fetches them`, spoken.files());
38
+ return null;
39
+ }
40
+ try {
41
+ return { tracker: load.tracker, feed: await d.openFeed() };
42
+ }
43
+ catch {
44
+ load.tracker.close();
45
+ d.failed('camera', `${subject}: the camera did not open — the child cannot ${loses}; allow the camera for this page, or plug one in`, spoken.camera());
46
+ return null;
47
+ }
48
+ }
@@ -0,0 +1,21 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ import type { ActionPreset } from '../core/actions.js';
3
+ import type { AccommodationAnswers } from '../core/accommodations.js';
4
+ import type { GameOption } from './game-options.js';
5
+ import type { HowToPlaySlide } from './help-panel.js';
6
+ import type { HudNumber } from './hud-bands.js';
7
+ /** The game's fields that carry declared words — a `Pick` of `CreateGameOptions`, written here so `ui` does not import `boot`. */
8
+ interface DeclaredWords {
9
+ readonly preset?: ActionPreset;
10
+ readonly accommodations?: AccommodationAnswers;
11
+ readonly gameOptions?: readonly GameOption[];
12
+ readonly howToPlay?: readonly HowToPlaySlide[];
13
+ readonly hud?: readonly HudNumber[];
14
+ }
15
+ /**
16
+ * THE DECLARED KEYS THIS GAME'S DICTIONARIES LACK, one line each for `problems` (ADR-0169). `declares` is the root's
17
+ * translator's (`Translator.declares`): a key in ANY language counts as present, because a key given in one language and not
18
+ * another is already a line of its own (`Translator.dictionaryGaps`).
19
+ */
20
+ export declare function missingDeclaredKeys(declared: DeclaredWords, declares: (key: string) => boolean): string[];
21
+ export {};
@@ -0,0 +1,66 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ const ACTION_NAME = 'the child sees this button unnamed: the help, the remap screen, the pad and the scan leave it out';
3
+ const ACTION_SHORT = 'the pad shows the long name squeezed under the glyph';
4
+ const ACTION_HINT = 'the help shows this button without its sentence';
5
+ const ACCOMMODATION_NAME = 'this accommodation has its row left out, and the child who needs it cannot reach it';
6
+ const ACCOMMODATION_SHORT = 'where only a short name fits, the long one is squeezed';
7
+ const ACCOMMODATION_HINT = 'the row explains itself in the engine\'s general words, or not at all';
8
+ const OPTION_NAME = 'this option has its row left out, and the child cannot change it';
9
+ const OPTION_HINT = 'the row shows without its explanation';
10
+ const SLIDE_TEXT = 'this slide is left out of the help';
11
+ const HUD_NAME = 'this number is hidden from the child';
12
+ function presetKeys(preset) {
13
+ if (!preset)
14
+ return [];
15
+ return Object.entries(preset).flatMap(([action, keys]) => (keys ? [
16
+ { subject: `preset.${action}.labelKey`, key: keys.labelKey, cost: ACTION_NAME, optional: false },
17
+ { subject: `preset.${action}.shortKey`, key: keys.shortKey, cost: ACTION_SHORT, optional: true },
18
+ { subject: `preset.${action}.hintKey`, key: keys.hintKey, cost: ACTION_HINT, optional: true },
19
+ ] : []));
20
+ }
21
+ function accommodationKeys(answers) {
22
+ if (!answers)
23
+ return [];
24
+ return Object.entries(answers).flatMap(([accommodation, keys]) => (keys ? [
25
+ { subject: `accommodations.${accommodation}.labelKey`, key: keys.labelKey, cost: ACCOMMODATION_NAME, optional: false },
26
+ { subject: `accommodations.${accommodation}.shortKey`, key: keys.shortKey, cost: ACCOMMODATION_SHORT, optional: true },
27
+ { subject: `accommodations.${accommodation}.hintKey`, key: keys.hintKey, cost: ACCOMMODATION_HINT, optional: true },
28
+ ] : []));
29
+ }
30
+ function optionKeys(options) {
31
+ return (options ?? []).flatMap((o) => [
32
+ { subject: `gameOptions «${o.id}».labelKey`, key: o.labelKey, cost: OPTION_NAME, optional: false },
33
+ { subject: `gameOptions «${o.id}».hintKey`, key: o.hintKey, cost: OPTION_HINT, optional: true },
34
+ ...(o.kind === 'switch' ? [] : o.values.map((v) => ({ subject: `gameOptions «${o.id}».values «${v.value}».labelKey`, key: v.labelKey, cost: OPTION_NAME, optional: false }))),
35
+ ]);
36
+ }
37
+ function slideKeys(slides) {
38
+ return (slides ?? []).map((s, i) => ({ subject: `howToPlay[${i}].textKey`, key: s.textKey, cost: SLIDE_TEXT, optional: false }));
39
+ }
40
+ function hudKeys(numbers) {
41
+ return (numbers ?? []).map((n, i) => ({ subject: `hud[${i}].nameKey`, key: n.nameKey, cost: HUD_NAME, optional: false }));
42
+ }
43
+ /** What is wrong with ONE declared key, or `null`: it must be text, and it must be in the game's dictionaries. */
44
+ function keyProblem(k, declares) {
45
+ if (k.optional && k.key === undefined)
46
+ return null;
47
+ const fix = 'add it to CreateGameOptions.dictionaries, in pt, en and es';
48
+ if (typeof k.key !== 'string' || !k.key.trim()) {
49
+ return `${k.subject} is not a key (since ADR-0232 D3 a game declares the KEY of each word, never the word): ${k.cost} — name a key, and ${fix}`;
50
+ }
51
+ return declares(k.key) ? null : `${k.subject} «${k.key}» is in none of this game's dictionaries: ${k.cost} — ${fix}`;
52
+ }
53
+ /**
54
+ * THE DECLARED KEYS THIS GAME'S DICTIONARIES LACK, one line each for `problems` (ADR-0169). `declares` is the root's
55
+ * translator's (`Translator.declares`): a key in ANY language counts as present, because a key given in one language and not
56
+ * another is already a line of its own (`Translator.dictionaryGaps`).
57
+ */
58
+ export function missingDeclaredKeys(declared, declares) {
59
+ return [
60
+ ...presetKeys(declared.preset),
61
+ ...accommodationKeys(declared.accommodations),
62
+ ...optionKeys(declared.gameOptions),
63
+ ...slideKeys(declared.howToPlay),
64
+ ...hudKeys(declared.hud),
65
+ ].map((k) => keyProblem(k, declares)).filter((line) => line !== null);
66
+ }
@@ -12,7 +12,7 @@
12
12
  // because a stale element still answers `getBoundingClientRect()`.
13
13
  import { barIntruders, belowFloor, minimumTarget } from './layout.js';
14
14
  /** The engine's OWN nodes inside the region — their sizes are held by their own gates — and what is not drawn for the eye. */
15
- const ENGINE_NODES = '#title-icons, .screen-pause, .overlay, #touch-controls, .rodape-da-tela, .pausa-rapida, #incl-parou, .sr-only, .hud-faixa';
15
+ const ENGINE_NODES = '#title-icons, .screen-pause, .overlay, #touch-controls, .rodape-da-tela, .pausa-rapida, #incl-parou, .sr-only, .hud-faixa, .hud-row';
16
16
  /** What a finger is meant to hit; anything else has no target floor to fail. */
17
17
  const TOUCH_TARGET = 'button, a[href], input:not([type="hidden"]), select, textarea, [role="button"], [tabindex]:not([tabindex="-1"])';
18
18
  /**
@@ -86,8 +86,9 @@ export function barIntruderProblems(ctx) {
86
86
  const nodes = [...region.querySelectorAll('*')].filter((el) => paints(el)).map((el) => ({
87
87
  name: nodeName(el),
88
88
  box: boxOf(el),
89
- // the engine's HUD bands are placed by the ENGINE: if one reaches the bar, that is the engine's defect and not the game's
90
- isBar: el === bar || bar.contains(el) || el.closest('.hud-faixa') !== null,
89
+ // the engine's HUD bands and HUD row are placed by the ENGINE: if one reaches the bar, that is the engine's defect and
90
+ // not the game's
91
+ isBar: el === bar || bar.contains(el) || el.closest('.hud-faixa, .hud-row') !== null,
91
92
  }));
92
93
  return barIntruders(boxOf(bar), nodes);
93
94
  }
@@ -9,14 +9,15 @@
9
9
  // · Nothing commands before the rest is measured: until then, and on a frame with no face or a head turning fast, the cycle is frozen.
10
10
  // · A reading that re-centres itself cancels the gesture in hand.
11
11
  // · Only the RIGHT eye is read (ADR-0213): the Dev's brain suppresses the left one, and a child's may too.
12
- // · What cannot work is said to the child and written in `problems`, once: the files not on the device, the camera refused, frames that
13
- // stop or crawl. A 📷 that is on and does nothing is the defect this module exists not to ship.
12
+ // · What cannot work is said to the child and written in `problems`, once: the files not on the device, a runtime that does not load,
13
+ // the camera refused, frames that stop or crawl (`ui/camera-control.startCameraReader` for the first three). A 📷 that is on and does nothing is the defect this module exists not to ship.
14
14
  import { headPoseFromMatrix, scoresFromCategories, eyeGazeFromScores, bothEyesClosed } from '../input/face-signals.js';
15
15
  import { createGazeReader, GAZE_DEFAULTS } from '../input/gaze-relative.js';
16
16
  import { createGazeCycle } from '../input/gaze-cycle.js';
17
17
  import { loadFaceTracker, openCamera, closeCamera } from '../platform/vision.js';
18
18
  import { createVisionLoop } from '../platform/vision-loop.js';
19
19
  import { drawGazeOverlay } from './gaze-overlay.js';
20
+ import { startCameraReader } from './camera-control.js';
20
21
  // 📌 THE SHAPE IS THE FAMILY'S (ADR-0221 step 7f): what this module returns IS a SwitchableControl — following the 📷: on in
21
22
  // the eyes position it opens the camera and the drawing, off it lets go of everything.
22
23
  export function videoFeed(doc, media) {
@@ -127,23 +128,22 @@ export function createEyeControl(d) {
127
128
  running = false;
128
129
  wasReady = false;
129
130
  };
130
- const start = async () => {
131
- const load = await loadTracker({ base: d.base, hasFile: d.hasFile });
132
- if (!load.ok) {
133
- once('files', `eye control: ${load.missing.join(', ')} not on this device — the child cannot play with the eyes; open the game once online so the install fetches them`, t('sr.eyes.needsInternet'));
134
- d.turnOff();
131
+ /** Nothing started: said, written once, and the 📷 back to off — unless it already moved on, and then nobody is waiting (as the 👄). */
132
+ const failed = (kind, line, spoken) => {
133
+ if (!current())
135
134
  return;
136
- }
137
- try {
138
- feed = await openFeed();
139
- }
140
- catch {
141
- load.tracker.close();
142
- once('camera', 'eye control: the camera did not open — the child cannot play with the eyes; allow the camera for this page, or plug one in', t('sr.eyes.noCamera'));
143
- d.turnOff();
135
+ once(kind, line, spoken);
136
+ d.turnOff();
137
+ };
138
+ const start = async () => {
139
+ const opened = await startCameraReader({
140
+ subject: 'eye control', loses: 'play with the eyes',
141
+ spoken: { files: () => t('sr.eyes.needsInternet'), runtime: () => t('sr.eyes.failed'), camera: () => t('sr.eyes.noCamera') },
142
+ }, { load: () => loadTracker({ base: d.base, hasFile: d.hasFile }), openFeed, failed });
143
+ if (!opened)
144
144
  return;
145
- }
146
- tracker = load.tracker;
145
+ tracker = opened.tracker;
146
+ feed = opened.feed;
147
147
  canvas = d.doc.createElement('canvas');
148
148
  canvas.className = 'gaze-overlay';
149
149
  canvas.setAttribute('aria-hidden', 'true');
@@ -13,6 +13,7 @@ import { faceScoresFromCategories, headTurn, createFaceMapReader, createFaceRest
13
13
  import { loadFaceTracker } from '../platform/vision.js';
14
14
  import { createVisionLoop } from '../platform/vision-loop.js';
15
15
  import { gazeFontPx } from './gaze-overlay.js';
16
+ import { startCameraReader } from './camera-control.js';
16
17
  // 📌 THE SHAPE IS THE FAMILY'S (ADR-0221 step 7f): this module declares no `FaceControl` of its own — what it returns IS a
17
18
  // `SwitchableControl`, and the compiler sees it.
18
19
  const REST_MS = 3000;
@@ -132,23 +133,22 @@ export function createFaceControl(d) {
132
133
  rest = null;
133
134
  read = null;
134
135
  };
135
- const start = async () => {
136
- const load = await loadTracker({ base: d.base, hasFile: d.hasFile });
137
- if (!load.ok) {
138
- once('files', `face control: ${load.missing.join(', ')} not on this device — the child cannot play with the face; open the game once online so the install fetches them`, t('sr.face.needsInternet'));
139
- d.turnOff();
136
+ /** Nothing started: said, written once, and the 📷 back to off — unless it already moved on, and then nobody is waiting (as the 👄). */
137
+ const failed = (kind, line, spoken) => {
138
+ if (!current())
140
139
  return;
141
- }
142
- try {
143
- feed = await d.openFeed();
144
- }
145
- catch {
146
- load.tracker.close();
147
- once('camera', 'face control: the camera did not open — the child cannot play with the face; allow the camera for this page, or plug one in', t('sr.face.noCamera'));
148
- d.turnOff();
140
+ once(kind, line, spoken);
141
+ d.turnOff();
142
+ };
143
+ const start = async () => {
144
+ const opened = await startCameraReader({
145
+ subject: 'face control', loses: 'play with the face',
146
+ spoken: { files: () => t('sr.face.needsInternet'), runtime: () => t('sr.face.failed'), camera: () => t('sr.face.noCamera') },
147
+ }, { load: () => loadTracker({ base: d.base, hasFile: d.hasFile }), openFeed: d.openFeed, failed });
148
+ if (!opened)
149
149
  return;
150
- }
151
- tracker = load.tracker;
150
+ tracker = opened.tracker;
151
+ feed = opened.feed;
152
152
  canvas = d.doc.createElement('canvas');
153
153
  canvas.className = 'face-overlay';
154
154
  canvas.setAttribute('aria-hidden', 'true');
@@ -85,6 +85,12 @@ export type FontItem = {
85
85
  fb: string;
86
86
  d?: string;
87
87
  off?: string;
88
+ /**
89
+ * The i18n KEY of a sentence ALWAYS shown after the description — unlike `off`, which only speaks while the face is
90
+ * disabled. It is for a stack whose preferred faces the device may lack: the ronde's names the three to install, because
91
+ * without them the child sees Cookie, a related hand that is not the one taught at school (ADR-0154).
92
+ */
93
+ notice?: string;
88
94
  /** Absent = `geral`. Only the handwriting faces declare it, because they are the exception. */
89
95
  role?: FontRole;
90
96
  /**
@@ -100,6 +106,16 @@ export type FontGroup = {
100
106
  g: string;
101
107
  items: FontItem[];
102
108
  };
109
+ /**
110
+ * THE TYPOGRAPHY PANEL'S KIT — every face a panel may list, grouped. The engine mounts no panel since ADR-0151 («quem escolhe a
111
+ * tipografia é o jogo»), and the kit stays for whoever mounts one (`ui/settings-typo`), because the eleventh button's cycle and
112
+ * the boot's stored choice go through its writer.
113
+ *
114
+ * 📌 MOST OF THESE FACES ARE THE FONT LIBRARY'S, not the engine's (ADR-0255): only the cycle's faces are packaged. A library face
115
+ * reaches a page when its cartridge declares the family in `uses.fonts` and the delivery carries it, so `faceAvailable` answers
116
+ * for it through the host's detector — a row whose family nobody declared is shown disabled, never offered and then drawn in the
117
+ * system font. Keeping the rows is what lets a game that declares its families offer them with no list of its own.
118
+ */
103
119
  export declare const FONT_GROUPS: FontGroup[];
104
120
  /**
105
121
  * How much larger the text is drawn with this face: enough to reach its `minPx` from the document's base, never smaller.
@@ -112,23 +128,39 @@ export declare function fontRole(it: FontItem): FontRole;
112
128
  /**
113
129
  * THE FAMILIES A FACE ACCEPTS, from a `fam` that may be a STACK.
114
130
  *
115
- * 📌 The Ronde declares three (`'Ronde Script, OPTIFrench-Script, Merveille'`) because any of them will do — three
116
- * drawings of the same hand, and an adult installs whichever they find. The other faces declare one, and for them this
131
+ * 📌 The ronde declares four (`'Ronde Script, OPTIFrench-Script, Merveille, Cookie'`): the three drawings of the same
132
+ * hand an adult may install, then the packaged fallback (ADR-0154). The other faces declare one, and for them this
117
133
  * returns a list of one.
118
134
  */
119
135
  export declare function faceFamilies(it: FontItem): string[];
136
+ /**
137
+ * A `fam` as a CSS `font-family` value — each family quoted on its own: `'Ronde Script','OPTIFrench-Script',…,'Cookie'`.
138
+ *
139
+ * 🔴 QUOTING THE WHOLE STRING ONCE NAMES ONE FAMILY THAT DOES NOT EXIST. `'Ronde Script, …, Cookie'` is a single name with
140
+ * commas in it, so the browser skips it and draws the generic fallback — the stack would never reach Cookie, and the
141
+ * fallback ADR-0154 decided would exist only in the catalogue. For a one-family `fam` the result is the same `'Inter'`.
142
+ */
143
+ export declare function familyStack(fam: string): string;
144
+ /**
145
+ * THE FACES THE ENGINE DRAWS WITH ITSELF, by key — the eleventh button's cycle over EVERY hand a tag can pick (ADR-0149,
146
+ * ADR-0150). With the mathematics face (`MATHEMATICS_FAMILY`) they are the only faces packaged and precached (ADR-0255 §1); every
147
+ * other face of the kit is the font library's. `tests/what-fonts-the-package-carries.node.test.js` holds `fonts.css` to this list.
148
+ */
149
+ export declare const ENGINE_FACE_KEYS: readonly string[];
150
+ /** The face the engine draws mathematics in (`--font-math` in `style.css`): packaged, never a typography choice (ADR-0012). */
151
+ export declare const MATHEMATICS_FAMILY = "Atkinson Hyperlegible Mono";
120
152
  /**
121
153
  * CAN THIS FACE BE USED NOW? — `off` stops being a sentence and becomes a CONDITION.
122
154
  *
123
- * ADR-0012 decided the ronde option stays DISABLED WHILE no font is present, and ADR-0108 §4 added what it says. The word
124
- * "while" is what this function builds: an `off` face becomes available again the instant the adult installs one of the
125
- * ones the message names.
155
+ * An engine face is always there: it is packaged and precached (ADR-0255 §1). Any other face is used WHILE one of its families
156
+ * is present — a library family its cartridge declared in `uses.fonts` (the root's detector answers for those), or, for an `off`
157
+ * face, one the adult installed from the ones its message names (ADR-0012's «enquanto»). The ronde's stack ends in Cookie, a
158
+ * library family, so it is available where the game declares Cookie (ADR-0154, ADR-0255).
126
159
  *
127
160
  * ⚠️ THE DETECTOR IS INJECTED, and `document.fonts` is never read here: this module is the catalogue, it runs in node in
128
161
  * the gates, and reading a browser global here would be a boot waiting to crash against an injected document.
129
- * 📌 And the DEFAULT is not installed, which is safe for a reason that does not hold for every default in this repository:
130
- * with no detector the option stays disabled WITH the message, and the message tells the adult exactly what to do. The
131
- * silence decides nothing against the child — it keeps a state that already existed and is actionable.
162
+ * 📌 And the DEFAULT is not present, which is safe: with no detector a library row stays disabled, never offered and then drawn
163
+ * in the system font, and an `off` row keeps the message that tells the adult exactly what to do.
132
164
  */
133
165
  export declare function faceAvailable(it: FontItem, installed?: (family: string) => boolean): boolean;
134
166
  /** The faces the MENU may offer: only the general ones (ADR-0012 amendment). */
@@ -138,6 +170,8 @@ export declare const OFERECIVEIS: FontItem[];
138
170
  * module wrote into at import, which the code-health measure reads as module state. The last item with a key wins, as it did.
139
171
  */
140
172
  export declare const FONT_BY_KEY: Record<string, FontItem>;
173
+ /** The families `vendor/fonts.css` declares — the engine's faces and the mathematics face. A cartridge never declares these. */
174
+ export declare const ENGINE_FAMILIES: ReadonlySet<string>;
141
175
  /** Narrow store shape these need — lets a caller inject a fake without touching real storage. */
142
176
  export interface FontStore {
143
177
  get(key: string, fallback: string | null): string | null;
@@ -87,11 +87,20 @@ export function handsForTag(tag) {
87
87
  return HAND_BY_COUNTRY[region];
88
88
  return HAND_BY_LANGUAGE[language] ?? [];
89
89
  }
90
+ /**
91
+ * THE TYPOGRAPHY PANEL'S KIT — every face a panel may list, grouped. The engine mounts no panel since ADR-0151 («quem escolhe a
92
+ * tipografia é o jogo»), and the kit stays for whoever mounts one (`ui/settings-typo`), because the eleventh button's cycle and
93
+ * the boot's stored choice go through its writer.
94
+ *
95
+ * 📌 MOST OF THESE FACES ARE THE FONT LIBRARY'S, not the engine's (ADR-0255): only the cycle's faces are packaged. A library face
96
+ * reaches a page when its cartridge declares the family in `uses.fonts` and the delivery carries it, so `faceAvailable` answers
97
+ * for it through the host's detector — a row whose family nobody declared is shown disabled, never offered and then drawn in the
98
+ * system font. Keeping the rows is what lets a game that declares its families offer them with no list of its own.
99
+ */
90
100
  export const FONT_GROUPS = [
91
101
  { g: 'font.group.sans', items: [
92
102
  { k: 'atkinson', id: 'atkinson_hyperlegible', fam: 'Atkinson Hyperlegible', fb: 'sans', d: 'font.desc.atkinson' },
93
103
  { k: 'lexend', id: 'lexend', fam: 'Lexend', fb: 'sans', d: 'font.desc.lexend' },
94
- { k: 'quattro', id: 'ia_writer_quattro', fam: 'iA Writer Quattro', fb: 'sans', d: 'font.desc.quattro' },
95
104
  { k: 'andika', id: 'andika', fam: 'Andika', fb: 'sans', d: 'font.desc.andika' },
96
105
  // ⚠️ OPENDYSLEXIC IS OFFERED WITH NO CLAIM OF EFFICACY — issue #87 item 3 and `docs/game-design/typography.md`: offer
97
106
  // Dyslexie and OpenDyslexic only as a user's choice, because research shows no reading gain from them. Its description
@@ -140,8 +149,8 @@ export const FONT_GROUPS = [
140
149
  { k: 'literata', id: 'literata', fam: 'Literata', fb: 'serif' },
141
150
  { k: 'sourceserif', id: 'source_serif_4', fam: 'Source Serif 4', fb: 'serif' },
142
151
  { k: 'newsreader', id: 'newsreader', fam: 'Newsreader', fb: 'serif' },
143
- // Five READING faces: serifs for running text, like the three above.
144
- { k: 'merriweather', id: 'merriweather', fam: 'Merriweather', fb: 'serif' },
152
+ // Four READING faces: serifs for running text, like the three above. (Merriweather left: its author's original is a 4.5 MB
153
+ // variable font, and the library carries only originals of a reserved name — ADR-0254.)
145
154
  { k: 'lora', id: 'lora', fam: 'Lora', fb: 'serif' },
146
155
  { k: 'spectral', id: 'spectral', fam: 'Spectral', fb: 'serif' },
147
156
  { k: 'domine', id: 'domine', fam: 'Domine', fb: 'serif' },
@@ -171,18 +180,17 @@ export const FONT_GROUPS = [
171
180
  // the menu — it is for the buttons INSIDE school activities, not for the interface.
172
181
  { k: 'fondamento', id: 'fondamento', fam: 'Fondamento', fb: 'cursive', d: 'font.desc.fondamento', role: 'caligrafica', minPx: 20 },
173
182
  /*
174
- * THE FRENCH RONDE — #87 item 4, decided in ADR-0108 §4. It is NEVER bundled: the three faces are free for PERSONAL
175
- * use only (ADR-0012), and distributing them would distribute what was not licensed for distribution. What the entry
176
- * does is SPEAK.
183
+ * THE FRENCH RONDE, AND ITS FALLBACK (ADR-0154). The three ronde faces are NEVER bundled: they are free for PERSONAL
184
+ * use only (ADR-0012), and distributing them would distribute what was not licensed for distribution. So the stack is
185
+ * the three the device may have, in the Dev's order, and then COOKIE, which the engine packages (OFL): the first one
186
+ * the device has wins, and Cookie answers when it has none. The option is therefore never disabled.
177
187
  *
178
- * ⚠️ AND IT IS NOT A "NOT YET" ROW. The difference is ACTIONABILITY, the reason ADR-0108 gives in full: a "not yet"
179
- * nobody can resolve; this names WHICH THREE FONTS TO INSTALL, which an adult resolves in an afternoon. 📌 "Install a
180
- * ronde font" would be the defect back — an adult does not act on a category —, which is why the message names all
181
- * three.
188
+ * 🔴 COOKIE IS NOT THE RONDE — a joined brush script, not the hand a French classroom teaches. That is why the entry
189
+ * keeps a `notice` naming the three: it is the remedy for a hand that is close but not hers. 📌 "Install a ronde font"
190
+ * would not do — an adult does not act on a category —, so the sentence names all three (ADR-0108 §4).
182
191
  *
183
192
  * ⚠️ `role` ABSENT, so `geral`, deliberately, although the ronde is handwriting by nature: handwriting faces are
184
- * filtered out of the menu (`fontRole === 'geral'`), and a filtered row can say nothing to anyone. Marking the
185
- * "right" role here would erase the one thing this entry exists to do.
193
+ * filtered out of the menu (`fontRole === 'geral'`), and a filtered row can say nothing to anyone.
186
194
  */
187
195
  /*
188
196
  * THE PLAYWRITE FACES — #87 item 3, decided in ADR-0108 §2.
@@ -224,8 +232,8 @@ export const FONT_GROUPS = [
224
232
  { k: 'pwgbs', id: 'playwrite_gb_s', fam: 'Playwrite GB S', fb: 'cursive', role: 'caligrafica', minPx: 20 },
225
233
  { k: 'pwcu', id: 'playwrite_cu', fam: 'Playwrite CU', fb: 'cursive', role: 'caligrafica', minPx: 20 },
226
234
  { k: 'pwpe', id: 'playwrite_pe', fam: 'Playwrite PE', fb: 'cursive', role: 'caligrafica', minPx: 20 },
227
- { k: 'ronde', id: 'ronde_script', fam: 'Ronde Script, OPTIFrench-Script, Merveille', fb: 'cursive',
228
- d: 'font.desc.ronde', off: 'font.off.ronde', minPx: 18 }
235
+ { k: 'ronde', id: 'ronde_script', fam: 'Ronde Script, OPTIFrench-Script, Merveille, Cookie', fb: 'cursive',
236
+ d: 'font.desc.ronde', notice: 'font.notice.ronde', minPx: 18 }
229
237
  ] },
230
238
  // ⚠️ THE GAME'S FACE has its own group because it is neither sans, nor serif, nor handwriting — it is a PIXEL face,
231
239
  // and putting it in any of the three would say the wrong thing about it in the list.
@@ -244,31 +252,55 @@ export const FONT_GROUPS = [
244
252
  export function faceScale(it) { return Math.max(1, (it.minPx ?? BASE_EM_PX) / BASE_EM_PX); }
245
253
  /** A face's role; absent in the catalogue means `geral`. */
246
254
  export function fontRole(it) { return it.role ?? 'geral'; }
255
+ /** The family names in a `fam` string, unquoted, in stack order. */
256
+ function familiesIn(fam) {
257
+ return fam.split(',').map((f) => f.trim().replace(/^['"]|['"]$/g, '')).filter(Boolean);
258
+ }
247
259
  /**
248
260
  * THE FAMILIES A FACE ACCEPTS, from a `fam` that may be a STACK.
249
261
  *
250
- * 📌 The Ronde declares three (`'Ronde Script, OPTIFrench-Script, Merveille'`) because any of them will do — three
251
- * drawings of the same hand, and an adult installs whichever they find. The other faces declare one, and for them this
262
+ * 📌 The ronde declares four (`'Ronde Script, OPTIFrench-Script, Merveille, Cookie'`): the three drawings of the same
263
+ * hand an adult may install, then the packaged fallback (ADR-0154). The other faces declare one, and for them this
252
264
  * returns a list of one.
253
265
  */
254
266
  export function faceFamilies(it) {
255
- return it.fam.split(',').map((f) => f.trim().replace(/^['"]|['"]$/g, '')).filter(Boolean);
267
+ return familiesIn(it.fam);
268
+ }
269
+ /**
270
+ * A `fam` as a CSS `font-family` value — each family quoted on its own: `'Ronde Script','OPTIFrench-Script',…,'Cookie'`.
271
+ *
272
+ * 🔴 QUOTING THE WHOLE STRING ONCE NAMES ONE FAMILY THAT DOES NOT EXIST. `'Ronde Script, …, Cookie'` is a single name with
273
+ * commas in it, so the browser skips it and draws the generic fallback — the stack would never reach Cookie, and the
274
+ * fallback ADR-0154 decided would exist only in the catalogue. For a one-family `fam` the result is the same `'Inter'`.
275
+ */
276
+ export function familyStack(fam) {
277
+ return familiesIn(fam).map((f) => `'${f}'`).join(',');
256
278
  }
279
+ /**
280
+ * THE FACES THE ENGINE DRAWS WITH ITSELF, by key — the eleventh button's cycle over EVERY hand a tag can pick (ADR-0149,
281
+ * ADR-0150). With the mathematics face (`MATHEMATICS_FAMILY`) they are the only faces packaged and precached (ADR-0255 §1); every
282
+ * other face of the kit is the font library's. `tests/what-fonts-the-package-carries.node.test.js` holds `fonts.css` to this list.
283
+ */
284
+ export const ENGINE_FACE_KEYS = Object.freeze([...new Set([
285
+ 'andika', 'atkinson', 'lexend', ...Object.values(HAND_BY_COUNTRY).flat(), ...Object.values(HAND_BY_LANGUAGE).flat(),
286
+ ])]);
287
+ /** The face the engine draws mathematics in (`--font-math` in `style.css`): packaged, never a typography choice (ADR-0012). */
288
+ export const MATHEMATICS_FAMILY = 'Atkinson Hyperlegible Mono';
257
289
  /**
258
290
  * CAN THIS FACE BE USED NOW? — `off` stops being a sentence and becomes a CONDITION.
259
291
  *
260
- * ADR-0012 decided the ronde option stays DISABLED WHILE no font is present, and ADR-0108 §4 added what it says. The word
261
- * "while" is what this function builds: an `off` face becomes available again the instant the adult installs one of the
262
- * ones the message names.
292
+ * An engine face is always there: it is packaged and precached (ADR-0255 §1). Any other face is used WHILE one of its families
293
+ * is present — a library family its cartridge declared in `uses.fonts` (the root's detector answers for those), or, for an `off`
294
+ * face, one the adult installed from the ones its message names (ADR-0012's «enquanto»). The ronde's stack ends in Cookie, a
295
+ * library family, so it is available where the game declares Cookie (ADR-0154, ADR-0255).
263
296
  *
264
297
  * ⚠️ THE DETECTOR IS INJECTED, and `document.fonts` is never read here: this module is the catalogue, it runs in node in
265
298
  * the gates, and reading a browser global here would be a boot waiting to crash against an injected document.
266
- * 📌 And the DEFAULT is not installed, which is safe for a reason that does not hold for every default in this repository:
267
- * with no detector the option stays disabled WITH the message, and the message tells the adult exactly what to do. The
268
- * silence decides nothing against the child — it keeps a state that already existed and is actionable.
299
+ * 📌 And the DEFAULT is not present, which is safe: with no detector a library row stays disabled, never offered and then drawn
300
+ * in the system font, and an `off` row keeps the message that tells the adult exactly what to do.
269
301
  */
270
302
  export function faceAvailable(it, installed) {
271
- if (!it.off)
303
+ if (!it.off && ENGINE_FACE_KEYS.includes(it.k))
272
304
  return true;
273
305
  return !!installed && faceFamilies(it).some((f) => installed(f));
274
306
  }
@@ -279,6 +311,8 @@ export const OFERECIVEIS = FONT_GROUPS.flatMap((g) => g.items).filter((it) => fo
279
311
  * module wrote into at import, which the code-health measure reads as module state. The last item with a key wins, as it did.
280
312
  */
281
313
  export const FONT_BY_KEY = Object.fromEntries(FONT_GROUPS.flatMap((g) => g.items.map((it) => [it.k, it])));
314
+ /** The families `vendor/fonts.css` declares — the engine's faces and the mathematics face. A cartridge never declares these. */
315
+ export const ENGINE_FAMILIES = new Set([...ENGINE_FACE_KEYS.map((k) => FONT_BY_KEY[k].fam), MATHEMATICS_FAMILY]);
282
316
  const FONT_KEY = 'incl_font_k';
283
317
  const FONT_KEY_LEGACY = 'incl_fonte'; // pre-Fase-2: 'alfabetizacao' | 'dislexia'
284
318
  /**