primocode 8.42.2 → 9.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.
Files changed (102) hide show
  1. package/README.md +41 -37
  2. package/bin/primocode.js +598 -360
  3. package/lib/acervo.js +8 -1
  4. package/lib/act.js +205 -114
  5. package/lib/api.js +35 -4
  6. package/lib/boas-vindas.js +31 -192
  7. package/lib/catalogo.js +60 -60
  8. package/lib/config.js +1 -1
  9. package/lib/conta.js +3 -24
  10. package/lib/diff.js +161 -0
  11. package/lib/diretor.js +37 -47
  12. package/lib/entrada.js +545 -0
  13. package/lib/especialistas.js +2 -2
  14. package/lib/estudio/assets.js +343 -0
  15. package/lib/estudio/identidade.js +560 -0
  16. package/lib/estudio/index.js +565 -0
  17. package/lib/estudio/plano.js +327 -0
  18. package/lib/estudio/projeto.js +246 -0
  19. package/lib/estudio/remotion.js +606 -0
  20. package/lib/estudio/slides.js +190 -0
  21. package/lib/estudio/video.js +296 -0
  22. package/lib/fala.js +13 -1
  23. package/lib/marca.js +4 -1
  24. package/lib/marketplace.js +189 -0
  25. package/lib/permissao.js +137 -0
  26. package/lib/registro.js +229 -0
  27. package/lib/skills.js +329 -0
  28. package/lib/tools.js +51 -181
  29. package/lib/ui.js +7 -3
  30. package/lib/voz.js +239 -0
  31. package/package.json +12 -3
  32. package/skills/acessibilidade/SKILL.md +28 -0
  33. package/skills/api-que-nao-quebra/SKILL.md +27 -0
  34. package/skills/depurar-de-verdade/SKILL.md +29 -0
  35. package/skills/editar-meu-video/SKILL.md +59 -0
  36. package/skills/escrever-em-portugues/SKILL.md +28 -0
  37. package/skills/git-limpo/SKILL.md +28 -0
  38. package/skills/motion-de-primeira/SKILL.md +62 -0
  39. package/skills/pagina-que-converte/SKILL.md +28 -0
  40. package/skills/performance-web/SKILL.md +28 -0
  41. package/skills/revisao-antes-de-entregar/SKILL.md +27 -0
  42. package/skills/slides-que-prendem/SKILL.md +40 -0
  43. package/studio/edge.py +16 -66
  44. package/studio/narrar.py +274 -0
  45. package/studio/pessoa.py +167 -0
  46. package/voz/primo_voz/__init__.py +0 -0
  47. package/voz/primo_voz/__main__.py +325 -0
  48. package/voz/primo_voz/escuta.py +235 -0
  49. package/voz/primo_voz/orbe.py +216 -0
  50. package/lib/audio-livre.js +0 -177
  51. package/lib/chaves.js +0 -149
  52. package/lib/claude-engine.js +0 -631
  53. package/lib/codex-engine.js +0 -208
  54. package/lib/remotion.js +0 -663
  55. package/lib/studio.js +0 -960
  56. package/studio/PRIMOCODE.md +0 -58
  57. package/studio/README.md +0 -154
  58. package/studio/__pycache__/edge.cpython-311.pyc +0 -0
  59. package/studio/__pycache__/elevenlabs.cpython-311.pyc +0 -0
  60. package/studio/__pycache__/voz.cpython-311.pyc +0 -0
  61. package/studio/arquivos/corte-teste-de-camera-fb823.json +0 -57
  62. package/studio/ferramentas/00-protocolo.md +0 -125
  63. package/studio/ferramentas/01-modelos.md +0 -270
  64. package/studio/ferramentas/README.md +0 -32
  65. package/studio/ferramentas/corte.md +0 -919
  66. package/studio/ferramentas/grade.md +0 -92
  67. package/studio/ferramentas/prisma.md +0 -107
  68. package/studio/ferramentas/prosa.md +0 -86
  69. package/studio/ferramentas/tela.md +0 -93
  70. package/studio/ferramentas/traco.md +0 -79
  71. package/studio/server.py +0 -2254
  72. package/studio/studio.log +0 -57
  73. package/studio/voz.py +0 -388
  74. package/studio/web/analise.html +0 -52
  75. package/studio/web/core.css +0 -1167
  76. package/studio/web/fontes/bebas.woff2 +0 -0
  77. package/studio/web/fontes/grotesk-700.woff2 +0 -0
  78. package/studio/web/fontes/inter-400.woff2 +0 -0
  79. package/studio/web/fontes/inter-700.woff2 +0 -0
  80. package/studio/web/fontes/jetbrains-400.woff2 +0 -0
  81. package/studio/web/fontes/playfair-700.woff2 +0 -0
  82. package/studio/web/fontes.css +0 -23
  83. package/studio/web/home.html +0 -217
  84. package/studio/web/js/analise.js +0 -510
  85. package/studio/web/js/audio.js +0 -607
  86. package/studio/web/js/editor.js +0 -1802
  87. package/studio/web/js/exportar.js +0 -222
  88. package/studio/web/js/ferramentas.js +0 -108
  89. package/studio/web/js/icones.js +0 -120
  90. package/studio/web/js/midia.js +0 -157
  91. package/studio/web/js/narracao.js +0 -325
  92. package/studio/web/js/player.js +0 -903
  93. package/studio/web/js/render-grade.js +0 -243
  94. package/studio/web/js/render-prosa.js +0 -104
  95. package/studio/web/js/render.js +0 -1852
  96. package/studio/web/js/sfx.js +0 -233
  97. package/studio/web/js/video.js +0 -464
  98. package/studio/web/js/voz-neural.js +0 -152
  99. package/studio/web/render.css +0 -1352
  100. package/studio/web/tf.html +0 -15
  101. package/studio/web/tool.html +0 -78
  102. package/studio/web/view.html +0 -27
