primocode 8.40.0 → 8.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.md +34 -5
  2. package/bin/primocode.js +90 -0
  3. package/lib/acervo.js +391 -0
  4. package/lib/act.js +96 -0
  5. package/lib/audio-livre.js +6 -1
  6. package/lib/boas-vindas.js +80 -21
  7. package/lib/catalogo.js +24 -0
  8. package/lib/conta.js +24 -3
  9. package/lib/diretor.js +309 -0
  10. package/lib/tools.js +43 -0
  11. package/lib/ui.js +3 -2
  12. package/package.json +2 -2
  13. package/studio/__pycache__/edge.cpython-311.pyc +0 -0
  14. package/studio/__pycache__/elevenlabs.cpython-311.pyc +0 -0
  15. package/studio/__pycache__/voz.cpython-311.pyc +0 -0
  16. package/studio/arquivos/corte-teste-de-camera-fb823.json +57 -0
  17. package/studio/ferramentas/corte.md +42 -0
  18. package/studio/studio.log +39 -10
  19. package/studio/web/core.css +178 -0
  20. package/studio/web/home.html +91 -24
  21. package/studio/web/js/editor.js +17 -0
  22. package/studio/web/js/render.js +155 -15
  23. package/studio/web/render.css +18 -0
  24. package/studio/arquivos/corte-estatico-corte-a5bab.json +0 -150
  25. package/studio/arquivos/corte-founders-ai-teste-do-player-2de1d.json +0 -65
  26. package/studio/arquivos/corte-legado-teste.json +0 -1
  27. package/studio/arquivos/corte-legado2.json +0 -1
  28. package/studio/arquivos/corte-minimo-2a829.json +0 -24
  29. package/studio/arquivos/corte-ok-00a3b.json +0 -18
  30. package/studio/arquivos/corte-ok-1270e.json +0 -22
  31. package/studio/arquivos/corte-sweep-corte-cfb29.json +0 -147
  32. package/studio/arquivos/corte-visual-completo-78f50.json +0 -35
  33. package/studio/arquivos/grade-sweep-grade-0c2f1.json +0 -119
  34. package/studio/arquivos/prisma-estatico-prisma-d8740.json +0 -182
  35. package/studio/arquivos/prisma-sweep-prisma-4c58c.json +0 -166
  36. package/studio/arquivos/prisma-y-b0785.json +0 -52
  37. package/studio/arquivos/prosa-sweep-prosa-f29cf.json +0 -80
  38. package/studio/arquivos/tela-estatico-tela-c7daf.json +0 -90
  39. package/studio/arquivos/tela-motion-3ecaa.json +0 -135
  40. package/studio/arquivos/tela-motion-d6150.json +0 -135
  41. package/studio/arquivos/tela-sweep-tela-0d2bf.json +0 -84
  42. package/studio/arquivos/traco-estatico-traco-25872.json +0 -113
  43. package/studio/arquivos/traco-sweep-traco-0bcf4.json +0 -102
package/lib/ui.js CHANGED
@@ -160,8 +160,9 @@ const TIPS = [
160
160
  ];
161
161
 
162
162
  const NEWS = [
163
- '/effort escolhe o esforço num slider — do rápido ao mais esperto',
164
- 'Do "high" para cima ele PESQUISA referências antes de escrever',
163
+ 'Vídeo com DIRETOR: roteiro, narração, voz e movimento antes de produzir',
164
+ 'Ele busca e baixa imagem, logo e música — com a licença junto',
165
+ 'A escolha da IA na abertura agora anda nas setinhas',
165
166
  'Esc Esc a qualquer momento para o agente no meio do trabalho',
166
167
  ];
167
168
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "primocode",
3
- "version": "8.40.0",
3
+ "version": "8.42.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/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"
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"
@@ -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:
package/studio/studio.log CHANGED
@@ -1,28 +1,57 @@
1
- [studio] 2026-09-09T12:28:40+00:00 — iniciando na porta 7797
1
+ [studio] 2026-09-11T18:38:32+00:00 — iniciando na porta 7777
2
2
 
3
3
  ██ PrimoCode Studio
4
4
  ────────────────────────────────────────────────────────
5
- Estúdio http://localhost:7797
6
- Manual da IA http://localhost:7797/api/manual
5
+ Estúdio http://localhost:7777
6
+ Manual da IA http://localhost:7777/api/manual
7
7
  Ferramentas Prisma, Tela, Prosa, Grade, Corte, Traço
8
8
  ────────────────────────────────────────────────────────
9
9
  Ctrl+C para parar
10
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
11
17
  GET /api/arquivos
12
18
  POST /api/arquivos
13
- GET /api/arquivos/tela-motion-3ecaa
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
14
25
  GET /api/voz/status
15
- [studio] 2026-09-09T12:31:01+00:00 — iniciando na porta 7797
26
+ [studio] 2026-09-11T18:40:34+00:00 — iniciando na porta 7777
16
27
 
17
28
  ██ PrimoCode Studio
18
29
  ────────────────────────────────────────────────────────
19
- Estúdio http://localhost:7797
20
- Manual da IA http://localhost:7797/api/manual
30
+ Estúdio http://localhost:7777
31
+ Manual da IA http://localhost:7777/api/manual
21
32
  Ferramentas Prisma, Tela, Prosa, Grade, Corte, Traço
22
33
  ────────────────────────────────────────────────────────
23
34
  Ctrl+C para parar
24
35
 
25
- GET /api/arquivos
26
- POST /api/arquivos
27
- GET /api/arquivos/tela-motion-d6150
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
28
54
  GET /api/voz/status
55
+ GET /api/ferramentas
56
+ GET /api/versao
57
+ POST /api/sair
@@ -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
+ }
@@ -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="hero">
37
- <h1>O estúdio que a IA opera</h1>
38
- <p>Seis ferramentas nativas com um modelo de documento em comum. A IA escreve o
39
- documento, o estúdio desenha — apresentação, arte, texto, planilha, vídeo ou diagrama —
40
- e você refina à mão no editor. Vídeo sai com narração neural, trilha, efeitos
41
- sonoros e download direto em MP4 ou áudio.</p>
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" style="font-size:12px;color:#4ade80;opacity:0;transition:opacity .2s">copiado</span>
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, "&amp;").replace(/</g, "&lt;"); }
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="icone" style="background:' + f.cor + '22;color:' + f.cor + '">' +
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
- if (!lista.length) { $("#vazio").style.display = "block"; return; }
116
- $("#arquivos").innerHTML = lista.map(function (a) {
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.style.opacity = 1;
134
- setTimeout(function () { c.style.opacity = 0; }, 1500);
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>
@@ -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 : ""); })));