primocode 10.3.0 → 10.4.1

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 CHANGED
@@ -1,4 +1,4 @@
1
- # PrimoCode v10.3.0
1
+ # PrimoCode v10.4.1
2
2
 
3
3
  Agente de engenharia brasileiro para o terminal, no estilo Claude Code.
4
4
  Cria arquivos de verdade, roda comandos, **controla o navegador e o desktop** —
@@ -9,7 +9,7 @@ OpenRouter, e cada apelido (`top`/`main`/`fast`) é uma **corrente**: se um mode
9
9
  está no limite de pedidos, o próximo atende. Você não vê o erro, e não paga nada.
10
10
 
11
11
  ```
12
- ▐▛███▜▌ Primo Code v10.3.0
12
+ ▐▛███▜▌ Primo Code v10.4.1
13
13
  ▝▜█████▛▘ Bem-vindo de volta, Joel
14
14
  ▘▘ ▝▝ ~/primocode · /dir <pasta> muda
15
15
  ```
@@ -17,6 +17,33 @@ está no limite de pedidos, o próximo atende. Você não vê o erro, e não pag
17
17
  Personalize o mascote (`primo`, `fox`, `cat`) e o nome com que ele te chama —
18
18
  veja `/mascot` e `/name`.
19
19
 
20
+ ## O que mudou na 10.4
21
+
22
+ - **A tela do OpenCode.** O PrimoCode abre em tela cheia: a conversa rola em
23
+ cima, a caixa de digitar fica presa no pé, e a barra da direita mostra a
24
+ sessão, o contexto (tokens e % usado), o modo, a pasta e a versão. Sem
25
+ conversa, a home põe o logo e a caixa no meio. `Tab` troca o modo, `Ctrl+P`
26
+ abre os comandos (o menu abre em cima da caixa), a roda do mouse e o
27
+ `PgUp`/`PgDn` rolam a conversa. `PRIMOCODE_TELA=linha` volta ao jeito antigo.
28
+ - **Motion sem encostar na borda.** Margem de 6% nas laterais e 7% em cima e
29
+ embaixo, e 4% entre elementos — garantido em código antes do render.
30
+ - **Vídeo gravado em 3D (`estilo: personagem`).** Personagens modelados — gente
31
+ ou robô — conversando num set (podcast, estúdio, escritório). A boca segue a
32
+ voz letra por letra, eles piscam, gesticulam e viram a cabeça para quem fala.
33
+ A câmera corta como num programa gravado: aberto, ombro, close e detalhe,
34
+ com o balanço de câmera na mão. Por cima: legenda grossa palavra por
35
+ palavra, o nome de quem fala, o cartão de destaque, o REC e o grão de filme.
36
+ Peça "um podcast", "uma entrevista" ou "o Claude falando em 3D".
37
+ - **A peça do site, tirada pelo código.** `estudio_midia acao:elementos` lê o
38
+ site e lista logo, menu, título, botões, cartões, preço; `acao:elemento` tira
39
+ uma delas sozinha, em PNG transparente no dobro da resolução, com o código
40
+ (HTML, fonte, cor, raio) para redesenhar se quiser.
41
+ - **Recorte de fundo com IA sem instalar nada.** Com o `uv` na máquina, o
42
+ `acao:recortar` usa o rembg num ambiente à parte, em vez do corte por cor.
43
+ - **O logo do Claude saiu do cardápio dos vídeos.**
44
+ - **Com Claude, Codex ou chave própria, o agente não esquece o arquivo que
45
+ acabou de ler** (antes relia o mesmo arquivo várias vezes).
46
+
20
47
  ## O que mudou na 10.0
21
48
 
22
49
  - **A potência vem do sistema, não do modelo.** O mesmo pedido de landing
package/bin/primocode.js CHANGED
@@ -427,30 +427,21 @@ function ramoDoGit(dir) {
427
427
  return ramo;
428
428
  }
429
429
 