package/lib/diff.js ADDED
@@ -0,0 +1,161 @@
1
+ /**
2
+ * diff.js — o que mudou no arquivo, linha a linha.
3
+ *
4
+ * ── POR QUE ISTO EXISTE ──────────────────────────────────────────────────
5
+ * O que a tela dizia quando o agente editava um arquivo era isto:
6
+ *
7
+ * ● Edit src/app.js
8
+ * ⎿ 2 trocas · +7 linhas
9
+ *
10
+ * Sete linhas a mais. Quais? A única forma de saber era abrir o arquivo
11
+ * depois — ou seja, o agente mexia no seu código e você via um número. É a
12
+ * diferença entre acompanhar e torcer.
13
+ *
14
+ * ── POR QUE NÃO UMA BIBLIOTECA ───────────────────────────────────────────
15
+ * O PrimoCode não tem dependência nenhuma, e é isso que faz o `npm i -g`
16
+ * terminar em segundos. Um diff de linhas é um algoritmo de uma tela, e está
17
+ * escrito aqui embaixo.
18
+ *
19
+ * ── O DIFF NÃO VAI PARA O MODELO ─────────────────────────────────────────
20
+ * Ele é desenhado para a PESSOA. Mandá-lo de volta ao modelo dentro do
21
+ * resultado da tool custaria centenas de tokens por edição, num plano que é
22
+ * limitado por token — e o modelo acabou de escrever aquele texto, então não
23
+ * aprende nada relendo. Por isso quem chama guarda o desenho numa chave com
24
+ * `_` na frente, que o act.js tira antes de a conversa seguir.
25
+ */
26
+
27
+ 'use strict';
28
+
29
+ const { c } = require('./ui.js');
30
+
31
+ /* Arquivo grande não ganha diff desenhado, ganha o resumo em números.
32
+ *
33
+ * O motivo não é o custo do algoritmo: é a tela. Um diff de 400 linhas
34
+ * empurra para fora da vista tudo o que veio antes — inclusive o pedido da
35
+ * pessoa e os passos anteriores. Um bloco que esconde o resto da conversa é
36
+ * pior que um número. */
37
+ const MAX_LINHAS_ARQUIVO = 1200;
38
+ /* E há um segundo teto, este por memória: a tabela do LCS é uma matriz
39
+ (linhasA+1) × (linhasB+1) de inteiros. Com 1200 × 1200 são ~5,8 MB, o que
40
+ é barato. O produto é o que importa, não cada lado: 1200 linhas contra 20
41
+ é trivial, 1200 contra 1200 é o pior caso. */
42
+ const MAX_CELULAS = 1200 * 1200;
43
+ const CONTEXTO = 2; // linhas iguais mostradas em volta de cada mudança
44
+ const MAX_MOSTRADAS = 24; // teto do bloco inteiro, contando as de contexto
45
+
46
+ /**
47
+ * O diff de duas versões de um texto, como lista de operações.
48
+ *
49
+ * Algoritmo: maior subsequência comum de LINHAS, em programação dinâmica.
50
+ * É O(n×m) em tempo e memória, o que é perfeitamente aceitável até alguns
51
+ * milhares de linhas — e acima disso o desenho nem sai (ver acima).
52
+ *
53
+ * @returns {Array<{tipo:' '|'+'|'-', texto:string, a:number, b:number}>}
54
+ * `a` é a linha no ANTES, `b` no DEPOIS (1-based, 0 quando não existe).
55
+ */
56
+ function operacoes(antes, depois) {
57
+ const A = String(antes ?? '').split('\n');
58
+ const B = String(depois ?? '').split('\n');
59
+
60
+ // A tabela do LCS: lcs[i][j] = tamanho da maior subsequência comum entre
61
+ // A[i..] e B[j..]. Construída de trás para frente para a leitura sair na
62
+ // ordem natural depois.
63
+ const lcs = Array.from({ length: A.length + 1 }, () => new Uint32Array(B.length + 1));
64
+ for (let i = A.length - 1; i >= 0; i--) {
65
+ for (let j = B.length - 1; j >= 0; j--) {
66
+ lcs[i][j] = A[i] === B[j]
67
+ ? lcs[i + 1][j + 1] + 1
68
+ : Math.max(lcs[i + 1][j], lcs[i][j + 1]);
69
+ }
70
+ }
71
+
72
+ const ops = [];
73
+ let i = 0, j = 0;
74
+ while (i < A.length && j < B.length) {
75
+ if (A[i] === B[j]) { ops.push({ tipo: ' ', texto: A[i], a: i + 1, b: j + 1 }); i++; j++; }
76
+ else if (lcs[i + 1][j] >= lcs[i][j + 1]) { ops.push({ tipo: '-', texto: A[i], a: i + 1, b: 0 }); i++; }
77
+ else { ops.push({ tipo: '+', texto: B[j], a: 0, b: j + 1 }); j++; }
78
+ }
79
+ while (i < A.length) { ops.push({ tipo: '-', texto: A[i], a: i + 1, b: 0 }); i++; }
80
+ while (j < B.length) { ops.push({ tipo: '+', texto: B[j], a: 0, b: j + 1 }); j++; }
81
+ return ops;
82
+ }
83
+
84
+ /** Quantas linhas entraram e quantas saíram. */
85
+ function contar(ops) {
86
+ let mais = 0, menos = 0;
87
+ for (const o of ops) { if (o.tipo === '+') mais++; else if (o.tipo === '-') menos++; }
88
+ return { mais, menos };
89
+ }
90
+
91
+ /**
92
+ * Só os pedaços que interessam: cada mudança com algumas linhas iguais em
93
+ * volta. O resto do arquivo não entra — num arquivo de 500 linhas com uma
94
+ * troca, mostrar as 500 é esconder a troca.
95
+ */
96
+ function pedacos(ops, contexto = CONTEXTO) {
97
+ const querida = new Array(ops.length).fill(false);
98
+ ops.forEach((o, k) => {
99
+ if (o.tipo === ' ') return;
100
+ for (let x = Math.max(0, k - contexto); x <= Math.min(ops.length - 1, k + contexto); x++) querida[x] = true;
101
+ });
102
+
103
+ const blocos = [];
104
+ let atual = null;
105
+ for (let k = 0; k < ops.length; k++) {
106
+ if (!querida[k]) { atual = null; continue; }
107
+ if (!atual) { atual = []; blocos.push(atual); }
108
+ atual.push(ops[k]);
109
+ }
110
+ return blocos;
111
+ }
112
+
113
+ /**
114
+ * O bloco de diff pronto para a tela.
115
+ *
116
+ * @param {string} antes
117
+ * @param {string} depois
118
+ * @param {object} op
119
+ * @param {string} op.prefixo o recuo de cada linha (vem do fala.resultado)
120
+ * @param {number} op.max teto de linhas desenhadas
121
+ * @returns {string} '' quando não vale a pena desenhar
122
+ */
123
+ function desenhar(antes, depois, { prefixo = '', max = MAX_MOSTRADAS } = {}) {
124
+ const linhasA = String(antes ?? '').split('\n').length;
125
+ const linhasB = String(depois ?? '').split('\n').length;
126
+ if (Math.max(linhasA, linhasB) > MAX_LINHAS_ARQUIVO) return '';
127
+ if (linhasA * linhasB > MAX_CELULAS) return '';
128
+ if (antes === depois) return '';
129
+
130
+ const ops = operacoes(antes, depois);
131
+ const blocos = pedacos(ops);
132
+ if (!blocos.length) return '';
133
+
134
+ const out = [];
135
+ let desenhadas = 0;
136
+ let cortou = false;
137
+
138
+ for (const bloco of blocos) {
139
+ if (desenhadas >= max) { cortou = true; break; }
140
+ for (const o of bloco) {
141
+ if (desenhadas >= max) { cortou = true; break; }
142
+ // O número da linha é o do DEPOIS quando a linha existe lá; nas
143
+ // removidas, o do antes. Sem número, "+ const x = 1" não diz onde.
144
+ const n = String(o.b || o.a || '').padStart(4);
145
+ const texto = o.texto.length > 120 ? o.texto.slice(0, 119) + '…' : o.texto;
146
+ if (o.tipo === '+') out.push(prefixo + c.dim(n) + ' ' + c.ok('+ ' + texto));
147
+ else if (o.tipo === '-') out.push(prefixo + c.dim(n) + ' ' + c.err('- ' + texto));
148
+ else out.push(prefixo + c.dim(n) + ' ' + c.dim(' ' + texto));
149
+ desenhadas++;
150
+ }
151
+ }
152
+
153
+ if (cortou) {
154
+ const { mais, menos } = contar(ops);
155
+ out.push(prefixo + c.dim(`… o bloco continua — ${mais} linha${mais === 1 ? '' : 's'} a mais `
156
+ + `e ${menos} a menos, no total`));
157
+ }
158
+ return out.join('\n');
159
+ }
160
+
161
+ module.exports = { operacoes, contar, pedacos, desenhar, MAX_LINHAS_ARQUIVO, MAX_CELULAS, MAX_MOSTRADAS };
package/lib/diretor.js CHANGED
@@ -23,11 +23,19 @@
23
23
  * diretor com o defeito apontado — uma vez. Aceitar "quase certo" aqui custa
