@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
|
@@ -6,17 +6,25 @@
|
|
|
6
6
|
// production). Overlay open/close plumbing (frontOverlay, focus management, the #typo hidden toggle, Escape
|
|
7
7
|
// handling) is the SHARED helper used by every settings panel and stays in game.js. The font catalog itself
|
|
8
8
|
// (FONT_GROUPS/FONT_BY_KEY) stays in ./fonts.js (Phase 2 extraction) — imported here, never duplicated.
|
|
9
|
-
|
|
9
|
+
// (`toggleLabel` saiu daqui em 2026-09-07: ele devolve «Ligado»/«Desligado», e este menu é uma ESCOLHA.)
|
|
10
10
|
import { t } from '../core/i18n.js';
|
|
11
|
-
import { FONT_GROUPS, FONT_BY_KEY, DEFAULT_FONT_KEY } from './fonts.js';
|
|
11
|
+
import { FONT_GROUPS, FONT_BY_KEY, DEFAULT_FONT_KEY, papelDaFonte, faceDisponivel } from './fonts.js';
|
|
12
12
|
import { markChanged, markMenuChanged, CHANGED_CLASS } from './changed-mark.js';
|
|
13
13
|
// ---------------------------------------------------------------------------------------------
|
|
14
14
|
// Pure logic (no `document`, testable in node)
|
|
15
15
|
// ---------------------------------------------------------------------------------------------
|
|
16
|
-
/**
|
|
17
|
-
|
|
16
|
+
/**
|
|
17
|
+
* A key is selectable when it exists in the catalog, is not marked `.off` (licence pending, etc.) — and is
|
|
18
|
+
* `geral`.
|
|
19
|
+
*
|
|
20
|
+
* ⚠️ O TERCEIRO TERMO ENTROU EM 2026-09-07 (issue #87), e sem ele o resto da mudança seria decoração: o menu
|
|
21
|
+
* deixaria de OFERECER as caligráficas e elas continuariam SELECIONÁVEIS por qualquer outro caminho — o
|
|
22
|
+
* `resolveFontKey` de uma chave guardada, um `data-font` num markup de consumidor. «Não está na lista» e «não
|
|
23
|
+
* pode ser escolhida» têm de ser a mesma afirmação, ou a lista é só uma sugestão.
|
|
24
|
+
*/
|
|
25
|
+
export function isSelectableFont(k, instalada) {
|
|
18
26
|
const it = FONT_BY_KEY[k];
|
|
19
|
-
return !!it &&
|
|
27
|
+
return !!it && faceDisponivel(it, instalada) && papelDaFonte(it) === 'geral';
|
|
20
28
|
}
|
|
21
29
|
// A semantica da chave (validacao + migracao da chave antiga) mora em ui/fonts.ts, que e o dono do
|
|
22
30
|
// catalogo; re-exportada aqui para quem ja consome este modulo. Uma implementacao, nao duas.
|
|
@@ -38,35 +46,76 @@ export function fontCssTarget(k, it) {
|
|
|
38
46
|
const suffix = it.fb === 'serif' ? ',Georgia,serif' : it.fb === 'cursive' ? ',cursive' : '';
|
|
39
47
|
return { fonte: 'custom', customFamily: `'${it.fam}'${suffix}` };
|
|
40
48
|
}
|
|
49
|
+
/**
|
|
50
|
+
* UMA linha da lista, a partir de uma face do catálogo. Extraída em 2026-09-07 (issue #87) porque o gate do
|
|
51
|
+
* mecanismo `.off` precisava de a exercitar com uma face de MENTIRA: as duas únicas entradas desligadas
|
|
52
|
+
* saíram do roster, e um caso que dependa da composição do catálogo reprova sempre que o roster muda.
|
|
53
|
+
*
|
|
54
|
+
* O mecanismo fica, e é preciso: o item 4 da #87 usa-o para a **Ronde**, que só pode ser oferecida se uma de
|
|
55
|
+
* três faces estiver instalada, porque duas delas são gratuitas apenas para uso pessoal e não podem ser
|
|
56
|
+
* empacotadas.
|
|
57
|
+
*/
|
|
58
|
+
export function linhaDaFonte(it, fontKey, instalada) {
|
|
59
|
+
// ⚠️ A MESMA pergunta que o `isSelectableFont` faz, pela MESMA função. Duas respostas dariam uma linha
|
|
60
|
+
// clicável que o clique recusa — ou, pior, uma linha cinzenta que o `resolveFontKey` aceita por outro
|
|
61
|
+
// caminho. «Não está disponível» e «não pode ser escolhida» têm de ser a mesma afirmação.
|
|
62
|
+
const disabled = !faceDisponivel(it, instalada);
|
|
63
|
+
// `d` e `off` também guardam CHAVE. O travessão que junta os dois é pontuação, não frase — as duas
|
|
64
|
+
// metades são independentes e cada uma traduz por si.
|
|
65
|
+
const desc = it.d ? t(it.d) : '', motivo = it.off ? t(it.off) : '';
|
|
66
|
+
const note = desc ? desc + (disabled ? ' — ' + motivo : '') : disabled ? motivo : '';
|
|
67
|
+
return { key: it.k, fam: it.fam, selected: fontKey === it.k, disabled, note };
|
|
68
|
+
}
|
|
41
69
|
/** Pure view-model for the typography list: which row is selected/disabled and its note, per catalog group. */
|
|
42
|
-
export function typoGroups(fontKey) {
|
|
70
|
+
export function typoGroups(fontKey, instalada) {
|
|
71
|
+
// ⚠️ SÓ AS GERAIS ENTRAM NA LISTA (emenda do ADR-0012, issue #87). As caligráficas existem para a criança
|
|
72
|
+
// APRENDER a ler letra cursiva — isso é matéria, e vive DENTRO das atividades, em botões próprios. Oferecê-
|
|
73
|
+
// las aqui é dar-lhe a matéria como obstáculo em todo lugar onde ela só quer navegar o menu.
|
|
74
|
+
//
|
|
75
|
+
// Um grupo que fique sem nenhuma face geral desaparece da lista, em vez de aparecer como título vazio.
|
|
43
76
|
return FONT_GROUPS.map((g) => ({
|
|
44
77
|
g: t(g.g), // `g` guarda CHAVE i18n desde o item 14 (ver ui/fonts)
|
|
45
|
-
rows: g.items.map((it) =>
|
|
46
|
-
|
|
47
|
-
// `d` e `off` também guardam CHAVE. O travessão que junta os dois é pontuação, não frase — as duas
|
|
48
|
-
// metades são independentes e cada uma traduz por si.
|
|
49
|
-
const desc = it.d ? t(it.d) : '', motivo = it.off ? t(it.off) : '';
|
|
50
|
-
const note = desc ? desc + (disabled ? ' — ' + motivo : '') : disabled ? motivo : '';
|
|
51
|
-
return { key: it.k, fam: it.fam, selected: fontKey === it.k, disabled, note };
|
|
52
|
-
}),
|
|
53
|
-
}));
|
|
78
|
+
rows: g.items.filter((it) => papelDaFonte(it) === 'geral').map((it) => linhaDaFonte(it, fontKey, instalada)),
|
|
79
|
+
})).filter((grupo) => grupo.rows.length > 0);
|
|
54
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* ⚠️ A MARCA DE SELEÇÃO, e ela existe porque COR NÃO É ESTADO.
|
|
83
|
+
*
|
|
84
|
+
* O `.mode-btn.is-on` pinta o botão com `var(--accent)` — o fundo amarelo que a emenda do ADR-0012 pede
|
|
85
|
+
* por extenso. Mas quem não distingue a cor não vê estado nenhum, e é a mesma razão pela qual
|
|
86
|
+
* `ui/activities-menu.ts:656` já emite ☑/☐ ao lado do `aria-checked`: «o estado em DUAS formas, e nenhuma
|
|
87
|
+
* delas é cor».
|
|
88
|
+
*/
|
|
89
|
+
const MARCA_ESCOLHIDA = '●';
|
|
90
|
+
const MARCA_ALTERNATIVA = '○';
|
|
55
91
|
function rowHTML(row) {
|
|
56
92
|
const noteHTML = row.note
|
|
57
93
|
? `<br><span class="opt-hint" style="margin:0;font-family:var(--font)">${row.note}</span>`
|
|
58
94
|
: '';
|
|
59
95
|
const ariaLabel = row.fam + (row.note ? ' — ' + row.note : '');
|
|
60
|
-
const stateLabel = toggleLabel(row.selected);
|
|
61
96
|
return (`<div class="ctrl-row"><span style="font-family:'${row.fam}'"><strong>${row.fam}</strong>${noteHTML}</span>` +
|
|
62
|
-
`<button class="mode-btn
|
|
63
|
-
`${row.disabled ? ' disabled' : ''} aria-
|
|
97
|
+
`<button class="mode-btn${row.selected ? ' is-on' : ''}" data-font="${row.key}" type="button" role="radio"` +
|
|
98
|
+
`${row.disabled ? ' disabled' : ''} aria-checked="${row.selected}" aria-label="${ariaLabel}">` +
|
|
99
|
+
`${row.selected ? MARCA_ESCOLHIDA : MARCA_ALTERNATIVA}</button></div>`);
|
|
64
100
|
}
|
|
65
|
-
/**
|
|
66
|
-
|
|
67
|
-
|
|
101
|
+
/**
|
|
102
|
+
* Full innerHTML for #typo-list, given the currently selected key. Pure string building — no DOM.
|
|
103
|
+
*
|
|
104
|
+
* ⚠️ É UM GRUPO DE RÁDIO SÓ, ATRAVESSANDO AS TRÊS SECÇÕES, e isso é a decisão e não um detalhe de
|
|
105
|
+
* marcação: a exclusividade é do MENU inteiro — uma fonte activa no total —, não de cada família. Três
|
|
106
|
+
* grupos diriam a quem escuta que dá para ter uma sans E uma serif ao mesmo tempo.
|
|
107
|
+
*
|
|
108
|
+
* ⚠️ E ISTO DEIXOU DE SER UM INTERRUPTOR EM 2026-09-07. Era `class="mode-btn switch"` + `aria-pressed` +
|
|
109
|
+
* `toggleLabel()`, ou seja dezassete interruptores independentes anunciando «Ligado»/«Desligado» para
|
|
110
|
+
* escolher UMA fonte. A emenda do ADR-0012 diz o contrário em tantas palavras: «THE MENU IS A CHOICE, NOT
|
|
111
|
+
* A TOGGLE […] One font is active; the others are alternatives, not switches.» O `switch` do
|
|
112
|
+
* `style.css:427` desenha uma chave de 52×28 px com bolinha — era o desenho de um estado que não existe.
|
|
113
|
+
*/
|
|
114
|
+
export function typoListHTML(fontKey, instalada) {
|
|
115
|
+
const grupos = typoGroups(fontKey, instalada)
|
|
68
116
|
.map((group) => `<h3 class="panel-sub">${group.g}</h3>` + group.rows.map(rowHTML).join(''))
|
|
69
117
|
.join('');
|
|
118
|
+
return `<div role="radiogroup" aria-label="${t('font.grupo.rotulo')}">${grupos}</div>`;
|
|
70
119
|
}
|
|
71
120
|
// ---------------------------------------------------------------------------------------------
|
|
72
121
|
// DOM-facing (thin) — requires `document`/injected ctx
|
|
@@ -75,7 +124,9 @@ export function initSettingsTypo(ctx) {
|
|
|
75
124
|
let fontKey = resolveFontKey(ctx.store);
|
|
76
125
|
function setFont(k, announce = false) {
|
|
77
126
|
const it = FONT_BY_KEY[k];
|
|
78
|
-
|
|
127
|
+
// A MESMA função das outras duas leituras: uma face que a lista mostra clicável tem de ser aceite aqui,
|
|
128
|
+
// e uma que ela mostra cinzenta tem de ser recusada. Três respostas à mesma pergunta divergem.
|
|
129
|
+
if (!it || !faceDisponivel(it, ctx.fonteInstalada))
|
|
79
130
|
return;
|
|
80
131
|
fontKey = k;
|
|
81
132
|
persistFontKey(ctx.store, k);
|
|
@@ -95,7 +146,7 @@ export function initSettingsTypo(ctx) {
|
|
|
95
146
|
const el = ctx.$('#typo-list');
|
|
96
147
|
if (!el)
|
|
97
148
|
return;
|
|
98
|
-
el.innerHTML = typoListHTML(fontKey);
|
|
149
|
+
el.innerHTML = typoListHTML(fontKey, ctx.fonteInstalada);
|
|
99
150
|
el.querySelectorAll('button[data-font]').forEach((b) => {
|
|
100
151
|
b.addEventListener('click', () => {
|
|
101
152
|
const k = b.dataset.font;
|
|
@@ -50,7 +50,14 @@ export interface SettingsVisualCtx {
|
|
|
50
50
|
/** Desenha UMA lista de rádio de modos visuais (+ abas por jogador). O MESMO helper que o painel de
|
|
51
51
|
* empatia usa — de propósito: as três correções mudaram de menu, e mudar junto a aparência delas faria a
|
|
52
52
|
* criança ter de reaprender um controle que ela já conhecia. */
|
|
53
|
-
|
|
53
|
+
/**
|
|
54
|
+
* Os DOIS eixos deste painel (#104). Substituiu o `renderVizGroup`, que fica com o painel de EMPATIA.
|
|
55
|
+
*
|
|
56
|
+
* ⚠️ A lista de sete que este painel oferecia era a forma honesta de contar uma exclusividade REAL, e o
|
|
57
|
+
* `VISUAL_MODES` explica-a em prosa logo acima. Ela deixou de existir: o estado tem dois eixos, e os
|
|
58
|
+
* escritores por eixo mexem num sem tocar no outro.
|
|
59
|
+
*/
|
|
60
|
+
renderEixosVisuais: (listSel: string, tabsSel: string) => void;
|
|
54
61
|
setLq: (t: number) => void;
|
|
55
62
|
setOwnerColors: (on: boolean) => void;
|
|
56
63
|
setCbSafe: (on: boolean) => void;
|
|
@@ -58,6 +65,19 @@ export interface SettingsVisualCtx {
|
|
|
58
65
|
setOutlineBg: (level: number) => void;
|
|
59
66
|
setRoleColor: (key: RoleKey, hex: string) => void;
|
|
60
67
|
resetRoleColors: () => void;
|
|
68
|
+
/**
|
|
69
|
+
* Move a prosa das linhas para o rodapé (`ui/settings-panel` → `fillExplain`). Chamado a CADA render.
|
|
70
|
+
*
|
|
71
|
+
* ⚠️ NÃO É OPCIONAL POR ELEGÂNCIA: `fillExplain` roda uma vez quando o overlay é frontalizado e move o
|
|
72
|
+
* `.opt-hint` de dentro de cada linha para o rodapé. Este painel RECONSTRÓI as linhas, e as linhas novas
|
|
73
|
+
* voltam com a prosa lá dentro — então a explicação aparece duas vezes, no rodapé e sob o rótulo, a
|
|
74
|
+
* partir do primeiro clique. O `CLAUDE.md` §4 regista exatamente isto, e a issue #109 já o consertou
|
|
75
|
+
* uma vez noutros painéis.
|
|
76
|
+
*
|
|
77
|
+
* Opcional na assinatura porque um consumidor pode montar o painel sem a casca (um teste, o segundo
|
|
78
|
+
* consumidor): sem casca não há rodapé para duplicar.
|
|
79
|
+
*/
|
|
80
|
+
fillExplain?: (card: HTMLElement | null) => void;
|
|
61
81
|
}
|
|
62
82
|
/**
|
|
63
83
|
* `viz` quando ele é um modo DESTE menu (contraste ou correção), senão 'normal'.
|
|
@@ -12,6 +12,8 @@ import { lqName as lqLabel } from '../render/lq-filter.js';
|
|
|
12
12
|
import { HC_ROLE_KEYS, HC_ROLE_DEF } from '../render/hc-role-data.js';
|
|
13
13
|
import { VIZ_CORRECTIONS, VIZ_MODES } from '../render/viz-modes.js';
|
|
14
14
|
import { DEFAULTS } from '../core/state.js';
|
|
15
|
+
// O padrao dos DOIS EIXOS (ADR-0076/#104). A marca pergunta ao modelo novo, nao ao espelho p.viz.
|
|
16
|
+
import { PADRAO as PADRAO_VISUAL } from '../render/viz-axes.js';
|
|
15
17
|
import { markChanged, markMenuChanged } from './changed-mark.js';
|
|
16
18
|
/** Contrast levels, in cycle order — mirrors game.js's HC_SEQ (also used there by the physical contrast-cycle button). */
|
|
17
19
|
export const CONTRAST_LEVELS = ['normal', 'hc-direto', 'hc-direto-45', 'hc-direto-7'];
|
|
@@ -77,6 +79,21 @@ export function rgbToHex(rgb) {
|
|
|
77
79
|
export function onOffLabel(on) {
|
|
78
80
|
return toggleLabel(on);
|
|
79
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Lê `player[i].visual` defensivamente, sem importar o tipo do jogador — o mesmo molde do `playerViz` abaixo,
|
|
84
|
+
* e pela mesma razão: `getPlayers()` devolve `readonly unknown[]` porque o tipo real é do JOGO (ADR-0033), e
|
|
85
|
+
* cada consumidor estreita para a SUA fatia.
|
|
86
|
+
*
|
|
87
|
+
* ⚠️ Sem jogador ou sem o campo, devolve o PADRÃO — que é a resposta certa para «esta criança mexeu em
|
|
88
|
+
* alguma coisa?»: quem não existe não mexeu. Inventar `hc7` aqui marcaria um menu que ninguém tocou.
|
|
89
|
+
*/
|
|
90
|
+
function playerVisual(list, i) {
|
|
91
|
+
const v = list[i]?.visual;
|
|
92
|
+
return {
|
|
93
|
+
tema: typeof v?.tema === 'string' ? v.tema : PADRAO_VISUAL.tema,
|
|
94
|
+
correcao: typeof v?.correcao === 'string' ? v.correcao : PADRAO_VISUAL.correcao,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
80
97
|
/** Reads player[i].viz defensively (no player at that index -> 'normal'), without a Player type import. */
|
|
81
98
|
function playerViz(list, i) {
|
|
82
99
|
const p = list[i];
|
|
@@ -144,7 +161,11 @@ export function initSettingsVisual(ctx) {
|
|
|
144
161
|
ctx.setSelectedPlayer(selected);
|
|
145
162
|
const contrastValue = resolveVisualMode(playerViz(ctx.getPlayers(), selected));
|
|
146
163
|
const settings = ctx.getVisualSettings();
|
|
147
|
-
|
|
164
|
+
// ⚠️ `renderEixosVisuais` E NÃO `renderVizGroup` desde a #104: este painel passou a ter DOIS controles,
|
|
165
|
+
// e o `renderVizGroup` continua a servir o painel de EMPATIA, cuja lista de simulações é mesmo exclusiva.
|
|
166
|
+
// Trocar o corpo daquela função em vez de acrescentar esta teria posto os dois eixos na lista de
|
|
167
|
+
// simulações — foi o que quase aconteceu, e o que a separação impede.
|
|
168
|
+
ctx.renderEixosVisuais('#visual-modes', '#visual-players');
|
|
148
169
|
el.innerHTML = renderVisualPanelHtml(contrastValue, settings);
|
|
149
170
|
const lq = ctx.$('#opt-lq');
|
|
150
171
|
const lqv = ctx.$('#opt-lq-val');
|
|
@@ -182,6 +203,8 @@ export function initSettingsVisual(ctx) {
|
|
|
182
203
|
rr.addEventListener('click', () => { ctx.resetRoleColors(); render(); });
|
|
183
204
|
reflectOutlines();
|
|
184
205
|
refreshMarks();
|
|
206
|
+
// A prosa volta para o rodapé depois de as linhas serem reconstruídas (CLAUDE.md §4, #109).
|
|
207
|
+
ctx.fillExplain?.(ctx.$('#visual .overlay__card'));
|
|
185
208
|
}
|
|
186
209
|
/**
|
|
187
210
|
* A marca de "saiu do padrão" (ADR-0029). Cada linha contra o SEU padrão, e o botão do menu por cima.
|
|
@@ -193,7 +216,27 @@ export function initSettingsVisual(ctx) {
|
|
|
193
216
|
*/
|
|
194
217
|
function refreshMarks() {
|
|
195
218
|
const s = ctx.getVisualSettings();
|
|
196
|
-
|
|
219
|
+
/**
|
|
220
|
+
* 🔴 A MARCA É POR EIXO DESDE 2026-09-08, e a granularidade que ela recupera foi perdida por mim na #104
|
|
221
|
+
* etapa 4d. Enquanto o `#visual-modes` era UM rádio de sete opções, marcar o contentor era marcar a
|
|
222
|
+
* escolha. A etapa 4d pôs DOIS eixos lá dentro, e uma marca no contentor deixou de dizer QUAL saiu do
|
|
223
|
+
* padrão — que é o terceiro canal do ADR-0029 a perder informação: «a criança cega percorre o menu e OUVE,
|
|
224
|
+
* em ordem, o que saiu do padrão». Ouvir «mudado» sem saber de quê manda-a procurar em oito linhas.
|
|
225
|
+
*
|
|
226
|
+
* ⚠️ E VEM DO MODELO NOVO. Isto era `resolveVisualMode(playerViz(...))`, que lê o `p.viz` que a etapa 1a
|
|
227
|
+
* marcou `@deprecated` — um leitor que ficou para trás na migração.
|
|
228
|
+
*
|
|
229
|
+
* 📌 E A REGRA DA SIMULAÇÃO DEIXA DE SER DERIVADA E PASSA A SER ESTRUTURAL, que é o ganho de fundo. Ela
|
|
230
|
+
* está escrita logo acima: uma simulação de empatia não marca ESTE menu, porque a marca dela pertence ao
|
|
231
|
+
* menu de empatia e é lá que ela leva a criança. Antes isso dependia de o `resolveVisualMode` responder
|
|
232
|
+
* `normal`; agora a simulação vive noutro campo do `VisualState`, e perguntar pelo tema e pela correcção
|
|
233
|
+
* nunca a alcança.
|
|
234
|
+
*/
|
|
235
|
+
const visual = playerVisual(ctx.getPlayers(), ctx.getSelectedPlayer());
|
|
236
|
+
const tema = visual.tema !== PADRAO_VISUAL.tema;
|
|
237
|
+
const correcao = visual.correcao !== PADRAO_VISUAL.correcao;
|
|
238
|
+
const linhaDoEixo = (eixo) => ctx.$(`#visual-modes button[data-eixo="${eixo}"][aria-checked="true"]`)
|
|
239
|
+
?.closest('.ctrl-row') ?? null;
|
|
197
240
|
const lqOff = s.lq !== DEFAULTS.lq;
|
|
198
241
|
const owner = s.ownerColors !== DEFAULTS.ownerColors;
|
|
199
242
|
const cb = s.cbSafe !== DEFAULTS.cbSafe;
|
|
@@ -201,14 +244,15 @@ export function initSettingsVisual(ctx) {
|
|
|
201
244
|
const bg = s.outlineBg !== DEFAULTS.hcOutlineBg;
|
|
202
245
|
const papeis = ROLE_KEYS.some((k) => !sameRgb(s.roleColors[k], HC_ROLE_DEF[k]));
|
|
203
246
|
const linha = (sel) => ctx.$(sel)?.closest('.ctrl-row') ?? null;
|
|
204
|
-
markChanged(
|
|
247
|
+
markChanged(linhaDoEixo('tema'), tema);
|
|
248
|
+
markChanged(linhaDoEixo('correcao'), correcao);
|
|
205
249
|
markChanged(linha('#opt-lq'), lqOff);
|
|
206
250
|
markChanged(linha('#opt-ownercolors'), owner);
|
|
207
251
|
markChanged(linha('#opt-cbsafe'), cb);
|
|
208
252
|
markChanged(linha('#opt-outline-fg'), fg);
|
|
209
253
|
markChanged(linha('#opt-outline-bg'), bg);
|
|
210
254
|
markChanged(linha('#opt-role-reset'), papeis);
|
|
211
|
-
markMenuChanged(ctx.$('[data-act="visual"]'), [
|
|
255
|
+
markMenuChanged(ctx.$('[data-act="visual"]'), [tema, correcao, lqOff, owner, cb, fg, bg, papeis]);
|
|
212
256
|
}
|
|
213
257
|
// ---- restaurar os padrões DESTE menu (ADR-0028) ----
|
|
214
258
|
//
|
package/dist-pkg/ui/shell.d.ts
CHANGED
|
@@ -32,9 +32,19 @@ export type { FatosDaCena } from '../core/scenes.js';
|
|
|
32
32
|
export interface PhaseView {
|
|
33
33
|
/** `#title-overlay`.hidden — o splash só aparece no título. */
|
|
34
34
|
titleOverlayHidden: boolean;
|
|
35
|
-
/**
|
|
36
|
-
*
|
|
37
|
-
|
|
35
|
+
/**
|
|
36
|
+
* ⚠️ `pauseOverlayHidden` SAIU EM 2026-09-08, e a ausência é a notícia.
|
|
37
|
+
*
|
|
38
|
+
* Ele era `true` em toda fase, e existia porque a Etapa 2 aposentou a pausa GLOBAL sem apagar o elemento: a
|
|
39
|
+
* casca continuava a procurá-lo e a escondê-lo a cada troca de fase, com dois gates a afirmar que ele ficava
|
|
40
|
+
* escondido. Código a segurar um cadáver.
|
|
41
|
+
*
|
|
42
|
+
* E o cadáver custava mais do que as linhas: aquelas quarenta linhas de `#pause-overlay` no `index.html` do
|
|
43
|
+
* cartucho eram o menu de pausa com aspecto mais OFICIAL do repositório — o que o próximo autor de cartucho
|
|
44
|
+
* copia junto com o ficheiro —, e já tinham derivado do que a engine gera (dois itens «Comunicação», e um
|
|
45
|
+
* `#opt-letra` que hoje sai do `dynLabel`). Um campo que diz «está escondido» aceita que ele exista; não o
|
|
46
|
+
* ter diz que ele não existe.
|
|
47
|
+
*/
|
|
38
48
|
/** `.screen-pause`.hidden de CADA tela — os menus por tela só aparecem na pausa. */
|
|
39
49
|
screenPauseHidden: boolean;
|
|
40
50
|
/** `setMasterMuted(...)` — GAG: fora de 'playing' TODO o som cala (loops de ambiente/chuva inclusive). */
|
package/dist-pkg/ui/shell.js
CHANGED
|
@@ -99,7 +99,6 @@ import { PM_ITENS_VISIVEIS } from './pause-icons.js';
|
|
|
99
99
|
export function phaseView(f) {
|
|
100
100
|
return {
|
|
101
101
|
titleOverlayHidden: !f.telaDeTitulo,
|
|
102
|
-
pauseOverlayHidden: true,
|
|
103
102
|
screenPauseHidden: !f.menuDePausa,
|
|
104
103
|
masterMuted: !f.mundoRodando,
|
|
105
104
|
hideTouchControls: !f.mundoRodando,
|
|
@@ -308,11 +307,11 @@ export function initShell(ctx) {
|
|
|
308
307
|
/* ===================== a troca de fase ===================== */
|
|
309
308
|
/** A metade IMPURA: pega a projeção pronta e a carimba no documento. */
|
|
310
309
|
function applyPhaseView(v) {
|
|
311
|
-
|
|
310
|
+
// A pausa GLOBAL não é procurada: ela foi aposentada na Etapa 2, e a casca deixou de a segurar em
|
|
311
|
+
// 2026-09-08. Ver a nota no `PhaseView`, onde o campo estava.
|
|
312
|
+
const t = ctx.$('#title-overlay');
|
|
312
313
|
if (t)
|
|
313
314
|
t.hidden = v.titleOverlayHidden;
|
|
314
|
-
if (pa)
|
|
315
|
-
pa.hidden = v.pauseOverlayHidden; // pausa GLOBAL aposentada (Etapa 2): agora é uma por tela
|
|
316
315
|
ctx.getPauseScreens().forEach((sp) => { sp.hidden = v.screenPauseHidden; });
|
|
317
316
|
}
|
|
318
317
|
/** O estado do `#touch-controls` ANTES de qualquer coisa desta troca de fase mexer nele. */
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import { type MotivoIndisponivel, type VisualState } from '../render/viz-axes.js';
|
|
3
|
+
/** Uma linha pronta a traduzir: a chave e o que ela precisa. `null` = não há nada a dizer. */
|
|
4
|
+
export interface Recusa {
|
|
5
|
+
readonly chave: string;
|
|
6
|
+
/** O que a criança tem de desfazer para a demonstração ser honesta. Entra na frase por `{eixo}`. */
|
|
7
|
+
readonly eixo: MotivoIndisponivel;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A chave i18n do motivo, por eixo fora do padrão.
|
|
11
|
+
*
|
|
12
|
+
* ⚠️ TRÊS CHAVES E NÃO UMA COM PARÂMETRO, e a diferença é de tradução e não de estilo: em português «o tema»
|
|
13
|
+
* e «a correção de cor» levam artigos diferentes, e «os dois» não é o plural de nenhum dos dois. Uma frase
|
|
14
|
+
* com `{eixo}` obrigaria cada idioma a montar concordância a partir de um substantivo solto — que é
|
|
15
|
+
* exactamente o defeito que o `sr.nav.clockOne` já registou para «às 1 horas».
|
|
16
|
+
*/
|
|
17
|
+
export declare const CHAVE_DO_MOTIVO: Readonly<Record<MotivoIndisponivel, string>>;
|
|
18
|
+
/**
|
|
19
|
+
* Esta simulação pode ser oferecida? E se não, o que dizer.
|
|
20
|
+
*
|
|
21
|
+
* `null` = pode; a interface não mostra nada, porque um aviso que aparece sempre deixa de ser lido.
|
|
22
|
+
*/
|
|
23
|
+
export declare function recusaDaSimulacao(v: VisualState): Recusa | null;
|
|
24
|
+
/**
|
|
25
|
+
* A simulação deve aparecer DESABILITADA em vez de sumir?
|
|
26
|
+
*
|
|
27
|
+
* ⚠️ SEMPRE, E É A METADE «never silently removed» DO ADR-0076. Existe como função com nome próprio, e não
|
|
28
|
+
* como um `!recusa` no ponto de uso, porque ela responde a uma pergunta diferente: `recusaDaSimulacao` diz
|
|
29
|
+
* *por que* não dá; esta diz *que a linha continua na tela*. Quem desenha precisa das duas, e juntá-las numa
|
|
30
|
+
* só faria a resposta «não há motivo» parecer «não desenhe a linha».
|
|
31
|
+
*/
|
|
32
|
+
export declare function mostraMesmoIndisponivel(): boolean;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// ui/simulation-refusal — POR QUE A SIMULAÇÃO NÃO ESTÁ DISPONÍVEL, dito à criança (ADR-0076, issue #104).
|
|
3
|
+
//
|
|
4
|
+
// ========================= O QUE A RECUSA TEM DE FAZER, E O QUE ELA NÃO PODE FAZER =========================
|
|
5
|
+
// O ADR-0076 exige que uma simulação indisponível apareça **VISÍVEL e explicada** — «never silently removed,
|
|
6
|
+
// never accepted then ignored». As duas metades dessa frase descrevem dois defeitos diferentes:
|
|
7
|
+
//
|
|
8
|
+
// · REMOVER EM SILÊNCIO ensina que a coisa não existe. Um adulto que ontem mostrou a simulação a uma turma
|
|
9
|
+
// e hoje não a acha conclui que ela foi tirada, e não que ele próprio ligou o alto contraste.
|
|
10
|
+
// · ACEITAR E IGNORAR é pior: a demonstração PARECE estar a correr. Sobre uma tela já corrigida, ela não
|
|
11
|
+
// mostra nem a deficiência nem a correção; sobre um tema de alto contraste, mostra o que o TEMA faz e não
|
|
12
|
+
// o que a deuteranopia faz. ⚠️ Não é uma demonstração mais fraca — **ela ensina uma coisa falsa**.
|
|
13
|
+
//
|
|
14
|
+
// ⚠️ E O MOTIVO É UM FACTO SOBRE A DEMONSTRAÇÃO, NUNCA UMA REPREENSÃO A QUEM ESCOLHEU. Quem ligou o alto
|
|
15
|
+
// contraste ligou-o porque precisa; quem ligou a correção de cor vê melhor com ela. A frase diz o que a
|
|
16
|
+
// demonstração precisa para ser honesta, e diz como voltar — nunca «desligue isso».
|
|
17
|
+
//
|
|
18
|
+
// ========================= POR QUE ISTO É UM MÓDULO PURO =========================
|
|
19
|
+
// Mesma forma do `ui/reach-notice`: devolve DADO (chave i18n + parâmetros), não texto pronto. A frase é da
|
|
20
|
+
// interface e tem de passar por `t()`; devolver português daqui repetiria o defeito que o `PADWIZ_STEPS` já
|
|
21
|
+
// deixou de cometer. E, sendo puro, o desenho da recusa é conferível no project `node`, sem documento.
|
|
22
|
+
//
|
|
23
|
+
// Módulo-folha: importa só os tipos e o predicado do modelo de dois eixos.
|
|
24
|
+
import { simulacaoIndisponivel } from '../render/viz-axes.js';
|
|
25
|
+
/**
|
|
26
|
+
* A chave i18n do motivo, por eixo fora do padrão.
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ TRÊS CHAVES E NÃO UMA COM PARÂMETRO, e a diferença é de tradução e não de estilo: em português «o tema»
|
|
29
|
+
* e «a correção de cor» levam artigos diferentes, e «os dois» não é o plural de nenhum dos dois. Uma frase
|
|
30
|
+
* com `{eixo}` obrigaria cada idioma a montar concordância a partir de um substantivo solto — que é
|
|
31
|
+
* exactamente o defeito que o `sr.nav.clockOne` já registou para «às 1 horas».
|
|
32
|
+
*/
|
|
33
|
+
export const CHAVE_DO_MOTIVO = Object.freeze({
|
|
34
|
+
tema: 'sim.indisponivel.tema',
|
|
35
|
+
correcao: 'sim.indisponivel.correcao',
|
|
36
|
+
ambos: 'sim.indisponivel.ambos',
|
|
37
|
+
});
|
|
38
|
+
/**
|
|
39
|
+
* Esta simulação pode ser oferecida? E se não, o que dizer.
|
|
40
|
+
*
|
|
41
|
+
* `null` = pode; a interface não mostra nada, porque um aviso que aparece sempre deixa de ser lido.
|
|
42
|
+
*/
|
|
43
|
+
export function recusaDaSimulacao(v) {
|
|
44
|
+
const motivo = simulacaoIndisponivel(v);
|
|
45
|
+
return motivo === null ? null : { chave: CHAVE_DO_MOTIVO[motivo], eixo: motivo };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* A simulação deve aparecer DESABILITADA em vez de sumir?
|
|
49
|
+
*
|
|
50
|
+
* ⚠️ SEMPRE, E É A METADE «never silently removed» DO ADR-0076. Existe como função com nome próprio, e não
|
|
51
|
+
* como um `!recusa` no ponto de uso, porque ela responde a uma pergunta diferente: `recusaDaSimulacao` diz
|
|
52
|
+
* *por que* não dá; esta diz *que a linha continua na tela*. Quem desenha precisa das duas, e juntá-las numa
|
|
53
|
+
* só faria a resposta «não há motivo» parecer «não desenhe a linha».
|
|
54
|
+
*/
|
|
55
|
+
export function mostraMesmoIndisponivel() {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
import { type Tema, type Correcao, type VisualState } from '../render/viz-axes.js';
|
|
3
|
+
/** O rótulo de cada tema. Chave i18n — quem exibe resolve, como todo o resto do menu. */
|
|
4
|
+
export declare const ROTULO_DO_TEMA: Readonly<Record<Tema, string>>;
|
|
5
|
+
/** O rótulo de cada correção de cor. */
|
|
6
|
+
export declare const ROTULO_DA_CORRECAO: Readonly<Record<Correcao, string>>;
|
|
7
|
+
/**
|
|
8
|
+
* Os rótulos CURTOS, para os ícones da barra rápida — «7:1», «deuteranopia».
|
|
9
|
+
*
|
|
10
|
+
* ⚠️ SÃO AS CHAVES QUE JÁ EXISTIAM (`contrast.*`, `cvd.*`), re-chaveadas por eixo. A barra rápida sempre
|
|
11
|
+
* falou curto porque anuncia UM ícone de cada vez, e o painel sempre falou por extenso porque a criança está
|
|
12
|
+
* a ler uma lista — a diferença é de contexto e sobrevive à divisão. Reaproveitar em vez de traduzir de novo
|
|
13
|
+
* é o que mantém a mesma palavra nos dois sítios.
|
|
14
|
+
*/
|
|
15
|
+
export declare const CURTO_DO_TEMA: Readonly<Record<Tema, string>>;
|
|
16
|
+
export declare const CURTO_DA_CORRECAO: Readonly<Record<Correcao, string>>;
|
|
17
|
+
/** Os dois eixos, como o painel os identifica no DOM. */
|
|
18
|
+
export type EixoVisual = 'tema' | 'correcao';
|
|
19
|
+
/** Um tradutor, igual ao que o resto da interface recebe. */
|
|
20
|
+
export type Tradutor = (chave: string, params?: Record<string, string | number>) => string;
|
|
21
|
+
/**
|
|
22
|
+
* As linhas de UM eixo, no formato de rádio que o painel já usa.
|
|
23
|
+
*
|
|
24
|
+
* ⚠️ RÁDIO E NÃO SETE BOTÕES, e o motivo sobrevive à divisão: DENTRO de um eixo os valores continuam
|
|
25
|
+
* exclusivos — um tema de cada vez, uma correção de cada vez. O que deixou de ser exclusivo é a relação
|
|
26
|
+
* ENTRE os eixos, e é por isso que eles viram dois rádios em vez de um.
|
|
27
|
+
*
|
|
28
|
+
* ⚠️ E MANTÉM-SE A FORMA DE LINHA VISÍVEL, não um `<select>`. O `settings-visual` regista o erro que o Dev
|
|
29
|
+
* apanhou na primeira tentativa: dentro de uma caixa fechada, um controle cuja razão de existir é ser ACHADO
|
|
30
|
+
* por quem enxerga mal fica «quase o mesmo que não ter movido».
|
|
31
|
+
*/
|
|
32
|
+
export declare function linhasDoEixo(eixo: EixoVisual, valores: readonly string[], rotulos: Readonly<Record<string, string>>, atual: string, t: Tradutor): string;
|
|
33
|
+
/**
|
|
34
|
+
* Os DOIS eixos, um a seguir ao outro, com um título cada.
|
|
35
|
+
*
|
|
36
|
+
* O título existe porque dois rádios seguidos sem nome são um rádio de oito para quem lê depressa — e essa
|
|
37
|
+
* leitura é exactamente o mal-entendido que a divisão existe para desfazer.
|
|
38
|
+
*/
|
|
39
|
+
export declare function eixosHtml(v: VisualState, t: Tradutor): string;
|
|
40
|
+
/** O que um clique num botão do painel quer dizer. `null` quando o botão não é de eixo nenhum. */
|
|
41
|
+
export interface EscolhaDeEixo {
|
|
42
|
+
readonly eixo: EixoVisual;
|
|
43
|
+
readonly valor: string;
|
|
44
|
+
}
|
|
45
|
+
export declare function escolhaDoBotao(dataset: {
|
|
46
|
+
eixo?: string;
|
|
47
|
+
valor?: string;
|
|
48
|
+
}): EscolhaDeEixo | null;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
2
|
+
// ui/visual-axes-panel — O MENU VISUAL COM DOIS CONTROLES (ADR-0076, issue #104), na metade pura.
|
|
3
|
+
//
|
|
4
|
+
// ========================= O COMENTÁRIO QUE AUTORIZA ESTA MUDANÇA =========================
|
|
5
|
+
// O `ui/settings-visual` justifica o controle ÚNICO com estas palavras, e elas continuam lá, verdadeiras
|
|
6
|
+
// sobre o dia em que foram escritas:
|
|
7
|
+
//
|
|
8
|
+
// «Os sete vivem num CONTROLE SÓ, e isso não é economia de espaço: `p.viz` guarda UM valor. Dois
|
|
9
|
+
// controles separados se sobrescreveriam em silêncio — a criança escolheria a correção, depois o
|
|
10
|
+
// contraste, e perderia a correção sem nada dizer que perdeu.»
|
|
11
|
+
//
|
|
12
|
+
// ⚠️ A RAZÃO QUE ELE DÁ DEIXOU DE EXISTIR. `p.viz` já não guarda um valor: guarda dois eixos mais a
|
|
13
|
+
// simulação, e os escritores por eixo (`setTemaDoJogador`/`setCorrecaoDoJogador`) mexem num sem tocar no
|
|
14
|
+
// outro. O controle único era a forma honesta de contar uma exclusividade REAL; mantê-lo agora seria contar
|
|
15
|
+
// uma exclusividade que já não existe — e continuar a negar os dois a quem precisa dos dois.
|
|
16
|
+
//
|
|
17
|
+
// ========================= OS NOMES DOS PADRÕES SÃO DECISÃO, NÃO ROTINA =========================
|
|
18
|
+
// ⚠️ «Tema padrão» e «Visão tricromática», e o ADR-0076 fecha a *definition of done* com a regra que os
|
|
19
|
+
// escolheu: **nenhum rótulo de padrão diagnostica quem lê**. `modo sem deficiência visual` foi oferecido e
|
|
20
|
+
// recusado — ele diz à criança o que ela NÃO é, no menu que ela abriu para conseguir jogar. Um padrão
|
|
21
|
+
// chama-se pelo que ele É.
|
|
22
|
+
//
|
|
23
|
+
// E `Modo padrão` (o `viz.normal` de hoje) deixa de servir aqui: era o neutro PARTILHADO, de quando os dois
|
|
24
|
+
// eixos eram um só. Com dois controles, um «padrão» sem dizer padrão de QUÊ é ambíguo em ambos.
|
|
25
|
+
//
|
|
26
|
+
// Módulo puro: devolve HTML e chaves i18n, não toca documento nenhum. Quem monta é o `render/viz-setters`.
|
|
27
|
+
import { TEMAS, CORRECOES } from '../render/viz-axes.js';
|
|
28
|
+
/** O rótulo de cada tema. Chave i18n — quem exibe resolve, como todo o resto do menu. */
|
|
29
|
+
export const ROTULO_DO_TEMA = Object.freeze({
|
|
30
|
+
padrao: 'eixo.tema.padrao',
|
|
31
|
+
hc3: 'viz.hc-direto',
|
|
32
|
+
hc45: 'viz.hc-direto-45',
|
|
33
|
+
hc7: 'viz.hc-direto-7',
|
|
34
|
+
});
|
|
35
|
+
/** O rótulo de cada correção de cor. */
|
|
36
|
+
export const ROTULO_DA_CORRECAO = Object.freeze({
|
|
37
|
+
tricro: 'eixo.correcao.tricro',
|
|
38
|
+
protan: 'viz.fix-protan',
|
|
39
|
+
deuter: 'viz.fix-deuter',
|
|
40
|
+
tritan: 'viz.fix-tritan',
|
|
41
|
+
});
|
|
42
|
+
/**
|
|
43
|
+
* Os rótulos CURTOS, para os ícones da barra rápida — «7:1», «deuteranopia».
|
|
44
|
+
*
|
|
45
|
+
* ⚠️ SÃO AS CHAVES QUE JÁ EXISTIAM (`contrast.*`, `cvd.*`), re-chaveadas por eixo. A barra rápida sempre
|
|
46
|
+
* falou curto porque anuncia UM ícone de cada vez, e o painel sempre falou por extenso porque a criança está
|
|
47
|
+
* a ler uma lista — a diferença é de contexto e sobrevive à divisão. Reaproveitar em vez de traduzir de novo
|
|
48
|
+
* é o que mantém a mesma palavra nos dois sítios.
|
|
49
|
+
*/
|
|
50
|
+
export const CURTO_DO_TEMA = Object.freeze({
|
|
51
|
+
padrao: 'contrast.off', hc3: 'contrast.3', hc45: 'contrast.45', hc7: 'contrast.7',
|
|
52
|
+
});
|
|
53
|
+
export const CURTO_DA_CORRECAO = Object.freeze({
|
|
54
|
+
tricro: 'cvd.off', protan: 'cvd.protan', deuter: 'cvd.deuter', tritan: 'cvd.tritan',
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* As linhas de UM eixo, no formato de rádio que o painel já usa.
|
|
58
|
+
*
|
|
59
|
+
* ⚠️ RÁDIO E NÃO SETE BOTÕES, e o motivo sobrevive à divisão: DENTRO de um eixo os valores continuam
|
|
60
|
+
* exclusivos — um tema de cada vez, uma correção de cada vez. O que deixou de ser exclusivo é a relação
|
|
61
|
+
* ENTRE os eixos, e é por isso que eles viram dois rádios em vez de um.
|
|
62
|
+
*
|
|
63
|
+
* ⚠️ E MANTÉM-SE A FORMA DE LINHA VISÍVEL, não um `<select>`. O `settings-visual` regista o erro que o Dev
|
|
64
|
+
* apanhou na primeira tentativa: dentro de uma caixa fechada, um controle cuja razão de existir é ser ACHADO
|
|
65
|
+
* por quem enxerga mal fica «quase o mesmo que não ter movido».
|
|
66
|
+
*/
|
|
67
|
+
export function linhasDoEixo(eixo, valores, rotulos, atual, t) {
|
|
68
|
+
return valores.map((valor) => {
|
|
69
|
+
const sel = valor === atual;
|
|
70
|
+
return `<div class="ctrl-row"><span><strong>${t(rotulos[valor])}</strong></span>`
|
|
71
|
+
+ `<button class="mode-btn${sel ? ' is-on' : ''}" role="radio" aria-checked="${sel}"`
|
|
72
|
+
+ ` data-eixo="${eixo}" data-valor="${valor}" type="button">${sel ? t('viz.escolhido') : t('viz.escolher')}</button></div>`;
|
|
73
|
+
}).join('');
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Os DOIS eixos, um a seguir ao outro, com um título cada.
|
|
77
|
+
*
|
|
78
|
+
* O título existe porque dois rádios seguidos sem nome são um rádio de oito para quem lê depressa — e essa
|
|
79
|
+
* leitura é exactamente o mal-entendido que a divisão existe para desfazer.
|
|
80
|
+
*/
|
|
81
|
+
export function eixosHtml(v, t) {
|
|
82
|
+
return `<h3 class="opt-sub">${t('eixo.tema.titulo')}</h3>`
|
|
83
|
+
+ linhasDoEixo('tema', TEMAS, ROTULO_DO_TEMA, v.tema, t)
|
|
84
|
+
+ `<h3 class="opt-sub">${t('eixo.correcao.titulo')}</h3>`
|
|
85
|
+
+ linhasDoEixo('correcao', CORRECOES, ROTULO_DA_CORRECAO, v.correcao, t);
|
|
86
|
+
}
|
|
87
|
+
export function escolhaDoBotao(dataset) {
|
|
88
|
+
const { eixo, valor } = dataset;
|
|
89
|
+
if (eixo !== 'tema' && eixo !== 'correcao')
|
|
90
|
+
return null;
|
|
91
|
+
if (!valor)
|
|
92
|
+
return null;
|
|
93
|
+
const validos = eixo === 'tema' ? TEMAS : CORRECOES;
|
|
94
|
+
return validos.includes(valor) ? { eixo, valor } : null;
|
|
95
|
+
}
|
package/dist-pkg/ui/webcam.js
CHANGED
|
@@ -7,6 +7,7 @@ import { t } from '../core/i18n.js';
|
|
|
7
7
|
import { $ } from './dom.js';
|
|
8
8
|
import { srAlert } from '../core/a11y-sr.js';
|
|
9
9
|
import { emFracao } from '../input/pointer-space.js'; // #105: um lugar so converte um ponto de tela
|
|
10
|
+
import { carimbarOrigem } from '../input/origem-sintetica.js'; // ADR-0109: a tecla sintetica declara quem a produziu
|
|
10
11
|
const wg = () => window.webgazer;
|
|
11
12
|
export let eyeMode = false; // ligado pelo botão; onGaze só age com ele ligado
|
|
12
13
|
export function setEyeMode(on) { eyeMode = on; }
|
|
@@ -16,7 +17,11 @@ function eyeSet(k, on, code) {
|
|
|
16
17
|
if (_eyeKeys[k] === on)
|
|
17
18
|
return;
|
|
18
19
|
_eyeKeys[k] = on;
|
|
19
|
-
|
|
20
|
+
// ⚠️ CARIMBADO `'olhos'` ANTES DE DESPACHAR (ADR-0109). Sem esta linha o evento chega ao `input/keydown`
|
|
21
|
+
// indistinguível de uma tecla premida e é carimbado `teclado` — e o efeito não é cosmético: a regra 3 diz
|
|
22
|
+
// que apertar uma tecla devolve o teclado SEM alternância, logo o olhar da criança desligaria, sozinho e em
|
|
23
|
+
// silêncio, a alternância de que ela depende para jogar. É o defeito que o §C desta issue existe para fechar.
|
|
24
|
+
const ev = carimbarOrigem(new KeyboardEvent(on ? 'keydown' : 'keyup', { code, bubbles: true }), 'olhos');
|
|
20
25
|
window.dispatchEvent(ev);
|
|
21
26
|
document.dispatchEvent(ev);
|
|
22
27
|
}
|
package/docs/CREDITS.md
CHANGED
|
@@ -61,4 +61,25 @@ A narração por voz roda **inteiramente no navegador/offline** graças ao traba
|
|
|
61
61
|
`es_MX` (ADR-0065, que supersede o ADR-0022).
|
|
62
62
|
|
|
63
63
|
> Os pesos das vozes são **baixados uma vez** (de um host público) e rodam **100% localmente** depois — nenhum áudio de
|
|
64
|
-
> criança sai do dispositivo. Ver `docs/2-Architecture/adr/ADR-0065-three-neural-voices-owned-by-the-engine-and-cached-on-first-use.yaml`.
|
|
64
|
+
> criança sai do dispositivo. Ver `the-inclusionist-docs · docs/2-Architecture/adr/ADR-0065-three-neural-voices-owned-by-the-engine-and-cached-on-first-use.yaml`.
|
|
65
|
+
|
|
66
|
+
## Arte importada — CC0, CC BY 3.0, CC BY 4.0 ou OGA-BY
|
|
67
|
+
|
|
68
|
+
A atribuição da arte que vem de fora é **por recurso**, e por isso não cabe nesta página em prosa: a autoria de um
|
|
69
|
+
acervo lê-se recurso a recurso, e a cadeia de derivações com ela.
|
|
70
|
+
|
|
71
|
+
**Ela mora em [`../art/ATTRIBUTION.csv`](../art/ATTRIBUTION.csv)** — caminho, autor, **URL da fonte**, licença e de
|
|
72
|
+
que recursos cada um derivou. É a mesma forma que o `LICENSES.md` §4 já usa para as 100 gerações do PixelLab: um
|
|
73
|
+
ficheiro tabular ao lado do apontador em prosa, porque um livro-razão que cresce com o catálogo não se mantém à mão.
|
|
74
|
+
|
|
75
|
+
⚠️ **Atribuição aqui é condição de uso, não linha de crédito.** Três das quatro licenças exigem-na, e um recurso
|
|
76
|
+
sem autor conhecido **não entra** — «não consegui descobrir» não é licença. O gate
|
|
77
|
+
`tests/arte-licencas-aceites.node.test.js` reprova a entrada vazia, a entrada órfã, o recurso sem entrada, a fonte
|
|
78
|
+
sem URL e **qualquer licença fora das quatro** — com ND, NC e share-alike recusados por nome e com o motivo de
|
|
79
|
+
cada um, que o [`LICENSES.md`](LICENSES.md) §3 e o **ADR-0133** explicam.
|
|
80
|
+
|
|
81
|
+
📌 **Não há quarentena, e já houve.** Até 2026-09-09 o Liberated Pixel Cup ia entrar sob CC BY-SA 3.0 atrás de uma
|
|
82
|
+
parede (ADR-0107). O Dev recusou o share-alike e a parede saiu com ele: sem arte cujo copyleft viaje, não há o que
|
|
83
|
+
segregar.
|
|
84
|
+
|
|
85
|
+
**Hoje o livro está vazio** — nenhum recurso entrou nunca.
|