@the-inclusionist/engine 9.0.0 → 11.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -78
- package/app/css/style.css +761 -377
- package/app/public/vendor/fonts/playwrite-cu.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-es-deco.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-es.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-gb-j.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-gb-s.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-pe.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-pt.woff2 +0 -0
- package/app/public/vendor/fonts-licences/NOTICE.txt +166 -0
- package/app/public/vendor/fonts-licences/OFL-1.1.txt +86 -0
- package/app/public/vendor/fonts.css +36 -84
- package/dist-pkg/boot/create-game.d.ts +499 -219
- package/dist-pkg/boot/create-game.js +3944 -562
- package/dist-pkg/core/a11y-sr.d.ts +25 -3
- package/dist-pkg/core/a11y-sr.js +38 -19
- package/dist-pkg/core/accessible-label.d.ts +17 -0
- package/dist-pkg/core/accessible-label.js +37 -0
- package/dist-pkg/core/accommodation-subjects.d.ts +26 -0
- package/dist-pkg/core/accommodation-subjects.js +32 -0
- package/dist-pkg/core/accommodations.d.ts +125 -0
- package/dist-pkg/core/accommodations.js +195 -0
- package/dist-pkg/core/actions.d.ts +108 -55
- package/dist-pkg/core/actions.js +122 -71
- package/dist-pkg/core/calm-mode.d.ts +31 -0
- package/dist-pkg/core/calm-mode.js +47 -0
- package/dist-pkg/core/camera-cycle.d.ts +15 -0
- package/dist-pkg/core/camera-cycle.js +7 -0
- package/dist-pkg/core/caption-duration.d.ts +15 -0
- package/dist-pkg/core/caption-duration.js +19 -0
- package/dist-pkg/core/cartridge-problems.d.ts +34 -0
- package/dist-pkg/core/cartridge-problems.js +53 -0
- package/dist-pkg/core/constants.d.ts +0 -54
- package/dist-pkg/core/constants.js +9 -76
- package/dist-pkg/core/contract.d.ts +142 -164
- package/dist-pkg/core/contract.js +222 -208
- package/dist-pkg/core/dom-query.d.ts +4 -4
- package/dist-pkg/core/dom-query.js +12 -20
- package/dist-pkg/core/entity.d.ts +62 -77
- package/dist-pkg/core/entity.js +18 -32
- package/dist-pkg/core/escape-html.d.ts +9 -10
- package/dist-pkg/core/escape-html.js +16 -20
- package/dist-pkg/core/flash-threshold.d.ts +17 -0
- package/dist-pkg/core/flash-threshold.js +113 -0
- package/dist-pkg/core/game-speed.d.ts +11 -0
- package/dist-pkg/core/game-speed.js +16 -0
- package/dist-pkg/core/genres.d.ts +20 -0
- package/dist-pkg/core/genres.js +44 -0
- package/dist-pkg/core/i18n.d.ts +71 -60
- package/dist-pkg/core/i18n.js +186 -200
- package/dist-pkg/core/loop.d.ts +17 -6
- package/dist-pkg/core/loop.js +26 -26
- package/dist-pkg/core/pause-icon-catalogue.d.ts +15 -0
- package/dist-pkg/core/pause-icon-catalogue.js +54 -0
- package/dist-pkg/core/ring.d.ts +17 -0
- package/dist-pkg/core/ring.js +29 -0
- package/dist-pkg/core/rng.d.ts +7 -12
- package/dist-pkg/core/rng.js +23 -53
- package/dist-pkg/core/route.d.ts +26 -26
- package/dist-pkg/core/route.js +126 -121
- package/dist-pkg/core/scenes.d.ts +37 -40
- package/dist-pkg/core/scenes.js +41 -43
- package/dist-pkg/core/screens.d.ts +4 -4
- package/dist-pkg/core/screens.js +11 -16
- package/dist-pkg/core/session-clock.d.ts +23 -0
- package/dist-pkg/core/session-clock.js +37 -0
- package/dist-pkg/core/setting-defaults.d.ts +61 -0
- package/dist-pkg/core/setting-defaults.js +66 -0
- package/dist-pkg/core/speech-rate.d.ts +29 -0
- package/dist-pkg/core/speech-rate.js +47 -0
- package/dist-pkg/core/state.d.ts +118 -118
- package/dist-pkg/core/state.js +322 -297
- package/dist-pkg/core/visual-cycles.d.ts +30 -0
- package/dist-pkg/core/visual-cycles.js +52 -0
- package/dist-pkg/core/visual-state.d.ts +13 -0
- package/dist-pkg/core/visual-state.js +6 -0
- package/dist-pkg/educational/activities-registry.d.ts +1 -1
- package/dist-pkg/educational/activities-registry.js +9 -8
- package/dist-pkg/educational/adaptive-engine.d.ts +11 -11
- package/dist-pkg/educational/adaptive-engine.js +17 -17
- package/dist-pkg/educational/segment-bar.d.ts +12 -12
- package/dist-pkg/educational/segment-bar.js +9 -9
- package/dist-pkg/i18n/en.js +324 -223
- package/dist-pkg/i18n/es.js +323 -223
- package/dist-pkg/i18n/pt.js +348 -226
- package/dist-pkg/input/default-bindings.d.ts +37 -38
- package/dist-pkg/input/default-bindings.js +127 -144
- package/dist-pkg/input/devices.d.ts +7 -7
- package/dist-pkg/input/devices.js +17 -16
- package/dist-pkg/input/edges.d.ts +23 -32
- package/dist-pkg/input/edges.js +23 -28
- package/dist-pkg/input/empathy-filter.d.ts +22 -0
- package/dist-pkg/input/empathy-filter.js +34 -0
- package/dist-pkg/input/face-map.d.ts +96 -0
- package/dist-pkg/input/face-map.js +255 -0
- package/dist-pkg/input/face-signals.d.ts +40 -0
- package/dist-pkg/input/face-signals.js +48 -0
- package/dist-pkg/input/gamepad.d.ts +195 -145
- package/dist-pkg/input/gamepad.js +383 -556
- package/dist-pkg/input/gaze-cycle.d.ts +64 -0
- package/dist-pkg/input/gaze-cycle.js +133 -0
- package/dist-pkg/input/gaze-relative.d.ts +68 -0
- package/dist-pkg/input/gaze-relative.js +142 -0
- package/dist-pkg/input/hand-map.d.ts +28 -0
- package/dist-pkg/input/hand-map.js +142 -0
- package/dist-pkg/input/input-cooldown.d.ts +17 -0
- package/dist-pkg/input/input-cooldown.js +39 -0
- package/dist-pkg/input/key-default.d.ts +30 -0
- package/dist-pkg/input/key-default.js +60 -0
- package/dist-pkg/input/keyboard-runtime.d.ts +15 -21
- package/dist-pkg/input/keyboard-runtime.js +23 -27
- package/dist-pkg/input/keyboard.d.ts +68 -63
- package/dist-pkg/input/keyboard.js +83 -105
- package/dist-pkg/input/keydown.d.ts +123 -137
- package/dist-pkg/input/keydown.js +284 -289
- package/dist-pkg/input/latch-edge.d.ts +33 -21
- package/dist-pkg/input/latch-edge.js +34 -35
- package/dist-pkg/input/latch-scope.d.ts +51 -50
- package/dist-pkg/input/latch-scope.js +61 -82
- package/dist-pkg/input/latch-store.d.ts +26 -30
- package/dist-pkg/input/latch-store.js +42 -50
- package/dist-pkg/input/latch-sync.d.ts +29 -29
- package/dist-pkg/input/latch-sync.js +25 -25
- package/dist-pkg/input/latch.d.ts +14 -17
- package/dist-pkg/input/latch.js +29 -37
- package/dist-pkg/input/pad-defaults.d.ts +13 -16
- package/dist-pkg/input/pad-defaults.js +21 -28
- package/dist-pkg/input/pad-reading.d.ts +76 -0
- package/dist-pkg/input/pad-reading.js +169 -0
- package/dist-pkg/input/pad-wizard.d.ts +99 -0
- package/dist-pkg/input/pad-wizard.js +256 -0
- package/dist-pkg/input/pointer-space.d.ts +19 -20
- package/dist-pkg/input/pointer-space.js +27 -32
- package/dist-pkg/input/pointer.d.ts +39 -40
- package/dist-pkg/input/pointer.js +42 -45
- package/dist-pkg/input/state.d.ts +101 -89
- package/dist-pkg/input/state.js +54 -134
- package/dist-pkg/input/switch-scan.d.ts +59 -0
- package/dist-pkg/input/switch-scan.js +67 -0
- package/dist-pkg/input/synthetic-source.d.ts +42 -0
- package/dist-pkg/input/synthetic-source.js +57 -0
- package/dist-pkg/input/touch-bindings.d.ts +127 -116
- package/dist-pkg/input/touch-bindings.js +158 -173
- package/dist-pkg/input/touch.d.ts +147 -72
- package/dist-pkg/input/touch.js +362 -126
- package/dist-pkg/input/transport-in-use.d.ts +107 -0
- package/dist-pkg/input/transport-in-use.js +130 -0
- package/dist-pkg/input/transports.d.ts +90 -117
- package/dist-pkg/input/transports.js +104 -106
- package/dist-pkg/input/virtual-controller.d.ts +82 -0
- package/dist-pkg/input/virtual-controller.js +69 -0
- package/dist-pkg/input/vocabulary-migration.d.ts +50 -49
- package/dist-pkg/input/vocabulary-migration.js +80 -79
- package/dist-pkg/input/voice-map.d.ts +59 -0
- package/dist-pkg/input/voice-map.js +123 -0
- package/dist-pkg/platform/audio-ambient.d.ts +13 -2
- package/dist-pkg/platform/audio-ambient.js +14 -14
- package/dist-pkg/platform/audio-earcons.d.ts +20 -19
- package/dist-pkg/platform/audio-earcons.js +51 -42
- package/dist-pkg/platform/audio-jingles.js +9 -10
- package/dist-pkg/platform/audio-mixer.d.ts +14 -2
- package/dist-pkg/platform/audio-mixer.js +31 -39
- package/dist-pkg/platform/audio-sonar.d.ts +49 -152
- package/dist-pkg/platform/audio-sonar.js +124 -302
- package/dist-pkg/platform/audio.d.ts +62 -19
- package/dist-pkg/platform/audio.js +193 -168
- package/dist-pkg/platform/choose-by-voice.d.ts +70 -0
- package/dist-pkg/platform/choose-by-voice.js +157 -0
- package/dist-pkg/platform/flash-sampler.d.ts +19 -0
- package/dist-pkg/platform/flash-sampler.js +79 -0
- package/dist-pkg/platform/font-library.d.ts +77 -0
- package/dist-pkg/platform/font-library.js +98 -0
- package/dist-pkg/platform/font-library.json +721 -0
- package/dist-pkg/platform/heavy-catalogue.d.ts +77 -0
- package/dist-pkg/platform/heavy-catalogue.js +256 -0
- package/dist-pkg/platform/heavy-mirror.d.ts +20 -0
- package/dist-pkg/platform/heavy-mirror.js +73 -0
- package/dist-pkg/platform/heavy.d.ts +136 -0
- package/dist-pkg/platform/heavy.js +324 -0
- package/dist-pkg/platform/interruptible-speech.d.ts +23 -16
- package/dist-pkg/platform/interruptible-speech.js +77 -54
- package/dist-pkg/platform/kokoro-port.d.ts +27 -0
- package/dist-pkg/platform/kokoro-port.js +50 -0
- package/dist-pkg/platform/kokoro-runtime.d.ts +20 -0
- package/dist-pkg/platform/kokoro-runtime.js +52 -0
- package/dist-pkg/platform/kokoro.d.ts +70 -0
- package/dist-pkg/platform/kokoro.js +133 -0
- package/dist-pkg/platform/listener-scope.d.ts +12 -0
- package/dist-pkg/platform/listener-scope.js +90 -0
- package/dist-pkg/platform/locale-host.d.ts +14 -0
- package/dist-pkg/platform/locale-host.js +45 -0
- package/dist-pkg/platform/microphone.d.ts +51 -0
- package/dist-pkg/platform/microphone.js +118 -0
- package/dist-pkg/platform/onnx-runtime.d.ts +34 -0
- package/dist-pkg/platform/onnx-runtime.js +42 -0
- package/dist-pkg/platform/reading-in-worker.d.ts +64 -0
- package/dist-pkg/platform/reading-in-worker.js +125 -0
- package/dist-pkg/platform/reading-model.d.ts +94 -0
- package/dist-pkg/platform/reading-model.js +272 -0
- package/dist-pkg/platform/reading-runtime.d.ts +24 -0
- package/dist-pkg/platform/reading-runtime.js +164 -0
- package/dist-pkg/platform/reading-worker.d.ts +41 -0
- package/dist-pkg/platform/reading-worker.js +58 -0
- package/dist-pkg/platform/reading.d.ts +80 -0
- package/dist-pkg/platform/reading.js +136 -0
- package/dist-pkg/platform/speech-recognition.d.ts +60 -0
- package/dist-pkg/platform/speech-recognition.js +104 -0
- package/dist-pkg/platform/speech.d.ts +23 -1
- package/dist-pkg/platform/speech.js +29 -18
- package/dist-pkg/platform/storage-keys.d.ts +96 -0
- package/dist-pkg/platform/storage-keys.js +106 -0
- package/dist-pkg/platform/storage.d.ts +46 -111
- package/dist-pkg/platform/storage.js +104 -169
- package/dist-pkg/platform/tts.d.ts +85 -39
- package/dist-pkg/platform/tts.js +447 -127
- package/dist-pkg/platform/vision-loop.d.ts +23 -0
- package/dist-pkg/platform/vision-loop.js +56 -0
- package/dist-pkg/platform/vision.d.ts +132 -0
- package/dist-pkg/platform/vision.js +117 -0
- package/dist-pkg/platform/voice-listener.d.ts +40 -0
- package/dist-pkg/platform/voice-listener.js +88 -0
- package/dist-pkg/platform/voice-plan.d.ts +15 -98
- package/dist-pkg/platform/voice-plan.js +56 -143
- package/dist-pkg/platform/vosk-runtime.d.ts +96 -0
- package/dist-pkg/platform/vosk-runtime.js +151 -0
- package/dist-pkg/platform/vosk-vocabulary.d.ts +6 -0
- package/dist-pkg/platform/vosk-vocabulary.js +178 -0
- package/dist-pkg/render/canvas.d.ts +13 -7
- package/dist-pkg/render/canvas.js +13 -13
- package/dist-pkg/render/crt.d.ts +48 -12
- package/dist-pkg/render/crt.js +78 -80
- package/dist-pkg/render/cvd-matrices.d.ts +22 -18
- package/dist-pkg/render/cvd-matrices.js +37 -38
- package/dist-pkg/render/hc-role-data.d.ts +7 -7
- package/dist-pkg/render/hc-role-data.js +14 -14
- package/dist-pkg/render/high-contrast.d.ts +67 -56
- package/dist-pkg/render/high-contrast.js +226 -217
- package/dist-pkg/render/low-vision-drawing.d.ts +5 -0
- package/dist-pkg/render/low-vision-drawing.js +46 -0
- package/dist-pkg/render/lq-filter.d.ts +32 -20
- package/dist-pkg/render/lq-filter.js +59 -65
- package/dist-pkg/render/port.d.ts +86 -88
- package/dist-pkg/render/port.js +17 -23
- package/dist-pkg/render/screen-pipeline.d.ts +42 -42
- package/dist-pkg/render/screen-pipeline.js +80 -87
- package/dist-pkg/render/sprite-fx.d.ts +2 -1
- package/dist-pkg/render/sprite-fx.js +16 -14
- package/dist-pkg/render/viewports.d.ts +20 -15
- package/dist-pkg/render/viewports.js +83 -114
- package/dist-pkg/render/viz-axes-labels.d.ts +47 -0
- package/dist-pkg/render/viz-axes-labels.js +88 -0
- package/dist-pkg/render/viz-axes.d.ts +89 -86
- package/dist-pkg/render/viz-axes.js +128 -116
- package/dist-pkg/render/viz-modes.d.ts +28 -29
- package/dist-pkg/render/viz-modes.js +40 -45
- package/dist-pkg/render/viz-refusal.d.ts +32 -0
- package/dist-pkg/render/viz-refusal.js +57 -0
- package/dist-pkg/render/viz-setters.d.ts +104 -66
- package/dist-pkg/render/viz-setters.js +182 -191
- package/dist-pkg/ui/aac-sets.d.ts +45 -0
- package/dist-pkg/ui/aac-sets.js +41 -0
- package/dist-pkg/ui/audio-choices.d.ts +68 -0
- package/dist-pkg/ui/audio-choices.js +98 -0
- package/dist-pkg/ui/audio-rows-that-apply.d.ts +12 -0
- package/dist-pkg/ui/audio-rows-that-apply.js +43 -0
- package/dist-pkg/ui/camera-control.d.ts +53 -0
- package/dist-pkg/ui/camera-control.js +48 -0
- package/dist-pkg/ui/changed-mark.d.ts +9 -8
- package/dist-pkg/ui/changed-mark.js +22 -53
- package/dist-pkg/ui/control-choices.d.ts +37 -0
- package/dist-pkg/ui/control-choices.js +84 -0
- package/dist-pkg/ui/debug-panel.d.ts +59 -43
- package/dist-pkg/ui/debug-panel.js +68 -69
- package/dist-pkg/ui/declared-words.d.ts +21 -0
- package/dist-pkg/ui/declared-words.js +66 -0
- package/dist-pkg/ui/dom.d.ts +10 -14
- package/dist-pkg/ui/dom.js +10 -47
- package/dist-pkg/ui/drawing-problems.d.ts +25 -0
- package/dist-pkg/ui/drawing-problems.js +94 -0
- package/dist-pkg/ui/eye-control.d.ts +36 -0
- package/dist-pkg/ui/eye-control.js +178 -0
- package/dist-pkg/ui/face-control.d.ts +25 -0
- package/dist-pkg/ui/face-control.js +183 -0
- package/dist-pkg/ui/focus-trap.d.ts +39 -49
- package/dist-pkg/ui/focus-trap.js +56 -65
- package/dist-pkg/ui/fonts.d.ts +157 -68
- package/dist-pkg/ui/fonts.js +305 -115
- package/dist-pkg/ui/footer-scroll-driver.d.ts +14 -0
- package/dist-pkg/ui/footer-scroll-driver.js +201 -0
- package/dist-pkg/ui/footer-scroll.d.ts +38 -0
- package/dist-pkg/ui/footer-scroll.js +55 -0
- package/dist-pkg/ui/game-options.d.ts +47 -0
- package/dist-pkg/ui/game-options.js +158 -0
- package/dist-pkg/ui/gaze-overlay.d.ts +64 -0
- package/dist-pkg/ui/gaze-overlay.js +165 -0
- package/dist-pkg/ui/hand-control.d.ts +25 -0
- package/dist-pkg/ui/hand-control.js +143 -0
- package/dist-pkg/ui/help-panel.d.ts +87 -0
- package/dist-pkg/ui/help-panel.js +242 -0
- package/dist-pkg/ui/hud-bands.d.ts +62 -0
- package/dist-pkg/ui/hud-bands.js +146 -0
- package/dist-pkg/ui/hud-row.d.ts +34 -0
- package/dist-pkg/ui/hud-row.js +80 -0
- package/dist-pkg/ui/hud.d.ts +73 -73
- package/dist-pkg/ui/hud.js +94 -98
- package/dist-pkg/ui/item-announcement.d.ts +13 -12
- package/dist-pkg/ui/item-announcement.js +25 -26
- package/dist-pkg/ui/layout.d.ts +103 -28
- package/dist-pkg/ui/layout.js +117 -99
- package/dist-pkg/ui/libras-avatar-clip.d.ts +30 -0
- package/dist-pkg/ui/libras-avatar-clip.js +86 -0
- package/dist-pkg/ui/libras-avatar-load.d.ts +52 -0
- package/dist-pkg/ui/libras-avatar-load.js +134 -0
- package/dist-pkg/ui/libras-avatar-plan.d.ts +102 -0
- package/dist-pkg/ui/libras-avatar-plan.js +211 -0
- package/dist-pkg/ui/libras-avatar-player.d.ts +28 -0
- package/dist-pkg/ui/libras-avatar-player.js +226 -0
- package/dist-pkg/ui/libras-avatar-stage.d.ts +37 -0
- package/dist-pkg/ui/libras-avatar-stage.js +208 -0
- package/dist-pkg/ui/libras-glosses.d.ts +28 -0
- package/dist-pkg/ui/libras-glosses.js +161 -0
- package/dist-pkg/ui/locale-flags.d.ts +16 -0
- package/dist-pkg/ui/locale-flags.js +30 -0
- package/dist-pkg/ui/loop-crash.d.ts +17 -17
- package/dist-pkg/ui/loop-crash.js +33 -62
- package/dist-pkg/ui/menu-intent.d.ts +61 -0
- package/dist-pkg/ui/menu-intent.js +88 -0
- package/dist-pkg/ui/menu-items.d.ts +20 -0
- package/dist-pkg/ui/menu-items.js +51 -0
- package/dist-pkg/ui/menu-nav.d.ts +98 -92
- package/dist-pkg/ui/menu-nav.js +398 -286
- package/dist-pkg/ui/mobility-choices.d.ts +26 -0
- package/dist-pkg/ui/mobility-choices.js +41 -0
- package/dist-pkg/ui/motion-choices.d.ts +31 -0
- package/dist-pkg/ui/motion-choices.js +68 -0
- package/dist-pkg/ui/motion-scene.d.ts +24 -19
- package/dist-pkg/ui/motion-scene.js +29 -61
- package/dist-pkg/ui/mount-panel.d.ts +81 -0
- package/dist-pkg/ui/mount-panel.js +61 -0
- package/dist-pkg/ui/panel-shell.d.ts +65 -31
- package/dist-pkg/ui/panel-shell.js +92 -66
- package/dist-pkg/ui/panel-widgets.d.ts +150 -0
- package/dist-pkg/ui/panel-widgets.js +294 -0
- package/dist-pkg/ui/pause-buttons.d.ts +35 -0
- package/dist-pkg/ui/pause-buttons.js +66 -0
- package/dist-pkg/ui/pause-icons.d.ts +261 -347
- package/dist-pkg/ui/pause-icons.js +671 -601
- package/dist-pkg/ui/pause-markup.d.ts +86 -0
- package/dist-pkg/ui/pause-markup.js +125 -0
- package/dist-pkg/ui/reach-notice.d.ts +22 -23
- package/dist-pkg/ui/reach-notice.js +55 -58
- package/dist-pkg/ui/scan-overlay.d.ts +23 -0
- package/dist-pkg/ui/scan-overlay.js +50 -0
- package/dist-pkg/ui/screen-text.d.ts +25 -0
- package/dist-pkg/ui/screen-text.js +152 -0
- package/dist-pkg/ui/session-clock.d.ts +32 -0
- package/dist-pkg/ui/session-clock.js +67 -0
- package/dist-pkg/ui/settings-aac.d.ts +70 -0
- package/dist-pkg/ui/settings-aac.js +208 -0
- package/dist-pkg/ui/settings-audio.d.ts +97 -114
- package/dist-pkg/ui/settings-audio.js +493 -425
- package/dist-pkg/ui/settings-controls.d.ts +53 -96
- package/dist-pkg/ui/settings-controls.js +143 -215
- package/dist-pkg/ui/settings-empathy.d.ts +39 -30
- package/dist-pkg/ui/settings-empathy.js +53 -60
- package/dist-pkg/ui/settings-mobility.d.ts +192 -0
- package/dist-pkg/ui/settings-mobility.js +332 -0
- package/dist-pkg/ui/settings-motion.d.ts +91 -70
- package/dist-pkg/ui/settings-motion.js +287 -198
- package/dist-pkg/ui/settings-panel.d.ts +58 -45
- package/dist-pkg/ui/settings-panel.js +189 -94
- package/dist-pkg/ui/settings-typo.d.ts +29 -78
- package/dist-pkg/ui/settings-typo.js +158 -129
- package/dist-pkg/ui/settings-visual.d.ts +29 -87
- package/dist-pkg/ui/settings-visual.js +240 -193
- package/dist-pkg/ui/shell.d.ts +112 -135
- package/dist-pkg/ui/shell.js +148 -217
- package/dist-pkg/ui/simulation-list.d.ts +27 -0
- package/dist-pkg/ui/simulation-list.js +76 -0
- package/dist-pkg/ui/simulation-over-the-world.d.ts +23 -0
- package/dist-pkg/ui/simulation-over-the-world.js +131 -0
- package/dist-pkg/ui/simulation-refusal.d.ts +5 -31
- package/dist-pkg/ui/simulation-refusal.js +8 -56
- package/dist-pkg/ui/switchable-control.d.ts +11 -0
- package/dist-pkg/ui/switchable-control.js +19 -0
- package/dist-pkg/ui/title.d.ts +3 -12
- package/dist-pkg/ui/title.js +49 -18
- package/dist-pkg/ui/top-band.d.ts +26 -0
- package/dist-pkg/ui/top-band.js +96 -0
- package/dist-pkg/ui/typo-choices.d.ts +71 -0
- package/dist-pkg/ui/typo-choices.js +85 -0
- package/dist-pkg/ui/visual-axes-panel.d.ts +11 -47
- package/dist-pkg/ui/visual-axes-panel.js +12 -94
- package/dist-pkg/ui/visual-choices.d.ts +77 -0
- package/dist-pkg/ui/visual-choices.js +107 -0
- package/dist-pkg/ui/vlibras.d.ts +77 -10
- package/dist-pkg/ui/vlibras.js +130 -81
- package/dist-pkg/ui/voice-control.d.ts +68 -0
- package/dist-pkg/ui/voice-control.js +276 -0
- package/dist-pkg/ui/voice-settings.d.ts +95 -0
- package/dist-pkg/ui/voice-settings.js +352 -0
- package/dist-pkg/ui/where-the-child-is.d.ts +31 -0
- package/dist-pkg/ui/where-the-child-is.js +70 -0
- package/docs/CREDITS.md +300 -44
- package/docs/LICENSES.md +189 -145
- package/package.json +51 -27
- package/scripts/check-cartridge.mjs +78 -0
- package/scripts/game-build.d.mts +25 -0
- package/scripts/game-build.mjs +144 -0
- package/scripts/heavy-into-the-delivery.mjs +388 -0
- package/scripts/libras-avatar.json +680 -0
- package/scripts/libras-avatar.mjs +246 -0
- package/scripts/libras-glosses/gloss.py +95 -0
- package/scripts/libras-glosses/pyproject.toml +32 -0
- package/scripts/libras-glosses/uv.lock +781 -0
- package/scripts/libras-glosses.mjs +351 -0
- package/scripts/licences/Apache-2.0.txt +176 -0
- package/scripts/licences/GPL-3.0.txt +674 -0
- package/scripts/licences/MIT.txt +17 -0
- package/scripts/licences/OFL-1.1.txt +86 -0
- package/scripts/licences/UFL-1.0.txt +96 -0
- package/scripts/licences/fonts.mjs +389 -0
- package/scripts/licences/third-party.mjs +301 -0
- package/scripts/licences/vosk-browser.NOTICE.txt +268 -0
- package/app/public/vendor/fonts/comicneue-400.woff2 +0 -0
- package/app/public/vendor/fonts/comicneue-700.woff2 +0 -0
- package/app/public/vendor/fonts/fondamento-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/fondamento-400.woff2 +0 -0
- package/app/public/vendor/fonts/inter-400.woff2 +0 -0
- package/app/public/vendor/fonts/inter-700.woff2 +0 -0
- package/app/public/vendor/fonts/lato-400.woff2 +0 -0
- package/app/public/vendor/fonts/lato-700.woff2 +0 -0
- package/app/public/vendor/fonts/literata-400.woff2 +0 -0
- package/app/public/vendor/fonts/literata-700.woff2 +0 -0
- package/app/public/vendor/fonts/newsreader-400.woff2 +0 -0
- package/app/public/vendor/fonts/newsreader-700.woff2 +0 -0
- package/app/public/vendor/fonts/opendyslexic-400.woff2 +0 -0
- package/app/public/vendor/fonts/opensans-400.woff2 +0 -0
- package/app/public/vendor/fonts/opensans-700.woff2 +0 -0
- package/app/public/vendor/fonts/pinyon-400.woff2 +0 -0
- package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
- package/app/public/vendor/fonts/quattro-400.woff2 +0 -0
- package/app/public/vendor/fonts/quattro-700.woff2 +0 -0
- package/app/public/vendor/fonts/sourcesans-400.woff2 +0 -0
- package/app/public/vendor/fonts/sourcesans-700.woff2 +0 -0
- package/app/public/vendor/fonts/sourceserif-400.woff2 +0 -0
- package/app/public/vendor/fonts/sourceserif-700.woff2 +0 -0
- package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
- package/dist-pkg/core/anel.d.ts +0 -17
- package/dist-pkg/core/anel.js +0 -31
- package/dist-pkg/core/collision.d.ts +0 -21
- package/dist-pkg/core/collision.js +0 -62
- package/dist-pkg/core/layers.d.ts +0 -63
- package/dist-pkg/core/layers.js +0 -95
- package/dist-pkg/core/letter-grid.d.ts +0 -55
- package/dist-pkg/core/letter-grid.js +0 -116
- package/dist-pkg/core/password.d.ts +0 -34
- package/dist-pkg/core/password.js +0 -205
- package/dist-pkg/core/rotulo-acessivel.d.ts +0 -17
- package/dist-pkg/core/rotulo-acessivel.js +0 -41
- package/dist-pkg/core/run-state.d.ts +0 -115
- package/dist-pkg/core/run-state.js +0 -40
- package/dist-pkg/core/tiles.d.ts +0 -12
- package/dist-pkg/core/tiles.js +0 -61
- package/dist-pkg/core/world.d.ts +0 -4
- package/dist-pkg/core/world.js +0 -58
- package/dist-pkg/input/origem-sintetica.d.ts +0 -44
- package/dist-pkg/input/origem-sintetica.js +0 -60
- package/dist-pkg/input/transporte-em-uso.d.ts +0 -101
- package/dist-pkg/input/transporte-em-uso.js +0 -130
- package/dist-pkg/platform/audio-nav.d.ts +0 -51
- package/dist-pkg/platform/audio-nav.js +0 -99
- package/dist-pkg/platform/guide-intensity.d.ts +0 -40
- package/dist-pkg/platform/guide-intensity.js +0 -73
- package/dist-pkg/platform/pesados-catalogo.d.ts +0 -14
- package/dist-pkg/platform/pesados-catalogo.js +0 -177
- package/dist-pkg/platform/pesados.d.ts +0 -31
- package/dist-pkg/platform/pesados.js +0 -75
- package/dist-pkg/platform/vozes-prontas.d.ts +0 -28
- package/dist-pkg/platform/vozes-prontas.js +0 -61
- package/dist-pkg/render/camera.d.ts +0 -58
- package/dist-pkg/render/camera.js +0 -105
- package/dist-pkg/render/cenario-data.d.ts +0 -127
- package/dist-pkg/render/cenario-data.js +0 -145
- package/dist-pkg/render/city-tex.d.ts +0 -63
- package/dist-pkg/render/city-tex.js +0 -228
- package/dist-pkg/render/city-tiles.d.ts +0 -11
- package/dist-pkg/render/city-tiles.js +0 -140
- package/dist-pkg/render/draw.d.ts +0 -149
- package/dist-pkg/render/draw.js +0 -231
- package/dist-pkg/render/fx.d.ts +0 -63
- package/dist-pkg/render/fx.js +0 -123
- package/dist-pkg/render/minimap.d.ts +0 -11
- package/dist-pkg/render/minimap.js +0 -94
- package/dist-pkg/render/parallax.d.ts +0 -85
- package/dist-pkg/render/parallax.js +0 -158
- package/dist-pkg/render/player-anim.d.ts +0 -71
- package/dist-pkg/render/player-anim.js +0 -152
- package/dist-pkg/render/recycling-tex.d.ts +0 -48
- package/dist-pkg/render/recycling-tex.js +0 -164
- package/dist-pkg/render/scene-city.d.ts +0 -77
- package/dist-pkg/render/scene-city.js +0 -181
- package/dist-pkg/render/scene-parallax.d.ts +0 -72
- package/dist-pkg/render/scene-parallax.js +0 -426
- package/dist-pkg/render/scene-sky.d.ts +0 -126
- package/dist-pkg/render/scene-sky.js +0 -295
- package/dist-pkg/render/set-cenario.d.ts +0 -54
- package/dist-pkg/render/set-cenario.js +0 -103
- package/dist-pkg/render/textures.d.ts +0 -75
- package/dist-pkg/render/textures.js +0 -240
- package/dist-pkg/render/title-scene.d.ts +0 -46
- package/dist-pkg/render/title-scene.js +0 -75
- package/dist-pkg/render/weather.d.ts +0 -96
- package/dist-pkg/render/weather.js +0 -171
- package/dist-pkg/render/wheelchair-sprites.d.ts +0 -32
- package/dist-pkg/render/wheelchair-sprites.js +0 -69
- package/dist-pkg/render/world-tex.d.ts +0 -18
- package/dist-pkg/render/world-tex.js +0 -87
- package/dist-pkg/ui/activities-menu.d.ts +0 -258
- package/dist-pkg/ui/activities-menu.js +0 -648
- package/dist-pkg/ui/caa-sets.d.ts +0 -39
- package/dist-pkg/ui/caa-sets.js +0 -65
- package/dist-pkg/ui/latch-refusal.d.ts +0 -32
- package/dist-pkg/ui/latch-refusal.js +0 -60
- package/dist-pkg/ui/map-hub.d.ts +0 -56
- package/dist-pkg/ui/map-hub.js +0 -138
- package/dist-pkg/ui/settings-caa.d.ts +0 -45
- package/dist-pkg/ui/settings-caa.js +0 -137
- package/dist-pkg/ui/settings-motor.d.ts +0 -175
- package/dist-pkg/ui/settings-motor.js +0 -342
- package/dist-pkg/ui/webcam.d.ts +0 -7
- package/dist-pkg/ui/webcam.js +0 -93
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// ui/libras-avatar-stage — THE FREE PLAYER'S STAGE: three.js, the avatar and its clips (ADR-0234, route B, phase B2).
|
|
3
|
+
//
|
|
4
|
+
// 📌 THE ONLY MODULE THAT NAMES three.js, and it is reached only by the dynamic `import()` in `ui/libras-avatar-player`, at the
|
|
5
|
+
// first sign in deaf mode (ADR-0234 errata): a child who never turns deaf mode on never downloads three.js. three.js 0.186.1,
|
|
6
|
+
// MIT, a runtime `dependency` pinned to that exact version; the types are `three-subset.d.ts`, beside this file.
|
|
7
|
+
//
|
|
8
|
+
// WHAT IT DOES: parses the delivery's `avatar.glb` (GLTFLoader, from the bytes the player fetched — no loader fetches anything),
|
|
9
|
+
// frames the signing space, and plays clips on one `AnimationMixer`, each once, its last frame held. Two things the export
|
|
10
|
+
// hands over are answered here and not hidden:
|
|
11
|
+
// · the face: a morph track names the head's node, which three.js loads as a group of two meshes — `bindFaceTracks` writes
|
|
12
|
+
// it once for each mesh that carries the targets;
|
|
13
|
+
// · the bones a clip does not move: the export dropped the tracks at the avatar's rest, so a clip that follows one that moved
|
|
14
|
+
// them must bring them back. The mixer does it: a property no running clip drives is blended back to the value it had when
|
|
15
|
+
// it was first bound — the avatar's rest, since nothing else ever poses it — and an action whose fade-out ended is stopped,
|
|
16
|
+
// which restores it outright (`tests/libras-avatar.browser.test.js` holds it).
|
|
17
|
+
//
|
|
18
|
+
// 🔴 TEXTURES ARE DECODED HERE, NOT BY THE LOADER'S OWN IMAGE LOADER: three.js reads an embedded image by `fetch`ing a `blob:`
|
|
19
|
+
// URL, which the delivery's `connect-src 'self'` refuses. `createImageBitmap` over the bytes fetches nothing, and it is also
|
|
20
|
+
// where the avatar's 2048² textures are brought down to `TEXTURE_SIZE` — the interpreter is a few hundred pixels tall.
|
|
21
|
+
import { AnimationClip, AnimationMixer, Box3, DirectionalLight, HemisphereLight, LoopOnce, PerspectiveCamera, Scene, Vector3, WebGLRenderer, } from 'three';
|
|
22
|
+
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
23
|
+
import { bindFaceTracks, clipFrom } from './libras-avatar-clip.js';
|
|
24
|
+
/**
|
|
25
|
+
* The longest side a texture keeps, in pixels. 📏 The export's head and body textures are 2048² each: 16 MiB of pixels apiece
|
|
26
|
+
* once decoded, 21 with mipmaps; at 1024² a quarter of that. The avatar is drawn a few hundred pixels tall.
|
|
27
|
+
*/
|
|
28
|
+
const TEXTURE_SIZE = 1024;
|
|
29
|
+
const isMesh = (o) => o.isMesh === true;
|
|
30
|
+
/** The loader's texture step, replaced: the embedded image decoded from its bytes (no `blob:` fetch), then brought down to `size`. */
|
|
31
|
+
function decodedTextures(size) {
|
|
32
|
+
return (parser) => ({
|
|
33
|
+
name: 'incl_decoded_textures',
|
|
34
|
+
loadTexture: (textureIndex) => {
|
|
35
|
+
const texture = parser.json.textures?.[textureIndex];
|
|
36
|
+
const source = texture?.source;
|
|
37
|
+
const image = source === undefined ? undefined : parser.json.images?.[source];
|
|
38
|
+
if (source === undefined || image?.bufferView === undefined)
|
|
39
|
+
return null; // not embedded: the loader's own path
|
|
40
|
+
return parser.getDependency('bufferView', image.bufferView).then(async (bytes) => {
|
|
41
|
+
const blob = new Blob([bytes], { type: image.mimeType ?? 'image/png' });
|
|
42
|
+
let bitmap = await createImageBitmap(blob, { premultiplyAlpha: 'none', colorSpaceConversion: 'none' });
|
|
43
|
+
const scale = Math.min(1, size / Math.max(bitmap.width, bitmap.height));
|
|
44
|
+
if (scale < 1) {
|
|
45
|
+
const full = bitmap;
|
|
46
|
+
bitmap = await createImageBitmap(full, {
|
|
47
|
+
premultiplyAlpha: 'none', colorSpaceConversion: 'none', resizeQuality: 'high',
|
|
48
|
+
resizeWidth: Math.max(1, Math.round(full.width * scale)), resizeHeight: Math.max(1, Math.round(full.height * scale)),
|
|
49
|
+
});
|
|
50
|
+
full.close();
|
|
51
|
+
}
|
|
52
|
+
const decoded = bitmap;
|
|
53
|
+
return parser.loadTextureImage(textureIndex, source, {
|
|
54
|
+
isImageBitmapLoader: true,
|
|
55
|
+
load: (_url, onLoad) => { onLoad(decoded); },
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* The meshes a morph track on each node must reach: for a node that is not itself a mesh with morph targets but holds meshes
|
|
63
|
+
* that have them (the head, loaded as a group of two), their uuids — three.js binds a track by uuid as well as by name.
|
|
64
|
+
*/
|
|
65
|
+
function faceMeshes(root) {
|
|
66
|
+
const out = new Map();
|
|
67
|
+
root.traverse((node) => {
|
|
68
|
+
if (isMesh(node) && node.morphTargetDictionary)
|
|
69
|
+
return;
|
|
70
|
+
const meshes = node.children.filter((c) => isMesh(c) && !!c.morphTargetDictionary).map((c) => c.uuid);
|
|
71
|
+
if (meshes.length && node.name)
|
|
72
|
+
out.set(node.name, meshes);
|
|
73
|
+
});
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The signing space, framed on the head (the mesh with morph targets): from just above it to below the waist, where Libras signs
|
|
78
|
+
* are made. 📏 Tuned on the export's avatar (screenshot of 2026-09-25): its head mesh runs from the hair's base to the neck, and
|
|
79
|
+
* 1.45 of its heights below it is a little under the belt — the face about a quarter of the frame's height.
|
|
80
|
+
*/
|
|
81
|
+
function frame(root, camera) {
|
|
82
|
+
root.updateMatrixWorld(true);
|
|
83
|
+
const face = new Box3();
|
|
84
|
+
root.traverse((node) => { if (isMesh(node) && node.morphTargetDictionary)
|
|
85
|
+
face.expandByObject(node); });
|
|
86
|
+
const box = face.isEmpty() ? new Box3().setFromObject(root) : face;
|
|
87
|
+
const center = box.getCenter(new Vector3());
|
|
88
|
+
const head = box.getSize(new Vector3()).y || 1;
|
|
89
|
+
const top = box.max.y + 0.15 * head;
|
|
90
|
+
const bottom = box.min.y - 1.45 * head;
|
|
91
|
+
const middle = (top + bottom) / 2;
|
|
92
|
+
const tall = top - bottom;
|
|
93
|
+
const fov = 30;
|
|
94
|
+
const distance = (tall / 2) / Math.tan((fov / 2) * Math.PI / 180);
|
|
95
|
+
camera.position.set(center.x, middle, box.max.z + distance);
|
|
96
|
+
camera.lookAt(center.x, middle, center.z);
|
|
97
|
+
camera.updateProjectionMatrix();
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Builds the stage over `canvas`. THROWS where it cannot draw — no WebGL, an avatar three.js cannot read — and the player turns
|
|
101
|
+
* that into «signing unavailable», with the reason.
|
|
102
|
+
*/
|
|
103
|
+
export async function createAvatarStage({ canvas, avatar, textureSize = TEXTURE_SIZE }) {
|
|
104
|
+
const renderer = new WebGLRenderer({ canvas, antialias: true, alpha: true, powerPreference: 'low-power' });
|
|
105
|
+
renderer.setClearColor(0x000000, 0);
|
|
106
|
+
let root;
|
|
107
|
+
try {
|
|
108
|
+
const loader = new GLTFLoader().register(decodedTextures(textureSize));
|
|
109
|
+
root = (await loader.parseAsync(avatar, '')).scene;
|
|
110
|
+
}
|
|
111
|
+
catch (failure) {
|
|
112
|
+
renderer.dispose();
|
|
113
|
+
renderer.forceContextLoss();
|
|
114
|
+
throw failure;
|
|
115
|
+
}
|
|
116
|
+
const scene = new Scene();
|
|
117
|
+
const camera = new PerspectiveCamera(30, 1, 0.05, 200);
|
|
118
|
+
const sky = new HemisphereLight(0xffffff, 0x8a8a9a, 2.2);
|
|
119
|
+
const key = new DirectionalLight(0xffffff, 1.6);
|
|
120
|
+
key.position.set(2, 6, 10);
|
|
121
|
+
scene.add(root, sky, key);
|
|
122
|
+
frame(root, camera);
|
|
123
|
+
const mixer = new AnimationMixer(root);
|
|
124
|
+
const meshesOf = faceMeshes(root);
|
|
125
|
+
/** Each clip twice: the same sign signed twice in a row fades into its own copy, not into itself. */
|
|
126
|
+
const clips = new Map();
|
|
127
|
+
let current = null;
|
|
128
|
+
/** Actions fading out or held: stopped once their weight reaches nothing, which returns what they drove to rest. */
|
|
129
|
+
const leaving = new Set();
|
|
130
|
+
const draw = () => { renderer.render(scene, camera); };
|
|
131
|
+
return {
|
|
132
|
+
scene: root,
|
|
133
|
+
prepare: (name, json, from) => {
|
|
134
|
+
const bound = bindFaceTracks(clipFrom(json, from), (node) => meshesOf.get(node) ?? null);
|
|
135
|
+
// 🔴 `AnimationClip.parse` copies the JSON's `uuid`, and the export writes none: every parsed clip has the SAME uuid
|
|
136
|
+
// (`undefined`), and the mixer keys its actions by uuid — so every clip played as the first one ever asked for. A clone
|
|
137
|
+
// is built by the constructor, which gives it a uuid of its own.
|
|
138
|
+
const clip = AnimationClip.parse(bound).clone();
|
|
139
|
+
clip.name = name;
|
|
140
|
+
clips.set(name, [clip, clip.clone()]);
|
|
141
|
+
},
|
|
142
|
+
has: (name) => clips.has(name),
|
|
143
|
+
start: (name, fade, at) => {
|
|
144
|
+
const pair = clips.get(name);
|
|
145
|
+
if (!pair)
|
|
146
|
+
return;
|
|
147
|
+
const clip = current?.getClip() === pair[0] ? pair[1] : pair[0];
|
|
148
|
+
const action = mixer.clipAction(clip);
|
|
149
|
+
leaving.delete(action);
|
|
150
|
+
action.reset();
|
|
151
|
+
action.setLoop(LoopOnce, 1);
|
|
152
|
+
action.clampWhenFinished = true;
|
|
153
|
+
action.time = at;
|
|
154
|
+
action.play();
|
|
155
|
+
if (current) {
|
|
156
|
+
if (fade > 0) {
|
|
157
|
+
action.crossFadeFrom(current, fade, false);
|
|
158
|
+
leaving.add(current);
|
|
159
|
+
}
|
|
160
|
+
else
|
|
161
|
+
current.stop();
|
|
162
|
+
}
|
|
163
|
+
current = action;
|
|
164
|
+
},
|
|
165
|
+
advance: (dt) => {
|
|
166
|
+
mixer.update(dt);
|
|
167
|
+
for (const action of leaving) {
|
|
168
|
+
if (!action.enabled) {
|
|
169
|
+
action.stop();
|
|
170
|
+
leaving.delete(action);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
draw();
|
|
174
|
+
},
|
|
175
|
+
resize: (width, height, pixelRatio) => {
|
|
176
|
+
renderer.setPixelRatio(pixelRatio);
|
|
177
|
+
renderer.setSize(Math.max(1, Math.round(width)), Math.max(1, Math.round(height)), false);
|
|
178
|
+
camera.aspect = width > 0 && height > 0 ? width / height : 1;
|
|
179
|
+
camera.updateProjectionMatrix();
|
|
180
|
+
draw();
|
|
181
|
+
},
|
|
182
|
+
stats: () => ({
|
|
183
|
+
textures: renderer.info.memory.textures, geometries: renderer.info.memory.geometries,
|
|
184
|
+
calls: renderer.info.render.calls, triangles: renderer.info.render.triangles,
|
|
185
|
+
}),
|
|
186
|
+
dispose: () => {
|
|
187
|
+
mixer.stopAllAction();
|
|
188
|
+
mixer.uncacheRoot(root);
|
|
189
|
+
root.traverse((node) => {
|
|
190
|
+
if (!isMesh(node))
|
|
191
|
+
return;
|
|
192
|
+
node.geometry.dispose();
|
|
193
|
+
for (const material of Array.isArray(node.material) ? node.material : [node.material]) {
|
|
194
|
+
for (const value of Object.values(material)) {
|
|
195
|
+
const texture = value;
|
|
196
|
+
if (texture?.isTexture) {
|
|
197
|
+
texture.dispose?.();
|
|
198
|
+
texture.image?.close?.();
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
material.dispose();
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
renderer.dispose();
|
|
205
|
+
renderer.forceContextLoss();
|
|
206
|
+
},
|
|
207
|
+
};
|
|
208
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
/** The file the delivery writes beside the avatar (`LIBRAS_AVATAR_FOLDER`), read by the interpreter. */
|
|
3
|
+
export declare const LIBRAS_GLOSSES_FILE = "glosses.json";
|
|
4
|
+
/** The file's shape. `glosses` holds `[text, gloss]` pairs; a text with `{name}` holes is a template, its gloss has them too. */
|
|
5
|
+
export interface GlossFile {
|
|
6
|
+
readonly format: 1;
|
|
7
|
+
/** What made the glosses, e.g. `vlibras-translator 1.3.3 (rules) · pt_core_news_md 3.8.0`. For whoever reads the file. */
|
|
8
|
+
readonly made: string;
|
|
9
|
+
readonly glosses: readonly (readonly [text: string, gloss: string])[];
|
|
10
|
+
}
|
|
11
|
+
/** The text a player is handed for a text on screen. */
|
|
12
|
+
export type Glosser = (text: string) => string;
|
|
13
|
+
/**
|
|
14
|
+
* The text as the player is handed it where no gloss is known: its words in capitals, accents stripped but Ç kept, anything
|
|
15
|
+
* that is not a letter or a digit a separator. Not a gloss — see the header. The marks are stripped from each run of text
|
|
16
|
+
* with no Ç in it: Ç is the only letter with a mark that the manual alphabet signs as itself.
|
|
17
|
+
*/
|
|
18
|
+
export declare function provisionalGloss(text: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* The glosser over a delivered file: known texts and templates first, today's rule for what neither covers. A file that is not
|
|
21
|
+
* the delivery's (`null`, a fallback page, another format) glosses nothing, and every text gets today's rule.
|
|
22
|
+
*/
|
|
23
|
+
export declare function glosserOf(data: unknown): Glosser;
|
|
24
|
+
/**
|
|
25
|
+
* The glosser of the delivery the page came from: `glosses.json` read through `fetchFile`, or — no fetch, no file, not the
|
|
26
|
+
* delivery's — today's rule for every text. Never rejects: a missing file costs the glosses, not the signing.
|
|
27
|
+
*/
|
|
28
|
+
export declare function loadGlosser(fetchFile: ((url: string) => Promise<Response>) | undefined, url: string): Promise<Glosser>;
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// ui/libras-glosses — WHAT THE INTERPRETER HANDS THE PLAYER: the Libras gloss of the text the sonar found (ADR-0234, plan
|
|
3
|
+
// item 5b).
|
|
4
|
+
//
|
|
5
|
+
// The player signs a GLOSS — Libras word order, in sign names — and the sonar finds Portuguese. Translating needs spaCy's full
|
|
6
|
+
// Portuguese parser, which does not run in a browser, and it does not have to: the text a child can be shown is AUTHORED, so the
|
|
7
|
+
// delivery glosses it once, at build time, with LAViD's rule-based `vlibras-translator` (`scripts/libras-glosses.mjs`, run by
|
|
8
|
+
// `inclusionist-heavy --libras`), and writes `glosses.json` beside the avatar. At run time nothing is translated: the text is
|
|
9
|
+
// LOOKED UP here.
|
|
10
|
+
//
|
|
11
|
+
// WHAT IS IN THE FILE: the engine's own Portuguese dictionary, and the game's when its delivery names it (`--libras-texts`).
|
|
12
|
+
// A string with `{param}` holes is kept as a TEMPLATE, glossed with its holes in place (`JOGADOR {n} ENTRAR [EXCLAMAÇÃO]`),
|
|
13
|
+
// because the value arrives only at run time: here the value is looked up in turn, and a value with no gloss of its own — a
|
|
14
|
+
// number, a name — is handed over by today's rule below, which the player fingerspells letter by letter.
|
|
15
|
+
//
|
|
16
|
+
// HOW A SCREEN IS LOOKED UP: the sonar reads the screen as sentences (`ui/screen-text`, one per line, joined), so the text is
|
|
17
|
+
// cut at sentence ends and, from the first sentence on, the LONGEST run of sentences that is a known text or matches a template
|
|
18
|
+
// is taken — a dictionary string may itself hold two sentences, or a colon the reader cut at. A sentence nothing matches falls
|
|
19
|
+
// back to today's rule, alone; the rest of the screen keeps its glosses.
|
|
20
|
+
//
|
|
21
|
+
// 📌 TODAY'S RULE (`provisionalGloss`), which stays the fallback: the words in capitals with the accents stripped, anything that
|
|
22
|
+
// is not a letter or a digit a separator. Not a gloss — the player fingerspells it — and the accents go because the player has
|
|
23
|
+
// no clip for an accented letter (the avatar's alphabet is A–Z and Ç, `scripts/libras-avatar.json`). Ç IS KEPT: it is a letter of
|
|
24
|
+
// the Libras manual alphabet, with its own handshape and movement, and the avatar carries its clip. The build spells with the same
|
|
25
|
+
// rule (`scripts/libras-glosses.mjs` `spelledWord`, ADR-0234 erratum «A WORD WITH NO SIGN IS SPELLED AS IT IS WRITTEN»).
|
|
26
|
+
//
|
|
27
|
+
// It reaches no global: the file comes through the fetch the interpreter was given (ADR-0232).
|
|
28
|
+
/** The file the delivery writes beside the avatar (`LIBRAS_AVATAR_FOLDER`), read by the interpreter. */
|
|
29
|
+
export const LIBRAS_GLOSSES_FILE = 'glosses.json';
|
|
30
|
+
/**
|
|
31
|
+
* The text as the player is handed it where no gloss is known: its words in capitals, accents stripped but Ç kept, anything
|
|
32
|
+
* that is not a letter or a digit a separator. Not a gloss — see the header. The marks are stripped from each run of text
|
|
33
|
+
* with no Ç in it: Ç is the only letter with a mark that the manual alphabet signs as itself.
|
|
34
|
+
*/
|
|
35
|
+
export function provisionalGloss(text) {
|
|
36
|
+
return text.normalize('NFC').replace(/[^Çç]+/gu, (run) => run.normalize('NFD').replace(/\p{M}+/gu, '')).toUpperCase()
|
|
37
|
+
.split(/[^\p{L}\p{N}]+/u).filter(Boolean).join(' ');
|
|
38
|
+
}
|
|
39
|
+
/** Where the sonar's text is cut: after a sentence's end, before the next. */
|
|
40
|
+
const SENTENCE_END = /(?<=[.!?…:;])\s+/u;
|
|
41
|
+
/** A hole of a template (`split` and `replace` read the capture; `HAS_HOLE` only asks). */
|
|
42
|
+
const HOLE = /\{([^{}\s]+)\}/gu;
|
|
43
|
+
const HAS_HOLE = /\{[^{}\s]+\}/u;
|
|
44
|
+
const NUMBERED_HOLE = /\{\d+\}/gu;
|
|
45
|
+
/**
|
|
46
|
+
* What a hole matches: any run of characters that does not cross a sentence's end — a template that starts or ends with a hole
|
|
47
|
+
* would otherwise swallow the sentences around it when a longer run is tried.
|
|
48
|
+
*/
|
|
49
|
+
const VALUE = String.raw `((?:(?![.!?…:;]\s).)+?)`;
|
|
50
|
+
/** What the edges of a text may carry that does not change it: punctuation, an icon, a space. */
|
|
51
|
+
const EDGES = /^[^\p{L}\p{N}{}]+|[^\p{L}\p{N}{}]+$/gu;
|
|
52
|
+
/** The same text, however the screen wrote it: one space, no punctuation or icon at the edges, lower case. */
|
|
53
|
+
function keyOf(text) {
|
|
54
|
+
return text.normalize('NFC').replace(/\s+/gu, ' ').replace(EDGES, '').toLocaleLowerCase('pt-BR');
|
|
55
|
+
}
|
|
56
|
+
const sentencesOf = (text) => text.split(SENTENCE_END).filter((s) => s.trim() !== '');
|
|
57
|
+
const escapeRegExp = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
58
|
+
/** A template's text as a pattern over keys: its fixed parts as keys are, each hole a `VALUE`. */
|
|
59
|
+
function templateOf(text, gloss) {
|
|
60
|
+
const names = [];
|
|
61
|
+
const parts = text.split(HOLE); // even indices are fixed text, odd ones hole names
|
|
62
|
+
for (let i = 1; i < parts.length; i += 2)
|
|
63
|
+
names.push(parts[i]);
|
|
64
|
+
// each hole becomes `{0}`, `{1}`…: braces survive `keyOf`, and a name's capitals would not
|
|
65
|
+
const marked = parts.map((p, i) => (i % 2 ? `{${(i - 1) / 2}}` : p)).join('');
|
|
66
|
+
const key = keyOf(marked);
|
|
67
|
+
const fixed = key.replace(NUMBERED_HOLE, '');
|
|
68
|
+
if (!/[\p{L}\p{N}]/u.test(fixed))
|
|
69
|
+
return null; // all holes: it would match anything
|
|
70
|
+
const source = key.split(NUMBERED_HOLE).map(escapeRegExp).join(VALUE);
|
|
71
|
+
return { pattern: new RegExp(`^${source}$`, 'u'), names, gloss, fixed: fixed.length };
|
|
72
|
+
}
|
|
73
|
+
/** Is this a file the delivery wrote? Anything else is read as no file at all. */
|
|
74
|
+
function isGlossFile(data) {
|
|
75
|
+
const file = data;
|
|
76
|
+
return !!file && file.format === 1 && Array.isArray(file.glosses)
|
|
77
|
+
&& file.glosses.every((g) => Array.isArray(g) && typeof g[0] === 'string' && typeof g[1] === 'string');
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The glosser over a delivered file: known texts and templates first, today's rule for what neither covers. A file that is not
|
|
81
|
+
* the delivery's (`null`, a fallback page, another format) glosses nothing, and every text gets today's rule.
|
|
82
|
+
*/
|
|
83
|
+
export function glosserOf(data) {
|
|
84
|
+
const exact = new Map();
|
|
85
|
+
const templates = [];
|
|
86
|
+
let longest = 1;
|
|
87
|
+
if (isGlossFile(data)) {
|
|
88
|
+
for (const [text, gloss] of data.glosses) {
|
|
89
|
+
if (!gloss.trim())
|
|
90
|
+
continue;
|
|
91
|
+
if (HAS_HOLE.test(text)) {
|
|
92
|
+
const template = templateOf(text, gloss);
|
|
93
|
+
if (template)
|
|
94
|
+
templates.push(template);
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
const key = keyOf(text);
|
|
98
|
+
if (key && !exact.has(key))
|
|
99
|
+
exact.set(key, gloss);
|
|
100
|
+
}
|
|
101
|
+
longest = Math.max(longest, sentencesOf(text).length);
|
|
102
|
+
}
|
|
103
|
+
templates.sort((a, b) => b.fixed - a.fixed);
|
|
104
|
+
}
|
|
105
|
+
/** A value put into a template's hole: its own gloss when it is a known text, else today's rule. */
|
|
106
|
+
const valueGloss = (value) => exact.get(keyOf(value)) ?? provisionalGloss(value);
|
|
107
|
+
const lookUp = (piece) => {
|
|
108
|
+
const key = keyOf(piece);
|
|
109
|
+
if (!key)
|
|
110
|
+
return null;
|
|
111
|
+
const known = exact.get(key);
|
|
112
|
+
if (known !== undefined)
|
|
113
|
+
return known;
|
|
114
|
+
for (const t of templates) {
|
|
115
|
+
const m = t.pattern.exec(key);
|
|
116
|
+
if (!m)
|
|
117
|
+
continue;
|
|
118
|
+
const values = new Map(t.names.map((name, i) => [name, valueGloss(m[i + 1])]));
|
|
119
|
+
return t.gloss.replace(HOLE, (_, name) => values.get(name) ?? '').replace(/\s+/gu, ' ').trim();
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
};
|
|
123
|
+
return (text) => {
|
|
124
|
+
const sentences = sentencesOf(text);
|
|
125
|
+
const out = [];
|
|
126
|
+
for (let i = 0; i < sentences.length;) {
|
|
127
|
+
let taken = 0;
|
|
128
|
+
for (let span = Math.min(longest, sentences.length - i); span >= 1 && !taken; span--) {
|
|
129
|
+
const gloss = lookUp(sentences.slice(i, i + span).join(' '));
|
|
130
|
+
if (gloss !== null) {
|
|
131
|
+
if (gloss)
|
|
132
|
+
out.push(gloss);
|
|
133
|
+
taken = span;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (!taken) {
|
|
137
|
+
const spelled = provisionalGloss(sentences[i]);
|
|
138
|
+
if (spelled)
|
|
139
|
+
out.push(spelled);
|
|
140
|
+
taken = 1;
|
|
141
|
+
}
|
|
142
|
+
i += taken;
|
|
143
|
+
}
|
|
144
|
+
return out.join(' ');
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* The glosser of the delivery the page came from: `glosses.json` read through `fetchFile`, or — no fetch, no file, not the
|
|
149
|
+
* delivery's — today's rule for every text. Never rejects: a missing file costs the glosses, not the signing.
|
|
150
|
+
*/
|
|
151
|
+
export async function loadGlosser(fetchFile, url) {
|
|
152
|
+
if (!fetchFile)
|
|
153
|
+
return glosserOf(null);
|
|
154
|
+
try {
|
|
155
|
+
const resp = await fetchFile(url);
|
|
156
|
+
return glosserOf(resp.ok ? await resp.json() : null);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return glosserOf(null);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
/** The locales the button cycles, in the Dev's order. */
|
|
3
|
+
export declare const LOCALE_CYCLE: readonly ["pt", "en", "es"];
|
|
4
|
+
export type CycleLocale = (typeof LOCALE_CYCLE)[number];
|
|
5
|
+
/** The next locale of the cycle; a locale outside it starts the cycle again. */
|
|
6
|
+
export declare const nextLocale: (current: string) => CycleLocale;
|
|
7
|
+
/** Each language in its own words, with the place its flag stands for. */
|
|
8
|
+
export declare const LANGUAGE_NAME: {
|
|
9
|
+
readonly [L in CycleLocale]: string;
|
|
10
|
+
};
|
|
11
|
+
/** Brazil, the United States and Mexico, simplified to icon size. */
|
|
12
|
+
export declare const FLAG_SVG: {
|
|
13
|
+
readonly [L in CycleLocale]: string;
|
|
14
|
+
};
|
|
15
|
+
/** The flag of a locale, or Brazil's for one outside the cycle (the engine's base language). */
|
|
16
|
+
export declare const flagOf: (locale: string) => string;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// ui/locale-flags — THE LANGUAGE BUTTON'S THREE FLAGS (the Dev, 2026-09-16: «três bandeiras que se intercalam cada vez que o botão é
|
|
3
|
+
// apertado: Brasil, Estados Unidos e México. Cada bandeira indica a localização para qual o app está configurado»).
|
|
4
|
+
//
|
|
5
|
+
// The flags are DRAWN, not emoji: 📏 measured in Chromium on Windows, 🇧🇷 🇺🇸 🇲🇽 render as the letters BR, US and MX (zero coloured pixels,
|
|
6
|
+
// against 716 for 🧑) — a school's Windows machine would show the child two letters where a flag was promised. Simplified to their shapes
|
|
7
|
+
// and colours at icon size. Each language is named in itself, so a child who does not read the current language still finds their own.
|
|
8
|
+
/** The locales the button cycles, in the Dev's order. */
|
|
9
|
+
export const LOCALE_CYCLE = ['pt', 'en', 'es'];
|
|
10
|
+
/** The next locale of the cycle; a locale outside it starts the cycle again. */
|
|
11
|
+
export const nextLocale = (current) => {
|
|
12
|
+
const i = LOCALE_CYCLE.indexOf(current);
|
|
13
|
+
return LOCALE_CYCLE[(i + 1) % LOCALE_CYCLE.length];
|
|
14
|
+
};
|
|
15
|
+
/** Each language in its own words, with the place its flag stands for. */
|
|
16
|
+
export const LANGUAGE_NAME = {
|
|
17
|
+
pt: 'Português (Brasil)', en: 'English (United States)', es: 'Español (México)',
|
|
18
|
+
};
|
|
19
|
+
const svg = (inner) => `<svg class="pi-flag" viewBox="0 0 30 20" width="1.4em" height="0.93em" aria-hidden="true" focusable="false">${inner}</svg>`;
|
|
20
|
+
/** Brazil, the United States and Mexico, simplified to icon size. */
|
|
21
|
+
export const FLAG_SVG = {
|
|
22
|
+
pt: svg('<rect width="30" height="20" fill="#009c3b"/><polygon points="15,2.2 27.4,10 15,17.8 2.6,10" fill="#ffdf00"/><circle cx="15" cy="10" r="4.4" fill="#002776"/>'),
|
|
23
|
+
en: svg('<rect width="30" height="20" fill="#b22234"/>'
|
|
24
|
+
+ [1, 3, 5, 7, 9, 11].map((r) => `<rect y="${(r * 20) / 13}" width="30" height="${20 / 13}" fill="#fff"/>`).join('')
|
|
25
|
+
+ '<rect width="12" height="10.77" fill="#3c3b6e"/>'
|
|
26
|
+
+ [[2, 2], [6, 2], [10, 2], [4, 5.4], [8, 5.4], [2, 8.8], [6, 8.8], [10, 8.8]].map(([x, y]) => `<circle cx="${x}" cy="${y}" r=".7" fill="#fff"/>`).join('')),
|
|
27
|
+
es: svg('<rect width="10" height="20" fill="#006847"/><rect x="10" width="10" height="20" fill="#fff"/><rect x="20" width="10" height="20" fill="#ce1126"/><circle cx="15" cy="10" r="2.6" fill="#8b5a2b"/>'),
|
|
28
|
+
};
|
|
29
|
+
/** The flag of a locale, or Brazil's for one outside the cycle (the engine's base language). */
|
|
30
|
+
export const flagOf = (locale) => FLAG_SVG[LOCALE_CYCLE.includes(locale) ? locale : 'pt'];
|
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
-
|
|
3
|
-
export
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
2
|
+
import type { Translate } from '../core/i18n.js';
|
|
3
|
+
export interface CrashNoticeCtx {
|
|
4
|
+
/** Translates in the page's language — the root's translator (ADR-0232 D3). REQUIRED: text built from nowhere is a raw key. */
|
|
5
|
+
t: Translate;
|
|
6
|
+
/** `querySelector` of this game's document. Injected: the engine receives its own. */
|
|
7
|
+
find: (sel: string) => HTMLElement | null;
|
|
8
|
+
/** `document.createElement` — the notice box is a real element, not a pseudo-element. */
|
|
9
|
+
create: (tag: string) => HTMLElement;
|
|
10
|
+
/** The spoken narration, when the game has one. Absent = the written notice is enough. */
|
|
11
|
+
narrate?: (text: string) => void;
|
|
11
12
|
}
|
|
12
13
|
/**
|
|
13
|
-
*
|
|
14
|
+
* Builds the handler for `startLoop(ticker, frame, maxDt, { speed, onFailure })`.
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* viu a tela parar.
|
|
16
|
+
* THE ORDER OF THE CHANNELS IS DELIBERATE: the console FIRST, because whoever debugs must not lose the error if the DOM
|
|
17
|
+
* is broken — and a broken DOM is one plausible reason the frame threw. Then the screen reader, which has no other clue
|
|
18
|
+
* at all. The visible one last, because whoever sees it has already seen the screen stop.
|
|
19
19
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
20
|
+
* Each channel is isolated from the next: if narration throws, the written notice has already gone out. A notice that
|
|
21
|
+
* fails halfway must deliver the other half — the same rule `startLoop` applies to this very callback.
|
|
22
22
|
*/
|
|
23
|
-
export declare function
|
|
23
|
+
export declare function createCrashNotice(ctx: CrashNoticeCtx): (failure: unknown) => void;
|
|
@@ -1,79 +1,50 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
// `core/loop.startLoop` já PARA quando um quadro lança, e já chama `aoFalhar`. O que faltava era alguém que
|
|
5
|
-
// passasse um `aoFalhar` — e a `confirmation` do ADR-0054 diz isso por escrito: *"a raiz de composição ainda
|
|
6
|
-
// não liga o `aoFalhar`, então hoje o laço para em silêncio... enquanto isto não existir este registro é só
|
|
7
|
-
// metade verdadeiro."*
|
|
8
|
-
//
|
|
9
|
-
// ⚠️ E A METADE QUE FALTAVA É A QUE IMPORTA. Tela congelada é sintoma VISUAL. No modo cego, um jogo parado e
|
|
10
|
-
// um jogo pensando produzem a mesma coisa: silêncio. A criança fica a esperar por um jogo que já morreu, sem
|
|
11
|
-
// nada que lhe diga para recarregar — e o único aviso que existia era um erro no console, que ela não lê.
|
|
12
|
-
//
|
|
13
|
-
// ⚠️ POR QUE UM MÓDULO E NÃO UMA LINHA DENTRO DE `createGame`. Porque o jogo de plataforma NÃO PASSA POR
|
|
14
|
-
// `createGame` — `main.ts` monta a engine à mão, e é ele quem chama `startLoop`. Uma solução que vivesse só
|
|
15
|
-
// na raiz de composição da engine deixaria de fora exatamente o jogo que existe. As duas raízes importam
|
|
16
|
-
// daqui.
|
|
17
|
-
//
|
|
18
|
-
// ⚠️ E NÃO USA `srAlert` DE `core/a11y-sr`, o que é uma escolha e não um esquecimento:
|
|
19
|
-
//
|
|
20
|
-
// · `srAlert` limpa, espera um `requestAnimationFrame` e só então escreve. Essa dança existe para forçar o
|
|
21
|
-
// leitor a REANUNCIAR texto repetido; uma queda é anunciada uma vez e nunca se repete, então não compra
|
|
22
|
-
// nada aqui — e faz o aviso depender de um quadro futuro, no instante em que os quadros pararam.
|
|
23
|
-
// · `srAlert` busca no documento GLOBAL. Quem recebe o documento por injeção (a engine, um teste, um
|
|
24
|
-
// segundo jogo na mesma página) perderia o aviso. O anúncio de que tudo parou é o último lugar que
|
|
25
|
-
// deveria depender de uma busca global.
|
|
26
|
-
import { t } from '../core/i18n.js';
|
|
27
|
-
/** O id da caixa do aviso. Estável porque a folha de estilo e o teste a procuram. */
|
|
28
|
-
export const AVISO_DE_QUEDA_ID = 'incl-parou';
|
|
2
|
+
/** The id of the notice box. Stable because the stylesheet and the test look for it. */
|
|
3
|
+
const CRASH_NOTICE_ID = 'incl-parou';
|
|
29
4
|
/**
|
|
30
|
-
*
|
|
5
|
+
* Builds the handler for `startLoop(ticker, frame, maxDt, { speed, onFailure })`.
|
|
31
6
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* viu a tela parar.
|
|
7
|
+
* THE ORDER OF THE CHANNELS IS DELIBERATE: the console FIRST, because whoever debugs must not lose the error if the DOM
|
|
8
|
+
* is broken — and a broken DOM is one plausible reason the frame threw. Then the screen reader, which has no other clue
|
|
9
|
+
* at all. The visible one last, because whoever sees it has already seen the screen stop.
|
|
36
10
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
11
|
+
* Each channel is isolated from the next: if narration throws, the written notice has already gone out. A notice that
|
|
12
|
+
* fails halfway must deliver the other half — the same rule `startLoop` applies to this very callback.
|
|
39
13
|
*/
|
|
40
|
-
export function
|
|
41
|
-
|
|
14
|
+
export function createCrashNotice(ctx) {
|
|
15
|
+
const { t } = ctx;
|
|
16
|
+
return (failure) => {
|
|
42
17
|
try {
|
|
43
|
-
console.error('[inclusionist] the frame threw; the loop stopped.',
|
|
18
|
+
console.error('[inclusionist] the frame threw; the loop stopped.', failure);
|
|
44
19
|
}
|
|
45
20
|
catch { /* noop */ }
|
|
46
21
|
const msg = t('sr.laco.parou');
|
|
47
|
-
//
|
|
48
|
-
const
|
|
49
|
-
if (
|
|
50
|
-
|
|
22
|
+
// The screen reader: assertive, because interrupting is exactly the point.
|
|
23
|
+
const alert = ctx.find('#sr-alert');
|
|
24
|
+
if (alert)
|
|
25
|
+
alert.textContent = msg;
|
|
51
26
|
try {
|
|
52
|
-
ctx.
|
|
27
|
+
ctx.narrate?.(msg);
|
|
53
28
|
}
|
|
54
|
-
catch { /* noop:
|
|
55
|
-
//
|
|
29
|
+
catch { /* noop: narration failed; the written notice has already gone out */ }
|
|
30
|
+
// THE VISIBLE ONE, and it is a REAL ELEMENT — not a pseudo-element, for two reasons:
|
|
56
31
|
//
|
|
57
|
-
// ⚠️
|
|
58
|
-
//
|
|
59
|
-
// elemento tem UM `::after` só — as duas regras não se empilham, a do CRT vem depois e vence. O
|
|
60
|
-
// `::before` está igualmente tomado, pela vinheta. O aviso disputava um lugar já ocupado.
|
|
32
|
+
// ⚠️ the region's `::after` and `::before` are already taken by the CRT scanlines and the vignette, and an element
|
|
33
|
+
// has ONE of each — a notice there competes for an occupied place and loses;
|
|
61
34
|
//
|
|
62
|
-
// ⚠️
|
|
63
|
-
//
|
|
64
|
-
// que não pode depender disso.
|
|
35
|
+
// ⚠️ and `content` text does not reliably enter the accessibility tree. The notice that the game died is exactly
|
|
36
|
+
// what cannot depend on that.
|
|
65
37
|
//
|
|
66
|
-
//
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (!regiao)
|
|
38
|
+
// The sentence comes from `t()` and goes in through `textContent`, so it arrives translated and escaped.
|
|
39
|
+
const region = ctx.find('#game-region');
|
|
40
|
+
if (!region)
|
|
70
41
|
return;
|
|
71
|
-
const
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (!
|
|
77
|
-
|
|
42
|
+
const previous = ctx.find('#' + CRASH_NOTICE_ID);
|
|
43
|
+
const notice = previous ?? ctx.create('div');
|
|
44
|
+
notice.id = CRASH_NOTICE_ID;
|
|
45
|
+
notice.setAttribute('role', 'alert');
|
|
46
|
+
notice.textContent = msg;
|
|
47
|
+
if (!previous)
|
|
48
|
+
region.appendChild(notice);
|
|
78
49
|
};
|
|
79
50
|
}
|