primocode 10.10.0 → 10.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/lib/catalogo.js +7 -3
- package/lib/comandos-extra.js +2 -1
- package/lib/entrada.js +18 -0
- package/lib/estudio/cena.js +47 -0
- package/lib/estudio/efeitos-fabrica/brilho-varredura.jsx +25 -0
- package/lib/estudio/efeitos-fabrica/cor-inunda.jsx +30 -0
- package/lib/estudio/efeitos-fabrica/mola-com-peso.jsx +28 -0
- package/lib/estudio/efeitos-fabrica/peca-impacto-3d.jsx +37 -0
- package/lib/estudio/efeitos-fabrica/raios-de-luz.jsx +29 -0
- package/lib/estudio/efeitos-fabrica/risco-multilinha.jsx +30 -0
- package/lib/estudio/efeitos.js +21 -4
- package/lib/estudio/estilos.js +147 -0
- package/lib/estudio/gerativo.js +19 -3
- package/lib/estudio/index.js +88 -14
- package/lib/estudio/kit/gerativo.jsx +16 -0
- package/lib/estudio/kit/mundo.jsx +11 -5
- package/lib/estudio/kit/natural.js +161 -0
- package/lib/estudio/kit/personagem.jsx +36 -20
- package/lib/estudio/kit/voxel.jsx +129 -64
- package/lib/estudio/kit.js +1 -1
- package/lib/estudio/sem-narracao.js +140 -0
- package/lib/tools.js +11 -0
- package/package.json +2 -2
- package/skills/motion-de-primeira/SKILL.md +39 -36
|
@@ -31,6 +31,8 @@ import { BokehPass } from 'three/examples/jsm/postprocessing/BokehPass.js';
|
|
|
31
31
|
import { OutputPass } from 'three/examples/jsm/postprocessing/OutputPass.js';
|
|
32
32
|
import { RoomEnvironment } from 'three/examples/jsm/environments/RoomEnvironment.js';
|
|
33
33
|
import { Mundo, Animal, Coisa, alturaDoBicho, frenteDoBicho, useIcones } from './mundo.jsx';
|
|
34
|
+
// O movimento com peso (mola, antecipação, sobreposição, respiração, salto, passo): kit/natural.js.
|
|
35
|
+
import { ruido, envelope, golpe, respiracao, salto, passo } from './natural';
|
|
34
36
|
|
|
35
37
|
const clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x));
|
|
36
38
|
const lerp = (a, b, k) => a + (b - a) * k;
|
|
@@ -144,10 +146,10 @@ function gesto(falas, quem, t) {
|
|
|
144
146
|
if (f.quem !== quem || t < f.de - 0.5 || t > f.ate + 1) continue;
|
|
145
147
|
f.palavras.forEach((w, k) => {
|
|
146
148
|
if (String(w.t).replace(/[^\p{L}\d]/gu, '').length < 6 && !/\d/.test(w.t)) return;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (k % 2) esq = Math.
|
|
149
|
+
// O braço recua, sobe passando do ponto e desce devagar (natural.golpe), não um seno.
|
|
150
|
+
const b = golpe(t - (w.de - 0.15));
|
|
151
|
+
if (b === 0) return;
|
|
152
|
+
if (k % 2) esq = Math.abs(b) > Math.abs(esq) ? b : esq; else dir = Math.abs(b) > Math.abs(dir) ? b : dir;
|
|
151
153
|
});
|
|
152
154
|
}
|
|
153
155
|
return [esq, dir];
|
|
@@ -372,26 +374,36 @@ function CabecaFacetada({ p, est }) {
|
|
|
372
374
|
/* A AÇÃO da fala mexe o braço por cima do gesto: acenar (a mão direita no
|
|
373
375
|
alto, abanando), apontar (o braço esticado para a frente), comemorar (os
|
|
374
376
|
dois para cima), pensar (a mão no queixo), dançar (os dois balançando). */
|
|
375
|
-
function poseDoBraco(acao, lado, t, k) {
|
|
377
|
+
function poseDoBraco(acao, lado, t, k, sem = 0) {
|
|
376
378
|
if (!acao || k <= 0) return null;
|
|
377
379
|
const dir = lado > 0;
|
|
378
|
-
|
|
379
|
-
if (acao === '
|
|
380
|
-
if (acao === '
|
|
381
|
-
if (acao === '
|
|
382
|
-
if (acao === '
|
|
383
|
-
if (acao === '
|
|
384
|
-
|
|
380
|
+
// O antebraço vem ATRASADO do braço (sobreposição): a mesma onda, a fase depois.
|
|
381
|
+
if (acao === 'acenar' && dir) return { x: -0.25, z: 2.55 + Math.sin(t * 9) * 0.22, c: -0.55 + Math.sin(t * 9 - 1.1) * 0.4 };
|
|
382
|
+
if (acao === 'apontar' && dir) return { x: -1.45 + ruido(t * 0.8, sem + 3) * 0.04, z: 0.12, c: -0.05 };
|
|
383
|
+
if (acao === 'comemorar') return { x: -0.2, z: lado * (2.7 + Math.sin(t * 8 + lado) * 0.15), c: -0.25 + Math.sin(t * 8 + lado - 1) * 0.2 };
|
|
384
|
+
if (acao === 'pensar' && dir) return { x: -1.5 + ruido(t * 0.5, sem + 4) * 0.05, z: -0.35, c: -2.1 };
|
|
385
|
+
if (acao === 'dancar') return { x: -0.6 + Math.sin(t * 6 + lado) * 0.5, z: lado * (0.6 + Math.sin(t * 6 - 0.5) * 0.3), c: -1.1 + Math.sin(t * 6 + lado - 0.9) * 0.3 };
|
|
386
|
+
// Andar e correr: o braço oposto à perna, atrasado dela (natural.passo).
|
|
387
|
+
if (acao === 'correr' || acao === 'andar') {
|
|
388
|
+
const ps = passo(t, { correr: acao === 'correr', semente: sem });
|
|
389
|
+
const b = ps.braco[dir ? 1 : 0];
|
|
390
|
+
return acao === 'correr' ? { x: b * 1.2, z: lado * 0.1, c: -1.3 + b * 0.25 } : { x: b, z: lado * 0.06, c: -0.25 + Math.min(0, b) * 0.5 };
|
|
391
|
+
}
|
|
385
392
|
return null;
|
|
386
393
|
}
|
|
387
394
|
|
|
388
|
-
function BracoBloco({ lado, g, cor, pele, t, acao, k = 1 }) {
|
|
389
|
-
// Repouso: o braço caído, um tico para fora
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
+
function BracoBloco({ lado, g, gc = g, cor, pele, t, acao, k = 1, kc = k, ar = 0, sem = 0 }) {
|
|
396
|
+
// Repouso: o braço caído, um tico para fora — e nunca parado: ele acompanha o
|
|
397
|
+
// fôlego (abre na inspiração) e deriva pelo ruído. O cotovelo vem depois do
|
|
398
|
+
// ombro (gc e kc são o gesto e a ação de ~0,1 s antes): a sobreposição.
|
|
399
|
+
const deriva = ruido(t * 0.45, sem + lado * 5) * 0.035;
|
|
400
|
+
let ombroX = lerp(0.08, -0.85, g) + Math.sin(t * 6 + lado) * 0.06 * Math.max(0, g) + deriva;
|
|
401
|
+
let ombroZ = lado * (lerp(0.06, 0.22, Math.max(0, g)) + ar * 0.025);
|
|
402
|
+
let cotovelo = lerp(-0.12, -1.05, gc) + Math.sin(t * 7.3 + lado * 2 - 0.8) * 0.14 * Math.max(0, gc) + deriva * 0.6;
|
|
403
|
+
const pose = poseDoBraco(acao, lado, t, k, sem);
|
|
404
|
+
const poseC = poseDoBraco(acao, lado, t - 0.09, kc, sem);
|
|
405
|
+
if (pose) { ombroX = lerp(ombroX, pose.x, k); ombroZ = lerp(ombroZ, pose.z, k); }
|
|
406
|
+
if (poseC) cotovelo = lerp(cotovelo, poseC.c, kc);
|
|
395
407
|
return (
|
|
396
408
|
<group position={[lado * 0.47, 1.4, 0]} rotation={[ombroX, 0, ombroZ]}>
|
|
397
409
|
<Bloco s={[0.2, 0.36, 0.2]} p={[0, -0.17, 0]} c={cor} r={0.04} rough={0.8} />
|
|
@@ -404,42 +416,65 @@ function BracoBloco({ lado, g, cor, pele, t, acao, k = 1 }) {
|
|
|
404
416
|
}
|
|
405
417
|
|
|
406
418
|
function PessoaBloco({ p, est }) {
|
|
407
|
-
const
|
|
419
|
+
const t = est.t, sem = est.semente || 0;
|
|
420
|
+
// O fôlego: inspira rápido, expira devagar, o ritmo varia (natural.respiracao).
|
|
421
|
+
const ar = respiracao(t, sem);
|
|
422
|
+
const resp = ar * 0.012;
|
|
408
423
|
const [cy, cp, cr] = est.cabeca;
|
|
409
424
|
const calca = escurecer(p.roupa, 0.35);
|
|
410
425
|
const a = est.acao, ka = est.forcaAcao || 0;
|
|
411
|
-
// O corpo inteiro
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
const
|
|
426
|
+
// O corpo inteiro. Pular e comemorar: agacha, estica, cai acelerando e amassa
|
|
427
|
+
// no pouso (natural.salto). Dançar: o quadril leva, o tronco e a cabeça vêm
|
|
428
|
+
// atrasados. Andar e correr: o passo com quadril, contato e contragiro.
|
|
429
|
+
const pula = a === 'pular' || a === 'comemorar';
|
|
430
|
+
const sl = pula ? salto(t, a === 'comemorar' ? { periodo: 0.8, altura: 0.22 } : { periodo: 0.95, altura: 0.36 }) : null;
|
|
431
|
+
const anda = a === 'andar' || a === 'correr';
|
|
432
|
+
const ps = anda ? passo(t, { correr: a === 'correr', semente: sem }) : null;
|
|
433
|
+
const danca = a === 'dancar';
|
|
434
|
+
// Parado, o peso troca de perna devagar: nunca congelado.
|
|
435
|
+
const peso = ruido(t * 0.22, sem + 11) * 0.018;
|
|
436
|
+
const y = (sl ? sl.y * ka : 0) + (ps ? ps.sobe * ka : 0) + (danca ? (0.5 + 0.5 * Math.cos(t * 12)) * 0.04 * ka : 0);
|
|
437
|
+
const sy = sl ? lerp(1, sl.sy, Math.min(1, ka)) : 1, sx = sl ? lerp(1, sl.sx, Math.min(1, ka)) : 1;
|
|
438
|
+
const quadril = peso + (ps ? ps.quadril * ka : 0) + (danca ? Math.sin(t * 6) * 0.14 * ka : 0);
|
|
439
|
+
// O tronco gira contra o quadril (andar) ou vem atrasado dele (dançar).
|
|
440
|
+
const troncoZ = danca ? -Math.sin(t * 6 - 0.6) * 0.07 * ka : -peso * 0.5;
|
|
441
|
+
const troncoY = ps ? ps.giro * ka : 0;
|
|
442
|
+
const troncoX = (ps ? ps.inclina * ka : 0) + ar * -0.006;
|
|
443
|
+
// A cabeça segura o olhar: desfaz parte do giro do tronco, e balança por último.
|
|
444
|
+
const cabecaZ = cr + (danca ? Math.sin(t * 6 - 1.2) * 0.08 * ka : 0) - quadril * 0.4;
|
|
415
445
|
return (
|
|
416
|
-
<group position={[0,
|
|
446
|
+
<group position={[0, y, 0]} rotation={[0, 0, quadril]} scale={[sx, sy, sx]}>
|
|
417
447
|
{/* as pernas e o sapato */}
|
|
418
448
|
{[-1, 1].map((k) => {
|
|
419
|
-
const
|
|
449
|
+
const perna = ps ? ps.perna[k > 0 ? 1 : 0] * ka : 0;
|
|
420
450
|
return (
|
|
421
|
-
<group key={k} position={[k * 0.15, 0.76, 0]} rotation={[
|
|
451
|
+
<group key={k} position={[k * 0.15, 0.76, 0]} rotation={[perna, 0, -quadril * 0.8]}>
|
|
422
452
|
<Bloco s={[0.25, 0.72, 0.27]} p={[0, -0.36, 0]} c={calca} r={0.03} rough={0.85} />
|
|
423
453
|
<Bloco s={[0.27, 0.1, 0.36]} p={[0, -0.71, 0.04]} c="#1a1a1f" r={0.03} />
|
|
424
454
|
</group>
|
|
425
455
|
);
|
|
426
456
|
})}
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
<
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
457
|
+
{/* do quadril para cima: o tronco, os braços e a cabeça giram juntos no quadril */}
|
|
458
|
+
<group position={[0, 0.76, 0]} rotation={[troncoX, troncoY, troncoZ]}>
|
|
459
|
+
<group position={[0, -0.76, 0]}>
|
|
460
|
+
<group position={[0, resp, 0]} scale={[1 + ar * 0.008, 1 + ar * 0.014, 1 + ar * 0.02]}>
|
|
461
|
+
{/* o tronco, com a faixa branca da camisa da referência */}
|
|
462
|
+
<Bloco s={[0.76, 0.66, 0.4]} p={[0, 1.1, 0]} c={p.roupa} r={0.05} rough={0.8} />
|
|
463
|
+
<Bloco s={[0.06, 0.5, 0.02]} p={[0.17, 1.12, 0.205]} c="#dcd8d0" r={0.008} sombra={false} />
|
|
464
|
+
<Bloco s={[0.22, 0.08, 0.22]} p={[0, 1.46, 0]} c={escurecer(p.pele, 0.88)} r={0.02} />
|
|
465
|
+
</group>
|
|
466
|
+
<BracoBloco lado={-1} g={est.gesto[0]} gc={(est.gestoAtras || est.gesto)[0]} cor={p.roupa} pele={p.pele} t={t} acao={a} k={ka} kc={est.forcaAtras ?? ka} ar={ar} sem={sem} />
|
|
467
|
+
<BracoBloco lado={1} g={est.gesto[1]} gc={(est.gestoAtras || est.gesto)[1]} cor={p.roupa} pele={p.pele} t={t} acao={a} k={ka} kc={est.forcaAtras ?? ka} ar={ar} sem={sem} />
|
|
468
|
+
<group position={[0, 1.8 + resp * 1.2, 0]} rotation={[cp - troncoX * 0.6, cy - troncoY * 0.6, cabecaZ]}>
|
|
469
|
+
{p.cabeca !== 'facetada' ? (
|
|
470
|
+
<>
|
|
471
|
+
<Bloco s={[CAB, CAB, CAB]} c={p.pele} r={0.04} rough={0.6} />
|
|
472
|
+
<RostoPixel p={p} est={est} />
|
|
473
|
+
<Cabelo estilo={p.cabelo} cor={p.corCabelo} />
|
|
474
|
+
</>
|
|
475
|
+
) : <CabecaFacetada p={p} est={est} />}
|
|
476
|
+
</group>
|
|
477
|
+
</group>
|
|
443
478
|
</group>
|
|
444
479
|
</group>
|
|
445
480
|
);
|
|
@@ -454,15 +489,20 @@ function PessoaBloco({ p, est }) {
|
|
|
454
489
|
function Mascote({ p, est }) {
|
|
455
490
|
const cor = p.cor || '#d9532b';
|
|
456
491
|
const [abre] = est.boca;
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
const
|
|
492
|
+
const ka = Math.min(1, est.forcaAcao || 0);
|
|
493
|
+
// O pulo com peso: agacha, estica no impulso, cai acelerando e amassa no pouso (natural.salto).
|
|
494
|
+
const sl = (est.acao === 'pular' || est.acao === 'comemorar') ? salto(est.t, { periodo: 0.85, altura: 0.3 }) : { y: 0, sx: 1, sy: 1 };
|
|
495
|
+
const pulo = sl.y * ka;
|
|
496
|
+
const respira = (respiracao(est.t, (est.semente || 0) + 0.7) - 0.5) * 0.024;
|
|
497
|
+
const estica = (1 + abre * 0.07 + respira) * lerp(1, sl.sy, ka), achata = (1 - abre * 0.035) * lerp(1, sl.sx, ka);
|
|
460
498
|
const pisca = clamp(est.pisca);
|
|
461
499
|
const [cy, cp, cr] = est.cabeca;
|
|
462
500
|
const W = 1.0, H = 0.92, D = 0.78, perna = 0.46;
|
|
463
501
|
const pernas = [-0.38, -0.19, 0, 0.19, 0.38];
|
|
502
|
+
// O corpo balança de leve sobre as perninhas, atrasado da cabeça (sobreposição).
|
|
503
|
+
const ginga = ruido(est.t * 0.3, (est.semente || 0) + 21) * 0.025;
|
|
464
504
|
return (
|
|
465
|
-
<group position={[0, pulo, 0]} rotation={[0, cy * 0.8, cr * 0.5]}>
|
|
505
|
+
<group position={[0, pulo, 0]} rotation={[0, cy * 0.8, cr * 0.5 + ginga]}>
|
|
466
506
|
{pernas.map((x, i) => (
|
|
467
507
|
<group key={i}>
|
|
468
508
|
<Bloco s={[0.13, perna, 0.13]} p={[x, perna / 2 + Math.max(0, Math.sin(est.t * 9 + i)) * (est.falando ? 0.015 : 0), D / 2 - 0.12]} c={escurecer(cor, 0.9)} r={0.015} rough={0.6} />
|
|
@@ -804,33 +844,58 @@ export function VoxelCena({ el, t }) {
|
|
|
804
844
|
for (const f of falas) if (f.quem === i && f.de <= t + 0.2) e = t <= f.ate + 2.5 ? f.emocao : 'neutro';
|
|
805
845
|
return e;
|
|
806
846
|
};
|
|
807
|
-
// A ação da fala vale da voz até 0,6 s depois
|
|
847
|
+
// A ação da fala vale da voz até 0,6 s depois: entra em mola (passa do ponto e
|
|
848
|
+
// assenta) e sai devagar. O cotovelo pega a mesma ação ~0,1 s depois (sobreposição).
|
|
808
849
|
const acaoDe = (i) => {
|
|
809
850
|
for (const f of falas) {
|
|
810
851
|
if (f.quem !== i || !f.acao || f.acao === 'falar') continue;
|
|
811
|
-
if (t >= f.de - 0.3 && t <= f.ate + 0.6)
|
|
852
|
+
if (t >= f.de - 0.3 && t <= f.ate + 0.6) {
|
|
853
|
+
const opc = { entra: 2.2, sai: 0.5, amort: 0.55 };
|
|
854
|
+
return [f.acao, envelope(t, f.de - 0.3, f.ate + 0.1, opc), envelope(t, f.de - 0.3, f.ate + 0.1, { ...opc, atraso: 0.09 })];
|
|
855
|
+
}
|
|
812
856
|
}
|
|
813
|
-
return [null, 0];
|
|
857
|
+
return [null, 0, 0];
|
|
814
858
|
};
|
|
815
|
-
|
|
859
|
+
// O quanto cada um está falando (0 a 1, sem degrau): o aceno de cabeça de quem
|
|
860
|
+
// fala e o de quem ouve trocam com mola, não num corte seco.
|
|
861
|
+
const pesoDaFala = (i, reacao = 0) => falas.reduce((m, f) => (f.quem === i ? Math.max(m, Math.min(1, envelope(t, f.de + reacao, f.ate, { entra: 2.6, sai: 0.45, amort: 0.7 }))) : m), 0);
|
|
816
862
|
const estado = (i, figurante = false) => {
|
|
863
|
+
const sem = i * 13 + (figurante ? 101 : 1);
|
|
817
864
|
const fala = !figurante && falandoAgora(i);
|
|
818
|
-
const
|
|
819
|
-
const outro = qf >= 0 && qf !== i;
|
|
865
|
+
const wFala = figurante ? 0 : pesoDaFala(i);
|
|
820
866
|
const enfase = figurante ? [0, 0] : gesto(falas, i, t);
|
|
821
|
-
const
|
|
867
|
+
const enfaseAtras = figurante ? [0, 0] : gesto(falas, i, t - 0.09);
|
|
868
|
+
// Quem ouve vira para quem fala com ~0,2 s de reação e mola, e volta devagar.
|
|
869
|
+
let paraQuemFala = 0, wOuve = 0, dx = 0;
|
|
870
|
+
if (!figurante) {
|
|
871
|
+
for (const f of falas) {
|
|
872
|
+
if (f.quem === i) continue;
|
|
873
|
+
const w = Math.min(1, envelope(t, f.de + 0.2, f.ate + 0.3, { entra: 1.6, sai: 0.7, amort: 0.62 }));
|
|
874
|
+
if (w > wOuve) { wOuve = w; dx = lugarDe(elenco, f.quem)[0] - lugarDe(elenco, i)[0]; }
|
|
875
|
+
}
|
|
876
|
+
paraQuemFala = clamp(dx * 0.35, -0.35, 0.35) * wOuve;
|
|
877
|
+
}
|
|
878
|
+
const nodFala = Math.sin(t * 5.2 + i) * 0.03 + ruido(t * 2.4, sem + 2) * 0.02;
|
|
879
|
+
const nodQuieto = ruido(t * 0.5, sem + 3) * 0.018;
|
|
880
|
+
const nod = lerp(nodQuieto, nodFala, wFala) + wOuve * Math.max(0, Math.sin(t * 2.1 + i)) * 0.04;
|
|
822
881
|
const emo = figurante ? 'feliz' : emocaoDe(i);
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
882
|
+
// A inclinação da emoção chega e vai embora com peso, não troca no quadro.
|
|
883
|
+
let inclina = 0;
|
|
884
|
+
if (!figurante) {
|
|
885
|
+
for (const f of falas) {
|
|
886
|
+
if (f.quem !== i || f.de > t + 0.2) continue;
|
|
887
|
+
const ef = EMO[f.emocao] || EMO.neutro;
|
|
888
|
+
inclina = (ef.inclina || 0) * Math.min(1.05, envelope(t, f.de - 0.2, f.ate + 2.5, { entra: 1.4, sai: 0.8, amort: 0.6 }));
|
|
889
|
+
}
|
|
890
|
+
} else inclina = (EMO.feliz.inclina || 0);
|
|
891
|
+
const micro = (hash(Math.floor(t * 1.5 + hash(i) * 0.9) + i * 7) - 0.5) * 0.6;
|
|
892
|
+
const [acao, forcaAcao, forcaAtras] = figurante ? [null, 0, 0] : acaoDe(i);
|
|
829
893
|
return {
|
|
830
|
-
t, falando: fala, emocao: emo, boca: figurante ? [0, 0.55] : boca(falas, i, t), pisca: piscada(i + (figurante ? 5 : 0), t),
|
|
831
|
-
gesto: enfase, enfase: Math.max(enfase[0], enfase[1]), acao, forcaAcao,
|
|
832
|
-
olhar: [micro +
|
|
833
|
-
|
|
894
|
+
t, semente: sem, falando: fala, emocao: emo, boca: figurante ? [0, 0.55] : boca(falas, i, t), pisca: piscada(i + (figurante ? 5 : 0), t),
|
|
895
|
+
gesto: enfase, gestoAtras: enfaseAtras, enfase: Math.max(enfase[0], enfase[1], 0), acao, forcaAcao, forcaAtras,
|
|
896
|
+
olhar: [micro + Math.sign(dx) * 0.8 * wOuve, 0.1],
|
|
897
|
+
// A cabeça deriva pelo ruído (nunca a mesma volta) e vem ~0,06 s atrás do corpo.
|
|
898
|
+
cabeca: [ruido((t - 0.06) * 0.35, sem + 5) * 0.08 + paraQuemFala, nod, inclina * (i % 2 ? -1 : 1) + ruido((t - 0.06) * 0.25, sem + 7) * 0.025],
|
|
834
899
|
};
|
|
835
900
|
};
|
|
836
901
|
const desenhar = (p, est) => (p.tipo === 'robo' ? <Mascote p={p} est={est} />
|
package/lib/estudio/kit.js
CHANGED
|
@@ -67,7 +67,7 @@ const PECAS = {
|
|
|
67
67
|
animacao: { precisa: ['src'] },
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
const ARQUIVOS = ['base.jsx', 'vox.jsx', 'marca.jsx', 'tres.jsx', 'gerativo.jsx', 'fluido.jsx', 'personagem.jsx', 'mundo.jsx', 'voxel.jsx', 'animacao.jsx', 'index.jsx'];
|
|
70
|
+
const ARQUIVOS = ['base.jsx', 'vox.jsx', 'marca.jsx', 'tres.jsx', 'gerativo.jsx', 'fluido.jsx', 'personagem.jsx', 'mundo.jsx', 'voxel.jsx', 'animacao.jsx', 'natural.js', 'index.jsx'];
|
|
71
71
|
|
|
72
72
|
/** Os arquivos do kit, para o projeto Remotion: [{nome: 'kit/base.jsx', conteudo}]. */
|
|
73
73
|
function arquivos() {
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* sem-narracao.js — o vídeo inteiro sem voz: só o motion e os barulhinhos.
|
|
3
|
+
*
|
|
4
|
+
* "Motions sem narração: só o motion e os efeitos sonoros, os barulhinhos."
|
|
5
|
+
* — Joel (14/10/2026)
|
|
6
|
+
*
|
|
7
|
+
* Sem voz não há tempo MEDIDO: cada parte dura o `duracao` (s) que o roteiro
|
|
8
|
+
* deu ou, sem ele, o que o texto da tela pede para ser lido. As palavras ganham
|
|
9
|
+
* tempos repartidos na parte, para as cenas que entram palavra por palavra
|
|
10
|
+
* continuarem entrando. E o som sobe de papel: um efeito leve em cada corte e
|
|
11
|
+
* em cada entrada marcante (sons-leves.js), sem repetir o mesmo em sequência.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
'use strict';
|
|
15
|
+
|
|
16
|
+
const r3 = (x) => Math.round(x * 1000) / 1000;
|
|
17
|
+
|
|
18
|
+
/* O pedido que tira a voz: "sem narração", "sem voz", "sem locução", "só efeito sonoro". */
|
|
19
|
+
const PEDIDO = /\bsem\s+(narra[cç][aã]o|narrador|voz|locu[cç][aã]o)\b|\bs[oó]\s+(o\s+motion\s+e\s+)?(os\s+)?(efeitos?\s+sonoros?|barulhinhos?|sfx)\b/i;
|
|
20
|
+
|
|
21
|
+
/** O vídeo é sem voz? `roteiro.semNarracao` manda (false desliga); sem ele, o pedido. */
|
|
22
|
+
function ativo(roteiro, pedido = '') {
|
|
23
|
+
const r = roteiro || {};
|
|
24
|
+
if (r.semNarracao === true || r.semNarracao === 'true') return true;
|
|
25
|
+
if (r.semNarracao === false || r.semNarracao === 'false') return false;
|
|
26
|
+
return PEDIDO.test(String(pedido || ''));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** O que a TELA mostra da parte (é o que se lê; a fala só se não há mais nada). */
|
|
30
|
+
function textoDaTela(pt) {
|
|
31
|
+
if (!pt || typeof pt !== 'object') return '';
|
|
32
|
+
const lista = (x) => (Array.isArray(x) ? x.map((i) => (typeof i === 'string' ? i : (i && (i.texto || i.titulo || i.nome || i.rotulo)) || '')) : []);
|
|
33
|
+
const txt = [pt.titulo, pt.sub, pt.texto, pt.nao, pt.sim, pt.valor, pt.rotulo, pt.autor, ...lista(pt.itens), ...lista(pt.linhas)]
|
|
34
|
+
.filter((x) => x != null && String(x).trim()).join(' ');
|
|
35
|
+
return txt.trim() || String(pt.fala || '').trim();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Quanto a parte fica na tela: `duracao` do roteiro; senão 1,8 s + 0,25 s por palavra, entre 1,5 e 4 s. */
|
|
39
|
+
function duracaoDe(pt) {
|
|
40
|
+
const d = Number(pt && pt.duracao);
|
|
41
|
+
if (d > 0) return Math.max(0.5, Math.min(30, d));
|
|
42
|
+
const n = textoDaTela(pt).split(/\s+/).filter(Boolean).length;
|
|
43
|
+
return Math.max(1.5, Math.min(4, 1.8 + 0.25 * n));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Os tempos de cada parte, no mesmo formato que a narração devolve
|
|
48
|
+
* ({de, ate, palavras:[{texto, de, ate}]}): partes coladas uma na outra e as
|
|
49
|
+
* palavras repartidas nos primeiros 70% da parte (o resto é o respiro).
|
|
50
|
+
* `falaDe` dá o texto cujas palavras a cena procura (o do compositor).
|
|
51
|
+
*/
|
|
52
|
+
function tempos(partes, { falaDe = textoDaTela } = {}) {
|
|
53
|
+
let t = 0;
|
|
54
|
+
return (partes || []).map((pt) => {
|
|
55
|
+
const dur = duracaoDe(pt);
|
|
56
|
+
const de = r3(t), ate = r3(t + dur);
|
|
57
|
+
const ws = String(falaDe(pt) || textoDaTela(pt) || '').split(/\s+/).filter(Boolean);
|
|
58
|
+
const ini = de + Math.min(0.2, dur * 0.1), janela = dur * 0.7 - (ini - de);
|
|
59
|
+
const passo = janela / Math.max(1, ws.length);
|
|
60
|
+
const palavras = ws.map((w, j) => ({ texto: w, de: r3(ini + j * passo), ate: r3(ini + j * passo + Math.max(0.08, passo * 0.8)) }));
|
|
61
|
+
t += dur;
|
|
62
|
+
return { de, ate, palavras };
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* Os momentos que pedem som: os CORTES (começo de parte, de batida, de cena)
|
|
67
|
+
e as ENTRADAS marcantes (o ícone, o item, a cena que a IA escreveu). */
|
|
68
|
+
function momentos({ tempos: tps = [], plano = null, partitura = null } = {}) {
|
|
69
|
+
const cortes = [], entradas = [];
|
|
70
|
+
const perto = (lista, t, d = 0.3) => lista.some((x) => Math.abs(x - t) < d);
|
|
71
|
+
const corte = (t) => { if (t > 0.3 && !perto(cortes, t)) cortes.push(r3(t)); };
|
|
72
|
+
const entrada = (t) => { if (t != null && t >= 0 && !perto(entradas, t, 0.35) && !perto(cortes, t, 0.2)) entradas.push(r3(t)); };
|
|
73
|
+
for (const t of tps.slice(1)) corte(t.de);
|
|
74
|
+
const bs = (partitura && partitura.batidas) || [];
|
|
75
|
+
for (const b of bs) corte(b.de);
|
|
76
|
+
// Vídeo de várias cenas (os outros estilos): o começo de cada cena é um corte.
|
|
77
|
+
let acc = 0;
|
|
78
|
+
for (const c of (plano && plano.cenas) || []) { corte(acc); acc += Number(c.duracao) || 0; }
|
|
79
|
+
entrada(tps.length ? tps[0].de + 0.1 : 0.1); // a abertura entra com som
|
|
80
|
+
for (const b of bs) {
|
|
81
|
+
if (b.icone3d && b.icone3d.t != null) entrada(b.icone3d.t);
|
|
82
|
+
for (const l of b.linhas || []) if ((l.papel === 'item' || l.papel === 'numero') && l.t != null) entrada(l.t);
|
|
83
|
+
if (b.comp === 'marca') entrada(b.de + 0.35);
|
|
84
|
+
}
|
|
85
|
+
for (const c of (plano && plano.cenas) || []) for (const el of c.elementos || []) if (el && el.tipo === 'componente' && el.de != null) entrada(el.de + 0.08);
|
|
86
|
+
return { cortes: cortes.sort((a, b) => a - b), entradas: entradas.sort((a, b) => a - b) };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// O corte passa com ar; a entrada chega com um toque pequeno. Ordem de rodízio.
|
|
90
|
+
const DO_CORTE = ['whoosh', 'sopro', 'toque', 'riser'];
|
|
91
|
+
const DA_ENTRADA = ['pop', 'bolha', 'clique', 'brilho'];
|
|
92
|
+
// O whoosh e o sopro antecipam o corte (o ar chega antes da imagem); o riser sobe até ele.
|
|
93
|
+
const ANTES = { whoosh: 0.18, sopro: 0.3, riser: 1.0, toque: 0 };
|
|
94
|
+
const VOLUME = { whoosh: 0.5, sopro: 0.6, toque: 0.6, riser: 0.4, pop: 0.42, bolha: 0.45, clique: 0.4, brilho: 0.32 };
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Soma aos efeitos do plano um som leve em cada corte e em cada entrada.
|
|
98
|
+
* `sons` é o mapa nome → arquivo do sons-leves.preparar. Não encosta em som
|
|
99
|
+
* que já existe (0,12 s) e nunca repete o mesmo som duas vezes seguidas.
|
|
100
|
+
*/
|
|
101
|
+
function sonorizar(efeitos, { cortes = [], entradas = [], sons = {}, duracao = Infinity } = {}) {
|
|
102
|
+
const fora = (efeitos || []).slice();
|
|
103
|
+
const eventos = [...cortes.map((t) => ({ t, tipo: 'corte' })), ...entradas.map((t) => ({ t, tipo: 'entrada' }))].sort((a, b) => a.t - b.t);
|
|
104
|
+
const giro = { corte: 0, entrada: 0 };
|
|
105
|
+
let ultimo = null;
|
|
106
|
+
const novos = [];
|
|
107
|
+
for (const ev of eventos) {
|
|
108
|
+
const roda = ev.tipo === 'corte' ? DO_CORTE : DA_ENTRADA;
|
|
109
|
+
let nome = null;
|
|
110
|
+
for (let k = 0; k < roda.length; k++) {
|
|
111
|
+
const n = roda[(giro[ev.tipo] + k) % roda.length];
|
|
112
|
+
if (!sons[n] || n === ultimo) continue;
|
|
113
|
+
// O riser precisa de espaço antes do corte.
|
|
114
|
+
if (n === 'riser' && ev.t < 1.2) continue;
|
|
115
|
+
nome = n; giro[ev.tipo] = (giro[ev.tipo] + k + 1) % roda.length; break;
|
|
116
|
+
}
|
|
117
|
+
if (!nome) continue;
|
|
118
|
+
const t = r3(Math.max(0, ev.t - (ANTES[nome] || 0)));
|
|
119
|
+
if (t > duracao - 0.1) continue;
|
|
120
|
+
if ([...fora, ...novos].some((e) => Math.abs(e.t - t) < 0.12)) continue;
|
|
121
|
+
novos.push({ t, src: sons[nome], volume: VOLUME[nome] || 0.4, som: nome });
|
|
122
|
+
ultimo = nome;
|
|
123
|
+
}
|
|
124
|
+
return { efeitos: [...fora, ...novos.map(({ som, ...e }) => e)].sort((a, b) => a.t - b.t), novos };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** O plano pronto ganha os barulhinhos (o plano é mudado no lugar). Devolve quantos entraram. */
|
|
128
|
+
function sonorizarPlano(r, { tempos: tps = [], sons = {} } = {}) {
|
|
129
|
+
if (!r || !r.plano) return 0;
|
|
130
|
+
const plano = r.plano;
|
|
131
|
+
plano.audio = plano.audio || {};
|
|
132
|
+
const duracao = (plano.cenas || []).reduce((n, c) => n + (Number(c.duracao) || 0), 0) || Infinity;
|
|
133
|
+
const m = momentos({ tempos: tps, plano, partitura: r.partitura });
|
|
134
|
+
const s = sonorizar(plano.audio.efeitos || [], { ...m, sons, duracao });
|
|
135
|
+
plano.audio.efeitos = s.efeitos;
|
|
136
|
+
delete plano.audio.narracao;
|
|
137
|
+
return s.novos.length;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
module.exports = { ativo, textoDaTela, duracaoDe, tempos, momentos, sonorizar, sonorizarPlano, PEDIDO };
|
package/lib/tools.js
CHANGED
|
@@ -594,6 +594,12 @@ async function despachar(name, args, ctx) {
|
|
|
594
594
|
{
|
|
595
595
|
switch (name) {
|
|
596
596
|
case 'write_file': {
|
|
597
|
+
// O modelo grátis manda o caminho com outro nome (file_path, caminho) ou nenhum,
|
|
598
|
+
// e o write caía na própria pasta: EISDIR, e ele tentava de novo igual (14/10).
|
|
599
|
+
if (!args.path) args.path = args.file_path || args.filePath || args.caminho || args.arquivo || args.file || '';
|
|
600
|
+
if (!String(args.path).trim()) {
|
|
601
|
+
return { ok: false, error: 'faltou o caminho do arquivo em "path".', comoResolver: 'chame write_file de novo com path:"pasta/nome.ext" e content.' };
|
|
602
|
+
}
|
|
597
603
|
// Vídeo e áudio NÃO se escrevem com write_file. Num pedido
|
|
598
604
|
// real o agente criou um "video.mp4" de 43 bytes de texto e
|
|
599
605
|
// disse "vídeo criado com sucesso" — um arquivo falso, morto,
|
|
@@ -610,6 +616,11 @@ async function despachar(name, args, ctx) {
|
|
|
610
616
|
}
|
|
611
617
|
const filePath = resolveInProject(args.path, projectDir);
|
|
612
618
|
const content = args.content || '';
|
|
619
|
+
try {
|
|
620
|
+
if (fs.statSync(filePath).isDirectory()) {
|
|
621
|
+
return { ok: false, error: `"${args.path}" é uma PASTA, não um arquivo.`, comoResolver: `ponha o nome do arquivo no fim: path:"${String(args.path).replace(/\/+$/, '')}/index.html" (ou o nome que for).` };
|
|
622
|
+
}
|
|
623
|
+
} catch { /* não existe: vai ser criado */ }
|
|
613
624
|
const created = !fs.existsSync(filePath);
|
|
614
625
|
// Reescrever um arquivo que já existe é a operação mais
|
|
615
626
|
// perigosa do agente: o conteúdo antigo some inteiro. Ler o
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.11.0",
|
|
4
4
|
"description": "PrimoCode — agente de engenharia com IA e cursor próprio. Requer conta Conecta Primo AI (Premium ou Super). Cria arquivos, roda comandos, controla navegador e desktop: abre apps, clica em botões e ícones pelo nome, digita e usa atalhos.",
|
|
5
5
|
"main": "bin/primocode.js",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
10
|
"start": "node bin/primocode.js",
|
|
11
|
-
"test": "node test/tools.test.js && node test/ui.test.js && node test/entrada.test.js && node test/transparencia.test.js && node test/permissao.test.js && node test/compatibilidade.test.js && node test/skills.test.js && node test/skills-de-fora.test.js && node test/skills-novas.test.js && node test/comandos.test.js && node test/estudio.test.js && node test/voz.test.js && node test/modo.test.js && node test/pasta.test.js && node test/projeto.test.js && node test/nome-projeto.test.js && node test/api-propria.test.js && node test/claude-engine.test.js && node test/codex.test.js && node test/conferir.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/seta.test.js && node test/effort.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/casar.test.js && node test/recado.test.js && node test/releitura.test.js && node test/snapshot.test.js && node test/video.test.js && node test/limpeza.test.js && node test/autonomia.test.js && node test/desfazer.test.js && node test/desfazer-git.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/pipeline.test.js && node test/conta.test.mjs && node test/planos-tela.test.js && node test/nuvem.test.js && node test/prazo.test.js && node test/primeira-vez.test.js && node test/fala.test.js && node test/especialistas.test.js && node test/empurrao.test.js && node test/terminal-novo.test.js && node test/markdown.test.js && node test/coerencia.test.js && node test/contexto.test.js && node test/perigo.test.js && node test/rapidez.test.js && node test/mascotes.test.js && node test/comandos-extra.test.js && node test/offline.test.js && node test/edicao.test.js && node test/composicao.test.js && node test/acervo-site.test.js && node test/impacto.test.js && node test/cinema.test.js && node test/producao.test.js && node test/ende.test.js && node test/capa.test.js && node test/broll.test.js && node test/morfar.test.js && node test/motion.test.js && node test/motion-roteiro.test.js && node test/kit-motion.test.js && node test/gerativo.test.js && node test/gerativo-identidade.test.js && node test/gerativo-moldura.test.js && node test/gerativo-heroi.test.js && node test/gerativo-ambiente.test.js && node test/gerativo-variedade.test.js && node test/hibrido.test.js && node test/batida.test.js && node test/estudio-acervo.test.js && node test/estudio-diretor.test.js && node test/estudio-marca.test.js && node test/estudio-novo-projeto.test.js && node test/estudio-plano.test.js && node test/estudio-slides.test.js && node test/estudio-video.test.js && node test/revisor.test.js && node test/conferencia.test.js && node test/cor.test.js && node test/assinatura.test.js && node test/biblioteca.test.js && node test/mcp.test.js && node test/personas.test.js && node test/efeitos.test.js && node test/auto.test.js && node test/colar.test.js && node test/orquestra.test.js && node test/shell-limpo.test.js && node test/editar-remotion.test.js && node test/variacao3d.test.js && node test/portao-skill.test.js && node test/clones.test.js && node test/kokoro.test.js && node test/fluido.test.js && node test/zoom.test.js && node test/personagem.test.js && node test/tela.test.js && node test/margem.test.js && node test/travado.test.js && node test/leitura.test.js && node test/producao-provedores.test.js && node test/broll-frase.test.js && node test/vazamento.test.js && node test/colecao.test.js && node test/mundo.test.js && node test/mcp-procurar.test.js && node test/selecao.test.js && node test/nome-curto.test.js && node test/agents-md.test.js && node test/outra-pasta.test.js && node test/estudio-sem-id.test.js && node test/estudio-objeto-do-site.test.js && node test/cenarios.test.js && node test/movimentos.test.js && node test/gerativo-so-fala.test.js"
|
|
11
|
+
"test": "node test/tools.test.js && node test/ui.test.js && node test/entrada.test.js && node test/transparencia.test.js && node test/permissao.test.js && node test/compatibilidade.test.js && node test/skills.test.js && node test/skills-de-fora.test.js && node test/skills-novas.test.js && node test/comandos.test.js && node test/estudio.test.js && node test/voz.test.js && node test/modo.test.js && node test/pasta.test.js && node test/projeto.test.js && node test/nome-projeto.test.js && node test/api-propria.test.js && node test/claude-engine.test.js && node test/codex.test.js && node test/conferir.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/seta.test.js && node test/effort.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/casar.test.js && node test/recado.test.js && node test/releitura.test.js && node test/snapshot.test.js && node test/video.test.js && node test/limpeza.test.js && node test/autonomia.test.js && node test/desfazer.test.js && node test/desfazer-git.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/pipeline.test.js && node test/conta.test.mjs && node test/planos-tela.test.js && node test/nuvem.test.js && node test/prazo.test.js && node test/primeira-vez.test.js && node test/fala.test.js && node test/especialistas.test.js && node test/empurrao.test.js && node test/terminal-novo.test.js && node test/markdown.test.js && node test/coerencia.test.js && node test/contexto.test.js && node test/perigo.test.js && node test/rapidez.test.js && node test/mascotes.test.js && node test/comandos-extra.test.js && node test/offline.test.js && node test/edicao.test.js && node test/composicao.test.js && node test/acervo-site.test.js && node test/impacto.test.js && node test/cinema.test.js && node test/producao.test.js && node test/ende.test.js && node test/capa.test.js && node test/broll.test.js && node test/morfar.test.js && node test/motion.test.js && node test/motion-roteiro.test.js && node test/kit-motion.test.js && node test/gerativo.test.js && node test/gerativo-identidade.test.js && node test/gerativo-moldura.test.js && node test/gerativo-heroi.test.js && node test/gerativo-ambiente.test.js && node test/gerativo-variedade.test.js && node test/hibrido.test.js && node test/batida.test.js && node test/estudio-acervo.test.js && node test/estudio-diretor.test.js && node test/estudio-marca.test.js && node test/estudio-novo-projeto.test.js && node test/estudio-plano.test.js && node test/estudio-slides.test.js && node test/estudio-video.test.js && node test/revisor.test.js && node test/conferencia.test.js && node test/cor.test.js && node test/assinatura.test.js && node test/biblioteca.test.js && node test/mcp.test.js && node test/personas.test.js && node test/efeitos.test.js && node test/auto.test.js && node test/colar.test.js && node test/rolar.test.js && node test/orquestra.test.js && node test/shell-limpo.test.js && node test/editar-remotion.test.js && node test/variacao3d.test.js && node test/portao-skill.test.js && node test/clones.test.js && node test/kokoro.test.js && node test/fluido.test.js && node test/zoom.test.js && node test/personagem.test.js && node test/tela.test.js && node test/margem.test.js && node test/travado.test.js && node test/leitura.test.js && node test/producao-provedores.test.js && node test/broll-frase.test.js && node test/vazamento.test.js && node test/colecao.test.js && node test/mundo.test.js && node test/mcp-procurar.test.js && node test/selecao.test.js && node test/nome-curto.test.js && node test/agents-md.test.js && node test/outra-pasta.test.js && node test/estudio-sem-id.test.js && node test/estudio-objeto-do-site.test.js && node test/cenarios.test.js && node test/movimentos.test.js && node test/gerativo-so-fala.test.js && node test/estilos.test.js && node test/proporcao.test.js && node test/cena.test.js && node test/sem-narracao.test.js"
|
|
12
12
|
},
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=18.17.0"
|
|
@@ -22,59 +22,62 @@ Não faça nada disto, em nenhum vídeo:
|
|
|
22
22
|
|
|
23
23
|
## O que separa motion bom de slide animado
|
|
24
24
|
|
|
25
|
-
**A câmera existe.**
|
|
26
|
-
|
|
27
|
-
quase nenhum vídeo automático faz isso.
|
|
25
|
+
**A câmera existe.** Um zoom lento de 1.0 para 1.08 na cena inteira dá vida a
|
|
26
|
+
um quadro parado.
|
|
28
27
|
|
|
29
28
|
**Nada entra do nada.** Um elemento entra vindo de algum lugar (de baixo, de
|
|
30
|
-
trás, de fora do quadro), com `
|
|
29
|
+
trás, de fora do quadro), com `spring` do Remotion ou easing de saída. Movimento
|
|
31
30
|
linear é a assinatura da máquina: no mundo físico nada começa e para na mesma
|
|
32
31
|
velocidade.
|
|
33
32
|
|
|
34
|
-
**Profundidade.**
|
|
35
|
-
|
|
36
|
-
rápido. Com `perspectiva` na cena, `rotacaoX`/`rotacaoY`/`z` viram 3D de
|
|
37
|
-
verdade.
|
|
33
|
+
**Profundidade.** Parallax (fundo devagar, frente rápido); `perspective` com
|
|
34
|
+
`rotateX`/`translateZ`, ou three, para 3D de verdade.
|
|
38
35
|
|
|
39
|
-
**Ritmo.**
|
|
40
|
-
a 4s. Fecho: 2s. Variar a duração é o que cria ritmo; repetir cria sono.
|
|
36
|
+
**Ritmo.** Informação simples 1,5–2,5 s; frase para ler 3–4 s; fecho 2 s.
|
|
41
37
|
|
|
42
38
|
**Silêncio visual.** Um quadro com uma frase e mais nada vale mais que um com
|
|
43
39
|
seis elementos. Espaço vazio é composição, não desperdício.
|
|
44
40
|
|
|
45
|
-
##
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
`
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
`
|
|
41
|
+
## O caminho: você escreve cada cena
|
|
42
|
+
|
|
43
|
+
Motion nesse nível não sai de molde: cada cena é UMA ideia desenhada para a
|
|
44
|
+
fala — a interface do produto montando, o número que cresce, o objeto em 3D,
|
|
45
|
+
o gráfico que mostra a ideia, a palavra gigante que se transforma. Quem
|
|
46
|
+
desenha é você, em React.
|
|
47
|
+
|
|
48
|
+
1. `estudio_projeto` (criar). A marca tem site? `estudio_site` mede cores e fontes.
|
|
49
|
+
2. `estudio_midia acao:"estilos"`: monte a identidade pelo assunto em
|
|
50
|
+
`roteiro.estiloVisual` (humor, paleta, display, texto…). Um vídeo, uma
|
|
51
|
+
identidade: 2 chãos, 1 acento, 2 fontes.
|
|
52
|
+
3. `estudio_narrar` com o roteiro: os tempos MEDIDOS de cada fala.
|
|
53
|
+
Sem voz: pule; `semNarracao:true` e `duracao` por parte.
|
|
54
|
+
4. Para cada parte, `write_file` com `path:"componentes/<nome>.jsx"`:
|
|
55
|
+
`export default function Cena({ el, t, fps })`, `t` em segundos desde o
|
|
56
|
+
começo da parte. Em `el.dados`: `titulo`, `fala`, `palavras` (cada uma com
|
|
57
|
+
o seu `t`, para entrar junto com a voz), `duracao`, `cores` {fundo, texto,
|
|
58
|
+
acento}, `fontes`, `largura`, `altura`. Imports só de react, remotion,
|
|
59
|
+
@remotion/*, three. Cor e fonte vêm de `el.dados`, nunca inventadas na cena.
|
|
60
|
+
5. `estudio_plano` com o roteiro: `estiloVisual` e, em cada parte,
|
|
61
|
+
`componente:"componentes/<nome>.jsx"`. Voltou `cenasQuebradas`? Conserte o
|
|
62
|
+
arquivo e mande o roteiro de novo.
|
|
63
|
+
6. `estudio_render`. A voz, a trilha, os sons e o fundo são do Estúdio.
|
|
64
|
+
|
|
65
|
+
**Variedade:** `estudio_midia acao:"efeitos"` antes das cenas; adapte e use
|
|
66
|
+
como `componente`. Uma técnica nova por vídeo. Risco em 2+ linhas: traço por linha.
|
|
67
|
+
|
|
68
|
+
Parte sem `componente` cai no gerativo (a tipografia anima sozinha): é a
|
|
69
|
+
reserva, não o objetivo. Sem imagem é o padrão (foto só se pedirem).
|
|
63
70
|
|
|
64
71
|
## Cor e tipo
|
|
65
72
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
não as que você imagina que ela usa.
|
|
73
|
+
A paleta sai do ASSUNTO, no `estiloVisual` (um nome do cardápio ou 5 cores
|
|
74
|
+
suas). Se a marca existe, as cores medidas pelo `estudio_site` mandam — não
|
|
75
|
+
as que você imagina que ela usa.
|
|
70
76
|
|
|
71
|
-
Duas fontes, no máximo:
|
|
72
|
-
que se lê. `estudio_midia acao:"fontes"` mostra um leque grande — sair do
|
|
73
|
-
Inter/Montserrat é metade do trabalho de não parecer genérico.
|
|
77
|
+
Duas fontes, no máximo: display para o que grita, neutra para o que se lê.
|
|
74
78
|
|
|
75
79
|
## Antes de renderizar
|
|
76
80
|
|
|
77
|
-
- Todo `src` aponta para arquivo que existe na pasta (baixe antes).
|
|
78
81
|
- Os tempos das cenas batem com os tempos MEDIDOS da narração.
|
|
79
82
|
- Nenhuma cena tem a mesma composição da anterior.
|
|
80
83
|
- Leia o seu próprio plano e pergunte: em que este vídeo é diferente do
|