@the-inclusionist/engine 10.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 +32 -1
- package/app/css/style.css +216 -42
- 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 +11 -1175
- package/dist-pkg/boot/create-game.d.ts +84 -33
- package/dist-pkg/boot/create-game.js +708 -307
- package/dist-pkg/core/a11y-sr.d.ts +1 -1
- package/dist-pkg/core/a11y-sr.js +3 -5
- package/dist-pkg/core/accommodations.d.ts +26 -8
- package/dist-pkg/core/accommodations.js +27 -10
- package/dist-pkg/core/actions.d.ts +39 -10
- package/dist-pkg/core/actions.js +28 -6
- package/dist-pkg/core/contract.d.ts +0 -2
- package/dist-pkg/core/contract.js +0 -2
- package/dist-pkg/core/i18n.d.ts +36 -71
- package/dist-pkg/core/i18n.js +131 -198
- package/dist-pkg/core/session-clock.d.ts +23 -0
- package/dist-pkg/core/session-clock.js +37 -0
- package/dist-pkg/i18n/en.js +44 -49
- package/dist-pkg/i18n/es.js +44 -49
- package/dist-pkg/i18n/pt.js +49 -54
- package/dist-pkg/input/gamepad.d.ts +42 -11
- package/dist-pkg/input/gamepad.js +50 -15
- 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 +0 -4
- package/dist-pkg/input/keyboard.d.ts +0 -1
- package/dist-pkg/input/keydown.d.ts +5 -11
- package/dist-pkg/input/keydown.js +3 -3
- package/dist-pkg/input/latch-edge.d.ts +10 -2
- package/dist-pkg/input/latch-edge.js +4 -3
- package/dist-pkg/input/latch-scope.d.ts +27 -22
- package/dist-pkg/input/latch-scope.js +25 -37
- package/dist-pkg/input/latch-store.d.ts +9 -12
- package/dist-pkg/input/latch-store.js +13 -18
- package/dist-pkg/input/latch-sync.d.ts +5 -4
- package/dist-pkg/input/latch-sync.js +5 -5
- package/dist-pkg/input/pad-reading.js +7 -2
- package/dist-pkg/input/pad-wizard.d.ts +4 -1
- package/dist-pkg/input/pad-wizard.js +60 -9
- package/dist-pkg/input/switch-scan.d.ts +29 -12
- package/dist-pkg/input/switch-scan.js +15 -2
- package/dist-pkg/input/touch-bindings.d.ts +3 -6
- package/dist-pkg/input/touch.d.ts +17 -2
- package/dist-pkg/input/touch.js +55 -17
- package/dist-pkg/input/transport-in-use.d.ts +5 -5
- package/dist-pkg/input/transport-in-use.js +5 -5
- package/dist-pkg/input/transports.d.ts +0 -9
- package/dist-pkg/input/transports.js +1 -1
- package/dist-pkg/input/virtual-controller.d.ts +39 -3
- package/dist-pkg/input/virtual-controller.js +32 -4
- package/dist-pkg/input/voice-map.d.ts +27 -11
- package/dist-pkg/input/voice-map.js +41 -43
- package/dist-pkg/platform/audio-ambient.d.ts +0 -1
- package/dist-pkg/platform/audio-ambient.js +3 -1
- package/dist-pkg/platform/audio-mixer.js +4 -9
- package/dist-pkg/platform/audio-sonar.d.ts +8 -94
- package/dist-pkg/platform/audio-sonar.js +22 -207
- package/dist-pkg/platform/audio.d.ts +10 -1
- package/dist-pkg/platform/audio.js +35 -2
- 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/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 +46 -0
- package/dist-pkg/platform/heavy-catalogue.js +47 -14
- package/dist-pkg/platform/heavy-mirror.d.ts +3 -9
- package/dist-pkg/platform/heavy-mirror.js +14 -17
- package/dist-pkg/platform/heavy.d.ts +40 -8
- package/dist-pkg/platform/heavy.js +177 -22
- package/dist-pkg/platform/interruptible-speech.d.ts +15 -8
- package/dist-pkg/platform/interruptible-speech.js +37 -13
- package/dist-pkg/platform/listener-scope.js +22 -13
- package/dist-pkg/platform/locale-host.d.ts +7 -3
- package/dist-pkg/platform/locale-host.js +13 -2
- package/dist-pkg/platform/reading-in-worker.d.ts +24 -0
- package/dist-pkg/platform/reading-in-worker.js +38 -0
- package/dist-pkg/platform/reading-model.d.ts +21 -0
- package/dist-pkg/platform/reading-model.js +25 -0
- package/dist-pkg/platform/reading-runtime.js +13 -8
- package/dist-pkg/platform/reading.d.ts +12 -1
- package/dist-pkg/platform/reading.js +9 -1
- package/dist-pkg/platform/speech-recognition.d.ts +10 -1
- package/dist-pkg/platform/speech-recognition.js +29 -9
- package/dist-pkg/platform/speech.d.ts +10 -1
- package/dist-pkg/platform/speech.js +24 -13
- package/dist-pkg/platform/tts.d.ts +37 -4
- package/dist-pkg/platform/tts.js +279 -57
- package/dist-pkg/platform/voice-listener.d.ts +6 -2
- package/dist-pkg/platform/voice-listener.js +1 -1
- package/dist-pkg/platform/voice-plan.d.ts +13 -0
- package/dist-pkg/platform/voice-plan.js +57 -2
- package/dist-pkg/platform/vosk-runtime.d.ts +25 -0
- package/dist-pkg/platform/vosk-runtime.js +59 -1
- package/dist-pkg/platform/vosk-vocabulary.d.ts +6 -0
- package/dist-pkg/platform/vosk-vocabulary.js +178 -0
- package/dist-pkg/render/crt.d.ts +11 -9
- package/dist-pkg/render/crt.js +6 -10
- package/dist-pkg/render/cvd-matrices.d.ts +5 -1
- package/dist-pkg/render/cvd-matrices.js +5 -1
- package/dist-pkg/render/high-contrast.d.ts +1 -2
- package/dist-pkg/render/high-contrast.js +5 -1
- package/dist-pkg/render/viz-axes.d.ts +9 -0
- package/dist-pkg/render/viz-axes.js +15 -2
- package/dist-pkg/ui/aac-sets.d.ts +29 -24
- package/dist-pkg/ui/aac-sets.js +22 -30
- package/dist-pkg/ui/audio-choices.d.ts +10 -5
- package/dist-pkg/ui/audio-choices.js +14 -10
- package/dist-pkg/ui/camera-control.d.ts +47 -0
- package/dist-pkg/ui/camera-control.js +35 -1
- package/dist-pkg/ui/declared-words.d.ts +21 -0
- package/dist-pkg/ui/declared-words.js +66 -0
- package/dist-pkg/ui/drawing-problems.js +4 -3
- package/dist-pkg/ui/eye-control.js +17 -17
- package/dist-pkg/ui/face-control.js +15 -15
- package/dist-pkg/ui/fonts.d.ts +42 -8
- package/dist-pkg/ui/fonts.js +58 -24
- 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 +14 -9
- package/dist-pkg/ui/game-options.js +41 -21
- package/dist-pkg/ui/hand-control.js +15 -15
- package/dist-pkg/ui/help-panel.d.ts +19 -8
- package/dist-pkg/ui/help-panel.js +15 -5
- package/dist-pkg/ui/hud-bands.d.ts +29 -12
- package/dist-pkg/ui/hud-bands.js +63 -25
- 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 +0 -3
- package/dist-pkg/ui/layout.js +5 -2
- 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/menu-intent.d.ts +29 -0
- package/dist-pkg/ui/menu-intent.js +35 -1
- package/dist-pkg/ui/menu-items.d.ts +10 -0
- package/dist-pkg/ui/menu-items.js +29 -0
- package/dist-pkg/ui/menu-nav.d.ts +40 -4
- package/dist-pkg/ui/menu-nav.js +132 -47
- package/dist-pkg/ui/panel-widgets.d.ts +34 -0
- package/dist-pkg/ui/panel-widgets.js +86 -0
- package/dist-pkg/ui/pause-icons.d.ts +30 -14
- package/dist-pkg/ui/pause-icons.js +106 -45
- package/dist-pkg/ui/scan-overlay.d.ts +3 -3
- package/dist-pkg/ui/scan-overlay.js +11 -3
- 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 +3 -1
- package/dist-pkg/ui/settings-aac.js +32 -13
- package/dist-pkg/ui/settings-audio.d.ts +10 -5
- package/dist-pkg/ui/settings-audio.js +101 -35
- package/dist-pkg/ui/settings-controls.d.ts +0 -4
- package/dist-pkg/ui/settings-mobility.d.ts +3 -6
- package/dist-pkg/ui/settings-mobility.js +4 -46
- package/dist-pkg/ui/settings-motion.js +2 -2
- package/dist-pkg/ui/settings-panel.d.ts +10 -0
- package/dist-pkg/ui/settings-panel.js +25 -1
- package/dist-pkg/ui/settings-typo.d.ts +5 -6
- package/dist-pkg/ui/settings-typo.js +20 -11
- package/dist-pkg/ui/settings-visual.js +2 -2
- package/dist-pkg/ui/shell.d.ts +1 -3
- package/dist-pkg/ui/shell.js +8 -4
- package/dist-pkg/ui/title.d.ts +2 -11
- package/dist-pkg/ui/title.js +39 -6
- package/dist-pkg/ui/top-band.d.ts +5 -3
- package/dist-pkg/ui/top-band.js +21 -25
- package/dist-pkg/ui/typo-choices.d.ts +3 -3
- package/dist-pkg/ui/typo-choices.js +9 -8
- package/dist-pkg/ui/vlibras.d.ts +71 -27
- package/dist-pkg/ui/vlibras.js +116 -44
- package/dist-pkg/ui/voice-control.d.ts +23 -1
- package/dist-pkg/ui/voice-control.js +149 -16
- package/dist-pkg/ui/voice-settings.d.ts +2 -3
- package/dist-pkg/ui/voice-settings.js +53 -3
- package/dist-pkg/ui/where-the-child-is.js +2 -2
- package/docs/CREDITS.md +122 -10
- package/docs/LICENSES.md +36 -7
- package/package.json +34 -9
- 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 +284 -40
- 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/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 +35 -21
- package/app/public/vendor/fonts/bitter-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/bitter-var.woff2 +0 -0
- package/app/public/vendor/fonts/bodonimoda-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/bodonimoda-var.woff2 +0 -0
- package/app/public/vendor/fonts/cat-abril_fatface-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-abril_fatface-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-adlam_display-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-adlam_display-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-agu_display-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-agu_display-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-alfa_slab_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-alfa_slab_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-are_you_serious-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-are_you_serious-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-asimovian-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-asimovian-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-atkinson_hyperlegible_next-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-atkinson_hyperlegible_next-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bakbak_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bakbak_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bangers-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bangers-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-barriecito-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-barriecito-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-berkshire_swash-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-berkshire_swash-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bevan-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bevan-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bitcount-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bitcount-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bowlby_one_sc-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bowlby_one_sc-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bungee_shade-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-bungee_shade-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-charm-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-charm-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-charm-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-charm-700.woff2 +0 -0
- package/app/public/vendor/fonts/cat-cherry_bomb_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-cherry_bomb_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-chokokutai-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-chokokutai-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-chonburi-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-chonburi-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-climate_crisis-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-climate_crisis-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-cookie-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-dela_gothic_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-dela_gothic_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-fascinate_inline-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-fascinate_inline-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ga_maamli-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ga_maamli-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-itim-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-itim-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-jacquard_24-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-jacquard_24-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-kablammo-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-kablammo-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-kalam-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-kalam-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-kalam-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-kalam-700.woff2 +0 -0
- package/app/public/vendor/fonts/cat-katibeh-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-katibeh-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-lemonada-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-lemonada-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-lilita_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-lilita_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-lobster-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-lobster-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-luckiest_guy-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-luckiest_guy-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-marhey-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-marhey-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-modak-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-modak-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-momo_signature-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-momo_signature-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-monofett-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-monofett-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-monomakh-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-monomakh-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-monoton-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-monoton-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-nerko_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-nerko_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_adlam-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_adlam-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_arabic-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_arabic-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_devanagari-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_devanagari-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_hebrew-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_hebrew-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_jp-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_jp-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_khmer-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_khmer-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_kr-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_kr-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_sc-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_sc-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_thai-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-noto_sans_thai-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ole-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ole-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-pacifico-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-pacifico-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-patrick_hand-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-patrick_hand-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-petit_formal_script-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-petit_formal_script-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-pixelify_sans-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-pixelify_sans-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-plaster-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-plaster-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_ar_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_at-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_at_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_nsw-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_nsw_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_qld-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_qld_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_sa-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_sa_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_tas-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_tas_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_vic-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_au_vic_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_be_vlg-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_be_vlg_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_be_wal-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_be_wal_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_br_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_ca_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_cl_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_co_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_cu_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_cz-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_cz_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_de_grund-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_de_grund_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_de_la-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_de_la_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_de_sas-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_de_sas_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_de_va-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_de_va_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_dk_loopet-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_dk_loopet_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_dk_uloopet-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_dk_uloopet_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_es_deco_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_es_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_fr_moderne-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_fr_moderne_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_fr_trad-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_fr_trad_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_gb_j_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_gb_s_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_hr-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_hr_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_hr_lijeva-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_hr_lijeva_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_hu-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_hu_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_id-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_id_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_ie-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_ie_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_in-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_in_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_is-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_is_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_it_moderna-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_it_moderna_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_it_trad-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_it_trad_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_mx_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_ng_modern-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_ng_modern_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_nl-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_nl_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_no-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_no_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_nz-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_nz_basic-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_nz_basic_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_nz_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_pe_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_pl-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_pl_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_pt_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_ro-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_ro_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_sk-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_sk_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_tz-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_tz_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_us_modern_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_us_trad_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_vn-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_vn_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_za-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-playwrite_za_guides-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-potta_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-potta_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ranchers-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ranchers-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ranga-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ranga-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ranga-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ranga-700.woff2 +0 -0
- package/app/public/vendor/fonts/cat-risque-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-risque-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-rowdies-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-rowdies-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-rowdies-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-rowdies-700.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ruge_boogie-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ruge_boogie-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ruslan_display-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-ruslan_display-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sacramento-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sacramento-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sancreek-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sancreek-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sankofa_display-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sankofa_display-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sedgwick_ave_display-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sedgwick_ave_display-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-shantell_sans-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-shantell_sans-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-smokum-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-smokum-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-solitreo-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-solitreo-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-spicy_rice-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-spicy_rice-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sriracha-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-sriracha-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-srisakdi-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-srisakdi-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-srisakdi-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-srisakdi-700.woff2 +0 -0
- package/app/public/vendor/fonts/cat-stix_two_math-400-0.woff2 +0 -0
- package/app/public/vendor/fonts/cat-tillana-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-tillana-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-tillana-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-tillana-700.woff2 +0 -0
- package/app/public/vendor/fonts/cat-titan_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-titan_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-yatra_one-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-yatra_one-400.woff2 +0 -0
- package/app/public/vendor/fonts/cat-yomogi-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/cat-yomogi-400.woff2 +0 -0
- package/app/public/vendor/fonts/dmserifdisplay-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/dmserifdisplay-400.woff2 +0 -0
- package/app/public/vendor/fonts/domine-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/domine-var.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/fraunces-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/fraunces-var.woff2 +0 -0
- package/app/public/vendor/fonts/fredoka-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/fredoka-var.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/jakarta-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/jakarta-var.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/lora-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/lora-var.woff2 +0 -0
- package/app/public/vendor/fonts/merriweather-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/merriweather-var.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/notosans-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/notosans-var.woff2 +0 -0
- package/app/public/vendor/fonts/nunito-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/nunito-var.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/playfair-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/playfair-var.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/quicksand-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/quicksand-var.woff2 +0 -0
- package/app/public/vendor/fonts/robotoflex-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/robotoflex-var.woff2 +0 -0
- package/app/public/vendor/fonts/sora-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/sora-var.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/spacegrotesk-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/spacegrotesk-var.woff2 +0 -0
- package/app/public/vendor/fonts/spectral-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/spectral-400.woff2 +0 -0
- package/app/public/vendor/fonts/spectral-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/spectral-700.woff2 +0 -0
- package/app/public/vendor/fonts/teachers-var-ext.woff2 +0 -0
- package/app/public/vendor/fonts/teachers-var.woff2 +0 -0
- package/app/public/vendor/fonts/ubuntu-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/ubuntu-400.woff2 +0 -0
- package/app/public/vendor/fonts/ubuntu-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/ubuntu-700.woff2 +0 -0
- package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
- package/dist-pkg/platform/guide-intensity.d.ts +0 -39
- package/dist-pkg/platform/guide-intensity.js +0 -71
- package/dist-pkg/ui/latch-refusal.d.ts +0 -33
- package/dist-pkg/ui/latch-refusal.js +0 -60
package/dist-pkg/ui/vlibras.js
CHANGED
|
@@ -1,64 +1,136 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Why a delivery that carries no Libras player cannot sign, and the fix — the ONE reason both `NO_INTERPRETER` and the free
|
|
4
|
+
* player give for it, so `problems` says the same thing whichever of them answered.
|
|
5
5
|
*/
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
export const LIBRAS_NOT_INSTALLED = 'a Libras player is not installed in this delivery — build it with `inclusionist-heavy <folder> --libras`';
|
|
7
|
+
/** The interpreter of a delivery that carries no Libras player (ADR-0234): it signs nothing and says why, and how to fix it. */
|
|
8
|
+
export const NO_INTERPRETER = {
|
|
9
|
+
sign: () => Promise.resolve({ signed: false, reason: LIBRAS_NOT_INSTALLED }),
|
|
10
|
+
hide: () => { },
|
|
11
|
+
dispose: () => { },
|
|
12
|
+
};
|
|
13
|
+
/** The line `problems` gets when the interpreter cannot sign: the subject, the child's cost and the fix (ADR-0169). */
|
|
14
|
+
export function signingUnavailableLine(reason) {
|
|
15
|
+
return `deaf mode's sign-language interpreter could not sign: ${reason}. A deaf child keeps the captions and the written `
|
|
16
|
+
+ 'text, but nothing is signed when she presses the sonar — give deaf mode a Libras player that can sign here (ADR-0234)';
|
|
17
|
+
}
|
|
18
|
+
/** The line `problems` gets when the interpreter signed a text but left part of it out: what, the child's cost, the fix. */
|
|
19
|
+
export function signedInPartLine(unsigned) {
|
|
20
|
+
return `deaf mode's sign-language interpreter signed only part of what the sonar found: ${unsigned}. A deaf child reads `
|
|
21
|
+
+ 'those words in the captions and is not signed them — give the player the signs or the letters it lacks (ADR-0234)';
|
|
22
|
+
}
|
|
23
|
+
/** Builds a deaf mode. The stored choice is read here, at build, never at import (ADR-0232). */
|
|
24
|
+
export function createDeafMode({ store, captionsSetting, t, interpreter, speak, caption, tell, report }) {
|
|
25
|
+
let on = store.getBool('incl_libras', false);
|
|
26
|
+
let disposed = false;
|
|
27
|
+
/** Whether the child has been told, since the mode last came on, that signing is unavailable — told once, not per press. */
|
|
28
|
+
let told = false;
|
|
29
|
+
/** The lines already in `problems`: a child pressing the sonar twenty times is one diagnosis, not twenty. */
|
|
30
|
+
const reported = new Set();
|
|
9
31
|
const changeListeners = new Set();
|
|
10
|
-
|
|
11
|
-
|
|
32
|
+
/**
|
|
33
|
+
* The answer to one request, heard only while the mode that asked is still on and the root still alive. The first «no»
|
|
34
|
+
* is told through the announcer AND written after the sonar's text: she is deaf, and the announcer alone is heard.
|
|
35
|
+
*/
|
|
36
|
+
const reportOnce = (line) => { if (!reported.has(line)) {
|
|
37
|
+
reported.add(line);
|
|
38
|
+
report(line);
|
|
39
|
+
} };
|
|
40
|
+
/** Signed, perhaps in part: what was left out is a diagnosis, not a notice — the child saw the rest signed. */
|
|
41
|
+
const signedPart = (unsigned) => { if (unsigned)
|
|
42
|
+
reportOnce(signedInPartLine(unsigned)); };
|
|
43
|
+
const heard = (text, result) => {
|
|
44
|
+
if (disposed || !on)
|
|
12
45
|
return;
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
next = text;
|
|
46
|
+
if (result.signed) {
|
|
47
|
+
signedPart(result.unsigned);
|
|
16
48
|
return;
|
|
17
49
|
}
|
|
18
|
-
|
|
19
|
-
if (
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
|
|
50
|
+
reportOnce(signingUnavailableLine(result.reason));
|
|
51
|
+
if (told)
|
|
52
|
+
return;
|
|
53
|
+
told = true;
|
|
54
|
+
const notice = t('sr.deaf.noSigning');
|
|
55
|
+
tell(notice);
|
|
56
|
+
caption(`${text} ${notice}`);
|
|
57
|
+
};
|
|
58
|
+
/** One request signed: captioned when its turn comes, so the caption and the hands say the same thing; ends when it is heard. */
|
|
59
|
+
const signOne = (text) => {
|
|
60
|
+
caption(text);
|
|
61
|
+
let answer;
|
|
26
62
|
try {
|
|
27
|
-
|
|
63
|
+
answer = interpreter.sign(text);
|
|
64
|
+
}
|
|
65
|
+
catch (failure) {
|
|
66
|
+
answer = Promise.reject(failure);
|
|
28
67
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
68
|
+
return answer.then((result) => { heard(text, result); }, (failure) => {
|
|
69
|
+
heard(text, { signed: false, reason: failure instanceof Error ? failure.message : String(failure) });
|
|
70
|
+
});
|
|
32
71
|
};
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
72
|
+
/** The players' requests waiting behind the one being signed, at most one per player, in the order they asked. */
|
|
73
|
+
const waiting = [];
|
|
74
|
+
/** Whose request is being signed, when it is a player's; `null` when none is, or when the one being signed named no player. */
|
|
75
|
+
let seatSigning = null;
|
|
76
|
+
/** Bumped by every request that takes the line, so one that ends after being overtaken does not sign the next in line. */
|
|
77
|
+
let line = 0;
|
|
78
|
+
/** Ends the line: nothing waits and nobody is being signed (the mode went off, or the root went away). */
|
|
79
|
+
const endLine = () => { line++; waiting.length = 0; seatSigning = null; };
|
|
80
|
+
const signInTurn = async (seat, text) => {
|
|
81
|
+
const mine = ++line;
|
|
82
|
+
for (let next = { seat, text }; next; next = waiting.shift()) {
|
|
83
|
+
seatSigning = next.seat;
|
|
84
|
+
await signOne(next.text);
|
|
85
|
+
if (mine !== line)
|
|
86
|
+
return; // overtaken: the line is the newer request's now
|
|
45
87
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
88
|
+
seatSigning = null;
|
|
89
|
+
};
|
|
90
|
+
const toggle = () => {
|
|
91
|
+
on = !on;
|
|
92
|
+
store.setBool('incl_libras', on);
|
|
93
|
+
if (on)
|
|
94
|
+
told = false;
|
|
95
|
+
else {
|
|
96
|
+
endLine();
|
|
97
|
+
interpreter.hide();
|
|
51
98
|
}
|
|
52
99
|
for (const fn of [...changeListeners])
|
|
53
100
|
fn();
|
|
54
|
-
|
|
55
|
-
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* THE INTERPRETER TAKES TURNS, as the narration does (ADR-0234, errata 2026-09-26 — the Dev: «Sim»): a request from ANOTHER
|
|
104
|
+
* player waits until the one being signed ends — replacing that player's own waiting one, in its place — and a player's newer
|
|
105
|
+
* request replaces that player's own, at once, the others waiting behind it. A request that names no player takes the line.
|
|
106
|
+
*/
|
|
107
|
+
const sonar = (text, seat) => {
|
|
108
|
+
if (!on) {
|
|
109
|
+
speak(text, seat);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
if (seat !== undefined && seatSigning !== null && seat !== seatSigning) {
|
|
113
|
+
const own = waiting.find((w) => w.seat === seat);
|
|
114
|
+
if (own)
|
|
115
|
+
own.text = text;
|
|
116
|
+
else
|
|
117
|
+
waiting.push({ seat, text });
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (seat === undefined || seatSigning === null)
|
|
121
|
+
waiting.length = 0;
|
|
122
|
+
void signInTurn(seat ?? null, text);
|
|
56
123
|
};
|
|
57
124
|
return {
|
|
58
|
-
|
|
125
|
+
isOn: () => on,
|
|
59
126
|
toggle,
|
|
60
|
-
|
|
61
|
-
|
|
127
|
+
captionsOn: () => on || captionsSetting(),
|
|
128
|
+
sonar,
|
|
62
129
|
onChange: (fn) => { changeListeners.add(fn); return () => { changeListeners.delete(fn); }; },
|
|
130
|
+
dispose: () => {
|
|
131
|
+
disposed = true;
|
|
132
|
+
endLine();
|
|
133
|
+
interpreter.dispose();
|
|
134
|
+
},
|
|
63
135
|
};
|
|
64
136
|
}
|
|
@@ -13,8 +13,18 @@ export interface VoiceControlDeps {
|
|
|
13
13
|
/** The child's language (`core/i18n.bcp47`): it chooses the model AND the words. */
|
|
14
14
|
readonly language: () => string;
|
|
15
15
|
readonly controller: VirtualController;
|
|
16
|
-
/** The names the open menu is showing right now, if any (ADR-0194). */
|
|
16
|
+
/** The names the open menu is showing right now, if any (ADR-0194) — `ui/menu-nav.itemNames`. */
|
|
17
17
|
readonly menuWords: () => readonly string[];
|
|
18
|
+
/**
|
|
19
|
+
* Puts the open menu's cursor on the item with this name, WITHOUT activating it — `ui/menu-nav.pointAt`. REQUIRED: it is
|
|
20
|
+
* what makes a name heard reach its item. `false` when that item is no longer there; then nothing is confirmed.
|
|
21
|
+
*/
|
|
22
|
+
readonly pointAt: (name: string) => boolean;
|
|
23
|
+
/**
|
|
24
|
+
* ONE BUTTON ONLY is on (ADR-0218 §4): whatever the child says is her switch, one press the controller takes as «take the one
|
|
25
|
+
* shown» — so a name heard is not a place and nothing is pointed at. REQUIRED: without it a name is a cursor move AND a take.
|
|
26
|
+
*/
|
|
27
|
+
readonly oneButtonOnly: () => boolean;
|
|
18
28
|
readonly say: (text: string) => void;
|
|
19
29
|
readonly alert: (text: string) => void;
|
|
20
30
|
readonly report: (line: string) => void;
|
|
@@ -23,6 +33,8 @@ export interface VoiceControlDeps {
|
|
|
23
33
|
readonly after: (fn: () => void, ms: number) => void;
|
|
24
34
|
readonly hasFile: VoskDeps['hasFile'];
|
|
25
35
|
readonly loadBundle: VoskDeps['loadBundle'];
|
|
36
|
+
/** The host's `fetch`, handed on to read the loaded model's vocabulary (ADR-0194 §4). Without it an unsayable name goes unreported. */
|
|
37
|
+
readonly fetch?: VoskDeps['fetch'];
|
|
26
38
|
readonly getUserMedia: VoiceListenerDeps['getUserMedia'];
|
|
27
39
|
readonly createContext: VoiceListenerDeps['createContext'];
|
|
28
40
|
readonly loadRuntime?: (deps: VoskDeps) => Promise<VoskLoad>;
|
|
@@ -42,5 +54,15 @@ export interface VoiceControl extends SwitchableControl {
|
|
|
42
54
|
* because the next start already reads the new language.
|
|
43
55
|
*/
|
|
44
56
|
languageChanged(): Promise<void>;
|
|
57
|
+
/**
|
|
58
|
+
* LENDS THIS EAR TO A CHOICE AMONG THE GAME'S OPTIONS (ADR-0256): while it is listening in `language`, the grammar becomes
|
|
59
|
+
* `grammar` and every text heard goes to `onHeard` instead of being obeyed; `stop()` gives the menu's grammar back. `null` when
|
|
60
|
+
* it is not listening, or listens in another language — then the choice opens an ear of its own.
|
|
61
|
+
* 📏 Without it every choice paused this recogniser and opened another: two workers and two microphones per press, 40 MiB of
|
|
62
|
+
* model unpacked each time, measured on 2026-09-27 — and the Dev's machine froze.
|
|
63
|
+
*/
|
|
64
|
+
borrow(language: string, grammar: readonly string[], onHeard: (text: string, final: boolean) => void): {
|
|
65
|
+
stop(): Promise<void>;
|
|
66
|
+
} | null;
|
|
45
67
|
}
|
|
46
68
|
export declare function createVoiceControl(d: VoiceControlDeps): VoiceControl;
|
|
@@ -9,14 +9,29 @@
|
|
|
9
9
|
// 📌 THE GRAMMAR FOLLOWS THE MENU (ADR-0194): saying the name of an item activates it, so the words the open menu is showing
|
|
10
10
|
// join the closed grammar while it is open and leave with it.
|
|
11
11
|
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// a child
|
|
12
|
+
// 🎯 A NAME HEARD IS A CURSOR AND A CONFIRM, NOT A CLICK (ADR-0194 §2): the root's menu navigation puts the cursor on the
|
|
13
|
+
// named item (`pointAt`), and the confirm position is pressed on the virtual controller like any other spoken word — so the
|
|
14
|
+
// item is activated by the same path, with the same spoken feedback, as a child confirming it with the cursor on it. WHEN a
|
|
15
|
+
// name fires is the reader's (`input/voice-map` over `platform/speech-recognition`): at once, unless another item's name
|
|
16
|
+
// continues it («voltar» / «voltar ao jogo»), which waits for the end of the utterance (§3). A LOCKED item's name is heard
|
|
17
|
+
// too and takes the same path: the confirm reaches the item's own press, which says its reason and does nothing (§5, ADR-0161).
|
|
18
|
+
// A name with a word the loaded model lacks cannot be heard at all, and is a line of `problems` (§4).
|
|
19
|
+
// ⚠️ EXCEPT WITH ONE BUTTON ONLY ON (ADR-0218 §4): then every word heard, a name included, is the child's switch and nothing more
|
|
20
|
+
// — one press, which the controller takes as «take the one shown», and no cursor put anywhere first.
|
|
21
|
+
//
|
|
22
|
+
// ⚠️ A SPOKEN COMMAND IS A TAP, NOT A HOLD. The word arrives, the position is pressed and let go — one press, one release.
|
|
23
|
+
// Whether that tap keeps a direction going is the latch's, and on speech the latch starts as the GAME answers `holdsKeys()`
|
|
24
|
+
// (ADR-0249, resolved by the root when the press arrives): a platform game keeps walking on «direita», a quiz moves once on
|
|
25
|
+
// «abaixo», and the child may change either for her voice.
|
|
15
26
|
import { voiceGrammar, createVoiceCommands } from '../input/voice-map.js';
|
|
16
27
|
import { loadVoskRuntime } from '../platform/vosk-runtime.js';
|
|
17
28
|
import { startVoiceListening } from '../platform/voice-listener.js';
|
|
29
|
+
import { spokenText } from '../platform/speech-recognition.js';
|
|
30
|
+
import { MENU_CONFIRM } from './menu-intent.js';
|
|
18
31
|
/** How long a spoken position stays pressed. The same pulse the scan uses: long enough for a game to see a press and a release. */
|
|
19
32
|
export const VOICE_PULSE_MS = 400;
|
|
33
|
+
/** The base of a language tag: `en-US` and `en` are the same model. */
|
|
34
|
+
const baseOf = (tag) => tag.split('-')[0].toLowerCase();
|
|
20
35
|
export function createVoiceControl(d) {
|
|
21
36
|
const { t } = d;
|
|
22
37
|
const loadRuntime = d.loadRuntime ?? loadVoskRuntime;
|
|
@@ -25,15 +40,77 @@ export function createVoiceControl(d) {
|
|
|
25
40
|
let on = false, starting = null;
|
|
26
41
|
let listener = null;
|
|
27
42
|
let commands = null;
|
|
28
|
-
|
|
43
|
+
let grammarGiven = '';
|
|
44
|
+
/** The words the model now listening knows, once read (`platform/vosk-vocabulary`); `null` while unknown. */
|
|
45
|
+
let vocabulary = null;
|
|
46
|
+
/** Bumped by every stop, so a vocabulary that arrives for a recogniser already replaced is dropped. */
|
|
47
|
+
let generation = 0;
|
|
48
|
+
/**
|
|
49
|
+
* 🔴 THE LANGUAGE THE LISTENING STARTED IN, read ONCE. The model and the grammar must come from the same reading: `start()` used
|
|
50
|
+
* to read the language again after the model had loaded, and a page booting in English (pt-BR until its stored language
|
|
51
|
+
* applies) opened the Portuguese model with the English grammar — 15 «missing in vocabulary», measured on 2026-09-27.
|
|
52
|
+
*/
|
|
53
|
+
let listeningLanguage = '';
|
|
54
|
+
/** A choice among the game's options borrowing this ear (ADR-0256): what is heard goes to it, and no command is obeyed. */
|
|
55
|
+
let lent = null;
|
|
56
|
+
/** The open menu's names that can enter the grammar. A one-letter name stays out: in a closed grammar every short noise lands on it. */
|
|
57
|
+
const sayableNames = () => d.menuWords().filter((n) => spokenText(n).length > 1);
|
|
58
|
+
/**
|
|
59
|
+
* 🔴 A NAME THE MODEL CANNOT HEAR IS SAID TO THE ADULT (ADR-0194 §4, ADR-0169): Kaldi drops a grammar word its vocabulary lacks,
|
|
60
|
+
* in silence, and the item goes mute for the child — she can still walk to it with the direction words. One line per item and
|
|
61
|
+
* language, naming the words, the cost and the fix. Nothing is said while the vocabulary is unknown: no line beats a false one.
|
|
62
|
+
*/
|
|
63
|
+
const reportUnsayable = (names) => {
|
|
64
|
+
const known = vocabulary;
|
|
65
|
+
if (!known)
|
|
66
|
+
return;
|
|
67
|
+
const language = listeningLanguage;
|
|
68
|
+
for (const name of names) {
|
|
69
|
+
const lacking = [...new Set(spokenText(name).split(' '))].filter((w) => w && !known.has(w));
|
|
70
|
+
const kind = `unsayable:${language}:${name}`;
|
|
71
|
+
if (!lacking.length || said.has(kind))
|
|
72
|
+
continue;
|
|
73
|
+
said.add(kind);
|
|
74
|
+
d.report(`voice control: the menu item "${name}" has ${lacking.length === 1 ? 'a word' : 'words'} the ${language} speech `
|
|
75
|
+
+ `model does not know (${lacking.map((w) => `"${w}"`).join(', ')}) — the child cannot choose this item by saying its name, `
|
|
76
|
+
+ `only by walking to it with the direction words; reword the item's label in the ${language} dictionary with words the `
|
|
77
|
+
+ 'model knows (ADR-0194 §4)');
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
/** The open menu's names, handed to the reader and checked against the model, and the grammar with them. */
|
|
81
|
+
const followMenu = () => {
|
|
82
|
+
const names = sayableNames();
|
|
83
|
+
commands?.items(names);
|
|
84
|
+
reportUnsayable(names);
|
|
85
|
+
return voiceGrammar(listeningLanguage, names);
|
|
86
|
+
};
|
|
29
87
|
const command = (action) => {
|
|
30
88
|
d.controller.press(action, 'fala');
|
|
31
89
|
d.after(() => d.controller.release(action, 'fala'), VOICE_PULSE_MS);
|
|
32
90
|
};
|
|
91
|
+
/*
|
|
92
|
+
* 🔴 ONE WORD HEARD IS ONE ACTION (ADR-0111 errata, ADR-0218 §4). With one button only on, the controller takes every press as
|
|
93
|
+
* «take the one shown», so a word may do nothing BESIDES its press: asked here, first, for every word — a position, a name, any
|
|
94
|
+
* kind a later reader adds. 📏 Before, a name was still pointed at: with the chip on «próximo» the cursor jumped to the named
|
|
95
|
+
* item and then stepped on from it; on «cancelar» the press meant nothing and the cursor had moved anyway.
|
|
96
|
+
*/
|
|
97
|
+
const obey = (heard) => {
|
|
98
|
+
for (const c of heard ?? []) {
|
|
99
|
+
if (d.oneButtonOnly())
|
|
100
|
+
command(c.kind === 'position' ? c.action : MENU_CONFIRM);
|
|
101
|
+
else if (c.kind === 'position')
|
|
102
|
+
command(c.action);
|
|
103
|
+
else if (d.pointAt(c.name))
|
|
104
|
+
command(MENU_CONFIRM);
|
|
105
|
+
}
|
|
106
|
+
};
|
|
33
107
|
const stop = () => {
|
|
34
108
|
const going = listener;
|
|
35
109
|
listener = null;
|
|
36
110
|
commands = null;
|
|
111
|
+
lent = null;
|
|
112
|
+
vocabulary = null;
|
|
113
|
+
generation += 1;
|
|
37
114
|
void going?.stop();
|
|
38
115
|
};
|
|
39
116
|
/**
|
|
@@ -60,7 +137,9 @@ export function createVoiceControl(d) {
|
|
|
60
137
|
let load;
|
|
61
138
|
const language = d.language();
|
|
62
139
|
try {
|
|
63
|
-
load = await loadRuntime({
|
|
140
|
+
load = await loadRuntime({
|
|
141
|
+
base: d.base, language, hasFile: d.hasFile, loadBundle: d.loadBundle, ...(d.fetch ? { fetch: d.fetch } : {}),
|
|
142
|
+
});
|
|
64
143
|
}
|
|
65
144
|
catch (e) {
|
|
66
145
|
failed('runtime', `voice control: the recogniser did not open (${e instanceof Error ? e.message : String(e)}) — the child `
|
|
@@ -69,26 +148,40 @@ export function createVoiceControl(d) {
|
|
|
69
148
|
}
|
|
70
149
|
if (!load.ok) {
|
|
71
150
|
/*
|
|
72
|
-
* 📌 ONE LINE PER LANGUAGE, NAMING THAT LANGUAGE'S FIX (ADR-0225, ADR-0169). The
|
|
73
|
-
*
|
|
74
|
-
* the
|
|
151
|
+
* 📌 ONE LINE PER LANGUAGE, NAMING THAT LANGUAGE'S FIX (ADR-0225 and its erratum, ADR-0169). The delivery carries every
|
|
152
|
+
* language unless its `--commands` list narrowed it, and the install fetches every one the delivery carries — so a model
|
|
153
|
+
* missing here was left out of the delivery or has not come down yet, and «open it online» alone sends the adult after a
|
|
154
|
+
* download that cannot happen in the first case. The reading's line names its two fixes the same way.
|
|
75
155
|
*/
|
|
76
156
|
const lang = language.split('-')[0].toLowerCase();
|
|
77
157
|
failed(`files:${lang}`, `voice control: ${load.missing.join(', ')} not on this device for ${language} — the child cannot play `
|
|
78
|
-
+ `by speaking in this language; build the delivery with \`npx inclusionist-heavy
|
|
79
|
-
+
|
|
158
|
+
+ `by speaking in this language; build the delivery with \`npx inclusionist-heavy\` without \`--commands\` (it carries every `
|
|
159
|
+
+ `language) or with \`--commands ${lang}\` in its list, and open the game once online so the install fetches them`, t('sr.voice.needsInternet'));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
// the language changed while the model loaded (a page booting before its stored language applies): start again in it
|
|
163
|
+
if (baseOf(d.language()) !== baseOf(language)) {
|
|
164
|
+
await start();
|
|
80
165
|
return;
|
|
81
166
|
}
|
|
82
|
-
|
|
167
|
+
listeningLanguage = language;
|
|
168
|
+
commands = createVoiceCommands(language);
|
|
169
|
+
const grammar = followMenu();
|
|
170
|
+
grammarGiven = grammar.join('\n');
|
|
83
171
|
try {
|
|
84
172
|
listener = await listen({
|
|
85
173
|
model: load.model,
|
|
86
|
-
grammar
|
|
174
|
+
grammar,
|
|
87
175
|
getUserMedia: d.getUserMedia,
|
|
88
176
|
createContext: d.createContext,
|
|
89
|
-
onPartial: (text) => {
|
|
90
|
-
|
|
91
|
-
|
|
177
|
+
onPartial: (text) => { if (lent)
|
|
178
|
+
lent(text, false);
|
|
179
|
+
else
|
|
180
|
+
obey(commands?.partial(text)); },
|
|
181
|
+
onFinal: (text) => { if (lent)
|
|
182
|
+
lent(text, true);
|
|
183
|
+
else
|
|
184
|
+
obey(commands?.final(text)); },
|
|
92
185
|
});
|
|
93
186
|
}
|
|
94
187
|
catch {
|
|
@@ -102,7 +195,18 @@ export function createVoiceControl(d) {
|
|
|
102
195
|
stop();
|
|
103
196
|
return;
|
|
104
197
|
}
|
|
198
|
+
// a menu that opened or closed while the microphone was opening changed the names after the grammar above was built
|
|
199
|
+
api.refreshGrammar();
|
|
105
200
|
d.say(t('sr.voice.ready'));
|
|
201
|
+
// 📌 THE VOCABULARY IS READ AFTER THE CHILD CAN SPEAK, never before: it is a report for the adult, and reading the model's
|
|
202
|
+
// archive a second time must not delay the first command. It checks the names showing when it arrives and every later menu.
|
|
203
|
+
const mine = generation;
|
|
204
|
+
void load.vocabulary?.().then((words) => {
|
|
205
|
+
if (mine !== generation || !listener)
|
|
206
|
+
return;
|
|
207
|
+
vocabulary = words;
|
|
208
|
+
reportUnsayable(sayableNames());
|
|
209
|
+
});
|
|
106
210
|
};
|
|
107
211
|
const api = {
|
|
108
212
|
async apply(next) {
|
|
@@ -117,7 +221,17 @@ export function createVoiceControl(d) {
|
|
|
117
221
|
await starting;
|
|
118
222
|
},
|
|
119
223
|
refreshGrammar() {
|
|
120
|
-
|
|
224
|
+
// lent to a choice: the options are the grammar until it gives the ear back
|
|
225
|
+
if (!listener || lent)
|
|
226
|
+
return;
|
|
227
|
+
// 📌 A new grammar is a new recogniser (`platform/voice-listener`), and the root asks on every menu change it sees —
|
|
228
|
+
// most of which change no name. Only a grammar that differs is handed on.
|
|
229
|
+
const grammar = followMenu();
|
|
230
|
+
const given = grammar.join('\n');
|
|
231
|
+
if (given === grammarGiven)
|
|
232
|
+
return;
|
|
233
|
+
grammarGiven = given;
|
|
234
|
+
listener.setGrammar(grammar);
|
|
121
235
|
},
|
|
122
236
|
async languageChanged() {
|
|
123
237
|
/*
|
|
@@ -135,9 +249,28 @@ export function createVoiceControl(d) {
|
|
|
135
249
|
// ⚠️ A START IN FLIGHT IS IN THE OLD LANGUAGE. Letting it finish and replacing it costs one opening; skipping it would
|
|
136
250
|
// leave `starting` to install the old model on top of the new one, which is the race the `if (!on)` in `start` exists for.
|
|
137
251
|
await starting;
|
|
252
|
+
// a start that noticed the change itself is already listening in the new language: nothing to redo
|
|
253
|
+
if (listener && baseOf(listeningLanguage) === baseOf(d.language()))
|
|
254
|
+
return;
|
|
138
255
|
stop();
|
|
139
256
|
await api.apply(true);
|
|
140
257
|
},
|
|
258
|
+
borrow(language, grammar, onHeard) {
|
|
259
|
+
if (!listener || lent || baseOf(language) !== baseOf(listeningLanguage))
|
|
260
|
+
return null;
|
|
261
|
+
const held = listener;
|
|
262
|
+
lent = onHeard;
|
|
263
|
+
held.setGrammar(grammar);
|
|
264
|
+
grammarGiven = '';
|
|
265
|
+
return {
|
|
266
|
+
async stop() {
|
|
267
|
+
if (lent !== onHeard)
|
|
268
|
+
return;
|
|
269
|
+
lent = null;
|
|
270
|
+
api.refreshGrammar(); // the menu's grammar back
|
|
271
|
+
},
|
|
272
|
+
};
|
|
273
|
+
},
|
|
141
274
|
};
|
|
142
275
|
return api;
|
|
143
276
|
}
|
|
@@ -47,8 +47,7 @@ export interface VoicePorts {
|
|
|
47
47
|
/** Speak the sample through the BROWSER's own synthesiser — the neural engine speaks through `tts` instead. */
|
|
48
48
|
readonly speakSample: (text: string, chosen: SpeechSynthesisVoice | null) => void;
|
|
49
49
|
/** Called again whenever the browser finishes loading its voice list: they arrive late, and often empty first.
|
|
50
|
-
* ⚠️ The parameter is
|
|
51
|
-
un, which is a GAME ACTION word this layer does not say (the boundary of 2026-09-06). */
|
|
50
|
+
* ⚠️ The parameter is `again` and not `run`, which is a GAME ACTION word this layer does not say (the boundary of 2026-09-06). */
|
|
52
51
|
readonly whenVoicesChange: (again: () => void) => void;
|
|
53
52
|
}
|
|
54
53
|
/**
|
|
@@ -90,7 +89,7 @@ export interface VoiceSettingsCtx {
|
|
|
90
89
|
export interface VoiceSettings {
|
|
91
90
|
/** Everything the section shows, refreshed from the stored state. Called by the panel's own render. */
|
|
92
91
|
render: () => void;
|
|
93
|
-
/** Just the narration switch and the chosen engine. Exported because the
|
|
92
|
+
/** Just the narration switch, its volume and the chosen engine. Exported because the root reflects it from outside. */
|
|
94
93
|
reflectTts: () => void;
|
|
95
94
|
}
|
|
96
95
|
export declare function createVoiceSettings(ctx: VoiceSettingsCtx, ports: VoicePorts): VoiceSettings;
|
|
@@ -34,8 +34,11 @@ function voiceName(v) {
|
|
|
34
34
|
function panelVoiceLabel(v) {
|
|
35
35
|
return (v.recommended ? '❤️ ' : '') + voiceName(v);
|
|
36
36
|
}
|
|
37
|
-
/**
|
|
38
|
-
|
|
37
|
+
/**
|
|
38
|
+
* The rows a missing voice locks (ADR-0185 §4): narration, its volume and rate, the spoken index, the voice — and the «test voice»
|
|
39
|
+
* button a page may carry, which with no voice of the language would ask the browser for one of another (ADR-0243 §3).
|
|
40
|
+
*/
|
|
41
|
+
const SPEECH_ROWS = ['#opt-tts', '#tts-vol', '#tts-ppm', '#opt-menuindex', '#tts-voz', '#opt-tts-test'];
|
|
39
42
|
export function createVoiceSettings(ctx, ports) {
|
|
40
43
|
const { t, bcp47 } = ctx.translator;
|
|
41
44
|
function reflectTts() {
|
|
@@ -46,6 +49,10 @@ export function createVoiceSettings(ctx, ports) {
|
|
|
46
49
|
ctx.toggleBtn(b, on);
|
|
47
50
|
b.textContent = toggleLabel(t, on);
|
|
48
51
|
}
|
|
52
|
+
// and its volume: the slider was never drawn from the mixer, so it opened at the range's middle whatever was stored
|
|
53
|
+
const vol = ctx.$('#tts-vol');
|
|
54
|
+
if (vol && cat?.tts)
|
|
55
|
+
vol.value = String(volPercent(cat.tts.vol));
|
|
49
56
|
const e = ctx.$('#tts-engine');
|
|
50
57
|
if (e)
|
|
51
58
|
e.value = ctx.tts.getEngineSel();
|
|
@@ -95,6 +102,8 @@ export function createVoiceSettings(ctx, ports) {
|
|
|
95
102
|
ctx.tts.setVoiceObj(pick);
|
|
96
103
|
}
|
|
97
104
|
const noVoice = () => !!ctx.tts.voices && ctx.tts.voices().length === 0;
|
|
105
|
+
/** The lock last drawn on the speech rows; `null` before it is first drawn. Compared when the voices change. */
|
|
106
|
+
let lockDrawn = null;
|
|
98
107
|
/** The speech rate list: each step «N PPM», the stored one selected (ADR-0183 §1, ADR-0196). */
|
|
99
108
|
function renderRate() {
|
|
100
109
|
const sel = ctx.$('#tts-ppm');
|
|
@@ -110,11 +119,15 @@ export function createVoiceSettings(ctx, ports) {
|
|
|
110
119
|
}
|
|
111
120
|
sel.value = String(ctx.settings.speechPpm);
|
|
112
121
|
}
|
|
122
|
+
/** The voices of the language as ids, one per line: what the «Voz» list was last drawn from. */
|
|
123
|
+
const voiceIds = () => (ctx.tts.voices?.() ?? []).map((v) => v.voice).join('\n');
|
|
124
|
+
let listDrawn = null;
|
|
113
125
|
function renderVoiceList() {
|
|
114
126
|
const sel = ctx.$('#tts-voz');
|
|
115
127
|
if (!sel || !ctx.tts.voices)
|
|
116
128
|
return;
|
|
117
129
|
const list = ctx.tts.voices();
|
|
130
|
+
listDrawn = voiceIds();
|
|
118
131
|
sel.replaceChildren(); // options built node by node: no markup sink
|
|
119
132
|
if (!list.length) {
|
|
120
133
|
const o = ports.newOption();
|
|
@@ -136,6 +149,7 @@ export function createVoiceSettings(ctx, ports) {
|
|
|
136
149
|
*/
|
|
137
150
|
function lockSpeechRows() {
|
|
138
151
|
const lock = noVoice();
|
|
152
|
+
lockDrawn = lock;
|
|
139
153
|
for (const id of SPEECH_ROWS) {
|
|
140
154
|
const el = ctx.$(id);
|
|
141
155
|
if (!el)
|
|
@@ -284,7 +298,43 @@ export function createVoiceSettings(ctx, ports) {
|
|
|
284
298
|
});
|
|
285
299
|
}
|
|
286
300
|
wireControls();
|
|
287
|
-
|
|
301
|
+
/**
|
|
302
|
+
* A LOCK THAT FLIPS UNDER THE CURSOR IS TOLD, and only there (ADR-0185 erratum 2026-09-26). The footer is written with what
|
|
303
|
+
* focusing the row would show NOW — the reason, or the row's own explanation — because it would otherwise go on saying «no
|
|
304
|
+
* voice» under a row that works. Locking is heard once, through the footer, which is `aria-live`, as when a locked row is
|
|
305
|
+
* focused. Unlocking gets one sentence more: the footer then gives the row's explanation, which does not say the state
|
|
306
|
+
* changed, and whether a removed `aria-disabled` on the focused control is spoken is left to each screen reader.
|
|
307
|
+
*/
|
|
308
|
+
function tellUnderCursor() {
|
|
309
|
+
const el = SPEECH_ROWS.map((id) => ctx.$(id)).find((e) => !!e && e.ownerDocument?.activeElement === e);
|
|
310
|
+
if (!el)
|
|
311
|
+
return;
|
|
312
|
+
const footer = el.closest('.overlay__card')?.querySelector('.opt-explain');
|
|
313
|
+
if (lockDrawn) {
|
|
314
|
+
if (footer)
|
|
315
|
+
footer.textContent = el.dataset.motivo ?? '';
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (footer)
|
|
319
|
+
footer.textContent = el.closest('.ctrl-row')?.dataset.explain || (footer.dataset.idle ?? '');
|
|
320
|
+
ctx.srSay(t('audio.comVoz'));
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* 🔴 THE DEVICE'S VOICES CHANGED (ADR-0185 §4, erratum 2026-09-26): a device may list them a moment after load — empty first,
|
|
324
|
+
* then full — and may lose them. The lock is a fact about those voices, so it is evaluated again here, both ways, IN PLACE:
|
|
325
|
+
* attributes on the same nodes, never a redraw, so the focus stays where the child left it. Only a change is drawn or told:
|
|
326
|
+
* the voice list when its voices differ, the rows and the cursor's row when the lock flips.
|
|
327
|
+
*/
|
|
328
|
+
function voicesChanged() {
|
|
329
|
+
populateTtsVoices();
|
|
330
|
+
if (voiceIds() !== listDrawn)
|
|
331
|
+
renderVoiceList(); // the same voices keep the same options, which the child may be reading
|
|
332
|
+
const before = lockDrawn;
|
|
333
|
+
lockSpeechRows();
|
|
334
|
+
if (lockDrawn !== before)
|
|
335
|
+
tellUnderCursor();
|
|
336
|
+
}
|
|
337
|
+
ports.whenVoicesChange(voicesChanged);
|
|
288
338
|
return {
|
|
289
339
|
render: () => {
|
|
290
340
|
reflectTts();
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import { navigableItems } from './menu-items.js';
|
|
22
22
|
import { announceItem } from './item-announcement.js';
|
|
23
23
|
import { controlParts } from './menu-nav.js';
|
|
24
|
-
import { PM_VISIBLE_ITEMS } from './pause-icons.js';
|
|
24
|
+
import { PM_VISIBLE_ITEMS, LIST_DOOR } from './pause-icons.js';
|
|
25
25
|
import { accessibleLabel } from '../core/accessible-label.js';
|
|
26
26
|
/** Title and item joined the way a screen reader reads them: the place first, then where the cursor is in it. */
|
|
27
27
|
const say = (title, item) => [title, item].filter(Boolean).join('. ');
|
|
@@ -41,7 +41,7 @@ function panelPlace(panel, ctx) {
|
|
|
41
41
|
function cardPlace(card, ctx) {
|
|
42
42
|
const sub = card.querySelector('.pause-menu:not([hidden])')?.dataset.sub ?? 'raiz';
|
|
43
43
|
// a submenu is named by the item that opens it; the root by the card's title
|
|
44
|
-
const door = sub
|
|
44
|
+
const door = LIST_DOOR[sub] ?? null;
|
|
45
45
|
const button = door ? card.querySelector(`.pm-btn[data-act="${door}"]`) : null;
|
|
46
46
|
const title = button ? accessibleLabel(button) : (card.querySelector('h2')?.textContent?.trim() ?? '');
|
|
47
47
|
const items = [...card.querySelectorAll(PM_VISIBLE_ITEMS)];
|