24
24
  * um vídeo de 40 segundos que dura 12.
25
25
  *
26
- * ── O MOTOR NÃO É GOSTO ──────────────────────────────────────────────────
27
- * Estúdio ou Remotion: a escolha tem regra (`motorPara`), porque a resposta
28
- * errada aparece tarde — no vídeo de três minutos que engasgou na gravação do
29
- * navegador, ou nos cinco minutos de `npm install` para fazer um story de
30
- * 15 segundos.
26
+ * ── O QUE O DIRETOR NÃO ESCOLHE MAIS ─────────────────────────────────────
27
+ * Ele escolhia o MOTOR (estúdio ou Remotion) e o TEMA, o LAYOUT e o FUNDO,
28
+ * cada um numa lista fechada — "meia-noite | claro | papel | neon", "capa |
29
+ * titulo | secao | duas-colunas". Motor não existe mais: o Remotion é o
30
+ * único renderizador. E as outras três listas saíram por decisão do produto:
31
+ *
32
+ * "tira os templates e os estilos prontos, eles deixam a IA preguiçosa e
33
+ * todo vídeo sai igual e genérico"
34
+ *
35
+ * Uma lista de seis temas é um template com outro nome — o modelo escolhe um
36
+ * dos seis e para de pensar em cor. No lugar delas o diretor DESCREVE: que
37
+ * paleta, que ar, o que está na tela, como a cena troca. Descrição não tem
38
+ * seis opções, e é o produtor quem compõe aquilo com os primitivos.
31
39
  */
