@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
|
@@ -19,11 +19,14 @@
|
|
|
19
19
|
// módulo rodar no project `node` sem importar PIXI. Mesmo precedente de render/scene-sky e game/traffic.
|
|
20
20
|
// SEM I/O no import: initVizSetters(ctx) só fecha closures, não chama nada.
|
|
21
21
|
import { VIZ_MODES, VIZ_BY_KEY, VIZ_FILTER, simulatesDisability } from './viz-modes.js';
|
|
22
|
+
import { migrarVisual, filtroChave, chaveDeTextura, chaveLegada, ehSimulacao, ehBaixaVisao, ehCego, temAltoContraste, PADRAO, } from './viz-axes.js';
|
|
22
23
|
import { t } from '../core/i18n.js'; // VIZ_MODES guarda CHAVE i18n desde o item 14; quem exibe resolve
|
|
24
|
+
import { eixosHtml, escolhaDoBotao, ROTULO_DO_TEMA, ROTULO_DA_CORRECAO, } from '../ui/visual-axes-panel.js';
|
|
25
|
+
import { recusaDaSimulacao } from '../ui/simulation-refusal.js';
|
|
23
26
|
import { DIRECT_CFG, worldTexFor, spriteTexFor, clearWorldTexCache, clearSpriteTexCache } from './high-contrast.js';
|
|
24
27
|
import { pupTexFor, resetPupTexCache } from './textures.js';
|
|
25
28
|
import { lqFilter } from './lq-filter.js';
|
|
26
|
-
import { setVizModeValue } from '../core/state.js';
|
|
29
|
+
import { setVizModeValue, setModoCegoValue } from '../core/state.js';
|
|
27
30
|
import * as store from '../platform/storage.js';
|
|
28
31
|
/* ===================== PURO (sem PIXI, sem DOM) — o que rende teste de verdade ===================== */
|
|
29
32
|
/**
|
|
@@ -38,6 +41,29 @@ export function alcanceDoModo(mode) {
|
|
|
38
41
|
export function resolveViz(key) {
|
|
39
42
|
return VIZ_BY_KEY[key] || VIZ_BY_KEY.normal;
|
|
40
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* O ESTADO VISUAL GUARDADO deste jogador, de qualquer das duas formas (issue #104).
|
|
46
|
+
*
|
|
47
|
+
* ⚠️ ESTA FUNÇÃO É A CAIXA «um ajuste salvo antes da divisão restaura o mesmo estado visível» da definition
|
|
48
|
+
* of done, e a ordem das duas leituras é a decisão inteira:
|
|
49
|
+
*
|
|
50
|
+
* 1. a chave NOVA (`visualP`), que é a única que sabe dizer dois eixos;
|
|
51
|
+
* 2. na falta dela, a chave VELHA (`vizP`), que guarda a string única — e é aqui que mora o ajuste de toda
|
|
52
|
+
* criança que já jogou este jogo antes de hoje;
|
|
53
|
+
* 3. na falta das duas, o padrão.
|
|
54
|
+
*
|
|
55
|
+
* ⚠️ O RECUO NÃO É ZELO: sem ele, a primeira sessão depois da actualização apagaria o modo visual que ela
|
|
56
|
+
* escolheu — e quem escolheu `fix-deuter` ou `hc-direto-7` escolheu-o porque enxerga assim. É a diferença
|
|
57
|
+
* entre migrar e recomeçar.
|
|
58
|
+
*
|
|
59
|
+
* `migrarVisual` aceita as duas formas e é idempotente, então isto pode correr quantas vezes for preciso.
|
|
60
|
+
*/
|
|
61
|
+
export function lerVisualGuardado(i) {
|
|
62
|
+
const novo = store.getJSON(store.KEYS.visualP(i), null);
|
|
63
|
+
if (novo !== null)
|
|
64
|
+
return migrarVisual(novo);
|
|
65
|
+
return migrarVisual(store.get(store.KEYS.vizP(i), null));
|
|
66
|
+
}
|
|
41
67
|
/** É um dos três níveis de Renderização Direta (alto contraste)? Mesmo teste do original: `!!DIRECT_CFG[mode]`. */
|
|
42
68
|
export function isDirectMode(mode) { return !!DIRECT_CFG[mode]; }
|
|
43
69
|
/** Filtro CSS da canvas no SOLO: simulação/correção de visão + realce L→Q, compostos e sem vazios. */
|
|
@@ -116,20 +142,58 @@ export function initVizSetters(ctx) {
|
|
|
116
142
|
}
|
|
117
143
|
function applyVpFilters() {
|
|
118
144
|
const spr = ctx.getVpSpr(), players = ctx.getPlayers();
|
|
145
|
+
// ⚠️ `filtroChave` E NÃO `p.viz` (#104). É a outra metade do par que a etapa 0 mediu: o TEMA vai pela
|
|
146
|
+
// textura (`playerVizTex`/`applySharedTextures`) e a CORREÇÃO ou SIMULAÇÃO vai pelo FILTRO — e é
|
|
147
|
+
// exactamente por serem dois caminhos que os dois eixos podem coexistir. `null` (sem filtro) entra como
|
|
148
|
+
// `'normal'`, que é a chave que o `pixiFilterFor` já usa para «nenhum», e ele cacheia por ela.
|
|
119
149
|
for (let i = 0; i < ctx.getNumPlayers(); i++) {
|
|
150
|
+
const p = players[i];
|
|
120
151
|
if (spr[i])
|
|
121
|
-
spr[i].filters = ctx.pixiFilterFor(
|
|
152
|
+
spr[i].filters = ctx.pixiFilterFor((p.visual && filtroChave(p.visual)) || 'normal');
|
|
122
153
|
}
|
|
123
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* O ESCRITOR DE VERDADE desde a etapa 4 da #104: recebe o ESTADO, não uma chave.
|
|
157
|
+
*
|
|
158
|
+
* ⚠️ E É AQUI QUE O ESPELHO MUDA DE SIGNIFICADO, o que estava previsto e escrito. Enquanto os controles
|
|
159
|
+
* escreviam um valor de cada vez, `viz` conseguia ser «o modo equivalente». Com dois eixos não há chave
|
|
160
|
+
* única que descreva `hc7 + fix-deuter`, então o espelho passa a ser exactamente o que ele ainda consegue
|
|
161
|
+
* ser com honestidade: **a CHAVE DE TEXTURA** — `simulação ?? tema ?? normal`, o que mais muda o que se vê.
|
|
162
|
+
*
|
|
163
|
+
* Isso não é uma perda escondida: é a mesma chave que o `setVizModeValue` já escreve, e o invariante do
|
|
164
|
+
* gate passou a afirmá-la nesses termos. Um leitor antigo continua a ver algo verdadeiro sobre a tela; o
|
|
165
|
+
* que ele deixa de ver é a metade que a forma antiga nunca soube dizer.
|
|
166
|
+
*/
|
|
167
|
+
function setVisualDoJogador(i, v) {
|
|
168
|
+
const p = ctx.getPlayers()[i];
|
|
169
|
+
p.visual = v;
|
|
170
|
+
p.viz = chaveLegada(v);
|
|
171
|
+
store.set(store.KEYS.vizP(i), p.viz); // legada: um leitor antigo faria `VIZ_BY_KEY[v]` e recusaria JSON
|
|
172
|
+
store.setJSON(store.KEYS.visualP(i), v); // nova: os dois eixos, que a chave velha não sabe dizer
|
|
173
|
+
aplicarVisualDoJogador(i, v);
|
|
174
|
+
}
|
|
175
|
+
/** Muda SÓ o tema deste jogador. A correção e a simulação ficam onde estavam — é o ponto da #104. */
|
|
176
|
+
function setTemaDoJogador(i, tema) {
|
|
177
|
+
const p = ctx.getPlayers()[i];
|
|
178
|
+
setVisualDoJogador(i, { ...(p.visual ?? PADRAO), tema });
|
|
179
|
+
}
|
|
180
|
+
/** Muda SÓ a correção de cor deste jogador. O tema e a simulação ficam onde estavam. */
|
|
181
|
+
function setCorrecaoDoJogador(i, correcao) {
|
|
182
|
+
const p = ctx.getPlayers()[i];
|
|
183
|
+
setVisualDoJogador(i, { ...(p.visual ?? PADRAO), correcao });
|
|
184
|
+
}
|
|
185
|
+
/** A API antiga, por chave única. Continua a valer: um jogo que escolhe um modo inteiro passa por aqui. */
|
|
124
186
|
function setPlayerViz(i, mode) {
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
187
|
+
setVisualDoJogador(i, migrarVisual(resolveViz(mode).key));
|
|
188
|
+
}
|
|
189
|
+
/** Os efeitos colaterais de ter mudado o visual de um jogador. Separados do ESCREVER de propósito: os dois
|
|
190
|
+
* escritores por eixo e o antigo por chave partilham-nos, e uma cópia a mais seria uma cópia a divergir. */
|
|
191
|
+
function aplicarVisualDoJogador(i, v) {
|
|
128
192
|
ctx.invalidateSharedViz();
|
|
129
|
-
if (
|
|
130
|
-
ctx.setModoCego(true); // empatia cegueira total liga o modo cego (áudio) por padrão
|
|
193
|
+
if (ehCego(v))
|
|
194
|
+
(ctx.setModoCego ?? setModoCegoValue)(true); // empatia cegueira total liga o modo cego (áudio) por padrão
|
|
131
195
|
if (ctx.getNumPlayers() <= 1 && i === 0) {
|
|
132
|
-
applyVizGlobal(
|
|
196
|
+
applyVizGlobal(v);
|
|
133
197
|
}
|
|
134
198
|
else {
|
|
135
199
|
applyVpFilters();
|
|
@@ -139,36 +203,60 @@ export function initVizSetters(ctx) {
|
|
|
139
203
|
ctx.renderVisualPanel();
|
|
140
204
|
ctx.renderEmpathyPanel();
|
|
141
205
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
206
|
+
/**
|
|
207
|
+
* ⚠️ AQUI É QUE OS DOIS EIXOS PASSAM A COEXISTIR (#104, ADR-0076), e a função nem cresceu — ela SEPAROU-SE.
|
|
208
|
+
*
|
|
209
|
+
* Enquanto o estado era uma chave só, cada linha abaixo perguntava a mesma coisa (`mode`, `m.kind`) e a
|
|
210
|
+
* resposta tinha de ser uma. Com dois eixos, as mesmas linhas dividem-se em três grupos que nunca se
|
|
211
|
+
* tocaram — e essa é a razão de a composição já ser mecanicamente possível, como o `viz-axes` regista:
|
|
212
|
+
*
|
|
213
|
+
* · TEMA (contraste) → textura e classe de DOM. `chaveDeTextura` e `temAltoContraste`.
|
|
214
|
+
* · CORREÇÃO ou SIMULAÇÃO → filtro CSS. `filtroChave`.
|
|
215
|
+
* · SIMULAÇÃO → as classes do corpo, o overlay, os controles de toque, a bolinha.
|
|
216
|
+
*
|
|
217
|
+
* ⚠️ E O ALCANCE DO FILTRO CONTINUA A DEPENDER DE SIMULAR OU CORRIGIR, que é a distinção do ADR-0046: uma
|
|
218
|
+
* CORREÇÃO alcança os menus, porque a criança precisa dela para LER o menu; uma SIMULAÇÃO fica no mundo,
|
|
219
|
+
* porque quem simula tem de conseguir sair.
|
|
220
|
+
*
|
|
221
|
+
* `setVizModeValue` continua a escrever a chave ÚNICA legada, e a que ele escreve é a de TEXTURA — que é
|
|
222
|
+
* `simulação ?? tema ?? normal`, ou seja o que mais muda o que se vê. É espelho, não fonte: o estado a
|
|
223
|
+
* sério são os dois eixos, e esta linha sai quando o último leitor da chave velha sair.
|
|
224
|
+
*/
|
|
225
|
+
function applyVizGlobal(v) {
|
|
226
|
+
const filtro = filtroChave(v);
|
|
227
|
+
const textura = chaveDeTextura(v);
|
|
228
|
+
// ⚠️ `chaveLegada` E NÃO `textura`: a de textura devolve `normal` para uma correção de cor, e escrevê-la
|
|
229
|
+
// aqui faria um leitor antigo da chave global perder a correção da criança. Ver a nota em `chaveLegada`.
|
|
230
|
+
setVizModeValue(chaveLegada(v)); // core/state: valor + persistência (incl_viz) + evento — espelho legado
|
|
146
231
|
// ANTES daqui saía também `ctx.setHcMode(m.kind === 'hcnew')`, alimentando um `let hcMode` no game.js cujo
|
|
147
232
|
// único leitor era o gancho window.__incl. Era `vizMode` reescrito com outro nome: derivar de VIZ_BY_KEY
|
|
148
233
|
// custa uma comparação e não pode divergir. (O inicializador daquele `let` usava OUTRA fórmula,
|
|
149
234
|
// `vizMode!=='normal'`, e discordava do setter — sem efeito, porque applyVizGlobal roda no boot antes de
|
|
150
235
|
// o gancho existir, mas é o sintoma clássico de cópia de estado.)
|
|
151
|
-
|
|
152
|
-
ctx.
|
|
153
|
-
|
|
154
|
-
ctx.
|
|
155
|
-
ctx.
|
|
156
|
-
ctx.
|
|
157
|
-
ctx.
|
|
236
|
+
// --- eixo CORREÇÃO/SIMULAÇÃO: o filtro CSS ---
|
|
237
|
+
ctx.aplicarFiltroCss(cssFilterFor(filtro ?? '', lqFilter()), ehSimulacao(v) ? 'mundo' : 'mundo-e-menus');
|
|
238
|
+
// --- eixo TEMA: DOM e textura. Não é filtro (ver `AplicarAltoContrasteNoDom`), e é por isso que compõe.
|
|
239
|
+
ctx.aplicarAltoContrasteNoDom(temAltoContraste(v));
|
|
240
|
+
ctx.camera.filters = temAltoContraste(v) ? ctx.pixiFilterFor(textura) : null; // solo: alto contraste na câmera
|
|
241
|
+
ctx.setFrontDim(temAltoContraste(v)); // HC: frente (carros/placas/semáforo) escurece como fundo
|
|
242
|
+
ctx.worldSprite.texture = worldTexFor(textura); // alto contraste direto = Renderização Direta · resto=normal
|
|
243
|
+
ctx.parallaxLayers.forEach((ts, i) => { ts.texture = ctx.parallaxTexFor(i, textura); });
|
|
244
|
+
ctx.decoSprites.forEach((s) => { s.texture = ctx.treeTexFor(textura); });
|
|
158
245
|
ctx.rebuildExtras();
|
|
159
246
|
ctx.rebuildCoins();
|
|
160
|
-
// baixa visão = névoa+manchas (overlay) + bolinha verde; cegueira = tela preta
|
|
161
|
-
|
|
162
|
-
ctx.body.classList.toggle('
|
|
247
|
+
// --- SIMULAÇÃO: baixa visão = névoa+manchas (overlay) + bolinha verde; cegueira = tela preta + esconde
|
|
248
|
+
// controles + bolinha branca. Nenhuma delas olha para o tema, e é por isso que o tema não as apaga.
|
|
249
|
+
ctx.body.classList.toggle('lowvision-mode', ehBaixaVisao(v));
|
|
250
|
+
ctx.body.classList.toggle('blind-mode', ehCego(v));
|
|
163
251
|
const ov = ctx.$('#viz-overlay');
|
|
164
252
|
if (ov) {
|
|
165
|
-
ov.hidden = (
|
|
166
|
-
ov.className =
|
|
253
|
+
ov.hidden = !ehBaixaVisao(v);
|
|
254
|
+
ov.className = ehBaixaVisao(v) ? 'lv-' + String(v.simulacao).slice(3) : '';
|
|
167
255
|
}
|
|
168
|
-
if (
|
|
256
|
+
if (ehCego(v)) {
|
|
169
257
|
ctx.hideTouchControls('cegueira');
|
|
170
258
|
}
|
|
171
|
-
updateVizIndicator(
|
|
259
|
+
updateVizIndicator(ehCego(v) ? 'blind' : ehBaixaVisao(v) ? 'lowvision' : 'normal');
|
|
172
260
|
ctx.reflectVizButtons(); // (a guarda `typeof ...==='function'` do original morreu: era declaração de função, sempre verdadeira)
|
|
173
261
|
ctx.renderVisualPanel();
|
|
174
262
|
ctx.renderEmpathyPanel();
|
|
@@ -188,7 +276,7 @@ export function initVizSetters(ctx) {
|
|
|
188
276
|
function reapplyVizAll() {
|
|
189
277
|
ctx.invalidateSharedViz();
|
|
190
278
|
if (ctx.getNumPlayers() <= 1) {
|
|
191
|
-
applyVizGlobal(ctx.getPlayers()[0].
|
|
279
|
+
applyVizGlobal(ctx.getPlayers()[0].visual ?? PADRAO);
|
|
192
280
|
}
|
|
193
281
|
else {
|
|
194
282
|
ctx.aplicarFiltroCss(lqFilter(), 'mundo-e-menus'); // realce L/Q é melhoria: alcança o menu
|
|
@@ -209,7 +297,7 @@ export function initVizSetters(ctx) {
|
|
|
209
297
|
ctx.clearPlayerDirectCache();
|
|
210
298
|
ctx.invalidateSharedViz();
|
|
211
299
|
if (ctx.getNumPlayers() <= 1)
|
|
212
|
-
applyVizGlobal(ctx.getPlayers()[0].
|
|
300
|
+
applyVizGlobal(ctx.getPlayers()[0].visual ?? PADRAO);
|
|
213
301
|
else
|
|
214
302
|
applyVpFilters();
|
|
215
303
|
}
|
|
@@ -232,13 +320,81 @@ export function initVizSetters(ctx) {
|
|
|
232
320
|
}));
|
|
233
321
|
}
|
|
234
322
|
const players = ctx.getPlayers(), sel = ctx.getSelVizPlayer();
|
|
235
|
-
const
|
|
323
|
+
const v = players[sel]?.visual ?? PADRAO;
|
|
324
|
+
const cur = chaveLegada(v);
|
|
236
325
|
el.innerHTML = vizGroupHtml(modes, cur);
|
|
237
|
-
|
|
326
|
+
// ⚠️ A RECUSA DA SIMULAÇÃO (#104, ADR-0076 §4). Com qualquer dos dois eixos fora do padrão, uma
|
|
327
|
+
// demonstração não mostra a deficiência — mostra o AJUSTE por cima do qual ela corre, e isso ensina uma
|
|
328
|
+
// coisa falsa. A linha CONTINUA na tela, desabilitada e com o motivo: sumir ensinaria que a coisa não
|
|
329
|
+
// existe, e um adulto concluiria que ela foi tirada em vez de perceber que foi ele que ligou o contraste.
|
|
330
|
+
//
|
|
331
|
+
// A prosa entra num `.opt-hint`, que é o que a casca (`ui/settings-panel.fillExplain`) MOVE para o rodapé
|
|
332
|
+
// — a regra das três zonas do CLAUDE.md: a explicação mora no rodapé, nunca na linha.
|
|
333
|
+
//
|
|
334
|
+
// ⚠️ SÓ AS LINHAS QUE SIMULAM. Esta função desenha hoje a lista de simulações (o painel visual passou a
|
|
335
|
+
// usar o `renderEixosVisuais`), mas ela continua a receber os modos por parâmetro — e uma correção de
|
|
336
|
+
// cor nesta lista não deve ser recusada por causa do eixo dela própria.
|
|
337
|
+
const recusa = recusaDaSimulacao(v);
|
|
338
|
+
el.querySelectorAll('button[data-viz]').forEach((btn) => {
|
|
238
339
|
const key = btn.dataset.viz;
|
|
239
|
-
|
|
240
|
-
|
|
340
|
+
if (recusa && simulatesDisability(key)) {
|
|
341
|
+
btn.setAttribute('aria-disabled', 'true');
|
|
342
|
+
const dica = btn.closest('.ctrl-row')?.querySelector('.opt-hint');
|
|
343
|
+
if (dica)
|
|
344
|
+
dica.textContent = `${dica.textContent} ${t(recusa.chave)}`.trim();
|
|
345
|
+
return; // sem ouvinte: aceitar o clique e ignorá-lo é a outra metade do que o ADR proíbe
|
|
346
|
+
}
|
|
347
|
+
btn.addEventListener('click', () => {
|
|
348
|
+
setPlayerViz(ctx.getSelVizPlayer(), key);
|
|
349
|
+
ctx.srSay(vizGroupSay(ctx.getNumPlayers(), ctx.getSelVizPlayer(), t(VIZ_MODES.find((m) => m.key === key).nome)));
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* OS DOIS EIXOS no painel VISUAL (#104). Irmão do `renderVizGroup`, e SEPARADO dele de propósito.
|
|
355
|
+
*
|
|
356
|
+
* ⚠️ QUASE FIZ ISTO DENTRO DO `renderVizGroup`, E TERIA PARTIDO O PAINEL DE EMPATIA. Aquela função serve os
|
|
357
|
+
* DOIS painéis — `#visual-modes` com os sete modos e `#empathy-list` com as nove simulações —, e trocar o
|
|
358
|
+
* corpo dela teria posto os dois eixos na lista de simulações. Ali o rádio único continua CERTO: as
|
|
359
|
+
* simulações são mesmo exclusivas entre si, e o que deixou de ser exclusivo foi outra coisa.
|
|
360
|
+
*
|
|
361
|
+
* ⚠️ E OS DOIS GRUPOS ENTRAM NO CONTENEDOR QUE JÁ EXISTE, sem markup nova do hospedeiro. Exigir um elemento
|
|
362
|
+
* a mais faria cada um dos 300 jogos ter de se lembrar dele — a forma de defeito que o ADR-0106 acabou de
|
|
363
|
+
* medir em cinco jogos sem barra de acessibilidade nenhuma.
|
|
364
|
+
*/
|
|
365
|
+
function renderEixosVisuais(listSel, tabsSel) {
|
|
366
|
+
const el = ctx.$(listSel);
|
|
367
|
+
if (!el)
|
|
368
|
+
return;
|
|
369
|
+
if (ctx.getSelVizPlayer() >= ctx.getNumPlayers())
|
|
370
|
+
ctx.setSelVizPlayer(0);
|
|
371
|
+
const tabs = ctx.$(tabsSel);
|
|
372
|
+
if (tabs) {
|
|
373
|
+
tabs.hidden = true;
|
|
374
|
+
tabs.innerHTML = '';
|
|
375
|
+
}
|
|
376
|
+
const sel = ctx.getSelVizPlayer();
|
|
377
|
+
const v = ctx.getPlayers()[sel]?.visual ?? PADRAO;
|
|
378
|
+
el.innerHTML = eixosHtml(v, t);
|
|
379
|
+
el.querySelectorAll('button[data-eixo]').forEach((btn) => btn.addEventListener('click', () => {
|
|
380
|
+
const escolha = escolhaDoBotao(btn.dataset);
|
|
381
|
+
if (!escolha)
|
|
382
|
+
return; // botão de outro assunto, ou um `data-` editado à mão: não se adivinha
|
|
383
|
+
const i = ctx.getSelVizPlayer();
|
|
384
|
+
if (escolha.eixo === 'tema') {
|
|
385
|
+
setTemaDoJogador(i, escolha.valor);
|
|
386
|
+
ctx.srSay(vizGroupSay(ctx.getNumPlayers(), i, t(ROTULO_DO_TEMA[escolha.valor])));
|
|
387
|
+
}
|
|
388
|
+
else {
|
|
389
|
+
setCorrecaoDoJogador(i, escolha.valor);
|
|
390
|
+
ctx.srSay(vizGroupSay(ctx.getNumPlayers(), i, t(ROTULO_DA_CORRECAO[escolha.valor])));
|
|
391
|
+
}
|
|
241
392
|
}));
|
|
242
393
|
}
|
|
243
|
-
return {
|
|
394
|
+
return {
|
|
395
|
+
applySharedTextures, updateVpDots, applyVpFilters, setPlayerViz, applyVizGlobal, reapplyVizAll,
|
|
396
|
+
updateVizIndicator, rebakeDirect, renderVizGroup, renderEixosVisuais,
|
|
397
|
+
// Os DOIS escritores por eixo (#104): é o que um painel de dois controles chama.
|
|
398
|
+
setVisualDoJogador, setTemaDoJogador, setCorrecaoDoJogador,
|
|
399
|
+
};
|
|
244
400
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import { type VisualState } from './viz-axes.js';
|
|
2
3
|
import type { PlayerView } from '../core/entity.js';
|
|
3
4
|
/** Minimal PIXI.Graphics surface these draws use (structural → module stays PIXI-free for node tests). */
|
|
4
5
|
export interface DrawGraphics {
|
|
@@ -10,10 +11,17 @@ export interface DrawGraphics {
|
|
|
10
11
|
drawCircle(x: number, y: number, r: number): unknown;
|
|
11
12
|
}
|
|
12
13
|
/** Cadeira e bengala: onde o jogador está, para que lado olha, e em que modo de visão desenhar. */
|
|
13
|
-
type DrawablePlayer = PlayerView<'facing' | 'x' | 'y' | '
|
|
14
|
-
/**
|
|
14
|
+
type DrawablePlayer = PlayerView<'facing' | 'x' | 'y' | 'visual'>;
|
|
15
|
+
/**
|
|
16
|
+
* Cor da bengala: verde na baixa visão, branca no resto (cegueira).
|
|
17
|
+
*
|
|
18
|
+
* ⚠️ LÊ O `visual` E NÃO O `viz` desde a #104. A pergunta não mudou — «isto é baixa visão?» — mas quem a
|
|
19
|
+
* responde passou a ser o `ehBaixaVisao` do modelo de dois eixos, em vez de uma consulta ao `kind` da tabela
|
|
20
|
+
* antiga. É a mesma resposta para os dezasseis modos de hoje (a rede da etapa 0 afirma-o, chave a chave), e
|
|
21
|
+
* passa a ser a resposta CERTA para um estado que a tabela antiga não sabe exprimir.
|
|
22
|
+
*/
|
|
15
23
|
export declare function caneColor(pl: {
|
|
16
|
-
|
|
24
|
+
visual: VisualState;
|
|
17
25
|
}): number;
|
|
18
26
|
/** Rigid half-block cane (~8px): front-hand extension, fixed to the body (does not swing on its own). */
|
|
19
27
|
export declare function drawCane(g: DrawGraphics, pl: DrawablePlayer): void;
|
|
@@ -3,11 +3,17 @@
|
|
|
3
3
|
// low-vision), the running cane (item), and the wheelchair. Stateless PIXI drawing (verbatim from game.js):
|
|
4
4
|
// each takes a Graphics `g` + the player. The layers (caneLayer/chairLayer) stay in game.js and are passed in.
|
|
5
5
|
// `caneColor` (pure) moves here too — it was only used by the cane draws. See docs/5-Refactoring/plano-modularizacao-mapa.md.
|
|
6
|
-
import {
|
|
7
|
-
/**
|
|
6
|
+
import { ehBaixaVisao } from './viz-axes.js';
|
|
7
|
+
/**
|
|
8
|
+
* Cor da bengala: verde na baixa visão, branca no resto (cegueira).
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ LÊ O `visual` E NÃO O `viz` desde a #104. A pergunta não mudou — «isto é baixa visão?» — mas quem a
|
|
11
|
+
* responde passou a ser o `ehBaixaVisao` do modelo de dois eixos, em vez de uma consulta ao `kind` da tabela
|
|
12
|
+
* antiga. É a mesma resposta para os dezasseis modos de hoje (a rede da etapa 0 afirma-o, chave a chave), e
|
|
13
|
+
* passa a ser a resposta CERTA para um estado que a tabela antiga não sabe exprimir.
|
|
14
|
+
*/
|
|
8
15
|
export function caneColor(pl) {
|
|
9
|
-
|
|
10
|
-
return m && m.kind === 'lowvision' ? 0x35d06a : 0xf2f2f2;
|
|
16
|
+
return ehBaixaVisao(pl.visual) ? 0x35d06a : 0xf2f2f2;
|
|
11
17
|
}
|
|
12
18
|
/** Rigid half-block cane (~8px): front-hand extension, fixed to the body (does not swing on its own). */
|
|
13
19
|
export function drawCane(g, pl) {
|
package/dist-pkg/ui/dom.js
CHANGED
|
@@ -13,8 +13,26 @@ import { t } from '../core/i18n.js';
|
|
|
13
13
|
// seleciona. `Element` e o padrao do lib.dom porque `querySelector` tambem serve para SVG e MathML —
|
|
14
14
|
// nenhum seletor deste projeto pega um desses (conferido). Quem precisar de um tipo mais estreito passa
|
|
15
15
|
// o parametro: `$<HTMLSelectElement>('#pad-design')`.
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* ⚠️ RESOLVIDO POR `globalThis` E NÃO PELO GLOBAL CRU, e a diferença é entre devolver `null` e LANÇAR.
|
|
18
|
+
*
|
|
19
|
+
* `document.querySelector(...)` com `document` inexistente dá `ReferenceError` — não `undefined` —, e a
|
|
20
|
+
* assinatura destas duas funções promete `T | null`. Uma consulta que lança onde promete `null` é um defeito
|
|
21
|
+
* pela própria assinatura, e ele viajava longe: medido em 2026-09-08, o `core/a11y-sr.srAlert` chama o `$`
|
|
22
|
+
* daqui, e o `createGame` chama o `srAlert` ao mostrar o aviso de alcance — logo bootar a engine contra um
|
|
23
|
+
* documento INJECTADO (um iframe, um editor ao lado do jogo, um teste) rebentava o boot inteiro num anúncio.
|
|
24
|
+
*
|
|
25
|
+
* 📌 É o ACHADO 15 do `boot/create-game` outra vez, e sobreviveu pela mesma razão: enquanto toda raiz era um
|
|
26
|
+
* `main.ts` num navegador, o global ERA o documento certo. `globalThis.document` é a mesma coisa onde ele
|
|
27
|
+
* existe, e é `undefined` — em vez de explosão — onde não existe.
|
|
28
|
+
*
|
|
29
|
+
* ⚠️ E ELAS CONTINUAM A OLHAR PARA O GLOBAL, de propósito: quem precisa de consultar OUTRO documento injecta
|
|
30
|
+
* o seu (`create-game` tem um `$` próprio ligado ao `doc` do hospedeiro, e o `ui/pause-icons` tem o
|
|
31
|
+
* `docDaMontagem`). O que este conserto muda não é ONDE se procura — é o que acontece quando não há onde.
|
|
32
|
+
*/
|
|
33
|
+
const docGlobal = () => globalThis.document;
|
|
34
|
+
export const $ = (s) => docGlobal()?.querySelector(s) ?? null;
|
|
35
|
+
export const $$ = (s) => [...(docGlobal()?.querySelectorAll(s) ?? [])];
|
|
18
36
|
/**
|
|
19
37
|
* Reflects an on/off state onto a toggle button: the visual class AND `aria-pressed`.
|
|
20
38
|
* Lives here because the two must never drift apart — a button that looks pressed but does not
|
package/dist-pkg/ui/fonts.d.ts
CHANGED
|
@@ -4,12 +4,42 @@
|
|
|
4
4
|
* descrição, não o texto: mesma decisão de `VIZ_MODES` e `RM_LABEL`, e pelo mesmo motivo — uma tabela de
|
|
5
5
|
* `const` com texto resolve uma vez, no import, e fica congelada no idioma do boot.
|
|
6
6
|
*/
|
|
7
|
+
/**
|
|
8
|
+
* O PAPEL de uma face, e é a emenda do ADR-0012 (27/08) posta em dado (issue #87).
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ NÃO É O MESMO QUE O GRUPO. O grupo (`sans`/`serif`/`hand`) é APARÊNCIA e serve para ler a lista; o papel
|
|
11
|
+
* é ONDE A FACE PODE SER USADA, e é regra:
|
|
12
|
+
*
|
|
13
|
+
* · `geral` — em qualquer lugar. São as únicas que o menu de tipografia oferece.
|
|
14
|
+
* · `caligrafica` — **só DENTRO das atividades escolares**, nunca no HUD nem nos menus, e por isso **não
|
|
15
|
+
* aparecem no menu de fonte**. Elas existem para a criança APRENDER a ler letra cursiva, o que é matéria;
|
|
16
|
+
* usá-las como interface é dar-lhe a matéria como obstáculo em todos os lugares onde ela só quer navegar.
|
|
17
|
+
* · `jogo` — a face que o JOGO usa no HUD, no título e em rótulos curtos de arcade. Também não aparece no
|
|
18
|
+
* menu, e pelo mesmo tipo de razão que as caligráficas: uma face de pixel de 8 bits é desenhada para
|
|
19
|
+
* dizer POUCAS palavras em tamanho grande. Como face de interface ela contradiz o argumento que faz a
|
|
20
|
+
* Atkinson Hyperlegible ser o padrão — pouca diferenciação entre letras, avanço largo, nenhuma variação
|
|
21
|
+
* de altura. É certa no HUD de um jogo de pixel-art e errada num menu que a criança precisa de LER.
|
|
22
|
+
*
|
|
23
|
+
* ⚠️ E O CORTE NÃO É O GRUPO `hand`. O `comicneue` está lá por aparência — a face é de propósito geral e é
|
|
24
|
+
* frequentemente recomendada para dislexia. Tirá-la do menu removeria uma opção legitimamente acessível. A
|
|
25
|
+
* definição boa é a lista do item 2 da #87, que nomeia as caligráficas dando-lhes tamanho mínimo.
|
|
26
|
+
*/
|
|
27
|
+
export type FontRole = 'geral' | 'caligrafica' | 'jogo';
|
|
7
28
|
export type FontItem = {
|
|
8
29
|
k: string;
|
|
9
30
|
fam: string;
|
|
10
31
|
fb: string;
|
|
11
32
|
d?: string;
|
|
12
33
|
off?: string;
|
|
34
|
+
/** Ausente = `geral`. Só as caligráficas se declaram, porque são a excepção. */
|
|
35
|
+
papel?: FontRole;
|
|
36
|
+
/**
|
|
37
|
+
* O menor tamanho, em px, em que esta face ainda é legível (item 2 da #87, números do Dev).
|
|
38
|
+
*
|
|
39
|
+
* ⚠️ Abaixo disto a face deixa de ser DIFÍCIL e passa a ser ILEGÍVEL, que são coisas diferentes: a
|
|
40
|
+
* dificuldade é o exercício, a ilegibilidade é a criança a desistir. Por isso é gate e não recomendação.
|
|
41
|
+
*/
|
|
42
|
+
minPx?: number;
|
|
13
43
|
};
|
|
14
44
|
/** Um grupo do catálogo. `g` também guarda CHAVE ('font.group.sans'), pelo mesmo motivo. */
|
|
15
45
|
export type FontGroup = {
|
|
@@ -17,6 +47,34 @@ export type FontGroup = {
|
|
|
17
47
|
items: FontItem[];
|
|
18
48
|
};
|
|
19
49
|
export declare const FONT_GROUPS: FontGroup[];
|
|
50
|
+
/** O papel de uma face; ausente no catálogo quer dizer `geral`. */
|
|
51
|
+
export declare function papelDaFonte(it: FontItem): FontRole;
|
|
52
|
+
/**
|
|
53
|
+
* AS FAMÍLIAS QUE UMA FACE ACEITA, do `fam` que pode ser uma PILHA.
|
|
54
|
+
*
|
|
55
|
+
* 📌 A Ronde declara três (`'Ronde Script, OPTIFrench-Script, Merveille'`) porque qualquer uma delas serve —
|
|
56
|
+
* são três desenhos da mesma letra de mão, e um adulto instala a que encontrar. As outras faces declaram uma
|
|
57
|
+
* só, e para elas isto devolve uma lista de um.
|
|
58
|
+
*/
|
|
59
|
+
export declare function familiasDaFace(it: FontItem): string[];
|
|
60
|
+
/**
|
|
61
|
+
* ESTA FACE PODE SER USADA AGORA? — o `off` deixa de ser uma sentença e passa a ser uma CONDIÇÃO.
|
|
62
|
+
*
|
|
63
|
+
* O ADR-0012 decidiu que a opção da ronde «fica DESABILITADA enquanto nenhuma fonte estiver presente», e o
|
|
64
|
+
* ADR-0108 §4 acrescentou o que ela diz. A palavra «enquanto» é o que esta função constrói: uma face `off`
|
|
65
|
+
* volta a ficar disponível no instante em que o adulto instala uma das que a mensagem nomeia.
|
|
66
|
+
*
|
|
67
|
+
* ⚠️ O DETECTOR É INJECTADO, e nunca `document.fonts` lido daqui: este módulo é o catálogo, corre em node nos
|
|
68
|
+
* gates, e ler um global do navegador aqui é o ACHADO 15 outra vez — o `srAlert` que rebentou o boot contra um
|
|
69
|
+
* documento injectado.
|
|
70
|
+
* 📌 E o PADRÃO É «não instalada», que é seguro por uma razão que não vale para todos os padrões deste
|
|
71
|
+
* repositório: sem detector a opção fica desabilitada COM a mensagem, e a mensagem diz ao adulto exactamente
|
|
72
|
+
* o que fazer. O silêncio não decide nada contra a criança — ele mantém o estado que já existia e que é
|
|
73
|
+
* accionável. É o oposto do `seguraTeclas`, onde os dois lados do padrão erravam.
|
|
74
|
+
*/
|
|
75
|
+
export declare function faceDisponivel(it: FontItem, instalada?: (familia: string) => boolean): boolean;
|
|
76
|
+
/** As faces que o MENU pode oferecer: só as gerais (emenda do ADR-0012). */
|
|
77
|
+
export declare const OFERECIVEIS: FontItem[];
|
|
20
78
|
export declare const FONT_BY_KEY: Record<string, FontItem>;
|
|
21
79
|
/** Narrow store shape these need — lets a caller inject a fake without touching real storage. */
|
|
22
80
|
export interface FontStore {
|
|
@@ -34,7 +92,19 @@ export declare const FONT_KEY_LEGACY = "incl_fonte";
|
|
|
34
92
|
* porque duas cópias de um padrão são duas chances de o reset devolver algo que o jogo nunca usou.
|
|
35
93
|
*/
|
|
36
94
|
export declare const DEFAULT_FONT_KEY = "atkinson";
|
|
37
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* Boot choice: validated persisted key (ignores .off fonts) -> legacy-key migration -> DEFAULT_FONT_KEY.
|
|
97
|
+
*
|
|
98
|
+
* ⚠️ E UMA CALIGRÁFICA GUARDADA VOLTA AO PADRÃO (issue #87, 2026-09-07). Antes da emenda do ADR-0012 o menu
|
|
99
|
+
* oferecia as sete caligráficas, então há crianças com `pinyon` ou `ufmag` guardados — e o valor salvo
|
|
100
|
+
* continua a ser escolha delas, que não é nossa para desfazer sem motivo. O motivo existe: a emenda diz que
|
|
101
|
+
* uma caligráfica **não pode ser a face da interface**, e o menu deixou de a oferecer. Deixá-la valer daria
|
|
102
|
+
* uma interface inteira em letra cursiva a quem já não tem como sair dela pelo menu — uma armadilha, e das
|
|
103
|
+
* silenciosas.
|
|
104
|
+
*
|
|
105
|
+
* ⚠️ E UMA CHAVE APAGADA TAMBÉM VOLTA, pelo mesmo caminho: as quatro faces que saíram do roster já não estão
|
|
106
|
+
* no `FONT_BY_KEY`, então quem tinha `greatvibes` guardado cai no padrão em vez de ficar sem face nenhuma.
|
|
107
|
+
*/
|
|
38
108
|
export declare function resolveFontKey(s: FontStore): string;
|
|
39
109
|
export declare function persistFontKey(s: FontStore, k: string): void;
|
|
40
110
|
export declare function loadFontKey(): string;
|
package/dist-pkg/ui/fonts.js
CHANGED
|
@@ -8,6 +8,12 @@ export const FONT_GROUPS = [
|
|
|
8
8
|
{ k: 'lexend', fam: 'Lexend', fb: 'sans', d: 'font.desc.lexend' },
|
|
9
9
|
{ k: 'quattro', fam: 'iA Writer Quattro', fb: 'sans', d: 'font.desc.quattro' },
|
|
10
10
|
{ k: 'andika', fam: 'Andika', fb: 'sans', d: 'font.desc.andika' },
|
|
11
|
+
// ⚠️ A OPENDYSLEXIC ENTRA SEM NENHUMA ALEGAÇÃO DE EFICÁCIA, e a restrição é da issue #87 item 3 e do
|
|
12
|
+
// `docs/game-design/typography.md`, que diz por extenso: «Ofereça a Dyslexie e a OpenDyslexic apenas como
|
|
13
|
+
// escolha do usuário. A pesquisa não mostra ganho de leitura com elas.» A descrição dela fala do DESENHO
|
|
14
|
+
// (hastes pesadas em baixo), nunca do efeito — prometer leitura melhor seria vender a uma criança
|
|
15
|
+
// disléxica uma coisa que a evidência não sustenta, e ela é quem menos pode pagar por isso.
|
|
16
|
+
{ k: 'opendyslexic', fam: 'OpenDyslexic', fb: 'sans', d: 'font.desc.opendyslexic' },
|
|
11
17
|
{ k: 'sourcesans', fam: 'Source Sans 3', fb: 'sans' },
|
|
12
18
|
{ k: 'inter', fam: 'Inter', fb: 'sans' },
|
|
13
19
|
{ k: 'opensans', fam: 'Open Sans', fb: 'sans' },
|
|
@@ -18,16 +24,101 @@ export const FONT_GROUPS = [
|
|
|
18
24
|
{ k: 'sourceserif', fam: 'Source Serif 4', fb: 'serif' },
|
|
19
25
|
{ k: 'newsreader', fam: 'Newsreader', fb: 'serif' }
|
|
20
26
|
] },
|
|
27
|
+
// ⚠️ QUATRO FACES SAÍRAM DAQUI EM 2026-09-07 (issue #87, item 3, decisão do Dev):
|
|
28
|
+
// · `greatvibes` (44 KB) e `ufcook` (20 KB) — peso que o roster não paga;
|
|
29
|
+
// · `learningcurve` e `kindergarten` — eram entradas `.off` SEM FICHEIRO, isto é, o menu oferecia-as
|
|
30
|
+
// desabilitadas e nada existia por trás. Uma linha que só serve para dizer «ainda não» é uma linha que
|
|
31
|
+
// a criança lê e não pode usar.
|
|
21
32
|
{ g: 'font.group.hand', items: [
|
|
22
|
-
{ k: '
|
|
23
|
-
{ k: '
|
|
24
|
-
|
|
25
|
-
|
|
33
|
+
{ k: 'pinyon', fam: 'Pinyon Script', fb: 'cursive', d: 'font.desc.pinyon', papel: 'caligrafica', minPx: 24 },
|
|
34
|
+
{ k: 'ufmag', fam: 'UnifrakturMaguntia', fb: 'cursive', d: 'font.desc.ufmag', papel: 'caligrafica', minPx: 20 },
|
|
35
|
+
// Fondamento entra pela emenda do ADR-0012 (#87 item 3) com o mínimo que o Dev fixou. Caligráfica, logo
|
|
36
|
+
// fora do menu — ela é para os botões DENTRO das atividades escolares, não para a interface.
|
|
37
|
+
{ k: 'fondamento', fam: 'Fondamento', fb: 'cursive', d: 'font.desc.fondamento', papel: 'caligrafica', minPx: 20 },
|
|
38
|
+
// ⚠️ `comicneue` NÃO é caligráfica, e está neste grupo só por aparência: é uma face de propósito geral,
|
|
39
|
+
// frequentemente recomendada para dislexia. Marcá-la como caligráfica tirá-la-ia do menu — removendo uma
|
|
40
|
+
// opção legitimamente acessível pelo formato do grupo em vez de pelo papel.
|
|
26
41
|
{ k: 'comicneue', fam: 'Comic Neue', fb: 'cursive', d: 'font.desc.comicneue' },
|
|
27
|
-
|
|
28
|
-
|
|
42
|
+
/*
|
|
43
|
+
* A RONDE FRANCESA — o item 4 da #87, decidido no ADR-0108 §4. Ela NUNCA é empacotada: as três faces são
|
|
44
|
+
* livres só para uso PESSOAL (ADR-0012), e distribuí-las seria distribuir o que não foi licenciado para
|
|
45
|
+
* distribuição. O que muda é que a opção passa a FALAR.
|
|
46
|
+
*
|
|
47
|
+
* ⚠️ E ISTO NÃO É A ENTRADA `.off` QUE ESTE CATÁLOGO JÁ REMOVEU. O cabeçalho acima tirou a `learningcurve`
|
|
48
|
+
* e a `kindergarten` com a razão certa — «uma linha que só serve para dizer "ainda não" é uma linha que a
|
|
49
|
+
* criança lê e não pode usar». A diferença é ACCIONABILIDADE, e é a razão que o ADR-0108 dá por extenso:
|
|
50
|
+
* aquelas diziam «ainda não», que ninguém pode resolver; esta diz QUAIS TRÊS FONTES INSTALAR, que um
|
|
51
|
+
* adulto resolve numa tarde. 📌 «Instale uma fonte ronde» seria o defeito de volta — um adulto não age
|
|
52
|
+
* sobre uma categoria —, e é por isso que a mensagem nomeia as três.
|
|
53
|
+
*
|
|
54
|
+
* ⚠️ `papel` AUSENTE, logo `geral`, e é deliberado apesar de a ronde ser caligráfica por natureza: as
|
|
55
|
+
* caligráficas são filtradas do menu (`papelDaFonte === 'geral'`), e uma linha filtrada não pode dizer
|
|
56
|
+
* nada a ninguém. Marcar o papel «certo» aqui apagaria a única coisa que este item existe para fazer.
|
|
57
|
+
*/
|
|
58
|
+
/*
|
|
59
|
+
* AS OITO PLAYWRITE — o item 3 da #87, decidido no ADR-0108 §2 e entregue em 2026-09-09.
|
|
60
|
+
*
|
|
61
|
+
* ⚠️ `papel: caligrafica`, logo FORA DO MENU: elas são a mão que se aprende a escrever, para os botões
|
|
62
|
+
* DENTRO das atividades escolares, e não uma opção de interface. É a divisão do item 1 desta issue.
|
|
63
|
+
*
|
|
64
|
+
* 📌 `minPx: 20` é o mesmo piso que as outras três cursivas carregam. A lista de mínimos da #87 não
|
|
65
|
+
* nomeia a Playwrite — o número é o das irmãs, e não uma medição própria; corrigir-se com uma linha.
|
|
66
|
+
*/
|
|
67
|
+
{ k: 'pwbr', fam: 'Playwrite BR', fb: 'cursive', d: 'font.desc.pw.br', papel: 'caligrafica', minPx: 20 },
|
|
68
|
+
{ k: 'pwustrad', fam: 'Playwrite US Trad', fb: 'cursive', d: 'font.desc.pw.ustrad', papel: 'caligrafica', minPx: 20 },
|
|
69
|
+
{ k: 'pwusmod', fam: 'Playwrite US Modern', fb: 'cursive', d: 'font.desc.pw.usmod', papel: 'caligrafica', minPx: 20 },
|
|
70
|
+
{ k: 'pwca', fam: 'Playwrite CA', fb: 'cursive', d: 'font.desc.pw.ca', papel: 'caligrafica', minPx: 20 },
|
|
71
|
+
{ k: 'pwmx', fam: 'Playwrite MX', fb: 'cursive', d: 'font.desc.pw.mx', papel: 'caligrafica', minPx: 20 },
|
|
72
|
+
{ k: 'pwar', fam: 'Playwrite AR', fb: 'cursive', d: 'font.desc.pw.ar', papel: 'caligrafica', minPx: 20 },
|
|
73
|
+
{ k: 'pwcl', fam: 'Playwrite CL', fb: 'cursive', d: 'font.desc.pw.cl', papel: 'caligrafica', minPx: 20 },
|
|
74
|
+
{ k: 'pwco', fam: 'Playwrite CO', fb: 'cursive', d: 'font.desc.pw.co', papel: 'caligrafica', minPx: 20 },
|
|
75
|
+
{ k: 'ronde', fam: 'Ronde Script, OPTIFrench-Script, Merveille', fb: 'cursive',
|
|
76
|
+
d: 'font.desc.ronde', off: 'font.off.ronde' }
|
|
77
|
+
] },
|
|
78
|
+
// ⚠️ A FACE DO JOGO, e ela tem grupo próprio porque não é nem sans, nem serifada, nem manuscrita — é uma
|
|
79
|
+
// face de PIXEL, e pô-la em qualquer um dos três diria a coisa errada sobre ela na lista.
|
|
80
|
+
//
|
|
81
|
+
// Ela NÃO aparece no menu (`papel:'jogo'`), e o ficheiro veio do `SP-the-inclusionist-whackwhack`, onde já
|
|
82
|
+
// estava verificado. Ver a nota do `@font-face` em `vendor/fonts.css`: o subconjunto errado desta família
|
|
83
|
+
// carrega, declara-se e reporta-se como certo, e não desenha uma única letra latina.
|
|
84
|
+
{ g: 'font.group.arcade', items: [
|
|
85
|
+
{ k: 'pressstart', fam: 'Press Start 2P', fb: 'monospace', d: 'font.desc.pressstart', papel: 'jogo' }
|
|
29
86
|
] },
|
|
30
87
|
];
|
|
88
|
+
/** O papel de uma face; ausente no catálogo quer dizer `geral`. */
|
|
89
|
+
export function papelDaFonte(it) { return it.papel ?? 'geral'; }
|
|
90
|
+
/**
|
|
91
|
+
* AS FAMÍLIAS QUE UMA FACE ACEITA, do `fam` que pode ser uma PILHA.
|
|
92
|
+
*
|
|
93
|
+
* 📌 A Ronde declara três (`'Ronde Script, OPTIFrench-Script, Merveille'`) porque qualquer uma delas serve —
|
|
94
|
+
* são três desenhos da mesma letra de mão, e um adulto instala a que encontrar. As outras faces declaram uma
|
|
95
|
+
* só, e para elas isto devolve uma lista de um.
|
|
96
|
+
*/
|
|
97
|
+
export function familiasDaFace(it) {
|
|
98
|
+
return it.fam.split(',').map((f) => f.trim().replace(/^['"]|['"]$/g, '')).filter(Boolean);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* ESTA FACE PODE SER USADA AGORA? — o `off` deixa de ser uma sentença e passa a ser uma CONDIÇÃO.
|
|
102
|
+
*
|
|
103
|
+
* O ADR-0012 decidiu que a opção da ronde «fica DESABILITADA enquanto nenhuma fonte estiver presente», e o
|
|
104
|
+
* ADR-0108 §4 acrescentou o que ela diz. A palavra «enquanto» é o que esta função constrói: uma face `off`
|
|
105
|
+
* volta a ficar disponível no instante em que o adulto instala uma das que a mensagem nomeia.
|
|
106
|
+
*
|
|
107
|
+
* ⚠️ O DETECTOR É INJECTADO, e nunca `document.fonts` lido daqui: este módulo é o catálogo, corre em node nos
|
|
108
|
+
* gates, e ler um global do navegador aqui é o ACHADO 15 outra vez — o `srAlert` que rebentou o boot contra um
|
|
109
|
+
* documento injectado.
|
|
110
|
+
* 📌 E o PADRÃO É «não instalada», que é seguro por uma razão que não vale para todos os padrões deste
|
|
111
|
+
* repositório: sem detector a opção fica desabilitada COM a mensagem, e a mensagem diz ao adulto exactamente
|
|
112
|
+
* o que fazer. O silêncio não decide nada contra a criança — ele mantém o estado que já existia e que é
|
|
113
|
+
* accionável. É o oposto do `seguraTeclas`, onde os dois lados do padrão erravam.
|
|
114
|
+
*/
|
|
115
|
+
export function faceDisponivel(it, instalada) {
|
|
116
|
+
if (!it.off)
|
|
117
|
+
return true;
|
|
118
|
+
return !!instalada && familiasDaFace(it).some((f) => instalada(f));
|
|
119
|
+
}
|
|
120
|
+
/** As faces que o MENU pode oferecer: só as gerais (emenda do ADR-0012). */
|
|
121
|
+
export const OFERECIVEIS = FONT_GROUPS.flatMap((g) => g.items).filter((it) => papelDaFonte(it) === 'geral');
|
|
31
122
|
export const FONT_BY_KEY = {};
|
|
32
123
|
FONT_GROUPS.forEach((g) => g.items.forEach((it) => { FONT_BY_KEY[it.k] = it; }));
|
|
33
124
|
export const FONT_KEY = 'incl_font_k';
|
|
@@ -41,10 +132,22 @@ export const FONT_KEY_LEGACY = 'incl_fonte'; // pre-Fase-2: 'alfabetizacao' | 'd
|
|
|
41
132
|
* porque duas cópias de um padrão são duas chances de o reset devolver algo que o jogo nunca usou.
|
|
42
133
|
*/
|
|
43
134
|
export const DEFAULT_FONT_KEY = 'atkinson';
|
|
44
|
-
/**
|
|
135
|
+
/**
|
|
136
|
+
* Boot choice: validated persisted key (ignores .off fonts) -> legacy-key migration -> DEFAULT_FONT_KEY.
|
|
137
|
+
*
|
|
138
|
+
* ⚠️ E UMA CALIGRÁFICA GUARDADA VOLTA AO PADRÃO (issue #87, 2026-09-07). Antes da emenda do ADR-0012 o menu
|
|
139
|
+
* oferecia as sete caligráficas, então há crianças com `pinyon` ou `ufmag` guardados — e o valor salvo
|
|
140
|
+
* continua a ser escolha delas, que não é nossa para desfazer sem motivo. O motivo existe: a emenda diz que
|
|
141
|
+
* uma caligráfica **não pode ser a face da interface**, e o menu deixou de a oferecer. Deixá-la valer daria
|
|
142
|
+
* uma interface inteira em letra cursiva a quem já não tem como sair dela pelo menu — uma armadilha, e das
|
|
143
|
+
* silenciosas.
|
|
144
|
+
*
|
|
145
|
+
* ⚠️ E UMA CHAVE APAGADA TAMBÉM VOLTA, pelo mesmo caminho: as quatro faces que saíram do roster já não estão
|
|
146
|
+
* no `FONT_BY_KEY`, então quem tinha `greatvibes` guardado cai no padrão em vez de ficar sem face nenhuma.
|
|
147
|
+
*/
|
|
45
148
|
export function resolveFontKey(s) {
|
|
46
149
|
const k = s.get(FONT_KEY, null);
|
|
47
|
-
if (k && FONT_BY_KEY[k] && !FONT_BY_KEY[k].off)
|
|
150
|
+
if (k && FONT_BY_KEY[k] && !FONT_BY_KEY[k].off && papelDaFonte(FONT_BY_KEY[k]) === 'geral')
|
|
48
151
|
return k;
|
|
49
152
|
const leg = s.get(FONT_KEY_LEGACY, null);
|
|
50
153
|
if (leg === 'alfabetizacao')
|