@the-inclusionist/engine 7.0.0 → 8.0.0-rc.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/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/pressstart-400.woff2 +0 -0
- package/app/public/vendor/fonts.css +43 -2
- package/dist-pkg/boot/create-game.d.ts +76 -2
- package/dist-pkg/boot/create-game.js +271 -13
- package/dist-pkg/core/constants.d.ts +0 -28
- package/dist-pkg/core/constants.js +34 -17
- package/dist-pkg/core/contract.d.ts +39 -0
- package/dist-pkg/core/contract.js +36 -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 +33 -0
- package/dist-pkg/i18n/es.js +33 -0
- package/dist-pkg/i18n/pt.js +46 -0
- package/dist-pkg/input/default-bindings.d.ts +40 -0
- package/dist-pkg/input/default-bindings.js +143 -9
- package/dist-pkg/input/gamepad.d.ts +18 -11
- package/dist-pkg/input/gamepad.js +75 -9
- 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 +10 -0
- package/dist-pkg/input/keyboard.js +41 -11
- package/dist-pkg/input/keydown.d.ts +27 -2
- package/dist-pkg/input/keydown.js +20 -6
- 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/origem-sintetica.d.ts +44 -0
- package/dist-pkg/input/origem-sintetica.js +60 -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 +11 -2
- package/dist-pkg/input/touch-bindings.js +9 -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/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 +90 -0
- package/dist-pkg/platform/voice-plan.js +137 -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 +47 -1
- package/dist-pkg/ui/fonts.js +47 -9
- 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 +134 -20
- package/dist-pkg/ui/pause-icons.js +307 -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 +81 -3
- package/dist-pkg/ui/settings-motor.js +118 -9
- package/dist-pkg/ui/settings-typo.d.ts +32 -2
- package/dist-pkg/ui/settings-typo.js +64 -18
- 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 +18 -0
- package/docs/LICENSES.md +12 -0
- package/package.json +24 -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,56 @@ 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
|
+
return PAUSE_ICONS.filter((ic) => (ic.k === 'contrast' ? escritores.tema
|
|
234
|
+
: ic.k === 'cvd' ? escritores.correcao
|
|
235
|
+
: true));
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* OS TRÊS ITENS QUE A ENGINE ACCIONA SOZINHA, e que por isso nunca dependem do `getPauseActs` de um jogo.
|
|
239
|
+
*
|
|
240
|
+
* 📏 Lidos do despacho, e não decididos aqui: `options`/`pmback` trocam qual lista está no cartão e
|
|
241
|
+
* `acessibilidade` leva o cursor à barra rápida — os três são tratados neste módulo e voltam antes de a
|
|
242
|
+
* tabela do jogo ser consultada.
|
|
243
|
+
*/
|
|
244
|
+
export const ITENS_DA_ENGINE = new Set(['options', 'pmback', 'acessibilidade']);
|
|
245
|
+
/**
|
|
246
|
+
* OS ITENS DO MENU QUE ESTE JOGO CONSEGUE MESMO ACCIONAR (ADR-0106 §5).
|
|
247
|
+
*
|
|
248
|
+
* ⚠️ HOJE UM ITEM SEM ACÇÃO É UM BOTÃO MORTO, E EM SILÊNCIO. O despacho faz `const fn = acts[act]; if (fn)
|
|
249
|
+
* fn();` — quem carrega num item que o jogo não implementou não recebe erro, não recebe anúncio, não recebe
|
|
250
|
+
* nada. Para quem vê, parece que o clique falhou; para quem navega por leitor de tela, o menu leu-lhe um
|
|
251
|
+
* item que não existe. É exactamente o que o §5 chama de pior do que a ausência: «uma barra que oferece um
|
|
252
|
+
* caminho e depois o recusa ensina-lhe que o caminho não é para ela».
|
|
253
|
+
*/
|
|
254
|
+
export function itensQueAccionam(botoes, acts) {
|
|
255
|
+
return botoes.filter((b) => ITENS_DA_ENGINE.has(b.act) || typeof acts[b.act] === 'function');
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* A LISTA RAIZ, com uma regra a mais: `options` é uma PORTA, e uma porta para uma sala vazia também é um
|
|
259
|
+
* botão morto.
|
|
260
|
+
*
|
|
261
|
+
* ⚠️ Esta é a parte que um filtro item-a-item não apanha. Se todos os painéis de ajuste forem filtrados —
|
|
262
|
+
* um jogo que não monta nenhum —, o item `options` sobrevive (a engine acciona-o) e abre uma lista sem nada.
|
|
263
|
+
* A criança atravessa uma porta e fica presa num submenu vazio, cuja única saída é o `pmback` que também
|
|
264
|
+
* sumiu com ele.
|
|
265
|
+
*/
|
|
266
|
+
export function raizQueAcciona(raiz, opcoes, acts) {
|
|
267
|
+
const opcoesVivas = itensQueAccionam(opcoes, acts).filter((b) => b.act !== 'pmback');
|
|
268
|
+
const viva = itensQueAccionam(raiz, acts);
|
|
269
|
+
return opcoesVivas.length > 0 ? viva : viva.filter((b) => b.act !== 'options');
|
|
270
|
+
}
|
|
204
271
|
/** 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
|
-
|
|
272
|
+
* by hand in game.js — that duplication dies with this export).
|
|
273
|
+
* O parâmetro é ADITIVO e o padrão é a lista inteira: quem já chamava sem argumentos não muda de resultado. */
|
|
274
|
+
export function iconsMarkup(icones = PAUSE_ICONS) {
|
|
275
|
+
return icones.map(iconBtnMarkup).join('');
|
|
276
|
+
}
|
|
207
277
|
/** One `.pm-btn`. Dynamic labels (`letra`/`nivel`) are rendered eagerly and carry NO `data-i18n`, so
|
|
208
278
|
* i18n.applyDom() cannot overwrite them. */
|
|
209
279
|
/**
|
|
@@ -268,8 +338,8 @@ export function mostrarSubmenuDaPausa(sp, sub) {
|
|
|
268
338
|
* A LEGENDA VIAJA JUNTO. Ela é a dica que substitui, para quem não vê, o `title` que só o mouse revela;
|
|
269
339
|
* deixá-la no cartão tornaria a barra do HUD muda.
|
|
270
340
|
*/
|
|
271
|
-
export function quickBarMarkup() {
|
|
272
|
-
return '<div class="pause-icons" role="group" aria-label="' + t('pause.iconBarAria') + '">' + iconsMarkup() +
|
|
341
|
+
export function quickBarMarkup(icones = PAUSE_ICONS) {
|
|
342
|
+
return '<div class="pause-icons" role="group" aria-label="' + t('pause.iconBarAria') + '">' + iconsMarkup(icones) +
|
|
273
343
|
'</div><p class="pause-icons-cap" aria-live="polite"></p>';
|
|
274
344
|
}
|
|
275
345
|
export function acaoNaBarra(k, temStart) {
|
|
@@ -298,11 +368,92 @@ export function screenPauseMarkup(o) {
|
|
|
298
368
|
// DOM-facing shell
|
|
299
369
|
// ---------------------------------------------------------------------------------------------
|
|
300
370
|
export function initPauseIcons(ctx) {
|
|
301
|
-
//
|
|
302
|
-
// — verbatim: game.js never wrote it to storage
|
|
303
|
-
|
|
371
|
+
// ⚠️ O NÍVEL TEA PASSOU A PERSISTIR EM 2026-09-07 (issue #61). Este comentário dizia «deliberately NOT
|
|
372
|
+
// persisted — verbatim: game.js never wrote it to storage», e o **verbatim** é o que o desqualificava como
|
|
373
|
+
// decisão: foi PRESERVADO na extração do monólito, não escolhido. O ADR-0028 diz que todo menu persiste, e
|
|
374
|
+
// este é um controlo de menu que vive na barra rápida.
|
|
375
|
+
//
|
|
376
|
+
// O custo de não persistir era da criança que mais precisa dele: quem usa o modo SILENCIOSO voltava a
|
|
377
|
+
// pô-lo a cada sessão — e é para quem o barulho inesperado custa mais. Um ajuste que se esquece não é um
|
|
378
|
+
// ajuste, é uma tarefa diária.
|
|
379
|
+
//
|
|
380
|
+
// ⚠️ LÊ NO INIT, NUNCA NO IMPORT: a regra vale para todo o projeto e aqui tem custo concreto — um teste que
|
|
381
|
+
// importasse este módulo passaria a depender do `localStorage` do ambiente, e um nível herdado de outro
|
|
382
|
+
// caso é uma falha que aparece longe da causa.
|
|
383
|
+
let calmMode = lerNivelTea();
|
|
304
384
|
const P = () => ctx.getPlayers();
|
|
385
|
+
/*
|
|
386
|
+
* ⚠️ A ALTERNÂNCIA DE MARCHA PASSOU A TER PADRÃO DA ENGINE (ADR-0106 §4, etapa 1b). Quem injecta continua a
|
|
387
|
+
* mandar; quem não injecta deixa de ficar sem ela — que era o caso dos cinco jogos sem barra.
|
|
388
|
+
*
|
|
389
|
+
* O `store` vem do import directo deste módulo e não do `ctx`, porque é assim que este ficheiro já persiste
|
|
390
|
+
* o resto: uma chave injectada é um campo que um consumidor pode omitir, e omiti-la faria escrever num nome
|
|
391
|
+
* torto.
|
|
392
|
+
*/
|
|
393
|
+
const setToggleMove = ctx.setToggleMove
|
|
394
|
+
?? ((i, on) => definirAlternanciaDeMarcha({
|
|
395
|
+
players: P(), store, srSay: ctx.srSay, getNumPlayers: ctx.getNumPlayers,
|
|
396
|
+
// ⚠️ ATRAVESSA, e não se resolve aqui: o ícone e o painel têm de escrever a MESMA coisa. Resolver
|
|
397
|
+
// o aparelho num deles e não no outro é como duas superfícies da mesma engine passam a discordar.
|
|
398
|
+
transporteEmUso: ctx.transporteEmUso,
|
|
399
|
+
}, i, on));
|
|
400
|
+
/*
|
|
401
|
+
* ⚠️ O MODO CEGO IDEM, e aqui o padrão é literalmente o que o `core/state` já decidiu que um setter faz:
|
|
402
|
+
* «grava, persiste, avisa» — e nada mais. Os efeitos de jogo (refazer os extras do nível) são REACÇÃO, e
|
|
403
|
+
* quem reage assina `on('modoCego', …)`. O anúncio não se perde para quem não injecta: este ícone já diz
|
|
404
|
+
* `sr.icon.blindOn`/`Off` por si, logo abaixo.
|
|
405
|
+
*/
|
|
406
|
+
const setModoCego = ctx.setModoCego ?? setModoCegoValue;
|
|
407
|
+
/** O documento onde se constroi. Resolvido a cada uso, e por globalThis — em node o identificador
|
|
408
|
+
* document nem existe, e um ?? sobre ele lançaria ReferenceError em vez de cair no padrão. */
|
|
409
|
+
const docDaMontagem = () => ctx.doc ?? globalThis.document;
|
|
410
|
+
/*
|
|
411
|
+
* ⚠️ O CONTRASTE E A COR SÓ APARECEM SE HOUVER QUEM OS ESCREVA (ADR-0106 §5).
|
|
412
|
+
*
|
|
413
|
+
* 📏 Medido em 2026-09-08, e é o que separa este caso dos outros seis campos «acidentais»: eles eram estado
|
|
414
|
+
* que um cartucho calhou de guardar, e a engine pôde reclamá-los. Estes precisam de um
|
|
415
|
+
* `render/viz-setters`, cujo contexto pede **34 campos** do grafo de render de UM jogo — `parallaxLayers`,
|
|
416
|
+
* `decoSprites`, `getPowerups`, `rebuildCoins`, `worldSprite`. O `createGame` não monta isso, e um quiz não
|
|
417
|
+
* tem nada disso para montar. A engine não pode dar um padrão aqui; o que ela pode é não FINGIR.
|
|
418
|
+
*
|
|
419
|
+
* Decidido uma vez, no arranque, e não a cada montagem de barra: o conjunto de escritores de um consumidor
|
|
420
|
+
* não muda a meio de uma partida, e recalcular por tela faria as telas discordarem entre si.
|
|
421
|
+
*/
|
|
422
|
+
const iconesDoJogo = iconesQueAccionam({
|
|
423
|
+
tema: Boolean(ctx.setTemaDoJogador),
|
|
424
|
+
correcao: Boolean(ctx.setCorrecaoDoJogador),
|
|
425
|
+
});
|
|
426
|
+
/*
|
|
427
|
+
* ⚠️ RESOLVIDOS UMA VEZ, no arranque, pela mesma razão do `settings-motion`: o `rm` é mutado in-place e
|
|
428
|
+
* partilhado por REFERÊNCIA com quem desenha a cena, e resolvê-lo a cada uso criaria um objecto novo por
|
|
429
|
+
* chamada — o interruptor deixaria de alcançar o desenho, sem erro nenhum.
|
|
430
|
+
*
|
|
431
|
+
* 📌 O `getPauseActs` fica FUNÇÃO e não valor, porque a laziness dele é a razão de ele existir assim: no
|
|
432
|
+
* cartucho a tabela é um `const` declarado ~1200 linhas abaixo, e lê-la aqui cairia na zona morta temporal.
|
|
433
|
+
*/
|
|
434
|
+
const rm = ctx.rm ?? lerCenaGuardada();
|
|
435
|
+
const rmKeys = ctx.rmKeys ?? CHAVES_DE_CENA;
|
|
436
|
+
const rmChar = ctx.rmChar ?? ANIMACOES_DO_PERSONAGEM;
|
|
437
|
+
const saveRM = ctx.saveRM ?? (() => guardarCena(rm));
|
|
438
|
+
/*
|
|
439
|
+
* ⚠️ ESTES TRÊS SÃO LIDOS A CADA CHAMADA, e não resolvidos uma vez como o `rm` acima. A diferença é
|
|
440
|
+
* deliberada e um teste apanhou-me a errá-la: congelar `ctx.getPauseActs` no arranque partiu um caso que
|
|
441
|
+
* TROCA a tabela depois do `init` — e trocar depois é legítimo, porque a laziness deste campo existe
|
|
442
|
+
* precisamente por a tabela chegar tarde. Um padrão não pode custar a ligação tardia que o campo tem.
|
|
443
|
+
*
|
|
444
|
+
* O `rm` é o contrário e por isso fica congelado: ali o que importa é a IDENTIDADE do objecto, partilhada
|
|
445
|
+
* por referência com quem desenha a cena.
|
|
446
|
+
*
|
|
447
|
+
* A anotação de tipo é necessária: sem ela o padrão `() => ({})` infere `{}`, que não aceita indexação por
|
|
448
|
+
* string, e o compilador passaria a recusar `acts[act]` — o despacho inteiro.
|
|
449
|
+
*/
|
|
450
|
+
const dynLabel = (b) => (ctx.dynLabel ? ctx.dynLabel(b) : null);
|
|
451
|
+
const getPauseActs = () => (ctx.getPauseActs ? ctx.getPauseActs() : {});
|
|
452
|
+
const setPauseActor = (i) => { if (ctx.setPauseActor)
|
|
453
|
+
ctx.setPauseActor(i); };
|
|
305
454
|
function hasPrivateOutput(i) { return hasPrivateOutputIn(P(), ctx.getNumPlayers(), i); }
|
|
455
|
+
/** A recusa da alternância para este jogador agora, ou `null`. Recalculada: o aparelho em uso muda. */
|
|
456
|
+
function recusaAgora(i) { return ctx.transporteEmUso ? recusaDaAlternancia(ctx.transporteEmUso(i)) : null; }
|
|
306
457
|
function iconState(i) {
|
|
307
458
|
const p = P()[i] || {};
|
|
308
459
|
const cat = ctx.getAudioCat();
|
|
@@ -312,19 +463,25 @@ export function initPauseIcons(ctx) {
|
|
|
312
463
|
librasOn: ctx.isLibrasOn(),
|
|
313
464
|
calmMode,
|
|
314
465
|
toggleMove: !!p.toggleMove,
|
|
315
|
-
|
|
466
|
+
// ⚠️ `DEFAULTS.viz` E NÃO `''` (issue #61). A cadeia vazia funcionava por ACIDENTE: não casa
|
|
467
|
+
// `hc-direto` nem `fix-*`, então os dois ícones ficavam apagados pelo motivo certo por engano. O padrão
|
|
468
|
+
// passou a ter nome em `core/state`, e `render/viz-modes` já declarava esse modo com `kind:'normal'` —
|
|
469
|
+
// 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
|
|
470
|
+
// aqui, porque ela lê `DEFAULTS` e mais nada.
|
|
471
|
+
visual: p.visual ?? PADRAO,
|
|
316
472
|
privateOutput: hasPrivateOutput(i),
|
|
473
|
+
alternanciaExigida: recusaAgora(i) !== null,
|
|
317
474
|
};
|
|
318
475
|
}
|
|
319
476
|
// --- TEA ---------------------------------------------------------------------------------
|
|
320
477
|
function applyCalm() {
|
|
321
478
|
const plan = calmMotionPlan(calmMode);
|
|
322
|
-
for (const k of
|
|
323
|
-
|
|
324
|
-
|
|
479
|
+
for (const k of rmKeys)
|
|
480
|
+
rm[k] = plan.sceneReduced;
|
|
481
|
+
saveRM();
|
|
325
482
|
// "silencioso" freezes the character too. Iterates the WHOLE players array (not just numPlayers) — verbatim.
|
|
326
483
|
for (const p of P())
|
|
327
|
-
for (const c of
|
|
484
|
+
for (const c of rmChar)
|
|
328
485
|
p[c.prop] = plan.charFrozen;
|
|
329
486
|
const cat = ctx.getAudioCat();
|
|
330
487
|
for (const k of CALM_AUDIO_CATS) {
|
|
@@ -341,7 +498,7 @@ export function initPauseIcons(ctx) {
|
|
|
341
498
|
// --- icon actions (the dispatcher, as a table) ---------------------------------------------
|
|
342
499
|
const ICON_ACTS = {
|
|
343
500
|
blind: () => {
|
|
344
|
-
|
|
501
|
+
setModoCego(!ctx.getModoCego());
|
|
345
502
|
ctx.srSay(t(ctx.getModoCego() ? 'sr.icon.blindOn' : 'sr.icon.blindOff'));
|
|
346
503
|
},
|
|
347
504
|
tts: () => {
|
|
@@ -360,22 +517,35 @@ export function initPauseIcons(ctx) {
|
|
|
360
517
|
},
|
|
361
518
|
tea: () => {
|
|
362
519
|
calmMode = nextCalmMode(calmMode);
|
|
520
|
+
store.set(store.KEYS.tea, calmMode); // ADR-0028: todo menu persiste. Ver a nota no `let` acima.
|
|
363
521
|
applyCalm();
|
|
364
522
|
ctx.srSay(t('sr.icon.tea', { v: t(CALM_NAMES[calmMode]) }));
|
|
365
523
|
},
|
|
366
524
|
altmove: (i) => {
|
|
367
525
|
// verbatim: `players[i].toggleMove` with no `||{}` guard (unlike contrast/cvd below).
|
|
368
|
-
|
|
526
|
+
setToggleMove(i, !P()[i].toggleMove);
|
|
369
527
|
},
|
|
528
|
+
// ⚠️ OS DOIS ÍCONES DEIXARAM DE SE APAGAR UM AO OUTRO (#104). Eles SEMPRE ciclaram dentro do seu eixo —
|
|
529
|
+
// `nextContrast` e `nextCvd` existem separados desde sempre —, mas escreviam os dois no mesmo campo, e
|
|
530
|
+
// por isso mexer num zerava o outro. O snapshot dizia isso como se fosse desenho: «they overwrite each
|
|
531
|
+
// other; that is by design». Agora cada um escreve no seu eixo e o outro fica onde estava.
|
|
532
|
+
// ⚠️ AS DUAS GUARDAS NÃO SÃO CINTO E SUSPENSÓRIOS. Sem escritor, o ícone nem sequer é montado
|
|
533
|
+
// (`iconesDoJogo`), então este ramo não deveria ser alcançável pela barra — mas `iconAct` é EXPORTADO e
|
|
534
|
+
// qualquer consumidor pode chamá-lo por chave. Sem a guarda, essa chamada rebentaria; com ela, não faz
|
|
535
|
+
// nada e não anuncia — que é o mesmo que dizer a verdade: este jogo não tem por onde.
|
|
370
536
|
contrast: (i) => {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
537
|
+
if (!ctx.setTemaDoJogador)
|
|
538
|
+
return;
|
|
539
|
+
const v = proximoTema((P()[i] || {}).visual ?? PADRAO);
|
|
540
|
+
ctx.setTemaDoJogador(i, v.tema);
|
|
541
|
+
ctx.srSay(t('sr.visual.contrast', { v: t(CURTO_DO_TEMA[v.tema]) }));
|
|
374
542
|
},
|
|
375
543
|
cvd: (i) => {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
544
|
+
if (!ctx.setCorrecaoDoJogador)
|
|
545
|
+
return;
|
|
546
|
+
const v = proximaCorrecao((P()[i] || {}).visual ?? PADRAO);
|
|
547
|
+
ctx.setCorrecaoDoJogador(i, v.correcao);
|
|
548
|
+
ctx.srSay(t('sr.icon.cvd', { v: t(CURTO_DA_CORRECAO[v.correcao]) }));
|
|
379
549
|
},
|
|
380
550
|
};
|
|
381
551
|
function iconAct(k, i) {
|
|
@@ -388,6 +558,17 @@ export function initPauseIcons(ctx) {
|
|
|
388
558
|
ctx.srAlert(t('sr.icon.needsPrivateOutput'));
|
|
389
559
|
return;
|
|
390
560
|
}
|
|
561
|
+
// ⚠️ A MESMA RECUSA DO PAINEL, na mesma forma que as duas acima: DIZER e voltar. Aceitar o clique e
|
|
562
|
+
// ignorá-lo é a outra metade do que o ADR-0076 proíbe, e aqui há um agravante — este ícone e o
|
|
563
|
+
// `#opt-altmove` escrevem o MESMO valor, logo um a aceitar enquanto o outro recusa daria à criança dois
|
|
564
|
+
// botões que discordam sobre o mesmo ajuste.
|
|
565
|
+
if (k === 'altmove') {
|
|
566
|
+
const recusa = recusaAgora(i);
|
|
567
|
+
if (recusa) {
|
|
568
|
+
ctx.srAlert(t(recusa.chave));
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
}
|
|
391
572
|
const act = ICON_ACTS[k];
|
|
392
573
|
if (act)
|
|
393
574
|
act(i);
|
|
@@ -405,10 +586,27 @@ export function initPauseIcons(ctx) {
|
|
|
405
586
|
b.classList.add(v.cvd);
|
|
406
587
|
b.classList.toggle('pi-on', v.on);
|
|
407
588
|
b.classList.toggle('pi-dis', v.dis);
|
|
589
|
+
/*
|
|
590
|
+
* 🔴 A ISSUE #128, E ELA É DE UMA LINHA: `pi-dis` é CLASSE CSS. A criança que enxerga vê o ícone
|
|
591
|
+
* apagado; a que navega por leitor de tela não recebe nada — o botão anuncia-se accionável e não
|
|
592
|
+
* responde. `aria-disabled` espelha o mesmo facto para quem ouve.
|
|
593
|
+
*
|
|
594
|
+
* ⚠️ E `aria-disabled` e NÃO `disabled`: o segundo tira o botão da ordem de tabulação, e quem navega
|
|
595
|
+
* por teclado deixaria de o alcançar — logo deixaria de poder ouvir POR QUE ele não responde. É a
|
|
596
|
+
* mesma escolha que o `#opt-altmove` faz no painel, pela mesma razão.
|
|
597
|
+
*/
|
|
598
|
+
if (v.dis)
|
|
599
|
+
b.setAttribute('aria-disabled', 'true');
|
|
600
|
+
else
|
|
601
|
+
b.removeAttribute('aria-disabled');
|
|
408
602
|
b.setAttribute('aria-pressed', String(v.active));
|
|
409
|
-
// `soon`
|
|
410
|
-
|
|
411
|
-
|
|
603
|
+
// ⚠️ TODO ÍCONE RECEBE RÓTULO, `soon` INCLUÍDO — e o guarda que aqui estava dizia por que não: «`soon`
|
|
604
|
+
// buttons keep the label the markup gave them (same string)». A segunda metade continua certa (não há
|
|
605
|
+
// estado a reportar), mas «mesma string» era verdade só enquanto a marcação e o reflexo corressem no
|
|
606
|
+
// MESMO IDIOMA — e desde que a engine passou a montar a barra (ADR-0106 etapa 2) deixam de correr.
|
|
607
|
+
// O `initI18n` carrega en/es de forma assíncrona; a marcação nasce em pt e só o reflexo a corrige.
|
|
608
|
+
// 📌 Medido num navegador: cinco ícones em inglês e três ainda em «(em construção)», na mesma barra.
|
|
609
|
+
b.setAttribute('aria-label', computeIconLabel(k, st));
|
|
412
610
|
}
|
|
413
611
|
function reflectIconsIn(root, i) {
|
|
414
612
|
if (!root)
|
|
@@ -419,8 +617,48 @@ export function initPauseIcons(ctx) {
|
|
|
419
617
|
* 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
618
|
* ADR-0044 os ícones vivem no HUD, e um cartão de pausa não contém `.pi-btn` nenhum.
|
|
421
619
|
*/
|
|
620
|
+
/**
|
|
621
|
+
* OS CARTÕES QUE ESTA INSTÂNCIA CONSTRUIU. Privado, e é a resposta a um problema que eu próprio criei.
|
|
622
|
+
*
|
|
623
|
+
* ⚠️ O `PauseIconsCtx` tinha um `getPauseScreens` e eu removi-o hoje, com razão: tinha ZERO leitores. Agora
|
|
624
|
+
* este módulo precisa de alcançar os cartões — e a saída certa NÃO é repor o campo. Quem os construiu foi
|
|
625
|
+
* ele; guardar o que construiu não pede nada a consumidor nenhum, e um campo de ctx é mais uma coisa que
|
|
626
|
+
* cada um dos 300 jogos teria de se lembrar de passar.
|
|
627
|
+
*/
|
|
628
|
+
const cartoes = [];
|
|
629
|
+
/**
|
|
630
|
+
* ESCONDE OS ITENS QUE ESTE JOGO NÃO CONSEGUE ACCIONAR — recalculado, e não decidido no arranque.
|
|
631
|
+
*
|
|
632
|
+
* ⚠️ ESTA FUNÇÃO EXISTE POR UM DEFEITO DE TEMPO. O cartão era FILTRADO no `buildScreenPause`, e o
|
|
633
|
+
* `getPauseActs` é um getter precisamente porque a tabela CHEGA TARDE — «`pauseActs` is a `const` declared
|
|
634
|
+
* far below the init site», diz o próprio campo. Um consumidor que siga esse padrão documentado montava um
|
|
635
|
+
* cartão sem os itens cuja acção só existiu depois do boot, e nunca mais os recuperava.
|
|
636
|
+
*
|
|
637
|
+
* 📌 A avaliação passou para o ÚLTIMO instante possível: o `ui/shell` chama `reflectPauseIcons()` quando a
|
|
638
|
+
* fase vira `pause-menu`, ou seja quando a pausa ABRE. O §5 continua respeitado — a criança nunca vê um
|
|
639
|
+
* item que não acciona —, e agora também vê os que passaram a accionar.
|
|
640
|
+
*/
|
|
641
|
+
function refrescarItensDaPausa() {
|
|
642
|
+
const acts = getPauseActs();
|
|
643
|
+
const raiz = ctx.pmButtons ?? PM_BTNS;
|
|
644
|
+
const opcoes = ctx.optionsButtons ?? PM_OPTIONS_BTNS;
|
|
645
|
+
const vivos = new Set([
|
|
646
|
+
...raizQueAcciona(raiz, opcoes, acts).map((b) => b.act),
|
|
647
|
+
...itensQueAccionam(opcoes, acts).map((b) => b.act),
|
|
648
|
+
]);
|
|
649
|
+
// ⚠️ `filter(Boolean)`: os cartões são indexados por JOGADOR, e montar só a tela 2 deixa um buraco no
|
|
650
|
+
// índice 0. Um `for…of` sobre array esparso entrega `undefined`, e foi o que rebentou à primeira.
|
|
651
|
+
for (const cartao of cartoes.filter(Boolean)) {
|
|
652
|
+
for (const btn of cartao.querySelectorAll('.pm-btn')) {
|
|
653
|
+
// ⚠️ `hidden` e não `remove()`: reaparecer tem de ser possível, porque a tabela pode crescer outra vez
|
|
654
|
+
// (um jogo que só liga «sair» depois da primeira fase). Remover seria decidir uma vez de novo.
|
|
655
|
+
btn.hidden = !vivos.has(btn.dataset.act ?? '');
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
422
659
|
function reflectPauseIcons() {
|
|
423
660
|
ctx.getA11yBars().forEach((bar, i) => reflectIconsIn(bar, i));
|
|
661
|
+
refrescarItensDaPausa();
|
|
424
662
|
}
|
|
425
663
|
// --- the pause screen ----------------------------------------------------------------------
|
|
426
664
|
/**
|
|
@@ -472,7 +710,7 @@ export function initPauseIcons(ctx) {
|
|
|
472
710
|
if (!bar || !primeiro)
|
|
473
711
|
return;
|
|
474
712
|
naBarra.add(i);
|
|
475
|
-
const acts =
|
|
713
|
+
const acts = getPauseActs();
|
|
476
714
|
if (acts.resume)
|
|
477
715
|
acts.resume(); // volta à tela normal: o modo é para usar DURANTE a partida
|
|
478
716
|
ctx.srSay(t('sr.a11y.barEnter'));
|
|
@@ -516,7 +754,7 @@ export function initPauseIcons(ctx) {
|
|
|
516
754
|
const cur = iconeSelecionado(bar);
|
|
517
755
|
const idx = cur ? icones.indexOf(cur) : 0;
|
|
518
756
|
if (acao === 'ativar') {
|
|
519
|
-
|
|
757
|
+
setPauseActor(i);
|
|
520
758
|
if (cur)
|
|
521
759
|
cur.click();
|
|
522
760
|
return;
|
|
@@ -527,19 +765,36 @@ export function initPauseIcons(ctx) {
|
|
|
527
765
|
}
|
|
528
766
|
}
|
|
529
767
|
function buildScreenPause(i) {
|
|
530
|
-
const sp =
|
|
768
|
+
const sp = docDaMontagem().createElement('div');
|
|
531
769
|
sp.className = 'screen-pause';
|
|
532
770
|
sp.hidden = true;
|
|
533
771
|
sp.dataset.player = String(i);
|
|
772
|
+
/*
|
|
773
|
+
* ⚠️ MONTA A LISTA INTEIRA E ESCONDE DEPOIS — e a versão anterior desta linha FILTRAVA aqui, o que estava
|
|
774
|
+
* errado por uma razão de TEMPO. O comentário que estava neste sítio dizia «montar é o primeiro instante
|
|
775
|
+
* em que a resposta existe»; não é. O `getPauseActs` é um getter precisamente porque a tabela chega
|
|
776
|
+
* TARDE — «`pauseActs` is a `const` declared far below the init site», diz o próprio campo —, e o
|
|
777
|
+
* `createGame` monta durante o próprio `createGame(...)`. Filtrar aqui apagava para sempre todo item cuja
|
|
778
|
+
* acção só passou a existir depois do boot.
|
|
779
|
+
*
|
|
780
|
+
* Quem decide o que se VÊ é o `refrescarItensDaPausa`, a cada `reflectPauseIcons()` — que o `ui/shell`
|
|
781
|
+
* dispara quando a fase vira `pause-menu`, ou seja quando a pausa ABRE. O §5 continua respeitado (a
|
|
782
|
+
* criança nunca vê um item que não acciona) e agora também vê os que passaram a accionar.
|
|
783
|
+
*/
|
|
534
784
|
sp.innerHTML = screenPauseMarkup({
|
|
535
|
-
player: i,
|
|
536
|
-
|
|
785
|
+
player: i,
|
|
786
|
+
numPlayers: ctx.getNumPlayers(),
|
|
787
|
+
pmButtons: ctx.pmButtons ?? PM_BTNS,
|
|
788
|
+
optionsButtons: ctx.optionsButtons ?? PM_OPTIONS_BTNS,
|
|
789
|
+
dynLabel: dynLabel, t,
|
|
537
790
|
});
|
|
791
|
+
cartoes[i] = sp;
|
|
792
|
+
refrescarItensDaPausa(); // o §5 vale já na montagem, e não só na primeira abertura
|
|
538
793
|
sp.addEventListener('click', (e) => {
|
|
539
794
|
const target = e.target;
|
|
540
795
|
const b = target && target.closest('.pm-btn');
|
|
541
796
|
if (b) {
|
|
542
|
-
|
|
797
|
+
setPauseActor(i);
|
|
543
798
|
const act = b.dataset.act || '';
|
|
544
799
|
// NAVEGAÇÃO DENTRO DO CARTÃO fica aqui, e não na tabela de ações: `options` e `pmback` não fazem nada
|
|
545
800
|
// ao jogo — trocam qual lista está na tela. A tabela vive em `ui/shell`, que não conhece este `sp`.
|
|
@@ -554,7 +809,7 @@ export function initPauseIcons(ctx) {
|
|
|
554
809
|
entrarNaBarra(i);
|
|
555
810
|
return;
|
|
556
811
|
}
|
|
557
|
-
const acts =
|
|
812
|
+
const acts = getPauseActs();
|
|
558
813
|
const fn = acts[act];
|
|
559
814
|
if (fn)
|
|
560
815
|
fn();
|
|
@@ -571,10 +826,10 @@ export function initPauseIcons(ctx) {
|
|
|
571
826
|
* facilidade não existe —, e degradar o que existe para DAR acesso seria o contrário do que ele serve.
|
|
572
827
|
*/
|
|
573
828
|
function buildQuickBar(i) {
|
|
574
|
-
const bar =
|
|
829
|
+
const bar = docDaMontagem().createElement('div');
|
|
575
830
|
bar.className = 'screen-a11y';
|
|
576
831
|
bar.dataset.player = String(i);
|
|
577
|
-
bar.innerHTML = quickBarMarkup();
|
|
832
|
+
bar.innerHTML = quickBarMarkup(iconesDoJogo);
|
|
578
833
|
// FORA DA ORDEM DE TABULAÇÃO durante a partida (ADR-0044, item 7). Dez paradas entre a criança e o jogo
|
|
579
834
|
// seria o preço de deixá-los lá — e o alcance por teclado não se perde: ele passa a ser o modo
|
|
580
835
|
// `accessibility`, que se abre pela pausa. A barra do TÍTULO não é afetada: lá não se está jogando, e o
|
|
@@ -585,7 +840,7 @@ export function initPauseIcons(ctx) {
|
|
|
585
840
|
const ib = e.target?.closest('.pi-btn');
|
|
586
841
|
if (!ib)
|
|
587
842
|
return;
|
|
588
|
-
|
|
843
|
+
setPauseActor(i);
|
|
589
844
|
iconAct(ib.dataset.pi || '', i);
|
|
590
845
|
reflectPauseIcons(); // must run BEFORE reading the label back — that is what makes the caption honest
|
|
591
846
|
if (cap)
|
|
@@ -614,7 +869,14 @@ export function initPauseIcons(ctx) {
|
|
|
614
869
|
}
|
|
615
870
|
return {
|
|
616
871
|
buildScreenPause, buildQuickBar, entrarNaBarra, sairDaBarra, naBarraDe, navBar,
|
|
872
|
+
iconesMontados: iconesDoJogo,
|
|
617
873
|
iconAct, iconLabel, reflectIconBtn, reflectIconsIn, reflectPauseIcons,
|
|
618
|
-
|
|
874
|
+
// ⚠️ O `setCalmMode` PERSISTE TAMBÉM, e sanea. Ele é a outra porta para o mesmo valor — se só o ciclo do
|
|
875
|
+
// ícone gravasse, um nível posto por aqui sobreviveria à sessão e não ao fecho da aba, que é a metade
|
|
876
|
+
// pior do defeito: o ajuste parece ter pegado e some depois.
|
|
877
|
+
applyCalm,
|
|
878
|
+
getCalmMode: () => calmMode,
|
|
879
|
+
setCalmMode: (n) => { calmMode = saneiaNivelTea(n); store.set(store.KEYS.tea, calmMode); },
|
|
880
|
+
iconState,
|
|
619
881
|
};
|
|
620
882
|
}
|
|
@@ -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
|