32
40
 
33
41
  'use strict';
@@ -56,32 +64,6 @@ function precisaDeProducao(texto) {
56
64
 
57
65
  // ── 2. as decisões que não são do modelo ─────────────────────────────────
58
66
 
59
- /**
60
- * Estúdio ou Remotion.
61
- *
62
- * O estúdio exporta em segundos e não instala nada: é o padrão, e para story,
63
- * reels e teaser é a resposta certa. Remotion custa um `npm install` de
64
- * minutos e meio giga, e devolve o que o estúdio não tem: quadro a quadro
65
- * determinístico, vídeo longo sem risco de engasgo, canal alfa e ProRes.
66
- */
67
- function motorPara({ segundos = 0, pedido = '', alfa = false, fps = 30, entrega } = {}) {
68
- const t = String(pedido).toLowerCase();
69
- if (/remotion/.test(t)) return { motor: 'remotion', motivo: 'foi pedido pelo nome' };
70
- if (/(est[uú]dio|studio|r[aá]pido|agora|j[aá])\b/.test(t) && segundos <= 60) {
71
- return { motor: 'estudio', motivo: 'pedido rápido e curto' };
72
- }
73
- if (alfa || /alfa|transpar[eê]nci|prores|canal alfa/.test(t)) {
74
- return { motor: 'remotion', motivo: 'canal alfa e ProRes só existem no Remotion' };
75
- }
76
- if (entrega === 'cinema' || fps > 30) {
77
- return { motor: 'remotion', motivo: 'fps alto pede render determinístico' };
78
- }
79
- if (segundos > 90) {
80
- return { motor: 'remotion', motivo: 'vídeo longo: a gravação do navegador engasga' };
81
- }
82
- return { motor: 'estudio', motivo: 'curto: sai em segundos, sem instalar nada' };
83
- }
84
-
85
67
  /**
86
68
  * A voz. Tom e idioma decidem; o nome exato quem resolve é o estúdio, que
87
69
  * sabe o que está instalado — aqui sai a INTENÇÃO, que é o que o diretor
@@ -115,7 +97,7 @@ function ritmoPara(segundos) {
115
97
 
116
98
  // ── 3. o plano ───────────────────────────────────────────────────────────
117
99
 
118
- const CAMPOS_CENA = ['titulo', 'narracao', 'duracao', 'layout', 'fundo', 'transicao',
100
+ const CAMPOS_CENA = ['titulo', 'narracao', 'duracao', 'aTela', 'fundo', 'transicao',
119
101
  'elementos', 'midia', 'camera', 'chaves', 'notas'];
120
102
 
121
103
  /**
@@ -132,16 +114,21 @@ function normalizarPlano(bruto, { segundos } = {}) {
132
114
  const cenas = Array.isArray(p.cenas) ? p.cenas : [];
133
115
  if (!cenas.length) defeitos.push('o plano veio sem cenas');
134
116
 
135
- const TRANSICOES = ['corte', 'fade', 'deslizar', 'subir', 'zoom', 'limpar', 'persiana', 'flash'];
136
117
  const limpas = cenas.map((c, i) => {
137
118
  const cena = {};
138
119
  for (const k of CAMPOS_CENA) if (c && c[k] !== undefined) cena[k] = c[k];
139
120
  cena.duracao = Math.max(1.2, Math.min(15, Number(cena.duracao) || 3.5));
140
121
  cena.narracao = typeof cena.narracao === 'string' ? cena.narracao.trim() : '';
141
122
  if (!cena.narracao) defeitos.push(`cena ${i + 1} sem narração`);
142
- if (!cena.titulo && !cena.elementos) defeitos.push(`cena ${i + 1} sem nada na tela`);
143
- // Tudo em fade parece slide, não vídeo — e o diretor esquece disso.
144
- if (!TRANSICOES.includes(cena.transicao)) cena.transicao = i === 0 ? 'fade' : TRANSICOES[(i % 6) + 1];
123
+ if (!cena.titulo && !cena.aTela && !cena.elementos) defeitos.push(`cena ${i + 1} sem nada na tela`);
124
+ /* A transição é texto: "o logo desce e o fundo abre com ele", não um
125
+ item de lista. O rodízio que havia aqui (fade, deslizar, subir…
126
+ em ordem) escondia o defeito em vez de apontá-lo: preenchia a
127
+ lacuna com a próxima palavra da fila, e o vídeo saía com oito
128
+ transições que ninguém escolheu. Cena sem transição é DEFEITO, e
129
+ volta para o diretor decidir. */
130
+ cena.transicao = typeof cena.transicao === 'string' ? cena.transicao.trim() : '';
131
+ if (!cena.transicao) defeitos.push(`cena ${i + 1} não diz como entra`);
145
132
  cena.midia = Array.isArray(cena.midia) ? cena.midia.filter((m) => m && m.busca) : [];
146
133
  return cena;
147
134
  });
