@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.
Files changed (116) hide show
  1. package/README.md +5 -3
  2. package/app/css/style.css +22 -5
  3. package/app/public/vendor/fonts/fondamento-400-ext.woff2 +0 -0
  4. package/app/public/vendor/fonts/fondamento-400.woff2 +0 -0
  5. package/app/public/vendor/fonts/opendyslexic-400.woff2 +0 -0
  6. package/app/public/vendor/fonts/pressstart-400.woff2 +0 -0
  7. package/app/public/vendor/fonts.css +43 -2
  8. package/dist-pkg/boot/create-game.d.ts +76 -2
  9. package/dist-pkg/boot/create-game.js +271 -13
  10. package/dist-pkg/core/constants.d.ts +0 -28
  11. package/dist-pkg/core/constants.js +34 -17
  12. package/dist-pkg/core/contract.d.ts +39 -0
  13. package/dist-pkg/core/contract.js +36 -0
  14. package/dist-pkg/core/entity.d.ts +47 -4
  15. package/dist-pkg/core/layers.d.ts +18 -0
  16. package/dist-pkg/core/layers.js +18 -0
  17. package/dist-pkg/core/rng.js +14 -4
  18. package/dist-pkg/core/route.d.ts +42 -0
  19. package/dist-pkg/core/route.js +158 -0
  20. package/dist-pkg/core/state.d.ts +10 -1
  21. package/dist-pkg/core/state.js +13 -0
  22. package/dist-pkg/educational/adaptive-engine.d.ts +65 -0
  23. package/dist-pkg/educational/adaptive-engine.js +117 -0
  24. package/dist-pkg/educational/segment-bar.d.ts +96 -0
  25. package/dist-pkg/educational/segment-bar.js +89 -0
  26. package/dist-pkg/i18n/en.js +33 -0
  27. package/dist-pkg/i18n/es.js +33 -0
  28. package/dist-pkg/i18n/pt.js +46 -0
  29. package/dist-pkg/input/default-bindings.d.ts +40 -0
  30. package/dist-pkg/input/default-bindings.js +143 -9
  31. package/dist-pkg/input/gamepad.d.ts +18 -11
  32. package/dist-pkg/input/gamepad.js +75 -9
  33. package/dist-pkg/input/keyboard-runtime.d.ts +6 -8
  34. package/dist-pkg/input/keyboard-runtime.js +24 -5
  35. package/dist-pkg/input/keyboard.d.ts +10 -0
  36. package/dist-pkg/input/keyboard.js +41 -11
  37. package/dist-pkg/input/keydown.d.ts +27 -2
  38. package/dist-pkg/input/keydown.js +20 -6
  39. package/dist-pkg/input/latch-scope.d.ts +70 -0
  40. package/dist-pkg/input/latch-scope.js +110 -0
  41. package/dist-pkg/input/latch-store.d.ts +45 -0
  42. package/dist-pkg/input/latch-store.js +74 -0
  43. package/dist-pkg/input/origem-sintetica.d.ts +44 -0
  44. package/dist-pkg/input/origem-sintetica.js +60 -0
  45. package/dist-pkg/input/pointer.d.ts +61 -0
  46. package/dist-pkg/input/pointer.js +71 -0
  47. package/dist-pkg/input/state.d.ts +86 -1
  48. package/dist-pkg/input/state.js +128 -1
  49. package/dist-pkg/input/touch-bindings.d.ts +11 -2
  50. package/dist-pkg/input/touch-bindings.js +9 -3
  51. package/dist-pkg/input/touch.js +9 -1
  52. package/dist-pkg/input/transporte-em-uso.d.ts +101 -0
  53. package/dist-pkg/input/transporte-em-uso.js +130 -0
  54. package/dist-pkg/input/transports.d.ts +88 -2
  55. package/dist-pkg/input/transports.js +60 -5
  56. package/dist-pkg/input/vocabulary-migration.d.ts +18 -7
  57. package/dist-pkg/platform/audio-earcons.d.ts +29 -2
  58. package/dist-pkg/platform/audio-earcons.js +10 -0
  59. package/dist-pkg/platform/audio-nav.d.ts +1 -1
  60. package/dist-pkg/platform/audio-sonar.d.ts +149 -9
  61. package/dist-pkg/platform/audio-sonar.js +232 -21
  62. package/dist-pkg/platform/guide-intensity.d.ts +40 -0
  63. package/dist-pkg/platform/guide-intensity.js +73 -0
  64. package/dist-pkg/platform/storage.d.ts +30 -0
  65. package/dist-pkg/platform/storage.js +35 -0
  66. package/dist-pkg/platform/tts.js +16 -1
  67. package/dist-pkg/platform/voice-plan.d.ts +90 -0
  68. package/dist-pkg/platform/voice-plan.js +137 -0
  69. package/dist-pkg/render/draw.d.ts +1 -1
  70. package/dist-pkg/render/draw.js +15 -5
  71. package/dist-pkg/render/viz-axes.d.ts +17 -0
  72. package/dist-pkg/render/viz-axes.js +19 -0
  73. package/dist-pkg/render/viz-setters.d.ts +34 -2
  74. package/dist-pkg/render/viz-setters.js +189 -33
  75. package/dist-pkg/render/wheelchair-sprites.d.ts +11 -3
  76. package/dist-pkg/render/wheelchair-sprites.js +10 -4
  77. package/dist-pkg/ui/dom.js +20 -2
  78. package/dist-pkg/ui/fonts.d.ts +47 -1
  79. package/dist-pkg/ui/fonts.js +47 -9
  80. package/dist-pkg/ui/latch-refusal.d.ts +32 -0
  81. package/dist-pkg/ui/latch-refusal.js +60 -0
  82. package/dist-pkg/ui/layout.d.ts +32 -0
  83. package/dist-pkg/ui/layout.js +64 -1
  84. package/dist-pkg/ui/motion-scene.d.ts +41 -0
  85. package/dist-pkg/ui/motion-scene.js +76 -0
  86. package/dist-pkg/ui/panel-shell.d.ts +53 -0
  87. package/dist-pkg/ui/panel-shell.js +103 -0
  88. package/dist-pkg/ui/pause-icons.d.ts +134 -20
  89. package/dist-pkg/ui/pause-icons.js +307 -45
  90. package/dist-pkg/ui/reach-notice.js +8 -0
  91. package/dist-pkg/ui/settings-audio.d.ts +14 -1
  92. package/dist-pkg/ui/settings-audio.js +43 -3
  93. package/dist-pkg/ui/settings-controls.d.ts +66 -6
  94. package/dist-pkg/ui/settings-controls.js +179 -17
  95. package/dist-pkg/ui/settings-empathy.d.ts +15 -0
  96. package/dist-pkg/ui/settings-empathy.js +2 -0
  97. package/dist-pkg/ui/settings-motion.d.ts +25 -19
  98. package/dist-pkg/ui/settings-motion.js +32 -19
  99. package/dist-pkg/ui/settings-motor.d.ts +81 -3
  100. package/dist-pkg/ui/settings-motor.js +118 -9
  101. package/dist-pkg/ui/settings-typo.d.ts +32 -2
  102. package/dist-pkg/ui/settings-typo.js +64 -18
  103. package/dist-pkg/ui/settings-visual.d.ts +21 -1
  104. package/dist-pkg/ui/settings-visual.js +48 -4
  105. package/dist-pkg/ui/shell.d.ts +13 -3
  106. package/dist-pkg/ui/shell.js +3 -4
  107. package/dist-pkg/ui/simulation-refusal.d.ts +32 -0
  108. package/dist-pkg/ui/simulation-refusal.js +57 -0
  109. package/dist-pkg/ui/visual-axes-panel.d.ts +48 -0
  110. package/dist-pkg/ui/visual-axes-panel.js +95 -0
  111. package/dist-pkg/ui/webcam.js +6 -1
  112. package/docs/CREDITS.md +18 -0
  113. package/docs/LICENSES.md +12 -0
  114. package/package.json +24 -4
  115. package/app/public/vendor/fonts/greatvibes-400.woff2 +0 -0
  116. package/app/public/vendor/fonts/ufcook-700.woff2 +0 -0
