@the-inclusionist/engine 8.0.0 → 10.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 (756) hide show
  1. package/README.md +89 -78
  2. package/app/css/style.css +584 -374
  3. package/app/public/vendor/fonts/bitter-var-ext.woff2 +0 -0
  4. package/app/public/vendor/fonts/bitter-var.woff2 +0 -0
  5. package/app/public/vendor/fonts/bodonimoda-var-ext.woff2 +0 -0
  6. package/app/public/vendor/fonts/bodonimoda-var.woff2 +0 -0
  7. package/app/public/vendor/fonts/cat-abril_fatface-400-ext.woff2 +0 -0
  8. package/app/public/vendor/fonts/cat-abril_fatface-400.woff2 +0 -0
  9. package/app/public/vendor/fonts/cat-adlam_display-400-ext.woff2 +0 -0
  10. package/app/public/vendor/fonts/cat-adlam_display-400.woff2 +0 -0
  11. package/app/public/vendor/fonts/cat-agu_display-400-ext.woff2 +0 -0
  12. package/app/public/vendor/fonts/cat-agu_display-400.woff2 +0 -0
  13. package/app/public/vendor/fonts/cat-alfa_slab_one-400-ext.woff2 +0 -0
  14. package/app/public/vendor/fonts/cat-alfa_slab_one-400.woff2 +0 -0
  15. package/app/public/vendor/fonts/cat-are_you_serious-400-ext.woff2 +0 -0
  16. package/app/public/vendor/fonts/cat-are_you_serious-400.woff2 +0 -0
  17. package/app/public/vendor/fonts/cat-asimovian-400-ext.woff2 +0 -0
  18. package/app/public/vendor/fonts/cat-asimovian-400.woff2 +0 -0
  19. package/app/public/vendor/fonts/cat-atkinson_hyperlegible_next-400-ext.woff2 +0 -0
  20. package/app/public/vendor/fonts/cat-atkinson_hyperlegible_next-400.woff2 +0 -0
  21. package/app/public/vendor/fonts/cat-bakbak_one-400-ext.woff2 +0 -0
  22. package/app/public/vendor/fonts/cat-bakbak_one-400.woff2 +0 -0
  23. package/app/public/vendor/fonts/cat-bangers-400-ext.woff2 +0 -0
  24. package/app/public/vendor/fonts/cat-bangers-400.woff2 +0 -0
  25. package/app/public/vendor/fonts/cat-barriecito-400-ext.woff2 +0 -0
  26. package/app/public/vendor/fonts/cat-barriecito-400.woff2 +0 -0
  27. package/app/public/vendor/fonts/cat-berkshire_swash-400-ext.woff2 +0 -0
  28. package/app/public/vendor/fonts/cat-berkshire_swash-400.woff2 +0 -0
  29. package/app/public/vendor/fonts/cat-bevan-400-ext.woff2 +0 -0
  30. package/app/public/vendor/fonts/cat-bevan-400.woff2 +0 -0
  31. package/app/public/vendor/fonts/cat-bitcount-400-ext.woff2 +0 -0
  32. package/app/public/vendor/fonts/cat-bitcount-400.woff2 +0 -0
  33. package/app/public/vendor/fonts/cat-bowlby_one_sc-400-ext.woff2 +0 -0
  34. package/app/public/vendor/fonts/cat-bowlby_one_sc-400.woff2 +0 -0
  35. package/app/public/vendor/fonts/cat-bungee_shade-400-ext.woff2 +0 -0
  36. package/app/public/vendor/fonts/cat-bungee_shade-400.woff2 +0 -0
  37. package/app/public/vendor/fonts/cat-charm-400-ext.woff2 +0 -0
  38. package/app/public/vendor/fonts/cat-charm-400.woff2 +0 -0
  39. package/app/public/vendor/fonts/cat-charm-700-ext.woff2 +0 -0
  40. package/app/public/vendor/fonts/cat-charm-700.woff2 +0 -0
  41. package/app/public/vendor/fonts/cat-cherry_bomb_one-400-ext.woff2 +0 -0
  42. package/app/public/vendor/fonts/cat-cherry_bomb_one-400.woff2 +0 -0
  43. package/app/public/vendor/fonts/cat-chokokutai-400-ext.woff2 +0 -0
  44. package/app/public/vendor/fonts/cat-chokokutai-400.woff2 +0 -0
  45. package/app/public/vendor/fonts/cat-chonburi-400-ext.woff2 +0 -0
  46. package/app/public/vendor/fonts/cat-chonburi-400.woff2 +0 -0
  47. package/app/public/vendor/fonts/cat-climate_crisis-400-ext.woff2 +0 -0
  48. package/app/public/vendor/fonts/cat-climate_crisis-400.woff2 +0 -0
  49. package/app/public/vendor/fonts/cat-cookie-400.woff2 +0 -0
  50. package/app/public/vendor/fonts/cat-dela_gothic_one-400-ext.woff2 +0 -0
  51. package/app/public/vendor/fonts/cat-dela_gothic_one-400.woff2 +0 -0
  52. package/app/public/vendor/fonts/cat-fascinate_inline-400-ext.woff2 +0 -0
  53. package/app/public/vendor/fonts/cat-fascinate_inline-400.woff2 +0 -0
  54. package/app/public/vendor/fonts/cat-ga_maamli-400-ext.woff2 +0 -0
  55. package/app/public/vendor/fonts/cat-ga_maamli-400.woff2 +0 -0
  56. package/app/public/vendor/fonts/cat-itim-400-ext.woff2 +0 -0
  57. package/app/public/vendor/fonts/cat-itim-400.woff2 +0 -0
  58. package/app/public/vendor/fonts/cat-jacquard_24-400-ext.woff2 +0 -0
  59. package/app/public/vendor/fonts/cat-jacquard_24-400.woff2 +0 -0
  60. package/app/public/vendor/fonts/cat-kablammo-400-ext.woff2 +0 -0
  61. package/app/public/vendor/fonts/cat-kablammo-400.woff2 +0 -0
  62. package/app/public/vendor/fonts/cat-kalam-400-ext.woff2 +0 -0
  63. package/app/public/vendor/fonts/cat-kalam-400.woff2 +0 -0
  64. package/app/public/vendor/fonts/cat-kalam-700-ext.woff2 +0 -0
  65. package/app/public/vendor/fonts/cat-kalam-700.woff2 +0 -0
  66. package/app/public/vendor/fonts/cat-katibeh-400-ext.woff2 +0 -0
  67. package/app/public/vendor/fonts/cat-katibeh-400.woff2 +0 -0
  68. package/app/public/vendor/fonts/cat-lemonada-400-ext.woff2 +0 -0
  69. package/app/public/vendor/fonts/cat-lemonada-400.woff2 +0 -0
  70. package/app/public/vendor/fonts/cat-lilita_one-400-ext.woff2 +0 -0
  71. package/app/public/vendor/fonts/cat-lilita_one-400.woff2 +0 -0
  72. package/app/public/vendor/fonts/cat-lobster-400-ext.woff2 +0 -0
  73. package/app/public/vendor/fonts/cat-lobster-400.woff2 +0 -0
  74. package/app/public/vendor/fonts/cat-luckiest_guy-400-ext.woff2 +0 -0
  75. package/app/public/vendor/fonts/cat-luckiest_guy-400.woff2 +0 -0
  76. package/app/public/vendor/fonts/cat-marhey-400-ext.woff2 +0 -0
  77. package/app/public/vendor/fonts/cat-marhey-400.woff2 +0 -0
  78. package/app/public/vendor/fonts/cat-modak-400-ext.woff2 +0 -0
  79. package/app/public/vendor/fonts/cat-modak-400.woff2 +0 -0
  80. package/app/public/vendor/fonts/cat-momo_signature-400-ext.woff2 +0 -0
  81. package/app/public/vendor/fonts/cat-momo_signature-400.woff2 +0 -0
  82. package/app/public/vendor/fonts/cat-monofett-400-ext.woff2 +0 -0
  83. package/app/public/vendor/fonts/cat-monofett-400.woff2 +0 -0
  84. package/app/public/vendor/fonts/cat-monomakh-400-ext.woff2 +0 -0
  85. package/app/public/vendor/fonts/cat-monomakh-400.woff2 +0 -0
  86. package/app/public/vendor/fonts/cat-monoton-400-ext.woff2 +0 -0
  87. package/app/public/vendor/fonts/cat-monoton-400.woff2 +0 -0
  88. package/app/public/vendor/fonts/cat-nerko_one-400-ext.woff2 +0 -0
  89. package/app/public/vendor/fonts/cat-nerko_one-400.woff2 +0 -0
  90. package/app/public/vendor/fonts/cat-noto_sans_adlam-400-ext.woff2 +0 -0
  91. package/app/public/vendor/fonts/cat-noto_sans_adlam-400.woff2 +0 -0
  92. package/app/public/vendor/fonts/cat-noto_sans_arabic-400-ext.woff2 +0 -0
  93. package/app/public/vendor/fonts/cat-noto_sans_arabic-400.woff2 +0 -0
  94. package/app/public/vendor/fonts/cat-noto_sans_devanagari-400-ext.woff2 +0 -0
  95. package/app/public/vendor/fonts/cat-noto_sans_devanagari-400.woff2 +0 -0
  96. package/app/public/vendor/fonts/cat-noto_sans_hebrew-400-ext.woff2 +0 -0
  97. package/app/public/vendor/fonts/cat-noto_sans_hebrew-400.woff2 +0 -0
  98. package/app/public/vendor/fonts/cat-noto_sans_jp-400-ext.woff2 +0 -0
  99. package/app/public/vendor/fonts/cat-noto_sans_jp-400.woff2 +0 -0
  100. package/app/public/vendor/fonts/cat-noto_sans_khmer-400-ext.woff2 +0 -0
  101. package/app/public/vendor/fonts/cat-noto_sans_khmer-400.woff2 +0 -0
  102. package/app/public/vendor/fonts/cat-noto_sans_kr-400-ext.woff2 +0 -0
  103. package/app/public/vendor/fonts/cat-noto_sans_kr-400.woff2 +0 -0
  104. package/app/public/vendor/fonts/cat-noto_sans_sc-400-ext.woff2 +0 -0
  105. package/app/public/vendor/fonts/cat-noto_sans_sc-400.woff2 +0 -0
  106. package/app/public/vendor/fonts/cat-noto_sans_thai-400-ext.woff2 +0 -0
  107. package/app/public/vendor/fonts/cat-noto_sans_thai-400.woff2 +0 -0
  108. package/app/public/vendor/fonts/cat-ole-400-ext.woff2 +0 -0
  109. package/app/public/vendor/fonts/cat-ole-400.woff2 +0 -0
  110. package/app/public/vendor/fonts/cat-pacifico-400-ext.woff2 +0 -0
  111. package/app/public/vendor/fonts/cat-pacifico-400.woff2 +0 -0
  112. package/app/public/vendor/fonts/cat-patrick_hand-400-ext.woff2 +0 -0
  113. package/app/public/vendor/fonts/cat-patrick_hand-400.woff2 +0 -0
  114. package/app/public/vendor/fonts/cat-petit_formal_script-400-ext.woff2 +0 -0
  115. package/app/public/vendor/fonts/cat-petit_formal_script-400.woff2 +0 -0
  116. package/app/public/vendor/fonts/cat-pixelify_sans-400-ext.woff2 +0 -0
  117. package/app/public/vendor/fonts/cat-pixelify_sans-400.woff2 +0 -0
  118. package/app/public/vendor/fonts/cat-plaster-400-ext.woff2 +0 -0
  119. package/app/public/vendor/fonts/cat-plaster-400.woff2 +0 -0
  120. package/app/public/vendor/fonts/cat-playwrite_ar_guides-400-0.woff2 +0 -0
  121. package/app/public/vendor/fonts/cat-playwrite_at-400-0.woff2 +0 -0
  122. package/app/public/vendor/fonts/cat-playwrite_at_guides-400-0.woff2 +0 -0
  123. package/app/public/vendor/fonts/cat-playwrite_au_nsw-400-0.woff2 +0 -0
  124. package/app/public/vendor/fonts/cat-playwrite_au_nsw_guides-400-0.woff2 +0 -0
  125. package/app/public/vendor/fonts/cat-playwrite_au_qld-400-0.woff2 +0 -0
  126. package/app/public/vendor/fonts/cat-playwrite_au_qld_guides-400-0.woff2 +0 -0
  127. package/app/public/vendor/fonts/cat-playwrite_au_sa-400-0.woff2 +0 -0
  128. package/app/public/vendor/fonts/cat-playwrite_au_sa_guides-400-0.woff2 +0 -0
  129. package/app/public/vendor/fonts/cat-playwrite_au_tas-400-0.woff2 +0 -0
  130. package/app/public/vendor/fonts/cat-playwrite_au_tas_guides-400-0.woff2 +0 -0
  131. package/app/public/vendor/fonts/cat-playwrite_au_vic-400-0.woff2 +0 -0
  132. package/app/public/vendor/fonts/cat-playwrite_au_vic_guides-400-0.woff2 +0 -0
  133. package/app/public/vendor/fonts/cat-playwrite_be_vlg-400-0.woff2 +0 -0
  134. package/app/public/vendor/fonts/cat-playwrite_be_vlg_guides-400-0.woff2 +0 -0
  135. package/app/public/vendor/fonts/cat-playwrite_be_wal-400-0.woff2 +0 -0
  136. package/app/public/vendor/fonts/cat-playwrite_be_wal_guides-400-0.woff2 +0 -0
  137. package/app/public/vendor/fonts/cat-playwrite_br_guides-400-0.woff2 +0 -0
  138. package/app/public/vendor/fonts/cat-playwrite_ca_guides-400-0.woff2 +0 -0
  139. package/app/public/vendor/fonts/cat-playwrite_cl_guides-400-0.woff2 +0 -0
  140. package/app/public/vendor/fonts/cat-playwrite_co_guides-400-0.woff2 +0 -0
  141. package/app/public/vendor/fonts/cat-playwrite_cu_guides-400-0.woff2 +0 -0
  142. package/app/public/vendor/fonts/cat-playwrite_cz-400-0.woff2 +0 -0
  143. package/app/public/vendor/fonts/cat-playwrite_cz_guides-400-0.woff2 +0 -0
  144. package/app/public/vendor/fonts/cat-playwrite_de_grund-400-0.woff2 +0 -0
  145. package/app/public/vendor/fonts/cat-playwrite_de_grund_guides-400-0.woff2 +0 -0
  146. package/app/public/vendor/fonts/cat-playwrite_de_la-400-0.woff2 +0 -0
  147. package/app/public/vendor/fonts/cat-playwrite_de_la_guides-400-0.woff2 +0 -0
  148. package/app/public/vendor/fonts/cat-playwrite_de_sas-400-0.woff2 +0 -0
  149. package/app/public/vendor/fonts/cat-playwrite_de_sas_guides-400-0.woff2 +0 -0
  150. package/app/public/vendor/fonts/cat-playwrite_de_va-400-0.woff2 +0 -0
  151. package/app/public/vendor/fonts/cat-playwrite_de_va_guides-400-0.woff2 +0 -0
  152. package/app/public/vendor/fonts/cat-playwrite_dk_loopet-400-0.woff2 +0 -0
  153. package/app/public/vendor/fonts/cat-playwrite_dk_loopet_guides-400-0.woff2 +0 -0
  154. package/app/public/vendor/fonts/cat-playwrite_dk_uloopet-400-0.woff2 +0 -0
  155. package/app/public/vendor/fonts/cat-playwrite_dk_uloopet_guides-400-0.woff2 +0 -0
  156. package/app/public/vendor/fonts/cat-playwrite_es_deco_guides-400-0.woff2 +0 -0
  157. package/app/public/vendor/fonts/cat-playwrite_es_guides-400-0.woff2 +0 -0
  158. package/app/public/vendor/fonts/cat-playwrite_fr_moderne-400-0.woff2 +0 -0
  159. package/app/public/vendor/fonts/cat-playwrite_fr_moderne_guides-400-0.woff2 +0 -0
  160. package/app/public/vendor/fonts/cat-playwrite_fr_trad-400-0.woff2 +0 -0
  161. package/app/public/vendor/fonts/cat-playwrite_fr_trad_guides-400-0.woff2 +0 -0
  162. package/app/public/vendor/fonts/cat-playwrite_gb_j_guides-400-0.woff2 +0 -0
  163. package/app/public/vendor/fonts/cat-playwrite_gb_s_guides-400-0.woff2 +0 -0
  164. package/app/public/vendor/fonts/cat-playwrite_hr-400-0.woff2 +0 -0
  165. package/app/public/vendor/fonts/cat-playwrite_hr_guides-400-0.woff2 +0 -0
  166. package/app/public/vendor/fonts/cat-playwrite_hr_lijeva-400-0.woff2 +0 -0
  167. package/app/public/vendor/fonts/cat-playwrite_hr_lijeva_guides-400-0.woff2 +0 -0
  168. package/app/public/vendor/fonts/cat-playwrite_hu-400-0.woff2 +0 -0
  169. package/app/public/vendor/fonts/cat-playwrite_hu_guides-400-0.woff2 +0 -0
  170. package/app/public/vendor/fonts/cat-playwrite_id-400-0.woff2 +0 -0
  171. package/app/public/vendor/fonts/cat-playwrite_id_guides-400-0.woff2 +0 -0
  172. package/app/public/vendor/fonts/cat-playwrite_ie-400-0.woff2 +0 -0
  173. package/app/public/vendor/fonts/cat-playwrite_ie_guides-400-0.woff2 +0 -0
  174. package/app/public/vendor/fonts/cat-playwrite_in-400-0.woff2 +0 -0
  175. package/app/public/vendor/fonts/cat-playwrite_in_guides-400-0.woff2 +0 -0
  176. package/app/public/vendor/fonts/cat-playwrite_is-400-0.woff2 +0 -0
  177. package/app/public/vendor/fonts/cat-playwrite_is_guides-400-0.woff2 +0 -0
  178. package/app/public/vendor/fonts/cat-playwrite_it_moderna-400-0.woff2 +0 -0
  179. package/app/public/vendor/fonts/cat-playwrite_it_moderna_guides-400-0.woff2 +0 -0
  180. package/app/public/vendor/fonts/cat-playwrite_it_trad-400-0.woff2 +0 -0
  181. package/app/public/vendor/fonts/cat-playwrite_it_trad_guides-400-0.woff2 +0 -0
  182. package/app/public/vendor/fonts/cat-playwrite_mx_guides-400-0.woff2 +0 -0
  183. package/app/public/vendor/fonts/cat-playwrite_ng_modern-400-0.woff2 +0 -0
  184. package/app/public/vendor/fonts/cat-playwrite_ng_modern_guides-400-0.woff2 +0 -0
  185. package/app/public/vendor/fonts/cat-playwrite_nl-400-0.woff2 +0 -0
  186. package/app/public/vendor/fonts/cat-playwrite_nl_guides-400-0.woff2 +0 -0
  187. package/app/public/vendor/fonts/cat-playwrite_no-400-0.woff2 +0 -0
  188. package/app/public/vendor/fonts/cat-playwrite_no_guides-400-0.woff2 +0 -0
  189. package/app/public/vendor/fonts/cat-playwrite_nz-400-0.woff2 +0 -0
  190. package/app/public/vendor/fonts/cat-playwrite_nz_basic-400-0.woff2 +0 -0
  191. package/app/public/vendor/fonts/cat-playwrite_nz_basic_guides-400-0.woff2 +0 -0
  192. package/app/public/vendor/fonts/cat-playwrite_nz_guides-400-0.woff2 +0 -0
  193. package/app/public/vendor/fonts/cat-playwrite_pe_guides-400-0.woff2 +0 -0
  194. package/app/public/vendor/fonts/cat-playwrite_pl-400-0.woff2 +0 -0
  195. package/app/public/vendor/fonts/cat-playwrite_pl_guides-400-0.woff2 +0 -0
  196. package/app/public/vendor/fonts/cat-playwrite_pt_guides-400-0.woff2 +0 -0
  197. package/app/public/vendor/fonts/cat-playwrite_ro-400-0.woff2 +0 -0
  198. package/app/public/vendor/fonts/cat-playwrite_ro_guides-400-0.woff2 +0 -0
  199. package/app/public/vendor/fonts/cat-playwrite_sk-400-0.woff2 +0 -0
  200. package/app/public/vendor/fonts/cat-playwrite_sk_guides-400-0.woff2 +0 -0
  201. package/app/public/vendor/fonts/cat-playwrite_tz-400-0.woff2 +0 -0
  202. package/app/public/vendor/fonts/cat-playwrite_tz_guides-400-0.woff2 +0 -0
  203. package/app/public/vendor/fonts/cat-playwrite_us_modern_guides-400-0.woff2 +0 -0
  204. package/app/public/vendor/fonts/cat-playwrite_us_trad_guides-400-0.woff2 +0 -0
  205. package/app/public/vendor/fonts/cat-playwrite_vn-400-0.woff2 +0 -0
  206. package/app/public/vendor/fonts/cat-playwrite_vn_guides-400-0.woff2 +0 -0
  207. package/app/public/vendor/fonts/cat-playwrite_za-400-0.woff2 +0 -0
  208. package/app/public/vendor/fonts/cat-playwrite_za_guides-400-0.woff2 +0 -0
  209. package/app/public/vendor/fonts/cat-potta_one-400-ext.woff2 +0 -0
  210. package/app/public/vendor/fonts/cat-potta_one-400.woff2 +0 -0
  211. package/app/public/vendor/fonts/cat-ranchers-400-ext.woff2 +0 -0
  212. package/app/public/vendor/fonts/cat-ranchers-400.woff2 +0 -0
  213. package/app/public/vendor/fonts/cat-ranga-400-ext.woff2 +0 -0
  214. package/app/public/vendor/fonts/cat-ranga-400.woff2 +0 -0
  215. package/app/public/vendor/fonts/cat-ranga-700-ext.woff2 +0 -0
  216. package/app/public/vendor/fonts/cat-ranga-700.woff2 +0 -0
  217. package/app/public/vendor/fonts/cat-risque-400-ext.woff2 +0 -0
  218. package/app/public/vendor/fonts/cat-risque-400.woff2 +0 -0
  219. package/app/public/vendor/fonts/cat-rowdies-400-ext.woff2 +0 -0
  220. package/app/public/vendor/fonts/cat-rowdies-400.woff2 +0 -0
  221. package/app/public/vendor/fonts/cat-rowdies-700-ext.woff2 +0 -0
  222. package/app/public/vendor/fonts/cat-rowdies-700.woff2 +0 -0
  223. package/app/public/vendor/fonts/cat-ruge_boogie-400-ext.woff2 +0 -0
  224. package/app/public/vendor/fonts/cat-ruge_boogie-400.woff2 +0 -0
  225. package/app/public/vendor/fonts/cat-ruslan_display-400-ext.woff2 +0 -0
  226. package/app/public/vendor/fonts/cat-ruslan_display-400.woff2 +0 -0
  227. package/app/public/vendor/fonts/cat-sacramento-400-ext.woff2 +0 -0
  228. package/app/public/vendor/fonts/cat-sacramento-400.woff2 +0 -0
  229. package/app/public/vendor/fonts/cat-sancreek-400-ext.woff2 +0 -0
  230. package/app/public/vendor/fonts/cat-sancreek-400.woff2 +0 -0
  231. package/app/public/vendor/fonts/cat-sankofa_display-400-ext.woff2 +0 -0
  232. package/app/public/vendor/fonts/cat-sankofa_display-400.woff2 +0 -0
  233. package/app/public/vendor/fonts/cat-sedgwick_ave_display-400-ext.woff2 +0 -0
  234. package/app/public/vendor/fonts/cat-sedgwick_ave_display-400.woff2 +0 -0
  235. package/app/public/vendor/fonts/cat-shantell_sans-400-ext.woff2 +0 -0
  236. package/app/public/vendor/fonts/cat-shantell_sans-400.woff2 +0 -0
  237. package/app/public/vendor/fonts/cat-smokum-400-ext.woff2 +0 -0
  238. package/app/public/vendor/fonts/cat-smokum-400.woff2 +0 -0
  239. package/app/public/vendor/fonts/cat-solitreo-400-ext.woff2 +0 -0
  240. package/app/public/vendor/fonts/cat-solitreo-400.woff2 +0 -0
  241. package/app/public/vendor/fonts/cat-spicy_rice-400-ext.woff2 +0 -0
  242. package/app/public/vendor/fonts/cat-spicy_rice-400.woff2 +0 -0
  243. package/app/public/vendor/fonts/cat-sriracha-400-ext.woff2 +0 -0
  244. package/app/public/vendor/fonts/cat-sriracha-400.woff2 +0 -0
  245. package/app/public/vendor/fonts/cat-srisakdi-400-ext.woff2 +0 -0
  246. package/app/public/vendor/fonts/cat-srisakdi-400.woff2 +0 -0
  247. package/app/public/vendor/fonts/cat-srisakdi-700-ext.woff2 +0 -0
  248. package/app/public/vendor/fonts/cat-srisakdi-700.woff2 +0 -0
  249. package/app/public/vendor/fonts/cat-stix_two_math-400-0.woff2 +0 -0
  250. package/app/public/vendor/fonts/cat-tillana-400-ext.woff2 +0 -0
  251. package/app/public/vendor/fonts/cat-tillana-400.woff2 +0 -0
  252. package/app/public/vendor/fonts/cat-tillana-700-ext.woff2 +0 -0
  253. package/app/public/vendor/fonts/cat-tillana-700.woff2 +0 -0
  254. package/app/public/vendor/fonts/cat-titan_one-400-ext.woff2 +0 -0
  255. package/app/public/vendor/fonts/cat-titan_one-400.woff2 +0 -0
  256. package/app/public/vendor/fonts/cat-yatra_one-400-ext.woff2 +0 -0
  257. package/app/public/vendor/fonts/cat-yatra_one-400.woff2 +0 -0
  258. package/app/public/vendor/fonts/cat-yomogi-400-ext.woff2 +0 -0
  259. package/app/public/vendor/fonts/cat-yomogi-400.woff2 +0 -0
  260. package/app/public/vendor/fonts/dmserifdisplay-400-ext.woff2 +0 -0
  261. package/app/public/vendor/fonts/dmserifdisplay-400.woff2 +0 -0
  262. package/app/public/vendor/fonts/domine-var-ext.woff2 +0 -0
  263. package/app/public/vendor/fonts/domine-var.woff2 +0 -0
  264. package/app/public/vendor/fonts/fraunces-var-ext.woff2 +0 -0
  265. package/app/public/vendor/fonts/fraunces-var.woff2 +0 -0
  266. package/app/public/vendor/fonts/fredoka-var-ext.woff2 +0 -0
  267. package/app/public/vendor/fonts/fredoka-var.woff2 +0 -0
  268. package/app/public/vendor/fonts/jakarta-var-ext.woff2 +0 -0
  269. package/app/public/vendor/fonts/jakarta-var.woff2 +0 -0
  270. package/app/public/vendor/fonts/lora-var-ext.woff2 +0 -0
  271. package/app/public/vendor/fonts/lora-var.woff2 +0 -0
  272. package/app/public/vendor/fonts/merriweather-var-ext.woff2 +0 -0
  273. package/app/public/vendor/fonts/merriweather-var.woff2 +0 -0
  274. package/app/public/vendor/fonts/notosans-var-ext.woff2 +0 -0
  275. package/app/public/vendor/fonts/notosans-var.woff2 +0 -0
  276. package/app/public/vendor/fonts/nunito-var-ext.woff2 +0 -0
  277. package/app/public/vendor/fonts/nunito-var.woff2 +0 -0
  278. package/app/public/vendor/fonts/playfair-var-ext.woff2 +0 -0
  279. package/app/public/vendor/fonts/playfair-var.woff2 +0 -0
  280. package/app/public/vendor/fonts/playwrite-cu.woff2 +0 -0
  281. package/app/public/vendor/fonts/playwrite-es-deco.woff2 +0 -0
  282. package/app/public/vendor/fonts/playwrite-es.woff2 +0 -0
  283. package/app/public/vendor/fonts/playwrite-gb-j.woff2 +0 -0
  284. package/app/public/vendor/fonts/playwrite-gb-s.woff2 +0 -0
  285. package/app/public/vendor/fonts/playwrite-pe.woff2 +0 -0
  286. package/app/public/vendor/fonts/playwrite-pt.woff2 +0 -0
  287. package/app/public/vendor/fonts/quicksand-var-ext.woff2 +0 -0
  288. package/app/public/vendor/fonts/quicksand-var.woff2 +0 -0
  289. package/app/public/vendor/fonts/robotoflex-var-ext.woff2 +0 -0
  290. package/app/public/vendor/fonts/robotoflex-var.woff2 +0 -0
  291. package/app/public/vendor/fonts/sora-var-ext.woff2 +0 -0
  292. package/app/public/vendor/fonts/sora-var.woff2 +0 -0
  293. package/app/public/vendor/fonts/spacegrotesk-var-ext.woff2 +0 -0
  294. package/app/public/vendor/fonts/spacegrotesk-var.woff2 +0 -0
  295. package/app/public/vendor/fonts/spectral-400-ext.woff2 +0 -0
  296. package/app/public/vendor/fonts/spectral-400.woff2 +0 -0
  297. package/app/public/vendor/fonts/spectral-700-ext.woff2 +0 -0
  298. package/app/public/vendor/fonts/spectral-700.woff2 +0 -0
  299. package/app/public/vendor/fonts/teachers-var-ext.woff2 +0 -0
  300. package/app/public/vendor/fonts/teachers-var.woff2 +0 -0
  301. package/app/public/vendor/fonts/ubuntu-400-ext.woff2 +0 -0
  302. package/app/public/vendor/fonts/ubuntu-400.woff2 +0 -0
  303. package/app/public/vendor/fonts/ubuntu-700-ext.woff2 +0 -0
  304. package/app/public/vendor/fonts/ubuntu-700.woff2 +0 -0
  305. package/app/public/vendor/fonts.css +1167 -51
  306. package/dist-pkg/boot/create-game.d.ts +467 -162
  307. package/dist-pkg/boot/create-game.js +3563 -426
  308. package/dist-pkg/core/a11y-sr.d.ts +25 -3
  309. package/dist-pkg/core/a11y-sr.js +40 -19
  310. package/dist-pkg/core/accessible-label.d.ts +17 -0
  311. package/dist-pkg/core/accessible-label.js +37 -0
  312. package/dist-pkg/core/accommodation-subjects.d.ts +26 -0
  313. package/dist-pkg/core/accommodation-subjects.js +32 -0
  314. package/dist-pkg/core/accommodations.d.ts +107 -0
  315. package/dist-pkg/core/accommodations.js +178 -0
  316. package/dist-pkg/core/actions.d.ts +75 -51
  317. package/dist-pkg/core/actions.js +98 -69
  318. package/dist-pkg/core/calm-mode.d.ts +31 -0
  319. package/dist-pkg/core/calm-mode.js +47 -0
  320. package/dist-pkg/core/camera-cycle.d.ts +15 -0
  321. package/dist-pkg/core/camera-cycle.js +7 -0
  322. package/dist-pkg/core/caption-duration.d.ts +15 -0
  323. package/dist-pkg/core/caption-duration.js +19 -0
  324. package/dist-pkg/core/cartridge-problems.d.ts +34 -0
  325. package/dist-pkg/core/cartridge-problems.js +53 -0
  326. package/dist-pkg/core/constants.d.ts +0 -54
  327. package/dist-pkg/core/constants.js +9 -76
  328. package/dist-pkg/core/contract.d.ts +143 -163
  329. package/dist-pkg/core/contract.js +223 -207
  330. package/dist-pkg/core/dom-query.d.ts +4 -4
  331. package/dist-pkg/core/dom-query.js +12 -20
  332. package/dist-pkg/core/entity.d.ts +62 -77
  333. package/dist-pkg/core/entity.js +18 -32
  334. package/dist-pkg/core/escape-html.d.ts +9 -10
  335. package/dist-pkg/core/escape-html.js +16 -20
  336. package/dist-pkg/core/flash-threshold.d.ts +17 -0
  337. package/dist-pkg/core/flash-threshold.js +113 -0
  338. package/dist-pkg/core/game-speed.d.ts +11 -0
  339. package/dist-pkg/core/game-speed.js +16 -0
  340. package/dist-pkg/core/genres.d.ts +20 -0
  341. package/dist-pkg/core/genres.js +44 -0
  342. package/dist-pkg/core/i18n.d.ts +87 -41
  343. package/dist-pkg/core/i18n.js +206 -153
  344. package/dist-pkg/core/loop.d.ts +17 -6
  345. package/dist-pkg/core/loop.js +26 -26
  346. package/dist-pkg/core/pause-icon-catalogue.d.ts +15 -0
  347. package/dist-pkg/core/pause-icon-catalogue.js +54 -0
  348. package/dist-pkg/core/ring.d.ts +17 -0
  349. package/dist-pkg/core/ring.js +29 -0
  350. package/dist-pkg/core/rng.d.ts +7 -12
  351. package/dist-pkg/core/rng.js +23 -53
  352. package/dist-pkg/core/route.d.ts +26 -26
  353. package/dist-pkg/core/route.js +126 -121
  354. package/dist-pkg/core/scenes.d.ts +37 -40
  355. package/dist-pkg/core/scenes.js +41 -43
  356. package/dist-pkg/core/screens.d.ts +4 -4
  357. package/dist-pkg/core/screens.js +11 -16
  358. package/dist-pkg/core/setting-defaults.d.ts +61 -0
  359. package/dist-pkg/core/setting-defaults.js +66 -0
  360. package/dist-pkg/core/speech-rate.d.ts +29 -0
  361. package/dist-pkg/core/speech-rate.js +47 -0
  362. package/dist-pkg/core/state.d.ts +118 -118
  363. package/dist-pkg/core/state.js +322 -297
  364. package/dist-pkg/core/visual-cycles.d.ts +30 -0
  365. package/dist-pkg/core/visual-cycles.js +52 -0
  366. package/dist-pkg/core/visual-state.d.ts +13 -0
  367. package/dist-pkg/core/visual-state.js +6 -0
  368. package/dist-pkg/educational/activities-registry.d.ts +1 -1
  369. package/dist-pkg/educational/activities-registry.js +9 -8
  370. package/dist-pkg/educational/adaptive-engine.d.ts +11 -11
  371. package/dist-pkg/educational/adaptive-engine.js +17 -17
  372. package/dist-pkg/educational/segment-bar.d.ts +12 -12
  373. package/dist-pkg/educational/segment-bar.js +9 -9
  374. package/dist-pkg/i18n/en.js +321 -215
  375. package/dist-pkg/i18n/es.js +320 -215
  376. package/dist-pkg/i18n/pt.js +342 -215
  377. package/dist-pkg/input/default-bindings.d.ts +37 -38
  378. package/dist-pkg/input/default-bindings.js +127 -144
  379. package/dist-pkg/input/devices.d.ts +7 -7
  380. package/dist-pkg/input/devices.js +17 -16
  381. package/dist-pkg/input/edges.d.ts +23 -32
  382. package/dist-pkg/input/edges.js +23 -28
  383. package/dist-pkg/input/empathy-filter.d.ts +22 -0
  384. package/dist-pkg/input/empathy-filter.js +34 -0
  385. package/dist-pkg/input/face-map.d.ts +96 -0
  386. package/dist-pkg/input/face-map.js +255 -0
  387. package/dist-pkg/input/face-signals.d.ts +40 -0
  388. package/dist-pkg/input/face-signals.js +48 -0
  389. package/dist-pkg/input/gamepad.d.ts +164 -145
  390. package/dist-pkg/input/gamepad.js +351 -559
  391. package/dist-pkg/input/gaze-cycle.d.ts +64 -0
  392. package/dist-pkg/input/gaze-cycle.js +133 -0
  393. package/dist-pkg/input/gaze-relative.d.ts +68 -0
  394. package/dist-pkg/input/gaze-relative.js +142 -0
  395. package/dist-pkg/input/hand-map.d.ts +28 -0
  396. package/dist-pkg/input/hand-map.js +142 -0
  397. package/dist-pkg/input/input-cooldown.d.ts +17 -0
  398. package/dist-pkg/input/input-cooldown.js +39 -0
  399. package/dist-pkg/input/keyboard-runtime.d.ts +18 -20
  400. package/dist-pkg/input/keyboard-runtime.js +23 -27
  401. package/dist-pkg/input/keyboard.d.ts +68 -62
  402. package/dist-pkg/input/keyboard.js +83 -105
  403. package/dist-pkg/input/keydown.d.ts +124 -132
  404. package/dist-pkg/input/keydown.js +283 -288
  405. package/dist-pkg/input/latch-edge.d.ts +25 -21
  406. package/dist-pkg/input/latch-edge.js +33 -35
  407. package/dist-pkg/input/latch-scope.d.ts +43 -47
  408. package/dist-pkg/input/latch-scope.js +66 -75
  409. package/dist-pkg/input/latch-store.d.ts +28 -29
  410. package/dist-pkg/input/latch-store.js +44 -47
  411. package/dist-pkg/input/latch-sync.d.ts +28 -29
  412. package/dist-pkg/input/latch-sync.js +25 -25
  413. package/dist-pkg/input/latch.d.ts +14 -17
  414. package/dist-pkg/input/latch.js +29 -37
  415. package/dist-pkg/input/pad-defaults.d.ts +13 -16
  416. package/dist-pkg/input/pad-defaults.js +21 -28
  417. package/dist-pkg/input/pad-reading.d.ts +76 -0
  418. package/dist-pkg/input/pad-reading.js +164 -0
  419. package/dist-pkg/input/pad-wizard.d.ts +96 -0
  420. package/dist-pkg/input/pad-wizard.js +205 -0
  421. package/dist-pkg/input/pointer-space.d.ts +19 -20
  422. package/dist-pkg/input/pointer-space.js +27 -32
  423. package/dist-pkg/input/pointer.d.ts +39 -40
  424. package/dist-pkg/input/pointer.js +42 -45
  425. package/dist-pkg/input/state.d.ts +101 -89
  426. package/dist-pkg/input/state.js +54 -134
  427. package/dist-pkg/input/switch-scan.d.ts +42 -0
  428. package/dist-pkg/input/switch-scan.js +54 -0
  429. package/dist-pkg/input/synthetic-source.d.ts +42 -0
  430. package/dist-pkg/input/synthetic-source.js +57 -0
  431. package/dist-pkg/input/touch-bindings.d.ts +127 -113
  432. package/dist-pkg/input/touch-bindings.js +158 -173
  433. package/dist-pkg/input/touch.d.ts +130 -70
  434. package/dist-pkg/input/touch.js +321 -123
  435. package/dist-pkg/input/transport-in-use.d.ts +107 -0
  436. package/dist-pkg/input/transport-in-use.js +130 -0
  437. package/dist-pkg/input/transports.d.ts +96 -114
  438. package/dist-pkg/input/transports.js +104 -106
  439. package/dist-pkg/input/virtual-controller.d.ts +46 -0
  440. package/dist-pkg/input/virtual-controller.js +41 -0
  441. package/dist-pkg/input/vocabulary-migration.d.ts +50 -49
  442. package/dist-pkg/input/vocabulary-migration.js +80 -79
  443. package/dist-pkg/input/voice-map.d.ts +43 -0
  444. package/dist-pkg/input/voice-map.js +125 -0
  445. package/dist-pkg/platform/audio-ambient.d.ts +13 -1
  446. package/dist-pkg/platform/audio-ambient.js +11 -13
  447. package/dist-pkg/platform/audio-earcons.d.ts +20 -19
  448. package/dist-pkg/platform/audio-earcons.js +51 -42
  449. package/dist-pkg/platform/audio-jingles.js +9 -10
  450. package/dist-pkg/platform/audio-mixer.d.ts +14 -2
  451. package/dist-pkg/platform/audio-mixer.js +36 -39
  452. package/dist-pkg/platform/audio-sonar.d.ts +96 -113
  453. package/dist-pkg/platform/audio-sonar.js +245 -238
  454. package/dist-pkg/platform/audio.d.ts +53 -19
  455. package/dist-pkg/platform/audio.js +161 -169
  456. package/dist-pkg/platform/flash-sampler.d.ts +19 -0
  457. package/dist-pkg/platform/flash-sampler.js +79 -0
  458. package/dist-pkg/platform/guide-intensity.d.ts +26 -27
  459. package/dist-pkg/platform/guide-intensity.js +50 -52
  460. package/dist-pkg/platform/heavy-catalogue.d.ts +31 -0
  461. package/dist-pkg/platform/heavy-catalogue.js +223 -0
  462. package/dist-pkg/platform/heavy-mirror.d.ts +26 -0
  463. package/dist-pkg/platform/heavy-mirror.js +76 -0
  464. package/dist-pkg/platform/heavy.d.ts +104 -0
  465. package/dist-pkg/platform/heavy.js +169 -0
  466. package/dist-pkg/platform/interruptible-speech.d.ts +16 -16
  467. package/dist-pkg/platform/interruptible-speech.js +50 -51
  468. package/dist-pkg/platform/kokoro-port.d.ts +27 -0
  469. package/dist-pkg/platform/kokoro-port.js +50 -0
  470. package/dist-pkg/platform/kokoro-runtime.d.ts +20 -0
  471. package/dist-pkg/platform/kokoro-runtime.js +52 -0
  472. package/dist-pkg/platform/kokoro.d.ts +70 -0
  473. package/dist-pkg/platform/kokoro.js +133 -0
  474. package/dist-pkg/platform/listener-scope.d.ts +12 -0
  475. package/dist-pkg/platform/listener-scope.js +81 -0
  476. package/dist-pkg/platform/locale-host.d.ts +10 -0
  477. package/dist-pkg/platform/locale-host.js +34 -0
  478. package/dist-pkg/platform/microphone.d.ts +51 -0
  479. package/dist-pkg/platform/microphone.js +118 -0
  480. package/dist-pkg/platform/onnx-runtime.d.ts +34 -0
  481. package/dist-pkg/platform/onnx-runtime.js +42 -0
  482. package/dist-pkg/platform/reading-in-worker.d.ts +40 -0
  483. package/dist-pkg/platform/reading-in-worker.js +87 -0
  484. package/dist-pkg/platform/reading-model.d.ts +73 -0
  485. package/dist-pkg/platform/reading-model.js +247 -0
  486. package/dist-pkg/platform/reading-runtime.d.ts +24 -0
  487. package/dist-pkg/platform/reading-runtime.js +159 -0
  488. package/dist-pkg/platform/reading-worker.d.ts +41 -0
  489. package/dist-pkg/platform/reading-worker.js +58 -0
  490. package/dist-pkg/platform/reading.d.ts +69 -0
  491. package/dist-pkg/platform/reading.js +128 -0
  492. package/dist-pkg/platform/speech-recognition.d.ts +51 -0
  493. package/dist-pkg/platform/speech-recognition.js +84 -0
  494. package/dist-pkg/platform/speech.d.ts +14 -1
  495. package/dist-pkg/platform/speech.js +13 -13
  496. package/dist-pkg/platform/storage-keys.d.ts +96 -0
  497. package/dist-pkg/platform/storage-keys.js +106 -0
  498. package/dist-pkg/platform/storage.d.ts +46 -111
  499. package/dist-pkg/platform/storage.js +104 -169
  500. package/dist-pkg/platform/tts.d.ts +51 -38
  501. package/dist-pkg/platform/tts.js +217 -119
  502. package/dist-pkg/platform/vision-loop.d.ts +23 -0
  503. package/dist-pkg/platform/vision-loop.js +56 -0
  504. package/dist-pkg/platform/vision.d.ts +132 -0
  505. package/dist-pkg/platform/vision.js +117 -0
  506. package/dist-pkg/platform/voice-listener.d.ts +36 -0
  507. package/dist-pkg/platform/voice-listener.js +88 -0
  508. package/dist-pkg/platform/voice-plan.d.ts +6 -102
  509. package/dist-pkg/platform/voice-plan.js +9 -151
  510. package/dist-pkg/platform/vosk-runtime.d.ts +71 -0
  511. package/dist-pkg/platform/vosk-runtime.js +93 -0
  512. package/dist-pkg/render/canvas.d.ts +13 -7
  513. package/dist-pkg/render/canvas.js +13 -13
  514. package/dist-pkg/render/crt.d.ts +46 -12
  515. package/dist-pkg/render/crt.js +82 -80
  516. package/dist-pkg/render/cvd-matrices.d.ts +18 -18
  517. package/dist-pkg/render/cvd-matrices.js +33 -38
  518. package/dist-pkg/render/hc-role-data.d.ts +7 -7
  519. package/dist-pkg/render/hc-role-data.js +14 -14
  520. package/dist-pkg/render/high-contrast.d.ts +66 -54
  521. package/dist-pkg/render/high-contrast.js +221 -216
  522. package/dist-pkg/render/low-vision-drawing.d.ts +5 -0
  523. package/dist-pkg/render/low-vision-drawing.js +46 -0
  524. package/dist-pkg/render/lq-filter.d.ts +32 -20
  525. package/dist-pkg/render/lq-filter.js +59 -65
  526. package/dist-pkg/render/port.d.ts +86 -88
  527. package/dist-pkg/render/port.js +17 -23
  528. package/dist-pkg/render/screen-pipeline.d.ts +42 -42
  529. package/dist-pkg/render/screen-pipeline.js +80 -87
  530. package/dist-pkg/render/sprite-fx.d.ts +2 -1
  531. package/dist-pkg/render/sprite-fx.js +16 -14
  532. package/dist-pkg/render/viewports.d.ts +20 -15
  533. package/dist-pkg/render/viewports.js +83 -114
  534. package/dist-pkg/render/viz-axes-labels.d.ts +47 -0
  535. package/dist-pkg/render/viz-axes-labels.js +88 -0
  536. package/dist-pkg/render/viz-axes.d.ts +80 -86
  537. package/dist-pkg/render/viz-axes.js +115 -116
  538. package/dist-pkg/render/viz-modes.d.ts +28 -29
  539. package/dist-pkg/render/viz-modes.js +40 -45
  540. package/dist-pkg/render/viz-refusal.d.ts +32 -0
  541. package/dist-pkg/render/viz-refusal.js +57 -0
  542. package/dist-pkg/render/viz-setters.d.ts +104 -66
  543. package/dist-pkg/render/viz-setters.js +182 -191
  544. package/dist-pkg/ui/aac-sets.d.ts +40 -0
  545. package/dist-pkg/ui/aac-sets.js +49 -0
  546. package/dist-pkg/ui/audio-choices.d.ts +63 -0
  547. package/dist-pkg/ui/audio-choices.js +94 -0
  548. package/dist-pkg/ui/audio-rows-that-apply.d.ts +12 -0
  549. package/dist-pkg/ui/audio-rows-that-apply.js +43 -0
  550. package/dist-pkg/ui/camera-control.d.ts +6 -0
  551. package/dist-pkg/ui/camera-control.js +14 -0
  552. package/dist-pkg/ui/changed-mark.d.ts +9 -8
  553. package/dist-pkg/ui/changed-mark.js +22 -53
  554. package/dist-pkg/ui/control-choices.d.ts +37 -0
  555. package/dist-pkg/ui/control-choices.js +84 -0
  556. package/dist-pkg/ui/debug-panel.d.ts +59 -43
  557. package/dist-pkg/ui/debug-panel.js +68 -69
  558. package/dist-pkg/ui/dom.d.ts +10 -14
  559. package/dist-pkg/ui/dom.js +10 -47
  560. package/dist-pkg/ui/drawing-problems.d.ts +25 -0
  561. package/dist-pkg/ui/drawing-problems.js +93 -0
  562. package/dist-pkg/ui/eye-control.d.ts +36 -0
  563. package/dist-pkg/ui/eye-control.js +178 -0
  564. package/dist-pkg/ui/face-control.d.ts +25 -0
  565. package/dist-pkg/ui/face-control.js +183 -0
  566. package/dist-pkg/ui/focus-trap.d.ts +39 -49
  567. package/dist-pkg/ui/focus-trap.js +56 -65
  568. package/dist-pkg/ui/fonts.d.ts +118 -63
  569. package/dist-pkg/ui/fonts.js +267 -111
  570. package/dist-pkg/ui/game-options.d.ts +42 -0
  571. package/dist-pkg/ui/game-options.js +138 -0
  572. package/dist-pkg/ui/gaze-overlay.d.ts +64 -0
  573. package/dist-pkg/ui/gaze-overlay.js +165 -0
  574. package/dist-pkg/ui/hand-control.d.ts +25 -0
  575. package/dist-pkg/ui/hand-control.js +143 -0
  576. package/dist-pkg/ui/help-panel.d.ts +76 -0
  577. package/dist-pkg/ui/help-panel.js +232 -0
  578. package/dist-pkg/ui/hud-bands.d.ts +45 -0
  579. package/dist-pkg/ui/hud-bands.js +108 -0
  580. package/dist-pkg/ui/hud.d.ts +73 -70
  581. package/dist-pkg/ui/hud.js +94 -98
  582. package/dist-pkg/ui/item-announcement.d.ts +13 -12
  583. package/dist-pkg/ui/item-announcement.js +25 -26
  584. package/dist-pkg/ui/latch-refusal.d.ts +22 -21
  585. package/dist-pkg/ui/latch-refusal.js +37 -37
  586. package/dist-pkg/ui/layout.d.ts +103 -28
  587. package/dist-pkg/ui/layout.js +114 -99
  588. package/dist-pkg/ui/locale-flags.d.ts +16 -0
  589. package/dist-pkg/ui/locale-flags.js +30 -0
  590. package/dist-pkg/ui/loop-crash.d.ts +17 -17
  591. package/dist-pkg/ui/loop-crash.js +33 -62
  592. package/dist-pkg/ui/menu-intent.d.ts +32 -0
  593. package/dist-pkg/ui/menu-intent.js +54 -0
  594. package/dist-pkg/ui/menu-items.d.ts +10 -0
  595. package/dist-pkg/ui/menu-items.js +22 -0
  596. package/dist-pkg/ui/menu-nav.d.ts +60 -90
  597. package/dist-pkg/ui/menu-nav.js +301 -274
  598. package/dist-pkg/ui/mobility-choices.d.ts +26 -0
  599. package/dist-pkg/ui/mobility-choices.js +41 -0
  600. package/dist-pkg/ui/motion-choices.d.ts +31 -0
  601. package/dist-pkg/ui/motion-choices.js +68 -0
  602. package/dist-pkg/ui/motion-scene.d.ts +24 -19
  603. package/dist-pkg/ui/motion-scene.js +29 -61
  604. package/dist-pkg/ui/mount-panel.d.ts +81 -0
  605. package/dist-pkg/ui/mount-panel.js +61 -0
  606. package/dist-pkg/ui/panel-shell.d.ts +65 -31
  607. package/dist-pkg/ui/panel-shell.js +92 -66
  608. package/dist-pkg/ui/panel-widgets.d.ts +116 -0
  609. package/dist-pkg/ui/panel-widgets.js +208 -0
  610. package/dist-pkg/ui/pause-buttons.d.ts +35 -0
  611. package/dist-pkg/ui/pause-buttons.js +66 -0
  612. package/dist-pkg/ui/pause-icons.d.ts +250 -343
  613. package/dist-pkg/ui/pause-icons.js +614 -605
  614. package/dist-pkg/ui/pause-markup.d.ts +86 -0
  615. package/dist-pkg/ui/pause-markup.js +125 -0
  616. package/dist-pkg/ui/reach-notice.d.ts +22 -23
  617. package/dist-pkg/ui/reach-notice.js +55 -58
  618. package/dist-pkg/ui/scan-overlay.d.ts +23 -0
  619. package/dist-pkg/ui/scan-overlay.js +42 -0
  620. package/dist-pkg/ui/settings-aac.d.ts +68 -0
  621. package/dist-pkg/ui/settings-aac.js +189 -0
  622. package/dist-pkg/ui/settings-audio.d.ts +91 -113
  623. package/dist-pkg/ui/settings-audio.js +407 -405
  624. package/dist-pkg/ui/settings-controls.d.ts +53 -92
  625. package/dist-pkg/ui/settings-controls.js +143 -215
  626. package/dist-pkg/ui/settings-empathy.d.ts +39 -30
  627. package/dist-pkg/ui/settings-empathy.js +53 -60
  628. package/dist-pkg/ui/settings-mobility.d.ts +195 -0
  629. package/dist-pkg/ui/settings-mobility.js +374 -0
  630. package/dist-pkg/ui/settings-motion.d.ts +91 -70
  631. package/dist-pkg/ui/settings-motion.js +287 -198
  632. package/dist-pkg/ui/settings-panel.d.ts +48 -45
  633. package/dist-pkg/ui/settings-panel.js +164 -93
  634. package/dist-pkg/ui/settings-typo.d.ts +28 -76
  635. package/dist-pkg/ui/settings-typo.js +145 -125
  636. package/dist-pkg/ui/settings-visual.d.ts +29 -87
  637. package/dist-pkg/ui/settings-visual.js +240 -193
  638. package/dist-pkg/ui/shell.d.ts +113 -134
  639. package/dist-pkg/ui/shell.js +143 -216
  640. package/dist-pkg/ui/simulation-list.d.ts +27 -0
  641. package/dist-pkg/ui/simulation-list.js +76 -0
  642. package/dist-pkg/ui/simulation-over-the-world.d.ts +23 -0
  643. package/dist-pkg/ui/simulation-over-the-world.js +131 -0
  644. package/dist-pkg/ui/simulation-refusal.d.ts +5 -31
  645. package/dist-pkg/ui/simulation-refusal.js +8 -56
  646. package/dist-pkg/ui/switchable-control.d.ts +11 -0
  647. package/dist-pkg/ui/switchable-control.js +19 -0
  648. package/dist-pkg/ui/title.d.ts +4 -4
  649. package/dist-pkg/ui/title.js +11 -13
  650. package/dist-pkg/ui/top-band.d.ts +24 -0
  651. package/dist-pkg/ui/top-band.js +100 -0
  652. package/dist-pkg/ui/typo-choices.d.ts +71 -0
  653. package/dist-pkg/ui/typo-choices.js +84 -0
  654. package/dist-pkg/ui/visual-axes-panel.d.ts +11 -47
  655. package/dist-pkg/ui/visual-axes-panel.js +12 -94
  656. package/dist-pkg/ui/visual-choices.d.ts +77 -0
  657. package/dist-pkg/ui/visual-choices.js +107 -0
  658. package/dist-pkg/ui/vlibras.d.ts +33 -10
  659. package/dist-pkg/ui/vlibras.js +58 -81
  660. package/dist-pkg/ui/voice-control.d.ts +46 -0
  661. package/dist-pkg/ui/voice-control.js +143 -0
  662. package/dist-pkg/ui/voice-settings.d.ts +96 -0
  663. package/dist-pkg/ui/voice-settings.js +302 -0
  664. package/dist-pkg/ui/where-the-child-is.d.ts +31 -0
  665. package/dist-pkg/ui/where-the-child-is.js +70 -0
  666. package/docs/CREDITS.md +198 -54
  667. package/docs/LICENSES.md +160 -145
  668. package/package.json +24 -25
  669. package/scripts/heavy-into-the-delivery.mjs +144 -0
  670. package/scripts/licences/Apache-2.0.txt +176 -0
  671. package/scripts/licences/GPL-3.0.txt +674 -0
  672. package/scripts/licences/MIT.txt +17 -0
  673. package/scripts/licences/third-party.mjs +287 -0
  674. package/scripts/licences/vosk-browser.NOTICE.txt +268 -0
  675. package/app/public/vendor/fonts/comicneue-400.woff2 +0 -0
  676. package/app/public/vendor/fonts/comicneue-700.woff2 +0 -0
  677. package/dist-pkg/core/anel.d.ts +0 -17
  678. package/dist-pkg/core/anel.js +0 -31
  679. package/dist-pkg/core/collision.d.ts +0 -21
  680. package/dist-pkg/core/collision.js +0 -62
  681. package/dist-pkg/core/layers.d.ts +0 -63
  682. package/dist-pkg/core/layers.js +0 -95
  683. package/dist-pkg/core/letter-grid.d.ts +0 -55
  684. package/dist-pkg/core/letter-grid.js +0 -116
  685. package/dist-pkg/core/password.d.ts +0 -34
  686. package/dist-pkg/core/password.js +0 -205
  687. package/dist-pkg/core/rotulo-acessivel.d.ts +0 -17
  688. package/dist-pkg/core/rotulo-acessivel.js +0 -41
  689. package/dist-pkg/core/run-state.d.ts +0 -115
  690. package/dist-pkg/core/run-state.js +0 -40
  691. package/dist-pkg/core/tiles.d.ts +0 -12
  692. package/dist-pkg/core/tiles.js +0 -61
  693. package/dist-pkg/core/world.d.ts +0 -4
  694. package/dist-pkg/core/world.js +0 -58
  695. package/dist-pkg/input/origem-sintetica.d.ts +0 -44
  696. package/dist-pkg/input/origem-sintetica.js +0 -60
  697. package/dist-pkg/input/transporte-em-uso.d.ts +0 -101
  698. package/dist-pkg/input/transporte-em-uso.js +0 -130
  699. package/dist-pkg/platform/audio-nav.d.ts +0 -51
  700. package/dist-pkg/platform/audio-nav.js +0 -99
  701. package/dist-pkg/platform/pesados-catalogo.d.ts +0 -14
  702. package/dist-pkg/platform/pesados-catalogo.js +0 -177
  703. package/dist-pkg/platform/pesados.d.ts +0 -31
  704. package/dist-pkg/platform/pesados.js +0 -75
  705. package/dist-pkg/platform/vozes-prontas.d.ts +0 -28
  706. package/dist-pkg/platform/vozes-prontas.js +0 -61
  707. package/dist-pkg/render/camera.d.ts +0 -58
  708. package/dist-pkg/render/camera.js +0 -105
  709. package/dist-pkg/render/cenario-data.d.ts +0 -127
  710. package/dist-pkg/render/cenario-data.js +0 -145
  711. package/dist-pkg/render/city-tex.d.ts +0 -63
  712. package/dist-pkg/render/city-tex.js +0 -228
  713. package/dist-pkg/render/city-tiles.d.ts +0 -11
  714. package/dist-pkg/render/city-tiles.js +0 -140
  715. package/dist-pkg/render/draw.d.ts +0 -149
  716. package/dist-pkg/render/draw.js +0 -231
  717. package/dist-pkg/render/fx.d.ts +0 -63
  718. package/dist-pkg/render/fx.js +0 -123
  719. package/dist-pkg/render/minimap.d.ts +0 -11
  720. package/dist-pkg/render/minimap.js +0 -94
  721. package/dist-pkg/render/parallax.d.ts +0 -85
  722. package/dist-pkg/render/parallax.js +0 -158
  723. package/dist-pkg/render/player-anim.d.ts +0 -71
  724. package/dist-pkg/render/player-anim.js +0 -152
  725. package/dist-pkg/render/recycling-tex.d.ts +0 -48
  726. package/dist-pkg/render/recycling-tex.js +0 -164
  727. package/dist-pkg/render/scene-city.d.ts +0 -77
  728. package/dist-pkg/render/scene-city.js +0 -181
  729. package/dist-pkg/render/scene-parallax.d.ts +0 -72
  730. package/dist-pkg/render/scene-parallax.js +0 -426
  731. package/dist-pkg/render/scene-sky.d.ts +0 -126
  732. package/dist-pkg/render/scene-sky.js +0 -295
  733. package/dist-pkg/render/set-cenario.d.ts +0 -54
  734. package/dist-pkg/render/set-cenario.js +0 -103
  735. package/dist-pkg/render/textures.d.ts +0 -75
  736. package/dist-pkg/render/textures.js +0 -240
  737. package/dist-pkg/render/title-scene.d.ts +0 -46
  738. package/dist-pkg/render/title-scene.js +0 -75
  739. package/dist-pkg/render/weather.d.ts +0 -96
  740. package/dist-pkg/render/weather.js +0 -171
  741. package/dist-pkg/render/wheelchair-sprites.d.ts +0 -32
  742. package/dist-pkg/render/wheelchair-sprites.js +0 -69
  743. package/dist-pkg/render/world-tex.d.ts +0 -18
  744. package/dist-pkg/render/world-tex.js +0 -87
  745. package/dist-pkg/ui/activities-menu.d.ts +0 -258
  746. package/dist-pkg/ui/activities-menu.js +0 -648
  747. package/dist-pkg/ui/caa-sets.d.ts +0 -39
  748. package/dist-pkg/ui/caa-sets.js +0 -65
  749. package/dist-pkg/ui/map-hub.d.ts +0 -56
  750. package/dist-pkg/ui/map-hub.js +0 -138
  751. package/dist-pkg/ui/settings-caa.d.ts +0 -45
  752. package/dist-pkg/ui/settings-caa.js +0 -137
  753. package/dist-pkg/ui/settings-motor.d.ts +0 -175
  754. package/dist-pkg/ui/settings-motor.js +0 -342
  755. package/dist-pkg/ui/webcam.d.ts +0 -7
  756. package/dist-pkg/ui/webcam.js +0 -93