@@ -153,11 +140,13 @@ function normalizarPlano(bruto, { segundos } = {}) {
153
140
  const fecha = !alvo || Math.abs(total - alvo) <= Math.max(3, alvo * 0.2);
154
141
  if (!fecha) defeitos.push(`a soma das cenas dá ${total}s e o pedido é ${alvo}s`);
155
142
 
143
+ if (!String(p.visual || '').trim()) defeitos.push('o plano não diz a direção de arte (visual): paleta, ar e tipografia');
144
+
156
145
  const plano = {
157
146
  titulo: String(p.titulo || '').trim() || 'Sem título',
158
147
  formato: p.formato === 'apresentacao' ? 'apresentacao' : 'video',
159
148
  proporcao: ['16:9', '9:16', '1:1', '4:5'].includes(p.proporcao) ? p.proporcao : '16:9',
160
- tema: p.tema || 'meia-noite',
149
+ visual: String(p.visual || '').trim() || null,
161
150
  tom: p.tom || 'neutro',
162
151
  publico: p.publico || null,
163
152
  promessa: p.promessa || null, // o que a pessoa leva do vídeo
@@ -172,8 +161,6 @@ function normalizarPlano(bruto, { segundos } = {}) {
172
161
  midia: limpas.flatMap((c, i) => c.midia.map((m) => ({ cena: i + 1, ...m }))),
173
162
  creditos: !!p.creditos,
174
163
  };
175
- plano.motor = motorPara({ segundos: total, pedido: p.pedido || '', alfa: !!p.alfa,
176
- fps: Number(p.fps) || 30 });
177
164
  return { plano, defeitos, ok: defeitos.length === 0 };
178
165
  }
179
166
 
@@ -192,7 +179,7 @@ Devolva SÓ um bloco \`\`\`json com este formato, sem texto antes nem depois:
192
179
  "titulo": "…",
193
180
  "formato": "${formato}",
194
181
  "proporcao": "16:9 | 9:16 | 1:1 | 4:5",
195
- "tema": "meia-noite | claro | papel | neon | brasa | floresta",
182
+ "visual": "a direção de arte em uma ou duas frases: paleta (diga as cores em hex), que ar tem (limpo, denso, luminoso, sujo), que tipografia pede. NÃO existe lista de temas — descreva o que este vídeo tem de próprio.",
196
183
  "tom": "como deve soar (institucional, urgente, documental, leve…)",
197
184
  "publico": "para quem é",
198
185
  "promessa": "o que a pessoa leva ao terminar de ver",
@@ -202,9 +189,9 @@ Devolva SÓ um bloco \`\`\`json com este formato, sem texto antes nem depois:
202
189
  "titulo": "o que aparece grande na tela",
203
190
  "narracao": "a frase FALADA, escrita para ser lida em voz alta",
204
191
  "duracao": 3.5,
205
- "layout": "capa | titulo | secao | duas-colunas | numeros | citacao | imagem-direita | centro | livre",
206
- "fundo": "malha | aurora | raios | ondas | nevoa | vinheta, uma cor, ou {\\"imagem\\":\\"<da lista midia>\\"}",
207
- "transicao": "corte | fade | deslizar | subir | zoom | limpar | persiana | flash",
192
+ "aTela": "o que se vê nesta cena, descrito como você descreveria para um motion designer: onde está cada coisa, que tamanho tem, o que domina o quadro",
193
+ "fundo": "o fundo desta cena — uma cor, um gradiente (diga as cores), uma imagem da lista midia, ou o que ele vira no meio da cena",
194
+ "transicao": "como esta cena ENTRA, descrito: o que se move, em que direção, quanto dura. Corte seco também é resposta — diga 'corte'.",
208
195
  "midia": [ { "busca": "o que procurar", "tipo": "imagem | logo", "papel": "fundo | destaque | selo", "semFundo": false } ],
209
196
  "camera": "aproximar | afastar | pan-direita | pan-esquerda | subir | descer | empurrar | balanco | nenhuma",
210
197
  "chaves": "descreva em uma frase o movimento principal desta cena (o que entra, o que atravessa, o que cresce)",
@@ -219,6 +206,7 @@ REGRAS QUE SEPARAM UM VÍDEO BOM DE UM GENÉRICO:
219
206
  3. ARCO. Primeira cena prende em 2 segundos, o miolo entrega, a última pede uma ação. Sem "Introdução / Desenvolvimento / Conclusão".
220
207
  4. NÚMERO É CONCRETO. Nada de "muitos clientes": ou tem o número, ou a frase muda.
221
208
  5. RITMO VARIADO. Cenas de durações diferentes e transições diferentes. Tudo em fade parece slide.
209
+ 5b. NADA DE GAVETA. Não existe tema pronto, layout pronto nem fundo pronto: é você que decide a cor, a tipografia e a composição DESTE vídeo. Dois vídeos seus sobre assuntos diferentes não podem sair parecidos.
222
210
  6. MÍDIA COM PROPÓSITO. Só peça imagem ou logo quando ela PROVA algo. Diga o termo de busca de verdade ("painel solar em telhado residencial", não "imagem legal").
223
211
  7. MOVIMENTO. Em cada cena diga em "chaves" o movimento principal — é ele que tira a cara de apresentação parada.
224
212
  8. CÂMERA COM PARCIMÔNIA. Movimento de quadro em UMA cena a cada duas ou três ("camera": "nenhuma" nas outras): o que dá ritmo é alternar cena parada e cena que se move. Em cena com foto de fundo, prefira "aproximar" ou "empurrar".`;
@@ -279,16 +267,18 @@ function pauta(plano) {
279
267
  const p = plano || {};
280
268
  const linhas = [];
281
269
  linhas.push(`PLANO DE PRODUÇÃO — ${p.titulo}`);
282
- linhas.push(`formato ${p.formato} · ${p.proporcao} · ${p.segundos}s · tema ${p.tema} · tom ${p.tom}`);
283
- linhas.push(`motor: ${p.motor && p.motor.motor} (${p.motor && p.motor.motivo})`);
270
+ linhas.push(`formato ${p.formato} · ${p.proporcao} · ${p.segundos}s · tom ${p.tom}`);
271
+ if (p.visual) linhas.push(`visual: ${p.visual}`);
284
272
  if (p.promessa) linhas.push(`promessa: ${p.promessa}`);
285
273
  if (p.publico) linhas.push(`público: ${p.publico}`);
286
274
  linhas.push(`voz: ${p.voz && p.voz.perfil} · ${p.voz && p.voz.instrucao} · ${p.voz && p.voz.idioma}`);
287
275
  if (p.trilha) linhas.push(`trilha: ${p.trilha.busca || p.trilha.arquivo} (volume ${p.trilha.volume})`);
288
276
  linhas.push('');
289
277
  (p.cenas || []).forEach((c, i) => {
290
- linhas.push(`CENA ${i + 1} — ${c.duracao}s · layout ${c.layout || 'titulo'} · entra em ${c.transicao}`);
278
+ linhas.push(`CENA ${i + 1} — ${c.duracao}s · entra: ${c.transicao}`);
291
279
  if (c.titulo) linhas.push(` na tela: ${c.titulo}`);
280
+ if (c.aTela) linhas.push(` composição: ${c.aTela}`);
281
+ if (c.fundo) linhas.push(` fundo: ${typeof c.fundo === 'string' ? c.fundo : JSON.stringify(c.fundo)}`);
292
282
  linhas.push(` narração: ${c.narracao}`);
293
283
  if (c.camera && c.camera !== 'nenhuma') linhas.push(` câmera: ${c.camera}`);
294
284
  if (c.chaves) linhas.push(` movimento: ${c.chaves}`);
@@ -303,7 +293,7 @@ function pauta(plano) {
303
293
  }
304
294
 
305
295
  module.exports = {
306
- precisaDeProducao, motorPara, vozPara, ritmoPara,
296
+ precisaDeProducao, vozPara, ritmoPara,
307
297
  normalizarPlano, extrairJSON, planejar, pauta,
308
298
  DIRETOR, REVISAO,
309
299
  };