@the-inclusionist/engine 9.0.0 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (532) hide show
  1. package/README.md +120 -78
  2. package/app/css/style.css +761 -377
  3. package/app/public/vendor/fonts/playwrite-cu.woff2 +0 -0
  4. package/app/public/vendor/fonts/playwrite-es-deco.woff2 +0 -0
  5. package/app/public/vendor/fonts/playwrite-es.woff2 +0 -0
  6. package/app/public/vendor/fonts/playwrite-gb-j.woff2 +0 -0
  7. package/app/public/vendor/fonts/playwrite-gb-s.woff2 +0 -0
  8. package/app/public/vendor/fonts/playwrite-pe.woff2 +0 -0
  9. package/app/public/vendor/fonts/playwrite-pt.woff2 +0 -0
  10. package/app/public/vendor/fonts-licences/NOTICE.txt +166 -0
  11. package/app/public/vendor/fonts-licences/OFL-1.1.txt +86 -0
  12. package/app/public/vendor/fonts.css +36 -84
  13. package/dist-pkg/boot/create-game.d.ts +499 -219
  14. package/dist-pkg/boot/create-game.js +3944 -562
  15. package/dist-pkg/core/a11y-sr.d.ts +25 -3
  16. package/dist-pkg/core/a11y-sr.js +38 -19
  17. package/dist-pkg/core/accessible-label.d.ts +17 -0
  18. package/dist-pkg/core/accessible-label.js +37 -0
  19. package/dist-pkg/core/accommodation-subjects.d.ts +26 -0
  20. package/dist-pkg/core/accommodation-subjects.js +32 -0
  21. package/dist-pkg/core/accommodations.d.ts +125 -0
  22. package/dist-pkg/core/accommodations.js +195 -0
  23. package/dist-pkg/core/actions.d.ts +108 -55
  24. package/dist-pkg/core/actions.js +122 -71
  25. package/dist-pkg/core/calm-mode.d.ts +31 -0
  26. package/dist-pkg/core/calm-mode.js +47 -0
  27. package/dist-pkg/core/camera-cycle.d.ts +15 -0
  28. package/dist-pkg/core/camera-cycle.js +7 -0
  29. package/dist-pkg/core/caption-duration.d.ts +15 -0
  30. package/dist-pkg/core/caption-duration.js +19 -0
  31. package/dist-pkg/core/cartridge-problems.d.ts +34 -0
  32. package/dist-pkg/core/cartridge-problems.js +53 -0
  33. package/dist-pkg/core/constants.d.ts +0 -54
  34. package/dist-pkg/core/constants.js +9 -76
  35. package/dist-pkg/core/contract.d.ts +142 -164
  36. package/dist-pkg/core/contract.js +222 -208
  37. package/dist-pkg/core/dom-query.d.ts +4 -4
  38. package/dist-pkg/core/dom-query.js +12 -20
  39. package/dist-pkg/core/entity.d.ts +62 -77
  40. package/dist-pkg/core/entity.js +18 -32
  41. package/dist-pkg/core/escape-html.d.ts +9 -10
  42. package/dist-pkg/core/escape-html.js +16 -20
  43. package/dist-pkg/core/flash-threshold.d.ts +17 -0
  44. package/dist-pkg/core/flash-threshold.js +113 -0
  45. package/dist-pkg/core/game-speed.d.ts +11 -0
  46. package/dist-pkg/core/game-speed.js +16 -0
  47. package/dist-pkg/core/genres.d.ts +20 -0
  48. package/dist-pkg/core/genres.js +44 -0
  49. package/dist-pkg/core/i18n.d.ts +71 -60
  50. package/dist-pkg/core/i18n.js +186 -200
  51. package/dist-pkg/core/loop.d.ts +17 -6
  52. package/dist-pkg/core/loop.js +26 -26
  53. package/dist-pkg/core/pause-icon-catalogue.d.ts +15 -0
  54. package/dist-pkg/core/pause-icon-catalogue.js +54 -0
  55. package/dist-pkg/core/ring.d.ts +17 -0
  56. package/dist-pkg/core/ring.js +29 -0
  57. package/dist-pkg/core/rng.d.ts +7 -12
  58. package/dist-pkg/core/rng.js +23 -53
  59. package/dist-pkg/core/route.d.ts +26 -26
  60. package/dist-pkg/core/route.js +126 -121
  61. package/dist-pkg/core/scenes.d.ts +37 -40
  62. package/dist-pkg/core/scenes.js +41 -43
  63. package/dist-pkg/core/screens.d.ts +4 -4
  64. package/dist-pkg/core/screens.js +11 -16
  65. package/dist-pkg/core/session-clock.d.ts +23 -0
  66. package/dist-pkg/core/session-clock.js +37 -0
  67. package/dist-pkg/core/setting-defaults.d.ts +61 -0
  68. package/dist-pkg/core/setting-defaults.js +66 -0
  69. package/dist-pkg/core/speech-rate.d.ts +29 -0
  70. package/dist-pkg/core/speech-rate.js +47 -0
  71. package/dist-pkg/core/state.d.ts +118 -118
  72. package/dist-pkg/core/state.js +322 -297
  73. package/dist-pkg/core/visual-cycles.d.ts +30 -0
  74. package/dist-pkg/core/visual-cycles.js +52 -0
  75. package/dist-pkg/core/visual-state.d.ts +13 -0
  76. package/dist-pkg/core/visual-state.js +6 -0
  77. package/dist-pkg/educational/activities-registry.d.ts +1 -1
  78. package/dist-pkg/educational/activities-registry.js +9 -8
  79. package/dist-pkg/educational/adaptive-engine.d.ts +11 -11
  80. package/dist-pkg/educational/adaptive-engine.js +17 -17
  81. package/dist-pkg/educational/segment-bar.d.ts +12 -12
  82. package/dist-pkg/educational/segment-bar.js +9 -9
  83. package/dist-pkg/i18n/en.js +324 -223
  84. package/dist-pkg/i18n/es.js +323 -223
  85. package/dist-pkg/i18n/pt.js +348 -226
  86. package/dist-pkg/input/default-bindings.d.ts +37 -38
  87. package/dist-pkg/input/default-bindings.js +127 -144
  88. package/dist-pkg/input/devices.d.ts +7 -7
  89. package/dist-pkg/input/devices.js +17 -16
  90. package/dist-pkg/input/edges.d.ts +23 -32
  91. package/dist-pkg/input/edges.js +23 -28
  92. package/dist-pkg/input/empathy-filter.d.ts +22 -0
  93. package/dist-pkg/input/empathy-filter.js +34 -0
  94. package/dist-pkg/input/face-map.d.ts +96 -0
  95. package/dist-pkg/input/face-map.js +255 -0
  96. package/dist-pkg/input/face-signals.d.ts +40 -0
  97. package/dist-pkg/input/face-signals.js +48 -0
  98. package/dist-pkg/input/gamepad.d.ts +195 -145
  99. package/dist-pkg/input/gamepad.js +383 -556
  100. package/dist-pkg/input/gaze-cycle.d.ts +64 -0
  101. package/dist-pkg/input/gaze-cycle.js +133 -0
  102. package/dist-pkg/input/gaze-relative.d.ts +68 -0
  103. package/dist-pkg/input/gaze-relative.js +142 -0
  104. package/dist-pkg/input/hand-map.d.ts +28 -0
  105. package/dist-pkg/input/hand-map.js +142 -0
  106. package/dist-pkg/input/input-cooldown.d.ts +17 -0
  107. package/dist-pkg/input/input-cooldown.js +39 -0
  108. package/dist-pkg/input/key-default.d.ts +30 -0
  109. package/dist-pkg/input/key-default.js +60 -0
  110. package/dist-pkg/input/keyboard-runtime.d.ts +15 -21
  111. package/dist-pkg/input/keyboard-runtime.js +23 -27
  112. package/dist-pkg/input/keyboard.d.ts +68 -63
  113. package/dist-pkg/input/keyboard.js +83 -105
  114. package/dist-pkg/input/keydown.d.ts +123 -137
  115. package/dist-pkg/input/keydown.js +284 -289
  116. package/dist-pkg/input/latch-edge.d.ts +33 -21
  117. package/dist-pkg/input/latch-edge.js +34 -35
  118. package/dist-pkg/input/latch-scope.d.ts +51 -50
  119. package/dist-pkg/input/latch-scope.js +61 -82
  120. package/dist-pkg/input/latch-store.d.ts +26 -30
  121. package/dist-pkg/input/latch-store.js +42 -50
  122. package/dist-pkg/input/latch-sync.d.ts +29 -29
  123. package/dist-pkg/input/latch-sync.js +25 -25
  124. package/dist-pkg/input/latch.d.ts +14 -17
  125. package/dist-pkg/input/latch.js +29 -37
  126. package/dist-pkg/input/pad-defaults.d.ts +13 -16
  127. package/dist-pkg/input/pad-defaults.js +21 -28
  128. package/dist-pkg/input/pad-reading.d.ts +76 -0
  129. package/dist-pkg/input/pad-reading.js +169 -0
  130. package/dist-pkg/input/pad-wizard.d.ts +99 -0
  131. package/dist-pkg/input/pad-wizard.js +256 -0
  132. package/dist-pkg/input/pointer-space.d.ts +19 -20
  133. package/dist-pkg/input/pointer-space.js +27 -32
  134. package/dist-pkg/input/pointer.d.ts +39 -40
  135. package/dist-pkg/input/pointer.js +42 -45
  136. package/dist-pkg/input/state.d.ts +101 -89
  137. package/dist-pkg/input/state.js +54 -134
  138. package/dist-pkg/input/switch-scan.d.ts +59 -0
  139. package/dist-pkg/input/switch-scan.js +67 -0
  140. package/dist-pkg/input/synthetic-source.d.ts +42 -0
  141. package/dist-pkg/input/synthetic-source.js +57 -0
  142. package/dist-pkg/input/touch-bindings.d.ts +127 -116
  143. package/dist-pkg/input/touch-bindings.js +158 -173
  144. package/dist-pkg/input/touch.d.ts +147 -72
  145. package/dist-pkg/input/touch.js +362 -126
  146. package/dist-pkg/input/transport-in-use.d.ts +107 -0
  147. package/dist-pkg/input/transport-in-use.js +130 -0
  148. package/dist-pkg/input/transports.d.ts +90 -117
  149. package/dist-pkg/input/transports.js +104 -106
  150. package/dist-pkg/input/virtual-controller.d.ts +82 -0
  151. package/dist-pkg/input/virtual-controller.js +69 -0
  152. package/dist-pkg/input/vocabulary-migration.d.ts +50 -49
  153. package/dist-pkg/input/vocabulary-migration.js +80 -79
  154. package/dist-pkg/input/voice-map.d.ts +59 -0
  155. package/dist-pkg/input/voice-map.js +123 -0
  156. package/dist-pkg/platform/audio-ambient.d.ts +13 -2
  157. package/dist-pkg/platform/audio-ambient.js +14 -14
  158. package/dist-pkg/platform/audio-earcons.d.ts +20 -19
  159. package/dist-pkg/platform/audio-earcons.js +51 -42
  160. package/dist-pkg/platform/audio-jingles.js +9 -10
  161. package/dist-pkg/platform/audio-mixer.d.ts +14 -2
  162. package/dist-pkg/platform/audio-mixer.js +31 -39
  163. package/dist-pkg/platform/audio-sonar.d.ts +49 -152
  164. package/dist-pkg/platform/audio-sonar.js +124 -302
  165. package/dist-pkg/platform/audio.d.ts +62 -19
  166. package/dist-pkg/platform/audio.js +193 -168
  167. package/dist-pkg/platform/choose-by-voice.d.ts +70 -0
  168. package/dist-pkg/platform/choose-by-voice.js +157 -0
  169. package/dist-pkg/platform/flash-sampler.d.ts +19 -0
  170. package/dist-pkg/platform/flash-sampler.js +79 -0
  171. package/dist-pkg/platform/font-library.d.ts +77 -0
  172. package/dist-pkg/platform/font-library.js +98 -0
  173. package/dist-pkg/platform/font-library.json +721 -0
  174. package/dist-pkg/platform/heavy-catalogue.d.ts +77 -0
  175. package/dist-pkg/platform/heavy-catalogue.js +256 -0
  176. package/dist-pkg/platform/heavy-mirror.d.ts +20 -0
  177. package/dist-pkg/platform/heavy-mirror.js +73 -0
  178. package/dist-pkg/platform/heavy.d.ts +136 -0
  179. package/dist-pkg/platform/heavy.js +324 -0
  180. package/dist-pkg/platform/interruptible-speech.d.ts +23 -16
  181. package/dist-pkg/platform/interruptible-speech.js +77 -54
  182. package/dist-pkg/platform/kokoro-port.d.ts +27 -0
  183. package/dist-pkg/platform/kokoro-port.js +50 -0
  184. package/dist-pkg/platform/kokoro-runtime.d.ts +20 -0
  185. package/dist-pkg/platform/kokoro-runtime.js +52 -0
  186. package/dist-pkg/platform/kokoro.d.ts +70 -0
  187. package/dist-pkg/platform/kokoro.js +133 -0
  188. package/dist-pkg/platform/listener-scope.d.ts +12 -0
  189. package/dist-pkg/platform/listener-scope.js +90 -0
  190. package/dist-pkg/platform/locale-host.d.ts +14 -0
  191. package/dist-pkg/platform/locale-host.js +45 -0
  192. package/dist-pkg/platform/microphone.d.ts +51 -0
  193. package/dist-pkg/platform/microphone.js +118 -0
  194. package/dist-pkg/platform/onnx-runtime.d.ts +34 -0
  195. package/dist-pkg/platform/onnx-runtime.js +42 -0
  196. package/dist-pkg/platform/reading-in-worker.d.ts +64 -0
  197. package/dist-pkg/platform/reading-in-worker.js +125 -0
  198. package/dist-pkg/platform/reading-model.d.ts +94 -0
  199. package/dist-pkg/platform/reading-model.js +272 -0
  200. package/dist-pkg/platform/reading-runtime.d.ts +24 -0
  201. package/dist-pkg/platform/reading-runtime.js +164 -0
  202. package/dist-pkg/platform/reading-worker.d.ts +41 -0
  203. package/dist-pkg/platform/reading-worker.js +58 -0
  204. package/dist-pkg/platform/reading.d.ts +80 -0
  205. package/dist-pkg/platform/reading.js +136 -0
  206. package/dist-pkg/platform/speech-recognition.d.ts +60 -0
  207. package/dist-pkg/platform/speech-recognition.js +104 -0
  208. package/dist-pkg/platform/speech.d.ts +23 -1
  209. package/dist-pkg/platform/speech.js +29 -18
  210. package/dist-pkg/platform/storage-keys.d.ts +96 -0
  211. package/dist-pkg/platform/storage-keys.js +106 -0
  212. package/dist-pkg/platform/storage.d.ts +46 -111
  213. package/dist-pkg/platform/storage.js +104 -169
  214. package/dist-pkg/platform/tts.d.ts +85 -39
  215. package/dist-pkg/platform/tts.js +447 -127
  216. package/dist-pkg/platform/vision-loop.d.ts +23 -0
  217. package/dist-pkg/platform/vision-loop.js +56 -0
  218. package/dist-pkg/platform/vision.d.ts +132 -0
  219. package/dist-pkg/platform/vision.js +117 -0
  220. package/dist-pkg/platform/voice-listener.d.ts +40 -0
  221. package/dist-pkg/platform/voice-listener.js +88 -0
  222. package/dist-pkg/platform/voice-plan.d.ts +15 -98
  223. package/dist-pkg/platform/voice-plan.js +56 -143
  224. package/dist-pkg/platform/vosk-runtime.d.ts +96 -0
  225. package/dist-pkg/platform/vosk-runtime.js +151 -0
  226. package/dist-pkg/platform/vosk-vocabulary.d.ts +6 -0
  227. package/dist-pkg/platform/vosk-vocabulary.js +178 -0
  228. package/dist-pkg/render/canvas.d.ts +13 -7
  229. package/dist-pkg/render/canvas.js +13 -13
  230. package/dist-pkg/render/crt.d.ts +48 -12
  231. package/dist-pkg/render/crt.js +78 -80
  232. package/dist-pkg/render/cvd-matrices.d.ts +22 -18
  233. package/dist-pkg/render/cvd-matrices.js +37 -38
  234. package/dist-pkg/render/hc-role-data.d.ts +7 -7
  235. package/dist-pkg/render/hc-role-data.js +14 -14
  236. package/dist-pkg/render/high-contrast.d.ts +67 -56
  237. package/dist-pkg/render/high-contrast.js +226 -217
  238. package/dist-pkg/render/low-vision-drawing.d.ts +5 -0
  239. package/dist-pkg/render/low-vision-drawing.js +46 -0
  240. package/dist-pkg/render/lq-filter.d.ts +32 -20
  241. package/dist-pkg/render/lq-filter.js +59 -65
  242. package/dist-pkg/render/port.d.ts +86 -88
  243. package/dist-pkg/render/port.js +17 -23
  244. package/dist-pkg/render/screen-pipeline.d.ts +42 -42
  245. package/dist-pkg/render/screen-pipeline.js +80 -87
  246. package/dist-pkg/render/sprite-fx.d.ts +2 -1
  247. package/dist-pkg/render/sprite-fx.js +16 -14
  248. package/dist-pkg/render/viewports.d.ts +20 -15
  249. package/dist-pkg/render/viewports.js +83 -114
  250. package/dist-pkg/render/viz-axes-labels.d.ts +47 -0
  251. package/dist-pkg/render/viz-axes-labels.js +88 -0
  252. package/dist-pkg/render/viz-axes.d.ts +89 -86
  253. package/dist-pkg/render/viz-axes.js +128 -116
  254. package/dist-pkg/render/viz-modes.d.ts +28 -29
  255. package/dist-pkg/render/viz-modes.js +40 -45
  256. package/dist-pkg/render/viz-refusal.d.ts +32 -0
  257. package/dist-pkg/render/viz-refusal.js +57 -0
  258. package/dist-pkg/render/viz-setters.d.ts +104 -66
  259. package/dist-pkg/render/viz-setters.js +182 -191
  260. package/dist-pkg/ui/aac-sets.d.ts +45 -0
  261. package/dist-pkg/ui/aac-sets.js +41 -0
  262. package/dist-pkg/ui/audio-choices.d.ts +68 -0
  263. package/dist-pkg/ui/audio-choices.js +98 -0
  264. package/dist-pkg/ui/audio-rows-that-apply.d.ts +12 -0
  265. package/dist-pkg/ui/audio-rows-that-apply.js +43 -0
  266. package/dist-pkg/ui/camera-control.d.ts +53 -0
  267. package/dist-pkg/ui/camera-control.js +48 -0
  268. package/dist-pkg/ui/changed-mark.d.ts +9 -8
  269. package/dist-pkg/ui/changed-mark.js +22 -53
  270. package/dist-pkg/ui/control-choices.d.ts +37 -0
  271. package/dist-pkg/ui/control-choices.js +84 -0
  272. package/dist-pkg/ui/debug-panel.d.ts +59 -43
  273. package/dist-pkg/ui/debug-panel.js +68 -69
  274. package/dist-pkg/ui/declared-words.d.ts +21 -0
  275. package/dist-pkg/ui/declared-words.js +66 -0
  276. package/dist-pkg/ui/dom.d.ts +10 -14
  277. package/dist-pkg/ui/dom.js +10 -47
  278. package/dist-pkg/ui/drawing-problems.d.ts +25 -0
  279. package/dist-pkg/ui/drawing-problems.js +94 -0
  280. package/dist-pkg/ui/eye-control.d.ts +36 -0
  281. package/dist-pkg/ui/eye-control.js +178 -0
  282. package/dist-pkg/ui/face-control.d.ts +25 -0
  283. package/dist-pkg/ui/face-control.js +183 -0
  284. package/dist-pkg/ui/focus-trap.d.ts +39 -49
  285. package/dist-pkg/ui/focus-trap.js +56 -65
  286. package/dist-pkg/ui/fonts.d.ts +157 -68
  287. package/dist-pkg/ui/fonts.js +305 -115
  288. package/dist-pkg/ui/footer-scroll-driver.d.ts +14 -0
  289. package/dist-pkg/ui/footer-scroll-driver.js +201 -0
  290. package/dist-pkg/ui/footer-scroll.d.ts +38 -0
  291. package/dist-pkg/ui/footer-scroll.js +55 -0
  292. package/dist-pkg/ui/game-options.d.ts +47 -0
  293. package/dist-pkg/ui/game-options.js +158 -0
  294. package/dist-pkg/ui/gaze-overlay.d.ts +64 -0
  295. package/dist-pkg/ui/gaze-overlay.js +165 -0
  296. package/dist-pkg/ui/hand-control.d.ts +25 -0
  297. package/dist-pkg/ui/hand-control.js +143 -0
  298. package/dist-pkg/ui/help-panel.d.ts +87 -0
  299. package/dist-pkg/ui/help-panel.js +242 -0
  300. package/dist-pkg/ui/hud-bands.d.ts +62 -0
  301. package/dist-pkg/ui/hud-bands.js +146 -0
  302. package/dist-pkg/ui/hud-row.d.ts +34 -0
  303. package/dist-pkg/ui/hud-row.js +80 -0
  304. package/dist-pkg/ui/hud.d.ts +73 -73
  305. package/dist-pkg/ui/hud.js +94 -98
  306. package/dist-pkg/ui/item-announcement.d.ts +13 -12
  307. package/dist-pkg/ui/item-announcement.js +25 -26
  308. package/dist-pkg/ui/layout.d.ts +103 -28
  309. package/dist-pkg/ui/layout.js +117 -99
  310. package/dist-pkg/ui/libras-avatar-clip.d.ts +30 -0
  311. package/dist-pkg/ui/libras-avatar-clip.js +86 -0
  312. package/dist-pkg/ui/libras-avatar-load.d.ts +52 -0
  313. package/dist-pkg/ui/libras-avatar-load.js +134 -0
  314. package/dist-pkg/ui/libras-avatar-plan.d.ts +102 -0
  315. package/dist-pkg/ui/libras-avatar-plan.js +211 -0
  316. package/dist-pkg/ui/libras-avatar-player.d.ts +28 -0
  317. package/dist-pkg/ui/libras-avatar-player.js +226 -0
  318. package/dist-pkg/ui/libras-avatar-stage.d.ts +37 -0
  319. package/dist-pkg/ui/libras-avatar-stage.js +208 -0
  320. package/dist-pkg/ui/libras-glosses.d.ts +28 -0
  321. package/dist-pkg/ui/libras-glosses.js +161 -0
  322. package/dist-pkg/ui/locale-flags.d.ts +16 -0
  323. package/dist-pkg/ui/locale-flags.js +30 -0
  324. package/dist-pkg/ui/loop-crash.d.ts +17 -17
  325. package/dist-pkg/ui/loop-crash.js +33 -62
  326. package/dist-pkg/ui/menu-intent.d.ts +61 -0
  327. package/dist-pkg/ui/menu-intent.js +88 -0
  328. package/dist-pkg/ui/menu-items.d.ts +20 -0
  329. package/dist-pkg/ui/menu-items.js +51 -0
  330. package/dist-pkg/ui/menu-nav.d.ts +98 -92
  331. package/dist-pkg/ui/menu-nav.js +398 -286
  332. package/dist-pkg/ui/mobility-choices.d.ts +26 -0
  333. package/dist-pkg/ui/mobility-choices.js +41 -0
  334. package/dist-pkg/ui/motion-choices.d.ts +31 -0
  335. package/dist-pkg/ui/motion-choices.js +68 -0
  336. package/dist-pkg/ui/motion-scene.d.ts +24 -19
  337. package/dist-pkg/ui/motion-scene.js +29 -61
  338. package/dist-pkg/ui/mount-panel.d.ts +81 -0
  339. package/dist-pkg/ui/mount-panel.js +61 -0
  340. package/dist-pkg/ui/panel-shell.d.ts +65 -31
  341. package/dist-pkg/ui/panel-shell.js +92 -66
  342. package/dist-pkg/ui/panel-widgets.d.ts +150 -0
  343. package/dist-pkg/ui/panel-widgets.js +294 -0
  344. package/dist-pkg/ui/pause-buttons.d.ts +35 -0
  345. package/dist-pkg/ui/pause-buttons.js +66 -0
  346. package/dist-pkg/ui/pause-icons.d.ts +261 -347
  347. package/dist-pkg/ui/pause-icons.js +671 -601
  348. package/dist-pkg/ui/pause-markup.d.ts +86 -0
  349. package/dist-pkg/ui/pause-markup.js +125 -0
  350. package/dist-pkg/ui/reach-notice.d.ts +22 -23
  351. package/dist-pkg/ui/reach-notice.js +55 -58
  352. package/dist-pkg/ui/scan-overlay.d.ts +23 -0
  353. package/dist-pkg/ui/scan-overlay.js +50 -0
  354. package/dist-pkg/ui/screen-text.d.ts +25 -0
  355. package/dist-pkg/ui/screen-text.js +152 -0
  356. package/dist-pkg/ui/session-clock.d.ts +32 -0
  357. package/dist-pkg/ui/session-clock.js +67 -0
  358. package/dist-pkg/ui/settings-aac.d.ts +70 -0
  359. package/dist-pkg/ui/settings-aac.js +208 -0
  360. package/dist-pkg/ui/settings-audio.d.ts +97 -114
  361. package/dist-pkg/ui/settings-audio.js +493 -425
  362. package/dist-pkg/ui/settings-controls.d.ts +53 -96
  363. package/dist-pkg/ui/settings-controls.js +143 -215
  364. package/dist-pkg/ui/settings-empathy.d.ts +39 -30
  365. package/dist-pkg/ui/settings-empathy.js +53 -60
  366. package/dist-pkg/ui/settings-mobility.d.ts +192 -0
  367. package/dist-pkg/ui/settings-mobility.js +332 -0
  368. package/dist-pkg/ui/settings-motion.d.ts +91 -70
  369. package/dist-pkg/ui/settings-motion.js +287 -198
  370. package/dist-pkg/ui/settings-panel.d.ts +58 -45
  371. package/dist-pkg/ui/settings-panel.js +189 -94
  372. package/dist-pkg/ui/settings-typo.d.ts +29 -78
  373. package/dist-pkg/ui/settings-typo.js +158 -129
  374. package/dist-pkg/ui/settings-visual.d.ts +29 -87
  375. package/dist-pkg/ui/settings-visual.js +240 -193
  376. package/dist-pkg/ui/shell.d.ts +112 -135
  377. package/dist-pkg/ui/shell.js +148 -217
  378. package/dist-pkg/ui/simulation-list.d.ts +27 -0
  379. package/dist-pkg/ui/simulation-list.js +76 -0
  380. package/dist-pkg/ui/simulation-over-the-world.d.ts +23 -0
  381. package/dist-pkg/ui/simulation-over-the-world.js +131 -0
  382. package/dist-pkg/ui/simulation-refusal.d.ts +5 -31
  383. package/dist-pkg/ui/simulation-refusal.js +8 -56
  384. package/dist-pkg/ui/switchable-control.d.ts +11 -0
  385. package/dist-pkg/ui/switchable-control.js +19 -0
  386. package/dist-pkg/ui/title.d.ts +3 -12
  387. package/dist-pkg/ui/title.js +49 -18
  388. package/dist-pkg/ui/top-band.d.ts +26 -0
  389. package/dist-pkg/ui/top-band.js +96 -0
  390. package/dist-pkg/ui/typo-choices.d.ts +71 -0
  391. package/dist-pkg/ui/typo-choices.js +85 -0
  392. package/dist-pkg/ui/visual-axes-panel.d.ts +11 -47
  393. package/dist-pkg/ui/visual-axes-panel.js +12 -94
  394. package/dist-pkg/ui/visual-choices.d.ts +77 -0
  395. package/dist-pkg/ui/visual-choices.js +107 -0
  396. package/dist-pkg/ui/vlibras.d.ts +77 -10
  397. package/dist-pkg/ui/vlibras.js +130 -81
  398. package/dist-pkg/ui/voice-control.d.ts +68 -0
  399. package/dist-pkg/ui/voice-control.js +276 -0
  400. package/dist-pkg/ui/voice-settings.d.ts +95 -0
  401. package/dist-pkg/ui/voice-settings.js +352 -0
  402. package/dist-pkg/ui/where-the-child-is.d.ts +31 -0
  403. package/dist-pkg/ui/where-the-child-is.js +70 -0
  404. package/docs/CREDITS.md +300 -44
  405. package/docs/LICENSES.md +189 -145
  406. package/package.json +51 -27
  407. package/scripts/check-cartridge.mjs +78 -0
  408. package/scripts/game-build.d.mts +25 -0
  409. package/scripts/game-build.mjs +144 -0
  410. package/scripts/heavy-into-the-delivery.mjs +388 -0
  411. package/scripts/libras-avatar.json +680 -0
  412. package/scripts/libras-avatar.mjs +246 -0
  413. package/scripts/libras-glosses/gloss.py +95 -0
  414. package/scripts/libras-glosses/pyproject.toml +32 -0
  415. package/scripts/libras-glosses/uv.lock +781 -0
  416. package/scripts/libras-glosses.mjs +351 -0
  417. package/scripts/licences/Apache-2.0.txt +176 -0
  418. package/scripts/licences/GPL-3.0.txt +674 -0
  419. package/scripts/licences/MIT.txt +17 -0
  420. package/scripts/licences/OFL-1.1.txt +86 -0
  421. package/scripts/licences/UFL-1.0.txt +96 -0
  422. package/scripts/licences/fonts.mjs +389 -0
  423. package/scripts/licences/third-party.mjs +301 -0
  424. package/scripts/licences/vosk-browser.NOTICE.txt +268 -0
  425. package/app/public/vendor/fonts/comicneue-400.woff2 +0 -0
  426. package/app/public/vendor/fonts/comicneue-700.woff2 +0 -0
  427. package/app/public/vendor/fonts/fondamento-400-ext.woff2 +0 -0
  428. package/app/public/vendor/fonts/fondamento-400.woff2 +0 -0
  429. package/app/public/vendor/fonts/inter-400.woff2 +0 -0
  430. package/app/public/vendor/fonts/inter-700.woff2 +0 -0
  431. package/app/public/vendor/fonts/lato-400.woff2 +0 -0
  432. package/app/public/vendor/fonts/lato-700.woff2 +0 -0
  433. package/app/public/vendor/fonts/literata-400.woff2 +0 -0
  434. package/app/public/vendor/fonts/literata-700.woff2 +0 -0
  435. package/app/public/vendor/fonts/newsreader-400.woff2 +0 -0
  436. package/app/public/vendor/fonts/newsreader-700.woff2 +0 -0
  437. package/app/public/vendor/fonts/opendyslexic-400.woff2 +0 -0
  438. package/app/public/vendor/fonts/opensans-400.woff2 +0 -0
  439. package/app/public/vendor/fonts/opensans-700.woff2 +0 -0
  440. package/app/public/vendor/fonts/pinyon-400.woff2 +0 -0
  441. package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
  442. package/app/public/vendor/fonts/quattro-400.woff2 +0 -0
  443. package/app/public/vendor/fonts/quattro-700.woff2 +0 -0
  444. package/app/public/vendor/fonts/sourcesans-400.woff2 +0 -0
  445. package/app/public/vendor/fonts/sourcesans-700.woff2 +0 -0
  446. package/app/public/vendor/fonts/sourceserif-400.woff2 +0 -0
  447. package/app/public/vendor/fonts/sourceserif-700.woff2 +0 -0
  448. package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
  449. package/dist-pkg/core/anel.d.ts +0 -17
  450. package/dist-pkg/core/anel.js +0 -31
  451. package/dist-pkg/core/collision.d.ts +0 -21
  452. package/dist-pkg/core/collision.js +0 -62
  453. package/dist-pkg/core/layers.d.ts +0 -63
  454. package/dist-pkg/core/layers.js +0 -95
  455. package/dist-pkg/core/letter-grid.d.ts +0 -55
  456. package/dist-pkg/core/letter-grid.js +0 -116
  457. package/dist-pkg/core/password.d.ts +0 -34
  458. package/dist-pkg/core/password.js +0 -205
  459. package/dist-pkg/core/rotulo-acessivel.d.ts +0 -17
  460. package/dist-pkg/core/rotulo-acessivel.js +0 -41
  461. package/dist-pkg/core/run-state.d.ts +0 -115
  462. package/dist-pkg/core/run-state.js +0 -40
  463. package/dist-pkg/core/tiles.d.ts +0 -12
  464. package/dist-pkg/core/tiles.js +0 -61
  465. package/dist-pkg/core/world.d.ts +0 -4
  466. package/dist-pkg/core/world.js +0 -58
  467. package/dist-pkg/input/origem-sintetica.d.ts +0 -44
  468. package/dist-pkg/input/origem-sintetica.js +0 -60
  469. package/dist-pkg/input/transporte-em-uso.d.ts +0 -101
  470. package/dist-pkg/input/transporte-em-uso.js +0 -130
  471. package/dist-pkg/platform/audio-nav.d.ts +0 -51
  472. package/dist-pkg/platform/audio-nav.js +0 -99
  473. package/dist-pkg/platform/guide-intensity.d.ts +0 -40
  474. package/dist-pkg/platform/guide-intensity.js +0 -73
  475. package/dist-pkg/platform/pesados-catalogo.d.ts +0 -14
  476. package/dist-pkg/platform/pesados-catalogo.js +0 -177
  477. package/dist-pkg/platform/pesados.d.ts +0 -31
  478. package/dist-pkg/platform/pesados.js +0 -75
  479. package/dist-pkg/platform/vozes-prontas.d.ts +0 -28
  480. package/dist-pkg/platform/vozes-prontas.js +0 -61
  481. package/dist-pkg/render/camera.d.ts +0 -58
  482. package/dist-pkg/render/camera.js +0 -105
  483. package/dist-pkg/render/cenario-data.d.ts +0 -127
  484. package/dist-pkg/render/cenario-data.js +0 -145
  485. package/dist-pkg/render/city-tex.d.ts +0 -63
  486. package/dist-pkg/render/city-tex.js +0 -228
  487. package/dist-pkg/render/city-tiles.d.ts +0 -11
  488. package/dist-pkg/render/city-tiles.js +0 -140
  489. package/dist-pkg/render/draw.d.ts +0 -149
  490. package/dist-pkg/render/draw.js +0 -231
  491. package/dist-pkg/render/fx.d.ts +0 -63
  492. package/dist-pkg/render/fx.js +0 -123
  493. package/dist-pkg/render/minimap.d.ts +0 -11
  494. package/dist-pkg/render/minimap.js +0 -94
  495. package/dist-pkg/render/parallax.d.ts +0 -85
  496. package/dist-pkg/render/parallax.js +0 -158
  497. package/dist-pkg/render/player-anim.d.ts +0 -71
  498. package/dist-pkg/render/player-anim.js +0 -152
  499. package/dist-pkg/render/recycling-tex.d.ts +0 -48
  500. package/dist-pkg/render/recycling-tex.js +0 -164
  501. package/dist-pkg/render/scene-city.d.ts +0 -77
  502. package/dist-pkg/render/scene-city.js +0 -181
  503. package/dist-pkg/render/scene-parallax.d.ts +0 -72
  504. package/dist-pkg/render/scene-parallax.js +0 -426
  505. package/dist-pkg/render/scene-sky.d.ts +0 -126
  506. package/dist-pkg/render/scene-sky.js +0 -295
  507. package/dist-pkg/render/set-cenario.d.ts +0 -54
  508. package/dist-pkg/render/set-cenario.js +0 -103
  509. package/dist-pkg/render/textures.d.ts +0 -75
  510. package/dist-pkg/render/textures.js +0 -240
  511. package/dist-pkg/render/title-scene.d.ts +0 -46
  512. package/dist-pkg/render/title-scene.js +0 -75
  513. package/dist-pkg/render/weather.d.ts +0 -96
  514. package/dist-pkg/render/weather.js +0 -171
  515. package/dist-pkg/render/wheelchair-sprites.d.ts +0 -32
  516. package/dist-pkg/render/wheelchair-sprites.js +0 -69
  517. package/dist-pkg/render/world-tex.d.ts +0 -18
  518. package/dist-pkg/render/world-tex.js +0 -87
  519. package/dist-pkg/ui/activities-menu.d.ts +0 -258
  520. package/dist-pkg/ui/activities-menu.js +0 -648
  521. package/dist-pkg/ui/caa-sets.d.ts +0 -39
  522. package/dist-pkg/ui/caa-sets.js +0 -65
  523. package/dist-pkg/ui/latch-refusal.d.ts +0 -32
  524. package/dist-pkg/ui/latch-refusal.js +0 -60
  525. package/dist-pkg/ui/map-hub.d.ts +0 -56
  526. package/dist-pkg/ui/map-hub.js +0 -138
  527. package/dist-pkg/ui/settings-caa.d.ts +0 -45
  528. package/dist-pkg/ui/settings-caa.js +0 -137
  529. package/dist-pkg/ui/settings-motor.d.ts +0 -175
  530. package/dist-pkg/ui/settings-motor.js +0 -342
  531. package/dist-pkg/ui/webcam.d.ts +0 -7
  532. package/dist-pkg/ui/webcam.js +0 -93
