primocode 10.6.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/lib/catalogo.js +5 -2
- package/lib/estudio/cenarios.js +91 -0
- package/lib/estudio/colecao.js +1 -0
- package/lib/estudio/index.js +45 -4
- package/lib/estudio/movimentos.js +212 -0
- package/lib/estudio/mundo.js +25 -2
- package/lib/estudio/plano.js +6 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PrimoCode v10.
|
|
1
|
+
# PrimoCode v10.7.0
|
|
2
2
|
|
|
3
3
|
Agente de engenharia brasileiro para o terminal, no estilo Claude Code.
|
|
4
4
|
Cria arquivos de verdade, roda comandos, **controla o navegador e o desktop** —
|
|
@@ -9,7 +9,7 @@ OpenRouter, e cada apelido (`top`/`main`/`fast`) é uma **corrente**: se um mode
|
|
|
9
9
|
está no limite de pedidos, o próximo atende. Você não vê o erro, e não paga nada.
|
|
10
10
|
|
|
11
11
|
```
|
|
12
|
-
▐▛███▜▌ Primo Code v10.
|
|
12
|
+
▐▛███▜▌ Primo Code v10.7.0
|
|
13
13
|
▝▜█████▛▘ Bem-vindo de volta, Joel
|
|
14
14
|
▘▘ ▝▝ ~/primocode · /dir <pasta> muda
|
|
15
15
|
```
|
package/lib/catalogo.js
CHANGED
|
@@ -109,7 +109,10 @@ const GRUPOS = [
|
|
|
109
109
|
+ 'o render empurra para dentro, encolhe o texto largo e afasta o colado; enfeite de fundo marque decorativo: true.' },
|
|
110
110
|
|
|
111
111
|
{ tool: 'estudio_midia', escreve: true, faz: 'procura e baixa ícone, imagem, música e efeito sonoro da internet, e lista as fontes',
|
|
112
|
-
args: 'acao: biblioteca | imagem | icones | icones3d | animacoes | som | trilha | efeito | fontes | print | elementos | elemento | recortar. termo. id (para baixar). '
|
|
112
|
+
args: 'acao: biblioteca | movimentos | cenarios | salvar_cenario | imagem | icones | icones3d | animacoes | som | trilha | efeito | fontes | print | elementos | elemento | recortar. termo. id (para baixar). '
|
|
113
|
+
+ 'movimentos termo:"quique" categoria:entrada|saida|enfase|loop: os 100+ movimentos prontos com nome; no elemento, movimento:"<nome>" (ou {nome, em, duracao}, ou uma lista) vira quadros-chave sozinho. Varie: não use o mesmo em todo elemento. Pode inventar o seu em anima. '
|
|
114
|
+
+ 'cenarios: os lugares prontos do vídeo 3D e os que a pessoa já salvou. Pode CRIAR um cenário novo na cena (lugar base + ceu, chao, parede, luz, acento, hora, elementos); '
|
|
115
|
+
+ 'no fim do vídeo pergunte "posso salvar este cenário pra usar na próxima vez?" e, SÓ com o sim, salvar_cenario com nome, cenario {…} e autorizado:true. Depois, lugar:"<nome>" traz ele de volta. '
|
|
113
116
|
+ 'biblioteca: o cardápio INTEIRO do que existe (ícone 2D, ícone 3D, animação pronta, efeito sonoro, música, fonte, personagem 3D) — olhe antes de escolher. '
|
|
114
117
|
+ 'icones3d termo:"coffee": os 1.500+ ícones 3D renderizados do Fluent Emoji (MIT); com item:"fluent3d:<nome>" e o id do projeto, baixa e devolve o elemento {tipo:"imagem"} pronto. '
|
|
115
118
|
+ 'animacoes termo:"fire": as 881 animações prontas do Noto (Lottie, vetor; CC BY 4.0, ponha o crédito); com item:"noto:<código>", baixa e devolve o elemento {tipo:"kit", peca:"animacao", src, loop} pronto. '
|
|
@@ -119,7 +122,7 @@ const GRUPOS = [
|
|
|
119
122
|
+ 'Use SE o pedido trouxer um site e o vídeo for SOBRE ele (lançamento, tour, case); vídeo que só cita a marca não precisa. '
|
|
120
123
|
+ 'elementos: url — lê o CÓDIGO do site e lista as peças que dá para tirar (logo, menu, titulo, hero, botao, cartao, imagem, formulario, preco, rodape), cada uma com o seletor. '
|
|
121
124
|
+ 'elemento: url + seletor (ou texto: o que está escrito nele) — tira SÓ aquela peça, em PNG com fundo transparente e no dobro da resolução, e devolve o código dela (html e estilo: fonte, cor, raio, sombra) e um elemento {tipo:"imagem"} pronto; '
|
|
122
|
-
+ 'use para pôr o botão, o cartão de preço ou a logo do site animando no vídeo (ou redesenhe a peça como componente a partir do código). '
|
|
125
|
+
+ 'use para pôr o botão, o cartão de preço ou a logo do site animando no vídeo (ou redesenhe a peça como componente a partir do código). No vídeo 3D (estilo personagem), a peça ENTRA NA CENA em blocos: passe roteiro.objetos:[{nome:"logo", src:<o src devolvido>}] (até 3). '
|
|
123
126
|
+ 'recortar: arquivo — tira o fundo de qualquer foto com IA (rembg), PNG transparente. '
|
|
124
127
|
+ 'efeitos: lista os componentes guardados (os menos usados primeiro). usar-efeito {nome}: copia para o projeto e devolve o elemento — use se um deles servir a ESTE roteiro. '
|
|
125
128
|
+ 'guardar-efeito {arquivo, nome, descricao}: guarda um componente que você escreveu e ficou bom, para os próximos vídeos '
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cenarios.js — os cenários que a IA criou e a pessoa deixou guardar.
|
|
3
|
+
*
|
|
4
|
+
* "a IA vai criar os próprios cenários, além de ter os próprios cenários
|
|
5
|
+
* prontos, e pede pro usuário se pode salvar pra usar na próxima vez."
|
|
6
|
+
* (Joel, 12/10)
|
|
7
|
+
*
|
|
8
|
+
* Os lugares prontos moram no mundo.js (praia, cidade, cobertura…). A IA já
|
|
9
|
+
* podia mudar o céu, o chão, a luz e os elementos de UMA cena; o que se perdia
|
|
10
|
+
* era o resultado: o próximo vídeo começava do zero. Aqui o cenário inventado
|
|
11
|
+
* ganha nome e fica em ~/.primocode/cenarios.json — só com o "sim" da pessoa
|
|
12
|
+
* (a ferramenta recusa sem `autorizado: true`) — e vira um lugar como os
|
|
13
|
+
* outros: `lugar: "<nome>"` na cena o traz de volta.
|
|
14
|
+
*
|
|
15
|
+
* Só se guarda o que o mundo.js sabe desenhar: o lugar base, cores #rrggbb,
|
|
16
|
+
* a hora e até 7 elementos. Nada de caminho de arquivo nem código.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const os = require('os');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
|
|
25
|
+
const arquivo = () => path.join(os.homedir(), '.primocode', 'cenarios.json');
|
|
26
|
+
const ehCor = (c) => typeof c === 'string' && /^#[0-9a-f]{6}$/i.test(c);
|
|
27
|
+
const TIPOS_DE_CHAO = ['areia', 'asfalto', 'grama', 'neve', 'lua', 'madeira', 'ladrilho'];
|
|
28
|
+
const HORAS = ['noite', 'por_do_sol', 'amanhecer'];
|
|
29
|
+
|
|
30
|
+
/** "Loja da Ana à noite" → "loja_da_ana_a_noite" (o mesmo jeito dos lugares do mundo.js). */
|
|
31
|
+
function chave(nome) {
|
|
32
|
+
return String(nome || '').normalize('NFD').replace(/[̀-ͯ]/g, '').toLowerCase()
|
|
33
|
+
.replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '').slice(0, 40);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function lerTudo() {
|
|
37
|
+
try { const j = JSON.parse(fs.readFileSync(arquivo(), 'utf8')); return j && typeof j === 'object' ? j : {}; } catch { return {}; }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Só o que o mundo sabe desenhar; o resto cai fora em silêncio. */
|
|
41
|
+
function limpar(c, bases) {
|
|
42
|
+
const o = c && typeof c === 'object' ? c : {};
|
|
43
|
+
const base = chave(o.base || o.lugar);
|
|
44
|
+
const out = { base: bases.includes(base) ? base : 'cobertura' };
|
|
45
|
+
if (typeof o.descricao === 'string') out.descricao = o.descricao.slice(0, 200);
|
|
46
|
+
if (Array.isArray(o.ceu) && o.ceu.length === 2 && o.ceu.every(ehCor)) out.ceu = o.ceu;
|
|
47
|
+
for (const k of ['parede', 'luz', 'acento']) if (ehCor(o[k])) out[k] = o[k];
|
|
48
|
+
if (o.chao && typeof o.chao === 'object') {
|
|
49
|
+
const ch = {};
|
|
50
|
+
if (TIPOS_DE_CHAO.includes(o.chao.tipo)) ch.tipo = o.chao.tipo;
|
|
51
|
+
if (ehCor(o.chao.cor)) ch.cor = o.chao.cor;
|
|
52
|
+
if (ehCor(o.chao.cor2)) ch.cor2 = o.chao.cor2;
|
|
53
|
+
if (Object.keys(ch).length) out.chao = ch;
|
|
54
|
+
} else if (ehCor(o.chao)) out.chao = { cor: o.chao, cor2: o.chao };
|
|
55
|
+
if (HORAS.includes(o.hora)) out.hora = o.hora.replace(/_/g, ' ');
|
|
56
|
+
if (Array.isArray(o.elementos)) {
|
|
57
|
+
const els = o.elementos.map((e) => (typeof e === 'string' ? e : e && e.nome) || '').map((s) => String(s).slice(0, 40)).filter(Boolean).slice(0, 7);
|
|
58
|
+
if (els.length) out.elementos = els;
|
|
59
|
+
}
|
|
60
|
+
return out;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Guarda o cenário. `autorizado` é a resposta da pessoa: sem ela, nada é escrito.
|
|
65
|
+
* @param {{nome:string, cenario:object, autorizado:boolean, bases:string[]}} op
|
|
66
|
+
*/
|
|
67
|
+
function salvar({ nome, cenario, autorizado, bases = [] } = {}) {
|
|
68
|
+
if (autorizado !== true) {
|
|
69
|
+
return { ok: false, error: 'falta o sim da pessoa.', comoResolver: 'pergunte "posso salvar este cenário pra usar na próxima vez?" e só chame de novo com autorizado:true se ela disser que sim.' };
|
|
70
|
+
}
|
|
71
|
+
const k = chave(nome);
|
|
72
|
+
if (!k) return { ok: false, error: 'dê um nome ao cenário (ex: "loja da Ana à noite").' };
|
|
73
|
+
if (bases.includes(k)) return { ok: false, error: `"${k}" já é um lugar pronto; escolha outro nome.` };
|
|
74
|
+
const tudo = lerTudo();
|
|
75
|
+
const novo = { ...limpar(cenario, bases), nome: String(nome).slice(0, 60), salvo: new Date().toISOString() };
|
|
76
|
+
tudo[k] = novo;
|
|
77
|
+
fs.mkdirSync(path.dirname(arquivo()), { recursive: true });
|
|
78
|
+
fs.writeFileSync(arquivo(), JSON.stringify(tudo, null, 2));
|
|
79
|
+
return { ok: true, chave: k, cenario: novo, comoUsar: `na próxima cena, lugar:"${k}" traz este cenário de volta.` };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function ler(nome) {
|
|
83
|
+
const k = chave(nome);
|
|
84
|
+
return k ? lerTudo()[k] || null : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function listar() {
|
|
88
|
+
return Object.entries(lerTudo()).map(([k, c]) => ({ chave: k, nome: c.nome, base: c.base, descricao: c.descricao || '' }));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = { salvar, ler, listar, limpar, chave, arquivo };
|
package/lib/estudio/colecao.js
CHANGED
|
@@ -198,6 +198,7 @@ function cardapio() {
|
|
|
198
198
|
personagens3d: 'o estilo personagem (voxel): bonecos de blocos e o mascote, com a câmera de cinema — estudio_roteiro estilo:"personagem"',
|
|
199
199
|
efeitosSonoros: { sintetizados: sons, internet: 'acao:efeito termo:"whoosh" (Openverse, licença aberta, até 6 s)' },
|
|
200
200
|
musicas: { internet: 'acao:trilha termo:"lofi" (Openverse, 30 s ou mais)', automatica: 'sem trilha pedida, o PrimoCode escolhe uma sem repetir as últimas' },
|
|
201
|
+
movimentos: { quantos: Object.keys(require('./movimentos.js').CATALOGO).length, acao: 'movimentos', exemplo: 'termo:"quique" → movimento:"quicar-de-baixo"' },
|
|
201
202
|
fontes: { quantas: fontes || 128, acao: 'fontes', nota: 'Google Fonts, por tipo (condensada, display, serifada, manuscrita…)' },
|
|
202
203
|
imagens: 'acao:imagem termo:"…" (Openverse e Wikimedia; Google com chave) — só quando a pessoa pediu imagem',
|
|
203
204
|
};
|
package/lib/estudio/index.js
CHANGED
|
@@ -67,6 +67,8 @@ const lancamento = require('./lancamento.js');
|
|
|
67
67
|
const glifos = require('./glifos.js');
|
|
68
68
|
const personagem = require('./personagem.js');
|
|
69
69
|
const mundo = require('./mundo.js');
|
|
70
|
+
const cenarios = require('./cenarios.js');
|
|
71
|
+
const movimentos = require('./movimentos.js');
|
|
70
72
|
const colecao = require('./colecao.js');
|
|
71
73
|
const { escolherEstilo, LEVES } = require('./estilo.js');
|
|
72
74
|
const sonsLeves = require('./sons-leves.js');
|
|
@@ -277,6 +279,28 @@ function projetoRecente() {
|
|
|
277
279
|
return lista[0] || null;
|
|
278
280
|
}
|
|
279
281
|
|
|
282
|
+
/* Sem id, o projeto é o que acabou de ser criado. Visto em 12/10: o agente
|
|
283
|
+
criou o projeto, chamou estudio_narrar sem id e o vídeo morreu ali. Só vale
|
|
284
|
+
para chamada SEM id (id errado continua sendo erro) e para projeto de até
|
|
285
|
+
30 minutos, como no estudio_plano. */
|
|
286
|
+
function projetoOuRecente(id) {
|
|
287
|
+
if (id != null && String(id).trim() !== '') return projeto.ler(id);
|
|
288
|
+
return projetoRecente();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/* roteiro.objetos: [{nome, src}] com o PNG DENTRO da pasta do projeto (o que
|
|
292
|
+
o estudio_midia baixou ou recortou). Caminho de fora some: o render só
|
|
293
|
+
enxerga a pasta do projeto, e a IA não escolhe arquivo da máquina. */
|
|
294
|
+
function objetosDoRoteiro(objetos, pasta) {
|
|
295
|
+
const raiz = path.resolve(pasta);
|
|
296
|
+
return (Array.isArray(objetos) ? objetos : []).map((o) => {
|
|
297
|
+
if (!o || typeof o.src !== 'string') return null;
|
|
298
|
+
const abs = path.resolve(raiz, o.src);
|
|
299
|
+
if (!abs.startsWith(raiz + path.sep) || !/\.png$/i.test(abs) || !fs.existsSync(abs)) return null;
|
|
300
|
+
return { nome: o.nome, src: path.relative(raiz, abs).replace(/\\/g, '/') };
|
|
301
|
+
}).filter(Boolean);
|
|
302
|
+
}
|
|
303
|
+
|
|
280
304
|
/** O roteiro é utilizável como veio? (objeto ou JSON, com partes de tipo conhecido) */
|
|
281
305
|
function roteiroValido(r) {
|
|
282
306
|
let x = r;
|
|
@@ -527,6 +551,19 @@ async function ferramentaMidia(args) {
|
|
|
527
551
|
/* A BIBLIOTECA (colecao.js, 11/10): o cardápio inteiro, o ícone 3D e a
|
|
528
552
|
animação pronta. Procurar não usa rede (o índice mora no pacote); com
|
|
529
553
|
id, baixa para o projeto e devolve o elemento pronto. */
|
|
554
|
+
/* OS MOVIMENTOS (movimentos.js, 12/10): 100+ entradas, saídas, ênfases e loops com nome. */
|
|
555
|
+
if (acao === 'movimentos' || acao === 'movimento') {
|
|
556
|
+
return movimentos.procurar({ termo: args.termo, categoria: args.categoria, limite: Number(args.limite) || 30 });
|
|
557
|
+
}
|
|
558
|
+
/* OS CENÁRIOS (cenarios.js, 12/10): os lugares prontos + os que a IA criou
|
|
559
|
+
e a pessoa deixou guardar. Salvar só com o sim dela (autorizado:true). */
|
|
560
|
+
if (acao === 'cenarios' || acao === 'cenários') {
|
|
561
|
+
return { ok: true, prontos: Object.keys(mundo.LUGARES), salvos: cenarios.listar(),
|
|
562
|
+
comoCriar: 'na cena do estilo personagem: lugar (um pronto como base), ceu [cor de cima, cor do horizonte], chao {tipo, cor, cor2}, parede, luz, acento, hora e elementos. Ficou bom? Pergunte se pode salvar e use acao:"salvar_cenario".' };
|
|
563
|
+
}
|
|
564
|
+
if (acao === 'salvar_cenario' || acao === 'salvar_cenário') {
|
|
565
|
+
return cenarios.salvar({ nome: args.nome, cenario: args.cenario, autorizado: args.autorizado, bases: Object.keys(mundo.LUGARES) });
|
|
566
|
+
}
|
|
530
567
|
if (acao === 'biblioteca' || acao === 'cardapio' || acao === 'cardápio') return colecao.cardapio();
|
|
531
568
|
if (acao === 'icones3d' || acao === 'icone3d' || acao === 'animacoes' || acao === 'animacao' || acao === 'animação' || acao === 'animações') {
|
|
532
569
|
const ehAnim = /^anima/.test(acao);
|
|
@@ -585,8 +622,9 @@ async function baixarEscolhido(lista, args, p) {
|
|
|
585
622
|
async function ferramentaNarrar(args) {
|
|
586
623
|
if (args.vozes) return rodarPython('narrar.py', ['vozes'], { timeout: 30000 });
|
|
587
624
|
|
|
588
|
-
const p =
|
|
625
|
+
const p = projetoOuRecente(args.id);
|
|
589
626
|
if (!p) return semProjeto(args.id);
|
|
627
|
+
if (args.id == null || String(args.id).trim() === '') args = { ...args, id: p.id };
|
|
590
628
|
const texto = String(args.texto || '').trim();
|
|
591
629
|
if (!texto) return { ok: false, error: 'não há texto para narrar.' };
|
|
592
630
|
|
|
@@ -616,8 +654,9 @@ async function ferramentaNarrar(args) {
|
|
|
616
654
|
|
|
617
655
|
async function ferramentaVideo(args, ctx = {}) {
|
|
618
656
|
const acao = String(args.acao || 'medir').toLowerCase();
|
|
619
|
-
const p =
|
|
657
|
+
const p = projetoOuRecente(args.id);
|
|
620
658
|
if (!p) return semProjeto(args.id);
|
|
659
|
+
if (args.id == null || String(args.id).trim() === '') args = { ...args, id: p.id };
|
|
621
660
|
|
|
622
661
|
const pronto = video.conferir();
|
|
623
662
|
if (!pronto.ok) return pronto;
|
|
@@ -1884,6 +1923,7 @@ async function prepararRoteiro(p, args) {
|
|
|
1884
1923
|
if (b && b.ok) achados[nome] = b.src;
|
|
1885
1924
|
}
|
|
1886
1925
|
mundo.aplicarIcones(r.partitura, achados);
|
|
1926
|
+
mundo.adicionarObjetos(r.partitura, objetosDoRoteiro(roteiro.objetos, p.pasta));
|
|
1887
1927
|
const icones = Object.keys(achados);
|
|
1888
1928
|
projeto.anotar(p.id, 'plano', `estilo personagem: ${r.resumo.elenco.join(' e ')}${r.resumo.figurantes.length ? ` (+ ${r.resumo.figurantes.join(', ')})` : ''} em ${r.resumo.lugares.join(' → ')}, ${r.resumo.planos} planos, ${r.resumo.segundos}s${icones.length ? `; em blocos: ${icones.join(', ')}` : ''}`);
|
|
1889
1929
|
}
|
|
@@ -1962,8 +2002,9 @@ async function transcrever(p, alvo, ctx) {
|
|
|
1962
2002
|
// ── estudio_render ───────────────────────────────────────────────────────
|
|
1963
2003
|
|
|
1964
2004
|
async function ferramentaRender(args, ctx = {}) {
|
|
1965
|
-
const p =
|
|
2005
|
+
const p = projetoOuRecente(args.id);
|
|
1966
2006
|
if (!p) return semProjeto(args.id);
|
|
2007
|
+
if (args.id == null || String(args.id).trim() === '') args = { ...args, id: p.id };
|
|
1967
2008
|
const pl = projeto.lerPlano(args.id);
|
|
1968
2009
|
if (!pl) return { ok: false, error: 'não há plano neste projeto.' };
|
|
1969
2010
|
|
|
@@ -2209,7 +2250,7 @@ async function ferramentaSite(args) {
|
|
|
2209
2250
|
}
|
|
2210
2251
|
|
|
2211
2252
|
module.exports = {
|
|
2212
|
-
vazamentoNoComeco, variarIcones,
|
|
2253
|
+
vazamentoNoComeco, variarIcones, objetosDoRoteiro,
|
|
2213
2254
|
ferramentaProjeto, ferramentaPlano, ferramentaMidia, ferramentaNarrar,
|
|
2214
2255
|
ferramentaVideo, ferramentaRender, ferramentaSite,
|
|
2215
2256
|
projeto, plano, remotion, assets, video, identidade, slides, python, rodarPython, alinharPartes, prepararRoteiro, roteiroValido, juntarVozes,
|
|
@@ -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 };
|
package/lib/estudio/mundo.js
CHANGED
|
@@ -237,7 +237,11 @@ function posicionar(nomes, { semente = 1, agua = false, dentro = false, largura
|
|
|
237
237
|
* pedido da pessoa: as coisas que ele cita entram na cena), semente, nElenco}
|
|
238
238
|
*/
|
|
239
239
|
function mundoDe(pedido, { texto = '', pedidoTexto = '', semente = 1, nElenco = 1 } = {}) {
|
|
240
|
-
|
|
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 };
|
|
241
245
|
const descr = [obj.lugar, obj.descricao, obj.hora].filter(Boolean).join(' ');
|
|
242
246
|
let { lugar, hora } = lerLugar(descr);
|
|
243
247
|
const nomeDireto = sem(obj.lugar).replace(/\s+/g, '_');
|
|
@@ -302,4 +306,23 @@ function aplicarIcones(partitura, achados) {
|
|
|
302
306
|
return partitura;
|
|
303
307
|
}
|
|
304
308
|
|
|
305
|
-
|
|
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 };
|
package/lib/estudio/plano.js
CHANGED
|
@@ -49,6 +49,7 @@ const fs = require('fs');
|
|
|
49
49
|
const path = require('path');
|
|
50
50
|
const morfar = require('./morfar.js');
|
|
51
51
|
const kit = require('./kit.js');
|
|
52
|
+
const movimentos = require('./movimentos.js');
|
|
52
53
|
|
|
53
54
|
// "kit": uma peça pronta do kit de motion (kit.js), pelo nome — ver kit/*.jsx.
|
|
54
55
|
const TIPOS = new Set(['texto', 'forma', 'imagem', 'video', 'icone', 'legenda', 'componente', 'grupo', 'kit']);
|
|
@@ -283,6 +284,11 @@ function conferir(plano, { pasta = null } = {}) {
|
|
|
283
284
|
if (!plano || typeof plano !== 'object') {
|
|
284
285
|
return { ok: false, problemas: [erro('plano', 'o plano é um objeto JSON')], duracao: 0 };
|
|
285
286
|
}
|
|
287
|
+
/* `movimento: "quicar-de-baixo"` vira quadros-chave (movimentos.js) antes
|
|
288
|
+
de tudo: o que segue confere o `anima` como se a IA o tivesse escrito. */
|
|
289
|
+
for (const d of movimentos.expandir(plano)) {
|
|
290
|
+
problemas.push(erro(d.onde, `o movimento "${d.nome}" não existe — procure com estudio_midia acao:"movimentos" termo:"…"`));
|
|
291
|
+
}
|
|
286
292
|
|
|
287
293
|
const f = plano.formato || {};
|
|
288
294
|
const largura = Number(f.largura) || 1920;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.7.0",
|
|
4
4
|
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Requer conta Conecta Primo AI (Premium ou Super). Cria arquivos, roda comandos, controla navegador e desktop: abre apps, clica em botões e ícones pelo nome, digita e usa atalhos.",
|
|
5
5
|
"main": "bin/primocode.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/primocode.js",
|
|
11
|
-
"test": "node test/tools.test.js && node test/ui.test.js && node test/entrada.test.js && node test/transparencia.test.js && node test/permissao.test.js && node test/compatibilidade.test.js && node test/skills.test.js && node test/skills-de-fora.test.js && node test/skills-novas.test.js && node test/comandos.test.js && node test/estudio.test.js && node test/voz.test.js && node test/modo.test.js && node test/pasta.test.js && node test/projeto.test.js && node test/nome-projeto.test.js && node test/api-propria.test.js && node test/claude-engine.test.js && node test/codex.test.js && node test/conferir.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/seta.test.js && node test/effort.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/casar.test.js && node test/recado.test.js && node test/releitura.test.js && node test/snapshot.test.js && node test/video.test.js && node test/limpeza.test.js && node test/autonomia.test.js && node test/desfazer.test.js && node test/desfazer-git.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/pipeline.test.js && node test/conta.test.mjs && node test/nuvem.test.js && node test/prazo.test.js && node test/primeira-vez.test.js && node test/fala.test.js && node test/especialistas.test.js && node test/empurrao.test.js && node test/terminal-novo.test.js && node test/markdown.test.js && node test/coerencia.test.js && node test/contexto.test.js && node test/perigo.test.js && node test/rapidez.test.js && node test/mascotes.test.js && node test/comandos-extra.test.js && node test/offline.test.js && node test/edicao.test.js && node test/composicao.test.js && node test/acervo-site.test.js && node test/impacto.test.js && node test/cinema.test.js && node test/producao.test.js && node test/ende.test.js && node test/capa.test.js && node test/broll.test.js && node test/morfar.test.js && node test/motion.test.js && node test/motion-roteiro.test.js && node test/kit-motion.test.js && node test/gerativo.test.js && node test/gerativo-identidade.test.js && node test/gerativo-moldura.test.js && node test/gerativo-heroi.test.js && node test/gerativo-ambiente.test.js && node test/gerativo-variedade.test.js && node test/hibrido.test.js && node test/batida.test.js && node test/estudio-acervo.test.js && node test/estudio-diretor.test.js && node test/estudio-marca.test.js && node test/estudio-novo-projeto.test.js && node test/estudio-plano.test.js && node test/estudio-slides.test.js && node test/estudio-video.test.js && node test/revisor.test.js && node test/assinatura.test.js && node test/biblioteca.test.js && node test/mcp.test.js && node test/personas.test.js && node test/efeitos.test.js && node test/auto.test.js && node test/colar.test.js && node test/orquestra.test.js && node test/shell-limpo.test.js && node test/editar-remotion.test.js && node test/variacao3d.test.js && node test/portao-skill.test.js && node test/clones.test.js && node test/kokoro.test.js && node test/fluido.test.js && node test/zoom.test.js && node test/personagem.test.js && node test/tela.test.js && node test/margem.test.js && node test/travado.test.js && node test/leitura.test.js && node test/producao-provedores.test.js && node test/broll-frase.test.js && node test/vazamento.test.js && node test/colecao.test.js && node test/mundo.test.js && node test/mcp-procurar.test.js && node test/selecao.test.js && node test/nome-curto.test.js && node test/agents-md.test.js && node test/outra-pasta.test.js"
|
|
11
|
+
"test": "node test/tools.test.js && node test/ui.test.js && node test/entrada.test.js && node test/transparencia.test.js && node test/permissao.test.js && node test/compatibilidade.test.js && node test/skills.test.js && node test/skills-de-fora.test.js && node test/skills-novas.test.js && node test/comandos.test.js && node test/estudio.test.js && node test/voz.test.js && node test/modo.test.js && node test/pasta.test.js && node test/projeto.test.js && node test/nome-projeto.test.js && node test/api-propria.test.js && node test/claude-engine.test.js && node test/codex.test.js && node test/conferir.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/seta.test.js && node test/effort.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/casar.test.js && node test/recado.test.js && node test/releitura.test.js && node test/snapshot.test.js && node test/video.test.js && node test/limpeza.test.js && node test/autonomia.test.js && node test/desfazer.test.js && node test/desfazer-git.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/pipeline.test.js && node test/conta.test.mjs && node test/nuvem.test.js && node test/prazo.test.js && node test/primeira-vez.test.js && node test/fala.test.js && node test/especialistas.test.js && node test/empurrao.test.js && node test/terminal-novo.test.js && node test/markdown.test.js && node test/coerencia.test.js && node test/contexto.test.js && node test/perigo.test.js && node test/rapidez.test.js && node test/mascotes.test.js && node test/comandos-extra.test.js && node test/offline.test.js && node test/edicao.test.js && node test/composicao.test.js && node test/acervo-site.test.js && node test/impacto.test.js && node test/cinema.test.js && node test/producao.test.js && node test/ende.test.js && node test/capa.test.js && node test/broll.test.js && node test/morfar.test.js && node test/motion.test.js && node test/motion-roteiro.test.js && node test/kit-motion.test.js && node test/gerativo.test.js && node test/gerativo-identidade.test.js && node test/gerativo-moldura.test.js && node test/gerativo-heroi.test.js && node test/gerativo-ambiente.test.js && node test/gerativo-variedade.test.js && node test/hibrido.test.js && node test/batida.test.js && node test/estudio-acervo.test.js && node test/estudio-diretor.test.js && node test/estudio-marca.test.js && node test/estudio-novo-projeto.test.js && node test/estudio-plano.test.js && node test/estudio-slides.test.js && node test/estudio-video.test.js && node test/revisor.test.js && node test/assinatura.test.js && node test/biblioteca.test.js && node test/mcp.test.js && node test/personas.test.js && node test/efeitos.test.js && node test/auto.test.js && node test/colar.test.js && node test/orquestra.test.js && node test/shell-limpo.test.js && node test/editar-remotion.test.js && node test/variacao3d.test.js && node test/portao-skill.test.js && node test/clones.test.js && node test/kokoro.test.js && node test/fluido.test.js && node test/zoom.test.js && node test/personagem.test.js && node test/tela.test.js && node test/margem.test.js && node test/travado.test.js && node test/leitura.test.js && node test/producao-provedores.test.js && node test/broll-frase.test.js && node test/vazamento.test.js && node test/colecao.test.js && node test/mundo.test.js && node test/mcp-procurar.test.js && node test/selecao.test.js && node test/nome-curto.test.js && node test/agents-md.test.js && node test/outra-pasta.test.js && node test/estudio-sem-id.test.js && node test/estudio-objeto-do-site.test.js && node test/cenarios.test.js && node test/movimentos.test.js"
|
|
12
12
|
},
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=18.17.0"
|