@the-inclusionist/engine 9.0.0 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (532) hide show
  1. package/README.md +120 -78
  2. package/app/css/style.css +761 -377
  3. package/app/public/vendor/fonts/playwrite-cu.woff2 +0 -0
  4. package/app/public/vendor/fonts/playwrite-es-deco.woff2 +0 -0
  5. package/app/public/vendor/fonts/playwrite-es.woff2 +0 -0
  6. package/app/public/vendor/fonts/playwrite-gb-j.woff2 +0 -0
  7. package/app/public/vendor/fonts/playwrite-gb-s.woff2 +0 -0
  8. package/app/public/vendor/fonts/playwrite-pe.woff2 +0 -0
  9. package/app/public/vendor/fonts/playwrite-pt.woff2 +0 -0
  10. package/app/public/vendor/fonts-licences/NOTICE.txt +166 -0
  11. package/app/public/vendor/fonts-licences/OFL-1.1.txt +86 -0
  12. package/app/public/vendor/fonts.css +36 -84
  13. package/dist-pkg/boot/create-game.d.ts +499 -219
  14. package/dist-pkg/boot/create-game.js +3944 -562
  15. package/dist-pkg/core/a11y-sr.d.ts +25 -3
  16. package/dist-pkg/core/a11y-sr.js +38 -19
  17. package/dist-pkg/core/accessible-label.d.ts +17 -0
  18. package/dist-pkg/core/accessible-label.js +37 -0
  19. package/dist-pkg/core/accommodation-subjects.d.ts +26 -0
  20. package/dist-pkg/core/accommodation-subjects.js +32 -0
  21. package/dist-pkg/core/accommodations.d.ts +125 -0
  22. package/dist-pkg/core/accommodations.js +195 -0
  23. package/dist-pkg/core/actions.d.ts +108 -55
  24. package/dist-pkg/core/actions.js +122 -71
  25. package/dist-pkg/core/calm-mode.d.ts +31 -0
  26. package/dist-pkg/core/calm-mode.js +47 -0
  27. package/dist-pkg/core/camera-cycle.d.ts +15 -0
  28. package/dist-pkg/core/camera-cycle.js +7 -0
  29. package/dist-pkg/core/caption-duration.d.ts +15 -0
  30. package/dist-pkg/core/caption-duration.js +19 -0
  31. package/dist-pkg/core/cartridge-problems.d.ts +34 -0
  32. package/dist-pkg/core/cartridge-problems.js +53 -0
  33. package/dist-pkg/core/constants.d.ts +0 -54
  34. package/dist-pkg/core/constants.js +9 -76
  35. package/dist-pkg/core/contract.d.ts +142 -164
  36. package/dist-pkg/core/contract.js +222 -208
  37. package/dist-pkg/core/dom-query.d.ts +4 -4
  38. package/dist-pkg/core/dom-query.js +12 -20
  39. package/dist-pkg/core/entity.d.ts +62 -77
  40. package/dist-pkg/core/entity.js +18 -32
  41. package/dist-pkg/core/escape-html.d.ts +9 -10
  42. package/dist-pkg/core/escape-html.js +16 -20
  43. package/dist-pkg/core/flash-threshold.d.ts +17 -0
  44. package/dist-pkg/core/flash-threshold.js +113 -0
  45. package/dist-pkg/core/game-speed.d.ts +11 -0
  46. package/dist-pkg/core/game-speed.js +16 -0
  47. package/dist-pkg/core/genres.d.ts +20 -0
  48. package/dist-pkg/core/genres.js +44 -0
  49. package/dist-pkg/core/i18n.d.ts +71 -60
  50. package/dist-pkg/core/i18n.js +186 -200
  51. package/dist-pkg/core/loop.d.ts +17 -6
  52. package/dist-pkg/core/loop.js +26 -26
  53. package/dist-pkg/core/pause-icon-catalogue.d.ts +15 -0
  54. package/dist-pkg/core/pause-icon-catalogue.js +54 -0
  55. package/dist-pkg/core/ring.d.ts +17 -0
  56. package/dist-pkg/core/ring.js +29 -0
  57. package/dist-pkg/core/rng.d.ts +7 -12
  58. package/dist-pkg/core/rng.js +23 -53
  59. package/dist-pkg/core/route.d.ts +26 -26
  60. package/dist-pkg/core/route.js +126 -121
  61. package/dist-pkg/core/scenes.d.ts +37 -40
  62. package/dist-pkg/core/scenes.js +41 -43
  63. package/dist-pkg/core/screens.d.ts +4 -4
  64. package/dist-pkg/core/screens.js +11 -16
  65. package/dist-pkg/core/session-clock.d.ts +23 -0
  66. package/dist-pkg/core/session-clock.js +37 -0
  67. package/dist-pkg/core/setting-defaults.d.ts +61 -0
  68. package/dist-pkg/core/setting-defaults.js +66 -0
  69. package/dist-pkg/core/speech-rate.d.ts +29 -0
  70. package/dist-pkg/core/speech-rate.js +47 -0
  71. package/dist-pkg/core/state.d.ts +118 -118
  72. package/dist-pkg/core/state.js +322 -297
  73. package/dist-pkg/core/visual-cycles.d.ts +30 -0
  74. package/dist-pkg/core/visual-cycles.js +52 -0
  75. package/dist-pkg/core/visual-state.d.ts +13 -0
  76. package/dist-pkg/core/visual-state.js +6 -0
  77. package/dist-pkg/educational/activities-registry.d.ts +1 -1
  78. package/dist-pkg/educational/activities-registry.js +9 -8
  79. package/dist-pkg/educational/adaptive-engine.d.ts +11 -11
  80. package/dist-pkg/educational/adaptive-engine.js +17 -17
  81. package/dist-pkg/educational/segment-bar.d.ts +12 -12
  82. package/dist-pkg/educational/segment-bar.js +9 -9
  83. package/dist-pkg/i18n/en.js +324 -223
  84. package/dist-pkg/i18n/es.js +323 -223
  85. package/dist-pkg/i18n/pt.js +348 -226
  86. package/dist-pkg/input/default-bindings.d.ts +37 -38
  87. package/dist-pkg/input/default-bindings.js +127 -144
  88. package/dist-pkg/input/devices.d.ts +7 -7
  89. package/dist-pkg/input/devices.js +17 -16
  90. package/dist-pkg/input/edges.d.ts +23 -32
  91. package/dist-pkg/input/edges.js +23 -28
  92. package/dist-pkg/input/empathy-filter.d.ts +22 -0
  93. package/dist-pkg/input/empathy-filter.js +34 -0
  94. package/dist-pkg/input/face-map.d.ts +96 -0
  95. package/dist-pkg/input/face-map.js +255 -0
  96. package/dist-pkg/input/face-signals.d.ts +40 -0
  97. package/dist-pkg/input/face-signals.js +48 -0
  98. package/dist-pkg/input/gamepad.d.ts +195 -145
  99. package/dist-pkg/input/gamepad.js +383 -556
  100. package/dist-pkg/input/gaze-cycle.d.ts +64 -0
  101. package/dist-pkg/input/gaze-cycle.js +133 -0
  102. package/dist-pkg/input/gaze-relative.d.ts +68 -0
  103. package/dist-pkg/input/gaze-relative.js +142 -0
  104. package/dist-pkg/input/hand-map.d.ts +28 -0
  105. package/dist-pkg/input/hand-map.js +142 -0
  106. package/dist-pkg/input/input-cooldown.d.ts +17 -0
  107. package/dist-pkg/input/input-cooldown.js +39 -0
  108. package/dist-pkg/input/key-default.d.ts +30 -0
  109. package/dist-pkg/input/key-default.js +60 -0
  110. package/dist-pkg/input/keyboard-runtime.d.ts +15 -21
  111. package/dist-pkg/input/keyboard-runtime.js +23 -27
  112. package/dist-pkg/input/keyboard.d.ts +68 -63
  113. package/dist-pkg/input/keyboard.js +83 -105
  114. package/dist-pkg/input/keydown.d.ts +123 -137
  115. package/dist-pkg/input/keydown.js +284 -289
  116. package/dist-pkg/input/latch-edge.d.ts +33 -21
  117. package/dist-pkg/input/latch-edge.js +34 -35
  118. package/dist-pkg/input/latch-scope.d.ts +51 -50
  119. package/dist-pkg/input/latch-scope.js +61 -82
  120. package/dist-pkg/input/latch-store.d.ts +26 -30
  121. package/dist-pkg/input/latch-store.js +42 -50
  122. package/dist-pkg/input/latch-sync.d.ts +29 -29
  123. package/dist-pkg/input/latch-sync.js +25 -25
  124. package/dist-pkg/input/latch.d.ts +14 -17
  125. package/dist-pkg/input/latch.js +29 -37
  126. package/dist-pkg/input/pad-defaults.d.ts +13 -16
  127. package/dist-pkg/input/pad-defaults.js +21 -28
  128. package/dist-pkg/input/pad-reading.d.ts +76 -0
  129. package/dist-pkg/input/pad-reading.js +169 -0
  130. package/dist-pkg/input/pad-wizard.d.ts +99 -0
  131. package/dist-pkg/input/pad-wizard.js +256 -0
  132. package/dist-pkg/input/pointer-space.d.ts +19 -20
  133. package/dist-pkg/input/pointer-space.js +27 -32
  134. package/dist-pkg/input/pointer.d.ts +39 -40
  135. package/dist-pkg/input/pointer.js +42 -45
  136. package/dist-pkg/input/state.d.ts +101 -89
  137. package/dist-pkg/input/state.js +54 -134
  138. package/dist-pkg/input/switch-scan.d.ts +59 -0
  139. package/dist-pkg/input/switch-scan.js +67 -0
  140. package/dist-pkg/input/synthetic-source.d.ts +42 -0
  141. package/dist-pkg/input/synthetic-source.js +57 -0
  142. package/dist-pkg/input/touch-bindings.d.ts +127 -116
  143. package/dist-pkg/input/touch-bindings.js +158 -173
  144. package/dist-pkg/input/touch.d.ts +147 -72
  145. package/dist-pkg/input/touch.js +362 -126
  146. package/dist-pkg/input/transport-in-use.d.ts +107 -0
  147. package/dist-pkg/input/transport-in-use.js +130 -0
  148. package/dist-pkg/input/transports.d.ts +90 -117
  149. package/dist-pkg/input/transports.js +104 -106
  150. package/dist-pkg/input/virtual-controller.d.ts +82 -0
  151. package/dist-pkg/input/virtual-controller.js +69 -0
  152. package/dist-pkg/input/vocabulary-migration.d.ts +50 -49
  153. package/dist-pkg/input/vocabulary-migration.js +80 -79
  154. package/dist-pkg/input/voice-map.d.ts +59 -0
  155. package/dist-pkg/input/voice-map.js +123 -0
  156. package/dist-pkg/platform/audio-ambient.d.ts +13 -2
  157. package/dist-pkg/platform/audio-ambient.js +14 -14
  158. package/dist-pkg/platform/audio-earcons.d.ts +20 -19
  159. package/dist-pkg/platform/audio-earcons.js +51 -42
  160. package/dist-pkg/platform/audio-jingles.js +9 -10
  161. package/dist-pkg/platform/audio-mixer.d.ts +14 -2
  162. package/dist-pkg/platform/audio-mixer.js +31 -39
  163. package/dist-pkg/platform/audio-sonar.d.ts +49 -152
  164. package/dist-pkg/platform/audio-sonar.js +124 -302
  165. package/dist-pkg/platform/audio.d.ts +62 -19
  166. package/dist-pkg/platform/audio.js +193 -168
  167. package/dist-pkg/platform/choose-by-voice.d.ts +70 -0
  168. package/dist-pkg/platform/choose-by-voice.js +157 -0
  169. package/dist-pkg/platform/flash-sampler.d.ts +19 -0
  170. package/dist-pkg/platform/flash-sampler.js +79 -0
  171. package/dist-pkg/platform/font-library.d.ts +77 -0
  172. package/dist-pkg/platform/font-library.js +98 -0
  173. package/dist-pkg/platform/font-library.json +721 -0
  174. package/dist-pkg/platform/heavy-catalogue.d.ts +77 -0
  175. package/dist-pkg/platform/heavy-catalogue.js +256 -0
  176. package/dist-pkg/platform/heavy-mirror.d.ts +20 -0
  177. package/dist-pkg/platform/heavy-mirror.js +73 -0
  178. package/dist-pkg/platform/heavy.d.ts +136 -0
  179. package/dist-pkg/platform/heavy.js +324 -0
  180. package/dist-pkg/platform/interruptible-speech.d.ts +23 -16
  181. package/dist-pkg/platform/interruptible-speech.js +77 -54
  182. package/dist-pkg/platform/kokoro-port.d.ts +27 -0
  183. package/dist-pkg/platform/kokoro-port.js +50 -0
  184. package/dist-pkg/platform/kokoro-runtime.d.ts +20 -0
  185. package/dist-pkg/platform/kokoro-runtime.js +52 -0
  186. package/dist-pkg/platform/kokoro.d.ts +70 -0
  187. package/dist-pkg/platform/kokoro.js +133 -0
  188. package/dist-pkg/platform/listener-scope.d.ts +12 -0
  189. package/dist-pkg/platform/listener-scope.js +90 -0
  190. package/dist-pkg/platform/locale-host.d.ts +14 -0
  191. package/dist-pkg/platform/locale-host.js +45 -0
  192. package/dist-pkg/platform/microphone.d.ts +51 -0
  193. package/dist-pkg/platform/microphone.js +118 -0
  194. package/dist-pkg/platform/onnx-runtime.d.ts +34 -0
  195. package/dist-pkg/platform/onnx-runtime.js +42 -0
  196. package/dist-pkg/platform/reading-in-worker.d.ts +64 -0
  197. package/dist-pkg/platform/reading-in-worker.js +125 -0
  198. package/dist-pkg/platform/reading-model.d.ts +94 -0
  199. package/dist-pkg/platform/reading-model.js +272 -0
  200. package/dist-pkg/platform/reading-runtime.d.ts +24 -0
  201. package/dist-pkg/platform/reading-runtime.js +164 -0
  202. package/dist-pkg/platform/reading-worker.d.ts +41 -0
  203. package/dist-pkg/platform/reading-worker.js +58 -0
  204. package/dist-pkg/platform/reading.d.ts +80 -0
  205. package/dist-pkg/platform/reading.js +136 -0
  206. package/dist-pkg/platform/speech-recognition.d.ts +60 -0
  207. package/dist-pkg/platform/speech-recognition.js +104 -0
  208. package/dist-pkg/platform/speech.d.ts +23 -1
  209. package/dist-pkg/platform/speech.js +29 -18
  210. package/dist-pkg/platform/storage-keys.d.ts +96 -0
  211. package/dist-pkg/platform/storage-keys.js +106 -0
  212. package/dist-pkg/platform/storage.d.ts +46 -111
  213. package/dist-pkg/platform/storage.js +104 -169
  214. package/dist-pkg/platform/tts.d.ts +85 -39
  215. package/dist-pkg/platform/tts.js +447 -127
  216. package/dist-pkg/platform/vision-loop.d.ts +23 -0
  217. package/dist-pkg/platform/vision-loop.js +56 -0
  218. package/dist-pkg/platform/vision.d.ts +132 -0
  219. package/dist-pkg/platform/vision.js +117 -0
  220. package/dist-pkg/platform/voice-listener.d.ts +40 -0
  221. package/dist-pkg/platform/voice-listener.js +88 -0
  222. package/dist-pkg/platform/voice-plan.d.ts +15 -98
  223. package/dist-pkg/platform/voice-plan.js +56 -143
  224. package/dist-pkg/platform/vosk-runtime.d.ts +96 -0
  225. package/dist-pkg/platform/vosk-runtime.js +151 -0
  226. package/dist-pkg/platform/vosk-vocabulary.d.ts +6 -0
  227. package/dist-pkg/platform/vosk-vocabulary.js +178 -0
  228. package/dist-pkg/render/canvas.d.ts +13 -7
  229. package/dist-pkg/render/canvas.js +13 -13
  230. package/dist-pkg/render/crt.d.ts +48 -12
  231. package/dist-pkg/render/crt.js +78 -80
  232. package/dist-pkg/render/cvd-matrices.d.ts +22 -18
  233. package/dist-pkg/render/cvd-matrices.js +37 -38
  234. package/dist-pkg/render/hc-role-data.d.ts +7 -7
  235. package/dist-pkg/render/hc-role-data.js +14 -14
  236. package/dist-pkg/render/high-contrast.d.ts +67 -56
  237. package/dist-pkg/render/high-contrast.js +226 -217
  238. package/dist-pkg/render/low-vision-drawing.d.ts +5 -0
  239. package/dist-pkg/render/low-vision-drawing.js +46 -0
  240. package/dist-pkg/render/lq-filter.d.ts +32 -20
  241. package/dist-pkg/render/lq-filter.js +59 -65
  242. package/dist-pkg/render/port.d.ts +86 -88
  243. package/dist-pkg/render/port.js +17 -23
  244. package/dist-pkg/render/screen-pipeline.d.ts +42 -42
  245. package/dist-pkg/render/screen-pipeline.js +80 -87
  246. package/dist-pkg/render/sprite-fx.d.ts +2 -1
  247. package/dist-pkg/render/sprite-fx.js +16 -14
  248. package/dist-pkg/render/viewports.d.ts +20 -15
  249. package/dist-pkg/render/viewports.js +83 -114
  250. package/dist-pkg/render/viz-axes-labels.d.ts +47 -0
  251. package/dist-pkg/render/viz-axes-labels.js +88 -0
  252. package/dist-pkg/render/viz-axes.d.ts +89 -86
  253. package/dist-pkg/render/viz-axes.js +128 -116
  254. package/dist-pkg/render/viz-modes.d.ts +28 -29
  255. package/dist-pkg/render/viz-modes.js +40 -45
  256. package/dist-pkg/render/viz-refusal.d.ts +32 -0
  257. package/dist-pkg/render/viz-refusal.js +57 -0
  258. package/dist-pkg/render/viz-setters.d.ts +104 -66
  259. package/dist-pkg/render/viz-setters.js +182 -191
  260. package/dist-pkg/ui/aac-sets.d.ts +45 -0
  261. package/dist-pkg/ui/aac-sets.js +41 -0
  262. package/dist-pkg/ui/audio-choices.d.ts +68 -0
  263. package/dist-pkg/ui/audio-choices.js +98 -0
  264. package/dist-pkg/ui/audio-rows-that-apply.d.ts +12 -0
  265. package/dist-pkg/ui/audio-rows-that-apply.js +43 -0
  266. package/dist-pkg/ui/camera-control.d.ts +53 -0
  267. package/dist-pkg/ui/camera-control.js +48 -0
  268. package/dist-pkg/ui/changed-mark.d.ts +9 -8
  269. package/dist-pkg/ui/changed-mark.js +22 -53
  270. package/dist-pkg/ui/control-choices.d.ts +37 -0
  271. package/dist-pkg/ui/control-choices.js +84 -0
  272. package/dist-pkg/ui/debug-panel.d.ts +59 -43
  273. package/dist-pkg/ui/debug-panel.js +68 -69
  274. package/dist-pkg/ui/declared-words.d.ts +21 -0
  275. package/dist-pkg/ui/declared-words.js +66 -0
  276. package/dist-pkg/ui/dom.d.ts +10 -14
  277. package/dist-pkg/ui/dom.js +10 -47
  278. package/dist-pkg/ui/drawing-problems.d.ts +25 -0
  279. package/dist-pkg/ui/drawing-problems.js +94 -0
  280. package/dist-pkg/ui/eye-control.d.ts +36 -0
  281. package/dist-pkg/ui/eye-control.js +178 -0
  282. package/dist-pkg/ui/face-control.d.ts +25 -0
  283. package/dist-pkg/ui/face-control.js +183 -0
  284. package/dist-pkg/ui/focus-trap.d.ts +39 -49
  285. package/dist-pkg/ui/focus-trap.js +56 -65
  286. package/dist-pkg/ui/fonts.d.ts +157 -68
  287. package/dist-pkg/ui/fonts.js +305 -115
  288. package/dist-pkg/ui/footer-scroll-driver.d.ts +14 -0
  289. package/dist-pkg/ui/footer-scroll-driver.js +201 -0
  290. package/dist-pkg/ui/footer-scroll.d.ts +38 -0
  291. package/dist-pkg/ui/footer-scroll.js +55 -0
  292. package/dist-pkg/ui/game-options.d.ts +47 -0
  293. package/dist-pkg/ui/game-options.js +158 -0
  294. package/dist-pkg/ui/gaze-overlay.d.ts +64 -0
  295. package/dist-pkg/ui/gaze-overlay.js +165 -0
  296. package/dist-pkg/ui/hand-control.d.ts +25 -0
  297. package/dist-pkg/ui/hand-control.js +143 -0
  298. package/dist-pkg/ui/help-panel.d.ts +87 -0
  299. package/dist-pkg/ui/help-panel.js +242 -0
  300. package/dist-pkg/ui/hud-bands.d.ts +62 -0
  301. package/dist-pkg/ui/hud-bands.js +146 -0
  302. package/dist-pkg/ui/hud-row.d.ts +34 -0
  303. package/dist-pkg/ui/hud-row.js +80 -0
  304. package/dist-pkg/ui/hud.d.ts +73 -73
  305. package/dist-pkg/ui/hud.js +94 -98
  306. package/dist-pkg/ui/item-announcement.d.ts +13 -12
  307. package/dist-pkg/ui/item-announcement.js +25 -26
  308. package/dist-pkg/ui/layout.d.ts +103 -28
  309. package/dist-pkg/ui/layout.js +117 -99
  310. package/dist-pkg/ui/libras-avatar-clip.d.ts +30 -0
  311. package/dist-pkg/ui/libras-avatar-clip.js +86 -0
  312. package/dist-pkg/ui/libras-avatar-load.d.ts +52 -0
  313. package/dist-pkg/ui/libras-avatar-load.js +134 -0
  314. package/dist-pkg/ui/libras-avatar-plan.d.ts +102 -0
  315. package/dist-pkg/ui/libras-avatar-plan.js +211 -0
  316. package/dist-pkg/ui/libras-avatar-player.d.ts +28 -0
  317. package/dist-pkg/ui/libras-avatar-player.js +226 -0
  318. package/dist-pkg/ui/libras-avatar-stage.d.ts +37 -0
  319. package/dist-pkg/ui/libras-avatar-stage.js +208 -0
  320. package/dist-pkg/ui/libras-glosses.d.ts +28 -0
  321. package/dist-pkg/ui/libras-glosses.js +161 -0
  322. package/dist-pkg/ui/locale-flags.d.ts +16 -0
  323. package/dist-pkg/ui/locale-flags.js +30 -0
  324. package/dist-pkg/ui/loop-crash.d.ts +17 -17
  325. package/dist-pkg/ui/loop-crash.js +33 -62
  326. package/dist-pkg/ui/menu-intent.d.ts +61 -0
  327. package/dist-pkg/ui/menu-intent.js +88 -0
  328. package/dist-pkg/ui/menu-items.d.ts +20 -0
  329. package/dist-pkg/ui/menu-items.js +51 -0
  330. package/dist-pkg/ui/menu-nav.d.ts +98 -92
  331. package/dist-pkg/ui/menu-nav.js +398 -286
  332. package/dist-pkg/ui/mobility-choices.d.ts +26 -0
  333. package/dist-pkg/ui/mobility-choices.js +41 -0
  334. package/dist-pkg/ui/motion-choices.d.ts +31 -0
  335. package/dist-pkg/ui/motion-choices.js +68 -0
  336. package/dist-pkg/ui/motion-scene.d.ts +24 -19
  337. package/dist-pkg/ui/motion-scene.js +29 -61
  338. package/dist-pkg/ui/mount-panel.d.ts +81 -0
  339. package/dist-pkg/ui/mount-panel.js +61 -0
  340. package/dist-pkg/ui/panel-shell.d.ts +65 -31
  341. package/dist-pkg/ui/panel-shell.js +92 -66
  342. package/dist-pkg/ui/panel-widgets.d.ts +150 -0
  343. package/dist-pkg/ui/panel-widgets.js +294 -0
  344. package/dist-pkg/ui/pause-buttons.d.ts +35 -0
  345. package/dist-pkg/ui/pause-buttons.js +66 -0
  346. package/dist-pkg/ui/pause-icons.d.ts +261 -347
  347. package/dist-pkg/ui/pause-icons.js +671 -601
  348. package/dist-pkg/ui/pause-markup.d.ts +86 -0
  349. package/dist-pkg/ui/pause-markup.js +125 -0
  350. package/dist-pkg/ui/reach-notice.d.ts +22 -23
  351. package/dist-pkg/ui/reach-notice.js +55 -58
  352. package/dist-pkg/ui/scan-overlay.d.ts +23 -0
  353. package/dist-pkg/ui/scan-overlay.js +50 -0
  354. package/dist-pkg/ui/screen-text.d.ts +25 -0
  355. package/dist-pkg/ui/screen-text.js +152 -0
  356. package/dist-pkg/ui/session-clock.d.ts +32 -0
  357. package/dist-pkg/ui/session-clock.js +67 -0
  358. package/dist-pkg/ui/settings-aac.d.ts +70 -0
  359. package/dist-pkg/ui/settings-aac.js +208 -0
  360. package/dist-pkg/ui/settings-audio.d.ts +97 -114
  361. package/dist-pkg/ui/settings-audio.js +493 -425
  362. package/dist-pkg/ui/settings-controls.d.ts +53 -96
  363. package/dist-pkg/ui/settings-controls.js +143 -215
  364. package/dist-pkg/ui/settings-empathy.d.ts +39 -30
  365. package/dist-pkg/ui/settings-empathy.js +53 -60
  366. package/dist-pkg/ui/settings-mobility.d.ts +192 -0
  367. package/dist-pkg/ui/settings-mobility.js +332 -0
  368. package/dist-pkg/ui/settings-motion.d.ts +91 -70
  369. package/dist-pkg/ui/settings-motion.js +287 -198
  370. package/dist-pkg/ui/settings-panel.d.ts +58 -45
  371. package/dist-pkg/ui/settings-panel.js +189 -94
  372. package/dist-pkg/ui/settings-typo.d.ts +29 -78
  373. package/dist-pkg/ui/settings-typo.js +158 -129
  374. package/dist-pkg/ui/settings-visual.d.ts +29 -87
  375. package/dist-pkg/ui/settings-visual.js +240 -193
  376. package/dist-pkg/ui/shell.d.ts +112 -135
  377. package/dist-pkg/ui/shell.js +148 -217
  378. package/dist-pkg/ui/simulation-list.d.ts +27 -0
  379. package/dist-pkg/ui/simulation-list.js +76 -0
  380. package/dist-pkg/ui/simulation-over-the-world.d.ts +23 -0
  381. package/dist-pkg/ui/simulation-over-the-world.js +131 -0
  382. package/dist-pkg/ui/simulation-refusal.d.ts +5 -31
  383. package/dist-pkg/ui/simulation-refusal.js +8 -56
  384. package/dist-pkg/ui/switchable-control.d.ts +11 -0
  385. package/dist-pkg/ui/switchable-control.js +19 -0
  386. package/dist-pkg/ui/title.d.ts +3 -12
  387. package/dist-pkg/ui/title.js +49 -18
  388. package/dist-pkg/ui/top-band.d.ts +26 -0
  389. package/dist-pkg/ui/top-band.js +96 -0
  390. package/dist-pkg/ui/typo-choices.d.ts +71 -0
  391. package/dist-pkg/ui/typo-choices.js +85 -0
  392. package/dist-pkg/ui/visual-axes-panel.d.ts +11 -47
  393. package/dist-pkg/ui/visual-axes-panel.js +12 -94
  394. package/dist-pkg/ui/visual-choices.d.ts +77 -0
  395. package/dist-pkg/ui/visual-choices.js +107 -0
  396. package/dist-pkg/ui/vlibras.d.ts +77 -10
  397. package/dist-pkg/ui/vlibras.js +130 -81
  398. package/dist-pkg/ui/voice-control.d.ts +68 -0
  399. package/dist-pkg/ui/voice-control.js +276 -0
  400. package/dist-pkg/ui/voice-settings.d.ts +95 -0
  401. package/dist-pkg/ui/voice-settings.js +352 -0
  402. package/dist-pkg/ui/where-the-child-is.d.ts +31 -0
  403. package/dist-pkg/ui/where-the-child-is.js +70 -0
  404. package/docs/CREDITS.md +300 -44
  405. package/docs/LICENSES.md +189 -145
  406. package/package.json +51 -27
  407. package/scripts/check-cartridge.mjs +78 -0
  408. package/scripts/game-build.d.mts +25 -0
  409. package/scripts/game-build.mjs +144 -0
  410. package/scripts/heavy-into-the-delivery.mjs +388 -0
  411. package/scripts/libras-avatar.json +680 -0
  412. package/scripts/libras-avatar.mjs +246 -0
  413. package/scripts/libras-glosses/gloss.py +95 -0
  414. package/scripts/libras-glosses/pyproject.toml +32 -0
  415. package/scripts/libras-glosses/uv.lock +781 -0
  416. package/scripts/libras-glosses.mjs +351 -0
  417. package/scripts/licences/Apache-2.0.txt +176 -0
  418. package/scripts/licences/GPL-3.0.txt +674 -0
  419. package/scripts/licences/MIT.txt +17 -0
  420. package/scripts/licences/OFL-1.1.txt +86 -0
  421. package/scripts/licences/UFL-1.0.txt +96 -0
  422. package/scripts/licences/fonts.mjs +389 -0
  423. package/scripts/licences/third-party.mjs +301 -0
  424. package/scripts/licences/vosk-browser.NOTICE.txt +268 -0
  425. package/app/public/vendor/fonts/comicneue-400.woff2 +0 -0
  426. package/app/public/vendor/fonts/comicneue-700.woff2 +0 -0
  427. package/app/public/vendor/fonts/fondamento-400-ext.woff2 +0 -0
  428. package/app/public/vendor/fonts/fondamento-400.woff2 +0 -0
  429. package/app/public/vendor/fonts/inter-400.woff2 +0 -0
  430. package/app/public/vendor/fonts/inter-700.woff2 +0 -0
  431. package/app/public/vendor/fonts/lato-400.woff2 +0 -0
  432. package/app/public/vendor/fonts/lato-700.woff2 +0 -0
  433. package/app/public/vendor/fonts/literata-400.woff2 +0 -0
  434. package/app/public/vendor/fonts/literata-700.woff2 +0 -0
  435. package/app/public/vendor/fonts/newsreader-400.woff2 +0 -0
  436. package/app/public/vendor/fonts/newsreader-700.woff2 +0 -0
  437. package/app/public/vendor/fonts/opendyslexic-400.woff2 +0 -0
  438. package/app/public/vendor/fonts/opensans-400.woff2 +0 -0
  439. package/app/public/vendor/fonts/opensans-700.woff2 +0 -0
  440. package/app/public/vendor/fonts/pinyon-400.woff2 +0 -0
  441. package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
  442. package/app/public/vendor/fonts/quattro-400.woff2 +0 -0
  443. package/app/public/vendor/fonts/quattro-700.woff2 +0 -0
  444. package/app/public/vendor/fonts/sourcesans-400.woff2 +0 -0
  445. package/app/public/vendor/fonts/sourcesans-700.woff2 +0 -0
  446. package/app/public/vendor/fonts/sourceserif-400.woff2 +0 -0
  447. package/app/public/vendor/fonts/sourceserif-700.woff2 +0 -0
  448. package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
  449. package/dist-pkg/core/anel.d.ts +0 -17
  450. package/dist-pkg/core/anel.js +0 -31
  451. package/dist-pkg/core/collision.d.ts +0 -21
  452. package/dist-pkg/core/collision.js +0 -62
  453. package/dist-pkg/core/layers.d.ts +0 -63
  454. package/dist-pkg/core/layers.js +0 -95
  455. package/dist-pkg/core/letter-grid.d.ts +0 -55
  456. package/dist-pkg/core/letter-grid.js +0 -116
  457. package/dist-pkg/core/password.d.ts +0 -34
  458. package/dist-pkg/core/password.js +0 -205
  459. package/dist-pkg/core/rotulo-acessivel.d.ts +0 -17
  460. package/dist-pkg/core/rotulo-acessivel.js +0 -41
  461. package/dist-pkg/core/run-state.d.ts +0 -115
  462. package/dist-pkg/core/run-state.js +0 -40
  463. package/dist-pkg/core/tiles.d.ts +0 -12
  464. package/dist-pkg/core/tiles.js +0 -61
  465. package/dist-pkg/core/world.d.ts +0 -4
  466. package/dist-pkg/core/world.js +0 -58
  467. package/dist-pkg/input/origem-sintetica.d.ts +0 -44
  468. package/dist-pkg/input/origem-sintetica.js +0 -60
  469. package/dist-pkg/input/transporte-em-uso.d.ts +0 -101
  470. package/dist-pkg/input/transporte-em-uso.js +0 -130
  471. package/dist-pkg/platform/audio-nav.d.ts +0 -51
  472. package/dist-pkg/platform/audio-nav.js +0 -99
  473. package/dist-pkg/platform/guide-intensity.d.ts +0 -40
  474. package/dist-pkg/platform/guide-intensity.js +0 -73
  475. package/dist-pkg/platform/pesados-catalogo.d.ts +0 -14
  476. package/dist-pkg/platform/pesados-catalogo.js +0 -177
  477. package/dist-pkg/platform/pesados.d.ts +0 -31
  478. package/dist-pkg/platform/pesados.js +0 -75
  479. package/dist-pkg/platform/vozes-prontas.d.ts +0 -28
  480. package/dist-pkg/platform/vozes-prontas.js +0 -61
  481. package/dist-pkg/render/camera.d.ts +0 -58
  482. package/dist-pkg/render/camera.js +0 -105
  483. package/dist-pkg/render/cenario-data.d.ts +0 -127
  484. package/dist-pkg/render/cenario-data.js +0 -145
  485. package/dist-pkg/render/city-tex.d.ts +0 -63
  486. package/dist-pkg/render/city-tex.js +0 -228
  487. package/dist-pkg/render/city-tiles.d.ts +0 -11
  488. package/dist-pkg/render/city-tiles.js +0 -140
  489. package/dist-pkg/render/draw.d.ts +0 -149
  490. package/dist-pkg/render/draw.js +0 -231
  491. package/dist-pkg/render/fx.d.ts +0 -63
  492. package/dist-pkg/render/fx.js +0 -123
  493. package/dist-pkg/render/minimap.d.ts +0 -11
  494. package/dist-pkg/render/minimap.js +0 -94
  495. package/dist-pkg/render/parallax.d.ts +0 -85
  496. package/dist-pkg/render/parallax.js +0 -158
  497. package/dist-pkg/render/player-anim.d.ts +0 -71
  498. package/dist-pkg/render/player-anim.js +0 -152
  499. package/dist-pkg/render/recycling-tex.d.ts +0 -48
  500. package/dist-pkg/render/recycling-tex.js +0 -164
  501. package/dist-pkg/render/scene-city.d.ts +0 -77
  502. package/dist-pkg/render/scene-city.js +0 -181
  503. package/dist-pkg/render/scene-parallax.d.ts +0 -72
  504. package/dist-pkg/render/scene-parallax.js +0 -426
  505. package/dist-pkg/render/scene-sky.d.ts +0 -126
  506. package/dist-pkg/render/scene-sky.js +0 -295
  507. package/dist-pkg/render/set-cenario.d.ts +0 -54
  508. package/dist-pkg/render/set-cenario.js +0 -103
  509. package/dist-pkg/render/textures.d.ts +0 -75
  510. package/dist-pkg/render/textures.js +0 -240
  511. package/dist-pkg/render/title-scene.d.ts +0 -46
  512. package/dist-pkg/render/title-scene.js +0 -75
  513. package/dist-pkg/render/weather.d.ts +0 -96
  514. package/dist-pkg/render/weather.js +0 -171
  515. package/dist-pkg/render/wheelchair-sprites.d.ts +0 -32
  516. package/dist-pkg/render/wheelchair-sprites.js +0 -69
  517. package/dist-pkg/render/world-tex.d.ts +0 -18
  518. package/dist-pkg/render/world-tex.js +0 -87
  519. package/dist-pkg/ui/activities-menu.d.ts +0 -258
  520. package/dist-pkg/ui/activities-menu.js +0 -648
  521. package/dist-pkg/ui/caa-sets.d.ts +0 -39
  522. package/dist-pkg/ui/caa-sets.js +0 -65
  523. package/dist-pkg/ui/latch-refusal.d.ts +0 -32
  524. package/dist-pkg/ui/latch-refusal.js +0 -60
  525. package/dist-pkg/ui/map-hub.d.ts +0 -56
  526. package/dist-pkg/ui/map-hub.js +0 -138
  527. package/dist-pkg/ui/settings-caa.d.ts +0 -45
  528. package/dist-pkg/ui/settings-caa.js +0 -137
  529. package/dist-pkg/ui/settings-motor.d.ts +0 -175
  530. package/dist-pkg/ui/settings-motor.js +0 -342
  531. package/dist-pkg/ui/webcam.d.ts +0 -7
  532. package/dist-pkg/ui/webcam.js +0 -93