@@ -1,137 +1,176 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-or-later
2
- // ui/settings-audio — Audio panel (Estágio 4, #audio overlay): extracted from game.js's renderAudio()/
3
- // renderAudioSinks()/catRowHTML/wireCatControls/renderNavSound/reflectAudioMaster + the TTS-panel functions that
4
- // platform/tts.ts already documents as belonging here (reflectTTS/populateTTSEngines/populateTTSVoices). Pure
5
- // logic (category markup, volume->percent, sink option/label, cane-div validation, pt-BR voice filtering) is
6
- // separated from the thin DOM-touching render/wire functions. DI via initSettingsAudio(ctx): `$`, `srSay`,
7
- // `store` (narrow get/set), the live sound/mixer primitives from platform/audio.ts (getSoundOn/setSoundOn/
8
- // getVolume/setVolume/getAudioCat/setCatGain), the injected `tts` panel API (platform/tts.ts), and the SHARED
9
- // game.js helpers other panels also use (`toggleBtn`, `getNumPlayers`/`getPlayers`) or that live outside audio
10
- // entirely (`getModoCego`/`setModoCego`, `getCaneBlockDiv`/`setCaneBlockDiv` — core collision state; the widgets
11
- // live in this overlay, the state does not). Overlay open/close plumbing (#audio hidden toggle, frontOverlay,
12
- // focus management, Escape, `ensureAC()`) is the shared infra every settings panel uses and stays in game.js,
13
- // which calls `renderAudio()` from its `openAudio()`. `reflectModoCego`/`reflectTts` are also exported because
14
- // game.js's own `setModoCego()` and the pause-menu icon bar (`iconAct('tts'|'blind', …)`) call them directly.
2
+ // ui/settings-audio — the hearing accessibility and audio panels (#audio overlay): the thin DOM-touching render/wire
3
+ // functions; what a choice IS lives in ./audio-choices.js and the voice section in ./voice-settings.js. DI via
4
+ // initSettingsAudio(ctx): `$`, `srSay`, `store` (narrow get/set), the browser through three required ports
5
+ // (`newElement`, `speech`, `audioOutputs`), the live sound/mixer primitives from platform/audio.ts
6
+ // (getSoundOn/setSoundOn/getVolume/setVolume/getAudioCat/setCatGain), the injected `tts` panel API (platform/tts.ts), and
7
+ // the host's shared helpers (`toggleBtn`, `getNumPlayers`/`getPlayers`) plus state that lives outside audio
8
+ // (`getBlindMode`/`setBlindMode`, `getCaneBlockDiv`/`setCaneBlockDiv` — the widgets live in this overlay, the state does
9
+ // not). Overlay open/close plumbing (frontOverlay, focus management, Escape) is the shared infrastructure every settings
10
+ // panel uses. `reflectBlindMode`/`reflectTts` are exported because the pause-menu icon bar calls them directly.
15
11
  /** Minimal DOM-selector shape (matches ui/dom.ts's `$`). */