430
+ /* O que a caixa mostra além do texto, no desenho do OpenCode: DENTRO do
431
+ painel, o modo e o esforço ("Auto · alto" — o "Build · Nemotron" de lá);
432
+ FORA, uma linha só, com a pasta e o ramo à esquerda e o contexto e os
433
+ atalhos à direita. A cor da barra do painel é a do modo. */
434
+ const COR_MODO = { auto: '\x1b[38;5;75m', primocode: '\x1b[38;5;216m', chat: '\x1b[38;5;45m' };
435
+ const NOME_MODO = { auto: 'Auto', primocode: 'Primo Code', chat: 'Chat' };
436
+
430
437
  function barraDeStatus() {
431
438
  const partes = [];
432
439
 
433
- /* O CONTEXTO. Só aparece depois que há conversa: um "ctx 0%" na abertura
434
- é ruído, e ruído que aparece sempre é o que a pessoa aprende a não
435
- enxergar — inclusive quando marcar 90%. Âmbar a partir de 70% e
436
- vermelho a partir de 100%, que é quando `/compact` deixa de ser
437
- conselho e vira o que fazer. */
438
- const uso = memoria.usoDoContexto(state.memoria, state.history);
439
- if (uso.pct >= 5) {
440
- partes.push(uso.pct >= 100 ? c.err(`${uso.pct}% ctx`)
441
- : uso.pct >= 70 ? c.warn(`${uso.pct}% ctx`)
442
- : c.dim(`${uso.pct}% ctx`));
443
- }
444
-
445
- // O esforço, que é o que escolhe o modelo. Não há provedor para mostrar:
446
- // a API vem com o produto.
447
- partes.push(c.muted(effort.normalizar(state.config.effort)));
448
-
449
- // A PASTA. O nome dela, e o caminho curto quando não é um projeto — quem
450
- // apontou com /dir para fora do ~/primocode precisa ver onde está.
440
+ // A PASTA, e o ramo do git colado nela ("~/primocode/loja:main"), como
441
+ // o rodapé do OpenCode. Quem apontou com /dir para fora do ~/primocode
442
+ // precisa ver onde está.
451
443
  const dir = state.workDir || process.cwd();
452
444
  partes.push(c.brandDim(shortPath(dir)));
453
-
454
445
  const ramo = ramoDoGit(dir);
455
446
  if (ramo) partes.push(c.dim('⎇ ' + ramo));
456
447
 
@@ -463,25 +454,73 @@ function barraDeStatus() {
463
454
  esquece que deu — e ela vale até o PrimoCode fechar. */
464
455
  if (state.controle && state.controle.liberado) partes.push(c.warn('controle do pc'));
465
456
 
466
- // No auto o rótulo mostra "Auto" e, depois do primeiro pedido, qual modo
467
- // ele acabou usando — senão você nunca sabe quem respondeu.
468
- let modo;
469
- if (state.mode === 'auto') {
470
- const ultimo = state.modoEfetivo === 'primocode' ? c.warn('primo')
471
- : state.modoEfetivo === 'chat' ? c.brandDim('chat') : '';
472
- modo = c.brand('auto') + (ultimo ? c.dim('·') + ultimo : '');
473
- } else {
474
- modo = state.mode === 'primocode' ? c.warn('primo code') : c.brandDim('chat');
457
+ /* O CONTEXTO. Só aparece depois que há conversa: um "ctx 0%" na abertura
458
+ é ruído, e ruído que aparece sempre é o que a pessoa aprende a não
459
+ enxergar — inclusive quando marcar 90%. Âmbar a partir de 70% e
460
+ vermelho a partir de 100%, que é quando `/compact` deixa de ser
461
+ conselho e vira o que fazer. */
462
+ const direita = [];
463
+ const uso = memoria.usoDoContexto(state.memoria, state.history);
464
+ if (uso.pct >= 5) {
465
+ direita.push(uso.pct >= 100 ? c.err(`${uso.pct}% ctx`)
466
+ : uso.pct >= 70 ? c.warn(`${uso.pct}% ctx`)
467
+ : c.dim(`${uso.pct}% ctx`));
468
+ }
469
+ direita.push(state.trabalhando
470
+ ? c.warn('trabalhando') + c.dim(' · ') + c.white('esc esc') + c.dim(' para')
471
+ : c.white('tab') + c.dim(' modos ') + c.white('ctrl+p') + c.dim(' comandos'));
472
+
473
+ /* O modo DENTRO do painel. No auto, depois do primeiro pedido, ele diz
474
+ qual modo acabou respondendo — senão você nunca sabe quem respondeu. */
475
+ const cor = COR_MODO[state.mode] || COR_MODO.auto;
476
+ let modo = `${cor}${NOME_MODO[state.mode] || 'Auto'}${c.reset}`;
477
+ if (state.mode === 'auto' && state.modoEfetivo) {
478
+ modo += c.dim(' → ') + c.muted(NOME_MODO[state.modoEfetivo] || state.modoEfetivo);
475
479
  }
476
- partes.push(modo);
480
+ modo += c.dim(' · ') + c.white(effort.normalizar(state.config.effort)) + ' ' + c.dim('PrimoCode');
477
481
 
478
- const primeira = partes.join(c.dim(' | '));
482
+ return { modo, cor, esquerda: partes.join(c.dim(' · ')), direita: direita.join(c.dim(' ')) };
483
+ }
479
484
 
480
- const atalhos = state.trabalhando
481
- ? c.warn('▸▸ trabalhando') + c.dim(' (Esc Esc interrompe)')
482
- : c.dim('/ comandos · ↑ histórico · \\ ou alt+enter quebra linha · Esc Esc interrompe');
485
+ /* A barra da direita da tela cheia, como a do OpenCode: o contexto (quantos
486
+ tokens a conversa já ocupa e quanto isso é do teto), o modo e o esforço, e
487
+ no pé a pasta com o ramo e a versão. */
488
+ function barraLateral() {
489
+ const uso = memoria.usoDoContexto(state.memoria, state.history);
490
+ const dir = state.workDir || process.cwd();
491
+ const ramo = ramoDoGit(dir);
492
+ const m = state.mode === 'auto' && state.modoEfetivo ? state.modoEfetivo : state.mode;
493
+ const secoes = [
494
+ { titulo: 'Contexto', linhas: [
495
+ c.muted(`${uso.tokens.toLocaleString('pt-BR')} tokens`),
496
+ (uso.pct >= 100 ? c.err : uso.pct >= 70 ? c.warn : c.muted)(`${uso.pct}% usado`),
497
+ ] },
498
+ { titulo: 'Modo', linhas: [
499
+ `${COR_MODO[m] || COR_MODO.auto}${NOME_MODO[m] || m}${c.reset}` + c.muted(` · esforço ${effort.normalizar(state.config.effort)}`),
500
+ ] },
501
+ ];
502
+ if (state.attachedFiles.length) {
503
+ secoes.push({ titulo: 'Anexos', linhas: state.attachedFiles.map((f) => c.muted(path.basename(f.path))) });
504
+ }
505
+ if (state.controle && state.controle.liberado) secoes.push({ titulo: 'Controle do PC', linhas: [c.warn('liberado até fechar')] });
506
+ return {
507
+ secoes,
508
+ rodape: [
509
+ c.muted(shortPath(dir)) + (ramo ? c.white(':' + ramo) : ''),
510
+ c.brand('●') + ' ' + c.bold(c.white('Primo')) + c.white('Code') + ' ' + c.dim(VERSION),
511
+ ],
512
+ };
513
+ }
483
514
 
484
- return [primeira, atalhos];
515
+ /* O Tab com a caixa vazia gira o modo, como o "tab agents" do OpenCode:
516
+ auto → primo code → chat → auto. A escolha fica salva, como a do /mode. */
517
+ function trocarModo() {
518
+ const ordem = ['auto', 'primocode', 'chat'];
519
+ const m = ordem[(ordem.indexOf(state.mode) + 1) % ordem.length];
520
+ state.mode = m;
521
+ state.modoEfetivo = null;
522
+ cfg.saveConfig({ mode: m });
523
+ refreshPrompt();
485
524
  }
486
525
 
487
526
  // A barra se redesenha sozinha a cada quadro; isto só pede um quadro novo.
@@ -1043,10 +1082,18 @@ async function sendPrompt(userText) {
1043
1082
 
1044
1083
  // PRIMOCODE_TEMPOS=1: uma linha no fim com o custo de cada fase.
1045
1084
  tempos.comecar();
1085
+ const inicio = Date.now();
1046
1086
  try { await enviarPedido(userText); }
1047
1087
  finally {
1048
1088
  const linha = tempos.relatorio();
1049
1089
  if (linha) console.log(c.dim(` ⏱ ${linha}`));
1090
+ /* O fecho de cada resposta, como no OpenCode ("▣ Build · Nemotron ·
1091
+ 4.0s"): quem respondeu, com que esforço, e quanto levou. */
1092
+ const m = state.mode === 'auto' ? (state.modoEfetivo || 'auto') : state.mode;
1093
+ if (!state.semTerminal) {
1094
+ console.log('\n ' + `${COR_MODO[m] || COR_MODO.auto}▣${c.reset} ` + c.white(NOME_MODO[m] || m)
1095
+ + c.dim(` · ${effort.normalizar(state.config.effort)} · ${((Date.now() - inicio) / 1000).toFixed(1)}s`) + '\n');
1096
+ }
1050
1097
  }
1051
1098
  }
1052
1099
 
@@ -2690,17 +2737,39 @@ async function main() {
2690
2737
  process.exit(0);
2691
2738
  }
2692
2739
 
2693
- console.log(welcomeBox(VERSION, state.workDir ? shortPath(state.workDir) : null, {
2694
- modo: state.mode,
2695
- mascot: state.config.mascot,
2696
- userName: state.config.userName,
2697
- pastaPadrao: state.workDirOrigem === 'padrao',
2698
- }));
2740
+ /* A TELA CHEIA do OpenCode (lib/tela.js) é o padrão: a conversa rola em
2741
+ cima, a caixa fica presa no pé, a barra da direita mostra a sessão e o
2742
+ contexto, e sem conversa a home põe o logo e a caixa no meio da tela.
2743
+ PRIMOCODE_TELA=linha (ou "tela": "linha" no config) volta ao jeito
2744
+ antigo, que escreve em linha pelo terminal. */
2745
+ const telaCheia = process.stdout.isTTY && process.stdout.rows >= 16
2746
+ && (process.env.PRIMOCODE_TELA || state.config.tela || 'cheia') !== 'linha';
2747
+ if (!telaCheia) {
2748
+ if (process.stdout.isTTY && process.stdout.rows >= 20) {
2749
+ const rows = process.stdout.rows;
2750
+ const alto = 8 + 6; // o logo com a versão + a caixa
2751
+ process.stdout.write('\n'.repeat(rows) + '\x1b[H' + '\n'.repeat(Math.max(0, Math.floor((rows - alto) / 2) - 1)));
2752
+ }
2753
+ console.log(welcomeBox(VERSION, state.workDir ? shortPath(state.workDir) : null, {
2754
+ pastaPadrao: state.workDirOrigem === 'padrao',
2755
+ }));
2756
+ }
2699
2757
  rl = entrada.criar({
2700
2758
  status: barraDeStatus,
2701
2759
  comandos: comandosDoMenu,
2702
- vazio: 'peça alguma coisa, ou / para os comandos',
2760
+ vazio: ui.convite(),
2761
+ telaCheia,
2762
+ lateral: barraLateral,
2763
+ versao: VERSION,
2764
+ pasta: () => {
2765
+ const dir = state.workDir || process.cwd();
2766
+ const ramo = ramoDoGit(dir);
2767
+ return shortPath(dir) + (ramo ? ':' + ramo : '');
2768
+ },
2703
2769
  });
2770
+ if (telaCheia && state.workDirOrigem === 'padrao') {
2771
+ console.log(c.warn(` pasta padrão: ${shortPath(state.workDir)}`) + c.dim(' · /dir <pasta> para trabalhar numa sua'));
2772
+ }
2704
2773
  // O spinner desenha dentro da caixa (lib/entrada.js mostrarSpinner).
2705
2774
  if (rl.tty) Spinner.caixa = rl;
2706
2775
  ui.titulo('PrimoCode');
@@ -2739,6 +2808,7 @@ async function main() {
2739
2808
  // Ctrl+C com a caixa vazia: sair é sair, mas sem deixar o último turno
2740
2809
  // pela metade — o sayBye espera a gravação subir.
2741
2810
  rl.on('sigint', () => rl.close());
2811
+ rl.on('trocar-modo', trocarModo);
2742
2812
 
2743
2813
  function sayBye() {
2744
2814
  if (saidBye) return;
package/lib/act.js CHANGED
@@ -333,6 +333,16 @@ function summarizeForModel(result) {
333
333
  // que o modelo precisa para decidir o próximo passo; os velhos viram peso.
334
334
  const RESULTADOS_INTEIROS = 3;
335
335
  const RESTO_DO_RESULTADO = 300;
336
+ /* ── COM MOTOR PRÓPRIO, A MEMÓRIA NÃO É AMPUTADA ────────────────────────
337
+ * Os 3 e os 300 de cima são para o teto de ~6 mil tokens por minuto do
338
+ * servidor grátis. Com Claude Code, Codex ou chave própria (`provedor.atual()`)
339
+ * eles valiam igual, e o modelo perdia o arquivo que tinha acabado de ler:
340
+ * no jogo de carro de 09/10 o Claude leu o world.js (458 linhas) sete vezes,
341
+ * em fatias, porque a cada três ferramentas a leitura anterior virava 300
342
+ * caracteres. A pre-leitura já folgava com motor próprio; o compactar agora
343
+ * também. */
344
+ const RESULTADOS_INTEIROS_FOLGADO = 20;
345
+ const RESTO_DO_RESULTADO_FOLGADO = 1500;
336
346
  // A pesquisa é MATÉRIA-PRIMA, não passo intermediário: é dela que saem os
337
347
  // fatos do vídeo, do post, do slide. Ela sempre fica velha (pesquisa-se no
338
348
  // começo, escreve-se no fim), e a 300 caracteres não sobrava fato nenhum — o
@@ -404,15 +414,17 @@ function encolherEscritas(m) {
404
414
  * Nenhuma mensagem é REMOVIDA: a API exige que cada role:'tool' continue
405
415
  * pareada com o tool_call que a pediu. Só o texto encolhe.
406
416
  */
407
- function compactar(messages) {
417
+ function compactar(messages, { folgado = false } = {}) {
408
418
  const { inteiras, duplicadas } = repeticao.referenciasNaConversa(messages);
419
+ const quantosInteiros = folgado ? RESULTADOS_INTEIROS_FOLGADO : RESULTADOS_INTEIROS;
420
+ const resto = folgado ? RESTO_DO_RESULTADO_FOLGADO : RESTO_DO_RESULTADO;
409
421
 
410
422
  const indices = [];
411
423
  for (let i = 0; i < messages.length; i++) if (messages[i].role === 'tool') indices.push(i);
412
- if (indices.length <= RESULTADOS_INTEIROS && !duplicadas.size && !assistentesAntigas(messages).size) return messages;
424
+ if (indices.length <= quantosInteiros && !duplicadas.size && !assistentesAntigas(messages).size) return messages;
413
425
 
414
426
  const chamadas = repeticao.chamadasPorId(messages);
415
- const encolher = new Set(indices.slice(0, -RESULTADOS_INTEIROS));
427
+ const encolher = new Set(indices.slice(0, -quantosInteiros));
416
428
  const antigas = assistentesAntigas(messages);
417
429
  return messages.map((m, i) => {
418
430
  if (antigas.has(i)) return encolherEscritas(m);
@@ -421,7 +433,7 @@ function compactar(messages) {
421
433
  }
422
434
  if (inteiras.has(i) || !encolher.has(i)) return m;
423
435
  const chamada = chamadas.get(m.tool_call_id);
424
- const limite = chamada && MATERIA_PRIMA.has(chamada.nome) ? RESTO_DA_PESQUISA : RESTO_DO_RESULTADO;
436
+ const limite = chamada && MATERIA_PRIMA.has(chamada.nome) ? Math.max(RESTO_DA_PESQUISA, resto) : resto;
425
437
  const texto = String(m.content || '');
426
438
  if (texto.length <= limite) return m;
427
439
  return { ...m, content: texto.slice(0, limite) + `…(resultado antigo encurtado; eram ${texto.length} caracteres)` };
@@ -984,7 +996,9 @@ async function runAct(userPrompt, opts) {
984
996
  emVoo = new AbortController();
985
997
  const tRodada = Date.now();
986
998
  try {
987
- const enviadas = compactar(messages);
999
+ let folgado = false;
1000
+ try { folgado = !!require('./local/provedor.js').atual(); } catch { /* sem provedor próprio */ }
1001
+ const enviadas = compactar(messages, { folgado });
988
1002
  traco(`volta ${voltas}${subagent ? ' (agente)' : ''} · ${enviadas.length} mensagens · ${JSON.stringify(enviadas).length} caracteres`);
989
1003
  await motor.requestAct(server, { messages: enviadas, model, effort, token, projectDir: pastaAtual, projectName, subagent, sinal: emVoo.signal }, (evt) => {
990
1004
  if (evt.erro) { streamError = evt.erro; return; }
package/lib/catalogo.js CHANGED
@@ -94,8 +94,8 @@ const GRUPOS = [
94
94
  + 'logo{titulo} cores{cores:[{nome,hex}]} produto{titulo,texto,destaque,selo,barras}. Em qualquer parte: destaque (a palavra-tese), fonte (a fonte citada). O texto da TELA sai da FALA: o título usa palavras que a fala diz, cedo na frase (a tela entra quando a voz diz; palavra que a voz não diz deixa a tela esperando). '
95
95
  + 'visual:{titulo,texto,paleta} (hibrido, cinema, impacto): fonte e cor do cardápio (estudio_midia acao:fontes); sem visual, o PrimoCode sorteia sem repetir os últimos vídeos. '
96
96
  + 'estilo (SEM IMAGEM é o padrão: escolha o motion que serve a ESTE roteiro, ou deixe vazio e ele gira entre gerativo, fluido e impacto): hibrido (SÓ quando a pessoa pediu imagem: foto e vídeo reais + o motion por cima; dê busca:"termo em inglês" só nas partes em que a imagem agrega) | gerativo (= motion: sem template — a tipografia anima nos eixos da fonte numa linha do tempo contínua, composição, fonte, cor e som sorteados do roteiro; modelo3d fone|celular|notebook|relogio|garrafa|lata|caixa: o objeto gira em 3D numa batida — ponha quando o vídeo é de um produto ou objeto físico; ideia abstrata, história ou explicação: deixe vazio; o vídeo tem UMA identidade (2 chãos, 1 acento, 2 fontes) e o fundo só troca na virada de seção — se o roteiro tem atos, dê secao:"nome curto" nas partes, senão o PrimoCode divide; moldura cantos|completa|false: o quadro fixo do reel (cantos, o assunto no alto, "01 · seção", o tempo) — ponha só se o vídeo pede cara de peça de design; vazio, o PrimoCode alterna com memória e às vezes não põe; heroi em cada parte: o desenho da batida além do texto, tirado do CONTEÚDO — um ícone do Iconify do objeto que a fala cita ("mdi:bread-slice", "mdi:qrcode"; procure com estudio_midia acao:icones), um 3D do kit (raio|relogio|check|balao|escudo|coracao|estrela|sol|play|engrenagem|cadeado|barras|fone|onda|celular|seta|bateria) ou "nenhum" quando a tipografia basta; forma abstrata (explosao|poligono|esfera|toro|truchet|aneis|grade|rastro) só em assunto abstrato ou de tecnologia — numa conversa de padaria, nunca; ambiente limpo|luz|bokeh e acento traco|viagem (o ponto que viaja entre as batidas): vazio, o PrimoCode escolhe com memória e o bokeh quase não sai fora de tecnologia) | classico (o motion 3D antigo) | vox (explainer editorial: tinta sobre papel, câmera que conta a frase) | '
97
- + 'marca (brand reveal: brilho, letras em vetor, marca cromada, padrão) | lancamento (produto: cartões, painel, calendário) | fluido (minimalista: uma ideia por batida, a forma de uma vira a próxima, sem corte; paleta noite|breu|preto|cobalto (escuras) ou lavanda|creme|laranja|branco (claras) — escolha pelo assunto e NÃO repita a do vídeo anterior; vazio, o PrimoCode escolhe; fontesFluido aprovado|editorial|revista|carater|tecnico|classico, vazio gira) | impacto | cinema. '
98
- + 'marca e lancamento aceitam visual:orbe|vetor|estrela|esfera|grade|site|padrao|cromo|cartoes|painel|cores numa parte. marca.nome dá o nome. '
97
+ + 'marca (brand reveal: brilho, letras em vetor, marca cromada, padrão) | lancamento (produto: cartões, painel, calendário) | fluido (minimalista: uma ideia por batida, a forma de uma vira a próxima, sem corte; paleta noite|breu|preto|cobalto (escuras) ou lavanda|creme|laranja|branco (claras) — escolha pelo assunto e NÃO repita a do vídeo anterior; vazio, o PrimoCode escolhe; fontesFluido aprovado|editorial|revista|carater|tecnico|classico, vazio gira) | impacto | cinema | personagem (o vídeo GRAVADO em 3D — cada parte é UMA fala {falante, texto, emocao?, destaque?}, sem tipo: personagens modelados — gente ou robô — conversando num set de podcast/estúdio/escritório, a boca segue a voz, gestos, e a câmera corta entre aberto, ombro e close; use quando o pedido fala em personagem, boneco, avatar, podcast, entrevista, ou alguém FALANDO em 3D. Com dois falantes (falante em cada parte) eles conversam; personagens:{"nome":{tipo:robo|pessoa, pele, cabelo:curto|cacheado|longo|topete|coque|careca, corCabelo, roupa, oculos, barba, rotulo}} — nome de IA vira robô sozinho; emocao feliz|serio|surpreso|pensativo|animado|curioso e destaque:"texto curto" (o cartão na tela) em cada parte; cenario podcast|estudio|escritorio|noite). '
98
+ + 'marca e lancamento aceitam visual:orbe|vetor|esfera|grade|site|padrao|cromo|cartoes|painel|cores numa parte. marca.nome dá o nome. '
99
99
  + 'voz (thalita|antonio|francisca). DUAS VOZES (o pedido fala em dois narradores, diálogo, conversa, entrevista): roteiro.vozes {"nome":"antonio","outro":"thalita"} e falante:"nome" em cada parte — cada um sai na sua voz. Ajuste fino: plano: {formato:{largura,altura,fps}, cenas:[…], audio:{…}} — ou cenas direto. '
100
100
  + 'cena (opcional): o número de UMA cena para trocar só ela, sem reescrever o resto. '
101
101
  + 'motivo (opcional): fica no caderno. voltarPara (opcional): nome de uma versão. '
@@ -104,13 +104,19 @@ const GRUPOS = [
104
104
  + '"anima": {propriedade: [{t,v,e}]} — quadros-chave em segundos. A cena tem "camera": {zoom,x,y,rotacao} e '
105
105
  + '"perspectiva" para 3D. Cena.transicao {tipo,duracao}: corte|esmaecer|deslizar|zoom|giro|mascara|morph|chicote|soco|flash|liquido, '
106
106
  + 'e as 3D virar|cubo|profundidade|inclinar (mexem nas duas cenas) — use 3D se o vídeo pede volume (produto, site, tecnologia); em editorial plano, prefira as planas. '
107
- + 'NÃO existe layout pronto nem tema — cada propriedade do vídeo é escolha sua, a cada vídeo.' },
107
+ + 'NÃO existe layout pronto nem tema — cada propriedade do vídeo é escolha sua, a cada vídeo. '
108
+ + 'ÁREA SEGURA: nada a menos de 6% da largura e 7% da altura da borda, e 4% do lado curto entre dois elementos no ar juntos — '
109
+ + 'o render empurra para dentro, encolhe o texto largo e afasta o colado; enfeite de fundo marque decorativo: true.' },
108
110
 
109
111
  { tool: 'estudio_midia', escreve: true, faz: 'procura e baixa ícone, imagem, música e efeito sonoro da internet, e lista as fontes',
110
- args: 'acao: imagem | icones | som | trilha | efeito | fontes | print. termo. id (para baixar). '
112
+ args: 'acao: imagem | icones | som | trilha | efeito | fontes | print | elementos | elemento | recortar. termo. id (para baixar). '
111
113
  + 'print: url — tira o print REAL do site e devolve o src e um elemento {tipo:"kit", peca:"printsite"} pronto '
112
114
  + '(o site de verdade numa janela 3D que rola a página; celular: true para a tela do celular; inteira: false só a primeira dobra). '
113
115
  + 'Use SE o pedido trouxer um site e o vídeo for SOBRE ele (lançamento, tour, case); vídeo que só cita a marca não precisa. '
116
+ + 'elementos: url — lê o CÓDIGO do site e lista as peças que dá para tirar (logo, menu, titulo, hero, botao, cartao, imagem, formulario, preco, rodape), cada uma com o seletor. '
117
+ + 'elemento: url + seletor (ou texto: o que está escrito nele) — tira SÓ aquela peça, em PNG com fundo transparente e no dobro da resolução, e devolve o código dela (html e estilo: fonte, cor, raio, sombra) e um elemento {tipo:"imagem"} pronto; '
118
+ + 'use para pôr o botão, o cartão de preço ou a logo do site animando no vídeo (ou redesenhe a peça como componente a partir do código). '
119
+ + 'recortar: arquivo — tira o fundo de qualquer foto com IA (rembg), PNG transparente. '
114
120
  + 'efeitos: lista os componentes guardados (os menos usados primeiro). usar-efeito {nome}: copia para o projeto e devolve o elemento — use se um deles servir a ESTE roteiro. '
115
121
  + 'guardar-efeito {arquivo, nome, descricao}: guarda um componente que você escreveu e ficou bom, para os próximos vídeos '
116
122
  + '(o componente: export default de uma função React que recebe {el, t, fps}, num arquivo só, importando só react, remotion, @remotion/*, three). '