package/app/css/style.css CHANGED
@@ -1,47 +1,60 @@
1
1
  /* SPDX-License-Identifier: AGPL-3.0-or-later */
2
2
  :root{
3
+ /* The engine stylesheet's sentinel: `createGame` reads it to say in `problems` when a page did not link this file. */
4
+ --incl-engine-stylesheet:1;
3
5
  --bg:#0b1020; --panel:#1b2440; --ink:#f5f8ff; --ink-soft:#cdd6f2;
4
6
  --accent:#ffd23f; --accent-ink:#1a1400; --focus:#ffd23f; --good:#34e29b;
5
- --changed:#fff; /* ADR-0029: moldura de quem saiu do padrão */
6
- /* Alto contraste no DOM (issue #83): o véu do painel sem transparência, e o fundo de botão. Ficam aqui,
7
- junto dos outros tokens, para o teste de contraste poder lê-los do CSS em vez de os copiar. */
7
+ --changed:#fff; /* ADR-0029: the frame of a setting that left its default */
8
+ /* High contrast in the DOM (issue #83): the panel veil without transparency, and the button background. They live here,
9
+ beside the other tokens, so the contrast test can read them from the CSS instead of copying them. */
8
10
  --bg-solid:#04070f; --panel-btn:#1a2740;
9
11
  --maxw:1000px; --tap:44px;
10
- --ui-fs:16px; /* fonte-base da UI (<p>): 16px = MÍNIMO a 640×360; o JS (layout) aumenta conforme o canvas cresce */
11
- /* ===== Tipografia oficial EdSP ===== */
12
- /* 'Noto Color Emoji' no fim do stack → emoji consistentes (nativo no Chromebook/Android; woff2 abaixo p/ Windows) */
12
+ --ui-fs:16px; /* the UI's base font size (<p>): 16px = the MINIMUM at 640×360; `ui/layout` raises it as the canvas grows */
13
+ /* ===== The engine's typography ===== */
14
+ /* 'Noto Color Emoji' at the end of the stack → consistent emoji (native on Chromebook/Android; the system face elsewhere) */
13
15
  --font-fallback:system-ui,"Segoe UI",Roboto,Arial,sans-serif,'Noto Color Emoji';
14
- --font:'Atkinson Hyperlegible',var(--font-fallback); /* PADRÃO */
15
- --font-math:'Atkinson Hyperlegible Mono',ui-monospace,"Cascadia Mono",Consolas,monospace; /* matemática */
16
- /* Espaçamento padrão do projeto (≥ recomendações WCAG 2.2 §1.4.12) */
17
- --ls:0.12em; /* letter-spacing */
18
- --ws:0.16em; /* word-spacing */
16
+ --font:'Atkinson Hyperlegible',var(--font-fallback); /* DEFAULT */
17
+ --font-math:'Atkinson Hyperlegible Mono',ui-monospace,"Cascadia Mono",Consolas,monospace; /* mathematics */
18
+ /* ===== SPACING: THE DEFAULT ROSE FROM THE WCAG FLOOR TO THE BDA'S (ADR-0149 §2) =====
19
+ It was 0.12em/0.16em — the floor of WCAG 2.2 §1.4.12 —, and it is now 0.18em/0.63em, the British Dyslexia
20
+ Association's recommendation: word ≥ 3.5× letter. The Dev's decision on 2026-09-12: «somente fontes
21
+ cursivas não precisam seguir; ao usar todas as outras fontes, é obrigatório seguir».
22
+
23
+ ⚠️ THIS USED TO BE THE EXCEPTION OF ONE FACE. The BDA numbers lived only in `[data-fonte="dislexia"]` (Lexend),
24
+ and the DEFAULT face — Atkinson, which every game draws — sat on the WCAG floor. It was not «no spacing at all»,
25
+ as a first version of the record said; it was the lower of the two floors, applied because of a LABEL instead of
26
+ because of a rule about faces.
27
+
28
+ 🔴 AND THE REACH OF THIS IS EVERY GAME: changing the default face's spacing changes the metrics of every screen at
29
+ once. It is the right rule, and it is a layout change in every game that uses the engine. */
30
+ --ls:0.18em; /* letter-spacing — BDA */
31
+ --ws:0.63em; /* word-spacing — BDA (≥ 3.5× the letter) */
19
32
  --lh:1.5; /* line-height */
20
33
 
21
- /* ===== ORDEM-Z CANÔNICA — a faixa de OVERLAY do `core/layers.ts` (ADR-0020) =====
22
- Estes valores são a MESMA fonte que o mundo usa em PIXI, trazida para o CSS. Eles existem antes do
23
- primeiro consumidor DE PROPÓSITO, e o motivo é o mesmo que a Fase C deu ao barramento tipado: o contrato
24
- precisa nascer certo em vez de ser retipado depois. `tests/z-order-css.node.test.js` compara este bloco,
25
- valor a valor, com o `Z` do TypeScript — divergir fica vermelho.
26
-
27
- ⚠️ NENHUM SELETOR AINDA OS USA, e isso NÃO é esquecimento. Adotá-los inverte três ordens que hoje valem,
28
- e inverter ordem é decisão de produto:
29
-
30
- · `.caption` (8) está ABAIXO de `#touch-controls` (14). Canonicamente CAPTIONS(26000) fica ACIMA de
31
- TOUCH_CONTROLS(25000). A inversão CONSERTA um defeito — legenda é recurso de surdez, e controle de
32
- toque cobrindo legenda é a coisa errada —, mas muda a tela.
33
- · `.quiz` (12) está ABAIXO de `#touch-controls` (14); canonicamente DIALOGUE(27000) fica acima.
34
- · `.skip-link` (100) está ACIMA dos modais (60). O comentário do `Z` põe o skip-link em CAPTIONS
35
- (26000), o que o deixaria ABAIXO de MENU(30000) — e um "pular para o conteúdo" embaixo de um modal
36
- não é alcançável. Aqui o comentário do `Z` é que está errado, não o CSS.
37
-
38
- E DUAS COISAS NÃO TÊM SLOT: os dois pseudo-elementos do CRT (`crt-vig-1::before` em 5 e
39
- `crt-scan-1::after` em 500). Pelo ADR-0020 o CRT é PÓS-PROCESSO, não camada — inventar um número para
40
- ele repetiria o erro de categoria que a emenda de 2026-08-26 já registrou no `app.stage`.
41
-
42
- ⚠️ E `z-index` de CSS NÃO É PLANO. `#game-hud` tem `z-index:4` e CRIA CONTEXTO DE EMPILHAMENTO, então o
43
- `.screen-pause` (6) compete só lá dentro — o menu de pausa vive efetivamente em "4" na raiz. Uma lista
44
- plana de valores globais não expressa isso. Adotar exige decidir, por elemento, em que contexto ele vive. */
34
+ /* ===== THE Z-ORDER SLOTS — the engine's canonical stacking order (ADR-0020, ADR-0102) =====
35
+ These `--z-*` variables are where the z-order lives now. They were born as the OVERLAY band of the `Z` table in
36
+ `core/layers.ts`; that table was the platformer's world order and left with the tile world (ADR-0228), so the
37
+ slots below are the only copy. `tests/the-skip-link-outranks-the-transition.node.test.js` holds what the engine
38
+ needs of them: the skip link sits on its slot, above the transition and every other slot except the loop-crash
39
+ notice and the debug panel, and no literal `z-index` in this file reaches it.
40
+
41
+ ⚠️ MOST SELECTORS STILL CARRY LITERALS, and that is not forgetfulness. Only `.skip-link`, `#incl-parou` and the
42
+ touch pad over an open panel read a slot. Adopting the slots everywhere inverts three orders that hold today, and
43
+ inverting an order is a product decision:
44
+
45
+ · `.caption` (8) is BELOW the touch pad (14). In the slots, captions (26000) are ABOVE touch controls (25000).
46
+ The inversion FIXES a defect — the caption serves deaf players, and a touch control covering it is the wrong
47
+ way round —, but it changes the screen.
48
+ · `.quiz` (12) is BELOW the touch pad (14); in the slots, dialogue (27000) is above.
49
+ · the skip link was already corrected: it sits on `--z-skip-link`, above the modals and the transition.
50
+
51
+ AND TWO THINGS HAVE NO SLOT: the two CRT pseudo-elements (`crt-vig-1::before` at 5 and `crt-scan-1::after` at
52
+ 500). Under ADR-0020 the CRT is POST-PROCESSING, not a layer — inventing a number for it would repeat the category
53
+ error the record already names.
54
+
55
+ ⚠️ AND CSS `z-index` IS NOT FLAT. `#game-hud` has `z-index:4` and CREATES A STACKING CONTEXT, so `.screen-pause`
56
+ (6) competes only inside it — the pause menu effectively lives at "4" at the root. A flat list of global values
57
+ cannot express that. Adopting the slots means deciding, per element, which context it lives in. */
45
58
  --z-hud:24000;
46
59
  --z-touch-controls:25000;
47
60
  --z-captions:26000;
@@ -55,326 +68,362 @@
55
68
  --z-loop-crash:45000;
56
69
  --z-debug:90000;
57
70
  }
58
- /* Emoji: usa o Noto Color Emoji do sistema (Chromebook/Android/Linux) e, se ausente, o woff2 embutido
59
- (offline). Se o arquivo não estiver presente, o navegador cai no emoji do SO — sem quebrar. */
60
- @font-face{ font-family:'Noto Color Emoji'; src:local('Noto Color Emoji'); /* só a fonte do sistema; sem download (o .woff2 nunca existiu) */ font-display:swap; }
61
- /* Modo ALFABETIZAÇÃO → Andika */
71
+ /* Emoji: the system's Noto Color Emoji (Chromebook/Android/Linux). No file is shipped for it, so where the system
72
+ lacks it the browser falls back to the OS emoji — without breaking. */
73
+ @font-face{ font-family:'Noto Color Emoji'; src:local('Noto Color Emoji'); /* the system face only; no download (the .woff2 never existed) */ font-display:swap; }
74
+ /* LITERACY mode → Andika */
62
75
  :root[data-fonte="alfabetizacao"]{ --font:'Andika',var(--font-fallback); }