16
12
  import { toggleLabel } from './dom.js';
17
- import { t, bcp47 } from '../core/i18n.js';
18
- import { DEFAULTS } from '../core/state.js';
19
- // O módulo INTEIRO, e não os nomes soltos: `menuIndexOn` é ligação viva e `setMenuIndexOnValue` a muda — ler
20
- // pelo namespace deixa isso à vista em cada uso, em vez de parecer uma constante importada.
21
- import * as state from '../core/state.js';
13
+ import { DEFAULTS } from '../core/setting-defaults.js';
22
14
  import { markChanged, markMenuChanged } from './changed-mark.js';
23
15
  import { defaultAudioCat } from '../platform/audio-mixer.js';
24
- // ---------------------------------------------------------------------------------------------
25
- // Pure logic (no `document`, testable in node)
26
- // ---------------------------------------------------------------------------------------------
27
- /** Categorias de NAVEGAÇÃO SONORA (bengala/sonar/guarda/guia) — volume geral separado do som do jogo. */
28
- export const NAV_CATS = ['sonar', 'guard', 'guide'];
29
- /** Categorias GERAIS do jogo (TTS fica na seção Voz, fora desta lista). */
30
- export const GEN_CATS = ['music', 'ambient', 'interact', 'earcons', 'other'];
31
- /** 0..1 -> 0..100 rounded (slider display value). */
32
- export function volPercent(v) {
33
- return Math.round(v * 100);
34
- }
35
- /** One category row's markup (volume slider + on/off switch). Pure — resolves label/state from the given data,
36
- * never from a global. Returns '' for an unknown/missing category (defensive; never hit with real catalogs). */
37
- export function catRowHTML(k, cats, state) {
38
- const c = cats.find((x) => x.k === k);
39
- const a = state[k];
40
- if (!c || !a)
41
- return '';
42
- return `<div class="ctrl-row"><span>${c.lbl}</span><span style="display:flex;gap:.5rem;align-items:center;flex-shrink:0">` +
43
- `<input class="vol" type="range" min="0" max="100" step="5" value="${volPercent(a.vol)}" data-avol="${k}" aria-label="Volume de ${c.lbl}">` +
44
- `<button class="mode-btn switch${a.on ? ' is-on' : ''}" data-acat="${k}" type="button" aria-pressed="${a.on}" aria-label="${c.lbl}"></button></span></div>`;
45
- }
46
- /** Full innerHTML for a category list (#audio-list or #navsound-list). Pure string building — no DOM. */
47
- export function catsListHTML(keys, cats, state) {
48
- return keys.map((k) => catRowHTML(k, cats, state)).join('');
49
- }
50
- /** #navsound-master's value: the loudest of the nav categories, as a 0..100 slider value. */
51
- export function navMasterVolume(state, navCats = NAV_CATS) {
52
- return volPercent(Math.max(...navCats.map((k) => state[k].vol)));
53
- }
54
- /** Cane-hit spacing select -> validated int (garbage/empty -> 1, "a batida por bloco"). */
55
- export function parseCaneDiv(raw) {
56
- return (+raw) || 1;
57
- }
58
- /** srSay text for a cane-hit spacing choice. The two halves are ONE sentence per case, not a shared prefix
59
- * plus a tail: a language that renders this as "One tap per block (cane)" needs to move the word "cane". */
60
- export function caneDivMessage(div) {
61
- return t(div === 2 ? 'sr.audio.caneHalfBlock' : 'sr.audio.canePerBlock');
62
- }
63
- /** #tts-engine's option catalog: (value, i18n KEY of the label). The engine NAMES are proper nouns and stay
64
- * put; what translates is the parenthetical that explains each one. Keys, not text — see input/devices. */
65
- export const TTS_ENGINE_OPTIONS = [
66
- ['webspeech', 'tts.engine.webspeech'],
67
- ['piper', 'tts.engine.piper'],
68
- ['kokoro', 'tts.engine.kokoro'],
69
- ['kitten', 'tts.engine.kitten'],
70
- ['espeak', 'tts.engine.espeak'],
71
- ];
16
+ import { controlRow, labelRow } from './panel-widgets.js';
17
+ /*
18
+ * 🔴 THE VOICE SECTION LIVES IN `ui/voice-settings` (ADR-0221, issue #203): speech — the narration switch, the engine, the
19
+ * voice, the rate, the spoken index and the test button — and the sound categories, cane, blind mode and outputs never
20
+ * needed each other.
21
+ */
22
+ import { createVoiceSettings } from './voice-settings.js';
23
+ /*
24
+ * 🔴 THE PURE HALF LIVES IN `ui/audio-choices` (ADR-0221, issue #203): what a choice IS — the category list, the volume
25
+ * arithmetic, the engine catalogue, the voice filter, an output's label — needs no document, and this file is about
26
+ * FINDING the controls and wiring them. The suite had already made the cut: the node test imported exactly those names
27
+ * and the browser test drove the rest.
28
+ */
29
+ import { NAV_CATS, GEN_CATS, volPercent, navMasterVolume, parseCaneDiv, caneDivMessage, sinksSupported, sinkOptionLabel, sinkSelectValue, } from './audio-choices.js';
72
30
  /**
73
- * Os motores que ESTA MONTAGEM pode de facto oferecer (ADR-0094).
31
+ * MOUNTS THIS PANEL'S INSIDE — the controls and containers it reaches.
32
+ *
33
+ * 🔴 IT IS THE LARGEST INVISIBLE CONTRACT OF THE PANELS: this file looks for `#opt-modocego`, `#cane-div`,
34
+ * `#opt-menuindex`, `#opt-tts`, `#tts-vol` and more, and nothing in the type says so — which is why it builds them.
74
35
  *
75
- * ⚠️ Desde que o motor neural passou a chegar por PORTA (`ctx.carregarVozNeural`), «o Piper existe» deixou
76
- * de ser verdade sobre a engine e passou a ser verdade sobre o JOGO. Oferecer uma opção que não pode
77
- * funcionar é pior que uma opção a menos: quem a escolhe fica à espera de um download que nunca começa, e
78
- * quem navega por escuta não tem como ver que não começou.
36
+ * ⚠️ AND EACH ONE'S TAG MATTERS, which makes this the worst place to guess: `#cane-div` and the voice selects have to be
37
+ * `<select>` — the panel writes `.value` into them —, and the volumes have to be `<input type=range>`. On a `<button>`,
38
+ * writing `.value` gives no error at all: it creates a property nobody reads, and the child's choice vanishes silently.
79
39
  *
80
- * Só o `piper` é filtrado. `kokoro`/`kitten`/`espeak` também não funcionam hoje — «ainda não entraram» —,
81
- * mas isso é anterior a este registro e escondê-los aqui mudaria comportamento que ninguém pediu para mudar.
40
+ * 📌 THE ORDER IS THE DECISION (ADR-0044 §2), from general to particular (see the composition below).
41
+ *
42
+ * ⚠️ `#opt-sound` IS NOT CREATED HERE, deliberately: it is this setting's mirror on the quick bar, outside the panel.
43
+ * `reflectMaster` reaches it with a guard (`if (sb)`), because a game may have no bar.
44
+ *
45
+ * Idempotent: calling it twice reuses what exists instead of duplicating it.
82
46
  */
