primocode 10.0.0 → 10.1.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 +4 -4
- package/bin/primocode.js +88 -3
- package/lib/act.js +122 -20
- package/lib/browser.js +89 -5
- package/lib/catalogo.js +6 -3
- package/lib/claude-engine.js +38 -17
- package/lib/codex-engine.js +27 -9
- package/lib/config.js +10 -2
- package/lib/entrada.js +73 -9
- package/lib/estudio/assets.js +12 -6
- package/lib/estudio/biblioteca.js +174 -0
- package/lib/estudio/cinema.js +63 -25
- package/lib/estudio/composicao.js +2 -1
- package/lib/estudio/diretor.js +13 -1
- package/lib/estudio/edicao.js +2 -1
- package/lib/estudio/ende.js +16 -5
- package/lib/estudio/estilo.js +41 -10
- package/lib/estudio/fluido.js +283 -0
- package/lib/estudio/gerativo.js +1471 -0
- package/lib/estudio/hibrido.js +169 -0
- package/lib/estudio/impacto.js +66 -31
- package/lib/estudio/index.js +366 -47
- package/lib/estudio/kit/fluido.jsx +493 -0
- package/lib/estudio/kit/gerativo.jsx +990 -0
- package/lib/estudio/kit/index.jsx +4 -1
- package/lib/estudio/kit/tres.jsx +375 -0
- package/lib/estudio/kit.js +9 -1
- package/lib/estudio/kokoro.js +111 -0
- package/lib/estudio/lancamento.js +27 -0
- package/lib/estudio/legibilidade.js +20 -0
- package/lib/estudio/referencias.js +105 -0
- package/lib/estudio/remotion.js +103 -14
- package/lib/estudio/ritmo.js +2 -2
- package/lib/estudio/sons-leves.js +38 -1
- package/lib/estudio/variacao3d.js +46 -0
- package/lib/estudio/variedade.js +183 -0
- package/lib/estudio/vox.js +37 -8
- package/lib/mcp.js +156 -0
- package/lib/personas.js +67 -0
- package/lib/portao-skill.js +150 -0
- package/lib/skills.js +51 -1
- package/lib/tools.js +53 -8
- package/lib/ui.js +41 -6
- package/lib/video/auto.js +329 -0
- package/lib/video/camadas.js +207 -0
- package/lib/video/cli.js +1 -1
- package/lib/video/filters.js +39 -6
- package/lib/video/fx3d/Camadas.jsx +64 -0
- package/lib/video/fx3d/Objetos.jsx +128 -0
- package/lib/video/fx3d/Root.jsx +18 -0
- package/lib/video/fx3d/modelos.jsx +304 -0
- package/lib/video/fx3d.js +108 -0
- package/lib/video/gerados.js +119 -0
- package/lib/video/objetos.js +111 -0
- package/lib/video/ops-fx.js +453 -0
- package/lib/video/ops.js +30 -5
- package/lib/video/paralelo.js +50 -0
- package/lib/video/pedido.js +191 -0
- package/lib/video/rastrear.js +95 -0
- package/lib/video/rastro.js +276 -0
- package/lib/video/render.js +67 -14
- package/lib/video/texto.js +6 -4
- package/lib/video/timeline.js +9 -1
- package/package.json +2 -2
- package/skills/escrever-em-portugues/SKILL.md +3 -0
- package/skills/interface-revisada/SKILL.md +7 -1
- package/skills/motion-de-primeira/SKILL.md +5 -4
- package/skills/projeto-no-github/SKILL.md +6 -0
- package/skills/publicar-na-internet/SKILL.md +3 -0
- package/skills/site-com-motion/SKILL.md +2 -0
- package/studio/narrar.py +10 -7
- package/studio/rastrear.py +399 -0
- package/studio/voz_kokoro.py +98 -0
package/lib/video/render.js
CHANGED
|
@@ -82,6 +82,8 @@ async function temPillow() {
|
|
|
82
82
|
|
|
83
83
|
/** As camadas de texto da janela: {índiceDaFaixa: arquivo .ffconcat}. */
|
|
84
84
|
async function camadasDeTexto(pasta, tl, { from, to, escala }) {
|
|
85
|
+
const avisosPos = [];
|
|
86
|
+
const posicao = require('./gerados.js').posicoesDeLegenda(pasta, tl, avisosPos);
|
|
85
87
|
const faixas = tl.tracks.map((t, i) => ({ t, i })).filter(({ t }) => t.type === 'text' && t.clips.length && !t.hidden);
|
|
86
88
|
if (!faixas.length) return { camadas: {}, avisos: [] };
|
|
87
89
|
if (!(await temPillow())) {
|
|
@@ -96,7 +98,7 @@ async function camadasDeTexto(pasta, tl, { from, to, escala }) {
|
|
|
96
98
|
const estados = [];
|
|
97
99
|
const camadas = {};
|
|
98
100
|
for (const { t, i } of faixas) {
|
|
99
|
-
const { segmentos, desenhos } = TX.estadosDaFaixa(tl, t, { from, to, escala });
|
|
101
|
+
const { segmentos, desenhos } = TX.estadosDaFaixa(tl, t, { from, to, escala, posicao });
|
|
100
102
|
if (!Object.keys(desenhos).length) continue;
|
|
101
103
|
for (const [chave, items] of Object.entries(desenhos)) {
|
|
102
104
|
if (!fs.existsSync(arquivoDe(chave)) && !estados.some((e) => e.chave === chave)) estados.push({ chave, file: arquivoDe(chave), items });
|
|
@@ -112,13 +114,13 @@ async function camadasDeTexto(pasta, tl, { from, to, escala }) {
|
|
|
112
114
|
try { fs.unlinkSync(job); } catch {}
|
|
113
115
|
if (!r.ok) return { camadas: {}, avisos: [`os textos não foram desenhados: ${r.error}`] };
|
|
114
116
|
}
|
|
115
|
-
return { camadas, avisos: [], estados: estados.length };
|
|
117
|
+
return { camadas, avisos: [...new Set(avisosPos)], estados: estados.length };
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
// ── rodar o ffmpeg ───────────────────────────────────────────────────────
|
|
119
121
|
|
|
120
|
-
async function rodarGrafo(pasta, g, saida) {
|
|
121
|
-
const arq = path.join(pasta, 'cache', `grafo-${process.pid}.txt`);
|
|
122
|
+
async function rodarGrafo(pasta, g, saida, sufixo = '') {
|
|
123
|
+
const arq = path.join(pasta, 'cache', `grafo-${process.pid}${sufixo}.txt`);
|
|
122
124
|
fs.mkdirSync(path.dirname(arq), { recursive: true });
|
|
123
125
|
fs.writeFileSync(arq, g.grafo);
|
|
124
126
|
// O grafo vai por arquivo: com cinquenta clipes ele passa do teto de
|
|
@@ -130,29 +132,80 @@ async function rodarGrafo(pasta, g, saida) {
|
|
|
130
132
|
return { ...r, grafo: arq };
|
|
131
133
|
}
|
|
132
134
|
|
|
133
|
-
|
|
135
|
+
/* O EXPORT EM PARTES: o grafo do ffmpeg é quase todo de um núcleo só (cada
|
|
136
|
+
filtro roda numa thread), e um vídeo com pessoa recortada, fundo novo,
|
|
137
|
+
3D e legenda passa a maior parte do tempo nele. Então o VÍDEO sai em
|
|
138
|
+
pedaços de quadros, cada um num ffmpeg, ao mesmo tempo, e junta por
|
|
139
|
+
concat sem recodificar; o ÁUDIO sai inteiro, numa passada só (é barato,
|
|
140
|
+
e cortar AAC em pedaços deixa estalo na emenda). As emendas caem em
|
|
141
|
+
quadro inteiro: o quadro N sai igual com 1 ou com 9 pedaços. */
|
|
142
|
+
async function exportar(pasta, tl, { out, from = 0, to, preview = false, crf = 20, partes } = {}) {
|
|
134
143
|
const c = conferir();
|
|
135
144
|
if (!c.ok) return c;
|
|
145
|
+
const ger = await require('./gerados.js').garantir(pasta, tl);
|
|
146
|
+
if (!ger.ok) return ger;
|
|
136
147
|
const escala = preview ? 0.5 : 1;
|
|
137
148
|
const ate = to === undefined ? T.duracao(tl) : Number(to);
|
|
138
|
-
const tx = await camadasDeTexto(pasta, tl, { from, to: ate, escala });
|
|
139
|
-
const g = F.montarGrafo(tl, { from, to: ate, camadasDeTexto: tx.camadas, escala });
|
|
140
149
|
const destino = path.resolve(out || path.join(pasta, 'saida', `${tl.nome || 'video'}${preview ? '-preview' : ''}.mp4`));
|
|
141
150
|
fs.mkdirSync(path.dirname(destino), { recursive: true });
|
|
142
151
|
const inicio = Date.now();
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
152
|
+
const PAR = require('./paralelo.js');
|
|
153
|
+
const total = ate - from;
|
|
154
|
+
const n = partes !== undefined ? Math.max(1, Number(partes)) : Math.max(1, Math.min(PAR.quantos(), Math.floor(total / (tl.fps * 2))));
|
|
155
|
+
const codec = argsDoCodec(codecDeVideo(), crf);
|
|
156
|
+
const extra = ger.feitos ? { gerados: ger.feitos, geradosS: ger.tookS } : {};
|
|
157
|
+
if (n === 1) {
|
|
158
|
+
const tx = await camadasDeTexto(pasta, tl, { from, to: ate, escala });
|
|
159
|
+
const g = F.montarGrafo(tl, { from, to: ate, camadasDeTexto: tx.camadas, escala });
|
|
160
|
+
const r = await rodarGrafo(pasta, g, [
|
|
161
|
+
'-map', g.saidaV, '-map', g.saidaA, '-t', F.num(g.segundos), '-r', String(tl.fps),
|
|
162
|
+
...codec, '-pix_fmt', 'yuv420p', '-c:a', 'aac', '-b:a', '192k', '-movflags', '+faststart', destino,
|
|
163
|
+
]);
|
|
164
|
+
if (!r.ok) return { ok: false, error: 'o ffmpeg falhou', detalhe: r.erro.slice(-600), grafo: r.grafo };
|
|
165
|
+
return { ok: true, file: destino, s: T.r2(g.segundos), size: `${g.W}x${g.H}`, mb: T.r2(fs.statSync(destino).size / 1048576),
|
|
166
|
+
tookS: Math.round((Date.now() - inicio) / 100) / 10, partes: 1, ...extra, ...(tx.avisos.length ? { avisos: tx.avisos } : {}) };
|
|
167
|
+
}
|
|
168
|
+
const tmp = path.join(pasta, 'cache', `export-${process.pid}`);
|
|
169
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
170
|
+
fs.mkdirSync(tmp, { recursive: true });
|
|
171
|
+
const cortes = Array.from({ length: n + 1 }, (_, i) => from + Math.round((total * i) / n));
|
|
172
|
+
// Os PNGs do texto antes, em fila: dois pedaços desenhando o mesmo estado ao mesmo tempo se atropelariam.
|
|
173
|
+
const avisos = new Set();
|
|
174
|
+
const pedacos = [];
|
|
175
|
+
for (let i = 0; i < n; i++) {
|
|
176
|
+
const tx = await camadasDeTexto(pasta, tl, { from: cortes[i], to: cortes[i + 1], escala });
|
|
177
|
+
tx.avisos.forEach((a) => avisos.add(a));
|
|
178
|
+
pedacos.push({ de: cortes[i], ate: cortes[i + 1], tx, arq: path.join(tmp, `v${String(i).padStart(3, '0')}.mp4`) });
|
|
179
|
+
}
|
|
180
|
+
const res = await PAR.emParalelo([
|
|
181
|
+
...pedacos.map((p, i) => () => {
|
|
182
|
+
const g = F.montarGrafo(tl, { from: p.de, to: p.ate, camadasDeTexto: p.tx.camadas, escala, audio: false });
|
|
183
|
+
return rodarGrafo(pasta, g, ['-map', g.saidaV, '-frames:v', String(p.ate - p.de), '-r', String(tl.fps), ...codec, '-pix_fmt', 'yuv420p', p.arq], `-v${i}`);
|
|
184
|
+
}),
|
|
185
|
+
() => {
|
|
186
|
+
const g = F.montarGrafo(tl, { from, to: ate, escala, video: false });
|
|
187
|
+
return rodarGrafo(pasta, g, ['-map', g.saidaA, '-t', F.num(g.segundos), '-c:a', 'aac', '-b:a', '192k', path.join(tmp, 'audio.m4a')], '-a');
|
|
188
|
+
},
|
|
189
|
+
], n + 1);
|
|
190
|
+
const ruim = res.find((r) => !r.ok);
|
|
191
|
+
if (ruim) return { ok: false, error: 'o ffmpeg falhou', detalhe: ruim.erro.slice(-600), grafo: ruim.grafo };
|
|
192
|
+
const lista = path.join(tmp, 'lista.txt');
|
|
193
|
+
fs.writeFileSync(lista, pedacos.map((p) => `file '${p.arq.replace(/'/g, "'\\''")}'`).join('\n') + '\n');
|
|
194
|
+
const j = await video.rodar(video.ffmpeg(), ['-hide_banner', '-loglevel', 'error', '-y', '-f', 'concat', '-safe', '0', '-i', lista,
|
|
195
|
+
'-i', path.join(tmp, 'audio.m4a'), '-map', '0:v', '-map', '1:a', '-c', 'copy', '-movflags', '+faststart', destino]);
|
|
196
|
+
if (!j.ok) return { ok: false, error: 'não juntei os pedaços do export', detalhe: j.erro.slice(-400) };
|
|
197
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
198
|
+
return { ok: true, file: destino, s: T.r2(total / tl.fps), size: `${T.par(tl.width * escala)}x${T.par(tl.height * escala)}`,
|
|
199
|
+
mb: T.r2(fs.statSync(destino).size / 1048576), tookS: Math.round((Date.now() - inicio) / 100) / 10, partes: n, ...extra,
|
|
200
|
+
...(avisos.size ? { avisos: [...avisos] } : {}) };
|
|
150
201
|
}
|
|
151
202
|
|
|
152
203
|
/** Os quadros pedidos, com a grade 0–1 e o número do quadro, numa folha só. */
|
|
153
204
|
async function inspecionar(pasta, tl, { frames, escala = 0.5, out } = {}) {
|
|
154
205
|
const c = conferir();
|
|
155
206
|
if (!c.ok) return c;
|
|
207
|
+
const ger = await require('./gerados.js').garantir(pasta, tl);
|
|
208
|
+
if (!ger.ok) return ger;
|
|
156
209
|
const dur = T.duracao(tl);
|
|
157
210
|
const lista = [...new Set(frames.map((f) => Math.max(0, Math.min(dur - 1, Math.round(Number(f))))))].slice(0, 12);
|
|
158
211
|
const dir = path.join(pasta, 'cache', 'inspect');
|
package/lib/video/texto.js
CHANGED
|
@@ -36,7 +36,7 @@ function palavrasDo(c) {
|
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
/** O estado de UM clipe de texto no quadro local `lf` — ou null (não aparece). */
|
|
39
|
-
function estado(tl, c, lf) {
|
|
39
|
+
function estado(tl, c, lf, pos = null) {
|
|
40
40
|
const st = { ...ESTILO_PADRAO, ...(c.text.style || {}) };
|
|
41
41
|
const d = c.durationFrames;
|
|
42
42
|
const anim = c.text.animation || 'none';
|
|
@@ -85,7 +85,7 @@ function estado(tl, c, lf) {
|
|
|
85
85
|
const W = tl.width;
|
|
86
86
|
const H = tl.height;
|
|
87
87
|
const item = {
|
|
88
|
-
cx: r3(c.transform.cx * W), cy: r3(c.transform.cy * H), w: Math.round(c.transform.w * W),
|
|
88
|
+
cx: r3((pos ? pos.cx : c.transform.cx) * W), cy: r3((pos ? pos.cy : c.transform.cy) * H), w: Math.round(c.transform.w * W),
|
|
89
89
|
size: Math.round(st.size * H), font: st.font, color: st.color, align: st.align,
|
|
90
90
|
stroke: st.stroke, sw: Math.round(st.strokeWidth * st.size * H), bg: st.bg,
|
|
91
91
|
scale: r3(scale), alpha: Math.round(alpha * 100) / 100, words,
|
|
@@ -97,8 +97,10 @@ function estado(tl, c, lf) {
|
|
|
97
97
|
/**
|
|
98
98
|
* Os estados de uma faixa na janela [from, to): segmentos consecutivos com o
|
|
99
99
|
* mesmo desenho viram um só. Devolve {segmentos:[{dur, chave}], desenhos}.
|
|
100
|
+
* `posicao(clipe, quadro)` → {cx, cy} | null: a legenda que SEGUE alguém
|
|
101
|
+
* (a cabeça) troca o lugar do transform pelo que ela devolver, quadro a quadro.
|
|
100
102
|
*/
|
|
101
|
-
function estadosDaFaixa(tl, faixa, { from = 0, to, escala = 1 } = {}) {
|
|
103
|
+
function estadosDaFaixa(tl, faixa, { from = 0, to, escala = 1, posicao = null } = {}) {
|
|
102
104
|
const ate = to === undefined ? T.duracao(tl) : to;
|
|
103
105
|
const clips = faixa.clips.filter((c) => !c.hidden && T.fim(c) > from && c.startFrame < ate);
|
|
104
106
|
const escalar = (it) => (escala === 1 ? it : {
|
|
@@ -114,7 +116,7 @@ function estadosDaFaixa(tl, faixa, { from = 0, to, escala = 1 } = {}) {
|
|
|
114
116
|
for (let j = k; j < clips.length && clips[j].startFrame <= f; j++) {
|
|
115
117
|
const c = clips[j];
|
|
116
118
|
if (f >= T.fim(c)) continue;
|
|
117
|
-
const it = estado(tl, c, f - c.startFrame);
|
|
119
|
+
const it = estado(tl, c, f - c.startFrame, posicao ? posicao(c, f) : null);
|
|
118
120
|
if (it) itens.push(escalar(it));
|
|
119
121
|
}
|
|
120
122
|
const json = JSON.stringify(itens);
|
package/lib/video/timeline.js
CHANGED
|
@@ -267,6 +267,7 @@ function compacto(tl, c, track) {
|
|
|
267
267
|
if (Object.keys(tf).length) o.xf = tf;
|
|
268
268
|
if (Object.values(c.crop).some((v) => v)) o.crop = c.crop;
|
|
269
269
|
if (c.fit !== 'cover') o.fit = c.fit;
|
|
270
|
+
if (c.papel) o.role = c.papel;
|
|
270
271
|
const kf = Object.keys(c.keyframes || {}).filter((k) => c.keyframes[k].length);
|
|
271
272
|
if (kf.length) o.kf = kf;
|
|
272
273
|
if (c.effects.length) o.fx = c.effects.map((e) => e.type);
|
|
@@ -289,15 +290,22 @@ function vista(tl, { from = 0, to = Infinity } = {}) {
|
|
|
289
290
|
});
|
|
290
291
|
const f = { i, type: t.type, n: t.clips.length, clips };
|
|
291
292
|
if (t.muted) f.muted = true;
|
|
293
|
+
if (t.role) f.role = t.role;
|
|
292
294
|
return f;
|
|
293
295
|
});
|
|
294
296
|
const assets = {};
|
|
295
297
|
for (const a of Object.values(tl.assets)) {
|
|
296
298
|
assets[a.id] = { nome: a.nome, kind: a.kind, s: r2(a.seconds || 0) };
|
|
297
299
|
if (a.width) assets[a.id].size = `${a.width}x${a.height}`;
|
|
298
|
-
if (!a.hasAudio && a.kind === 'video') assets[a.id].mudo = true;
|
|
300
|
+
if (!a.hasAudio && a.kind === 'video' && !a.gerado) assets[a.id].mudo = true;
|
|
301
|
+
if (a.gerado) assets[a.id].gerado = a.gerado.tipo;
|
|
299
302
|
}
|
|
300
303
|
const out = { fps: tl.fps, size: `${tl.width}x${tl.height}`, dur: duracao(tl), durS: r2(segundos(tl, duracao(tl))), assets, tracks };
|
|
304
|
+
if (tl.fx) {
|
|
305
|
+
// As receitas dos efeitos, em segundos da ORIGEM (o que o rastro mediu).
|
|
306
|
+
out.fx = {};
|
|
307
|
+
for (const [k, v] of Object.entries(tl.fx)) out.fx[k] = { objetos: (v.objetos || []).map((o) => `${o.id}:${o.modelo}@${o.mao} ${o.de}-${o.ate}s`), ...(v.fusao ? { fusao: v.fusao } : {}) };
|
|
308
|
+
}
|
|
301
309
|
if (tl.markers.length) out.markers = tl.markers.filter((m) => m.frame >= from && m.frame < to).slice(0, 60);
|
|
302
310
|
return out;
|
|
303
311
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.1.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/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"
|
|
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/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/hibrido.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"
|
|
12
12
|
},
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=18.17.0"
|
|
@@ -25,4 +25,7 @@ superlativo sem número.
|
|
|
25
25
|
Número concreto vence adjetivo: "responde em 2 segundos" vale mais que
|
|
26
26
|
"ultrarrápido".
|
|
27
27
|
|
|
28
|
+
**Revisando um texto pronto**: aponte o trecho que soa errado e reescreva só
|
|
29
|
+
ele. O resto, que já funciona, fica como está.
|
|
30
|
+
|
|
28
31
|
Não use emoji para dar emoção a um texto que não tem nenhuma.
|
|
@@ -12,7 +12,8 @@ a correção. Primeiro o que impede alguém de usar; acabamento vai no fim.
|
|
|
12
12
|
- O que é clicável é `button` ou `a`, com hover, estado pressionado e foco
|
|
13
13
|
visível (`:focus-visible`).
|
|
14
14
|
- Alvo de toque de pelo menos 44×44px. Nada importante escondido só no hover.
|
|
15
|
-
-
|
|
15
|
+
- Todo clique responde em até 200 ms. Ação que demora mostra que está
|
|
16
|
+
trabalhando e não pode ser enviada duas vezes.
|
|
16
17
|
- Ação que apaga pede confirmação ou oferece desfazer.
|
|
17
18
|
- O endereço guarda o estado (aba, filtro, página): o voltar do navegador e o
|
|
18
19
|
link compartilhado levam ao mesmo lugar.
|
|
@@ -32,6 +33,7 @@ novo) e cheio (texto longo quebra ou termina em `…`; mil itens não travam).
|
|
|
32
33
|
**Layout**
|
|
33
34
|
- De 360px a 1440px sem rolagem para o lado; respeita a área segura do celular
|
|
34
35
|
(`env(safe-area-inset-bottom)`).
|
|
36
|
+
- Tela cheia é `100dvh`, nunca `100vh` (no celular, `vh` some sob a barra).
|
|
35
37
|
- Imagem com `width`/`height` ou `aspect-ratio`, para a página não pular.
|
|
36
38
|
- Número em coluna com `tabular-nums`; título com `text-wrap: balance`.
|
|
37
39
|
|
|
@@ -43,5 +45,9 @@ rolagem acompanham) e contraste de 4.5:1 nos dois temas. Animação só em
|
|
|
43
45
|
`transform` e `opacity`, curta (150 a 300ms) e desligada em
|
|
44
46
|
`prefers-reduced-motion`. Nunca `transition: all`.
|
|
45
47
|
|
|
48
|
+
**Pior caso** (teste com o dado ruim): Texto enorme (`min-width: 0` e
|
|
49
|
+
`overflow-wrap: anywhere`); Lista vazia; largura de 320px; Rede lenta (DevTools,
|
|
50
|
+
Slow 3G); Mil itens (paginar ou virtualizar).
|
|
51
|
+
|
|
46
52
|
**Feche com a contagem**: quantos impedem o uso, quantos atrapalham, quantos são
|
|
47
53
|
acabamento. E conserte nessa mesma ordem.
|
|
@@ -54,13 +54,14 @@ Em vídeo de produto, anúncio e vinheta, escreva **uma cena de 8 a 15 s** no
|
|
|
54
54
|
- **`forma: "traco"`**: forma livre por `caminho` (`d` de SVG, caixa 0 0 100
|
|
55
55
|
100, M/L/C/Q/Z). `caminho` e `cor` animam:
|
|
56
56
|
`"caminho": [{"t":1.6,"v":BOLA},{"t":2.5,"v":FOGUETE,"e":"elastico"}]`.
|
|
57
|
-
- **`tipo: "grupo"`**: elementos que andam juntos, ou a tela dentro da tela
|
|
58
|
-
(`"recorte": true`). Filhos contam tempo e posição a partir do grupo.
|
|
59
57
|
- **A troca é no mesmo lugar e instante**: o foguete some onde o ícone nasce; o
|
|
60
58
|
app nasce do tamanho e da COR do ícone.
|
|
61
59
|
|
|
62
|
-
Por roteiro, o
|
|
63
|
-
`marca`, `lancamento
|
|
60
|
+
Por roteiro, o padrão é `hibrido` (dê `busca` em inglês por parte); `gerativo` (o motion), `vox` (explicar; dê
|
|
61
|
+
`destaque` e `fonte`), `marca`, `lancamento` pelo nome; `visual` troca a peça de uma parte.
|
|
62
|
+
|
|
63
|
+
Pedido de motion (ou sem referências): `estilo: "gerativo"`, só o roteiro
|
|
64
|
+
(`fala`, `destaque`); `roteiro.semente` sorteia outro, `modelo3d` traz o 3D.
|
|
64
65
|
|
|
65
66
|
## Cor e tipo
|
|
66
67
|
|
|
@@ -27,6 +27,12 @@ Tudo pelo `gh` (GitHub CLI) e pelo `git`, rodando no terminal do projeto.
|
|
|
27
27
|
4. `gh pr create --fill` (ou `--title` e `--body` dizendo o que e por quê).
|
|
28
28
|
Diga o link do PR.
|
|
29
29
|
|
|
30
|
+
**Segredo:** `.env` só existe na máquina. Nunca crie, edite ou renomeie
|
|
31
|
+
`.env` pelo site do GitHub (renomear `.env.example` para `.env` lá já vazou
|
|
32
|
+
chave em repositório público). O `.env.example` leva só os nomes, sem valor.
|
|
33
|
+
Antes do push: `git ls-files | grep '\.env$'` tem de vir vazio. Vazou? Troque a
|
|
34
|
+
chave na hora; apagar o arquivo não basta, ela fica no histórico.
|
|
35
|
+
|
|
30
36
|
**Nunca** faça `push --force` em `main`, nunca apague branch dos outros, nunca
|
|
31
37
|
commite `.env`. Conflito: resolva lendo os dois lados, não escolhendo um às
|
|
32
38
|
cegas.
|
|
@@ -31,6 +31,9 @@ ela quem entra na conta dela.
|
|
|
31
31
|
**3. Confira:** abra a URL publicada com browser_open e leia a página. Só diga
|
|
32
32
|
que está no ar se ela abriu com o conteúdo certo. Termine dizendo o LINK.
|
|
33
33
|
|
|
34
|
+
**Link compartilhável:** `og:image` com URL absoluta (`https://.../capa.png`),
|
|
35
|
+
nunca `/capa.png`; sem isso o link sai sem imagem no WhatsApp.
|
|
36
|
+
|
|
34
37
|
**Nunca** publique `.env`, chave ou senha: confira se `.gitignore` e o
|
|
35
38
|
`.vercelignore`/`.netlifyignore` os deixam de fora. Variáveis secretas vão no
|
|
36
39
|
painel do serviço (ou `vercel env add`), não no código.
|
|
@@ -26,6 +26,8 @@ tudo com a mesma duração; `transition: all`; texto piscando; nada que responda
|
|
|
26
26
|
que acompanha o scroll (parallax, barra de progresso, imagem que cresce), use
|
|
27
27
|
`animation-timeline: view()` e deixe a página parada onde isso não existe (`@supports`).
|
|
28
28
|
- **Só `transform` e `opacity`.** Animar `top`, `width`, `height` ou `box-shadow` trava no celular.
|
|
29
|
+
Confira com grep: zero `transition: all` no CSS.
|
|
30
|
+
- **Seção de tela cheia** usa `100dvh`, e não `100vh`, pra não ficar atrás da barra do celular.
|
|
29
31
|
- **Troca de página ou de estado:** a View Transitions API (`document.startViewTransition`), com o
|
|
30
32
|
caminho sem animação quando o navegador não tiver.
|
|
31
33
|
- **Biblioteca só se precisar.** CSS e um pouco de JS resolvem quase tudo. Timeline longa ou texto
|
package/studio/narrar.py
CHANGED
|
@@ -256,13 +256,16 @@ def comando_falar(args):
|
|
|
256
256
|
# tempo de CADA PALAVRA volta junto (WordBoundary do edge-tts): é o que
|
|
257
257
|
# sincroniza a tipografia com a voz.
|
|
258
258
|
REGISTRO = {
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
"
|
|
263
|
-
"
|
|
264
|
-
"
|
|
265
|
-
"
|
|
259
|
+
# 10.0: "os vídeos estão muito lentos" (29/09). Ritmo de anúncio por
|
|
260
|
+
# padrão — ninguém fala devagar num reels — e o respiro entre as partes
|
|
261
|
+
# caiu para o de uma vírgula: o corte seco é que dá energia.
|
|
262
|
+
"gancho": {"ritmo": "+18%", "tom": "+4Hz", "respiro": 0.12},
|
|
263
|
+
"numero": {"ritmo": "+8%", "tom": "+1Hz", "respiro": 0.18},
|
|
264
|
+
"contraste": {"ritmo": "+14%", "tom": "+2Hz", "respiro": 0.15},
|
|
265
|
+
"lista": {"ritmo": "+14%", "tom": "+2Hz", "respiro": 0.12},
|
|
266
|
+
"citacao": {"ritmo": "+6%", "tom": "+0Hz", "respiro": 0.2},
|
|
267
|
+
"cta": {"ritmo": "+16%", "tom": "+4Hz", "respiro": 0.1},
|
|
268
|
+
"normal": {"ritmo": "+14%", "tom": "+2Hz", "respiro": 0.12},
|
|
266
269
|
}
|
|
267
270
|
|
|
268
271
|
|