@@ -1,153 +1,342 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
- // ui/fonts.ts — catálogo de fontes (dados) + índice por chave + carga/persistência da escolha. Módulo-folha
3
- // (só depende de storage). A instância `fontKey` e o setGameFont (aplica família/espaçamento) ficam no game.js.
4
- import * as store from '../platform/storage.js';
2
+ // ui/fonts.ts — the font catalogue (data) + index by key + loading/persisting the choice. A leaf module WITH NO IMPORTS:
3
+ // whoever stores comes in as a parameter (`FontStore`), in `resolveFontKey` and `persistFontKey` — so the decision of
4
+ // WHERE it is stored belongs entirely to the caller.
5
+ /**
6
+ * THIS CHILD'S COUNTRY HAND, and the COLONISER's fallback when the country has none of its own (ADR-0150 §1).
7
+ *
8
+ * 🎯 The rule is the Dev's and is truer than the one it replaced: ADR-0012 says the hand one learns to write is NATIONAL,
9
+ * not linguistic, and never said what to do with nations without a face of their own. Falling back to what the United
10
+ * States teaches was a default dressed as a country; the coloniser's hand is a true statement about how that child's
11
+ * school taught them to write.
12
+ *
13
+ * 📌 AND THREE FACES CLOSE THE WHOLE GAP, because the repertoire is already limited to English, Portuguese and Spanish
14
+ * (ADR-0012). That is what makes the truer rule also the cheapest to bundle.
15
+ *
16
+ * ⚠️ WHERE THE COUNTRY TEACHES TWO HANDS, IT RETURNS BOTH, in the Dev's order — the traditional first. That gives the
17
+ * eleventh button's cycle SIX positions in those countries instead of five.
18
+ */
19
+ const HAND_BY_COUNTRY = Object.freeze({
20
+ BR: ['pwbr'],
21
+ US: ['pwustrad', 'pwusmod'], // the US teaches two, and choosing one would be choosing for the child
22
+ GB: ['pwgbj', 'pwgbs'], // joined and semi-joined
23
+ ES: ['pwes', 'pwesdeco'],
24
+ PT: ['pwpt'],
25
+ CA: ['pwca'], MX: ['pwmx'], AR: ['pwar'], CL: ['pwcl'], CO: ['pwco'], CU: ['pwcu'], PE: ['pwpe'],
26
+ });
27
+ /** The fallback by LANGUAGE: the coloniser's hand. Three entries cover every country the repertoire admits. */
28
+ const HAND_BY_LANGUAGE = Object.freeze({
29
+ es: ['pwes', 'pwesdeco'],
30
+ pt: ['pwpt'],
31
+ en: ['pwgbj', 'pwgbs'],
32
+ });
33
+ /** The country hand's increase. Named so the gate can assert it against `minPx` instead of repeating it. */
34
+ export const HANDWRITING_SCALE = 1.25;
35
+ /** The document's base, in px — the `font-size` of `html,body`. The floor comes from multiplying it by the scale. */
36
+ export const BASE_EM_PX = 16;
37
+ /**
38
+ * THE ELEVENTH BUTTON'S CYCLE — five positions, or six where the country teaches two hands (ADR-0149 §1, ADR-0150 §2).
39
+ *
40
+ * 🎯 EACH STEP CHANGES THE CASE **AND** THE FACE, and that is the whole decision: `letterCase` (`core/state`, ADR-0028)
41
+ * and the face are two settings, but Andika in upper case is ONE pedagogical choice of whoever teaches literacy, not
42
+ * two. A child should not have to know the model to make it.
43
+ *
44
+ * 📌 IT STARTS AT ATKINSON, which is position (c) and the project's default. The cycle is a ring: from it, one press goes
45
+ * to Lexend and the last goes back to the start.
46
+ *
47
+ * ⚠️ THE COUNTRY HAND MAY NOT EXIST — a tag with no region and a language outside the repertoire returns nothing. Then the
48
+ * cycle has FOUR positions, which is the right answer: one position fewer is better than one showing the hand of a
49
+ * country that is not that child's.
50
+ *
51
+ * 🔴 ARASAAC AND PCS ARE NOT POSITIONS, a decision and not an oversight: ADR-0155 §3 says to skip them — while the licence
52
+ * does not allow, the cycle neither stops on them nor announces them. A position existing only to be skipped would be
53
+ * data with no reader; they enter the day they work.
54
+ */
55
+ export function typographyCycle(tag) {
56
+ const hands = handsForTag(tag);
57
+ return Object.freeze([
58
+ { letterCase: 'upper', font: 'andika', scale: 1 }, // (a) the literacy pair
59
+ { letterCase: 'mixed', font: 'andika', scale: 1 }, // (b)
60
+ { letterCase: 'mixed', font: 'atkinson', scale: 1 }, // (c) the default — the cycle starts here
61
+ { letterCase: 'mixed', font: 'lexend', scale: 1 }, // (d)
62
+ // (e), and (f) where the country teaches two. ⚠️ 25% LARGER, and the number is not taste: the document's base is
63
+ // 16 px, the Playwrite faces declare `minPx: 20`, and 16 × 1.25 is exactly 20. The scale IS the floor.
64
+ ...hands.map((face) => ({ letterCase: 'mixed', font: face, scale: HANDWRITING_SCALE })),
65
+ ]);
66
+ }
67
+ /** The index where the cycle STARTS — position (c). Named so the gate can assert it without recounting. */
68
+ export const CYCLE_START = 2;
69
+ /**
70
+ * The handwriting font keys for a BCP-47 tag — `pt-BR` → `['pwbr']`, `es-MX` → `['pwmx']`.
71
+ *
72
+ * ⚠️ IT READS THE REGION AND THEN THE LANGUAGE, in that order, and the forgotten case is the tag WITHOUT a region: `en`
73
+ * alone names no country, and falling back is the right answer — not an error, a child whose browser did not say where
74
+ * they are.
75
+ *
76
+ * 📌 It returns a LIST and not one face: where the country teaches two hands, both enter the cycle.
77
+ * 📌 It returns NOTHING for a language outside the repertoire, and nothing is sayable: the caller drops the position
78
+ * from the cycle instead of showing a hand that is nobody's.
79
+ */
80
+ export function handsForTag(tag) {
81
+ if (!tag)
82
+ return [];
83
+ const parts = String(tag).split('-');
84
+ const language = (parts[0] ?? '').toLowerCase();
85
+ const region = parts.slice(1).find((p) => /^[A-Za-z]{2}$/.test(p))?.toUpperCase();
86
+ if (region && HAND_BY_COUNTRY[region])
87
+ return HAND_BY_COUNTRY[region];
88
+ return HAND_BY_LANGUAGE[language] ?? [];
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
+ */
5
100
  export const FONT_GROUPS = [
6
101
  { g: 'font.group.sans', items: [
7
- { k: 'atkinson', fam: 'Atkinson Hyperlegible', fb: 'sans', d: 'font.desc.atkinson' },
8
- { k: 'lexend', fam: 'Lexend', fb: 'sans', d: 'font.desc.lexend' },
9
- { k: 'quattro', fam: 'iA Writer Quattro', fb: 'sans', d: 'font.desc.quattro' },
10
- { k: 'andika', fam: 'Andika', fb: 'sans', d: 'font.desc.andika' },
11
- // ⚠️ A OPENDYSLEXIC ENTRA SEM NENHUMA ALEGAÇÃO DE EFICÁCIA, e a restrição é da issue #87 item 3 e do
12
- // `docs/game-design/typography.md`, que diz por extenso: «Ofereça a Dyslexie e a OpenDyslexic apenas como
13
- // escolha do usuário. A pesquisa não mostra ganho de leitura com elas.» A descrição dela fala do DESENHO
14
- // (hastes pesadas em baixo), nunca do efeito — prometer leitura melhor seria vender a uma criança
15
- // disléxica uma coisa que a evidência não sustenta, e ela é quem menos pode pagar por isso.
16
- { k: 'opendyslexic', fam: 'OpenDyslexic', fb: 'sans', d: 'font.desc.opendyslexic' },
17
- { k: 'sourcesans', fam: 'Source Sans 3', fb: 'sans' },
18
- { k: 'inter', fam: 'Inter', fb: 'sans' },
19
- { k: 'opensans', fam: 'Open Sans', fb: 'sans' },
20
- { k: 'lato', fam: 'Lato', fb: 'sans' }
102
+ { k: 'atkinson', id: 'atkinson_hyperlegible', fam: 'Atkinson Hyperlegible', fb: 'sans', d: 'font.desc.atkinson' },
103
+ { k: 'lexend', id: 'lexend', fam: 'Lexend', fb: 'sans', d: 'font.desc.lexend' },
104
+ { k: 'andika', id: 'andika', fam: 'Andika', fb: 'sans', d: 'font.desc.andika' },
105
+ // ⚠️ OPENDYSLEXIC IS OFFERED WITH NO CLAIM OF EFFICACY — issue #87 item 3 and `docs/game-design/typography.md`: offer
106
+ // Dyslexie and OpenDyslexic only as a user's choice, because research shows no reading gain from them. Its description
107
+ // speaks of the DESIGN (heavy stems at the bottom), never of the effect — promising better reading would sell a
108
+ // dyslexic child something the evidence does not support, and they are who can least afford it.
109
+ { k: 'opendyslexic', id: 'opendyslexic', fam: 'OpenDyslexic', fb: 'sans', d: 'font.desc.opendyslexic' },
110
+ { k: 'sourcesans', id: 'source_sans_3', fam: 'Source Sans 3', fb: 'sans' },
111
+ { k: 'inter', id: 'inter', fam: 'Inter', fb: 'sans' },
112
+ { k: 'opensans', id: 'open_sans', fam: 'Open Sans', fb: 'sans' },
113
+ { k: 'lato', id: 'lato', fam: 'Lato', fb: 'sans' },
114
+ // ⚠️ THE FOUR ROUNDED FACES are here at the Dev's request, WITHOUT a description on purpose: a `d` exists when there
115
+ // is something to say the name does not — Atkinson has one because the Braille Institute designed it for this,
116
+ // OpenDyslexic because its description has to speak of the DESIGN and never the effect. Rounded is not a claim, it is
117
+ // what you see.
118
+ //
119
+ // 📌 `geral` (implicit role): interface faces, neither handwriting nor game — so they follow the BDA spacing ADR-0149
120
+ // §2 generalises, and appear in the typography menu like the others.
121
+ //
122
+ // ⚠️ `Fredoka` AND NOT "Fredoka One": that is the legacy name. Google publishes the variable family as `Fredoka`; the
123
+ // old static one was its 600 weight. See the matching block in `fonts.css`.
124
+ { k: 'fredoka', id: 'fredoka', fam: 'Fredoka', fb: 'sans' },
125
+ { k: 'quicksand', id: 'quicksand', fam: 'Quicksand', fb: 'sans' },
126
+ { k: 'nunito', id: 'nunito', fam: 'Nunito', fb: 'sans' },
127
+ { k: 'teachers', id: 'teachers', fam: 'Teachers', fb: 'sans' },
128
+ /*
129
+ * SEVEN MORE SANS FACES (ADR-0150, the Dev's request).
130
+ *
131
+ * 🔴 CLASH DISPLAY IS NOT HERE, and the absence is read, not forgotten: it is **not on Google Fonts** — it comes from
132
+ * Fontshare (Indian Type Foundry), and `LICENSES.md` §3 requires the licence checked BEFORE bundling. Read, it FAILS:
133
+ * it is «Closed Source» under the ITF Free Font License, whose §02 forbids distributing the file through a repository,
134
+ * an application or a public server and serving it as a selectable font to third parties. The gate is in
135
+ * `tests/what-fonts-the-package-carries.node.test.js`.
136
+ */
137
+ { k: 'robotoflex', id: 'roboto_flex', fam: 'Roboto Flex', fb: 'sans' },
138
+ { k: 'ubuntu', id: 'ubuntu', fam: 'Ubuntu', fb: 'sans' },
139
+ { k: 'notosans', id: 'noto_sans', fam: 'Noto Sans', fb: 'sans' },
140
+ { k: 'spacegrotesk', id: 'space_grotesk', fam: 'Space Grotesk', fb: 'sans', minPx: 20 },
141
+ { k: 'sora', id: 'sora', fam: 'Sora', fb: 'sans', minPx: 20 },
142
+ { k: 'jakarta', id: 'plus_jakarta_sans', fam: 'Plus Jakarta Sans', fb: 'sans', minPx: 20 }
21
143
  ] },
144
+ // ⚠️ NO COMFORTAA, by the Dev's decision: «ruim para dislexia». The reason is in the face — its near-geometric shapes
145
+ // reduce the differentiation between letters, the very axis that makes Atkinson Hyperlegible this project's default.
146
+ // Written down because a face that leaves no trace gets proposed again by the next person who thinks a rounded one is
147
+ // missing.
22
148
  { g: 'font.group.serif', items: [
23
- { k: 'literata', fam: 'Literata', fb: 'serif' },
24
- { k: 'sourceserif', fam: 'Source Serif 4', fb: 'serif' },
25
- { k: 'newsreader', fam: 'Newsreader', fb: 'serif' }
149
+ { k: 'literata', id: 'literata', fam: 'Literata', fb: 'serif' },
150
+ { k: 'sourceserif', id: 'source_serif_4', fam: 'Source Serif 4', fb: 'serif' },
151
+ { k: 'newsreader', id: 'newsreader', fam: 'Newsreader', 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.)
154
+ { k: 'lora', id: 'lora', fam: 'Lora', fb: 'serif' },
155
+ { k: 'spectral', id: 'spectral', fam: 'Spectral', fb: 'serif' },
156
+ { k: 'domine', id: 'domine', fam: 'Domine', fb: 'serif' },
157
+ { k: 'bitter', id: 'bitter', fam: 'Bitter', fb: 'serif' },
158
+ /*
159
+ * ⚠️ THE FOUR DISPLAY FACES ARE SOMETHING ELSE, and come with the warning written instead of mixed with the reading
160
+ * ones. Playfair Display, DM Serif Display, Fraunces and Bodoni Moda have HIGH CONTRAST — thick stems beside very thin
161
+ * ones — and Bodoni is the extreme of the axis. It is exactly what Atkinson Hyperlegible was designed NOT to be, and at
162
+ * small sizes the thin stems disappear first for whoever sees least.
163
+ *
164
+ * 📌 They stay, because OFFERING is not applying: the default is still Atkinson, and whoever picks one of these is
165
+ * choosing. The defect would be the engine ADOPTING one by itself.
166
+ * 🔴 And they are not for activity body text; a game using them for a long prompt is using a title face as a reading
167
+ * face.
168
+ */
169
+ { k: 'playfair', id: 'playfair_display', fam: 'Playfair Display', fb: 'serif', minPx: 20 },
170
+ { k: 'dmserifdisplay', id: 'dm_serif_display', fam: 'DM Serif Display', fb: 'serif', minPx: 20 },
171
+ { k: 'fraunces', id: 'fraunces', fam: 'Fraunces', fb: 'serif', minPx: 20 },
172
+ { k: 'bodonimoda', id: 'bodoni_moda', fam: 'Bodoni Moda', fb: 'serif', minPx: 20 }
26
173
  ] },
27
- // ⚠️ QUATRO FACES SAÍRAM DAQUI EM 2026-09-07 (issue #87, item 3, decisão do Dev):
28
- // · `greatvibes` (44 KB) e `ufcook` (20 KB) — peso que o roster não paga;
29
- // · `learningcurve` e `kindergarten` — eram entradas `.off` SEM FICHEIRO, isto é, o menu oferecia-as
30
- // desabilitadas e nada existia por trás. Uma linha que só serve para dizer «ainda não» é uma linha que
31
- // a criança lê e não pode usar.
174
+ // ⚠️ NO `.off` ENTRY WITHOUT A FILE (issue #87 item 3): a row that only exists to say "not yet" is a row the child
175
+ // reads and cannot use.
32
176
  { g: 'font.group.hand', items: [
33
- { k: 'pinyon', fam: 'Pinyon Script', fb: 'cursive', d: 'font.desc.pinyon', papel: 'caligrafica', minPx: 24 },
34
- { k: 'ufmag', fam: 'UnifrakturMaguntia', fb: 'cursive', d: 'font.desc.ufmag', papel: 'caligrafica', minPx: 20 },
35
- // Fondamento entra pela emenda do ADR-0012 (#87 item 3) com o mínimo que o Dev fixou. Caligráfica, logo
36
- // fora do menu — ela é para os botões DENTRO das atividades escolares, não para a interface.
37
- { k: 'fondamento', fam: 'Fondamento', fb: 'cursive', d: 'font.desc.fondamento', papel: 'caligrafica', minPx: 20 },
38
- // ⚠️ `comicneue` NÃO é caligráfica, e está neste grupo só por aparência: é uma face de propósito geral,
39
- // frequentemente recomendada para dislexia. Marcá-la como caligráfica tirá-la-ia do menu — removendo uma
40
- // opção legitimamente acessível pelo formato do grupo em vez de pelo papel.
41
- { k: 'comicneue', fam: 'Comic Neue', fb: 'cursive', d: 'font.desc.comicneue' },
177
+ { k: 'pinyon', id: 'pinyon_script', fam: 'Pinyon Script', fb: 'cursive', d: 'font.desc.pinyon', role: 'caligrafica', minPx: 24 },
178
+ { k: 'ufmag', id: 'unifrakturmaguntia', fam: 'UnifrakturMaguntia', fb: 'cursive', d: 'font.desc.ufmag', role: 'caligrafica', minPx: 20 },
179
+ // Fondamento comes through the ADR-0012 amendment (#87 item 3) with the minimum the Dev set. Handwriting, so out of
180
+ // the menu — it is for the buttons INSIDE school activities, not for the interface.
181
+ { k: 'fondamento', id: 'fondamento', fam: 'Fondamento', fb: 'cursive', d: 'font.desc.fondamento', role: 'caligrafica', minPx: 20 },
42
182
  /*
43
- * A RONDE FRANCESA — o item 4 da #87, decidido no ADR-0108 §4. Ela NUNCA é empacotada: as três faces são
44
- * livres só para uso PESSOAL (ADR-0012), e distribuí-las seria distribuir o que não foi licenciado para
45
- * distribuição. O que muda é que a opção passa a FALAR.
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.
46
187
  *
47
- * ⚠️ E ISTO NÃO É A ENTRADA `.off` QUE ESTE CATÁLOGO JÁ REMOVEU. O cabeçalho acima tirou a `learningcurve`
48
- * e a `kindergarten` com a razão certa — «uma linha que só serve para dizer "ainda não" é uma linha que a
49
- * criança lê e não pode usar». A diferença é ACCIONABILIDADE, e é a razão que o ADR-0108 dá por extenso:
50
- * aquelas diziam «ainda não», que ninguém pode resolver; esta diz QUAIS TRÊS FONTES INSTALAR, que um
51
- * adulto resolve numa tarde. 📌 «Instale uma fonte ronde» seria o defeito de volta — um adulto não age
52
- * sobre uma categoria —, e é por isso que a mensagem nomeia as três.
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).
53
191
  *
54
- * ⚠️ `papel` AUSENTE, logo `geral`, e é deliberado apesar de a ronde ser caligráfica por natureza: as
55
- * caligráficas são filtradas do menu (`papelDaFonte === 'geral'`), e uma linha filtrada não pode dizer
56
- * nada a ninguém. Marcar o papel «certo» aqui apagaria a única coisa que este item existe para fazer.
192
+ * ⚠️ `role` ABSENT, so `geral`, deliberately, although the ronde is handwriting by nature: handwriting faces are
193
+ * filtered out of the menu (`fontRole === 'geral'`), and a filtered row can say nothing to anyone.
57
194
  */
58
195
  /*
59
- * AS OITO PLAYWRITE — o item 3 da #87, decidido no ADR-0108 §2 e entregue em 2026-09-09.
196
+ * THE PLAYWRITE FACES — #87 item 3, decided in ADR-0108 §2.
60
197
  *
61
- * ⚠️ `papel: caligrafica`, logo FORA DO MENU: elas são a mão que se aprende a escrever, para os botões
62
- * DENTRO das atividades escolares, e não uma opção de interface. É a divisão do item 1 desta issue.
198
+ * ⚠️ `role: caligrafica`, so OUT OF THE MENU: they are the hand one learns to write, for the buttons INSIDE school
199
+ * activities, not an interface option.
63
200
  *
64
- * 📌 `minPx: 20` é o mesmo piso que as outras três cursivas carregam. A lista de mínimos da #87 não
65
- * nomeia a Playwrite — o número é o das irmãs, e não uma medição própria; corrigir-se com uma linha.
201
+ * 📌 `minPx: 20` is the same floor the other cursive faces carry. #87's list of minimums does not name the
202
+ * Playwrite — the number is its siblings', not a measurement of its own; correctable in one line.
66
203
  */
67
- { k: 'pwbr', fam: 'Playwrite BR', fb: 'cursive', d: 'font.desc.pw.br', papel: 'caligrafica', minPx: 20 },
68
- { k: 'pwustrad', fam: 'Playwrite US Trad', fb: 'cursive', d: 'font.desc.pw.ustrad', papel: 'caligrafica', minPx: 20 },
69
- { k: 'pwusmod', fam: 'Playwrite US Modern', fb: 'cursive', d: 'font.desc.pw.usmod', papel: 'caligrafica', minPx: 20 },
70
- { k: 'pwca', fam: 'Playwrite CA', fb: 'cursive', d: 'font.desc.pw.ca', papel: 'caligrafica', minPx: 20 },
71
- { k: 'pwmx', fam: 'Playwrite MX', fb: 'cursive', d: 'font.desc.pw.mx', papel: 'caligrafica', minPx: 20 },
72
- { k: 'pwar', fam: 'Playwrite AR', fb: 'cursive', d: 'font.desc.pw.ar', papel: 'caligrafica', minPx: 20 },
73
- { k: 'pwcl', fam: 'Playwrite CL', fb: 'cursive', d: 'font.desc.pw.cl', papel: 'caligrafica', minPx: 20 },
74
- { k: 'pwco', fam: 'Playwrite CO', fb: 'cursive', d: 'font.desc.pw.co', papel: 'caligrafica', minPx: 20 },
75
- { k: 'ronde', fam: 'Ronde Script, OPTIFrench-Script, Merveille', fb: 'cursive',
76
- d: 'font.desc.ronde', off: 'font.off.ronde' }
204
+ // THE HANDWRITING GROUP'S GENERAL FACE (ADR-0176 §6, the Dev): in the menu, drawn at `minPx` by the face's scale.
205
+ { k: 'pwbr', id: 'playwrite_br', fam: 'Playwrite BR', fb: 'cursive', d: 'font.desc.pw.br', minPx: 20 },
206
+ { k: 'pwustrad', id: 'playwrite_us_trad', fam: 'Playwrite US Trad', fb: 'cursive', d: 'font.desc.pw.ustrad', role: 'caligrafica', minPx: 20 },
207
+ { k: 'pwusmod', id: 'playwrite_us_modern', fam: 'Playwrite US Modern', fb: 'cursive', d: 'font.desc.pw.usmod', role: 'caligrafica', minPx: 20 },
208
+ { k: 'pwca', id: 'playwrite_ca', fam: 'Playwrite CA', fb: 'cursive', d: 'font.desc.pw.ca', role: 'caligrafica', minPx: 20 },
209
+ { k: 'pwmx', id: 'playwrite_mx', fam: 'Playwrite MX', fb: 'cursive', d: 'font.desc.pw.mx', role: 'caligrafica', minPx: 20 },
210
+ { k: 'pwar', id: 'playwrite_ar', fam: 'Playwrite AR', fb: 'cursive', d: 'font.desc.pw.ar', role: 'caligrafica', minPx: 20 },
211
+ { k: 'pwcl', id: 'playwrite_cl', fam: 'Playwrite CL', fb: 'cursive', d: 'font.desc.pw.cl', role: 'caligrafica', minPx: 20 },
212
+ { k: 'pwco', id: 'playwrite_co', fam: 'Playwrite CO', fb: 'cursive', d: 'font.desc.pw.co', role: 'caligrafica', minPx: 20 },
213
+ /*
214
+ * SEVEN MORE, and they exist by a RULE and not by taste (ADR-0150, the Dev's decision).
215
+ *
216
+ * 🎯 `pwes`, `pwpt` and `pwgbj` are the FALLBACK BY LANGUAGE: a country without its own Playwrite gets the
217
+ * COLONISER's — Spanish → Spain, Portuguese → Portugal, English → England.
218
+ * 📌 `pwcu` and `pwpe` were asked for by name; `pwesdeco` and `pwgbs` come through the other half of the rule: where
219
+ * the country has more than one STROKE, both appear — like US Trad and US Modern.
220
+ *
221
+ * ⚠️ NO "GUIDES" FACES, by the Dev's decision: a `Playwrite XX Guides` is not a second stroke, it is the SAME stroke
222
+ * with handwriting ruling on top. The engine does not use them; a game that needs them brings its own font.
223
+ *
224
+ * 🔴 AND THEY ARE BUNDLED, which REVOKES ADR-0108's P3. The newer decision is the Dev's, literally: «estas fontes
225
+ * devem ser baixadas no primeiro dia para fazer parte do PWA». Written down because the old record still says the
226
+ * opposite.
227
+ */
228
+ { k: 'pwes', id: 'playwrite_es', fam: 'Playwrite ES', fb: 'cursive', role: 'caligrafica', minPx: 20 },
229
+ { k: 'pwesdeco', id: 'playwrite_es_deco', fam: 'Playwrite ES Deco', fb: 'cursive', role: 'caligrafica', minPx: 20 },
230
+ { k: 'pwpt', id: 'playwrite_pt', fam: 'Playwrite PT', fb: 'cursive', role: 'caligrafica', minPx: 20 },
231
+ { k: 'pwgbj', id: 'playwrite_gb_j', fam: 'Playwrite GB J', fb: 'cursive', role: 'caligrafica', minPx: 20 },
232
+ { k: 'pwgbs', id: 'playwrite_gb_s', fam: 'Playwrite GB S', fb: 'cursive', role: 'caligrafica', minPx: 20 },
233
+ { k: 'pwcu', id: 'playwrite_cu', fam: 'Playwrite CU', fb: 'cursive', role: 'caligrafica', minPx: 20 },
234
+ { k: 'pwpe', id: 'playwrite_pe', fam: 'Playwrite PE', fb: 'cursive', role: 'caligrafica', minPx: 20 },
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 }
77
237
  ] },
78
- // ⚠️ A FACE DO JOGO, e ela tem grupo próprio porque não é nem sans, nem serifada, nem manuscrita — é uma
79
- // face de PIXEL, e pô-la em qualquer um dos três diria a coisa errada sobre ela na lista.
238
+ // ⚠️ THE GAME'S FACE has its own group because it is neither sans, nor serif, nor handwriting — it is a PIXEL face,
239
+ // and putting it in any of the three would say the wrong thing about it in the list.
80
240
  //
81
- // Ela NÃO aparece no menu (`papel:'jogo'`), e o ficheiro veio do `SP-the-inclusionist-whackwhack`, onde já
82
- // estava verificado. Ver a nota do `@font-face` em `vendor/fonts.css`: o subconjunto errado desta família
83
- // carrega, declara-se e reporta-se como certo, e não desenha uma única letra latina.
241
+ // It does NOT appear in the menu (`role:'jogo'`). See the `@font-face` note in `vendor/fonts.css`: the wrong subset of
242
+ // this family loads, declares itself and reports as correct, and draws not a single Latin letter.
84
243
  { g: 'font.group.arcade', items: [
85
- { k: 'pressstart', fam: 'Press Start 2P', fb: 'monospace', d: 'font.desc.pressstart', papel: 'jogo' }
244
+ { k: 'pressstart', id: 'press_start_2p', fam: 'Press Start 2P', fb: 'monospace', d: 'font.desc.pressstart', role: 'jogo' }
86
245
  ] },
87
246
  ];
