primocode 9.10.0 → 10.0.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 +30 -6
- package/bin/primocode.js +100 -341
- package/lib/act.js +245 -23
- package/lib/browser.js +17 -0
- package/lib/casar.js +108 -0
- package/lib/catalogo.js +6 -2
- package/lib/config.js +10 -3
- package/lib/cursor.js +10 -1
- package/lib/entrada.js +4 -2
- package/lib/estudio/diretor.js +9 -1
- package/lib/estudio/estilo.js +51 -0
- package/lib/estudio/glifos.js +215 -0
- package/lib/estudio/index.js +41 -18
- package/lib/estudio/kit/base.jsx +285 -0
- package/lib/estudio/kit/index.jsx +11 -0
- package/lib/estudio/kit/marca.jsx +741 -0
- package/lib/estudio/kit/vox.jsx +357 -0
- package/lib/estudio/kit.js +72 -0
- package/lib/estudio/lancamento.js +413 -0
- package/lib/estudio/motion.js +2 -5
- package/lib/estudio/plano.js +28 -3
- package/lib/estudio/remotion.js +87 -43
- package/lib/estudio/ritmo.js +77 -0
- package/lib/estudio/vox.js +385 -0
- package/lib/fala.js +1 -94
- package/lib/pipeline.js +55 -56
- package/lib/repeticao.js +3 -1
- package/lib/skills.js +8 -2
- package/lib/snapshot.js +111 -0
- package/lib/tools.js +79 -20
- package/lib/ui.js +13 -6
- package/lib/video/analise.js +89 -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 +247 -0
- package/lib/video/ops.js +322 -0
- package/lib/video/projeto.js +133 -0
- package/lib/video/render.js +221 -0
- package/lib/video/texto.js +142 -0
- package/lib/video/timeline.js +310 -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 +3 -4
- 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,413 @@
|
|
|
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
|
+
const VISUAL = {
|
|
330
|
+
esfera(p, d, tp, t0) {
|
|
331
|
+
const f = fit(p.titulo || '', W - 2 * M, 1, 64);
|
|
332
|
+
return [cena(d, ESCURO, true, [
|
|
333
|
+
{ 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 },
|
|
334
|
+
{ 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',
|
|
335
|
+
t: gatilhos([p.titulo || ''], tp, t0, d)[0], ...base },
|
|
336
|
+
])];
|
|
337
|
+
},
|
|
338
|
+
estrela(p, d, tp, t0) {
|
|
339
|
+
return [cena(d, ESCURO, true, [
|
|
340
|
+
{ 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 },
|
|
341
|
+
], { zoom: 1.1 })];
|
|
342
|
+
},
|
|
343
|
+
grade(p, d) {
|
|
344
|
+
return [cena(d, '#ffffff', false, [
|
|
345
|
+
{ tipo: 'kit', peca: 'grade', largura: W, altura: H, horizonte: r2(H * 0.46), cor: alfaHex(ACENTO, 0.28) },
|
|
346
|
+
{ 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 },
|
|
347
|
+
...(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),
|
|
348
|
+
peso: 800, cor: '#111114', t: 0.6, ...base }] : []),
|
|
349
|
+
])];
|
|
350
|
+
},
|
|
351
|
+
site(p, d) {
|
|
352
|
+
const sw = r2(deitado ? W * 0.8 : W - 2 * M), sh = r2(deitado ? sw * 0.56 : sw * 1.45);
|
|
353
|
+
return [cena(d, fundoClaro, false, [
|
|
354
|
+
{ 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),
|
|
355
|
+
largura: sw, altura: sh, t: 0, ...base, fonte: F.display },
|
|
356
|
+
])];
|
|
357
|
+
},
|
|
358
|
+
padrao(p, d) {
|
|
359
|
+
return [cena(d, ACENTO, true, [{ tipo: 'kit', peca: 'padrao', modo: p.modo === 'truchet' ? 'truchet' : 'icone', logo, largura: W, altura: H, celula: r2(170 * b),
|
|
360
|
+
cor: sobre(ACENTO), corOnda: misturarCor(sobre(ACENTO), ACENTO, 0.4), t: 0 }], { zoom: 1.08 })];
|
|
361
|
+
},
|
|
362
|
+
orbe: (p, d, tp, t0) => CENAS.abertura(p, d, tp, t0),
|
|
363
|
+
vetor: (p, d, tp, t0) => CENAS.logo(p, d, tp, t0),
|
|
364
|
+
cromo(p, d) {
|
|
365
|
+
const f = fit(p.titulo || '', W - 2 * M, 1, 72);
|
|
366
|
+
return [cena(d, fundoEstudio, true, [
|
|
367
|
+
{ tipo: 'kit', peca: 'cromo', logo, x: W / 2, y: r2(H * 0.44), tamanho: r2(300 * b), t: 0.05 },
|
|
368
|
+
...(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 }] : []),
|
|
369
|
+
])];
|
|
370
|
+
},
|
|
371
|
+
cartoes: (p, d, tp, t0) => CENAS.passos(p, d, tp, t0),
|
|
372
|
+
painel: (p, d, tp, t0) => CENAS.produto(p, d, tp, t0),
|
|
373
|
+
cores: (p, d, tp, t0) => CENAS.cores(p, d, tp, t0),
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
// ── montar ─────────────────────────────────────────────────────────
|
|
377
|
+
const cenas = [];
|
|
378
|
+
partes.forEach((p, i) => {
|
|
379
|
+
const fazer = (p.visual && VISUAL[p.visual]) || CENAS[p.tipo] || CENAS.titulo;
|
|
380
|
+
const feitas = fazer(p, dur[i], tempos[i], ini[i], i);
|
|
381
|
+
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 : ''); });
|
|
382
|
+
cenas.push(...feitas);
|
|
383
|
+
});
|
|
384
|
+
/* A transição cruza as cenas: a que entra começa antes de a anterior
|
|
385
|
+
acabar. Para a voz não adiantar, a anterior ganha esse tempo. */
|
|
386
|
+
cenas.forEach((c, i) => {
|
|
387
|
+
const tr = c.transicao && typeof c.transicao === 'object' ? c.transicao.duracao || 0 : 0;
|
|
388
|
+
if (i > 0 && tr) cenas[i - 1].duracao = r2(cenas[i - 1].duracao + tr);
|
|
389
|
+
if (!c.transicao) c.transicao = 'corte';
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
const plano = {
|
|
393
|
+
formato: { largura: W, altura: H, fps: FPS },
|
|
394
|
+
fundoGeral: ESCURO,
|
|
395
|
+
estilo: ehMarca ? 'marca' : 'lancamento',
|
|
396
|
+
hud: { marca: String(nome).toUpperCase(), rotulo: ehMarca ? 'revelação de marca' : 'filme de lançamento', direita: W + '×' + H, fonte: F.mono },
|
|
397
|
+
acabamento: { grao: 0.05, vinheta: 0.18 },
|
|
398
|
+
cenas,
|
|
399
|
+
audio: {
|
|
400
|
+
...(narracao ? { narracao } : {}),
|
|
401
|
+
...(trilha ? { trilha: { src: trilha, volumes: volumesDaTrilha(temVoz ? tempos : [], total), loop: true } } : {}),
|
|
402
|
+
efeitos,
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
return { ok: true, plano, resumo: { cenas: cenas.length, segundos: total, partes: partes.length, fps: FPS, estilo: plano.estilo } };
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
function alfaHex(hex, a) {
|
|
409
|
+
const h = String(hex).replace('#', '');
|
|
410
|
+
return '#' + (h.length === 3 ? h.split('').map((c) => c + c).join('') : h.slice(0, 6)) + Math.round(a * 255).toString(16).padStart(2, '0');
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
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)) {
|