83
- export function opcoesDeMotor(neuralDisponivel) {
84
- return neuralDisponivel ? TTS_ENGINE_OPTIONS : TTS_ENGINE_OPTIONS.filter(([v]) => v !== 'piper');
47
+ export function mountAudioInside(t, ctx, card, list) {
48
+ // Each entry is either a control row or a CONTAINER the panel fills with rows of its own.
49
+ const pieces = [
50
+ /*
51
+ * 🔴 THE COMPOSITION OF ADR-0151 AND ITS ERRATA, from general to particular:
52
+ * · BLIND MODE first, because it is the mode where the other sounds become the screen — and WITHOUT the hint: «é
53
+ * redundante. Quem precisa sabe o que é»;
54
+ * · the CANE beside it (and only in a game that answers that someone walks, ADR-0153);
55
+ * · the SONAR, GUARD and GUIDE, each with its switch and volume: they are the shell's list;
56
+ * · NARRATION and its volume, with the SPOKEN INDEX right after, because narration is what it shortens.
57
+ * 🔴 The GENERAL sound and volume live in the audio panel (`mountSoundInside`) — «toggle + barra para som geral
58
+ * voltam», the Dev said, and they came back to the sound panel, not the accessibility one. There is no separate
59
+ * navigation volume: it would be a second place for the list's three volumes (one place per choice, D2 of the record).
60
+ */
61
+ // ⚠️ Their own short keys, not the bar's `icon.blind`/`icon.tts`: those carry «(navegação sonora)» and «(TTS)», and
62
+ // a row keeps no explanation in parentheses (ADR-0158).
63
+ { id: 'opt-modocego', label: t('audio.modocego') },
64
+ { id: 'cane-div', label: t('audio.cane'), hint: t('audio.cane.dica'), shape: 'escolha' },
65
+ { container: '@lista' }, // the shell's list: sonar, guard and guide
66
+ { id: 'opt-tts', label: t('audio.narracao'), hint: t('audio.tts.dica') },
67
+ { id: 'tts-vol', label: t('audio.ttsVol'), shape: 'cursor' },
68
+ // ADR-0183 §1, ADR-0196: the speech rate, 254 to 504 by 50 — six positions, so a list (ADR-0130 erratum)
69
+ { id: 'tts-ppm', label: t('audio.ttsPpm'), hint: t('audio.ttsPpm.dica'), shape: 'escolha' },
70
+ // ADR-0185: the child picks the voice, among the voices that speak the language — a list, since English passes five
71
+ { id: 'tts-voz', label: t('audio.voz'), shape: 'escolha' },
72
+ { id: 'opt-menuindex', label: t('audio.menuindex'), hint: t('audio.menuindex.dica') },
73
+ /*
74
+ * 🔴 NOT OFFERED by the engine (ADR-0151), in the Dev's words:
75
+ * · the ENGINE, the VOICE and the voice TEST — «quem escolhe a voz é o jogo (cartucho), não o jogador. Jogador só
76
+ * habilita/desabilita o TTS»;
77
+ * · the PER-PLAYER AUDIO OUTPUTS and their detect button — «navegadores não são bons nisso».
78
+ * 📌 `initSettingsAudio`'s wiring for those ids STAYS and is guarded (`if (el)`): a game with markup of its own does
79
+ * not break. What changed is that the engine no longer OFFERS the choice to the child.
80
+ */
81
+ ];
82
+ /*
83
+ * ⚠️ IT RELABELS INSTEAD OF SKIPPING what already exists, which is why this function is also called from every open's
84
+ * `render()`: anything built once at boot captures the fallback language's text — `initI18n` applies the fallback
85
+ * synchronously and REQUESTS the preferred one, which arrives later — and a panel would serve its TITLE in one
86
+ * language and its ROWS in another. No unit test catches it: they all run in one language.
87
+ */
88
+ // ADR-0158: the rows go BEFORE the reset, never after it — the reset is the panel's last item, and «Voltar» its first.
89
+ const actions = card.querySelector(':scope > .overlay__actions');
90
+ for (const piece of pieces) {
91
+ if ('container' in piece) {
92
+ if (piece.container === '@lista') {
93
+ card.insertBefore(list, actions);
94
+ continue;
95
+ }
96
+ const jaHa = ctx.find('#' + piece.container);
97
+ if (jaHa) {
98
+ if (piece.label)
99
+ jaHa.setAttribute('aria-label', piece.label);
100
+ continue;
101
+ }
102
+ const c = ctx.create('div');
103
+ c.id = piece.container;
104
+ c.className = 'ctrl-list';
105
+ c.setAttribute('role', 'group');
106
+ if (piece.label)
107
+ c.setAttribute('aria-label', piece.label);
108
+ card.insertBefore(c, actions);
109
+ continue;
110
+ }
111
+ const alreadyThere = ctx.find('#' + piece.id);
112
+ if (alreadyThere) {
113
+ const rowNode = alreadyThere.closest('.ctrl-row');
114
+ if (rowNode)
115
+ labelRow(rowNode, piece);
116
+ continue;
117
+ }
118
+ card.insertBefore(controlRow(ctx, piece).row, actions);
119
+ }
85
120
  }
86
121
  /**
87
- * As vozes do sistema no idioma pedido; sem nenhuma, a lista inteira.
88
- *
89
- * Chamava-se `pickPtVoices` e filtrava `/^pt/i` fixo, de modo que o jogo em inglês oferecia à pessoa uma
90
- * lista de vozes PORTUGUESAS para ler texto em inglês. O nome dizia a verdade sobre o que fazia e mentia
91
- * sobre o que devia fazer.
122
+ * MOUNTS THE AUDIO PANEL'S INSIDE (ADR-0151 §2 item 4): the general sound — switch and volume — and then the shell's
123
+ * list with the four taste categories (music, ambience, interaction, earcons).
92
124
  *
93
- * A comparação é pelo PREFIXO de idioma, não pela etiqueta inteira: quem joga em pt-BR também deve poder
94
- * escolher uma voz pt-PT se for a única instalada, e o navegador de uma escola raramente tem a variante
95
- * exata. O recuo para a lista inteira fica: uma lista vazia seria pior que uma lista no idioma errado, que
96
- * ao menos a pessoa pode ouvir e rejeitar.
125
+ * ⚠️ THE IDS ARE THE ONES `initSettingsAudio` ALREADY LISTENS TO (`#audio-master`, `#audio-master-vol`, `#audio-list`). And
126
+ * the same order rule: mount BEFORE `init`. Idempotent and relabellable, like its hearing sibling.
97
127
  */
98
- export function pickVoicesFor(voices, lang) {
99
- const pref = lang.slice(0, 2).toLowerCase();
100
- const iguais = voices.filter((v) => v.lang.slice(0, 2).toLowerCase() === pref);
101
- return iguais.length ? iguais : voices;
102
- }
103
- /** "<name> (<lang>)" option label. */
104
- export function voiceLabel(v) {
105
- return v.name + ' (' + v.lang + ')';
106
- }
107
- /** Whether the browser can list/switch audio outputs at all (gates the #audio-sinks hint copy). */
108
- export function sinksSupported(hasEnumerateDevices, hasAudioContextCtor) {
109
- return hasEnumerateDevices && hasAudioContextCtor;
110
- }
111
- /** A device's option label, falling back to a 1-based "Saída N" when the browser withholds the real label
112
- * (no getUserMedia permission granted yet). */
113
- export function sinkOptionLabel(d, index) {
114
- return d.label || t('audio.sinkFallback', { n: index + 1 });
115
- }
116
- /** A player's current sink select value ('' = default/shared). */
117
- export function sinkSelectValue(p) {
118
- return (p && p.audioSink) || '';
128
+ export function mountSoundInside(t, ctx, card, list) {
129
+ const actions = card.querySelector(':scope > .overlay__actions');
130
+ const rows = [
131
+ { id: 'audio-master', label: t('audio.som'), hint: t('audio.som.dica') },
132
+ { id: 'audio-master-vol', label: t('audio.volume'), shape: 'cursor' },
133
+ ];
134
+ for (const piece of rows) {
135
+ const alreadyThere = ctx.find('#' + piece.id);
136
+ if (alreadyThere) {
137
+ const rowNode = alreadyThere.closest('.ctrl-row');
138
+ if (rowNode)
139
+ labelRow(rowNode, piece);
140
+ continue;
141
+ }
142
+ card.insertBefore(controlRow(ctx, piece).row, list.parentNode === card ? list : actions);
143
+ }
144
+ if (list.parentNode !== card)
145
+ card.insertBefore(list, actions);
119
146
  }
120
147
  // ---------------------------------------------------------------------------------------------
121
148
  // DOM-facing (thin) — requires `document`/injected ctx
122
149
  // ---------------------------------------------------------------------------------------------
123
150
  export function initSettingsAudio(ctx) {
124
- // ⚠️ PADRÃO DA ENGINE (ADR-0106 §4): quem injecta manda; quem não injecta deixa de ficar sem modo cego.
125
- // O `setModoCegoValue` faz as três coisas que o `core/state` diz que um setter faz — grava, persiste, avisa
126
- // — e nada mais: os efeitos (refazer os extras do nível) são reação, e quem reage assina o evento.
127
- const setModoCego = ctx.setModoCego ?? state.setModoCegoValue;
151
+ const { t } = ctx.translator;
152
+ // ⚠️ THE ENGINE'S DEFAULT (ADR-0106 §4): whoever injects rules; whoever does not still gets blind mode.
153
+ // `setBlindModeValue` does the three things `core/state` says a setter does — store, persist, notify — and nothing
154
+ // more: the effects are reactions, and whoever reacts subscribes to the event.
155
+ const writeBlindMode = ctx.setBlindMode ?? ((on) => { ctx.settings.setBlindModeValue(on); });
128
156
  let audioDevices = [];
157
+ /*
158
+ * ⚠️ THE BROWSER TRAVELS AS FOUR FUNCTIONS, not as a global reached inside: a module that reaches `document` or
159
+ * `window` is refused by the ratchet (the reach ceiling is ZERO). What this file received through its own ports it
160
+ * passes down as ports.
161
+ */
162
+ const voice = createVoiceSettings(ctx, {
163
+ newOption: () => ctx.newElement('option'),
164
+ systemVoices: () => [...ctx.speech.voices()],
165
+ speakSample: (sample, chosen) => ctx.speech.speakSample(sample, chosen),
166
+ whenVoicesChange: (again) => ctx.speech.whenVoicesChange(again),
167
+ });
129
168
  function reflectMaster() {
130
169
  const b = ctx.$('#audio-master');
131
170
  if (b) {
132
171
  b.classList.toggle('is-on', ctx.getSoundOn());
133
172
  b.setAttribute('aria-pressed', String(ctx.getSoundOn()));
134
- b.textContent = ctx.getSoundOn() ? '🔊 Ligado' : '🔇 Desligado';
173
+ b.textContent = toggleLabel(t, ctx.getSoundOn()); // the dictionary's words, no glyph in the name (ADR-0159 rule 12)
135
174
  }
136
175
  const v = ctx.$('#audio-master-vol');
137
176
  if (v)
@@ -140,40 +179,144 @@ export function initSettingsAudio(ctx) {
140
179
  if (sb)
141
180
  ctx.toggleBtn(sb, ctx.getSoundOn());
142
181
  }
182
+ /**
183
+ * The kit's ctx comes from the LIST NODE itself, not from a global `document` nor from a new contract field —
184
+ * `ownerDocument` is the document that list lives in, which is where its rows have to be born. Same shape as
185
+ * `ui/settings-aac`, and it is what removes this file's reach to `document` without touching `SettingsAudioCtx`.
186
+ */
187
+ const kitCtx = (list) => ({
188
+ find: (sel) => ctx.$(sel),
189
+ create: (tag) => list.ownerDocument.createElement(tag),
190
+ });
191
+ /**
192
+ * The same document, but KEEPING the element type.
193
+ *
194
+ * 📌 `PanelShellCtx.create` is `string → HTMLElement` on purpose: the shell only ever appends generic nodes, and
195
+ * widening it would be a one-way door on a published shape (ADR-0172). The sinks section needs a `<select>` that
196
+ * answers `.value`, so the typed factory lives HERE, over the same `ownerDocument`, instead of the contract
197
+ * growing a field for one caller.
198
+ */
199
+ const makerFor = (host) => (tag) => host.ownerDocument.createElement(tag);
200
+ /**
201
+ * ONE CATEGORY ROW, in nodes: label, volume slider and toggle.
202
+ *
203
+ * 📌 NOT A KIT ROW, and not by taste: 📏 measured on 2026-09-23, `controlRow` builds «a label, a hint and ONE
204
+ * control» and this row carries TWO — the volume and the on/off of the same category. Same reason the visual
205
+ * panel's four paper-colour row stayed hand-built: inventing a shape for it would be giving a second answer to
206
+ * the question «what is a row».
207
+ */
208
+ function buildCatRow(kit, k) {
209
+ const row = kit.create('div');
210
+ row.className = 'ctrl-row';
211
+ const text = kit.create('span');
212
+ text.appendChild(kit.create('strong'));
213
+ row.appendChild(text);
214
+ const box = kit.create('span');
215
+ box.style.cssText = 'display:flex;gap:.5rem;align-items:center;flex-shrink:0';
216
+ const vol = kit.create('input');
217
+ vol.className = 'vol';
218
+ vol.setAttribute('type', 'range');
219
+ vol.setAttribute('min', '0');
220
+ vol.setAttribute('max', '100');
221
+ vol.setAttribute('step', '5');
222
+ vol.setAttribute('data-avol', k);
223
+ box.appendChild(vol);
224
+ const toggle = kit.create('button');
225
+ toggle.className = 'mode-btn switch';
226
+ toggle.setAttribute('type', 'button');
227
+ toggle.setAttribute('aria-pressed', 'false');
228
+ toggle.setAttribute('data-acat', k);
229
+ box.appendChild(toggle);
230
+ row.appendChild(box);
231
+ return row;
232
+ }
233
+ /** Creates what is missing IN THE RIGHT POSITION, rewrites the words of what stayed, removes what is no longer asked for. */
234
+ function mountCatRows(list, keys) {
235
+ const kit = kitCtx(list);
236
+ let previous = null;
237
+ // ⚠️ WHAT SURVIVES IS WHAT THE HOST STILL NAMES, not simply what is in `keys`. A row whose category lost its
238
+ // name would otherwise stay on screen with the old label — asked for by the key list, offered by nobody.
239
+ const named = new Set();
240
+ for (const k of keys) {
241
+ const c = ctx.audioCats.find((x) => x.k === k);
242
+ if (!c)
243
+ continue; // a category this host does not name: what has no name is not offered
244
+ named.add(k);
245
+ let row = list.querySelector(`[data-acat="${k}"]`)?.closest('.ctrl-row') ?? null;
246
+ if (!row) {
247
+ row = buildCatRow(kit, k);
248
+ // ⚠️ INSERTED in place, never appended and reordered: a node that changes parent is removed and put back,
249
+ // and the browser blurs it on removal — that would take the cursor from whoever is setting the volume.
250
+ list.insertBefore(row, previous ? previous.nextSibling : list.firstChild);
251
+ }
252
+ const label = t(c.lbl);
253
+ const strong = row.querySelector('strong');
254
+ if (strong)
255
+ strong.textContent = label;
256
+ row.querySelector(`[data-avol="${k}"]`)?.setAttribute('aria-label', t('audio.cat.volumeDe', { c: label }));
257
+ row.querySelector(`[data-acat="${k}"]`)?.setAttribute('aria-label', label);
258
+ previous = row;
259
+ }
260
+ for (const b of [...list.querySelectorAll('[data-acat]')]) {
261
+ if (!named.has(b.dataset.acat ?? ''))
262
+ b.closest('.ctrl-row')?.remove();
263
+ }
264
+ }
265
+ /** The STATE of each row — the volume where it is and the toggle saying what it says. */
266
+ function reflectCatRows(list, keys) {
267
+ const state = ctx.getAudioCat();
268
+ if (!state)
269
+ return;
270
+ for (const k of keys) {
271
+ const a = state[k];
272
+ if (!a)
273
+ continue;
274
+ const vol = list.querySelector(`input[data-avol="${k}"]`);
275
+ if (vol)
276
+ vol.value = String(volPercent(a.vol));
277
+ const toggle = list.querySelector(`button[data-acat="${k}"]`);
278
+ if (toggle) {
279
+ toggle.classList.toggle('is-on', a.on);
280
+ toggle.setAttribute('aria-pressed', String(a.on));
281
+ }
282
+ }
283
+ }
284
+ /** The listeners, ONCE per list and by delegation — rows come and go, the list stays. */
143
285
  function wireCatControls(el) {
144
- el.querySelectorAll('button[data-acat]').forEach((b) => {
145
- b.addEventListener('click', () => {
146
- const k = b.dataset.acat;
147
- if (!k)
148
- return;
149
- const state = ctx.getAudioCat();
150
- if (!state || !state[k])
151
- return;
152
- state[k].on = !state[k].on;
153
- ctx.setCatGain(k);
154
- b.classList.toggle('is-on', state[k].on);
155
- b.setAttribute('aria-pressed', String(state[k].on));
156
- refreshMarks(); // a marca acompanha a MUDANÇA, não só o redesenho — ver a nota em refreshMarks
157
- });
286
+ if (el.dataset.catsWired)
287
+ return;
288
+ el.dataset.catsWired = '1';
289
+ el.addEventListener('click', (ev) => {
290
+ const b = ev.target?.closest('button[data-acat]');
291
+ const k = b?.dataset.acat;
292
+ if (!b || !k)
293
+ return;
294
+ const state = ctx.getAudioCat();
295
+ if (!state || !state[k])
296
+ return;
297
+ state[k].on = !state[k].on;
298
+ ctx.setCatGain(k);
299
+ b.classList.toggle('is-on', state[k].on);
300
+ b.setAttribute('aria-pressed', String(state[k].on));
301
+ refreshMarks(); // the mark follows the CHANGE, not only the redraw — see the note in refreshMarks
158
302
  });
159
- el.querySelectorAll('input[data-avol]').forEach((s) => {
160
- s.addEventListener('input', () => {
161
- const k = s.dataset.avol;
162
- if (!k)
163
- return;
164
- const state = ctx.getAudioCat();
165
- if (!state || !state[k])
166
- return;
167
- state[k].vol = (+s.value) / 100;
168
- state[k].on = true;
169
- ctx.setCatGain(k);
170
- const bb = el.querySelector('button[data-acat="' + k + '"]');
171
- if (bb) {
172
- bb.classList.add('is-on');
173
- bb.setAttribute('aria-pressed', 'true');
174
- }
175
- refreshMarks();
176
- });
303
+ el.addEventListener('input', (ev) => {
304
+ const s = ev.target?.closest('input[data-avol]');
305
+ const k = s?.dataset.avol;
306
+ if (!s || !k)
307
+ return;
308
+ const state = ctx.getAudioCat();
309
+ if (!state || !state[k])
310
+ return;
311
+ state[k].vol = (+s.value) / 100;
312
+ state[k].on = true;
313
+ ctx.setCatGain(k);
314
+ const bb = el.querySelector('button[data-acat="' + k + '"]');
315
+ if (bb) {
316
+ bb.classList.add('is-on');
317
+ bb.setAttribute('aria-pressed', 'true');
318
+ }
319
+ refreshMarks();
177
320
  });
178
321
  }
179
322
  function renderCategoryList(sel, keys) {
@@ -181,10 +324,12 @@ export function initSettingsAudio(ctx) {
181
324
  const state = ctx.getAudioCat();
182
325
  if (!el || !state)
183
326
  return;
184
- el.innerHTML = catsListHTML(keys, ctx.audioCats, state);
327
+ mountCatRows(el, keys);
328
+ reflectCatRows(el, keys);
185
329
  wireCatControls(el);
186
- // A prosa volta para o rodapé depois de as linhas serem reconstruídas (CLAUDE.md §4, #109).
187
- ctx.fillExplain?.(ctx.$('#audio .overlay__card'));
330
+ // The prose goes back to the footer after the rows change (CLAUDE.md §4, #109) — in the card of WHOEVER holds
331
+ // this list: since ADR-0151 there are two panels, and the other one's footer is not this child's.
332
+ ctx.fillExplain?.(el.closest('.overlay__card'));
188
333
  }
189
334
  function renderNavSound() {
190
335
  renderCategoryList('#navsound-list', NAV_CATS);
@@ -193,113 +338,50 @@ export function initSettingsAudio(ctx) {
193
338
  if (m && state)
194
339
  m.value = String(navMasterVolume(state, NAV_CATS));
195
340
  }
196
- function reflectTts() {
197
- const b = ctx.$('#opt-tts');
198
- const state = ctx.getAudioCat();
199
- const on = !!(state && state.tts && state.tts.on);
200
- if (b) {
201
- ctx.toggleBtn(b, on);
202
- b.textContent = toggleLabel(on);
203
- }
204
- const e = ctx.$('#tts-engine');
205
- if (e)
206
- e.value = ctx.tts.getEngineSel();
207
- }
208
- /**
209
- * O alternador do ÍNDICE "6 de 10" (ADR-0044, item 3).
210
- *
211
- * Ele mora ao lado da narração por voz e não num painel de visual, porque é a NARRAÇÃO que ele muda: quem
212
- * desliga o índice está encurtando o que ouve a cada passo, e é aqui que essa pessoa vem quando o que ouve
213
- * incomoda.
214
- */
215
- function reflectMenuIndex() {
216
- const b = ctx.$('#opt-menuindex');
217
- if (b) {
218
- ctx.toggleBtn(b, state.menuIndexOn);
219
- b.textContent = toggleLabel(state.menuIndexOn);
220
- }
221
- }
222
- function populateTtsEngines() {
223
- const sel = ctx.$('#tts-engine');
224
- if (!sel || sel.dataset.filled)
225
- return;
226
- sel.dataset.filled = '1';
227
- opcoesDeMotor(ctx.tts.neuralDisponivel !== false).forEach(([v, l]) => {
228
- const o = document.createElement('option');
229
- o.value = v;
230
- o.textContent = t(l);
231
- sel.appendChild(o);
232
- });
233
- sel.value = ctx.tts.getEngineSel();
234
- }
235
- function populateTtsVoices() {
236
- const sel = ctx.$('#tts-voice');
237
- if (!sel)
238
- return;
239
- let voices = [];
240
- try {
241
- voices = (window.speechSynthesis && window.speechSynthesis.getVoices()) || [];
242
- }
243
- catch (e) { /* noop */ }
244
- const list = pickVoicesFor(voices, bcp47());
245
- sel.innerHTML = '';
246
- if (!list.length) {
247
- const o = document.createElement('option');
248
- o.textContent = t('audio.noSystemVoices');
249
- sel.appendChild(o);
250
- ctx.tts.setVoiceObj(null);
251
- return;
252
- }
253
- list.forEach((v) => {
254
- const o = document.createElement('option');
255
- o.value = v.name;
256
- o.textContent = voiceLabel(v);
257
- sel.appendChild(o);
258
- });
259
- const saved = ctx.store.get('incl_tts_voice', null);
260
- const pick = list.find((v) => v.name === saved) || list[0];
261
- sel.value = pick.name;
262
- ctx.tts.setVoiceObj(pick);
263
- }
264
- function hasEnumerateDevices() {
265
- return !!(navigator.mediaDevices && navigator.mediaDevices.enumerateDevices);
266
- }
267
- function hasAudioContextCtor() {
268
- const w = window;
269
- return typeof (w.AudioContext || w.webkitAudioContext) !== 'undefined';
270
- }
341
+ // 📌 Both questions are the HOST's (ADR-0227), and they stay TWO because they produce different sentences: the browser
342
+ // cannot do it, and there is no device at all, are not the same news to someone looking for their headphones.
343
+ const hasEnumerateDevices = () => ctx.audioOutputs.canList();
344
+ const hasAudioContextCtor = () => ctx.audioOutputs.canRoute();
271
345
  function renderSinks(devices) {
272
346
  const el = ctx.$('#audio-sinks');
273
347
  if (!el)
274
348
  return;
275
- el.innerHTML = '';
349
+ // 📌 The document comes from the list node, never from the global one — same shape as the rest of this file
350
+ // since the conversion to nodes, and it is what removes the reach to `document` without asking the contract
351
+ // for a new field (ADR-0221 step 7d).
352
+ const make = makerFor(el);
353
+ while (el.firstChild)
354
+ el.removeChild(el.firstChild);
276
355
  if (!devices.length) {
277
356
  const supported = sinksSupported(hasEnumerateDevices(), hasAudioContextCtor());
278
- el.innerHTML = '<p class="opt-hint">' +
279
- t(supported ? 'audio.sinksHint' : 'audio.sinksUnsupported') +
280
- '</p>';
357
+ // ⚠️ TWO SENTENCES, not one: «this browser CANNOT do it» and «there is no device yet» are different things to
358
+ // a child who is looking for their own headphones, and the dictionary has always had both.
359
+ const hint = make('p');
360
+ hint.className = 'opt-hint';
361
+ hint.textContent = t(supported ? 'audio.sinksHint' : 'audio.sinksUnsupported');
362
+ el.appendChild(hint);
281
363
  return;
282
364
  }
283
365
  const players = ctx.getPlayers();
284
366
  const n = Math.max(1, ctx.getNumPlayers());
285
367
  for (let i = 0; i < n; i++) {
286
368
  const p = players[i];
287
- const row = document.createElement('div');
369
+ const row = make('div');
288
370
  row.className = 'ctrl-row';
289
- const lbl = document.createElement('label');
371
+ const lbl = make('label');
290
372
  lbl.textContent = t('audio.playerN', { n: i + 1 });
291
- lbl.setAttribute('for', 'sink-p' + i);
292
- const sel = document.createElement('select');
373
+ lbl.htmlFor = 'sink-p' + i;
374
+ const sel = make('select');
293
375
  sel.className = 'vol';
294
376
  sel.id = 'sink-p' + i;
295
- const o0 = document.createElement('option');
377
+ const o0 = make('option');
296
378
  o0.value = '';
297
379
  o0.textContent = t('audio.sinkShared');
298
380
  sel.appendChild(o0);
299
381
  devices.forEach((d, k) => {
300
- const o = document.createElement('option');
382
+ const o = make('option');
301
383
  o.value = d.deviceId;
302
- o.textContent = sinkOptionLabel(d, k);
384
+ o.textContent = sinkOptionLabel(t, d, k);
303
385
  sel.appendChild(o);
304
386
  });
305
387
  sel.value = sinkSelectValue(p);
@@ -324,78 +406,83 @@ export function initSettingsAudio(ctx) {
324
406
  }
325
407
  }
326
408
  async function enumerateSinks() {
409
+ // without asking permission: only the outputs already known
327
410
  try {
328
- if (navigator.mediaDevices && navigator.mediaDevices.enumerateDevices) {
329
- const devs = await navigator.mediaDevices.enumerateDevices();
330
- audioDevices = devs.filter((d) => d.kind === 'audiooutput');
331
- }
411
+ audioDevices = [...await ctx.audioOutputs.list()];
332
412
  }
333
- catch (e) { /* sem pedir permissão: só lista as saídas já conhecidas */ }
413
+ catch (e) { /* the port refused; the list stays as it is */ }
334
414
  renderSinks(audioDevices);
335
415
  }
336
416
  async function detectAudioDevices() {
417
+ // ⚠️ THE LIST EMPTIES when detection fails, and `enumerateSinks` above does NOT empty it: there an error means "I
418
+ // could not ask", here it means "the child asked and the answer is none". The two sentences the panel draws next are
419
+ // different, and so are the two `catch`es.
337
420
  try {
338
- await navigator.mediaDevices.getUserMedia({ audio: true }).then((s) => s.getTracks().forEach((t) => t.stop())).catch(() => { });
339
- const devs = await navigator.mediaDevices.enumerateDevices();
340
- audioDevices = devs.filter((d) => d.kind === 'audiooutput');
421
+ audioDevices = [...await ctx.audioOutputs.detect()];
341
422
  }
342
423
  catch (e) {
343
424
  audioDevices = [];
344
425
  }
345
426
  renderSinks(audioDevices);
346
427
  }
347
- function reflectModoCego() {
428
+ function drawBlindMode() {
348
429
  const b = ctx.$('#opt-modocego');
349
430
  if (b) {
350
- ctx.toggleBtn(b, ctx.getModoCego());
351
- b.textContent = toggleLabel(ctx.getModoCego());
431
+ ctx.toggleBtn(b, ctx.getBlindMode());
432
+ b.textContent = toggleLabel(t, ctx.getBlindMode());
352
433
  }
353
434
  }
354
435
  function renderAudio() {
355
436
  reflectMaster();
356
437
  renderCategoryList('#audio-list', GEN_CATS);
357
438
  renderNavSound();
358
- reflectModoCego();
359
- reflectTts();
360
- populateTtsEngines();
361
- populateTtsVoices();
439
+ drawBlindMode();
440
+ voice.render();
362
441
  const cd = ctx.$('#cane-div');
363
442
  if (cd)
364
443
  cd.value = String(ctx.getCaneBlockDiv());
365
- void enumerateSinks(); // sem await no original: dispara e segue (lista assíncrona atualiza sozinha)
444
+ void enumerateSinks(); // not awaited: fire and carry on (the asynchronous list updates by itself)
366
445
  refreshMarks();
367
446
  }
368
447
  /**
369
- * A marca de "saiu do padrão" (ADR-0029).
448
+ * The left-the-default mark (ADR-0029).
370
449
  *
371
- * Chamada de dentro dos HANDLERS de mudança, e não só do `renderAudio()`. Pendurei-a primeiro no render, e
372
- * no jogo a marca não aparecia: mexer numa categoria atualiza aquela linha sozinha, sem redesenhar o painel.
373
- * O ADR-0029 já avisava disso — "pode envelhecer na tela se um painel esquecer de atualizar depois de uma
374
- * mudança" — e eu escrevi o aviso e caí nele na mesma tarde. A regra que sobra: a marca anda com quem
375
- * ESCREVE o valor, nunca com quem desenha.
450
+ * Called from inside the change HANDLERS, not only from `renderAudio()`: changing a category updates that row by
451
+ * itself, without redrawing the panel, so a mark hung only on render goes stale on screen — ADR-0029 warns about it.
452
+ * The rule: the mark travels with whoever WRITES the value, never with whoever draws.
376
453
  *
377
- * O recorte é o MESMO do reset deste menu, e isso não é economia —
378
- * é a regra: só pode ser marcado o que tem padrão em DEFAULTS/`defaultAudioCat`. O motor de voz e a saída
379
- * de áudio por jogador não têm, porque são escolha de DISPOSITIVO e não preferência restaurável; marcá-los
380
- * exigiria inventar uma segunda opinião sobre o que é "padrão" para um fone.
454
+ * The cut is the SAME as this menu's reset, and that is the rule, not thrift: only what has a default in
455
+ * DEFAULTS/`defaultAudioCat` can be marked. The voice engine and the per-player audio output have none, because they
456
+ * are a DEVICE choice and not a restorable preference; marking them would mean inventing a second opinion on what a
457
+ * headset's "default" is.
381
458
  */
382
459
  function refreshMarks() {
383
460
  const state = ctx.getAudioCat();
384
- const mudou = [];
385
- const marcar = (sel, changed) => {
386
- mudou.push(changed);
387
- markChanged(ctx.$(sel)?.closest('.ctrl-row') ?? null, changed);
461
+ const didChange = [];
462
+ const mark = (sel, changed) => {
463
+ didChange.push(changed);
464
+ markChanged(t, ctx.$(sel)?.closest('.ctrl-row') ?? null, changed);
388
465
  };
389
- marcar('#opt-modocego', ctx.getModoCego() !== DEFAULTS.modoCego);
390
- marcar('#cane-div', ctx.getCaneBlockDiv() !== DEFAULTS.caneBlockDiv);
466
+ mark('#opt-modocego', ctx.getBlindMode() !== DEFAULTS.blindMode);
467
+ mark('#cane-div', ctx.getCaneBlockDiv() !== DEFAULTS.caneBlockDiv);
468
+ // ⚠️ TWO MENU MARKS, one per panel: the audio panel's lit by a changed sonar would send the child searching the
469
+ // wrong panel.
470
+ const fromSound = [];
391
471
  for (const c of ctx.audioCats) {
392
472
  const d = defaultAudioCat(c.k);
393
473
  const a = state?.[c.k];
394
- marcar(`[data-acat="${c.k}"]`, !!a && (a.on !== d.on || a.vol !== d.vol));
474
+ const changedHere = !!a && (a.on !== d.on || a.vol !== d.vol);
475
+ if (GEN_CATS.includes(c.k)) {
476
+ fromSound.push(changedHere);
477
+ markChanged(t, ctx.$(`[data-acat="${c.k}"]`)?.closest('.ctrl-row') ?? null, changedHere);
478
+ }
479
+ else
480
+ mark(`[data-acat="${c.k}"]`, changedHere);
395
481
  }
396
- markMenuChanged(ctx.$('[data-act="audio"]'), mudou);
482
+ markMenuChanged(t, ctx.$('[data-act="audio"]'), didChange);
483
+ markMenuChanged(t, ctx.$('[data-act="som"]'), fromSound);
397
484
  }
398
- // ----- widgets estáticos (existem sempre no #audio; fiados UMA vez, nunca recriados por renderAudio) -----
485
+ // ----- static widgets (always present in #audio; wired ONCE, never recreated by renderAudio) -----
399
486
  const audioMasterBtn = ctx.$('#audio-master');
400
487
  if (audioMasterBtn)
401
488
  audioMasterBtn.addEventListener('click', () => {
@@ -424,25 +511,21 @@ export function initSettingsAudio(ctx) {
424
511
  renderNavSound();
425
512
  });
426
513
  /*
427
- * ⚠️ ESTE BOTÃO ERA O ÚNICO DESTE PAINEL QUE NÃO ANUNCIAVA. Medido em 2026-09-08: os cinco irmãos daqui
428
- * anunciam (som, TTS, divisor da bengala, índice de menu, saída de áudio) e o modo cego não — ele parecia
429
- * anunciar porque UM cartucho o fazia a partir do próprio `setModoCego`, e o painel herdava o efeito.
514
+ * ⚠️ THIS BUTTON ANNOUNCES, like its siblings (sound, TTS, cane spacing, menu index, audio output): with the engine's
515
+ * default setter (`setBlindModeValue`, which stores/persists/notifies and does NOT speak), a game that does not inject
516
+ * its own setter would otherwise get a mute button. A toggle that changes state without saying so is invisible to a
517
+ * screen-reader user.
430
518
  *
431
- * ⚠️ E ISSO PASSOU A EXPOR SILÊNCIO no mesmo dia: desde que o campo ganhou padrão da engine
432
- * (`setModoCegoValue`, que grava/persiste/avisa e NÃO fala), um jogo que não injecta o seu próprio setter
433
- * ficava com este botão mudo. Um alternador que muda estado sem o dizer é invisível para quem usa leitor de
434
- * tela — a mesma família de defeito que o `reflectTTS` e o `reflectModoCego` já custaram aqui.
435
- *
436
- * O anúncio pertence a QUEM É ACCIONADO, não ao setter: `core/state` diz que o setter faz três coisas e só
437
- * três. ⚠️ Consequência de lockstep, escrita para não se descobrir depois: quando o `game-platformer` subir
438
- * de versão, tem de TIRAR o `srSay` do `setModoCego` dele, senão a criança ouve o estado duas vezes.
519
+ * The announcement belongs to WHOEVER IS ACTIVATED, not to the setter: `core/state` says a setter does three things
520
+ * and only three. ⚠️ Lockstep consequence, written so it is not discovered later: a cartridge whose own `setBlindMode`
521
+ * also speaks must drop that, or the child hears the state twice.
439
522
  */
440
523
  const mcBtn = ctx.$('#opt-modocego');
441
524
  if (mcBtn) {
442
525
  mcBtn.addEventListener('click', () => {
443
- setModoCego(!ctx.getModoCego());
444
- reflectModoCego();
445
- ctx.srSay(t(ctx.getModoCego() ? 'sr.blind.on' : 'sr.blind.off'));
526
+ writeBlindMode(!ctx.getBlindMode());
527
+ drawBlindMode();
528
+ ctx.srSay(t(ctx.getBlindMode() ? 'sr.blind.on' : 'sr.blind.off'));
446
529
  });
447
530
  }
448
531
  const caneDivSel = ctx.$('#cane-div');
@@ -451,148 +534,67 @@ export function initSettingsAudio(ctx) {
451
534
  caneDivSel.addEventListener('change', () => {
452
535
  const div = parseCaneDiv(caneDivSel.value);
453
536
  ctx.setCaneBlockDiv(div);
454
- ctx.srSay(caneDivMessage(div));
537
+ ctx.srSay(caneDivMessage(t, div));
455
538
  });
456
539
  }
457
- const ttsBtn = ctx.$('#opt-tts');
458
- if (ttsBtn)
459
- ttsBtn.addEventListener('click', () => {
460
- const state = ctx.getAudioCat();
461
- if (!state)
462
- return;
463
- state.tts.on = !state.tts.on;
464
- ctx.setCatGain('tts');
465
- reflectTts();
466
- ctx.srSay(t(state.tts.on ? 'sr.audio.ttsOn' : 'sr.audio.ttsOff'));
467
- if (state.tts.on)
468
- ctx.tts.narrate(t('sr.audio.ttsOnSpoken'));
469
- });
470
- const idxBtn = ctx.$('#opt-menuindex');
471
- if (idxBtn)
472
- idxBtn.addEventListener('click', () => {
473
- state.setMenuIndexOnValue(!state.menuIndexOn);
474
- reflectMenuIndex();
475
- // O anúncio da troca NÃO leva índice: ele não é item de lista nenhuma, e um "1 de 1" aqui seria ruído
476
- // justamente no momento em que a criança está julgando se o ruído incomoda.
477
- ctx.srSay(t(state.menuIndexOn ? 'sr.menu.indexOn' : 'sr.menu.indexOff'));
478
- });
479
- const ttsEngSel = ctx.$('#tts-engine');
480
- if (ttsEngSel)
481
- ttsEngSel.addEventListener('change', () => {
482
- ctx.tts.setEngineSel(ttsEngSel.value);
483
- ctx.store.set('incl_tts_engine', ttsEngSel.value);
484
- if (ttsEngSel.value !== 'webspeech')
485
- ctx.tts.loadTTS();
486
- const opt = ttsEngSel.options[ttsEngSel.selectedIndex];
487
- ctx.tts.narrate(t('sr.audio.engineSet', { motor: opt ? opt.text : '' }));
488
- });
489
- const ttsVoiceSel = ctx.$('#tts-voice');
490
- if (ttsVoiceSel)
491
- ttsVoiceSel.addEventListener('change', () => {
492
- try {
493
- const vs = window.speechSynthesis.getVoices();
494
- ctx.tts.setVoiceObj(vs.find((v) => v.name === ttsVoiceSel.value) || null);
495
- ctx.store.set('incl_tts_voice', ttsVoiceSel.value);
496
- }
497
- catch (e) { /* noop */ }
498
- ctx.tts.narrate(t('sr.audio.voicePicked'));
499
- });
500
- const ttsTestBtn = ctx.$('#opt-tts-test');
501
- if (ttsTestBtn)
502
- ttsTestBtn.addEventListener('click', () => {
503
- const txt = t('audio.voiceSample');
504
- const engine = ctx.tts.getEngine();
505
- if (ctx.tts.getEngineSel() !== 'webspeech' && engine && engine.speak) {
506
- try {
507
- engine.speak(txt);
508
- }
509
- catch (e) { /* noop */ } // motor neural já carregado
510
- }
511
- else {
512
- try {
513
- const ss = window.speechSynthesis;
514
- if (ss) {
515
- ss.cancel();
516
- const u = new SpeechSynthesisUtterance(txt);
517
- u.lang = 'pt-BR';
518
- const vo = ctx.tts.getVoiceObj();
519
- if (vo)
520
- u.voice = vo;
521
- u.rate = 1;
522
- u.volume = 1;
523
- ss.speak(u);
524
- }
525
- }
526
- catch (e) { /* noop */ } // fallback audível (volume 1) + dispara download do neural
527
- if (ctx.tts.getEngineSel() !== 'webspeech')
528
- ctx.tts.loadTTS();
529
- }
530
- ctx.srSay(t('sr.audio.testingVoice'));
531
- });
532
- // ---- restaurar os padrões DESTE menu (ADR-0028) ----
533
540
  //
534
- // A regra dura é o escopo: este botão restaura o que o menu AUDITIVO contém e nada mais. Um reset que
535
- // alcançasse fora de si seria pior que a armadilha que ele existe para desfazer — a criança que desfaz um
536
- // ajuste de som e perde de quebra a configuração motora fica sem conseguir jogar, e sem entender por quê.
541
+ // The hard rule is scope: this button restores what the HEARING menu contains and nothing else. A reset that reached
542
+ // beyond itself would be worse than the trap it exists to undo — a child who undoes a sound setting and loses the
543
+ // mobility setup along with it can no longer play, and does not understand why.
537
544
  //
538
- // O que é deste menu: o modo cego, o espaçamento da bengala e as nove categorias do mixer. O motor de voz
539
- // e a saída de áudio por jogador NÃO entram — são escolha de dispositivo, não preferência restaurável, e
540
- // zerá-las tiraria da criança o fone que é dela numa sala compartilhada.
545
+ // What belongs to this menu: blind mode, the cane spacing and the mixer categories that are not the audio panel's.
546
+ // The voice engine and the per-player audio output do NOT — they are a device choice, not a restorable preference,
547
+ // and clearing them would take away the child's own headset in a shared room.
548
+ /** Resets the categories in `keys` (those that exist in the mixer) to their factory state. */
549
+ function resetCategories(keys) {
550
+ const state = ctx.getAudioCat();
551
+ if (!state)
552
+ return;
553
+ for (const k of keys) {
554
+ if (!state[k])
555
+ continue;
556
+ const d = defaultAudioCat(k);
557
+ state[k].on = d.on;
558
+ state[k].vol = d.vol;
559
+ ctx.setCatGain(k);
560
+ }
561
+ }
541
562
  const resetBtn = ctx.$('#audio-reset');
542
563
  if (resetBtn)
543
564
  resetBtn.addEventListener('click', () => {
544
- setModoCego(DEFAULTS.modoCego);
565
+ writeBlindMode(DEFAULTS.blindMode);
545
566
  ctx.setCaneBlockDiv(DEFAULTS.caneBlockDiv);
546
- const state = ctx.getAudioCat();
547
- if (state)
548
- for (const c of ctx.audioCats) {
549
- const d = defaultAudioCat(c.k);
550
- if (!state[c.k])
551
- continue;
552
- state[c.k].on = d.on;
553
- state[c.k].vol = d.vol;
554
- ctx.setCatGain(c.k);
555
- }
567
+ // 🔴 THIS MENU DOES NOT HOLD THE MUSIC (ADR-0151): resetting it here would reach beyond itself — the scope rule,
568
+ // above. Everything that is not a taste category belongs to this panel.
569
+ resetCategories(ctx.audioCats.map((c) => c.k).filter((k) => !GEN_CATS.includes(k)));
556
570
  renderAudio();
557
- reflectModoCego();
558
- reflectTts();
559
- reflectMenuIndex();
571
+ drawBlindMode();
560
572
  ctx.srSay(t('sr.audio.reset'));
561
573
  });
562
- try {
563
- if (window.speechSynthesis)
564
- window.speechSynthesis.onvoiceschanged = populateTtsVoices;
565
- }
566
- catch (e) { /* noop */ }
567
- const ttsVolEl = ctx.$('#tts-vol');
568
- if (ttsVolEl)
569
- ttsVolEl.addEventListener('input', () => {
570
- const state = ctx.getAudioCat();
571
- if (!state)
572
- return;
573
- state.tts.vol = (+ttsVolEl.value) / 100;
574
- state.tts.on = true;
575
- ctx.setCatGain('tts');
576
- reflectTts();
574
+ // The AUDIO panel's reset: the taste categories and nothing else.
575
+ const soundReset = ctx.$('#som-reset');
576
+ if (soundReset)
577
+ soundReset.addEventListener('click', () => {
578
+ resetCategories(GEN_CATS);
579
+ renderAudio();
580
+ ctx.srSay(t('sr.audio.reset'));
577
581
  });
578
582
  const audioDetectBtn = ctx.$('#audio-detect');
579
583
  if (audioDetectBtn)
580
584
  audioDetectBtn.addEventListener('click', () => { void detectAudioDevices(); });
581
- reflectMaster(); // estado inicial do botão/slider mestre, antes de qualquer abertura do painel
585
+ reflectMaster(); // initial state of the master button/slider, before the panel is ever opened
582
586
  /*
583
- * ⚠️ O PAINEL ASSINA O EVENTO, e isto não é uma ideia nova: é a decisão que o `core/state` já tinha
584
- * escrito ao lado do `setModoCegoValue` — «o setter faz três coisas e só três: grava, persiste, avisa. Os
585
- * efeitos … são reação, e quem reage assina o evento».
587
+ * ⚠️ THE PANEL SUBSCRIBES TO THE EVENT — the decision `core/state` writes next to `setBlindModeValue`: a setter stores,
588
+ * persists and notifies, and effects are reactions, subscribed by whoever reacts.
586
589
  *
587
- * Sem esta assinatura, um jogo que NÃO injecta o seu próprio `setModoCego` liga o modo cego pelo ícone da
588
- * barra e o botão `#opt-modocego` deste painel continua a dizer «Desligado», com `aria-pressed=false` — o
589
- * controlo a mentir o estado para o leitor de tela. É o gémeo exacto do defeito do `reflectTTS` que já está
590
- * registado no `ui/pause-icons`, e não vale a pena descobri-lo uma terceira vez.
590
+ * Without it, a game that does NOT inject its own `setBlindMode` switches blind mode on through the bar's icon and this
591
+ * panel's `#opt-modocego` keeps saying off, with `aria-pressed=false` — the control lying about the state to the screen
592
+ * reader.
591
593
  *
592
- * ⚠️ É seguro para quem JÁ reflecte a partir do seu próprio setter: reflectir é idempotente — relê o estado
593
- * e reescreve o botão. Um anúncio duplicado seria outra história, e por isso a assinatura NÃO anuncia: o
594
- * ícone da barra já diz `sr.icon.blindOn`/`Off` por si.
594
+ * ⚠️ It is safe for whoever ALREADY reflects from their own setter: reflecting is idempotent — it rereads the state and
595
+ * rewrites the button. A duplicated announcement would be another matter, which is why the subscription does NOT
596
+ * announce: the bar's icon already speaks for itself.
595
597
  */
596
- state.on('modoCego', () => { reflectModoCego(); });
597
- return { renderAudio, reflectModoCego, reflectTts };
598
+ ctx.on('blindMode', () => { drawBlindMode(); }); // through the root's door: released by its `dispose()`
599
+ return { renderAudio, reflectBlindMode: drawBlindMode, reflectTts: voice.reflectTts };
598
600
  }