@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
|
@@ -1,137 +1,213 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
-
// ui/settings-audio —
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
// live in this overlay, the state does not). Overlay open/close plumbing (#audio hidden toggle, frontOverlay,
|
|
12
|
-
// focus management, Escape, `ensureAC()`) is the shared infra every settings panel uses and stays in game.js,
|
|
13
|
-
// which calls `renderAudio()` from its `openAudio()`. `reflectModoCego`/`reflectTts` are also exported because
|
|
14
|
-
// game.js's own `setModoCego()` and the pause-menu icon bar (`iconAct('tts'|'blind', …)`) call them directly.
|
|
2
|
+
// ui/settings-audio — the hearing accessibility and audio panels (#audio overlay): the thin DOM-touching render/wire
|
|
3
|
+
// functions; what a choice IS lives in ./audio-choices.js and the voice section in ./voice-settings.js. DI via
|
|
4
|
+
// initSettingsAudio(ctx): `$`, `srSay`, `store` (narrow get/set), the browser through three required ports
|
|
5
|
+
// (`newElement`, `speech`, `audioOutputs`), the live sound/mixer primitives from platform/audio.ts
|
|
6
|
+
// (getSoundOn/setSoundOn/getVolume/setVolume/getAudioCat/setCatGain), the injected `tts` panel API (platform/tts.ts), and
|
|
7
|
+
// the host's shared helpers (`toggleBtn`, `getNumPlayers`/`getPlayers`) plus state that lives outside audio
|
|
8
|
+
// (`getBlindMode`/`setBlindMode`, `getCaneBlockDiv`/`setCaneBlockDiv` — the widgets live in this overlay, the state does
|
|
9
|
+
// not). Overlay open/close plumbing (frontOverlay, focus management, Escape) is the shared infrastructure every settings
|
|
10
|
+
// panel uses. `reflectBlindMode`/`reflectTts` are exported because the pause-menu icon bar calls them directly.
|
|
15
11
|
/** Minimal DOM-selector shape (matches ui/dom.ts's `$`). */
|
|
16
12
|
import { toggleLabel } from './dom.js';
|
|
17
|
-
import {
|
|
18
|
-
import { DEFAULTS } from '../core/state.js';
|
|
19
|
-
// O módulo INTEIRO, e não os nomes soltos: `menuIndexOn` é ligação viva e `setMenuIndexOnValue` a muda — ler
|
|
20
|
-
// pelo namespace deixa isso à vista em cada uso, em vez de parecer uma constante importada.
|
|
21
|
-
import * as state from '../core/state.js';
|
|
13
|
+
import { DEFAULTS } from '../core/setting-defaults.js';
|
|
22
14
|
import { markChanged, markMenuChanged } from './changed-mark.js';
|
|
23
15
|
import { defaultAudioCat } from '../platform/audio-mixer.js';
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
return '';
|
|
42
|
-
return `<div class="ctrl-row"><span>${c.lbl}</span><span style="display:flex;gap:.5rem;align-items:center;flex-shrink:0">` +
|
|
43
|
-
`<input class="vol" type="range" min="0" max="100" step="5" value="${volPercent(a.vol)}" data-avol="${k}" aria-label="Volume de ${c.lbl}">` +
|
|
44
|
-
`<button class="mode-btn switch${a.on ? ' is-on' : ''}" data-acat="${k}" type="button" aria-pressed="${a.on}" aria-label="${c.lbl}"></button></span></div>`;
|
|
45
|
-
}
|
|
46
|
-
/** Full innerHTML for a category list (#audio-list or #navsound-list). Pure string building — no DOM. */
|
|
47
|
-
export function catsListHTML(keys, cats, state) {
|
|
48
|
-
return keys.map((k) => catRowHTML(k, cats, state)).join('');
|
|
49
|
-
}
|
|
50
|
-
/** #navsound-master's value: the loudest of the nav categories, as a 0..100 slider value. */
|
|
51
|
-
export function navMasterVolume(state, navCats = NAV_CATS) {
|
|
52
|
-
return volPercent(Math.max(...navCats.map((k) => state[k].vol)));
|
|
53
|
-
}
|
|
54
|
-
/** Cane-hit spacing select -> validated int (garbage/empty -> 1, "a batida por bloco"). */
|
|
55
|
-
export function parseCaneDiv(raw) {
|
|
56
|
-
return (+raw) || 1;
|
|
57
|
-
}
|
|
58
|
-
/** srSay text for a cane-hit spacing choice. The two halves are ONE sentence per case, not a shared prefix
|
|
59
|
-
* plus a tail: a language that renders this as "One tap per block (cane)" needs to move the word "cane". */
|
|
60
|
-
export function caneDivMessage(div) {
|
|
61
|
-
return t(div === 2 ? 'sr.audio.caneHalfBlock' : 'sr.audio.canePerBlock');
|
|
16
|
+
import { controlRow, labelRow, mountChoice, updateChoice } from './panel-widgets.js';
|
|
17
|
+
/*
|
|
18
|
+
* 🔴 THE VOICE SECTION LIVES IN `ui/voice-settings` (ADR-0221, issue #203): speech — the narration switch, the engine, the
|
|
19
|
+
* voice, the rate, the spoken index and the test button — and the sound categories, cane, blind mode and outputs never
|
|
20
|
+
* needed each other.
|
|
21
|
+
*/
|
|
22
|
+
import { createVoiceSettings } from './voice-settings.js';
|
|
23
|
+
/*
|
|
24
|
+
* 🔴 THE PURE HALF LIVES IN `ui/audio-choices` (ADR-0221, issue #203): what a choice IS — the category list, the volume
|
|
25
|
+
* arithmetic, the engine catalogue, the voice filter, an output's label — needs no document, and this file is about
|
|
26
|
+
* FINDING the controls and wiring them. The suite had already made the cut: the node test imported exactly those names
|
|
27
|
+
* and the browser test drove the rest.
|
|
28
|
+
*/
|
|
29
|
+
import { NAV_CATS, GEN_CATS, volPercent, navMasterVolume, parseCaneDiv, caneDivMessage, sinksSupported, sinkOptionLabel, sinkSelectValue, } from './audio-choices.js';
|
|
30
|
+
/** The cane's two positions — one tap per block, one every half block — in the page's language. */
|
|
31
|
+
function canePositions(t) {
|
|
32
|
+
return [{ value: '1', label: t('audio.cane.block') }, { value: '2', label: t('audio.cane.halfBlock') }];
|
|
62
33
|
}
|
|
63
|
-
/** #tts-engine's option catalog: (value, i18n KEY of the label). The engine NAMES are proper nouns and stay
|
|
64
|
-
* put; what translates is the parenthetical that explains each one. Keys, not text — see input/devices. */
|
|
65
|
-
export const TTS_ENGINE_OPTIONS = [
|
|
66
|
-
['webspeech', 'tts.engine.webspeech'],
|
|
67
|
-
['piper', 'tts.engine.piper'],
|
|
68
|
-
['kokoro', 'tts.engine.kokoro'],
|
|
69
|
-
['kitten', 'tts.engine.kitten'],
|
|
70
|
-
['espeak', 'tts.engine.espeak'],
|
|
71
|
-
];
|
|
72
34
|
/**
|
|
73
|
-
*
|
|
35
|
+
* THE CANE ROW, a CYCLE ROW «◀ Batida da bengala: uma batida por bloco ▶» (ADR-0130 rule 3: two positions).
|
|
74
36
|
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
* funcionar é pior que uma opção a menos: quem a escolhe fica à espera de um download que nunca começa, e
|
|
78
|
-
* quem navega por escuta não tem como ver que não começou.
|
|
37
|
+
* 🔴 It was the kit's `escolha`, a `<select>` nobody filled: offered to every game with a walker, it opened on nothing to
|
|
38
|
+
* choose. Two positions are a cycle, and the positions are the engine's words.
|
|
79
39
|
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
40
|
+
* 📌 THE `<strong>` STAYS, HIDDEN: it is where the game's word for the cane lands (`ui/audio-rows-that-apply`, ADR-0153),
|
|
41
|
+
* and `renderAudio` reads it into the cycle's own text. `hidden` keeps it out of the accessibility tree, so the name is
|
|
42
|
+
* heard once — from the control.
|
|
82
43
|
*/
|
|
83
|
-
|
|
84
|
-
|
|
44
|
+
function caneRow(t, ctx, spec) {
|
|
45
|
+
const row = ctx.create('div');
|
|
46
|
+
row.className = 'ctrl-row ctrl-row--passos';
|
|
47
|
+
const text = ctx.create('span');
|
|
48
|
+
const name = ctx.create('strong');
|
|
49
|
+
name.textContent = spec.label;
|
|
50
|
+
name.hidden = true;
|
|
51
|
+
text.appendChild(name);
|
|
52
|
+
if (spec.hint) {
|
|
53
|
+
const explanation = ctx.create('span');
|
|
54
|
+
explanation.className = 'opt-hint';
|
|
55
|
+
explanation.textContent = spec.hint;
|
|
56
|
+
text.appendChild(explanation);
|
|
57
|
+
}
|
|
58
|
+
row.appendChild(text);
|
|
59
|
+
// the pick is `initSettingsAudio`'s, which listens for the `change` every choice fires: this builder has no setter
|
|
60
|
+
const control = mountChoice(ctx, { label: spec.label, values: canePositions(t), current: '1' }, () => { });
|
|
61
|
+
control.id = spec.id;
|
|
62
|
+
row.appendChild(control);
|
|
63
|
+
return row;
|
|
85
64
|
}
|
|
86
65
|
/**
|
|
87
|
-
*
|
|
66
|
+
* MOUNTS THIS PANEL'S INSIDE — the controls and containers it reaches.
|
|
67
|
+
*
|
|
68
|
+
* 🔴 IT IS THE LARGEST INVISIBLE CONTRACT OF THE PANELS: this file looks for `#opt-modocego`, `#cane-div`,
|
|
69
|
+
* `#opt-menuindex`, `#opt-tts`, `#tts-vol` and more, and nothing in the type says so — which is why it builds them.
|
|
70
|
+
*
|
|
71
|
+
* ⚠️ AND EACH ONE'S TAG MATTERS, which makes this the worst place to guess: the voice selects have to be `<select>` — the
|
|
72
|
+
* panel writes `.value` into them —, the volumes `<input type=range>`, and `#cane-div` is the cycle row `caneRow` builds
|
|
73
|
+
* (a host's own `<select>` is still read). On a `<button>`,
|
|
74
|
+
* writing `.value` gives no error at all: it creates a property nobody reads, and the child's choice vanishes silently.
|
|
88
75
|
*
|
|
89
|
-
*
|
|
90
|
-
* lista de vozes PORTUGUESAS para ler texto em inglês. O nome dizia a verdade sobre o que fazia e mentia
|
|
91
|
-
* sobre o que devia fazer.
|
|
76
|
+
* 📌 THE ORDER IS THE DECISION (ADR-0044 §2), from general to particular (see the composition below).
|
|
92
77
|
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
78
|
+
* ⚠️ `#opt-sound` IS NOT CREATED HERE, deliberately: it is this setting's mirror on the quick bar, outside the panel.
|
|
79
|
+
* `reflectMaster` reaches it with a guard (`if (sb)`), because a game may have no bar.
|
|
80
|
+
*
|
|
81
|
+
* Idempotent: calling it twice reuses what exists instead of duplicating it.
|
|
97
82
|
*/
|
|
98
|
-
export function
|
|
99
|
-
|
|
100
|
-
const
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
83
|
+
export function mountAudioInside(t, ctx, card, list) {
|
|
84
|
+
// Each entry is either a control row or a CONTAINER the panel fills with rows of its own.
|
|
85
|
+
const pieces = [
|
|
86
|
+
/*
|
|
87
|
+
* 🔴 THE COMPOSITION OF ADR-0151 AND ITS ERRATA, from general to particular:
|
|
88
|
+
* · BLIND MODE first, because it is the mode where the other sounds become the screen — and WITHOUT the hint: «é
|
|
89
|
+
* redundante. Quem precisa sabe o que é»;
|
|
90
|
+
* · the CANE beside it (and only in a game that answers that someone walks, ADR-0153);
|
|
91
|
+
* · the SONAR, GUARD and GUIDE, each with its switch and volume: they are the shell's list;
|
|
92
|
+
* · NARRATION and its volume, with the SPOKEN INDEX right after, because narration is what it shortens.
|
|
93
|
+
* 🔴 The GENERAL sound and volume live in the audio panel (`mountSoundInside`) — «toggle + barra para som geral
|
|
94
|
+
* voltam», the Dev said, and they came back to the sound panel, not the accessibility one. There is no separate
|
|
95
|
+
* navigation volume: it would be a second place for the list's three volumes (one place per choice, D2 of the record).
|
|
96
|
+
*/
|
|
97
|
+
// ⚠️ Their own short keys, not the bar's `icon.blind`/`icon.tts`: those carry «(navegação sonora)» and «(TTS)», and
|
|
98
|
+
// a row keeps no explanation in parentheses (ADR-0158).
|
|
99
|
+
{ id: 'opt-modocego', label: t('audio.modocego') },
|
|
100
|
+
// two positions, so a cycle row (ADR-0130 rule 3): built by `caneRow`, not by the kit
|
|
101
|
+
{ id: 'cane-div', label: t('audio.cane'), hint: t('audio.cane.dica') },
|
|
102
|
+
{ container: '@lista' }, // the shell's list: sonar, guard and guide
|
|
103
|
+
{ id: 'opt-tts', label: t('audio.narracao'), hint: t('audio.tts.dica') },
|
|
104
|
+
{ id: 'tts-vol', label: t('audio.ttsVol'), shape: 'cursor' },
|
|
105
|
+
// ADR-0183 §1, ADR-0196: the speech rate, 254 to 504 by 50 — six positions, so a list (ADR-0130 erratum)
|
|
106
|
+
{ id: 'tts-ppm', label: t('audio.ttsPpm'), hint: t('audio.ttsPpm.dica'), shape: 'escolha' },
|
|
107
|
+
// ADR-0185: the child picks the voice, among the voices that speak the language — a list, since English passes five
|
|
108
|
+
{ id: 'tts-voz', label: t('audio.voz'), shape: 'escolha' },
|
|
109
|
+
{ id: 'opt-menuindex', label: t('audio.menuindex'), hint: t('audio.menuindex.dica') },
|
|
110
|
+
/*
|
|
111
|
+
* 🔴 NOT OFFERED by the engine (ADR-0151), in the Dev's words:
|
|
112
|
+
* · the ENGINE, the VOICE and the voice TEST — «quem escolhe a voz é o jogo (cartucho), não o jogador. Jogador só
|
|
113
|
+
* habilita/desabilita o TTS»;
|
|
114
|
+
* · the PER-PLAYER AUDIO OUTPUTS and their detect button — «navegadores não são bons nisso».
|
|
115
|
+
* 📌 `initSettingsAudio`'s wiring for those ids STAYS and is guarded (`if (el)`): a game with markup of its own does
|
|
116
|
+
* not break. What changed is that the engine no longer OFFERS the choice to the child.
|
|
117
|
+
*/
|
|
118
|
+
];
|
|
119
|
+
/*
|
|
120
|
+
* ⚠️ IT RELABELS INSTEAD OF SKIPPING what already exists, which is why this function is also called from every open's
|
|
121
|
+
* `render()`: anything built once at boot captures the fallback language's text — `initI18n` applies the fallback
|
|
122
|
+
* synchronously and REQUESTS the preferred one, which arrives later — and a panel would serve its TITLE in one
|
|
123
|
+
* language and its ROWS in another. No unit test catches it: they all run in one language.
|
|
124
|
+
*/
|
|
125
|
+
// ADR-0158: the rows go BEFORE the reset, never after it — the reset is the panel's last item, and «Voltar» its first.
|
|
126
|
+
const actions = card.querySelector(':scope > .overlay__actions');
|
|
127
|
+
for (const piece of pieces) {
|
|
128
|
+
if ('container' in piece) {
|
|
129
|
+
if (piece.container === '@lista') {
|
|
130
|
+
card.insertBefore(list, actions);
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
const jaHa = ctx.find('#' + piece.container);
|
|
134
|
+
if (jaHa) {
|
|
135
|
+
if (piece.label)
|
|
136
|
+
jaHa.setAttribute('aria-label', piece.label);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const c = ctx.create('div');
|
|
140
|
+
c.id = piece.container;
|
|
141
|
+
c.className = 'ctrl-list';
|
|
142
|
+
c.setAttribute('role', 'group');
|
|
143
|
+
if (piece.label)
|
|
144
|
+
c.setAttribute('aria-label', piece.label);
|
|
145
|
+
card.insertBefore(c, actions);
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
const alreadyThere = ctx.find('#' + piece.id);
|
|
149
|
+
if (alreadyThere) {
|
|
150
|
+
const rowNode = alreadyThere.closest('.ctrl-row');
|
|
151
|
+
if (rowNode)
|
|
152
|
+
labelRow(rowNode, piece);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
card.insertBefore(piece.id === 'cane-div' ? caneRow(t, ctx, piece) : controlRow(ctx, piece).row, actions);
|
|
156
|
+
}
|
|
115
157
|
}
|
|
116
|
-
/**
|
|
117
|
-
|
|
118
|
-
|
|
158
|
+
/**
|
|
159
|
+
* MOUNTS THE AUDIO PANEL'S INSIDE (ADR-0151 §2 item 4): the general sound — switch and volume — and then the shell's
|
|
160
|
+
* list with the four taste categories (music, ambience, interaction, earcons).
|
|
161
|
+
*
|
|
162
|
+
* ⚠️ THE IDS ARE THE ONES `initSettingsAudio` ALREADY LISTENS TO (`#audio-master`, `#audio-master-vol`, `#audio-list`). And
|
|
163
|
+
* the same order rule: mount BEFORE `init`. Idempotent and relabellable, like its hearing sibling.
|
|
164
|
+
*/
|
|
165
|
+
export function mountSoundInside(t, ctx, card, list) {
|
|
166
|
+
const actions = card.querySelector(':scope > .overlay__actions');
|
|
167
|
+
const rows = [
|
|
168
|
+
{ id: 'audio-master', label: t('audio.som'), hint: t('audio.som.dica') },
|
|
169
|
+
{ id: 'audio-master-vol', label: t('audio.volume'), shape: 'cursor' },
|
|
170
|
+
];
|
|
171
|
+
for (const piece of rows) {
|
|
172
|
+
const alreadyThere = ctx.find('#' + piece.id);
|
|
173
|
+
if (alreadyThere) {
|
|
174
|
+
const rowNode = alreadyThere.closest('.ctrl-row');
|
|
175
|
+
if (rowNode)
|
|
176
|
+
labelRow(rowNode, piece);
|
|
177
|
+
continue;
|
|
178
|
+
}
|
|
179
|
+
card.insertBefore(controlRow(ctx, piece).row, list.parentNode === card ? list : actions);
|
|
180
|
+
}
|
|
181
|
+
if (list.parentNode !== card)
|
|
182
|
+
card.insertBefore(list, actions);
|
|
119
183
|
}
|
|
120
184
|
// ---------------------------------------------------------------------------------------------
|
|
121
185
|
// DOM-facing (thin) — requires `document`/injected ctx
|
|
122
186
|
// ---------------------------------------------------------------------------------------------
|
|
123
187
|
export function initSettingsAudio(ctx) {
|
|
124
|
-
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
|
|
188
|
+
const { t } = ctx.translator;
|
|
189
|
+
// ⚠️ THE ENGINE'S DEFAULT (ADR-0106 §4): whoever injects rules; whoever does not still gets blind mode.
|
|
190
|
+
// `setBlindModeValue` does the three things `core/state` says a setter does — store, persist, notify — and nothing
|
|
191
|
+
// more: the effects are reactions, and whoever reacts subscribes to the event.
|
|
192
|
+
const writeBlindMode = ctx.setBlindMode ?? ((on) => { ctx.settings.setBlindModeValue(on); });
|
|
128
193
|
let audioDevices = [];
|
|
194
|
+
/*
|
|
195
|
+
* ⚠️ THE BROWSER TRAVELS AS FOUR FUNCTIONS, not as a global reached inside: a module that reaches `document` or
|
|
196
|
+
* `window` is refused by the ratchet (the reach ceiling is ZERO). What this file received through its own ports it
|
|
197
|
+
* passes down as ports.
|
|
198
|
+
*/
|
|
199
|
+
const voice = createVoiceSettings(ctx, {
|
|
200
|
+
newOption: () => ctx.newElement('option'),
|
|
201
|
+
systemVoices: () => [...ctx.speech.voices()],
|
|
202
|
+
speakSample: (sample, chosen) => ctx.speech.speakSample(sample, chosen),
|
|
203
|
+
whenVoicesChange: (again) => ctx.speech.whenVoicesChange(again),
|
|
204
|
+
});
|
|
129
205
|
function reflectMaster() {
|
|
130
206
|
const b = ctx.$('#audio-master');
|
|
131
207
|
if (b) {
|
|
132
208
|
b.classList.toggle('is-on', ctx.getSoundOn());
|
|
133
209
|
b.setAttribute('aria-pressed', String(ctx.getSoundOn()));
|
|
134
|
-
b.textContent = ctx.getSoundOn()
|
|
210
|
+
b.textContent = toggleLabel(t, ctx.getSoundOn()); // the dictionary's words, no glyph in the name (ADR-0159 rule 12)
|
|
135
211
|
}
|
|
136
212
|
const v = ctx.$('#audio-master-vol');
|
|
137
213
|
if (v)
|
|
@@ -140,40 +216,144 @@ export function initSettingsAudio(ctx) {
|
|
|
140
216
|
if (sb)
|
|
141
217
|
ctx.toggleBtn(sb, ctx.getSoundOn());
|
|
142
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* The kit's ctx comes from the LIST NODE itself, not from a global `document` nor from a new contract field —
|
|
221
|
+
* `ownerDocument` is the document that list lives in, which is where its rows have to be born. Same shape as
|
|
222
|
+
* `ui/settings-aac`, and it is what removes this file's reach to `document` without touching `SettingsAudioCtx`.
|
|
223
|
+
*/
|
|
224
|
+
const kitCtx = (list) => ({
|
|
225
|
+
find: (sel) => ctx.$(sel),
|
|
226
|
+
create: (tag) => list.ownerDocument.createElement(tag),
|
|
227
|
+
});
|
|
228
|
+
/**
|
|
229
|
+
* The same document, but KEEPING the element type.
|
|
230
|
+
*
|
|
231
|
+
* 📌 `PanelShellCtx.create` is `string → HTMLElement` on purpose: the shell only ever appends generic nodes, and
|
|
232
|
+
* widening it would be a one-way door on a published shape (ADR-0172). The sinks section needs a `<select>` that
|
|
233
|
+
* answers `.value`, so the typed factory lives HERE, over the same `ownerDocument`, instead of the contract
|
|
234
|
+
* growing a field for one caller.
|
|
235
|
+
*/
|
|
236
|
+
const makerFor = (host) => (tag) => host.ownerDocument.createElement(tag);
|
|
237
|
+
/**
|
|
238
|
+
* ONE CATEGORY ROW, in nodes: label, volume slider and toggle.
|
|
239
|
+
*
|
|
240
|
+
* 📌 NOT A KIT ROW, and not by taste: 📏 measured on 2026-09-23, `controlRow` builds «a label, a hint and ONE
|
|
241
|
+
* control» and this row carries TWO — the volume and the on/off of the same category. Same reason the visual
|
|
242
|
+
* panel's four paper-colour row stayed hand-built: inventing a shape for it would be giving a second answer to
|
|
243
|
+
* the question «what is a row».
|
|
244
|
+
*/
|
|
245
|
+
function buildCatRow(kit, k) {
|
|
246
|
+
const row = kit.create('div');
|
|
247
|
+
row.className = 'ctrl-row';
|
|
248
|
+
const text = kit.create('span');
|
|
249
|
+
text.appendChild(kit.create('strong'));
|
|
250
|
+
row.appendChild(text);
|
|
251
|
+
const box = kit.create('span');
|
|
252
|
+
box.style.cssText = 'display:flex;gap:.5rem;align-items:center;flex-shrink:0';
|
|
253
|
+
const vol = kit.create('input');
|
|
254
|
+
vol.className = 'vol';
|
|
255
|
+
vol.setAttribute('type', 'range');
|
|
256
|
+
vol.setAttribute('min', '0');
|
|
257
|
+
vol.setAttribute('max', '100');
|
|
258
|
+
vol.setAttribute('step', '5');
|
|
259
|
+
vol.setAttribute('data-avol', k);
|
|
260
|
+
box.appendChild(vol);
|
|
261
|
+
const toggle = kit.create('button');
|
|
262
|
+
toggle.className = 'mode-btn switch';
|
|
263
|
+
toggle.setAttribute('type', 'button');
|
|
264
|
+
toggle.setAttribute('aria-pressed', 'false');
|
|
265
|
+
toggle.setAttribute('data-acat', k);
|
|
266
|
+
box.appendChild(toggle);
|
|
267
|
+
row.appendChild(box);
|
|
268
|
+
return row;
|
|
269
|
+
}
|
|
270
|
+
/** Creates what is missing IN THE RIGHT POSITION, rewrites the words of what stayed, removes what is no longer asked for. */
|
|
271
|
+
function mountCatRows(list, keys) {
|
|
272
|
+
const kit = kitCtx(list);
|
|
273
|
+
let previous = null;
|
|
274
|
+
// ⚠️ WHAT SURVIVES IS WHAT THE HOST STILL NAMES, not simply what is in `keys`. A row whose category lost its
|
|
275
|
+
// name would otherwise stay on screen with the old label — asked for by the key list, offered by nobody.
|
|
276
|
+
const named = new Set();
|
|
277
|
+
for (const k of keys) {
|
|
278
|
+
const c = ctx.audioCats.find((x) => x.k === k);
|
|
279
|
+
if (!c)
|
|
280
|
+
continue; // a category this host does not name: what has no name is not offered
|
|
281
|
+
named.add(k);
|
|
282
|
+
let row = list.querySelector(`[data-acat="${k}"]`)?.closest('.ctrl-row') ?? null;
|
|
283
|
+
if (!row) {
|
|
284
|
+
row = buildCatRow(kit, k);
|
|
285
|
+
// ⚠️ INSERTED in place, never appended and reordered: a node that changes parent is removed and put back,
|
|
286
|
+
// and the browser blurs it on removal — that would take the cursor from whoever is setting the volume.
|
|
287
|
+
list.insertBefore(row, previous ? previous.nextSibling : list.firstChild);
|
|
288
|
+
}
|
|
289
|
+
const label = t(c.lbl);
|
|
290
|
+
const strong = row.querySelector('strong');
|
|
291
|
+
if (strong)
|
|
292
|
+
strong.textContent = label;
|
|
293
|
+
row.querySelector(`[data-avol="${k}"]`)?.setAttribute('aria-label', t('audio.cat.volumeDe', { c: label }));
|
|
294
|
+
row.querySelector(`[data-acat="${k}"]`)?.setAttribute('aria-label', label);
|
|
295
|
+
previous = row;
|
|
296
|
+
}
|
|
297
|
+
for (const b of [...list.querySelectorAll('[data-acat]')]) {
|
|
298
|
+
if (!named.has(b.dataset.acat ?? ''))
|
|
299
|
+
b.closest('.ctrl-row')?.remove();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
/** The STATE of each row — the volume where it is and the toggle saying what it says. */
|
|
303
|
+
function reflectCatRows(list, keys) {
|
|
304
|
+
const state = ctx.getAudioCat();
|
|
305
|
+
if (!state)
|
|
306
|
+
return;
|
|
307
|
+
for (const k of keys) {
|
|
308
|
+
const a = state[k];
|
|
309
|
+
if (!a)
|
|
310
|
+
continue;
|
|
311
|
+
const vol = list.querySelector(`input[data-avol="${k}"]`);
|
|
312
|
+
if (vol)
|
|
313
|
+
vol.value = String(volPercent(a.vol));
|
|
314
|
+
const toggle = list.querySelector(`button[data-acat="${k}"]`);
|
|
315
|
+
if (toggle) {
|
|
316
|
+
toggle.classList.toggle('is-on', a.on);
|
|
317
|
+
toggle.setAttribute('aria-pressed', String(a.on));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
/** The listeners, ONCE per list and by delegation — rows come and go, the list stays. */
|
|
143
322
|
function wireCatControls(el) {
|
|
144
|
-
el.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
323
|
+
if (el.dataset.catsWired)
|
|
324
|
+
return;
|
|
325
|
+
el.dataset.catsWired = '1';
|
|
326
|
+
el.addEventListener('click', (ev) => {
|
|
327
|
+
const b = ev.target?.closest('button[data-acat]');
|
|
328
|
+
const k = b?.dataset.acat;
|
|
329
|
+
if (!b || !k)
|
|
330
|
+
return;
|
|
331
|
+
const state = ctx.getAudioCat();
|
|
332
|
+
if (!state || !state[k])
|
|
333
|
+
return;
|
|
334
|
+
state[k].on = !state[k].on;
|
|
335
|
+
ctx.setCatGain(k);
|
|
336
|
+
b.classList.toggle('is-on', state[k].on);
|
|
337
|
+
b.setAttribute('aria-pressed', String(state[k].on));
|
|
338
|
+
refreshMarks(); // the mark follows the CHANGE, not only the redraw — see the note in refreshMarks
|
|
158
339
|
});
|
|
159
|
-
el.
|
|
160
|
-
s.
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
});
|
|
340
|
+
el.addEventListener('input', (ev) => {
|
|
341
|
+
const s = ev.target?.closest('input[data-avol]');
|
|
342
|
+
const k = s?.dataset.avol;
|
|
343
|
+
if (!s || !k)
|
|
344
|
+
return;
|
|
345
|
+
const state = ctx.getAudioCat();
|
|
346
|
+
if (!state || !state[k])
|
|
347
|
+
return;
|
|
348
|
+
state[k].vol = (+s.value) / 100;
|
|
349
|
+
state[k].on = true;
|
|
350
|
+
ctx.setCatGain(k);
|
|
351
|
+
const bb = el.querySelector('button[data-acat="' + k + '"]');
|
|
352
|
+
if (bb) {
|
|
353
|
+
bb.classList.add('is-on');
|
|
354
|
+
bb.setAttribute('aria-pressed', 'true');
|
|
355
|
+
}
|
|
356
|
+
refreshMarks();
|
|
177
357
|
});
|
|
178
358
|
}
|
|
179
359
|
function renderCategoryList(sel, keys) {
|
|
@@ -181,10 +361,12 @@ export function initSettingsAudio(ctx) {
|
|
|
181
361
|
const state = ctx.getAudioCat();
|
|
182
362
|
if (!el || !state)
|
|
183
363
|
return;
|
|
184
|
-
el
|
|
364
|
+
mountCatRows(el, keys);
|
|
365
|
+
reflectCatRows(el, keys);
|
|
185
366
|
wireCatControls(el);
|
|
186
|
-
//
|
|
187
|
-
|
|
367
|
+
// The prose goes back to the footer after the rows change (CLAUDE.md §4, #109) — in the card of WHOEVER holds
|
|
368
|
+
// this list: since ADR-0151 there are two panels, and the other one's footer is not this child's.
|
|
369
|
+
ctx.fillExplain?.(el.closest('.overlay__card'));
|
|
188
370
|
}
|
|
189
371
|
function renderNavSound() {
|
|
190
372
|
renderCategoryList('#navsound-list', NAV_CATS);
|
|
@@ -193,120 +375,47 @@ export function initSettingsAudio(ctx) {
|
|
|
193
375
|
if (m && state)
|
|
194
376
|
m.value = String(navMasterVolume(state, NAV_CATS));
|
|
195
377
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
if (b) {
|
|
201
|
-
ctx.toggleBtn(b, on);
|
|
202
|
-
b.textContent = toggleLabel(on);
|
|
203
|
-
}
|
|
204
|
-
const e = ctx.$('#tts-engine');
|
|
205
|
-
if (e)
|
|
206
|
-
e.value = ctx.tts.getEngineSel();
|
|
207
|
-
}
|
|
208
|
-
/**
|
|
209
|
-
* O alternador do ÍNDICE "6 de 10" (ADR-0044, item 3).
|
|
210
|
-
*
|
|
211
|
-
* Ele mora ao lado da narração por voz e não num painel de visual, porque é a NARRAÇÃO que ele muda: quem
|
|
212
|
-
* desliga o índice está encurtando o que ouve a cada passo, e é aqui que essa pessoa vem quando o que ouve
|
|
213
|
-
* incomoda.
|
|
214
|
-
*/
|
|
215
|
-
function reflectMenuIndex() {
|
|
216
|
-
const b = ctx.$('#opt-menuindex');
|
|
217
|
-
if (b) {
|
|
218
|
-
ctx.toggleBtn(b, state.menuIndexOn);
|
|
219
|
-
b.textContent = toggleLabel(state.menuIndexOn);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
function populateTtsEngines() {
|
|
223
|
-
const sel = ctx.$('#tts-engine');
|
|
224
|
-
if (!sel || sel.dataset.filled)
|
|
225
|
-
return;
|
|
226
|
-
sel.dataset.filled = '1';
|
|
227
|
-
opcoesDeMotor(ctx.tts.neuralDisponivel !== false).forEach(([v, l]) => {
|
|
228
|
-
const o = document.createElement('option');
|
|
229
|
-
o.value = v;
|
|
230
|
-
o.textContent = t(l);
|
|
231
|
-
sel.appendChild(o);
|
|
232
|
-
});
|
|
233
|
-
sel.value = ctx.tts.getEngineSel();
|
|
234
|
-
}
|
|
235
|
-
function populateTtsVoices() {
|
|
236
|
-
const sel = ctx.$('#tts-voice');
|
|
237
|
-
if (!sel)
|
|
238
|
-
return;
|
|
239
|
-
let voices = [];
|
|
240
|
-
try {
|
|
241
|
-
voices = (window.speechSynthesis && window.speechSynthesis.getVoices()) || [];
|
|
242
|
-
}
|
|
243
|
-
catch (e) { /* noop */ }
|
|
244
|
-
const list = pickVoicesFor(voices, bcp47());
|
|
245
|
-
sel.innerHTML = '';
|
|
246
|
-
if (!list.length) {
|
|
247
|
-
const o = document.createElement('option');
|
|
248
|
-
o.textContent = t('audio.noSystemVoices');
|
|
249
|
-
sel.appendChild(o);
|
|
250
|
-
ctx.tts.setVoiceObj(null);
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
list.forEach((v) => {
|
|
254
|
-
const o = document.createElement('option');
|
|
255
|
-
o.value = v.name;
|
|
256
|
-
o.textContent = voiceLabel(v);
|
|
257
|
-
sel.appendChild(o);
|
|
258
|
-
});
|
|
259
|
-
const saved = ctx.store.get('incl_tts_voice', null);
|
|
260
|
-
const pick = list.find((v) => v.name === saved) || list[0];
|
|
261
|
-
sel.value = pick.name;
|
|
262
|
-
ctx.tts.setVoiceObj(pick);
|
|
263
|
-
}
|
|
264
|
-
function hasEnumerateDevices() {
|
|
265
|
-
return !!(navigator.mediaDevices && navigator.mediaDevices.enumerateDevices);
|
|
266
|
-
}
|
|
267
|
-
function hasAudioContextCtor() {
|
|
268
|
-
const w = window;
|
|
269
|
-
return typeof (w.AudioContext || w.webkitAudioContext) !== 'undefined';
|
|
270
|
-
}
|
|
378
|
+
// 📌 Both questions are the HOST's (ADR-0227), and they stay TWO because they produce different sentences: the browser
|
|
379
|
+
// cannot do it, and there is no device at all, are not the same news to someone looking for their headphones.
|
|
380
|
+
const hasEnumerateDevices = () => ctx.audioOutputs.canList();
|
|
381
|
+
const hasAudioContextCtor = () => ctx.audioOutputs.canRoute();
|
|
271
382
|
function renderSinks(devices) {
|
|
272
383
|
const el = ctx.$('#audio-sinks');
|
|
273
384
|
if (!el)
|
|
274
385
|
return;
|
|
275
|
-
|
|
386
|
+
// 📌 The document comes from the list node, never from the global one — same shape as the rest of this file
|
|
387
|
+
// since the conversion to nodes, and it is what removes the reach to `document` without asking the contract
|
|
388
|
+
// for a new field (ADR-0221 step 7d).
|
|
389
|
+
const make = makerFor(el);
|
|
390
|
+
while (el.firstChild)
|
|
391
|
+
el.removeChild(el.firstChild);
|
|
276
392
|
if (!devices.length) {
|
|
277
393
|
const supported = sinksSupported(hasEnumerateDevices(), hasAudioContextCtor());
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
394
|
+
// ⚠️ TWO SENTENCES, not one: «this browser CANNOT do it» and «there is no device yet» are different things to
|
|
395
|
+
// a child who is looking for their own headphones, and the dictionary has always had both.
|
|
396
|
+
const hint = make('p');
|
|
397
|
+
hint.className = 'opt-hint';
|
|
398
|
+
hint.textContent = t(supported ? 'audio.sinksHint' : 'audio.sinksUnsupported');
|
|
399
|
+
el.appendChild(hint);
|
|
281
400
|
return;
|
|
282
401
|
}
|
|
283
402
|
const players = ctx.getPlayers();
|
|
284
403
|
const n = Math.max(1, ctx.getNumPlayers());
|
|
404
|
+
/*
|
|
405
|
+
* 🔴 A PLAYER'S OUTPUT IS A CHOICE, AND ITS SIZE PICKS THE CONTROL (ADR-0130 rule 3): «shared» and up to four devices are
|
|
406
|
+
* a cycle row «◀ Jogador 1: Fone USB ▶», more a dropdown. The size is the room's today — it is drawn again at every
|
|
407
|
+
* detection, so a fifth headset plugged in turns the rows into dropdowns.
|
|
408
|
+
*/
|
|
409
|
+
const outputs = [{ value: '', label: t('audio.sinkShared') }, ...devices.map((d, k) => ({ value: d.deviceId, label: sinkOptionLabel(t, d, k) }))];
|
|
285
410
|
for (let i = 0; i < n; i++) {
|
|
286
411
|
const p = players[i];
|
|
287
|
-
const
|
|
412
|
+
const name = t('audio.playerN', { n: i + 1 });
|
|
413
|
+
const row = make('div');
|
|
288
414
|
row.className = 'ctrl-row';
|
|
289
|
-
const
|
|
290
|
-
lbl.textContent = t('audio.playerN', { n: i + 1 });
|
|
291
|
-
lbl.setAttribute('for', 'sink-p' + i);
|
|
292
|
-
const sel = document.createElement('select');
|
|
293
|
-
sel.className = 'vol';
|
|
294
|
-
sel.id = 'sink-p' + i;
|
|
295
|
-
const o0 = document.createElement('option');
|
|
296
|
-
o0.value = '';
|
|
297
|
-
o0.textContent = t('audio.sinkShared');
|
|
298
|
-
sel.appendChild(o0);
|
|
299
|
-
devices.forEach((d, k) => {
|
|
300
|
-
const o = document.createElement('option');
|
|
301
|
-
o.value = d.deviceId;
|
|
302
|
-
o.textContent = sinkOptionLabel(d, k);
|
|
303
|
-
sel.appendChild(o);
|
|
304
|
-
});
|
|
305
|
-
sel.value = sinkSelectValue(p);
|
|
306
|
-
sel.addEventListener('change', () => {
|
|
415
|
+
const choice = mountChoice(kitCtx(el), { label: name, values: outputs, current: sinkSelectValue(p) }, (value) => {
|
|
307
416
|
if (!p)
|
|
308
417
|
return;
|
|
309
|
-
p.audioSink =
|
|
418
|
+
p.audioSink = value || null;
|
|
310
419
|
ctx.store.set('incl_sink_p' + i, p.audioSink || '');
|
|
311
420
|
if (p._ac) {
|
|
312
421
|
try {
|
|
@@ -318,84 +427,96 @@ export function initSettingsAudio(ctx) {
|
|
|
318
427
|
}
|
|
319
428
|
ctx.srSay(t(p.audioSink ? 'sr.audio.sinkChanged' : 'sr.audio.sinkDefault', { n: i + 1 }));
|
|
320
429
|
});
|
|
321
|
-
|
|
322
|
-
|
|
430
|
+
choice.id = 'sink-p' + i;
|
|
431
|
+
if (choice.tagName === 'SELECT') {
|
|
432
|
+
// a dropdown keeps its name beside it; a cycle row already carries it in its own text
|
|
433
|
+
const lbl = make('label');
|
|
434
|
+
lbl.textContent = name;
|
|
435
|
+
lbl.htmlFor = choice.id;
|
|
436
|
+
row.appendChild(lbl);
|
|
437
|
+
}
|
|
438
|
+
else
|
|
439
|
+
row.classList.add('ctrl-row--passos');
|
|
440
|
+
row.appendChild(choice);
|
|
323
441
|
el.appendChild(row);
|
|
324
442
|
}
|
|
325
443
|
}
|
|
326
444
|
async function enumerateSinks() {
|
|
445
|
+
// without asking permission: only the outputs already known
|
|
327
446
|
try {
|
|
328
|
-
|
|
329
|
-
const devs = await navigator.mediaDevices.enumerateDevices();
|
|
330
|
-
audioDevices = devs.filter((d) => d.kind === 'audiooutput');
|
|
331
|
-
}
|
|
447
|
+
audioDevices = [...await ctx.audioOutputs.list()];
|
|
332
448
|
}
|
|
333
|
-
catch (e) { /*
|
|
449
|
+
catch (e) { /* the port refused; the list stays as it is */ }
|
|
334
450
|
renderSinks(audioDevices);
|
|
335
451
|
}
|
|
336
452
|
async function detectAudioDevices() {
|
|
453
|
+
// ⚠️ THE LIST EMPTIES when detection fails, and `enumerateSinks` above does NOT empty it: there an error means "I
|
|
454
|
+
// could not ask", here it means "the child asked and the answer is none". The two sentences the panel draws next are
|
|
455
|
+
// different, and so are the two `catch`es.
|
|
337
456
|
try {
|
|
338
|
-
|
|
339
|
-
const devs = await navigator.mediaDevices.enumerateDevices();
|
|
340
|
-
audioDevices = devs.filter((d) => d.kind === 'audiooutput');
|
|
457
|
+
audioDevices = [...await ctx.audioOutputs.detect()];
|
|
341
458
|
}
|
|
342
459
|
catch (e) {
|
|
343
460
|
audioDevices = [];
|
|
344
461
|
}
|
|
345
462
|
renderSinks(audioDevices);
|
|
346
463
|
}
|
|
347
|
-
function
|
|
464
|
+
function drawBlindMode() {
|
|
348
465
|
const b = ctx.$('#opt-modocego');
|
|
349
466
|
if (b) {
|
|
350
|
-
ctx.toggleBtn(b, ctx.
|
|
351
|
-
b.textContent = toggleLabel(ctx.
|
|
467
|
+
ctx.toggleBtn(b, ctx.getBlindMode());
|
|
468
|
+
b.textContent = toggleLabel(t, ctx.getBlindMode());
|
|
352
469
|
}
|
|
353
470
|
}
|
|
354
471
|
function renderAudio() {
|
|
355
472
|
reflectMaster();
|
|
356
473
|
renderCategoryList('#audio-list', GEN_CATS);
|
|
357
474
|
renderNavSound();
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
const cd = ctx.$('#cane-div');
|
|
363
|
-
if (cd)
|
|
364
|
-
cd.value = String(ctx.getCaneBlockDiv());
|
|
365
|
-
void enumerateSinks(); // sem await no original: dispara e segue (lista assíncrona atualiza sozinha)
|
|
475
|
+
drawBlindMode();
|
|
476
|
+
voice.render();
|
|
477
|
+
reflectCane();
|
|
478
|
+
void enumerateSinks(); // not awaited: fire and carry on (the asynchronous list updates by itself)
|
|
366
479
|
refreshMarks();
|
|
367
480
|
}
|
|
368
481
|
/**
|
|
369
|
-
*
|
|
482
|
+
* The left-the-default mark (ADR-0029).
|
|
370
483
|
*
|
|
371
|
-
*
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
* mudança" — e eu escrevi o aviso e caí nele na mesma tarde. A regra que sobra: a marca anda com quem
|
|
375
|
-
* ESCREVE o valor, nunca com quem desenha.
|
|
484
|
+
* Called from inside the change HANDLERS, not only from `renderAudio()`: changing a category updates that row by
|
|
485
|
+
* itself, without redrawing the panel, so a mark hung only on render goes stale on screen — ADR-0029 warns about it.
|
|
486
|
+
* The rule: the mark travels with whoever WRITES the value, never with whoever draws.
|
|
376
487
|
*
|
|
377
|
-
*
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
488
|
+
* The cut is the SAME as this menu's reset, and that is the rule, not thrift: only what has a default in
|
|
489
|
+
* DEFAULTS/`defaultAudioCat` can be marked. The voice engine and the per-player audio output have none, because they
|
|
490
|
+
* are a DEVICE choice and not a restorable preference; marking them would mean inventing a second opinion on what a
|
|
491
|
+
* headset's "default" is.
|
|
381
492
|
*/
|
|
382
493
|
function refreshMarks() {
|
|
383
494
|
const state = ctx.getAudioCat();
|
|
384
|
-
const
|
|
385
|
-
const
|
|
386
|
-
|
|
387
|
-
markChanged(ctx.$(sel)?.closest('.ctrl-row') ?? null, changed);
|
|
495
|
+
const didChange = [];
|
|
496
|
+
const mark = (sel, changed) => {
|
|
497
|
+
didChange.push(changed);
|
|
498
|
+
markChanged(t, ctx.$(sel)?.closest('.ctrl-row') ?? null, changed);
|
|
388
499
|
};
|
|
389
|
-
|
|
390
|
-
|
|
500
|
+
mark('#opt-modocego', ctx.getBlindMode() !== DEFAULTS.blindMode);
|
|
501
|
+
mark('#cane-div', ctx.getCaneBlockDiv() !== DEFAULTS.caneBlockDiv);
|
|
502
|
+
// ⚠️ TWO MENU MARKS, one per panel: the audio panel's lit by a changed sonar would send the child searching the
|
|
503
|
+
// wrong panel.
|
|
504
|
+
const fromSound = [];
|
|
391
505
|
for (const c of ctx.audioCats) {
|
|
392
506
|
const d = defaultAudioCat(c.k);
|
|
393
507
|
const a = state?.[c.k];
|
|
394
|
-
|
|
508
|
+
const changedHere = !!a && (a.on !== d.on || a.vol !== d.vol);
|
|
509
|
+
if (GEN_CATS.includes(c.k)) {
|
|
510
|
+
fromSound.push(changedHere);
|
|
511
|
+
markChanged(t, ctx.$(`[data-acat="${c.k}"]`)?.closest('.ctrl-row') ?? null, changedHere);
|
|
512
|
+
}
|
|
513
|
+
else
|
|
514
|
+
mark(`[data-acat="${c.k}"]`, changedHere);
|
|
395
515
|
}
|
|
396
|
-
markMenuChanged(ctx.$('[data-act="audio"]'),
|
|
516
|
+
markMenuChanged(t, ctx.$('[data-act="audio"]'), didChange);
|
|
517
|
+
markMenuChanged(t, ctx.$('[data-act="som"]'), fromSound);
|
|
397
518
|
}
|
|
398
|
-
// ----- widgets
|
|
519
|
+
// ----- static widgets (always present in #audio; wired ONCE, never recreated by renderAudio) -----
|
|
399
520
|
const audioMasterBtn = ctx.$('#audio-master');
|
|
400
521
|
if (audioMasterBtn)
|
|
401
522
|
audioMasterBtn.addEventListener('click', () => {
|
|
@@ -424,175 +545,122 @@ export function initSettingsAudio(ctx) {
|
|
|
424
545
|
renderNavSound();
|
|
425
546
|
});
|
|
426
547
|
/*
|
|
427
|
-
* ⚠️
|
|
428
|
-
*
|
|
429
|
-
*
|
|
548
|
+
* ⚠️ THIS BUTTON ANNOUNCES, like its siblings (sound, TTS, cane spacing, menu index, audio output): with the engine's
|
|
549
|
+
* default setter (`setBlindModeValue`, which stores/persists/notifies and does NOT speak), a game that does not inject
|
|
550
|
+
* its own setter would otherwise get a mute button. A toggle that changes state without saying so is invisible to a
|
|
551
|
+
* screen-reader user.
|
|
430
552
|
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
433
|
-
*
|
|
434
|
-
* tela — a mesma família de defeito que o `reflectTTS` e o `reflectModoCego` já custaram aqui.
|
|
435
|
-
*
|
|
436
|
-
* O anúncio pertence a QUEM É ACCIONADO, não ao setter: `core/state` diz que o setter faz três coisas e só
|
|
437
|
-
* três. ⚠️ Consequência de lockstep, escrita para não se descobrir depois: quando o `game-platformer` subir
|
|
438
|
-
* de versão, tem de TIRAR o `srSay` do `setModoCego` dele, senão a criança ouve o estado duas vezes.
|
|
553
|
+
* The announcement belongs to WHOEVER IS ACTIVATED, not to the setter: `core/state` says a setter does three things
|
|
554
|
+
* and only three. ⚠️ Lockstep consequence, written so it is not discovered later: a cartridge whose own `setBlindMode`
|
|
555
|
+
* also speaks must drop that, or the child hears the state twice.
|
|
439
556
|
*/
|
|
440
557
|
const mcBtn = ctx.$('#opt-modocego');
|
|
441
558
|
if (mcBtn) {
|
|
442
559
|
mcBtn.addEventListener('click', () => {
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
ctx.srSay(t(ctx.
|
|
560
|
+
writeBlindMode(!ctx.getBlindMode());
|
|
561
|
+
drawBlindMode();
|
|
562
|
+
ctx.srSay(t(ctx.getBlindMode() ? 'sr.blind.on' : 'sr.blind.off'));
|
|
446
563
|
});
|
|
447
564
|
}
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
565
|
+
/**
|
|
566
|
+
* The cane's position in use. A host's own `<select>` gets its value; the engine's cycle row (`caneRow`) its words and
|
|
567
|
+
* value, with its name read from the row's hidden `<strong>` — where the game's word for the cane lands (ADR-0153).
|
|
568
|
+
*/
|
|
569
|
+
function reflectCane() {
|
|
570
|
+
const cd = ctx.$('#cane-div');
|
|
571
|
+
if (!cd)
|
|
572
|
+
return;
|
|
573
|
+
const current = String(ctx.getCaneBlockDiv());
|
|
574
|
+
if (cd.tagName === 'SELECT') {
|
|
575
|
+
cd.value = current;
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
const label = cd.closest('.ctrl-row')?.querySelector('strong')?.textContent || t('audio.cane');
|
|
579
|
+
updateChoice(cd, { label, values: canePositions(t), current });
|
|
580
|
+
}
|
|
581
|
+
const caneDiv = ctx.$('#cane-div');
|
|
582
|
+
if (caneDiv) {
|
|
583
|
+
reflectCane();
|
|
584
|
+
caneDiv.addEventListener('change', () => {
|
|
585
|
+
// a host's `<select>` holds its value; the engine's cycle row carries it in `data-value` (`mountChoice`)
|
|
586
|
+
const div = parseCaneDiv(caneDiv.tagName === 'SELECT' ? caneDiv.value : (caneDiv.dataset.value ?? ''));
|
|
453
587
|
ctx.setCaneBlockDiv(div);
|
|
454
|
-
ctx.srSay(caneDivMessage(div));
|
|
588
|
+
ctx.srSay(caneDivMessage(t, div));
|
|
455
589
|
});
|
|
456
590
|
}
|
|
457
|
-
const ttsBtn = ctx.$('#opt-tts');
|
|
458
|
-
if (ttsBtn)
|
|
459
|
-
ttsBtn.addEventListener('click', () => {
|
|
460
|
-
const state = ctx.getAudioCat();
|
|
461
|
-
if (!state)
|
|
462
|
-
return;
|
|
463
|
-
state.tts.on = !state.tts.on;
|
|
464
|
-
ctx.setCatGain('tts');
|
|
465
|
-
reflectTts();
|
|
466
|
-
ctx.srSay(t(state.tts.on ? 'sr.audio.ttsOn' : 'sr.audio.ttsOff'));
|
|
467
|
-
if (state.tts.on)
|
|
468
|
-
ctx.tts.narrate(t('sr.audio.ttsOnSpoken'));
|
|
469
|
-
});
|
|
470
|
-
const idxBtn = ctx.$('#opt-menuindex');
|
|
471
|
-
if (idxBtn)
|
|
472
|
-
idxBtn.addEventListener('click', () => {
|
|
473
|
-
state.setMenuIndexOnValue(!state.menuIndexOn);
|
|
474
|
-
reflectMenuIndex();
|
|
475
|
-
// O anúncio da troca NÃO leva índice: ele não é item de lista nenhuma, e um "1 de 1" aqui seria ruído
|
|
476
|
-
// justamente no momento em que a criança está julgando se o ruído incomoda.
|
|
477
|
-
ctx.srSay(t(state.menuIndexOn ? 'sr.menu.indexOn' : 'sr.menu.indexOff'));
|
|
478
|
-
});
|
|
479
|
-
const ttsEngSel = ctx.$('#tts-engine');
|
|
480
|
-
if (ttsEngSel)
|
|
481
|
-
ttsEngSel.addEventListener('change', () => {
|
|
482
|
-
ctx.tts.setEngineSel(ttsEngSel.value);
|
|
483
|
-
ctx.store.set('incl_tts_engine', ttsEngSel.value);
|
|
484
|
-
if (ttsEngSel.value !== 'webspeech')
|
|
485
|
-
ctx.tts.loadTTS();
|
|
486
|
-
const opt = ttsEngSel.options[ttsEngSel.selectedIndex];
|
|
487
|
-
ctx.tts.narrate(t('sr.audio.engineSet', { motor: opt ? opt.text : '' }));
|
|
488
|
-
});
|
|
489
|
-
const ttsVoiceSel = ctx.$('#tts-voice');
|
|
490
|
-
if (ttsVoiceSel)
|
|
491
|
-
ttsVoiceSel.addEventListener('change', () => {
|
|
492
|
-
try {
|
|
493
|
-
const vs = window.speechSynthesis.getVoices();
|
|
494
|
-
ctx.tts.setVoiceObj(vs.find((v) => v.name === ttsVoiceSel.value) || null);
|
|
495
|
-
ctx.store.set('incl_tts_voice', ttsVoiceSel.value);
|
|
496
|
-
}
|
|
497
|
-
catch (e) { /* noop */ }
|
|
498
|
-
ctx.tts.narrate(t('sr.audio.voicePicked'));
|
|
499
|
-
});
|
|
500
|
-
const ttsTestBtn = ctx.$('#opt-tts-test');
|
|
501
|
-
if (ttsTestBtn)
|
|
502
|
-
ttsTestBtn.addEventListener('click', () => {
|
|
503
|
-
const txt = t('audio.voiceSample');
|
|
504
|
-
const engine = ctx.tts.getEngine();
|
|
505
|
-
if (ctx.tts.getEngineSel() !== 'webspeech' && engine && engine.speak) {
|
|
506
|
-
try {
|
|
507
|
-
engine.speak(txt);
|
|
508
|
-
}
|
|
509
|
-
catch (e) { /* noop */ } // motor neural já carregado
|
|
510
|
-
}
|
|
511
|
-
else {
|
|
512
|
-
try {
|
|
513
|
-
const ss = window.speechSynthesis;
|
|
514
|
-
if (ss) {
|
|
515
|
-
ss.cancel();
|
|
516
|
-
const u = new SpeechSynthesisUtterance(txt);
|
|
517
|
-
u.lang = 'pt-BR';
|
|
518
|
-
const vo = ctx.tts.getVoiceObj();
|
|
519
|
-
if (vo)
|
|
520
|
-
u.voice = vo;
|
|
521
|
-
u.rate = 1;
|
|
522
|
-
u.volume = 1;
|
|
523
|
-
ss.speak(u);
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
catch (e) { /* noop */ } // fallback audível (volume 1) + dispara download do neural
|
|
527
|
-
if (ctx.tts.getEngineSel() !== 'webspeech')
|
|
528
|
-
ctx.tts.loadTTS();
|
|
529
|
-
}
|
|
530
|
-
ctx.srSay(t('sr.audio.testingVoice'));
|
|
531
|
-
});
|
|
532
|
-
// ---- restaurar os padrões DESTE menu (ADR-0028) ----
|
|
533
591
|
//
|
|
534
|
-
//
|
|
535
|
-
//
|
|
536
|
-
//
|
|
592
|
+
// The hard rule is scope: this button restores what the HEARING menu contains and nothing else. A reset that reached
|
|
593
|
+
// beyond itself would be worse than the trap it exists to undo — a child who undoes a sound setting and loses the
|
|
594
|
+
// mobility setup along with it can no longer play, and does not understand why.
|
|
537
595
|
//
|
|
538
|
-
//
|
|
539
|
-
//
|
|
540
|
-
//
|
|
596
|
+
// What belongs to this menu: blind mode, the cane spacing and the mixer categories that are not the audio panel's.
|
|
597
|
+
// The voice engine and the per-player audio output do NOT — they are a device choice, not a restorable preference,
|
|
598
|
+
// and clearing them would take away the child's own headset in a shared room.
|
|
599
|
+
/** Resets the categories in `keys` (those that exist in the mixer) to their factory state. */
|
|
600
|
+
function resetCategories(keys) {
|
|
601
|
+
const state = ctx.getAudioCat();
|
|
602
|
+
if (!state)
|
|
603
|
+
return;
|
|
604
|
+
for (const k of keys) {
|
|
605
|
+
if (!state[k])
|
|
606
|
+
continue;
|
|
607
|
+
const d = defaultAudioCat(k);
|
|
608
|
+
state[k].on = d.on;
|
|
609
|
+
state[k].vol = d.vol;
|
|
610
|
+
ctx.setCatGain(k);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
541
613
|
const resetBtn = ctx.$('#audio-reset');
|
|
542
614
|
if (resetBtn)
|
|
543
615
|
resetBtn.addEventListener('click', () => {
|
|
544
|
-
|
|
616
|
+
writeBlindMode(DEFAULTS.blindMode);
|
|
545
617
|
ctx.setCaneBlockDiv(DEFAULTS.caneBlockDiv);
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
const d = defaultAudioCat(c.k);
|
|
550
|
-
if (!state[c.k])
|
|
551
|
-
continue;
|
|
552
|
-
state[c.k].on = d.on;
|
|
553
|
-
state[c.k].vol = d.vol;
|
|
554
|
-
ctx.setCatGain(c.k);
|
|
555
|
-
}
|
|
618
|
+
// 🔴 THIS MENU DOES NOT HOLD THE MUSIC (ADR-0151): resetting it here would reach beyond itself — the scope rule,
|
|
619
|
+
// above. Everything that is not a taste category belongs to this panel.
|
|
620
|
+
resetCategories(ctx.audioCats.map((c) => c.k).filter((k) => !GEN_CATS.includes(k)));
|
|
556
621
|
renderAudio();
|
|
557
|
-
|
|
558
|
-
reflectTts();
|
|
559
|
-
reflectMenuIndex();
|
|
622
|
+
drawBlindMode();
|
|
560
623
|
ctx.srSay(t('sr.audio.reset'));
|
|
561
624
|
});
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
ttsVolEl.addEventListener('input', () => {
|
|
570
|
-
const state = ctx.getAudioCat();
|
|
571
|
-
if (!state)
|
|
572
|
-
return;
|
|
573
|
-
state.tts.vol = (+ttsVolEl.value) / 100;
|
|
574
|
-
state.tts.on = true;
|
|
575
|
-
ctx.setCatGain('tts');
|
|
576
|
-
reflectTts();
|
|
625
|
+
// The AUDIO panel's reset: the taste categories and nothing else.
|
|
626
|
+
const soundReset = ctx.$('#som-reset');
|
|
627
|
+
if (soundReset)
|
|
628
|
+
soundReset.addEventListener('click', () => {
|
|
629
|
+
resetCategories(GEN_CATS);
|
|
630
|
+
renderAudio();
|
|
631
|
+
ctx.srSay(t('sr.audio.reset'));
|
|
577
632
|
});
|
|
578
633
|
const audioDetectBtn = ctx.$('#audio-detect');
|
|
579
634
|
if (audioDetectBtn)
|
|
580
635
|
audioDetectBtn.addEventListener('click', () => { void detectAudioDevices(); });
|
|
581
|
-
reflectMaster(); //
|
|
636
|
+
reflectMaster(); // initial state of the master button/slider, before the panel is ever opened
|
|
582
637
|
/*
|
|
583
|
-
* ⚠️
|
|
584
|
-
*
|
|
585
|
-
* efeitos … são reação, e quem reage assina o evento».
|
|
638
|
+
* ⚠️ THE PANEL SUBSCRIBES TO THE EVENT — the decision `core/state` writes next to `setBlindModeValue`: a setter stores,
|
|
639
|
+
* persists and notifies, and effects are reactions, subscribed by whoever reacts.
|
|
586
640
|
*
|
|
587
|
-
*
|
|
588
|
-
*
|
|
589
|
-
*
|
|
590
|
-
* registado no `ui/pause-icons`, e não vale a pena descobri-lo uma terceira vez.
|
|
641
|
+
* Without it, a game that does NOT inject its own `setBlindMode` switches blind mode on through the bar's icon and this
|
|
642
|
+
* panel's `#opt-modocego` keeps saying off, with `aria-pressed=false` — the control lying about the state to the screen
|
|
643
|
+
* reader.
|
|
591
644
|
*
|
|
592
|
-
* ⚠️
|
|
593
|
-
*
|
|
594
|
-
*
|
|
645
|
+
* ⚠️ It is safe for whoever ALREADY reflects from their own setter: reflecting is idempotent — it rereads the state and
|
|
646
|
+
* rewrites the button. A duplicated announcement would be another matter, which is why the subscription does NOT
|
|
647
|
+
* announce: the bar's icon already speaks for itself.
|
|
595
648
|
*/
|
|
596
|
-
|
|
597
|
-
|
|
649
|
+
ctx.on('blindMode', () => { drawBlindMode(); }); // through the root's door: released by its `dispose()`
|
|
650
|
+
/** One category, in place: the lists hold rows only once the panel has drawn them, and a row not there is not drawn here. */
|
|
651
|
+
function reflectCategory(k) {
|
|
652
|
+
if (k === 'tts')
|
|
653
|
+
voice.reflectTts();
|
|
654
|
+
for (const sel of ['#audio-list', '#navsound-list']) {
|
|
655
|
+
const list = ctx.$(sel);
|
|
656
|
+
if (list)
|
|
657
|
+
reflectCatRows(list, [k]);
|
|
658
|
+
}
|
|
659
|
+
const navMaster = ctx.$('#navsound-master');
|
|
660
|
+
const state = ctx.getAudioCat();
|
|
661
|
+
if (navMaster && state)
|
|
662
|
+
navMaster.value = String(navMasterVolume(state, NAV_CATS));
|
|
663
|
+
refreshMarks();
|
|
664
|
+
}
|
|
665
|
+
return { renderAudio, reflectBlindMode: drawBlindMode, reflectTts: voice.reflectTts, reflectCategory };
|
|
598
666
|
}
|