@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
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
export interface FooterScrollHost {
|
|
3
|
+
readonly doc: Pick<Document, 'createRange' | 'createTreeWalker' | 'createElement'>;
|
|
4
|
+
readonly getComputedStyle: (el: Element) => CSSStyleDeclaration;
|
|
5
|
+
readonly MutationObserver: typeof MutationObserver;
|
|
6
|
+
readonly setTimeout: (fn: () => void, ms: number) => number;
|
|
7
|
+
readonly clearTimeout: (id: number) => void;
|
|
8
|
+
/** The child's caption rate (ADR-0183 §4), read at every start. */
|
|
9
|
+
readonly ppm: () => number;
|
|
10
|
+
/** Reduced motion — the system's or the child's — read at every start. */
|
|
11
|
+
readonly reduced: () => boolean;
|
|
12
|
+
}
|
|
13
|
+
/** Starts watching `root`. Returns the way to stop: every animation cancelled and timer cleared, the observer disconnected. */
|
|
14
|
+
export declare function watchFooterScroll(root: HTMLElement, host: FooterScrollHost): () => void;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// ui/footer-scroll-driver — A FOOTER EXPLANATION LONGER THAN ITS LINES MOVES INSIDE THEM (ADR-0245): the DOM half of
|
|
3
|
+
// `ui/footer-scroll`, which holds the pace.
|
|
4
|
+
//
|
|
5
|
+
// Watches the region for the two explanation footers — the screen's band (`.barra-explicacao`) and a panel's
|
|
6
|
+
// (`.opt-explain`). A NEW text, or a footer that comes into view, starts from the top; a text that fits is measured once and
|
|
7
|
+
// left alone, its DOM untouched. The box does not change: `[data-scroll]` swaps the two-line clamp for a block of the same
|
|
8
|
+
// lines (style.css).
|
|
9
|
+
// · GLIDE: the words rise continuously (ADR-0245 erratum, 2026-09-26 — a line at a time was what the Dev rejected). They are
|
|
10
|
+
// moved into one `.footer-scroll-text` block, and a Web Animation translates it: sub-pixel and on the compositor, where
|
|
11
|
+
// `scrollTop` stands on whole pixels. The text node the game wrote is MOVED, not copied, in the same task as the write and
|
|
12
|
+
// before a frame is drawn, and nothing is written into the live region after — narration and the screen reader still get
|
|
13
|
+
// the whole text once (§4).
|
|
14
|
+
// · PAGES (reduced motion): no slide, so `scrollTop` and a timer per page, as the plan says.
|
|
15
|
+
// Pointer over it or focus in it holds the motion where it is (§3, WCAG 2.2.2): the animation paused, or the page timer
|
|
16
|
+
// cleared. A footer that leaves the screen keeps no animation and no timer.
|
|
17
|
+
//
|
|
18
|
+
// 📌 Everything it reaches arrives through the host (ADR-0232): the document, the timers, the styles and the observer.
|
|
19
|
+
import { footerScrollPlan, readLines } from './footer-scroll.js';
|
|
20
|
+
const FOOTERS = '.barra-explicacao, .opt-explain';
|
|
21
|
+
/** The block the words glide in (style.css makes it a block while the footer scrolls). */
|
|
22
|
+
const TEXT = 'footer-scroll-text';
|
|
23
|
+
/** Starts watching `root`. Returns the way to stop: every animation cancelled and timer cleared, the observer disconnected. */
|
|
24
|
+
export function watchFooterScroll(root, host) {
|
|
25
|
+
const seen = new Map();
|
|
26
|
+
const held = new WeakSet();
|
|
27
|
+
const wired = new WeakSet();
|
|
28
|
+
const visible = (el) => el.isConnected && el.getClientRects().length > 0;
|
|
29
|
+
const linesShown = (el) => parseInt(host.getComputedStyle(el).getPropertyValue('--footer-lines'), 10) || 2;
|
|
30
|
+
function wordTops(el) {
|
|
31
|
+
const tops = [];
|
|
32
|
+
const walker = host.doc.createTreeWalker(el, 4); // NodeFilter.SHOW_TEXT
|
|
33
|
+
const range = host.doc.createRange();
|
|
34
|
+
for (let n = walker.nextNode(); n; n = walker.nextNode()) {
|
|
35
|
+
for (const m of (n.textContent ?? '').matchAll(/\S+/g)) {
|
|
36
|
+
range.setStart(n, m.index);
|
|
37
|
+
range.setEnd(n, m.index + m[0].length);
|
|
38
|
+
const box = range.getClientRects()[0];
|
|
39
|
+
if (box)
|
|
40
|
+
tops.push(box.top);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return tops;
|
|
44
|
+
}
|
|
45
|
+
function forget(el) {
|
|
46
|
+
const s = seen.get(el);
|
|
47
|
+
if (s?.timer != null)
|
|
48
|
+
host.clearTimeout(s.timer);
|
|
49
|
+
if (s?.glide) {
|
|
50
|
+
s.glide.anim.onfinish = null;
|
|
51
|
+
s.glide.anim.cancel();
|
|
52
|
+
}
|
|
53
|
+
seen.delete(el);
|
|
54
|
+
delete el.dataset.scroll;
|
|
55
|
+
delete el.dataset.topLine;
|
|
56
|
+
el.scrollTop = 0;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The words inside the one block that glides, MOVED there (never copied: the game's text node stays the one it wrote). The
|
|
60
|
+
* block a footer already has is kept — a restart, or the same text written again, goes on in it.
|
|
61
|
+
*/
|
|
62
|
+
function gather(el, text) {
|
|
63
|
+
let block = text;
|
|
64
|
+
if (!block) {
|
|
65
|
+
const only = el.childNodes.length === 1 ? el.firstElementChild : null;
|
|
66
|
+
if (only instanceof HTMLElement && only.classList.contains(TEXT))
|
|
67
|
+
return only;
|
|
68
|
+
block = host.doc.createElement('span');
|
|
69
|
+
block.className = TEXT;
|
|
70
|
+
}
|
|
71
|
+
const loose = [...el.childNodes].filter((n) => n !== block);
|
|
72
|
+
if (loose.length)
|
|
73
|
+
block.replaceChildren(...loose);
|
|
74
|
+
if (block.parentNode !== el)
|
|
75
|
+
el.append(block);
|
|
76
|
+
return block;
|
|
77
|
+
}
|
|
78
|
+
/** Paces a footer as it is now: held, it stays where it is; free, it goes on from there. */
|
|
79
|
+
function pace(el, s) {
|
|
80
|
+
if (s.glide) {
|
|
81
|
+
if (held.has(el))
|
|
82
|
+
s.glide.anim.pause();
|
|
83
|
+
else
|
|
84
|
+
s.glide.anim.play();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
arm(el, s);
|
|
88
|
+
}
|
|
89
|
+
function glide(el, s, plan) {
|
|
90
|
+
const text = gather(el);
|
|
91
|
+
const rise = `translateY(${-s.lines.starts[plan.toLine]}px)`;
|
|
92
|
+
const total = plan.waitMs + plan.travelMs + plan.holdMs;
|
|
93
|
+
// still while the first view is read, one speed to the last view, still while it is read
|
|
94
|
+
const anim = text.animate([
|
|
95
|
+
{ transform: 'translateY(0)', offset: 0 },
|
|
96
|
+
{ transform: 'translateY(0)', offset: plan.waitMs / total },
|
|
97
|
+
{ transform: rise, offset: (plan.waitMs + plan.travelMs) / total },
|
|
98
|
+
{ transform: rise, offset: 1 },
|
|
99
|
+
], { duration: total, easing: 'linear' });
|
|
100
|
+
anim.onfinish = () => start(el, true); // the end: again from the top, measured again
|
|
101
|
+
s.glide = { text, anim };
|
|
102
|
+
pace(el, s);
|
|
103
|
+
}
|
|
104
|
+
const pagesOf = (s) => (s.plan?.mode === 'pages' ? s.plan : null);
|
|
105
|
+
function arm(el, s) {
|
|
106
|
+
if (s.timer != null)
|
|
107
|
+
host.clearTimeout(s.timer);
|
|
108
|
+
s.timer = null;
|
|
109
|
+
const pages = pagesOf(s);
|
|
110
|
+
if (!pages || held.has(el))
|
|
111
|
+
return;
|
|
112
|
+
s.timer = host.setTimeout(() => {
|
|
113
|
+
s.timer = null;
|
|
114
|
+
if (s.step + 1 < pages.steps.length)
|
|
115
|
+
show(el, s, pages, s.step + 1);
|
|
116
|
+
else
|
|
117
|
+
start(el, true); // the end: again from the top, measured again
|
|
118
|
+
}, pages.steps[s.step].holdMs);
|
|
119
|
+
}
|
|
120
|
+
function show(el, s, pages, step) {
|
|
121
|
+
s.step = step;
|
|
122
|
+
const line = pages.steps[step].line;
|
|
123
|
+
el.dataset.topLine = String(line);
|
|
124
|
+
el.scrollTop = s.lines.starts[line];
|
|
125
|
+
arm(el, s);
|
|
126
|
+
}
|
|
127
|
+
function wire(el) {
|
|
128
|
+
if (wired.has(el))
|
|
129
|
+
return;
|
|
130
|
+
wired.add(el);
|
|
131
|
+
const hold = () => { held.add(el); const s = seen.get(el); if (s)
|
|
132
|
+
pace(el, s); };
|
|
133
|
+
const release = () => { held.delete(el); const s = seen.get(el); if (s)
|
|
134
|
+
pace(el, s); };
|
|
135
|
+
el.addEventListener('pointerenter', hold);
|
|
136
|
+
el.addEventListener('pointerleave', release);
|
|
137
|
+
el.addEventListener('focusin', hold);
|
|
138
|
+
el.addEventListener('focusout', (e) => { if (!el.contains(e.relatedTarget))
|
|
139
|
+
release(); });
|
|
140
|
+
}
|
|
141
|
+
/** A footer whose text or lines changed, or that came into view: measured, and moved only if it does not fit. */
|
|
142
|
+
function start(el, again = false) {
|
|
143
|
+
const key = `${linesShown(el)}|${el.textContent ?? ''}`;
|
|
144
|
+
const had = seen.get(el);
|
|
145
|
+
if (!again && had?.key === key) {
|
|
146
|
+
// the same text written again is not a new text: it goes on gliding, in the block it was in
|
|
147
|
+
if (had.glide)
|
|
148
|
+
gather(el, had.glide.text);
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
forget(el);
|
|
152
|
+
if (!visible(el))
|
|
153
|
+
return;
|
|
154
|
+
el.dataset.scroll = ''; // measured as it will scroll: a block, no clamp
|
|
155
|
+
const lines = readLines(wordTops(el), parseFloat(host.getComputedStyle(el).lineHeight));
|
|
156
|
+
// a page without Web Animations turns pages: the text still passes whole, at the same pace
|
|
157
|
+
const reduced = host.reduced() || typeof el.animate !== 'function';
|
|
158
|
+
const plan = footerScrollPlan(lines.words, linesShown(el), host.ppm(), reduced);
|
|
159
|
+
const s = { key, plan, lines, step: 0, timer: null, glide: null };
|
|
160
|
+
seen.set(el, s);
|
|
161
|
+
if (!plan) {
|
|
162
|
+
delete el.dataset.scroll;
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
el.dataset.scroll = plan.mode;
|
|
166
|
+
wire(el);
|
|
167
|
+
if (plan.mode === 'glide')
|
|
168
|
+
glide(el, s, plan);
|
|
169
|
+
else
|
|
170
|
+
show(el, s, plan, 0);
|
|
171
|
+
}
|
|
172
|
+
function footersIn(node, into) {
|
|
173
|
+
const el = node.nodeType === 1 ? node : node.parentElement;
|
|
174
|
+
const own = el?.closest(FOOTERS);
|
|
175
|
+
if (own)
|
|
176
|
+
into.add(own);
|
|
177
|
+
if (node.nodeType === 1)
|
|
178
|
+
el.querySelectorAll(FOOTERS).forEach((f) => into.add(f));
|
|
179
|
+
}
|
|
180
|
+
const observer = new host.MutationObserver((records) => {
|
|
181
|
+
const touched = new Set();
|
|
182
|
+
for (const r of records) {
|
|
183
|
+
footersIn(r.target, touched);
|
|
184
|
+
r.addedNodes.forEach((n) => footersIn(n, touched));
|
|
185
|
+
}
|
|
186
|
+
// a `hidden` changed somewhere: every footer may have come into view, or changed its number of lines
|
|
187
|
+
if (records.some((r) => r.type === 'attributes'))
|
|
188
|
+
footersIn(root, touched);
|
|
189
|
+
for (const el of [...seen.keys()])
|
|
190
|
+
if (!visible(el))
|
|
191
|
+
forget(el);
|
|
192
|
+
touched.forEach((el) => start(el));
|
|
193
|
+
});
|
|
194
|
+
observer.observe(root, { childList: true, characterData: true, subtree: true, attributes: true, attributeFilter: ['hidden'] });
|
|
195
|
+
root.querySelectorAll(FOOTERS).forEach((el) => start(el));
|
|
196
|
+
return () => {
|
|
197
|
+
observer.disconnect();
|
|
198
|
+
for (const el of [...seen.keys()])
|
|
199
|
+
forget(el);
|
|
200
|
+
};
|
|
201
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
/** One page: the first line on show, and how long it stays before the next. */
|
|
3
|
+
export interface FooterScrollStep {
|
|
4
|
+
readonly line: number;
|
|
5
|
+
readonly holdMs: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Rises continuously: still for `waitMs`, then up at one speed for `travelMs` until `toLine` tops the view (the last view),
|
|
9
|
+
* held there `holdMs`.
|
|
10
|
+
*/
|
|
11
|
+
export interface FooterGlidePlan {
|
|
12
|
+
readonly mode: 'glide';
|
|
13
|
+
readonly waitMs: number;
|
|
14
|
+
readonly travelMs: number;
|
|
15
|
+
readonly holdMs: number;
|
|
16
|
+
readonly toLine: number;
|
|
17
|
+
}
|
|
18
|
+
/** Turns a whole view at a time, without sliding (reduced motion). */
|
|
19
|
+
export interface FooterPagesPlan {
|
|
20
|
+
readonly mode: 'pages';
|
|
21
|
+
readonly steps: readonly FooterScrollStep[];
|
|
22
|
+
}
|
|
23
|
+
export type FooterScrollPlan = FooterGlidePlan | FooterPagesPlan;
|
|
24
|
+
/** The rendered lines: how many words each holds, and where each starts, from the top of the first. */
|
|
25
|
+
export interface FooterLines {
|
|
26
|
+
readonly words: readonly number[];
|
|
27
|
+
readonly starts: readonly number[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The lines, from the TOP of each word as laid out. A word lower than the line's top by more than half a line opens a new one;
|
|
31
|
+
* a word broken across two lines counts where it starts.
|
|
32
|
+
*/
|
|
33
|
+
export declare function readLines(wordTops: readonly number[], lineHeight: number): FooterLines;
|
|
34
|
+
/**
|
|
35
|
+
* The plan for a text of `wordsPerLine.length` lines shown `shown` at a time, or `null` when it fits — a text that fits never
|
|
36
|
+
* moves. The rate is one of the three the child can choose; any other number reads as the slowest (`isCaptionRate`).
|
|
37
|
+
*/
|
|
38
|
+
export declare function footerScrollPlan(wordsPerLine: readonly number[], shown: number, ppm: number, reduced: boolean): FooterScrollPlan | null;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// ui/footer-scroll — WHEN A LONG FOOTER EXPLANATION MOVES, AND FOR HOW LONG (ADR-0245).
|
|
3
|
+
//
|
|
4
|
+
// The footer shows its lines (two; one while the button legend shares the column, ADR-0164). A text that needs more moves
|
|
5
|
+
// INSIDE them, at the child's caption rate — the arithmetic of `core/caption-duration`, 60000 / ppm ms a word:
|
|
6
|
+
// · GLIDE (ADR-0245 erratum, 2026-09-26: «suave e contínua, lentamente»): it waits for the first view to be read, then rises
|
|
7
|
+
// at ONE constant speed until its last line is in view — the distance passes in the time the words below the first view
|
|
8
|
+
// take, so the last line arrives when every word has been read —, holds the last view for its words, and the caller starts
|
|
9
|
+
// again from the top;
|
|
10
|
+
// · PAGES, with reduced motion: no slide — a whole view at a time, each page held for the words it brings.
|
|
11
|
+
//
|
|
12
|
+
// 📌 PURE: word positions and numbers in, a plan out. The DOM half (`ui/footer-scroll-driver`) measures and moves.
|
|
13
|
+
import { isCaptionRate } from '../core/caption-duration.js';
|
|
14
|
+
/**
|
|
15
|
+
* The lines, from the TOP of each word as laid out. A word lower than the line's top by more than half a line opens a new one;
|
|
16
|
+
* a word broken across two lines counts where it starts.
|
|
17
|
+
*/
|
|
18
|
+
export function readLines(wordTops, lineHeight) {
|
|
19
|
+
const words = [];
|
|
20
|
+
const starts = [];
|
|
21
|
+
let top = -Infinity;
|
|
22
|
+
for (const t of wordTops) {
|
|
23
|
+
if (t > top + lineHeight / 2) {
|
|
24
|
+
top = t;
|
|
25
|
+
words.push(0);
|
|
26
|
+
starts.push(t - (wordTops[0] ?? 0));
|
|
27
|
+
}
|
|
28
|
+
words[words.length - 1] += 1;
|
|
29
|
+
}
|
|
30
|
+
return { words, starts };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* The plan for a text of `wordsPerLine.length` lines shown `shown` at a time, or `null` when it fits — a text that fits never
|
|
34
|
+
* moves. The rate is one of the three the child can choose; any other number reads as the slowest (`isCaptionRate`).
|
|
35
|
+
*/
|
|
36
|
+
export function footerScrollPlan(wordsPerLine, shown, ppm, reduced) {
|
|
37
|
+
const last = wordsPerLine.length - shown; // the first line of the last view
|
|
38
|
+
if (shown < 1 || last <= 0)
|
|
39
|
+
return null;
|
|
40
|
+
const msPerWord = 60_000 / isCaptionRate(ppm);
|
|
41
|
+
const read = (from, to) => Math.round(wordsPerLine.slice(from, to).reduce((a, b) => a + b, 0) * msPerWord);
|
|
42
|
+
// the last view is held as long as its lines take to read
|
|
43
|
+
const endHold = read(last, last + shown);
|
|
44
|
+
if (!reduced) {
|
|
45
|
+
return { mode: 'glide', waitMs: read(0, shown), travelMs: read(shown, wordsPerLine.length), holdMs: endHold, toLine: last };
|
|
46
|
+
}
|
|
47
|
+
const steps = [{ line: 0, holdMs: read(0, shown) }];
|
|
48
|
+
for (let line = shown; steps[steps.length - 1].line < last; line += shown) {
|
|
49
|
+
const top = Math.min(line, last);
|
|
50
|
+
const before = steps[steps.length - 1].line;
|
|
51
|
+
// every other page, as long as the lines it brought
|
|
52
|
+
steps.push({ line: top, holdMs: top === last ? endHold : read(before + shown, top + shown) });
|
|
53
|
+
}
|
|
54
|
+
return { mode: 'pages', steps };
|
|
55
|
+
}
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
2
|
import type { Translate } from '../core/i18n.js';
|
|
3
3
|
import type { PanelShellCtx } from './panel-shell.js';
|
|
4
|
-
/** One position of a steps or list row: the value the cartridge reads and writes, and
|
|
4
|
+
/** One position of a steps or list row: the value the cartridge reads and writes, and the key of its label. */
|
|
5
5
|
export interface GameOptionValue {
|
|
6
6
|
readonly value: string;
|
|
7
|
-
readonly
|
|
7
|
+
readonly labelKey: string;
|
|
8
8
|
}
|
|
9
9
|
interface GameOptionBase {
|
|
10
10
|
/** Stable and unique among the game's rows; the control is `#game-option-<id>`. */
|
|
11
11
|
readonly id: string;
|
|
12
|
-
/** The row's short name, in the
|
|
13
|
-
readonly
|
|
14
|
-
/** The explanation, shown in the footer (CLAUDE.md §4). */
|
|
15
|
-
readonly
|
|
12
|
+
/** The key of the row's short name, in the game's dictionary. */
|
|
13
|
+
readonly labelKey: string;
|
|
14
|
+
/** The key of the explanation, shown in the footer (CLAUDE.md §4). */
|
|
15
|
+
readonly hintKey?: string;
|
|
16
16
|
}
|
|
17
17
|
export type GameOption = (GameOptionBase & {
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* An exclusive choice. The engine draws it by its SIZE (ADR-0130 rule 3): five positions or fewer are one row «◀ Label:
|
|
20
|
+
* value ▶», more a dropdown — whichever kind is declared. `steps` still refuses more than five, since the game asked for a cycle.
|
|
21
|
+
*/
|
|
19
22
|
readonly kind: 'steps' | 'list';
|
|
20
23
|
readonly values: readonly GameOptionValue[];
|
|
21
24
|
/** The value in use. Read at every opening and after every write: what shows is what the cartridge keeps. */
|
|
@@ -29,14 +32,16 @@ export type GameOption = (GameOptionBase & {
|
|
|
29
32
|
/** Why a `gameOptions` declaration is malformed; empty when it is well formed. Absent is well formed: no options of its own. */
|
|
30
33
|
export declare function gameOptionsProblems(options: unknown): string[];
|
|
31
34
|
export interface GameOptionsDrawCtx extends PanelShellCtx {
|
|
32
|
-
/** Translates in the page's language — the root's translator (ADR-0232 D3).
|
|
35
|
+
/** Translates the engine's own words in the page's language — the root's translator (ADR-0232 D3). */
|
|
33
36
|
t: Translate;
|
|
37
|
+
/** Resolves a key the GAME declared, in the page's language; `null` when its dictionaries lack it (`Translator.word`). */
|
|
38
|
+
readonly word: (key: string) => string | null;
|
|
34
39
|
/** Where a change is said (the polite live region). */
|
|
35
40
|
readonly say: (text: string) => void;
|
|
36
41
|
}
|
|
37
42
|
/**
|
|
38
43
|
* Draws the rows into `list`, replacing what was there — the rows are the CURRENT cartridge's, and `mount()` may have
|
|
39
|
-
* swapped it (ADR-0142). Every value shown is READ from the cartridge, at drawing and after each write.
|
|
44
|
+
* swapped it (ADR-0142). Every value shown is READ from the cartridge, and every word RESOLVED, at drawing and after each write.
|
|
40
45
|
*/
|
|
41
46
|
export declare function drawGameOptions(ctx: GameOptionsDrawCtx, list: HTMLElement, options: readonly GameOption[]): void;
|
|
42
47
|
export {};
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
-
import { controlRow, mountSteps, updateSteps, nextStep } from './panel-widgets.js';
|
|
2
|
+
import { controlRow, mountSteps, updateSteps, nextStep, MAX_CYCLE_POSITIONS } from './panel-widgets.js';
|
|
3
3
|
import { toggleLabel } from './dom.js';
|
|
4
4
|
const KINDS = ['steps', 'list', 'switch'];
|
|
5
|
-
/** ADR-0130 erratum, rule 3: steps cycle among FEW positions — up to five; past that, a list. */
|
|
6
|
-
const MAX_STEPS = 5;
|
|
7
5
|
/** Why a `gameOptions` declaration is malformed; empty when it is well formed. Absent is well formed: no options of its own. */
|
|
8
6
|
export function gameOptionsProblems(options) {
|
|
9
7
|
if (options === undefined)
|
|
@@ -31,20 +29,22 @@ function idProblems(o, at, ids) {
|
|
|
31
29
|
ids.add(o.id);
|
|
32
30
|
return [];
|
|
33
31
|
}
|
|
32
|
+
/** A key: text that is not blank. */
|
|
33
|
+
const isKey = (k) => typeof k === 'string' && !!k.trim();
|
|
34
34
|
/** Each field a row must have, and what is said when it does not: a row comes from outside the engine, so its TYPE is checked. */
|
|
35
35
|
const FIELD_CHECKS = [
|
|
36
36
|
[(o) => KINDS.includes(o.kind), `kind must be one of ${KINDS.join(', ')}`],
|
|
37
|
-
[(o) =>
|
|
38
|
-
[(o) => o.
|
|
37
|
+
[(o) => isKey(o.labelKey), "labelKey must name the row by a key of the game's dictionary"],
|
|
38
|
+
[(o) => o.hintKey === undefined || isKey(o.hintKey), "hintKey must be a key of the game's dictionary"],
|
|
39
39
|
[(o) => typeof o.read === 'function', 'read must be a function returning the value in use'],
|
|
40
40
|
[(o) => typeof o.write === 'function', 'write must be a function taking the new value'],
|
|
41
41
|
];
|
|
42
|
-
/** The positions of a steps or list row: at least two, at most five for steps, each with a value and a label, none repeated. */
|
|
42
|
+
/** The positions of a steps or list row: at least two, at most five for steps, each with a value and a label key, none repeated. */
|
|
43
43
|
function valuesProblems(o, at) {
|
|
44
44
|
const values = o.values;
|
|
45
45
|
if (!Array.isArray(values) || values.length < 2)
|
|
46
46
|
return [`${at}.values must hold at least two positions`];
|
|
47
|
-
const out = o.kind === 'steps' && values.length >
|
|
47
|
+
const out = o.kind === 'steps' && values.length > MAX_CYCLE_POSITIONS
|
|
48
48
|
? [`${at}.values holds ${values.length} steps: steps hold at most five positions — past that, declare a list (ADR-0130)`] : [];
|
|
49
49
|
const seen = new Set();
|
|
50
50
|
values.forEach((v, j) => {
|
|
@@ -55,25 +55,44 @@ function valuesProblems(o, at) {
|
|
|
55
55
|
return out;
|
|
56
56
|
}
|
|
57
57
|
function positionProblem(v, at, seen) {
|
|
58
|
-
if (!v || typeof v.value !== 'string' ||
|
|
59
|
-
return `${at} must have a value and a
|
|
58
|
+
if (!v || typeof v.value !== 'string' || !isKey(v.labelKey))
|
|
59
|
+
return `${at} must have a value and a labelKey`;
|
|
60
60
|
if (seen.has(v.value))
|
|
61
61
|
return `${at} «${v.value}» repeats an earlier position`;
|
|
62
62
|
seen.add(v.value);
|
|
63
63
|
return null;
|
|
64
64
|
}
|
|
65
|
+
function rowWords(o, word) {
|
|
66
|
+
const label = word(o.labelKey);
|
|
67
|
+
if (!label)
|
|
68
|
+
return null;
|
|
69
|
+
const hint = o.hintKey ? word(o.hintKey) : null;
|
|
70
|
+
const values = new Map();
|
|
71
|
+
if (o.kind !== 'switch') {
|
|
72
|
+
for (const v of o.values) {
|
|
73
|
+
const positionLabel = word(v.labelKey);
|
|
74
|
+
if (!positionLabel)
|
|
75
|
+
return null;
|
|
76
|
+
values.set(v.value, positionLabel);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { label, ...(hint ? { hint } : {}), values };
|
|
80
|
+
}
|
|
65
81
|
/**
|
|
66
82
|
* Draws the rows into `list`, replacing what was there — the rows are the CURRENT cartridge's, and `mount()` may have
|
|
67
|
-
* swapped it (ADR-0142). Every value shown is READ from the cartridge, at drawing and after each write.
|
|
83
|
+
* swapped it (ADR-0142). Every value shown is READ from the cartridge, and every word RESOLVED, at drawing and after each write.
|
|
68
84
|
*/
|
|
69
85
|
export function drawGameOptions(ctx, list, options) {
|
|
70
86
|
const { t } = ctx;
|
|
71
87
|
while (list.firstChild)
|
|
72
88
|
list.removeChild(list.firstChild); // node by node: no markup sink, and a host without `replaceChildren` still clears
|
|
73
89
|
for (const o of options) {
|
|
90
|
+
const words = rowWords(o, ctx.word);
|
|
91
|
+
if (!words)
|
|
92
|
+
continue;
|
|
74
93
|
const id = `game-option-${o.id}`;
|
|
75
94
|
if (o.kind === 'switch') {
|
|
76
|
-
const { row: rowNode, control: switchButton } = controlRow(ctx, { id, label:
|
|
95
|
+
const { row: rowNode, control: switchButton } = controlRow(ctx, { id, label: words.label, hint: words.hint });
|
|
77
96
|
const reflect = () => {
|
|
78
97
|
const on = o.read();
|
|
79
98
|
switchButton.classList.toggle('is-on', on);
|
|
@@ -84,40 +103,41 @@ export function drawGameOptions(ctx, list, options) {
|
|
|
84
103
|
reflect();
|
|
85
104
|
switchButton.addEventListener('click', () => {
|
|
86
105
|
o.write(!o.read());
|
|
87
|
-
ctx.say(`${
|
|
106
|
+
ctx.say(`${words.label}: ${toggleLabel(t, reflect())}`);
|
|
88
107
|
});
|
|
89
108
|
list.appendChild(rowNode);
|
|
90
109
|
continue;
|
|
91
110
|
}
|
|
92
|
-
const labelOf = (chosen) =>
|
|
93
|
-
|
|
94
|
-
|
|
111
|
+
const labelOf = (chosen) => words.values.get(chosen) ?? '';
|
|
112
|
+
// ⚠️ BY SIZE, NOT BY KIND (ADR-0130 rule 3): a `list` of five or fewer is a cycle row like `steps`, below
|
|
113
|
+
if (o.values.length > MAX_CYCLE_POSITIONS) {
|
|
114
|
+
const { row: rowNode, control: control } = controlRow(ctx, { id, label: words.label, hint: words.hint, shape: 'escolha' });
|
|
95
115
|
const sel = control;
|
|
96
116
|
for (const v of o.values) {
|
|
97
117
|
const op = ctx.create('option');
|
|
98
118
|
op.value = v.value;
|
|
99
|
-
op.textContent = v.
|
|
119
|
+
op.textContent = labelOf(v.value);
|
|
100
120
|
sel.appendChild(op);
|
|
101
121
|
}
|
|
102
122
|
sel.value = o.read();
|
|
103
123
|
sel.addEventListener('change', () => {
|
|
104
124
|
o.write(sel.value);
|
|
105
125
|
sel.value = o.read();
|
|
106
|
-
ctx.say(`${
|
|
126
|
+
ctx.say(`${words.label}: ${labelOf(sel.value)}`);
|
|
107
127
|
});
|
|
108
128
|
list.appendChild(rowNode);
|
|
109
129
|
continue;
|
|
110
130
|
}
|
|
111
131
|
// steps: the row IS the control, «◀ Label: value ▶» (ADR-0130 erratum); the hint rides in the row for the footer
|
|
112
132
|
const selectedIndex = () => Math.max(0, o.values.findIndex((v) => v.value === o.read()));
|
|
113
|
-
const spec = () => ({ label:
|
|
133
|
+
const spec = () => ({ label: words.label, values: o.values.map((v) => labelOf(v.value)), current: selectedIndex() });
|
|
114
134
|
const rowNode = ctx.create('div');
|
|
115
135
|
rowNode.className = 'ctrl-row ctrl-row--passos';
|
|
116
136
|
const envelope = ctx.create('span');
|
|
117
|
-
if (
|
|
137
|
+
if (words.hint) {
|
|
118
138
|
const explanation = ctx.create('span');
|
|
119
139
|
explanation.className = 'opt-hint';
|
|
120
|
-
explanation.textContent =
|
|
140
|
+
explanation.textContent = words.hint;
|
|
121
141
|
envelope.appendChild(explanation);
|
|
122
142
|
}
|
|
123
143
|
rowNode.appendChild(envelope);
|
|
@@ -131,7 +151,7 @@ export function drawGameOptions(ctx, list, options) {
|
|
|
131
151
|
return; // at the wall nothing moved, and nothing is said
|
|
132
152
|
o.write(o.values[nextIndex].value);
|
|
133
153
|
updateSteps(stepper, spec());
|
|
134
|
-
ctx.say(`${
|
|
154
|
+
ctx.say(`${words.label}: ${labelOf(o.read())}`);
|
|
135
155
|
});
|
|
136
156
|
list.appendChild(rowNode);
|
|
137
157
|
}
|
|
@@ -11,6 +11,7 @@ import { gesturesSeen, createHandMapReader } from '../input/hand-map.js';
|
|
|
11
11
|
import { loadHandTracker } from '../platform/vision.js';
|
|
12
12
|
import { createVisionLoop } from '../platform/vision-loop.js';
|
|
13
13
|
import { gazeFontPx } from './gaze-overlay.js';
|
|
14
|
+
import { startCameraReader } from './camera-control.js';
|
|
14
15
|
// 📌 THE SHAPE IS THE FAMILY'S (ADR-0221 step 7f): what this module returns is a SwitchableControl, not a fourth copy of it.
|
|
15
16
|
/** The hand's bones, mirrored so they move the way the child moves; a bone to a landmark the hand does not have is skipped. */
|
|
16
17
|
function drawBones(g, w, h, bones, hand) {
|
|
@@ -94,23 +95,22 @@ export function createHandControl(d) {
|
|
|
94
95
|
canvas = null;
|
|
95
96
|
running = false;
|
|
96
97
|
};
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
if (!
|
|
100
|
-
once('files', `gesture control: ${load.missing.join(', ')} not on this device — the child cannot play with gestures; open the game once online so the install fetches them`, t('sr.hands.needsInternet'));
|
|
101
|
-
d.turnOff();
|
|
98
|
+
/** Nothing started: said, written once, and the 📷 back to off — unless it already moved on, and then nobody is waiting (as the 👄). */
|
|
99
|
+
const failed = (kind, line, spoken) => {
|
|
100
|
+
if (!current())
|
|
102
101
|
return;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
102
|
+
once(kind, line, spoken);
|
|
103
|
+
d.turnOff();
|
|
104
|
+
};
|
|
105
|
+
const start = async () => {
|
|
106
|
+
const opened = await startCameraReader({
|
|
107
|
+
subject: 'gesture control', loses: 'play with gestures',
|
|
108
|
+
spoken: { files: () => t('sr.hands.needsInternet'), runtime: () => t('sr.hands.failed'), camera: () => t('sr.hands.noCamera') },
|
|
109
|
+
}, { load: () => loadTracker({ base: d.base, hasFile: d.hasFile }), openFeed: d.openFeed, failed });
|
|
110
|
+
if (!opened)
|
|
111
111
|
return;
|
|
112
|
-
|
|
113
|
-
|
|
112
|
+
tracker = opened.tracker;
|
|
113
|
+
feed = opened.feed;
|
|
114
114
|
canvas = d.doc.createElement('canvas');
|
|
115
115
|
canvas.className = 'hand-overlay';
|
|
116
116
|
canvas.setAttribute('aria-hidden', 'true');
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
-
import { type Action, type
|
|
2
|
+
import { type Action, type ActionWords } from '../core/actions.js';
|
|
3
3
|
/** One line of the help table. `key` is `null` when this child's keyboard does not reach the position. */
|
|
4
4
|
export interface HelpRow {
|
|
5
5
|
readonly action: Action;
|
|
@@ -18,12 +18,12 @@ export interface HelpRow {
|
|
|
18
18
|
* on-screen legend. A help screen ordered by whatever order the game happened to write its object in would
|
|
19
19
|
* teach a different order from every other surface.
|
|
20
20
|
*
|
|
21
|
-
* @param preset
|
|
21
|
+
* @param preset this game's positions and their words, RESOLVED now in the page's language (`core/actions.wordsOf`).
|
|
22
22
|
* @param keysOf the codes bound to a position FOR THIS SEAT. `kbFor(seat)[action]`, so a child who remapped
|
|
23
23
|
* sees her own key. `null`/empty means the keyboard does not reach it.
|
|
24
24
|
* @param keyName code → readable label (`ui/settings-controls.keyName`), injected so this half stays pure.
|
|
25
25
|
*/
|
|
26
|
-
export declare function helpRows(preset:
|
|
26
|
+
export declare function helpRows(preset: ActionWords | null | undefined, keysOf: (a: Action) => readonly string[] | null | undefined, keyName: (code: string) => string): HelpRow[];
|
|
27
27
|
/** What the slide says where the keyboard reaches nothing. A KEY, not a sentence — the caller translates it. */
|
|
28
28
|
export declare const NO_KEY = "help.noKey";
|
|
29
29
|
/** What building the slide show needs from the document — injected, as the panel shell takes it. */
|
|
@@ -39,13 +39,24 @@ export interface HowToPlaySurface {
|
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* One «how to play» slide, declared by the cartridge (ADR-0195; issue #188): «O "Como jogar" é justamente algo a ser feito pelo
|
|
42
|
-
* cartucho.»
|
|
43
|
-
*
|
|
42
|
+
* cartucho.» Its text is a KEY of the game's dictionary, resolved by the root's translator at every showing, so it follows the
|
|
43
|
+
* language (ADR-0232 D3, erratum of 2026-09-25); the figure is drawn by the game on the engine's surface — art stays data the
|
|
44
|
+
* game draws, never an embedded picture — and may animate by the time it is given.
|
|
44
45
|
*/
|
|
45
46
|
export interface HowToPlaySlide {
|
|
46
|
-
readonly
|
|
47
|
+
readonly textKey: string;
|
|
47
48
|
readonly figure?: (surface: HowToPlaySurface) => void;
|
|
48
49
|
}
|
|
50
|
+
/** A «how to play» slide as it is SHOWN: its text resolved now (`playSlidesOf`), and its figure. */
|
|
51
|
+
export interface PlaySlide {
|
|
52
|
+
readonly text: string;
|
|
53
|
+
readonly figure?: (surface: HowToPlaySurface) => void;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* The cartridge's slides, their texts resolved NOW through `word` (`Translator.word`). A slide whose text the game's
|
|
57
|
+
* dictionaries lack is left out — never shown as its key — and `problems` names it (`ui/declared-words`).
|
|
58
|
+
*/
|
|
59
|
+
export declare function playSlidesOf(slides: readonly HowToPlaySlide[], word: (key: string) => string | null): PlaySlide[];
|
|
49
60
|
/** Why a `howToPlay` declaration is malformed; empty when well formed. Absent is well formed: the help shows the buttons alone. */
|
|
50
61
|
export declare function howToPlayProblems(slides: unknown): string[];
|
|
51
62
|
/** What the engine's frame loop gives an animated figure; injected, so the loop is the page's. */
|
|
@@ -59,7 +70,7 @@ export interface FigureClock {
|
|
|
59
70
|
* Draws the shown slide's figure, and keeps drawing it while the slide show is in the document and the same slide is shown.
|
|
60
71
|
* Returns a stop. A figure that throws is a cartridge defect: it stops drawing and the text stays.
|
|
61
72
|
*/
|
|
62
|
-
export declare function animateFigure(el: HTMLElement, slide:
|
|
73
|
+
export declare function animateFigure(el: HTMLElement, slide: Pick<PlaySlide, 'figure'>, clock: FigureClock): () => void;
|
|
63
74
|
/** Builds the slide show's frame: the stop, its two arrows, the slide and the dots. `showSlide` fills it. */
|
|
64
75
|
export declare function mountSlides(ctx: SlideCtx): HTMLElement;
|
|
65
76
|
/**
|
|
@@ -67,7 +78,7 @@ export declare function mountSlides(ctx: SlideCtx): HTMLElement;
|
|
|
67
78
|
* the index shown, and what a screen reader hears of it. A slide is a button row (`helpRows`) or a cartridge's «how to play» slide
|
|
68
79
|
* (ADR-0195), which the help puts first.
|
|
69
80
|
*/
|
|
70
|
-
export declare function showSlide(el: HTMLElement, rows: readonly (HelpRow |
|
|
81
|
+
export declare function showSlide(el: HTMLElement, rows: readonly (HelpRow | PlaySlide)[], i: number, ctx: SlideCtx & {
|
|
71
82
|
readonly t: (k: string, p?: Record<string, string>) => string;
|
|
72
83
|
readonly title: string;
|
|
73
84
|
}): {
|