63
- /* Modo DISLEXIA/TDAH → Lexend + espaçamento da British Dyslexia Association
64
- (palavra ≥ 3,5× letra: letter 0.18em / word 0.63em) */
65
- :root[data-fonte="dislexia"]{ --font:'Lexend',var(--font-fallback); --ls:0.18em; --ws:0.63em; }
66
- /* Tipografia: qualquer fonte do catálogo (menu próprio na pausa); --font-custom traz a pilha com fallback */
76
+ /* DYSLEXIA/ADHD mode → Lexend. 📌 The BDA spacing is not here: it is the document's default (see `:root`, ADR-0149 §2),
77
+ not this face's exception. The rule keeps only the FAMILY, which is all it ever had of its own. */
78
+ :root[data-fonte="dislexia"]{ --font:'Lexend',var(--font-fallback); }
79
+ /* ===== THE CURSIVE EXCEPTION (ADR-0149 §3) =====
80
+ 🔴 Letter spacing on a JOINED face breaks it exactly at the joins that make it cursive: what is meant to help the
81
+ reading would destroy the thing being read. The Dev's words: «para manter os conectores».
82
+ It is not a concession — it is the same rule applied to a face where it inverts.
83
+ ⚠️ `normal` and not `0`: `word-spacing:0` is still a spacing declaration; `normal` gives back the metrics the face's
84
+ designer chose, which is precisely what is meant to be preserved. */
85
+ :root[data-cursiva="1"]{ --ls:normal; --ws:normal; }
86
+ /* A COLOUR-VISION-SAFE PALETTE FOR THE MENUS AND THE HUD (ADR-0151) — Okabe-Ito.
87
+ 📏 The interface's meaning-by-colour sits in two tokens: `--accent` (selection, «on», choice) and `--good` (the right
88
+ answer revealed). The usual pair, yellow and green, is the one protans and deutans confuse: CIE76 ΔE of 48 and 55
89
+ after Machado's (2009) simulation. Orange and sky blue give 101, 111 and 87 (tritan) — the largest worst case among
90
+ the Okabe-Ito pairs measured — and every contrast stays ≥ 4.5:1 (the tightest is `--good` on the selected button,
91
+ 4.88:1). The gate is `tests/okabe-ito-palette.node.test.js`. */
92
+ :root[data-paleta="okabe-ito"]{ --accent:#E69F00; --focus:#E69F00; --good:#56B4E9; }
93
+ /* Typography: any face of the catalogue (its own menu in the pause); --font-custom brings the stack with its fallback */
67
94
  :root[data-fonte="custom"]{ --font:var(--font-custom,'Atkinson Hyperlegible'),var(--font-fallback); }
68
- /* Menu Tipografia: caixa branca de pré-visualização (pangrama), escrita escura */
95
+ /* Typography menu: a white preview box (pangram), dark text */
69
96
  .typo-preview{background:#fff;color:#14141f;border:2px solid var(--ink-soft);border-radius:8px;padding:.65rem .9rem;margin:.2rem 0 .6rem;font-size:1.3em;line-height:1.3;text-align:center}
70
- /* Tela de título (visual da v3): overlay TRANSPARENTE — a cena (céu/nuvens/grama) vem do canvas */
71
- #title-overlay{background:transparent;font-size:var(--ui-fs,16px)} /* UI do splash escala com o k inteiro do canvas (igual pausa/diálogos) — NÃO fica fixa em 16px */
72
- body.at-title #game-hud,body.at-title .start-tips,body.at-title .hud{display:none} /* HUD do jogo não vaza no menu inicial (display: filhos não conseguem re-exibir) */
97
+ /* Title screen: a TRANSPARENT overlay — the scene behind it comes from the canvas */
98
+ #title-overlay{background:transparent;font-size:var(--ui-fs,16px)} /* the splash UI scales with the canvas's integer k (like the pause and the dialogs) — it does NOT stay at 16px */
99
+ body.at-title #game-hud,body.at-title .start-tips,body.at-title .hud{display:none} /* the game's HUD does not leak into the start menu (display: children cannot show themselves again) */
73
100
  .title-wrap{display:flex;flex-direction:column;align-items:center;gap:12px;max-height:96%;}
74
- .game-title{color:#fff;font-size:2em;line-height:1.2;margin:0;text-align:center;letter-spacing:1px;text-shadow:2px 2px 0 #0d0d1a,-1px -1px 0 #0d0d1a}
101
+ .game-title{color:#fff;font-size:2em;line-height:1.2;margin:0;text-align:center;letter-spacing:var(--ls);text-shadow:2px 2px 0 #0d0d1a,-1px -1px 0 #0d0d1a}
75
102
  .title-block{display:inline-block}
76
- .game-byline{color:#f6f5f0;font-size:.8em;text-align:right;margin-top:2px;text-shadow:1px 1px 0 #0d0d1a}
77
- .game-version{color:#9999b3;font-size:.6em;text-align:right;letter-spacing:1px;margin-top:2px;text-shadow:1px 1px 0 #0d0d1a}
78
- .game-subtitle{color:#f6f5f0;font-size:.8em;margin:0 0 4px;text-shadow:1px 1px 0 #0d0d1a;text-align:center}
79
- /* Splash DENTRO do canvas, SEM rolagem. GESTALT (José): a11y no CABEÇALHO · menu no ESPAÇO DE TRABALHO · legenda no RODAPÉ */
103
+ .game-byline{color:#f6f5f0;font-size:1em;text-align:right;margin-top:2px;text-shadow:1px 1px 0 #0d0d1a}
104
+ .game-version{color:#9999b3;font-size:1em;text-align:right;letter-spacing:var(--ls);margin-top:2px;text-shadow:1px 1px 0 #0d0d1a}
105
+ .game-subtitle{color:#f6f5f0;font-size:1em;margin:0 0 4px;text-shadow:1px 1px 0 #0d0d1a;text-align:center}
106
+ /* The splash INSIDE the canvas, with NO scrolling. GESTALT (the Dev): a11y in the HEADER · menu in the WORKSPACE · legend in the FOOTER */
80
107
  .title-wrap{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;max-height:none}
81
- .title-legend{position:absolute;left:0;right:0;bottom:8px} /* RODAPÉ, como a legenda da pausa */
82
- .title-menu{display:flex;flex-direction:column;gap:.35em;align-items:stretch;padding:2px;width:13em;margin:0 auto} /* largura fixa ENXUTA */
108
+ .title-legend{position:absolute;left:0;right:0;bottom:8px} /* FOOTER, like the pause legend */
109
+ .title-menu{display:flex;flex-direction:column;gap:.35em;align-items:stretch;padding:2px;width:13em;margin:0 auto} /* a fixed, LEAN width */
110
+ /* NO MENU LEAVES THE CANVAS (ADR-0130 rule 5): every opening menu scrolls when its items outgrow the frame — a RULE of the
111
+ class, so a menu a game adds inherits it. The main menu had no scroll: with twenty items it ran 208 px above and 288 px
112
+ below a 360 px stage, and its first and last items could not be reached. */
113
+ .title-menu{max-height:72%;overflow-y:auto;overflow-x:hidden}
83
114
  .title-menu[hidden]{display:none}
84
- /* O menu principal com o DOBRO da largura base (13em → 26em): a 13em os rótulos quebravam em DUAS linhas, e
85
- um botão de duas linhas muda de altura conforme o idioma — a mesma tela com uma geometria em pt-BR e outra
86
- em espanhol. Quem se orienta por alinhamento perde a referência; quem escuta nem percebe que ela existia. */
87
- #tm-main{width:26em}
88
- /* ===================== SUBMENUS DA ABERTURA: LISTA VERTICAL COM ROLAGEM (ADR-0044) =====================
89
- Eram GRADES de duas colunas, e a seta anda em ordem de DOM: “para baixo” pulava para a coluna da direita,
90
- atravessando a tela. O anel do item 1 já estava certo no código (`nextTitleIndex` dá a volta) — era o
91
- LAYOUT que mentia sobre ele. Em lista, a ordem que se VÊ é a ordem em que se ANDA.
92
-
93
- Onze frações não cabem no quadro, então a lista ROLA — foi o que o Dev pediu, e o preço é barato:
94
- `focus()` traz o item para dentro da janela sozinho, de modo que quem anda por teclado ou controle nunca
95
- perde o foco fora da vista.
96
-
97
- O painel é OPACO pelo mesmo motivo do véu da pausa: por trás da abertura corre o modo de atração, e
98
- texto sobre quadro variável tem a razão de contraste que o quadro permitir — média de sorte, não garantia. */
99
- #tm-alf,#tm-mat,#tm-tab,#tm-fr,#tm-cen{width:26em;max-height:72%;overflow-y:auto;overflow-x:hidden;background:var(--bg-solid);border-radius:10px;padding:.5em .7em}
100
- /* O título do submenu fica GRUDADO no topo: numa lista que rola, um cabeçalho que sobe leva embora a única
101
- pista de “onde estou” que quem lê por resto de visão tem. */
115
+ /* THE WIDTH FOLLOWS THE WIDEST ITEM (ADR-0130 rule 6, issue #134). A fixed width is a translation defect that only
116
+ shows after translation: a label that fits in Portuguese wraps onto two lines, or is cut, in a longer language — and a
117
+ two-line button changes height with the language. `fit-content` is the widest label's own width, capped by the room
118
+ there is, so a label wraps only when the frame itself is narrower than it and the menu never leaves the canvas. */
119
+ #tm-main{width:fit-content}
120
+ /* ===================== THE OPENING SUBMENUS: A VERTICAL LIST THAT SCROLLS (ADR-0044) =====================
121
+ They were two-column GRIDS, and the arrow moves in DOM order: “down” jumped to the right-hand column, across the
122
+ screen. The ring of item 1 was already right in the code (`nextTitleIndex` wraps around) — it was the LAYOUT that
123
+ lied about it. In a list, the order you SEE is the order you MOVE in.
124
+
125
+ Eleven fractions do not fit the frame, so the list SCROLLS — which is what the Dev asked for, and the price is low:
126
+ `focus()` brings the item into view by itself, so whoever moves by keyboard or controller never loses the focus
127
+ out of sight.
128
+
129
+ The panel is OPAQUE for the same reason as the pause veil: the attract mode runs behind the opening, and text over a
130
+ changing frame has whatever contrast the frame allows — an average of luck, not a guarantee. */
131
+ #tm-alf,#tm-mat,#tm-tab,#tm-fr,#tm-cen{width:fit-content;max-height:72%;overflow-y:auto;overflow-x:hidden;background:var(--bg-solid);border-radius:10px;padding:.5em .7em}
132
+ /* The submenu's title STICKS to the top: in a scrolling list, a heading that scrolls away takes with it the only
133
+ “where am I” cue that someone reading with residual vision has. */
102
134
  #tm-alf .tm-title,#tm-mat .tm-title,#tm-tab .tm-title,#tm-fr .tm-title,#tm-cen .tm-title{position:sticky;top:-.5em;z-index:1;background:var(--bg-solid);padding:.35em 0 .25em;margin:0}
103
- /* …e por isso os itens precisam de MARGEM DE ROLAGEM: um cabeçalho grudado cobre o item que o navegador acaba
104
- de "trazer para a vista", porque para ele o item já estava dentro do quadro. MEDIDO no navegador antes de
105
- existir esta linha: dando a volta do último para o primeiro, o primeiro botão parava com o topo em 143px e
106
- o cabeçalho terminava em 155 — 12px encobertos, e a criança via um item cortado no lugar do foco. O valor
107
- cobre a altura do cabeçalho (34px ≈ 2,1em) com folga. */
135
+ /* …and that is why the items need a SCROLL MARGIN: a sticky heading covers the item the browser has just "brought into
136
+ view", because for the browser the item was already inside the frame. MEASURED in the browser before this line
137
+ existed: wrapping from the last item to the first, the first button stopped with its top at 143px while the heading
138
+ ended at 155 — 12px covered, and the child saw a cut-off item where the focus was. The value covers the heading's
139
+ height (34px ≈ 2.1em) with room to spare. */
108
140
  #tm-alf .title-btn,#tm-mat .title-btn,#tm-tab .title-btn,#tm-fr .title-btn,#tm-cen .title-btn{scroll-margin-top:2.4em;scroll-margin-bottom:.5em}
109
- /* As notações de fração continuam LADO A LADO: são cinco alternadores de UM grupo, e não itens da lista —
110
- virar coluna aqui alongaria a rolagem sem ganhar ordem nenhuma. */
141
+ /* The fraction notations stay SIDE BY SIDE: they are five toggles of ONE group, not list items — turning them into a
142
+ column here would lengthen the scroll without adding any order. */
111
143
  #tm-fr .frac-nots{display:flex;flex-direction:column;gap:.25em}
112
- /* AS CINCO NOTAÇÕES SÃO AJUSTE, NÃO ITEM DE MENU (pedido do Dev). Elas usavam `.title-btn` — a mesma classe
113
- dos botões que ABREM uma atividade — e ficavam empilhadas logo acima deles: cinco coisas com cara de
114
- "entrar" que na verdade ligam e desligam. A moldura (`fieldset`/`legend`) é a fronteira, e a marca ☑/☐ é o
115
- estado sem depender de cor (a cor sozinha reprova a WCAG 1.4.1). */
144
+ /* THE FIVE NOTATIONS ARE SETTINGS, NOT MENU ITEMS (the Dev's request). They used `.title-btn` — the same class as the
145
+ buttons that OPEN an activity — and were stacked right above them: five things that looked like "enter" and in fact
146
+ switch on and off. The frame (`fieldset`/`legend`) is the boundary, and the ☑/☐ mark is the state without relying
147
+ on colour (colour alone fails WCAG 1.4.1). */
116
148
  #tm-fr .frac-nots{border:2px solid var(--ink-soft);border-radius:10px;padding:.3em .5em .5em;margin:0 0 .45em}
117
- #tm-fr .frac-nots legend{padding:0 .4em;font-size:.8em;font-weight:700;color:var(--ink-soft)}
149
+ #tm-fr .frac-nots legend{padding:0 .4em;font-size:1em;font-weight:700;color:var(--ink-soft)}
118
150
  #tm-fr .fnot-opt{display:flex;align-items:center;gap:.5em;width:100%;min-height:44px;padding:.2em .4em;background:transparent;color:#fff;border:0;border-radius:8px;font:inherit;cursor:pointer;text-align:left}
119
151
  #tm-fr .fnot-opt:hover,#tm-fr .fnot-opt:focus-visible{background:#16233f}
120
152
  #tm-fr .fnot-marca{font-size:1.15em;line-height:1;min-width:1.1em}
121
153
  #tm-fr .fnot-opt[aria-checked="true"] .fnot-marca{color:var(--accent)}
122
154
  #tm-fr .fnot-sym{font-size:1em}
123
- #tm-fr .fnot-opt .fv{font-size:.8em}
124
- .tm-title{color:#fff;font-size:1em;margin:0 0 .1em;text-align:center;text-shadow:1px 1px 0 #0d0d1a} /* título do submenu */
125
- .title-btn{background:var(--accent);color:var(--accent-ink);border:3px solid #0d0d1a;font-family:inherit;font-weight:bold;font-size:.95em;padding:.4em .6em;border-radius:6px;cursor:pointer;min-width:0;width:100%;min-height:2.3em} /* contorno PRETO grosso (3px) = mesma cor do texto (José) */
126
- .title-btn.ghost{background:#e8862b;color:#0d0d1a;border:2px solid #0d0d1a} /* Voltar = preenchimento LARANJA */
127
- .act-sub{display:block;font-size:.72em;font-weight:600;opacity:.7;letter-spacing:.02em;margin-top:.06em;font-family:var(--font-math,inherit);white-space:nowrap} /* subtítulo de exemplo (2ª linha, NUNCA quebra — botão largo o bastante) */
128
- /* Seletor VERMELHO fixo (piscar removido a pedido do José). Especificidade via #title-overlay. */
155
+ #tm-fr .fnot-opt .fv{font-size:1em}
156
+ .tm-title{color:#fff;font-size:1em;margin:0 0 .1em;text-align:center;text-shadow:1px 1px 0 #0d0d1a} /* the submenu's title */
157
+ .title-btn{background:var(--accent);color:var(--accent-ink);border:3px solid #0d0d1a;font-family:inherit;font-weight:bold;font-size:1em;padding:.4em .6em;border-radius:6px;cursor:pointer;min-width:0;width:100%;min-height:2.3em} /* a thick BLACK outline (3px) = the same colour as the text (the Dev) */
158
+ .title-btn.ghost{background:#e8862b;color:#0d0d1a;border:2px solid #0d0d1a} /* Back = ORANGE fill */
159
+ .act-sub{display:block;font-size:1em;font-weight:600;opacity:.7;letter-spacing:var(--ls);margin-top:.06em;font-family:var(--font-math,inherit);white-space:nowrap} /* the example subtitle (2nd line, NEVER wraps — the button is wide enough) */
160
+ /* A fixed RED selector (blinking removed at the Dev's request). Specificity through #title-overlay. */
129
161
  #title-overlay .title-btn:focus-visible,#title-overlay .title-btn:focus{outline:4px solid #ff3b30;outline-offset:2px}
130
- #title-overlay .title-btn.act-fx{animation:titleAct .22s ease} /* efeito de ATIVAÇÃO (antes de trocar de tela) */
162
+ #title-overlay .title-btn.act-fx{animation:titleAct .22s ease} /* the ACTIVATION effect (before the screen changes) */
131
163
  @keyframes titleAct{0%{transform:scale(.92);filter:brightness(1.6)}60%{transform:scale(1.03)}100%{transform:scale(1)}}
132
164
  @media (prefers-reduced-motion: reduce){ #title-overlay .title-btn.act-fx{animation:none;filter:brightness(1.4)} }
133
165
  .title-btn.tab-on{box-shadow:inset 0 0 0 3px #14141f}
134
- /* As fileiras da tabuada eram DUAS linhas de números lado a lado, e o Dev cobrou de novo depois de ver o
135
- resultado: menu "misto" é menu onde "para baixo" às vezes anda para o lado. Viraram coluna, como todo o
136
- resto da abertura. O seletor ganhou o `#tm-tab` na frente de propósito — é o que deixa o gate de layout
137
- enxergar esta regra como sendo DE um submenu da abertura, em vez de uma classe solta que ninguém vigia. */
166
+ /* The times-table rows were TWO lines of numbers side by side, and the Dev objected again after seeing the result: a
167
+ "mixed" menu is a menu where "down" sometimes moves sideways. They became a column, like the rest of the opening.
168
+ The selector carries `#tm-tab` in front on purpose — that is what lets the layout gate see this rule as belonging TO
169
+ an opening submenu, instead of a loose class nobody watches. */
138
170
  #tm-tab .tab-row{display:flex;flex-direction:column;gap:.35em}
139
171
  #tm-tab .tab-row .title-btn{width:100%;padding:.4em .5em}
140
- /* RODAPÉ (mesmo estilo do menu de pausa): descrição no fundo do canvas, NÃO colada ao grupo de botões (José).
141
- Fundo fumê PRETO → contraste ≥7:1 entre o texto claro (#f6f5f0) e o fundo (≈#0a0a12). */
142
- .tm-desc:not(:empty){position:absolute;left:50%;transform:translateX(-50%);bottom:10px;max-width:88%;color:#f6f5f0;font-size:.8em;text-align:center;background:rgba(8,8,16,.92);padding:.35em .7em;border-radius:8px;line-height:1.25}
172
+ /* FOOTER (the same style as the pause menu): the description at the bottom of the canvas, NOT glued to the button group (the Dev).
173
+ A smoked BLACK background → contrast ≥7:1 between the light text (#f6f5f0) and the background (≈#0a0a12). */
174
+ .tm-desc:not(:empty){position:absolute;left:50%;transform:translateX(-50%);bottom:10px;max-width:88%;color:#f6f5f0;font-size:1em;text-align:center;background:rgba(8,8,16,.92);padding:.35em .7em;border-radius:8px;line-height:1.25}
143
175
  .tm-desc:empty{position:absolute;left:0;bottom:10px}
144
- /* Legenda de controles: mesmo visual/posição da legenda do menu de pausa (chips), em 2 LINHAS */
145
- .title-legend{display:flex;flex-direction:column;gap:.25em;align-items:center;margin:.5em 0 .1em;font-size:.82em;color:#c3cfe0}
176
+ /* Controls legend: the same look and place as the pause menu's legend (chips), on 2 LINES */
177
+ .title-legend{display:flex;flex-direction:column;gap:.25em;align-items:center;margin:.5em 0 .1em;font-size:1em;color:#c3cfe0}
146
178
  .title-legend .lg-row{display:flex;gap:1.1em;justify-content:center;flex-wrap:wrap}
147
179
  .title-legend .lg{display:inline-flex;align-items:center;gap:.35em}
148
- .title-legend .lg-ico{display:inline-flex;align-items:center;justify-content:center;min-width:1.35em;height:1.35em;padding:0 .2em;border-radius:.68em;font-weight:800;font-size:.9em;color:#fff;background:rgba(13,13,26,.85);text-shadow:0 1px 1px rgba(0,0,0,.55)}
149
- #title-icons{position:absolute;top:10px;left:50%;transform:translateX(-50%);display:flex;gap:.3em;justify-content:center;flex-wrap:nowrap;white-space:nowrap} /* CABEÇALHO: 10px do topo, UMA fileira */
150
- /* Seletor de jogadores = UM botão só ("◀ Nº de jogadores: X ▶"); o lado clicado decide +1/−1 */
151
- #np-btn{letter-spacing:.02em}
180
+ .title-legend .lg-ico{display:inline-flex;align-items:center;justify-content:center;min-width:1.35em;height:1.35em;padding:0 .2em;border-radius:.68em;font-weight:800;font-size:1em;color:#fff;background:rgba(13,13,26,.85);text-shadow:0 1px 1px rgba(0,0,0,.55)}
181
+ #title-icons{position:absolute;top:0;left:50%;transform:translateX(-50%);display:flex;gap:0;justify-content:center;flex-wrap:nowrap;white-space:nowrap} /* header: on the screen's top edge, giving that room to the game (interface log 2026-09-13), ONE row */
182
+ #title-icons.pause-icons{margin:0 0 calc(.35rem * var(--espaco-fixo,1))}
183
+ /* The NAME of the pointed icon, just below the row. Absolute, so the bar's height — the band the game must not draw
184
+ over (ADR-0148) — does not grow when a name appears; the explanation goes to the footer, not here. */
185
+ #title-icons .pause-icons-cap{position:absolute;top:100%;line-height:1.25;left:50%;transform:translateX(-50%);margin:0;text-align:center;font-size:1em;color:#f6f5f0;pointer-events:none;white-space:nowrap;min-height:0;padding:calc(var(--ui-fs,16px) * .2) calc(var(--ui-fs,16px) * .6)}
186
+ /* the padding stays when empty: the room `createGame` reserves under the bar reads it (issue #160) */
187
+ #title-icons .pause-icons-cap:not(:empty){background:rgba(8,8,16,.92);border-radius:8px}
188
+ /* While a name shows, the bar rises over the HUD so the name covers the mission under it, not the other way round (ADR-0239
189
+ erratum: the name is momentary, and it is what the child is pointing at). Only then: at rest the bar keeps its layer. */
190
+ #title-icons:has(.pause-icons-cap:not(:empty)),.screen-a11y:has(.pause-icons-cap:not(:empty)){z-index:13}
191
+ /* Player-count selector = ONE button ("◀ No. of players: X ▶"); the side clicked decides +1/−1 */
192
+ #np-btn{letter-spacing:var(--ls)}
152
193
  #np-btn #np-n{display:inline-block;min-width:1.1em;text-align:center}
153
- /* Fração VERTICAL: pilha número/traço/número em HTML puro (zero dependência) */
194
+ /* VERTICAL fraction: a number/bar/number stack in plain HTML (zero dependencies) */
154
195
  .fv{display:inline-flex;flex-direction:column;align-items:center;vertical-align:middle;line-height:1.05;margin:0 .1em}
155
196
  .fv b{font-weight:800;padding:0 .25em}
156
197
  .fv b:first-child{border-bottom:2px solid currentColor}
157
- /* Fração como FIGURA (pizza/quadrado): SVG inline, 2-6 partes iguais; imprópria = várias unidades lado a lado */
158
- :root{--frac-fill:#f2a03d} /* laranja: alto contraste sobre branco + contorno preto (não depende só da cor — WCAG 1.4.1) */
198
+ /* A fraction as a FIGURE (pie/square): inline SVG, 2-6 equal parts; an improper one = several units side by side */
199
+ :root{--frac-fill:#f2a03d} /* orange: high contrast on white + a black outline (not colour alone — WCAG 1.4.1) */
159
200
  .frac-fig{display:inline-flex;align-items:center;gap:.25em;vertical-align:middle}
160
- .frac-shape{display:inline-flex;align-items:center;gap:.15em} /* círculo + quadrado da mesma fração, lado a lado */
161
- .frac-op{display:inline-flex;flex-direction:column;align-items:center;gap:.15em;vertical-align:middle} /* operando da conta: número EM CIMA, gráfico embaixo */
201
+ .frac-shape{display:inline-flex;align-items:center;gap:.15em} /* circle + square of the same fraction, side by side */
202
+ .frac-op{display:inline-flex;flex-direction:column;align-items:center;gap:.15em;vertical-align:middle} /* an operand of the sum: the number ON TOP, the figure below */
162
203
  .frac-svg{width:2.4em;height:2.4em;display:inline-block;vertical-align:middle}
163
204
  .quiz-choice .frac-svg{width:2.6em;height:2.6em}
164
205
  .quiz-prob .frac-svg{width:2.6em;height:2.6em}
165
206
  .quiz-dots{font-size:1.3em;letter-spacing:.15em;line-height:1.3;margin:.3rem 0;max-width:12em}
166
207
  @media (prefers-contrast: more){
167
- /* Aqui `--ink-soft` JÁ é branco: a moldura de "alterado" tem que deixar de ser branca, ou marcado e
168
- não-marcado viram a mesma moldura — justamente para a criança cujos ajustes têm mais chance de estar
169
- fora do padrão. Uma marca cuja visibilidade depende do tema não é uma marca (ADR-0029). */
208
+ /* Here `--ink-soft` is ALREADY white: the "changed" frame has to stop being white, or marked and unmarked become the
209
+ same frame — precisely for the child whose settings are most likely to be off the default. A mark whose
210
+ visibility depends on the theme is not a mark (ADR-0029). */
170
211
  :root{ --bg:#000; --panel:#000; --ink:#fff; --ink-soft:#fff; --accent:#ffe600; --changed:#ffe600; }
171
212
  }
172
213
  *{box-sizing:border-box}
214
+ /* A face with a floor above 16 px (`--fonte-escala`, written by the typography cycle and menu; ADR-0176 rule 4) enlarges
215
+ ITS OWN TEXT and not the whole document (interface log 2026-09-13, issue #172): the scale lives on `#game-region`'s
216
+ `font-size`, spacing bound to the text is written in `--ui-fs`, and the root stays 16 px so `rem` does not grow.
217
+ `--espaco-fixo` is the factor of the spaces that do NOT follow the scale — the ones the Dev saw shrink under a 25% browser
218
+ zoom while the text held «mantendo o design muito bom»: 1 at the base, down to that quarter at the 20 px floor. */
219
+ :root{--espaco-fixo:max(.25, 4 - 3 * var(--fonte-escala,1))}
173
220
  html,body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--font);font-size:16px;line-height:var(--lh);letter-spacing:var(--ls);word-spacing:var(--ws)}
174
- /* matemática em mono tabular (algarismos alinhados/legíveis) */
221
+ /* THE SPACING IS THE TEXT'S, WHEREVER THE TEXT IS (ADR-0149 erratum, issue #187): «Se dentro de um botão tem texto, as regras de
222
+ espaçamento valem para este texto, mesmo que seja uma única palavra.» The browser's own stylesheet gives controls
223
+ `letter-spacing: normal`, which beat the inheritance — measured: pause items and quiz options had none. Zero specificity, so a
224
+ rule that spaces a control on purpose still decides. */
225
+ :where(button,select,option,input,textarea){letter-spacing:inherit;word-spacing:inherit}
226
+ /* mathematics in tabular mono (aligned, legible digits) */
175
227
  .quiz-box--math .quiz-prob,.quiz-box--math .quiz-choice,#hud-coins,#hud-total{font-family:var(--font-math);font-variant-numeric:tabular-nums}
176
- /* espaçamento de parágrafo ≥2× só em blocos de leitura (não quebra HUD/menus) */
228
+ /* paragraph spacing ≥2× only in reading blocks (it does not break the HUD or the menus) */
177
229
  .overlay__card p + p{margin-top:2em}
178
- /* A página NUNCA rola (José): altura travada no viewport e overflow escondido — o excedente do canvas
179
- (tolerância de 5px lógicos/lado) é cortado pelo .stage-wrap, e os menus rolam DENTRO dos cards. */
230
+ /* The page NEVER scrolls (the Dev): height locked to the viewport and overflow hidden — the canvas's excess (a tolerance
231
+ of 5 logical px per side) is cut by .stage-wrap, and the menus scroll INSIDE their cards. */
180
232
  body{display:flex;flex-direction:column;height:100vh;height:100dvh;overflow:hidden}
181
233
  .sr-only{position:absolute!important;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
182
- /* O `z-index` ADOTOU O SLOT em 2026-09-07. Era `100`, um literal que estava CERTO e cuja tabela estava
183
- errada: o `Z` punha o skip-link em CAPTIONS(26000), abaixo dos modais. Ver `SKIP_LINK` em core/layers. */
234
+ /* The skip link sits on its SLOT, `--z-skip-link`, and not on a literal: above the modals and the transition, below only
235
+ the loop-crash notice and the debug panel. `tests/the-skip-link-outranks-the-transition.node.test.js` holds it (ADR-0102). */
184
236
  .skip-link{position:absolute;left:8px;top:-60px;background:var(--accent);color:var(--accent-ink);padding:.6em 1em;border-radius:0 0 10px 10px;font-weight:700;z-index:var(--z-skip-link);transition:top .15s}
185
237
  .skip-link:focus{top:0}
186
238
  :focus-visible{outline:4px solid var(--focus);outline-offset:2px;border-radius:6px}
187
239
 
188
240
  .topbar{max-width:var(--maxw);margin:0 auto;padding:.6rem 1rem;display:flex;align-items:center;justify-content:space-between;gap:1rem;flex-wrap:wrap}
189
- body:not(.dbg) .topbar{display:none} /* título da PÁGINA só em ?debug=true (o jogo já mostra no splash); recupera a vertical */
190
- .title{font-size:1.3rem;margin:0}
191
- .ver{color:var(--ink-soft);font-weight:400;font-size:.9rem}
192
- .badge{margin:0;color:var(--accent-ink);background:var(--accent);border-radius:999px;padding:.2em .8em;font-weight:700;font-size:.8rem}
241
+ body:not(.dbg) .topbar{display:none} /* the PAGE title only under ?debug=true (the game already shows it on the splash); gives back the vertical room */
242
+ .title{font-size:1.3em;margin:0}
243
+ .ver{color:var(--ink-soft);font-weight:400;font-size:1em}
244
+ .badge{margin:0;color:var(--accent-ink);background:var(--accent);border-radius:999px;padding:.2em .8em;font-weight:700;font-size:1em}
193
245
 
194
246
  .hud{max-width:var(--maxw);margin:0 auto;padding:0 1rem .5rem;display:flex;gap:1.5rem;flex-wrap:wrap}
195
- .hud__item{margin:0;font-size:1.05rem}
196
- /* moedas/missão/poder migraram para dentro da tela de cada jogador; ficam sr-only (leitor de tela) */
247
+ .hud__item{margin:0;font-size:1.05em}
248
+ /* coins/mission/power moved into each player's screen; they stay sr-only (for the screen reader) */
197
249
  .hud__item--sr{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap;border:0}
198
- /* HUD por jogador SOBRE o canvas (DOM = alta definição, não pixela). Um por viewport; moedas na 1ª coluna, poder na 2ª. */
199
- #game-hud{position:absolute;inset:0;pointer-events:none;z-index:4;font-family:var(--font);font-size:var(--hud-fs,14px);font-weight:800}
200
- /* "Tela do jogador": um contêiner por jogador (posicionado na fatia do viewport dele). Hospeda HUD, e nas
201
- próximas etapas a pausa e os menus daquele jogador. O render segue compartilhado (um contexto WebGL). */
250
+ /* Per-player HUD OVER the canvas (DOM = high definition, no pixelation). One per viewport; coins in the 1st column, power in the 2nd. */
251
+ #game-hud{position:absolute;inset:0;pointer-events:none;z-index:4;font-family:var(--font);font-size:var(--hud-fs,16px);font-weight:800}
252
+ /* "Player screen": one container per player (placed on that player's slice of the viewport). It hosts the HUD, and the
253
+ pause and menus of that player. The render stays shared (one WebGL context). */
202
254
  .player-screen{position:absolute;pointer-events:none}
203
255
  .vphud{position:absolute;top:0;left:0;width:100%;display:flex;align-items:flex-start;gap:0;padding:.4em .55em;box-sizing:border-box;color:#fff;text-shadow:0 1px 2px #000,0 0 3px #000,0 0 6px #000;line-height:1.15}
204
256
  .vphud-obj,.vphud-power{flex:1 1 50%;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
205
257
  .vphud-ico{font-weight:400}
206
- /* jogador que saiu (MP): viewport preto com "Jogo abandonado" no centro; os outros seguem */
207
- .vphud-quit{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#000;color:#cdd6f2;font-family:var(--font);font-weight:800;font-size:var(--hud-fs,14px);text-align:center}
258
+ /* a player who left (multiplayer): a black viewport with "Game abandoned" in the middle; the others carry on */
259
+ .vphud-quit{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:#000;color:#cdd6f2;font-family:var(--font);font-weight:800;font-size:var(--hud-fs,16px);text-align:center}
208
260
  .vphud-quit[hidden]{display:none}
209
- /* PAUSA dentro do canvas (tela = canvas) — sobreposta ao #game-region, não à página */
261
+ /* PAUSE inside the canvas (screen = canvas) — laid over #game-region, not over the page */
210
262
  .pause-incanvas{position:absolute;inset:0;z-index:6;display:flex;align-items:center;justify-content:center;background:var(--bg-solid);font-family:var(--font)}
211
263
  .pause-incanvas[hidden]{display:none}
212
- /* Etapa 2: menu de pausa POR TELA (dentro de cada .player-screen). Cada jogador vê e navega o seu. */
213
- .screen-pause{position:absolute;inset:0;z-index:6;display:flex;align-items:center;justify-content:center;background:var(--bg-solid);font-family:var(--font);font-size:var(--ui-fs,16px);pointer-events:auto} /* base = --ui-fs (16 no mínimo, escala com o canvas) */
264
+ /* The pause menu PER SCREEN (inside each .player-screen). Each player sees and moves through their own. */
265
+ .screen-pause{position:absolute;inset:0;z-index:6;display:flex;align-items:center;justify-content:center;background:var(--bg-solid);font-family:var(--font);font-size:var(--ui-fs,16px);pointer-events:auto} /* base = --ui-fs (16 at least, scales with the canvas) */
214
266
  .screen-pause[hidden]{display:none}
215
- .screen-pause .pause-card{max-width:94%;max-height:98%;padding:.5rem .7rem}
216
- .screen-pause .pause-card h2{font-size:1.2em;margin:.05rem 0 .28rem}
267
+ /* ADR-0163: seven 44 px items are 308 of the 360 px at 640×360 (measured). The title keeps the 16 px text floor with a tight
268
+ line (20 px, not 29) and the card may use the whole height. */
269
+ .screen-pause .pause-card{max-width:94%;max-height:100%;padding:.25rem .7rem}
270
+ /* ONE HEIGHT for every card of the menus (interface log, 2026-09-13): the root, its submenu, the panels and the help. The root's
271
+ six items fitted and its card shrank to them, 46 px shorter than the submenu at 640×360 (measured); it now holds the whole
272
+ height too, its items from the top, as a panel's rows are. */
273
+ .screen-pause .pause-card{height:100%;box-sizing:border-box}
274
+ /* THE EDGE MARGIN (interface log 2026-09-26): the card's CONTENT starts at the margin — its 2 px border just above it, no
275
+ padding under the border — so neither its title nor an item scrolled up under its top edge comes nearer the region's edge
276
+ than the margin (a padding inside the card would not do: a scrolled list shows through its own padding). 📏 The card had
277
+ 3 px to spare at 640×360 (pause-target-44px); the border and the old .25rem padding give back what the margin takes. */
278
+ .screen-pause{padding-top:calc(var(--margem-borda,8px) - 2px)}
279
+ .screen-pause .pause-card{padding-top:0}
280
+ /* THE CARD RUNS UNDER THE BUTTON LEGEND AT THE FOOT OF THE SCREEN, as a panel runs under its explanation (issue #134): the
281
+ room at its end and the scroll padding let its LAST item, and the one under the cursor, come to rest above the band —
282
+ and above the HUD row under it (`--rodape-h`), which is drawn over the card too. Measured at 640×360 before: «Sair do jogo»
283
+ at 288–332 px under a legend at 313–342, a third of it covered. */
284
+ .screen-pause .pause-card{scroll-padding-bottom:calc(var(--rodape-h,0px) + var(--footer-band-h,50px))}
285
+ .screen-pause .pause-card::after{content:"";display:block;height:calc(var(--rodape-h,0px) + var(--footer-band-h,50px))}
286
+ .screen-pause .pause-card h2{font-size:1em;line-height:1.25;margin:0 0 .1rem}
217
287
  .screen-pause .pause-menu{gap:.3rem}
218
- /* O quadro por jogador aperta o PADDING, nunca a altura mínima: o quadro apertado é o do jogo com quatro
219
- crianças, onde os dedos disputam espaço — o pior lugar possível para encolher um alvo de toque. O cartão
220
- tem `overflow:auto` e rola; o botão não diminui. */
288
+ /* The per-player frame tightens the PADDING, never the minimum height: the tight frame is the one of a game with four
289
+ children, where fingers compete for room — the worst possible place to shrink a touch target. The card has
290
+ `overflow:auto` and scrolls; the button does not shrink. */
221
291
  .screen-pause .pm-btn{padding:.3rem .7rem}
222
- .screen-pause .pause-legend{margin:.28rem 0 0}
223
- /* Atalhos de a11y = botões do menu em forma de ícone, DENTRO do card (sem barra/sombra própria).
224
- Navegáveis por teclado (cima a partir de Continuar) e por toque/clique. */
225
- .pause-icons{display:flex;flex-wrap:wrap;gap:.4rem;justify-content:center;align-items:center;margin:.1rem 0 .35rem}
226
- /* ===================== A BARRA RÁPIDA NO HUD (ADR-0044, item 7) =====================
227
- Ela saiu do cartão de pausa e passou a viver na tela do jogador, disponível DURANTE a partida — que é
228
- quando uma criança precisa mudar um ajuste que está a atrapalhando, e não depois de pausar.
229
-
230
- É IRMÃ da `.screen-exp` e não filha, e a razão é o #82: `filter` de CSS desce para os descendentes e um
231
- filho não consegue cancelá-lo. Dentro da experiência, o modo empatia degradaria justamente o que existe
232
- para DAR acesso — e simulação não é acessibilidade, é o contrário dela.
233
-
234
- `pointer-events:auto` porque a `.player-screen` inteira é `none` (o jogo recebe o toque); os dez ícones
235
- são a exceção, como o cartão de pausa já era. */
236
- /* A ALTURA DA BARRA, num token: é ela que empurra o HUD para baixo. Sem isto os dois ficavam ancorados no
237
- topo e se sobrepunham — o HUD lido por cima dos ícones, que foi o que o Dev viu e chamou de péssimo. */
238
- .player-screen{--a11y-h:calc(var(--tap) + .5rem)}
292
+ .screen-pause .pause-legend{margin:.1rem 0 0}
293
+ /* ADR-0163: with 42 px items the seven-item submenu overflowed 640×360 by 28 px (measured). The gap goes, not the target:
294
+ centres stay 44 px apart, far above the 24 px the WCAG 2.5.8 spacing exception protects. */
295
+ .screen-pause .pause-menu{gap:2px}
296
+ /* a11y shortcuts = menu buttons shaped as icons, INSIDE the card (no bar or shadow of their own).
297
+ Reachable by keyboard (up from Continue) and by touch/click. */
298
+ .pause-icons{display:flex;flex-wrap:wrap;gap:0;justify-content:center;align-items:center;margin:.1rem 0 .35rem}
299
+ /* ===================== THE QUICK BAR IN THE HUD (ADR-0044, item 7) =====================
300
+ It lives on the player's screen, available DURING play — which is when a child needs to change a setting that is
301
+ getting in the way, and not after pausing.
302
+
303
+ It is a SIBLING of `.screen-exp` and not a child, and the reason is #82: a CSS `filter` reaches every descendant and a
304
+ child cannot cancel it. Inside the experience, the empathy mode would degrade exactly what exists to GIVE access — and
305
+ simulation is not accessibility, it is its opposite.
306
+
307
+ `pointer-events:auto` because the whole `.player-screen` is `none` (the game receives the touch); the icons are the
308
+ exception, as the pause card already was. */
309
+ /* THE BAR'S HEIGHT, in one token: it is what pushes the HUD down. Without it both were anchored to the top and overlapped
310
+ — the HUD read over the icons, which is what the Dev saw and called terrible. */
311
+ .player-screen{--a11y-h:calc(var(--alvo-min,44px) + .5rem)}
239
312
  .screen-a11y{position:absolute;left:0;right:0;top:0;height:var(--a11y-h);z-index:5;pointer-events:auto;font-family:var(--font);font-size:var(--ui-fs,16px)}
240
- .screen-a11y .pause-icons{flex-wrap:nowrap;white-space:nowrap;gap:.25rem;margin:0;height:100%}
241
- /* SÓ OS BOTÕES OCUPAM ESPAÇO. A explicação é ABSOLUTA e fica fora do fluxo — ela aparece por cima do jogo ao
242
- passar o mouse (ou quando o cursor do modo `accessibility` pousa num ícone) e some sem deixar buraco. Uma
243
- faixa de texto reservando altura na tela de jogo é o contrário do que uma barra de atalho serve. */
244
- .screen-a11y .pause-icons-cap{position:absolute;left:50%;transform:translateX(-50%);top:calc(var(--a11y-h) + .1rem);margin:0;max-width:92%;text-align:center;font-size:.8em;color:#f6f5f0;pointer-events:none}
313
+ .screen-a11y .pause-icons{flex-wrap:nowrap;white-space:nowrap;gap:0;margin:0;height:100%}
314
+ /* ONLY THE BUTTONS TAKE ROOM. The explanation is ABSOLUTE and stays out of the flow — it appears over the game on mouse
315
+ hover (or when the `accessibility` mode's cursor rests on an icon) and goes without leaving a hole. A strip of text
316
+ reserving height on the game screen is the opposite of what a shortcut bar is for. */
317
+ .screen-a11y .pause-icons-cap{position:absolute;left:50%;transform:translateX(-50%);top:calc(var(--a11y-h) + .1rem);margin:0;max-width:92%;text-align:center;font-size:1em;color:#f6f5f0;pointer-events:none}
245
318
  .screen-a11y .pause-icons-cap:not(:empty){background:rgba(8,8,16,.92);border-radius:8px;padding:.2em .6em}
246
- /* O HUD desce para DEBAIXO da barra. É a mesma conta, e é por isso que ela mora num token: as duas medidas
247
- não podem divergir sem que uma tape a outra. */
319
+ /* The HUD moves BELOW the bar. It is the same sum, and that is why it lives in a token: the two measures cannot
320
+ diverge without one covering the other. */
248
321
  .screen-exp .vphud{top:var(--a11y-h)}
249
- .pi-btn{width:var(--tap);height:var(--tap);min-width:var(--tap);min-height:var(--tap);border-radius:10px;border:2px solid var(--ink-soft);background:#1a2740;font-size:1.25em;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center}
250
- /* Wizard de gamepad: demonstração com ANIMAÇÃO REAL (frames do jogo trocados via JS) + deslocamento CSS */
251
- #padwiz-demo{height:86px;display:flex;align-items:flex-end;justify-content:center;position:relative;margin:.15rem 0 .4rem;overflow:hidden}
252
- #padwiz-demo-img{height:64px;image-rendering:pixelated}
253
- #padwiz-demo-fx{position:absolute;top:2px;font-size:1.5em;line-height:1}
254
- #padwiz-demo.pw-left #padwiz-demo-img{animation:pwSlideL 1.4s linear infinite}
255
- #padwiz-demo.pw-right #padwiz-demo-img{animation:pwSlideR 1.4s linear infinite}
256
- #padwiz-demo.pw-up #padwiz-demo-img{animation:pwClimbUp 1.4s linear infinite}
257
- #padwiz-demo.pw-down #padwiz-demo-img{animation:pwClimbDown 1.4s linear infinite}
258
- #padwiz-demo.pw-jump #padwiz-demo-img{animation:pwJump .9s cubic-bezier(.3,0,.7,1) infinite}
259
- #padwiz-demo.pw-run #padwiz-demo-img{animation:pwSlideR .8s linear infinite}
260
- #padwiz-demo.pw-swap,#padwiz-demo.pw-start{align-items:center}
261
- #padwiz-demo.pw-swap #padwiz-demo-fx{position:static;white-space:nowrap;font-size:1.7em;animation:pwIcons 4s linear infinite}
262
- #padwiz-demo.pw-especial #padwiz-demo-fx{animation:pwPulse .8s ease-in-out infinite}
263
- #padwiz-demo.pw-start #padwiz-demo-fx{position:static;font-weight:800;font-size:1.9em;letter-spacing:.1em;animation:pwBlink 1s steps(2,end) infinite}
264
- @keyframes pwSlideL{0%{translate:34px 0}100%{translate:-34px 0}}
265
- @keyframes pwSlideR{0%{translate:-34px 0}100%{translate:34px 0}}
266
- @keyframes pwClimbUp{0%{translate:0 22px}100%{translate:0 -22px}}
267
- @keyframes pwClimbDown{0%{translate:0 -22px}100%{translate:0 22px}}
268
- @keyframes pwJump{0%,100%{translate:0 0}35%{translate:0 -26px}55%{translate:0 -22px}}
269
- @keyframes pwIcons{0%{translate:60px 0}100%{translate:-60px 0}}
270
- @keyframes pwPulse{0%,100%{scale:1;opacity:.7}50%{scale:1.5;opacity:1}}
271
- @keyframes pwBlink{0%,100%{opacity:1}50%{opacity:.25}}
272
- @media (prefers-reduced-motion: reduce){ #padwiz-demo *{animation:none !important} }
273
- .pi-btn.pi-on{background:var(--accent);border-color:var(--accent)}
274
- /* Modo TEA (🧩): pi-on preenchido de amarelo = silencioso · fundo branco = calmo · base (azul) = off.
275
- (fundo, não borda — a borda é reservada ao realce de seleção :focus/.pi-sel e sobreporia o sinal.) */
322
+ .pi-btn{width:var(--alvo-min,44px);height:var(--alvo-min,44px);min-width:var(--alvo-min,44px);min-height:var(--alvo-min,44px);border-radius:10px;border:2px solid var(--ink-soft);background:#1a2740;color:#f6f5f0;font-size:1.25em;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center}
323
+ /* Gamepad wizard: a demonstration with REAL ANIMATION (game frames swapped by JS) + a CSS offset */
324
+ /* ADR-0159 rule 10 / WCAG 1.4.1: «on» is not colour alone — a thick stripe along the base appears with it. */
325
+ .pi-btn.pi-on{background:var(--accent);border-color:var(--accent);box-shadow:inset 0 -5px 0 var(--accent-ink)}
326
+ /* ASD mode (🧩): pi-on filled yellow = silent · white background = calm · base (blue) = off.
327
+ (the background, not the border — the border is kept for the selection highlight :focus/.pi-sel and would hide the signal.) */
276
328
  .pi-btn.pi-calm{background:#fff;border-color:var(--ink-soft)}
277
- /* Correção de daltonismo (🚥): fundo bicolor "diagnóstico" — pares escolhidos por busca (simulação Machado
278
- 2009, ΔE Lab) p/ que quem TEM a deficiência-alvo veja o maior contraste e as outras duas o menor. Tritan
279
- (eixo azul-amarelo, isolado) separa limpo com vermelho/verde; protan e deutan compartilham o eixo
280
- vermelho-verde → separação só PARCIAL (limite físico). off = base (azul, borda cinza); borda clara = ativo. */
329
+ /* Colour-vision correction (🚥): a two-colour "diagnostic" background — pairs chosen by search (Machado's 2009 simulation,
330
+ Lab ΔE) so that whoever HAS the target deficiency sees the largest contrast and the other two the smallest. Tritan
331
+ (the blue-yellow axis, on its own) separates cleanly with red/green; protan and deutan share the red-green axis
332
+ → only a PARTIAL separation (a physical limit). off = base (blue, grey border); a light border = active. */
281
333
  .pi-btn.pi-cvd-protan{background:linear-gradient(90deg,#b3671b 0 50%,#b31b41 50% 100%);border-color:var(--ink)}
282
334
  .pi-btn.pi-cvd-deuter{background:linear-gradient(90deg,#0000f2 0 50%,#aa61f2 50% 100%);border-color:var(--ink)}
283
335
  .pi-btn.pi-cvd-tritan{background:linear-gradient(90deg,#f23d00 0 50%,#1bb341 50% 100%);border-color:var(--ink)}
284
336
  .pi-btn.pi-dis{opacity:.35;cursor:not-allowed}
285
- .pi-btn.pi-soon{opacity:.55}
286
337
  .pi-btn.pi-sel,.pi-btn:focus,.pi-btn:focus-visible{outline:2px solid var(--accent);outline-offset:1px;border-color:var(--accent)}
287
- .pause-icons-cap{text-align:center;margin:0 0 .3rem;font-size:.82em;color:var(--ink-soft);min-height:1.1em}
288
- /* `scrollbar-gutter: stable both-edges` — MEDIDO no navegador depois do item 6: com a lista em 44 px o
289
- cartão passa a rolar, a barra de rolagem come largura de UM lado só, e a lista centrada saía 7,5 px do
290
- centro. Reservar a calha nos dois lados devolve a simetria, role ou não. */
338
+ .pause-icons-cap{text-align:center;margin:0 0 .3rem;font-size:1em;color:var(--ink-soft);min-height:1.1em}
339
+ /* `scrollbar-gutter: stable both-edges` — MEASURED in the browser after item 6: with the list at 44 px the card starts
340
+ scrolling, the scrollbar eats width on ONE side only, and the centred list sat 7.5 px off centre. Reserving the gutter
341
+ on both sides gives the symmetry back, scrolling or not. */
291
342
  .pause-card{background:#0e1626;border:2px solid #ffd23f;border-radius:14px;padding:.8rem 1rem;max-width:88%;max-height:92%;overflow:auto;scrollbar-gutter:stable both-edges;text-align:center;box-shadow:0 8px 30px rgba(0,0,0,.5)}
292
343
  .pause-card h2{margin:.1rem 0 .6rem;color:#ffd23f}
293
- /* ===================== A LISTA DE PAUSA (ADR-0044, item 6) =====================
294
- Era `grid-template-columns:1fr 1fr`, e a grade contava a mesma mentira dos submenus da abertura: a seta
295
- anda em ordem de DOM, então "para baixo" pulava para a coluna da direita. Uma coluna, e o que se vê volta
296
- a ser a ordem em que se anda.
297
- CENTRADA E MAIS LARGA: 26rem contra os 262 px medidos antes — o número está no gate, não na opinião.
298
- `width` e não `min(26rem,100%)`: com a barra de ícones fora do cartão (item 7), o cartão passou a se
299
- encolher ao conteúdo, e um `100%` medido contra ele virava circular — a lista caiu para 182 px. Quem
300
- declara a largura é a LISTA; o cartão a acompanha até o `max-width` dele. */
301
- .pause-menu{display:flex;flex-direction:column;gap:.45rem;width:26rem;max-width:100%;margin:0 auto}
302
- /* A lista ESCONDIDA tem de sumir de verdade: `display:grid` de autor vence o `[hidden]{display:none}` do
303
- navegador, e sem esta linha as duas listas do cartão de pausa apareceriam empilhadas (ADR-0044, item 5). */
344
+ /* ===================== THE PAUSE LIST (ADR-0044, item 6) =====================
345
+ It was `grid-template-columns:1fr 1fr`, and the grid told the same lie as the opening submenus: the arrow moves in
346
+ DOM order, so "down" jumped to the right-hand column. One column, and what you see is again the order you move in.
347
+ CENTRED AND WIDER: 26 letters against the 262 px measured before — the number is in the gate, not in an opinion.
348
+ ⚠️ IN `em`, NOT `rem` (pointed out by the Dev): the menus' letters grow with the region (`--ui-fs`) and `rem` stays at the
349
+ root's 16 px — measured at 1920×1080, 48 px letters in a 416 px list, and every item wrapped onto two lines.
350
+ `width` and not `min(26em,100%)`: with the icon bar out of the card (item 7), the card shrinks to its content, and a
351
+ `100%` measured against it became circular — the list fell to 182 px. The LIST declares the width; the card follows
352
+ it up to its own `max-width`. */
353
+ .pause-menu{display:flex;flex-direction:column;gap:.45rem;width:26em;max-width:100%;margin:0 auto}
354
+ /* The HIDDEN list has to really disappear: an author's `display:grid` beats the browser's `[hidden]{display:none}`, and
355
+ without this line the two lists of the pause card would show stacked (ADR-0044, item 5). */
304
356
  .pause-menu[hidden]{display:none}
305
- /* 44 px NÃO é o mínimo da WCAG — o 2.5.8 pede 24×24, e o cartão já passava nisso com folga. 44 é a
306
- recomendação da Apple (HIG). `min-height` e não `padding` porque a altura de antes (35,6 px) não estava
307
- declarada em lugar nenhum: ela RESULTAVA da soma de padding, fonte e entrelinha. Um alvo que ninguém
308
- escreveu é um alvo que ninguém defende.
309
-
310
- ⚠️ ESTA LINHA DIZIA TAMBÉM «e este projeto trata piso como piso», e essa metade morreu no ADR-0095: o
311
- piso deixou de ser um número único e passou a ser a régua abaixo. Fica o resto, que continua a valer. */
312
- /* ⚠️ O ALVO DO ITEM DE MENU SEGUE A RÉGUA DO ADR-0095, e não um literal.
313
- Era `min-height:44px` cravado. O registro fixou que o alvo é função da ALTURA DA TELA — 24 px (WCAG
314
- 2.5.8, AA) abaixo de 540, 34 a partir de 540, 44 (2.5.5, AAA) a partir de 720 — porque um alvo grande
315
- que exige ROLAGEM para ser alcançado pode custar mais dedo do que um menor que está à vista.
316
-
317
- ⚠️ E ELE USA `--alvo-min` E NÃO `--tap`, que são coisas diferentes: `--tap` é o tamanho PREFERIDO e
318
- cresce com a escala do canvas (22 × k, nunca menos de 44); `--alvo-min` é o CHÃO por altura. Um botão
319
- isolado pode ser o preferido; um item de LISTA, que tem de caber inteiro, usa o chão.
320
-
321
- O `44px` do fallback é o comportamento de antes, para o caso de `layout()` não ter corrido — que era
322
- exatamente o que acontecia no `quiz.html` até 07/09. */
323
- .pm-btn{background:#1a2740;color:#eaf2f8;border:1px solid #3a4a6a;border-radius:10px;padding:.5rem .8rem;min-height:var(--alvo-min,44px);display:flex;align-items:center;justify-content:center;font:inherit;font-weight:700;cursor:pointer;white-space:nowrap}
357
+ /* 44 px is NOT the WCAG minimum — 2.5.8 asks for 24×24, and the card already passed that with room to spare. 44 is
358
+ Apple's recommendation (HIG). `min-height` and not `padding` because the earlier height (35.6 px) was declared
359
+ nowhere: it RESULTED from the sum of padding, font and line height. A target nobody wrote down is a target nobody
360
+ defends. */
361
+ /* The menu item's target is `--alvo-min`, never a literal. ADR-0163 superseded ADR-0095's height ruler (24/34/44 px):
362
+ the engine forces the resolution, no screen is under 640×360, and the floor is 22 × k — 44 px there, growing with the
363
+ scale. `applyScale` (`ui/layout`) writes it on the game region; the 44 px fallback is that same floor at k = 2. */
364
+ /* ADR-0159 rule 8 / WCAG 1.4.11: the item's border is its boundary against the card (#0e1626). #3a4a6a measured 2.04:1;
365
+ #6a7a96 is 4.2:1. */
366
+ .pm-btn{background:#1a2740;color:#eaf2f8;border:1px solid #6a7a96;border-radius:10px;padding:.5rem .8rem;min-height:var(--alvo-min,44px);display:flex;align-items:center;justify-content:center;font:inherit;font-weight:700;cursor:pointer;white-space:nowrap}
367
+ /* ADR-0161: a locked item stays in the list, visibly muted; the cursor still lands on it and it says its reason. */
368
+ .pm-btn[aria-disabled="true"]{color:#9aa6b8;border-style:dashed;cursor:not-allowed}
324
369
  .pm-btn:hover,.pm-btn:focus,.pm-btn.pm-sel{background:#2a3a5e;border-color:#ffd23f;outline:2px solid #ffd23f;outline-offset:1px}
325
- /* Legenda sim/não: ícone do botão (conforme layout do controle) + a palavra. Sony/Nintendo invertem 0↔1. */
326
- .pause-legend{display:flex;gap:1.1rem;justify-content:center;margin:.7rem 0 .1rem;font-size:.82em;color:#c3cfe0}
370
+ /* ADR-0167: no item shows a position number — the place is SPOKEN after the name («…, 2 de 6»), never drawn.
371
+ ADR-0159 rule 12: a pause item's glyph is drawn with empty alternative text, out of the name. */
372
+ .pm-btn[data-glifo]::before{content:attr(data-glifo) / "";margin-inline-end:.5em}
373
+ /* Yes/no legend: the button's glyph (from the pad's design) + the word. By POSITION on every design — yes is the south button
374
+ (A · ✕ · B), no is the east one (B · ◯ · A); no design swaps them (the Dev's association, ADR-0013 erratum). */
375
+ .pause-legend{display:flex;gap:1.1rem;justify-content:center;margin:.7rem 0 .1rem;font-size:1em;color:#c3cfe0}
327
376
  .pause-legend .lg{display:inline-flex;align-items:center;gap:.35rem}
328
- .pause-legend .lg-ico{display:inline-flex;align-items:center;justify-content:center;width:1.35rem;height:1.35rem;border-radius:50%;font-weight:800;font-size:.8rem;color:#fff;text-shadow:0 1px 1px rgba(0,0,0,.55)}
377
+ .pause-legend .lg-ico{display:inline-flex;align-items:center;justify-content:center;width:1.35rem;height:1.35rem;border-radius:50%;font-weight:800;font-size:1em;color:#fff;text-shadow:0 1px 1px rgba(0,0,0,.55)}
329
378
  .hud__item strong{font-variant-numeric:tabular-nums}
330
379
  .hud__fps{color:var(--ink-soft)}
331
380
 
332
381
  main{flex:1;display:flex;flex-direction:column;width:100%;margin:0 auto;padding:0 .25rem .25rem;min-height:0;overflow:hidden}
333
- /* tela = prioridade: ocupa todo o espaço; o excedente (até 5px×k por lado) é cortado sem rolagem */
382
+ /* screen = priority: it takes all the room; the excess (up to 5px×k per side) is cut without scrolling */
334
383
  .stage-wrap{flex:1;display:flex;align-items:center;justify-content:center;min-height:0;padding:0;overflow:hidden}
335
384
  .stage{position:relative;display:flex;align-items:center}
336
- /* ===== A CAMADA DOM (issue #82) =====
337
- Tudo o que não é a canvas. Existe para o filtro de acessibilidade poder cair numa superfície que contém os
338
- MENUS e não contém o mundo — filtro de PIXI não alcança DOM, e filtrar o `#game-region` inteiro aplicaria
339
- duas vezes na canvas.
385
+ /* ===== THE DOM LAYER (issue #82) =====
386
+ Everything that is not the canvas. It exists so the accessibility filter can land on a surface that holds the MENUS
387
+ and not the world — a PIXI filter does not reach the DOM, and filtering the whole `#game-region` would apply twice
388
+ on the canvas.
340
389
 
341
- `pointer-events:none` porque o invólucro cobre a região inteira e não pode engolir clique; os quatro filhos
342
- que precisam de clique e não o declaravam (herdavam do `#game-region`) ganham `auto` logo abaixo.
390
+ `pointer-events:none` because the wrapper covers the whole region and must not swallow clicks; the four children
391
+ that need clicks and did not declare it (they inherited it from `#game-region`) get `auto` just below.
343
392
 
344
- `z-index:1`: acima da canvas, ABAIXO dos dois pseudo-elementos do CRT (vinheta em 5, scanline em 500), que
345
- ficam no `#game-region`. O CRT é efeito de tela e passa por cima de tudo — o que a scanline já fazia por
346
- decisão do Dev, e a vinheta agora acompanha. */
393
+ `z-index:1`: above the canvas, BELOW the two CRT pseudo-elements (vignette at 5, scanline at 500), which sit on
394
+ `#game-region`. The CRT is a screen effect and goes over everything — which the scanline already did by the Dev's
395
+ decision, and the vignette now follows. */
347
396
  #dom-layer{position:absolute;inset:0;z-index:1;pointer-events:none}
348
397
 
349
- /* ===== ALTO CONTRASTE NO DOM (issue #83) =====
350
- O alto contraste NÃO é filtro: ele é Renderização Direta, e repinta as TEXTURAS da canvas. No DOM não há
351
- textura para repintar — há que escrever o equivalente, e é isto.
398
+ /* ===== HIGH CONTRAST IN THE DOM (issue #83) =====
399
+ High contrast is NOT a filter: it is Direct Rendering, and it repaints the canvas's TEXTURES. The DOM has no texture
400
+ to repaint — the equivalent has to be written, and this is it.
352
401
 
353
- MEDIDO, não estimado (WCAG 1.4.3, razão sobre sRGB linearizado). O que falhava, e só no nível 7:1:
402
+ MEASURED, not estimated (WCAG 1.4.3, the ratio over linearised sRGB). What failed, and only at the 7:1 level:
354
403
 
355
- · o VÉU do painel de pausa é `rgba(4,7,15,.72)` — translúcido. A razão do texto sobre ele DEPENDE do
356
- jogo por trás: 19,17:1 sobre um quadro preto e 8,07:1 sobre um branco. Com `--ink-soft`, `--accent` e
357
- `--good` a conta cai a 5,92 / 5,94 / 5,11:1 — AA, não AAA. Nos níveis 3:1 e 4,5:1 nada falhava.
358
- · o botão SELECIONADO é `#2a3a5e`, e `--good` sobre ele dá 6,70:1.
404
+ · the pause panel's VEIL was `rgba(4,7,15,.72)` — translucent. The text's ratio over it DEPENDED on the game behind:
405
+ 19.17:1 over a black frame and 8.07:1 over a white one. With `--ink-soft`, `--accent` and `--good` the sum fell to
406
+ 5.92 / 5.94 / 5.11:1 — AA, not AAA. At the 3:1 and 4.5:1 levels nothing failed.
407
+ · the SELECTED button is `#2a3a5e`, and `--good` on it gives 6.70:1.
359
408
 
360
- Duas mudanças, e nenhuma inventa cor:
409
+ Two changes, and neither invents a colour:
361
410
 
362
- 1. O VÉU FICA OPACO. É o que "alto contraste" quer dizer: sai o meio-tom, e com ele a dependência do que
363
- está atrás. Sozinha, esta já leva tudo a AAA menos um caso.
364
- 2. O CURSOR INVERTE — fundo `--accent`, texto `--accent-ink`, 12,71:1. Escurecer o selecionado o
365
- aproximaria do não-selecionado (`#1a2740`) e a criança PERDERIA O CURSOR; inverter resolve as duas
366
- coisas de uma vez, e é o padrão de alto contraste de qualquer sistema operacional.
411
+ 1. THE VEIL IS OPAQUE. That is what "high contrast" means: the half-tone goes, and with it the dependence on what is
412
+ behind. On its own this takes everything to AAA but one case.
413
+ 2. THE CURSOR INVERTS — `--accent` background, `--accent-ink` text, 12.71:1. Darkening the selected item would bring
414
+ it closer to the unselected one (`#1a2740`) and the child would LOSE THE CURSOR; inverting solves both at once, and
415
+ it is the high-contrast pattern of every operating system.
367
416
 
368
- Resultado: o pior par do menu vai de 5,11:1 para 10,29:1 (`--ink-soft` sobre botão). Margem de +47% sobre
369
- o 7:1. `tests/contraste-menu.node.test.js` afirma isso a cada rodada, com as razões calculadas do próprio
370
- CSS — não copiadas para dentro do teste.
417
+ Result: the worst pair of the menu goes from 5.11:1 to 10.29:1 (`--ink-soft` on a button). A margin of +47% over
418
+ 7:1. `tests/menu-contrast-measured.node.test.js` asserts it on every run, with the ratios computed from this CSS
419
+ itself — not copied into the test.
371
420
 
372
- ⚠️ VALE PARA OS TRÊS NÍVEIS, e não só para o 7:1: nos outros dois o menu já passava, mas passava POR SORTE
373
- do que estivesse na tela. Determinismo é ganho em todos. */
374
- /* (A regra do véu SAIU daqui em 2026-08-26: ele passou a ser opaco NA BASE, por decisão do Dev — os menus
375
- de título e de pausa têm de estar em 7:1 DESDE O INÍCIO, porque é justamente neles que a pessoa com
376
- deficiência ajusta os controles para si. Um contraste que só chega depois de achar o ajuste chega tarde.
377
- Sobra aqui o que é específico do modo: o cursor invertido.) */
421
+ ⚠️ IT HOLDS FOR ALL THREE LEVELS, not only 7:1: at the other two the menu already passed, but it passed BY THE LUCK
422
+ of whatever was on screen. Determinism is a gain at every level. */
423
+ /* (The veil's rule is not here: the veil is opaque AT THE BASE, by the Dev's decision — the title and pause menus have to
424
+ be at 7:1 FROM THE START, because they are precisely where a person with a disability adjusts the controls for
425
+ themselves. A contrast that only arrives after finding the setting arrives late.
426
+ What is left here is what is specific to the mode: the inverted cursor.) */
378
427
  #dom-layer.hc .pm-btn{background:var(--panel-btn)}
379
428
  #dom-layer.hc .pm-btn:hover,
380
429
  #dom-layer.hc .pm-btn:focus,
@@ -384,63 +433,76 @@ main{flex:1;display:flex;flex-direction:column;width:100%;margin:0 auto;padding:
384
433
  .game-region{position:relative;background:#05070f;box-shadow:0 0 0 3px var(--ink-soft);border-radius:8px;overflow:hidden;flex:none}
385
434
  .libras-slot{flex:none;width:0;height:0;background:#0b1020;border-radius:8px;overflow:hidden}
386
435
  .game-region:focus-visible{outline:4px solid var(--focus);outline-offset:3px}
387
- /* ===== O AVISO DE ALCANCE (issue #112) =====
388
- Reaproveita `.overlay` e `.overlay__card` inteiros — o cartão nasce com a mesma casca dos outros diálogos,
389
- e é isso que o faz parecer parte do jogo em vez de um erro do navegador. Só o destaque do título é próprio. */
436
+ /* ===== THE REACH NOTICE (issue #112) =====
437
+ It reuses `.overlay` and `.overlay__card` whole — the card is born with the same shell as the other dialogs, and
438
+ that is what makes it look like part of the game instead of a browser error. Only the title's emphasis is its own. */
390
439
  #reach-notice .reach-notice__titulo{font-weight:700;margin:0 0 .5rem}
391
440
  #reach-notice p{margin:0 0 .5rem}
392
441
  #reach-notice button{margin-top:.75rem}
393
- /* ===== O LAÇO PAROU (ADR-0054, issue #109) =====
394
- O TEXTO VEM DO ATRIBUTO, e é esse o ponto: `ui/loop-crash` escreve a frase JÁ TRADUZIDA em
395
- `data-incl-parou`, e o CSS só a mostra. Um aviso cravado aqui estaria em inglês para uma criança
396
- brasileira — o defeito que o pilar 3 existe para impedir.
397
- ⚠️ E AS CORES SÃO CRUAS, DE PROPÓSITO: nada de `var(--…)`. Este aviso aparece justamente quando alguma
398
- coisa quebrou, e a máquina de temas é candidata plausível. Branco sobre vinho dá 9,5:1, acima do 7:1 do
399
- 1.4.6, e não depende de nada ter sobrevivido. */
400
- /* ⚠️ ERA UM `::after` COM `content: attr(...)`, E NUNCA APARECERIA. Medido no arranque real: o
401
- `.game-region.crt-scan-1::after` logo abaixo é a scanline do CRT, ligada por padrão — e um elemento tem UM
402
- `::after` só. As duas regras não se empilham; a do CRT vem depois e vence. O `::before` está tomado pela
403
- vinheta. Agora é um ELEMENTO, que não disputa lugar com ninguém — e cujo texto entra na árvore de
404
- acessibilidade, coisa que `content` não garante. `z-index` acima do 500 da scanline, de propósito. */
442
+ /* ===== THE LOOP STOPPED (ADR-0054, issue #109) =====
443
+ THE TEXT COMES FROM THE SCRIPT, and that is the point: `ui/loop-crash` writes the sentence ALREADY TRANSLATED (from
444
+ `t()`, through `textContent`), and the CSS only shows it. A notice hard-coded here would be in English for a Brazilian
445
+ child — the defect pillar 3 exists to prevent.
446
+ ⚠️ AND THE COLOURS ARE RAW, ON PURPOSE: no `var(--…)`. This notice appears exactly when something broke, and the
447
+ theming machinery is a plausible suspect. White on wine gives 9.5:1, above the 7:1 of 1.4.6, and does not depend on
448
+ anything having survived. */
449
+ /* ⚠️ IT IS AN ELEMENT, NOT A `::after` WITH `content: attr(...)` — that version would never show. The
450
+ `.game-region.crt-scan-1::after` just below is the CRT scanline, on by default — and an element has ONE `::after`
451
+ only. The two rules do not stack; the CRT's comes later and wins. The `::before` is taken by the vignette. An
452
+ ELEMENT competes with nobody for its place — and its text enters the accessibility tree, which `content` does not
453
+ guarantee. Its slot, `--z-loop-crash`, is above the scanline's 500 on purpose. */
405
454
  #incl-parou{
406
455
  position:absolute;inset:auto 0 0 0;z-index:var(--z-loop-crash);
407
456
  padding:14px 18px;
408
457
  background:#7f1d1d;color:#fff;border-top:3px solid #fff;
409
- font:700 15px/1.35 system-ui,-apple-system,"Segoe UI",sans-serif;
458
+ font:700 1em/1.35 system-ui,-apple-system,"Segoe UI",sans-serif;
410
459
  text-align:center;text-wrap:balance
411
460
  }
412
461
  .pixi-mount,.pixi-mount canvas{display:block;width:100%;height:100%}
413
- /* pixel-art nítida ao escalar */
462
+ /* crisp pixel art when scaling */
414
463
  .pixi-mount canvas{image-rendering:pixelated;image-rendering:crisp-edges}
415
- /* L2: Estética CRT (menu Sensibilidade visual) em 3 níveis: -1=pequeno, -2=grande. z-index 5 = sobre o
416
- canvas/HUD do jogo, ABAIXO da pausa in-canvas (6) e dos diálogos (60) → menus nunca perdem legibilidade. */
464
+ /* The CRT look (visual sensitivity menu) in 3 levels: -1=small, -2=large. z-index 5 = over the game's canvas/HUD, BELOW the
465
+ in-canvas pause (6) and the dialogs (60) → the menus never lose legibility. */
417
466
  .game-region.crt-scan-1::after,
418
467
  .game-region.crt-vig-1::before{content:'';position:absolute;inset:0;z-index:5;pointer-events:none}
419
- /* Scanlines ALINHADAS: período = 1 pixel LÓGICO do jogo (k px), arredondado p/ pixel FÍSICO (dpr) e
420
- linha = 1 pixel físico — o JS (layout/applyCrt) calcula --scan-per/--scan-line; sem isso o gradiente
421
- 1px/3px em px CSS caía fora da grade (linhas desiguais em telas com escala do Windows). */
422
- .game-region.crt-scan-1::after{z-index:500;background:repeating-linear-gradient(to bottom, rgba(0,0,0,.16) 0 var(--scan-line,1px), transparent var(--scan-line,1px) var(--scan-per,3px))} /* z-index alto = scanlines POR CIMA de tudo (canvas E menus DOM), pedido do José */
468
+ /* ALIGNED scanlines: period = 1 LOGICAL game pixel (k px), rounded to a PHYSICAL pixel (dpr), and line = 1 physical
469
+ pixel — the JS (`render/crt`, applyCrt) computes --scan-per/--scan-line; without it the 1px/3px gradient in CSS px
470
+ fell off the grid (uneven lines on screens with Windows scaling). */
471
+ .game-region.crt-scan-1::after{z-index:500;background:repeating-linear-gradient(to bottom, rgba(0,0,0,.16) 0 var(--scan-line,1px), transparent var(--scan-line,1px) var(--scan-per,3px))} /* a high z-index = scanlines OVER everything (canvas AND DOM menus), the Dev's request */
423
472
  .game-region.crt-vig-1::before{background:radial-gradient(ellipse at center, transparent 60%, rgba(0,0,0,.34) 100%)}
424
- .game-region.crt-round-0{border-radius:0} /* tela quadrada */
425
- .game-region.crt-round-2{border-radius:24px} /* nível 1 (pequeno) = o padrão de 8px, sem classe */
473
+ .game-region.crt-round-0{border-radius:0} /* square screen */
474
+ .game-region.crt-round-2{border-radius:24px} /* level 1 (small) = the 8px default, no class */
426
475
  @media (prefers-reduced-transparency: reduce){ .game-region.crt-scan-1::after,.game-region.crt-vig-1::before{display:none} }
427
476
 
428
- /* dicas de início sobre o canvas */
477
+ /* start tips over the canvas */
429
478
  .start-tips{position:absolute;left:50%;top:14%;transform:translateX(-50%);
430
479
  display:flex;flex-direction:column;gap:.5rem;align-items:center;pointer-events:none;
431
480
  transition:opacity .5s ease;z-index:6;max-width:92%}
432
481
  .start-tips p{margin:0;background:rgba(5,7,15,.85);color:#fff;border:2px solid rgba(255,255,255,.45);
433
- border-radius:10px;padding:.4em .9em;font-size:1.05rem;font-weight:600;text-align:center}
482
+ border-radius:10px;padding:.4em .9em;font-size:1.05em;font-weight:600;text-align:center}
434
483
  .start-tips kbd{background:var(--accent);color:var(--accent-ink);border-radius:6px;padding:.05em .5em;font-weight:800}
435
484
  .start-tips.hide{opacity:0}
436
485
  @media (prefers-reduced-motion: reduce){ .start-tips{transition:none} }
437
486
 
438
- /* seletor de modo */
487
+ /* mode selector */
439
488
  .modes{max-width:var(--maxw);margin:0 auto .5rem;padding:0 1rem;display:flex;gap:.5rem;flex-wrap:wrap}
440
- .mode-btn{min-height:var(--tap);padding:.4em .9em;font:inherit;font-weight:700;color:var(--ink);background:var(--panel);border:2px solid var(--ink-soft);border-radius:999px;cursor:pointer}
489
+ .mode-btn{min-height:var(--alvo-min,44px);padding:.4em .9em;font:inherit;font-weight:700;color:var(--ink);background:var(--panel);border:2px solid var(--ink-soft);border-radius:999px;cursor:pointer}
441
490
  .mode-btn.is-on{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}
442
- /* TOGGLE (switch): substitui os botões "ícone + Ligado/Desligado". Estado = trilho + manopla que desliza.
443
- Semântica de botão de alternância via aria-pressed (o texto interno fica oculto por font-size:0). */
491
+ /* STEPS ⯇ ⯈ (ADR-0151): choosing between positions with left and right. The arrows are FINGER targets and follow the
492
+ target ruler (`--alvo-min`, ADR-0163); the focus belongs to the whole control, and the end that cannot move is dimmed. */
493
+ .passos{display:inline-flex;align-items:center;gap:.25rem;min-height:var(--alvo-min,44px);border:2px solid var(--ink-soft);border-radius:8px;padding:0 .15rem;background:var(--panel-btn);color:var(--ink);font-weight:700;user-select:none;-webkit-user-select:none}
494
+ .passos:focus,.passos:focus-visible{outline:3px solid var(--focus);outline-offset:2px}
495
+ .passo-seta{display:inline-flex;align-items:center;justify-content:center;min-width:var(--alvo-min,44px);min-height:var(--alvo-min,44px);cursor:pointer;font-size:1.1em;touch-action:manipulation}
496
+ .passo-seta.no-limite{opacity:.35;cursor:default}
497
+ .passo-valor{min-width:6ch;text-align:center;flex:1}
498
+ /* «◀ Label: value ▶» takes the whole ROW (ADR-0130 erratum): there is no label on the left competing for the room. */
499
+ /* 🔴 AND NO BOX INSIDE THE BOX: the ROW is the item. The first version kept `.passos`'s border and background, and the
500
+ text wrapped onto four lines inside a box beside the hint — the Dev: «Por que continua desenhando os botões de seleção
501
+ de <=5 itens do jeito errado?!». On one line, `◀ Label: value ▶`, with no wrapping. */
502
+ .ctrl-row--passos .passos{flex:1;justify-content:space-between;border:0;background:transparent;padding:0;white-space:nowrap}
503
+ .ctrl-row--passos .passo-valor{white-space:nowrap}
504
+ /* TOGGLE (switch): replaces the "icon + On/Off" buttons. State = a track + a knob that slides.
505
+ Toggle-button semantics through aria-pressed (the inner text is hidden by font-size:0). */
444
506
  .mode-btn.switch{--sw-w:52px;--sw-h:28px;position:relative;width:var(--sw-w);min-width:var(--sw-w);height:var(--sw-h);min-height:var(--sw-h);padding:0;flex:0 0 auto;border-radius:999px;border:2px solid var(--ink-soft);background:#0e1326;color:transparent;font-size:0;transition:background .18s,border-color .18s}
445
507
  .mode-btn.switch::after{content:"";position:absolute;top:50%;left:3px;transform:translateY(-50%);width:calc(var(--sw-h) - 10px);height:calc(var(--sw-h) - 10px);border-radius:50%;background:var(--ink-soft);transition:left .18s,background .18s}
446
508
  .mode-btn.switch[aria-pressed="true"]{background:var(--accent);border-color:var(--accent)}
@@ -448,33 +510,33 @@ main{flex:1;display:flex;flex-direction:column;width:100%;margin:0 auto;padding:
448
510
  .mode-btn.switch:focus-visible,.mode-btn.switch:focus{outline:3px solid var(--accent);outline-offset:2px}
449
511
  @media (prefers-reduced-motion: reduce){ .mode-btn.switch,.mode-btn.switch::after{transition:none} }
450
512
 
451
- /* desafio Soma-Sub (overlay sobre o canvas) */
513
+ /* the quiz challenge (an overlay over the canvas) */
452
514
  .quiz{position:absolute;inset:0;z-index:12;display:grid;place-items:center;background:rgba(0,0,0,.55)}
453
515
  .quiz-box{position:relative;background:#14141f;color:var(--ink);border:3px solid var(--accent);border-radius:12px;padding:1rem 1.1rem;max-width:90%;text-align:center}
454
- /* 3 luzes de progresso (canto sup. direito): apagada = cinza; acesa = amarela com luz acesa em volta */
516
+ /* 3 progress lights (top right corner): off = grey; on = yellow with a glow around it */
455
517
  .quiz-wins{position:absolute;top:.5rem;right:.6rem;display:flex;gap:.4rem}
456
518
  .qw-dot{width:.95rem;height:.95rem;border-radius:50%;background:#3a4152;border:2px solid #0d0d1a}
457
- .qw-dot.on{background:var(--accent);border-color:#8a6d00;box-shadow:0 0 .55rem .12rem var(--accent)} /* iluminação em volta (luz acesa) */
519
+ .qw-dot.on{background:var(--accent);border-color:#8a6d00;box-shadow:0 0 .55rem .12rem var(--accent)} /* the glow around it (a light that is on) */
458
520
  .quiz-wins.celebrate .qw-dot.on{animation:qwPop .6s ease}
459
521
  @keyframes qwPop{0%{transform:scale(1)}45%{transform:scale(1.4);box-shadow:0 0 .9rem .3rem var(--accent)}100%{transform:scale(1)}}
460
522
  @media (prefers-reduced-motion:reduce){ .quiz-wins.celebrate .qw-dot.on{animation:none} }
461
523
  .quiz-shape{color:var(--accent);font-weight:800;text-transform:capitalize}
462
- .quiz-prob{font-size:1.6rem;font-weight:800;margin:.4rem 0 .8rem}
524
+ .quiz-prob{font-size:1.6em;font-weight:800;margin:.4rem 0 .8rem}
463
525
  .quiz-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:.4rem}
464
- .quiz-choice{min-height:var(--tap);min-width:var(--tap);font:inherit;font-size:1.2rem;font-weight:700;color:var(--ink);background:#131a30;border:2px solid var(--ink-soft);border-radius:8px;cursor:pointer}
526
+ .quiz-choice{min-height:var(--alvo-min,44px);min-width:var(--alvo-min,44px);font:inherit;font-size:1.2em;font-weight:700;color:var(--ink);background:#131a30;border:2px solid var(--ink-soft);border-radius:8px;cursor:pointer}
465
527
  .quiz-choice.sel{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}
466
528
  .quiz-choice.reveal{outline:3px solid var(--good);outline-offset:2px}
467
- .quiz-hint{font-size:.85rem;margin-top:.6rem;color:var(--ink-soft)}
468
- .quiz-emoji{font-size:2.4rem;line-height:1}
469
- /* Palavra do topo = emoji SELECIONÁVEL (pular sobre ela repete a fala / VLibras gesticula) */
470
- .quiz-word{font-size:2.4rem;line-height:1;background:transparent;border:2px solid transparent;border-radius:12px;padding:.05em .2em;cursor:pointer}
529
+ .quiz-hint{font-size:1em;margin-top:.6rem;color:var(--ink-soft)}
530
+ .quiz-emoji{font-size:2.4em;line-height:1}
531
+ /* The top word = a SELECTABLE emoji (jumping on it repeats the speech / VLibras signs it) */
532
+ .quiz-word{font-size:2.4em;line-height:1;background:transparent;border:2px solid transparent;border-radius:12px;padding:.05em .2em;cursor:pointer}
471
533
  .quiz-word.sel{border-color:var(--accent);background:rgba(255,230,0,.12)}
472
- .quiz-letter{font-size:.8rem;color:var(--ink-soft);margin:.1rem 0 .4rem}
534
+ .quiz-letter{font-size:1em;color:var(--ink-soft);margin:.1rem 0 .4rem}
473
535
  .silaba-boxes{display:flex;gap:.4rem;justify-content:center;margin-bottom:.5rem}
474
- .silaba-box{min-width:34px;min-height:28px;border:2px dashed var(--ink-soft);border-radius:6px;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:1.15rem}
536
+ .silaba-box{min-width:34px;min-height:28px;border:2px dashed var(--ink-soft);border-radius:6px;display:flex;align-items:center;justify-content:center;font-weight:800;font-size:1.15em}
475
537
  .silaba-box.filled{border-style:solid;border-color:var(--accent);background:rgba(255,255,255,.08)}
476
538
  .silaba-actions{display:flex;gap:.4rem;justify-content:center;margin-top:.4rem}
477
- /* E8: cela Braille */
539
+ /* a Braille cell */
478
540
  .bcells{display:flex;gap:.5rem;justify-content:center;flex-wrap:wrap;margin:.4rem 0}
479
541
  .bcell{display:flex;flex-direction:column;align-items:center;gap:.2rem}
480
542
  .bcell-grid{display:grid;grid-template-columns:repeat(2,10px);grid-auto-rows:10px;gap:3px;padding:4px;background:#0e1326;border:1px solid var(--ink-soft);border-radius:4px}
@@ -484,94 +546,394 @@ main{flex:1;display:flex;flex-direction:column;width:100%;margin:0 auto;padding:
484
546
  @media (prefers-contrast: more){ .quiz-box{background:#000} .quiz-choice{background:#000} .quiz-choice.sel{background:#fff;color:#000} }
485
547
 
486
548
  .opts{max-width:var(--maxw);margin:0 auto .5rem;padding:0 1rem;display:flex;gap:.4rem;flex-wrap:wrap;justify-content:center}
487
- /* barra de controles compacta → cabe em UMA fileira em telas comuns (não afeta botões de overlay) */
488
- .opts .mode-btn{padding:.35em .65em;font-size:.92rem;white-space:nowrap}
549
+ /* a compact controls bar → fits on ONE row on common screens (does not affect overlay buttons) */
550
+ .opts .mode-btn{padding:.35em .65em;font-size:1em;white-space:nowrap}
489
551
  .caption{position:absolute;left:50%;bottom:8px;transform:translateX(-50%);background:rgba(0,0,0,.82);color:#fff;
490
- border:2px solid rgba(255,255,255,.5);border-radius:8px;padding:.25em .7em;font-weight:700;font-size:.95rem;
552
+ border:2px solid rgba(255,255,255,.5);border-radius:8px;padding:.25em .7em;font-weight:700;font-size:1em;
491
553
  z-index:8;pointer-events:none;max-width:90%;opacity:0;transition:opacity .15s}
492
554
  .caption.show{opacity:1}
493
555
  @media (prefers-reduced-motion: reduce){ .caption{transition:none} }
494
556
 
495
557
  .help{max-width:var(--maxw);margin:1rem auto 0;color:var(--ink-soft)}
496
- .help summary{cursor:pointer;font-weight:700;color:var(--ink);min-height:var(--tap);display:flex;align-items:center}
558
+ .help summary{cursor:pointer;font-weight:700;color:var(--ink);min-height:var(--alvo-min,44px);display:flex;align-items:center}
497
559
  .help ul{padding-left:1.1rem}
498
560
  .help li{margin:.3rem 0}
499
561
 
500
- .btn{min-height:var(--tap);min-width:var(--tap);padding:.5em 1em;font:inherit;font-weight:600;color:var(--ink);background:var(--panel);border:2px solid var(--ink-soft);border-radius:10px;cursor:pointer}
562
+ .btn{min-height:var(--alvo-min,44px);min-width:var(--alvo-min,44px);padding:.5em 1em;font:inherit;font-weight:600;color:var(--ink);background:var(--panel);border:2px solid var(--ink-soft);border-radius:10px;cursor:pointer}
501
563
  .btn--primary{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}
502
564
 
503
565
  .overlay{position:fixed;inset:0;z-index:50;display:grid;place-items:center;background:rgba(0,0,0,.7);padding:1rem}
504
- /* "tela = canvas": os diálogos de a11y são reparentados para dentro do #game-region (JS) e ficam
505
- presos aos limites do canvas; o último aberto sobe (z-index via JS). Só afeta os que estão dentro. */
566
+ /* "screen = canvas": the a11y dialogs are re-parented into #game-region (JS) and stay held within the canvas's bounds;
567
+ the last one opened goes on top (z-index by JS). Only affects the ones inside. */
506
568
  #game-region .overlay{position:absolute;z-index:60;padding:.6rem}
507
- #game-region .overlay__card{max-height:94%;max-width:96%}
508
- /* Rodapé de explicação: branco sobre preto translúcido 0,82 → contraste do branco >7:1 mesmo sobre 100% branco
509
- (composição: 1-0,82=0,18 → L≈0,027 → ~13:1), sem ser 100% opaco. Fixo no rodapé do card. */
510
- .opt-explain{position:sticky;bottom:calc(-1 * var(--card-pad,1.5rem));margin:.8rem calc(-1 * var(--card-pad,1.5rem)) calc(-1 * var(--card-pad,1.5rem));padding:.6rem .9rem;min-height:2.8rem;display:flex;align-items:center;text-align:left;
511
- background:rgba(0,0,0,.82);color:#fff;font-size:.9em;line-height:1.35;border-top:1px solid rgba(255,255,255,.18);border-radius:0 0 9px 9px;backdrop-filter:blur(1px)}
569
+ /* THE QUIZ STARTS BELOW THE QUICK BAR (ADR-0148). The engine declares `--barra-a11y-h` on `#game-region`; the bar sits
570
+ 10 px from the top (`#title-icons`), and the statement was drawn behind it — measured on a screenshot of the Dev's. */
571
+ /* Since issue #160 the variable is the WHOLE room (the bar's offset, the bar, the icon name line and a light gap): read as is. */
572
+ .quiz-app{padding-top:var(--barra-a11y-h,0px);padding-bottom:var(--rodape-h,0px)} /* the HUD row is always there; the footer bands overlay (ADR-0239 erratum) */
573
+ /* THE EDGE MARGIN (interface log 2026-09-26; `--margem-borda`, written by `ui/layout` from k): the Dev, «os textos estão tocando
574
+ na borda do jogo». The sides get it here; the top is the quick bar's room and the bottom the HUD row's, which keeps its own. */
575
+ .quiz-app{padding-left:var(--margem-borda,8px);padding-right:var(--margem-borda,8px)}
576
+ /* ADR-0163: at 640×360 the bar, the statement, four 44 px options and the footer band had 404 px to fit in 360 (measured).
577
+ The spacing goes, not the targets or the 16 px floor: the statement keeps its size on a 1.25 line and no outer margins. */
578
+ .quiz-pergunta{margin:0 0 calc(.25rem * var(--espaco-fixo,1));line-height:1.25}
579
+ /* The quiz's MENU button (ADR-0166 erratum), in the top-left corner the bar leaves free: the engine's target size. */
580
+ /* THE QUICK PAUSE (ADR-0155): PAUSED in the middle of the frozen screen — THE WORD ONLY, white, with a BLACK OUTLINE ON
581
+ THE LETTERS and no box: «Borda nas letras, não numa caixa» (the Dev, the record's erratum). The outline is what keeps it
582
+ legible over a light scene; `paint-order` puts the stroke UNDER the fill, so the letter does not get thinner, and the
583
+ shadow in eight directions covers browsers without `-webkit-text-stroke`. z-index 12, the same literal as the quiz
584
+ overlay (`.quiz`), not yet a slot: over the content by document order, under the pad (14) and the panels (60). The
585
+ right slot is `--z-game-msg`, and adopting it alone would put PAUSED over a panel. No pointer events. */
586
+ .pausa-rapida{position:absolute;left:50%;top:50%;transform:translate(-50%,-50%);z-index:12;pointer-events:none;
587
+ color:#fff;-webkit-text-stroke:.08em #000;paint-order:stroke fill;
588
+ text-shadow:2px 0 #000,-2px 0 #000,0 2px #000,0 -2px #000,2px 2px #000,-2px 2px #000,2px -2px #000,-2px -2px #000;
589
+ font-family:var(--font);font-weight:800;font-size:calc(var(--ui-fs,16px)*2);letter-spacing:var(--ls);white-space:nowrap}
590
+ .pausa-rapida[hidden]{display:none}
591
+ /* WHAT THE SCAN IS OFFERING (ADR-0218, #201): with «um botão só», the child does not choose — they wait, and what they
592
+ wait for has to be in view. The SAME outlined white letters as the quick pause, no box, at the TOP of the region,
593
+ below the bar's band (`--barra-a11y-h`), which is the zone no game uses. No pointer events: it is a notice, not a
594
+ control — what fires it is its single button, wherever that is. */
595
+ /* ⚠️ `--scan-top` and NOT `--barra-a11y-h`: this label makes the band grow, and placing it by the band made a loop — it
596
+ pushed the band, the band pushed it, and it went down the screen on every measure (caught by a test case). `--scan-top`
597
+ is where the bar ends, and does not depend on it (`ui/top-band` writes both). */
598
+ .scan-now{position:absolute;left:50%;top:var(--scan-top,0px);transform:translateX(-50%);z-index:12;
599
+ pointer-events:none;color:#fff;-webkit-text-stroke:.06em #000;paint-order:stroke fill;
600
+ text-shadow:2px 0 #000,-2px 0 #000,0 2px #000,0 -2px #000,2px 2px #000,-2px 2px #000,2px -2px #000,-2px -2px #000;
601
+ font-family:var(--font);font-weight:800;font-size:calc(var(--ui-fs,16px)*1.5);letter-spacing:var(--ls);white-space:nowrap}
602
+ .scan-now[hidden]{display:none}
603
+ /* THE FOOTER LEGEND of the quick pause (ADR-0155 erratum): the same outlined white letters, no box, right ABOVE the pad's
604
+ START pill — which stays in view, because it is the way out for someone who only has a finger. ⚠️ `left`/`right` and
605
+ not `left:50%` + `translate`: with that, the box shrank to HALF of the region (measured: 271 px, three lines, over
606
+ PAUSED). */
607
+ /* 🔴 The footer is ONE column (2026-09-12): the explanation of the pointed bar icon above the quick-pause legend. Two
608
+ absolute strips covered each other as soon as one wrapped. */
609
+ /* 🔴 ADR-0164 (2026-09-12): the footer is at the LOWEST edge of the region, over a darkened band, and holds at most TWO
610
+ lines of text. The Dev saw a three-line explanation in the middle of the screen, over a quiz option, with outlined
611
+ letters and no background. White on black at .82 over pure white composes to ~13:1 (the panel footer's arithmetic).
612
+ With the quick-pause legend also showing, each keeps ONE line, so the band never grows past two.
613
+ `--footer-band-h` is the room those two lines take: the engine's panels keep it free under their last row.
614
+ 🔴 `--rodape-h` is what a GAME leaves free at the bottom, and since ADR-0239's erratum it is the HUD ROW's room only — its
615
+ height plus how far the pad lifts it. The caption, the legend and the explanation are MOMENTARY and overlay the workspace
616
+ while they show: «a legenda é momentânea» (the Dev). Counting them made a band that is off most of the time take the
617
+ game's floor for good (the demo quiz lost its last option at 640×360). */
618
+ #game-region{--footer-band-h:calc(var(--ui-fs,16px) * 2.6 + 4px + var(--margem-borda,8px));--rodape-h:calc(var(--hud-row-bottom,0px) + var(--hud-row-h,0px))}
619
+ /* ADR-0163 rule 3: the region's text grows with the scale the engine forces — 16 px at 640×360, 32 at 1280×720 — times the
620
+ communication cycle's type scale, which the root size also carries. Without it everything in the region read the ROOT
621
+ 16 px: measured at 1280×720, quiz options at 16 px and the bar's emoji at 20 px under a `--ui-fs` of 32. */
622
+ #game-region{font-size:calc(var(--ui-fs,16px) * var(--fonte-escala,1))}
623
+ /* ADR-0164 as the Dev corrected it: the EXPLANATION stands on a full-width dark band at the lowest edge (rule 2, rule 4);
624
+ the BUTTON LEGEND runs the full width with NO band, a dark background only behind each «name: function» (rule 3), and
625
+ sits above the explanation. The column itself paints nothing. Padding adds to 8 px, so two lines fit `--footer-band-h`. */
626
+ /* THE PAD'S SHOULDER COLUMNS, as what else stands on the region must keep clear of them: while the pad shows them they own the
627
+ TOP CORNERS (ADR-0160; drawn only when the game names them, ADR-0162), and they are placed for REACH — a thumb finds a corner
628
+ without looking — so where they and anything else meet, the other thing moves. A shoulder is a `.touch-btn`, 56 px with the
629
+ target floor under it; a column holds one or two, 6 px apart. `--shoulders-*-reach` is how far down a column reaches, the edge
630
+ margin under it counted: 0 while that corner holds none. */
631
+ #game-region,.game-region{--shoulder-size:max(56px,var(--alvo-min,44px));--shoulders-left-reach:0px;--shoulders-right-reach:0px}
632
+ #game-region:has(#touch-controls:not([hidden]) > .touch-ombros--esq),.game-region:has(#touch-controls:not([hidden]) > .touch-ombros--esq){
633
+ --shoulders-left-reach:calc(var(--margem-borda,8px) * 2 + var(--shoulder-size))}
634
+ #game-region:has(#touch-controls:not([hidden]) > .touch-ombros--esq > :nth-child(2)),.game-region:has(#touch-controls:not([hidden]) > .touch-ombros--esq > :nth-child(2)){
635
+ --shoulders-left-reach:calc(var(--margem-borda,8px) * 2 + var(--shoulder-size) * 2 + 6px)}
636
+ #game-region:has(#touch-controls:not([hidden]) > .touch-ombros--dir),.game-region:has(#touch-controls:not([hidden]) > .touch-ombros--dir){
637
+ --shoulders-right-reach:calc(var(--margem-borda,8px) * 2 + var(--shoulder-size))}
638
+ #game-region:has(#touch-controls:not([hidden]) > .touch-ombros--dir > :nth-child(2)),.game-region:has(#touch-controls:not([hidden]) > .touch-ombros--dir > :nth-child(2)){
639
+ --shoulders-right-reach:calc(var(--margem-borda,8px) * 2 + var(--shoulder-size) * 2 + 6px)}
640
+ /* THE HUD the engine mounts from a cartridge's `hud` (ADR-0168; issue #162): the mission at the top centre, just under the quick
641
+ bar, and the rest in the HUD row at the bottom (ADR-0239 and its erratum; the row's rules are below). Where a caller mounts the
642
+ bands with no row they stand on the region: the points and the power down the right edge, the learning bars at the bottom
643
+ centre. State on dark chips, like the icon's name; no pointer events, so a finger passes through to the game.
644
+ 📌 What it keeps clear of: the quick bar, which owns the top (ADR-0239's driver: «the top belongs to the quick accessibility
645
+ bar; the HUD no longer shares it») — `--barra-a11y-h` is the band `ui/top-band` measures —, the pad's shoulder columns (above),
646
+ and the pad itself, which the row stands above (ADR-0239 §6). */
647
+ /* Where the bands have no row, the right edge's stack (the points, the power under them) starts under the bar's band and under
648
+ the right shoulders — whichever reaches lower — and never nearer the top than the edge margin. */
649
+ #game-region,.game-region{--hud-corner-top:max(var(--margem-borda,8px),var(--barra-a11y-h,0px),var(--shoulders-right-reach))}
650
+ .hud-faixa{position:absolute;z-index:12;margin:0;pointer-events:none;font-size:var(--ui-fs,16px);line-height:1.25;display:flex;gap:2px}
651
+ .hud-faixa[hidden]{display:none}
652
+ /* The MISSION sits at the TOP CENTRE, just under the quick bar (ADR-0239 erratum): `ui/top-band` writes its `top` from the
653
+ bar's box. The class keeps its old name — a page-surface name is not renamed in passing. The bar's momentary name line is
654
+ drawn over it while a name shows (the Dev: «a legenda é momentânea, a escrita da missão aparece o tempo todo»). */
655
+ /* THE EDGE MARGIN (interface log 2026-09-26) where a caller mounts the bands on the region, with no row: `--margem-borda` from
656
+ the side, and from the top the same place under the bar the root's mission takes (`--hud-mission-top`, which `ui/top-band`
657
+ writes; 0 with no bar) — at the margin it stood behind the bar's icons. Where the root mounts the mission, `ui/top-band`
658
+ writes that `top` on the element itself. */
659
+ .hud-esquerda{top:max(var(--margem-borda,8px),var(--hud-mission-top,0px));left:50%;transform:translateX(-50%);max-width:calc(100% - 2 * var(--margem-borda,8px));flex-direction:column;align-items:center;text-align:center}
660
+ .hud-direita{top:var(--hud-corner-top,8px);right:var(--margem-borda,8px);flex-direction:column;align-items:flex-end;text-align:right}
661
+ /* The learning bars (ADR-0049 §5): one ten-segment bar per skill, one skill a line as in the row (ADR-0239 §1), centred at the
662
+ bottom — under the footer column, which comes later in the region on the same layer and covers them. Okabe-Ito colours, and
663
+ a second cue beside each, so no state is told by colour alone (WCAG 1.4.1): green carries a dot, red stripes, an empty slot
664
+ only its outline; a purple bar a ▲, an orange one a ▼. 📏 Side by side (ADR-0168 §3's footer, superseded), three bars were
665
+ 501 px wide at 640×360 and, lifted above the pad, ran into the points and the power under the right shoulders. */
666
+ .hud-aprendizagem{left:50%;bottom:var(--margem-borda,8px);transform:translateX(-50%);flex-direction:column;align-items:center;gap:2px}
667
+ /* …and with no row, ABOVE the on-screen pad while it shows, as the row would stand (ADR-0239 §6): `--hud-row-bottom` is how high
668
+ the pad's lower parts reach (`ui/hud-row`). 📏 At the lower edge they stood under the START and SELECT pills. */
669
+ .game-region > .hud-aprendizagem,#game-region > .hud-aprendizagem{bottom:calc(var(--hud-row-bottom,0px) + var(--margem-borda,8px))}
670
+ /* The explanation band is 82% opaque: the bars showed through under its words. While it shows they are not drawn at all —
671
+ the explanation has priority (ADR-0168 §3), and a segment behind a letter is noise for the child reading it. */
672
+ .game-region:has(.barra-explicacao:not([hidden])) .hud-aprendizagem,#game-region:has(.barra-explicacao:not([hidden])) .hud-aprendizagem{visibility:hidden}
673
+ .hud-barra{display:flex;align-items:center;gap:.15em;padding:.2em .3em;background:rgba(8,8,16,.92);border-radius:8px}
674
+ .hud-seg{display:block;width:.8em;height:1em;border-radius:2px;box-sizing:border-box}
675
+ .hud-seg[data-seg="vazio"]{border:1px solid #8a8f98}
676
+ .hud-seg[data-seg="azul"]{background:#0072B2}
677
+ .hud-seg[data-seg="verde"]{background:radial-gradient(circle,#08080f 0 .14em,transparent .16em),#009E73}
678
+ .hud-seg[data-seg="vermelho"]{background:repeating-linear-gradient(45deg,#08080f 0 .12em,transparent .12em .3em),#D55E00}
679
+ .hud-barra[data-cor="roxa"] .hud-seg:not([data-seg="vazio"]){background:#CC79A7}
680
+ .hud-barra[data-cor="laranja"] .hud-seg:not([data-seg="vazio"]){background:#E69F00}
681
+ .hud-barra[data-cor="roxa"]::after,.hud-barra[data-cor="laranja"]::after{color:#f6f5f0;font-size:1em;line-height:1;margin-left:.15em}
682
+ .hud-barra[data-cor="roxa"]::after{content:"▲"}
683
+ .hud-barra[data-cor="laranja"]::after{content:"▼"}
684
+ .hud-numero{margin:0;padding:0 .3em;color:#f6f5f0;background:rgba(8,8,16,.92);border-radius:8px;max-width:100%;overflow-wrap:anywhere}
685
+ /* ===================== THE HUD ROW AT THE BOTTOM (ADR-0239; issue #94) =====================
686
+ One row along the lower edge, left to right: the learning bars · the session clock, centred · the power over the score ·
687
+ the game's map. `ui/hud-row` writes where it stands (`--hud-row-bottom`: above the on-screen pad while it shows) and its
688
+ height (`--hud-row-h`, which the footer and `--rodape-h` read — the row is all a game must leave free). Three columns of which the outer two are equal, so the
689
+ clock is at the true centre; `minmax(0,…)` so a long cell never pushes it off. No pointer events: a finger passes through
690
+ to the game. Empty — no clock, no band, no map — it is not drawn. */
691
+ .hud-row{position:absolute;left:0;right:0;bottom:var(--hud-row-bottom,0px);z-index:12;pointer-events:none;box-sizing:border-box;
692
+ display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr);align-items:end;column-gap:8px;
693
+ padding:0 var(--margem-borda,8px) var(--margem-borda,8px); /* the edge margin (interface log 2026-09-26) */
694
+ font-family:var(--font);font-size:var(--ui-fs,16px)}
695
+ /* …and while the pad shows a shoulder column on a side, the row's cells stand clear of it on that side: lifted above the pad at
696
+ 640×360 the row reaches up to the shoulders (the pad's directional is 179 px tall), and they keep their corner for reach
697
+ (ADR-0160). 📏 The power over the score stood 5 px into R1. */
698
+ #game-region:has(#touch-controls:not([hidden]) > .touch-ombros--esq) > .hud-row,.game-region:has(#touch-controls:not([hidden]) > .touch-ombros--esq) > .hud-row{
699
+ padding-left:calc(var(--margem-borda,8px) * 2 + var(--shoulder-size))}
700
+ #game-region:has(#touch-controls:not([hidden]) > .touch-ombros--dir) > .hud-row,.game-region:has(#touch-controls:not([hidden]) > .touch-ombros--dir) > .hud-row{
701
+ padding-right:calc(var(--margem-borda,8px) * 2 + var(--shoulder-size))}
702
+ .hud-row:not(:has(.session-clock, .hud-faixa:not([hidden]), .hud-map > *)){display:none}
703
+ .hud-row-learning{justify-self:start;min-width:0}
704
+ .hud-row-clock{justify-self:center}
705
+ .hud-row-end{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:end;column-gap:8px;min-width:0}
706
+ .hud-score{justify-self:center;display:flex;flex-direction:column;align-items:center;gap:2px;min-width:0}
707
+ .hud-map{display:flex;align-items:flex-end} /* empty, it is 0 px wide: nothing is drawn until a game mounts its map */
708
+ /* In the row the bands are laid out by their cell, not placed on the region; the learning bars stack, one skill a line,
709
+ because three side by side do not fit a third of 640 px. */
710
+ .hud-row .hud-faixa{position:static;transform:none}
711
+ .hud-row .hud-aprendizagem{flex-direction:column;align-items:flex-start;gap:2px}
712
+ .hud-row .hud-direita{align-items:center;text-align:center}
713
+ /* The explanation band covers the row while it shows (a menu is open, the game is paused): the row is not drawn under its
714
+ translucent band, and the band sits where the row stands. The caption and the legend otherwise stand ABOVE the row. */
715
+ .game-region:has(.barra-explicacao:not([hidden])) .hud-row,#game-region:has(.barra-explicacao:not([hidden])) .hud-row{visibility:hidden}
716
+ /* THE LOOK OF THE CLOCK AND THE SCORE: a small label over bold digits, white with a dark outline — the reference is Super
717
+ Mario World's HUD, and nothing of its art or face is taken: the engine's own faces, the mathematics one with tabular
718
+ figures for the digits, so they never change width. Text at the 16 px floor and above (ADR-0163); white on its black
719
+ outline, so it holds its contrast over any scene. */
720
+ .session-clock-text,.hud-points .hud-numero{color:#fff;font-weight:800;-webkit-text-stroke:.06em #000;paint-order:stroke fill;
721
+ text-shadow:2px 0 #000,-2px 0 #000,0 2px #000,0 -2px #000,2px 2px #000,-2px 2px #000,2px -2px #000,-2px -2px #000}
722
+ .session-clock-digits,.hud-points .hud-numero{font-family:var(--font-math);font-variant-numeric:tabular-nums;
723
+ font-size:calc(var(--ui-fs,16px) * 1.25);line-height:1.1;white-space:nowrap}
724
+ .hud-points .hud-numero{background:none;padding:0}
725
+ .hud-points{top:var(--hud-corner-top,8px);right:var(--margem-borda,8px);flex-direction:column;align-items:flex-end} /* only where a caller mounts the bands with no row; `--hud-corner-top` above */
726
+ /* …and there, at the top edge, the digits get a line that holds their glyphs: at 1.1 the face's box rose 1 px over the line
727
+ (measured: 24 px of glyph box on a 22 px line at 640×360, 46 on 44 at 1280×720), into the edge margin */
728
+ .game-region > .hud-points .hud-numero,#game-region > .hud-points .hud-numero{line-height:1.2}
729
+ /* …and the POWER, which in the row stands over the points (ADR-0239 §3), has no room over them there: the same pair reads
730
+ top-down, the points first and the power directly under them, on the same right edge — both at one `top` drew one box over
731
+ the other. The points' line is one line of digits, 1.25 × the text size on a 1.2 line (above); 2 px apart, as in the row's
732
+ cell. With no points shown the power is the stack's first line. */
733
+ .game-region:has(> .hud-points:not([hidden]) > .hud-numero:not([hidden])) > .hud-direita,
734
+ #game-region:has(> .hud-points:not([hidden]) > .hud-numero:not([hidden])) > .hud-direita{top:calc(var(--hud-corner-top,8px) + var(--ui-fs,16px) * 1.25 * 1.2 + 2px)}
735
+ /* THE SESSION CLOCK (ADR-0236, ADR-0239): the label over the time left, and the Time Timer pie to their right, as tall as a
736
+ target. The pie is the share still to go (`--left`, written by `ui/session-clock`), green on a white face (ADR-0050 §3)
737
+ inside a dark rim and a white ring, drawn inside its box. At the end the WHOLE pie turns red — an almost empty face
738
+ becoming a full one, so the change is not told by colour alone (WCAG 1.4.1). Okabe-Ito green and vermillion. */
739
+ .session-clock{display:flex;align-items:center;gap:6px;pointer-events:none}
740
+ .session-clock-text{display:flex;flex-direction:column;align-items:center;line-height:1.1}
741
+ /* The label stays at the 16 px text floor (ADR-0163) and loses weight instead, and the digits are an eighth over the floor:
742
+ the Dev found «TEMPO» too big and wants the label-and-digits block no wider than two and a half pies (ADR-0239 errata). */
743
+ .session-clock-label{font-size:var(--ui-fs,16px);letter-spacing:var(--ls);font-weight:600}
744
+ .session-clock-digits{font-size:calc(var(--ui-fs,16px) * 1.125)}
745
+ .session-clock-pie{flex:none;box-sizing:border-box;width:var(--alvo-min,44px);height:var(--alvo-min,44px);border-radius:50%;
746
+ border:2px solid #08080f;box-shadow:inset 0 0 0 2px #f6f5f0;
747
+ background:conic-gradient(#f6f5f0 0 calc((1 - var(--left,1)) * 1turn),#009E73 0)}
748
+ /* The spent share is the face from twelve o'clock CLOCKWISE, and the green is what is left up to twelve: the edge moves
749
+ clockwise as time passes, like a Time Timer (the Dev, 2026-09-25: it was emptying anticlockwise). */
750
+ /* The end is red and still — no pulse, no lock (ADR-0240). */
751
+ .session-clock[data-look="red"] .session-clock-pie{background:#D55E00}
752
+ .rodape-da-tela{position:absolute;left:0;right:0;bottom:0;z-index:12;display:flex;flex-direction:column;gap:0;pointer-events:none}
753
+ /* WITH THE HUD ROW (ADR-0239 point 6): the caption and the legend stand ABOVE the row; while the explanation shows, the column
754
+ comes down to where the row stands, and the explanation covers it. */
755
+ #game-region:has(> .hud-row) .rodape-da-tela{bottom:calc(var(--hud-row-bottom,0px) + var(--hud-row-h,0px))}
756
+ #game-region:has(> .hud-row) .rodape-da-tela:has(> .barra-explicacao:not([hidden])){bottom:var(--hud-row-bottom,0px)}
757
+ .rodape-da-tela:not(:has(> :not([hidden]))){display:none}
758
+ /* `--footer-lines` is how many lines a footer text shows (ADR-0164): the clamp reads it, and so do the scrolling box below and
759
+ `ui/footer-scroll-driver`, which must know the view it scrolls a longer text through (ADR-0245). */
760
+ .pausa-legenda,.barra-explicacao{color:#fff;font-family:var(--font);font-weight:700;font-size:var(--ui-fs,16px);--footer-lines:2;
761
+ line-height:1.3;text-align:center;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:var(--footer-lines);overflow:hidden}
762
+ /* The band reaches the edges; its WORDS stand the edge margin off them (interface log 2026-09-26) — `--footer-band-h` counts it.
763
+ At the sides .6em is already more than the margin (4.8·k against 4·k); under them the margin is a transparent BORDER the
764
+ band's colour shows through, not padding: the two-line clamp clips at the padding box, and a padding there would show the
765
+ top of a third line. */
766
+ .barra-explicacao{order:2;padding:4px .6em 0;border-bottom:var(--margem-borda,8px) solid transparent;background:rgba(0,0,0,.82)}
767
+ .pausa-legenda{order:1;padding:0 .6em}
768
+ .rodape-da-tela:not(:has(> .barra-explicacao:not([hidden]))) > .pausa-legenda{padding-bottom:var(--margem-borda,8px)}
769
+ .pausa-legenda .lg-nome{background:rgba(0,0,0,.82);padding:0 .35em;border-radius:4px;-webkit-box-decoration-break:clone;box-decoration-break:clone}
770
+ /* The SOUND CAPTION (study item D3) tops the column — ADR-0164 rule 4: «the sound caption above, the explanation below
771
+ it» — on a dark background behind its words only, two lines at most (rule 5). */
772
+ .legenda-de-som{order:0;align-self:center;max-width:100%;box-sizing:border-box;margin:0 0 4px;padding:2px .6em;border-radius:6px;
773
+ background:rgba(0,0,0,.82);color:#fff;font-family:var(--font);font-weight:700;font-size:var(--ui-fs,16px);line-height:1.3;
774
+ text-align:center;display:-webkit-box;-webkit-box-orient:vertical;--footer-lines:2;-webkit-line-clamp:var(--footer-lines);overflow:hidden}
775
+ /* the caption as the column's lowest line (no legend, no explanation under it): the edge margin under it, not 4 px */
776
+ .rodape-da-tela:not(:has(> .barra-explicacao:not([hidden]), > .pausa-legenda:not([hidden]))) > .legenda-de-som{margin-bottom:var(--margem-borda,8px)}
777
+ /* two or more lines of the footer showing at once: one line each, so the footer does not climb into the workspace */
778
+ .rodape-da-tela:has(> :not([hidden]) ~ :not([hidden])) > *{--footer-lines:1}
779
+ .pausa-legenda[hidden],.barra-explicacao[hidden],.legenda-de-som[hidden]{display:none}
780
+ /* ADR-0245: AN EXPLANATION LONGER THAN ITS LINES MOVES INSIDE THEM. `ui/footer-scroll-driver` marks it `data-scroll`: a glide
781
+ translates the words' `.footer-scroll-text` block (a Web Animation, sub-pixel — a transform needs a block, an inline span
782
+ ignores it), reduced motion turns pages by `scrollTop`, at once. The box stays the clamped one to the pixel: a block of
783
+ exactly its lines (`1lh` each, no clamp and so no ellipsis), with the 4 px over the words as a transparent BORDER instead of
784
+ padding — the view clips at the padding box, and a padding would show a slice of the next line. Pointer events come back to
785
+ the band while it moves, so a pointer over it can hold it (WCAG 2.2.2). */
786
+ .barra-explicacao[data-scroll],#game-region .opt-explain[data-scroll]{display:block;-webkit-line-clamp:none;box-sizing:content-box;
787
+ height:calc(var(--footer-lines,2) * 1lh);padding-top:0;border-top:4px solid transparent;pointer-events:auto}
788
+ .barra-explicacao[data-scroll] > .footer-scroll-text,#game-region .opt-explain[data-scroll] > .footer-scroll-text{display:block}
789
+ /* A PANEL WEARS THE PAUSE CARD (interface log, 2026-09-13). The Dev: the settings submenu «mantém a mesma altura e identidade
790
+ visual», and every panel it opens had «outra identidade visual, com alturas menores, título maior e cores diferentes».
791
+ So a panel in the region takes `.screen-pause .pause-card` as it is: the whole height, the same dark card and gold border,
792
+ the gold title at the text size, items of `.pm-btn`'s colour, border and radius with the same 2 px gaps. It is at least
793
+ the pause card's width and grows only when a row needs it — a steps row never wraps (ADR-0130 erratum), and with the BDA
794
+ spacing (ADR-0149) «◀ Realce de contraste: desligado ▶» is wider than the pause list's 26em. */
795
+ #game-region .overlay__card{background:#0e1626;border:2px solid #ffd23f;border-radius:14px;padding:.25rem .7rem;width:auto;
796
+ max-width:94%;height:100%;max-height:100%;box-sizing:border-box;
797
+ scrollbar-gutter:stable both-edges;box-shadow:0 8px 30px rgba(0,0,0,.5);scroll-padding-bottom:var(--footer-band-h,50px)}
798
+ #game-region .overlay__card h2{font-size:1em;line-height:1.25;margin:0 0 .1rem;color:#ffd23f}
799
+ #game-region .overlay__back{width:100%;margin:0 0 2px;justify-content:center;background:#1a2740;border:1px solid #6a7a96;border-radius:10px;color:#eaf2f8}
800
+ #game-region .overlay__back:hover,#game-region .overlay__back:focus{background:#2a3a5e;border-color:#ffd23f;outline:2px solid #ffd23f;outline-offset:1px}
801
+ #game-region .ctrl-list{gap:2px}
802
+ /* the pause list's width is the floor, as `.pause-menu` declares it: the card follows with the same padding and scrollbar gutters */
803
+ #game-region .overlay__back,#game-region .ctrl-list{min-width:26em;max-width:100%} /* em: grows with the menu's text, like the pause list */
804
+ #game-region .ctrl-row{box-sizing:border-box;min-height:var(--alvo-min,44px);padding:.3rem .7rem;background:#1a2740;border:1px solid #6a7a96;border-radius:10px;color:#eaf2f8;font-weight:700}
805
+ #game-region .overlay__card > .ctrl-row{margin-top:2px}
806
+ #game-region .ctrl-row:hover,#game-region .ctrl-row:focus-within{background:#2a3a5e;border-color:#ffd23f}
807
+ /* The card runs under the explanation band, as the pause card runs under its legend: the room at the end of the card and
808
+ the scroll padding let the last row, and the focused one, come to rest above the band. */
809
+ #game-region .overlay__card::after{content:"";display:block;height:var(--footer-band-h,50px)}
810
+ /* HELP IS A SLIDE SHOW (interface log, 2026-09-13; `ui/help-panel`): «textos, figuras e no máximo animações», not a menu. One stop
811
+ with the page arrows at its sides; the slide holds the child's key drawn as a key cap, the game's word and its sentence, and
812
+ dots that say where the child is by fill and not by colour alone. The key cap presses itself now and then — the one animation,
813
+ gone under reduced motion. */
814
+ /* a slide explains itself: no footer band telling the child to point at an option, and no room kept for it */
815
+ #help .opt-explain,#help .overlay__card::after{display:none}
816
+ .slides{display:flex;align-items:stretch;gap:.25rem;min-height:11em;border:1px solid #6a7a96;border-radius:10px;background:#1a2740;color:#eaf2f8}
817
+ .slides:focus,.slides:focus-visible{outline:2px solid #ffd23f;outline-offset:1px;border-color:#ffd23f}
818
+ .slides .passo-seta{align-self:center;color:#ffd23f;font-size:1.3em}
819
+ .slide{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:.45em;padding:.5em .2em;text-align:center}
820
+ .slide-tecla{display:inline-flex;align-items:center;justify-content:center;min-width:3em;min-height:2em;padding:.15em .6em;font:inherit;font-size:1.4em;
821
+ font-weight:800;color:#0e1626;background:#eaf2f8;border-radius:10px;box-shadow:0 .22em 0 #6a7a96;animation:tecla-aperta 2.4s ease-in-out infinite}
822
+ .slide-tecla[data-sem-tecla]{color:#c3cfe0;background:transparent;border:2px dashed #6a7a96;box-shadow:none;animation:none}
823
+ @keyframes tecla-aperta{0%,70%,100%{transform:translateY(0);box-shadow:0 .22em 0 #6a7a96}82%{transform:translateY(.16em);box-shadow:0 .06em 0 #6a7a96}}
824
+ @media (prefers-reduced-motion: reduce){ .slide-tecla{animation:none} }
825
+ /* a cartridge's «how to play» figure (ADR-0195): the surface the game draws on, crisp at the pixel scale */
826
+ .slide-figura{display:block;width:min(100%,16em);height:7em;image-rendering:pixelated}
827
+ .slide-palavra{margin:0;font-size:1.2em;font-weight:800;color:#ffd23f}
828
+ .slide-texto{margin:0;max-width:28em;line-height:1.35}
829
+ .slide-pontos{display:flex;gap:.45em;justify-content:center;margin-top:.2em}
830
+ .slide-ponto{width:.6em;height:.6em;border-radius:50%;border:1px solid #eaf2f8;box-sizing:border-box}
831
+ .slide-ponto.is-on{background:#ffd23f;border-color:#ffd23f}
832
+ /* Explanation footer: white on 0.82 translucent black → the white's contrast is >7:1 even over 100% white
833
+ (composition: 1-0.82=0.18 → L≈0.027 → ~13:1), without being 100% opaque. Fixed at the card's foot. */
834
+ .opt-explain{position:sticky;bottom:calc(-1 * var(--card-pad,1.5rem));margin:.8rem calc(-1 * var(--card-pad,1.5rem)) calc(-1 * var(--card-pad,1.5rem));padding:.6rem .9rem;min-height:2.8rem;text-align:left;
835
+ background:rgba(0,0,0,.82);color:#fff;font-size:1em;line-height:1.35;border-top:1px solid rgba(255,255,255,.18);border-radius:0 0 9px 9px;backdrop-filter:blur(1px);
836
+ /* ADR-0164: two lines at most — in the region a longer explanation scrolls through them (ADR-0245, below) */
837
+ display:-webkit-box;-webkit-box-orient:vertical;--footer-lines:2;-webkit-line-clamp:var(--footer-lines);overflow:hidden}
838
+ /* ADR-0164 rule 1: a panel's explanation is a footer of the SCREEN, not of the card — «está ajustado ao menu, não ao
839
+ canvas», the Dev on a screenshot. The card sets no position, so the overlay (the region's size) is the containing block:
840
+ the band lands at the region's lowest edge, side to side, and does not scroll with the card. The card runs under it,
841
+ the pause card's height (see the panel rules above). */
842
+ #game-region .opt-explain{position:absolute;left:0;right:0;bottom:0;z-index:1;margin:0;padding:4px .6em 0;min-height:0;
843
+ border:0;border-bottom:var(--margem-borda,8px) solid transparent;border-radius:0;text-align:center;line-height:1.3;font-size:var(--ui-fs,16px)}
844
+ /* ↑ the transparent bottom border is the edge margin under the words, as the footer band's (interface log 2026-09-26) */
845
+ /* the card's content from the edge margin down, as the pause card's (interface log 2026-09-26) */
846
+ #game-region .overlay{padding:calc(var(--margem-borda,8px) - 2px) 0 0}
847
+ #game-region .overlay__card{padding-top:0}
512
848
  #game-region .overlay__actions{position:static;background:transparent;padding:.5rem 0 0;margin:0}
513
- #visual{background:rgba(0,0,0,.28);align-items:start;padding-top:3.5rem} /* cor: fundo translúcido p/ ver o efeito imediato no jogo */
849
+ #visual{background:rgba(0,0,0,.28)} /* colour: a translucent background, to see the effect on the game at once */
514
850
 
515
- /* Indicador de simulação (canto sup. dir.): branca=cegueira, verde=baixa visão. Duplo toque volta ao normal. */
516
- #viz-indicator{position:absolute;top:6px;right:6px;z-index:20;width:16px;height:16px;border-radius:50%;border:2px solid rgba(0,0,0,.6);padding:0;cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.7)} /* DENTRO do canvas (game-region) — funciona como botão */
851
+ /* Simulation indicator (TOP LEFT corner): white=blindness, green=low vision. A double tap returns to normal. The top right is
852
+ the right shoulders' (ADR-0160) and, with no HUD row, the points'; nothing of the HUD sits top left, so the dot is alone
853
+ there — under the left shoulders while the pad shows them (`--shoulders-left-reach`, with the HUD's rules). */
854
+ #viz-indicator{position:absolute;top:max(var(--margem-borda,8px),var(--shoulders-left-reach,0px));left:var(--margem-borda,8px);z-index:20;width:16px;height:16px;border-radius:50%;border:2px solid rgba(0,0,0,.6);padding:0;cursor:pointer;box-shadow:0 1px 4px rgba(0,0,0,.7)} /* INSIDE the canvas (game-region) — it works as a button */
517
855
  #viz-indicator.blind{background:#fff}
518
856
  #viz-indicator.low{background:#36d36a}
519
- /* Baixa visão — 5 simulações (desfoque vem do filter na canvas; aqui só os overlays) */
857
+ /* Low vision — 5 simulations (the blur comes from the filter on the canvas; only the overlays are here) */
520
858
  #viz-overlay{position:absolute;inset:0;z-index:8;pointer-events:none}
521
- #viz-overlay.lv-blur{background:none} /* miopia/astigmatismo: só desfoque */
522
- #viz-overlay.lv-haze{background:rgba(244,246,250,.42)} /* catarata: película esbranquiçada */
523
- #viz-overlay.lv-tunnel{background:radial-gradient(circle at 50% 50%, transparent 0 15%, rgba(0,0,0,.55) 27%, rgba(0,0,0,.99) 42%)} /* glaucoma: visão de túnel */
524
- #viz-overlay.lv-macular{background:radial-gradient(circle at 50% 50%, rgba(12,12,15,.95) 0 10%, rgba(12,12,15,.55) 17%, transparent 28%)} /* degeneração macular: borrão central */
525
- #viz-overlay.lv-diabetic{background: /* retinopatia diabética: manchas dispersas */
859
+ #viz-overlay.lv-blur{background:none} /* myopia/astigmatism: blur only */
860
+ #viz-overlay.lv-haze{background:rgba(244,246,250,.42)} /* cataract: a whitish film */
861
+ #viz-overlay.lv-tunnel{background:radial-gradient(circle at 50% 50%, transparent 0 15%, rgba(0,0,0,.55) 27%, rgba(0,0,0,.99) 42%)} /* glaucoma: tunnel vision */
862
+ #viz-overlay.lv-macular{background:radial-gradient(circle at 50% 50%, rgba(12,12,15,.95) 0 10%, rgba(12,12,15,.55) 17%, transparent 28%)} /* macular degeneration: a central blur */
863
+ #viz-overlay.lv-diabetic{background: /* diabetic retinopathy: scattered spots */
526
864
  radial-gradient(circle at 22% 30%, rgba(10,10,14,.95) 0 4%, transparent 11%),
527
865
  radial-gradient(circle at 64% 22%, rgba(10,10,14,.9) 0 3%, transparent 9%),
528
866
  radial-gradient(circle at 80% 58%, rgba(10,10,14,.95) 0 5%, transparent 12%),
529
867
  radial-gradient(circle at 40% 70%, rgba(10,10,14,.9) 0 4%, transparent 10%),
530
868
  radial-gradient(circle at 16% 80%, rgba(10,10,14,.85) 0 3%, transparent 9%),
531
869
  radial-gradient(circle at 54% 48%, rgba(10,10,14,.8) 0 3%, transparent 8%)}
532
- /* Cegueira total: a canvas vira preta (filter brightness(0)); escondemos HUD e controles na tela */
870
+ /* Total blindness: the canvas turns black (filter brightness(0)); we hide the HUD and the on-screen controls */
533
871
  body.blind-mode .hud, body.blind-mode #touch-controls, body.blind-mode #start-tips, body.blind-mode #caption{visibility:hidden}
534
872
  [hidden]{display:none!important}
535
- /* CAIXA ALTA (menu de CAA, ADR-0028). O jogo tem DOIS caminhos de texto e por isso dois mecanismos, que não
536
- são redundância: o `disp()` do main.js cobre o que a PIXI desenha na canvas, e esta regra cobre o que o
537
- navegador desenha no DOM — menus, HUD, legendas. O botão só ligava o primeiro, então a criança escolhia
538
- "maiúsculas" e os menus continuavam mistos.
539
- `text-transform` e NÃO transformar o texto: o conteúdo acessível fica intacto, e um leitor de tela não passa
540
- a soletrar letra por letra, que é o que ALL CAPS de verdade provoca em NVDA/JAWS. */
541
- /* Alternativas do quiz (segundo consumidor): altura mínima em MILÍMETROS REAIS, calculada por
542
- `input/touch.padPxPerMm` — WCAG 2.5.5 / GAG. O valor chega em `--quiz-alt-min`. */
543
- .quiz-alt{min-height:var(--quiz-alt-min,44px);display:block;width:100%;margin:.25rem 0}
544
- .quiz-alts{display:flex;flex-direction:column;gap:.25rem}
873
+ /* CAPITAL LETTERS (the AAC menu, ADR-0028). What a game draws on the canvas is the game's to capitalise; this rule covers
874
+ what the browser draws in the DOM — menus, HUD, captions. `createGame` writes `data-letras` on the root (ADR-0149 §1).
875
+ `text-transform` and NOT transforming the text: the accessible content stays intact, and a screen reader does not start
876
+ spelling letter by letter, which is what real ALL CAPS causes in NVDA/JAWS. */
877
+ /* Quiz options take the ENGINE's target floor (ADR-0163 rule 2): 44 px at 640×360, growing with the scale. They had
878
+ 9 mm computed by the quiz (54 px on a desktop), and four of them pushed the last one out of a 360 px region. */
879
+ .quiz-alt{min-height:var(--alvo-min,44px);display:block;width:100%;margin:0;padding:calc(var(--ui-fs,16px) * .25) calc(var(--ui-fs,16px) * .5)} /* the list gap spaces them (#160); padding in `--ui-fs`, so a larger face grows the text only (#172) */
880
+ /* 📏 .25/.5 and not .4/.9 since the edge margin (interface log 2026-09-26): with 16 px less width and a HUD row 4 px taller, the
881
+ quiz's own questions ended under the row at 640×360 (quiz-bench-layout); the 44 px floor is `min-height`, untouched. */
882
+ /* the marked option is not colour alone either (ADR-0159 rule 10): the same base stripe as an icon switched on */
883
+ .quiz-alt.is-on{box-shadow:inset 0 0 0 3px var(--accent-ink)} /* a ring, not a bottom strip: the strip left the fill looking shifted up (#166) */
884
+ /* FIVE OPTIONS IN THREE COLUMNS — «1 2 3» over «4 5», read left to right (`quiz-round.OPTION_COLUMNS`, written as `--quiz-cols`).
885
+ 📏 In one column the fifth ended under the HUD row at 640×360, and in two a two-line statement with its explanation did. */
886
+ .quiz-alts{display:grid;grid-template-columns:repeat(var(--quiz-cols,3),minmax(0,1fr));gap:calc(.25rem * var(--espaco-fixo,1))}
887
+ /* An option already tried is out: struck through as well as dimmed, so it is not told by colour alone (WCAG 1.4.1); an inactive
888
+ control is outside the contrast minimum (WCAG 1.4.3). The answer to COPY (ADR-0049 §6) carries a mark before its words. */
889
+ .quiz-alt.is-off{opacity:.55;text-decoration:line-through;cursor:default}
890
+ .quiz-alt.is-answer{outline:3px solid var(--accent);outline-offset:2px}
891
+ .quiz-alt.is-answer::before{content:"➜ "}
892
+ /* The explanation after the third wrong attempt, and the copy line: under the statement, at the text size, not bold. While it
893
+ shows, the statement comes down to the text size too: 📏 a two-line statement at its heading size, a three-line explanation
894
+ and two rows of options ended under the HUD row at 640×360 (quiz-bench-layout). */
895
+ .quiz-note{margin:0 0 calc(.25rem * var(--espaco-fixo,1));line-height:1.25;font-weight:400}
896
+ .quiz-app:has(.quiz-note) .quiz-pergunta{font-size:1em}
897
+ /* THE START SCREEN (the Dev: «Tela inicial para escolher a habilidade via sigla da BNCC»): each stage's name over a grid of
898
+ `--quiz-cols` codes (`quiz-round.SKILL_COLUMNS`), the engine's target size; a code never overflows its button. */
899
+ .quiz-stage{margin:0}
900
+ .quiz-stage-name{margin:0;font-size:1em;line-height:1.25}
901
+ .quiz-skills{display:grid;grid-template-columns:repeat(var(--quiz-cols,5),minmax(0,1fr));gap:calc(.25rem * var(--espaco-fixo,1));margin:0 0 calc(.25rem * var(--espaco-fixo,1))}
902
+ .quiz-skill{min-height:var(--alvo-min,44px);margin:0;padding:calc(var(--ui-fs,16px) * .3);overflow-wrap:anywhere;line-height:1.15}
903
+ .quiz-skill.is-on{box-shadow:inset 0 0 0 3px var(--accent-ink)}
545
904
  :root[data-letras="upper"] body,
546
905
  :root[data-letras="upper"] button,
547
906
  :root[data-letras="upper"] select,
548
907
  :root[data-letras="upper"] option,
549
908
  :root[data-letras="upper"] input,
550
909
  :root[data-letras="upper"] textarea{text-transform:uppercase}
551
- /* Os controles de formulário estão na lista porque a folha do NAVEGADOR lhes impõe `text-transform:none`, e
552
- `none` numa regra do UA vence a herança. Sem eles a caixa alta pegava no texto das telas e falhava em cada
553
- BOTÃO — que é justamente onde os rótulos dos menus moram. Foi o que o Dev viu: "não torna todas as letras
554
- do menu maiúsculas". */
555
- /* menus nunca ultrapassam a tela: limitados à altura do viewport e roláveis (rodapé de ações fica fixo embaixo) */
910
+ /* The form controls are on the list because the BROWSER's stylesheet imposes `text-transform:none` on them, and `none`
911
+ in a UA rule beats inheritance. Without them the capitals took the screens' text and failed on every BUTTON — which
912
+ is exactly where the menus' labels live. It is what the Dev saw: «não torna todas as letras do menu maiúsculas». */
913
+ /* menus never overflow the screen: limited to the viewport's height and scrollable (the actions footer stays fixed at the bottom) */
556
914
  .overlay__card{background:var(--panel);border:3px solid var(--accent);border-radius:12px;padding:1.5rem;max-width:420px;width:100%;text-align:center;font-size:var(--ui-fs,16px);
557
- max-height:92dvh;overflow-y:auto;overscroll-behavior:contain} /* base = --ui-fs (16 mín, escala com o canvas); internos em em */
915
+ max-height:92dvh;overflow-y:auto;overscroll-behavior:contain} /* base = --ui-fs (16 min, scales with the canvas); inner sizes in em */
558
916
  .overlay__card h2{margin-top:0}
917
+ /* ADR-0158: «Voltar» is the panel's item 1 — a full-width row above the list, not a pill at the bottom. */
918
+ .overlay__back{display:flex;align-items:center;width:100%;margin:0 0 .45rem;text-align:left;border-radius:8px}
919
+ /* ADR-0167: a panel's stops draw no number; «Voltar»'s glyph stays, out of the accessible name (ADR-0159 rule 12). */
920
+ .overlay__card button[data-glifo]::before{content:attr(data-glifo) / "";margin-inline-end:.5em}
559
921
  .topbar__right{display:flex;align-items:center;gap:.6rem;flex-wrap:wrap}
560
922
  .title-card{text-align:center}
561
- .title-big{font-size:2rem;margin:.2rem 0;color:var(--accent)}
923
+ .title-big{font-size:2em;margin:.2rem 0;color:var(--accent)}
562
924
  .title-by{color:var(--ink-soft);margin:.2rem 0 1rem}
563
- .title-card .btn{margin-top:.4rem;font-size:1.1rem;padding:.6em 1.6em}
925
+ .title-card .btn{margin-top:.4rem;font-size:1.1em;padding:.6em 1.6em}
564
926
  .overlay__actions{display:flex;gap:.6rem;flex-wrap:wrap;margin-top:1rem;justify-content:flex-end;
565
927
  position:sticky;bottom:-1.5rem;background:var(--panel);padding:.6rem 0 1.5rem;margin-bottom:-1.5rem}
566
- .opt-hint{color:var(--ink-soft);font-size:.9em;margin:.2rem 0 .8rem}
567
- .panel-sub{text-align:left;font-size:1rem;margin:1rem 0 .4rem;border-bottom:1px solid var(--accent);padding-bottom:.2rem}
928
+ .opt-hint{color:var(--ink-soft);font-size:1em;margin:.2rem 0 .8rem}
929
+ .panel-sub{text-align:left;font-size:1em;margin:1rem 0 .4rem;border-bottom:1px solid var(--accent);padding-bottom:.2rem}
568
930
  .panel-sub:first-of-type{margin-top:.4rem}
569
- .panel-sub__tag{color:var(--ink-soft);font-size:.78em;font-weight:400}
570
- .vol{width:140px;max-width:45%;accent-color:var(--accent);height:1.5rem;cursor:pointer} /* barra de volume (range) */
571
- /* Caixas de seleção com a IDENTIDADE do jogo: fonte escolhida, cores e tamanho (não o estilo do SO) */
931
+ .panel-sub__tag{color:var(--ink-soft);font-size:1em;font-weight:400}
932
+ .vol{width:140px;max-width:45%;accent-color:var(--accent);height:1.5rem;cursor:pointer} /* the volume bar (range) */
933
+ /* Select boxes with the game's IDENTITY: the chosen face, colours and size (not the OS style) */
572
934
  select.vol, .overlay__card select, .ctrl-col select{
573
- font-family:var(--font);font-size:.95em;font-weight:600;color:var(--ink);
574
- width:auto;max-width:100%;height:auto;min-height:var(--tap);
935
+ font-family:var(--font);font-size:1em;font-weight:600;color:var(--ink);
936
+ width:auto;max-width:100%;height:auto;min-height:var(--alvo-min,44px);
575
937
  background:#0e1326;border:2px solid var(--ink-soft);border-radius:8px;
576
938
  padding:.35em 2em .35em .7em;cursor:pointer;
577
939
  -webkit-appearance:none;-moz-appearance:none;appearance:none;
@@ -580,52 +942,74 @@ select.vol, .overlay__card select, .ctrl-col select{
580
942
  select.vol:focus, .overlay__card select:focus{outline:3px solid var(--accent);outline-offset:1px;border-color:var(--accent)}
581
943
  .overlay__card select option{background:#0e1326;color:var(--ink);font-weight:600}
582
944
  .ctrl-players{display:flex;gap:.4rem;flex-wrap:wrap;justify-content:center;margin-bottom:.7rem}
583
- .ctrl-players .mode-btn{padding:.3em .7em;font-size:.95rem}
945
+ .ctrl-players .mode-btn{padding:.3em .7em;font-size:1em}
584
946
  .ctrl-list{display:flex;flex-direction:column;gap:.45rem;text-align:left}
585
947
  .ctrl-row{display:flex;align-items:center;justify-content:space-between;gap:.6rem;background:#0e1326;border:2px solid var(--ink-soft);border-radius:8px;padding:.4rem .6rem}
586
- /* ADR-0029 — SAIU DO PADRÃO. Dois canais visuais no mesmo lugar: a COR da moldura e a FORMA, que é uma
587
- moldura DUPLA (borda, respiro, anel). O anel transparente de 2px revela o fundo do próprio elemento e é o
588
- que cria o respiro; sem ele os dois anéis colariam e viraria só uma borda mais grossa — uma diferença de
589
- espessura que ninguém percebe sem ter o não-marcado ao lado para comparar. A contagem de anéis sobrevive a
590
- escala de cinza, a daltonismo e a toda paleta do jogo, porque não é uma matiz. */
948
+ /* ADR-0029 — LEFT THE DEFAULT. Two visual channels in the same place: the frame's COLOUR and its SHAPE, which is a
949
+ DOUBLE frame (border, gap, ring). The transparent 2px ring reveals the element's own background and is what makes
950
+ the gap; without it the two rings would touch and become just a thicker border — a difference in thickness nobody
951
+ notices without the unmarked one beside it for comparison. The ring count survives greyscale, colour-vision
952
+ deficiency and every palette of the game, because it is not a hue. */
591
953
  .is-changed{border-color:var(--changed)!important;box-shadow:inset 0 0 0 2px transparent,inset 0 0 0 4px var(--changed)}
592
- /* A marca CEDE ao foco: perder o lugar é pior que perder uma dica. */
954
+ /* The mark YIELDS to the focus: losing your place is worse than losing a hint. */
593
955
  .is-changed:focus-visible,.is-changed:focus{box-shadow:none}
594
956
  .ctrl-row kbd{background:var(--ink);color:var(--bg);border-radius:5px;padding:.05em .45em;font-weight:700}
595
- .ctrl-row.row-off{opacity:.42} /* item desabilitado (ex.: modo de teclado indisponível) = só cinza claro, sem texto extra */
957
+ .ctrl-row.row-off{opacity:.42} /* a disabled item (e.g. an unavailable keyboard mode) = light grey only, no extra text */
596
958
  .ctrl-row .mode-btn[disabled]{cursor:not-allowed;opacity:.7}
597
959
  .ctrl-row.ctrl-col{flex-direction:column;align-items:stretch;gap:.4rem}
598
960
  .ctrl-row.ctrl-col label{font-weight:700}
599
961
  .pad-presets{display:flex;gap:.5rem;flex-wrap:wrap}
600
- .pad-tag{font-size:.8rem;font-weight:700;padding:.05em .5em;border-radius:999px;background:#20304e;color:#bcd}
962
+ .pad-tag{font-size:1em;font-weight:700;padding:.05em .5em;border-radius:999px;background:#20304e;color:#bcd}
601
963
  .pad-tag[data-who="crianca"]{background:#2a4a2e;color:#bfe9c2}
602
964
  .pad-tag[data-who="adulto"]{background:#2a3a5e;color:#c3d4f0}
603
- .pad-warn{font-size:.85rem;color:#ffdf9e;background:#3a2e12;border:1px solid #6a5220;border-radius:8px;padding:.45rem .6rem}
604
- .ctrl-col input[type=range]{width:100%;min-height:var(--tap);accent-color:var(--accent)}
965
+ .pad-warn{font-size:1em;color:#ffdf9e;background:#3a2e12;border:1px solid #6a5220;border-radius:8px;padding:.45rem .6rem}
966
+ .ctrl-col input[type=range]{width:100%;min-height:var(--alvo-min,44px);accent-color:var(--accent)}
605
967
 
606
- /* E13: controles de toque (mobile) — overlay nas bordas inferiores, não cobre o centro */
607
- .touch{position:absolute;inset:auto 0 0 0;z-index:14;display:flex;justify-content:space-between;align-items:flex-end;gap:8px;padding:6px 8px;pointer-events:none}
608
- .touch-pad{display:flex;gap:8px;pointer-events:auto}
609
- .touch-btn{width:56px;height:56px;min-width:var(--tap);min-height:var(--tap);border-radius:12px;border:2px solid rgba(255,255,255,.75);background:rgba(13,17,32,.92);color:#fff;font-size:1.4rem;font-weight:800;line-height:1;display:flex;align-items:center;justify-content:center;touch-action:none;-webkit-user-select:none;user-select:none;cursor:pointer}
968
+ /* touch controls (mobile) — an overlay on the bottom edges, not covering the centre */
969
+ .touch{position:absolute;inset:0;z-index:14;display:flex;justify-content:space-between;align-items:flex-end;gap:8px;padding:var(--margem-borda,8px);pointer-events:none}
970
+ /* ADR-0160: the shoulders in the TOP corners of the region, the trigger (2) above the bumper (1). `.touch` covers the whole
971
+ region since then (`inset:0`, still `pointer-events:none`) so a corner is the region's corner, not the pad strip's. */
972
+ .touch-ombros{position:absolute;top:var(--margem-borda,8px);display:flex;flex-direction:column;gap:6px;pointer-events:auto}
973
+ .touch-ombros--esq{left:var(--margem-borda,8px)}
974
+ .touch-ombros--dir{right:var(--margem-borda,8px)}
975
+ /* ADR-0160: the action buttons sit by ACTION NUMBER — 1 and 4 on top, 2 and 3 below — whatever slot fires them.
976
+ `margin-left:auto` keeps the block on the right when the game names no direction (ADR-0162). */
977
+ .touch-pad{display:grid;grid-template-areas:"a1 a4" "a2 a3";gap:8px;margin-left:auto;pointer-events:auto}
978
+ .touch-btn[data-acao="action1"]{grid-area:a1}
979
+ .touch-btn[data-acao="action4"]{grid-area:a4}
980
+ .touch-btn[data-acao="action2"]{grid-area:a2}
981
+ .touch-btn[data-acao="action3"]{grid-area:a3}
982
+ .touch-btn{width:56px;height:56px;min-width:var(--alvo-min,44px);min-height:var(--alvo-min,44px);border-radius:12px;border:2px solid rgba(255,255,255,.75);background:rgba(13,17,32,.92);color:#fff;font-size:1.4em;font-weight:800;line-height:1;display:flex;align-items:center;justify-content:center;touch-action:none;-webkit-user-select:none;user-select:none;cursor:pointer}
610
983
  .touch-btn:active{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}
611
984
  .touch-jump{background:rgba(18,74,52,.94)}
612
- /* Botões de ação em LOSANGO (4 bolhas: 0 baixo, 1 direita, 2 esquerda, 3 cima) + START pílula */
985
+ /* Action buttons in a DIAMOND (4 bubbles: 0 bottom, 1 right, 2 left, 3 top) + a START pill */
613
986
  .touch-actions{align-items:center}
614
- /* Tamanho FÍSICO (mm), não px: --pad-btn e --pad-diam são calculados no JS a partir de mm reais
615
- (referência: iPhone 16 ocupando a tela toda). Config em A12e motora. Fallback antes do JS rodar. */
987
+ /* PHYSICAL size (mm), not px: --pad-btn and --pad-diam are computed by `input/touch` from real millimetres
988
+ (reference: an iPhone 16 filling the screen). These are the fallbacks before the JS runs. */
616
989
  .pad-diamond{position:relative;width:var(--pad-diam,182px);height:var(--pad-diam,182px);pointer-events:none}
617
990
  .pad-b{position:absolute;width:var(--pad-btn,66px);height:var(--pad-btn,66px);border-radius:50%;font-size:calc(var(--pad-btn,66px)*.42);font-weight:800;pointer-events:auto;color:#fff;text-shadow:0 1px 2px rgba(0,0,0,.6);display:flex;align-items:center;justify-content:center}
618
991
  .pad-top{top:0;left:50%;transform:translateX(-50%)}
619
992
  .pad-bottom{bottom:0;left:50%;transform:translateX(-50%)}
620
993
  .pad-left{left:0;top:50%;transform:translateY(-50%)}
621
994
  .pad-right{right:0;top:50%;transform:translateY(-50%)}
622
- .touch-start{position:absolute;left:50%;bottom:6px;transform:translateX(-50%);min-height:calc(var(--pad-btn,66px)*.7);box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;pointer-events:auto;border-radius:999px;padding:0 22px;font-weight:800;font-size:.82rem;letter-spacing:.08em;background:rgba(13,17,32,.92);color:#fff;border:2px solid rgba(255,255,255,.75);cursor:pointer;z-index:15}
995
+ /* `width:auto`: the pill is also a `.touch-btn`, whose fixed 56px cut the word — measured in the dist, «START» with 66px
996
+ of text in a 56px button at the cursive hand's 1.25 scale (ADR-0149). The pill grows with the word, and the floor is the target. */
997
+ /* The two system pills, SELECT and START, in a container at the centre (ADR-0155). Inside it they no longer place
998
+ themselves: the container puts them side by side, and each keeps the width of its text. */
999
+ /* 🔴 WITH A PANEL OPEN, THE PAD SITS ABOVE IT (ADR-0157): its directional is what moves in the panel. The panel's veil
1000
+ (`#game-region .overlay`, 60) covered the pad (14), and a tap on an arrow landed on the veil. `--z-menu-max` is the top
1001
+ of the menu band, and that is the pad's role while a menu is open — a slot and not a new literal. */
1002
+ #game-region:has(.overlay:not([hidden])) .touch{z-index:var(--z-menu-max)}
1003
+ /* the two pills stand the edge margin off the bottom (interface log 2026-09-26) — alone or side by side */
1004
+ .touch-sistema{position:absolute;left:50%;bottom:var(--margem-borda,8px);transform:translateX(-50%);display:flex;gap:10px;pointer-events:none}
1005
+ .touch-sistema .touch-start,.touch-sistema .touch-select{position:static;transform:none}
1006
+ .touch-start,.touch-select{position:absolute;left:50%;bottom:var(--margem-borda,8px);transform:translateX(-50%);width:auto;min-width:var(--alvo-min,44px);white-space:nowrap;min-height:calc(var(--pad-btn,66px)*.7);box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;pointer-events:auto;border-radius:999px;padding:0 22px;font-weight:800;font-size:1em;letter-spacing:var(--ls);background:rgba(13,17,32,.92);color:#fff;border:2px solid rgba(255,255,255,.75);cursor:pointer;z-index:15}
623
1007
  .touch-start:active{background:var(--accent);color:var(--accent-ink);border-color:var(--accent)}
624
- /* joystick digital: base (círculo maior) + manopla (círculo menor) */
625
- /* Direcional (analógico/"alavanca"): base = superfície + deslocamento; manopla = superfície de contato. Ambos em mm (JS). */
1008
+ /* digital joystick: base (larger circle) + knob (smaller circle) */
1009
+ /* Directional (analogue/"lever"): base = surface + travel; knob = contact surface. Both in mm (JS). */
626
1010
  .touch-stick{position:relative;width:var(--stick-base,124px);height:var(--stick-base,124px);border-radius:50%;border:3px solid rgba(255,255,255,.7);background:rgba(13,17,32,.85);touch-action:none;pointer-events:auto;-webkit-user-select:none;user-select:none}
627
1011
  .touch-knob{position:absolute;left:50%;top:50%;width:var(--stick-knob,58px);height:var(--stick-knob,58px);margin:calc(var(--stick-knob,58px)/-2) 0 0 calc(var(--stick-knob,58px)/-2);border-radius:50%;background:rgba(255,255,255,.88);box-shadow:0 0 0 2px rgba(0,0,0,.45);transition:transform .04s linear;pointer-events:none}
628
- /* D-pad em cruz (estilo alternativo): 4 braços + miolo; tamanho do braço em mm (JS → --dpad-arm-l/w, --dpad-span) */
1012
+ /* Cross D-pad (an alternative style): 4 arms + a core; the arm's size in mm (JS → --dpad-arm-l/w, --dpad-span) */
629
1013
  .touch-cross{position:relative;width:var(--dpad-span,120px);height:var(--dpad-span,120px);touch-action:none;pointer-events:auto;-webkit-user-select:none;user-select:none}
630
1014
  .touch-cross[hidden]{display:none}
631
1015
  .dpad-arm{position:absolute;display:flex;align-items:center;justify-content:center;background:rgba(13,17,32,.92);border:2px solid rgba(255,255,255,.75);color:rgba(255,255,255,.85);font-size:calc(var(--dpad-arm-w,34px)*.5);line-height:1;pointer-events:none}
@@ -641,4 +1025,4 @@ select.vol:focus, .overlay__card select:focus{outline:3px solid var(--accent);ou
641
1025
  @media (prefers-reduced-motion: reduce){ .touch-knob{transition:none} }
642
1026
  @media (prefers-contrast: more){ .touch-btn{background:#000;border-color:#fff} .touch-btn:active{background:#fff;color:#000} .touch-stick{background:#000;border-color:#fff} .touch-knob{background:#fff} }
643
1027
 
644
- @media (max-width:560px){.title{font-size:1.1rem}.hud{gap:1rem}}
1028
+ @media (max-width:560px){.title{font-size:1.1em}.hud{gap:1rem}}