primocode 10.5.0 → 10.7.0
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 +2 -2
- package/bin/primocode.js +117 -24
- package/lib/act.js +141 -9
- package/lib/agents-md.js +140 -0
- package/lib/auditoria.js +122 -0
- package/lib/browser.js +48 -1
- package/lib/catalogo.js +29 -6
- package/lib/comandos-extra.js +2 -0
- package/lib/config.js +32 -4
- package/lib/entrada.js +33 -11
- package/lib/estudio/cenarios.js +91 -0
- package/lib/estudio/colecao.js +35 -1
- package/lib/estudio/diretor.js +1 -0
- package/lib/estudio/falantes.js +30 -6
- package/lib/estudio/hibrido.js +144 -8
- package/lib/estudio/index.js +196 -63
- package/lib/estudio/kit/mundo.jsx +1060 -0
- package/lib/estudio/kit/voxel.jsx +315 -70
- package/lib/estudio/kit.js +1 -1
- package/lib/estudio/kokoro.js +18 -4
- package/lib/estudio/margem.js +1 -1
- package/lib/estudio/movimentos.js +212 -0
- package/lib/estudio/mundo.js +328 -0
- package/lib/estudio/personagem.js +164 -11
- package/lib/estudio/plano.js +6 -0
- package/lib/estudio/producao.js +107 -17
- package/lib/estudio/projeto.js +77 -22
- package/lib/leitura.js +176 -0
- package/lib/local/cerebro.json +180 -3
- package/lib/local/entrada.js +61 -4
- package/lib/mcp.js +296 -12
- package/lib/memoria-conversa.js +32 -3
- package/lib/mencoes.js +5 -1
- package/lib/nome-curto.js +126 -0
- package/lib/nome-projeto.js +5 -2
- package/lib/outra-pasta.js +161 -0
- package/lib/perigo.js +34 -2
- package/lib/referencia.js +40 -11
- package/lib/repeticao.js +20 -1
- package/lib/tela.js +1 -1
- package/lib/tools.js +76 -6
- package/lib/travado.js +141 -0
- package/lib/ui.js +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* movimentos.js — a biblioteca de MOVIMENTOS prontos: entradas, saídas,
|
|
3
|
+
* ênfases e loops, cada um com nome, que viram quadros-chave do `anima`.
|
|
4
|
+
*
|
|
5
|
+
* "preciso que você deixe os motions com + de 100 tipos de animações …
|
|
6
|
+
* além da IA poder criar os próprios e pesquisar" (Joel, 12/10)
|
|
7
|
+
*
|
|
8
|
+
* Até aqui todo movimento era escrito à mão pelo modelo, quadro a quadro
|
|
9
|
+
* (`anima: {opacidade: [{t, v}]}`). Modelo grátis escreve sempre os mesmos
|
|
10
|
+
* dois: esmaecer e subir. Com nome, ele PROCURA ("quique", "desfoque",
|
|
11
|
+
* "saída para a esquerda"), escolhe e escreve uma palavra:
|
|
12
|
+
*
|
|
13
|
+
* elemento.movimento = "quicar-de-baixo"
|
|
14
|
+
* elemento.movimento = {nome: "sair-girando", em: 2.4, duracao: 0.5}
|
|
15
|
+
* elemento.movimento = ["surgir-desfocado", {nome: "flutuar", em: 0.6}]
|
|
16
|
+
*
|
|
17
|
+
* O plano expande isso em `anima` antes de conferir (plano.js). O que a IA
|
|
18
|
+
* escreveu em `anima` vence o movimento pronto na mesma propriedade — e ela
|
|
19
|
+
* continua livre para inventar o seu, quadro a quadro.
|
|
20
|
+
*
|
|
21
|
+
* Os números: x/y/z são DESLOCAMENTOS em px (o render soma à posição), as
|
|
22
|
+
* rotações em graus, o desfoque em px, opacidade/escala/brilho/saturação em
|
|
23
|
+
* fração. Todo movimento de entrada termina no repouso (0 de deslocamento,
|
|
24
|
+
* escala 1, opacidade 1); toda saída começa nele.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
// Cada quadro: [fração do tempo 0..1, valor, suavização até ele].
|
|
30
|
+
const DIRECOES = {
|
|
31
|
+
'de-baixo': { eixo: 'y', sinal: 1 }, 'de-cima': { eixo: 'y', sinal: -1 },
|
|
32
|
+
'da-esquerda': { eixo: 'x', sinal: -1 }, 'da-direita': { eixo: 'x', sinal: 1 },
|
|
33
|
+
};
|
|
34
|
+
const PARA = { 'de-baixo': 'para-baixo', 'de-cima': 'para-cima', 'da-esquerda': 'para-a-esquerda', 'da-direita': 'para-a-direita' };
|
|
35
|
+
|
|
36
|
+
/* As entradas com direção: cada estilo × as quatro direções. */
|
|
37
|
+
const ESTILOS_DIRECAO = {
|
|
38
|
+
deslizar: { d: 0.6, desc: 'desliza e aparece', f: (e, s) => ({ [e]: [[0, 80 * s], [1, 0, 'saida']], opacidade: [[0, 0], [0.6, 1, 'suave']] }) },
|
|
39
|
+
'deslizar-desfocado': { d: 0.7, desc: 'desliza saindo do desfoque', f: (e, s) => ({ [e]: [[0, 70 * s], [1, 0, 'saida']], opacidade: [[0, 0], [0.5, 1]], desfoque: [[0, 18], [1, 0, 'saida']] }) },
|
|
40
|
+
'chegar-elastico': { d: 0.9, desc: 'chega passando do ponto e volta, como mola', f: (e, s) => ({ [e]: [[0, 220 * s], [1, 0, 'elastico']], opacidade: [[0, 0], [0.25, 1]] }) },
|
|
41
|
+
quicar: { d: 0.9, desc: 'cai e quica', f: (e, s) => ({ [e]: [[0, 260 * s], [1, 0, 'salto']], opacidade: [[0, 0], [0.2, 1]] }) },
|
|
42
|
+
varrer: { d: 0.8, desc: 'atravessa rápido de longe e freia', f: (e, s) => ({ [e]: [[0, 900 * s], [1, 0, 'arrasto']], opacidade: [[0, 0], [0.3, 1]] }) },
|
|
43
|
+
'girar-entrando': { d: 0.8, desc: 'entra girando', f: (e, s) => ({ [e]: [[0, 160 * s], [1, 0, 'saida']], rotacao: [[0, -90 * s], [1, 0, 'saida']], opacidade: [[0, 0], [0.4, 1]] }) },
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/* As entradas sem direção. */
|
|
47
|
+
const ENTRADAS = {
|
|
48
|
+
surgir: { d: 0.5, desc: 'só aparece (esmaecer)', f: () => ({ opacidade: [[0, 0], [1, 1, 'suave']] }) },
|
|
49
|
+
crescer: { d: 0.6, desc: 'cresce do nada', f: () => ({ escala: [[0, 0], [1, 1, 'saida']], opacidade: [[0, 0], [0.3, 1]] }) },
|
|
50
|
+
'crescer-elastico': { d: 0.9, desc: 'cresce como mola', f: () => ({ escala: [[0, 0], [1, 1, 'elastico']] }) },
|
|
51
|
+
'pular-pra-dentro': { d: 0.6, desc: 'cresce, passa um pouco e assenta', f: () => ({ escala: [[0, 0], [0.7, 1.15, 'saida'], [1, 1, 'suave']], opacidade: [[0, 0], [0.3, 1]] }) },
|
|
52
|
+
'encolher-pra-dentro': { d: 0.6, desc: 'vem grande e encolhe até o lugar', f: () => ({ escala: [[0, 1.8], [1, 1, 'saida']], opacidade: [[0, 0], [0.5, 1]] }) },
|
|
53
|
+
'surgir-desfocado': { d: 0.7, desc: 'sai do desfoque', f: () => ({ desfoque: [[0, 24], [1, 0, 'saida']], opacidade: [[0, 0], [0.6, 1]] }) },
|
|
54
|
+
'virar-de-lado': { d: 0.7, desc: 'vira em Y como carta', f: () => ({ rotacaoY: [[0, 90], [1, 0, 'saida']], opacidade: [[0, 0], [0.3, 1]] }) },
|
|
55
|
+
'virar-de-cima': { d: 0.7, desc: 'tomba em X, de cima para baixo', f: () => ({ rotacaoX: [[0, -90], [1, 0, 'saida']], opacidade: [[0, 0], [0.3, 1]] }) },
|
|
56
|
+
'girar-crescendo': { d: 0.8, desc: 'cresce dando meia volta', f: () => ({ rotacao: [[0, -180], [1, 0, 'saida']], escala: [[0, 0], [1, 1, 'saida']] }) },
|
|
57
|
+
'vir-do-fundo': { d: 0.8, desc: 'avança da profundidade (3D)', f: () => ({ z: [[0, -700], [1, 0, 'saida']], opacidade: [[0, 0], [0.4, 1]] }) },
|
|
58
|
+
'cair-do-alto': { d: 0.8, desc: 'cai do topo e assenta com um quique', f: () => ({ y: [[0, -500], [1, 0, 'salto']], rotacao: [[0, -12], [1, 0, 'saida']] }) },
|
|
59
|
+
'estourar-de-luz': { d: 0.6, desc: 'acende estourado e assenta', f: () => ({ brilho: [[0, 3], [1, 1, 'saida']], opacidade: [[0, 0], [0.3, 1]] }) },
|
|
60
|
+
'ganhar-cor': { d: 0.8, desc: 'sai do cinza para a cor', f: () => ({ saturacao: [[0, 0], [1, 1, 'suave']], opacidade: [[0, 0], [0.4, 1]] }) },
|
|
61
|
+
'abrir-letras': { d: 0.8, desc: 'as letras chegam juntas de longe (texto)', f: () => ({ espacamento: [[0, 40], [1, 0, 'saida']], opacidade: [[0, 0], [0.5, 1]] }) },
|
|
62
|
+
'esticar-de-lado': { d: 0.6, desc: 'estica na horizontal', f: () => ({ escalaX: [[0, 0], [1, 1, 'saida']] }) },
|
|
63
|
+
'esticar-de-pe': { d: 0.6, desc: 'estica na vertical', f: () => ({ escalaY: [[0, 0], [1, 1, 'saida']] }) },
|
|
64
|
+
'carimbo': { d: 0.35, desc: 'bate na tela como carimbo', f: () => ({ escala: [[0, 2.4], [1, 1, 'entrada']], opacidade: [[0, 0], [0.2, 1]] }) },
|
|
65
|
+
'aproximar-editorial': { d: 0.9, desc: 'entra devagar, como revista', f: () => ({ y: [[0, 24], [1, 0, 'editorial']], escala: [[0, 0.96], [1, 1, 'editorial']], opacidade: [[0, 0], [1, 1, 'editorial']] }) },
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/* As ênfases: o elemento já está lá e chama atenção (começam e terminam no repouso). */
|
|
69
|
+
const ENFASES = {
|
|
70
|
+
pulsar: { d: 0.5, desc: 'cresce e volta uma vez', f: () => ({ escala: [[0, 1], [0.5, 1.12, 'suave'], [1, 1, 'suave']] }) },
|
|
71
|
+
tremer: { d: 0.5, desc: 'treme de lado (erro, negação)', f: () => ({ x: [[0, 0], [0.15, -14], [0.3, 12], [0.45, -10], [0.6, 8], [0.75, -4], [1, 0]] }) },
|
|
72
|
+
balancar: { d: 0.8, desc: 'balança como pêndulo', f: () => ({ rotacao: [[0, 0], [0.25, 12, 'suave'], [0.5, -8, 'suave'], [0.75, 4, 'suave'], [1, 0, 'suave']] }) },
|
|
73
|
+
piscar: { d: 0.6, desc: 'pisca duas vezes', f: () => ({ opacidade: [[0, 1], [0.25, 0.1], [0.5, 1], [0.75, 0.1], [1, 1]] }) },
|
|
74
|
+
'dar-uma-volta': { d: 0.8, desc: 'gira 360°', f: () => ({ rotacao: [[0, 0], [1, 360, 'suave']] }) },
|
|
75
|
+
'virar-moeda': { d: 0.8, desc: 'gira em Y como moeda', f: () => ({ rotacaoY: [[0, 0], [1, 360, 'suave']] }) },
|
|
76
|
+
'pular': { d: 0.6, desc: 'dá um pulinho', f: () => ({ y: [[0, 0], [0.4, -40, 'saida'], [1, 0, 'salto']] }) },
|
|
77
|
+
'tada': { d: 0.9, desc: 'cresce e chacoalha (conquista)', f: () => ({ escala: [[0, 1], [0.2, 0.9], [0.4, 1.12], [0.9, 1.12], [1, 1]], rotacao: [[0, 0], [0.3, -4], [0.45, 4], [0.6, -4], [0.75, 4], [1, 0]] }) },
|
|
78
|
+
'borracha': { d: 0.8, desc: 'amassa e estica como borracha', f: () => ({ escalaX: [[0, 1], [0.3, 1.25], [0.4, 0.75], [0.5, 1.15], [0.65, 0.95], [1, 1]], escalaY: [[0, 1], [0.3, 0.75], [0.4, 1.25], [0.5, 0.85], [0.65, 1.05], [1, 1]] }) },
|
|
79
|
+
'gelatina': { d: 0.9, desc: 'treme mole', f: () => ({ escalaX: [[0, 1], [0.2, 1.1], [0.4, 0.92], [0.6, 1.04], [0.8, 0.98], [1, 1]], escalaY: [[0, 1], [0.2, 0.92], [0.4, 1.08], [0.6, 0.97], [0.8, 1.01], [1, 1]] }) },
|
|
80
|
+
'acenar': { d: 0.8, desc: 'balança curto, como aceno', f: () => ({ rotacao: [[0, 0], [0.2, 14], [0.4, -8], [0.6, 14], [0.8, -4], [1, 0]] }) },
|
|
81
|
+
'acender': { d: 0.6, desc: 'brilha e apaga uma vez', f: () => ({ brilho: [[0, 1], [0.4, 1.8, 'suave'], [1, 1, 'suave']] }) },
|
|
82
|
+
'inclinar-3d': { d: 0.9, desc: 'inclina em 3D e volta', f: () => ({ rotacaoX: [[0, 0], [0.5, 18, 'suave'], [1, 0, 'suave']], rotacaoY: [[0, 0], [0.5, -14, 'suave'], [1, 0, 'suave']] }) },
|
|
83
|
+
'cabecear': { d: 0.6, desc: 'acena com a cabeça (sim)', f: () => ({ rotacaoX: [[0, 0], [0.3, 20], [0.6, -6], [1, 0]] }) },
|
|
84
|
+
'empurrao': { d: 0.4, desc: 'leva um soco e volta', f: () => ({ escala: [[0, 1], [0.25, 0.86, 'entrada'], [1, 1, 'elastico']] }) },
|
|
85
|
+
'sublinhar-luz': { d: 0.7, desc: 'fica vivo por um instante', f: () => ({ saturacao: [[0, 1], [0.4, 1.8, 'suave'], [1, 1, 'suave']], brilho: [[0, 1], [0.4, 1.3, 'suave'], [1, 1]] }) },
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
/* Os loops: repetem pela duração inteira (o padrão é a cena toda, 4 s). */
|
|
89
|
+
const LOOPS = {
|
|
90
|
+
flutuar: { d: 4, ciclo: 2.4, desc: 'sobe e desce devagar', f: () => ({ y: [[0, 0], [0.5, -16, 'suave'], [1, 0, 'suave']] }) },
|
|
91
|
+
respirar: { d: 4, ciclo: 2, desc: 'cresce e encolhe de leve', f: () => ({ escala: [[0, 1], [0.5, 1.05, 'suave'], [1, 1, 'suave']] }) },
|
|
92
|
+
'girar-sem-parar': { d: 4, ciclo: 4, desc: 'gira sem parar', f: () => ({ rotacao: [[0, 0], [1, 360, 'linear']] }) },
|
|
93
|
+
'oscilar': { d: 4, ciclo: 1.6, desc: 'balança de leve sem parar', f: () => ({ rotacao: [[0, -5], [0.5, 5, 'suave'], [1, -5, 'suave']] }) },
|
|
94
|
+
'batimento': { d: 4, ciclo: 1, desc: 'pulsa como coração', f: () => ({ escala: [[0, 1], [0.15, 1.12], [0.3, 1], [0.45, 1.08], [0.6, 1], [1, 1]] }) },
|
|
95
|
+
'piscar-devagar': { d: 4, ciclo: 1.6, desc: 'some e volta devagar', f: () => ({ opacidade: [[0, 1], [0.5, 0.35, 'suave'], [1, 1, 'suave']] }) },
|
|
96
|
+
'orbitar': { d: 4, ciclo: 3, desc: 'anda em círculo pequeno', f: () => ({ x: [[0, 20], [0.25, 0, 'suave'], [0.5, -20, 'suave'], [0.75, 0, 'suave'], [1, 20, 'suave']], y: [[0, 0], [0.25, 20, 'suave'], [0.5, 0, 'suave'], [0.75, -20, 'suave'], [1, 0, 'suave']] }) },
|
|
97
|
+
'deriva': { d: 4, ciclo: 4, desc: 'anda de lado bem devagar', f: () => ({ x: [[0, -12], [0.5, 12, 'suave'], [1, -12, 'suave']] }) },
|
|
98
|
+
'girar-3d': { d: 4, ciclo: 5, desc: 'gira em Y sem parar, como vitrine', f: () => ({ rotacaoY: [[0, 0], [1, 360, 'linear']] }) },
|
|
99
|
+
'brilho-vivo': { d: 4, ciclo: 2, desc: 'o brilho respira', f: () => ({ brilho: [[0, 1], [0.5, 1.25, 'suave'], [1, 1, 'suave']] }) },
|
|
100
|
+
'balanco-3d': { d: 4, ciclo: 3, desc: 'inclina em 3D de um lado para o outro', f: () => ({ rotacaoY: [[0, -12], [0.5, 12, 'suave'], [1, -12, 'suave']] }) },
|
|
101
|
+
'tremor-leve': { d: 4, ciclo: 0.3, desc: 'vibra o tempo todo (tensão, alerta)', f: () => ({ x: [[0, 0], [0.25, -2], [0.5, 2], [0.75, -1], [1, 0]] }) },
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/* A SAÍDA é a entrada ao contrário: o mesmo desenho, tempo invertido. */
|
|
105
|
+
function inverter(gerado) {
|
|
106
|
+
const out = {};
|
|
107
|
+
for (const [prop, qs] of Object.entries(gerado)) {
|
|
108
|
+
const ordem = [...qs].reverse();
|
|
109
|
+
// A suavização de um quadro vale ATÉ ele; invertida, passa ao vizinho.
|
|
110
|
+
out[prop] = ordem.map(([f, v], i) => [1 - f, v, i > 0 ? inverterE(ordem[i - 1][2]) : undefined]);
|
|
111
|
+
}
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
const inverterE = (e) => ({ saida: 'entrada', entrada: 'saida', arrasto: 'acelera', acelera: 'arrasto', elastico: 'entrada', salto: 'entrada' }[e] || e);
|
|
115
|
+
const nomeDaSaida = (n) => n.replace(/^surgir/, 'sumir').replace(/^crescer/, 'encolher-sumindo').replace(/^chegar/, 'partir')
|
|
116
|
+
.replace(/^vir-do-fundo/, 'ir-pro-fundo').replace(/^cair-do-alto/, 'cair-pra-fora').replace(/^ganhar-cor/, 'perder-cor')
|
|
117
|
+
.replace(/^abrir-letras/, 'espalhar-letras').replace(/^estourar-de-luz/, 'apagar-estourando');
|
|
118
|
+
|
|
119
|
+
function montarCatalogo() {
|
|
120
|
+
const cat = {};
|
|
121
|
+
const pore = (nome, categoria, def, gerar) => { cat[nome] = { nome, categoria, descricao: def.desc, duracao: def.d, ciclo: def.ciclo, gerar }; };
|
|
122
|
+
for (const [estilo, def] of Object.entries(ESTILOS_DIRECAO)) {
|
|
123
|
+
for (const [dir, { eixo, sinal }] of Object.entries(DIRECOES)) {
|
|
124
|
+
const gerar = () => def.f(eixo, sinal);
|
|
125
|
+
pore(`${estilo}-${dir}`, 'entrada', { ...def, desc: `${def.desc}, vindo ${dir.replace('-', ' ')}` }, gerar);
|
|
126
|
+
pore(`${estilo}-${PARA[dir]}`.replace(/^deslizar-desfocado/, 'sair-desfocando').replace(/^deslizar/, 'sair-deslizando')
|
|
127
|
+
.replace(/^chegar-elastico/, 'sair-elastico').replace(/^quicar/, 'sair-quicando').replace(/^varrer/, 'sair-varrendo')
|
|
128
|
+
.replace(/^girar-entrando/, 'sair-girando'), 'saida', { ...def, desc: `sai: ${def.desc.split(',')[0]}, ${PARA[dir].replace(/-/g, ' ')}` },
|
|
129
|
+
() => inverter(def.f(eixo, sinal)));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
for (const [nome, def] of Object.entries(ENTRADAS)) {
|
|
133
|
+
pore(nome, 'entrada', def, def.f);
|
|
134
|
+
pore(nomeDaSaida(nome) === nome ? `${nome}-saindo` : nomeDaSaida(nome), 'saida', { ...def, desc: `sai: ${def.desc}` }, () => inverter(def.f()));
|
|
135
|
+
}
|
|
136
|
+
for (const [nome, def] of Object.entries(ENFASES)) pore(nome, 'enfase', def, def.f);
|
|
137
|
+
for (const [nome, def] of Object.entries(LOOPS)) pore(nome, 'loop', def, def.f);
|
|
138
|
+
return cat;
|
|
139
|
+
}
|
|
140
|
+
const CATALOGO = montarCatalogo();
|
|
141
|
+
|
|
142
|
+
const r3 = (n) => Math.round(n * 1000) / 1000;
|
|
143
|
+
const semAcento = (s) => String(s || '').normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase();
|
|
144
|
+
|
|
145
|
+
/** Os quadros-chave de um movimento, a partir de `em` segundos. */
|
|
146
|
+
const achar = (nome) => CATALOGO[semAcento(nome).trim().replace(/\s+/g, '-')] || null;
|
|
147
|
+
|
|
148
|
+
function quadros(nome, { em = 0, duracao } = {}) {
|
|
149
|
+
const m = achar(nome);
|
|
150
|
+
if (!m) return null;
|
|
151
|
+
const d = Number(duracao) > 0 ? Number(duracao) : m.duracao;
|
|
152
|
+
const gerado = m.gerar();
|
|
153
|
+
const anima = {};
|
|
154
|
+
if (m.categoria === 'loop') {
|
|
155
|
+
const ciclo = Math.min(m.ciclo || d, d);
|
|
156
|
+
const voltas = Math.max(1, Math.round(d / ciclo));
|
|
157
|
+
for (const [prop, qs] of Object.entries(gerado)) {
|
|
158
|
+
const lista = [];
|
|
159
|
+
for (let k = 0; k < voltas; k++) {
|
|
160
|
+
for (const [f, v, e] of qs) {
|
|
161
|
+
if (k > 0 && f === 0) continue; // o fim de uma volta é o começo da próxima
|
|
162
|
+
// o giro sem parar soma: a 2ª volta vai de 360 a 720
|
|
163
|
+
const soma = /rotacao/.test(prop) && qs[qs.length - 1][1] === 360 ? 360 * k : 0;
|
|
164
|
+
lista.push({ t: r3(em + (k + f) * ciclo), v: v + soma, ...(e ? { e } : {}) });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
anima[prop] = lista;
|
|
168
|
+
}
|
|
169
|
+
return anima;
|
|
170
|
+
}
|
|
171
|
+
for (const [prop, qs] of Object.entries(gerado)) anima[prop] = qs.map(([f, v, e]) => ({ t: r3(em + f * d), v, ...(e ? { e } : {}) }));
|
|
172
|
+
return anima;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Procura por palavra (no nome ou na descrição) e por categoria. Sem termo, devolve um pouco de cada. */
|
|
176
|
+
function procurar({ termo = '', categoria = '', limite = 30 } = {}) {
|
|
177
|
+
const palavras = semAcento(termo).split(/[^a-z0-9]+/).filter(Boolean);
|
|
178
|
+
const cat = semAcento(categoria).replace('ê', 'e');
|
|
179
|
+
const lista = Object.values(CATALOGO).filter((m) => (!cat || m.categoria === cat.replace(/s$/, ''))
|
|
180
|
+
&& palavras.every((p) => semAcento(m.nome + ' ' + m.descricao).includes(p)));
|
|
181
|
+
return {
|
|
182
|
+
ok: true, total: Object.keys(CATALOGO).length, achados: lista.length,
|
|
183
|
+
movimentos: lista.slice(0, limite).map((m) => ({ nome: m.nome, categoria: m.categoria, faz: m.descricao, duracao: m.duracao })),
|
|
184
|
+
comoUsar: 'no elemento do plano: movimento:"<nome>" ou {nome, em (s), duracao (s)} ou uma lista deles. O que você escrever em anima vence na mesma propriedade; pode inventar o seu em anima, quadro a quadro.',
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Troca `movimento` por `anima` em todos os elementos do plano. Muda o plano
|
|
190
|
+
* no lugar e devolve os nomes que não existem (o plano.js vira problema).
|
|
191
|
+
*/
|
|
192
|
+
function expandir(plano) {
|
|
193
|
+
const desconhecidos = [];
|
|
194
|
+
for (const [i, cena] of ((plano && plano.cenas) || []).entries()) {
|
|
195
|
+
for (const [j, el] of ((cena && cena.elementos) || []).entries()) {
|
|
196
|
+
if (!el || el.movimento == null) continue;
|
|
197
|
+
const pedidos = (Array.isArray(el.movimento) ? el.movimento : [el.movimento]).map((m) => (typeof m === 'string' ? { nome: m } : m || {}));
|
|
198
|
+
const juntado = {};
|
|
199
|
+
for (const p of pedidos) {
|
|
200
|
+
const a = quadros(p.nome, { em: Number(p.em) || 0, duracao: p.duracao ?? ((achar(p.nome) || {}).categoria === 'loop' ? cena.duracao : undefined) });
|
|
201
|
+
if (!a) { desconhecidos.push({ onde: `cenas[${i}].elementos[${j}].movimento`, nome: String(p.nome) }); continue; }
|
|
202
|
+
// Dois movimentos na mesma propriedade: os quadros se somam na linha do tempo.
|
|
203
|
+
for (const [prop, qs] of Object.entries(a)) juntado[prop] = [...(juntado[prop] || []), ...qs].sort((x, y) => x.t - y.t);
|
|
204
|
+
}
|
|
205
|
+
el.anima = { ...juntado, ...(el.anima || {}) };
|
|
206
|
+
delete el.movimento;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return desconhecidos;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
module.exports = { CATALOGO, quadros, procurar, expandir, inverter };
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mundo.js — o lugar, os bichos e as coisas do vídeo 3D, a partir do PEDIDO.
|
|
3
|
+
*
|
|
4
|
+
* "não quero que ele só troque as falas e os textos ... se eu pedir dois
|
|
5
|
+
* humanos ele vai fazer dois humanos, se eu pedir um cachorro ele vai fazer,
|
|
6
|
+
* se eu pedir um humano na praia ele vai fazer — vai mudar o cenário, vai
|
|
7
|
+
* mudar as coisas, e não é coisa pré-definida" (Joel, 11/10)
|
|
8
|
+
*
|
|
9
|
+
* Até a 10.5 o 3D tinha UM set (a parede de telas) e o elenco era gente ou o
|
|
10
|
+
* mascote. Aqui o set vira um MUNDO montado de peças:
|
|
11
|
+
*
|
|
12
|
+
* · o LUGAR (praia, cidade, floresta, espaço, cozinha…): céu, chão, água,
|
|
13
|
+
* luz e hora do dia; vem do texto livre ("numa praia ao pôr do sol");
|
|
14
|
+
* · os ELEMENTOS: o que há no lugar. Uma palavra do catálogo (palmeira, carro,
|
|
15
|
+
* prédio, foguete…) vira uma peça de blocos desenhada no kit/mundo.jsx; uma
|
|
16
|
+
* palavra FORA do catálogo vira um ícone 3D da biblioteca (colecao.js,
|
|
17
|
+
* 1.595 objetos) transformado em blocos na hora do render — é isso que
|
|
18
|
+
* tira o "pré-definido": qualquer coisa que tenha ícone entra na cena;
|
|
19
|
+
* · o ELENCO: pessoa, robô/mascote, BICHO (cachorro, gato, cavalo, vaca,
|
|
20
|
+
* pinguim, peixe, dinossauro… — o kit/mundo.jsx monta cada espécie) ou
|
|
21
|
+
* COISA (um ícone em blocos que fala: "uma pizza conversando com um café").
|
|
22
|
+
* E os FIGURANTES: quem está na cena sem falar ("um humano com o cachorro").
|
|
23
|
+
*
|
|
24
|
+
* A IA decide (roteiro.cena, roteiro.personagens, parte.cenario); o que ela
|
|
25
|
+
* não disse sai do texto do pedido e das falas — a mesma regra do resto do
|
|
26
|
+
* Estúdio: o modelo manda, a leitura do texto só completa. É o que faz um
|
|
27
|
+
* modelo local pequeno, que só escreve as falas, ainda ganhar a praia que a
|
|
28
|
+
* pessoa pediu.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
'use strict';
|
|
32
|
+
|
|
33
|
+
const sem = (s) => String(s || '').toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '');
|
|
34
|
+
|
|
35
|
+
/* ── OS LUGARES ─────────────────────────────────────────────────────────
|
|
36
|
+
ceu: [alto, horizonte] · chao: {tipo, cor, cor2} · agua: cor do mar ·
|
|
37
|
+
dentro: set fechado (parede no fundo) · props: o que o lugar tem quando a
|
|
38
|
+
cena não disse. As palavras são lidas sem acento. */
|
|
39
|
+
const LUGARES = {
|
|
40
|
+
/* A COBERTURA À NOITE (2ª referência do Joel, 11/10): a janela de vidro
|
|
41
|
+
com a cidade acesa lá fora, as luzes grandes desfocadas (bokeh), o
|
|
42
|
+
parapeito escuro e os CARTÕES de interface flutuando na frente (o
|
|
43
|
+
assunto do vídeo, desfocados pela lente). É o set padrão quando o
|
|
44
|
+
pedido não diz lugar. */
|
|
45
|
+
cobertura: { palavras: /\b(cobertura|terraco|rooftop|janela|vista da cidade|apartamento alto|predio alto|escritorio a noite)\b/, dentro: true, cobertura: true,
|
|
46
|
+
parede: '#050816', chao: { tipo: 'madeira', cor: '#2a1f1a', cor2: '#241a16' }, luz: '#ffd2b0', recorte: '#6f8bff', acento: '#ff6a2b', props: [] },
|
|
47
|
+
telas: { palavras: /\b(estudio|podcast|tecnologia|telas?|set|programa|live|transmissao)\b/, dentro: true, telas: true,
|
|
48
|
+
parede: '#07061a', chao: { tipo: 'ladrilho', cor: '#4338ca', cor2: '#3b32b8' }, luz: '#ffd2b0', recorte: '#8b7bff', acento: '#ff7a45', props: [] },
|
|
49
|
+
praia: { palavras: /\b(praia|mar|litoral|beira[ -]mar|areia|orla|surf\w*|ilha)\b/, ceu: ['#3d8fe0', '#c4e8ff'], agua: '#1d8bc9',
|
|
50
|
+
chao: { tipo: 'areia', cor: '#ead29c', cor2: '#dfc48a' }, luz: '#fff2d6', recorte: '#ffd08a', acento: '#ff7a45', sol: true,
|
|
51
|
+
props: ['palmeira', 'palmeira', 'guarda-sol', 'cadeira de praia', 'palmeira', 'castelo de areia', 'pedra'] },
|
|
52
|
+
cidade: { palavras: /\b(cidade|rua|avenida|centro|predios?|esquina|calcada|metropole|urbano)\b/, ceu: ['#5b9be6', '#dbeaff'],
|
|
53
|
+
chao: { tipo: 'asfalto', cor: '#5b5e67', cor2: '#53565f' }, luz: '#fff1dc', recorte: '#9fc3ff', acento: '#facc15', sol: true,
|
|
54
|
+
props: ['predio', 'predio', 'predio', 'poste', 'carro', 'arvore', 'predio', 'poste', 'carro', 'predio'] },
|
|
55
|
+
floresta: { palavras: /\b(floresta|mata|selva|bosque|trilha|acampamento|amazonia)\b/, ceu: ['#6fb3e8', '#dcf2d6'],
|
|
56
|
+
chao: { tipo: 'grama', cor: '#4f9a3c', cor2: '#468c35' }, luz: '#fff0d0', recorte: '#b6f0a0', acento: '#facc15', sol: true, neblina: 0.6,
|
|
57
|
+
props: ['arvore', 'pinheiro', 'arvore', 'pinheiro', 'arbusto', 'pedra', 'arvore', 'pinheiro', 'cogumelo', 'arvore'] },
|
|
58
|
+
parque: { palavras: /\b(parque|jardim|praca|gramado|piquenique)\b/, ceu: ['#58a6ee', '#d9eeff'],
|
|
59
|
+
chao: { tipo: 'grama', cor: '#5aa845', cor2: '#4f9a3c' }, luz: '#fff2d6', recorte: '#c8f0b0', acento: '#f472b6', sol: true,
|
|
60
|
+
props: ['arvore', 'banco', 'flor', 'arvore', 'poste', 'flor', 'arbusto', 'arvore', 'flor'] },
|
|
61
|
+
fazenda: { palavras: /\b(fazenda|sitio|roca|interior|celeiro|curral|plantacao|colheita)\b/, ceu: ['#68aef0', '#f3ecd0'],
|
|
62
|
+
chao: { tipo: 'grama', cor: '#6aa84f', cor2: '#5e9a44' }, luz: '#fff1d0', recorte: '#ffe0a0', acento: '#dc2626', sol: true,
|
|
63
|
+
props: ['celeiro', 'cerca', 'cerca', 'arvore', 'feno', 'cerca', 'arvore', 'feno'] },
|
|
64
|
+
neve: { palavras: /\b(neve|inverno|gelo|polo|nevando|esqui|frio)\b/, ceu: ['#9fbfe6', '#eef4fb'],
|
|
65
|
+
chao: { tipo: 'neve', cor: '#f4f8fc', cor2: '#e6eef6' }, luz: '#eef4ff', recorte: '#a8c8ff', acento: '#38bdf8', sol: true,
|
|
66
|
+
props: ['pinheiro nevado', 'pinheiro nevado', 'boneco de neve', 'montanha', 'pinheiro nevado', 'pedra', 'pinheiro nevado'] },
|
|
67
|
+
deserto: { palavras: /\b(deserto|duna|sertao|saara|oasis)\b/, ceu: ['#f0a35a', '#ffe2ae'],
|
|
68
|
+
chao: { tipo: 'areia', cor: '#e2b06c', cor2: '#d6a05c' }, luz: '#ffe6c0', recorte: '#ffb070', acento: '#ea580c', sol: true,
|
|
69
|
+
props: ['cacto', 'cacto', 'pedra', 'montanha', 'cacto', 'pedra'] },
|
|
70
|
+
espaco: { palavras: /\b(espaco|lua|planeta|galaxia|foguete|astronauta|marte|orbita|estrelas|universo|nave)\b/, ceu: ['#02020f', '#0d0b2c'], estrelas: true,
|
|
71
|
+
chao: { tipo: 'lua', cor: '#a1a1ad', cor2: '#90909c' }, luz: '#e8ecff', recorte: '#8b7bff', acento: '#a78bfa',
|
|
72
|
+
props: ['planeta', 'foguete', 'pedra', 'planeta', 'cratera', 'pedra'] },
|
|
73
|
+
fundo_do_mar: { palavras: /\b(fundo do mar|submarino|oceano|aquario|mergulh\w*|recife|coral)\b/, ceu: ['#022c46', '#0b6f94'], subaquatico: true,
|
|
74
|
+
chao: { tipo: 'areia', cor: '#c9b98a', cor2: '#bba97a' }, luz: '#bfefff', recorte: '#38bdf8', acento: '#f97316', neblina: 1,
|
|
75
|
+
props: ['alga', 'coral', 'pedra', 'alga', 'coral', 'bau', 'alga'] },
|
|
76
|
+
escritorio: { palavras: /\b(escritorio|trabalho|reuniao|empresa|firma|coworking|startup)\b/, dentro: true, parede: '#d8d0c2',
|
|
77
|
+
chao: { tipo: 'madeira', cor: '#9a7350', cor2: '#8d6846' }, luz: '#fff1dc', recorte: '#ffe2b8', acento: '#2563eb',
|
|
78
|
+
props: ['mesa', 'computador', 'planta', 'estante', 'cadeira', 'quadro'] },
|
|
79
|
+
sala: { palavras: /\b(sala|casa|sofa|quarto|lar|apartamento|sala de estar)\b/, dentro: true, parede: '#e6d6c3',
|
|
80
|
+
chao: { tipo: 'madeira', cor: '#a87b52', cor2: '#9a6e47' }, luz: '#ffe8c8', recorte: '#ffd6a0', acento: '#e11d48',
|
|
81
|
+
props: ['sofa', 'luminaria', 'planta', 'estante', 'tapete', 'quadro'] },
|
|
82
|
+
cozinha: { palavras: /\b(cozinha|restaurante|padaria|cafeteria|cafe|lanchonete|confeitaria|pizzaria|bar)\b/, dentro: true, parede: '#efe4d2',
|
|
83
|
+
chao: { tipo: 'ladrilho', cor: '#e8e1d4', cor2: '#cfc5b4' }, luz: '#fff0d6', recorte: '#ffd9a0', acento: '#dc2626',
|
|
84
|
+
props: ['balcao', 'mesa', 'cadeira', 'planta', 'estante', 'luminaria'] },
|
|
85
|
+
escola: { palavras: /\b(escola|sala de aula|aula|professor|faculdade|universidade|colegio)\b/, dentro: true, parede: '#dfe8dc',
|
|
86
|
+
chao: { tipo: 'madeira', cor: '#b08458', cor2: '#a3794f' }, luz: '#fffaf0', recorte: '#d6f0ff', acento: '#16a34a',
|
|
87
|
+
props: ['lousa', 'mesa', 'mesa', 'estante', 'planta'] },
|
|
88
|
+
palco: { palavras: /\b(palco|show|teatro|concerto|festa|balada|evento)\b/, dentro: true, parede: '#0b0612',
|
|
89
|
+
chao: { tipo: 'madeira', cor: '#3a2a20', cor2: '#33251c' }, luz: '#ffe1f5', recorte: '#ff4fd8', acento: '#22d3ee', holofotes: true,
|
|
90
|
+
props: ['caixa de som', 'caixa de som', 'luminaria'] },
|
|
91
|
+
campo: { palavras: /\b(campo|montanhas?|colina|natureza|ao ar livre|vale)\b/, ceu: ['#5aa3ec', '#dcefff'],
|
|
92
|
+
chao: { tipo: 'grama', cor: '#5ca846', cor2: '#529b3e' }, luz: '#fff2d6', recorte: '#c8f0b0', acento: '#facc15', sol: true,
|
|
93
|
+
props: ['montanha', 'arvore', 'flor', 'arvore', 'pedra', 'montanha', 'flor'] },
|
|
94
|
+
};
|
|
95
|
+
// O que o set antigo chamava por outro nome.
|
|
96
|
+
const APELIDOS_LUGAR = { neon: 'palco', oceano: 'fundo_do_mar', terra: 'deserto', podcast: 'telas', estudio: 'telas', noite: 'telas' };
|
|
97
|
+
|
|
98
|
+
const HORAS = {
|
|
99
|
+
noite: { palavras: /\b(noite|noturn\w*|madrugada|luar|a noite|de noite)\b/, ceu: ['#070b24', '#1c2552'], luz: '#b8c6ff', forca: 0.45, lua: true, estrelas: true },
|
|
100
|
+
por_do_sol: { palavras: /\b(por do sol|entardecer|fim de tarde|crepusculo|poente|anoitecer)\b/, ceu: ['#46368a', '#ff9a5a'], luz: '#ffb27a', forca: 0.8, solBaixo: true },
|
|
101
|
+
amanhecer: { palavras: /\b(amanhecer|nascer do sol|alvorada|de manha cedo)\b/, ceu: ['#6a8fd8', '#ffd2a8'], luz: '#ffd9b0', forca: 0.85, solBaixo: true },
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/* ── O CATÁLOGO DE PEÇAS (kit/mundo.jsx desenha cada uma) ───────────────
|
|
105
|
+
A chave é a peça; as palavras são como a pessoa (ou a IA) diz. */
|
|
106
|
+
const PECAS = {
|
|
107
|
+
palmeira: /\bpalmeiras?|coqueiros?\b/, arvore: /\barvores?|arvorezinha\b/, pinheiro: /\bpinheiros?\b/, 'pinheiro nevado': /\bpinheiros? nevados?\b/,
|
|
108
|
+
arbusto: /\barbustos?|moitas?\b/, flor: /\bflor(es)?\b/, pedra: /\bpedras?|rochas?\b/, cacto: /\bcactos?\b/, montanha: /\bmontanhas?|morros?\b/,
|
|
109
|
+
casa: /\bcasas?|casinha\b/, predio: /\bpredios?|edificios?|arranha[- ]ceus?\b/, celeiro: /\bceleiros?\b/, poste: /\bpostes?\b/,
|
|
110
|
+
carro: /\bcarros?|automove(l|is)\b/, 'guarda-sol': /\bguarda[- ]sois?|guarda[- ]sol\b/, 'cadeira de praia': /\bcadeiras? de praia\b/,
|
|
111
|
+
'castelo de areia': /\bcastelos? de areia\b/, mesa: /\bmesas?\b/, cadeira: /\bcadeiras?\b/, sofa: /\bsofas?\b/, planta: /\bplantas?|vasos?\b/,
|
|
112
|
+
estante: /\bestantes?|prateleiras?\b/, computador: /\bcomputador(es)?|notebooks?|pc\b/, luminaria: /\bluminarias?|abajur\b/,
|
|
113
|
+
quadro: /\bquadros?\b/, lousa: /\blousas?|quadro negro\b/, balcao: /\bbalca(o|oes)\b/, tapete: /\btapetes?\b/, banco: /\bbancos? de praca|banquinhos?\b/,
|
|
114
|
+
cerca: /\bcercas?\b/, feno: /\bfenos?\b/, 'boneco de neve': /\bbonecos? de neve\b/, foguete: /\bfoguetes?\b/, planeta: /\bplanetas?\b/,
|
|
115
|
+
cratera: /\bcrateras?\b/, alga: /\balgas?\b/, coral: /\bcorais?|coral\b/, bau: /\bbaus?|tesouros?\b/, cogumelo: /\bcogumelos?\b/,
|
|
116
|
+
nuvem: /\bnuvens?\b/, fogueira: /\bfogueiras?\b/, barraca: /\bbarracas?|tendas?\b/, barco: /\bbarcos?|canoas?\b/,
|
|
117
|
+
'caixa de som': /\bcaixas? de som|alto[- ]falantes?\b/,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
/* ── AS ESPÉCIES (kit/mundo.jsx monta cada uma de blocos) ────────────── */
|
|
121
|
+
const ESPECIES = {
|
|
122
|
+
cachorro: /\b(cachorr\w*|cao|caes|cachorrinho|dog|vira[- ]lata|labrador|filhote)\b/, gato: /\b(gat[oa]s?|gatinh\w*|felino)\b/,
|
|
123
|
+
cavalo: /\b(caval\w*|egua|potro|pônei|ponei)\b/, vaca: /\b(vacas?|boi|bois|touro|bezerro)\b/, porco: /\b(porc\w*|leitao)\b/,
|
|
124
|
+
ovelha: /\b(ovelhas?|carneiros?|cordeiro)\b/, raposa: /\b(raposas?)\b/, lobo: /\b(lob[oa]s?)\b/, urso: /\b(urs[oa]s?)\b/,
|
|
125
|
+
panda: /\b(pandas?)\b/, coelho: /\b(coelh\w*)\b/, leao: /\b(leao|leoes|leoa)\b/, tigre: /\b(tigres?)\b/, elefante: /\b(elefantes?)\b/,
|
|
126
|
+
girafa: /\b(girafas?)\b/, galinha: /\b(galinhas?|galos?|pintinhos?)\b/, pato: /\b(pat[oa]s?|patinho)\b/, pinguim: /\b(pinguins?)\b/,
|
|
127
|
+
passaro: /\b(passar\w*|ave|aves|passarinho)\b/, papagaio: /\b(papagaios?|arara)\b/, coruja: /\b(corujas?)\b/,
|
|
128
|
+
peixe: /\b(peixes?|peixinho)\b/, tubarao: /\b(tubara(o|oes))\b/, golfinho: /\b(golfinhos?)\b/, tartaruga: /\b(tartarugas?|jabuti)\b/,
|
|
129
|
+
dinossauro: /\b(dinossauros?|dino|t[- ]?rex)\b/, sapo: /\b(sapos?|ra)\b/, macaco: /\b(macac\w*)\b/,
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const PESSOA = /\b(humanos?|pessoas?|homens?|mulher(es)?|menin[oa]s?|crianc\w*|amig[oa]s?|garot[oa]s?|rapaz|mo[cç][oa]s?|senhor(a|es)?|vov[oóô]|av[oóô]s?|casal|familia|cliente|vendedor\w*|professor\w*|alun[oa]s?|medic[oa]s?|astronautas?|surfistas?|chef|cozinheir[oa]s?)\b/;
|
|
133
|
+
const ROBO = /\b(robo|robos|ia|inteligencia artificial|mascote|claude|chatgpt|gpt|gemini|assistente|bot)\b/;
|
|
134
|
+
const NUMEROS = { um: 1, uma: 1, dois: 2, duas: 2, tres: 3, quatro: 4, cinco: 5, casal: 2, par: 2 };
|
|
135
|
+
|
|
136
|
+
function contar(texto, re) {
|
|
137
|
+
// "dois humanos", "3 cachorros", "um casal": o número logo antes da palavra.
|
|
138
|
+
const s = sem(texto);
|
|
139
|
+
if (re === PESSOA && /\b(um )?casal\b/.test(s)) return 2;
|
|
140
|
+
const m = new RegExp(`(\\d+|um|uma|dois|duas|tres|quatro|cinco)\\s+(?:\\w+\\s+)?${re.source.replace(/^\\b|\\b$/g, '')}`).exec(s);
|
|
141
|
+
if (m) return Math.min(4, Number(m[1]) || NUMEROS[m[1]] || 1);
|
|
142
|
+
return re.test(s) ? 1 : 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** O lugar e a hora que o texto pede, ou null. */
|
|
146
|
+
function lerLugar(texto) {
|
|
147
|
+
const s = sem(texto);
|
|
148
|
+
let lugar = null, onde = -1;
|
|
149
|
+
// O mais específico primeiro: "fundo do mar" antes de "mar", "sala de aula" antes de "sala".
|
|
150
|
+
const ordem = ['cobertura', 'fundo_do_mar', 'escola', 'praia', 'espaco', 'neve', 'deserto', 'fazenda', 'floresta', 'parque', 'cidade', 'cozinha', 'escritorio', 'palco', 'sala', 'campo', 'telas'];
|
|
151
|
+
for (const n of ordem) { const m = LUGARES[n].palavras.exec(s); if (m) { lugar = n; onde = m.index; break; } }
|
|
152
|
+
/* A hora vale para AQUELE lugar: "na praia, depois na cidade à noite" é
|
|
153
|
+
praia de dia. Com lugar no texto, só a hora a até 40 letras dele. */
|
|
154
|
+
let hora = null;
|
|
155
|
+
for (const [n, h] of Object.entries(HORAS)) {
|
|
156
|
+
const m = h.palavras.exec(s);
|
|
157
|
+
if (!m) continue;
|
|
158
|
+
if (onde < 0) { hora = n; break; }
|
|
159
|
+
// Outro lugar no meio ("praia, depois vão pra cidade à noite"): a hora é DELE.
|
|
160
|
+
const entre = s.slice(Math.min(m.index, onde) + 1, Math.max(m.index, onde));
|
|
161
|
+
const outroLugar = Object.entries(LUGARES).some(([k, L]) => k !== lugar && L.palavras.test(entre));
|
|
162
|
+
if (!outroLugar && Math.abs(m.index - onde) <= 40) { hora = n; break; }
|
|
163
|
+
}
|
|
164
|
+
return { lugar, hora };
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/** As peças do catálogo e os bichos que o texto cita (em ordem de aparição). */
|
|
168
|
+
function lerCoisas(texto) {
|
|
169
|
+
const s = sem(texto);
|
|
170
|
+
const pecas = [];
|
|
171
|
+
for (const [nome, re] of Object.entries(PECAS)) {
|
|
172
|
+
const m = re.exec(s);
|
|
173
|
+
if (m) pecas.push({ nome, em: m.index });
|
|
174
|
+
}
|
|
175
|
+
// "pinheiro nevado" ganha de "pinheiro"; "cadeira de praia" ganha de "cadeira".
|
|
176
|
+
const filtradas = pecas.filter((p) => !pecas.some((q) => q !== p && q.nome.startsWith(p.nome + ' ') && Math.abs(q.em - p.em) < 3)
|
|
177
|
+
&& !pecas.some((q) => q !== p && q.nome.includes(p.nome) && q.nome !== p.nome && Math.abs(q.em - p.em) < 12));
|
|
178
|
+
const bichos = [];
|
|
179
|
+
for (const [esp, re] of Object.entries(ESPECIES)) {
|
|
180
|
+
const m = re.exec(s);
|
|
181
|
+
if (m) bichos.push({ especie: esp, em: m.index, quantos: contar(texto, re) || 1 });
|
|
182
|
+
}
|
|
183
|
+
return { pecas: filtradas.sort((a, b) => a.em - b.em).map((p) => p.nome), bichos: bichos.sort((a, b) => a.em - b.em) };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** O que o PEDIDO descreve do elenco: quantas pessoas, que bichos, se há IA. */
|
|
187
|
+
function lerElenco(texto) {
|
|
188
|
+
const s = sem(texto);
|
|
189
|
+
const { bichos } = lerCoisas(texto);
|
|
190
|
+
return { pessoas: contar(texto, PESSOA), robo: ROBO.test(s), bichos };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* ── O MUNDO MONTADO ────────────────────────────────────────────────────
|
|
194
|
+
A peça é posta numa faixa ATRÁS e DOS LADOS do elenco, nunca no corredor
|
|
195
|
+
entre a câmera e quem fala. Com mar, a faixa é a areia antes da água. */
|
|
196
|
+
function rngDe(semente) {
|
|
197
|
+
let s = (Number(semente) >>> 0) || 1;
|
|
198
|
+
return () => { s = (Math.imul(s ^ (s >>> 15), 2246822507) + 0x9e3779b9) >>> 0; return s / 4294967296; };
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const ehCor = (c) => typeof c === 'string' && /^#[0-9a-f]{6}$/i.test(c);
|
|
202
|
+
|
|
203
|
+
function posicionar(nomes, { semente = 1, agua = false, dentro = false, largura = 1 } = {}) {
|
|
204
|
+
const r = rngDe(semente);
|
|
205
|
+
const out = [];
|
|
206
|
+
const livre = (x, z, raio) => out.every((p) => Math.hypot(p.x - x, p.z - z) > raio + (p.raio || 0.8));
|
|
207
|
+
nomes.forEach((nome, i) => {
|
|
208
|
+
const grande = /predio|montanha|celeiro|planeta|foguete|palmeira|pinheiro|arvore/.test(typeof nome === 'string' ? nome : '');
|
|
209
|
+
const raio = grande ? 1.3 : 0.75;
|
|
210
|
+
for (let tent = 0; tent < 40; tent++) {
|
|
211
|
+
let x, z;
|
|
212
|
+
if (dentro) {
|
|
213
|
+
// Encostado na parede do fundo ou nos lados do set.
|
|
214
|
+
x = (r() * 2 - 1) * 4.6 * largura; z = -3.1 + r() * 0.9;
|
|
215
|
+
if (Math.abs(x) < 1.7 && tent < 30) continue;
|
|
216
|
+
} else if (agua) {
|
|
217
|
+
const lado = i % 2 ? 1 : -1;
|
|
218
|
+
x = lado * (1.9 + r() * 4.5) * largura; z = -4.2 + r() * 3.6;
|
|
219
|
+
} else {
|
|
220
|
+
const lado = i % 2 ? 1 : -1;
|
|
221
|
+
const longe = grande ? 0.55 + r() * 0.45 : r();
|
|
222
|
+
x = lado * (1.8 + r() * 5.5) * largura; z = -1.6 - longe * 9;
|
|
223
|
+
if (r() < 0.25 && !grande) { x = lado * (2.2 + r() * 1.5); z = -0.4 + r() * 1.4; }
|
|
224
|
+
}
|
|
225
|
+
if (!livre(x, z, raio) && tent < 39) continue;
|
|
226
|
+
out.push({ x: Math.round(x * 100) / 100, z: Math.round(z * 100) / 100, raio, giro: Math.round((r() * 2 - 1) * 60) / 100, escala: Math.round((0.85 + r() * 0.35) * 100) / 100 });
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
return out;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* O mundo de um trecho do vídeo.
|
|
235
|
+
* @param {string|object} pedido "praia ao pôr do sol" ou {lugar, hora, elementos, ceu, chao, ...}
|
|
236
|
+
* @param {object} op {texto (o pedido e as falas: completa o lugar), pedidoTexto (só o
|
|
237
|
+
* pedido da pessoa: as coisas que ele cita entram na cena), semente, nElenco}
|
|
238
|
+
*/
|
|
239
|
+
function mundoDe(pedido, { texto = '', pedidoTexto = '', semente = 1, nElenco = 1 } = {}) {
|
|
240
|
+
let obj = pedido && typeof pedido === 'object' ? pedido : { lugar: String(pedido || '') };
|
|
241
|
+
/* Um cenário que a IA criou e a pessoa deixou salvar (cenarios.js): o
|
|
242
|
+
nome dele no lugar traz tudo de volta; o que a cena disser por cima vence. */
|
|
243
|
+
const salvo = require('./cenarios.js').ler(obj.cenario || obj.lugar);
|
|
244
|
+
if (salvo) obj = { ...salvo, ...obj, lugar: salvo.base, hora: obj.hora || salvo.hora, elementos: obj.elementos || salvo.elementos };
|
|
245
|
+
const descr = [obj.lugar, obj.descricao, obj.hora].filter(Boolean).join(' ');
|
|
246
|
+
let { lugar, hora } = lerLugar(descr);
|
|
247
|
+
const nomeDireto = sem(obj.lugar).replace(/\s+/g, '_');
|
|
248
|
+
if (!lugar && LUGARES[nomeDireto]) lugar = nomeDireto;
|
|
249
|
+
if (!lugar && APELIDOS_LUGAR[nomeDireto]) lugar = APELIDOS_LUGAR[nomeDireto];
|
|
250
|
+
// Sem lugar no pedido da cena, o texto do vídeo (o pedido da pessoa e as falas) completa.
|
|
251
|
+
const doTexto = lerLugar(texto);
|
|
252
|
+
if (!lugar) lugar = doTexto.lugar || 'cobertura';
|
|
253
|
+
if (!hora) hora = doTexto.hora;
|
|
254
|
+
if (APELIDOS_LUGAR[lugar]) lugar = APELIDOS_LUGAR[lugar];
|
|
255
|
+
const L = LUGARES[lugar];
|
|
256
|
+
const H = hora && !L.dentro ? HORAS[hora] : null;
|
|
257
|
+
|
|
258
|
+
// Os elementos: os que a cena listou (qualquer palavra) + os que o texto cita + os do lugar.
|
|
259
|
+
const pedidos = Array.isArray(obj.elementos) ? obj.elementos.map((e) => (typeof e === 'string' ? e : (e && (e.nome || e.tipo)) || '')).filter(Boolean) : [];
|
|
260
|
+
// As falas citam coisa de passagem ("comprei um carro"): só o PEDIDO põe coisa na cena.
|
|
261
|
+
const citados = lerCoisas([descr, pedidoTexto, Array.isArray(obj.elementos) ? '' : String(obj.elementos || '')].join(' ')).pecas;
|
|
262
|
+
const nomes = [];
|
|
263
|
+
for (const e of [...pedidos, ...citados]) {
|
|
264
|
+
const s = sem(e).trim();
|
|
265
|
+
const peca = Object.keys(PECAS).find((k) => PECAS[k].test(s) || sem(k) === s);
|
|
266
|
+
nomes.push(peca ? { peca } : { icone: s.slice(0, 40) });
|
|
267
|
+
}
|
|
268
|
+
// O lugar completa até 7 peças: a cena nunca fica vazia, e o que a IA pediu vem primeiro.
|
|
269
|
+
for (const p of L.props) if (nomes.length < 7 || nomes.length < L.props.length) nomes.push({ peca: p });
|
|
270
|
+
const lugares = posicionar(nomes.map((n) => n.peca || n.icone), { semente, agua: !!L.agua, dentro: !!L.dentro, largura: nElenco > 2 ? 1.25 : 1 });
|
|
271
|
+
const props = nomes.slice(0, lugares.length).map((n, i) => ({ ...n, ...lugares[i] }));
|
|
272
|
+
|
|
273
|
+
const ceu = Array.isArray(obj.ceu) && obj.ceu.every(ehCor) ? obj.ceu : (H ? H.ceu : L.ceu) || null;
|
|
274
|
+
const chao = { ...L.chao, ...(obj.chao && typeof obj.chao === 'object' ? obj.chao : {}), ...(ehCor(obj.chao) ? { cor: obj.chao, cor2: obj.chao } : {}) };
|
|
275
|
+
return {
|
|
276
|
+
nome: lugar + (H ? ':' + hora : ''), lugar, hora: H ? hora : null,
|
|
277
|
+
dentro: !!L.dentro, telas: !!L.telas, cobertura: !!L.cobertura, parede: ehCor(obj.parede) ? obj.parede : L.parede || null,
|
|
278
|
+
ceu, chao, agua: L.agua || null, subaquatico: !!L.subaquatico, holofotes: !!L.holofotes,
|
|
279
|
+
sol: !!(L.sol && !(H && H.lua)), solBaixo: !!(H && H.solBaixo), lua: !!(H && H.lua), estrelas: !!(L.estrelas || (H && H.estrelas)),
|
|
280
|
+
neblina: L.neblina || 0,
|
|
281
|
+
luz: ehCor(obj.luz) ? obj.luz : (H ? H.luz : L.luz), forca: H ? H.forca : 1,
|
|
282
|
+
recorte: L.recorte, acento: ehCor(obj.acento) ? obj.acento : L.acento,
|
|
283
|
+
props,
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** Os ícones que o mundo e o elenco precisam baixar (colecao.js) antes do render. */
|
|
288
|
+
function iconesPendentes(partitura) {
|
|
289
|
+
const out = new Set();
|
|
290
|
+
for (const m of partitura.mundos || []) for (const p of m.props) if (p.icone && !p.src) out.add(p.icone);
|
|
291
|
+
for (const e of [...(partitura.elenco || []), ...(partitura.figurantes || [])]) if (e.tipo === 'coisa' && !e.src) out.add(e.icone);
|
|
292
|
+
return [...out];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Põe o PNG baixado em cada peça e personagem que pediu aquele ícone; o que não achou ícone sai da cena. */
|
|
296
|
+
function aplicarIcones(partitura, achados) {
|
|
297
|
+
for (const m of partitura.mundos || []) {
|
|
298
|
+
m.props = m.props.filter((p) => !p.icone || achados[p.icone]).map((p) => (p.icone ? { ...p, src: achados[p.icone] } : p));
|
|
299
|
+
}
|
|
300
|
+
for (const e of [...(partitura.elenco || []), ...(partitura.figurantes || [])]) {
|
|
301
|
+
if (e.tipo === 'coisa') {
|
|
302
|
+
if (achados[e.icone]) e.src = achados[e.icone];
|
|
303
|
+
else { e.tipo = 'robo'; e.cor = e.cor || '#d97757'; } // sem ícone, a coisa que fala vira o mascote
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return partitura;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* As peças que a IA TROUXE para a cena: a logo, o botão ou o produto recortado
|
|
311
|
+
* do site (estudio_midia acao:elemento), um PNG sem fundo que vira blocos como
|
|
312
|
+
* os ícones 3D. "Imagens e parte de sites devem entrar no motion como ícone
|
|
313
|
+
* 3D" (Joel, 12/10). Ficam na frente, no centro, um pouco maiores que o resto
|
|
314
|
+
* do lugar: é o assunto. No máximo três, para não virar vitrine.
|
|
315
|
+
*/
|
|
316
|
+
function adicionarObjetos(partitura, objetos) {
|
|
317
|
+
const lista = (Array.isArray(objetos) ? objetos : []).filter((o) => o && o.src).slice(0, 3);
|
|
318
|
+
if (!lista.length) return partitura;
|
|
319
|
+
for (const m of partitura.mundos || []) {
|
|
320
|
+
lista.forEach((o, i) => {
|
|
321
|
+
const x = (i - (lista.length - 1) / 2) * 2.4;
|
|
322
|
+
m.props.push({ icone: String(o.nome || 'objeto').slice(0, 40), src: o.src, x, z: -1.6, raio: 1, giro: 0, escala: 1.35, trazido: true });
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
return partitura;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
module.exports = { mundoDe, lerLugar, lerCoisas, lerElenco, iconesPendentes, aplicarIcones, adicionarObjetos, LUGARES, HORAS, PECAS, ESPECIES, sem };
|