88
- /** O papel de uma face; ausente no catálogo quer dizer `geral`. */
89
- export function papelDaFonte(it) { return it.papel ?? 'geral'; }
90
247
  /**
91
- * AS FAMÍLIAS QUE UMA FACE ACEITA, do `fam` que pode ser uma PILHA.
248
+ * How much larger the text is drawn with this face: enough to reach its `minPx` from the document's base, never smaller.
249
+ * A face whose catalogue floor is above the base (a display sans, the handwriting) is offered at that size, not under it
250
+ * (ADR-0176 §4, the catalogue's rule R2). The cycle's hand of the country is the same rule (`HANDWRITING_SCALE`).
251
+ */
252
+ export function faceScale(it) { return Math.max(1, (it.minPx ?? BASE_EM_PX) / BASE_EM_PX); }
253
+ /** A face's role; absent in the catalogue means `geral`. */
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
+ }
259
+ /**
260
+ * THE FAMILIES A FACE ACCEPTS, from a `fam` that may be a STACK.
261
+ *
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
264
+ * returns a list of one.
265
+ */
266
+ export function faceFamilies(it) {
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'`.
92
271
  *
93
- * 📌 A Ronde declara três (`'Ronde Script, OPTIFrench-Script, Merveille'`) porque qualquer uma delas serve —
94
- * são três desenhos da mesma letra de mão, e um adulto instala a que encontrar. As outras faces declaram uma
95
- * só, e para elas isto devolve uma lista de um.
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'`.
96
275
  */
97
- export function familiasDaFace(it) {
98
- return it.fam.split(',').map((f) => f.trim().replace(/^['"]|['"]$/g, '')).filter(Boolean);
276
+ export function familyStack(fam) {
277
+ return familiesIn(fam).map((f) => `'${f}'`).join(',');
99
278
  }
100
279
  /**
101
- * ESTA FACE PODE SER USADA AGORA? — o `off` deixa de ser uma sentença e passa a ser uma CONDIÇÃO.
102
- *
103
- * O ADR-0012 decidiu que a opção da ronde «fica DESABILITADA enquanto nenhuma fonte estiver presente», e o
104
- * ADR-0108 §4 acrescentou o que ela diz. A palavra «enquanto» é o que esta função constrói: uma face `off`
105
- * volta a ficar disponível no instante em que o adulto instala uma das que a mensagem nomeia.
106
- *
107
- * ⚠️ O DETECTOR É INJECTADO, e nunca `document.fonts` lido daqui: este módulo é o catálogo, corre em node nos
108
- * gates, e ler um global do navegador aqui é o ACHADO 15 outra vez — o `srAlert` que rebentou o boot contra um
109
- * documento injectado.
110
- * 📌 E o PADRÃO É «não instalada», que é seguro por uma razão que não vale para todos os padrões deste
111
- * repositório: sem detector a opção fica desabilitada COM a mensagem, e a mensagem diz ao adulto exactamente
112
- * o que fazer. O silêncio não decide nada contra a criança — ele mantém o estado que já existia e que é
113
- * accionável. É o oposto do `seguraTeclas`, onde os dois lados do padrão erravam.
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.
114
283
  */
115
- export function faceDisponivel(it, instalada) {
116
- if (!it.off)
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';
289
+ /**
290
+ * CAN THIS FACE BE USED NOW? — `off` stops being a sentence and becomes a CONDITION.
291
+ *
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).
296
+ *
297
+ * ⚠️ THE DETECTOR IS INJECTED, and `document.fonts` is never read here: this module is the catalogue, it runs in node in
298
+ * the gates, and reading a browser global here would be a boot waiting to crash against an injected document.
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.
301
+ */
302
+ export function faceAvailable(it, installed) {
303
+ if (!it.off && ENGINE_FACE_KEYS.includes(it.k))
117
304
  return true;
118
- return !!instalada && familiasDaFace(it).some((f) => instalada(f));
305
+ return !!installed && faceFamilies(it).some((f) => installed(f));
119
306
  }
120
- /** As faces que o MENU pode oferecer: só as gerais (emenda do ADR-0012). */
121
- export const OFERECIVEIS = FONT_GROUPS.flatMap((g) => g.items).filter((it) => papelDaFonte(it) === 'geral');
122
- export const FONT_BY_KEY = {};
123
- FONT_GROUPS.forEach((g) => g.items.forEach((it) => { FONT_BY_KEY[it.k] = it; }));
124
- export const FONT_KEY = 'incl_font_k';
125
- export const FONT_KEY_LEGACY = 'incl_fonte'; // pre-Fase-2: 'alfabetizacao' | 'dislexia'
307
+ /** The faces the MENU may offer: only the general ones (ADR-0012 amendment). */
308
+ export const OFERECIVEIS = FONT_GROUPS.flatMap((g) => g.items).filter((it) => fontRole(it) === 'geral');
309
+ /**
310
+ * The catalogue by key. BUILT IN ONE EXPRESSION and never filled after it (ADR-0232 D4): the table was an empty object the
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.
312
+ */
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]);
316
+ const FONT_KEY = 'incl_font_k';
317
+ const FONT_KEY_LEGACY = 'incl_fonte'; // pre-Fase-2: 'alfabetizacao' | 'dislexia'
126
318
  /**
127
- * A fonte de fábrica, com nome. Atkinson Hyperlegible foi desenhada pelo Braille Institute justamente para
128
- * quem tem baixa visão — distingue as formas que mais se confundem (I/l/1, O/0). É por isso que ela é o padrão
129
- * e não uma preferência estética, e é por isso que o "restaurar padrões" da tipografia (ADR-0028) volta para
130
- * cá: o caminho de volta de um menu de acessibilidade tem que terminar na escolha mais legível, não numa
131
- * qualquer. Usada em DOIS lugares — o fim da cadeia de boot logo abaixo e o reset do painel —, e uma constante
132
- * porque duas cópias de um padrão são duas chances de o reset devolver algo que o jogo nunca usou.
319
+ * The factory font, named. Atkinson Hyperlegible was designed by the Braille Institute precisely for low vision — it
320
+ * tells apart the shapes most often confused (I/l/1, O/0). That is why it is the default and not an aesthetic preference,
321
+ * and why typography's reset (ADR-0028) returns here: the way back from an accessibility menu has to end at the most
322
+ * legible choice, not at any one. Used in TWO places — the end of the boot chain just below and the panel's reset —, and
323
+ * a constant because two copies of a default are two chances for the reset to return something the game never used.
133
324
  */
134
325
  export const DEFAULT_FONT_KEY = 'atkinson';
135
326
  /**
136
327
  * Boot choice: validated persisted key (ignores .off fonts) -> legacy-key migration -> DEFAULT_FONT_KEY.
137
328
  *
138
- * ⚠️ E UMA CALIGRÁFICA GUARDADA VOLTA AO PADRÃO (issue #87, 2026-09-07). Antes da emenda do ADR-0012 o menu
139
- * oferecia as sete caligráficas, então há crianças com `pinyon` ou `ufmag` guardados — e o valor salvo
140
- * continua a ser escolha delas, que não é nossa para desfazer sem motivo. O motivo existe: a emenda diz que
141
- * uma caligráfica **não pode ser a face da interface**, e o menu deixou de a oferecer. Deixá-la valer daria
142
- * uma interface inteira em letra cursiva a quem já não tem como sair dela pelo menu — uma armadilha, e das
143
- * silenciosas.
329
+ * ⚠️ AND A STORED HANDWRITING FACE GOES BACK TO THE DEFAULT (issue #87). A child may have one stored from when the menu
330
+ * offered them, and a stored value is their choice, not ours to undo without a reason. The reason exists: the ADR-0012
331
+ * amendment says a handwriting face **cannot be the interface face**, and the menu no longer offers it. Letting it stand
332
+ * would give a whole interface in cursive to someone with no way out of it through the menu — a trap, and a silent one.
144
333
  *
145
- * ⚠️ E UMA CHAVE APAGADA TAMBÉM VOLTA, pelo mesmo caminho: as quatro faces que saíram do roster já não estão
146
- * no `FONT_BY_KEY`, então quem tinha `greatvibes` guardado cai no padrão em vez de ficar sem face nenhuma.
334
+ * ⚠️ AND A DELETED KEY GOES BACK TOO, by the same path: a face no longer in `FONT_BY_KEY` falls back to the default
335
+ * instead of leaving the child with no face at all.
147
336
  */
148
337
  export function resolveFontKey(s) {
149
338
  const k = s.get(FONT_KEY, null);
150
- if (k && FONT_BY_KEY[k] && !FONT_BY_KEY[k].off && papelDaFonte(FONT_BY_KEY[k]) === 'geral')
339
+ if (k && FONT_BY_KEY[k] && !FONT_BY_KEY[k].off && fontRole(FONT_BY_KEY[k]) === 'geral')
151
340
  return k;
152
341
  const leg = s.get(FONT_KEY_LEGACY, null);
153
342
  if (leg === 'alfabetizacao')
@@ -157,6 +346,7 @@ export function resolveFontKey(s) {
157
346
  return DEFAULT_FONT_KEY;
158
347
  }
159
348
  export function persistFontKey(s, k) { s.set(FONT_KEY, k); }
160
- // Conveniencia sobre o storage real — mesma logica, sem duplicá-la.
161
- export function loadFontKey() { return resolveFontKey(store); }
162
- export function saveFontKey(k) { persistFontKey(store, k); }
349
+ // ⚠️ `loadFontKey` AND `saveFontKey` ARE GONE (R3 of the plan, 2026-09-23): they were conveniences over
350
+ // `resolveFontKey` and `persistFontKey` — the same two lines with `store` already filled in — and NOBODY took
351
+ // them, not even a test. A shortcut nobody takes is not a convenience: it is published surface that asks whoever
352
+ // reads it to migrate.
@@ -0,0 +1,14 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ export interface FooterScrollHost {
3
+ readonly doc: Pick<Document, 'createRange' | 'createTreeWalker' | 'createElement'>;
4
+ readonly getComputedStyle: (el: Element) => CSSStyleDeclaration;
5
+ readonly MutationObserver: typeof MutationObserver;
6
+ readonly setTimeout: (fn: () => void, ms: number) => number;
7
+ readonly clearTimeout: (id: number) => void;
8
+ /** The child's caption rate (ADR-0183 §4), read at every start. */
9
+ readonly ppm: () => number;
10
+ /** Reduced motion — the system's or the child's — read at every start. */
11
+ readonly reduced: () => boolean;
12
+ }
13
+ /** Starts watching `root`. Returns the way to stop: every animation cancelled and timer cleared, the observer disconnected. */
14
+ export declare function watchFooterScroll(root: HTMLElement, host: FooterScrollHost): () => void;