primocode 8.41.0 → 8.42.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 +8 -2
- package/bin/primocode.js +20 -22
- package/lib/act.js +25 -0
- package/lib/diretor.js +5 -2
- package/package.json +2 -2
- package/studio/__pycache__/edge.cpython-311.pyc +0 -0
- package/studio/__pycache__/elevenlabs.cpython-311.pyc +0 -0
- package/studio/arquivos/corte-teste-de-camera-fb823.json +57 -0
- package/studio/ferramentas/corte.md +42 -0
- package/studio/studio.log +57 -0
- package/studio/web/core.css +178 -0
- package/studio/web/home.html +91 -24
- package/studio/web/js/editor.js +17 -0
- package/studio/web/js/render.js +123 -2
- package/studio/web/render.css +18 -0
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# PrimoCode v8.
|
|
1
|
+
# PrimoCode v8.42.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** —
|
|
@@ -11,7 +11,7 @@ do Claude ou do GPT escolhe isso na abertura, com as setinhas, e passa a usar a
|
|
|
11
11
|
assinatura dele.
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
▐▛███▜▌ Primo Code v8.
|
|
14
|
+
▐▛███▜▌ Primo Code v8.42.1
|
|
15
15
|
▝▜█████▛▘ Bem-vindo de volta, Joel
|
|
16
16
|
▘▘ ▝▝ ~/primocode · /dir <pasta> muda
|
|
17
17
|
```
|
|
@@ -50,6 +50,12 @@ crédito; a identidade de uma empresa — logo, cor, paleta, fontes e frase —
|
|
|
50
50
|
arquivo, e os créditos saem prontos para a cena final. Recorte de fundo com
|
|
51
51
|
`rembg`, quando instalado.
|
|
52
52
|
|
|
53
|
+
O quadro também se move: cada cena aceita uma **câmera** (`aproximar`,
|
|
54
|
+
`empurrar`, `pan-direita`, `balanco`…) e cada elemento uma **profundidade**, e
|
|
55
|
+
aí as camadas andam em velocidades diferentes — é o que separa motion de slide
|
|
56
|
+
animado. Junto com as chaves em 3D, as curvas com nome e o som por elemento,
|
|
57
|
+
dá para montar no navegador o que antes pedia After Effects.
|
|
58
|
+
|
|
53
59
|
O motor não é gosto: vídeo curto sai pelo estúdio, em segundos, sem instalar
|
|
54
60
|
nada; vídeo longo, canal alfa, ProRes ou fps alto viram projeto **Remotion** na
|
|
55
61
|
sua pasta — e a resposta sempre diz por que escolheu.
|
package/bin/primocode.js
CHANGED
|
@@ -1332,31 +1332,29 @@ async function handleSlash(text) {
|
|
|
1332
1332
|
abrir: (u) => tools.openInBrowser(u),
|
|
1333
1333
|
contar: (info) => {
|
|
1334
1334
|
sp.stop();
|
|
1335
|
-
|
|
1336
|
-
|
|
1335
|
+
/* O CÓDIGO NÃO APARECE MAIS NO TERMINAL.
|
|
1336
|
+
*
|
|
1337
|
+
* "Pode tirar essa parte no login onde aparece o código
|
|
1338
|
+
* — o terminal mandou o código, pode tirar."
|
|
1339
|
+
*
|
|
1340
|
+
* A página do token já resolve o pareamento sozinha: ela
|
|
1341
|
+
* pergunta o código ao 127.0.0.1 desta máquina, e onde o
|
|
1342
|
+
* navegador bloqueia isso (Safari) ela oferece "Colar" —
|
|
1343
|
+
* o código continua indo para a área de transferência,
|
|
1344
|
+
* que é o caminho sem leitura. Oito letras impressas aqui
|
|
1345
|
+
* eram uma tarefa para a pessoa que o produto já não
|
|
1346
|
+
* precisa, e a tela ficava dizendo que talvez precisasse.
|
|
1347
|
+
*
|
|
1348
|
+
* O código continua existindo no protocolo (lib/conta.js)
|
|
1349
|
+
* — some da TELA, não do pareamento. */
|
|
1337
1350
|
console.log(c.muted(' Abrindo no navegador:'));
|
|
1338
1351
|
console.log(' ' + c.brand(info.url.replace(/[?&]porta=\d+/, '')));
|
|
1339
1352
|
console.log('');
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
nada, porque ele já libera". Mostrar as oito letras
|
|
1346
|
-
grandes aqui era pedir para ler o que ninguém precisa
|
|
1347
|
-
mais; elas ficam numa linha apagada, só para o caso
|
|
1348
|
-
de o navegador bloquear o 127.0.0.1 (Safari) e a
|
|
1349
|
-
página voltar a pedir. */
|
|
1350
|
-
console.log(' ' + c.white('Entre com a sua conta na página que abriu — o resto é automático.'));
|
|
1351
|
-
console.log(c.dim(` Se a página pedir um código: ${meio}${info.copiado ? ' (já copiado)' : ''}.`));
|
|
1352
|
-
} else {
|
|
1353
|
-
/* Sem porta local (ocupadas, ou bloqueadas), o código
|
|
1354
|
-
volta a ser o caminho — grande, partido em dois,
|
|
1355
|
-
porque é o que se lê de uma tela e se confere noutra. */
|
|
1356
|
-
console.log(' ' + c.bold(c.brand(meio.split('').join(' '))));
|
|
1357
|
-
console.log('');
|
|
1358
|
-
console.log(c.dim(' ' + (info.copiado ? 'O código já está copiado — cole na página.'
|
|
1359
|
-
: 'Digite o código na página.')));
|
|
1353
|
+
console.log(' ' + c.white('Entre com a sua conta na página que abriu — o resto é automático.'));
|
|
1354
|
+
if (!info.porta && info.copiado) {
|
|
1355
|
+
// Sem a porta local, quem fecha o pareamento é a área de
|
|
1356
|
+
// transferência: a página pede um toque em "Colar".
|
|
1357
|
+
console.log(c.dim(' Se a página pedir para colar, é só colar — já está na sua área de transferência.'));
|
|
1360
1358
|
}
|
|
1361
1359
|
console.log(c.dim(' Esperando o navegador…'));
|
|
1362
1360
|
},
|
package/lib/act.js
CHANGED
|
@@ -460,6 +460,31 @@ async function runAct(userPrompt, opts) {
|
|
|
460
460
|
if (m && m.role && m.content) messages.push({ role: m.role, content: m.content });
|
|
461
461
|
}
|
|
462
462
|
}
|
|
463
|
+
/* ── O GATILHO DO DIRETOR ────────────────────────────────────────────
|
|
464
|
+
*
|
|
465
|
+
* "A IA identifica que precisa criar vídeo, ativa o agente, esse agente
|
|
466
|
+
* faz a estrutura e manda pra IA, e depois ela só faz o vídeo."
|
|
467
|
+
*
|
|
468
|
+
* Deixar isso só escrito no catálogo não basta: com um pedido curto
|
|
469
|
+
* ("faz um vídeo do produto") o modelo pula direto para o studio_criar,
|
|
470
|
+
* porque criar é o que ele sabe fazer. Quem reconhece o pedido é uma
|
|
471
|
+
* regra (diretor.precisaDeProducao), e ela entra como ORDEM na conversa —
|
|
472
|
+
* não como conselho no meio de cinquenta ferramentas.
|
|
473
|
+
*
|
|
474
|
+
* Num subagente não entra: ele já está fazendo uma parte de um plano, e
|
|
475
|
+
* um diretor dentro do diretor é uma volta a mais para o mesmo lugar. */
|
|
476
|
+
if (!subagent && profundidade === 0) {
|
|
477
|
+
const producao = require('./diretor.js').precisaDeProducao(userPrompt);
|
|
478
|
+
if (producao.producao) {
|
|
479
|
+
messages.push({ role: 'system', content:
|
|
480
|
+
'ESTE PEDIDO É DE PRODUÇÃO AUDIOVISUAL. Antes de criar qualquer peça, chame '
|
|
481
|
+
+ `planejar_producao com o pedido do usuário (formato "${producao.formato}"). `
|
|
482
|
+
+ 'Ele devolve a pauta — roteiro, narração falada, voz, câmera e a mídia a buscar — '
|
|
483
|
+
+ 'e é ela que você produz, cena a cena. Não comece pelo studio_criar: o roteiro '
|
|
484
|
+
+ 'escrito no meio do JSON da cena vira legenda de slide, que é o que se quer evitar.' });
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
463
488
|
messages.push({ role: 'user', content: userPrompt });
|
|
464
489
|
|
|
465
490
|
let finalText = '';
|
package/lib/diretor.js
CHANGED
|
@@ -116,7 +116,7 @@ function ritmoPara(segundos) {
|
|
|
116
116
|
// ── 3. o plano ───────────────────────────────────────────────────────────
|
|
117
117
|
|
|
118
118
|
const CAMPOS_CENA = ['titulo', 'narracao', 'duracao', 'layout', 'fundo', 'transicao',
|
|
119
|
-
'elementos', 'midia', 'chaves', 'notas'];
|
|
119
|
+
'elementos', 'midia', 'camera', 'chaves', 'notas'];
|
|
120
120
|
|
|
121
121
|
/**
|
|
122
122
|
* Normaliza o que o diretor devolveu e aponta o que ficou faltando.
|
|
@@ -206,6 +206,7 @@ Devolva SÓ um bloco \`\`\`json com este formato, sem texto antes nem depois:
|
|
|
206
206
|
"fundo": "malha | aurora | raios | ondas | nevoa | vinheta, uma cor, ou {\\"imagem\\":\\"<da lista midia>\\"}",
|
|
207
207
|
"transicao": "corte | fade | deslizar | subir | zoom | limpar | persiana | flash",
|
|
208
208
|
"midia": [ { "busca": "o que procurar", "tipo": "imagem | logo", "papel": "fundo | destaque | selo", "semFundo": false } ],
|
|
209
|
+
"camera": "aproximar | afastar | pan-direita | pan-esquerda | subir | descer | empurrar | balanco | nenhuma",
|
|
209
210
|
"chaves": "descreva em uma frase o movimento principal desta cena (o que entra, o que atravessa, o que cresce)",
|
|
210
211
|
"notas": "o que o produtor precisa saber e não está acima"
|
|
211
212
|
}
|
|
@@ -219,7 +220,8 @@ REGRAS QUE SEPARAM UM VÍDEO BOM DE UM GENÉRICO:
|
|
|
219
220
|
4. NÚMERO É CONCRETO. Nada de "muitos clientes": ou tem o número, ou a frase muda.
|
|
220
221
|
5. RITMO VARIADO. Cenas de durações diferentes e transições diferentes. Tudo em fade parece slide.
|
|
221
222
|
6. MÍDIA COM PROPÓSITO. Só peça imagem ou logo quando ela PROVA algo. Diga o termo de busca de verdade ("painel solar em telhado residencial", não "imagem legal").
|
|
222
|
-
7. MOVIMENTO. Em cada cena diga em "chaves" o movimento principal — é ele que tira a cara de apresentação parada
|
|
223
|
+
7. MOVIMENTO. Em cada cena diga em "chaves" o movimento principal — é ele que tira a cara de apresentação parada.
|
|
224
|
+
8. CÂMERA COM PARCIMÔNIA. Movimento de quadro em UMA cena a cada duas ou três ("camera": "nenhuma" nas outras): o que dá ritmo é alternar cena parada e cena que se move. Em cena com foto de fundo, prefira "aproximar" ou "empurrar".`;
|
|
223
225
|
};
|
|
224
226
|
|
|
225
227
|
const REVISAO = (defeitos) => `O plano voltou com defeitos de FORMA. Corrija SÓ o que está listado e devolva o JSON inteiro de novo, no mesmo formato:
|
|
@@ -288,6 +290,7 @@ function pauta(plano) {
|
|
|
288
290
|
linhas.push(`CENA ${i + 1} — ${c.duracao}s · layout ${c.layout || 'titulo'} · entra em ${c.transicao}`);
|
|
289
291
|
if (c.titulo) linhas.push(` na tela: ${c.titulo}`);
|
|
290
292
|
linhas.push(` narração: ${c.narracao}`);
|
|
293
|
+
if (c.camera && c.camera !== 'nenhuma') linhas.push(` câmera: ${c.camera}`);
|
|
291
294
|
if (c.chaves) linhas.push(` movimento: ${c.chaves}`);
|
|
292
295
|
(c.midia || []).forEach((m) => linhas.push(
|
|
293
296
|
` mídia: ${m.tipo || 'imagem'} "${m.busca}" como ${m.papel || 'destaque'}${m.semFundo ? ' (sem fundo)' : ''}`));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "primocode",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.42.1",
|
|
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/modo.test.js && node test/pasta.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/effort.test.js && node test/claude-engine.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/studio-spec.test.js && node test/studio-versoes.test.js && node test/voz.test.js && node test/editor.test.js && node test/estudio-projeto.test.js && node test/vocabulario.test.js && node test/video-export.test.js && node test/monotonia.test.js && node test/clipe.test.js && node test/midia-envio.test.js && node test/pausas.test.js && node test/fala-repetida.test.js && node test/trilha.test.js && node test/anuncio.test.js && node test/pipeline.test.js && node test/app.test.js && node test/elementos.test.js && node test/remotion.test.js && node test/aplicativo.test.js && node test/conta.test.mjs && node test/nuvem.test.js && node test/codex.test.js && node test/chaves.test.js && node test/prazo.test.js && node test/primeira-vez.test.js && node test/janela.test.js && node test/pareamento.test.js && node test/fala.test.js && node test/especialistas.test.js && node test/icone.test.js && node test/exportar.test.js && node test/ate-o-fim.test.js && node test/motion.test.js && node test/audio-marca.test.js && node test/diretor.test.js"
|
|
11
|
+
"test": "node test/tools.test.js && node test/ui.test.js && node test/modo.test.js && node test/pasta.test.js && node test/catalogo.test.js && node test/desktop.test.js && node test/effort.test.js && node test/claude-engine.test.js && node test/regressao.test.js && node test/memoria-conversa.test.js && node test/repeticao.test.js && node test/referencia.test.js && node test/parar.test.js && node test/continuar.test.js && node test/studio-spec.test.js && node test/studio-versoes.test.js && node test/voz.test.js && node test/editor.test.js && node test/estudio-projeto.test.js && node test/vocabulario.test.js && node test/video-export.test.js && node test/monotonia.test.js && node test/clipe.test.js && node test/midia-envio.test.js && node test/pausas.test.js && node test/fala-repetida.test.js && node test/trilha.test.js && node test/anuncio.test.js && node test/pipeline.test.js && node test/app.test.js && node test/elementos.test.js && node test/remotion.test.js && node test/aplicativo.test.js && node test/conta.test.mjs && node test/nuvem.test.js && node test/codex.test.js && node test/chaves.test.js && node test/prazo.test.js && node test/primeira-vez.test.js && node test/janela.test.js && node test/pareamento.test.js && node test/fala.test.js && node test/especialistas.test.js && node test/icone.test.js && node test/exportar.test.js && node test/ate-o-fim.test.js && node test/motion.test.js && node test/audio-marca.test.js && node test/diretor.test.js && node test/camera.test.js"
|
|
12
12
|
},
|
|
13
13
|
"engines": {
|
|
14
14
|
"node": ">=18.17.0"
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "corte-teste-de-camera-fb823",
|
|
3
|
+
"ferramenta": "corte",
|
|
4
|
+
"titulo": "Teste de câmera",
|
|
5
|
+
"doc": {
|
|
6
|
+
"tema": "meia-noite",
|
|
7
|
+
"largura": 1920,
|
|
8
|
+
"altura": 1080,
|
|
9
|
+
"acabamento": "grao",
|
|
10
|
+
"cenas": [
|
|
11
|
+
{
|
|
12
|
+
"layout": "capa",
|
|
13
|
+
"duracao": 4,
|
|
14
|
+
"fundo": "aurora",
|
|
15
|
+
"transicao": "fade",
|
|
16
|
+
"camera": "empurrar",
|
|
17
|
+
"elementos": [
|
|
18
|
+
{
|
|
19
|
+
"tipo": "texto",
|
|
20
|
+
"papel": "chapeu",
|
|
21
|
+
"texto": "Câmera",
|
|
22
|
+
"paralaxe": 1.3
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"tipo": "texto",
|
|
26
|
+
"papel": "titulo",
|
|
27
|
+
"texto": "O quadro se move"
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"tipo": "texto",
|
|
31
|
+
"papel": "apoio",
|
|
32
|
+
"texto": "e as camadas andam em velocidades diferentes.",
|
|
33
|
+
"paralaxe": 0.5
|
|
34
|
+
}
|
|
35
|
+
]
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
"layout": "numeros",
|
|
39
|
+
"duracao": 3.5,
|
|
40
|
+
"fundo": "malha",
|
|
41
|
+
"transicao": "zoom",
|
|
42
|
+
"camera": "aproximar",
|
|
43
|
+
"elementos": [
|
|
44
|
+
{
|
|
45
|
+
"tipo": "numeros",
|
|
46
|
+
"itens": [
|
|
47
|
+
"+38% | em 12 meses"
|
|
48
|
+
],
|
|
49
|
+
"contar": true,
|
|
50
|
+
"som": "ding"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"atualizado": "2026-09-11T18:39:36+00:00"
|
|
57
|
+
}
|
|
@@ -796,6 +796,48 @@ Duas chaves já bastam. Quem tem `chaves` ignora `anim` — as duas disputariam
|
|
|
796
796
|
mesmo movimento, e a chave é mais específica. Use em **um ou dois** elementos
|
|
797
797
|
por vídeo: tudo se mexendo ao mesmo tempo cansa e esconde a mensagem.
|
|
798
798
|
|
|
799
|
+
## Câmera da cena — o quadro também se move
|
|
800
|
+
|
|
801
|
+
`chaves` move um elemento. `camera` move o **quadro inteiro**: é o que separa
|
|
802
|
+
motion de slide animado. Na cena:
|
|
803
|
+
|
|
804
|
+
```json
|
|
805
|
+
{ "layout": "capa", "duracao": 4, "camera": "aproximar" }
|
|
806
|
+
```
|
|
807
|
+
|
|
808
|
+
Movimentos com nome: `aproximar` · `afastar` · `pan-direita` · `pan-esquerda` ·
|
|
809
|
+
`subir` · `descer` · `empurrar` (zoom com um grau de giro — a mão humana) ·
|
|
810
|
+
`balanco`.
|
|
811
|
+
|
|
812
|
+
Para um movimento seu, as mesmas chaves dos elementos:
|
|
813
|
+
|
|
814
|
+
```json
|
|
815
|
+
"camera": { "chaves": [ { "t": 0, "escala": 1.15, "x": -40 },
|
|
816
|
+
{ "t": 1, "escala": 1, "x": 0 } ], "suavizar": "saida" }
|
|
817
|
+
```
|
|
818
|
+
|
|
819
|
+
O **fundo não anda junto** — é assim de propósito: o quadro passa sobre o
|
|
820
|
+
fundo, e é daí que vem a sensação de profundidade sem custo nenhum.
|
|
821
|
+
|
|
822
|
+
### Paralaxe — as camadas andam em velocidades diferentes
|
|
823
|
+
|
|
824
|
+
No elemento, `paralaxe` diz o quanto ele acompanha a câmera: `1` é o padrão
|
|
825
|
+
(anda com o quadro), acima de 1 vem à frente e anda **mais**, abaixo de 1 fica
|
|
826
|
+
ao fundo e anda **menos**.
|
|
827
|
+
|
|
828
|
+
```json
|
|
829
|
+
{ "tipo": "imagem", "src": "/midia/cidade.jpg", "paralaxe": 0.4 }
|
|
830
|
+
{ "tipo": "texto", "papel": "titulo", "texto": "São Paulo", "paralaxe": 1.25 }
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
Duas ou três camadas bastam: fundo em 0,3–0,5, conteúdo em 1, o que está na
|
|
834
|
+
frente em 1,2–1,4. Um elemento que já tem `chaves` próprias e está no fluxo
|
|
835
|
+
ignora a paralaxe — a animação dele é mais específica, e embrulhar mudaria o
|
|
836
|
+
espaçamento da cena.
|
|
837
|
+
|
|
838
|
+
**Um movimento de câmera por cena, e não em todas.** Câmera em tudo embrulha o
|
|
839
|
+
olho; o que dá ritmo é alternar cena parada e cena com movimento.
|
|
840
|
+
|
|
799
841
|
## Acabamento
|
|
800
842
|
|
|
801
843
|
Textura por cima da cena inteira — é o que tira a cara de template:
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
[studio] 2026-09-11T18:38:32+00:00 — iniciando na porta 7777
|
|
2
|
+
|
|
3
|
+
██ PrimoCode Studio
|
|
4
|
+
────────────────────────────────────────────────────────
|
|
5
|
+
Estúdio http://localhost:7777
|
|
6
|
+
Manual da IA http://localhost:7777/api/manual
|
|
7
|
+
Ferramentas Prisma, Tela, Prosa, Grade, Corte, Traço
|
|
8
|
+
────────────────────────────────────────────────────────
|
|
9
|
+
Ctrl+C para parar
|
|
10
|
+
|
|
11
|
+
GET /api/ferramentas
|
|
12
|
+
GET /api/ferramentas
|
|
13
|
+
GET /api/arquivos
|
|
14
|
+
GET /api/ferramentas
|
|
15
|
+
GET /api/arquivos
|
|
16
|
+
GET /api/ferramentas
|
|
17
|
+
GET /api/arquivos
|
|
18
|
+
POST /api/arquivos
|
|
19
|
+
GET /api/arquivos/corte-teste-de-camera-fb823
|
|
20
|
+
GET /api/voz/status
|
|
21
|
+
GET /api/voz/status
|
|
22
|
+
GET /api/arquivos/corte-teste-de-camera-fb823
|
|
23
|
+
GET /api/voz/status
|
|
24
|
+
GET /api/arquivos/corte-teste-de-camera-fb823
|
|
25
|
+
GET /api/voz/status
|
|
26
|
+
[studio] 2026-09-11T18:40:34+00:00 — iniciando na porta 7777
|
|
27
|
+
|
|
28
|
+
██ PrimoCode Studio
|
|
29
|
+
────────────────────────────────────────────────────────
|
|
30
|
+
Estúdio http://localhost:7777
|
|
31
|
+
Manual da IA http://localhost:7777/api/manual
|
|
32
|
+
Ferramentas Prisma, Tela, Prosa, Grade, Corte, Traço
|
|
33
|
+
────────────────────────────────────────────────────────
|
|
34
|
+
Ctrl+C para parar
|
|
35
|
+
|
|
36
|
+
GET /api/arquivos/corte-teste-de-camera-fb823
|
|
37
|
+
GET /api/voz/status
|
|
38
|
+
GET /api/voz/status
|
|
39
|
+
GET /api/voz/status
|
|
40
|
+
GET /api/voz/status
|
|
41
|
+
GET /api/arquivos/corte-teste-de-camera-fb823
|
|
42
|
+
GET /api/voz/status
|
|
43
|
+
GET /api/voz/status
|
|
44
|
+
GET /api/arquivos/corte-teste-de-camera-fb823
|
|
45
|
+
GET /api/voz/status
|
|
46
|
+
GET /api/voz/status
|
|
47
|
+
GET /api/arquivos/corte-teste-de-camera-fb823
|
|
48
|
+
GET /api/voz/status
|
|
49
|
+
GET /api/voz/status
|
|
50
|
+
GET /api/arquivos/corte-teste-de-camera-fb823
|
|
51
|
+
GET /api/voz/status
|
|
52
|
+
GET /api/voz/status
|
|
53
|
+
GET /api/arquivos/corte-teste-de-camera-fb823
|
|
54
|
+
GET /api/voz/status
|
|
55
|
+
GET /api/ferramentas
|
|
56
|
+
GET /api/versao
|
|
57
|
+
POST /api/sair
|
package/studio/web/core.css
CHANGED
|
@@ -987,3 +987,181 @@ body {
|
|
|
987
987
|
font: 400 11.5px/1.2 var(--sans);
|
|
988
988
|
color: var(--fraco);
|
|
989
989
|
}
|
|
990
|
+
|
|
991
|
+
/* ==========================================================================
|
|
992
|
+
REDESENHO DA CAPA — o estúdio se apresentando
|
|
993
|
+
--------------------------------------------------------------------------
|
|
994
|
+
O que estava aqui era uma página de lista: título, cartões, lista de
|
|
995
|
+
arquivos. Funcionava e não dizia nada — quem abria pela primeira vez não
|
|
996
|
+
descobria que o vídeo sai com narração, câmera e keyframes, nem que dá para
|
|
997
|
+
pedir tudo por texto ao agente.
|
|
998
|
+
|
|
999
|
+
Este bloco vem DEPOIS do núcleo de propósito: ele acrescenta a capa, os
|
|
1000
|
+
poderes e o acabamento dos cartões sem reescrever a primitiva (.btn, .campo,
|
|
1001
|
+
.topo) que o editor usa — mudar aquilo mexeria em cinco telas de uma vez.
|
|
1002
|
+
========================================================================== */
|
|
1003
|
+
|
|
1004
|
+
.home { max-width: 1140px; padding-top: 34px; }
|
|
1005
|
+
|
|
1006
|
+
.busca-topo { width: 220px; background: var(--sup2); }
|
|
1007
|
+
.busca-topo::placeholder { color: var(--fraco2); }
|
|
1008
|
+
@media (max-width: 720px) { .busca-topo { display: none; } }
|
|
1009
|
+
|
|
1010
|
+
/* ------------------------------------------------------------------ capa -- */
|
|
1011
|
+
|
|
1012
|
+
.capa {
|
|
1013
|
+
position: relative;
|
|
1014
|
+
display: grid;
|
|
1015
|
+
grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
|
|
1016
|
+
gap: 30px;
|
|
1017
|
+
align-items: center;
|
|
1018
|
+
padding: 46px 40px;
|
|
1019
|
+
margin-bottom: 54px;
|
|
1020
|
+
border: 1px solid var(--linha);
|
|
1021
|
+
border-radius: 22px;
|
|
1022
|
+
background: linear-gradient(180deg, var(--sup) 0%, #101014 100%);
|
|
1023
|
+
overflow: hidden;
|
|
1024
|
+
}
|
|
1025
|
+
/* A luz é a única coisa que se move na capa, e devagar: movimento na página
|
|
1026
|
+
inteira briga com o vídeo que a pessoa veio fazer. */
|
|
1027
|
+
.capa-luz {
|
|
1028
|
+
position: absolute;
|
|
1029
|
+
inset: -40% -10% auto -20%;
|
|
1030
|
+
height: 520px;
|
|
1031
|
+
background:
|
|
1032
|
+
radial-gradient(420px 240px at 20% 40%, rgba(255, 90, 54, .22), transparent 70%),
|
|
1033
|
+
radial-gradient(360px 220px at 70% 30%, rgba(139, 92, 246, .18), transparent 72%);
|
|
1034
|
+
filter: blur(6px);
|
|
1035
|
+
animation: capaLuz 18s ease-in-out infinite alternate;
|
|
1036
|
+
pointer-events: none;
|
|
1037
|
+
}
|
|
1038
|
+
@keyframes capaLuz { from { transform: translate3d(-3%, 0, 0); } to { transform: translate3d(6%, 3%, 0); } }
|
|
1039
|
+
|
|
1040
|
+
.capa-texto { position: relative; z-index: 1; }
|
|
1041
|
+
.capa h1 {
|
|
1042
|
+
font-size: 40px;
|
|
1043
|
+
line-height: 1.12;
|
|
1044
|
+
font-weight: 680;
|
|
1045
|
+
letter-spacing: -.04em;
|
|
1046
|
+
margin: 14px 0 12px;
|
|
1047
|
+
}
|
|
1048
|
+
.capa h1 .grad {
|
|
1049
|
+
background: linear-gradient(92deg, var(--primo), #ff9f7a 55%, #8b5cf6);
|
|
1050
|
+
-webkit-background-clip: text;
|
|
1051
|
+
background-clip: text;
|
|
1052
|
+
color: transparent;
|
|
1053
|
+
}
|
|
1054
|
+
.capa p { color: var(--fraco); font-size: 14.5px; line-height: 1.7; margin: 0 0 22px; max-width: 560px; }
|
|
1055
|
+
.capa-acoes { display: flex; gap: 10px; flex-wrap: wrap; }
|
|
1056
|
+
.btn.grande { height: 40px; padding: 0 18px; font-size: 13.5px; border-radius: 12px; }
|
|
1057
|
+
|
|
1058
|
+
.selo-vivo { color: var(--fraco); }
|
|
1059
|
+
.selo-vivo i {
|
|
1060
|
+
width: 7px; height: 7px; border-radius: 999px; background: #4ade80;
|
|
1061
|
+
box-shadow: 0 0 0 3px rgba(74, 222, 128, .16);
|
|
1062
|
+
animation: pulso 2.4s ease-in-out infinite;
|
|
1063
|
+
}
|
|
1064
|
+
@keyframes pulso { 50% { box-shadow: 0 0 0 6px rgba(74, 222, 128, 0); } }
|
|
1065
|
+
|
|
1066
|
+
/* A arte da direita é o produto em miniatura: três quadros de cena, com a
|
|
1067
|
+
mesma linguagem do render. Nada de screenshot — captura envelhece e mente. */
|
|
1068
|
+
.capa-arte { position: relative; height: 250px; }
|
|
1069
|
+
.capa-arte .quadro {
|
|
1070
|
+
position: absolute;
|
|
1071
|
+
border: 1px solid var(--linha2);
|
|
1072
|
+
border-radius: 14px;
|
|
1073
|
+
background: linear-gradient(160deg, #16161d, #0c0c11);
|
|
1074
|
+
box-shadow: 0 24px 60px rgba(0, 0, 0, .45);
|
|
1075
|
+
animation: flutua 9s ease-in-out infinite alternate;
|
|
1076
|
+
}
|
|
1077
|
+
.capa-arte .q1 {
|
|
1078
|
+
left: 0; top: 16px; width: 62%; height: 132px; padding: 16px;
|
|
1079
|
+
display: flex; flex-direction: column; gap: 9px; justify-content: center;
|
|
1080
|
+
}
|
|
1081
|
+
.capa-arte .q1 span { height: 9px; border-radius: 5px; background: rgba(255, 255, 255, .16); }
|
|
1082
|
+
.capa-arte .q1 span:first-child { width: 42%; background: var(--primo); height: 11px; }
|
|
1083
|
+
.capa-arte .q1 span:last-child { width: 68%; }
|
|
1084
|
+
.capa-arte .q2 {
|
|
1085
|
+
right: 6px; top: 0; width: 42%; height: 104px;
|
|
1086
|
+
display: grid; place-content: center; text-align: center;
|
|
1087
|
+
animation-delay: -3s;
|
|
1088
|
+
}
|
|
1089
|
+
.capa-arte .q2 b { font-size: 26px; font-weight: 680; letter-spacing: -.03em; display: block; }
|
|
1090
|
+
.capa-arte .q2 i { font-size: 11px; color: var(--fraco2); font-style: normal; }
|
|
1091
|
+
.capa-arte .q3 {
|
|
1092
|
+
right: 22px; bottom: 4px; width: 54%; height: 96px; padding: 14px;
|
|
1093
|
+
display: flex; align-items: flex-end; gap: 8px;
|
|
1094
|
+
animation-delay: -6s;
|
|
1095
|
+
}
|
|
1096
|
+
.capa-arte .q3 u { flex: 1; border-radius: 4px 4px 0 0; background: linear-gradient(180deg, var(--primo), rgba(255, 90, 54, .25)); }
|
|
1097
|
+
.capa-arte .q3 u:nth-child(1) { height: 38%; }
|
|
1098
|
+
.capa-arte .q3 u:nth-child(2) { height: 72%; }
|
|
1099
|
+
.capa-arte .q3 u:nth-child(3) { height: 54%; }
|
|
1100
|
+
@keyframes flutua { from { transform: translateY(-5px); } to { transform: translateY(7px); } }
|
|
1101
|
+
|
|
1102
|
+
@media (max-width: 880px) {
|
|
1103
|
+
.capa { grid-template-columns: 1fr; padding: 34px 24px; }
|
|
1104
|
+
.capa-arte { display: none; }
|
|
1105
|
+
.capa h1 { font-size: 31px; }
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/* ------------------------------------------------------- cartões e listas -- */
|
|
1109
|
+
|
|
1110
|
+
/* Seis ferramentas em três colunas fecham duas fileiras cheias; em quatro
|
|
1111
|
+
colunas sobra uma fileira pela metade, e a página parece interrompida. */
|
|
1112
|
+
.grade-ferr {
|
|
1113
|
+
gap: 16px;
|
|
1114
|
+
margin-bottom: 52px;
|
|
1115
|
+
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
.cartao-ferr { padding: 22px; background: linear-gradient(180deg, var(--sup), #101015); }
|
|
1119
|
+
.cartao-ferr .brilho {
|
|
1120
|
+
position: absolute; inset: -1px -1px auto -1px; height: 2px;
|
|
1121
|
+
background: linear-gradient(90deg, transparent, var(--cor, var(--primo)), transparent);
|
|
1122
|
+
opacity: 0; transition: opacity .18s;
|
|
1123
|
+
}
|
|
1124
|
+
.cartao-ferr:hover .brilho, .cartao-ferr:focus-visible .brilho { opacity: .9; }
|
|
1125
|
+
.cartao-ferr:focus-visible { outline: 2px solid var(--cor, var(--primo)); outline-offset: 2px; }
|
|
1126
|
+
.cartao-ferr .icone {
|
|
1127
|
+
width: 38px; height: 38px; border-radius: 11px;
|
|
1128
|
+
background: color-mix(in srgb, var(--cor, var(--primo)) 16%, transparent);
|
|
1129
|
+
color: var(--cor, var(--primo));
|
|
1130
|
+
border: 1px solid color-mix(in srgb, var(--cor, var(--primo)) 28%, transparent);
|
|
1131
|
+
}
|
|
1132
|
+
.cartao-ferr .icone svg { width: 19px; height: 19px; }
|
|
1133
|
+
.cartao-ferr:hover { transform: translateY(-3px); box-shadow: 0 18px 40px rgba(0, 0, 0, .35); }
|
|
1134
|
+
|
|
1135
|
+
.item-arq { border-radius: 12px; }
|
|
1136
|
+
.item-arq .tag {
|
|
1137
|
+
background: color-mix(in srgb, var(--cor, var(--primo)) 16%, transparent);
|
|
1138
|
+
color: var(--cor, var(--primo));
|
|
1139
|
+
}
|
|
1140
|
+
.item-arq:hover { transform: translateX(2px); }
|
|
1141
|
+
|
|
1142
|
+
/* ------------------------------------------------------------- poderes --- */
|
|
1143
|
+
|
|
1144
|
+
.poderes { margin-bottom: 50px; }
|
|
1145
|
+
.grade-poder {
|
|
1146
|
+
display: grid;
|
|
1147
|
+
grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
|
|
1148
|
+
gap: 12px;
|
|
1149
|
+
}
|
|
1150
|
+
.grade-poder article {
|
|
1151
|
+
border: 1px solid var(--linha);
|
|
1152
|
+
border-radius: var(--r2);
|
|
1153
|
+
background: var(--sup);
|
|
1154
|
+
padding: 16px 18px;
|
|
1155
|
+
}
|
|
1156
|
+
.grade-poder b { font-size: 13.5px; font-weight: 620; display: block; margin-bottom: 5px; }
|
|
1157
|
+
.grade-poder p { margin: 0; font-size: 12.5px; line-height: 1.6; color: var(--fraco); }
|
|
1158
|
+
|
|
1159
|
+
.copiado { font-size: 12px; color: #4ade80; opacity: 0; transition: opacity .2s; }
|
|
1160
|
+
.copiado.on { opacity: 1; }
|
|
1161
|
+
|
|
1162
|
+
/* Quem pediu menos movimento recebe menos movimento — a capa continua
|
|
1163
|
+
inteira, só para de respirar. */
|
|
1164
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1165
|
+
.capa-luz, .capa-arte .quadro, .selo-vivo i { animation: none; }
|
|
1166
|
+
.cartao-ferr:hover, .item-arq:hover { transform: none; }
|
|
1167
|
+
}
|
package/studio/web/home.html
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
6
6
|
<title>PrimoCode Studio</title>
|
|
7
7
|
<link rel="stylesheet" href="/core.css">
|
|
8
|
+
<link rel="stylesheet" href="/fontes.css">
|
|
8
9
|
</head>
|
|
9
10
|
<body>
|
|
10
11
|
|
|
@@ -27,22 +28,48 @@
|
|
|
27
28
|
<header class="topo">
|
|
28
29
|
<a class="marca" href="/"><span class="cubo">P</span>PrimoCode <em>Studio</em></a>
|
|
29
30
|
<div class="cresce"></div>
|
|
31
|
+
<input class="campo busca-topo" id="busca" placeholder="Buscar no estúdio /" autocomplete="off">
|
|
30
32
|
<button class="btn vazio" id="btn-manual">Manual da IA</button>
|
|
31
33
|
<a class="btn vazio" href="/api/manual" target="_blank">API</a>
|
|
32
34
|
</header>
|
|
33
35
|
|
|
34
36
|
<div class="home">
|
|
35
37
|
|
|
36
|
-
<section class="
|
|
37
|
-
<
|
|
38
|
-
<
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
<section class="capa">
|
|
39
|
+
<div class="capa-luz" aria-hidden="true"></div>
|
|
40
|
+
<div class="capa-texto">
|
|
41
|
+
<span class="pilula selo-vivo"><i></i> o estúdio está no ar em localhost:7777</span>
|
|
42
|
+
<h1>Seis ferramentas.<br>Um documento só.<br><span class="grad">A IA opera tudo.</span></h1>
|
|
43
|
+
<p>Apresentação, arte, texto, planilha, vídeo e diagrama nascem do mesmo modelo —
|
|
44
|
+
a IA escreve o documento, o estúdio desenha, você refina à mão. Vídeo sai com
|
|
45
|
+
narração neural, trilha, efeito sonoro, câmera e keyframes em 3D, e baixa em MP4.</p>
|
|
46
|
+
<div class="capa-acoes">
|
|
47
|
+
<button class="btn primo grande" id="ir-corte">Criar um vídeo</button>
|
|
48
|
+
<button class="btn grande" id="ir-prisma">Criar uma apresentação</button>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<div class="capa-arte" aria-hidden="true">
|
|
52
|
+
<div class="quadro q1"><span></span><span></span><span></span></div>
|
|
53
|
+
<div class="quadro q2"><b>+38%</b><i>em 12 meses</i></div>
|
|
54
|
+
<div class="quadro q3"><u></u><u></u><u></u></div>
|
|
55
|
+
</div>
|
|
42
56
|
</section>
|
|
43
57
|
|
|
58
|
+
<h2 class="secao-tit" id="tit-ferramentas">As ferramentas</h2>
|
|
44
59
|
<section class="grade-ferr" id="ferramentas"></section>
|
|
45
60
|
|
|
61
|
+
<section class="poderes">
|
|
62
|
+
<h2 class="secao-tit">O que a IA consegue fazer aqui</h2>
|
|
63
|
+
<div class="grade-poder">
|
|
64
|
+
<article><b>Diretor antes da produção</b><p>Roteiro, narração falada, voz, câmera e a mídia a buscar — decidido antes de a primeira cena existir.</p></article>
|
|
65
|
+
<article><b>Câmera e paralaxe</b><p>O quadro se aproxima, atravessa e balança; as camadas andam em velocidades diferentes.</p></article>
|
|
66
|
+
<article><b>Keyframes em 3D</b><p>Giro em profundidade, curvas com nome (elástico, salto) e som no instante em que o elemento entra.</p></article>
|
|
67
|
+
<article><b>Mídia da internet</b><p>Foto, logo em SVG da marca e música com licença aberta — com o crédito guardado junto do arquivo.</p></article>
|
|
68
|
+
<article><b>Narração neural</b><p>Voz sintetizada em pt-BR, sincronia automática com a cena e a trilha abaixando sozinha enquanto a voz fala.</p></article>
|
|
69
|
+
<article><b>MP4 sem clique</b><p>Exporta o arquivo direto, ou vira projeto Remotion quando o vídeo é longo ou precisa de canal alfa.</p></article>
|
|
70
|
+
</div>
|
|
71
|
+
</section>
|
|
72
|
+
|
|
46
73
|
<section class="faixa-ia">
|
|
47
74
|
<h2>Como pedir</h2>
|
|
48
75
|
<p>Fale com seu agente em português normal. Ele lê o manual, escolhe a ferramenta e devolve o link.</p>
|
|
@@ -51,7 +78,7 @@ usando o Prisma. Me devolva a URL "ver" da resposta.</pre>
|
|
|
51
78
|
<div class="acoes">
|
|
52
79
|
<button class="btn primo" id="copiar">Copiar</button>
|
|
53
80
|
<a class="btn" href="/api/manual" target="_blank">Ler o manual</a>
|
|
54
|
-
<span id="copiado"
|
|
81
|
+
<span id="copiado" class="copiado">copiado</span>
|
|
55
82
|
</div>
|
|
56
83
|
</section>
|
|
57
84
|
|
|
@@ -70,15 +97,16 @@ usando o Prisma. Me devolva a URL "ver" da resposta.</pre>
|
|
|
70
97
|
(function () {
|
|
71
98
|
var $ = function (s) { return document.querySelector(s); };
|
|
72
99
|
var FERRS = [];
|
|
100
|
+
var ARQUIVOS = [];
|
|
73
101
|
|
|
74
102
|
function fugir(s) { return String(s).replace(/&/g, "&").replace(/</g, "<"); }
|
|
75
103
|
|
|
76
104
|
fetch("/api/ferramentas").then(function (r) { return r.json(); }).then(function (lista) {
|
|
77
105
|
FERRS = lista;
|
|
78
106
|
$("#ferramentas").innerHTML = lista.map(function (f) {
|
|
79
|
-
return '<div class="cartao-ferr" data-slug="' + f.slug + '">' +
|
|
80
|
-
'<div class="
|
|
81
|
-
svgIcone(iconeDe(f.slug)) + "</div>" +
|
|
107
|
+
return '<div class="cartao-ferr" data-slug="' + f.slug + '" style="--cor:' + f.cor + '" tabindex="0" role="button">' +
|
|
108
|
+
'<div class="brilho" aria-hidden="true"></div>' +
|
|
109
|
+
'<div class="icone">' + svgIcone(iconeDe(f.slug)) + "</div>" +
|
|
82
110
|
"<b>" + fugir(f.nome) + "</b>" +
|
|
83
111
|
'<div class="papel">' + fugir(f.papel) + "</div>" +
|
|
84
112
|
"<p>" + fugir(f.resumo) + "</p>" +
|
|
@@ -87,6 +115,11 @@ usando o Prisma. Me devolva a URL "ver" da resposta.</pre>
|
|
|
87
115
|
|
|
88
116
|
Array.prototype.forEach.call(document.querySelectorAll(".cartao-ferr"), function (c) {
|
|
89
117
|
c.onclick = function () { criar(c.dataset.slug); };
|
|
118
|
+
// Teclado abre igual ao mouse: o cartão é um botão, e um botão que só
|
|
119
|
+
// responde a clique deixa de fora quem navega por Tab.
|
|
120
|
+
c.onkeydown = function (ev) {
|
|
121
|
+
if (ev.key === "Enter" || ev.key === " ") { ev.preventDefault(); criar(c.dataset.slug); }
|
|
122
|
+
};
|
|
90
123
|
});
|
|
91
124
|
carregarArquivos();
|
|
92
125
|
});
|
|
@@ -110,28 +143,59 @@ usando o Prisma. Me devolva a URL "ver" da resposta.</pre>
|
|
|
110
143
|
});
|
|
111
144
|
}
|
|
112
145
|
|
|
146
|
+
$("#ir-corte").onclick = function () { criar("corte"); };
|
|
147
|
+
$("#ir-prisma").onclick = function () { criar("prisma"); };
|
|
148
|
+
|
|
113
149
|
function carregarArquivos() {
|
|
114
150
|
fetch("/api/arquivos").then(function (r) { return r.json(); }).then(function (lista) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
var cor = corDe(a.ferramenta);
|
|
118
|
-
var quando = a.atualizado ? a.atualizado.slice(0, 10).split("-").reverse().join("/") : "";
|
|
119
|
-
return '<div class="item-arq">' +
|
|
120
|
-
'<div class="tag" style="background:' + cor + '22;color:' + cor + '">' + svgIcone(iconeDe(a.ferramenta)) + "</div>" +
|
|
121
|
-
'<div class="cresce"><b>' + fugir(a.titulo) + "</b>" +
|
|
122
|
-
'<div class="meta">' + fugir(a.ferramenta) + " · " + a.pecas +
|
|
123
|
-
(a.pecas === 1 ? " peça · " : " peças · ") + quando + "</div></div>" +
|
|
124
|
-
'<a class="btn" href="/t/' + a.ferramenta + "/" + a.id + '">Editar</a>' +
|
|
125
|
-
'<a class="btn primo" href="/v/' + a.id + '" target="_blank">Abrir</a></div>';
|
|
126
|
-
}).join("");
|
|
151
|
+
ARQUIVOS = lista;
|
|
152
|
+
desenharArquivos(lista);
|
|
127
153
|
});
|
|
128
154
|
}
|
|
129
155
|
|
|
156
|
+
function desenharArquivos(lista) {
|
|
157
|
+
if (!lista.length) {
|
|
158
|
+
$("#arquivos").innerHTML = "";
|
|
159
|
+
$("#vazio").style.display = "block";
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
$("#vazio").style.display = "none";
|
|
163
|
+
$("#arquivos").innerHTML = lista.map(function (a) {
|
|
164
|
+
var cor = corDe(a.ferramenta);
|
|
165
|
+
var quando = a.atualizado ? a.atualizado.slice(0, 10).split("-").reverse().join("/") : "";
|
|
166
|
+
return '<div class="item-arq" style="--cor:' + cor + '">' +
|
|
167
|
+
'<div class="tag">' + svgIcone(iconeDe(a.ferramenta)) + "</div>" +
|
|
168
|
+
'<div class="cresce"><b>' + fugir(a.titulo) + "</b>" +
|
|
169
|
+
'<div class="meta">' + fugir(a.ferramenta) + " · " + a.pecas +
|
|
170
|
+
(a.pecas === 1 ? " peça · " : " peças · ") + quando + "</div></div>" +
|
|
171
|
+
'<a class="btn" href="/t/' + a.ferramenta + "/" + a.id + '">Editar</a>' +
|
|
172
|
+
'<a class="btn primo" href="/v/' + a.id + '" target="_blank">Abrir</a></div>';
|
|
173
|
+
}).join("");
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/* A BUSCA filtra o que já está na tela — ferramentas e arquivos.
|
|
177
|
+
Sem ida ao servidor: a lista inteira já veio, e um fetch por tecla
|
|
178
|
+
digitada seria trabalho para devolver o que está na memória. */
|
|
179
|
+
var busca = $("#busca");
|
|
180
|
+
busca.oninput = function () {
|
|
181
|
+
var q = busca.value.trim().toLowerCase();
|
|
182
|
+
Array.prototype.forEach.call(document.querySelectorAll(".cartao-ferr"), function (c) {
|
|
183
|
+
c.style.display = !q || c.textContent.toLowerCase().indexOf(q) >= 0 ? "" : "none";
|
|
184
|
+
});
|
|
185
|
+
desenharArquivos(!q ? ARQUIVOS : ARQUIVOS.filter(function (a) {
|
|
186
|
+
return (a.titulo + " " + a.ferramenta).toLowerCase().indexOf(q) >= 0;
|
|
187
|
+
}));
|
|
188
|
+
};
|
|
189
|
+
document.addEventListener("keydown", function (ev) {
|
|
190
|
+
if (ev.key === "/" && document.activeElement !== busca) { ev.preventDefault(); busca.focus(); }
|
|
191
|
+
if (ev.key === "Escape" && document.activeElement === busca) { busca.value = ""; busca.oninput(); busca.blur(); }
|
|
192
|
+
});
|
|
193
|
+
|
|
130
194
|
$("#copiar").onclick = function () {
|
|
131
195
|
navigator.clipboard.writeText($("#frase").textContent).then(function () {
|
|
132
196
|
var c = $("#copiado");
|
|
133
|
-
c.
|
|
134
|
-
setTimeout(function () { c.
|
|
197
|
+
c.classList.add("on");
|
|
198
|
+
setTimeout(function () { c.classList.remove("on"); }, 1500);
|
|
135
199
|
});
|
|
136
200
|
};
|
|
137
201
|
|
|
@@ -144,6 +208,9 @@ usando o Prisma. Me devolva a URL "ver" da resposta.</pre>
|
|
|
144
208
|
}
|
|
145
209
|
};
|
|
146
210
|
folha.onclick = function (e) { if (e.target === folha) folha.classList.remove("on"); };
|
|
211
|
+
document.addEventListener("keydown", function (e) {
|
|
212
|
+
if (e.key === "Escape") folha.classList.remove("on");
|
|
213
|
+
});
|
|
147
214
|
})();
|
|
148
215
|
</script>
|
|
149
216
|
</body>
|
package/studio/web/js/editor.js
CHANGED
|
@@ -24,6 +24,12 @@
|
|
|
24
24
|
Os valores NÃO mudam — são os mesmos que o render.css entende. O que
|
|
25
25
|
muda é só o que a pessoa lê. */
|
|
26
26
|
var OPC = {
|
|
27
|
+
// A câmera da cena: o quadro que se move. Os valores são os mesmos que o
|
|
28
|
+
// render.js conhece (CAMERAS); aqui vão só os nomes que se leem.
|
|
29
|
+
camera: [["", "Parada"], ["aproximar", "Aproximar"], ["afastar", "Afastar"],
|
|
30
|
+
["pan-direita", "Atravessar para a direita"], ["pan-esquerda", "Atravessar para a esquerda"],
|
|
31
|
+
["subir", "Subir"], ["descer", "Descer"], ["empurrar", "Empurrão (zoom com giro)"],
|
|
32
|
+
["balanco", "Balanço"]],
|
|
27
33
|
transicao: [["", "Nenhuma (corte seco)"], ["fade", "Suave"], ["deslizar", "Deslizar para o lado"],
|
|
28
34
|
["subir", "Subir"], ["zoom", "Aproximar"], ["limpar", "Varrer"],
|
|
29
35
|
["persiana", "Persiana"], ["flash", "Flash"]],
|
|
@@ -1221,6 +1227,12 @@
|
|
|
1221
1227
|
g0.appendChild(linha("Duração (s)", inp(c.duracao || 4, function (v) { mudar(function () { c.duracao = v || 4; }); }, "number")));
|
|
1222
1228
|
g0.appendChild(linha("Como entra", menu(OPC.transicao, c.transicao || "",
|
|
1223
1229
|
function (v) { mudar(function () { if (v) c.transicao = v; else delete c.transicao; }); })));
|
|
1230
|
+
/* A câmera só aparece onde há tempo (vídeo): num slide parado ela
|
|
1231
|
+
não teria o que animar, e uma caixa que não faz nada é pior que
|
|
1232
|
+
caixa nenhuma. Guardada como string — é o formato que a spec
|
|
1233
|
+
documenta e o que o render.js aceita direto. */
|
|
1234
|
+
g0.appendChild(linha("Câmera", menu(OPC.camera, typeof c.camera === "string" ? c.camera : "",
|
|
1235
|
+
function (v) { mudar(function () { if (v) c.camera = v; else delete c.camera; }); })));
|
|
1224
1236
|
}
|
|
1225
1237
|
if (SLUG === "traco") {
|
|
1226
1238
|
g0.appendChild(linha("Arranjo", selecao(["", "horizontal", "vertical"], c.auto || "",
|
|
@@ -1292,6 +1304,11 @@
|
|
|
1292
1304
|
gp.appendChild(linha("Opacidade", inp(e.opacidade != null ? e.opacidade : 1, function (v) { edita("opacidade", v); }, "number")));
|
|
1293
1305
|
gp.appendChild(linha("Como entra", menu(OPC.animacao, e.anim || "",
|
|
1294
1306
|
function (v) { edita("anim", v); })));
|
|
1307
|
+
/* Profundidade: o quanto este elemento acompanha a câmera. 1 anda com o
|
|
1308
|
+
quadro, acima vem à frente, abaixo fica ao fundo. Sem câmera na cena
|
|
1309
|
+
não faz nada — e é por isso que mora aqui embaixo, e não em destaque. */
|
|
1310
|
+
gp.appendChild(linha("Profundidade", inp(e.paralaxe != null ? e.paralaxe : 1,
|
|
1311
|
+
function (v) { edita("paralaxe", v === 1 || v === "" ? "" : v); }, "number")));
|
|
1295
1312
|
if (e.tipo === "numeros") {
|
|
1296
1313
|
gp.appendChild(linha("Contar", selecao(["não", "sim"], e.contar ? "sim" : "não",
|
|
1297
1314
|
function (v) { edita("contar", v === "sim" ? true : ""); })));
|
package/studio/web/js/render.js
CHANGED
|
@@ -590,6 +590,11 @@ function renderElemento(e, ctx) {
|
|
|
590
590
|
atrás, com bisel de luz em cima e o reflexo diagonal. Vale em QUALQUER
|
|
591
591
|
elemento (texto, forma, ícone, imagem, lista, cartões), por isso mora
|
|
592
592
|
aqui e não num tipo. O CSS faz o resto por `data-material`. */
|
|
593
|
+
/* A profundidade do elemento na cena: quanto ele acompanha a câmera.
|
|
594
|
+
>1 vem à frente (anda mais), <1 fica ao fundo (anda menos). É o que
|
|
595
|
+
transforma um zoom num plano com camadas. */
|
|
596
|
+
if (e.paralaxe != null && isFinite(Number(e.paralaxe))) no.dataset.paralaxe = Number(e.paralaxe);
|
|
597
|
+
|
|
593
598
|
if (e.material) {
|
|
594
599
|
no.dataset.material = e.material;
|
|
595
600
|
if (e.raio != null) no.style.borderRadius = _px(e.raio);
|
|
@@ -1467,7 +1472,7 @@ function desenharConectores(cena, elementos, W, H) {
|
|
|
1467
1472
|
|
|
1468
1473
|
/* ------------------------------------------------------------------ cena */
|
|
1469
1474
|
|
|
1470
|
-
function
|
|
1475
|
+
function renderCenaBase(cena, doc, op) {
|
|
1471
1476
|
op = op || {};
|
|
1472
1477
|
cena = cena || {};
|
|
1473
1478
|
var W = doc.largura || 1920, H = doc.altura || 1080;
|
|
@@ -1728,4 +1733,120 @@ function comporCabecaCorpo(fluxo, layout, nCabeca, nCorpo) {
|
|
|
1728
1733
|
filhos[filhos.length - 1].classList.add("corpo-fim");
|
|
1729
1734
|
}
|
|
1730
1735
|
|
|
1731
|
-
|
|
1736
|
+
|
|
1737
|
+
/* ══ A CÂMERA DA CENA ══════════════════════════════════════════════════════
|
|
1738
|
+
*
|
|
1739
|
+
* `chaves` move UM elemento. O que faltava era mover o QUADRO: a cena inteira
|
|
1740
|
+
* se aproximando enquanto o texto entra, a panorâmica que atravessa a arte, o
|
|
1741
|
+
* leve giro que tira a cara de página parada. É a câmera do After Effects, e
|
|
1742
|
+
* sem ela todo movimento nasce preso ao elemento — o vídeo parece um slide com
|
|
1743
|
+
* animação, que é exatamente a reclamação.
|
|
1744
|
+
*
|
|
1745
|
+
* COMO FUNCIONA: o conteúdo da cena vai para dentro de um `.palco`, e a câmera
|
|
1746
|
+
* anima o palco com a mesma máquina de chaves dos elementos (mesma curva,
|
|
1747
|
+
* mesma interpolação, mesma conta de duração). O FUNDO fica onde está, na raiz
|
|
1748
|
+
* da cena: é o que dá profundidade de graça — o quadro anda sobre o fundo em
|
|
1749
|
+
* vez de arrastar tudo junto, que é como um zoom de câmera se comporta.
|
|
1750
|
+
*
|
|
1751
|
+
* PARALAXE: um elemento com `paralaxe` anda MAIS (>1, perto da câmera) ou
|
|
1752
|
+
* MENOS (<1, ao longe) que o palco. Como ele já pode ter as próprias chaves, a
|
|
1753
|
+
* diferença vai num embrulho em volta dele — duas animações no mesmo nó
|
|
1754
|
+
* disputariam a mesma propriedade `transform`, e a segunda ganharia calada.
|
|
1755
|
+
*/
|
|
1756
|
+
|
|
1757
|
+
// Movimentos com NOME, porque quem escreve o documento não quer pensar em
|
|
1758
|
+
// chave: são os quatro que aparecem em toda peça de motion.
|
|
1759
|
+
var CAMERAS = {
|
|
1760
|
+
aproximar: [{ t: 0, escala: 1 }, { t: 1, escala: 1.12 }],
|
|
1761
|
+
afastar: [{ t: 0, escala: 1.12 }, { t: 1, escala: 1 }],
|
|
1762
|
+
"pan-direita": [{ t: 0, x: -70 }, { t: 1, x: 70 }],
|
|
1763
|
+
"pan-esquerda": [{ t: 0, x: 70 }, { t: 1, x: -70 }],
|
|
1764
|
+
subir: [{ t: 0, y: 60 }, { t: 1, y: -60 }],
|
|
1765
|
+
descer: [{ t: 0, y: -60 }, { t: 1, y: 60 }],
|
|
1766
|
+
// O "empurrão" é o zoom com um grau de giro: é o que dá mão humana ao quadro.
|
|
1767
|
+
empurrar: [{ t: 0, escala: 1.02, giro: -0.6 }, { t: 1, escala: 1.14, giro: 0.3 }],
|
|
1768
|
+
balanco: [{ t: 0, x: -18, giro: -0.5 }, { t: 0.5, x: 12, giro: 0.4 }, { t: 1, x: -6, giro: -0.2 }],
|
|
1769
|
+
};
|
|
1770
|
+
|
|
1771
|
+
function chavesDaCamera(camera) {
|
|
1772
|
+
if (!camera) return null;
|
|
1773
|
+
if (typeof camera === "string") return CAMERAS[camera] ? CAMERAS[camera].slice() : null;
|
|
1774
|
+
if (Array.isArray(camera)) return camera.slice();
|
|
1775
|
+
if (camera.chaves && camera.chaves.length) return camera.chaves.slice();
|
|
1776
|
+
if (camera.movimento && CAMERAS[camera.movimento]) return CAMERAS[camera.movimento].slice();
|
|
1777
|
+
return null;
|
|
1778
|
+
}
|
|
1779
|
+
|
|
1780
|
+
/* A chave do elemento de paralaxe: a da câmera, menos a que o palco já deu.
|
|
1781
|
+
Fator 0 devolve nada (anda com o palco, que é o padrão). */
|
|
1782
|
+
function chavesDeParalaxe(chaves, fator) {
|
|
1783
|
+
var f = fator - 1;
|
|
1784
|
+
if (!f) return null;
|
|
1785
|
+
return chaves.map(function (k) {
|
|
1786
|
+
var nova = { t: _numero(k.t, 0) };
|
|
1787
|
+
if (typeof k.x === "number") nova.x = k.x * f;
|
|
1788
|
+
if (typeof k.y === "number") nova.y = k.y * f;
|
|
1789
|
+
if (typeof k.escala === "number") nova.escala = 1 + (k.escala - 1) * f;
|
|
1790
|
+
return nova;
|
|
1791
|
+
});
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
function aplicarCamera(no, cena, op) {
|
|
1795
|
+
var chaves = chavesDaCamera(cena.camera);
|
|
1796
|
+
if (!chaves || !chaves.length || !op.animar) return no;
|
|
1797
|
+
|
|
1798
|
+
var palco = document.createElement("div");
|
|
1799
|
+
palco.className = "palco";
|
|
1800
|
+
while (no.firstChild) palco.appendChild(no.firstChild);
|
|
1801
|
+
no.appendChild(palco);
|
|
1802
|
+
|
|
1803
|
+
var ctx = { raiz: no, duracao: cena.duracao || 4, animar: true };
|
|
1804
|
+
aplicarChaves(palco, {
|
|
1805
|
+
chaves: chaves,
|
|
1806
|
+
suavizar: (cena.camera && cena.camera.suavizar) || "suave",
|
|
1807
|
+
}, ctx);
|
|
1808
|
+
|
|
1809
|
+
/* PARALAXE — e o cuidado que decide se isto funciona ou quebra o layout.
|
|
1810
|
+
· Elemento SEM animação própria recebe a diferença direto: é o caso
|
|
1811
|
+
comum, e assim nada muda no DOM.
|
|
1812
|
+
· Elemento que JÁ anima (tem chaves suas) precisa de um embrulho —
|
|
1813
|
+
duas animações no mesmo nó disputam `transform` e a segunda ganha
|
|
1814
|
+
calada. Embrulhar só é seguro quando ele está posicionado em
|
|
1815
|
+
absoluto; no fluxo, o embrulho viraria um filho a mais no flex e
|
|
1816
|
+
mudaria o espaçamento da cena. Nesse caso a paralaxe é ignorada, e
|
|
1817
|
+
vale a animação do elemento, que é a mais específica. */
|
|
1818
|
+
var comProfundidade = palco.querySelectorAll("[data-paralaxe]");
|
|
1819
|
+
Array.prototype.forEach.call(comProfundidade, function (el) {
|
|
1820
|
+
var fator = parseFloat(el.getAttribute("data-paralaxe"));
|
|
1821
|
+
if (!isFinite(fator) || fator === 1) return;
|
|
1822
|
+
var extras = chavesDeParalaxe(chaves, fator);
|
|
1823
|
+
if (!extras) return;
|
|
1824
|
+
var suave = (cena.camera && cena.camera.suavizar) || "suave";
|
|
1825
|
+
|
|
1826
|
+
if (!el.style.animationName) {
|
|
1827
|
+
aplicarChaves(el, { chaves: extras, suavizar: suave }, ctx);
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
if (el.style.position !== "absolute") return;
|
|
1831
|
+
|
|
1832
|
+
var capa = document.createElement("div");
|
|
1833
|
+
capa.className = "paralaxe";
|
|
1834
|
+
capa.style.position = "absolute";
|
|
1835
|
+
["left", "top", "right", "bottom", "width", "height"].forEach(function (k) {
|
|
1836
|
+
capa.style[k] = el.style[k];
|
|
1837
|
+
el.style[k] = "";
|
|
1838
|
+
});
|
|
1839
|
+
el.style.position = "relative";
|
|
1840
|
+
el.parentNode.insertBefore(capa, el);
|
|
1841
|
+
capa.appendChild(el);
|
|
1842
|
+
aplicarChaves(capa, { chaves: extras, suavizar: suave }, ctx);
|
|
1843
|
+
});
|
|
1844
|
+
return no;
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
function renderCena(cena, doc, op) {
|
|
1848
|
+
var no = renderCenaBase(cena, doc, op);
|
|
1849
|
+
return aplicarCamera(no, cena || {}, op || {});
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
if (typeof module !== "undefined" && module.exports) module.exports = { renderCena: renderCena, CAMERAS: CAMERAS };
|
package/studio/web/render.css
CHANGED
|
@@ -1332,3 +1332,21 @@
|
|
|
1332
1332
|
column-gap: calc(var(--u, 19px) * 3);
|
|
1333
1333
|
}
|
|
1334
1334
|
.cena .fluxo[data-composicao="cabeca-corpo"] .el.numeros .num b { white-space: nowrap; }
|
|
1335
|
+
|
|
1336
|
+
/* ─────────────────────────────────────────────────────────── câmera ────
|
|
1337
|
+
O palco é o conteúdo da cena; a câmera anima ele. O FUNDO fica na raiz
|
|
1338
|
+
de propósito: o quadro anda sobre o fundo, como faz uma câmera, em vez
|
|
1339
|
+
de arrastar tudo junto — é daí que vem a profundidade sem custo.
|
|
1340
|
+
|
|
1341
|
+
`inset: 0` repete a geometria que o `.fluxo` já tinha, para o embrulho
|
|
1342
|
+
não mudar uma única posição de elemento quando a cena não tem câmera. */
|
|
1343
|
+
.cena > .palco {
|
|
1344
|
+
position: absolute;
|
|
1345
|
+
inset: 0;
|
|
1346
|
+
z-index: 1;
|
|
1347
|
+
transform-origin: 50% 50%;
|
|
1348
|
+
will-change: transform;
|
|
1349
|
+
}
|
|
1350
|
+
/* O embrulho de paralaxe só existe em volta de elemento posicionado, e
|
|
1351
|
+
herda a caixa dele — ver o comentário em render.js. */
|
|
1352
|
+
.cena .paralaxe { transform-origin: 50% 50%; will-change: transform; }
|