@the-inclusionist/engine 7.0.1 → 8.0.0-rc.2
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 +5 -3
- package/app/css/style.css +22 -5
- 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/opendyslexic-400.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-ar.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-br.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-ca.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-cl.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-co.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-mx.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-us-modern.woff2 +0 -0
- package/app/public/vendor/fonts/playwrite-us-trad.woff2 +0 -0
- package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
- package/app/public/vendor/fonts.css +64 -2
- package/dist-pkg/boot/create-game.d.ts +107 -4
- package/dist-pkg/boot/create-game.js +337 -18
- package/dist-pkg/core/constants.d.ts +0 -28
- package/dist-pkg/core/constants.js +34 -17
- package/dist-pkg/core/contract.d.ts +99 -0
- package/dist-pkg/core/contract.js +78 -0
- package/dist-pkg/core/entity.d.ts +47 -4
- package/dist-pkg/core/layers.d.ts +18 -0
- package/dist-pkg/core/layers.js +18 -0
- package/dist-pkg/core/rng.js +14 -4
- package/dist-pkg/core/route.d.ts +42 -0
- package/dist-pkg/core/route.js +158 -0
- package/dist-pkg/core/state.d.ts +10 -1
- package/dist-pkg/core/state.js +13 -0
- package/dist-pkg/educational/adaptive-engine.d.ts +65 -0
- package/dist-pkg/educational/adaptive-engine.js +117 -0
- package/dist-pkg/educational/segment-bar.d.ts +96 -0
- package/dist-pkg/educational/segment-bar.js +89 -0
- package/dist-pkg/i18n/en.js +44 -0
- package/dist-pkg/i18n/es.js +44 -0
- package/dist-pkg/i18n/pt.js +60 -0
- package/dist-pkg/input/default-bindings.d.ts +40 -0
- package/dist-pkg/input/default-bindings.js +151 -9
- package/dist-pkg/input/gamepad.d.ts +33 -12
- package/dist-pkg/input/gamepad.js +103 -16
- package/dist-pkg/input/keyboard-runtime.d.ts +6 -8
- package/dist-pkg/input/keyboard-runtime.js +24 -5
- package/dist-pkg/input/keyboard.d.ts +42 -0
- package/dist-pkg/input/keyboard.js +87 -17
- package/dist-pkg/input/keydown.d.ts +40 -2
- package/dist-pkg/input/keydown.js +29 -6
- package/dist-pkg/input/latch-edge.d.ts +28 -0
- package/dist-pkg/input/latch-edge.js +45 -0
- package/dist-pkg/input/latch-scope.d.ts +70 -0
- package/dist-pkg/input/latch-scope.js +110 -0
- package/dist-pkg/input/latch-store.d.ts +45 -0
- package/dist-pkg/input/latch-store.js +74 -0
- package/dist-pkg/input/latch-sync.d.ts +47 -0
- package/dist-pkg/input/latch-sync.js +43 -0
- package/dist-pkg/input/origem-sintetica.d.ts +44 -0
- package/dist-pkg/input/origem-sintetica.js +60 -0
- package/dist-pkg/input/pad-defaults.d.ts +22 -0
- package/dist-pkg/input/pad-defaults.js +34 -0
- package/dist-pkg/input/pointer.d.ts +61 -0
- package/dist-pkg/input/pointer.js +71 -0
- package/dist-pkg/input/state.d.ts +86 -1
- package/dist-pkg/input/state.js +128 -1
- package/dist-pkg/input/touch-bindings.d.ts +21 -2
- package/dist-pkg/input/touch-bindings.js +15 -3
- package/dist-pkg/input/touch.js +9 -1
- package/dist-pkg/input/transporte-em-uso.d.ts +101 -0
- package/dist-pkg/input/transporte-em-uso.js +130 -0
- package/dist-pkg/input/transports.d.ts +88 -2
- package/dist-pkg/input/transports.js +60 -5
- package/dist-pkg/input/vocabulary-migration.d.ts +18 -7
- package/dist-pkg/platform/audio-earcons.d.ts +29 -2
- package/dist-pkg/platform/audio-earcons.js +10 -0
- package/dist-pkg/platform/audio-nav.d.ts +1 -1
- package/dist-pkg/platform/audio-sonar.d.ts +149 -9
- package/dist-pkg/platform/audio-sonar.js +232 -21
- package/dist-pkg/platform/guide-intensity.d.ts +40 -0
- package/dist-pkg/platform/guide-intensity.js +73 -0
- package/dist-pkg/platform/pesados-catalogo.d.ts +14 -0
- package/dist-pkg/platform/pesados-catalogo.js +177 -0
- package/dist-pkg/platform/pesados.d.ts +31 -0
- package/dist-pkg/platform/pesados.js +75 -0
- package/dist-pkg/platform/storage.d.ts +30 -0
- package/dist-pkg/platform/storage.js +35 -0
- package/dist-pkg/platform/tts.js +16 -1
- package/dist-pkg/platform/voice-plan.d.ts +109 -0
- package/dist-pkg/platform/voice-plan.js +156 -0
- package/dist-pkg/platform/vozes-prontas.d.ts +28 -0
- package/dist-pkg/platform/vozes-prontas.js +61 -0
- package/dist-pkg/render/draw.d.ts +1 -1
- package/dist-pkg/render/draw.js +15 -5
- package/dist-pkg/render/viz-axes.d.ts +17 -0
- package/dist-pkg/render/viz-axes.js +19 -0
- package/dist-pkg/render/viz-setters.d.ts +34 -2
- package/dist-pkg/render/viz-setters.js +189 -33
- package/dist-pkg/render/wheelchair-sprites.d.ts +11 -3
- package/dist-pkg/render/wheelchair-sprites.js +10 -4
- package/dist-pkg/ui/dom.js +20 -2
- package/dist-pkg/ui/fonts.d.ts +71 -1
- package/dist-pkg/ui/fonts.js +111 -8
- package/dist-pkg/ui/latch-refusal.d.ts +32 -0
- package/dist-pkg/ui/latch-refusal.js +60 -0
- package/dist-pkg/ui/layout.d.ts +32 -0
- package/dist-pkg/ui/layout.js +64 -1
- package/dist-pkg/ui/motion-scene.d.ts +41 -0
- package/dist-pkg/ui/motion-scene.js +76 -0
- package/dist-pkg/ui/panel-shell.d.ts +53 -0
- package/dist-pkg/ui/panel-shell.js +103 -0
- package/dist-pkg/ui/pause-icons.d.ts +168 -20
- package/dist-pkg/ui/pause-icons.js +315 -45
- package/dist-pkg/ui/reach-notice.js +8 -0
- package/dist-pkg/ui/settings-audio.d.ts +14 -1
- package/dist-pkg/ui/settings-audio.js +43 -3
- package/dist-pkg/ui/settings-controls.d.ts +66 -6
- package/dist-pkg/ui/settings-controls.js +179 -17
- package/dist-pkg/ui/settings-empathy.d.ts +15 -0
- package/dist-pkg/ui/settings-empathy.js +2 -0
- package/dist-pkg/ui/settings-motion.d.ts +25 -19
- package/dist-pkg/ui/settings-motion.js +32 -19
- package/dist-pkg/ui/settings-motor.d.ts +101 -3
- package/dist-pkg/ui/settings-motor.js +136 -9
- package/dist-pkg/ui/settings-typo.d.ts +49 -5
- package/dist-pkg/ui/settings-typo.js +74 -23
- package/dist-pkg/ui/settings-visual.d.ts +21 -1
- package/dist-pkg/ui/settings-visual.js +48 -4
- package/dist-pkg/ui/shell.d.ts +13 -3
- package/dist-pkg/ui/shell.js +3 -4
- package/dist-pkg/ui/simulation-refusal.d.ts +32 -0
- package/dist-pkg/ui/simulation-refusal.js +57 -0
- package/dist-pkg/ui/visual-axes-panel.d.ts +48 -0
- package/dist-pkg/ui/visual-axes-panel.js +95 -0
- package/dist-pkg/ui/webcam.js +6 -1
- package/docs/CREDITS.md +22 -1
- package/docs/LICENSES.md +170 -132
- package/package.json +25 -4
- package/app/public/vendor/fonts/greatvibes-400.woff2 +0 -0
- package/app/public/vendor/fonts/ufcook-700.woff2 +0 -0
|
@@ -25,12 +25,24 @@
|
|
|
25
25
|
// · `rm`/`saveRM` — the reduced-motion flags object, co-owned with ui/settings-motion (same reference).
|
|
26
26
|
// · `PM_BTNS`/`QL_NAME` — owned by ui/activities-menu; injected, never copied.
|
|
27
27
|
import { t } from '../core/i18n.js';
|
|
28
|
-
import { CONTRAST_LEVELS
|
|
28
|
+
import { CONTRAST_LEVELS } from './settings-visual.js';
|
|
29
|
+
import { CURTO_DO_TEMA, CURTO_DA_CORRECAO } from './visual-axes-panel.js';
|
|
30
|
+
import { proximoTema, proximaCorrecao, temAltoContraste, PADRAO, } from '../render/viz-axes.js';
|
|
29
31
|
import { anunciarItem } from './item-announcement.js';
|
|
30
32
|
import { rotuloAcessivel } from '../core/rotulo-acessivel.js';
|
|
31
33
|
import { passoNoAnel } from '../core/anel.js'; // da FOLHA, e não de ui/menu-nav: ver a nota lá
|
|
32
34
|
// LIGAÇÃO VIVA (ESM): o índice pode ser desligado no menu, e o valor aqui acompanha sem assinatura.
|
|
33
|
-
import { menuIndexOn } from '../core/state.js';
|
|
35
|
+
import { menuIndexOn, DEFAULTS, setModoCegoValue } from '../core/state.js';
|
|
36
|
+
// ⚠️ IMPORT DIRETO DE `platform/storage`, e não uma peça a mais no `ctx`, e a escolha é sobre quem pode
|
|
37
|
+
// esquecer: `initPauseIcons` é chamado pela raiz de composição de CADA jogo, e um `store` injetado é um
|
|
38
|
+
// campo que um consumidor pode omitir — e omiti-lo faria o nível TEA voltar a não persistir, em silêncio,
|
|
39
|
+
// exactamente no jogo que se esqueceu. É a mesma forma que `ui/fonts` usa, e `ui/` depender de `platform/`
|
|
40
|
+
// não inverte camada nenhuma.
|
|
41
|
+
import * as store from '../platform/storage.js';
|
|
42
|
+
import { definirAlternanciaDeMarcha } from './settings-motor.js';
|
|
43
|
+
import { recusaDaAlternancia } from './latch-refusal.js';
|
|
44
|
+
import { PM_BTNS, PM_OPTIONS_BTNS } from './activities-menu.js';
|
|
45
|
+
import { CHAVES_DE_CENA, ANIMACOES_DO_PERSONAGEM, lerCenaGuardada, guardarCena } from './motion-scene.js';
|
|
34
46
|
/**
|
|
35
47
|
* A LEGENDA de um ícone da barra de acessibilidade — uma função, e não três cópias da mesma expressão.
|
|
36
48
|
*
|
|
@@ -69,6 +81,17 @@ const ICON_BY_KEY = new Map(PAUSE_ICONS.map((ic) => [ic.k, ic]));
|
|
|
69
81
|
export function pauseIcon(k) { return ICON_BY_KEY.get(k); }
|
|
70
82
|
/** TEA cycle: 0 = normal · 1 = calmo (reduces) · 2 = silencioso (switches off). Never touches TTS/blind mode. */
|
|
71
83
|
export const CALM_NAMES = ['calm.off', 'calm.quiet', 'calm.silent'];
|
|
84
|
+
/**
|
|
85
|
+
* O nível TEA guardado, saneado. Fora de 0..2 devolve o padrão — dado do navegador é dado de fora, e um
|
|
86
|
+
* nível inventado escolheria `CALM_NAMES[3]`, que é `undefined`, e o anúncio ao leitor de tela sairia vazio.
|
|
87
|
+
*/
|
|
88
|
+
export function saneiaNivelTea(bruto) {
|
|
89
|
+
return Number.isInteger(bruto) && bruto >= 0 && bruto < CALM_NAMES.length ? bruto : DEFAULTS.calmMode;
|
|
90
|
+
}
|
|
91
|
+
/** Lê o nível TEA do armazenamento. Chamado no `init`, nunca no import. */
|
|
92
|
+
function lerNivelTea() {
|
|
93
|
+
return saneiaNivelTea(store.getNum(store.KEYS.tea, DEFAULTS.calmMode));
|
|
94
|
+
}
|
|
72
95
|
/** The audio categories `applyCalm` governs. TTS/sonar/guarda/guia stay untouched — a calm player still needs them. */
|
|
73
96
|
export const CALM_AUDIO_CATS = ['ambient', 'music', 'earcons', 'other', 'interact'];
|
|
74
97
|
/** Colour-vision-deficiency cycle, in `player.viz` values. */
|
|
@@ -151,9 +174,9 @@ export function computeIconLabel(k, s) {
|
|
|
151
174
|
if (k === 'altmove')
|
|
152
175
|
return rotulo(s.toggleMove ? 'state.on' : 'state.off');
|
|
153
176
|
if (k === 'contrast')
|
|
154
|
-
return rotulo(
|
|
177
|
+
return rotulo(CURTO_DO_TEMA[s.visual.tema]);
|
|
155
178
|
if (k === 'cvd')
|
|
156
|
-
return t('icon.state', { nome: t('icon.cvd.short'), v: t(
|
|
179
|
+
return t('icon.state', { nome: t('icon.cvd.short'), v: t(CURTO_DA_CORRECAO[s.visual.correcao]) });
|
|
157
180
|
return t(ic.n);
|
|
158
181
|
}
|
|
159
182
|
/** Pure form of reflectIconBtn's branching. A `soon` icon lands on all-false — it never claims to be on. */
|
|
@@ -176,17 +199,17 @@ export function computeIconVisual(k, s) {
|
|
|
176
199
|
}
|
|
177
200
|
else if (k === 'altmove') {
|
|
178
201
|
on = s.toggleMove;
|
|
202
|
+
dis = !!s.alternanciaExigida;
|
|
179
203
|
}
|
|
180
204
|
else if (k === 'contrast') {
|
|
181
|
-
on =
|
|
205
|
+
on = temAltoContraste(s.visual);
|
|
182
206
|
}
|
|
183
207
|
else if (k === 'cvd') {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
cvd = 'pi-cvd-tritan';
|
|
208
|
+
// ⚠️ O FUNDO DE DUAS CORES É O SINAL DE LIGADO deste ícone, e agora ele lê o EIXO da correção — que
|
|
209
|
+
// continua a dizer o mesmo quando o tema também está ligado, coisa que a chave única não conseguia: com
|
|
210
|
+
// `hc-direto-7` no campo, a correção da criança desaparecia do ícone que existe para a mostrar.
|
|
211
|
+
if (s.visual.correcao !== 'tricro')
|
|
212
|
+
cvd = 'pi-cvd-' + s.visual.correcao;
|
|
190
213
|
}
|
|
191
214
|
return { on, dis, calm, cvd, active: on || calm || !!cvd };
|
|
192
215
|
}
|
|
@@ -201,9 +224,60 @@ export function iconBtnMarkup(ic) {
|
|
|
201
224
|
return '<button class="pi-btn' + (ic.soon ? ' pi-soon' : '') + '" type="button" data-pi="' + ic.k +
|
|
202
225
|
'" aria-label="' + (ic.soon ? t('icon.soon', { nome: t(ic.n) }) : t(ic.n)) + '">' + ic.e + '</button>';
|
|
203
226
|
}
|
|
227
|
+
export function iconesQueAccionam(escritores) {
|
|
228
|
+
// ⚠️ POR ÍCONE, e não um booleano para os dois — e foi uma MUTAÇÃO SOBREVIVENTE que o mostrou. Com uma
|
|
229
|
+
// única bandeira, `&&` e `||` produziam o mesmo resultado nos casos que eu tinha escrito, porque todos
|
|
230
|
+
// tiravam os DOIS escritores. O `&&` escondia um ícone que FUNCIONA quando só um escritor falta, e o `||`
|
|
231
|
+
// mostrava um que NÃO funciona. Os dois erram, em direcções opostas, e a pergunta certa nunca foi «este
|
|
232
|
+
// jogo tem escritores visuais» — é «este ÍCONE tem quem o accione».
|
|
233
|
+
// 📌 E o `altmove` entra pela MESMA porta, que é o achado: «este jogo segura teclas?» é a mesma pergunta
|
|
234
|
+
// que «este ícone tem quem o accione», feita a um campo do contrato em vez de a um escritor injectado.
|
|
235
|
+
// Um terceiro ramo, e não uma regra nova.
|
|
236
|
+
return PAUSE_ICONS.filter((ic) => (ic.k === 'contrast' ? escritores.tema
|
|
237
|
+
: ic.k === 'cvd' ? escritores.correcao
|
|
238
|
+
: ic.k === 'altmove' ? escritores.seguraTeclas
|
|
239
|
+
: true));
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* OS TRÊS ITENS QUE A ENGINE ACCIONA SOZINHA, e que por isso nunca dependem do `getPauseActs` de um jogo.
|
|
243
|
+
*
|
|
244
|
+
* 📏 Lidos do despacho, e não decididos aqui: `options`/`pmback` trocam qual lista está no cartão e
|
|
245
|
+
* `acessibilidade` leva o cursor à barra rápida — os três são tratados neste módulo e voltam antes de a
|
|
246
|
+
* tabela do jogo ser consultada.
|
|
247
|
+
*/
|
|
248
|
+
export const ITENS_DA_ENGINE = new Set(['options', 'pmback', 'acessibilidade']);
|
|
249
|
+
/**
|
|
250
|
+
* OS ITENS DO MENU QUE ESTE JOGO CONSEGUE MESMO ACCIONAR (ADR-0106 §5).
|
|
251
|
+
*
|
|
252
|
+
* ⚠️ HOJE UM ITEM SEM ACÇÃO É UM BOTÃO MORTO, E EM SILÊNCIO. O despacho faz `const fn = acts[act]; if (fn)
|
|
253
|
+
* fn();` — quem carrega num item que o jogo não implementou não recebe erro, não recebe anúncio, não recebe
|
|
254
|
+
* nada. Para quem vê, parece que o clique falhou; para quem navega por leitor de tela, o menu leu-lhe um
|
|
255
|
+
* item que não existe. É exactamente o que o §5 chama de pior do que a ausência: «uma barra que oferece um
|
|
256
|
+
* caminho e depois o recusa ensina-lhe que o caminho não é para ela».
|
|
257
|
+
*/
|
|
258
|
+
export function itensQueAccionam(botoes, acts) {
|
|
259
|
+
return botoes.filter((b) => ITENS_DA_ENGINE.has(b.act) || typeof acts[b.act] === 'function');
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* A LISTA RAIZ, com uma regra a mais: `options` é uma PORTA, e uma porta para uma sala vazia também é um
|
|
263
|
+
* botão morto.
|
|
264
|
+
*
|
|
265
|
+
* ⚠️ Esta é a parte que um filtro item-a-item não apanha. Se todos os painéis de ajuste forem filtrados —
|
|
266
|
+
* um jogo que não monta nenhum —, o item `options` sobrevive (a engine acciona-o) e abre uma lista sem nada.
|
|
267
|
+
* A criança atravessa uma porta e fica presa num submenu vazio, cuja única saída é o `pmback` que também
|
|
268
|
+
* sumiu com ele.
|
|
269
|
+
*/
|
|
270
|
+
export function raizQueAcciona(raiz, opcoes, acts) {
|
|
271
|
+
const opcoesVivas = itensQueAccionam(opcoes, acts).filter((b) => b.act !== 'pmback');
|
|
272
|
+
const viva = itensQueAccionam(raiz, acts);
|
|
273
|
+
return opcoesVivas.length > 0 ? viva : viva.filter((b) => b.act !== 'options');
|
|
274
|
+
}
|
|
204
275
|
/** The whole icon bar. Used by the pause screen AND by the splash `#title-icons` (which built the same string
|
|
205
|
-
* by hand in game.js — that duplication dies with this export).
|
|
206
|
-
|
|
276
|
+
* by hand in game.js — that duplication dies with this export).
|
|
277
|
+
* O parâmetro é ADITIVO e o padrão é a lista inteira: quem já chamava sem argumentos não muda de resultado. */
|
|
278
|
+
export function iconsMarkup(icones = PAUSE_ICONS) {
|
|
279
|
+
return icones.map(iconBtnMarkup).join('');
|
|
280
|
+
}
|
|
207
281
|
/** One `.pm-btn`. Dynamic labels (`letra`/`nivel`) are rendered eagerly and carry NO `data-i18n`, so
|
|
208
282
|
* i18n.applyDom() cannot overwrite them. */
|
|
209
283
|
/**
|
|
@@ -268,8 +342,8 @@ export function mostrarSubmenuDaPausa(sp, sub) {
|
|
|
268
342
|
* A LEGENDA VIAJA JUNTO. Ela é a dica que substitui, para quem não vê, o `title` que só o mouse revela;
|
|
269
343
|
* deixá-la no cartão tornaria a barra do HUD muda.
|
|
270
344
|
*/
|
|
271
|
-
export function quickBarMarkup() {
|
|
272
|
-
return '<div class="pause-icons" role="group" aria-label="' + t('pause.iconBarAria') + '">' + iconsMarkup() +
|
|
345
|
+
export function quickBarMarkup(icones = PAUSE_ICONS) {
|
|
346
|
+
return '<div class="pause-icons" role="group" aria-label="' + t('pause.iconBarAria') + '">' + iconsMarkup(icones) +
|
|
273
347
|
'</div><p class="pause-icons-cap" aria-live="polite"></p>';
|
|
274
348
|
}
|
|
275
349
|
export function acaoNaBarra(k, temStart) {
|
|
@@ -298,11 +372,96 @@ export function screenPauseMarkup(o) {
|
|
|
298
372
|
// DOM-facing shell
|
|
299
373
|
// ---------------------------------------------------------------------------------------------
|
|
300
374
|
export function initPauseIcons(ctx) {
|
|
301
|
-
//
|
|
302
|
-
// — verbatim: game.js never wrote it to storage
|
|
303
|
-
|
|
375
|
+
// ⚠️ O NÍVEL TEA PASSOU A PERSISTIR EM 2026-09-07 (issue #61). Este comentário dizia «deliberately NOT
|
|
376
|
+
// persisted — verbatim: game.js never wrote it to storage», e o **verbatim** é o que o desqualificava como
|
|
377
|
+
// decisão: foi PRESERVADO na extração do monólito, não escolhido. O ADR-0028 diz que todo menu persiste, e
|
|
378
|
+
// este é um controlo de menu que vive na barra rápida.
|
|
379
|
+
//
|
|
380
|
+
// O custo de não persistir era da criança que mais precisa dele: quem usa o modo SILENCIOSO voltava a
|
|
381
|
+
// pô-lo a cada sessão — e é para quem o barulho inesperado custa mais. Um ajuste que se esquece não é um
|
|
382
|
+
// ajuste, é uma tarefa diária.
|
|
383
|
+
//
|
|
384
|
+
// ⚠️ LÊ NO INIT, NUNCA NO IMPORT: a regra vale para todo o projeto e aqui tem custo concreto — um teste que
|
|
385
|
+
// importasse este módulo passaria a depender do `localStorage` do ambiente, e um nível herdado de outro
|
|
386
|
+
// caso é uma falha que aparece longe da causa.
|
|
387
|
+
let calmMode = lerNivelTea();
|
|
304
388
|
const P = () => ctx.getPlayers();
|
|
389
|
+
/*
|
|
390
|
+
* ⚠️ A ALTERNÂNCIA DE MARCHA PASSOU A TER PADRÃO DA ENGINE (ADR-0106 §4, etapa 1b). Quem injecta continua a
|
|
391
|
+
* mandar; quem não injecta deixa de ficar sem ela — que era o caso dos cinco jogos sem barra.
|
|
392
|
+
*
|
|
393
|
+
* O `store` vem do import directo deste módulo e não do `ctx`, porque é assim que este ficheiro já persiste
|
|
394
|
+
* o resto: uma chave injectada é um campo que um consumidor pode omitir, e omiti-la faria escrever num nome
|
|
395
|
+
* torto.
|
|
396
|
+
*/
|
|
397
|
+
const setToggleMove = ctx.setToggleMove
|
|
398
|
+
?? ((i, on) => definirAlternanciaDeMarcha({
|
|
399
|
+
players: P(), store, srSay: ctx.srSay, getNumPlayers: ctx.getNumPlayers,
|
|
400
|
+
// ⚠️ ATRAVESSA, e não se resolve aqui: o ícone e o painel têm de escrever a MESMA coisa. Resolver
|
|
401
|
+
// o aparelho num deles e não no outro é como duas superfícies da mesma engine passam a discordar.
|
|
402
|
+
transporteEmUso: ctx.transporteEmUso,
|
|
403
|
+
}, i, on));
|
|
404
|
+
/*
|
|
405
|
+
* ⚠️ O MODO CEGO IDEM, e aqui o padrão é literalmente o que o `core/state` já decidiu que um setter faz:
|
|
406
|
+
* «grava, persiste, avisa» — e nada mais. Os efeitos de jogo (refazer os extras do nível) são REACÇÃO, e
|
|
407
|
+
* quem reage assina `on('modoCego', …)`. O anúncio não se perde para quem não injecta: este ícone já diz
|
|
408
|
+
* `sr.icon.blindOn`/`Off` por si, logo abaixo.
|
|
409
|
+
*/
|
|
410
|
+
const setModoCego = ctx.setModoCego ?? setModoCegoValue;
|
|
411
|
+
/** O documento onde se constroi. Resolvido a cada uso, e por globalThis — em node o identificador
|
|
412
|
+
* document nem existe, e um ?? sobre ele lançaria ReferenceError em vez de cair no padrão. */
|
|
413
|
+
const docDaMontagem = () => ctx.doc ?? globalThis.document;
|
|
414
|
+
/*
|
|
415
|
+
* ⚠️ O CONTRASTE E A COR SÓ APARECEM SE HOUVER QUEM OS ESCREVA (ADR-0106 §5).
|
|
416
|
+
*
|
|
417
|
+
* 📏 Medido em 2026-09-08, e é o que separa este caso dos outros seis campos «acidentais»: eles eram estado
|
|
418
|
+
* que um cartucho calhou de guardar, e a engine pôde reclamá-los. Estes precisam de um
|
|
419
|
+
* `render/viz-setters`, cujo contexto pede **34 campos** do grafo de render de UM jogo — `parallaxLayers`,
|
|
420
|
+
* `decoSprites`, `getPowerups`, `rebuildCoins`, `worldSprite`. O `createGame` não monta isso, e um quiz não
|
|
421
|
+
* tem nada disso para montar. A engine não pode dar um padrão aqui; o que ela pode é não FINGIR.
|
|
422
|
+
*
|
|
423
|
+
* Decidido uma vez, no arranque, e não a cada montagem de barra: o conjunto de escritores de um consumidor
|
|
424
|
+
* não muda a meio de uma partida, e recalcular por tela faria as telas discordarem entre si.
|
|
425
|
+
*/
|
|
426
|
+
const iconesDoJogo = iconesQueAccionam({
|
|
427
|
+
tema: Boolean(ctx.setTemaDoJogador),
|
|
428
|
+
correcao: Boolean(ctx.setCorrecaoDoJogador),
|
|
429
|
+
// 📌 Sem `Boolean(...)`: os dois de cima perguntam «existe escritor?» a um campo opcional; este é uma
|
|
430
|
+
// RESPOSTA que o jogo deu, e envolvê-la faria um `undefined` de um ctx mal montado virar `false` —
|
|
431
|
+
// esconder o controle em silêncio, que é metade do defeito que este campo existe para não cometer.
|
|
432
|
+
seguraTeclas: ctx.seguraTeclas,
|
|
433
|
+
});
|
|
434
|
+
/*
|
|
435
|
+
* ⚠️ RESOLVIDOS UMA VEZ, no arranque, pela mesma razão do `settings-motion`: o `rm` é mutado in-place e
|
|
436
|
+
* partilhado por REFERÊNCIA com quem desenha a cena, e resolvê-lo a cada uso criaria um objecto novo por
|
|
437
|
+
* chamada — o interruptor deixaria de alcançar o desenho, sem erro nenhum.
|
|
438
|
+
*
|
|
439
|
+
* 📌 O `getPauseActs` fica FUNÇÃO e não valor, porque a laziness dele é a razão de ele existir assim: no
|
|
440
|
+
* cartucho a tabela é um `const` declarado ~1200 linhas abaixo, e lê-la aqui cairia na zona morta temporal.
|
|
441
|
+
*/
|
|
442
|
+
const rm = ctx.rm ?? lerCenaGuardada();
|
|
443
|
+
const rmKeys = ctx.rmKeys ?? CHAVES_DE_CENA;
|
|
444
|
+
const rmChar = ctx.rmChar ?? ANIMACOES_DO_PERSONAGEM;
|
|
445
|
+
const saveRM = ctx.saveRM ?? (() => guardarCena(rm));
|
|
446
|
+
/*
|
|
447
|
+
* ⚠️ ESTES TRÊS SÃO LIDOS A CADA CHAMADA, e não resolvidos uma vez como o `rm` acima. A diferença é
|
|
448
|
+
* deliberada e um teste apanhou-me a errá-la: congelar `ctx.getPauseActs` no arranque partiu um caso que
|
|
449
|
+
* TROCA a tabela depois do `init` — e trocar depois é legítimo, porque a laziness deste campo existe
|
|
450
|
+
* precisamente por a tabela chegar tarde. Um padrão não pode custar a ligação tardia que o campo tem.
|
|
451
|
+
*
|
|
452
|
+
* O `rm` é o contrário e por isso fica congelado: ali o que importa é a IDENTIDADE do objecto, partilhada
|
|
453
|
+
* por referência com quem desenha a cena.
|
|
454
|
+
*
|
|
455
|
+
* A anotação de tipo é necessária: sem ela o padrão `() => ({})` infere `{}`, que não aceita indexação por
|
|
456
|
+
* string, e o compilador passaria a recusar `acts[act]` — o despacho inteiro.
|
|
457
|
+
*/
|
|
458
|
+
const dynLabel = (b) => (ctx.dynLabel ? ctx.dynLabel(b) : null);
|
|
459
|
+
const getPauseActs = () => (ctx.getPauseActs ? ctx.getPauseActs() : {});
|
|
460
|
+
const setPauseActor = (i) => { if (ctx.setPauseActor)
|
|
461
|
+
ctx.setPauseActor(i); };
|
|
305
462
|
function hasPrivateOutput(i) { return hasPrivateOutputIn(P(), ctx.getNumPlayers(), i); }
|
|
463
|
+
/** A recusa da alternância para este jogador agora, ou `null`. Recalculada: o aparelho em uso muda. */
|
|
464
|
+
function recusaAgora(i) { return ctx.transporteEmUso ? recusaDaAlternancia(ctx.transporteEmUso(i)) : null; }
|
|
306
465
|
function iconState(i) {
|
|
307
466
|
const p = P()[i] || {};
|
|
308
467
|
const cat = ctx.getAudioCat();
|
|
@@ -312,19 +471,25 @@ export function initPauseIcons(ctx) {
|
|
|
312
471
|
librasOn: ctx.isLibrasOn(),
|
|
313
472
|
calmMode,
|
|
314
473
|
toggleMove: !!p.toggleMove,
|
|
315
|
-
|
|
474
|
+
// ⚠️ `DEFAULTS.viz` E NÃO `''` (issue #61). A cadeia vazia funcionava por ACIDENTE: não casa
|
|
475
|
+
// `hc-direto` nem `fix-*`, então os dois ícones ficavam apagados pelo motivo certo por engano. O padrão
|
|
476
|
+
// passou a ter nome em `core/state`, e `render/viz-modes` já declarava esse modo com `kind:'normal'` —
|
|
477
|
+
// o que não faz nada. Dizer o padrão em vez de o deduzir é o que torna a marca do ADR-0029 possível
|
|
478
|
+
// aqui, porque ela lê `DEFAULTS` e mais nada.
|
|
479
|
+
visual: p.visual ?? PADRAO,
|
|
316
480
|
privateOutput: hasPrivateOutput(i),
|
|
481
|
+
alternanciaExigida: recusaAgora(i) !== null,
|
|
317
482
|
};
|
|
318
483
|
}
|
|
319
484
|
// --- TEA ---------------------------------------------------------------------------------
|
|
320
485
|
function applyCalm() {
|
|
321
486
|
const plan = calmMotionPlan(calmMode);
|
|
322
|
-
for (const k of
|
|
323
|
-
|
|
324
|
-
|
|
487
|
+
for (const k of rmKeys)
|
|
488
|
+
rm[k] = plan.sceneReduced;
|
|
489
|
+
saveRM();
|
|
325
490
|
// "silencioso" freezes the character too. Iterates the WHOLE players array (not just numPlayers) — verbatim.
|
|
326
491
|
for (const p of P())
|
|
327
|
-
for (const c of
|
|
492
|
+
for (const c of rmChar)
|
|
328
493
|
p[c.prop] = plan.charFrozen;
|
|
329
494
|
const cat = ctx.getAudioCat();
|
|
330
495
|
for (const k of CALM_AUDIO_CATS) {
|
|
@@ -341,7 +506,7 @@ export function initPauseIcons(ctx) {
|
|
|
341
506
|
// --- icon actions (the dispatcher, as a table) ---------------------------------------------
|
|
342
507
|
const ICON_ACTS = {
|
|
343
508
|
blind: () => {
|
|
344
|
-
|
|
509
|
+
setModoCego(!ctx.getModoCego());
|
|
345
510
|
ctx.srSay(t(ctx.getModoCego() ? 'sr.icon.blindOn' : 'sr.icon.blindOff'));
|
|
346
511
|
},
|
|
347
512
|
tts: () => {
|
|
@@ -360,22 +525,35 @@ export function initPauseIcons(ctx) {
|
|
|
360
525
|
},
|
|
361
526
|
tea: () => {
|
|
362
527
|
calmMode = nextCalmMode(calmMode);
|
|
528
|
+
store.set(store.KEYS.tea, calmMode); // ADR-0028: todo menu persiste. Ver a nota no `let` acima.
|
|
363
529
|
applyCalm();
|
|
364
530
|
ctx.srSay(t('sr.icon.tea', { v: t(CALM_NAMES[calmMode]) }));
|
|
365
531
|
},
|
|
366
532
|
altmove: (i) => {
|
|
367
533
|
// verbatim: `players[i].toggleMove` with no `||{}` guard (unlike contrast/cvd below).
|
|
368
|
-
|
|
534
|
+
setToggleMove(i, !P()[i].toggleMove);
|
|
369
535
|
},
|
|
536
|
+
// ⚠️ OS DOIS ÍCONES DEIXARAM DE SE APAGAR UM AO OUTRO (#104). Eles SEMPRE ciclaram dentro do seu eixo —
|
|
537
|
+
// `nextContrast` e `nextCvd` existem separados desde sempre —, mas escreviam os dois no mesmo campo, e
|
|
538
|
+
// por isso mexer num zerava o outro. O snapshot dizia isso como se fosse desenho: «they overwrite each
|
|
539
|
+
// other; that is by design». Agora cada um escreve no seu eixo e o outro fica onde estava.
|
|
540
|
+
// ⚠️ AS DUAS GUARDAS NÃO SÃO CINTO E SUSPENSÓRIOS. Sem escritor, o ícone nem sequer é montado
|
|
541
|
+
// (`iconesDoJogo`), então este ramo não deveria ser alcançável pela barra — mas `iconAct` é EXPORTADO e
|
|
542
|
+
// qualquer consumidor pode chamá-lo por chave. Sem a guarda, essa chamada rebentaria; com ela, não faz
|
|
543
|
+
// nada e não anuncia — que é o mesmo que dizer a verdade: este jogo não tem por onde.
|
|
370
544
|
contrast: (i) => {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
545
|
+
if (!ctx.setTemaDoJogador)
|
|
546
|
+
return;
|
|
547
|
+
const v = proximoTema((P()[i] || {}).visual ?? PADRAO);
|
|
548
|
+
ctx.setTemaDoJogador(i, v.tema);
|
|
549
|
+
ctx.srSay(t('sr.visual.contrast', { v: t(CURTO_DO_TEMA[v.tema]) }));
|
|
374
550
|
},
|
|
375
551
|
cvd: (i) => {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
552
|
+
if (!ctx.setCorrecaoDoJogador)
|
|
553
|
+
return;
|
|
554
|
+
const v = proximaCorrecao((P()[i] || {}).visual ?? PADRAO);
|
|
555
|
+
ctx.setCorrecaoDoJogador(i, v.correcao);
|
|
556
|
+
ctx.srSay(t('sr.icon.cvd', { v: t(CURTO_DA_CORRECAO[v.correcao]) }));
|
|
379
557
|
},
|
|
380
558
|
};
|
|
381
559
|
function iconAct(k, i) {
|
|
@@ -388,6 +566,17 @@ export function initPauseIcons(ctx) {
|
|
|
388
566
|
ctx.srAlert(t('sr.icon.needsPrivateOutput'));
|
|
389
567
|
return;
|
|
390
568
|
}
|
|
569
|
+
// ⚠️ A MESMA RECUSA DO PAINEL, na mesma forma que as duas acima: DIZER e voltar. Aceitar o clique e
|
|
570
|
+
// ignorá-lo é a outra metade do que o ADR-0076 proíbe, e aqui há um agravante — este ícone e o
|
|
571
|
+
// `#opt-altmove` escrevem o MESMO valor, logo um a aceitar enquanto o outro recusa daria à criança dois
|
|
572
|
+
// botões que discordam sobre o mesmo ajuste.
|
|
573
|
+
if (k === 'altmove') {
|
|
574
|
+
const recusa = recusaAgora(i);
|
|
575
|
+
if (recusa) {
|
|
576
|
+
ctx.srAlert(t(recusa.chave));
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
391
580
|
const act = ICON_ACTS[k];
|
|
392
581
|
if (act)
|
|
393
582
|
act(i);
|
|
@@ -405,10 +594,27 @@ export function initPauseIcons(ctx) {
|
|
|
405
594
|
b.classList.add(v.cvd);
|
|
406
595
|
b.classList.toggle('pi-on', v.on);
|
|
407
596
|
b.classList.toggle('pi-dis', v.dis);
|
|
597
|
+
/*
|
|
598
|
+
* 🔴 A ISSUE #128, E ELA É DE UMA LINHA: `pi-dis` é CLASSE CSS. A criança que enxerga vê o ícone
|
|
599
|
+
* apagado; a que navega por leitor de tela não recebe nada — o botão anuncia-se accionável e não
|
|
600
|
+
* responde. `aria-disabled` espelha o mesmo facto para quem ouve.
|
|
601
|
+
*
|
|
602
|
+
* ⚠️ E `aria-disabled` e NÃO `disabled`: o segundo tira o botão da ordem de tabulação, e quem navega
|
|
603
|
+
* por teclado deixaria de o alcançar — logo deixaria de poder ouvir POR QUE ele não responde. É a
|
|
604
|
+
* mesma escolha que o `#opt-altmove` faz no painel, pela mesma razão.
|
|
605
|
+
*/
|
|
606
|
+
if (v.dis)
|
|
607
|
+
b.setAttribute('aria-disabled', 'true');
|
|
608
|
+
else
|
|
609
|
+
b.removeAttribute('aria-disabled');
|
|
408
610
|
b.setAttribute('aria-pressed', String(v.active));
|
|
409
|
-
// `soon`
|
|
410
|
-
|
|
411
|
-
|
|
611
|
+
// ⚠️ TODO ÍCONE RECEBE RÓTULO, `soon` INCLUÍDO — e o guarda que aqui estava dizia por que não: «`soon`
|
|
612
|
+
// buttons keep the label the markup gave them (same string)». A segunda metade continua certa (não há
|
|
613
|
+
// estado a reportar), mas «mesma string» era verdade só enquanto a marcação e o reflexo corressem no
|
|
614
|
+
// MESMO IDIOMA — e desde que a engine passou a montar a barra (ADR-0106 etapa 2) deixam de correr.
|
|
615
|
+
// O `initI18n` carrega en/es de forma assíncrona; a marcação nasce em pt e só o reflexo a corrige.
|
|
616
|
+
// 📌 Medido num navegador: cinco ícones em inglês e três ainda em «(em construção)», na mesma barra.
|
|
617
|
+
b.setAttribute('aria-label', computeIconLabel(k, st));
|
|
412
618
|
}
|
|
413
619
|
function reflectIconsIn(root, i) {
|
|
414
620
|
if (!root)
|
|
@@ -419,8 +625,48 @@ export function initPauseIcons(ctx) {
|
|
|
419
625
|
* Reflete os ícones de TODAS as telas. Varre as BARRAS e não mais os cartões de pausa: desde o item 7 do
|
|
420
626
|
* ADR-0044 os ícones vivem no HUD, e um cartão de pausa não contém `.pi-btn` nenhum.
|
|
421
627
|
*/
|
|
628
|
+
/**
|
|
629
|
+
* OS CARTÕES QUE ESTA INSTÂNCIA CONSTRUIU. Privado, e é a resposta a um problema que eu próprio criei.
|
|
630
|
+
*
|
|
631
|
+
* ⚠️ O `PauseIconsCtx` tinha um `getPauseScreens` e eu removi-o hoje, com razão: tinha ZERO leitores. Agora
|
|
632
|
+
* este módulo precisa de alcançar os cartões — e a saída certa NÃO é repor o campo. Quem os construiu foi
|
|
633
|
+
* ele; guardar o que construiu não pede nada a consumidor nenhum, e um campo de ctx é mais uma coisa que
|
|
634
|
+
* cada um dos 300 jogos teria de se lembrar de passar.
|
|
635
|
+
*/
|
|
636
|
+
const cartoes = [];
|
|
637
|
+
/**
|
|
638
|
+
* ESCONDE OS ITENS QUE ESTE JOGO NÃO CONSEGUE ACCIONAR — recalculado, e não decidido no arranque.
|
|
639
|
+
*
|
|
640
|
+
* ⚠️ ESTA FUNÇÃO EXISTE POR UM DEFEITO DE TEMPO. O cartão era FILTRADO no `buildScreenPause`, e o
|
|
641
|
+
* `getPauseActs` é um getter precisamente porque a tabela CHEGA TARDE — «`pauseActs` is a `const` declared
|
|
642
|
+
* far below the init site», diz o próprio campo. Um consumidor que siga esse padrão documentado montava um
|
|
643
|
+
* cartão sem os itens cuja acção só existiu depois do boot, e nunca mais os recuperava.
|
|
644
|
+
*
|
|
645
|
+
* 📌 A avaliação passou para o ÚLTIMO instante possível: o `ui/shell` chama `reflectPauseIcons()` quando a
|
|
646
|
+
* fase vira `pause-menu`, ou seja quando a pausa ABRE. O §5 continua respeitado — a criança nunca vê um
|
|
647
|
+
* item que não acciona —, e agora também vê os que passaram a accionar.
|
|
648
|
+
*/
|
|
649
|
+
function refrescarItensDaPausa() {
|
|
650
|
+
const acts = getPauseActs();
|
|
651
|
+
const raiz = ctx.pmButtons ?? PM_BTNS;
|
|
652
|
+
const opcoes = ctx.optionsButtons ?? PM_OPTIONS_BTNS;
|
|
653
|
+
const vivos = new Set([
|
|
654
|
+
...raizQueAcciona(raiz, opcoes, acts).map((b) => b.act),
|
|
655
|
+
...itensQueAccionam(opcoes, acts).map((b) => b.act),
|
|
656
|
+
]);
|
|
657
|
+
// ⚠️ `filter(Boolean)`: os cartões são indexados por JOGADOR, e montar só a tela 2 deixa um buraco no
|
|
658
|
+
// índice 0. Um `for…of` sobre array esparso entrega `undefined`, e foi o que rebentou à primeira.
|
|
659
|
+
for (const cartao of cartoes.filter(Boolean)) {
|
|
660
|
+
for (const btn of cartao.querySelectorAll('.pm-btn')) {
|
|
661
|
+
// ⚠️ `hidden` e não `remove()`: reaparecer tem de ser possível, porque a tabela pode crescer outra vez
|
|
662
|
+
// (um jogo que só liga «sair» depois da primeira fase). Remover seria decidir uma vez de novo.
|
|
663
|
+
btn.hidden = !vivos.has(btn.dataset.act ?? '');
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
422
667
|
function reflectPauseIcons() {
|
|
423
668
|
ctx.getA11yBars().forEach((bar, i) => reflectIconsIn(bar, i));
|
|
669
|
+
refrescarItensDaPausa();
|
|
424
670
|
}
|
|
425
671
|
// --- the pause screen ----------------------------------------------------------------------
|
|
426
672
|
/**
|
|
@@ -472,7 +718,7 @@ export function initPauseIcons(ctx) {
|
|
|
472
718
|
if (!bar || !primeiro)
|
|
473
719
|
return;
|
|
474
720
|
naBarra.add(i);
|
|
475
|
-
const acts =
|
|
721
|
+
const acts = getPauseActs();
|
|
476
722
|
if (acts.resume)
|
|
477
723
|
acts.resume(); // volta à tela normal: o modo é para usar DURANTE a partida
|
|
478
724
|
ctx.srSay(t('sr.a11y.barEnter'));
|
|
@@ -516,7 +762,7 @@ export function initPauseIcons(ctx) {
|
|
|
516
762
|
const cur = iconeSelecionado(bar);
|
|
517
763
|
const idx = cur ? icones.indexOf(cur) : 0;
|
|
518
764
|
if (acao === 'ativar') {
|
|
519
|
-
|
|
765
|
+
setPauseActor(i);
|
|
520
766
|
if (cur)
|
|
521
767
|
cur.click();
|
|
522
768
|
return;
|
|
@@ -527,19 +773,36 @@ export function initPauseIcons(ctx) {
|
|
|
527
773
|
}
|
|
528
774
|
}
|
|
529
775
|
function buildScreenPause(i) {
|
|
530
|
-
const sp =
|
|
776
|
+
const sp = docDaMontagem().createElement('div');
|
|
531
777
|
sp.className = 'screen-pause';
|
|
532
778
|
sp.hidden = true;
|
|
533
779
|
sp.dataset.player = String(i);
|
|
780
|
+
/*
|
|
781
|
+
* ⚠️ MONTA A LISTA INTEIRA E ESCONDE DEPOIS — e a versão anterior desta linha FILTRAVA aqui, o que estava
|
|
782
|
+
* errado por uma razão de TEMPO. O comentário que estava neste sítio dizia «montar é o primeiro instante
|
|
783
|
+
* em que a resposta existe»; não é. O `getPauseActs` é um getter precisamente porque a tabela chega
|
|
784
|
+
* TARDE — «`pauseActs` is a `const` declared far below the init site», diz o próprio campo —, e o
|
|
785
|
+
* `createGame` monta durante o próprio `createGame(...)`. Filtrar aqui apagava para sempre todo item cuja
|
|
786
|
+
* acção só passou a existir depois do boot.
|
|
787
|
+
*
|
|
788
|
+
* Quem decide o que se VÊ é o `refrescarItensDaPausa`, a cada `reflectPauseIcons()` — que o `ui/shell`
|
|
789
|
+
* dispara quando a fase vira `pause-menu`, ou seja quando a pausa ABRE. O §5 continua respeitado (a
|
|
790
|
+
* criança nunca vê um item que não acciona) e agora também vê os que passaram a accionar.
|
|
791
|
+
*/
|
|
534
792
|
sp.innerHTML = screenPauseMarkup({
|
|
535
|
-
player: i,
|
|
536
|
-
|
|
793
|
+
player: i,
|
|
794
|
+
numPlayers: ctx.getNumPlayers(),
|
|
795
|
+
pmButtons: ctx.pmButtons ?? PM_BTNS,
|
|
796
|
+
optionsButtons: ctx.optionsButtons ?? PM_OPTIONS_BTNS,
|
|
797
|
+
dynLabel: dynLabel, t,
|
|
537
798
|
});
|
|
799
|
+
cartoes[i] = sp;
|
|
800
|
+
refrescarItensDaPausa(); // o §5 vale já na montagem, e não só na primeira abertura
|
|
538
801
|
sp.addEventListener('click', (e) => {
|
|
539
802
|
const target = e.target;
|
|
540
803
|
const b = target && target.closest('.pm-btn');
|
|
541
804
|
if (b) {
|
|
542
|
-
|
|
805
|
+
setPauseActor(i);
|
|
543
806
|
const act = b.dataset.act || '';
|
|
544
807
|
// NAVEGAÇÃO DENTRO DO CARTÃO fica aqui, e não na tabela de ações: `options` e `pmback` não fazem nada
|
|
545
808
|
// ao jogo — trocam qual lista está na tela. A tabela vive em `ui/shell`, que não conhece este `sp`.
|
|
@@ -554,7 +817,7 @@ export function initPauseIcons(ctx) {
|
|
|
554
817
|
entrarNaBarra(i);
|
|
555
818
|
return;
|
|
556
819
|
}
|
|
557
|
-
const acts =
|
|
820
|
+
const acts = getPauseActs();
|
|
558
821
|
const fn = acts[act];
|
|
559
822
|
if (fn)
|
|
560
823
|
fn();
|
|
@@ -571,10 +834,10 @@ export function initPauseIcons(ctx) {
|
|
|
571
834
|
* facilidade não existe —, e degradar o que existe para DAR acesso seria o contrário do que ele serve.
|
|
572
835
|
*/
|
|
573
836
|
function buildQuickBar(i) {
|
|
574
|
-
const bar =
|
|
837
|
+
const bar = docDaMontagem().createElement('div');
|
|
575
838
|
bar.className = 'screen-a11y';
|
|
576
839
|
bar.dataset.player = String(i);
|
|
577
|
-
bar.innerHTML = quickBarMarkup();
|
|
840
|
+
bar.innerHTML = quickBarMarkup(iconesDoJogo);
|
|
578
841
|
// FORA DA ORDEM DE TABULAÇÃO durante a partida (ADR-0044, item 7). Dez paradas entre a criança e o jogo
|
|
579
842
|
// seria o preço de deixá-los lá — e o alcance por teclado não se perde: ele passa a ser o modo
|
|
580
843
|
// `accessibility`, que se abre pela pausa. A barra do TÍTULO não é afetada: lá não se está jogando, e o
|
|
@@ -585,7 +848,7 @@ export function initPauseIcons(ctx) {
|
|
|
585
848
|
const ib = e.target?.closest('.pi-btn');
|
|
586
849
|
if (!ib)
|
|
587
850
|
return;
|
|
588
|
-
|
|
851
|
+
setPauseActor(i);
|
|
589
852
|
iconAct(ib.dataset.pi || '', i);
|
|
590
853
|
reflectPauseIcons(); // must run BEFORE reading the label back — that is what makes the caption honest
|
|
591
854
|
if (cap)
|
|
@@ -614,7 +877,14 @@ export function initPauseIcons(ctx) {
|
|
|
614
877
|
}
|
|
615
878
|
return {
|
|
616
879
|
buildScreenPause, buildQuickBar, entrarNaBarra, sairDaBarra, naBarraDe, navBar,
|
|
880
|
+
iconesMontados: iconesDoJogo,
|
|
617
881
|
iconAct, iconLabel, reflectIconBtn, reflectIconsIn, reflectPauseIcons,
|
|
618
|
-
|
|
882
|
+
// ⚠️ O `setCalmMode` PERSISTE TAMBÉM, e sanea. Ele é a outra porta para o mesmo valor — se só o ciclo do
|
|
883
|
+
// ícone gravasse, um nível posto por aqui sobreviveria à sessão e não ao fecho da aba, que é a metade
|
|
884
|
+
// pior do defeito: o ajuste parece ter pegado e some depois.
|
|
885
|
+
applyCalm,
|
|
886
|
+
getCalmMode: () => calmMode,
|
|
887
|
+
setCalmMode: (n) => { calmMode = saneiaNivelTea(n); store.set(store.KEYS.tea, calmMode); },
|
|
888
|
+
iconState,
|
|
619
889
|
};
|
|
620
890
|
}
|
|
@@ -17,6 +17,14 @@ export function linhasDoAviso(a, t) {
|
|
|
17
17
|
for (const c of a.curtos) {
|
|
18
18
|
linhas.push(t('reach.curto', { transporte: nome(c.id), lugares: c.slots }));
|
|
19
19
|
}
|
|
20
|
+
// ⚠️ A TERCEIRA FRASE, e ela existe porque um transporte pode CHEGAR a todas as ações e ainda assim não
|
|
21
|
+
// deixar a criança jogar (ADR-0104). Medido: a plataforma pede nove ações, o controle de tela tem nove
|
|
22
|
+
// lugares, e o cartão nunca aparecia — mas correr, andar e pular ao mesmo tempo são três dedos, e um
|
|
23
|
+
// telemóvel que reconhece dois não os dá. A criança tentava, não acontecia nada, e concluía que o jogo
|
|
24
|
+
// estava partido. Uma frase antes de começar é a resposta honesta; meia tela jogável não é.
|
|
25
|
+
for (const s of a.naoSeguram) {
|
|
26
|
+
linhas.push(t('reach.naoSegura', { transporte: nome(s.id), segura: s.holds, pedidas: a.seguraPedidas }));
|
|
27
|
+
}
|
|
20
28
|
linhas.push(a.serviriamSeLigados.length
|
|
21
29
|
? t('reach.ligue', { saida: a.serviriamSeLigados.map(nome).join(t('reach.ou')) })
|
|
22
30
|
: t('reach.semSaida'));
|
|
@@ -74,10 +74,23 @@ export interface SettingsAudioCtx {
|
|
|
74
74
|
/** Core collision state (NOT owned by this panel — core/collision.ts reads it via isModoCego). The toggle's
|
|
75
75
|
* widget lives inside #audio; the state and its gameplay side effects (setupExtras) stay in game.js. */
|
|
76
76
|
getModoCego: () => boolean;
|
|
77
|
-
setModoCego
|
|
77
|
+
setModoCego?: (on: boolean) => void;
|
|
78
78
|
/** Core collision state (cane hit spacing). Same reasoning as modo cego. */
|
|
79
79
|
getCaneBlockDiv: () => number;
|
|
80
80
|
setCaneBlockDiv: (div: number) => void;
|
|
81
|
+
/**
|
|
82
|
+
* Move a prosa das linhas para o rodapé (`ui/settings-panel` → `fillExplain`). Chamado a CADA render.
|
|
83
|
+
*
|
|
84
|
+
* ⚠️ NÃO É OPCIONAL POR ELEGÂNCIA: `fillExplain` roda uma vez quando o overlay é frontalizado e move o
|
|
85
|
+
* `.opt-hint` de dentro de cada linha para o rodapé. Este painel RECONSTRÓI as linhas, e as linhas novas
|
|
86
|
+
* voltam com a prosa lá dentro — então a explicação aparece duas vezes, no rodapé e sob o rótulo, a
|
|
87
|
+
* partir do primeiro clique. O `CLAUDE.md` §4 regista exatamente isto, e a issue #109 já o consertou
|
|
88
|
+
* uma vez noutros painéis.
|
|
89
|
+
*
|
|
90
|
+
* Opcional na assinatura porque um consumidor pode montar o painel sem a casca (um teste, o segundo
|
|
91
|
+
* consumidor): sem casca não há rodapé para duplicar.
|
|
92
|
+
*/
|
|
93
|
+
fillExplain?: (card: HTMLElement | null) => void;
|
|
81
94
|
}
|
|
82
95
|
export interface SettingsAudioApi {
|
|
83
96
|
/** Re-renders the whole #audio overlay content (master, categories, nav-sound, TTS panel, sinks, modo-cego and
|