@the-inclusionist/engine 6.36.1
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/LICENSE +661 -0
- package/README.md +107 -0
- package/app/css/style.css +622 -0
- package/app/public/vendor/fonts/andika-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/andika-400.woff2 +0 -0
- package/app/public/vendor/fonts/andika-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/andika-700.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-400.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-700.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-mono-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/atkinson-mono-400.woff2 +0 -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/greatvibes-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/lexend-400-ext.woff2 +0 -0
- package/app/public/vendor/fonts/lexend-400.woff2 +0 -0
- package/app/public/vendor/fonts/lexend-700-ext.woff2 +0 -0
- package/app/public/vendor/fonts/lexend-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/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/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/ufcook-700.woff2 +0 -0
- package/app/public/vendor/fonts/ufmag-400.woff2 +0 -0
- package/app/public/vendor/fonts.css +88 -0
- package/dist-pkg/boot/create-game.d.ts +159 -0
- package/dist-pkg/boot/create-game.js +266 -0
- package/dist-pkg/core/a11y-sr.d.ts +4 -0
- package/dist-pkg/core/a11y-sr.js +20 -0
- package/dist-pkg/core/actions.d.ts +100 -0
- package/dist-pkg/core/actions.js +149 -0
- package/dist-pkg/core/anel.d.ts +17 -0
- package/dist-pkg/core/anel.js +31 -0
- package/dist-pkg/core/collision.d.ts +21 -0
- package/dist-pkg/core/collision.js +62 -0
- package/dist-pkg/core/constants.d.ts +84 -0
- package/dist-pkg/core/constants.js +64 -0
- package/dist-pkg/core/contract.d.ts +227 -0
- package/dist-pkg/core/contract.js +184 -0
- package/dist-pkg/core/dom-query.d.ts +9 -0
- package/dist-pkg/core/dom-query.js +25 -0
- package/dist-pkg/core/entity.d.ts +150 -0
- package/dist-pkg/core/entity.js +38 -0
- package/dist-pkg/core/escape-html.d.ts +16 -0
- package/dist-pkg/core/escape-html.js +32 -0
- package/dist-pkg/core/i18n.d.ts +71 -0
- package/dist-pkg/core/i18n.js +236 -0
- package/dist-pkg/core/layers.d.ts +39 -0
- package/dist-pkg/core/layers.js +71 -0
- package/dist-pkg/core/letter-grid.d.ts +55 -0
- package/dist-pkg/core/letter-grid.js +116 -0
- package/dist-pkg/core/loop.d.ts +17 -0
- package/dist-pkg/core/loop.js +36 -0
- package/dist-pkg/core/password.d.ts +34 -0
- package/dist-pkg/core/password.js +205 -0
- package/dist-pkg/core/rng.d.ts +20 -0
- package/dist-pkg/core/rng.js +61 -0
- package/dist-pkg/core/rotulo-acessivel.d.ts +17 -0
- package/dist-pkg/core/rotulo-acessivel.js +41 -0
- package/dist-pkg/core/run-state.d.ts +115 -0
- package/dist-pkg/core/run-state.js +40 -0
- package/dist-pkg/core/scenes.d.ts +68 -0
- package/dist-pkg/core/scenes.js +64 -0
- package/dist-pkg/core/screens.d.ts +16 -0
- package/dist-pkg/core/screens.js +28 -0
- package/dist-pkg/core/state.d.ts +138 -0
- package/dist-pkg/core/state.js +290 -0
- package/dist-pkg/core/tiles.d.ts +12 -0
- package/dist-pkg/core/tiles.js +61 -0
- package/dist-pkg/core/world.d.ts +4 -0
- package/dist-pkg/core/world.js +58 -0
- package/dist-pkg/educational/activities-registry.d.ts +47 -0
- package/dist-pkg/educational/activities-registry.js +95 -0
- package/dist-pkg/i18n/en.d.ts +3 -0
- package/dist-pkg/i18n/en.js +560 -0
- package/dist-pkg/i18n/es.d.ts +3 -0
- package/dist-pkg/i18n/es.js +558 -0
- package/dist-pkg/i18n/pt.d.ts +3 -0
- package/dist-pkg/i18n/pt.js +618 -0
- package/dist-pkg/input/default-bindings.d.ts +24 -0
- package/dist-pkg/input/default-bindings.js +122 -0
- package/dist-pkg/input/devices.d.ts +15 -0
- package/dist-pkg/input/devices.js +31 -0
- package/dist-pkg/input/edges.d.ts +58 -0
- package/dist-pkg/input/edges.js +49 -0
- package/dist-pkg/input/gamepad.d.ts +203 -0
- package/dist-pkg/input/gamepad.js +531 -0
- package/dist-pkg/input/keyboard-runtime.d.ts +61 -0
- package/dist-pkg/input/keyboard-runtime.js +83 -0
- package/dist-pkg/input/keyboard.d.ts +45 -0
- package/dist-pkg/input/keyboard.js +80 -0
- package/dist-pkg/input/keydown.d.ts +270 -0
- package/dist-pkg/input/keydown.js +381 -0
- package/dist-pkg/input/latch.d.ts +30 -0
- package/dist-pkg/input/latch.js +63 -0
- package/dist-pkg/input/pointer-space.d.ts +40 -0
- package/dist-pkg/input/pointer-space.js +51 -0
- package/dist-pkg/input/state.d.ts +11 -0
- package/dist-pkg/input/state.js +12 -0
- package/dist-pkg/input/touch-bindings.d.ts +210 -0
- package/dist-pkg/input/touch-bindings.js +365 -0
- package/dist-pkg/input/touch.d.ts +173 -0
- package/dist-pkg/input/touch.js +314 -0
- package/dist-pkg/input/transports.d.ts +90 -0
- package/dist-pkg/input/transports.js +96 -0
- package/dist-pkg/input/vocabulary-migration.d.ts +58 -0
- package/dist-pkg/input/vocabulary-migration.js +125 -0
- package/dist-pkg/platform/audio-ambient.d.ts +27 -0
- package/dist-pkg/platform/audio-ambient.js +89 -0
- package/dist-pkg/platform/audio-earcons.d.ts +24 -0
- package/dist-pkg/platform/audio-earcons.js +62 -0
- package/dist-pkg/platform/audio-jingles.d.ts +17 -0
- package/dist-pkg/platform/audio-jingles.js +54 -0
- package/dist-pkg/platform/audio-mixer.d.ts +14 -0
- package/dist-pkg/platform/audio-mixer.js +62 -0
- package/dist-pkg/platform/audio-nav.d.ts +51 -0
- package/dist-pkg/platform/audio-nav.js +99 -0
- package/dist-pkg/platform/audio-sonar.d.ts +71 -0
- package/dist-pkg/platform/audio-sonar.js +173 -0
- package/dist-pkg/platform/audio.d.ts +32 -0
- package/dist-pkg/platform/audio.js +191 -0
- package/dist-pkg/platform/interruptible-speech.d.ts +19 -0
- package/dist-pkg/platform/interruptible-speech.js +79 -0
- package/dist-pkg/platform/speech.d.ts +2 -0
- package/dist-pkg/platform/speech.js +35 -0
- package/dist-pkg/platform/storage.d.ts +87 -0
- package/dist-pkg/platform/storage.js +148 -0
- package/dist-pkg/platform/tts.d.ts +33 -0
- package/dist-pkg/platform/tts.js +142 -0
- package/dist-pkg/render/camera.d.ts +58 -0
- package/dist-pkg/render/camera.js +105 -0
- package/dist-pkg/render/canvas.d.ts +13 -0
- package/dist-pkg/render/canvas.js +26 -0
- package/dist-pkg/render/cenario-data.d.ts +127 -0
- package/dist-pkg/render/cenario-data.js +145 -0
- package/dist-pkg/render/city-tex.d.ts +63 -0
- package/dist-pkg/render/city-tex.js +228 -0
- package/dist-pkg/render/city-tiles.d.ts +11 -0
- package/dist-pkg/render/city-tiles.js +140 -0
- package/dist-pkg/render/crt.d.ts +19 -0
- package/dist-pkg/render/crt.js +92 -0
- package/dist-pkg/render/cvd-matrices.d.ts +35 -0
- package/dist-pkg/render/cvd-matrices.js +91 -0
- package/dist-pkg/render/draw.d.ts +149 -0
- package/dist-pkg/render/draw.js +221 -0
- package/dist-pkg/render/fx.d.ts +63 -0
- package/dist-pkg/render/fx.js +123 -0
- package/dist-pkg/render/hc-role-data.d.ts +14 -0
- package/dist-pkg/render/hc-role-data.js +24 -0
- package/dist-pkg/render/high-contrast.d.ts +109 -0
- package/dist-pkg/render/high-contrast.js +254 -0
- package/dist-pkg/render/lq-filter.d.ts +37 -0
- package/dist-pkg/render/lq-filter.js +94 -0
- package/dist-pkg/render/minimap.d.ts +11 -0
- package/dist-pkg/render/minimap.js +94 -0
- package/dist-pkg/render/parallax.d.ts +85 -0
- package/dist-pkg/render/parallax.js +158 -0
- package/dist-pkg/render/player-anim.d.ts +71 -0
- package/dist-pkg/render/player-anim.js +152 -0
- package/dist-pkg/render/port.d.ts +143 -0
- package/dist-pkg/render/port.js +29 -0
- package/dist-pkg/render/recycling-tex.d.ts +48 -0
- package/dist-pkg/render/recycling-tex.js +164 -0
- package/dist-pkg/render/scene-city.d.ts +77 -0
- package/dist-pkg/render/scene-city.js +181 -0
- package/dist-pkg/render/scene-parallax.d.ts +72 -0
- package/dist-pkg/render/scene-parallax.js +426 -0
- package/dist-pkg/render/scene-sky.d.ts +126 -0
- package/dist-pkg/render/scene-sky.js +295 -0
- package/dist-pkg/render/screen-pipeline.d.ts +134 -0
- package/dist-pkg/render/screen-pipeline.js +177 -0
- package/dist-pkg/render/set-cenario.d.ts +54 -0
- package/dist-pkg/render/set-cenario.js +103 -0
- package/dist-pkg/render/sprite-fx.d.ts +3 -0
- package/dist-pkg/render/sprite-fx.js +40 -0
- package/dist-pkg/render/textures.d.ts +75 -0
- package/dist-pkg/render/textures.js +240 -0
- package/dist-pkg/render/title-scene.d.ts +46 -0
- package/dist-pkg/render/title-scene.js +75 -0
- package/dist-pkg/render/viewports.d.ts +52 -0
- package/dist-pkg/render/viewports.js +185 -0
- package/dist-pkg/render/viz-axes.d.ts +98 -0
- package/dist-pkg/render/viz-axes.js +184 -0
- package/dist-pkg/render/viz-modes.d.ts +61 -0
- package/dist-pkg/render/viz-modes.js +74 -0
- package/dist-pkg/render/viz-setters.d.ts +130 -0
- package/dist-pkg/render/viz-setters.js +244 -0
- package/dist-pkg/render/weather.d.ts +96 -0
- package/dist-pkg/render/weather.js +171 -0
- package/dist-pkg/render/wheelchair-sprites.d.ts +24 -0
- package/dist-pkg/render/wheelchair-sprites.js +63 -0
- package/dist-pkg/render/world-tex.d.ts +18 -0
- package/dist-pkg/render/world-tex.js +87 -0
- package/dist-pkg/ui/activities-menu.d.ts +258 -0
- package/dist-pkg/ui/activities-menu.js +648 -0
- package/dist-pkg/ui/caa-sets.d.ts +39 -0
- package/dist-pkg/ui/caa-sets.js +65 -0
- package/dist-pkg/ui/changed-mark.d.ts +15 -0
- package/dist-pkg/ui/changed-mark.js +96 -0
- package/dist-pkg/ui/debug-panel.d.ts +67 -0
- package/dist-pkg/ui/debug-panel.js +173 -0
- package/dist-pkg/ui/dom.d.ts +24 -0
- package/dist-pkg/ui/dom.js +47 -0
- package/dist-pkg/ui/focus-trap.d.ts +71 -0
- package/dist-pkg/ui/focus-trap.js +99 -0
- package/dist-pkg/ui/fonts.d.ts +41 -0
- package/dist-pkg/ui/fonts.js +59 -0
- package/dist-pkg/ui/hud.d.ts +137 -0
- package/dist-pkg/ui/hud.js +229 -0
- package/dist-pkg/ui/item-announcement.d.ts +19 -0
- package/dist-pkg/ui/item-announcement.js +36 -0
- package/dist-pkg/ui/layout.d.ts +6 -0
- package/dist-pkg/ui/layout.js +54 -0
- package/dist-pkg/ui/loop-crash.d.ts +19 -0
- package/dist-pkg/ui/loop-crash.js +60 -0
- package/dist-pkg/ui/map-hub.d.ts +56 -0
- package/dist-pkg/ui/map-hub.js +138 -0
- package/dist-pkg/ui/menu-nav.d.ts +141 -0
- package/dist-pkg/ui/menu-nav.js +390 -0
- package/dist-pkg/ui/pause-icons.d.ts +327 -0
- package/dist-pkg/ui/pause-icons.js +620 -0
- package/dist-pkg/ui/reach-notice.d.ts +34 -0
- package/dist-pkg/ui/reach-notice.js +71 -0
- package/dist-pkg/ui/settings-audio.d.ts +139 -0
- package/dist-pkg/ui/settings-audio.js +544 -0
- package/dist-pkg/ui/settings-caa.d.ts +45 -0
- package/dist-pkg/ui/settings-caa.js +137 -0
- package/dist-pkg/ui/settings-controls.d.ts +100 -0
- package/dist-pkg/ui/settings-controls.js +152 -0
- package/dist-pkg/ui/settings-empathy.d.ts +62 -0
- package/dist-pkg/ui/settings-empathy.js +137 -0
- package/dist-pkg/ui/settings-motion.d.ts +95 -0
- package/dist-pkg/ui/settings-motion.js +282 -0
- package/dist-pkg/ui/settings-motor.d.ts +77 -0
- package/dist-pkg/ui/settings-motor.js +215 -0
- package/dist-pkg/ui/settings-panel.d.ts +76 -0
- package/dist-pkg/ui/settings-panel.js +187 -0
- package/dist-pkg/ui/settings-typo.d.ts +74 -0
- package/dist-pkg/ui/settings-typo.js +150 -0
- package/dist-pkg/ui/settings-visual.d.ts +111 -0
- package/dist-pkg/ui/settings-visual.js +252 -0
- package/dist-pkg/ui/shell.d.ts +239 -0
- package/dist-pkg/ui/shell.js +430 -0
- package/dist-pkg/ui/title.d.ts +36 -0
- package/dist-pkg/ui/title.js +60 -0
- package/dist-pkg/ui/vlibras.d.ts +11 -0
- package/dist-pkg/ui/vlibras.js +87 -0
- package/dist-pkg/ui/webcam.d.ts +7 -0
- package/dist-pkg/ui/webcam.js +88 -0
- package/docs/CREDITS.md +64 -0
- package/docs/LICENSES.md +132 -0
- package/package.json +115 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// ui/settings-caa.ts — O SÉTIMO MENU: COMUNICAÇÃO AUMENTADA E ALTERNATIVA (ADR-0028, issue #57).
|
|
3
|
+
//
|
|
4
|
+
// O catálogo (quem existe, sob qual licença, em qual camada) mora em ./caa-sets.js; aqui só a tela. DI por
|
|
5
|
+
// ctx, como os seis painéis irmãos: nada de global fora do que for injetado.
|
|
6
|
+
//
|
|
7
|
+
// O QUE ESTE MENU É HOJE, dito sem maquiagem: um seletor de caixa de letra que já mostra, ao lado, os sete
|
|
8
|
+
// conjuntos de pictogramas que virão. Só as duas caixas de letra funcionam — os pictogramas são milhares de
|
|
9
|
+
// arquivos que ainda não entraram no repositório, e três deles dependem de negociação que não é nossa.
|
|
10
|
+
//
|
|
11
|
+
// Mostrar o que não funciona seria desonesto se o menu não dissesse POR QUE. Ele diz, e com duas respostas
|
|
12
|
+
// diferentes de propósito: "em preparação" (a licença está resolvida, o trabalho é nosso) e "aguardando
|
|
13
|
+
// negociação" (a permissão é de outra pessoa). Um educador que lê a primeira sabe esperar; que lê a segunda
|
|
14
|
+
// sabe que esperar não adianta. Esconder tudo o que ainda não anda faria o educador concluir que o jogo não
|
|
15
|
+
// faz pictograma nenhum — que é a conclusão errada, e a que mais custa a quem precisa deles.
|
|
16
|
+
//
|
|
17
|
+
// LETRA vs PICTOGRAMA: a escolha de caixa é `letterCase` (core/state). NÃO existe um `caaMode` paralelo, e
|
|
18
|
+
// isso é decisão: enquanto só duas opções forem escolhíveis, uma segunda variável para a mesma pergunta
|
|
19
|
+
// seria duplicação com aparência de arquitetura. Ver a nota em core/state.
|
|
20
|
+
import { toggleLabel } from './dom.js';
|
|
21
|
+
import { t } from '../core/i18n.js';
|
|
22
|
+
import { DEFAULTS } from '../core/state.js';
|
|
23
|
+
import { CAA_SETS, CAA_BY_KEY, caaMotivo } from './caa-sets.js';
|
|
24
|
+
import { markChanged, markMenuChanged } from './changed-mark.js';
|
|
25
|
+
/**
|
|
26
|
+
* A linha das LETRAS é um INTERRUPTOR, não duas opções (decisão do Dev). Ligado = só maiúsculas; desligado =
|
|
27
|
+
* maiúsculas e minúsculas. Uma pergunta binária apresentada como duas linhas obriga a criança a comparar as
|
|
28
|
+
* duas para descobrir que são a mesma pergunta.
|
|
29
|
+
*/
|
|
30
|
+
export function caixaAltaLigada(caso) {
|
|
31
|
+
return caso === 'upper';
|
|
32
|
+
}
|
|
33
|
+
/** O interruptor das letras. Sempre disponível: é o piso offline, e nunca dependeu de arquivo nenhum. */
|
|
34
|
+
export function letrasRowHtml(ligado) {
|
|
35
|
+
return ('<div class="ctrl-row" id="caa-letras"><span><strong>Letras maiúsculas</strong>' +
|
|
36
|
+
'<span class="opt-hint">Ligado: o jogo inteiro em caixa alta, como a alfabetização brasileira costuma ' +
|
|
37
|
+
'começar. Desligado: maiúsculas e minúsculas, a escrita do dia a dia.</span></span>' +
|
|
38
|
+
`<button class="mode-btn switch${ligado ? ' is-on' : ''}" id="caa-caixa-alta" type="button"` +
|
|
39
|
+
` aria-pressed="${ligado}" aria-label="Letras maiúsculas">${toggleLabel(ligado)}</button></div>`);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Uma linha do menu: RÓTULO CURTO e nada mais à vista.
|
|
43
|
+
*
|
|
44
|
+
* Tudo o que explica — nota, licença, situação — entra num único `.opt-hint`, que é o que a casca
|
|
45
|
+
* (`fillExplain`) reconhece e MOVE para o rodapé. A primeira versão pendurava três blocos de prosa dentro da
|
|
46
|
+
* linha, e o Dev viu o resultado: o menu virou um manual, mais parecido com um arquivo de configuração do que
|
|
47
|
+
* com um menu de videogame. A explicação já tinha um lugar; eu é que não a pus lá.
|
|
48
|
+
*
|
|
49
|
+
* O `disabled` fica: a situação some do TEXTO, não do controle. E o `aria-label` guarda o motivo, para quem
|
|
50
|
+
* navega por teclado ouvir por que a linha não responde sem precisar caçar o rodapé.
|
|
51
|
+
*/
|
|
52
|
+
export function caaRowHtml(s, selecionada) {
|
|
53
|
+
const motivo = caaMotivo(s);
|
|
54
|
+
const explica = [s.nota, s.licenca ? `Licença: ${s.licenca}` : '', motivo ? t(motivo) : '']
|
|
55
|
+
.filter(Boolean).join(' · ');
|
|
56
|
+
const hint = explica ? `<span class="opt-hint">${explica}</span>` : '';
|
|
57
|
+
const estado = toggleLabel(selecionada);
|
|
58
|
+
return (`<div class="ctrl-row"><span><strong>${s.nome}</strong>${hint}</span>` +
|
|
59
|
+
`<button class="mode-btn switch${selecionada ? ' is-on' : ''}" data-caa="${s.key}" type="button"` +
|
|
60
|
+
`${s.disponivel ? '' : ' disabled'} aria-pressed="${selecionada}"` +
|
|
61
|
+
` aria-label="${s.nome}${motivo ? ', ' + t(motivo) : ''}">${estado}</button></div>`);
|
|
62
|
+
}
|
|
63
|
+
/** O corpo do menu, em três seções — a seção diz de quem é a vez de agir. */
|
|
64
|
+
export function caaListHtml(caso) {
|
|
65
|
+
const bloco = (titulo, tag, corpo) => corpo ? `<h3 class="panel-sub">${titulo} <span class="panel-sub__tag">${tag}</span></h3>${corpo}` : '';
|
|
66
|
+
const sets = (f) => CAA_SETS.filter(f).map((s) => caaRowHtml(s, false)).join('');
|
|
67
|
+
return (bloco(t('caa.secao.agora'), t('caa.secao.agoraTag'), letrasRowHtml(caixaAltaLigada(caso))) +
|
|
68
|
+
bloco(t('caa.secao.preparo'), t('caa.secao.preparoTag'), sets((s) => s.tier !== 'negotiating')) +
|
|
69
|
+
bloco(t('caa.secao.negociacao'), t('caa.secao.negociacaoTag'), sets((s) => s.tier === 'negotiating')));
|
|
70
|
+
}
|
|
71
|
+
export function initSettingsCaa(ctx) {
|
|
72
|
+
function render() {
|
|
73
|
+
const el = ctx.$('#caa-list');
|
|
74
|
+
if (!el)
|
|
75
|
+
return;
|
|
76
|
+
el.innerHTML = caaListHtml(ctx.getLetterCase());
|
|
77
|
+
const alta = el.querySelector('#caa-caixa-alta');
|
|
78
|
+
if (alta)
|
|
79
|
+
alta.addEventListener('click', () => {
|
|
80
|
+
const ligar = !caixaAltaLigada(ctx.getLetterCase());
|
|
81
|
+
ctx.setLetterCase(ligar ? 'upper' : 'mixed');
|
|
82
|
+
render();
|
|
83
|
+
ctx.srSay(t(ligar ? 'sr.caa.caixaAltaOn' : 'sr.caa.caixaAltaOff'));
|
|
84
|
+
});
|
|
85
|
+
el.querySelectorAll('button[data-caa]').forEach((b) => {
|
|
86
|
+
b.addEventListener('click', () => {
|
|
87
|
+
const set = b.dataset.caa ? CAA_BY_KEY[b.dataset.caa] : undefined;
|
|
88
|
+
// Nenhum conjunto está disponível hoje, e todos vêm `disabled`. A guarda existe para o dia em que
|
|
89
|
+
// alguém remover o atributo "só para testar": um conjunto que não está no jogo não pode virar a
|
|
90
|
+
// escolha da criança por acidente, deixando a tela sem nada para desenhar.
|
|
91
|
+
if (!set || !set.disponivel)
|
|
92
|
+
return;
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
ctx.fillExplain(ctx.$('#caa .overlay__card'));
|
|
96
|
+
refreshMarks();
|
|
97
|
+
}
|
|
98
|
+
/** A marca de "saiu do padrão" (ADR-0029), na linha escolhida e no botão do menu. */
|
|
99
|
+
function refreshMarks() {
|
|
100
|
+
const mudou = ctx.getLetterCase() !== DEFAULTS.letterCase;
|
|
101
|
+
markChanged(ctx.$('#caa-letras'), mudou);
|
|
102
|
+
markMenuChanged(ctx.$('[data-act="caa"]'), [mudou]);
|
|
103
|
+
}
|
|
104
|
+
function open() {
|
|
105
|
+
const ov = ctx.$('#caa');
|
|
106
|
+
if (!ov)
|
|
107
|
+
return;
|
|
108
|
+
render();
|
|
109
|
+
ov.hidden = false;
|
|
110
|
+
ctx.frontOverlay(ov);
|
|
111
|
+
const f = ov.querySelector('button');
|
|
112
|
+
if (f)
|
|
113
|
+
f.focus();
|
|
114
|
+
}
|
|
115
|
+
function close() {
|
|
116
|
+
const ov = ctx.$('#caa');
|
|
117
|
+
if (!ov)
|
|
118
|
+
return;
|
|
119
|
+
ov.hidden = true;
|
|
120
|
+
ctx.restoreFocus?.('caa');
|
|
121
|
+
}
|
|
122
|
+
const closeBtn = ctx.$('#caa-close');
|
|
123
|
+
if (closeBtn)
|
|
124
|
+
closeBtn.addEventListener('click', close);
|
|
125
|
+
// ---- restaurar os padrões DESTE menu (ADR-0028) ----
|
|
126
|
+
// O menu tem uma escolha só, então o reset é uma linha — mas vale dizer para onde ele volta: maiúsculas, que
|
|
127
|
+
// é onde a alfabetização brasileira costuma começar. Uma criança que trocou a caixa e não consegue mais ler
|
|
128
|
+
// a tela precisa de um caminho de volta que termine no que a professora dela usa.
|
|
129
|
+
const resetBtn = ctx.$('#caa-reset');
|
|
130
|
+
if (resetBtn)
|
|
131
|
+
resetBtn.addEventListener('click', () => {
|
|
132
|
+
ctx.setLetterCase(DEFAULTS.letterCase);
|
|
133
|
+
render();
|
|
134
|
+
ctx.srSay(t('sr.caa.reset'));
|
|
135
|
+
});
|
|
136
|
+
return { render, open, close };
|
|
137
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import type { DomQuery } from '../core/dom-query.js';
|
|
3
|
+
import type { KeyScheme } from '../core/entity.js';
|
|
4
|
+
import type { KBDefaults } from '../input/keyboard.js';
|
|
5
|
+
import type { KeydownEventLike } from '../input/keydown.js';
|
|
6
|
+
/** Minimal DOM-selector shape (matches ui/dom.ts's `$`). */
|
|
7
|
+
export type { DomQuery } from '../core/dom-query.js';
|
|
8
|
+
/** action -> list of physical key codes (KeyboardEvent.code), e.g. {jump:['KeyJ','Space']}. */
|
|
9
|
+
export type { KeyScheme } from '../core/entity.js';
|
|
10
|
+
/** Opaque keyboard config (input/keyboard.ts's KBDefaults shape: {solo,p2,p3,p4}) — never indexed directly here;
|
|
11
|
+
* all per-player reads go through the injected `kbFor`, so this module stays decoupled from its exact shape. */
|
|
12
|
+
/** O `KBDefaults` de `input/keyboard`, que é o dono. Este módulo continua NÃO INDEXANDO o valor — toda
|
|
13
|
+
* leitura por jogador passa pelo `kbFor` injetado —, e é essa disciplina que o desacopla, não um tipo largo. */
|
|
14
|
+
export type KeyboardConfig = KBDefaults;
|
|
15
|
+
/** Minimal persistence shape this module needs (input/keyboard.ts's saveKB/resetKB — no direct localStorage). */
|
|
16
|
+
export interface ControlsStore {
|
|
17
|
+
saveKB(kb: KeyboardConfig): void;
|
|
18
|
+
resetKB(): KeyboardConfig;
|
|
19
|
+
}
|
|
20
|
+
export interface SettingsControlsCtx {
|
|
21
|
+
/** DOM selector (querySelector), injected — never reaches `document` globally. */
|
|
22
|
+
$: DomQuery;
|
|
23
|
+
/**
|
|
24
|
+
* AS POSIÇÕES QUE ESTE JOGO USA, cada uma com a palavra dele, no idioma vigente.
|
|
25
|
+
*
|
|
26
|
+
* ⚠️ É a fronteira do corte de 2026-09-06. A tela de remapeamento mostrava as OITO linhas de uma tabela
|
|
27
|
+
* deste ficheiro — quer dizer, a engine decidia que todo jogo tem exatamente pular, correr, trocar e
|
|
28
|
+
* especial. Um quiz mostraria quatro linhas para ações que não existem nele, e uma criança tentaria
|
|
29
|
+
* remapear um botão que não faz nada.
|
|
30
|
+
*/
|
|
31
|
+
acoesDoJogo: () => readonly {
|
|
32
|
+
readonly acao: string;
|
|
33
|
+
readonly rotulo: string;
|
|
34
|
+
}[];
|
|
35
|
+
/** Screen-reader "polite" announcement (core/a11y-sr's srSay), injected. */
|
|
36
|
+
srSay: (msg: string) => void;
|
|
37
|
+
/** Screen-reader "assertive" announcement (core/a11y-sr's srAlert) — used for the capture prompt/conflict. */
|
|
38
|
+
srAlert: (msg: string) => void;
|
|
39
|
+
/** Persistence (input/keyboard.ts's saveKB/resetKB), injected. */
|
|
40
|
+
store: ControlsStore;
|
|
41
|
+
/** The live keyboard config object (game.js's `KB`). Mutated in place by successful remaps. */
|
|
42
|
+
kb: KeyboardConfig;
|
|
43
|
+
/** Replaces game.js's `KB` binding wholesale — only used by "restaurar padrões" (reset reassigns, doesn't mutate). */
|
|
44
|
+
setKB: (kb: KeyboardConfig) => void;
|
|
45
|
+
/** Shared helper (game.js): the scheme for a given player index, given `kb`/numPlayers. Not owned by this panel —
|
|
46
|
+
* other systems (gamepad binding, HUD) call the same game.js function. */
|
|
47
|
+
kbFor: (playerIndex: number) => KeyScheme;
|
|
48
|
+
/** Shared: current player count (core/state.ts's numPlayers, read live via game.js). */
|
|
49
|
+
getNumPlayers: () => number;
|
|
50
|
+
/** Shared: propagates `kb` -> the live control aliases (game.js's applyControls). Called after remap/reset. */
|
|
51
|
+
applyControls: () => void;
|
|
52
|
+
/** Shared: propagates `kb` -> each player's `p.ctrl` (game.js's assignControls). Called after remap/reset. */
|
|
53
|
+
assignControls: () => void;
|
|
54
|
+
}
|
|
55
|
+
export interface SettingsControlsApi {
|
|
56
|
+
/** Re-renders #ctrl-list for the given player index and (re)wires its "Alterar" buttons. Idempotent. */
|
|
57
|
+
render: (selPlayer: number) => void;
|
|
58
|
+
/** True while a key capture is in progress (game.js's menuNavKey gates menu navigation on this). */
|
|
59
|
+
isCapturing: () => boolean;
|
|
60
|
+
/** Cancels any in-progress capture without re-rendering (dialog is closing anyway). */
|
|
61
|
+
cancelCapture: () => void;
|
|
62
|
+
/**
|
|
63
|
+
* Feeds a keydown to the in-progress capture, if any. Returns true when the event was consumed (capture was
|
|
64
|
+
* active — Escape/conflict/success all consume it) so game.js's own keydown handler can early-return exactly
|
|
65
|
+
* like the old inline `if(captureAction){...}` block did. Returns false (no-op) when nothing is being captured.
|
|
66
|
+
*/
|
|
67
|
+
/** `KeydownEventLike` de `input/keydown`, que é quem escuta o teclado e portanto é dono da forma do
|
|
68
|
+
* evento nesta engine. Este manipulador lê só `e.code` e chama `preventDefault()` — um subconjunto —,
|
|
69
|
+
* mas pedir o `KeyboardEvent` inteiro obrigava o despacho a entregar mais do que tem (ADR-0039). */
|
|
70
|
+
handleCaptureKeydown: (e: KeydownEventLike) => boolean;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* action key -> i18n KEY of the label shown in the panel. Also reused by main.js's openHelp() (pause-menu help
|
|
74
|
+
* screen), which is why it lives here instead of being duplicated in two places.
|
|
75
|
+
*
|
|
76
|
+
* Holds keys, not text, for the reason spelled out in input/devices: a module-level const is evaluated once at
|
|
77
|
+
* import, and core/i18n's `dict` is a `let` that setLocale reassigns — text captured here would freeze the
|
|
78
|
+
* language at boot. Resolve with `t(ACT_LABEL[a])` at the point of use.
|
|
79
|
+
*/
|
|
80
|
+
export declare const ACT_LABEL: Record<string, string>;
|
|
81
|
+
/**
|
|
82
|
+
* ⚠️ A TABELA ACIMA E DÍVIDA DECLARADA, e o cabeçalho dela ficou desatualizado no dia em que o corte
|
|
83
|
+
* aconteceu: ela ainda diz quais são as palavras DESTE jogo — `act.run`, `act.jump` — dentro de um módulo de
|
|
84
|
+
* engine. Continua exportada porque `openHelp()` (a tela de ajuda do menu de pausa) ainda a lê, e mover as
|
|
85
|
+
* duas coisas no mesmo commit misturaria dois assuntos.
|
|
86
|
+
*
|
|
87
|
+
* O que MUDOU é quem manda: a lista de linhas e as palavras vêm agora de `ctx.acoesDoJogo()`, e esta tabela é
|
|
88
|
+
* só o que sobra para o consumidor que ainda não migrou. Quando `openHelp` perguntar ao jogo, ela sai.
|
|
89
|
+
*/
|
|
90
|
+
/** Physical key code -> short readable label. Only 'Space' has a word to translate; the rest are glyphs and
|
|
91
|
+
* bare letters, identical in every language (that is why this is a chain of replaces and not a table). */
|
|
92
|
+
export declare function keyName(code: string): string;
|
|
93
|
+
/**
|
|
94
|
+
* Which OTHER player already owns `code`, among `schemes` (one entry per player, same order as player index) —
|
|
95
|
+
* or -1 if free. `mapRef` (the scheme currently being edited) is excluded by reference, mirroring the original
|
|
96
|
+
* `keyUsedByOther(code, mapRef)` closing over `kbFor`/numPlayers in game.js. Built over a Map (code -> owner
|
|
97
|
+
* index) so a scheme with many bound keys doesn't cost a full re-scan per lookup.
|
|
98
|
+
*/
|
|
99
|
+
export declare function keyUsedByOther(code: string, mapRef: KeyScheme, schemes: readonly KeyScheme[]): number;
|
|
100
|
+
export declare function initSettingsControls(ctx: SettingsControlsCtx): SettingsControlsApi;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// ui/settings-controls — Keyboard-remap panel (Estágio 4): extracted from game.js's renderControls()/keyName()/
|
|
3
|
+
// the captureAction+captureMapRef remap flow. Pure logic (key→label, cross-player conflict lookup) is separated
|
|
4
|
+
// from the thin DOM-touching render()/handleCaptureKeydown(). DI via initSettingsControls(ctx): `$` (DOM
|
|
5
|
+
// selector), `srSay`/`srAlert`, `store` (save/reset persistence), the live `kb` value + `setKB` setter, and the
|
|
6
|
+
// shared per-player helpers (`kbFor`/`getNumPlayers`/`applyControls`/`assignControls`) that game.js also uses
|
|
7
|
+
// elsewhere (gamepad binding, HUD, other settings panels) and therefore stay there, injected. Overlay open/close
|
|
8
|
+
// plumbing (#options hidden toggle, focus management, Escape-closes-dialog) and the pad-button-design select are
|
|
9
|
+
// shared/unrelated infra and stay in game.js. `openHelp()` (pause-menu help screen) reuses ACT_LABEL/keyName —
|
|
10
|
+
// both are exported here instead of duplicated.
|
|
11
|
+
import { t } from '../core/i18n.js';
|
|
12
|
+
// ---------------------------------------------------------------------------------------------
|
|
13
|
+
// Pure logic (no `document`, testable in node)
|
|
14
|
+
// ---------------------------------------------------------------------------------------------
|
|
15
|
+
/**
|
|
16
|
+
* action key -> i18n KEY of the label shown in the panel. Also reused by main.js's openHelp() (pause-menu help
|
|
17
|
+
* screen), which is why it lives here instead of being duplicated in two places.
|
|
18
|
+
*
|
|
19
|
+
* Holds keys, not text, for the reason spelled out in input/devices: a module-level const is evaluated once at
|
|
20
|
+
* import, and core/i18n's `dict` is a `let` that setLocale reassigns — text captured here would freeze the
|
|
21
|
+
* language at boot. Resolve with `t(ACT_LABEL[a])` at the point of use.
|
|
22
|
+
*/
|
|
23
|
+
export const ACT_LABEL = {
|
|
24
|
+
left: 'act.left', right: 'act.right', up: 'act.up', down: 'act.down',
|
|
25
|
+
action1: 'act.run', action2: 'act.jump', action4: 'act.swap', action3: 'act.especial',
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* ⚠️ A TABELA ACIMA E DÍVIDA DECLARADA, e o cabeçalho dela ficou desatualizado no dia em que o corte
|
|
29
|
+
* aconteceu: ela ainda diz quais são as palavras DESTE jogo — `act.run`, `act.jump` — dentro de um módulo de
|
|
30
|
+
* engine. Continua exportada porque `openHelp()` (a tela de ajuda do menu de pausa) ainda a lê, e mover as
|
|
31
|
+
* duas coisas no mesmo commit misturaria dois assuntos.
|
|
32
|
+
*
|
|
33
|
+
* O que MUDOU é quem manda: a lista de linhas e as palavras vêm agora de `ctx.acoesDoJogo()`, e esta tabela é
|
|
34
|
+
* só o que sobra para o consumidor que ainda não migrou. Quando `openHelp` perguntar ao jogo, ela sai.
|
|
35
|
+
*/
|
|
36
|
+
/** Physical key code -> short readable label. Only 'Space' has a word to translate; the rest are glyphs and
|
|
37
|
+
* bare letters, identical in every language (that is why this is a chain of replaces and not a table). */
|
|
38
|
+
export function keyName(code) {
|
|
39
|
+
return String(code)
|
|
40
|
+
.replace('Arrow', '↔')
|
|
41
|
+
.replace('Key', '')
|
|
42
|
+
.replace('Space', t('key.space'))
|
|
43
|
+
.replace('ShiftLeft', 'Shift')
|
|
44
|
+
.replace('ShiftRight', 'Shift');
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Which OTHER player already owns `code`, among `schemes` (one entry per player, same order as player index) —
|
|
48
|
+
* or -1 if free. `mapRef` (the scheme currently being edited) is excluded by reference, mirroring the original
|
|
49
|
+
* `keyUsedByOther(code, mapRef)` closing over `kbFor`/numPlayers in game.js. Built over a Map (code -> owner
|
|
50
|
+
* index) so a scheme with many bound keys doesn't cost a full re-scan per lookup.
|
|
51
|
+
*/
|
|
52
|
+
export function keyUsedByOther(code, mapRef, schemes) {
|
|
53
|
+
const owners = new Map();
|
|
54
|
+
schemes.forEach((m, i) => {
|
|
55
|
+
if (m === mapRef)
|
|
56
|
+
return;
|
|
57
|
+
for (const a in m)
|
|
58
|
+
for (const c of m[a] || [])
|
|
59
|
+
if (!owners.has(c))
|
|
60
|
+
owners.set(c, i);
|
|
61
|
+
});
|
|
62
|
+
return owners.get(code) ?? -1;
|
|
63
|
+
}
|
|
64
|
+
export function initSettingsControls(ctx) {
|
|
65
|
+
let kb = ctx.kb;
|
|
66
|
+
let capture = null;
|
|
67
|
+
let lastPlayer = 0;
|
|
68
|
+
function schemesFor() {
|
|
69
|
+
const n = ctx.getNumPlayers();
|
|
70
|
+
return Array.from({ length: n }, (_, i) => ctx.kbFor(i));
|
|
71
|
+
}
|
|
72
|
+
function render(selPlayer) {
|
|
73
|
+
const el = ctx.$('#ctrl-list');
|
|
74
|
+
if (!el)
|
|
75
|
+
return;
|
|
76
|
+
const n = ctx.getNumPlayers();
|
|
77
|
+
const player = selPlayer >= n ? 0 : selPlayer;
|
|
78
|
+
lastPlayer = player;
|
|
79
|
+
// E3: sem abas de outros jogadores — você edita só o seu controle; só o hint muda com o modo.
|
|
80
|
+
const tabs = ctx.$('#ctrl-players');
|
|
81
|
+
if (tabs) {
|
|
82
|
+
tabs.hidden = false;
|
|
83
|
+
tabs.innerHTML = `<span class="opt-hint" style="width:100%;margin:0">Editando o <strong>seu</strong> controle — modo <strong>${n === 1 ? '1 jogador' : n + ' jogadores'}</strong>.</span>`;
|
|
84
|
+
}
|
|
85
|
+
const map = ctx.kbFor(player);
|
|
86
|
+
// ⚠️ O `rotulo` SAIU DO MARKUP (issue #106) e entra logo abaixo por `textContent`. Ele é a PALAVRA do
|
|
87
|
+
// jogo — vem do preset —, e um jogo vive noutro repositório (ADR-0083): esta árvore não revê esse texto.
|
|
88
|
+
// `data-act="${a}"` fica, e a diferença é a razão: `a` é o nome ABSTRATO da ação, que a engine enumera em
|
|
89
|
+
// `core/actions`. É separação que o ADR-0086 fez, e é ela que torna um dos dois seguro e o outro não.
|
|
90
|
+
el.innerHTML = ctx.acoesDoJogo().map(({ acao: a }) => `<div class="ctrl-row"><span><b class="ctrl-nome"></b>: ${(map[a] || []).map(keyName).map((k) => `<kbd>${k}</kbd>`).join(' ')}</span>` +
|
|
91
|
+
`<button class="mode-btn" data-act="${a}" type="button" aria-label="${t('ctrl.changeKeyAria', { acao: t(ACT_LABEL[a]), n: player + 1 })}">${t('ctrl.change')}</button></div>`).join('');
|
|
92
|
+
// As palavras do jogo, por `textContent` — que escapa por construção. A ordem casa porque é a mesma lista.
|
|
93
|
+
{
|
|
94
|
+
const nomes = el.querySelectorAll('.ctrl-nome');
|
|
95
|
+
const palavras = ctx.acoesDoJogo();
|
|
96
|
+
for (let i = 0; i < nomes.length && i < palavras.length; i++)
|
|
97
|
+
nomes[i].textContent = palavras[i].rotulo;
|
|
98
|
+
}
|
|
99
|
+
el.querySelectorAll('button[data-act]').forEach((b) => {
|
|
100
|
+
b.addEventListener('click', () => {
|
|
101
|
+
const act = b.dataset.act;
|
|
102
|
+
if (!act)
|
|
103
|
+
return;
|
|
104
|
+
capture = { action: act, mapRef: map, player };
|
|
105
|
+
b.textContent = 'Pressione…';
|
|
106
|
+
ctx.srAlert(t('sr.ctrl.pressNewKey', { acao: t(ACT_LABEL[act]), n: player + 1 }));
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
function isCapturing() {
|
|
111
|
+
return capture !== null;
|
|
112
|
+
}
|
|
113
|
+
function cancelCapture() {
|
|
114
|
+
capture = null;
|
|
115
|
+
}
|
|
116
|
+
function handleCaptureKeydown(e) {
|
|
117
|
+
if (!capture)
|
|
118
|
+
return false;
|
|
119
|
+
if (e.code === 'Escape') {
|
|
120
|
+
capture = null;
|
|
121
|
+
render(lastPlayer);
|
|
122
|
+
e.preventDefault();
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
const other = keyUsedByOther(e.code, capture.mapRef, schemesFor());
|
|
126
|
+
if (other >= 0) {
|
|
127
|
+
ctx.srAlert(t('sr.ctrl.keyTaken', { n: other + 1 }));
|
|
128
|
+
e.preventDefault();
|
|
129
|
+
return true; // não associa: segue capturando
|
|
130
|
+
}
|
|
131
|
+
capture.mapRef[capture.action] = [e.code];
|
|
132
|
+
ctx.store.saveKB(kb);
|
|
133
|
+
ctx.applyControls();
|
|
134
|
+
ctx.assignControls();
|
|
135
|
+
capture = null;
|
|
136
|
+
render(lastPlayer);
|
|
137
|
+
e.preventDefault();
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
const resetBtn = ctx.$('#ctrl-reset');
|
|
141
|
+
if (resetBtn) {
|
|
142
|
+
resetBtn.addEventListener('click', () => {
|
|
143
|
+
kb = ctx.store.resetKB();
|
|
144
|
+
ctx.setKB(kb);
|
|
145
|
+
ctx.applyControls();
|
|
146
|
+
ctx.assignControls();
|
|
147
|
+
render(lastPlayer);
|
|
148
|
+
ctx.srSay(t('sr.ctrl.reset'));
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
return { render, isCapturing, cancelCapture, handleCaptureKeydown };
|
|
152
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import { type VizMode } from '../render/viz-modes.js';
|
|
3
|
+
/**
|
|
4
|
+
* A fatia do catálogo que ESTE painel mostra: só o que SIMULA uma deficiência.
|
|
5
|
+
*
|
|
6
|
+
* Era `VIZ_MODES.filter(m => isSimKind(m.kind))`, e `kind` não distingue simular de corrigir — então as três
|
|
7
|
+
* "Correção de daltonismo" vinham junto, num menu chamado "sentir como é ter uma deficiência". A criança
|
|
8
|
+
* daltônica precisava entrar ali para achar a correção da própria condição, ao lado do botão que simula a
|
|
9
|
+
* condição dela para quem não a tem. Dois públicos opostos na mesma lista.
|
|
10
|
+
*
|
|
11
|
+
* O Dev decidiu (#60): as correções vão para a Acessibilidade visual, e a lista passa a se recortar pelo
|
|
12
|
+
* campo `sim` do catálogo, que responde a pergunta certa. `isSimKind` foi apagado — havia TRÊS cópias dele
|
|
13
|
+
* (aqui, no main.js e uma derivada morta), todas respondendo pelo `kind`, todas errando junto.
|
|
14
|
+
*/
|
|
15
|
+
export declare const EMPATHY_VIZ_MODES: VizMode[];
|
|
16
|
+
/** On/off label shared by every toggle button in this panel (hearing, one-button, wheelchair). */
|
|
17
|
+
export interface EmpathySettingsCtx {
|
|
18
|
+
/** ui/dom.ts querySelector shortcut. */
|
|
19
|
+
$<T extends Element = Element>(sel: string): T | null;
|
|
20
|
+
/** core/a11y-sr.ts screen-reader announcer. */
|
|
21
|
+
srSay(msg: string): void;
|
|
22
|
+
/** platform/storage.ts (or a test double) — read here only, to restore hearing-loss simulation on boot. */
|
|
23
|
+
store: {
|
|
24
|
+
getBool(key: string, fallback?: boolean): boolean;
|
|
25
|
+
};
|
|
26
|
+
/** Renders one viz-mode radio list (+ per-player tabs); shared with settings-visual, so it stays in game.js. */
|
|
27
|
+
renderVizGroup(listSel: string, tabsSel: string, modes: VizMode[]): void;
|
|
28
|
+
/** Reflects #opt-onebtn/#opt-wheelchair; game.js also calls it from its setOneButton/setWheelchair bodies. */
|
|
29
|
+
reflectMotorEmpathy(): void;
|
|
30
|
+
/** Reflects the #opt-visual/#opt-empathy summary buttons; shared across every viz-mode change in the game. */
|
|
31
|
+
reflectVizButtons(): void;
|
|
32
|
+
/** Brings an overlay to front + fills its footer explanations; shared by every Sensibilidade panel. */
|
|
33
|
+
frontOverlay(el: HTMLElement | null): void;
|
|
34
|
+
/** Devolve o foco a quem abriu o diálogo (ui/settings-panel `restoreFocus`). Injetado, e não um `#opt-*`
|
|
35
|
+
* fixo: o id que este módulo focava não existe no documento, então fechar deixava o foco no `<body>`. */
|
|
36
|
+
restoreFocus?: (id: string) => boolean;
|
|
37
|
+
/** Persists + applies the audio graph change (and announces); body stays in game.js. */
|
|
38
|
+
setHearingLoss(on: boolean): void;
|
|
39
|
+
/** Persists + applies one-button-only play (and announces); body stays in game.js. */
|
|
40
|
+
setOneButton(on: boolean): void;
|
|
41
|
+
/** Persists + rebuilds world geometry for wheelchair mode (and announces); body stays in game.js. */
|
|
42
|
+
setWheelchair(on: boolean): void;
|
|
43
|
+
/** Live reads of game.js's oneButton/wheelchair booleans (not yet migrated to core/state.ts). */
|
|
44
|
+
getOneButton(): boolean;
|
|
45
|
+
getWheelchair(): boolean;
|
|
46
|
+
/** Jogadores vivos (core/state `players`), só para saber QUAL modo visual cada um está usando agora. */
|
|
47
|
+
getPlayers(): readonly {
|
|
48
|
+
viz: string;
|
|
49
|
+
}[];
|
|
50
|
+
/** Mesmo setPlayerViz do painel visual e do atalho de contraste; usado aqui só pelo "restaurar padrões". */
|
|
51
|
+
setPlayerViz(i: number, mode: string): void;
|
|
52
|
+
}
|
|
53
|
+
export interface EmpathySettingsApi {
|
|
54
|
+
/** Redraws the simulation list + reflects the hearing/one-button/wheelchair toggle buttons. */
|
|
55
|
+
render(): void;
|
|
56
|
+
/** Opens the #empathy overlay (renders first, fronts it, focuses its first button). */
|
|
57
|
+
open(): void;
|
|
58
|
+
/** Closes the #empathy overlay and returns focus to #opt-empathy. */
|
|
59
|
+
close(): void;
|
|
60
|
+
}
|
|
61
|
+
/** Wires the empathy panel's DOM (buttons + boot restore) and returns render/open/close for game.js to call. */
|
|
62
|
+
export declare function initSettingsEmpathy(ctx: EmpathySettingsCtx): EmpathySettingsApi;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// ui/settings-empathy.ts — Empathy/simulation panel (Sensibilidade → Empatia): lets a player WITHOUT a disability
|
|
3
|
+
// experience one (VIZ_SIM: color-blindness/low-vision/blindness simulation, simulated hearing loss, one-button
|
|
4
|
+
// play, wheelchair). This is the opposite of settings-visual, which CORRECTS the game for a player who has the
|
|
5
|
+
// disability — do not merge the two, even where they share the viz-mode catalog or a helper.
|
|
6
|
+
// Injected via initSettingsEmpathy(ctx): DOM selector, srSay, store, the state setters (setHearingLoss/
|
|
7
|
+
// setOneButton/setWheelchair — still game.js: they touch the audio graph, player powers and world geometry far
|
|
8
|
+
// beyond this panel) plus their live getters, and the shared reflect/overlay helpers (renderVizGroup,
|
|
9
|
+
// reflectMotorEmpathy, reflectVizButtons, frontOverlay — used by sibling panels too, so they stay in game.js).
|
|
10
|
+
// Pure catalog logic (isSimKind/EMPATHY_VIZ_MODES) is unit-tested in node; render/open/close
|
|
11
|
+
// are a thin DOM shell tested in browser. Model: app/js/render/fx.ts.
|
|
12
|
+
import { toggleLabel } from './dom.js';
|
|
13
|
+
import { VIZ_MODES, simulatesDisability } from '../render/viz-modes.js';
|
|
14
|
+
import { hearingLoss, setHearingLossGraph } from '../platform/audio.js';
|
|
15
|
+
import { t } from '../core/i18n.js';
|
|
16
|
+
import { DEFAULTS } from '../core/state.js';
|
|
17
|
+
import { markChanged, markMenuChanged } from './changed-mark.js';
|
|
18
|
+
/**
|
|
19
|
+
* A fatia do catálogo que ESTE painel mostra: só o que SIMULA uma deficiência.
|
|
20
|
+
*
|
|
21
|
+
* Era `VIZ_MODES.filter(m => isSimKind(m.kind))`, e `kind` não distingue simular de corrigir — então as três
|
|
22
|
+
* "Correção de daltonismo" vinham junto, num menu chamado "sentir como é ter uma deficiência". A criança
|
|
23
|
+
* daltônica precisava entrar ali para achar a correção da própria condição, ao lado do botão que simula a
|
|
24
|
+
* condição dela para quem não a tem. Dois públicos opostos na mesma lista.
|
|
25
|
+
*
|
|
26
|
+
* O Dev decidiu (#60): as correções vão para a Acessibilidade visual, e a lista passa a se recortar pelo
|
|
27
|
+
* campo `sim` do catálogo, que responde a pergunta certa. `isSimKind` foi apagado — havia TRÊS cópias dele
|
|
28
|
+
* (aqui, no main.js e uma derivada morta), todas respondendo pelo `kind`, todas errando junto.
|
|
29
|
+
*/
|
|
30
|
+
export const EMPATHY_VIZ_MODES = VIZ_MODES.filter((m) => simulatesDisability(m.key));
|
|
31
|
+
/** Wires the empathy panel's DOM (buttons + boot restore) and returns render/open/close for game.js to call. */
|
|
32
|
+
export function initSettingsEmpathy(ctx) {
|
|
33
|
+
function render() {
|
|
34
|
+
ctx.renderVizGroup('#empathy-list', '#empathy-players', EMPATHY_VIZ_MODES);
|
|
35
|
+
const h = ctx.$('#opt-hearing');
|
|
36
|
+
if (h) {
|
|
37
|
+
h.classList.toggle('is-on', hearingLoss);
|
|
38
|
+
h.setAttribute('aria-pressed', String(hearingLoss));
|
|
39
|
+
h.textContent = toggleLabel(hearingLoss); // ui/dom
|
|
40
|
+
}
|
|
41
|
+
ctx.reflectMotorEmpathy();
|
|
42
|
+
refreshMarks();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* A marca de "saiu do padrão" (ADR-0029). Neste menu ela quer dizer "esta simulação está LIGADA", e por
|
|
46
|
+
* isso é a mais útil dos sete: uma criança que ligou a simulação de cegueira está com a tela preta e não
|
|
47
|
+
* consegue ler nada — mas o leitor de tela dela percorre o menu e diz qual linha saiu do padrão.
|
|
48
|
+
*
|
|
49
|
+
* O recorte é o do reset, pelo mesmo motivo dele: as três CORREÇÕES de daltonismo que este painel lista
|
|
50
|
+
* não são deste menu. Marcá-las aqui mandaria a criança daltônica desfazer, no menu de empatia, a correção
|
|
51
|
+
* que a faz enxergar o jogo.
|
|
52
|
+
*/
|
|
53
|
+
function refreshMarks() {
|
|
54
|
+
const simulando = ctx.getPlayers().some((p) => simulatesDisability(p.viz));
|
|
55
|
+
const surdez = hearingLoss;
|
|
56
|
+
const um = ctx.getOneButton() !== DEFAULTS.oneButton;
|
|
57
|
+
const cadeira = ctx.getWheelchair() !== DEFAULTS.wheelchair;
|
|
58
|
+
const linha = (sel) => ctx.$(sel)?.closest('.ctrl-row') ?? null;
|
|
59
|
+
markChanged(linha('#opt-hearing'), surdez);
|
|
60
|
+
markChanged(linha('#opt-onebtn'), um);
|
|
61
|
+
markChanged(linha('#opt-wheelchair'), cadeira);
|
|
62
|
+
markChanged(ctx.$('#empathy-list'), simulando);
|
|
63
|
+
markMenuChanged(ctx.$('[data-act="empatia"]'), [simulando, surdez, um, cadeira]);
|
|
64
|
+
}
|
|
65
|
+
function open() {
|
|
66
|
+
const ov = ctx.$('#empathy');
|
|
67
|
+
if (!ov)
|
|
68
|
+
return;
|
|
69
|
+
render();
|
|
70
|
+
ov.hidden = false;
|
|
71
|
+
ctx.frontOverlay(ov);
|
|
72
|
+
const f = ov.querySelector('button');
|
|
73
|
+
if (f)
|
|
74
|
+
f.focus();
|
|
75
|
+
}
|
|
76
|
+
function close() {
|
|
77
|
+
const ov = ctx.$('#empathy');
|
|
78
|
+
if (!ov)
|
|
79
|
+
return;
|
|
80
|
+
ov.hidden = true;
|
|
81
|
+
if (ctx.restoreFocus && ctx.restoreFocus('empathy'))
|
|
82
|
+
return;
|
|
83
|
+
const b = ctx.$('#opt-empathy');
|
|
84
|
+
if (b)
|
|
85
|
+
b.focus(); // recuo: este id nao existe no documento hoje (gancho de uma barra futura)
|
|
86
|
+
}
|
|
87
|
+
const empathyBtn = ctx.$('#opt-empathy');
|
|
88
|
+
if (empathyBtn)
|
|
89
|
+
empathyBtn.addEventListener('click', open);
|
|
90
|
+
const empathyClose = ctx.$('#empathy-close');
|
|
91
|
+
if (empathyClose)
|
|
92
|
+
empathyClose.addEventListener('click', close);
|
|
93
|
+
const hearingBtn = ctx.$('#opt-hearing');
|
|
94
|
+
if (hearingBtn)
|
|
95
|
+
hearingBtn.addEventListener('click', () => { ctx.setHearingLoss(!hearingLoss); render(); ctx.reflectVizButtons(); });
|
|
96
|
+
if (ctx.store.getBool('incl_hearingloss'))
|
|
97
|
+
setHearingLossGraph(true); // restaura o grafo de áudio persistido no boot
|
|
98
|
+
// `refreshMarks()` depois de CADA um: estes dois setters não passam por `render()` — eles refletem o botão
|
|
99
|
+
// por conta própria —, então a marca precisa ser puxada aqui ou nunca acompanha a mudança.
|
|
100
|
+
const oneBtn = ctx.$('#opt-onebtn');
|
|
101
|
+
if (oneBtn)
|
|
102
|
+
oneBtn.addEventListener('click', () => { ctx.setOneButton(!ctx.getOneButton()); refreshMarks(); });
|
|
103
|
+
const wheelBtn = ctx.$('#opt-wheelchair');
|
|
104
|
+
if (wheelBtn)
|
|
105
|
+
wheelBtn.addEventListener('click', () => { ctx.setWheelchair(!ctx.getWheelchair()); refreshMarks(); });
|
|
106
|
+
// ---- restaurar os padrões DESTE menu (ADR-0028) ----
|
|
107
|
+
//
|
|
108
|
+
// De todos os oito, este é o reset que mais precisa existir e o que mais precisa ter cuidado, pelo mesmo
|
|
109
|
+
// motivo: é o menu que simula deficiências. Uma criança que liga "Simular cegueira total" fica com a tela
|
|
110
|
+
// preta, e a saída — o toque duplo na bolinha — é justamente o que ela acabou de perder a capacidade de ver.
|
|
111
|
+
//
|
|
112
|
+
// E o cuidado: A LISTA DESTE PAINEL NÃO É A LISTA QUE O RESET DESLIGA, e a diferença é deliberada. O painel
|
|
113
|
+
// mostra também `fix-protan/fix-deuter/fix-tritan`, que são CORREÇÃO de daltonismo, porque hoje elas não têm
|
|
114
|
+
// outro lugar onde morar — o painel visual só cobre os 4 níveis de contraste. Desligá-las junto tiraria de
|
|
115
|
+
// uma criança daltônica a única correção que ela tem, a mando de um menu feito para quem não é daltônico.
|
|
116
|
+
// Por isso o reset pergunta `simulatesDisability`, que responde pelo modo, e não `isSimKind`, que responde
|
|
117
|
+
// pelo `kind` e não distingue simular de corrigir.
|
|
118
|
+
const resetBtn = ctx.$('#empathy-reset');
|
|
119
|
+
if (resetBtn)
|
|
120
|
+
resetBtn.addEventListener('click', () => {
|
|
121
|
+
// Cada setter já é idempotente e anuncia sozinho ao mudar; chamar só na diferença evita anúncio falso de
|
|
122
|
+
// "desligado" para algo que nunca esteve ligado. O anúncio-resumo vem por último, e é o que fica no
|
|
123
|
+
// `#sr-status` — uma ação, uma frase, em vez de três.
|
|
124
|
+
ctx.getPlayers().forEach((p, i) => { if (simulatesDisability(p.viz))
|
|
125
|
+
ctx.setPlayerViz(i, 'normal'); });
|
|
126
|
+
if (hearingLoss)
|
|
127
|
+
ctx.setHearingLoss(false);
|
|
128
|
+
if (ctx.getOneButton() !== DEFAULTS.oneButton)
|
|
129
|
+
ctx.setOneButton(DEFAULTS.oneButton);
|
|
130
|
+
if (ctx.getWheelchair() !== DEFAULTS.wheelchair)
|
|
131
|
+
ctx.setWheelchair(DEFAULTS.wheelchair);
|
|
132
|
+
render();
|
|
133
|
+
ctx.reflectVizButtons();
|
|
134
|
+
ctx.srSay(t('sr.empathy.reset'));
|
|
135
|
+
});
|
|
136
|
+
return { render, open, close };
|
|
137
|
+
}
|