@@ -0,0 +1,65 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /** O que sobra de uma questão depois de as tentativas dela colapsarem. */
3
+ export type ResultadoDaQuestao = 'primeira' | 'mediada' | 'falhou';
4
+ /** A faixa em que a criança está, e o que ela manda fazer com o nível. */
5
+ export type Faixa = 'proficiente' | 'zona' | 'frustracao';
6
+ /** Por que a faixa é essa. Existe para o anúncio ao leitor de tela e para a barra do ADR-0049 poderem dizer
7
+ * a razão em vez de só o resultado — e para um teste poder distinguir duas faixas iguais por motivos
8
+ * diferentes, que é onde um motor adaptativo erra sem dar sinal. */
9
+ export type Motivo = 'acertos-de-primeira' | 'quatro-seguidas' | 'resolvidas-no-piso' | 'dentro-da-zona' | 'janela-incompleta';
10
+ export interface Veredicto {
11
+ faixa: Faixa;
12
+ motivo: Motivo;
13
+ /** −1 desce, 0 mantém, +1 sobe. O ADR-0048 §5 chama a isto o efeito da faixa. */
14
+ efeito: -1 | 0 | 1;
15
+ }
16
+ /** Quantas questões a janela olha. Dez é o número do registo. */
17
+ export declare const JANELA = 10;
18
+ /** A fração de acertos DE PRIMEIRA a partir da qual se sobe. 0,80 = 8 em 10, e vale para qualquer tipo. */
19
+ export declare const ALVO_DE_SUBIDA = 0.8;
20
+ /** Quantas QUESTÕES falhadas seguidas descem o nível na hora. Quatro, porque 0,40⁴ < 5%. */
21
+ export declare const FALHAS_SEGUIDAS_QUE_DESCEM = 4;
22
+ /**
23
+ * O piso de chute de um tipo de questão: a fração de questões que a sorte pura resolve.
24
+ *
25
+ * Sorteio SEM reposição — quem erra uma alternativa não a volta a escolher —, então a chance de falhar as
26
+ * `tentativas` todas é o produto de `(alternativas−1−i)/(alternativas−i)`, e o piso é o complemento.
27
+ *
28
+ * ⚠️ ELE É PARÂMETRO E NÃO CONSTANTE, e a issue #92 pede isso pelo nome: «o módulo recebe o piso como
29
+ * parâmetro, não o embute». Uma atividade de duas alternativas tem piso 0,50 com uma tentativa; uma de dez
30
+ * com uma tentativa tem 0,10. Embutir o 0,60 do caso de cinco faria o motor julgar todas as outras pela
31
+ * aritmética da errada.
32
+ */
33
+ export declare function pisoDeChute(alternativas: number, tentativas: number): number;
34
+ /**
35
+ * As tentativas de UMA questão colapsam num resultado.
36
+ *
37
+ * `acertouNaTentativa` é 1-based; `null` quando a criança não acertou em nenhuma. É aqui que quatro tentativas
38
+ * erradas deixam de poder ser confundidas com quatro questões falhadas: elas produzem UM `'falhou'`.
39
+ */
40
+ export declare function resultadoDaQuestao(acertouNaTentativa: number | null): ResultadoDaQuestao;
41
+ /** Quantas questões terminaram em falha, contadas do fim para trás até à primeira que não falhou. */
42
+ export declare function falhasSeguidas(historico: readonly ResultadoDaQuestao[]): number;
43
+ /**
44
+ * A faixa da criança nesta habilidade, dado o histórico de questões e o piso de chute do tipo.
45
+ *
46
+ * A ORDEM DAS PERGUNTAS É A DECISÃO, e é esta:
47
+ *
48
+ * 1. QUATRO FALHAS SEGUIDAS descem na hora — antes de tudo, e mesmo com a janela incompleta. Não é um
49
+ * atalho: quatro questões falhadas seguidas é o evento de 2,56%, e ele não fica mais provável por a
50
+ * criança ter respondido poucas questões. Esperar a janela fechar seria deixá-la mais seis questões
51
+ * num nível que já se sabe alto.
52
+ * 2. JANELA INCOMPLETA mantém. ⚠️ Esta regra NÃO está na issue #92 e é decisão minha, declarada: com três
53
+ * questões respondidas, «resolvidas ≤ piso» dispararia com duas falhas, e rebaixar uma criança por duas
54
+ * questões é exactamente o erro que a unidade-questão existe para evitar. A regra da janela pede dez
55
+ * questões porque é sobre dez que a aritmética foi feita.
56
+ * 3. ≥ 80% DE PRIMEIRA sobe.
57
+ * 4. RESOLVIDAS NO PISO OU ABAIXO descem.
58
+ * 5. O resto é a zona, que é onde se quer estar.
59
+ *
60
+ * ⚠️ O CORTE DE BAIXO É `≤` E NÃO `<`, e a diferença é uma questão inteira: a tabela do registo diz «≤ 6 das
61
+ * últimas 10», e 6/10 é exactamente o piso de 0,60 do caso de cinco alternativas. A prosa da mesma issue diz
62
+ * «abaixo do piso», que é o resumo e não a regra. Quem seguir a prosa deixa a criança um nível acima com um
63
+ * desempenho que o chute puro reproduz.
64
+ */
65
+ export declare function faixaDe(historico: readonly ResultadoDaQuestao[], piso: number, janela?: number): Veredicto;
@@ -0,0 +1,117 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // educational/adaptive-engine — AS TRÊS FAIXAS, e as DUAS medidas que hoje viram uma (ADR-0048 §5/§6, #92).
3
+ //
4
+ // ========================= O DEFEITO QUE ISTO EXISTE PARA NÃO REPETIR =========================
5
+ // Uma atividade com retentativa produz DUAS informações por questão e o jogo guarda uma: se acertou. A que se
6
+ // perde é a que importa mais — **acertou de primeira** ou **acertou com apoio**. Sem essa distinção não há
7
+ // zona de desenvolvimento proximal; há só dificuldade, que é uma escada sem corrimão.
8
+ //
9
+ // A retentativa É o apoio graduado. Uma criança que acerta na terceira está a aprender COM mediação, e é
10
+ // exactamente essa a faixa em que se quer que ela fique. Deitar fora o número da tentativa transforma-a numa
11
+ // criança «que acertou», indistinguível de quem acertou sozinha — e o motor sobe o nível dela.
12
+ //
13
+ // ========================= POR QUE A PROFICIÊNCIA É 8/10 DE PRIMEIRA =========================
14
+ // A aritmética é o argumento inteiro, e por isso está presa por teste em vez de comentada:
15
+ //
16
+ // · Chutar com 3 tentativas em 5 alternativas acerta em 60% das questões: 1 − (4/5 · 3/4 · 2/3) = 0,60.
17
+ // ⚠️ É POR ISSO que a proficiência lê acertos DE PRIMEIRA e não questões resolvidas: «8 de 10 resolvidas»
18
+ // é praticamente o que o chute puro produz, e promoveria por sorte.
19
+ // · Falhar QUATRO questões seguidas tem 0,40⁴ = 0,0256 de probabilidade — abaixo do corte de 5%. É por isso
20
+ // que quatro seguidas descem o nível na hora, sem esperar a janela fechar.
21
+ //
22
+ // ⚠️ A UNIDADE É A QUESTÃO, NUNCA A TENTATIVA, e confundi-las é o defeito caro: quatro tentativas erradas
23
+ // acontecem dentro de UMA questão difícil e são normais; quatro QUESTÕES falhadas é o evento de 2,56%.
24
+ // `resultadoDaQuestao` existe para que as tentativas colapsem numa questão ANTES de a faixa as ver — a
25
+ // distinção é estrutural, e não uma regra que alguém tenha de lembrar.
26
+ //
27
+ // ========================= O QUE ESTE FICHEIRO NÃO FAZ =========================
28
+ // Não guarda nada. Recebe a lista de resultados e devolve a faixa — onde vive o histórico por habilidade está
29
+ // indecidido (ADR-0049 §5 diz, textualmente, que «a casa da barra está indecidida», e o ADR-0048 §6c manda-o
30
+ // para um sistema que ainda não existe em repositório nenhum). A função pura constrói-se hoje; a persistência
31
+ // não, e escrevê-la aqui seria decidir por omissão o que dois registos aceites deixaram em aberto.
32
+ //
33
+ // E não importa NADA, como toda a camada `educational/` (ADR-0032): ela é dado, e o gate de fronteira afere.
34
+ /** Quantas questões a janela olha. Dez é o número do registo. */
35
+ export const JANELA = 10;
36
+ /** A fração de acertos DE PRIMEIRA a partir da qual se sobe. 0,80 = 8 em 10, e vale para qualquer tipo. */
37
+ export const ALVO_DE_SUBIDA = 0.8;
38
+ /** Quantas QUESTÕES falhadas seguidas descem o nível na hora. Quatro, porque 0,40⁴ < 5%. */
39
+ export const FALHAS_SEGUIDAS_QUE_DESCEM = 4;
40
+ /**
41
+ * O piso de chute de um tipo de questão: a fração de questões que a sorte pura resolve.
42
+ *
43
+ * Sorteio SEM reposição — quem erra uma alternativa não a volta a escolher —, então a chance de falhar as
44
+ * `tentativas` todas é o produto de `(alternativas−1−i)/(alternativas−i)`, e o piso é o complemento.
45
+ *
46
+ * ⚠️ ELE É PARÂMETRO E NÃO CONSTANTE, e a issue #92 pede isso pelo nome: «o módulo recebe o piso como
47
+ * parâmetro, não o embute». Uma atividade de duas alternativas tem piso 0,50 com uma tentativa; uma de dez
48
+ * com uma tentativa tem 0,10. Embutir o 0,60 do caso de cinco faria o motor julgar todas as outras pela
49
+ * aritmética da errada.
50
+ */
51
+ export function pisoDeChute(alternativas, tentativas) {
52
+ if (!Number.isFinite(alternativas) || alternativas < 2)
53
+ return 0;
54
+ // O tecto é o número de ALTERNATIVAS, não uma a menos: com tantas tentativas quantas alternativas a
55
+ // criança esgota todas as erradas e a certeza é 1. Pedir mais não pode passar disso.
56
+ const t = Math.max(0, Math.min(Math.floor(tentativas), Math.floor(alternativas)));
57
+ let falhaTudo = 1;
58
+ for (let i = 0; i < t; i++)
59
+ falhaTudo *= (alternativas - 1 - i) / (alternativas - i);
60
+ return 1 - falhaTudo;
61
+ }
62
+ /**
63
+ * As tentativas de UMA questão colapsam num resultado.
64
+ *
65
+ * `acertouNaTentativa` é 1-based; `null` quando a criança não acertou em nenhuma. É aqui que quatro tentativas
66
+ * erradas deixam de poder ser confundidas com quatro questões falhadas: elas produzem UM `'falhou'`.
67
+ */
68
+ export function resultadoDaQuestao(acertouNaTentativa) {
69
+ if (acertouNaTentativa === null || !Number.isFinite(acertouNaTentativa))
70
+ return 'falhou';
71
+ return acertouNaTentativa <= 1 ? 'primeira' : 'mediada';
72
+ }
73
+ /** Quantas questões terminaram em falha, contadas do fim para trás até à primeira que não falhou. */
74
+ export function falhasSeguidas(historico) {
75
+ let n = 0;
76
+ for (let i = historico.length - 1; i >= 0 && historico[i] === 'falhou'; i--)
77
+ n++;
78
+ return n;
79
+ }
80
+ /**
81
+ * A faixa da criança nesta habilidade, dado o histórico de questões e o piso de chute do tipo.
82
+ *
83
+ * A ORDEM DAS PERGUNTAS É A DECISÃO, e é esta:
84
+ *
85
+ * 1. QUATRO FALHAS SEGUIDAS descem na hora — antes de tudo, e mesmo com a janela incompleta. Não é um
86
+ * atalho: quatro questões falhadas seguidas é o evento de 2,56%, e ele não fica mais provável por a
87
+ * criança ter respondido poucas questões. Esperar a janela fechar seria deixá-la mais seis questões
88
+ * num nível que já se sabe alto.
89
+ * 2. JANELA INCOMPLETA mantém. ⚠️ Esta regra NÃO está na issue #92 e é decisão minha, declarada: com três
90
+ * questões respondidas, «resolvidas ≤ piso» dispararia com duas falhas, e rebaixar uma criança por duas
91
+ * questões é exactamente o erro que a unidade-questão existe para evitar. A regra da janela pede dez
92
+ * questões porque é sobre dez que a aritmética foi feita.
93
+ * 3. ≥ 80% DE PRIMEIRA sobe.
94
+ * 4. RESOLVIDAS NO PISO OU ABAIXO descem.
95
+ * 5. O resto é a zona, que é onde se quer estar.
96
+ *
97
+ * ⚠️ O CORTE DE BAIXO É `≤` E NÃO `<`, e a diferença é uma questão inteira: a tabela do registo diz «≤ 6 das
98
+ * últimas 10», e 6/10 é exactamente o piso de 0,60 do caso de cinco alternativas. A prosa da mesma issue diz
99
+ * «abaixo do piso», que é o resumo e não a regra. Quem seguir a prosa deixa a criança um nível acima com um
100
+ * desempenho que o chute puro reproduz.
101
+ */
102
+ export function faixaDe(historico, piso, janela = JANELA) {
103
+ if (falhasSeguidas(historico) >= FALHAS_SEGUIDAS_QUE_DESCEM) {
104
+ return { faixa: 'frustracao', motivo: 'quatro-seguidas', efeito: -1 };
105
+ }
106
+ if (historico.length < janela) {
107
+ return { faixa: 'zona', motivo: 'janela-incompleta', efeito: 0 };
108
+ }
109
+ const ultimas = historico.slice(-janela);
110
+ const dePrimeira = ultimas.filter((r) => r === 'primeira').length / janela;
111
+ const resolvidas = ultimas.filter((r) => r !== 'falhou').length / janela;
112
+ if (dePrimeira >= ALVO_DE_SUBIDA)
113
+ return { faixa: 'proficiente', motivo: 'acertos-de-primeira', efeito: 1 };
114
+ if (resolvidas <= piso)
115
+ return { faixa: 'frustracao', motivo: 'resolvidas-no-piso', efeito: -1 };
116
+ return { faixa: 'zona', motivo: 'dentro-da-zona', efeito: 0 };
117
+ }
@@ -0,0 +1,96 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ /**
3
+ * O que sobra de uma questão depois de as tentativas dela colapsarem.
4
+ *
5
+ * ⚠️ DECLARADO AQUI, e não importado do `adaptive-engine`, porque a camada não importa nada. As duas uniões
6
+ * são estruturalmente idênticas — em TypeScript isso basta para o valor de um atravessar para o outro sem
7
+ * conversão — e o caso `[Interface]` do gate afirma que continuam idênticas. Se alguém acrescentar um quarto
8
+ * resultado ao motor e esquecer este ficheiro, é lá que se descobre.
9
+ */
10
+ export type Resultado = 'primeira' | 'mediada' | 'falhou';
11
+ /** A cor de UM segmento. Uma questão, nunca uma tentativa. */
12
+ export type Segmento = 'azul' | 'verde' | 'vermelho';
13
+ /**
14
+ * A cor da barra INTEIRA, que é o veredicto do motor adaptativo a ficar visível.
15
+ *
16
+ * `nenhuma` não é «apagada» — é a barra a mostrar os seus segmentos, que é o estado normal. Laranja e roxa
17
+ * cobrem-nos porque são a única coisa que interessa naquele instante.
18
+ */
19
+ export type CorDaBarra = 'nenhuma' | 'laranja' | 'roxa';
20
+ /**
21
+ * O VEREDICTO, visto daqui: a fatia mínima do que o `faixaDe` devolve.
22
+ *
23
+ * Só `efeito` e `motivo`, e nem sequer a `faixa` — a barra não precisa de saber que a criança está «na zona»,
24
+ * precisa de saber se subiu, desceu ou ficou. `motivo` existe para o anúncio ao leitor de tela poder dizer a
25
+ * RAZÃO: «desceu porque quatro seguidas» é informação; «ficou laranja» é decoração.
26
+ */
27
+ export interface VeredictoLido {
28
+ readonly efeito: -1 | 0 | 1;
29
+ readonly motivo: string;
30
+ }
31
+ /**
32
+ * Quantos segmentos a barra tem.
33
+ *
34
+ * Dez, e tem de ser a mesma `JANELA` que o motor adaptativo julga: uma barra que mostrasse doze questões
35
+ * enquanto o motor julga dez estaria a mostrar duas que não contam para o veredicto que a colore. O gate
36
+ * afirma a igualdade, porque este ficheiro não pode importar a constante.
37
+ */
38
+ export declare const SEGMENTOS_DA_BARRA = 10;
39
+ /** Uma barra pronta a desenhar. Tudo aqui é derivado; não há nada para mutar. */
40
+ export interface Barra {
41
+ /** A árvore de habilidade desta barra. O ADR-0049 §5 manda uma barra POR habilidade, de uma a três, e o
42
+ * segmento cai na barra da habilidade que a questão exercitou — sem isto, duas habilidades ligadas ao
43
+ * mesmo tempo partilhariam um histórico e as faixas mediriam a média de duas crianças diferentes. */
44
+ readonly skill: string;
45
+ /** Os segmentos, do mais ANTIGO para o mais novo, no máximo `SEGMENTOS_DA_BARRA`. Menos do que isso no
46
+ * começo da sessão: a barra enche-se, e não nasce cheia de espaços que pareçam erros. */
47
+ readonly segmentos: readonly Segmento[];
48
+ readonly cor: CorDaBarra;
49
+ /** O veredicto que produziu a cor, tal como entrou. */
50
+ readonly veredicto: VeredictoLido;
51
+ }
52
+ /**
53
+ * Um resultado de questão vira uma cor. É a tradução inteira, e é 1 para 1 de propósito.
54
+ *
55
+ * ⚠️ UMA QUESTÃO, NUNCA UMA TENTATIVA. Duas tentativas erradas não são um erro — são a tolerância; o
56
+ * segmento só fica vermelho depois da terceira. O `resultadoDaQuestao` do motor é quem faz esse colapso, e é
57
+ * por isso que esta função recebe o resultado e não o número de tentativas: se recebesse tentativas, uma
58
+ * questão difícil pintaria três vermelhos e desceria de nível uma criança que estava apenas a pensar.
59
+ */
60
+ export declare function corDoSegmento(r: Resultado): Segmento;
61
+ /** O que se sabe além do histórico e do veredicto. */
62
+ export interface Contexto {
63
+ /**
64
+ * ⚠️ A CRIANÇA CHEGOU A COPIAR A RESPOSTA (ADR-0049): falhou as três tentativas, falhou as três da
65
+ * explicação, e a resposta apareceu para ela copiar. Aí «o nível desce IMEDIATAMENTE, sem esperar por
66
+ * quatro questões falhadas — a barra fica laranja de uma vez».
67
+ *
68
+ * É a única entrada que não vem do histórico nem do veredicto, e tem de existir separada porque o
69
+ * histórico não a distingue: copiar a resposta produz um `'falhou'` igual ao de quem errou três vezes e
70
+ * seguiu em frente. A diferença entre as duas é pedagógica, e é ela que decide se o nível desce agora ou
71
+ * daqui a três questões.
72
+ */
73
+ readonly copiouAResposta?: boolean;
74
+ }
75
+ /**
76
+ * A barra de uma habilidade: o histórico dela, colorido pelo veredicto que o motor já emitiu.
77
+ *
78
+ * ⚠️ O HISTÓRICO É O DA HABILIDADE, não o da sessão, e o veredicto tem de ser o DAQUELE histórico. Passar
79
+ * aqui o histórico misturado de duas habilidades é o defeito que o ADR-0049 §5 nomeia: as faixas passariam a
80
+ * medir a média de duas crianças diferentes. Quem chama separa; este módulo não tem como saber que não
81
+ * separou — nem tem como julgar, que é precisamente o ponto.
82
+ */
83
+ export declare function barraDe(skill: string, historico: readonly Resultado[], veredicto: VeredictoLido, ctx?: Contexto): Barra;
84
+ /**
85
+ * O histórico DEPOIS de a barra ter sinalizado — porque «sobe» leva consigo «e a contagem zera».
86
+ *
87
+ * ⚠️ O ZERAR ESTÁ ESCRITO PARA O ROXO E INFERIDO PARA O LARANJA, e a inferência fica declarada aqui em vez de
88
+ * escondida no código. O ADR-0049 §5 diz «ALL PURPLE — eight blues: the level rises, AND THE COUNT RESETS», e
89
+ * não diz nada equivalente para o laranja. Mas não zerar no laranja não é neutro: os quatro vermelhos
90
+ * continuariam na janela e a barra voltaria a mandar descer a CADA questão nova, até eles rolarem para fora
91
+ * — quatro descidas de nível onde a decisão foi uma. Um registo que manda descer de nível não pode, pelo
92
+ * mesmo facto, mandar descer quatro vezes.
93
+ *
94
+ * Fica como pergunta para o Dev; enquanto ele não a responder, esta é a leitura que não produz um defeito.
95
+ */
96
+ export declare function aposSinalizar(historico: readonly Resultado[], barra: Barra): readonly Resultado[];
@@ -0,0 +1,89 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // educational/segment-bar — A BARRA DE DEZ SEGMENTOS do ADR-0049 §5 (issue #93), e ela não decide nada.
3
+ //
4
+ // ========================= A BARRA NÃO PODE CONTAR, E ISSO É ESTRUTURAL =========================
5
+ // A leitura literal da issue #93 produz um módulo com contadores próprios: «oito azuis → roxa, cinco
6
+ // vermelhos → laranja». Escrevê-lo assim seria pôr no repositório uma SEGUNDA implementação de uma decisão
7
+ // que já existe no `faixaDe` do motor adaptativo:
8
+ //
9
+ // · «oito azuis sobem» é o `ALVO_DE_SUBIDA = 0.8` — 8 de primeira em 10;
10
+ // · «quatro vermelhos seguidos» é o `FALHAS_SEGUIDAS_QUE_DESCEM = 4`;
11
+ // · «cinco vermelhos espalhados» é `resolvidas <= piso` — 5 falhadas em 10 dá 0,50, abaixo do piso 0,60.
12
+ //
13
+ // O próprio ADR-0049 diz isto em voz alta: «That is what makes every number in ADR-0048 §5 line up.»
14
+ //
15
+ // ⚠️ E AS DUAS CÓPIAS JÁ DISCORDARIAM HOJE, o que torna isto um defeito e não um gosto: «cinco espalhados»
16
+ // só é o limiar quando o piso é 0,60, e o piso é PARÂMETRO — sai do número de alternativas da questão
17
+ // (`pisoDoChute`). Num item de verdadeiro-ou-falso o piso é muito mais alto e menos falhas já bastam; a
18
+ // barra escrita com o «cinco» literal continuaria a mostrar verde a uma criança que o motor já teria descido.
19
+ //
20
+ // ⚠️ POR ISSO O VEREDICTO ENTRA PRONTO, e é a arquitectura que o obriga em vez de a disciplina. O `educational/`
21
+ // não importa NADA — nem os seus vizinhos (ADR-0032; a camada é DADO e viaja sozinha para o
22
+ // `the-inclusionist-knowledge-tree` do ADR-0058). Este módulo não tem, portanto, como chamar o `faixaDe`; e
23
+ // como também não recebe o `piso`, não tem sequer os números com que recalcular a regra. Ele SÓ pode
24
+ // projectar o que lhe deram. Um gate consegue afirmar que duas implementações concordam; esta forma torna a
25
+ // segunda implementação impossível de escrever aqui dentro.
26
+ //
27
+ // ========================= E ELA NÃO GUARDA NADA (ADR-0103) =========================
28
+ // ⚠️ Este módulo não persiste e não pode vir a persistir. O ADR-0103 decidiu que a barra não vive em lado
29
+ // nenhum — nem engine, nem cartucho, nem Bússola Escolar — e a razão é PEDAGÓGICA antes de jurídica: o
30
+ // desenvolvimento oscila, a criança joga uma dada actividade talvez uma vez por semana, e um histórico
31
+ // guardado achata a oscilação numa linha de tendência que lê um recuo normal como regressão. O argumento
32
+ // jurídico permitiria guardar assim que a controladoria estivesse resolvida; este não permite nunca. Os dois
33
+ // gates que o §confirmation daquele registo deixou em dívida são `tests/segment-bar.node.test.js` e
34
+ // `tests/no-stored-history.node.test.js`.
35
+ //
36
+ // «Permanente» no ADR-0049 §5 quer dizer PERMANENTE ENTRE ACTIVIDADES DA MESMA SESSÃO — a actividade é o
37
+ // veículo, a barra é a leitura da criança numa árvore de habilidade, e uma árvore sobrevive a uma
38
+ // actividade. Não quer dizer permanente entre dias.
39
+ /**
40
+ * Quantos segmentos a barra tem.
41
+ *
42
+ * Dez, e tem de ser a mesma `JANELA` que o motor adaptativo julga: uma barra que mostrasse doze questões
43
+ * enquanto o motor julga dez estaria a mostrar duas que não contam para o veredicto que a colore. O gate
44
+ * afirma a igualdade, porque este ficheiro não pode importar a constante.
45
+ */
46
+ export const SEGMENTOS_DA_BARRA = 10;
47
+ /**
48
+ * Um resultado de questão vira uma cor. É a tradução inteira, e é 1 para 1 de propósito.
49
+ *
50
+ * ⚠️ UMA QUESTÃO, NUNCA UMA TENTATIVA. Duas tentativas erradas não são um erro — são a tolerância; o
51
+ * segmento só fica vermelho depois da terceira. O `resultadoDaQuestao` do motor é quem faz esse colapso, e é
52
+ * por isso que esta função recebe o resultado e não o número de tentativas: se recebesse tentativas, uma
53
+ * questão difícil pintaria três vermelhos e desceria de nível uma criança que estava apenas a pensar.
54
+ */
55
+ export function corDoSegmento(r) {
56
+ return r === 'primeira' ? 'azul' : r === 'mediada' ? 'verde' : 'vermelho';
57
+ }
58
+ /**
59
+ * A barra de uma habilidade: o histórico dela, colorido pelo veredicto que o motor já emitiu.
60
+ *
61
+ * ⚠️ O HISTÓRICO É O DA HABILIDADE, não o da sessão, e o veredicto tem de ser o DAQUELE histórico. Passar
62
+ * aqui o histórico misturado de duas habilidades é o defeito que o ADR-0049 §5 nomeia: as faixas passariam a
63
+ * medir a média de duas crianças diferentes. Quem chama separa; este módulo não tem como saber que não
64
+ * separou — nem tem como julgar, que é precisamente o ponto.
65
+ */
66
+ export function barraDe(skill, historico, veredicto, ctx = {}) {
67
+ const segmentos = historico.slice(-SEGMENTOS_DA_BARRA).map(corDoSegmento);
68
+ // A rendição vence o veredicto porque ela É uma descida, decidida antes e por outro caminho. Escrevê-la
69
+ // como `|| efeito < 0` em vez de a testar primeiro daria o mesmo resultado hoje e mentiria sobre a ordem.
70
+ const cor = ctx.copiouAResposta ? 'laranja'
71
+ : veredicto.efeito > 0 ? 'roxa'
72
+ : veredicto.efeito < 0 ? 'laranja' : 'nenhuma';
73
+ return { skill, segmentos, cor, veredicto };
74
+ }
75
+ /**
76
+ * O histórico DEPOIS de a barra ter sinalizado — porque «sobe» leva consigo «e a contagem zera».
77
+ *
78
+ * ⚠️ O ZERAR ESTÁ ESCRITO PARA O ROXO E INFERIDO PARA O LARANJA, e a inferência fica declarada aqui em vez de
79
+ * escondida no código. O ADR-0049 §5 diz «ALL PURPLE — eight blues: the level rises, AND THE COUNT RESETS», e
80
+ * não diz nada equivalente para o laranja. Mas não zerar no laranja não é neutro: os quatro vermelhos
81
+ * continuariam na janela e a barra voltaria a mandar descer a CADA questão nova, até eles rolarem para fora
82
+ * — quatro descidas de nível onde a decisão foi uma. Um registo que manda descer de nível não pode, pelo
83
+ * mesmo facto, mandar descer quatro vezes.
84
+ *
85
+ * Fica como pergunta para o Dev; enquanto ele não a responder, esta é a leitura que não produz um defeito.
86
+ */
87
+ export function aposSinalizar(historico, barra) {
88
+ return barra.cor === 'nenhuma' ? historico : [];
89
+ }
@@ -101,6 +101,7 @@ const en = {
101
101
  'touch.dir.cross': 'cross (D-pad)',
102
102
  'touch.dir.stick': 'analogue stick',
103
103
  'sr.touch.slotSet': '{slot}: {acao}.',
104
+ 'sr.touch.slotSetUnnamed': '{slot}: set.',
104
105
  'sr.touch.dirSet': 'Directional: {tipo}.',
105
106
  'sr.touch.presetChild': 'Controls sized for a child hand (6 to 12 years).',
106
107
  'sr.touch.presetAdult': 'Controls sized for an adult hand.',
@@ -115,9 +116,15 @@ const en = {
115
116
  'act.especial': 'Special',
116
117
  'key.space': 'Space',
117
118
  'ctrl.change': 'Change',
119
+ 'ctrl.pressing': 'Press…',
120
+ 'ctrl.editingYours': 'Editing your controls — {modo} mode.',
121
+ 'ctrl.mode.one': '1 player',
122
+ 'ctrl.mode.many': '{n} players',
118
123
  'ctrl.changeKeyAria': 'Change the {acao} key for Player {n}',
119
124
  'sr.ctrl.pressNewKey': 'Press the new key for {acao}, Player {n}, or Esc to cancel.',
120
125
  'sr.ctrl.keyTaken': 'That key already belongs to Player {n}. Pick another, or Esc to cancel.',
126
+ 'sr.ctrl.keyTakenHere': 'That key already belongs to {acao}. Choose another, or Esc to cancel.',
127
+ 'sr.ctrl.keyTakenHereUnnamed': 'That key is already in use on this controller. Choose another, or Esc to cancel.',
121
128
  'sr.ctrl.reset': 'Controls restored to their defaults.',
122
129
  // Audio panel (#audio) — per-player outputs, sound/narration, cane, speech engines.
123
130
  'audio.playerN': 'Player {n}',
@@ -206,6 +213,11 @@ const en = {
206
213
  'pad.glyph.circle': 'circle',
207
214
  'pad.glyph.square': 'square',
208
215
  'pad.glyph.triangle': 'triangle',
216
+ 'pad.wiz.step': '{n} of {total} — press: {acao}',
217
+ 'pad.wiz.mapped': 'Mapped: {lista}',
218
+ 'pad.wiz.pressAny': 'Press ANY button on the controller you want to map.',
219
+ 'pad.wiz.detected': 'New controller detected: {id}. The game paused so you can set it up. RELEASE everything to start.',
220
+ 'pad.wiz.releaseAll': 'Controller: {id}. Now RELEASE everything.',
209
221
  'menu.legendSpoken': 'Button {sim} to select, button {nao} to go back.',
210
222
  'sr.menu.fractionsIntro': 'Adding and subtracting fractions: choose the notation and the type.',
211
223
  'sr.menu.keepOneNotation': 'Keep at least one notation on.',
@@ -231,6 +243,16 @@ const en = {
231
243
  // ===== THE REACH NOTICE (issue #112, ADR-0079 section 3) =====
232
244
  'reach.titulo': 'This game uses {pedidas} actions.',
233
245
  'reach.curto': 'The {transporte} has {lugares} places - not enough for all of them.',
246
+ 'reach.naoSegura': 'The {transporte} holds {segura} buttons at a time, and this game needs {pedidas} together.',
247
+ 'eixo.tema.titulo': 'Theme (contrast)',
248
+ 'eixo.tema.padrao': 'Default theme',
249
+ 'eixo.correcao.titulo': 'Colour correction',
250
+ 'eixo.correcao.tricro': 'Trichromatic vision',
251
+ 'viz.escolher': 'Select',
252
+ 'viz.escolhido': '✓ Selected',
253
+ 'sim.indisponivel.tema': 'To see the simulation, the theme needs to be at its default: over high contrast it would show what the theme does, not what the condition does.',
254
+ 'sim.indisponivel.correcao': 'To see the simulation, colour correction needs to be at trichromatic vision: from an already-corrected screen it would show neither the condition nor the correction.',
255
+ 'sim.indisponivel.ambos': 'To see the simulation, both the theme and colour correction need to be at their defaults: over an adjustment, the demonstration shows the adjustment and not the condition.',
234
256
  'reach.ligue': 'Connect {saida} and you can use them all.',
235
257
  'reach.semSaida': 'No control on this device reaches all of them. You can still play, but some actions will have nowhere to go.',
236
258
  'reach.continuar': 'Play anyway',
@@ -304,6 +326,8 @@ const en = {
304
326
  'sr.quiz.wellDone': 'Well done! {palavra}. {n} out of 3.',
305
327
  'sr.quiz.correctSoFar': 'Correct! {n} out of 3 to win the coin.',
306
328
  'menu.restoreDefaults': 'Restore this menu to its defaults',
329
+ 'menu.close': 'Close',
330
+ 'menu.typo': 'Typography',
307
331
  'sr.audio.reset': 'Hearing accessibility restored to its defaults. The other menus did not change.',
308
332
  'sr.empathy.reset': 'Empathy mode restored to its defaults: simulations off. Colour-blindness corrections and the other menus did not change.',
309
333
  'sr.motor.reset': 'Easy Mode and latched movement restored to their defaults. Eye control and the key mapping are unchanged.',
@@ -397,6 +421,10 @@ const en = {
397
421
  'sr.motor.easyOff': 'Easy Mode off.',
398
422
  'sr.motor.toggleMoveOn': 'Tap-to-move on: tap a direction to walk without holding; tap again to stop; hold to go faster. Jumping does not interrupt the walk.',
399
423
  'sr.motor.toggleMoveOff': 'Tap-to-move off.',
424
+ 'alt.exigida.olhos': 'Gaze control needs tap-to-move to work.',
425
+ 'alt.exigida.rosto': 'Face control needs tap-to-move to work.',
426
+ 'alt.exigida.gestos': 'Gesture control needs tap-to-move to work.',
427
+ 'alt.exigida.fala': 'Voice command needs tap-to-move to work.',
400
428
  'sr.motor.toggleRunOn': 'Run toggle on.',
401
429
  'sr.motor.toggleRunOff': 'Run toggle off.',
402
430
  'sr.physics.runLatchOn': 'Running.',
@@ -541,18 +569,23 @@ const en = {
541
569
  'legend.run': 'run',
542
570
  'legend.swap': 'switch',
543
571
  // ===================== FONT CATALOGUE =====================
572
+ 'font.grupo.rotulo': 'Letter family',
544
573
  'font.group.sans': 'Sans serif',
545
574
  'font.group.serif': 'Serif',
546
575
  'font.group.hand': 'Handwritten',
576
+ 'font.group.arcade': 'Arcade',
547
577
  'font.desc.atkinson': 'made by the Braille Institute for people with low vision (the game default)',
548
578
  'font.desc.lexend': 'made to reduce visual stress and serve dyslexic readers (turns on the extra spacing)',
549
579
  'font.desc.quattro': 'built to ease the eye strain of long hours on a screen',
550
580
  'font.desc.andika': 'based on Sassoon; the fruit of research into how children read and write',
581
+ 'font.desc.opendyslexic': 'Letters weighted at the bottom, so they do not flip upside down as you read.',
582
+ 'font.desc.fondamento': 'Pen calligraphy, for the handwriting activities.',
551
583
  'font.desc.greatvibes': 'English calligraphy',
552
584
  'font.desc.pinyon': 'English calligraphy',
553
585
  'font.desc.ufcook': 'German blackletter',
554
586
  'font.desc.ufmag': 'German blackletter',
555
587
  'font.desc.comicneue': 'ball and stick (early literacy)',
588
+ 'font.desc.pressstart': '8-bit pixel (game HUD and title)',
556
589
  'font.desc.learningcurve': 'English cursive',
557
590
  'font.desc.kindergarten': 'Brazilian cursive',
558
591
  'font.off.pending': 'licence to be confirmed — not shipped yet',
@@ -102,6 +102,7 @@ const es = {
102
102
  'touch.dir.cross': 'cruceta (D-pad)',
103
103
  'touch.dir.stick': 'palanca analógica',
104
104
  'sr.touch.slotSet': '{slot}: {acao}.',
105
+ 'sr.touch.slotSetUnnamed': '{slot}: asignado.',
105
106
  'sr.touch.dirSet': 'Direccional: {tipo}.',
106
107
  'sr.touch.presetChild': 'Controles del tamaño de una mano infantil (6 a 12 años).',
107
108
  'sr.touch.presetAdult': 'Controles del tamaño de una mano adulta.',
@@ -116,9 +117,15 @@ const es = {
116
117
  'act.especial': 'Especial',
117
118
  'key.space': 'Espacio',
118
119
  'ctrl.change': 'Cambiar',
120
+ 'ctrl.pressing': 'Pulsa…',
121
+ 'ctrl.editingYours': 'Editando tu mando — modo {modo}.',
122
+ 'ctrl.mode.one': '1 jugador',
123
+ 'ctrl.mode.many': '{n} jugadores',
119
124
  'ctrl.changeKeyAria': 'Cambiar la tecla de {acao} del Jugador {n}',
120
125
  'sr.ctrl.pressNewKey': 'Pulsa la nueva tecla para {acao} del Jugador {n}, o Esc para cancelar.',
121
126
  'sr.ctrl.keyTaken': 'Esa tecla ya es del Jugador {n}. Elige otra, o Esc para cancelar.',
127
+ 'sr.ctrl.keyTakenHere': 'Esa tecla ya es de {acao}. Elige otra, o Esc para cancelar.',
128
+ 'sr.ctrl.keyTakenHereUnnamed': 'Esa tecla ya está en uso en este control. Elige otra, o Esc para cancelar.',
122
129
  'sr.ctrl.reset': 'Controles restaurados a los valores predeterminados.',
123
130
  // Panel de audio (#audio) — salidas por jugador, sonido/narración, bastón, motores de voz.
124
131
  'audio.playerN': 'Jugador {n}',
@@ -207,6 +214,11 @@ const es = {
207
214
  'pad.glyph.circle': 'círculo',
208
215
  'pad.glyph.square': 'cuadrado',
209
216
  'pad.glyph.triangle': 'triángulo',
217
+ 'pad.wiz.step': '{n} de {total} — pulsa: {acao}',
218
+ 'pad.wiz.mapped': 'Asignados: {lista}',
219
+ 'pad.wiz.pressAny': 'Pulsa CUALQUIER botón del mando que quieras configurar.',
220
+ 'pad.wiz.detected': 'Mando nuevo detectado: {id}. El juego se pausó para que puedas configurarlo. SUELTA todo para empezar.',
221
+ 'pad.wiz.releaseAll': 'Mando: {id}. Ahora SUELTA todo.',
210
222
  'menu.legendSpoken': 'Botón {sim} para confirmar, botón {nao} para volver.',
211
223
  'sr.menu.fractionsIntro': 'Suma y resta de fracciones: elige la notación y el tipo.',
212
224
  'sr.menu.keepOneNotation': 'Deja al menos una notación activada.',
@@ -231,6 +243,16 @@ const es = {
231
243
  // ===== EL AVISO DE ALCANCE (issue #112, ADR-0079 seccion 3) =====
232
244
  'reach.titulo': 'Este juego usa {pedidas} acciones.',
233
245
  'reach.curto': 'El {transporte} tiene {lugares} lugares: no alcanzan para todas.',
246
+ 'reach.naoSegura': 'El {transporte} sostiene {segura} botones a la vez, y este juego pide {pedidas} al mismo tiempo.',
247
+ 'eixo.tema.titulo': 'Tema (contraste)',
248
+ 'eixo.tema.padrao': 'Tema predeterminado',
249
+ 'eixo.correcao.titulo': 'Corrección de color',
250
+ 'eixo.correcao.tricro': 'Visión tricromática',
251
+ 'viz.escolher': 'Seleccionar',
252
+ 'viz.escolhido': '✓ Seleccionado',
253
+ 'sim.indisponivel.tema': 'Para ver la simulación, el tema tiene que estar en su valor predeterminado: sobre el alto contraste mostraría lo que hace el tema, y no lo que hace la deficiencia.',
254
+ 'sim.indisponivel.correcao': 'Para ver la simulación, la corrección de color tiene que estar en visión tricromática: desde una pantalla ya corregida no mostraría ni la deficiencia ni la corrección.',
255
+ 'sim.indisponivel.ambos': 'Para ver la simulación, el tema y la corrección de color tienen que estar en sus valores predeterminados: sobre un ajuste, la demostración muestra el ajuste y no la deficiencia.',
234
256
  'reach.ligue': 'Conecta {saida} y podrás usarlas todas.',
235
257
  'reach.semSaida': 'Ningún control de este aparato alcanza todas. Puedes jugar igual, pero algunas acciones quedarán sin lugar.',
236
258
  'reach.continuar': 'Jugar así',
@@ -304,6 +326,8 @@ const es = {
304
326
  'sr.quiz.wellDone': '¡Muy bien! {palavra}. {n} de 3.',
305
327
  'sr.quiz.correctSoFar': '¡Acertaste! {n} de 3 para ganar la moneda.',
306
328
  'menu.restoreDefaults': 'Restaurar los valores predeterminados de este menú',
329
+ 'menu.close': 'Cerrar',
330
+ 'menu.typo': 'Tipografía',
307
331
  'sr.audio.reset': 'Accesibilidad auditiva restaurada a sus valores predeterminados. Los demás menús no cambiaron.',
308
332
  'sr.empathy.reset': 'Modo empatía restaurado a sus valores predeterminados: simulaciones desactivadas. Las correcciones de daltonismo y los demás menús no cambiaron.',
309
333
  'sr.motor.reset': 'Modo Fácil y movimiento por alternancia restaurados a sus valores predeterminados. El control con los ojos y el mapeo de teclas siguen igual.',
@@ -397,6 +421,10 @@ const es = {
397
421
  'sr.motor.easyOff': 'Modo Fácil desactivado.',
398
422
  'sr.motor.toggleMoveOn': 'Movimiento por alternancia activado: toca la dirección para andar sin mantener; toca de nuevo para parar; mantén para ir más rápido. El salto no interrumpe la marcha.',
399
423
  'sr.motor.toggleMoveOff': 'Movimiento por alternancia desactivado.',
424
+ 'alt.exigida.olhos': 'El control por mirada necesita el movimiento por alternancia para funcionar.',
425
+ 'alt.exigida.rosto': 'El control por rostro necesita el movimiento por alternancia para funcionar.',
426
+ 'alt.exigida.gestos': 'Los gestos necesitan el movimiento por alternancia para funcionar.',
427
+ 'alt.exigida.fala': 'El comando de voz necesita el movimiento por alternancia para funcionar.',
400
428
  'sr.motor.toggleRunOn': 'Alternancia de correr activada.',
401
429
  'sr.motor.toggleRunOff': 'Alternancia de correr desactivada.',
402
430
  'sr.physics.runLatchOn': 'Corriendo.',
@@ -539,18 +567,23 @@ const es = {
539
567
  'legend.run': 'correr',
540
568
  'legend.swap': 'cambiar',
541
569
  // ===================== CATÁLOGO DE FUENTES =====================
570
+ 'font.grupo.rotulo': 'Familia de letra',
542
571
  'font.group.sans': 'Sin serifa',
543
572
  'font.group.serif': 'Con serifa',
544
573
  'font.group.hand': 'Manuscrita',
574
+ 'font.group.arcade': 'Arcade',
545
575
  'font.desc.atkinson': 'hecha por el Braille Institute para personas con baja visión (la predeterminada del juego)',
546
576
  'font.desc.lexend': 'hecha para reducir el estrés visual y atender a personas disléxicas (activa el espaciado extra)',
547
577
  'font.desc.quattro': 'creada para reducir la fatiga visual de quien pasa mucho tiempo ante la pantalla',
548
578
  'font.desc.andika': 'basada en la Sassoon; fruto de la investigación sobre cómo leen y escriben los niños',
579
+ 'font.desc.opendyslexic': 'Letras con la base más pesada, para que no se den vuelta al leer.',
580
+ 'font.desc.fondamento': 'Caligráfica de pluma, para las actividades de escritura a mano.',
549
581
  'font.desc.greatvibes': 'caligráfica inglesa',
550
582
  'font.desc.pinyon': 'caligráfica inglesa',
551
583
  'font.desc.ufcook': 'blackletter alemana',
552
584
  'font.desc.ufmag': 'blackletter alemana',
553
585
  'font.desc.comicneue': 'bola y palo (alfabetización)',
586
+ 'font.desc.pressstart': 'píxel de 8 bits (HUD y título del juego)',
554
587
  'font.desc.learningcurve': 'cursiva inglesa',
555
588
  'font.desc.kindergarten': 'cursiva brasileña',
556
589
  'font.off.pending': 'licencia por confirmar — aún no incluida',