@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.
- package/README.md +120 -78
- package/app/css/style.css +761 -377
- package/app/public/vendor/fonts/playwrite-cu.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-es-deco.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-es.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-gb-j.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-gb-s.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-pe.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-pt.woff2 +0 -0
- package/app/public/vendor/fonts-licences/NOTICE.txt +166 -0
- package/app/public/vendor/fonts-licences/OFL-1.1.txt +86 -0
- package/app/public/vendor/fonts.css +36 -84
- package/dist-pkg/boot/create-game.d.ts +499 -219
- package/dist-pkg/boot/create-game.js +3944 -562
- package/dist-pkg/core/a11y-sr.d.ts +25 -3
- package/dist-pkg/core/a11y-sr.js +38 -19
- package/dist-pkg/core/accessible-label.d.ts +17 -0
- package/dist-pkg/core/accessible-label.js +37 -0
- package/dist-pkg/core/accommodation-subjects.d.ts +26 -0
- package/dist-pkg/core/accommodation-subjects.js +32 -0
- package/dist-pkg/core/accommodations.d.ts +125 -0
- package/dist-pkg/core/accommodations.js +195 -0
- package/dist-pkg/core/actions.d.ts +108 -55
- package/dist-pkg/core/actions.js +122 -71
- package/dist-pkg/core/calm-mode.d.ts +31 -0
- package/dist-pkg/core/calm-mode.js +47 -0
- package/dist-pkg/core/camera-cycle.d.ts +15 -0
- package/dist-pkg/core/camera-cycle.js +7 -0
- package/dist-pkg/core/caption-duration.d.ts +15 -0
- package/dist-pkg/core/caption-duration.js +19 -0
- package/dist-pkg/core/cartridge-problems.d.ts +34 -0
- package/dist-pkg/core/cartridge-problems.js +53 -0
- package/dist-pkg/core/constants.d.ts +0 -54
- package/dist-pkg/core/constants.js +9 -76
- package/dist-pkg/core/contract.d.ts +142 -164
- package/dist-pkg/core/contract.js +222 -208
- package/dist-pkg/core/dom-query.d.ts +4 -4
- package/dist-pkg/core/dom-query.js +12 -20
- package/dist-pkg/core/entity.d.ts +62 -77
- package/dist-pkg/core/entity.js +18 -32
- package/dist-pkg/core/escape-html.d.ts +9 -10
- package/dist-pkg/core/escape-html.js +16 -20
- package/dist-pkg/core/flash-threshold.d.ts +17 -0
- package/dist-pkg/core/flash-threshold.js +113 -0
- package/dist-pkg/core/game-speed.d.ts +11 -0
- package/dist-pkg/core/game-speed.js +16 -0
- package/dist-pkg/core/genres.d.ts +20 -0
- package/dist-pkg/core/genres.js +44 -0
- package/dist-pkg/core/i18n.d.ts +71 -60
- package/dist-pkg/core/i18n.js +186 -200
- package/dist-pkg/core/loop.d.ts +17 -6
- package/dist-pkg/core/loop.js +26 -26
- package/dist-pkg/core/pause-icon-catalogue.d.ts +15 -0
- package/dist-pkg/core/pause-icon-catalogue.js +54 -0
- package/dist-pkg/core/ring.d.ts +17 -0
- package/dist-pkg/core/ring.js +29 -0
- package/dist-pkg/core/rng.d.ts +7 -12
- package/dist-pkg/core/rng.js +23 -53
- package/dist-pkg/core/route.d.ts +26 -26
- package/dist-pkg/core/route.js +126 -121
- package/dist-pkg/core/scenes.d.ts +37 -40
- package/dist-pkg/core/scenes.js +41 -43
- package/dist-pkg/core/screens.d.ts +4 -4
- package/dist-pkg/core/screens.js +11 -16
- package/dist-pkg/core/session-clock.d.ts +23 -0
- package/dist-pkg/core/session-clock.js +37 -0
- package/dist-pkg/core/setting-defaults.d.ts +61 -0
- package/dist-pkg/core/setting-defaults.js +66 -0
- package/dist-pkg/core/speech-rate.d.ts +29 -0
- package/dist-pkg/core/speech-rate.js +47 -0
- package/dist-pkg/core/state.d.ts +118 -118
- package/dist-pkg/core/state.js +322 -297
- package/dist-pkg/core/visual-cycles.d.ts +30 -0
- package/dist-pkg/core/visual-cycles.js +52 -0
- package/dist-pkg/core/visual-state.d.ts +13 -0
- package/dist-pkg/core/visual-state.js +6 -0
- package/dist-pkg/educational/activities-registry.d.ts +1 -1
- package/dist-pkg/educational/activities-registry.js +9 -8
- package/dist-pkg/educational/adaptive-engine.d.ts +11 -11
- package/dist-pkg/educational/adaptive-engine.js +17 -17
- package/dist-pkg/educational/segment-bar.d.ts +12 -12
- package/dist-pkg/educational/segment-bar.js +9 -9
- package/dist-pkg/i18n/en.js +324 -223
- package/dist-pkg/i18n/es.js +323 -223
- package/dist-pkg/i18n/pt.js +348 -226
- package/dist-pkg/input/default-bindings.d.ts +37 -38
- package/dist-pkg/input/default-bindings.js +127 -144
- package/dist-pkg/input/devices.d.ts +7 -7
- package/dist-pkg/input/devices.js +17 -16
- package/dist-pkg/input/edges.d.ts +23 -32
- package/dist-pkg/input/edges.js +23 -28
- package/dist-pkg/input/empathy-filter.d.ts +22 -0
- package/dist-pkg/input/empathy-filter.js +34 -0
- package/dist-pkg/input/face-map.d.ts +96 -0
- package/dist-pkg/input/face-map.js +255 -0
- package/dist-pkg/input/face-signals.d.ts +40 -0
- package/dist-pkg/input/face-signals.js +48 -0
- package/dist-pkg/input/gamepad.d.ts +195 -145
- package/dist-pkg/input/gamepad.js +383 -556
- package/dist-pkg/input/gaze-cycle.d.ts +64 -0
- package/dist-pkg/input/gaze-cycle.js +133 -0
- package/dist-pkg/input/gaze-relative.d.ts +68 -0
- package/dist-pkg/input/gaze-relative.js +142 -0
- package/dist-pkg/input/hand-map.d.ts +28 -0
- package/dist-pkg/input/hand-map.js +142 -0
- package/dist-pkg/input/input-cooldown.d.ts +17 -0
- package/dist-pkg/input/input-cooldown.js +39 -0
- package/dist-pkg/input/key-default.d.ts +30 -0
- package/dist-pkg/input/key-default.js +60 -0
- package/dist-pkg/input/keyboard-runtime.d.ts +15 -21
- package/dist-pkg/input/keyboard-runtime.js +23 -27
- package/dist-pkg/input/keyboard.d.ts +68 -63
- package/dist-pkg/input/keyboard.js +83 -105
- package/dist-pkg/input/keydown.d.ts +123 -137
- package/dist-pkg/input/keydown.js +284 -289
- package/dist-pkg/input/latch-edge.d.ts +33 -21
- package/dist-pkg/input/latch-edge.js +34 -35
- package/dist-pkg/input/latch-scope.d.ts +51 -50
- package/dist-pkg/input/latch-scope.js +61 -82
- package/dist-pkg/input/latch-store.d.ts +26 -30
- package/dist-pkg/input/latch-store.js +42 -50
- package/dist-pkg/input/latch-sync.d.ts +29 -29
- package/dist-pkg/input/latch-sync.js +25 -25
- package/dist-pkg/input/latch.d.ts +14 -17
- package/dist-pkg/input/latch.js +29 -37
- package/dist-pkg/input/pad-defaults.d.ts +13 -16
- package/dist-pkg/input/pad-defaults.js +21 -28
- package/dist-pkg/input/pad-reading.d.ts +76 -0
- package/dist-pkg/input/pad-reading.js +169 -0
- package/dist-pkg/input/pad-wizard.d.ts +99 -0
- package/dist-pkg/input/pad-wizard.js +256 -0
- package/dist-pkg/input/pointer-space.d.ts +19 -20
- package/dist-pkg/input/pointer-space.js +27 -32
- package/dist-pkg/input/pointer.d.ts +39 -40
- package/dist-pkg/input/pointer.js +42 -45
- package/dist-pkg/input/state.d.ts +101 -89
- package/dist-pkg/input/state.js +54 -134
- package/dist-pkg/input/switch-scan.d.ts +59 -0
- package/dist-pkg/input/switch-scan.js +67 -0
- package/dist-pkg/input/synthetic-source.d.ts +42 -0
- package/dist-pkg/input/synthetic-source.js +57 -0
- package/dist-pkg/input/touch-bindings.d.ts +127 -116
- package/dist-pkg/input/touch-bindings.js +158 -173
- package/dist-pkg/input/touch.d.ts +147 -72
- package/dist-pkg/input/touch.js +362 -126
- package/dist-pkg/input/transport-in-use.d.ts +107 -0
- package/dist-pkg/input/transport-in-use.js +130 -0
- package/dist-pkg/input/transports.d.ts +90 -117
- package/dist-pkg/input/transports.js +104 -106
- package/dist-pkg/input/virtual-controller.d.ts +82 -0
- package/dist-pkg/input/virtual-controller.js +69 -0
- package/dist-pkg/input/vocabulary-migration.d.ts +50 -49
- package/dist-pkg/input/vocabulary-migration.js +80 -79
- package/dist-pkg/input/voice-map.d.ts +59 -0
- package/dist-pkg/input/voice-map.js +123 -0
- package/dist-pkg/platform/audio-ambient.d.ts +13 -2
- package/dist-pkg/platform/audio-ambient.js +14 -14
- package/dist-pkg/platform/audio-earcons.d.ts +20 -19
- package/dist-pkg/platform/audio-earcons.js +51 -42
- package/dist-pkg/platform/audio-jingles.js +9 -10
- package/dist-pkg/platform/audio-mixer.d.ts +14 -2
- package/dist-pkg/platform/audio-mixer.js +31 -39
- package/dist-pkg/platform/audio-sonar.d.ts +49 -152
- package/dist-pkg/platform/audio-sonar.js +124 -302
- package/dist-pkg/platform/audio.d.ts +62 -19
- package/dist-pkg/platform/audio.js +193 -168
- package/dist-pkg/platform/choose-by-voice.d.ts +70 -0
- package/dist-pkg/platform/choose-by-voice.js +157 -0
- package/dist-pkg/platform/flash-sampler.d.ts +19 -0
- package/dist-pkg/platform/flash-sampler.js +79 -0
- package/dist-pkg/platform/font-library.d.ts +77 -0
- package/dist-pkg/platform/font-library.js +98 -0
- package/dist-pkg/platform/font-library.json +721 -0
- package/dist-pkg/platform/heavy-catalogue.d.ts +77 -0
- package/dist-pkg/platform/heavy-catalogue.js +256 -0
- package/dist-pkg/platform/heavy-mirror.d.ts +20 -0
- package/dist-pkg/platform/heavy-mirror.js +73 -0
- package/dist-pkg/platform/heavy.d.ts +136 -0
- package/dist-pkg/platform/heavy.js +324 -0
- package/dist-pkg/platform/interruptible-speech.d.ts +23 -16
- package/dist-pkg/platform/interruptible-speech.js +77 -54
- package/dist-pkg/platform/kokoro-port.d.ts +27 -0
- package/dist-pkg/platform/kokoro-port.js +50 -0
- package/dist-pkg/platform/kokoro-runtime.d.ts +20 -0
- package/dist-pkg/platform/kokoro-runtime.js +52 -0
- package/dist-pkg/platform/kokoro.d.ts +70 -0
- package/dist-pkg/platform/kokoro.js +133 -0
- package/dist-pkg/platform/listener-scope.d.ts +12 -0
- package/dist-pkg/platform/listener-scope.js +90 -0
- package/dist-pkg/platform/locale-host.d.ts +14 -0
- package/dist-pkg/platform/locale-host.js +45 -0
- package/dist-pkg/platform/microphone.d.ts +51 -0
- package/dist-pkg/platform/microphone.js +118 -0
- package/dist-pkg/platform/onnx-runtime.d.ts +34 -0
- package/dist-pkg/platform/onnx-runtime.js +42 -0
- package/dist-pkg/platform/reading-in-worker.d.ts +64 -0
- package/dist-pkg/platform/reading-in-worker.js +125 -0
- package/dist-pkg/platform/reading-model.d.ts +94 -0
- package/dist-pkg/platform/reading-model.js +272 -0
- package/dist-pkg/platform/reading-runtime.d.ts +24 -0
- package/dist-pkg/platform/reading-runtime.js +164 -0
- package/dist-pkg/platform/reading-worker.d.ts +41 -0
- package/dist-pkg/platform/reading-worker.js +58 -0
- package/dist-pkg/platform/reading.d.ts +80 -0
- package/dist-pkg/platform/reading.js +136 -0
- package/dist-pkg/platform/speech-recognition.d.ts +60 -0
- package/dist-pkg/platform/speech-recognition.js +104 -0
- package/dist-pkg/platform/speech.d.ts +23 -1
- package/dist-pkg/platform/speech.js +29 -18
- package/dist-pkg/platform/storage-keys.d.ts +96 -0
- package/dist-pkg/platform/storage-keys.js +106 -0
- package/dist-pkg/platform/storage.d.ts +46 -111
- package/dist-pkg/platform/storage.js +104 -169
- package/dist-pkg/platform/tts.d.ts +85 -39
- package/dist-pkg/platform/tts.js +447 -127
- package/dist-pkg/platform/vision-loop.d.ts +23 -0
- package/dist-pkg/platform/vision-loop.js +56 -0
- package/dist-pkg/platform/vision.d.ts +132 -0
- package/dist-pkg/platform/vision.js +117 -0
- package/dist-pkg/platform/voice-listener.d.ts +40 -0
- package/dist-pkg/platform/voice-listener.js +88 -0
- package/dist-pkg/platform/voice-plan.d.ts +15 -98
- package/dist-pkg/platform/voice-plan.js +56 -143
- package/dist-pkg/platform/vosk-runtime.d.ts +96 -0
- package/dist-pkg/platform/vosk-runtime.js +151 -0
- package/dist-pkg/platform/vosk-vocabulary.d.ts +6 -0
- package/dist-pkg/platform/vosk-vocabulary.js +178 -0
- package/dist-pkg/render/canvas.d.ts +13 -7
- package/dist-pkg/render/canvas.js +13 -13
- package/dist-pkg/render/crt.d.ts +48 -12
- package/dist-pkg/render/crt.js +78 -80
- package/dist-pkg/render/cvd-matrices.d.ts +22 -18
- package/dist-pkg/render/cvd-matrices.js +37 -38
- package/dist-pkg/render/hc-role-data.d.ts +7 -7
- package/dist-pkg/render/hc-role-data.js +14 -14
- package/dist-pkg/render/high-contrast.d.ts +67 -56
- package/dist-pkg/render/high-contrast.js +226 -217
- package/dist-pkg/render/low-vision-drawing.d.ts +5 -0
- package/dist-pkg/render/low-vision-drawing.js +46 -0
- package/dist-pkg/render/lq-filter.d.ts +32 -20
- package/dist-pkg/render/lq-filter.js +59 -65
- package/dist-pkg/render/port.d.ts +86 -88
- package/dist-pkg/render/port.js +17 -23
- package/dist-pkg/render/screen-pipeline.d.ts +42 -42
- package/dist-pkg/render/screen-pipeline.js +80 -87
- package/dist-pkg/render/sprite-fx.d.ts +2 -1
- package/dist-pkg/render/sprite-fx.js +16 -14
- package/dist-pkg/render/viewports.d.ts +20 -15
- package/dist-pkg/render/viewports.js +83 -114
- package/dist-pkg/render/viz-axes-labels.d.ts +47 -0
- package/dist-pkg/render/viz-axes-labels.js +88 -0
- package/dist-pkg/render/viz-axes.d.ts +89 -86
- package/dist-pkg/render/viz-axes.js +128 -116
- package/dist-pkg/render/viz-modes.d.ts +28 -29
- package/dist-pkg/render/viz-modes.js +40 -45
- package/dist-pkg/render/viz-refusal.d.ts +32 -0
- package/dist-pkg/render/viz-refusal.js +57 -0
- package/dist-pkg/render/viz-setters.d.ts +104 -66
- package/dist-pkg/render/viz-setters.js +182 -191
- package/dist-pkg/ui/aac-sets.d.ts +45 -0
- package/dist-pkg/ui/aac-sets.js +41 -0
- package/dist-pkg/ui/audio-choices.d.ts +68 -0
- package/dist-pkg/ui/audio-choices.js +98 -0
- package/dist-pkg/ui/audio-rows-that-apply.d.ts +12 -0
- package/dist-pkg/ui/audio-rows-that-apply.js +43 -0
- package/dist-pkg/ui/camera-control.d.ts +53 -0
- package/dist-pkg/ui/camera-control.js +48 -0
- package/dist-pkg/ui/changed-mark.d.ts +9 -8
- package/dist-pkg/ui/changed-mark.js +22 -53
- package/dist-pkg/ui/control-choices.d.ts +37 -0
- package/dist-pkg/ui/control-choices.js +84 -0
- package/dist-pkg/ui/debug-panel.d.ts +59 -43
- package/dist-pkg/ui/debug-panel.js +68 -69
- package/dist-pkg/ui/declared-words.d.ts +21 -0
- package/dist-pkg/ui/declared-words.js +66 -0
- package/dist-pkg/ui/dom.d.ts +10 -14
- package/dist-pkg/ui/dom.js +10 -47
- package/dist-pkg/ui/drawing-problems.d.ts +25 -0
- package/dist-pkg/ui/drawing-problems.js +94 -0
- package/dist-pkg/ui/eye-control.d.ts +36 -0
- package/dist-pkg/ui/eye-control.js +178 -0
- package/dist-pkg/ui/face-control.d.ts +25 -0
- package/dist-pkg/ui/face-control.js +183 -0
- package/dist-pkg/ui/focus-trap.d.ts +39 -49
- package/dist-pkg/ui/focus-trap.js +56 -65
- package/dist-pkg/ui/fonts.d.ts +157 -68
- package/dist-pkg/ui/fonts.js +305 -115
- package/dist-pkg/ui/footer-scroll-driver.d.ts +14 -0
- package/dist-pkg/ui/footer-scroll-driver.js +201 -0
- package/dist-pkg/ui/footer-scroll.d.ts +38 -0
- package/dist-pkg/ui/footer-scroll.js +55 -0
- package/dist-pkg/ui/game-options.d.ts +47 -0
- package/dist-pkg/ui/game-options.js +158 -0
- package/dist-pkg/ui/gaze-overlay.d.ts +64 -0
- package/dist-pkg/ui/gaze-overlay.js +165 -0
- package/dist-pkg/ui/hand-control.d.ts +25 -0
- package/dist-pkg/ui/hand-control.js +143 -0
- package/dist-pkg/ui/help-panel.d.ts +87 -0
- package/dist-pkg/ui/help-panel.js +242 -0
- package/dist-pkg/ui/hud-bands.d.ts +62 -0
- package/dist-pkg/ui/hud-bands.js +146 -0
- package/dist-pkg/ui/hud-row.d.ts +34 -0
- package/dist-pkg/ui/hud-row.js +80 -0
- package/dist-pkg/ui/hud.d.ts +73 -73
- package/dist-pkg/ui/hud.js +94 -98
- package/dist-pkg/ui/item-announcement.d.ts +13 -12
- package/dist-pkg/ui/item-announcement.js +25 -26
- package/dist-pkg/ui/layout.d.ts +103 -28
- package/dist-pkg/ui/layout.js +117 -99
- package/dist-pkg/ui/libras-avatar-clip.d.ts +30 -0
- package/dist-pkg/ui/libras-avatar-clip.js +86 -0
- package/dist-pkg/ui/libras-avatar-load.d.ts +52 -0
- package/dist-pkg/ui/libras-avatar-load.js +134 -0
- package/dist-pkg/ui/libras-avatar-plan.d.ts +102 -0
- package/dist-pkg/ui/libras-avatar-plan.js +211 -0
- package/dist-pkg/ui/libras-avatar-player.d.ts +28 -0
- package/dist-pkg/ui/libras-avatar-player.js +226 -0
- package/dist-pkg/ui/libras-avatar-stage.d.ts +37 -0
- package/dist-pkg/ui/libras-avatar-stage.js +208 -0
- package/dist-pkg/ui/libras-glosses.d.ts +28 -0
- package/dist-pkg/ui/libras-glosses.js +161 -0
- package/dist-pkg/ui/locale-flags.d.ts +16 -0
- package/dist-pkg/ui/locale-flags.js +30 -0
- package/dist-pkg/ui/loop-crash.d.ts +17 -17
- package/dist-pkg/ui/loop-crash.js +33 -62
- package/dist-pkg/ui/menu-intent.d.ts +61 -0
- package/dist-pkg/ui/menu-intent.js +88 -0
- package/dist-pkg/ui/menu-items.d.ts +20 -0
- package/dist-pkg/ui/menu-items.js +51 -0
- package/dist-pkg/ui/menu-nav.d.ts +98 -92
- package/dist-pkg/ui/menu-nav.js +398 -286
- package/dist-pkg/ui/mobility-choices.d.ts +26 -0
- package/dist-pkg/ui/mobility-choices.js +41 -0
- package/dist-pkg/ui/motion-choices.d.ts +31 -0
- package/dist-pkg/ui/motion-choices.js +68 -0
- package/dist-pkg/ui/motion-scene.d.ts +24 -19
- package/dist-pkg/ui/motion-scene.js +29 -61
- package/dist-pkg/ui/mount-panel.d.ts +81 -0
- package/dist-pkg/ui/mount-panel.js +61 -0
- package/dist-pkg/ui/panel-shell.d.ts +65 -31
- package/dist-pkg/ui/panel-shell.js +92 -66
- package/dist-pkg/ui/panel-widgets.d.ts +150 -0
- package/dist-pkg/ui/panel-widgets.js +294 -0
- package/dist-pkg/ui/pause-buttons.d.ts +35 -0
- package/dist-pkg/ui/pause-buttons.js +66 -0
- package/dist-pkg/ui/pause-icons.d.ts +261 -347
- package/dist-pkg/ui/pause-icons.js +671 -601
- package/dist-pkg/ui/pause-markup.d.ts +86 -0
- package/dist-pkg/ui/pause-markup.js +125 -0
- package/dist-pkg/ui/reach-notice.d.ts +22 -23
- package/dist-pkg/ui/reach-notice.js +55 -58
- package/dist-pkg/ui/scan-overlay.d.ts +23 -0
- package/dist-pkg/ui/scan-overlay.js +50 -0
- package/dist-pkg/ui/screen-text.d.ts +25 -0
- package/dist-pkg/ui/screen-text.js +152 -0
- package/dist-pkg/ui/session-clock.d.ts +32 -0
- package/dist-pkg/ui/session-clock.js +67 -0
- package/dist-pkg/ui/settings-aac.d.ts +70 -0
- package/dist-pkg/ui/settings-aac.js +208 -0
- package/dist-pkg/ui/settings-audio.d.ts +97 -114
- package/dist-pkg/ui/settings-audio.js +493 -425
- package/dist-pkg/ui/settings-controls.d.ts +53 -96
- package/dist-pkg/ui/settings-controls.js +143 -215
- package/dist-pkg/ui/settings-empathy.d.ts +39 -30
- package/dist-pkg/ui/settings-empathy.js +53 -60
- package/dist-pkg/ui/settings-mobility.d.ts +192 -0
- package/dist-pkg/ui/settings-mobility.js +332 -0
- package/dist-pkg/ui/settings-motion.d.ts +91 -70
- package/dist-pkg/ui/settings-motion.js +287 -198
- package/dist-pkg/ui/settings-panel.d.ts +58 -45
- package/dist-pkg/ui/settings-panel.js +189 -94
- package/dist-pkg/ui/settings-typo.d.ts +29 -78
- package/dist-pkg/ui/settings-typo.js +158 -129
- package/dist-pkg/ui/settings-visual.d.ts +29 -87
- package/dist-pkg/ui/settings-visual.js +240 -193
- package/dist-pkg/ui/shell.d.ts +112 -135
- package/dist-pkg/ui/shell.js +148 -217
- package/dist-pkg/ui/simulation-list.d.ts +27 -0
- package/dist-pkg/ui/simulation-list.js +76 -0
- package/dist-pkg/ui/simulation-over-the-world.d.ts +23 -0
- package/dist-pkg/ui/simulation-over-the-world.js +131 -0
- package/dist-pkg/ui/simulation-refusal.d.ts +5 -31
- package/dist-pkg/ui/simulation-refusal.js +8 -56
- package/dist-pkg/ui/switchable-control.d.ts +11 -0
- package/dist-pkg/ui/switchable-control.js +19 -0
- package/dist-pkg/ui/title.d.ts +3 -12
- package/dist-pkg/ui/title.js +49 -18
- package/dist-pkg/ui/top-band.d.ts +26 -0
- package/dist-pkg/ui/top-band.js +96 -0
- package/dist-pkg/ui/typo-choices.d.ts +71 -0
- package/dist-pkg/ui/typo-choices.js +85 -0
- package/dist-pkg/ui/visual-axes-panel.d.ts +11 -47
- package/dist-pkg/ui/visual-axes-panel.js +12 -94
- package/dist-pkg/ui/visual-choices.d.ts +77 -0
- package/dist-pkg/ui/visual-choices.js +107 -0
- package/dist-pkg/ui/vlibras.d.ts +77 -10
- package/dist-pkg/ui/vlibras.js +130 -81
- package/dist-pkg/ui/voice-control.d.ts +68 -0
- package/dist-pkg/ui/voice-control.js +276 -0
- package/dist-pkg/ui/voice-settings.d.ts +95 -0
- package/dist-pkg/ui/voice-settings.js +352 -0
- package/dist-pkg/ui/where-the-child-is.d.ts +31 -0
- package/dist-pkg/ui/where-the-child-is.js +70 -0
- package/docs/CREDITS.md +300 -44
- package/docs/LICENSES.md +189 -145
- package/package.json +51 -27
- package/scripts/check-cartridge.mjs +78 -0
- package/scripts/game-build.d.mts +25 -0
- package/scripts/game-build.mjs +144 -0
- package/scripts/heavy-into-the-delivery.mjs +388 -0
- package/scripts/libras-avatar.json +680 -0
- package/scripts/libras-avatar.mjs +246 -0
- package/scripts/libras-glosses/gloss.py +95 -0
- package/scripts/libras-glosses/pyproject.toml +32 -0
- package/scripts/libras-glosses/uv.lock +781 -0
- package/scripts/libras-glosses.mjs +351 -0
- package/scripts/licences/Apache-2.0.txt +176 -0
- package/scripts/licences/GPL-3.0.txt +674 -0
- package/scripts/licences/MIT.txt +17 -0
- package/scripts/licences/OFL-1.1.txt +86 -0
- package/scripts/licences/UFL-1.0.txt +96 -0
- package/scripts/licences/fonts.mjs +389 -0
- package/scripts/licences/third-party.mjs +301 -0
- package/scripts/licences/vosk-browser.NOTICE.txt +268 -0
- package/app/public/vendor/fonts/comicneue-400.woff2 +0 -0
- package/app/public/vendor/fonts/comicneue-700.woff2 +0 -0
- package/app/public/vendor/fonts/fondamento-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/fondamento-400.woff2 +0 -0
- package/app/public/vendor/fonts/inter-400.woff2 +0 -0
- package/app/public/vendor/fonts/inter-700.woff2 +0 -0
- package/app/public/vendor/fonts/lato-400.woff2 +0 -0
- package/app/public/vendor/fonts/lato-700.woff2 +0 -0
- package/app/public/vendor/fonts/literata-400.woff2 +0 -0
- package/app/public/vendor/fonts/literata-700.woff2 +0 -0
- package/app/public/vendor/fonts/newsreader-400.woff2 +0 -0
- package/app/public/vendor/fonts/newsreader-700.woff2 +0 -0
- package/app/public/vendor/fonts/opendyslexic-400.woff2 +0 -0
- package/app/public/vendor/fonts/opensans-400.woff2 +0 -0
- package/app/public/vendor/fonts/opensans-700.woff2 +0 -0
- package/app/public/vendor/fonts/pinyon-400.woff2 +0 -0
- package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
- package/app/public/vendor/fonts/quattro-400.woff2 +0 -0
- package/app/public/vendor/fonts/quattro-700.woff2 +0 -0
- package/app/public/vendor/fonts/sourcesans-400.woff2 +0 -0
- package/app/public/vendor/fonts/sourcesans-700.woff2 +0 -0
- package/app/public/vendor/fonts/sourceserif-400.woff2 +0 -0
- package/app/public/vendor/fonts/sourceserif-700.woff2 +0 -0
- package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
- package/dist-pkg/core/anel.d.ts +0 -17
- package/dist-pkg/core/anel.js +0 -31
- package/dist-pkg/core/collision.d.ts +0 -21
- package/dist-pkg/core/collision.js +0 -62
- package/dist-pkg/core/layers.d.ts +0 -63
- package/dist-pkg/core/layers.js +0 -95
- package/dist-pkg/core/letter-grid.d.ts +0 -55
- package/dist-pkg/core/letter-grid.js +0 -116
- package/dist-pkg/core/password.d.ts +0 -34
- package/dist-pkg/core/password.js +0 -205
- package/dist-pkg/core/rotulo-acessivel.d.ts +0 -17
- package/dist-pkg/core/rotulo-acessivel.js +0 -41
- package/dist-pkg/core/run-state.d.ts +0 -115
- package/dist-pkg/core/run-state.js +0 -40
- package/dist-pkg/core/tiles.d.ts +0 -12
- package/dist-pkg/core/tiles.js +0 -61
- package/dist-pkg/core/world.d.ts +0 -4
- package/dist-pkg/core/world.js +0 -58
- package/dist-pkg/input/origem-sintetica.d.ts +0 -44
- package/dist-pkg/input/origem-sintetica.js +0 -60
- package/dist-pkg/input/transporte-em-uso.d.ts +0 -101
- package/dist-pkg/input/transporte-em-uso.js +0 -130
- package/dist-pkg/platform/audio-nav.d.ts +0 -51
- package/dist-pkg/platform/audio-nav.js +0 -99
- package/dist-pkg/platform/guide-intensity.d.ts +0 -40
- package/dist-pkg/platform/guide-intensity.js +0 -73
- package/dist-pkg/platform/pesados-catalogo.d.ts +0 -14
- package/dist-pkg/platform/pesados-catalogo.js +0 -177
- package/dist-pkg/platform/pesados.d.ts +0 -31
- package/dist-pkg/platform/pesados.js +0 -75
- package/dist-pkg/platform/vozes-prontas.d.ts +0 -28
- package/dist-pkg/platform/vozes-prontas.js +0 -61
- package/dist-pkg/render/camera.d.ts +0 -58
- package/dist-pkg/render/camera.js +0 -105
- package/dist-pkg/render/cenario-data.d.ts +0 -127
- package/dist-pkg/render/cenario-data.js +0 -145
- package/dist-pkg/render/city-tex.d.ts +0 -63
- package/dist-pkg/render/city-tex.js +0 -228
- package/dist-pkg/render/city-tiles.d.ts +0 -11
- package/dist-pkg/render/city-tiles.js +0 -140
- package/dist-pkg/render/draw.d.ts +0 -149
- package/dist-pkg/render/draw.js +0 -231
- package/dist-pkg/render/fx.d.ts +0 -63
- package/dist-pkg/render/fx.js +0 -123
- package/dist-pkg/render/minimap.d.ts +0 -11
- package/dist-pkg/render/minimap.js +0 -94
- package/dist-pkg/render/parallax.d.ts +0 -85
- package/dist-pkg/render/parallax.js +0 -158
- package/dist-pkg/render/player-anim.d.ts +0 -71
- package/dist-pkg/render/player-anim.js +0 -152
- package/dist-pkg/render/recycling-tex.d.ts +0 -48
- package/dist-pkg/render/recycling-tex.js +0 -164
- package/dist-pkg/render/scene-city.d.ts +0 -77
- package/dist-pkg/render/scene-city.js +0 -181
- package/dist-pkg/render/scene-parallax.d.ts +0 -72
- package/dist-pkg/render/scene-parallax.js +0 -426
- package/dist-pkg/render/scene-sky.d.ts +0 -126
- package/dist-pkg/render/scene-sky.js +0 -295
- package/dist-pkg/render/set-cenario.d.ts +0 -54
- package/dist-pkg/render/set-cenario.js +0 -103
- package/dist-pkg/render/textures.d.ts +0 -75
- package/dist-pkg/render/textures.js +0 -240
- package/dist-pkg/render/title-scene.d.ts +0 -46
- package/dist-pkg/render/title-scene.js +0 -75
- package/dist-pkg/render/weather.d.ts +0 -96
- package/dist-pkg/render/weather.js +0 -171
- package/dist-pkg/render/wheelchair-sprites.d.ts +0 -32
- package/dist-pkg/render/wheelchair-sprites.js +0 -69
- package/dist-pkg/render/world-tex.d.ts +0 -18
- package/dist-pkg/render/world-tex.js +0 -87
- package/dist-pkg/ui/activities-menu.d.ts +0 -258
- package/dist-pkg/ui/activities-menu.js +0 -648
- package/dist-pkg/ui/caa-sets.d.ts +0 -39
- package/dist-pkg/ui/caa-sets.js +0 -65
- package/dist-pkg/ui/latch-refusal.d.ts +0 -32
- package/dist-pkg/ui/latch-refusal.js +0 -60
- package/dist-pkg/ui/map-hub.d.ts +0 -56
- package/dist-pkg/ui/map-hub.js +0 -138
- package/dist-pkg/ui/settings-caa.d.ts +0 -45
- package/dist-pkg/ui/settings-caa.js +0 -137
- package/dist-pkg/ui/settings-motor.d.ts +0 -175
- package/dist-pkg/ui/settings-motor.js +0 -342
- package/dist-pkg/ui/webcam.d.ts +0 -7
- package/dist-pkg/ui/webcam.js +0 -93
package/dist-pkg/ui/menu-nav.js
CHANGED
|
@@ -1,201 +1,132 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
-
// ui/menu-nav.ts —
|
|
2
|
+
// ui/menu-nav.ts — UNIVERSAL menu and dialog navigation: move, choose and go back without a mouse.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// ·
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// input/gamepad.ts monta exatamente o mesmo `{yes,no,up,down,left,right}` e chama `navDialog`/`navPause` daqui.
|
|
13
|
-
// É essa forma comum que faz "o jogo inteiro é operável de seis maneiras" ser verdade por construção e não por
|
|
14
|
-
// disciplina.
|
|
4
|
+
// Operation by keyboard (WCAG 2.1.1) and focus order (2.4.3). Any open menu — the pause card or a settings panel —
|
|
5
|
+
// answers the SAME six intents, whether they come from the keyboard, a controller, the eyes or speech:
|
|
6
|
+
// · up/down/left/right — move between items; on a list, a slider or a steps control, left/right ADJUST the value
|
|
7
|
+
// · yes — confirm, toggle, enter
|
|
8
|
+
// · no — back one level; at the pause card's root, back to the game
|
|
9
|
+
// So the module speaks of INTENT (`NavKeys`), never of keys. The keyboard is one translator (`menuNavKey`, below);
|
|
10
|
+
// `input/gamepad` builds the same `NavKeys` and calls `navDialog`/`navPause` from here. That shared shape is what makes
|
|
11
|
+
// «every menu works six ways» true by construction rather than by discipline.
|
|
15
12
|
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// · `pauseSetSel` · `navPause` — andar dentro de um menu de pausa, que NÃO usa foco do navegador e sim uma
|
|
20
|
-
// classe de seleção própria (`.pm-sel`/`.pi-sel`), porque ele é desenhado dentro da tela do jogador.
|
|
21
|
-
// · `menuNavKey` — o tradutor de teclado, em fase de CAPTURA.
|
|
13
|
+
// TWO KINDS OF MENU. A settings panel selects by browser FOCUS (`navDialog`). The pause card is drawn inside the player's
|
|
14
|
+
// screen and selects by a CLASS (`.pm-sel`, `navPause`) — and a class fires no announcement, which is why the pause card
|
|
15
|
+
// speaks its items through `srSay` and a panel does not need to for its focus.
|
|
22
16
|
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
// duas vezes: mesmo seletor de escopo (`#game-region .overlay`), mesmo filtro (`!hidden`), mesma ordenação
|
|
26
|
-
// estável por z-index efetivo, mesmo "vence o último". Conferi as duas linha a linha antes de unificar. Aqui
|
|
27
|
-
// `sharedDialogOpen` é um ALIAS de `ctx.topVisibleOverlay` — o nome antigo sobrevive porque input/gamepad.ts o
|
|
28
|
-
// consome por esse nome, mas o CÓDIGO agora existe uma vez só, em ui/settings-panel.ts. O ganho não é linha de
|
|
29
|
-
// menos: enquanto eram duas, uma correção de escopo (é justamente o que o defeito 1 abaixo pede) podia ser
|
|
30
|
-
// aplicada num lado e não no outro, e o sintoma seria "às vezes o foco volta".
|
|
17
|
+
// `sharedDialogOpen` is an ALIAS of `ui/settings-panel.topVisibleOverlay`, not a copy: the two were once the same function
|
|
18
|
+
// written twice, and a scope fix applied to one would not have reached the other.
|
|
31
19
|
//
|
|
32
|
-
// =======================
|
|
20
|
+
// ======================= TWO KNOWN DEFECTS, PINNED BY TESTS — DO NOT FIX ONE WITHOUT THE OTHER =======================
|
|
33
21
|
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
//
|
|
37
|
-
//
|
|
38
|
-
// `menuFocus(sharedDialogOpen())` devolve `null` quando o último diálogo se fecha, `menuFocus` sai pelo guarda
|
|
39
|
-
// de nulo, e o foco fica no botão que acabou de ficar `hidden` — o navegador o joga no `<body>`. Quem sofre são
|
|
40
|
-
// `#typo` (closeTypo) e `#help` (closeHelp), os dois únicos que devolvem o foco por esse caminho.
|
|
22
|
+
// DEFECT 1 — `sharedDialogOpen`'s scope does not see the pause card (WCAG 2.4.3, focus lost). The scope is
|
|
23
|
+
// `#game-region .overlay`; the pause card is `.screen-pause`, never `.overlay`. When the last dialog closes over an open
|
|
24
|
+
// pause card, `menuFocus(sharedDialogOpen())` receives `null`, leaves at its guard, and the focus stays on a button that
|
|
25
|
+
// has just become hidden — the browser drops it on `<body>`.
|
|
41
26
|
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
//
|
|
48
|
-
// · audio → `#opt-sound` — NÃO existe. No-op.
|
|
49
|
-
// · animation → `#opt-animation` — NÃO existe (o próprio game.js já anota isso na linha do `#animation-close`).
|
|
50
|
-
// · empathy → `#opt-empathy` — NÃO existe. No-op.
|
|
51
|
-
// · typo/help → `menuFocus(sharedDialogOpen())` — o caminho quebrado descrito acima.
|
|
52
|
-
// Os ids que EXISTEM no index.html com prefixo `opt-` são: opt-title, opt-telas, opt-letra, opt-facil,
|
|
53
|
-
// opt-altmove, opt-touchcfg, opt-eyes, opt-modocego, opt-tts, opt-onebtn, opt-wheelchair, opt-hearing,
|
|
54
|
-
// opt-captions. Nenhum `#opt-visual`/`#opt-sound`/`#opt-movement`/`#opt-controls`/`#opt-empathy`/`#opt-animation`
|
|
55
|
-
// — são ganchos para uma barra de botões que ainda não existe. Ou seja: NÃO é "sete certos e dois errados". É
|
|
56
|
-
// "um certo e oito quebrados, por duas causas diferentes" — e a causa dos oito (botão inexistente) não é a
|
|
57
|
-
// mesma dos dois (escopo do seletor). Quem consertar precisa saber disso, porque arrumar só o escopo deixa oito
|
|
58
|
-
// painéis ainda largando o foco.
|
|
27
|
+
// DEFECT 2 — Escape is resolved in CAPTURE, by z-index, and consumed with `stopPropagation()`. `menuNavKey` runs before
|
|
28
|
+
// any bubble listener on the window, so the topmost visible dialog is the one that closes, and the registration-order
|
|
29
|
+
// chain (`overlays.escapeTarget()`, read by `input/keydown`) never decides while a dialog is open. Escape is also the same
|
|
30
|
+
// intent as action3 (`no`): at the pause card's root it means «back to the game», and nothing tells «close a dialog» from
|
|
31
|
+
// «leave the pause» apart. The capture and the `stopPropagation()` are also what keep a later window listener — a game's
|
|
32
|
+
// own keydown — from acting on the same Escape: removing them without a replacement lets one key do two things.
|
|
59
33
|
//
|
|
60
|
-
//
|
|
61
|
-
//
|
|
62
|
-
//
|
|
63
|
-
//
|
|
64
|
-
// lendo os dois ouvintes lado a lado:
|
|
65
|
-
// (a) a cadeia `overlays.escapeTarget()` (ORDEM DE REGISTRO: options→movement→animation→visual→empathy→
|
|
66
|
-
// audio→typo→touchcfg→help) é código morto com o jogo pausado. Quem decide é o topo da pilha de z-index.
|
|
67
|
-
// O próprio game.js já anota isso ("MEDIDO no navegador") no ouvinte de bolha.
|
|
68
|
-
// (b) `#help` e `#touchcfg` estão registrados com `inEscapeChain:false` e por isso ficam fora da cadeia de Escape.
|
|
69
|
-
// Enquanto a captura os cobrir por z-index, Escape os fecha; se alguém tirar o `stopPropagation()` ou a
|
|
70
|
-
// fase de captura sem antes dar flag aos dois, a tecla cai no ouvinte de bolha e o
|
|
71
|
-
// `if(Escape||Enter) togglePause()` de lá DESPAUSA O JOGO com o diálogo de Ajuda ainda aberto.
|
|
72
|
-
// Ou seja: o comportamento correto de hoje depende de um `stopPropagation()`, não da cadeia — é uma rede
|
|
73
|
-
// de segurança acidental, e é exatamente o que o conserto vai mexer.
|
|
74
|
-
// (c) Escape é a MESMA intenção que "especial" do gamepad (`no`). Não há como distinguir "fechar diálogo" de
|
|
75
|
-
// "voltar ao jogo" — na raiz do menu de pausa, `no` faz `setPhase('playing')`.
|
|
76
|
-
// `menuNavKey` é exportado por si só (e não só instalado) justamente para o teste poder dispará-lo sem depender
|
|
77
|
-
// de fase de propagação, e `attach()` existe para o game.js instalar exatamente como antes.
|
|
34
|
+
// `tests/menu-nav.browser.test.js` and `tests/menu-nav.node.test.js` pin both as they are today, and fail on purpose if
|
|
35
|
+
// the region is «improved» without the whole fix. `menuNavKey` is exported on its own so a test can fire it without
|
|
36
|
+
// depending on the propagation phase; `attach()` installs it.
|
|
37
|
+
// ======================================================================================================================
|
|
78
38
|
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
81
|
-
//
|
|
39
|
+
// WHAT IS NOT HERE: the overlay stack, the registry and closing by id belong to `ui/settings-panel` — this module
|
|
40
|
+
// consumes `topVisibleOverlay` and `closeById` and reimplements nothing. The quick bar belongs to `ui/pause-icons`; this
|
|
41
|
+
// module only asks whether a screen is on it (`onBar`) and hands it a step (`navBar`).
|
|
82
42
|
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
// · `padKind()` não veio para lugar nenhum: JÁ ESTÁ em `input/touch.ts` (Onda A), com corpo idêntico, e a
|
|
88
|
-
// cópia do game.js não tem chamador nenhum. É código morto duplicado — apagar, não mover.
|
|
89
|
-
// · `navTitle`/`titleButtons` são de ui/activities-menu.ts (menu inicial), já extraídos.
|
|
90
|
-
// · A pilha de z-index, o registro de overlays e o fechar por id são de ui/settings-panel.ts. Este módulo
|
|
91
|
-
// CONSOME (`topVisibleOverlay`, `closeById`) e não reimplementa.
|
|
43
|
+
// INJECTION: nothing is reached. `isNavigable`, `withIndex` and `onBar` are QUESTIONS asked of the ctx rather than state
|
|
44
|
+
// read from `core/state`, so a game answers in its own model — a quiz whose settings are always open answers
|
|
45
|
+
// `isNavigable(): true` without pretending to be paused. `getPauseMenu`, `closePadWiz`, `isCapturing` and `setPhase` are
|
|
46
|
+
// resolved at call time, because what they reach is built in the root after this module.
|
|
92
47
|
//
|
|
93
|
-
//
|
|
94
|
-
// · `phase` entra como binding vivo de core/state.ts (deixou de ser `let` do game.js na Fase 2) — mesmo
|
|
95
|
-
// precedente de game/session.ts e input/touch.ts. O que AINDA é `let` do game.js entra por getter:
|
|
96
|
-
// `vpPause` (`getPauseMenu(i)`), reatribuído por `buildGameHud`.
|
|
97
|
-
// · `closePadWiz` é LAZY (`(save) => gamepadApi.closePadWiz(save)`): input/gamepad.ts é `const` do game.js e o
|
|
98
|
-
// ctx DELE consome `sharedDialogOpen`/`navDialog`/`navPause` daqui. A dependência é mútua, e a única forma
|
|
99
|
-
// de quebrar o ciclo sem reordenar o boot é ela ser resolvida na CHAMADA. Mesmo motivo para `isCapturing`
|
|
100
|
-
// (ui/settings-controls.ts) e para `setPhase` (o envelope içado do game.js sobre ui/shell.ts).
|
|
101
|
-
// · `actionOf`/`whichPlayer` entram já ligados a input/keyboard-runtime.ts, que é `const` declarado bem no
|
|
102
|
-
// alto do game.js — mas entram como callback mesmo assim, porque o roteamento por jogador é dele e não daqui.
|
|
103
|
-
//
|
|
104
|
-
// SEM I/O NO IMPORT: o corpo do módulo só declara dados e funções puras. Todo efeito passa por `initMenuNav`.
|
|
105
|
-
//
|
|
106
|
-
// Ver docs/5-Refactoring/plano-modularizacao-mapa.md (C3).
|
|
48
|
+
// NO I/O ON IMPORT: the module body only declares data and pure functions. Every effect goes through `initMenuNav`.
|
|
107
49
|
// ---------------------------------------------------------------------------------------------------------
|
|
108
|
-
//
|
|
50
|
+
// PURE — the navigation decision, without a DOM. Testable in the `node` project.
|
|
109
51
|
// ---------------------------------------------------------------------------------------------------------
|
|
110
|
-
|
|
111
|
-
o que elas sempre foram semanticamente — pertinência, não ordem — e porque um `Set` nomeado deixa a tabela
|
|
112
|
-
auditável de fora (o teste importa a constante em vez de repetir os literais). Conteúdo VERBATIM. */
|
|
113
|
-
/** Confirma/entra. `NumpadEnter` conta aqui (mas NÃO pausa: ver o ouvinte de bolha do game.js). */
|
|
114
|
-
export const KEY_YES = new Set(['Space', 'KeyJ', 'Enter', 'NumpadEnter']);
|
|
115
|
-
/** Volta. ⚠️ DEFEITO 2: `Escape` é a MESMA intenção que a ação "especial" do gamepad. Verbatim. */
|
|
116
|
-
export const KEY_NO = new Set(['Escape']);
|
|
117
|
-
export const KEY_UP = new Set(['ArrowUp', 'KeyW']);
|
|
118
|
-
export const KEY_DOWN = new Set(['ArrowDown', 'KeyS']);
|
|
119
|
-
export const KEY_LEFT = new Set(['ArrowLeft', 'KeyA']);
|
|
120
|
-
export const KEY_RIGHT = new Set(['ArrowRight', 'KeyD']);
|
|
121
|
-
/**
|
|
122
|
-
* Traduz (tecla física, ação remapeada do dono da tecla) → intenção. `act` é `null` quando a tecla não é de
|
|
123
|
-
* nenhum jogador (tecla genérica). Verbatim das seis linhas de `menuNavKey`.
|
|
124
|
-
*/
|
|
125
|
-
export function menuKeyIntent(code, act) {
|
|
126
|
-
return {
|
|
127
|
-
yes: KEY_YES.has(code) || act === 'action2',
|
|
128
|
-
no: KEY_NO.has(code) || act === 'action3',
|
|
129
|
-
up: KEY_UP.has(code) || act === 'up',
|
|
130
|
-
down: KEY_DOWN.has(code) || act === 'down',
|
|
131
|
-
left: KEY_LEFT.has(code) || act === 'left',
|
|
132
|
-
right: KEY_RIGHT.has(code) || act === 'right',
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
/** Alguma intenção foi expressa? Definição única em input/edges; aqui só o nome que este módulo sempre teve. */
|
|
52
|
+
/** Was any intent expressed? Defined once in input/edges; here under the name this module has always used. */
|
|
136
53
|
import { hasNavIntent as hasIntent } from '../input/edges.js';
|
|
137
|
-
import {
|
|
138
|
-
import {
|
|
139
|
-
import {
|
|
140
|
-
import {
|
|
54
|
+
import { backToRoot, markPauseItem, PM_VISIBLE_ITEMS } from './pause-icons.js';
|
|
55
|
+
import { announceItem } from './item-announcement.js';
|
|
56
|
+
import { accessibleLabel } from '../core/accessible-label.js';
|
|
57
|
+
import { stepInRing } from '../core/ring.js';
|
|
58
|
+
import { navigableItems } from './menu-items.js';
|
|
59
|
+
import { menuKeyIntent, selectStep, selectWrap, rangeStep, stepInPause } from './menu-intent.js';
|
|
141
60
|
export { hasNavIntent as hasIntent } from '../input/edges.js';
|
|
142
|
-
//
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
export function selectStep(selectedIndex, optionsLen, delta) {
|
|
149
|
-
return Math.max(0, Math.min(optionsLen - 1, selectedIndex + delta));
|
|
150
|
-
}
|
|
151
|
-
/** `select` com "sim": um passo, COM volta. É a diferença deliberada entre confirmar e ajustar. */
|
|
152
|
-
export function selectWrap(selectedIndex, optionsLen) {
|
|
153
|
-
return (selectedIndex + 1) % optionsLen;
|
|
154
|
-
}
|
|
155
|
-
/** `input[type=range]` com esquerda/direita: um `step` (default 1), preso entre `min` e `max`. */
|
|
156
|
-
export function rangeStep(value, min, max, step, delta) {
|
|
157
|
-
const st = step || 1; // `+cur.step||1`: step ausente/0/NaN vira 1, verbatim
|
|
158
|
-
return Math.max(min, Math.min(max, value + delta * st));
|
|
159
|
-
}
|
|
61
|
+
// The ring's arithmetic lives in `core/ring`: `ui/pause-icons` needs it too, and this module imports that one, so keeping
|
|
62
|
+
// it here would close an import cycle — which in ESM does not fail on the spot, it fails at boot in the TDZ. The public
|
|
63
|
+
// name stays here because the menus and the tests import it from here.
|
|
64
|
+
export { stepInRing } from '../core/ring.js';
|
|
65
|
+
// The items' selector lives in `ui/menu-items` (ADR-0158): what counts as an item is defined once, and the spoken
|
|
66
|
+
// «N of M» and the panel shell read the same list.
|
|
160
67
|
/**
|
|
161
|
-
*
|
|
162
|
-
*
|
|
163
|
-
* ISTO SUBSTITUI `pauseGridMove`, e a substituição é o desfecho do ADR-0044, não uma limpeza. O que havia
|
|
164
|
-
* era uma GRADE de duas zonas — a barra de dez ícones em cima, a lista de itens em duas colunas embaixo — e
|
|
165
|
-
* uma fronteira entre elas com quatro regras próprias ("de cima, 'baixo' cai sempre no primeiro item", "da
|
|
166
|
-
* primeira linha, 'cima' sobe para o ícone de mesmo índice", …). Cada regra dessas era uma coisa a mais para
|
|
167
|
-
* a criança descobrir sem ver, e nenhuma delas era descobrível: só se aprendia esbarrando.
|
|
68
|
+
* The parts a panel control SAYS (ADR-0159 rule 1, XAG 106: «Gamma, slider, 38%, 6 of 9»): the label with the ROLE, and
|
|
69
|
+
* the VALUE. The index is added by `announceItem`.
|
|
168
70
|
*
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
-
* item 1 valia para todo menu do jogo MENOS este. Com a barra no HUD (item 7), o cartão passou a ter uma
|
|
172
|
-
* lista só, e o laço deixou de ser proibido para virar o recomendado.
|
|
173
|
-
*
|
|
174
|
-
* O que se ganha em troca das quatro regras: `quit` fica a UMA tecla para CIMA de `resume`. Último na
|
|
175
|
-
* leitura, vizinho no dedo.
|
|
71
|
+
* The label is the row's `<strong>` when the control lives in one (it is what is SEEN, and a switch's text is its state,
|
|
72
|
+
* not its name); outside a row, or on a slider with a name of its own («Volume de Música»), it is the accessible name.
|
|
176
73
|
*/
|
|
177
|
-
export function
|
|
178
|
-
const
|
|
179
|
-
|
|
74
|
+
export function controlParts(t, el) {
|
|
75
|
+
const row = el.closest('.ctrl-row')?.querySelector('strong')?.textContent?.trim() || '';
|
|
76
|
+
const [, role, read] = CONTROL_KINDS.find(([recognises]) => recognises(el));
|
|
77
|
+
const { label, value } = read(el, row, accessibleLabel(el), t);
|
|
78
|
+
return { label: `${label}, ${t(role)}`, state: value };
|
|
79
|
+
}
|
|
80
|
+
/** A slider's position as a whole percentage: a missing maximum reads as 100, and a range of zero width as 0. */
|
|
81
|
+
function sliderPercent(r) {
|
|
82
|
+
const min = +r.min || 0, max = +r.max || 100;
|
|
83
|
+
const pct = max > min ? Math.round(((+r.value - min) / (max - min)) * 100) : 0;
|
|
84
|
+
return `${pct}%`;
|
|
180
85
|
}
|
|
181
|
-
/**
|
|
182
|
-
|
|
183
|
-
|
|
86
|
+
/**
|
|
87
|
+
* HOW EACH KIND OF CONTROL IS RECOGNISED AND READ, and the ORDER is the rule: the first row that recognises the element reads
|
|
88
|
+
* it, and the last — a button — recognises everything. A stepper and a slider prefer their own name (a slider can be «Music
|
|
89
|
+
* volume» on a row called «Sound»); the others prefer the row's label, because the text of a switch is its state, not its name.
|
|
90
|
+
*/
|
|
91
|
+
const CONTROL_KINDS = [
|
|
92
|
+
[(el) => el.hasAttribute('data-passos'), 'sr.papel.passos',
|
|
93
|
+
(el, row) => ({ label: el.getAttribute('aria-label') || row, value: el.getAttribute('aria-valuetext') ?? '' })],
|
|
94
|
+
[(el) => el.tagName === 'SELECT', 'sr.papel.lista',
|
|
95
|
+
(el, row, name) => ({ label: row || name, value: el.selectedOptions?.[0]?.textContent?.trim() ?? '' })],
|
|
96
|
+
[(el) => el.tagName === 'INPUT', 'sr.papel.cursor',
|
|
97
|
+
(el, row) => ({ label: el.getAttribute('aria-label') || row, value: sliderPercent(el) })],
|
|
98
|
+
[(el) => el.hasAttribute('aria-pressed'), 'sr.papel.interruptor',
|
|
99
|
+
(el, row, name, t) => ({ label: row || name, value: t(el.getAttribute('aria-pressed') === 'true' ? 'state.on' : 'state.off') })],
|
|
100
|
+
[(el) => el.getAttribute('role') === 'radio', 'sr.papel.opcao',
|
|
101
|
+
(el, row, name, t) => ({ label: row || name, value: el.getAttribute('aria-checked') === 'true' ? t('sr.estado.selecionado') : '' })],
|
|
102
|
+
[() => true, 'sr.papel.botao', (_el, _row, name) => ({ label: name, value: '' })],
|
|
103
|
+
];
|
|
104
|
+
/** Where the items live: the dialog's card (`.overlay__card`) or the pause card (`.pause-card`). */
|
|
184
105
|
const CARD_SELECTOR = '.overlay__card, .pause-card';
|
|
106
|
+
/**
|
|
107
|
+
* WHAT LEFT AND RIGHT DO TO A PANEL'S CONTROL — ONE answer, read by the keys (`sideways`, `confirm`) and by one-button scanning
|
|
108
|
+
* (`underCursor`), so a scan never offers «increase» where the right key would walk the ring instead: a `<select>` is a list, a
|
|
109
|
+
* slider (the only `<input>` an item can be, `ui/menu-items`) or a ⯇ ⯈ steps control (ADR-0151) is a value, anything else
|
|
110
|
+
* is an item.
|
|
111
|
+
*/
|
|
112
|
+
function cursorKind(el) {
|
|
113
|
+
if (el.tagName === 'SELECT')
|
|
114
|
+
return 'list';
|
|
115
|
+
return el.tagName === 'INPUT' || el.hasAttribute('data-passos') ? 'value' : 'item';
|
|
116
|
+
}
|
|
185
117
|
export function initMenuNav(ctx) {
|
|
186
|
-
|
|
187
|
-
|
|
118
|
+
const { t } = ctx;
|
|
119
|
+
/* ===================== settings dialogs ===================== */
|
|
120
|
+
// An ALIAS, not a copy: the body lives in ui/settings-panel.ts (see the header).
|
|
188
121
|
const sharedDialogOpen = () => ctx.topVisibleOverlay();
|
|
189
122
|
function menuItems(menu) {
|
|
190
123
|
const card = menu.querySelector(CARD_SELECTOR) || menu;
|
|
191
|
-
//
|
|
192
|
-
|
|
193
|
-
// leitor de tela anuncia um controle que ninguém vê. Verbatim.
|
|
194
|
-
return [...card.querySelectorAll(ITEM_SELECTOR)].filter((el) => el.offsetParent !== null);
|
|
124
|
+
// The VISIBILITY filter (`offsetParent`) lives with the selector in `ui/menu-items`.
|
|
125
|
+
return navigableItems(card);
|
|
195
126
|
}
|
|
196
127
|
function menuFocus(menu) {
|
|
197
128
|
if (!menu)
|
|
198
|
-
return; // ⚠️
|
|
129
|
+
return; // ⚠️ DEFECT 1: with the pause card outside the scope, this guard is the end of the line
|
|
199
130
|
const it = menuItems(menu);
|
|
200
131
|
if (!it.length)
|
|
201
132
|
return;
|
|
@@ -204,187 +135,368 @@ export function initMenuNav(ctx) {
|
|
|
204
135
|
}
|
|
205
136
|
function dialogBack(menu) {
|
|
206
137
|
if (!ctx.closeById(menu.id))
|
|
207
|
-
menu.hidden = true; //
|
|
138
|
+
menu.hidden = true; // no registered entry: hide it outright
|
|
208
139
|
menuFocus(sharedDialogOpen());
|
|
209
140
|
}
|
|
210
|
-
/**
|
|
141
|
+
/** Adjusts a `select` (left/right = a step that stops at the ends; yes = a step that wraps) and fires `change`. */
|
|
211
142
|
function tweakSelect(el, delta) {
|
|
212
143
|
el.selectedIndex = delta === 'wrap' ? selectWrap(el.selectedIndex, el.options.length) : selectStep(el.selectedIndex, el.options.length, delta);
|
|
213
144
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
214
145
|
}
|
|
215
|
-
/**
|
|
146
|
+
/** Moves an `input[type=range]` by one `step` and fires `input` (the event the panels listen to). */
|
|
216
147
|
function tweakRange(el, delta) {
|
|
217
148
|
el.value = String(rangeStep(+el.value, +el.min, +el.max, +el.step, delta));
|
|
218
149
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
219
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* THE ITEM REACHED SAYS ITSELF (ADR-0159 rule 1): label, role, value and «N de M», in that order.
|
|
153
|
+
*
|
|
154
|
+
* The browser's focus moves by itself, but the engine's narration (the one a child playing in blind mode hears) does
|
|
155
|
+
* not listen to focus; it listens to this.
|
|
156
|
+
*/
|
|
157
|
+
function sayItem(items, n) {
|
|
158
|
+
const el = items[n];
|
|
159
|
+
if (!el)
|
|
160
|
+
return;
|
|
161
|
+
const { label: partLabel, state: partState } = controlParts(t, el);
|
|
162
|
+
ctx.srSay(announceItem(t, { label: partLabel, state: partState, position: n + 1, total: items.length }, ctx.withIndex()));
|
|
163
|
+
}
|
|
164
|
+
function focusAndSay(items, n) {
|
|
165
|
+
items[n]?.focus();
|
|
166
|
+
sayItem(items, n);
|
|
167
|
+
}
|
|
168
|
+
/** Where the cursor is. With the focus outside the dialog (or on its card), it enters by the first item — and says it. */
|
|
169
|
+
function cursorIn(items) {
|
|
170
|
+
const idx = items.indexOf(ctx.getActiveElement());
|
|
171
|
+
if (idx >= 0)
|
|
172
|
+
return idx;
|
|
173
|
+
focusAndSay(items, 0);
|
|
174
|
+
return 0;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Left or right: a list, a slider or a steps control is ADJUSTED, and the new value is said — someone adjusting by ear has
|
|
178
|
+
* no other way to know where it stopped; anything else walks the ring, like up and down.
|
|
179
|
+
*/
|
|
180
|
+
function sideways(items, idx, d) {
|
|
181
|
+
const cur = items[idx];
|
|
182
|
+
if (cursorKind(cur) === 'item') {
|
|
183
|
+
focusAndSay(items, stepInRing(items.length, idx, d));
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
if (cur.tagName === 'SELECT') {
|
|
187
|
+
const before = cur.value;
|
|
188
|
+
tweakSelect(cur, d);
|
|
189
|
+
// an adjustment its owner refused (and put back) says nothing here: the owner already said why
|
|
190
|
+
if (cur.value !== before)
|
|
191
|
+
sayItem(items, idx);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if (cur.tagName === 'INPUT') {
|
|
195
|
+
tweakRange(cur, d);
|
|
196
|
+
sayItem(items, idx);
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
// A steps control ⯇ ⯈ (ADR-0151): left and right are the adjustment itself; its owner listens to `passo` (and announces).
|
|
200
|
+
cur.dispatchEvent(new CustomEvent('passo', { detail: d, bubbles: true }));
|
|
201
|
+
}
|
|
202
|
+
/** «Yes»: a list goes round and says the new option; a slider or a steps control has nothing to confirm; anything else is clicked. */
|
|
203
|
+
function confirm(items, idx) {
|
|
204
|
+
const cur = items[idx];
|
|
205
|
+
const kind = cursorKind(cur);
|
|
206
|
+
if (kind === 'list') {
|
|
207
|
+
tweakSelect(cur, 'wrap');
|
|
208
|
+
sayItem(items, idx);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (kind === 'item')
|
|
212
|
+
cur.click();
|
|
213
|
+
}
|
|
220
214
|
function navDialog(menu, k) {
|
|
221
215
|
const items = menuItems(menu);
|
|
222
216
|
if (!items.length)
|
|
223
217
|
return;
|
|
224
|
-
|
|
225
|
-
if (idx < 0) {
|
|
226
|
-
idx = 0;
|
|
227
|
-
items[0].focus();
|
|
228
|
-
} // foco fora do diálogo (ou no card): entra pelo primeiro
|
|
229
|
-
const cur = items[idx];
|
|
218
|
+
const idx = cursorIn(items);
|
|
230
219
|
if (k.no) {
|
|
231
220
|
dialogBack(menu);
|
|
232
221
|
return;
|
|
233
222
|
}
|
|
234
223
|
if (k.left || k.right) {
|
|
235
|
-
|
|
236
|
-
if (cur.tagName === 'SELECT') {
|
|
237
|
-
tweakSelect(cur, d);
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
if (cur.tagName === 'INPUT') {
|
|
241
|
-
tweakRange(cur, d);
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
items[passoNoAnel(items.length, idx, d)].focus();
|
|
224
|
+
sideways(items, idx, k.right ? 1 : -1);
|
|
245
225
|
return;
|
|
246
226
|
}
|
|
247
227
|
if (k.up || k.down) {
|
|
248
|
-
items
|
|
228
|
+
focusAndSay(items, stepInRing(items.length, idx, k.down ? 1 : -1));
|
|
249
229
|
return;
|
|
250
230
|
}
|
|
251
|
-
if (k.yes)
|
|
252
|
-
|
|
253
|
-
tweakSelect(cur, 'wrap');
|
|
254
|
-
return;
|
|
255
|
-
}
|
|
256
|
-
if (cur.tagName === 'INPUT')
|
|
257
|
-
return; // slider não tem "confirmar" — só ajuste
|
|
258
|
-
cur.click();
|
|
259
|
-
}
|
|
231
|
+
if (k.yes)
|
|
232
|
+
confirm(items, idx);
|
|
260
233
|
}
|
|
261
|
-
/* =====================
|
|
234
|
+
/* ===================== pause card (selected by class, not by focus) ===================== */
|
|
262
235
|
/**
|
|
263
|
-
*
|
|
264
|
-
* cursor
|
|
265
|
-
*
|
|
266
|
-
* O QUE SAIU DAQUI, e vale registrar: esta função também escrevia a legenda dos ícones, porque o cursor
|
|
267
|
-
* atravessava a fronteira entre as duas zonas. Sem a fronteira, escrever legenda de ícone a partir do menu
|
|
268
|
-
* de pausa seria um módulo mexendo na tela de outro.
|
|
236
|
+
* Moves the pause card's cursor. Items only: since ADR-0044 item 7 the quick bar lives in the HUD and keeps its own
|
|
237
|
+
* cursor — writing the bar's caption from here would be one module drawing on another's screen.
|
|
269
238
|
*/
|
|
270
239
|
function pauseSetSel(menu, el) {
|
|
271
240
|
if (!el)
|
|
272
|
-
return; //
|
|
273
|
-
menu
|
|
274
|
-
el.classList.add('pm-sel');
|
|
241
|
+
return; // an index outside the list (an empty card): touch nothing
|
|
242
|
+
markPauseItem(menu, el); // the one place the card's cursor moves, and is kept in view (issue #134)
|
|
275
243
|
}
|
|
276
244
|
function navPause(menu, playerIndex, k) {
|
|
277
245
|
if (k.no) {
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
//
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
246
|
+
// «No» inside any sub-list goes up ONE level, to the root, and not back to the game (ADR-0044 item 5) — the same
|
|
247
|
+
// «back leaves one level» the settings dialogs have (`dialogBack`). The question is «is this NOT the root?», not
|
|
248
|
+
// «is this the options list?»: with three lists (ADR-0146), asking the second would make «no» from the game's own
|
|
249
|
+
// options resume the game when the child asked to go back.
|
|
250
|
+
const open = menu.querySelector('.pause-menu:not([hidden])');
|
|
251
|
+
// ⚠️ AND THE CURSOR LANDS ON THE DOOR THAT OPENED THE LIST (ADR-0130 rule 1), not on the root's first item.
|
|
252
|
+
if (open && open.dataset.sub && open.dataset.sub !== 'raiz') {
|
|
253
|
+
backToRoot(menu);
|
|
284
254
|
return;
|
|
285
255
|
}
|
|
286
256
|
ctx.setPhase('playing');
|
|
287
|
-
return; //
|
|
257
|
+
return; // «no» at the root → back to the game (resumes everyone)
|
|
288
258
|
}
|
|
289
|
-
const items = [...menu.querySelectorAll(
|
|
259
|
+
const items = [...menu.querySelectorAll(PM_VISIBLE_ITEMS)];
|
|
290
260
|
const cur = menu.querySelector('.pm-sel') || items[0];
|
|
291
|
-
//
|
|
261
|
+
// «Yes»: the player who acted becomes the `pauseActor` (the accessibility options open on their tab) and the item is clicked.
|
|
292
262
|
if (k.yes) {
|
|
293
263
|
ctx.setPauseActor(playerIndex);
|
|
294
264
|
if (cur)
|
|
295
265
|
cur.click();
|
|
296
266
|
return;
|
|
297
267
|
}
|
|
298
|
-
//
|
|
299
|
-
// preservado — é um TypeError, e trocar um crash por um no-op não tira rede de conserto nenhum.
|
|
268
|
+
// A card with no item at all: nothing to move (without this guard the next line throws on `cur`).
|
|
300
269
|
if (!cur)
|
|
301
270
|
return;
|
|
302
|
-
//
|
|
303
|
-
|
|
304
|
-
|
|
271
|
+
// ONE list, one ring: the quick bar is not in the card (ADR-0044 item 7), so there are no borders between zones to cross.
|
|
272
|
+
const n = stepInPause(items.length, items.indexOf(cur), k);
|
|
273
|
+
selectAndSayInPause(menu, items, n);
|
|
274
|
+
}
|
|
275
|
+
function selectAndSayInPause(menu, items, n) {
|
|
305
276
|
pauseSetSel(menu, items[n]);
|
|
306
|
-
//
|
|
307
|
-
//
|
|
308
|
-
//
|
|
309
|
-
//
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
|
|
277
|
+
// AND THE NEW ITEM IS SPOKEN. This card selects by class, not by browser focus — it is drawn inside the player's
|
|
278
|
+
// screen — so nothing announces it by itself: no focus, no `aria-activedescendant`, no live region. ADR-0044 item 3
|
|
279
|
+
// asks for position and total everywhere.
|
|
280
|
+
// `accessibleLabel` and not `textContent`: an item with an `aria-label` would otherwise be narrated one way by the
|
|
281
|
+
// game and another by the screen reader, and whoever hears both could not tell which is true.
|
|
282
|
+
// 🔴 A LOCKED ITEM SAYS WHY when it is reached (ADR-0161): right after its name, and written in the footer.
|
|
283
|
+
const reason = items[n].getAttribute('aria-disabled') === 'true' ? (items[n].dataset.motivo ?? '') : '';
|
|
284
|
+
const announcement = announceItem(t, { label: accessibleLabel(items[n]), position: n + 1, total: items.length }, ctx.withIndex());
|
|
285
|
+
ctx.srSay(reason ? `${announcement}. ${reason}` : announcement);
|
|
286
|
+
ctx.explainItem?.(reason || null);
|
|
314
287
|
}
|
|
315
|
-
/* =====================
|
|
316
|
-
|
|
288
|
+
/* ===================== keyboard ===================== */
|
|
289
|
+
const padWizOpen = () => { const pw = ctx.$('#padwiz'); return !!pw && !pw.hidden; };
|
|
290
|
+
/** The controller-mapping panel sits ON TOP of everything: while it is open, only Escape gets through (and cancels). */
|
|
317
291
|
function padWizKey(e) {
|
|
318
|
-
|
|
319
|
-
if (!pw || pw.hidden)
|
|
292
|
+
if (!padWizOpen())
|
|
320
293
|
return false;
|
|
321
294
|
if (e.code === 'Escape') {
|
|
322
295
|
ctx.closePadWiz(false);
|
|
323
|
-
e
|
|
324
|
-
e.stopPropagation();
|
|
296
|
+
consume(e);
|
|
325
297
|
}
|
|
326
|
-
return true; //
|
|
298
|
+
return true; // open = consumed (even when it is not Escape: no menu underneath navigates while it is open)
|
|
327
299
|
}
|
|
328
|
-
/**
|
|
329
|
-
const
|
|
300
|
+
/** The key events the menus consumed, for `consumed` — weak, so an event is forgotten with it. */
|
|
301
|
+
const consumedKeys = new WeakSet();
|
|
302
|
+
/** Consumes the key: it was ours, and nobody else should see it. One function, so the three never come apart. */
|
|
303
|
+
const consume = (e) => { e.preventDefault(); e.stopPropagation(); consumedKeys.add(e); };
|
|
330
304
|
function menuNavKey(e) {
|
|
331
305
|
if (ctx.isCapturing())
|
|
332
|
-
return; // remap
|
|
306
|
+
return; // a remap in progress: the key is its
|
|
333
307
|
if (padWizKey(e))
|
|
334
308
|
return;
|
|
335
|
-
// =====================
|
|
336
|
-
// `isNavigable()`
|
|
337
|
-
//
|
|
338
|
-
//
|
|
309
|
+
// ===================== THE QUICK BAR COMES BEFORE THE «NAVIGABLE» GUARD =====================
|
|
310
|
+
// `isNavigable()` can be false while the game runs (a platformer answers `phase === 'paused'`), and the bar works
|
|
311
|
+
// with the game RUNNING — that is what it is for: adjusting accessibility DURING play. After the guard, the
|
|
312
|
+
// direction would fall to the character and the bar would do nothing, silently.
|
|
339
313
|
//
|
|
340
|
-
//
|
|
341
|
-
// `input/gamepad`;
|
|
342
|
-
//
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
if (
|
|
346
|
-
const kb = menuKeyIntent(e.code, donoTecla >= 0 ? ctx.actionOf(e.code, pTecla) : null);
|
|
347
|
-
if (hasIntent(kb)) {
|
|
348
|
-
consumir(e);
|
|
349
|
-
ctx.navBar(pTecla, kb);
|
|
350
|
-
}
|
|
351
|
-
return; // na barra, tecla de menu é da barra — com ou sem intenção, não desce para o personagem
|
|
352
|
-
}
|
|
353
|
-
if (!ctx.isNavigable())
|
|
314
|
+
// On the KEYBOARD the way out is Escape (the project's `no`). A controller's START is the second way out and comes
|
|
315
|
+
// through `input/gamepad`; the keyboard has no pair of its own for it, because Enter is already «confirm» and taking
|
|
316
|
+
// it would leave the child no way to ACTIVATE the icon under the cursor.
|
|
317
|
+
const { player, keys } = intentOf(e.code);
|
|
318
|
+
// on the bar, a key with no intent is not consumed either — it simply moves nothing
|
|
319
|
+
if (!hasIntent(keys) || !menusTake(player))
|
|
354
320
|
return;
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
321
|
+
// THE KEY IS CONSUMED BEFORE THE MENU MOVES: a step that closes the last menu leaves no menu for a later listener to see
|
|
322
|
+
consume(e);
|
|
323
|
+
steerMenus(player, keys);
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Would a key of player `pi` with an intent move a menu now? The quick bar first — it works with the game RUNNING — then the
|
|
327
|
+
* dialog on top or that player's open pause card, and only where a menu may be navigated.
|
|
328
|
+
*
|
|
329
|
+
* 📌 THE KEY IS CONSUMED ONLY IF THERE IS SOMETHING TO NAVIGATE. A game whose settings are always open answers
|
|
330
|
+
* `isNavigable(): true`, and consuming before knowing would swallow every key with a menu intent — the arrows that choose
|
|
331
|
+
* an answer in the quiz, for one. DEFECT 2's safety net stays: with a dialog open the key IS consumed, so one Escape closes
|
|
332
|
+
* the dialog and no later listener sees it.
|
|
333
|
+
*/
|
|
334
|
+
function menusTake(pi) {
|
|
335
|
+
return ctx.onBar(pi) || (ctx.isNavigable() && !!menuUnderKeys(pi));
|
|
336
|
+
}
|
|
337
|
+
/** One step of the menu a key of `pi` moves: the bar, else the dialog on top, else that player's own pause card. */
|
|
338
|
+
function steerMenus(pi, k) {
|
|
339
|
+
if (ctx.onBar(pi)) {
|
|
340
|
+
ctx.navBar(pi, k);
|
|
359
341
|
return;
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
342
|
+
}
|
|
343
|
+
const open = menuUnderKeys(pi);
|
|
344
|
+
if (open?.inPause)
|
|
345
|
+
navPause(open.menu, pi, k);
|
|
346
|
+
else if (open)
|
|
347
|
+
navDialog(open.menu, k);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* AN INTENT WITH NO KEY (ADR-0218 erratum of 2026-09-26): one-button scanning offers a menu's own steps and takes one with a
|
|
351
|
+
* press that is not a menu key — so it asks here, and the step goes the way a key's would, under the same guards.
|
|
352
|
+
* The controller-mapping panel on top takes only «back», which cancels it, as Escape does.
|
|
353
|
+
*/
|
|
354
|
+
function navIntent(pi, k) {
|
|
355
|
+
if (ctx.isCapturing())
|
|
356
|
+
return false;
|
|
357
|
+
if (padWizOpen()) {
|
|
358
|
+
if (k.no)
|
|
359
|
+
ctx.closePadWiz(false);
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
if (!hasIntent(k) || !menusTake(pi))
|
|
363
|
+
return false;
|
|
364
|
+
steerMenus(pi, k);
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
/** Whose key this is — a key no player owns is Player 1's — and what it asks of a menu. */
|
|
368
|
+
function intentOf(code) {
|
|
369
|
+
const owner = ctx.whichPlayer(code);
|
|
370
|
+
const player = owner < 0 ? 0 : owner;
|
|
371
|
+
return { player, keys: menuKeyIntent(code, owner >= 0 ? ctx.actionOf(code, player) : null) };
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* The menu a key of player `pi` moves: the dialog on top if there is one, else that player's OWN open pause card — and none for
|
|
375
|
+
* a player who does not lead this screen (ADR-0144 erratum): the dialog on top is the screen's, not every seat's.
|
|
376
|
+
*/
|
|
377
|
+
function menuUnderKeys(pi) {
|
|
378
|
+
if (ctx.leadsTheScreen && !ctx.leadsTheScreen(pi))
|
|
379
|
+
return null;
|
|
380
|
+
const dlg = sharedDialogOpen();
|
|
381
|
+
if (dlg)
|
|
382
|
+
return { menu: dlg, inPause: false };
|
|
383
|
+
const menu = ctx.getPauseMenu(pi);
|
|
384
|
+
return menu && !menu.hidden ? { menu, inPause: true } : null;
|
|
385
|
+
}
|
|
386
|
+
/* ===================== an item said by name (ADR-0194) ===================== */
|
|
387
|
+
/**
|
|
388
|
+
* The stops of the menu a key of `pi` would move NOW, under the same guards `menuNavKey` applies before moving one: a name is
|
|
389
|
+
* sayable exactly where confirming would reach it, so the confirm that follows `pointAt` lands on the named item.
|
|
390
|
+
*
|
|
391
|
+
* 🔴 LOCKED STOPS INCLUDED (ADR-0194 §5, ADR-0161): the cursor stops on a locked item, so its name is sayable too — and what
|
|
392
|
+
* saying it does is what confirming it does, because it IS a confirm: the item's own press says its reason and acts on nothing
|
|
393
|
+
* (`ui/pause-icons.pressPauseItem`, a panel's refusal). No second, voice-only «say the reason» exists to drift from that one.
|
|
394
|
+
*/
|
|
395
|
+
function sayableItems(pi) {
|
|
396
|
+
if (ctx.isCapturing() || padWizOpen() || ctx.onBar(pi) || !ctx.isNavigable())
|
|
397
|
+
return null;
|
|
398
|
+
const open = menuUnderKeys(pi);
|
|
399
|
+
if (!open)
|
|
400
|
+
return null;
|
|
401
|
+
return { ...open, items: open.inPause ? [...open.menu.querySelectorAll(PM_VISIBLE_ITEMS)] : menuItems(open.menu) };
|
|
402
|
+
}
|
|
403
|
+
function itemNames(pi) {
|
|
404
|
+
return (sayableItems(pi)?.items ?? []).map((el) => accessibleLabel(el)).filter(Boolean);
|
|
405
|
+
}
|
|
406
|
+
/** A panel's control under the cursor — where `navDialog` would put a step: the focused item, else the first (`cursorIn`). */
|
|
407
|
+
function underCursor(pi) {
|
|
408
|
+
const open = sayableItems(pi);
|
|
409
|
+
if (!open || open.inPause)
|
|
410
|
+
return 'item';
|
|
411
|
+
const at = open.items.indexOf(ctx.getActiveElement());
|
|
412
|
+
const el = open.items[at >= 0 ? at : 0];
|
|
413
|
+
return el ? cursorKind(el) : 'item';
|
|
414
|
+
}
|
|
415
|
+
function pointAt(name, pi) {
|
|
416
|
+
const open = sayableItems(pi);
|
|
417
|
+
const el = open?.items.find((it) => accessibleLabel(it) === name);
|
|
418
|
+
if (!open || !el)
|
|
419
|
+
return false;
|
|
420
|
+
if (open.inPause) {
|
|
421
|
+
pauseSetSel(open.menu, el);
|
|
422
|
+
ctx.explainItem?.(null);
|
|
423
|
+
}
|
|
424
|
+
else
|
|
425
|
+
el.focus();
|
|
426
|
+
return true;
|
|
427
|
+
}
|
|
428
|
+
/* ===================== holding on an item ===================== */
|
|
429
|
+
/*
|
|
430
|
+
* A PRESS HELD ON AN ITEM PLACES THE CURSOR THERE AND SAYS IT, WITHOUT ACTIVATING IT (ADR-0159 rule 2, erratum of
|
|
431
|
+
* 2026-09-13). «Deixar o dedo apertado ou o botão do mouse apertado sobre o item deve dar a função de posicionar o
|
|
432
|
+
* cursor sem "apertar".» (Dev) — the pad left the menus (ADR-0166), and a pointer had one meaning only: activation.
|
|
433
|
+
* 📌 A short press is untouched; after a hold, the ONE click the browser fires on release is swallowed, in capture,
|
|
434
|
+
* before the item's own listener. Threshold: the iOS long-press default, 0.5 s.
|
|
435
|
+
*/
|
|
436
|
+
const HOLD_MS = 500;
|
|
437
|
+
let holding = null;
|
|
438
|
+
let clickToSwallow = null;
|
|
439
|
+
/** The open menu and the item under `target`, when `target` is inside one: the top dialog first, else a pause card. */
|
|
440
|
+
function itemUnder(target) {
|
|
441
|
+
const no = target;
|
|
442
|
+
if (!no || typeof no.closest !== 'function')
|
|
443
|
+
return null;
|
|
373
444
|
const dlg = sharedDialogOpen();
|
|
374
445
|
if (dlg) {
|
|
375
|
-
|
|
376
|
-
|
|
446
|
+
const items = menuItems(dlg);
|
|
447
|
+
const n = items.findIndex((el) => el.contains(no));
|
|
448
|
+
return n >= 0 ? { menu: dlg, items, n, inPause: false } : null;
|
|
449
|
+
}
|
|
450
|
+
const menu = no.closest('.screen-pause');
|
|
451
|
+
if (!menu || menu.hidden)
|
|
452
|
+
return null;
|
|
453
|
+
const items = [...menu.querySelectorAll(PM_VISIBLE_ITEMS)];
|
|
454
|
+
const n = items.findIndex((el) => el.contains(no));
|
|
455
|
+
return n >= 0 ? { menu, items, n, inPause: true } : null;
|
|
456
|
+
}
|
|
457
|
+
function release() {
|
|
458
|
+
if (holding)
|
|
459
|
+
clearTimeout(holding.timer);
|
|
460
|
+
holding = null;
|
|
461
|
+
}
|
|
462
|
+
function onPress(e) {
|
|
463
|
+
release();
|
|
464
|
+
clickToSwallow = null;
|
|
465
|
+
const under = itemUnder(e.target);
|
|
466
|
+
if (!under)
|
|
377
467
|
return;
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
468
|
+
const id = e.pointerId;
|
|
469
|
+
holding = {
|
|
470
|
+
id,
|
|
471
|
+
timer: setTimeout(() => {
|
|
472
|
+
holding = null;
|
|
473
|
+
clickToSwallow = under.items[under.n] ?? null;
|
|
474
|
+
if (under.inPause)
|
|
475
|
+
selectAndSayInPause(under.menu, under.items, under.n);
|
|
476
|
+
else
|
|
477
|
+
focusAndSay(under.items, under.n);
|
|
478
|
+
}, HOLD_MS),
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
function onRelease(e) {
|
|
482
|
+
if (holding && holding.id === e.pointerId)
|
|
483
|
+
release();
|
|
484
|
+
}
|
|
485
|
+
function onClick(e) {
|
|
486
|
+
const swallowed = clickToSwallow;
|
|
487
|
+
clickToSwallow = null;
|
|
488
|
+
if (swallowed && e.target instanceof Node && swallowed.contains(e.target)) {
|
|
489
|
+
e.preventDefault();
|
|
490
|
+
e.stopPropagation();
|
|
383
491
|
}
|
|
384
|
-
// Sem diálogo e sem menu: a tecla NÃO é nossa. Segue o caminho dela até quem for o dono.
|
|
385
492
|
}
|
|
386
493
|
function attach() {
|
|
387
494
|
ctx.win.addEventListener('keydown', menuNavKey, true);
|
|
495
|
+
ctx.win.addEventListener('pointerdown', onPress, true);
|
|
496
|
+
ctx.win.addEventListener('pointerup', onRelease, true);
|
|
497
|
+
ctx.win.addEventListener('pointercancel', onRelease, true);
|
|
498
|
+
ctx.win.addEventListener('click', onClick, true);
|
|
388
499
|
}
|
|
389
|
-
|
|
500
|
+
const consumed = (e) => consumedKeys.has(e);
|
|
501
|
+
return { sharedDialogOpen, menuItems, menuFocus, dialogBack, navDialog, pauseSetSel, navPause, menuNavKey, consumed, navIntent, underCursor, itemNames, pointAt, attach };
|
|
390
502
|
}
|