@the-inclusionist/engine 7.0.1 → 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
package/dist-pkg/ui/layout.d.ts
CHANGED
|
@@ -1,4 +1,36 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
/**
|
|
3
|
+
* A RÉGUA DO ALVO DE TOQUE, INDEXADA PELA ALTURA DO VIEWPORT (ADR-0095, decisão do Dev).
|
|
4
|
+
*
|
|
5
|
+
* ⚠️ O ALVO DEIXOU DE SER UM NÚMERO E PASSOU A SER UMA FUNÇÃO DA TELA, e o motivo é um custo que o gate de
|
|
6
|
+
* `pausa-44px` já tinha MEDIDO e deixado por resolver: a 640×360 o cartão de pausa não cabe e a lista ROLA.
|
|
7
|
+
* Remedido em 06/09, porque o cartão mudou desde então: 391 px de conteúdo para 349 visíveis, ou seja 42 px
|
|
8
|
+
* de excesso (o comentário antigo dizia 413/353). Um alvo de 44 px que exige rolagem para ser alcançado
|
|
9
|
+
* pode custar mais dedo do que um de 24 px que está à vista.
|
|
10
|
+
*
|
|
11
|
+
* Os três degraus são os do Dev, e os dois extremos são as duas normas — não números de gosto:
|
|
12
|
+
*
|
|
13
|
+
* altura ≥ 720 44 px WCAG 2.2 · 2.5.5 Target Size (Enhanced) — AAA
|
|
14
|
+
* altura ≥ 540 34 px o degrau do meio
|
|
15
|
+
* altura < 540 24 px WCAG 2.2 · 2.5.8 Target Size (Minimum) — AA
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ ISTO É «MARCAR HONESTAMENTE ONDE SÓ DÁ AA», que é regra escrita do projeto — e não uma renúncia
|
|
18
|
+
* silenciosa. O que se perde em 360 está registrado com número no ADR-0095: a 96 px/pol, 24 CSS px são
|
|
19
|
+
* 6,4 mm, abaixo do alvo de polegar de 9,6 mm que o painel de toque deste jogo cita. É por isso que o
|
|
20
|
+
* ESPAÇAMENTO entre alvos passa a ser o que protege o dedo onde o tamanho não pode — a mesma saída que a
|
|
21
|
+
* própria 2.5.8 dá na sua exceção de spacing.
|
|
22
|
+
*/
|
|
23
|
+
export declare const REGUA_DE_ALVO: readonly {
|
|
24
|
+
readonly altura: number;
|
|
25
|
+
readonly alvo: number;
|
|
26
|
+
}[];
|
|
27
|
+
/**
|
|
28
|
+
* O menor alvo de toque aceitável num viewport desta altura, em CSS px.
|
|
29
|
+
*
|
|
30
|
+
* ⚠️ NUNCA DEVOLVE MENOS DE 24: abaixo disso não é «AA num aparelho pequeno», é furar o piso da WCAG. Uma
|
|
31
|
+
* tela mais baixa que 360 não compra o direito de encolher mais — compra o direito de mostrar menos itens.
|
|
32
|
+
*/
|
|
33
|
+
export declare function alvoMinimoDeToque(alturaCss: number): number;
|
|
2
34
|
/** Liga a contagem de jogadores. Chamado uma vez pela raiz, antes do primeiro `layout()`. */
|
|
3
35
|
export declare function initLayout(deps: {
|
|
4
36
|
numJogadores: () => number;
|
package/dist-pkg/ui/layout.js
CHANGED
|
@@ -11,6 +11,45 @@
|
|
|
11
11
|
import { $ } from './dom.js';
|
|
12
12
|
import { screenBaseSize } from '../core/screens.js';
|
|
13
13
|
import { crtScanVars } from '../render/crt.js';
|
|
14
|
+
/**
|
|
15
|
+
* A RÉGUA DO ALVO DE TOQUE, INDEXADA PELA ALTURA DO VIEWPORT (ADR-0095, decisão do Dev).
|
|
16
|
+
*
|
|
17
|
+
* ⚠️ O ALVO DEIXOU DE SER UM NÚMERO E PASSOU A SER UMA FUNÇÃO DA TELA, e o motivo é um custo que o gate de
|
|
18
|
+
* `pausa-44px` já tinha MEDIDO e deixado por resolver: a 640×360 o cartão de pausa não cabe e a lista ROLA.
|
|
19
|
+
* Remedido em 06/09, porque o cartão mudou desde então: 391 px de conteúdo para 349 visíveis, ou seja 42 px
|
|
20
|
+
* de excesso (o comentário antigo dizia 413/353). Um alvo de 44 px que exige rolagem para ser alcançado
|
|
21
|
+
* pode custar mais dedo do que um de 24 px que está à vista.
|
|
22
|
+
*
|
|
23
|
+
* Os três degraus são os do Dev, e os dois extremos são as duas normas — não números de gosto:
|
|
24
|
+
*
|
|
25
|
+
* altura ≥ 720 44 px WCAG 2.2 · 2.5.5 Target Size (Enhanced) — AAA
|
|
26
|
+
* altura ≥ 540 34 px o degrau do meio
|
|
27
|
+
* altura < 540 24 px WCAG 2.2 · 2.5.8 Target Size (Minimum) — AA
|
|
28
|
+
*
|
|
29
|
+
* ⚠️ ISTO É «MARCAR HONESTAMENTE ONDE SÓ DÁ AA», que é regra escrita do projeto — e não uma renúncia
|
|
30
|
+
* silenciosa. O que se perde em 360 está registrado com número no ADR-0095: a 96 px/pol, 24 CSS px são
|
|
31
|
+
* 6,4 mm, abaixo do alvo de polegar de 9,6 mm que o painel de toque deste jogo cita. É por isso que o
|
|
32
|
+
* ESPAÇAMENTO entre alvos passa a ser o que protege o dedo onde o tamanho não pode — a mesma saída que a
|
|
33
|
+
* própria 2.5.8 dá na sua exceção de spacing.
|
|
34
|
+
*/
|
|
35
|
+
export const REGUA_DE_ALVO = Object.freeze([
|
|
36
|
+
{ altura: 720, alvo: 44 },
|
|
37
|
+
{ altura: 540, alvo: 34 },
|
|
38
|
+
{ altura: 0, alvo: 24 },
|
|
39
|
+
]);
|
|
40
|
+
/**
|
|
41
|
+
* O menor alvo de toque aceitável num viewport desta altura, em CSS px.
|
|
42
|
+
*
|
|
43
|
+
* ⚠️ NUNCA DEVOLVE MENOS DE 24: abaixo disso não é «AA num aparelho pequeno», é furar o piso da WCAG. Uma
|
|
44
|
+
* tela mais baixa que 360 não compra o direito de encolher mais — compra o direito de mostrar menos itens.
|
|
45
|
+
*/
|
|
46
|
+
export function alvoMinimoDeToque(alturaCss) {
|
|
47
|
+
const h = Number.isFinite(alturaCss) ? alturaCss : 0;
|
|
48
|
+
for (const degrau of REGUA_DE_ALVO)
|
|
49
|
+
if (h >= degrau.altura)
|
|
50
|
+
return degrau.alvo;
|
|
51
|
+
return 24;
|
|
52
|
+
}
|
|
14
53
|
// A CONTAGEM DE JOGADORES entra por injeção desde 2026-08-26. Era `numPlayers`, um `let` de `core/state`
|
|
15
54
|
// importado como binding vivo — e um `let` de módulo é compartilhado por qualquer segundo jogo que a
|
|
16
55
|
// mesma página carregue (D13 do `demos`, ADR-0038). O que entra aqui é o GETTER da rodada que a raiz
|
|
@@ -18,8 +57,23 @@ import { crtScanVars } from '../render/crt.js';
|
|
|
18
57
|
let _numJogadores = () => 1;
|
|
19
58
|
/** Liga a contagem de jogadores. Chamado uma vez pela raiz, antes do primeiro `layout()`. */
|
|
20
59
|
export function initLayout(deps) { _numJogadores = deps.numJogadores; }
|
|
60
|
+
/**
|
|
61
|
+
* A CASCA QUE DÁ O ESPAÇO DISPONÍVEL — por id OU por classe, e as duas formas valem o mesmo.
|
|
62
|
+
*
|
|
63
|
+
* ⚠️ ERA SÓ `#stage-wrap`, E ISSO DEIXOU A ENGINE SEM ESCALA NO PRÓPRIO HOST. O `app/index.html` do jogo
|
|
64
|
+
* trazia `<div id="stage-wrap">`; quando o cartucho saiu (issue #111) sobrou o `app/quiz.html`, que tem
|
|
65
|
+
* `<div class="stage-wrap">`. A procura por id falhava, `layout()` fazia early-return, e **nada reportava
|
|
66
|
+
* nada**: um `return` silencioso é indistinguível de «não havia o que fazer». O comentário do próprio
|
|
67
|
+
* `quiz.html` dizia «mesmo id que ui/layout escala» — quem o escreveu acreditava que corria.
|
|
68
|
+
*
|
|
69
|
+
* O consumidor não erra ao usar a classe: um documento pode ter várias telas, e um id é único. Aceitar as
|
|
70
|
+
* duas é o que torna a engine consumível por quem não copiou o markup dela.
|
|
71
|
+
*/
|
|
72
|
+
function cascaDoPalco() {
|
|
73
|
+
return $('#stage-wrap') ?? $('.stage-wrap');
|
|
74
|
+
}
|
|
21
75
|
export function layout() {
|
|
22
|
-
const wrap =
|
|
76
|
+
const wrap = cascaDoPalco();
|
|
23
77
|
if (!wrap)
|
|
24
78
|
return;
|
|
25
79
|
wrap.style.paddingRight = '0px';
|
|
@@ -47,6 +101,15 @@ export function layout() {
|
|
|
47
101
|
gr.style.setProperty('--hud-fs', Math.max(9, Math.round(180 * k * 0.052)) + 'px');
|
|
48
102
|
gr.style.setProperty('--ui-fs', (8 * k) + 'px'); // base LÓGICA 8px × k (16px em k=2)
|
|
49
103
|
gr.style.setProperty('--tap', (22 * k) + 'px'); // toque 22px × k (44px em k=2, piso WCAG)
|
|
104
|
+
// ⚠️ O PISO DA RÉGUA (ADR-0095), e ele é OUTRA COISA que o `--tap`. O `--tap` é o tamanho PREFERIDO e
|
|
105
|
+
// cresce com a escala do canvas; `--alvo-min` é o CHÃO por altura de tela — 24 px abaixo de 540, 34 a
|
|
106
|
+
// partir de 540, 44 a partir de 720. Um botão isolado usa o preferido; um item de LISTA, que tem de
|
|
107
|
+
// caber inteiro na tela, usa o chão.
|
|
108
|
+
//
|
|
109
|
+
// ⚠️ A ALTURA É A DO ESPAÇO DISPONÍVEL, e não a da sub-tela de um jogador: o dedo toca o aparelho, não
|
|
110
|
+
// o viewport lógico. Em quatro telas divididas cada uma tem 180 px de alto, e encolher o alvo por causa
|
|
111
|
+
// disso seria ler o número errado — o aparelho continua o mesmo.
|
|
112
|
+
gr.style.setProperty('--alvo-min', alvoMinimoDeToque(availH) + 'px');
|
|
50
113
|
}
|
|
51
114
|
crtScanVars(); // scanlines re-alinham quando a escala k muda
|
|
52
115
|
if (/[?&]debug=true/.test(location.search))
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import type { PlayerView } from '../core/entity.js';
|
|
3
|
+
/** As quatro animações de CENA, como vocabulário fechado. */
|
|
4
|
+
export type MotionSceneKey = 'parallax' | 'decor' | 'items' | 'particles';
|
|
5
|
+
/** As três do PERSONAGEM, que são campos do jogador. */
|
|
6
|
+
export type MotionCharProp = 'rmWalk' | 'rmBreath' | 'rmFlavor';
|
|
7
|
+
/** Uma animação do personagem e a chave i18n do seu rótulo. */
|
|
8
|
+
export interface MotionCharDef {
|
|
9
|
+
readonly prop: MotionCharProp;
|
|
10
|
+
readonly lbl: string;
|
|
11
|
+
}
|
|
12
|
+
/** ⚠️ `PlayerView` e não `Record`: um `Record` aceita qualquer objecto com essas chaves, jogador ou não. */
|
|
13
|
+
export type MotionPlayer = PlayerView<'rmWalk' | 'rmBreath' | 'rmFlavor'>;
|
|
14
|
+
/** Os quatro interruptores de cena. Objecto VIVO — ver a nota sobre partilha por referência no topo. */
|
|
15
|
+
export type MotionSceneFlags = Record<MotionSceneKey, boolean>;
|
|
16
|
+
/** As quatro animações de CENA. São a união inteira, e o compilador prova-o logo abaixo. */
|
|
17
|
+
export declare const CHAVES_DE_CENA: readonly ["parallax", "decor", "items", "particles"];
|
|
18
|
+
/** As três animações do PERSONAGEM, com as chaves que o `RM_LABEL` desta mesma camada já traduz. */
|
|
19
|
+
export declare const ANIMACOES_DO_PERSONAGEM: readonly [{
|
|
20
|
+
readonly prop: "rmWalk";
|
|
21
|
+
readonly lbl: "rm.walk";
|
|
22
|
+
}, {
|
|
23
|
+
readonly prop: "rmBreath";
|
|
24
|
+
readonly lbl: "rm.breath";
|
|
25
|
+
}, {
|
|
26
|
+
readonly prop: "rmFlavor";
|
|
27
|
+
readonly lbl: "rm.flavor";
|
|
28
|
+
}];
|
|
29
|
+
/** Os quatro interruptores no padrão do sistema — `prefers-reduced-motion`, por `defaultReducedMotion()`. */
|
|
30
|
+
export declare function padraoDeCena(): MotionSceneFlags;
|
|
31
|
+
/**
|
|
32
|
+
* O estado guardado, ou o padrão do sistema quando não há nada guardado.
|
|
33
|
+
*
|
|
34
|
+
* ⚠️ CADA CHAVE É LIDA UMA A UMA, e não `{...guardado}`. O que está no armazenamento veio do navegador de uma
|
|
35
|
+
* criança e pode estar truncado ou de uma versão anterior: espalhar o objecto traria chaves a mais e deixaria
|
|
36
|
+
* chaves a menos por preencher, e uma chave em falta lê-se como `undefined` — que é «não reduzido» para quem
|
|
37
|
+
* pediu redução. O laço garante exactamente as quatro.
|
|
38
|
+
*/
|
|
39
|
+
export declare function lerCenaGuardada(): MotionSceneFlags;
|
|
40
|
+
/** Guarda os quatro interruptores. Chamada depois de cada mudança, como o `saveRM` do cartucho fazia. */
|
|
41
|
+
export declare function guardarCena(rm: MotionSceneFlags): void;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// ui/motion-scene — O MOVIMENTO REDUZIDO DE CENA VOLTA PARA A ENGINE (ADR-0106 §4, etapa 1).
|
|
3
|
+
//
|
|
4
|
+
// ========================= O QUE ESTAVA ERRADO, MEDIDO E NÃO SUPOSTO =========================
|
|
5
|
+
// O `PauseIconsCtx` e o `SettingsMotionCtx` pediam ao JOGO quatro coisas — `rm`, `rmKeys`, `rmChar`,
|
|
6
|
+
// `saveRM` — e o ADR-0106 chamou-lhes «do jogo POR ACIDENTE». A medição de 2026-09-08 no `game-platformer`
|
|
7
|
+
// mostra que a palavra é exacta, porque nenhuma das quatro contém uma escolha do jogo:
|
|
8
|
+
//
|
|
9
|
+
// · `RM_KEYS` era `['parallax','decor','items','particles']` escrito à mão — que é a união `MotionSceneKey`
|
|
10
|
+
// INTEIRA, declarada na engine. Não é «quais destes este jogo tem»: são os quatro, sempre;
|
|
11
|
+
// · `RM_CHAR` era as três propriedades de `MotionCharProp` com as chaves i18n `rm.walk`/`rm.breath`/
|
|
12
|
+
// `rm.flavor` — e o `RM_LABEL` da engine já traduz essas mesmas chaves;
|
|
13
|
+
// · `rm` era lido de `store.KEYS.reducedMotion` (chave da engine) com `defaultReducedMotion()` (padrão da
|
|
14
|
+
// engine);
|
|
15
|
+
// · `saveRM` era `store.setJSON` para a mesma chave da engine.
|
|
16
|
+
//
|
|
17
|
+
// ⚠️ ERA UMA CÓPIA, NÃO UMA DECISÃO. E o custo não é elegância: **cinco jogos não têm nada disto**, porque
|
|
18
|
+
// cada cartucho tinha de se lembrar de escrever as quatro linhas. Uma criança que precisa de parar o
|
|
19
|
+
// movimento da cena abre esses cinco e não tem por onde.
|
|
20
|
+
//
|
|
21
|
+
// ⚠️ O QUE CONTINUA A SER DO JOGO, e por natureza: o EFEITO. Quem lê `rm.decor` para congelar as nuvens é o
|
|
22
|
+
// jogo — a engine possui o interruptor, não o que ele apaga. É a mesma divisão do ADR-0106: o valor é da
|
|
23
|
+
// engine, o efeito colateral é do cartucho.
|
|
24
|
+
//
|
|
25
|
+
// ⚠️ E O OBJECTO É PARTILHADO POR REFERÊNCIA, de propósito. No cartucho ele entra em oito módulos
|
|
26
|
+
// (`weather`, `life`, `fx`, …) que leem `rm.decor`/`rm.particles` a cada quadro. Devolver uma cópia faria
|
|
27
|
+
// cada leitor ver um valor congelado no arranque, e o interruptor deixaria de fazer nada — em silêncio.
|
|
28
|
+
// ⚠️ O VOCABULÁRIO DESCEU PARA CÁ, e foi um gate que o mandou. Escrito ao contrário — os tipos no
|
|
29
|
+
// `settings-motion` e este módulo a importá-los —, o `tests/lotes-passo5` reprovou por CICLO: o painel importa
|
|
30
|
+
// os valores daqui e este importava os tipos de lá. O analisador conta o `import type` como aresta, e tem
|
|
31
|
+
// razão para o que mede (ordem de extração). A saída certa não era calar o gate: o vocabulário pertence a quem
|
|
32
|
+
// possui os VALORES, e o painel é consumidor dele. O `settings-motion` mantém os nomes publicados por alias,
|
|
33
|
+
// então nenhuma linha de importação de nenhum consumidor muda.
|
|
34
|
+
import * as store from '../platform/storage.js';
|
|
35
|
+
import { defaultReducedMotion } from '../core/state.js';
|
|
36
|
+
/** As quatro animações de CENA. São a união inteira, e o compilador prova-o logo abaixo. */
|
|
37
|
+
export const CHAVES_DE_CENA = ['parallax', 'decor', 'items', 'particles'];
|
|
38
|
+
const _COBRE_A_UNIAO = true;
|
|
39
|
+
void _COBRE_A_UNIAO;
|
|
40
|
+
/** As três animações do PERSONAGEM, com as chaves que o `RM_LABEL` desta mesma camada já traduz. */
|
|
41
|
+
export const ANIMACOES_DO_PERSONAGEM = Object.freeze([
|
|
42
|
+
{ prop: 'rmWalk', lbl: 'rm.walk' },
|
|
43
|
+
{ prop: 'rmBreath', lbl: 'rm.breath' },
|
|
44
|
+
{ prop: 'rmFlavor', lbl: 'rm.flavor' },
|
|
45
|
+
]);
|
|
46
|
+
const _COBRE_O_PERSONAGEM = true;
|
|
47
|
+
void _COBRE_O_PERSONAGEM;
|
|
48
|
+
/** Os quatro interruptores no padrão do sistema — `prefers-reduced-motion`, por `defaultReducedMotion()`. */
|
|
49
|
+
export function padraoDeCena() {
|
|
50
|
+
const o = {};
|
|
51
|
+
const padrao = defaultReducedMotion();
|
|
52
|
+
for (const k of CHAVES_DE_CENA)
|
|
53
|
+
o[k] = padrao;
|
|
54
|
+
return o;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* O estado guardado, ou o padrão do sistema quando não há nada guardado.
|
|
58
|
+
*
|
|
59
|
+
* ⚠️ CADA CHAVE É LIDA UMA A UMA, e não `{...guardado}`. O que está no armazenamento veio do navegador de uma
|
|
60
|
+
* criança e pode estar truncado ou de uma versão anterior: espalhar o objecto traria chaves a mais e deixaria
|
|
61
|
+
* chaves a menos por preencher, e uma chave em falta lê-se como `undefined` — que é «não reduzido» para quem
|
|
62
|
+
* pediu redução. O laço garante exactamente as quatro.
|
|
63
|
+
*/
|
|
64
|
+
export function lerCenaGuardada() {
|
|
65
|
+
const guardado = store.getJSON(store.KEYS.reducedMotion, null);
|
|
66
|
+
if (!guardado || typeof guardado !== 'object')
|
|
67
|
+
return padraoDeCena();
|
|
68
|
+
const o = {};
|
|
69
|
+
for (const k of CHAVES_DE_CENA)
|
|
70
|
+
o[k] = !!guardado[k];
|
|
71
|
+
return o;
|
|
72
|
+
}
|
|
73
|
+
/** Guarda os quatro interruptores. Chamada depois de cada mudança, como o `saveRM` do cartucho fazia. */
|
|
74
|
+
export function guardarCena(rm) {
|
|
75
|
+
store.setJSON(store.KEYS.reducedMotion, rm);
|
|
76
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
/** As três coisas do `document` de que a casca precisa. Mesma forma de `ui/loop-crash`. */
|
|
3
|
+
export interface PanelShellCtx {
|
|
4
|
+
/** `document.querySelector`, injetado — a casca nunca alcança o `document` global. */
|
|
5
|
+
procurar: (sel: string) => HTMLElement | null;
|
|
6
|
+
/** `document.createElement`, injetado. */
|
|
7
|
+
criar: (tag: string) => HTMLElement;
|
|
8
|
+
}
|
|
9
|
+
export interface PanelShellSpec {
|
|
10
|
+
/** O id do painel: `typo`, `audio`, `visual`… Gera `#X`, `#X-title`, `#X-list`, `#X-reset`, `#X-close`. */
|
|
11
|
+
id: string;
|
|
12
|
+
/** O título, JÁ TRADUZIDO. Vai por `textContent`. */
|
|
13
|
+
titulo: string;
|
|
14
|
+
/** O `aria-label` da lista, já traduzido — o nome do grupo que a criança ouve ao entrar nele. */
|
|
15
|
+
rotuloDaLista: string;
|
|
16
|
+
/** Os rótulos dos dois botões, já traduzidos. */
|
|
17
|
+
rotuloReset: string;
|
|
18
|
+
rotuloFechar: string;
|
|
19
|
+
/**
|
|
20
|
+
* A introdução do painel, já traduzida. Vira o texto de REPOUSO do rodapé, via `data-explain-idle`.
|
|
21
|
+
*
|
|
22
|
+
* ⚠️ É O ÚNICO CAMINHO QUE ESTA CASCA OFERECE PARA UMA INTRODUÇÃO, e é o ponto da issue #62: não há por
|
|
23
|
+
* onde passar um parágrafo de prosa para o topo do cartão. Ausente = o painel não tem introdução, que é
|
|
24
|
+
* uma resposta legítima e não uma omissão.
|
|
25
|
+
*/
|
|
26
|
+
introducao?: string;
|
|
27
|
+
}
|
|
28
|
+
/** O que a casca devolve: o nó e os ids que ela criou, para o painel não os adivinhar. */
|
|
29
|
+
export interface PanelShell {
|
|
30
|
+
overlay: HTMLElement;
|
|
31
|
+
card: HTMLElement;
|
|
32
|
+
lista: HTMLElement;
|
|
33
|
+
reset: HTMLElement;
|
|
34
|
+
fechar: HTMLElement;
|
|
35
|
+
/** Os cinco selectores que este painel passa a garantir. É o contrato, agora dito em vez de descoberto. */
|
|
36
|
+
ids: {
|
|
37
|
+
overlay: string;
|
|
38
|
+
title: string;
|
|
39
|
+
lista: string;
|
|
40
|
+
reset: string;
|
|
41
|
+
fechar: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** Os ids que um painel de `id` ocupa. Exportado porque um gate e um consumidor precisam de os nomear. */
|
|
45
|
+
export declare function idsDaCasca(id: string): PanelShell['ids'];
|
|
46
|
+
/**
|
|
47
|
+
* Monta (ou reaproveita) a casca do painel `spec.id` e devolve as suas partes.
|
|
48
|
+
*
|
|
49
|
+
* IDEMPOTENTE: se já existir um `#id`, ele é reutilizado e o conteúdo do cartão é reconstruído. Um painel que
|
|
50
|
+
* a raiz monte duas vezes não pode acabar com dois véus — e a raiz monta mais do que uma vez, porque a
|
|
51
|
+
* contagem de jogadores muda a grade de telas.
|
|
52
|
+
*/
|
|
53
|
+
export declare function montarCasca(ctx: PanelShellCtx, spec: PanelShellSpec): PanelShell;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// ui/panel-shell — A CASCA DE UM PAINEL DE AJUSTES, construída em vez de exigida.
|
|
3
|
+
//
|
|
4
|
+
// ========================= O ACHADO QUE ISTO CONSERTA =========================
|
|
5
|
+
// O segundo consumidor (a etapa C da issue #63) mediu-o e escreveu-o no achado 6:
|
|
6
|
+
//
|
|
7
|
+
// «O CONTRATO DE MARKUP É INVISÍVEL. O ctx do painel pede `$` e `store`; o que ele REALMENTE exige é que
|
|
8
|
+
// o documento do consumidor contenha `#typo`, `#typo-list`, `#typo-preview`, `#typo-close` e
|
|
9
|
+
// `#typo-reset`. Nada no tipo diz isso — descobre-se por tentativa, e o modo de falhar é o pior
|
|
10
|
+
// possível: o painel abre VAZIO, sem erro.»
|
|
11
|
+
//
|
|
12
|
+
// Cada `ui/settings-*.ts` preenche o INTERIOR do seu painel; o EXTERIOR — o véu, o cartão, o título, o
|
|
13
|
+
// rodapé de ações e o botão de restaurar — vinha do `app/index.html`, que saiu com o cartucho (#111). Desde
|
|
14
|
+
// então a engine EXIGE cinco ids por painel e não os declara em lado nenhum.
|
|
15
|
+
//
|
|
16
|
+
// ⚠️ E A REGRA DE MENU DO `CLAUDE.md` §4 DEPENDIA DE ALGUÉM SE LEMBRAR DELA. A introdução de um painel vai no
|
|
17
|
+
// `data-explain-idle` do cartão — nunca num `<p>` de prosa no topo —, porque um menu que explica item a item
|
|
18
|
+
// obriga a criança a LER TUDO para achar o que procura. A issue #62 mandava editar seis blocos de markup para
|
|
19
|
+
// isso; o markup saiu, e a regra ficou sem alvo. Aqui ela deixa de ser lembrete e passa a ser construção: a
|
|
20
|
+
// casca não tem por onde receber um `<p>` no topo.
|
|
21
|
+
//
|
|
22
|
+
// ========================= A FORMA, QUE FOI MEDIDA E NÃO INVENTADA =========================
|
|
23
|
+
// É a do painel de tipografia do `app/quiz.html`, que é o único que sobrou e o que o segundo consumidor
|
|
24
|
+
// exercitou de facto:
|
|
25
|
+
//
|
|
26
|
+
// <div id="X" class="overlay" hidden>
|
|
27
|
+
// <div class="overlay__card" role="dialog" aria-modal="true" aria-labelledby="X-title" [data-explain-idle]>
|
|
28
|
+
// <h2 id="X-title">…</h2>
|
|
29
|
+
// <div id="X-list" class="ctrl-list" role="group" aria-label="…"></div> ← o interior, do settings-*
|
|
30
|
+
// <div class="overlay__actions">
|
|
31
|
+
// <button id="X-reset">…</button> <button id="X-close">…</button>
|
|
32
|
+
// </div>
|
|
33
|
+
// </div>
|
|
34
|
+
// </div>
|
|
35
|
+
//
|
|
36
|
+
// ⚠️ O RODAPÉ `.opt-explain` NÃO É CRIADO AQUI, e isso é deliberado: `ui/settings-panel.fillExplain` cria-o
|
|
37
|
+
// quando move a primeira dica para lá. Criá-lo vazio aqui daria uma região `aria-live` que anuncia nada, e
|
|
38
|
+
// duas mãos a criar o mesmo nó é como ele acabaria duplicado.
|
|
39
|
+
//
|
|
40
|
+
// Sem `innerHTML`: tudo por `criar` + `textContent`, no molde de `ui/loop-crash` e `ui/focus-trap`. O título
|
|
41
|
+
// e o rótulo da lista vêm do CHAMADOR já resolvidos — este módulo não traduz, para poder ser exercitado sem
|
|
42
|
+
// dicionário.
|
|
43
|
+
//
|
|
44
|
+
// ⚠️ E NÃO IMPORTA NADA. Uma casca que não usa `innerHTML` também não precisa de escapar texto: `textContent`
|
|
45
|
+
// escapa por construção. Chegou a haver aqui um `escaparHtml` importado «por conveniência» — que é como um
|
|
46
|
+
// módulo-folha deixa de o ser, e como um leitor futuro passa a procurar a interpolação que não existe.
|
|
47
|
+
/** Os ids que um painel de `id` ocupa. Exportado porque um gate e um consumidor precisam de os nomear. */
|
|
48
|
+
export function idsDaCasca(id) {
|
|
49
|
+
return { overlay: id, title: `${id}-title`, lista: `${id}-list`, reset: `${id}-reset`, fechar: `${id}-close` };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Monta (ou reaproveita) a casca do painel `spec.id` e devolve as suas partes.
|
|
53
|
+
*
|
|
54
|
+
* IDEMPOTENTE: se já existir um `#id`, ele é reutilizado e o conteúdo do cartão é reconstruído. Um painel que
|
|
55
|
+
* a raiz monte duas vezes não pode acabar com dois véus — e a raiz monta mais do que uma vez, porque a
|
|
56
|
+
* contagem de jogadores muda a grade de telas.
|
|
57
|
+
*/
|
|
58
|
+
export function montarCasca(ctx, spec) {
|
|
59
|
+
const ids = idsDaCasca(spec.id);
|
|
60
|
+
const overlay = ctx.procurar('#' + ids.overlay) ?? ctx.criar('div');
|
|
61
|
+
overlay.id = ids.overlay;
|
|
62
|
+
overlay.className = 'overlay';
|
|
63
|
+
overlay.hidden = true;
|
|
64
|
+
while (overlay.firstChild)
|
|
65
|
+
overlay.removeChild(overlay.firstChild);
|
|
66
|
+
const card = ctx.criar('div');
|
|
67
|
+
card.className = 'overlay__card';
|
|
68
|
+
card.setAttribute('role', 'dialog');
|
|
69
|
+
card.setAttribute('aria-modal', 'true');
|
|
70
|
+
card.setAttribute('aria-labelledby', ids.title);
|
|
71
|
+
// A introdução do painel é o texto de REPOUSO do rodapé (CLAUDE.md §4), nunca um `<p>` no topo.
|
|
72
|
+
if (spec.introducao)
|
|
73
|
+
card.setAttribute('data-explain-idle', spec.introducao);
|
|
74
|
+
const h2 = ctx.criar('h2');
|
|
75
|
+
h2.id = ids.title;
|
|
76
|
+
h2.textContent = spec.titulo;
|
|
77
|
+
card.appendChild(h2);
|
|
78
|
+
const lista = ctx.criar('div');
|
|
79
|
+
lista.id = ids.lista;
|
|
80
|
+
lista.className = 'ctrl-list';
|
|
81
|
+
lista.setAttribute('role', 'group');
|
|
82
|
+
lista.setAttribute('aria-label', spec.rotuloDaLista);
|
|
83
|
+
card.appendChild(lista);
|
|
84
|
+
const acoes = ctx.criar('div');
|
|
85
|
+
acoes.className = 'overlay__actions';
|
|
86
|
+
const reset = botao(ctx, ids.reset, spec.rotuloReset, 'mode-btn');
|
|
87
|
+
const fechar = botao(ctx, ids.fechar, spec.rotuloFechar, 'mode-btn is-on');
|
|
88
|
+
acoes.appendChild(reset);
|
|
89
|
+
acoes.appendChild(fechar);
|
|
90
|
+
card.appendChild(acoes);
|
|
91
|
+
overlay.appendChild(card);
|
|
92
|
+
return { overlay, card, lista, reset, fechar, ids };
|
|
93
|
+
}
|
|
94
|
+
function botao(ctx, id, rotulo, classe) {
|
|
95
|
+
const b = ctx.criar('button');
|
|
96
|
+
b.id = id;
|
|
97
|
+
b.className = classe;
|
|
98
|
+
b.setAttribute('type', 'button');
|
|
99
|
+
// `textContent` e não `innerHTML`: um rótulo traduzido é dado de fora como qualquer outro, e um dicionário
|
|
100
|
+
// de consumidor pode trazer o que quiser dentro dele.
|
|
101
|
+
b.textContent = rotulo;
|
|
102
|
+
return b;
|
|
103
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
2
|
import type { PlayerView } from '../core/entity.js';
|
|
3
3
|
import type { NavKeys } from '../input/edges.js';
|
|
4
|
+
import { type Tema, type Correcao, type VisualState } from '../render/viz-axes.js';
|
|
4
5
|
import type { MotionSceneFlags, MotionSceneKey, MotionCharDef } from './settings-motion.js';
|
|
5
6
|
import type { AudioCatState } from './settings-audio.js';
|
|
6
7
|
/**
|
|
@@ -32,6 +33,11 @@ export declare const PAUSE_ICONS: readonly PauseIcon[];
|
|
|
32
33
|
export declare function pauseIcon(k: string): PauseIcon | undefined;
|
|
33
34
|
/** TEA cycle: 0 = normal · 1 = calmo (reduces) · 2 = silencioso (switches off). Never touches TTS/blind mode. */
|
|
34
35
|
export declare const CALM_NAMES: readonly string[];
|
|
36
|
+
/**
|
|
37
|
+
* O nível TEA guardado, saneado. Fora de 0..2 devolve o padrão — dado do navegador é dado de fora, e um
|
|
38
|
+
* nível inventado escolheria `CALM_NAMES[3]`, que é `undefined`, e o anúncio ao leitor de tela sairia vazio.
|
|
39
|
+
*/
|
|
40
|
+
export declare function saneiaNivelTea(bruto: number): number;
|
|
35
41
|
/** The audio categories `applyCalm` governs. TTS/sonar/guarda/guia stay untouched — a calm player still needs them. */
|
|
36
42
|
export declare const CALM_AUDIO_CATS: readonly string[];
|
|
37
43
|
/** Colour-vision-deficiency cycle, in `player.viz` values. */
|
|
@@ -62,7 +68,7 @@ export declare const CVD_LABELS: Readonly<Record<string, string>>;
|
|
|
62
68
|
* tipar `core/state.players` como `Player[]`: o compilador recusou converter um `Player` — que não tem
|
|
63
69
|
* assinatura de índice — para ela, e essa recusa é a informação.
|
|
64
70
|
*/
|
|
65
|
-
export type PausePlayer = PlayerView<'
|
|
71
|
+
export type PausePlayer = PlayerView<'visual' | 'toggleMove' | 'walkDir' | 'audioSink' | 'rmWalk' | 'rmBreath' | 'rmFlavor'>;
|
|
66
72
|
/** One `.pm-btn` descriptor — the shape of game.js's PM_BTNS (owned by ui/activities-menu). */
|
|
67
73
|
export interface PauseMenuButton {
|
|
68
74
|
act: string;
|
|
@@ -81,10 +87,32 @@ export interface IconStateSnapshot {
|
|
|
81
87
|
librasOn: boolean;
|
|
82
88
|
calmMode: number;
|
|
83
89
|
toggleMove: boolean;
|
|
84
|
-
/**
|
|
85
|
-
|
|
90
|
+
/**
|
|
91
|
+
* O estado visual em DOIS EIXOS (#104).
|
|
92
|
+
*
|
|
93
|
+
* ⚠️ ERA `viz: string`, E O COMENTÁRIO DIZIA: «shared by the contrast and CVD icons (they overwrite each
|
|
94
|
+
* other; **that is by design**)». Não era desenho — era o campo único a impor-se, e a frase é o defeito
|
|
95
|
+
* escrito como se fosse decisão. Os dois ícones sempre ciclaram DENTRO do seu eixo (`nextContrast` e
|
|
96
|
+
* `nextCvd` existem desde sempre, separados); só não tinham onde guardar o resultado sem apagar o vizinho.
|
|
97
|
+
*/
|
|
98
|
+
visual: VisualState;
|
|
86
99
|
/** False disables the blind/TTS icons: those need an audio output nobody else is listening to. */
|
|
87
100
|
privateOutput: boolean;
|
|
101
|
+
/**
|
|
102
|
+
* O aparelho em uso EXIGE a alternância? (ADR-0113 cláusula 3.)
|
|
103
|
+
*
|
|
104
|
+
* ⚠️ Em olhos, rosto, gestos e fala ela é o que faz a entrada funcionar, logo não há escolha a oferecer.
|
|
105
|
+
* O ícone fica desabilitado COM MOTIVO, como o painel — e não pode divergir dele: as duas superfícies
|
|
106
|
+
* escrevem o mesmo valor, e uma que aceitasse o clique enquanto a outra recusa deixaria a criança com
|
|
107
|
+
* dois botões que discordam sobre o mesmo ajuste.
|
|
108
|
+
*
|
|
109
|
+
* 🔴 OPCIONAL PORQUE O GATE DA FORMA ME APANHOU: eu escrevi-o obrigatório, e o
|
|
110
|
+
* `tests/superficie-publica` reprovou com «ENTROU como obrigatório» — que é uma MUDANÇA QUEBRANTE do
|
|
111
|
+
* pacote, porque quem constrói este snapshot passa a ter de o preencher. Foi para isto que o gate da
|
|
112
|
+
* FORMA (`2f2582f`) foi escrito, e é a primeira vez que ele apanha um campo A ENTRAR e não a sair.
|
|
113
|
+
* Ausente significa «ninguém me disse», que degrada para «não exijo» — o comportamento de hoje.
|
|
114
|
+
*/
|
|
115
|
+
alternanciaExigida?: boolean;
|
|
88
116
|
}
|
|
89
117
|
/** A player has private output when nobody else is on the same sink. Single screen ⇒ always private.
|
|
90
118
|
* Pure form of game.js's hasPrivateOutput (see the report: it had no other caller left). */
|
|
@@ -135,9 +163,61 @@ export declare const ICON_STATE_CLASSES: readonly string[];
|
|
|
135
163
|
* reflected. `ic.n` is an i18n key, so it must be resolved here too — the markup is rendered once at build
|
|
136
164
|
* time and would otherwise ship the raw key to a screen reader. */
|
|
137
165
|
export declare function iconBtnMarkup(ic: PauseIcon): string;
|
|
166
|
+
/**
|
|
167
|
+
* OS ÍCONES QUE ESTE JOGO CONSEGUE MESMO ACCIONAR (ADR-0106 §5).
|
|
168
|
+
*
|
|
169
|
+
* ⚠️ NENHUMA ETAPA PODE ENTREGAR BOTÃO MORTO, e o registo diz porquê com todas as letras: «uma barra que
|
|
170
|
+
* oferece a uma criança um caminho e depois o recusa é pior do que uma barra que ela vê que não está lá,
|
|
171
|
+
* porque a primeira ensina-lhe que o caminho não é para ela».
|
|
172
|
+
*
|
|
173
|
+
* ⚠️ E O CONTRASTE E A COR SÃO O CASO REAL, medido em 2026-09-08 e diferente dos outros seis campos
|
|
174
|
+
* «acidentais»: eles não são estado que um cartucho calhou de guardar — precisam de um `render/viz-setters`,
|
|
175
|
+
* cujo contexto tem **34 campos** do grafo de render de UM jogo (`parallaxLayers`, `decoSprites`,
|
|
176
|
+
* `getPowerups`, `rebuildCoins`, `worldSprite`…). O `createGame` não monta isso, e um quiz não tem nada
|
|
177
|
+
* disso para montar. Então aqui a engine não pode oferecer um padrão — o que ela pode é **não fingir**.
|
|
178
|
+
*
|
|
179
|
+
* ⚠️ Isto NÃO é o mesmo que `soon`. `soon` é «ainda não construímos»; isto é «este jogo não tem por onde», e
|
|
180
|
+
* um botão que anuncia «em breve» diria a coisa errada.
|
|
181
|
+
*/
|
|
182
|
+
export interface EscritoresVisuais {
|
|
183
|
+
/** Há quem escreva o TEMA (o alto contraste)? Sem ele, o ícone `contrast` não é montado. */
|
|
184
|
+
readonly tema: boolean;
|
|
185
|
+
/** Há quem escreva a CORREÇÃO de cor? Sem ela, o ícone `cvd` não é montado. */
|
|
186
|
+
readonly correcao: boolean;
|
|
187
|
+
}
|
|
188
|
+
export declare function iconesQueAccionam(escritores: EscritoresVisuais): readonly PauseIcon[];
|
|
189
|
+
/**
|
|
190
|
+
* OS TRÊS ITENS QUE A ENGINE ACCIONA SOZINHA, e que por isso nunca dependem do `getPauseActs` de um jogo.
|
|
191
|
+
*
|
|
192
|
+
* 📏 Lidos do despacho, e não decididos aqui: `options`/`pmback` trocam qual lista está no cartão e
|
|
193
|
+
* `acessibilidade` leva o cursor à barra rápida — os três são tratados neste módulo e voltam antes de a
|
|
194
|
+
* tabela do jogo ser consultada.
|
|
195
|
+
*/
|
|
196
|
+
export declare const ITENS_DA_ENGINE: ReadonlySet<string>;
|
|
197
|
+
/**
|
|
198
|
+
* OS ITENS DO MENU QUE ESTE JOGO CONSEGUE MESMO ACCIONAR (ADR-0106 §5).
|
|
199
|
+
*
|
|
200
|
+
* ⚠️ HOJE UM ITEM SEM ACÇÃO É UM BOTÃO MORTO, E EM SILÊNCIO. O despacho faz `const fn = acts[act]; if (fn)
|
|
201
|
+
* fn();` — quem carrega num item que o jogo não implementou não recebe erro, não recebe anúncio, não recebe
|
|
202
|
+
* nada. Para quem vê, parece que o clique falhou; para quem navega por leitor de tela, o menu leu-lhe um
|
|
203
|
+
* item que não existe. É exactamente o que o §5 chama de pior do que a ausência: «uma barra que oferece um
|
|
204
|
+
* caminho e depois o recusa ensina-lhe que o caminho não é para ela».
|
|
205
|
+
*/
|
|
206
|
+
export declare function itensQueAccionam(botoes: readonly PauseMenuButton[], acts: Record<string, (() => void) | undefined>): readonly PauseMenuButton[];
|
|
207
|
+
/**
|
|
208
|
+
* A LISTA RAIZ, com uma regra a mais: `options` é uma PORTA, e uma porta para uma sala vazia também é um
|
|
209
|
+
* botão morto.
|
|
210
|
+
*
|
|
211
|
+
* ⚠️ Esta é a parte que um filtro item-a-item não apanha. Se todos os painéis de ajuste forem filtrados —
|
|
212
|
+
* um jogo que não monta nenhum —, o item `options` sobrevive (a engine acciona-o) e abre uma lista sem nada.
|
|
213
|
+
* A criança atravessa uma porta e fica presa num submenu vazio, cuja única saída é o `pmback` que também
|
|
214
|
+
* sumiu com ele.
|
|
215
|
+
*/
|
|
216
|
+
export declare function raizQueAcciona(raiz: readonly PauseMenuButton[], opcoes: readonly PauseMenuButton[], acts: Record<string, (() => void) | undefined>): readonly PauseMenuButton[];
|
|
138
217
|
/** The whole icon bar. Used by the pause screen AND by the splash `#title-icons` (which built the same string
|
|
139
|
-
* by hand in game.js — that duplication dies with this export).
|
|
140
|
-
|
|
218
|
+
* by hand in game.js — that duplication dies with this export).
|
|
219
|
+
* O parâmetro é ADITIVO e o padrão é a lista inteira: quem já chamava sem argumentos não muda de resultado. */
|
|
220
|
+
export declare function iconsMarkup(icones?: readonly PauseIcon[]): string;
|
|
141
221
|
/** One `.pm-btn`. Dynamic labels (`letra`/`nivel`) are rendered eagerly and carry NO `data-i18n`, so
|
|
142
222
|
* i18n.applyDom() cannot overwrite them. */
|
|
143
223
|
/**
|
|
@@ -194,7 +274,7 @@ export declare function mostrarSubmenuDaPausa(sp: HTMLElement, sub: PauseSub): H
|
|
|
194
274
|
* A LEGENDA VIAJA JUNTO. Ela é a dica que substitui, para quem não vê, o `title` que só o mouse revela;
|
|
195
275
|
* deixá-la no cartão tornaria a barra do HUD muda.
|
|
196
276
|
*/
|
|
197
|
-
export declare function quickBarMarkup(): string;
|
|
277
|
+
export declare function quickBarMarkup(icones?: readonly PauseIcon[]): string;
|
|
198
278
|
/**
|
|
199
279
|
* O QUE UMA INTENÇÃO SIGNIFICA DENTRO DO MODO `accessibility` (ADR-0044, item 7).
|
|
200
280
|
*
|
|
@@ -228,6 +308,21 @@ export interface ScreenPauseMarkupOpts {
|
|
|
228
308
|
/** The full innerHTML of a `.screen-pause`. Pure — every input is a parameter. */
|
|
229
309
|
export declare function screenPauseMarkup(o: ScreenPauseMarkupOpts): string;
|
|
230
310
|
export interface PauseIconsCtx {
|
|
311
|
+
/**
|
|
312
|
+
* O DOCUMENTO onde a pausa e a barra são CONSTRUÍDAS. Ausente, vale o global.
|
|
313
|
+
*
|
|
314
|
+
* ⚠️ ISTO É O ACHADO 15 OUTRA VEZ, e o `boot/create-game` já o descreve no próprio cabeçalho: «`initI18n()`
|
|
315
|
+
* chamava `applyDom(document)`, o GLOBAL, por baixo de quem a chamasse. Num navegador dá no mesmo e por
|
|
316
|
+
* isso sobreviveu; num teste de lógica pura é a diferença entre bootar e não bootar, e num futuro com dois
|
|
317
|
+
* documentos (uma engine em iframe, um editor ao lado do jogo) seria a diferença entre traduzir o documento
|
|
318
|
+
* certo e o outro.»
|
|
319
|
+
*
|
|
320
|
+
* 📏 Medido em 2026-09-08: as duas metades que constroem DOM (`buildScreenPause`, `buildQuickBar`) faziam
|
|
321
|
+
* `document.createElement` no global. Enquanto a raiz de composição de cada jogo era um `main.ts` a correr
|
|
322
|
+
* num navegador, dava no mesmo — e foi por isso que sobreviveu. Deixa de dar assim que a ENGINE monta,
|
|
323
|
+
* porque o `createGame` recebe o documento por `host.doc` e pode estar a montar noutro.
|
|
324
|
+
*/
|
|
325
|
+
doc?: Document;
|
|
231
326
|
/** Quantos jogadores/telas. Estado de RODADA (ADR-0038): vem da instância que a raiz possui.
|
|
232
327
|
* Era `numPlayers`, um `let` de `core/state` importado como binding vivo — e um `let` de módulo
|
|
233
328
|
* é compartilhado por qualquer segundo jogo que a mesma página carregue (D13 do `demos`). */
|
|
@@ -248,9 +343,9 @@ export interface PauseIconsCtx {
|
|
|
248
343
|
*/
|
|
249
344
|
getA11yBars: () => readonly HTMLElement[];
|
|
250
345
|
/** PM_OPTIONS_BTNS — o submenu de opções. Mesma dona, mesmo motivo: ninguém tem duas cópias de uma lista. */
|
|
251
|
-
optionsButtons
|
|
346
|
+
optionsButtons?: readonly PauseMenuButton[];
|
|
252
347
|
/** PM_BTNS — the `.pm-btn` list. Owned by ui/activities-menu; injected, never copied. */
|
|
253
|
-
pmButtons
|
|
348
|
+
pmButtons?: readonly PauseMenuButton[];
|
|
254
349
|
/** QL_NAME — literacy-level names, for the (dormant) `nivel` button. Same owner as pmButtons. */
|
|
255
350
|
/**
|
|
256
351
|
* O RÓTULO de um botão dinâmico, pronto — ou `null` quando aquele botão não tem um (item 19).
|
|
@@ -259,16 +354,15 @@ export interface PauseIconsCtx {
|
|
|
259
354
|
* frase. Um menu de pausa da ENGINE não sabe o que é nível de alfabetização, nem em que idioma dizê-lo.
|
|
260
355
|
* Função e não valor, porque o rótulo muda em execução — de nível E de idioma.
|
|
261
356
|
*/
|
|
262
|
-
|
|
357
|
+
/** O rótulo dinâmico, pronto — ou `null`. Ausente: nenhum botão deste jogo tem rótulo dinâmico. */
|
|
358
|
+
dynLabel?: (b: PauseMenuButton) => string | null;
|
|
263
359
|
/** The `.pm-btn` action table. LAZY: `pauseActs` is a `const` declared far below the init site in game.js. */
|
|
264
|
-
getPauseActs
|
|
360
|
+
getPauseActs?: () => Record<string, (() => void) | undefined>;
|
|
265
361
|
/** Records which player opened the menu. `pauseActor` itself stays in game.js — the gamepad, the keyboard
|
|
266
362
|
* router, openHelp() and openOptions() all read it there. */
|
|
267
|
-
setPauseActor
|
|
268
|
-
/** The live `vpPause` array (game.js rebuilds it on every buildGameHud). Getter, not the array. */
|
|
269
|
-
getPauseScreens: () => readonly Element[];
|
|
363
|
+
setPauseActor?: (i: number) => void;
|
|
270
364
|
getModoCego: () => boolean;
|
|
271
|
-
setModoCego
|
|
365
|
+
setModoCego?: (on: boolean) => void;
|
|
272
366
|
/**
|
|
273
367
|
* O mixer por categoria. NULO até `initAudioMixer()` — `platform/audio` o declara
|
|
274
368
|
* `Record<string, CatState> | null` porque o import dele é PURO (não lê localStorage), e quem inicializa
|
|
@@ -286,18 +380,38 @@ export interface PauseIconsCtx {
|
|
|
286
380
|
reflectTtsPanelEnabled: boolean;
|
|
287
381
|
isLibrasOn: () => boolean;
|
|
288
382
|
toggleLibras: () => void;
|
|
289
|
-
rm
|
|
290
|
-
rmKeys
|
|
291
|
-
rmChar
|
|
292
|
-
saveRM
|
|
293
|
-
setToggleMove
|
|
294
|
-
|
|
383
|
+
rm?: MotionSceneFlags;
|
|
384
|
+
rmKeys?: readonly MotionSceneKey[];
|
|
385
|
+
rmChar?: readonly MotionCharDef[];
|
|
386
|
+
saveRM?: () => void;
|
|
387
|
+
setToggleMove?: (i: number, on: boolean) => void;
|
|
388
|
+
/**
|
|
389
|
+
* QUAL APARELHO ESTE JOGADOR ESTÁ A USAR (ADR-0113).
|
|
390
|
+
*
|
|
391
|
+
* 📌 O ícone `altmove` desta barra é a OUTRA superfície que escreve a alternância — a mesma razão pela
|
|
392
|
+
* qual o escritor voltou para a engine (ADR-0106 §4). Sem este campo, ele escreveria só a chave antiga
|
|
393
|
+
* enquanto o painel escreve as duas, e as duas superfícies divergiriam em silêncio.
|
|
394
|
+
*/
|
|
395
|
+
transporteEmUso?: (jogador: number) => string;
|
|
396
|
+
setPlayerViz?: (i: number, mode: string) => void;
|
|
397
|
+
/** Os escritores POR EIXO (#104): mexer no tema não apaga a correção, e vice-versa. */
|
|
398
|
+
setTemaDoJogador?: (i: number, tema: Tema) => void;
|
|
399
|
+
setCorrecaoDoJogador?: (i: number, correcao: Correcao) => void;
|
|
295
400
|
}
|
|
296
401
|
export interface PauseIconsApi {
|
|
297
402
|
/** Builds one `.screen-pause` (hidden), wired for click + hover/focus caption. Caller appends it. */
|
|
298
403
|
buildScreenPause: (i: number) => HTMLElement;
|
|
299
404
|
/** Monta a BARRA RÁPIDA (`.screen-a11y`) da tela `i`, já fiada. Chamada por ui/hud.ts, uma por tela. */
|
|
300
405
|
buildQuickBar: (i: number) => HTMLElement;
|
|
406
|
+
/**
|
|
407
|
+
* OS ÍCONES QUE ESTA INSTÂNCIA MONTA — já filtrados pelo §5 do ADR-0106.
|
|
408
|
+
*
|
|
409
|
+
* ⚠️ Existe para que quem monta a barra do TÍTULO não repita o filtro. A barra do título não pode usar
|
|
410
|
+
* `buildQuickBar` (ele põe `tabIndex = -1`, e o próprio comentário lá diz porquê: no título não se está a
|
|
411
|
+
* jogar), então ela chama `iconsMarkup` directamente — e sem este acessor teria de recalcular quais ícones
|
|
412
|
+
* accionam, que é uma segunda cópia da mesma decisão.
|
|
413
|
+
*/
|
|
414
|
+
iconesMontados: readonly PauseIcon[];
|
|
301
415
|
/** ENTRA no modo `accessibility` da tela `i` — é o que o item `acessibilidade` da pausa faz. */
|
|
302
416
|
entrarNaBarra: (i: number) => void;
|
|
303
417
|
/** SAI do modo e devolve o direcional ao personagem. */
|