primocode 9.10.0 → 10.0.9
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 +30 -6
- package/bin/primocode.js +169 -342
- package/lib/act.js +360 -39
- package/lib/browser.js +105 -4
- package/lib/casar.js +108 -0
- package/lib/catalogo.js +6 -2
- package/lib/claude-engine.js +38 -17
- package/lib/codex-engine.js +27 -9
- package/lib/config.js +10 -3
- package/lib/cursor.js +10 -1
- package/lib/entrada.js +77 -11
- package/lib/estudio/cinema.js +27 -9
- package/lib/estudio/diretor.js +21 -1
- package/lib/estudio/ende.js +16 -5
- package/lib/estudio/estilo.js +66 -0
- package/lib/estudio/gerativo.js +1453 -0
- package/lib/estudio/glifos.js +215 -0
- package/lib/estudio/hibrido.js +165 -0
- package/lib/estudio/index.js +284 -53
- package/lib/estudio/kit/base.jsx +285 -0
- package/lib/estudio/kit/gerativo.jsx +980 -0
- package/lib/estudio/kit/index.jsx +13 -0
- package/lib/estudio/kit/marca.jsx +741 -0
- package/lib/estudio/kit/tres.jsx +308 -0
- package/lib/estudio/kit/vox.jsx +357 -0
- package/lib/estudio/kit.js +78 -0
- package/lib/estudio/lancamento.js +441 -0
- package/lib/estudio/motion.js +2 -5
- package/lib/estudio/plano.js +28 -3
- package/lib/estudio/referencias.js +105 -0
- package/lib/estudio/remotion.js +139 -51
- package/lib/estudio/ritmo.js +77 -0
- package/lib/estudio/sons-leves.js +38 -1
- package/lib/estudio/variedade.js +105 -0
- package/lib/estudio/vox.js +414 -0
- package/lib/fala.js +1 -94
- package/lib/mcp.js +156 -0
- package/lib/personas.js +67 -0
- package/lib/pipeline.js +55 -56
- package/lib/repeticao.js +3 -1
- package/lib/skills.js +44 -2
- package/lib/snapshot.js +111 -0
- package/lib/tools.js +100 -22
- package/lib/ui.js +50 -8
- package/lib/video/analise.js +89 -0
- package/lib/video/auto.js +329 -0
- package/lib/video/camadas.js +207 -0
- package/lib/video/cli.js +74 -0
- package/lib/video/delta.js +90 -0
- package/lib/video/edits.js +379 -0
- package/lib/video/filters.js +280 -0
- package/lib/video/fx3d/Camadas.jsx +64 -0
- package/lib/video/fx3d/Objetos.jsx +128 -0
- package/lib/video/fx3d/Root.jsx +18 -0
- package/lib/video/fx3d/modelos.jsx +304 -0
- package/lib/video/fx3d.js +108 -0
- package/lib/video/gerados.js +119 -0
- package/lib/video/objetos.js +111 -0
- package/lib/video/ops-fx.js +453 -0
- package/lib/video/ops.js +347 -0
- package/lib/video/paralelo.js +50 -0
- package/lib/video/pedido.js +191 -0
- package/lib/video/projeto.js +133 -0
- package/lib/video/rastrear.js +95 -0
- package/lib/video/rastro.js +276 -0
- package/lib/video/render.js +274 -0
- package/lib/video/texto.js +144 -0
- package/lib/video/timeline.js +318 -0
- package/lib/video/transcript.js +124 -0
- package/lib/voz.js +75 -223
- package/package.json +2 -3
- package/skills/motion-de-primeira/SKILL.md +5 -5
- package/studio/narrar.py +10 -7
- package/studio/rastrear.py +399 -0
- package/studio/texto.py +208 -0
- package/voz/primo_voz/__main__.py +96 -236
- package/voz/primo_voz/transcrever.py +31 -1
- package/app/ansi.js +0 -55
- package/app/app.css +0 -500
- package/app/app.js +0 -1017
- package/app/icone.svg +0 -6
- package/app/index.html +0 -220
- package/app/primo-marca.js +0 -192
- package/lib/app.js +0 -615
- package/lib/icone.js +0 -263
- package/lib/instalar-app.js +0 -231
- package/lib/painel.js +0 -291
- package/voz/primo_voz/desperta.py +0 -195
- package/voz/primo_voz/orbe.py +0 -406
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lancamento.js — os estilos "marca" (brand reveal) e "lancamento" (filme de
|
|
3
|
+
* lançamento de produto).
|
|
4
|
+
*
|
|
5
|
+
* O repertório dos vídeos de referência que o Joel mandou (29/09/2026): o
|
|
6
|
+
* brilho no escuro que vira a marca, as letras desenhadas em vetor com as
|
|
7
|
+
* âncoras e o cursor, a marca chapada na cor, o padrão ladrilhado, a
|
|
8
|
+
* mancha líquida, a marca cromada, os cartões de cor em 3D; o HUD de motion
|
|
9
|
+
* designer; os cartões flutuando, o número gigante, o cartão do produto com
|
|
10
|
+
* o marca-texto e as barras, o calendário e o mural que se afasta.
|
|
11
|
+
*
|
|
12
|
+
* Uma parte pode virar DUAS cenas (a logo: o vetor e depois a marca chapada;
|
|
13
|
+
* a chamada: o padrão e depois o fecho). O corte cai no beat; as cenas que
|
|
14
|
+
* pedem ("cta") entram pela mancha líquida. As peças são do kit
|
|
15
|
+
* (kit/marca.jsx); aqui só se decide o quê, onde e quando.
|
|
16
|
+
*
|
|
17
|
+
* Tipos de parte: abertura, logo, titulo, numero, lista, passos, contraste,
|
|
18
|
+
* citacao, imagem, grafico, cores, produto, cta. Qualquer parte pode pedir
|
|
19
|
+
* a peça que a conduz com "visual": orbe, vetor, estrela, esfera, grade,
|
|
20
|
+
* site, padrao, cromo, cartoes, painel, cores.
|
|
21
|
+
*
|
|
22
|
+
* Puro: roteiro, tempos da voz, glifos da fonte (glifos.js) e arquivos
|
|
23
|
+
* entram; o plano sai.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
'use strict';
|
|
27
|
+
|
|
28
|
+
const { caber } = require('./composicao.js');
|
|
29
|
+
const { misturarCor, ehCor } = require('./morfar.js');
|
|
30
|
+
const { tempoDasPartes, gatilhos, volumesDaTrilha, r2 } = require('./ritmo.js');
|
|
31
|
+
const { tese, termoDef } = require('./vox.js');
|
|
32
|
+
|
|
33
|
+
const FPS = 30;
|
|
34
|
+
const k = (t, v, e) => (e ? { t: r2(t), v, e } : { t: r2(t), v });
|
|
35
|
+
const luz = (hex) => {
|
|
36
|
+
const h = String(hex || '').replace('#', '');
|
|
37
|
+
const n = h.length === 3 ? h.split('').map((c) => c + c).join('') : h.slice(0, 6);
|
|
38
|
+
const [r, g, b] = [0, 2, 4].map((i) => parseInt(n.slice(i, i + 2), 16) || 0);
|
|
39
|
+
return (0.299 * r + 0.587 * g + 0.114 * b) / 255;
|
|
40
|
+
};
|
|
41
|
+
const sobre = (c) => (luz(c) > 0.6 ? '#111114' : '#ffffff');
|
|
42
|
+
const itensDe = (p) => (p.itens || []).map((i) => (typeof i === 'string' ? i : i.texto || i.titulo || '')).filter(Boolean);
|
|
43
|
+
const VISUAIS = ['orbe', 'vetor', 'estrela', 'esfera', 'grade', 'site', 'padrao', 'cromo', 'cartoes', 'painel', 'cores'];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @param {object} op
|
|
47
|
+
* @param {object} op.roteiro { marca: {nome, cores, fontes, logo}, partes: [...] }
|
|
48
|
+
* @param {string} op.variante 'marca' | 'lancamento'
|
|
49
|
+
* @param {object} op.glifos { d, pontos, caixa } do nome da marca (glifos.js), para o vetor
|
|
50
|
+
* @param {object} op.imagens { indiceDaParte: {src, tipo} }
|
|
51
|
+
*/
|
|
52
|
+
function montarLancamento({ roteiro, variante = 'lancamento', largura = 1920, altura = 1080, tempos = [], narracao = null, sons = {}, trilha = null, imagens = {}, glifos = null }) {
|
|
53
|
+
const partes = (roteiro && roteiro.partes) || [];
|
|
54
|
+
if (!partes.length) return { ok: false, error: 'o roteiro veio sem partes.' };
|
|
55
|
+
const ehMarca = variante === 'marca';
|
|
56
|
+
const W = largura, H = altura, deitado = W >= H;
|
|
57
|
+
const b = Math.min(W, H) / 1080;
|
|
58
|
+
const M = Math.round(Math.min(W, H) * 0.08);
|
|
59
|
+
const TOPO = Math.round(M * 1.45); // abaixo do HUD: o rótulo dele mora na banda de cima
|
|
60
|
+
|
|
61
|
+
// ── a marca ────────────────────────────────────────────────────────
|
|
62
|
+
const marca = roteiro.marca || {};
|
|
63
|
+
const cores = marca.cores || {};
|
|
64
|
+
const ACENTO = ehCor(cores.destaque) ? cores.destaque : (ehMarca ? '#1f3cff' : '#ff5a1f');
|
|
65
|
+
const ESCURO = ehCor(cores.fundo) && luz(cores.fundo) < 0.2 ? cores.fundo : '#09090c';
|
|
66
|
+
const CLARO = '#f1f1ef';
|
|
67
|
+
const nome = marca.nome || (partes.find((p) => p.tipo === 'abertura' || p.tipo === 'logo') || {}).titulo || 'marca';
|
|
68
|
+
const F = {
|
|
69
|
+
display: (marca.fontes && marca.fontes.titulo) || (ehMarca ? 'Plus Jakarta Sans' : 'Inter Tight'),
|
|
70
|
+
texto: (marca.fontes && marca.fontes.texto) || 'Inter',
|
|
71
|
+
mono: 'JetBrains Mono',
|
|
72
|
+
};
|
|
73
|
+
const logo = marca.logo || null;
|
|
74
|
+
const base = { fonte: F.display, fonteMono: F.mono, corAcento: ACENTO };
|
|
75
|
+
|
|
76
|
+
// ── o tempo ────────────────────────────────────────────────────────
|
|
77
|
+
const leitura = (p) => 1.8 + 0.28 * String([p.titulo, p.sub, p.valor, p.rotulo, p.nao, p.sim, p.texto, ...itensDe(p)].filter(Boolean).join(' ')).split(/\s+/).length;
|
|
78
|
+
const { ini, dur, total, temVoz } = tempoDasPartes(partes, tempos, { semVoz: leitura, minimo: 1.4 });
|
|
79
|
+
const efeitos = [];
|
|
80
|
+
const som = (t, nome_, volume) => { if (sons[nome_]) efeitos.push({ t: r2(Math.max(0, t)), src: sons[nome_], volume }); };
|
|
81
|
+
|
|
82
|
+
// ── ferramentas de cena ────────────────────────────────────────────
|
|
83
|
+
const fundoClaro = { tipo: 'gradiente', radial: true, centro: '50% 40%', cores: ['#fbfbfa', '#e6e6e3'] };
|
|
84
|
+
const fundoEscuro = { tipo: 'gradiente', radial: true, centro: '50% 30%', cores: [misturarCor(ESCURO, '#ffffff', 0.08), ESCURO] };
|
|
85
|
+
const fundoEstudio = { tipo: 'gradiente', angulo: 180, cores: [misturarCor(ESCURO, '#ffffff', 0.22), misturarCor(ESCURO, '#ffffff', 0.05), ESCURO] };
|
|
86
|
+
const fundoAcento = { tipo: 'gradiente', radial: true, centro: '50% 45%', cores: [misturarCor(ACENTO, '#ffffff', 0.12), ACENTO, misturarCor(ACENTO, '#000000', 0.12)] };
|
|
87
|
+
const push = (d, z = 1.06) => ({ zoom: [k(0, 1), k(Math.max(0.3, d - 0.25), z, 'camera'), k(d, z + 0.025, 'acelera')] });
|
|
88
|
+
const cena = (d, fundo, escuro, elementos, { zoom = 1.05, ...extra } = {}) => ({ duracao: r2(d), fundo, escuro, camera: push(d, zoom), elementos, ...extra });
|
|
89
|
+
const fit = (t, larg, linhas, max, min = 36) => caber(t, { largura: larg, linhas, max: max * b, min: min * b, fonte: F.display });
|
|
90
|
+
const alturaDe = (f) => f.linhas * f.tamanho * 1.0;
|
|
91
|
+
const palavrasT = (texto, tp, t0, d, op) => gatilhos(String(texto).split(/\s+/).filter(Boolean), tp, t0, d, op);
|
|
92
|
+
const partir = (d, k1) => [r2(d * k1), r2(d - d * k1)];
|
|
93
|
+
const brilhoSom = (t) => som(t, 'brilho', 0.28);
|
|
94
|
+
|
|
95
|
+
/* O cartão de enfeite: interface genérica, sem texto inventado. É a
|
|
96
|
+
"multidão" de posts do filme de lançamento, e não diz nada falso. */
|
|
97
|
+
const enfeites = (n, semente = 0) => Array.from({ length: n }).map((_, j) => ({ titulo: nome, rotulo: ['agora', '2 min', 'ontem', '1 h'][(j + semente) % 4] }));
|
|
98
|
+
|
|
99
|
+
// ── cada tipo de parte vira uma (ou duas) cenas ────────────────────
|
|
100
|
+
const CENAS = {
|
|
101
|
+
abertura(p, d, tp, t0) {
|
|
102
|
+
const titulo = p.titulo || nome;
|
|
103
|
+
const els = [
|
|
104
|
+
{ tipo: 'kit', peca: 'orbe', x: W / 2, y: r2(H * 0.46), tamanho: r2(240 * b), cor: ehMarca ? '#5fd6c8' : ACENTO, corMarca: ehMarca ? '#dffcff' : ACENTO,
|
|
105
|
+
t: 0.05, tMarca: r2(Math.min(1.1, d * 0.4)), logo, particulas: ehMarca ? 30 : 56 },
|
|
106
|
+
];
|
|
107
|
+
const tt = Math.min(d - 0.9, Math.max(1.4, gatilhos([titulo], tp, t0, d)[0]));
|
|
108
|
+
els.push({ tipo: 'kit', peca: 'manchete', modo: 'digitar', texto: titulo, x: W / 2, y: r2(H * 0.46 + 190 * b), largura: W - 2 * M, alinhamento: 'center',
|
|
109
|
+
tamanho: r2(fit(titulo, W * 0.6, 1, 64).tamanho), peso: 700, cor: '#f4f4f6', t: tt, ...base, fonte: F.display });
|
|
110
|
+
if (p.sub) els.push({ tipo: 'kit', peca: 'manchete', modo: 'desfoque', texto: p.sub, x: W / 2, y: r2(H * 0.46 + 280 * b), largura: W - 2 * M, alinhamento: 'center',
|
|
111
|
+
tamanho: r2(30 * b), peso: 500, cor: '#9a9ca6', t: Math.min(d - 0.6, tt + 0.6), passo: 2, fonte: F.texto });
|
|
112
|
+
brilhoSom(t0 + Math.min(1.1, d * 0.4));
|
|
113
|
+
return [cena(d, ehMarca ? { tipo: 'gradiente', angulo: 100, cores: ['#1c5f64', '#06080c', '#06080c', '#233a8a'] } : fundoEscuro, true, els, { zoom: 1.08 })];
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
logo(p, d, tp, t0) {
|
|
117
|
+
const [d1, d2] = partir(d, 0.58);
|
|
118
|
+
const palavra = p.titulo || nome;
|
|
119
|
+
const altura = r2(Math.min(H * 0.6, (W * 1.5) / Math.max(3, palavra.length * 0.6)));
|
|
120
|
+
const a = cena(d1, '#050506', true, [
|
|
121
|
+
{ tipo: 'kit', peca: 'vetor', glifos: glifos || undefined, texto: palavra, fonte: F.display, x: W / 2, y: r2(H * 0.52), altura, t: 0.05, durTraco: Math.min(1.3, d1 * 0.55),
|
|
122
|
+
pan: { de: r2(W * 0.22), ate: r2(-W * 0.22), dur: d1 } },
|
|
123
|
+
], { camera: { zoom: [k(0, 1.18), k(d1, 1.0, 'camera')] } });
|
|
124
|
+
const tam = fit(palavra, W * 0.4, 1, 120).tamanho;
|
|
125
|
+
const icone = r2(tam * 1.05);
|
|
126
|
+
const larg = palavra.length * tam * 0.56 + icone + 30 * b;
|
|
127
|
+
const b2 = cena(d2, ACENTO, luz(ACENTO) < 0.6, [
|
|
128
|
+
{ tipo: 'kit', peca: 'cromo', modo: 'solido', tom: sobre(ACENTO), logo, reflexo: false, giro: 0, x: r2(W / 2 - larg / 2 + icone / 2), y: r2(H / 2), tamanho: icone, t: 0 },
|
|
129
|
+
{ tipo: 'kit', peca: 'manchete', modo: 'digitar', texto: palavra.toLowerCase(), x: r2(W / 2 - larg / 2 + icone + 30 * b), y: r2(H / 2), ancora: 'meio', tamanho: tam, peso: 700,
|
|
130
|
+
cor: sobre(ACENTO), t: 0.25, ...base, espacamento: '-0.03em' },
|
|
131
|
+
], { zoom: 1.04 });
|
|
132
|
+
som(t0 + d1, 'toque', 0.35);
|
|
133
|
+
return [a, b2];
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
titulo(p, d, tp, t0) {
|
|
137
|
+
const titulo = p.titulo || '';
|
|
138
|
+
const curto = titulo.length <= 12;
|
|
139
|
+
const f = fit(curto ? titulo.toUpperCase() : titulo, W - 2 * M, curto ? 1 : 3, curto ? 300 : 150, 44);
|
|
140
|
+
const ts = palavrasT(titulo, tp, t0, d);
|
|
141
|
+
if (!ehMarca && !curto) {
|
|
142
|
+
// O campo de cartões flutuando, e a pergunta entrando palavra a palavra por cima.
|
|
143
|
+
return [cena(d, fundoAcento, true, [
|
|
144
|
+
{ tipo: 'kit', peca: 'cartoes', modo: 'voo', cartoes: enfeites(12, 1), x: 0, y: 0, largura: W, altura: H, larguraCartao: r2((deitado ? 330 : 420) * b), t: 0, ...base, corAcento: ACENTO },
|
|
145
|
+
{ tipo: 'forma', forma: 'circulo', x: r2(W / 2), y: r2(H / 2), largura: r2(W * 0.9), altura: r2(W * 0.5), ancora: 'centro', cor: ACENTO, opacidade: 0.55, anima: { desfoque: [k(0, 90)] } },
|
|
146
|
+
{ tipo: 'kit', peca: 'manchete', modo: 'desfoque', texto: titulo, destaque: '', x: W / 2, y: r2(H / 2 - alturaDe(f) / 2), largura: W - 2 * M, alinhamento: 'center',
|
|
147
|
+
tamanho: f.tamanho, peso: 800, cor: '#ffffff', tempos: ts, ...base },
|
|
148
|
+
], { perspectiva: 1400, zoom: 1.07 })];
|
|
149
|
+
}
|
|
150
|
+
const els = [{ tipo: 'kit', peca: 'manchete', modo: curto ? 'carimbo' : 'subir', texto: curto ? titulo.toUpperCase() : titulo, destaque: curto ? '' : tese(titulo, p.destaque),
|
|
151
|
+
x: W / 2, y: r2(H / 2 - alturaDe(f) / 2), largura: W - 2 * M, alinhamento: 'center', tamanho: f.tamanho, peso: 900, cor: '#111114', tempos: ts,
|
|
152
|
+
corAcento: '#ffffff', fonte: F.display, espacamento: curto ? '-0.02em' : '-0.035em',
|
|
153
|
+
saida: curto && d > 1.6 ? { t: r2(d - 0.55), modo: 'pontos' } : undefined }];
|
|
154
|
+
if (p.sub) els.push({ tipo: 'kit', peca: 'manchete', modo: 'desfoque', texto: p.sub, x: W / 2, y: r2(H / 2 + alturaDe(f) / 2 + 30 * b), largura: W - 2 * M, alinhamento: 'center',
|
|
155
|
+
tamanho: r2(34 * b), peso: 500, cor: '#111114', t: Math.min(d - 0.7, ts[ts.length - 1] + 0.3), passo: 2, fonte: F.texto });
|
|
156
|
+
som(t0 + ts[0], 'toque', 0.35);
|
|
157
|
+
return [cena(d, ehMarca ? ACENTO : fundoAcento, true, els, { zoom: 1.08 })];
|
|
158
|
+
},
|
|
159
|
+
|
|
160
|
+
numero(p, d, tp, t0) {
|
|
161
|
+
const v = String(p.valor || p.titulo || '');
|
|
162
|
+
const m = v.match(/^(\D*)([\d.,]+)(.*)$/);
|
|
163
|
+
const n = m ? Number(m[2].replace(/\./g, '').replace(',', '.')) : null;
|
|
164
|
+
const rot = p.rotulo || (p.valor ? p.titulo : '') || '';
|
|
165
|
+
const xL = deitado ? M * 1.4 : M;
|
|
166
|
+
const tamN = fit(v, deitado ? W * 0.36 : W - 2 * M, 1, 300).tamanho;
|
|
167
|
+
const tamR = fit(rot, deitado ? W * 0.42 : W - 2 * M, 2, 76).tamanho;
|
|
168
|
+
const yN = deitado ? H * 0.28 : H * 0.2;
|
|
169
|
+
const [tv, tr] = gatilhos([v, rot || v], tp, t0, d);
|
|
170
|
+
const els = [
|
|
171
|
+
{ tipo: 'kit', peca: 'fonte', texto: (p.secao || p.tipo).toUpperCase(), x: r2(xL), y: r2(yN - 60 * b), t: 0.05, tamanho: r2(18 * b), fonteMono: F.mono, corAcento: ACENTO, cor: ACENTO },
|
|
172
|
+
{ tipo: 'kit', peca: 'manchete', texto: v, ...(n != null ? { contar: { ate: n, antes: m[1], depois: m[3], casas: /,\d/.test(m[2]) ? 1 : 0, dur: 0.9 } } : { modo: 'carimbo' }),
|
|
173
|
+
x: r2(xL), y: r2(yN), tamanho: tamN, peso: 900, cor: '#111114', t: tv, ...base },
|
|
174
|
+
];
|
|
175
|
+
if (rot) els.push({ tipo: 'kit', peca: 'manchete', modo: 'desfoque', texto: rot, x: r2(xL), y: r2(yN + tamN * 0.98), largura: r2(deitado ? W * 0.44 : W - 2 * M), tamanho: tamR, peso: 800,
|
|
176
|
+
cor: '#111114', t: tr, passo: 4, ...base });
|
|
177
|
+
if (p.sub) els.push({ tipo: 'kit', peca: 'manchete', modo: 'desfoque', texto: p.sub, destaque: tese(p.sub), x: r2(xL), y: r2(yN + tamN * 0.98 + alturaDe({ linhas: Math.min(2, Math.ceil(rot.length * tamR * 0.56 / (W * 0.44))), tamanho: tamR }) + 26 * b),
|
|
178
|
+
largura: r2(W * 0.5), tamanho: r2(34 * b), peso: 600, cor: '#8a8c94', corAcento: ACENTO, t: Math.min(d - 0.6, tr + 0.5), passo: 3, fonte: F.texto });
|
|
179
|
+
// A pilha de cartões: ao lado do número no deitado, embaixo dele em pé.
|
|
180
|
+
const area = deitado ? { x: W * 0.52, y: H * 0.12, w: W * 0.44, h: H * 0.6 } : { x: M, y: H * 0.52, w: W - 2 * M, h: H * 0.36 };
|
|
181
|
+
els.push({ tipo: 'kit', peca: 'cartoes', modo: 'voo', x: r2(area.x), y: r2(area.y), largura: r2(area.w), altura: r2(area.h), larguraCartao: r2((deitado ? 380 : 520) * b), t: tv + 0.1,
|
|
182
|
+
cartoes: enfeites(Math.min(6, Math.max(3, Math.round(n || 4))), 2).map((c, j) => ({ ...c, x: r2(area.w / 2 + (j % 2 ? 60 : -40) * b), y: r2(j * 70 * b) })), ...base, fonte: F.texto });
|
|
183
|
+
som(t0 + tv, 'brilho', 0.25);
|
|
184
|
+
return [cena(d, fundoClaro, false, els)];
|
|
185
|
+
},
|
|
186
|
+
|
|
187
|
+
lista(p, d, tp, t0) {
|
|
188
|
+
if (!ehMarca) return CENAS.passos(p, d, tp, t0);
|
|
189
|
+
const itens = itensDe(p).slice(0, 6);
|
|
190
|
+
const titulo = p.titulo || '';
|
|
191
|
+
const ts = gatilhos(itens, tp, t0, d, { t0: 0.5 });
|
|
192
|
+
const w = r2((deitado ? 440 : 520) * b);
|
|
193
|
+
const lugares = deitado
|
|
194
|
+
? [[0.2, 0.2], [0.8, 0.18], [0.16, 0.66], [0.84, 0.62], [0.5, 0.78], [0.62, 0.08]]
|
|
195
|
+
: [[0.5, 0.12], [0.3, 0.3], [0.7, 0.62], [0.32, 0.72], [0.66, 0.86], [0.5, 0.95]];
|
|
196
|
+
const cartoes = itens.map((it, j) => ({ titulo: nome, texto: it, rotulo: String(j + 1).padStart(2, '0'), x: r2(W * lugares[j][0]), y: r2(H * lugares[j][1] - 60 * b), t: ts[j], destaque: j === itens.length - 1 }));
|
|
197
|
+
const f = fit(titulo, W * 0.5, 2, 110);
|
|
198
|
+
return [cena(d, fundoEscuro, true, [
|
|
199
|
+
{ tipo: 'kit', peca: 'cartoes', modo: 'voo', cartoes, x: 0, y: 0, largura: W, altura: H, larguraCartao: w, t: 0, ...base, fonte: F.texto },
|
|
200
|
+
{ tipo: 'kit', peca: 'manchete', modo: 'subir', texto: titulo, destaque: tese(titulo, p.destaque), x: W / 2, y: r2(H / 2 - alturaDe(f) / 2), largura: W * 0.52, alinhamento: 'center',
|
|
201
|
+
tamanho: f.tamanho, peso: 800, cor: '#ffffff', t: 0.1, passo: 3, ...base },
|
|
202
|
+
], { perspectiva: 1400 })];
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
passos(p, d, tp, t0) {
|
|
206
|
+
const itens = itensDe(p).slice(0, deitado ? 5 : 3);
|
|
207
|
+
const titulo = p.titulo || '';
|
|
208
|
+
const ts = gatilhos(itens.map((x) => termoDef(x).def || x), tp, t0, d, { t0: 0.6 });
|
|
209
|
+
const colunas = itens.map((x, j) => {
|
|
210
|
+
const td = termoDef(x);
|
|
211
|
+
return { rotulo: td.def ? td.termo : 'passo', titulo: td.def || td.termo, numero: j + 1, itens: [], t: ts[j] };
|
|
212
|
+
});
|
|
213
|
+
const f = fit(titulo, W - 2 * M, 1, 96);
|
|
214
|
+
const yC = r2(TOPO + f.tamanho * 1.4 + 30 * b);
|
|
215
|
+
return [cena(d, fundoClaro, false, [
|
|
216
|
+
{ tipo: 'kit', peca: 'fonte', texto: (p.secao || 'semana').toUpperCase(), x: M, y: r2(TOPO - 38 * b), t: 0.05, tamanho: r2(18 * b), fonteMono: F.mono, corAcento: ACENTO, cor: ACENTO },
|
|
217
|
+
{ tipo: 'kit', peca: 'manchete', modo: 'subir', texto: titulo, destaque: tese(titulo, p.destaque), x: M, y: r2(TOPO), largura: W - 2 * M, tamanho: f.tamanho, peso: 800,
|
|
218
|
+
cor: '#111114', t: 0.1, passo: 3, ...base },
|
|
219
|
+
{ tipo: 'kit', peca: 'cartoes', modo: 'colunas', cartoes: colunas, x: M, y: yC, largura: W - 2 * M, altura: r2(Math.min(H - yC - M, (deitado ? 0.56 : 0.5) * H)), t: 0.35, ...base, fonte: F.texto },
|
|
220
|
+
])];
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
contraste(p, d, tp, t0) {
|
|
224
|
+
const [d1, d2] = partir(d, 0.5);
|
|
225
|
+
const nao = p.nao || '—', sim = p.sim || p.titulo || '';
|
|
226
|
+
const campo = ehMarca ? '#d8f53a' : ACENTO;
|
|
227
|
+
const tinta = sobre(campo);
|
|
228
|
+
const a = cena(d1, campo, luz(campo) < 0.6, [
|
|
229
|
+
{ tipo: 'kit', peca: 'textura', modo: 'contorno', texto: nao, largura: W, altura: H, tamanho: r2(fit(nao.toUpperCase(), W * 0.5, 1, 170).tamanho), cor: tinta, corDestaque: tinta,
|
|
230
|
+
fonte: F.display, t: 0.02, velocidade: 90, separador: ' ' },
|
|
231
|
+
], { zoom: 1.1 });
|
|
232
|
+
const f = fit(sim, W - 2 * M, 2, 190);
|
|
233
|
+
const b2 = cena(d2, ESCURO, true, [
|
|
234
|
+
{ tipo: 'kit', peca: 'particulas', x: W / 2, y: H / 2, t: 0.05, n: 46, raio: r2(W * 0.4), cores: [ACENTO, '#ffffff', misturarCor(ACENTO, '#ffffff', 0.5)] },
|
|
235
|
+
{ tipo: 'kit', peca: 'manchete', modo: 'carimbo', texto: sim, x: W / 2, y: r2(H / 2 - alturaDe(f) / 2), largura: W - 2 * M, alinhamento: 'center', tamanho: f.tamanho,
|
|
236
|
+
peso: 900, cor: '#ffffff', t: 0.02, passo: 3, destaque: tese(sim, p.destaque), ...base },
|
|
237
|
+
], { zoom: 1.1 });
|
|
238
|
+
som(t0 + d1, 'toque', 0.4);
|
|
239
|
+
return [a, b2];
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
citacao(p, d, tp, t0) {
|
|
243
|
+
const pw = r2(deitado ? W * 0.7 : W - 2 * M);
|
|
244
|
+
const texto = p.titulo || p.texto || '';
|
|
245
|
+
return [cena(d, fundoClaro, false, [
|
|
246
|
+
{ tipo: 'kit', peca: 'painel', titulo: p.autor || nome, texto: '“' + texto + '”', destaque: p.destaque || tese(texto), abas: [], botao: 'Responder',
|
|
247
|
+
x: r2((W - pw) / 2), y: r2(H * 0.5 - pw * (deitado ? 0.2 : 0.3)), largura: pw, t: 0, escala: deitado ? 1 : 1.35, tMarca: Math.max(0.7, gatilhos([p.destaque || tese(texto)], tp, t0, d)[0]), ...base, fonte: F.texto },
|
|
248
|
+
], { perspectiva: 1600 })];
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
imagem(p, d, tp, t0, i) {
|
|
252
|
+
const m = imagens[i] || (p.src ? { src: p.src, tipo: 'imagem' } : null);
|
|
253
|
+
if (!m) return CENAS.titulo(p, d, tp, t0);
|
|
254
|
+
const iw = r2(deitado ? W * 0.62 : W - 2 * M), ih = r2(iw * 0.6);
|
|
255
|
+
const f = fit(p.titulo || '', deitado ? W * 0.3 : W - 2 * M, 3, 84);
|
|
256
|
+
const x = deitado ? W - M - iw : M, y = deitado ? (H - ih) / 2 : H * 0.14;
|
|
257
|
+
const els = [
|
|
258
|
+
{ tipo: 'grupo', x: r2(x), y: r2(y), largura: iw, altura: ih, recorte: true, raio: r2(22 * b), sombra: '0 40px 90px -30px rgba(0,0,0,0.55)',
|
|
259
|
+
anima: { rotacaoY: [k(0, -24), k(0.8, -6, 'editorial'), k(d, -3, 'camera')], x: [k(0, 220 * b), k(0.8, 0, 'editorial')], opacidade: [k(0, 0), k(0.15, 1)] },
|
|
260
|
+
elementos: [{ tipo: m.tipo === 'video' ? 'video' : 'imagem', src: m.src, ...(m.tipo === 'video' ? { volume: 0 } : {}), x: 0, y: 0, largura: '100%', altura: '100%', ajuste: 'cover',
|
|
261
|
+
anima: { escala: [k(0, 1.1), k(d, 1.0, 'camera')] } }] },
|
|
262
|
+
];
|
|
263
|
+
if (p.titulo) els.push({ tipo: 'kit', peca: 'manchete', modo: 'desfoque', texto: p.titulo, destaque: tese(p.titulo, p.destaque), x: M, y: r2(deitado ? H / 2 - alturaDe(f) / 2 : y + ih + 60 * b),
|
|
264
|
+
largura: r2(deitado ? W * 0.3 : W - 2 * M), tamanho: f.tamanho, peso: 800, cor: '#111114', t: 0.4, ...base });
|
|
265
|
+
return [cena(d, fundoClaro, false, els, { perspectiva: 1600 })];
|
|
266
|
+
},
|
|
267
|
+
|
|
268
|
+
grafico(p, d, tp, t0) {
|
|
269
|
+
const barras = (p.barras || itensDe(p).map((x, j, l) => ({ rotulo: x, valor: 100 - j * (60 / Math.max(1, l.length)) }))).slice(0, 5);
|
|
270
|
+
const max = Math.max(1, ...barras.map((q) => Number(q.valor) || 0));
|
|
271
|
+
const pw = r2(deitado ? W * 0.7 : W - 2 * M);
|
|
272
|
+
return [cena(d, fundoClaro, false, [
|
|
273
|
+
{ tipo: 'kit', peca: 'painel', titulo: nome, texto: p.titulo || '', destaque: tese(p.titulo || '', p.destaque), abas: [], barras: barras.map((q) => ({ rotulo: q.rotulo, valor: Math.round(100 * (Number(q.valor) || 0) / max) })),
|
|
274
|
+
tituloBarras: p.sub || 'resultado', x: r2((W - pw) / 2), y: r2(deitado ? H * 0.5 - pw * 0.18 : H * 0.28), largura: pw, t: 0.15, tBarras: 0.6, empilhar: !deitado, escala: deitado ? 1 : 1.3, ...base, fonte: F.texto },
|
|
275
|
+
], { perspectiva: 1600 })];
|
|
276
|
+
},
|
|
277
|
+
|
|
278
|
+
cores(p, d) {
|
|
279
|
+
const lista = (p.cores || [cores.fundo, cores.destaque, cores.tinta, cores.apoio].filter(ehCor).map((hex, j) => ({ nome: ['Fundo', 'Destaque', 'Tinta', 'Apoio'][j], hex })))
|
|
280
|
+
.slice(0, 5).map((c) => (typeof c === 'string' ? { nome: c, hex: c } : c));
|
|
281
|
+
const cs = lista.length >= 2 ? lista : [{ nome: 'Acento', hex: ACENTO }, { nome: 'Escuro', hex: ESCURO }, { nome: 'Claro', hex: '#f7f7f5' }];
|
|
282
|
+
return [cena(d, '#fafafa', false, [
|
|
283
|
+
...(p.titulo ? [{ tipo: 'kit', peca: 'fonte', texto: p.titulo.toUpperCase(), x: M, y: TOPO, t: 0.1, tamanho: r2(20 * b), fonteMono: F.mono, corAcento: ACENTO, cor: '#555862' }] : []),
|
|
284
|
+
{ tipo: 'kit', peca: 'cores', cores: cs, x: W / 2, y: r2(H / 2), largura: r2((deitado ? 290 : 300) * b), raioX: r2(deitado ? W * 0.3 : W * 0.3), raioY: r2(deitado ? H * 0.3 : H * 0.28),
|
|
285
|
+
t: 0.1, fonte: F.texto, fonteMono: F.mono },
|
|
286
|
+
{ tipo: 'kit', peca: 'cromo', logo, x: W / 2, y: r2(H / 2), tamanho: r2(250 * b), t: 0, reflexo: false },
|
|
287
|
+
], { zoom: 1.04 })];
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
produto(p, d, tp, t0) {
|
|
291
|
+
const titulo = p.titulo || '';
|
|
292
|
+
const pw = r2(deitado ? W * 0.72 : W - 2 * M);
|
|
293
|
+
const f = fit(titulo, W - 2 * M, 1, 96);
|
|
294
|
+
const texto = p.texto || p.sub || titulo;
|
|
295
|
+
return [cena(d, fundoClaro, false, [
|
|
296
|
+
{ tipo: 'kit', peca: 'fonte', texto: (p.secao || 'produto').toUpperCase(), x: M, y: r2(TOPO - 38 * b), t: 0.05, tamanho: r2(18 * b), fonteMono: F.mono, corAcento: ACENTO, cor: ACENTO },
|
|
297
|
+
{ tipo: 'kit', peca: 'manchete', modo: 'subir', texto: titulo, destaque: tese(titulo, p.destaque), x: M, y: r2(TOPO), largura: W - 2 * M, tamanho: f.tamanho, peso: 800, cor: '#111114', t: 0.05, passo: 3, ...base },
|
|
298
|
+
{ tipo: 'kit', peca: 'painel', titulo: nome, texto, destaque: p.destaque || tese(texto), selo: p.selo, barras: p.barras, x: r2((W - pw) / 2), y: r2(deitado ? TOPO + f.tamanho * 1.5 + 40 * b : H * 0.24), largura: pw,
|
|
299
|
+
empilhar: !deitado, escala: deitado ? 1 : 1.3,
|
|
300
|
+
t: 0.3, tMarca: Math.max(0.8, gatilhos([p.destaque || tese(texto)], tp, t0, d)[0] - 0.3), ...base, fonte: F.texto },
|
|
301
|
+
], { perspectiva: 1600 })];
|
|
302
|
+
},
|
|
303
|
+
|
|
304
|
+
cta(p, d, tp, t0) {
|
|
305
|
+
const [d1, d2] = partir(d, ehMarca ? 0.4 : 0.46);
|
|
306
|
+
const titulo = p.titulo || '';
|
|
307
|
+
const a = ehMarca
|
|
308
|
+
? cena(d1, ACENTO, true, [{ tipo: 'kit', peca: 'padrao', logo, largura: W, altura: H, celula: r2(170 * b), cor: sobre(ACENTO), corOnda: misturarCor(sobre(ACENTO), ACENTO, 0.35), t: 0 }], { zoom: 1.1 })
|
|
309
|
+
: cena(d1, { tipo: 'gradiente', radial: true, centro: '50% 50%', cores: ['#d9d9d6', '#9c9c99'] }, false, [
|
|
310
|
+
{ tipo: 'kit', peca: 'cartoes', modo: 'mural', cartoes: enfeites(10), x: 0, y: 0, largura: W, altura: H, colunas: deitado ? 7 : 4, linhas: deitado ? 5 : 8, dur: d1, ...base, fonte: F.texto },
|
|
311
|
+
], { camera: { zoom: [k(0, 1)] } });
|
|
312
|
+
const f = fit(titulo, W - 2 * M, 2, 120);
|
|
313
|
+
const icone = r2(220 * b);
|
|
314
|
+
const els = [
|
|
315
|
+
{ tipo: 'kit', peca: 'cromo', logo, x: W / 2, y: r2(H * 0.36), tamanho: icone, t: 0.05, ...(ehMarca ? {} : { modo: 'solido', tom: ACENTO, reflexo: false, giro: 6 }) },
|
|
316
|
+
{ tipo: 'kit', peca: 'particulas', x: W / 2, y: r2(H * 0.36), t: 0.2, n: 40, raio: r2(420 * b), cores: ehMarca ? ['#ffffff', ACENTO, '#9fb0ff'] : [ACENTO, '#111114', misturarCor(ACENTO, '#ffffff', 0.5)] },
|
|
317
|
+
{ tipo: 'kit', peca: 'manchete', modo: 'desfoque', texto: titulo, destaque: tese(titulo, p.destaque), x: W / 2, y: r2(H * 0.36 + icone * 0.75 + 30 * b), largura: W - 2 * M, alinhamento: 'center',
|
|
318
|
+
tamanho: f.tamanho, peso: 800, cor: ehMarca ? '#ffffff' : '#111114', t: 0.45, passo: 3, ...base },
|
|
319
|
+
];
|
|
320
|
+
if (p.link) els.push({ tipo: 'kit', peca: 'manchete', modo: 'digitar', texto: p.link, x: W / 2, y: r2(H * 0.36 + icone * 0.75 + 60 * b + alturaDe(f)), largura: W - 2 * M, alinhamento: 'center',
|
|
321
|
+
tamanho: r2(38 * b), peso: 500, cor: ehMarca ? '#b8bcc8' : '#555862', t: 1.0, fonte: F.mono });
|
|
322
|
+
const b2 = cena(d2, ehMarca ? fundoEstudio : '#fbfbfa', ehMarca, els, { transicao: { tipo: 'liquido', duracao: 0.6 }, zoom: 1.04 });
|
|
323
|
+
brilhoSom(t0 + d1 + 0.2);
|
|
324
|
+
return [a, b2];
|
|
325
|
+
},
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
// ── as peças que qualquer parte pode pedir pelo nome ("visual") ────
|
|
329
|
+
/* Qual modelo 3D: o que a parte ou o roteiro disser, ou o que o texto
|
|
330
|
+
deixa claro. Sem pista, a caixa — um produto genérico que ainda gira. */
|
|
331
|
+
const modeloDe = (txt) => (/fone|headset|headphone|auricular/i.test(txt) ? 'fone'
|
|
332
|
+
: /celular|smartphone|iphone|phone|telefone/i.test(txt) ? 'celular'
|
|
333
|
+
: /lata|bebida|refri|cerveja|energ[eé]tico|garrafa/i.test(txt) ? 'lata' : 'caixa');
|
|
334
|
+
const VISUAL = {
|
|
335
|
+
/* O PRODUTO EM 3D (kit/tres.jsx): modelado e iluminado aqui, sem foto
|
|
336
|
+
de fora — girando devagar no centro, o nome embaixo. */
|
|
337
|
+
'3d'(p, d, tp, t0) {
|
|
338
|
+
const texto = `${p.titulo || ''} ${p.texto || ''} ${nome}`;
|
|
339
|
+
const modelo = p.modelo || roteiro.modelo3d || modeloDe(texto);
|
|
340
|
+
const f = fit(p.titulo || nome, W - 2 * M, 1, deitado ? 84 : 96);
|
|
341
|
+
const yT = deitado ? H * 0.8 : H * 0.76;
|
|
342
|
+
const els = [
|
|
343
|
+
{ tipo: 'kit', peca: 'produto3d', modelo, cor: p.cor3d || roteiro.cor3d || '#1b1c20', corAcento: ACENTO, t: 0, giro: 0.4,
|
|
344
|
+
x: 0, y: r2(deitado ? -H * 0.02 : -H * 0.08), largura: W, altura: H, escala: deitado ? 0.8 : 1.15 }, // deitado a 1,0 o arco saía cortado no topo
|
|
345
|
+
{ tipo: 'kit', peca: 'manchete', modo: 'desfoque', texto: p.titulo || nome, x: W / 2, y: r2(yT), largura: W - 2 * M, alinhamento: 'center',
|
|
346
|
+
tamanho: f.tamanho, peso: 800, cor: '#f4f4f6', t: Math.min(0.9, gatilhos([p.titulo || nome], tp, t0, d)[0] || 0.6), ...base },
|
|
347
|
+
];
|
|
348
|
+
if (p.texto) els.push({ tipo: 'kit', peca: 'manchete', modo: 'desfoque', texto: p.texto, x: W / 2, y: r2(yT + f.tamanho * 1.25), largura: W - 2 * M, alinhamento: 'center',
|
|
349
|
+
tamanho: r2(fit(p.texto, W - 2 * M, 2, 40).tamanho), peso: 500, cor: '#b8bcc8', t: 1.1, ...base });
|
|
350
|
+
return [cena(d, ESCURO, true, els, { zoom: 1.06 })];
|
|
351
|
+
},
|
|
352
|
+
esfera(p, d, tp, t0) {
|
|
353
|
+
const f = fit(p.titulo || '', W - 2 * M, 1, 64);
|
|
354
|
+
return [cena(d, ESCURO, true, [
|
|
355
|
+
{ tipo: 'kit', peca: 'esfera', x: W / 2, y: r2(H * 0.46), raio: r2(Math.min(W, H) * 0.3), cor: '#f4f4f6', corAcento: ACENTO, t: 0 },
|
|
356
|
+
{ tipo: 'kit', peca: 'manchete', modo: 'desfoque', texto: p.titulo || '', x: W / 2, y: r2(H * 0.84), largura: W - 2 * M, alinhamento: 'center', tamanho: f.tamanho, peso: 700, cor: '#f4f4f6',
|
|
357
|
+
t: gatilhos([p.titulo || ''], tp, t0, d)[0], ...base },
|
|
358
|
+
])];
|
|
359
|
+
},
|
|
360
|
+
estrela(p, d, tp, t0) {
|
|
361
|
+
return [cena(d, ESCURO, true, [
|
|
362
|
+
{ tipo: 'kit', peca: 'estrela', x: W / 2, y: H / 2, tamanho: r2(170 * b), cor: ACENTO, t: 0.05, texto: (p.titulo || nome) + ' · ' + nome, fonteMono: F.mono },
|
|
363
|
+
], { zoom: 1.1 })];
|
|
364
|
+
},
|
|
365
|
+
grade(p, d) {
|
|
366
|
+
return [cena(d, '#ffffff', false, [
|
|
367
|
+
{ tipo: 'kit', peca: 'grade', largura: W, altura: H, horizonte: r2(H * 0.46), cor: alfaHex(ACENTO, 0.28) },
|
|
368
|
+
{ tipo: 'kit', peca: 'cromo', logo, x: W / 2, y: r2(H * 0.42), tamanho: r2(220 * b), t: 0.05, modo: 'solido', tom: ACENTO, reflexo: false },
|
|
369
|
+
...(p.titulo ? [{ tipo: 'kit', peca: 'manchete', modo: 'desfoque', texto: p.titulo, x: W / 2, y: r2(H * 0.66), largura: W - 2 * M, alinhamento: 'center', tamanho: r2(fit(p.titulo, W * 0.7, 1, 72).tamanho),
|
|
370
|
+
peso: 800, cor: '#111114', t: 0.6, ...base }] : []),
|
|
371
|
+
])];
|
|
372
|
+
},
|
|
373
|
+
site(p, d) {
|
|
374
|
+
const sw = r2(deitado ? W * 0.8 : W - 2 * M), sh = r2(deitado ? sw * 0.56 : sw * 1.45);
|
|
375
|
+
return [cena(d, fundoClaro, false, [
|
|
376
|
+
{ tipo: 'kit', peca: 'site', marca: nome, logo, titulo: p.titulo || '', sub: p.sub, destaque: tese(p.titulo || '', p.destaque), menu: p.menu, x: r2((W - sw) / 2), y: r2((H - sh) / 2),
|
|
377
|
+
largura: sw, altura: sh, t: 0, ...base, fonte: F.display },
|
|
378
|
+
])];
|
|
379
|
+
},
|
|
380
|
+
padrao(p, d) {
|
|
381
|
+
return [cena(d, ACENTO, true, [{ tipo: 'kit', peca: 'padrao', modo: p.modo === 'truchet' ? 'truchet' : 'icone', logo, largura: W, altura: H, celula: r2(170 * b),
|
|
382
|
+
cor: sobre(ACENTO), corOnda: misturarCor(sobre(ACENTO), ACENTO, 0.4), t: 0 }], { zoom: 1.08 })];
|
|
383
|
+
},
|
|
384
|
+
orbe: (p, d, tp, t0) => CENAS.abertura(p, d, tp, t0),
|
|
385
|
+
vetor: (p, d, tp, t0) => CENAS.logo(p, d, tp, t0),
|
|
386
|
+
cromo(p, d) {
|
|
387
|
+
const f = fit(p.titulo || '', W - 2 * M, 1, 72);
|
|
388
|
+
return [cena(d, fundoEstudio, true, [
|
|
389
|
+
{ tipo: 'kit', peca: 'cromo', logo, x: W / 2, y: r2(H * 0.44), tamanho: r2(300 * b), t: 0.05 },
|
|
390
|
+
...(p.titulo ? [{ tipo: 'kit', peca: 'manchete', modo: 'desfoque', texto: p.titulo, x: W / 2, y: r2(H * 0.74), largura: W - 2 * M, alinhamento: 'center', tamanho: f.tamanho, peso: 700, cor: '#f4f4f6', t: 0.7, ...base }] : []),
|
|
391
|
+
])];
|
|
392
|
+
},
|
|
393
|
+
cartoes: (p, d, tp, t0) => CENAS.passos(p, d, tp, t0),
|
|
394
|
+
painel: (p, d, tp, t0) => CENAS.produto(p, d, tp, t0),
|
|
395
|
+
cores: (p, d, tp, t0) => CENAS.cores(p, d, tp, t0),
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// ── montar ─────────────────────────────────────────────────────────
|
|
399
|
+
const cenas = [];
|
|
400
|
+
/* Com modelo 3D no roteiro (ou "3d" pedido), a parte de produto — ou a
|
|
401
|
+
abertura, se não houver produto — ganha o produto girando. */
|
|
402
|
+
if (roteiro.modelo3d) {
|
|
403
|
+
const alvo = partes.find((p) => p.tipo === 'produto' && !p.visual) || partes.find((p) => p.tipo === 'abertura' && !p.visual);
|
|
404
|
+
if (alvo) alvo.visual = '3d';
|
|
405
|
+
}
|
|
406
|
+
partes.forEach((p, i) => {
|
|
407
|
+
const fazer = (p.visual && VISUAL[p.visual]) || CENAS[p.tipo] || CENAS.titulo;
|
|
408
|
+
const feitas = fazer(p, dur[i], tempos[i], ini[i], i);
|
|
409
|
+
feitas.forEach((c, j) => { c.secao = String(i + 1).padStart(2, '0') + ' · ' + String(p.visual || p.tipo || 'parte').toUpperCase() + (feitas.length > 1 ? ' ' + (j + 1) + '/' + feitas.length : ''); });
|
|
410
|
+
cenas.push(...feitas);
|
|
411
|
+
});
|
|
412
|
+
/* A transição cruza as cenas: a que entra começa antes de a anterior
|
|
413
|
+
acabar. Para a voz não adiantar, a anterior ganha esse tempo. */
|
|
414
|
+
cenas.forEach((c, i) => {
|
|
415
|
+
const tr = c.transicao && typeof c.transicao === 'object' ? c.transicao.duracao || 0 : 0;
|
|
416
|
+
if (i > 0 && tr) cenas[i - 1].duracao = r2(cenas[i - 1].duracao + tr);
|
|
417
|
+
if (!c.transicao) c.transicao = 'corte';
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
const plano = {
|
|
421
|
+
formato: { largura: W, altura: H, fps: FPS },
|
|
422
|
+
fundoGeral: ESCURO,
|
|
423
|
+
estilo: ehMarca ? 'marca' : 'lancamento',
|
|
424
|
+
hud: { marca: String(nome).toUpperCase(), rotulo: ehMarca ? 'revelação de marca' : 'filme de lançamento', direita: W + '×' + H, fonte: F.mono },
|
|
425
|
+
acabamento: { grao: 0.05, vinheta: 0.18 },
|
|
426
|
+
cenas,
|
|
427
|
+
audio: {
|
|
428
|
+
...(narracao ? { narracao } : {}),
|
|
429
|
+
...(trilha ? { trilha: { src: trilha, volumes: volumesDaTrilha(temVoz ? tempos : [], total), loop: true } } : {}),
|
|
430
|
+
efeitos,
|
|
431
|
+
},
|
|
432
|
+
};
|
|
433
|
+
return { ok: true, plano, resumo: { cenas: cenas.length, segundos: total, partes: partes.length, fps: FPS, estilo: plano.estilo } };
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function alfaHex(hex, a) {
|
|
437
|
+
const h = String(hex).replace('#', '');
|
|
438
|
+
return '#' + (h.length === 3 ? h.split('').map((c) => c + c).join('') : h.slice(0, 6)) + Math.round(a * 255).toString(16).padStart(2, '0');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
module.exports = { montarLancamento, VISUAIS };
|
package/lib/estudio/motion.js
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
|
|
33
33
|
const { caber, falaDe } = require('./composicao.js');
|
|
34
34
|
const { misturarCor, ehCor } = require('./morfar.js');
|
|
35
|
+
const { volumesDaTrilha } = require('./ritmo.js');
|
|
35
36
|
|
|
36
37
|
const k = (t, v, e) => (e ? { t, v, e } : { t, v });
|
|
37
38
|
const r2 = (n) => Math.round(n * 100) / 100;
|
|
@@ -306,11 +307,7 @@ function montarMotion({ roteiro, largura = 1920, altura = 1080, tempos = [], nar
|
|
|
306
307
|
cam.rotacaoX.push(k(DUR, 0, 'suave'));
|
|
307
308
|
|
|
308
309
|
// ── o som: a música abaixa quando a voz fala ─────────────────────────
|
|
309
|
-
const volumes =
|
|
310
|
-
if (temVoz) {
|
|
311
|
-
for (const tp of tempos) volumes.push(k(r2(Math.max(0.41, tp.de - 0.15)), 0.22), k(r2(tp.de), 0.11), k(r2(tp.ate), 0.11), k(r2(tp.ate + 0.35), 0.22));
|
|
312
|
-
}
|
|
313
|
-
volumes.push(k(r2(DUR - 0.6), 0.2), k(DUR, 0));
|
|
310
|
+
const volumes = volumesDaTrilha(temVoz ? tempos : [], DUR);
|
|
314
311
|
som(0.05, 'sopro', 0.3);
|
|
315
312
|
som(DUR - 1.2, 'brilho', 0.35);
|
|
316
313
|
|
package/lib/estudio/plano.js
CHANGED
|
@@ -48,11 +48,13 @@
|
|
|
48
48
|
const fs = require('fs');
|
|
49
49
|
const path = require('path');
|
|
50
50
|
const morfar = require('./morfar.js');
|
|
51
|
+
const kit = require('./kit.js');
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
// "kit": uma peça pronta do kit de motion (kit.js), pelo nome — ver kit/*.jsx.
|
|
54
|
+
const TIPOS = new Set(['texto', 'forma', 'imagem', 'video', 'icone', 'legenda', 'componente', 'grupo', 'kit']);
|
|
53
55
|
const FORMAS = new Set(['retangulo', 'circulo', 'linha', 'poligono', 'traco']);
|
|
54
56
|
const AJUSTES = new Set(['cover', 'contain', 'preencher']);
|
|
55
|
-
const TRANSICOES = new Set(['corte', 'esmaecer', 'deslizar', 'zoom', 'giro', 'mascara', 'morph', 'chicote', 'soco', 'flash']);
|
|
57
|
+
const TRANSICOES = new Set(['corte', 'esmaecer', 'deslizar', 'zoom', 'giro', 'mascara', 'morph', 'chicote', 'soco', 'flash', 'liquido']);
|
|
56
58
|
|
|
57
59
|
/* As propriedades que a animação por quadro-chave alcança. Cada uma é um
|
|
58
60
|
número no tempo; a curva entre dois quadros é a suavização declarada.
|
|
@@ -78,7 +80,9 @@ const TEXTUAIS = new Set(['cor', 'caminho']);
|
|
|
78
80
|
// rotacaoX/rotacaoY/z: a câmera gira em volta do mundo e avança nele (precisa de `perspectiva` na cena).
|
|
79
81
|
const ANIMAVEIS_CAMERA = new Set(['zoom', 'x', 'y', 'rotacao', 'rotacaoX', 'rotacaoY', 'z']);
|
|
80
82
|
|
|
81
|
-
|
|
83
|
+
/* editorial (ataque rápido, freio forte), camera (bezier longa), arrasto (a
|
|
84
|
+
mão que desenha) e acelera (a câmera antes do corte) vêm do guia VOX. */
|
|
85
|
+
const SUAVIZACOES = new Set(['linear', 'suave', 'entrada', 'saida', 'elastico', 'salto', 'editorial', 'camera', 'arrasto', 'acelera']);
|
|
82
86
|
|
|
83
87
|
const COR = /^(#[0-9a-f]{3,8}|rgba?\([^)]+\)|hsla?\([^)]+\)|transparent|currentColor)$/i;
|
|
84
88
|
|
|
@@ -240,6 +244,11 @@ function conferirElemento(caminho, el, pasta, problemas, duracao) {
|
|
|
240
244
|
// A válvula de escape: um arquivo React escrito pela própria IA.
|
|
241
245
|
conferirArquivo(`${caminho}.src`, el.src, pasta, problemas);
|
|
242
246
|
break;
|
|
247
|
+
case 'kit':
|
|
248
|
+
for (const oque of kit.conferirPeca(el)) problemas.push(erro(`${caminho}.peca`, oque));
|
|
249
|
+
// A logo de uma peça é arquivo da pasta, como a imagem.
|
|
250
|
+
if (el.logo) conferirArquivo(`${caminho}.logo`, el.logo, pasta, problemas);
|
|
251
|
+
break;
|
|
243
252
|
}
|
|
244
253
|
}
|
|
245
254
|
|
|
@@ -298,6 +307,22 @@ function conferir(plano, { pasta = null } = {}) {
|
|
|
298
307
|
conferirFundo(`${onde}.fundo`, cena.fundo, pasta, problemas);
|
|
299
308
|
conferirAnima(`${onde}.camera`, cena.camera, problemas, ANIMAVEIS_CAMERA);
|
|
300
309
|
|
|
310
|
+
/* As CAMADAS de profundidade: [{p, desfoque, sujeito}]. "p" é a
|
|
311
|
+
profundidade (0,12 longe … 1,25 perto), e o elemento diz a dele por
|
|
312
|
+
índice. Índice fora da lista põe o elemento em lugar nenhum. */
|
|
313
|
+
const nCamadas = Array.isArray(cena.camadas) ? cena.camadas.length : 0;
|
|
314
|
+
if (cena.camadas != null) {
|
|
315
|
+
if (!nCamadas) problemas.push(erro(`${onde}.camadas`, 'camadas é uma lista de {p, desfoque, sujeito}'));
|
|
316
|
+
else cena.camadas.forEach((c, k) => {
|
|
317
|
+
if (!c || typeof c.p !== 'number' || !(c.p > 0)) problemas.push(erro(`${onde}.camadas[${k}].p`, 'p é a profundidade, um número maior que 0'));
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
for (const [k, el] of (Array.isArray(cena.elementos) ? cena.elementos : []).entries()) {
|
|
321
|
+
if (el && el.camada != null && !(Number.isInteger(el.camada) && el.camada >= 0 && el.camada < nCamadas)) {
|
|
322
|
+
problemas.push(erro(`${onde}.elementos[${k}].camada`, nCamadas ? `camada vai de 0 a ${nCamadas - 1}` : 'a cena não declara "camadas"'));
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
301
326
|
if (cena.transicao) {
|
|
302
327
|
const t = cena.transicao.tipo || cena.transicao;
|
|
303
328
|
if (!TRANSICOES.has(t)) {
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* referencias.js — de onde o vídeo tira as referências, decidido pela
|
|
3
|
+
* PESSOA antes de começar.
|
|
4
|
+
*
|
|
5
|
+
* "A IA deve pedir: pegar referências da web com escopo aberto, pegar
|
|
6
|
+
* referências da web com escopo que você define (escopo fechado), ou sem
|
|
7
|
+
* referências — só motion." (29/09)
|
|
8
|
+
*
|
|
9
|
+
* Três modos:
|
|
10
|
+
* aberto pesquisa, site da marca e b-roll de banco à vontade (o híbrido)
|
|
11
|
+
* fechado só o que a pessoa disse: os sites e termos do escopo, nada além
|
|
12
|
+
* sem nenhuma foto nem vídeo de fora: motion puro (o kit), sem b-roll
|
|
13
|
+
*
|
|
14
|
+
* A escolha vale para o pedido (o terminal pergunta a cada vídeo novo) e é
|
|
15
|
+
* lida em dois lugares: a instrução que vai para o agente e o Estúdio, que
|
|
16
|
+
* não baixa b-roll no "sem" e só busca o escopo no "fechado".
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
'use strict';
|
|
20
|
+
|
|
21
|
+
let ATUAL = { modo: null, escopo: '' };
|
|
22
|
+
|
|
23
|
+
function definir(modo, escopo = '') {
|
|
24
|
+
ATUAL = { modo: ['aberto', 'fechado', 'sem'].includes(modo) ? modo : null, escopo: String(escopo || '').trim() };
|
|
25
|
+
return ATUAL;
|
|
26
|
+
}
|
|
27
|
+
function atual() { return ATUAL; }
|
|
28
|
+
function limpar() { ATUAL = { modo: null, escopo: '' }; }
|
|
29
|
+
|
|
30
|
+
/** Os termos do escopo fechado: "site:anthropic.com, claude ai, sonnet" → lista. */
|
|
31
|
+
function termos(escopo = ATUAL.escopo) {
|
|
32
|
+
return String(escopo || '').split(/[,;\n]+/).map((x) => x.trim()).filter(Boolean).slice(0, 8);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A resposta da pergunta ("1", "2", "aberto", "só motion"…) → modo. */
|
|
36
|
+
function entender(resposta) {
|
|
37
|
+
const r = String(resposta || '').toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '').trim();
|
|
38
|
+
if (/^1\b|aberto|livre|qualquer/.test(r)) return 'aberto';
|
|
39
|
+
if (/^2\b|fechad|escopo|defino|so (d|n)(e|o)ss/.test(r)) return 'fechado';
|
|
40
|
+
if (/^3\b|sem ref|nenhum|so motion|motion|sem foto|sem imagem/.test(r)) return 'sem';
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* O MODO DEDUZIDO DO PEDIDO. Quem diz no pedido não é perguntado:
|
|
45
|
+
"só motion", "sem referência", "sem foto" → sem
|
|
46
|
+
"só do site x.com", "usa apenas anthropic.com e …" → fechado (o escopo)
|
|
47
|
+
"web aberta", "escopo aberto", "pesquisa à vontade" → aberto
|
|
48
|
+
O resto volta com `explicito: false` e o terminal pergunta antes de
|
|
49
|
+
começar: "eu posso pedir um vídeo mas estar me referindo ao motion"
|
|
50
|
+
(Joel, 02/10) — chutar o aberto fazia vídeo com foto e b-roll quando a
|
|
51
|
+
pessoa queria só motion. */
|
|
52
|
+
function inferir(texto) {
|
|
53
|
+
const t = String(texto || '').toLowerCase().normalize('NFD').replace(/[̀-ͯ]/g, '');
|
|
54
|
+
if (/so (o )?motion|motion puro|sem refer|sem (foto|imagem|imagens|video de banco|pesquisa)|nao (pesquis|busqu)|so anima/.test(t)) return { modo: 'sem', escopo: '', explicito: true };
|
|
55
|
+
const dominios = String(texto || '').match(/\b[a-z0-9-]+(\.[a-z0-9-]+)+\.?[a-z]{2,}(\/[^\s,]*)?/gi) || [];
|
|
56
|
+
if (/\b(so|apenas|somente|unicamente)\b[^.]{0,30}\b(site|sites|link|links|fonte|fontes|pagina|de|do|da|em)\b/.test(t) && dominios.length) {
|
|
57
|
+
return { modo: 'fechado', escopo: dominios.join(', '), explicito: true };
|
|
58
|
+
}
|
|
59
|
+
if (/escopo aberto|web aberta|pesquis\w* (a vontade|na web|na internet)|busc\w* (imagens|fotos|videos) na (web|internet)|com (imagens|fotos) da (web|internet)/.test(t)) {
|
|
60
|
+
return { modo: 'aberto', escopo: '', explicito: true };
|
|
61
|
+
}
|
|
62
|
+
return { modo: 'aberto', escopo: '', explicito: false };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** A instrução para o agente, conforme o modo. */
|
|
66
|
+
function instrucao({ modo, escopo } = ATUAL) {
|
|
67
|
+
if (modo === 'sem') {
|
|
68
|
+
return '[REFERÊNCIAS: NENHUMA] A pessoa quer SÓ MOTION: não pesquise (nada de pesquisar_referencia nem estudio_site), '
|
|
69
|
+
+ 'não busque foto nem vídeo, não use estudio_midia de banco. Escreva o roteiro com o que o pedido já diz e deixe '
|
|
70
|
+
+ 'o motion gerativo fazer o trabalho (roteiro.estilo "gerativo": a tipografia anima sozinha, sem template).';
|
|
71
|
+
}
|
|
72
|
+
if (modo === 'fechado') {
|
|
73
|
+
const t = termos(escopo);
|
|
74
|
+
return `[REFERÊNCIAS: ESCOPO FECHADO] Use SÓ estas fontes e termos: ${t.join(' · ') || escopo}. `
|
|
75
|
+
+ 'Pesquise apenas dentro delas (em pesquisar_referencia, cite o site ou o termo no próprio texto da busca). '
|
|
76
|
+
+ 'Nada de fato, foto ou vídeo de fora desse escopo; se faltar algo, diga no fim em uma linha. '
|
|
77
|
+
+ `No roteiro, ponha roteiro.busca = ${JSON.stringify(t)} para o b-roll.`;
|
|
78
|
+
}
|
|
79
|
+
if (modo === 'aberto') {
|
|
80
|
+
return '[REFERÊNCIAS: ESCOPO ABERTO] Pesquise o assunto à vontade (pesquisar_referencia; estudio_site se for de uma marca) '
|
|
81
|
+
+ 'e dê em cada parte do roteiro um "busca" em inglês com o que mostrar de fundo — gente, lugar, tela, objeto de verdade.';
|
|
82
|
+
}
|
|
83
|
+
return '';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Os domínios do escopo fechado: "só do site anthropic.com" → ['anthropic.com']. */
|
|
87
|
+
function dominios(escopo = ATUAL.escopo) {
|
|
88
|
+
return termos(escopo).map((t) => t.replace(/^site:/, '').replace(/^https?:\/\//, '').replace(/^www\./, '').split('/')[0].toLowerCase())
|
|
89
|
+
.filter((t) => /^[a-z0-9-]+(\.[a-z0-9-]+)+$/.test(t));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** O resultado da pesquisa sem o que está fora do escopo fechado (subdomínios valem: www-cdn.anthropic.com). */
|
|
93
|
+
function filtrar(r, { modo, escopo } = ATUAL) {
|
|
94
|
+
if (modo !== 'fechado' || !r || !Array.isArray(r.referencias)) return r;
|
|
95
|
+
const ds = dominios(escopo);
|
|
96
|
+
if (!ds.length) return r;
|
|
97
|
+
const dentro = (u) => { try { const h = new URL(u).hostname.toLowerCase(); return ds.some((d) => h === d || h.endsWith('.' + d)); } catch { return false; } };
|
|
98
|
+
const refs = r.referencias.filter((x) => dentro(x.url));
|
|
99
|
+
const fora = r.referencias.length - refs.length;
|
|
100
|
+
if (!refs.length) return { ok: false, consulta: r.consulta, error: `nada desta busca é de ${ds.join(', ')} (escopo fechado)`,
|
|
101
|
+
comoCorrigir: `Abra o site direto: browser_open https://${ds[0]} e browser_read. Não use fonte de fora.` };
|
|
102
|
+
return { ...r, fontes: refs.length, referencias: refs, ...(fora ? { foraDoEscopo: fora } : {}) };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = { definir, atual, limpar, termos, entender, inferir, instrucao, dominios, filtrar };
|